From 2bd52fb107528d459dabd5929794987c01a7270e Mon Sep 17 00:00:00 2001 From: Luke Shumaker Date: Sat, 30 Jul 2016 11:51:52 -0400 Subject: stuff --- src/Makefile | 36 +- src/grp-boot/Makefile | 2 +- src/grp-boot/bootctl/bootctl.c | 2 +- src/grp-boot/kernel-install/50-depmod.install | 8 + src/grp-boot/kernel-install/90-loaderentry.install | 88 + src/grp-boot/kernel-install/Makefile | 33 + .../kernel-install/bash-completion_kernel-install | 50 + src/grp-boot/kernel-install/kernel-install | 136 + src/grp-boot/kernel-install/kernel-install.xml | 192 + .../kernel-install/zsh-completion_kernel-install | 26 + src/grp-helperunits/systemd-backlight/Makefile | 43 - src/grp-helperunits/systemd-backlight/backlight.c | 434 --- src/grp-helperunits/systemd-binfmt/Makefile | 56 - src/grp-helperunits/systemd-binfmt/binfmt.c | 203 - src/grp-helperunits/systemd-detect-virt/Makefile | 36 - .../systemd-detect-virt/detect-virt.c | 169 - src/grp-helperunits/systemd-quotacheck/Makefile | 48 - .../systemd-quotacheck/quotacheck.c | 124 - src/grp-helperunits/systemd-random-seed/Makefile | 50 - .../systemd-random-seed/random-seed.c | 176 - src/grp-helperunits/systemd-rfkill/Makefile | 46 - src/grp-helperunits/systemd-rfkill/rfkill.c | 426 -- src/grp-helperunits/systemd-sleep/Makefile | 35 - src/grp-helperunits/systemd-sleep/sleep.c | 215 -- src/grp-helperunits/systemd-user-sessions/Makefile | 48 - .../systemd-user-sessions/user-sessions.c | 84 - .../systemd-vconsole-setup/.gitignore | 1 - .../systemd-vconsole-setup/90-vconsole.rules.in | 10 - .../systemd-vconsole-setup/Makefile | 50 - .../systemd-vconsole-setup/vconsole-setup.c | 332 -- src/grp-hostname/hostnamectl.c | 6 +- src/grp-import/.gitignore | 1 - src/grp-import/Makefile | 89 - src/grp-import/curl-util.c | 448 --- src/grp-import/curl-util.h | 56 - src/grp-import/org.freedesktop.import1.conf | 62 - src/grp-import/org.freedesktop.import1.policy.in | 49 - src/grp-import/org.freedesktop.import1.service | 12 - src/grp-import/qcow2-util.c | 352 -- src/grp-import/qcow2-util.h | 23 - src/grp-import/systemd-export/Makefile | 51 - src/grp-import/systemd-export/export-raw.c | 352 -- src/grp-import/systemd-export/export-raw.h | 36 - src/grp-import/systemd-export/export-tar.c | 328 -- src/grp-import/systemd-export/export-tar.h | 36 - src/grp-import/systemd-export/export.c | 320 -- src/grp-import/systemd-import/Makefile | 52 - src/grp-import/systemd-import/import-common.c | 225 -- src/grp-import/systemd-import/import-common.h | 26 - src/grp-import/systemd-import/import-compress.c | 469 --- src/grp-import/systemd-import/import-compress.h | 61 - src/grp-import/systemd-import/import-pubring.gpg | Bin 9551 -> 0 bytes src/grp-import/systemd-import/import-raw.c | 467 --- src/grp-import/systemd-import/import-raw.h | 36 - src/grp-import/systemd-import/import-tar.c | 388 -- src/grp-import/systemd-import/import-tar.h | 36 - src/grp-import/systemd-import/import.c | 337 -- src/grp-import/systemd-importd/Makefile | 39 - src/grp-import/systemd-importd/importd.c | 1221 ------ src/grp-import/systemd-pull/Makefile | 64 - src/grp-import/systemd-pull/pull-common.c | 549 --- src/grp-import/systemd-pull/pull-common.h | 36 - src/grp-import/systemd-pull/pull-job.c | 618 --- src/grp-import/systemd-pull/pull-job.h | 106 - src/grp-import/systemd-pull/pull-raw.c | 651 ---- src/grp-import/systemd-pull/pull-raw.h | 36 - src/grp-import/systemd-pull/pull-tar.c | 563 --- src/grp-import/systemd-pull/pull-tar.h | 36 - src/grp-import/systemd-pull/pull.c | 337 -- src/grp-import/test-qcow2.c | 53 - src/grp-initutils/Makefile | 43 + src/grp-initutils/systemd-backlight/Makefile | 43 + src/grp-initutils/systemd-backlight/backlight.c | 434 +++ src/grp-initutils/systemd-binfmt/Makefile | 56 + src/grp-initutils/systemd-binfmt/binfmt.c | 203 + src/grp-initutils/systemd-detect-virt/Makefile | 36 + .../systemd-detect-virt/detect-virt.c | 169 + src/grp-initutils/systemd-firstboot/Makefile | 47 + src/grp-initutils/systemd-firstboot/firstboot.c | 870 +++++ src/grp-initutils/systemd-fsck/Makefile | 33 + src/grp-initutils/systemd-fsck/fsck.c | 487 +++ src/grp-initutils/systemd-quotacheck/Makefile | 48 + src/grp-initutils/systemd-quotacheck/quotacheck.c | 124 + src/grp-initutils/systemd-random-seed/Makefile | 50 + .../systemd-random-seed/random-seed.c | 176 + src/grp-initutils/systemd-rfkill/Makefile | 46 + src/grp-initutils/systemd-rfkill/rfkill.c | 426 ++ src/grp-initutils/systemd-sleep/Makefile | 35 + src/grp-initutils/systemd-sleep/sleep.c | 215 ++ src/grp-initutils/systemd-sysctl/Makefile | 33 + src/grp-initutils/systemd-sysctl/sysctl.c | 287 ++ src/grp-initutils/systemd-sysusers/Makefile | 61 + src/grp-initutils/systemd-sysusers/sysusers.c | 1919 +++++++++ src/grp-initutils/systemd-tmpfiles/Makefile | 84 + src/grp-initutils/systemd-tmpfiles/tmpfiles.c | 2343 +++++++++++ src/grp-initutils/systemd-update-done/Makefile | 34 + .../systemd-update-done/update-done.c | 115 + src/grp-initutils/systemd-update-utmp/Makefile | 42 + .../systemd-update-utmp/update-utmp.c | 283 ++ src/grp-initutils/systemd-user-sessions/Makefile | 48 + .../systemd-user-sessions/user-sessions.c | 84 + .../systemd-vconsole-setup/.gitignore | 1 + .../systemd-vconsole-setup/90-vconsole.rules.in | 10 + src/grp-initutils/systemd-vconsole-setup/Makefile | 50 + .../systemd-vconsole-setup/vconsole-setup.c | 332 ++ src/grp-journal-remote/.gitignore | 2 - src/grp-journal-remote/Makefile | 30 - src/grp-journal-remote/browse.html | 544 --- src/grp-journal-remote/log-generator.py | 76 - src/grp-journal-remote/microhttpd-util.c | 327 -- src/grp-journal-remote/microhttpd-util.h | 60 - .../systemd-journal-gatewayd/Makefile | 68 - .../systemd-journal-gatewayd/journal-gatewayd.c | 1077 ------ .../systemd-journal-remote/Makefile | 85 - .../systemd-journal-remote/journal-remote-parse.c | 506 --- .../systemd-journal-remote/journal-remote-parse.h | 69 - .../systemd-journal-remote/journal-remote-write.c | 168 - .../systemd-journal-remote/journal-remote-write.h | 70 - .../systemd-journal-remote/journal-remote.c | 1601 -------- .../systemd-journal-remote/journal-remote.conf.in | 6 - .../systemd-journal-remote/journal-remote.h | 52 - .../systemd-journal-upload/Makefile | 54 - .../journal-upload-journal.c | 422 -- .../systemd-journal-upload/journal-upload.c | 880 ----- .../systemd-journal-upload/journal-upload.conf.in | 5 - .../systemd-journal-upload/journal-upload.h | 71 - src/grp-journal/Makefile | 4 + src/grp-journal/grp-remote/.gitignore | 2 + src/grp-journal/grp-remote/Makefile | 30 + src/grp-journal/grp-remote/browse.html | 544 +++ src/grp-journal/grp-remote/log-generator.py | 76 + src/grp-journal/grp-remote/microhttpd-util.c | 327 ++ src/grp-journal/grp-remote/microhttpd-util.h | 60 + .../grp-remote/systemd-journal-gatewayd/Makefile | 68 + .../systemd-journal-gatewayd/journal-gatewayd.c | 1077 ++++++ .../grp-remote/systemd-journal-remote/Makefile | 85 + .../systemd-journal-remote/journal-remote-parse.c | 506 +++ .../systemd-journal-remote/journal-remote-parse.h | 69 + .../systemd-journal-remote/journal-remote-write.c | 168 + .../systemd-journal-remote/journal-remote-write.h | 70 + .../systemd-journal-remote/journal-remote.c | 1601 ++++++++ .../systemd-journal-remote/journal-remote.conf.in | 6 + .../systemd-journal-remote/journal-remote.h | 52 + .../grp-remote/systemd-journal-upload/Makefile | 54 + .../journal-upload-journal.c | 422 ++ .../systemd-journal-upload/journal-upload.c | 880 +++++ .../systemd-journal-upload/journal-upload.conf.in | 5 + .../systemd-journal-upload/journal-upload.h | 71 + src/grp-journal/journalctl/Makefile | 5 + src/grp-journal/journalctl/journal-qrcode.c | 135 + src/grp-journal/journalctl/journal-qrcode.h | 27 + src/grp-journal/journalctl/journalctl.c | 29 +- src/grp-journal/libjournal-core/Makefile | 2 + src/grp-journal/libjournal-core/cat.c | 161 - src/grp-journal/libjournal-core/journal-qrcode.c | 135 - src/grp-journal/libjournal-core/journal-qrcode.h | 27 - src/grp-journal/libjournal-core/journald-audit.c | 2 +- src/grp-journal/libjournal-core/journald-gperf.c | 173 + src/grp-journal/libjournal-core/journald-server.c | 31 +- src/grp-journal/systemd-cat/Makefile | 36 + src/grp-journal/systemd-cat/cat.c | 161 + src/grp-journal/systemd-journald/Makefile | 13 - src/grp-journal/systemd-journald/journald.c | 5 +- src/grp-locale/Makefile | 3 + src/grp-locale/localectl.c | 8 +- src/grp-locale/localed.c | 6 +- src/grp-machine/Makefile | 1 + src/grp-machine/grp-import/.gitignore | 1 + src/grp-machine/grp-import/Makefile | 89 + src/grp-machine/grp-import/curl-util.c | 448 +++ src/grp-machine/grp-import/curl-util.h | 56 + .../grp-import/org.freedesktop.import1.conf | 62 + .../grp-import/org.freedesktop.import1.policy.in | 49 + .../grp-import/org.freedesktop.import1.service | 12 + src/grp-machine/grp-import/qcow2-util.c | 352 ++ src/grp-machine/grp-import/qcow2-util.h | 23 + src/grp-machine/grp-import/systemd-export/Makefile | 51 + .../grp-import/systemd-export/export-raw.c | 352 ++ .../grp-import/systemd-export/export-raw.h | 36 + .../grp-import/systemd-export/export-tar.c | 328 ++ .../grp-import/systemd-export/export-tar.h | 36 + src/grp-machine/grp-import/systemd-export/export.c | 320 ++ src/grp-machine/grp-import/systemd-import/Makefile | 52 + .../grp-import/systemd-import/import-common.c | 225 ++ .../grp-import/systemd-import/import-common.h | 26 + .../grp-import/systemd-import/import-compress.c | 469 +++ .../grp-import/systemd-import/import-compress.h | 61 + .../grp-import/systemd-import/import-pubring.gpg | Bin 0 -> 9551 bytes .../grp-import/systemd-import/import-raw.c | 467 +++ .../grp-import/systemd-import/import-raw.h | 36 + .../grp-import/systemd-import/import-tar.c | 388 ++ .../grp-import/systemd-import/import-tar.h | 36 + src/grp-machine/grp-import/systemd-import/import.c | 337 ++ .../grp-import/systemd-importd/Makefile | 39 + .../grp-import/systemd-importd/importd.c | 1221 ++++++ src/grp-machine/grp-import/systemd-pull/Makefile | 64 + .../grp-import/systemd-pull/pull-common.c | 549 +++ .../grp-import/systemd-pull/pull-common.h | 36 + src/grp-machine/grp-import/systemd-pull/pull-job.c | 618 +++ src/grp-machine/grp-import/systemd-pull/pull-job.h | 106 + src/grp-machine/grp-import/systemd-pull/pull-raw.c | 651 ++++ src/grp-machine/grp-import/systemd-pull/pull-raw.h | 36 + src/grp-machine/grp-import/systemd-pull/pull-tar.c | 563 +++ src/grp-machine/grp-import/systemd-pull/pull-tar.h | 36 + src/grp-machine/grp-import/systemd-pull/pull.c | 337 ++ src/grp-machine/grp-import/test-qcow2.c | 53 + src/grp-system/Makefile | 3 +- src/grp-system/grp-utils/Makefile | 32 + .../grp-utils/systemd-analyze/.gitignore | 1 + src/grp-system/grp-utils/systemd-analyze/Makefile | 40 + .../grp-utils/systemd-analyze/analyze-verify.c | 304 ++ .../grp-utils/systemd-analyze/analyze-verify.h | 26 + src/grp-system/grp-utils/systemd-analyze/analyze.c | 1485 +++++++ src/grp-system/grp-utils/systemd-delta/Makefile | 33 + src/grp-system/grp-utils/systemd-delta/delta.c | 635 +++ .../grp-utils/systemd-fstab-generator/Makefile | 34 + .../systemd-fstab-generator/fstab-generator.c | 713 ++++ .../systemd-fstab-generator/mount-setup.c | 1 + .../systemd-fstab-generator/mount-setup.h | 1 + src/grp-system/grp-utils/systemd-run/Makefile | 33 + src/grp-system/grp-utils/systemd-run/run.c | 1261 ++++++ .../grp-utils/systemd-sysv-generator/Makefile | 32 + .../systemd-sysv-generator/sysv-generator.c | 1039 +++++ src/grp-system/libcore/.gitignore | 3 + src/grp-system/libcore/Makefile | 175 + src/grp-system/libcore/audit-fd.c | 73 + src/grp-system/libcore/audit-fd.h | 23 + src/grp-system/libcore/automount.c | 1108 ++++++ src/grp-system/libcore/automount.h | 59 + src/grp-system/libcore/bus-policy.c | 180 + src/grp-system/libcore/bus-policy.h | 64 + src/grp-system/libcore/busname.c | 1082 ++++++ src/grp-system/libcore/busname.h | 69 + src/grp-system/libcore/cgroup.c | 1895 +++++++++ src/grp-system/libcore/cgroup.h | 181 + src/grp-system/libcore/dbus-automount.c | 34 + src/grp-system/libcore/dbus-automount.h | 23 + src/grp-system/libcore/dbus-busname.c | 37 + src/grp-system/libcore/dbus-busname.h | 23 + src/grp-system/libcore/dbus-cgroup.c | 1004 +++++ src/grp-system/libcore/dbus-cgroup.h | 28 + src/grp-system/libcore/dbus-device.c | 28 + src/grp-system/libcore/dbus-device.h | 24 + src/grp-system/libcore/dbus-execute.c | 1545 ++++++++ src/grp-system/libcore/dbus-execute.h | 45 + src/grp-system/libcore/dbus-job.c | 193 + src/grp-system/libcore/dbus-job.h | 31 + src/grp-system/libcore/dbus-kill.c | 122 + src/grp-system/libcore/dbus-kill.h | 29 + src/grp-system/libcore/dbus-manager.c | 2305 +++++++++++ src/grp-system/libcore/dbus-manager.h | 28 + src/grp-system/libcore/dbus-mount.c | 211 + src/grp-system/libcore/dbus-mount.h | 29 + src/grp-system/libcore/dbus-path.c | 86 + src/grp-system/libcore/dbus-path.h | 24 + src/grp-system/libcore/dbus-scope.c | 229 ++ src/grp-system/libcore/dbus-scope.h | 31 + src/grp-system/libcore/dbus-service.c | 322 ++ src/grp-system/libcore/dbus-service.h | 29 + src/grp-system/libcore/dbus-slice.c | 52 + src/grp-system/libcore/dbus-slice.h | 29 + src/grp-system/libcore/dbus-socket.c | 184 + src/grp-system/libcore/dbus-socket.h | 29 + src/grp-system/libcore/dbus-swap.c | 115 + src/grp-system/libcore/dbus-swap.h | 30 + src/grp-system/libcore/dbus-target.c | 26 + src/grp-system/libcore/dbus-target.h | 24 + src/grp-system/libcore/dbus-timer.c | 352 ++ src/grp-system/libcore/dbus-timer.h | 28 + src/grp-system/libcore/dbus-unit.c | 1423 +++++++ src/grp-system/libcore/dbus-unit.h | 41 + src/grp-system/libcore/dbus.c | 1243 ++++++ src/grp-system/libcore/dbus.h | 44 + src/grp-system/libcore/device.c | 876 +++++ src/grp-system/libcore/device.h | 47 + src/grp-system/libcore/execute.c | 3092 +++++++++++++++ src/grp-system/libcore/execute.h | 287 ++ src/grp-system/libcore/failure-action.c | 127 + src/grp-system/libcore/failure-action.h | 41 + src/grp-system/libcore/hostname-setup.c | 68 + src/grp-system/libcore/hostname-setup.h | 22 + src/grp-system/libcore/ima-setup.c | 80 + src/grp-system/libcore/ima-setup.h | 24 + src/grp-system/libcore/job.c | 1259 ++++++ src/grp-system/libcore/job.h | 242 ++ src/grp-system/libcore/kill.c | 68 + src/grp-system/libcore/kill.h | 65 + src/grp-system/libcore/killall.c | 249 ++ src/grp-system/libcore/killall.h | 22 + src/grp-system/libcore/kmod-setup.c | 131 + src/grp-system/libcore/kmod-setup.h | 22 + src/grp-system/libcore/linux/auto_dev-ioctl.h | 228 ++ src/grp-system/libcore/load-dropin.c | 90 + src/grp-system/libcore/load-dropin.h | 34 + .../libcore/load-fragment-gperf.gperf.m4 | 391 ++ src/grp-system/libcore/load-fragment.c | 4083 ++++++++++++++++++++ src/grp-system/libcore/load-fragment.h | 123 + src/grp-system/libcore/locale-setup.c | 124 + src/grp-system/libcore/locale-setup.h | 22 + src/grp-system/libcore/loopback-setup.c | 89 + src/grp-system/libcore/loopback-setup.h | 22 + src/grp-system/libcore/machine-id-setup.c | 364 ++ src/grp-system/libcore/machine-id-setup.h | 23 + src/grp-system/libcore/manager.c | 3134 +++++++++++++++ src/grp-system/libcore/manager.h | 379 ++ src/grp-system/libcore/mount-setup.c | 413 ++ src/grp-system/libcore/mount-setup.h | 30 + src/grp-system/libcore/mount.c | 1881 +++++++++ src/grp-system/libcore/mount.h | 108 + src/grp-system/libcore/namespace.c | 650 ++++ src/grp-system/libcore/namespace.h | 63 + src/grp-system/libcore/path.c | 784 ++++ src/grp-system/libcore/path.h | 93 + src/grp-system/libcore/scope.c | 608 +++ src/grp-system/libcore/scope.h | 55 + src/grp-system/libcore/selinux-access.c | 283 ++ src/grp-system/libcore/selinux-access.h | 45 + src/grp-system/libcore/selinux-setup.c | 121 + src/grp-system/libcore/selinux-setup.h | 24 + src/grp-system/libcore/service.c | 3353 ++++++++++++++++ src/grp-system/libcore/service.h | 220 ++ src/grp-system/libcore/show-status.c | 124 + src/grp-system/libcore/show-status.h | 39 + src/grp-system/libcore/slice.c | 346 ++ src/grp-system/libcore/slice.h | 32 + src/grp-system/libcore/smack-setup.c | 346 ++ src/grp-system/libcore/smack-setup.h | 24 + src/grp-system/libcore/socket.c | 2992 ++++++++++++++ src/grp-system/libcore/socket.h | 185 + src/grp-system/libcore/swap.c | 1532 ++++++++ src/grp-system/libcore/swap.h | 110 + src/grp-system/libcore/target.c | 223 ++ src/grp-system/libcore/target.h | 30 + src/grp-system/libcore/timer.c | 859 ++++ src/grp-system/libcore/timer.h | 89 + src/grp-system/libcore/transaction.c | 1099 ++++++ src/grp-system/libcore/transaction.h | 51 + src/grp-system/libcore/unit-printf.c | 304 ++ src/grp-system/libcore/unit-printf.h | 26 + src/grp-system/libcore/unit.c | 3818 ++++++++++++++++++ src/grp-system/libcore/unit.h | 639 +++ src/grp-system/systemctl/Makefile | 4 + src/grp-system/systemctl/systemctl.c | 32 +- src/grp-system/systemd/Makefile | 6 + src/grp-system/systemd/main.c | 34 +- src/grp-udev/systemd-hwdb/Makefile | 78 + src/grp-udev/systemd-hwdb/hwdb.c | 739 ++++ src/grp-utils/Makefile | 32 + src/grp-utils/systemd-ac-power/Makefile | 33 + src/grp-utils/systemd-ac-power/ac-power.c | 35 + src/grp-utils/systemd-escape/Makefile | 34 + src/grp-utils/systemd-escape/escape.c | 237 ++ src/grp-utils/systemd-notify/Makefile | 33 + src/grp-utils/systemd-notify/notify.c | 203 + src/grp-utils/systemd-path/Makefile | 38 + src/grp-utils/systemd-path/_sd-common.h | 1 + src/grp-utils/systemd-path/path.c | 198 + src/grp-utils/systemd-path/sd-path.c | 638 +++ src/grp-utils/systemd-path/sd-path.h | 91 + src/grp-utils/systemd-socket-activate/Makefile | 36 + src/grp-utils/systemd-socket-activate/activate.c | 545 +++ src/kernel-install/50-depmod.install | 8 - src/kernel-install/90-loaderentry.install | 88 - src/kernel-install/Makefile | 33 - src/kernel-install/bash-completion_kernel-install | 50 - src/kernel-install/kernel-install | 136 - src/kernel-install/kernel-install.xml | 192 - src/kernel-install/zsh-completion_kernel-install | 26 - src/libcore/.gitignore | 3 - src/libcore/Makefile | 175 - src/libcore/audit-fd.c | 73 - src/libcore/audit-fd.h | 23 - src/libcore/automount.c | 1108 ------ src/libcore/automount.h | 59 - src/libcore/bus-policy.c | 180 - src/libcore/bus-policy.h | 64 - src/libcore/busname.c | 1082 ------ src/libcore/busname.h | 69 - src/libcore/cgroup.c | 1895 --------- src/libcore/cgroup.h | 181 - 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 | 1004 ----- src/libcore/dbus-cgroup.h | 28 - src/libcore/dbus-device.c | 28 - src/libcore/dbus-device.h | 24 - src/libcore/dbus-execute.c | 1545 -------- 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 | 2305 ----------- 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 | 184 - 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 | 1423 ------- src/libcore/dbus-unit.h | 41 - src/libcore/dbus.c | 1243 ------ src/libcore/dbus.h | 44 - src/libcore/device.c | 876 ----- src/libcore/device.h | 47 - src/libcore/execute.c | 3092 --------------- src/libcore/execute.h | 287 -- src/libcore/failure-action.c | 127 - 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 | 1259 ------ 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 | 90 - src/libcore/load-dropin.h | 34 - src/libcore/load-fragment-gperf.gperf.m4 | 391 -- src/libcore/load-fragment.c | 4083 -------------------- src/libcore/load-fragment.h | 123 - src/libcore/locale-setup.c | 124 - src/libcore/locale-setup.h | 22 - src/libcore/loopback-setup.c | 89 - src/libcore/loopback-setup.h | 22 - src/libcore/machine-id-setup.c | 364 -- src/libcore/machine-id-setup.h | 23 - src/libcore/manager.c | 3134 --------------- src/libcore/manager.h | 379 -- src/libcore/mount-setup.c | 413 -- src/libcore/mount-setup.h | 30 - src/libcore/mount.c | 1881 --------- src/libcore/mount.h | 108 - src/libcore/namespace.c | 650 ---- src/libcore/namespace.h | 63 - src/libcore/path.c | 784 ---- src/libcore/path.h | 93 - src/libcore/scope.c | 608 --- src/libcore/scope.h | 55 - src/libcore/selinux-access.c | 283 -- src/libcore/selinux-access.h | 45 - src/libcore/selinux-setup.c | 121 - src/libcore/selinux-setup.h | 24 - src/libcore/service.c | 3353 ---------------- src/libcore/service.h | 220 -- src/libcore/show-status.c | 124 - src/libcore/show-status.h | 39 - src/libcore/slice.c | 346 -- src/libcore/slice.h | 32 - src/libcore/smack-setup.c | 346 -- src/libcore/smack-setup.h | 24 - src/libcore/socket.c | 2992 -------------- src/libcore/socket.h | 185 - src/libcore/swap.c | 1532 -------- src/libcore/swap.h | 110 - src/libcore/target.c | 223 -- src/libcore/target.h | 30 - src/libcore/timer.c | 859 ---- src/libcore/timer.h | 89 - src/libcore/transaction.c | 1099 ------ src/libcore/transaction.h | 51 - src/libcore/unit-printf.c | 304 -- src/libcore/unit-printf.h | 26 - src/libcore/unit.c | 3818 ------------------ src/libcore/unit.h | 639 --- .../include/systemd-network/_sd-common.h | 1 + .../include/systemd-network/sd-dhcp-client.h | 2 +- .../include/systemd-network/sd-dhcp-lease.h | 2 +- .../include/systemd-network/sd-dhcp-server.h | 2 +- .../include/systemd-network/sd-dhcp6-client.h | 2 +- .../include/systemd-network/sd-dhcp6-lease.h | 2 +- .../include/systemd-network/sd-ipv4acd.h | 2 +- .../include/systemd-network/sd-ipv4ll.h | 2 +- .../include/systemd-network/sd-lldp.h | 2 +- .../include/systemd-network/sd-ndisc.h | 2 +- src/libsystemd/Makefile | 2 +- src/libudev/src/Makefile | 2 +- src/systemd-ac-power/Makefile | 33 - src/systemd-ac-power/ac-power.c | 35 - src/systemd-activate/Makefile | 36 - src/systemd-activate/activate.c | 545 --- src/systemd-analyze/.gitignore | 1 - src/systemd-analyze/Makefile | 40 - src/systemd-analyze/analyze-verify.c | 304 -- src/systemd-analyze/analyze-verify.h | 26 - src/systemd-analyze/analyze.c | 1485 ------- src/systemd-delta/Makefile | 33 - src/systemd-delta/delta.c | 635 --- src/systemd-escape/Makefile | 34 - src/systemd-escape/escape.c | 237 -- src/systemd-firstboot/Makefile | 47 - src/systemd-firstboot/firstboot.c | 870 ----- src/systemd-fsck/Makefile | 33 - src/systemd-fsck/fsck.c | 487 --- src/systemd-fstab-generator/Makefile | 34 - src/systemd-fstab-generator/fstab-generator.c | 713 ---- src/systemd-fstab-generator/mount-setup.c | 1 - src/systemd-fstab-generator/mount-setup.h | 1 - src/systemd-hwdb/Makefile | 78 - src/systemd-hwdb/hwdb.c | 739 ---- src/systemd-machine-id-setup/machine-id-setup.c | 2 +- src/systemd-machine-id-setup/machine-id-setup.h | 2 +- src/systemd-notify/Makefile | 33 - src/systemd-notify/notify.c | 203 - src/systemd-nspawn/loopback-setup.c | 2 +- src/systemd-nspawn/loopback-setup.h | 2 +- src/systemd-nspawn/machine-id-setup.c | 2 +- src/systemd-nspawn/machine-id-setup.h | 2 +- src/systemd-nspawn/mount-setup.c | 2 +- src/systemd-nspawn/mount-setup.h | 2 +- src/systemd-path/Makefile | 38 - src/systemd-path/_sd-common.h | 1 - src/systemd-path/path.c | 198 - src/systemd-path/sd-path.c | 638 --- src/systemd-path/sd-path.h | 91 - src/systemd-remount-fs/mount-setup.c | 2 +- src/systemd-remount-fs/mount-setup.h | 2 +- src/systemd-run/Makefile | 33 - src/systemd-run/run.c | 1261 ------ src/systemd-shutdown/killall.c | 2 +- src/systemd-shutdown/killall.h | 2 +- src/systemd-shutdown/mount-setup.c | 2 +- src/systemd-shutdown/mount-setup.h | 2 +- src/systemd-sysctl/Makefile | 33 - src/systemd-sysctl/sysctl.c | 287 -- src/systemd-sysusers/Makefile | 61 - src/systemd-sysusers/sysusers.c | 1919 --------- src/systemd-sysv-generator/Makefile | 32 - src/systemd-sysv-generator/sysv-generator.c | 1039 ----- src/systemd-timesyncd/timesyncd.c | 5 +- src/systemd-tmpfiles/Makefile | 84 - src/systemd-tmpfiles/tmpfiles.c | 2343 ----------- src/systemd-update-done/Makefile | 34 - src/systemd-update-done/update-done.c | 115 - src/systemd-update-utmp/Makefile | 42 - src/systemd-update-utmp/update-utmp.c | 283 -- 557 files changed, 86330 insertions(+), 86016 deletions(-) create mode 100644 src/grp-boot/kernel-install/50-depmod.install create mode 100644 src/grp-boot/kernel-install/90-loaderentry.install create mode 100644 src/grp-boot/kernel-install/Makefile create mode 100644 src/grp-boot/kernel-install/bash-completion_kernel-install create mode 100644 src/grp-boot/kernel-install/kernel-install create mode 100644 src/grp-boot/kernel-install/kernel-install.xml create mode 100644 src/grp-boot/kernel-install/zsh-completion_kernel-install delete mode 100644 src/grp-helperunits/systemd-backlight/Makefile delete mode 100644 src/grp-helperunits/systemd-backlight/backlight.c delete mode 100644 src/grp-helperunits/systemd-binfmt/Makefile delete mode 100644 src/grp-helperunits/systemd-binfmt/binfmt.c delete mode 100644 src/grp-helperunits/systemd-detect-virt/Makefile delete mode 100644 src/grp-helperunits/systemd-detect-virt/detect-virt.c delete mode 100644 src/grp-helperunits/systemd-quotacheck/Makefile delete mode 100644 src/grp-helperunits/systemd-quotacheck/quotacheck.c delete mode 100644 src/grp-helperunits/systemd-random-seed/Makefile delete mode 100644 src/grp-helperunits/systemd-random-seed/random-seed.c delete mode 100644 src/grp-helperunits/systemd-rfkill/Makefile delete mode 100644 src/grp-helperunits/systemd-rfkill/rfkill.c delete mode 100644 src/grp-helperunits/systemd-sleep/Makefile delete mode 100644 src/grp-helperunits/systemd-sleep/sleep.c delete mode 100644 src/grp-helperunits/systemd-user-sessions/Makefile delete mode 100644 src/grp-helperunits/systemd-user-sessions/user-sessions.c delete mode 100644 src/grp-helperunits/systemd-vconsole-setup/.gitignore delete mode 100644 src/grp-helperunits/systemd-vconsole-setup/90-vconsole.rules.in delete mode 100644 src/grp-helperunits/systemd-vconsole-setup/Makefile delete mode 100644 src/grp-helperunits/systemd-vconsole-setup/vconsole-setup.c delete mode 100644 src/grp-import/.gitignore delete mode 100644 src/grp-import/Makefile delete mode 100644 src/grp-import/curl-util.c delete mode 100644 src/grp-import/curl-util.h delete mode 100644 src/grp-import/org.freedesktop.import1.conf delete mode 100644 src/grp-import/org.freedesktop.import1.policy.in delete mode 100644 src/grp-import/org.freedesktop.import1.service delete mode 100644 src/grp-import/qcow2-util.c delete mode 100644 src/grp-import/qcow2-util.h delete mode 100644 src/grp-import/systemd-export/Makefile delete mode 100644 src/grp-import/systemd-export/export-raw.c delete mode 100644 src/grp-import/systemd-export/export-raw.h delete mode 100644 src/grp-import/systemd-export/export-tar.c delete mode 100644 src/grp-import/systemd-export/export-tar.h delete mode 100644 src/grp-import/systemd-export/export.c delete mode 100644 src/grp-import/systemd-import/Makefile delete mode 100644 src/grp-import/systemd-import/import-common.c delete mode 100644 src/grp-import/systemd-import/import-common.h delete mode 100644 src/grp-import/systemd-import/import-compress.c delete mode 100644 src/grp-import/systemd-import/import-compress.h delete mode 100644 src/grp-import/systemd-import/import-pubring.gpg delete mode 100644 src/grp-import/systemd-import/import-raw.c delete mode 100644 src/grp-import/systemd-import/import-raw.h delete mode 100644 src/grp-import/systemd-import/import-tar.c delete mode 100644 src/grp-import/systemd-import/import-tar.h delete mode 100644 src/grp-import/systemd-import/import.c delete mode 100644 src/grp-import/systemd-importd/Makefile delete mode 100644 src/grp-import/systemd-importd/importd.c delete mode 100644 src/grp-import/systemd-pull/Makefile delete mode 100644 src/grp-import/systemd-pull/pull-common.c delete mode 100644 src/grp-import/systemd-pull/pull-common.h delete mode 100644 src/grp-import/systemd-pull/pull-job.c delete mode 100644 src/grp-import/systemd-pull/pull-job.h delete mode 100644 src/grp-import/systemd-pull/pull-raw.c delete mode 100644 src/grp-import/systemd-pull/pull-raw.h delete mode 100644 src/grp-import/systemd-pull/pull-tar.c delete mode 100644 src/grp-import/systemd-pull/pull-tar.h delete mode 100644 src/grp-import/systemd-pull/pull.c delete mode 100644 src/grp-import/test-qcow2.c create mode 100644 src/grp-initutils/Makefile create mode 100644 src/grp-initutils/systemd-backlight/Makefile create mode 100644 src/grp-initutils/systemd-backlight/backlight.c create mode 100644 src/grp-initutils/systemd-binfmt/Makefile create mode 100644 src/grp-initutils/systemd-binfmt/binfmt.c create mode 100644 src/grp-initutils/systemd-detect-virt/Makefile create mode 100644 src/grp-initutils/systemd-detect-virt/detect-virt.c create mode 100644 src/grp-initutils/systemd-firstboot/Makefile create mode 100644 src/grp-initutils/systemd-firstboot/firstboot.c create mode 100644 src/grp-initutils/systemd-fsck/Makefile create mode 100644 src/grp-initutils/systemd-fsck/fsck.c create mode 100644 src/grp-initutils/systemd-quotacheck/Makefile create mode 100644 src/grp-initutils/systemd-quotacheck/quotacheck.c create mode 100644 src/grp-initutils/systemd-random-seed/Makefile create mode 100644 src/grp-initutils/systemd-random-seed/random-seed.c create mode 100644 src/grp-initutils/systemd-rfkill/Makefile create mode 100644 src/grp-initutils/systemd-rfkill/rfkill.c create mode 100644 src/grp-initutils/systemd-sleep/Makefile create mode 100644 src/grp-initutils/systemd-sleep/sleep.c create mode 100644 src/grp-initutils/systemd-sysctl/Makefile create mode 100644 src/grp-initutils/systemd-sysctl/sysctl.c create mode 100644 src/grp-initutils/systemd-sysusers/Makefile create mode 100644 src/grp-initutils/systemd-sysusers/sysusers.c create mode 100644 src/grp-initutils/systemd-tmpfiles/Makefile create mode 100644 src/grp-initutils/systemd-tmpfiles/tmpfiles.c create mode 100644 src/grp-initutils/systemd-update-done/Makefile create mode 100644 src/grp-initutils/systemd-update-done/update-done.c create mode 100644 src/grp-initutils/systemd-update-utmp/Makefile create mode 100644 src/grp-initutils/systemd-update-utmp/update-utmp.c create mode 100644 src/grp-initutils/systemd-user-sessions/Makefile create mode 100644 src/grp-initutils/systemd-user-sessions/user-sessions.c create mode 100644 src/grp-initutils/systemd-vconsole-setup/.gitignore create mode 100644 src/grp-initutils/systemd-vconsole-setup/90-vconsole.rules.in create mode 100644 src/grp-initutils/systemd-vconsole-setup/Makefile create mode 100644 src/grp-initutils/systemd-vconsole-setup/vconsole-setup.c delete mode 100644 src/grp-journal-remote/.gitignore delete mode 100644 src/grp-journal-remote/Makefile delete mode 100644 src/grp-journal-remote/browse.html delete mode 100755 src/grp-journal-remote/log-generator.py delete mode 100644 src/grp-journal-remote/microhttpd-util.c delete mode 100644 src/grp-journal-remote/microhttpd-util.h delete mode 100644 src/grp-journal-remote/systemd-journal-gatewayd/Makefile delete mode 100644 src/grp-journal-remote/systemd-journal-gatewayd/journal-gatewayd.c delete mode 100644 src/grp-journal-remote/systemd-journal-remote/Makefile delete mode 100644 src/grp-journal-remote/systemd-journal-remote/journal-remote-parse.c delete mode 100644 src/grp-journal-remote/systemd-journal-remote/journal-remote-parse.h delete mode 100644 src/grp-journal-remote/systemd-journal-remote/journal-remote-write.c delete mode 100644 src/grp-journal-remote/systemd-journal-remote/journal-remote-write.h delete mode 100644 src/grp-journal-remote/systemd-journal-remote/journal-remote.c delete mode 100644 src/grp-journal-remote/systemd-journal-remote/journal-remote.conf.in delete mode 100644 src/grp-journal-remote/systemd-journal-remote/journal-remote.h delete mode 100644 src/grp-journal-remote/systemd-journal-upload/Makefile delete mode 100644 src/grp-journal-remote/systemd-journal-upload/journal-upload-journal.c delete mode 100644 src/grp-journal-remote/systemd-journal-upload/journal-upload.c delete mode 100644 src/grp-journal-remote/systemd-journal-upload/journal-upload.conf.in delete mode 100644 src/grp-journal-remote/systemd-journal-upload/journal-upload.h create mode 100644 src/grp-journal/grp-remote/.gitignore create mode 100644 src/grp-journal/grp-remote/Makefile create mode 100644 src/grp-journal/grp-remote/browse.html create mode 100755 src/grp-journal/grp-remote/log-generator.py create mode 100644 src/grp-journal/grp-remote/microhttpd-util.c create mode 100644 src/grp-journal/grp-remote/microhttpd-util.h create mode 100644 src/grp-journal/grp-remote/systemd-journal-gatewayd/Makefile create mode 100644 src/grp-journal/grp-remote/systemd-journal-gatewayd/journal-gatewayd.c create mode 100644 src/grp-journal/grp-remote/systemd-journal-remote/Makefile create mode 100644 src/grp-journal/grp-remote/systemd-journal-remote/journal-remote-parse.c create mode 100644 src/grp-journal/grp-remote/systemd-journal-remote/journal-remote-parse.h create mode 100644 src/grp-journal/grp-remote/systemd-journal-remote/journal-remote-write.c create mode 100644 src/grp-journal/grp-remote/systemd-journal-remote/journal-remote-write.h create mode 100644 src/grp-journal/grp-remote/systemd-journal-remote/journal-remote.c create mode 100644 src/grp-journal/grp-remote/systemd-journal-remote/journal-remote.conf.in create mode 100644 src/grp-journal/grp-remote/systemd-journal-remote/journal-remote.h create mode 100644 src/grp-journal/grp-remote/systemd-journal-upload/Makefile create mode 100644 src/grp-journal/grp-remote/systemd-journal-upload/journal-upload-journal.c create mode 100644 src/grp-journal/grp-remote/systemd-journal-upload/journal-upload.c create mode 100644 src/grp-journal/grp-remote/systemd-journal-upload/journal-upload.conf.in create mode 100644 src/grp-journal/grp-remote/systemd-journal-upload/journal-upload.h create mode 100644 src/grp-journal/journalctl/journal-qrcode.c create mode 100644 src/grp-journal/journalctl/journal-qrcode.h delete mode 100644 src/grp-journal/libjournal-core/cat.c delete mode 100644 src/grp-journal/libjournal-core/journal-qrcode.c delete mode 100644 src/grp-journal/libjournal-core/journal-qrcode.h create mode 100644 src/grp-journal/libjournal-core/journald-gperf.c create mode 100644 src/grp-journal/systemd-cat/Makefile create mode 100644 src/grp-journal/systemd-cat/cat.c create mode 100644 src/grp-machine/grp-import/.gitignore create mode 100644 src/grp-machine/grp-import/Makefile create mode 100644 src/grp-machine/grp-import/curl-util.c create mode 100644 src/grp-machine/grp-import/curl-util.h create mode 100644 src/grp-machine/grp-import/org.freedesktop.import1.conf create mode 100644 src/grp-machine/grp-import/org.freedesktop.import1.policy.in create mode 100644 src/grp-machine/grp-import/org.freedesktop.import1.service create mode 100644 src/grp-machine/grp-import/qcow2-util.c create mode 100644 src/grp-machine/grp-import/qcow2-util.h create mode 100644 src/grp-machine/grp-import/systemd-export/Makefile create mode 100644 src/grp-machine/grp-import/systemd-export/export-raw.c create mode 100644 src/grp-machine/grp-import/systemd-export/export-raw.h create mode 100644 src/grp-machine/grp-import/systemd-export/export-tar.c create mode 100644 src/grp-machine/grp-import/systemd-export/export-tar.h create mode 100644 src/grp-machine/grp-import/systemd-export/export.c create mode 100644 src/grp-machine/grp-import/systemd-import/Makefile create mode 100644 src/grp-machine/grp-import/systemd-import/import-common.c create mode 100644 src/grp-machine/grp-import/systemd-import/import-common.h create mode 100644 src/grp-machine/grp-import/systemd-import/import-compress.c create mode 100644 src/grp-machine/grp-import/systemd-import/import-compress.h create mode 100644 src/grp-machine/grp-import/systemd-import/import-pubring.gpg create mode 100644 src/grp-machine/grp-import/systemd-import/import-raw.c create mode 100644 src/grp-machine/grp-import/systemd-import/import-raw.h create mode 100644 src/grp-machine/grp-import/systemd-import/import-tar.c create mode 100644 src/grp-machine/grp-import/systemd-import/import-tar.h create mode 100644 src/grp-machine/grp-import/systemd-import/import.c create mode 100644 src/grp-machine/grp-import/systemd-importd/Makefile create mode 100644 src/grp-machine/grp-import/systemd-importd/importd.c create mode 100644 src/grp-machine/grp-import/systemd-pull/Makefile create mode 100644 src/grp-machine/grp-import/systemd-pull/pull-common.c create mode 100644 src/grp-machine/grp-import/systemd-pull/pull-common.h create mode 100644 src/grp-machine/grp-import/systemd-pull/pull-job.c create mode 100644 src/grp-machine/grp-import/systemd-pull/pull-job.h create mode 100644 src/grp-machine/grp-import/systemd-pull/pull-raw.c create mode 100644 src/grp-machine/grp-import/systemd-pull/pull-raw.h create mode 100644 src/grp-machine/grp-import/systemd-pull/pull-tar.c create mode 100644 src/grp-machine/grp-import/systemd-pull/pull-tar.h create mode 100644 src/grp-machine/grp-import/systemd-pull/pull.c create mode 100644 src/grp-machine/grp-import/test-qcow2.c create mode 100644 src/grp-system/grp-utils/Makefile create mode 100644 src/grp-system/grp-utils/systemd-analyze/.gitignore create mode 100644 src/grp-system/grp-utils/systemd-analyze/Makefile create mode 100644 src/grp-system/grp-utils/systemd-analyze/analyze-verify.c create mode 100644 src/grp-system/grp-utils/systemd-analyze/analyze-verify.h create mode 100644 src/grp-system/grp-utils/systemd-analyze/analyze.c create mode 100644 src/grp-system/grp-utils/systemd-delta/Makefile create mode 100644 src/grp-system/grp-utils/systemd-delta/delta.c create mode 100644 src/grp-system/grp-utils/systemd-fstab-generator/Makefile create mode 100644 src/grp-system/grp-utils/systemd-fstab-generator/fstab-generator.c create mode 120000 src/grp-system/grp-utils/systemd-fstab-generator/mount-setup.c create mode 120000 src/grp-system/grp-utils/systemd-fstab-generator/mount-setup.h create mode 100644 src/grp-system/grp-utils/systemd-run/Makefile create mode 100644 src/grp-system/grp-utils/systemd-run/run.c create mode 100644 src/grp-system/grp-utils/systemd-sysv-generator/Makefile create mode 100644 src/grp-system/grp-utils/systemd-sysv-generator/sysv-generator.c create mode 100644 src/grp-system/libcore/.gitignore create mode 100644 src/grp-system/libcore/Makefile create mode 100644 src/grp-system/libcore/audit-fd.c create mode 100644 src/grp-system/libcore/audit-fd.h create mode 100644 src/grp-system/libcore/automount.c create mode 100644 src/grp-system/libcore/automount.h create mode 100644 src/grp-system/libcore/bus-policy.c create mode 100644 src/grp-system/libcore/bus-policy.h create mode 100644 src/grp-system/libcore/busname.c create mode 100644 src/grp-system/libcore/busname.h create mode 100644 src/grp-system/libcore/cgroup.c create mode 100644 src/grp-system/libcore/cgroup.h create mode 100644 src/grp-system/libcore/dbus-automount.c create mode 100644 src/grp-system/libcore/dbus-automount.h create mode 100644 src/grp-system/libcore/dbus-busname.c create mode 100644 src/grp-system/libcore/dbus-busname.h create mode 100644 src/grp-system/libcore/dbus-cgroup.c create mode 100644 src/grp-system/libcore/dbus-cgroup.h create mode 100644 src/grp-system/libcore/dbus-device.c create mode 100644 src/grp-system/libcore/dbus-device.h create mode 100644 src/grp-system/libcore/dbus-execute.c create mode 100644 src/grp-system/libcore/dbus-execute.h create mode 100644 src/grp-system/libcore/dbus-job.c create mode 100644 src/grp-system/libcore/dbus-job.h create mode 100644 src/grp-system/libcore/dbus-kill.c create mode 100644 src/grp-system/libcore/dbus-kill.h create mode 100644 src/grp-system/libcore/dbus-manager.c create mode 100644 src/grp-system/libcore/dbus-manager.h create mode 100644 src/grp-system/libcore/dbus-mount.c create mode 100644 src/grp-system/libcore/dbus-mount.h create mode 100644 src/grp-system/libcore/dbus-path.c create mode 100644 src/grp-system/libcore/dbus-path.h create mode 100644 src/grp-system/libcore/dbus-scope.c create mode 100644 src/grp-system/libcore/dbus-scope.h create mode 100644 src/grp-system/libcore/dbus-service.c create mode 100644 src/grp-system/libcore/dbus-service.h create mode 100644 src/grp-system/libcore/dbus-slice.c create mode 100644 src/grp-system/libcore/dbus-slice.h create mode 100644 src/grp-system/libcore/dbus-socket.c create mode 100644 src/grp-system/libcore/dbus-socket.h create mode 100644 src/grp-system/libcore/dbus-swap.c create mode 100644 src/grp-system/libcore/dbus-swap.h create mode 100644 src/grp-system/libcore/dbus-target.c create mode 100644 src/grp-system/libcore/dbus-target.h create mode 100644 src/grp-system/libcore/dbus-timer.c create mode 100644 src/grp-system/libcore/dbus-timer.h create mode 100644 src/grp-system/libcore/dbus-unit.c create mode 100644 src/grp-system/libcore/dbus-unit.h create mode 100644 src/grp-system/libcore/dbus.c create mode 100644 src/grp-system/libcore/dbus.h create mode 100644 src/grp-system/libcore/device.c create mode 100644 src/grp-system/libcore/device.h create mode 100644 src/grp-system/libcore/execute.c create mode 100644 src/grp-system/libcore/execute.h create mode 100644 src/grp-system/libcore/failure-action.c create mode 100644 src/grp-system/libcore/failure-action.h create mode 100644 src/grp-system/libcore/hostname-setup.c create mode 100644 src/grp-system/libcore/hostname-setup.h create mode 100644 src/grp-system/libcore/ima-setup.c create mode 100644 src/grp-system/libcore/ima-setup.h create mode 100644 src/grp-system/libcore/job.c create mode 100644 src/grp-system/libcore/job.h create mode 100644 src/grp-system/libcore/kill.c create mode 100644 src/grp-system/libcore/kill.h create mode 100644 src/grp-system/libcore/killall.c create mode 100644 src/grp-system/libcore/killall.h create mode 100644 src/grp-system/libcore/kmod-setup.c create mode 100644 src/grp-system/libcore/kmod-setup.h create mode 100644 src/grp-system/libcore/linux/auto_dev-ioctl.h create mode 100644 src/grp-system/libcore/load-dropin.c create mode 100644 src/grp-system/libcore/load-dropin.h create mode 100644 src/grp-system/libcore/load-fragment-gperf.gperf.m4 create mode 100644 src/grp-system/libcore/load-fragment.c create mode 100644 src/grp-system/libcore/load-fragment.h create mode 100644 src/grp-system/libcore/locale-setup.c create mode 100644 src/grp-system/libcore/locale-setup.h create mode 100644 src/grp-system/libcore/loopback-setup.c create mode 100644 src/grp-system/libcore/loopback-setup.h create mode 100644 src/grp-system/libcore/machine-id-setup.c create mode 100644 src/grp-system/libcore/machine-id-setup.h create mode 100644 src/grp-system/libcore/manager.c create mode 100644 src/grp-system/libcore/manager.h create mode 100644 src/grp-system/libcore/mount-setup.c create mode 100644 src/grp-system/libcore/mount-setup.h create mode 100644 src/grp-system/libcore/mount.c create mode 100644 src/grp-system/libcore/mount.h create mode 100644 src/grp-system/libcore/namespace.c create mode 100644 src/grp-system/libcore/namespace.h create mode 100644 src/grp-system/libcore/path.c create mode 100644 src/grp-system/libcore/path.h create mode 100644 src/grp-system/libcore/scope.c create mode 100644 src/grp-system/libcore/scope.h create mode 100644 src/grp-system/libcore/selinux-access.c create mode 100644 src/grp-system/libcore/selinux-access.h create mode 100644 src/grp-system/libcore/selinux-setup.c create mode 100644 src/grp-system/libcore/selinux-setup.h create mode 100644 src/grp-system/libcore/service.c create mode 100644 src/grp-system/libcore/service.h create mode 100644 src/grp-system/libcore/show-status.c create mode 100644 src/grp-system/libcore/show-status.h create mode 100644 src/grp-system/libcore/slice.c create mode 100644 src/grp-system/libcore/slice.h create mode 100644 src/grp-system/libcore/smack-setup.c create mode 100644 src/grp-system/libcore/smack-setup.h create mode 100644 src/grp-system/libcore/socket.c create mode 100644 src/grp-system/libcore/socket.h create mode 100644 src/grp-system/libcore/swap.c create mode 100644 src/grp-system/libcore/swap.h create mode 100644 src/grp-system/libcore/target.c create mode 100644 src/grp-system/libcore/target.h create mode 100644 src/grp-system/libcore/timer.c create mode 100644 src/grp-system/libcore/timer.h create mode 100644 src/grp-system/libcore/transaction.c create mode 100644 src/grp-system/libcore/transaction.h create mode 100644 src/grp-system/libcore/unit-printf.c create mode 100644 src/grp-system/libcore/unit-printf.h create mode 100644 src/grp-system/libcore/unit.c create mode 100644 src/grp-system/libcore/unit.h create mode 100644 src/grp-udev/systemd-hwdb/Makefile create mode 100644 src/grp-udev/systemd-hwdb/hwdb.c create mode 100644 src/grp-utils/Makefile create mode 100644 src/grp-utils/systemd-ac-power/Makefile create mode 100644 src/grp-utils/systemd-ac-power/ac-power.c create mode 100644 src/grp-utils/systemd-escape/Makefile create mode 100644 src/grp-utils/systemd-escape/escape.c create mode 100644 src/grp-utils/systemd-notify/Makefile create mode 100644 src/grp-utils/systemd-notify/notify.c create mode 100644 src/grp-utils/systemd-path/Makefile create mode 120000 src/grp-utils/systemd-path/_sd-common.h create mode 100644 src/grp-utils/systemd-path/path.c create mode 100644 src/grp-utils/systemd-path/sd-path.c create mode 100644 src/grp-utils/systemd-path/sd-path.h create mode 100644 src/grp-utils/systemd-socket-activate/Makefile create mode 100644 src/grp-utils/systemd-socket-activate/activate.c delete mode 100644 src/kernel-install/50-depmod.install delete mode 100644 src/kernel-install/90-loaderentry.install delete mode 100644 src/kernel-install/Makefile delete mode 100644 src/kernel-install/bash-completion_kernel-install delete mode 100644 src/kernel-install/kernel-install delete mode 100644 src/kernel-install/kernel-install.xml delete mode 100644 src/kernel-install/zsh-completion_kernel-install delete mode 100644 src/libcore/.gitignore delete mode 100644 src/libcore/Makefile delete mode 100644 src/libcore/audit-fd.c delete mode 100644 src/libcore/audit-fd.h delete mode 100644 src/libcore/automount.c delete mode 100644 src/libcore/automount.h delete mode 100644 src/libcore/bus-policy.c delete mode 100644 src/libcore/bus-policy.h delete mode 100644 src/libcore/busname.c delete mode 100644 src/libcore/busname.h delete mode 100644 src/libcore/cgroup.c delete mode 100644 src/libcore/cgroup.h delete mode 100644 src/libcore/dbus-automount.c delete mode 100644 src/libcore/dbus-automount.h delete mode 100644 src/libcore/dbus-busname.c delete mode 100644 src/libcore/dbus-busname.h delete mode 100644 src/libcore/dbus-cgroup.c delete mode 100644 src/libcore/dbus-cgroup.h delete mode 100644 src/libcore/dbus-device.c delete mode 100644 src/libcore/dbus-device.h delete mode 100644 src/libcore/dbus-execute.c delete mode 100644 src/libcore/dbus-execute.h delete mode 100644 src/libcore/dbus-job.c delete mode 100644 src/libcore/dbus-job.h delete mode 100644 src/libcore/dbus-kill.c delete mode 100644 src/libcore/dbus-kill.h delete mode 100644 src/libcore/dbus-manager.c delete mode 100644 src/libcore/dbus-manager.h delete mode 100644 src/libcore/dbus-mount.c delete mode 100644 src/libcore/dbus-mount.h delete mode 100644 src/libcore/dbus-path.c delete mode 100644 src/libcore/dbus-path.h delete mode 100644 src/libcore/dbus-scope.c delete mode 100644 src/libcore/dbus-scope.h delete mode 100644 src/libcore/dbus-service.c delete mode 100644 src/libcore/dbus-service.h delete mode 100644 src/libcore/dbus-slice.c delete mode 100644 src/libcore/dbus-slice.h delete mode 100644 src/libcore/dbus-socket.c delete mode 100644 src/libcore/dbus-socket.h delete mode 100644 src/libcore/dbus-swap.c delete mode 100644 src/libcore/dbus-swap.h delete mode 100644 src/libcore/dbus-target.c delete mode 100644 src/libcore/dbus-target.h delete mode 100644 src/libcore/dbus-timer.c delete mode 100644 src/libcore/dbus-timer.h delete mode 100644 src/libcore/dbus-unit.c delete mode 100644 src/libcore/dbus-unit.h delete mode 100644 src/libcore/dbus.c delete mode 100644 src/libcore/dbus.h delete mode 100644 src/libcore/device.c delete mode 100644 src/libcore/device.h delete mode 100644 src/libcore/execute.c delete mode 100644 src/libcore/execute.h delete mode 100644 src/libcore/failure-action.c delete mode 100644 src/libcore/failure-action.h delete mode 100644 src/libcore/hostname-setup.c delete mode 100644 src/libcore/hostname-setup.h delete mode 100644 src/libcore/ima-setup.c delete mode 100644 src/libcore/ima-setup.h delete mode 100644 src/libcore/job.c delete mode 100644 src/libcore/job.h delete mode 100644 src/libcore/kill.c delete mode 100644 src/libcore/kill.h delete mode 100644 src/libcore/killall.c delete mode 100644 src/libcore/killall.h delete mode 100644 src/libcore/kmod-setup.c delete mode 100644 src/libcore/kmod-setup.h delete mode 100644 src/libcore/linux/auto_dev-ioctl.h delete mode 100644 src/libcore/load-dropin.c delete mode 100644 src/libcore/load-dropin.h delete mode 100644 src/libcore/load-fragment-gperf.gperf.m4 delete mode 100644 src/libcore/load-fragment.c delete mode 100644 src/libcore/load-fragment.h delete mode 100644 src/libcore/locale-setup.c delete mode 100644 src/libcore/locale-setup.h delete mode 100644 src/libcore/loopback-setup.c delete mode 100644 src/libcore/loopback-setup.h delete mode 100644 src/libcore/machine-id-setup.c delete mode 100644 src/libcore/machine-id-setup.h delete mode 100644 src/libcore/manager.c delete mode 100644 src/libcore/manager.h delete mode 100644 src/libcore/mount-setup.c delete mode 100644 src/libcore/mount-setup.h delete mode 100644 src/libcore/mount.c delete mode 100644 src/libcore/mount.h delete mode 100644 src/libcore/namespace.c delete mode 100644 src/libcore/namespace.h delete mode 100644 src/libcore/path.c delete mode 100644 src/libcore/path.h delete mode 100644 src/libcore/scope.c delete mode 100644 src/libcore/scope.h delete mode 100644 src/libcore/selinux-access.c delete mode 100644 src/libcore/selinux-access.h delete mode 100644 src/libcore/selinux-setup.c delete mode 100644 src/libcore/selinux-setup.h delete mode 100644 src/libcore/service.c delete mode 100644 src/libcore/service.h delete mode 100644 src/libcore/show-status.c delete mode 100644 src/libcore/show-status.h delete mode 100644 src/libcore/slice.c delete mode 100644 src/libcore/slice.h delete mode 100644 src/libcore/smack-setup.c delete mode 100644 src/libcore/smack-setup.h delete mode 100644 src/libcore/socket.c delete mode 100644 src/libcore/socket.h delete mode 100644 src/libcore/swap.c delete mode 100644 src/libcore/swap.h delete mode 100644 src/libcore/target.c delete mode 100644 src/libcore/target.h delete mode 100644 src/libcore/timer.c delete mode 100644 src/libcore/timer.h delete mode 100644 src/libcore/transaction.c delete mode 100644 src/libcore/transaction.h delete mode 100644 src/libcore/unit-printf.c delete mode 100644 src/libcore/unit-printf.h delete mode 100644 src/libcore/unit.c delete mode 100644 src/libcore/unit.h create mode 120000 src/libsystemd-network/include/systemd-network/_sd-common.h delete mode 100644 src/systemd-ac-power/Makefile delete mode 100644 src/systemd-ac-power/ac-power.c delete mode 100644 src/systemd-activate/Makefile delete mode 100644 src/systemd-activate/activate.c delete mode 100644 src/systemd-analyze/.gitignore delete mode 100644 src/systemd-analyze/Makefile delete mode 100644 src/systemd-analyze/analyze-verify.c delete mode 100644 src/systemd-analyze/analyze-verify.h delete mode 100644 src/systemd-analyze/analyze.c delete mode 100644 src/systemd-delta/Makefile delete mode 100644 src/systemd-delta/delta.c delete mode 100644 src/systemd-escape/Makefile delete mode 100644 src/systemd-escape/escape.c delete mode 100644 src/systemd-firstboot/Makefile delete mode 100644 src/systemd-firstboot/firstboot.c delete mode 100644 src/systemd-fsck/Makefile delete mode 100644 src/systemd-fsck/fsck.c delete mode 100644 src/systemd-fstab-generator/Makefile delete mode 100644 src/systemd-fstab-generator/fstab-generator.c delete mode 120000 src/systemd-fstab-generator/mount-setup.c delete mode 120000 src/systemd-fstab-generator/mount-setup.h delete mode 100644 src/systemd-hwdb/Makefile delete mode 100644 src/systemd-hwdb/hwdb.c delete mode 100644 src/systemd-notify/Makefile delete mode 100644 src/systemd-notify/notify.c delete mode 100644 src/systemd-path/Makefile delete mode 120000 src/systemd-path/_sd-common.h delete mode 100644 src/systemd-path/path.c delete mode 100644 src/systemd-path/sd-path.c delete mode 100644 src/systemd-path/sd-path.h delete mode 100644 src/systemd-run/Makefile delete mode 100644 src/systemd-run/run.c delete mode 100644 src/systemd-sysctl/Makefile delete mode 100644 src/systemd-sysctl/sysctl.c delete mode 100644 src/systemd-sysusers/Makefile delete mode 100644 src/systemd-sysusers/sysusers.c delete mode 100644 src/systemd-sysv-generator/Makefile delete mode 100644 src/systemd-sysv-generator/sysv-generator.c delete mode 100644 src/systemd-tmpfiles/Makefile delete mode 100644 src/systemd-tmpfiles/tmpfiles.c delete mode 100644 src/systemd-update-done/Makefile delete mode 100644 src/systemd-update-done/update-done.c delete mode 100644 src/systemd-update-utmp/Makefile delete mode 100644 src/systemd-update-utmp/update-utmp.c (limited to 'src') diff --git a/src/Makefile b/src/Makefile index 5da6a48833..6ce1db256f 100644 --- a/src/Makefile +++ b/src/Makefile @@ -26,30 +26,25 @@ include $(topsrcdir)/build-aux/Makefile.head.mk at.subdirs += busctl at.subdirs += grp-boot at.subdirs += grp-coredump -#at.subdirs += grp-hostname -#at.subdirs += grp-import -#at.subdirs += grp-journal -#at.subdirs += grp-journal-remote -#at.subdirs += grp-locale -#at.subdirs += grp-login +at.subdirs += grp-hostname +at.subdirs += grp-initutils +at.subdirs += grp-journal +at.subdirs += grp-locale +at.subdirs += grp-login #at.subdirs += grp-machine -#at.subdirs += grp-network +at.subdirs += grp-network at.subdirs += grp-resolve -#at.subdirs += grp-system +at.subdirs += grp-system at.subdirs += grp-timedate at.subdirs += grp-udev -at.subdirs += kernel-install +at.subdirs += grp-utils at.subdirs += libbasic -at.subdirs += libcore at.subdirs += libfirewall at.subdirs += libshared at.subdirs += libsystemd at.subdirs += libsystemd-network at.subdirs += libudev at.subdirs += nss-myhostname -at.subdirs += systemd-activate -at.subdirs += systemd-ac-power -at.subdirs += systemd-analyze at.subdirs += systemd-ask-password at.subdirs += systemd-cgls at.subdirs += systemd-cgroups-agent @@ -57,36 +52,21 @@ at.subdirs += systemd-cgtop at.subdirs += systemd-cryptsetup at.subdirs += systemd-dbus1-generator at.subdirs += systemd-debug-generator -at.subdirs += systemd-delta -at.subdirs += systemd-escape -at.subdirs += systemd-firstboot -at.subdirs += systemd-fsck -at.subdirs += systemd-fstab-generator at.subdirs += systemd-getty-generator at.subdirs += systemd-gpt-auto-generator at.subdirs += systemd-hibernate-resume -at.subdirs += systemd-hwdb at.subdirs += systemd-initctl at.subdirs += systemd-machine-id-setup at.subdirs += systemd-modules-load -at.subdirs += systemd-notify at.subdirs += systemd-nspawn -at.subdirs += systemd-path at.subdirs += systemd-rc-local-generator at.subdirs += systemd-remount-fs at.subdirs += systemd-reply-password -at.subdirs += systemd-run at.subdirs += systemd-shutdown at.subdirs += systemd-socket-proxyd at.subdirs += systemd-stdio-bridge -at.subdirs += systemd-sysctl -at.subdirs += systemd-sysusers at.subdirs += systemd-system-update-generator -at.subdirs += systemd-sysv-generator at.subdirs += systemd-timesyncd -at.subdirs += systemd-tmpfiles at.subdirs += systemd-tty-ask-password-agent -at.subdirs += systemd-update-done -at.subdirs += systemd-update-utmp include $(topsrcdir)/build-aux/Makefile.tail.mk diff --git a/src/grp-boot/Makefile b/src/grp-boot/Makefile index 922d8a32d3..771b80b448 100644 --- a/src/grp-boot/Makefile +++ b/src/grp-boot/Makefile @@ -23,6 +23,6 @@ include $(dir $(lastword $(MAKEFILE_LIST)))/../../config.mk include $(topsrcdir)/build-aux/Makefile.head.mk -at.subdirs += bootctl systemd-boot +at.subdirs += bootctl systemd-boot kernel-install include $(topsrcdir)/build-aux/Makefile.tail.mk diff --git a/src/grp-boot/bootctl/bootctl.c b/src/grp-boot/bootctl/bootctl.c index 487ad429e0..3ea43e0624 100644 --- a/src/grp-boot/bootctl/bootctl.c +++ b/src/grp-boot/bootctl/bootctl.c @@ -38,13 +38,13 @@ #include "basic/alloc-util.h" #include "basic/blkid-util.h" #include "basic/dirent-util.h" -#include "shared/efivars.h" #include "basic/fd-util.h" #include "basic/fileio.h" #include "basic/locale-util.h" #include "basic/rm-rf.h" #include "basic/string-util.h" #include "basic/util.h" +#include "shared/efivars.h" static int verify_esp(const char *p, uint32_t *part, uint64_t *pstart, uint64_t *psize, sd_id128_t *uuid) { struct statfs sfs; diff --git a/src/grp-boot/kernel-install/50-depmod.install b/src/grp-boot/kernel-install/50-depmod.install new file mode 100644 index 0000000000..68c24bed7a --- /dev/null +++ b/src/grp-boot/kernel-install/50-depmod.install @@ -0,0 +1,8 @@ +#!/bin/bash +# -*- mode: shell-script; indent-tabs-mode: nil; sh-basic-offset: 4; -*- +# ex: ts=8 sw=4 sts=4 et filetype=sh + +[[ $1 == "add" ]] || exit 0 +[[ $2 ]] || exit 1 + +exec depmod -a "$2" diff --git a/src/grp-boot/kernel-install/90-loaderentry.install b/src/grp-boot/kernel-install/90-loaderentry.install new file mode 100644 index 0000000000..6e94e12f94 --- /dev/null +++ b/src/grp-boot/kernel-install/90-loaderentry.install @@ -0,0 +1,88 @@ +#!/bin/bash +# -*- mode: shell-script; indent-tabs-mode: nil; sh-basic-offset: 4; -*- +# ex: ts=8 sw=4 sts=4 et filetype=sh + +COMMAND="$1" +KERNEL_VERSION="$2" +BOOT_DIR_ABS="$3" +KERNEL_IMAGE="$4" + +if [[ -f /etc/machine-id ]]; then + read MACHINE_ID < /etc/machine-id +fi + +if ! [[ $MACHINE_ID ]]; then + exit 1 +fi + +BOOT_DIR="/$MACHINE_ID/$KERNEL_VERSION" +LOADER_ENTRY="/boot/loader/entries/$MACHINE_ID-$KERNEL_VERSION.conf" + +if [[ $COMMAND == remove ]]; then + exec rm -f "$LOADER_ENTRY" +fi + +if ! [[ $COMMAND == add ]]; then + exit 1 +fi + +if ! [[ $KERNEL_IMAGE ]]; then + exit 1 +fi + +if [[ -f /etc/os-release ]]; then + . /etc/os-release +elif [[ -f /usr/lib/os-release ]]; then + . /usr/lib/os-release +fi + +if ! [[ $PRETTY_NAME ]]; then + PRETTY_NAME="GNU/Linux $KERNEL_VERSION" +fi + +declare -a BOOT_OPTIONS + +if [[ -f /etc/kernel/cmdline ]]; then + read -r -d '' -a BOOT_OPTIONS < /etc/kernel/cmdline +fi + +if ! [[ ${BOOT_OPTIONS[*]} ]]; then + read -r -d '' -a line < /proc/cmdline + for i in "${line[@]}"; do + [[ "${i#initrd=*}" != "$i" ]] && continue + BOOT_OPTIONS+=("$i") + done +fi + +if ! [[ ${BOOT_OPTIONS[*]} ]]; then + echo "Could not determine the kernel command line parameters." >&2 + echo "Please specify the kernel command line in /etc/kernel/cmdline!" >&2 + exit 1 +fi + +cp "$KERNEL_IMAGE" "$BOOT_DIR_ABS/linux" && + chown root:root "$BOOT_DIR_ABS/linux" && + chmod 0644 "$BOOT_DIR_ABS/linux" || { + echo "Could not copy '$KERNEL_IMAGE to '$BOOT_DIR_ABS/linux'." >&2 + exit 1 +} + +mkdir -p "${LOADER_ENTRY%/*}" || { + echo "Could not create loader entry directory '${LOADER_ENTRY%/*}'." >&2 + exit 1 +} + +{ + echo "title $PRETTY_NAME" + echo "version $KERNEL_VERSION" + echo "machine-id $MACHINE_ID" + echo "options ${BOOT_OPTIONS[*]}" + echo "linux $BOOT_DIR/linux" + [[ -f $BOOT_DIR_ABS/initrd ]] && \ + echo "initrd $BOOT_DIR/initrd" + : +} > "$LOADER_ENTRY" || { + echo "Could not create loader entry '$LOADER_ENTRY'." >&2 + exit 1 +} +exit 0 diff --git a/src/grp-boot/kernel-install/Makefile b/src/grp-boot/kernel-install/Makefile new file mode 100644 index 0000000000..6a937f516c --- /dev/null +++ b/src/grp-boot/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/grp-boot/kernel-install/bash-completion_kernel-install b/src/grp-boot/kernel-install/bash-completion_kernel-install new file mode 100644 index 0000000000..7cd2494cf7 --- /dev/null +++ b/src/grp-boot/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/grp-boot/kernel-install/kernel-install b/src/grp-boot/kernel-install/kernel-install new file mode 100644 index 0000000000..3ae1d77e33 --- /dev/null +++ b/src/grp-boot/kernel-install/kernel-install @@ -0,0 +1,136 @@ +#!/bin/bash +# -*- mode: shell-script; indent-tabs-mode: nil; sh-basic-offset: 4; -*- +# ex: ts=8 sw=4 sts=4 et filetype=sh +# +# This file is part of systemd. +# +# 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 . + +usage() +{ + echo "Usage:" + echo " $0 add KERNEL-VERSION KERNEL-IMAGE" + echo " $0 remove KERNEL-VERSION" +} + +dropindirs_sort() +{ + local suffix=$1; shift + local -a files + local f d i + + readarray -t files < <( + for d in "$@"; do + for i in "$d/"*"$suffix"; do + if [[ -e "$i" ]]; then + echo "${i##*/}" + fi + done + done | sort -Vu + ) + + for f in "${files[@]}"; do + for d in "$@"; do + if [[ -e "$d/$f" ]]; then + echo "$d/$f" + continue 2 + fi + done + done +} + +export LC_COLLATE=C + +for i in "$@"; do + if [ "$i" == "--help" -o "$i" == "-h" ]; then + usage + exit 0 + fi +done + +if [[ "${0##*/}" == 'installkernel' ]]; then + COMMAND='add' +else + COMMAND="$1" + shift +fi + +KERNEL_VERSION="$1" +KERNEL_IMAGE="$2" + +if [[ -f /etc/machine-id ]]; then + read MACHINE_ID < /etc/machine-id +fi + +if ! [[ $MACHINE_ID ]]; then + echo "Could not determine your machine ID from /etc/machine-id." >&2 + echo "Please run 'systemd-machine-id-setup' as root. See man:machine-id(5)" >&2 + exit 1 +fi + +if [[ ! $COMMAND ]] || [[ ! $KERNEL_VERSION ]]; then + echo "Not enough arguments" >&2 + exit 1 +fi + +BOOT_DIR_ABS="/boot/$MACHINE_ID/$KERNEL_VERSION" +ret=0 + +readarray -t PLUGINS < <( + dropindirs_sort ".install" \ + "/etc/kernel/install.d" \ + "/usr/lib/kernel/install.d" +) + +case $COMMAND in + add) + if [[ ! "$KERNEL_IMAGE" ]]; then + echo "Command 'add' requires an argument" >&2 + exit 1 + fi + + mkdir -p "$BOOT_DIR_ABS" || { + echo "Could not create boot directory '$BOOT_DIR_ABS'." >&2 + exit 1 + } + + for f in "${PLUGINS[@]}"; do + if [[ -x $f ]]; then + "$f" add "$KERNEL_VERSION" "$BOOT_DIR_ABS" "$KERNEL_IMAGE" + ((ret+=$?)) + fi + done + ;; + + remove) + for f in "${PLUGINS[@]}"; do + if [[ -x $f ]]; then + "$f" remove "$KERNEL_VERSION" "$BOOT_DIR_ABS" + ((ret+=$?)) + fi + done + + rm -rf "$BOOT_DIR_ABS" + ((ret+=$?)) + ;; + + *) + echo "Unknown command '$COMMAND'" >&2 + exit 1 + ;; +esac + +exit $ret diff --git a/src/grp-boot/kernel-install/kernel-install.xml b/src/grp-boot/kernel-install/kernel-install.xml new file mode 100644 index 0000000000..eb519188a6 --- /dev/null +++ b/src/grp-boot/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/grp-boot/kernel-install/zsh-completion_kernel-install b/src/grp-boot/kernel-install/zsh-completion_kernel-install new file mode 100644 index 0000000000..4fdd3a4ae7 --- /dev/null +++ b/src/grp-boot/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/grp-helperunits/systemd-backlight/Makefile b/src/grp-helperunits/systemd-backlight/Makefile deleted file mode 100644 index cf55ac4db9..0000000000 --- a/src/grp-helperunits/systemd-backlight/Makefile +++ /dev/null @@ -1,43 +0,0 @@ -# -*- 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 # ENABLE_BACKLIGHT - -EXTRA_DIST += \ - units/systemd-backlight@.service.in - -include $(topsrcdir)/build-aux/Makefile.tail.mk diff --git a/src/grp-helperunits/systemd-backlight/backlight.c b/src/grp-helperunits/systemd-backlight/backlight.c deleted file mode 100644 index c7931ac584..0000000000 --- a/src/grp-helperunits/systemd-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 "basic/alloc-util.h" -#include "basic/def.h" -#include "basic/escape.h" -#include "basic/fileio.h" -#include "basic/mkdir.h" -#include "basic/parse-util.h" -#include "basic/proc-cmdline.h" -#include "basic/string-util.h" -#include "shared/udev-util.h" -#include "basic/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/grp-helperunits/systemd-binfmt/Makefile b/src/grp-helperunits/systemd-binfmt/Makefile deleted file mode 100644 index 3e5c1ac270..0000000000 --- a/src/grp-helperunits/systemd-binfmt/Makefile +++ /dev/null @@ -1,56 +0,0 @@ -# -*- 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 # ENABLE_BINFMT - -EXTRA_DIST += \ - units/systemd-binfmt.service.in - -include $(topsrcdir)/build-aux/Makefile.tail.mk diff --git a/src/grp-helperunits/systemd-binfmt/binfmt.c b/src/grp-helperunits/systemd-binfmt/binfmt.c deleted file mode 100644 index 39bbdb5335..0000000000 --- a/src/grp-helperunits/systemd-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 "basic/alloc-util.h" -#include "basic/conf-files.h" -#include "basic/def.h" -#include "basic/fd-util.h" -#include "basic/fileio.h" -#include "basic/log.h" -#include "basic/string-util.h" -#include "basic/strv.h" -#include "basic/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/grp-helperunits/systemd-detect-virt/Makefile b/src/grp-helperunits/systemd-detect-virt/Makefile deleted file mode 100644 index 7b9b9f667b..0000000000 --- a/src/grp-helperunits/systemd-detect-virt/Makefile +++ /dev/null @@ -1,36 +0,0 @@ -# -*- 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 - -bin_PROGRAMS += systemd-detect-virt -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/grp-helperunits/systemd-detect-virt/detect-virt.c b/src/grp-helperunits/systemd-detect-virt/detect-virt.c deleted file mode 100644 index 06cdab5297..0000000000 --- a/src/grp-helperunits/systemd-detect-virt/detect-virt.c +++ /dev/null @@ -1,169 +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 "basic/util.h" -#include "basic/virt.h" - -static bool arg_quiet = false; -static enum { - ANY_VIRTUALIZATION, - ONLY_VM, - ONLY_CONTAINER, - ONLY_CHROOT, -} arg_mode = ANY_VIRTUALIZATION; - -static void help(void) { - printf("%s [OPTIONS...]\n\n" - "Detect execution in a virtualized environment.\n\n" - " -h --help Show this help\n" - " --version Show package version\n" - " -c --container Only detect whether we are run in a container\n" - " -v --vm Only detect whether we are run in a VM\n" - " -r --chroot Detect whether we are run in a chroot() environment\n" - " -q --quiet Don't output anything, just set return value\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 }, - { "container", no_argument, NULL, 'c' }, - { "vm", no_argument, NULL, 'v' }, - { "chroot", no_argument, NULL, 'r' }, - { "quiet", no_argument, NULL, 'q' }, - {} - }; - - int c; - - assert(argc >= 0); - assert(argv); - - while ((c = getopt_long(argc, argv, "hqcvr", options, NULL)) >= 0) - - switch (c) { - - case 'h': - help(); - return 0; - - case ARG_VERSION: - return version(); - - case 'q': - arg_quiet = true; - break; - - case 'c': - arg_mode = ONLY_CONTAINER; - break; - - case 'v': - arg_mode = ONLY_VM; - break; - - case 'r': - arg_mode = ONLY_CHROOT; - 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; - - /* This is mostly intended to be used for scripts which want - * to detect whether we are being run in a virtualized - * environment or not */ - - log_parse_environment(); - log_open(); - - r = parse_argv(argc, argv); - if (r <= 0) - return r < 0 ? EXIT_FAILURE : EXIT_SUCCESS; - - switch (arg_mode) { - - case ONLY_VM: - r = detect_vm(); - if (r < 0) { - log_error_errno(r, "Failed to check for VM: %m"); - return EXIT_FAILURE; - } - - break; - - case ONLY_CONTAINER: - r = detect_container(); - if (r < 0) { - log_error_errno(r, "Failed to check for container: %m"); - return EXIT_FAILURE; - } - - break; - - case ONLY_CHROOT: - r = running_in_chroot(); - if (r < 0) { - log_error_errno(r, "Failed to check for chroot() environment: %m"); - return EXIT_FAILURE; - } - - return r ? EXIT_SUCCESS : EXIT_FAILURE; - - case ANY_VIRTUALIZATION: - default: - r = detect_virtualization(); - if (r < 0) { - log_error_errno(r, "Failed to check for virtualization: %m"); - return EXIT_FAILURE; - } - - break; - } - - if (!arg_quiet) - puts(virtualization_to_string(r)); - - return r != VIRTUALIZATION_NONE ? EXIT_SUCCESS : EXIT_FAILURE; -} diff --git a/src/grp-helperunits/systemd-quotacheck/Makefile b/src/grp-helperunits/systemd-quotacheck/Makefile deleted file mode 100644 index 979ae07c9e..0000000000 --- a/src/grp-helperunits/systemd-quotacheck/Makefile +++ /dev/null @@ -1,48 +0,0 @@ -# -*- 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 # ENABLE_QUOTACHECK - -EXTRA_DIST += \ - units/systemd-quotacheck.service.in - -nodist_systemunit_DATA += \ - units/quotaon.service - -systemd.CPPFLAGS += -DQUOTACHECK=\"$(QUOTACHECK)\" - -include $(topsrcdir)/build-aux/Makefile.tail.mk diff --git a/src/grp-helperunits/systemd-quotacheck/quotacheck.c b/src/grp-helperunits/systemd-quotacheck/quotacheck.c deleted file mode 100644 index 3c2292b0c4..0000000000 --- a/src/grp-helperunits/systemd-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 "basic/proc-cmdline.h" -#include "basic/process-util.h" -#include "basic/signal-util.h" -#include "basic/string-util.h" -#include "basic/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/grp-helperunits/systemd-random-seed/Makefile b/src/grp-helperunits/systemd-random-seed/Makefile deleted file mode 100644 index 65ae60828b..0000000000 --- a/src/grp-helperunits/systemd-random-seed/Makefile +++ /dev/null @@ -1,50 +0,0 @@ -# -*- 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 # ENABLE_RANDOMSEED - -EXTRA_DIST += \ - units/systemd-random-seed.service.in - -systemd.CPPFLAGS += -DRANDOM_SEED_DIR=\"$(localstatedir)/lib/systemd/\" -systemd.CPPFLAGS += -DRANDOM_SEED=\"$(localstatedir)/lib/systemd/random-seed\" - -include $(topsrcdir)/build-aux/Makefile.tail.mk diff --git a/src/grp-helperunits/systemd-random-seed/random-seed.c b/src/grp-helperunits/systemd-random-seed/random-seed.c deleted file mode 100644 index 7fea6069f3..0000000000 --- a/src/grp-helperunits/systemd-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 "basic/alloc-util.h" -#include "basic/fd-util.h" -#include "basic/io-util.h" -#include "basic/log.h" -#include "basic/mkdir.h" -#include "basic/string-util.h" -#include "basic/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/grp-helperunits/systemd-rfkill/Makefile b/src/grp-helperunits/systemd-rfkill/Makefile deleted file mode 100644 index 2b4430b712..0000000000 --- a/src/grp-helperunits/systemd-rfkill/Makefile +++ /dev/null @@ -1,46 +0,0 @@ -# -*- 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 # ENABLE_RFKILL - -EXTRA_DIST += \ - units/systemd-rfkill.service.in - -include $(topsrcdir)/build-aux/Makefile.tail.mk diff --git a/src/grp-helperunits/systemd-rfkill/rfkill.c b/src/grp-helperunits/systemd-rfkill/rfkill.c deleted file mode 100644 index d3dd2ef18d..0000000000 --- a/src/grp-helperunits/systemd-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 - -#include "basic/alloc-util.h" -#include "basic/escape.h" -#include "basic/fd-util.h" -#include "basic/fileio.h" -#include "basic/io-util.h" -#include "basic/mkdir.h" -#include "basic/parse-util.h" -#include "basic/proc-cmdline.h" -#include "basic/string-table.h" -#include "basic/string-util.h" -#include "shared/udev-util.h" -#include "basic/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/grp-helperunits/systemd-sleep/Makefile b/src/grp-helperunits/systemd-sleep/Makefile deleted file mode 100644 index 9ac849f817..0000000000 --- a/src/grp-helperunits/systemd-sleep/Makefile +++ /dev/null @@ -1,35 +0,0 @@ -# -*- 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-sleep -systemd_sleep_SOURCES = \ - src/sleep/sleep.c - -systemd_sleep_LDADD = \ - libshared.la - -systemd.CPPFLAGS += -DSYSTEM_SLEEP_PATH=\"$(systemsleepdir)\" - -include $(topsrcdir)/build-aux/Makefile.tail.mk diff --git a/src/grp-helperunits/systemd-sleep/sleep.c b/src/grp-helperunits/systemd-sleep/sleep.c deleted file mode 100644 index 100066d4c1..0000000000 --- a/src/grp-helperunits/systemd-sleep/sleep.c +++ /dev/null @@ -1,215 +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 "basic/def.h" -#include "basic/fd-util.h" -#include "basic/fileio.h" -#include "basic/log.h" -#include "shared/sleep-config.h" -#include "basic/string-util.h" -#include "basic/strv.h" -#include "basic/util.h" - -static char* arg_verb = NULL; - -static int write_mode(char **modes) { - int r = 0; - char **mode; - - STRV_FOREACH(mode, modes) { - int k; - - k = write_string_file("/sys/power/disk", *mode, 0); - if (k == 0) - return 0; - - log_debug_errno(k, "Failed to write '%s' to /sys/power/disk: %m", - *mode); - if (r == 0) - r = k; - } - - if (r < 0) - log_error_errno(r, "Failed to write mode to /sys/power/disk: %m"); - - return r; -} - -static int write_state(FILE **f, char **states) { - char **state; - int r = 0; - - STRV_FOREACH(state, states) { - int k; - - k = write_string_stream(*f, *state, true); - if (k == 0) - return 0; - log_debug_errno(k, "Failed to write '%s' to /sys/power/state: %m", - *state); - if (r == 0) - r = k; - - fclose(*f); - *f = fopen("/sys/power/state", "we"); - if (!*f) - return log_error_errno(errno, "Failed to open /sys/power/state: %m"); - } - - return r; -} - -static int execute(char **modes, char **states) { - - char *arguments[] = { - NULL, - (char*) "pre", - arg_verb, - NULL - }; - static const char* const dirs[] = {SYSTEM_SLEEP_PATH, NULL}; - - int r; - _cleanup_fclose_ FILE *f = NULL; - - /* This file is opened first, so that if we hit an error, - * we can abort before modifying any state. */ - f = fopen("/sys/power/state", "we"); - if (!f) - return log_error_errno(errno, "Failed to open /sys/power/state: %m"); - - /* Configure the hibernation mode */ - r = write_mode(modes); - if (r < 0) - return r; - - execute_directories(dirs, DEFAULT_TIMEOUT_USEC, arguments); - - log_struct(LOG_INFO, - LOG_MESSAGE_ID(SD_MESSAGE_SLEEP_START), - LOG_MESSAGE("Suspending system..."), - "SLEEP=%s", arg_verb, - NULL); - - r = write_state(&f, states); - if (r < 0) - return r; - - log_struct(LOG_INFO, - LOG_MESSAGE_ID(SD_MESSAGE_SLEEP_STOP), - LOG_MESSAGE("System resumed."), - "SLEEP=%s", arg_verb, - NULL); - - arguments[1] = (char*) "post"; - execute_directories(dirs, DEFAULT_TIMEOUT_USEC, arguments); - - return r; -} - -static void help(void) { - printf("%s COMMAND\n\n" - "Suspend the system, hibernate the system, or both.\n\n" - "Commands:\n" - " -h --help Show this help and exit\n" - " --version Print version string and exit\n" - " suspend Suspend the system\n" - " hibernate Hibernate the system\n" - " hybrid-sleep Both hibernate and suspend the system\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; /* done */ - - case ARG_VERSION: - return version(); - - case '?': - return -EINVAL; - - default: - assert_not_reached("Unhandled option"); - } - - if (argc - optind != 1) { - log_error("Usage: %s COMMAND", - program_invocation_short_name); - return -EINVAL; - } - - arg_verb = argv[optind]; - - if (!streq(arg_verb, "suspend") && - !streq(arg_verb, "hibernate") && - !streq(arg_verb, "hybrid-sleep")) { - log_error("Unknown command '%s'.", arg_verb); - return -EINVAL; - } - - return 1 /* work to do */; -} - -int main(int argc, char *argv[]) { - _cleanup_strv_free_ char **modes = NULL, **states = NULL; - int r; - - log_set_target(LOG_TARGET_AUTO); - log_parse_environment(); - log_open(); - - r = parse_argv(argc, argv); - if (r <= 0) - goto finish; - - r = parse_sleep_config(arg_verb, &modes, &states); - if (r < 0) - goto finish; - - r = execute(modes, states); - -finish: - return r < 0 ? EXIT_FAILURE : EXIT_SUCCESS; -} diff --git a/src/grp-helperunits/systemd-user-sessions/Makefile b/src/grp-helperunits/systemd-user-sessions/Makefile deleted file mode 100644 index 66fa3b7059..0000000000 --- a/src/grp-helperunits/systemd-user-sessions/Makefile +++ /dev/null @@ -1,48 +0,0 @@ -# -*- 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_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 # HAVE_PAM - -EXTRA_DIST += \ - units/systemd-user-sessions.service.in - -include $(topsrcdir)/build-aux/Makefile.tail.mk diff --git a/src/grp-helperunits/systemd-user-sessions/user-sessions.c b/src/grp-helperunits/systemd-user-sessions/user-sessions.c deleted file mode 100644 index 4cec29c39e..0000000000 --- a/src/grp-helperunits/systemd-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 "basic/fileio.h" -#include "basic/fileio-label.h" -#include "basic/log.h" -#include "basic/selinux-util.h" -#include "basic/string-util.h" -#include "basic/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(); - - 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/grp-helperunits/systemd-vconsole-setup/.gitignore b/src/grp-helperunits/systemd-vconsole-setup/.gitignore deleted file mode 100644 index 82741b2fb3..0000000000 --- a/src/grp-helperunits/systemd-vconsole-setup/.gitignore +++ /dev/null @@ -1 +0,0 @@ -/90-vconsole.rules diff --git a/src/grp-helperunits/systemd-vconsole-setup/90-vconsole.rules.in b/src/grp-helperunits/systemd-vconsole-setup/90-vconsole.rules.in deleted file mode 100644 index 35b9ad5151..0000000000 --- a/src/grp-helperunits/systemd-vconsole-setup/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/grp-helperunits/systemd-vconsole-setup/Makefile b/src/grp-helperunits/systemd-vconsole-setup/Makefile deleted file mode 100644 index 99b369967e..0000000000 --- a/src/grp-helperunits/systemd-vconsole-setup/Makefile +++ /dev/null @@ -1,50 +0,0 @@ -# -*- 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 # ENABLE_VCONSOLE - -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/grp-helperunits/systemd-vconsole-setup/vconsole-setup.c b/src/grp-helperunits/systemd-vconsole-setup/vconsole-setup.c deleted file mode 100644 index dd05231982..0000000000 --- a/src/grp-helperunits/systemd-vconsole-setup/vconsole-setup.c +++ /dev/null @@ -1,332 +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 "basic/alloc-util.h" -#include "basic/fd-util.h" -#include "basic/fileio.h" -#include "basic/io-util.h" -#include "basic/locale-util.h" -#include "basic/log.h" -#include "basic/process-util.h" -#include "basic/signal-util.h" -#include "basic/stdio-util.h" -#include "basic/string-util.h" -#include "basic/terminal-util.h" -#include "basic/util.h" -#include "basic/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; - _cleanup_free_ struct unipair* unipairs = NULL; - int i, r; - - unipairs = new(struct unipair, USHRT_MAX); - if (!unipairs) { - log_oom(); - return; - } - - /* get active, and 16 bit mask of used VT numbers */ - r = ioctl(fd, VT_GETSTATE, &vcs); - if (r < 0) { - log_debug_errno(errno, "VT_GETSTATE failed, ignoring: %m"); - 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/grp-hostname/hostnamectl.c b/src/grp-hostname/hostnamectl.c index 30f0f06d89..8a24813934 100644 --- a/src/grp-hostname/hostnamectl.c +++ b/src/grp-hostname/hostnamectl.c @@ -28,11 +28,11 @@ #include "basic/alloc-util.h" #include "basic/architecture.h" -#include "bus-error.h" -#include "shared/bus-util.h" #include "basic/hostname-util.h" -#include "shared/spawn-polkit-agent.h" #include "basic/util.h" +#include "sd-bus/bus-error.h" +#include "shared/bus-util.h" +#include "shared/spawn-polkit-agent.h" static bool arg_ask_password = true; static BusTransport arg_transport = BUS_TRANSPORT_LOCAL; diff --git a/src/grp-import/.gitignore b/src/grp-import/.gitignore deleted file mode 100644 index 01106e2e68..0000000000 --- a/src/grp-import/.gitignore +++ /dev/null @@ -1 +0,0 @@ -/org.freedesktop.import1.policy diff --git a/src/grp-import/Makefile b/src/grp-import/Makefile deleted file mode 100644 index 46be93c42f..0000000000 --- a/src/grp-import/Makefile +++ /dev/null @@ -1,89 +0,0 @@ -# -*- 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),) - -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 # HAVE_GCRYPT -endif # HAVE_BZIP2 -endif # HAVE_ZLIB -endif # HAVE_XZ -endif # HAVE_LIBCURL - -endif # ENABLE_IMPORTD - -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/grp-import/curl-util.c b/src/grp-import/curl-util.c deleted file mode 100644 index d6570a3656..0000000000 --- a/src/grp-import/curl-util.c +++ /dev/null @@ -1,448 +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 "basic/alloc-util.h" -#include "curl-util.h" -#include "basic/fd-util.h" -#include "basic/string-util.h" - -static void curl_glue_check_finished(CurlGlue *g) { - CURLMsg *msg; - int k = 0; - - assert(g); - - msg = curl_multi_info_read(g->curl, &k); - if (!msg) - return; - - if (msg->msg != CURLMSG_DONE) - return; - - if (g->on_finished) - g->on_finished(g, msg->easy_handle, msg->data.result); -} - -static int curl_glue_on_io(sd_event_source *s, int fd, uint32_t revents, void *userdata) { - CurlGlue *g = userdata; - int action, k = 0, translated_fd; - - assert(s); - assert(g); - - translated_fd = PTR_TO_FD(hashmap_get(g->translate_fds, FD_TO_PTR(fd))); - - if ((revents & (EPOLLIN|EPOLLOUT)) == (EPOLLIN|EPOLLOUT)) - action = CURL_POLL_INOUT; - else if (revents & EPOLLIN) - action = CURL_POLL_IN; - else if (revents & EPOLLOUT) - action = CURL_POLL_OUT; - else - action = 0; - - if (curl_multi_socket_action(g->curl, translated_fd, action, &k) < 0) { - log_debug("Failed to propagate IO event."); - return -EINVAL; - } - - curl_glue_check_finished(g); - return 0; -} - -static int curl_glue_socket_callback(CURLM *curl, curl_socket_t s, int action, void *userdata, void *socketp) { - sd_event_source *io; - CurlGlue *g = userdata; - uint32_t events = 0; - int r; - - assert(curl); - assert(g); - - io = hashmap_get(g->ios, FD_TO_PTR(s)); - - if (action == CURL_POLL_REMOVE) { - if (io) { - int fd; - - fd = sd_event_source_get_io_fd(io); - assert(fd >= 0); - - sd_event_source_set_enabled(io, SD_EVENT_OFF); - sd_event_source_unref(io); - - hashmap_remove(g->ios, FD_TO_PTR(s)); - hashmap_remove(g->translate_fds, FD_TO_PTR(fd)); - - safe_close(fd); - } - - return 0; - } - - r = hashmap_ensure_allocated(&g->ios, &trivial_hash_ops); - if (r < 0) { - log_oom(); - return -1; - } - - r = hashmap_ensure_allocated(&g->translate_fds, &trivial_hash_ops); - if (r < 0) { - log_oom(); - return -1; - } - - if (action == CURL_POLL_IN) - events = EPOLLIN; - else if (action == CURL_POLL_OUT) - events = EPOLLOUT; - else if (action == CURL_POLL_INOUT) - events = EPOLLIN|EPOLLOUT; - - if (io) { - if (sd_event_source_set_io_events(io, events) < 0) - return -1; - - if (sd_event_source_set_enabled(io, SD_EVENT_ON) < 0) - return -1; - } else { - _cleanup_close_ int fd = -1; - - /* When curl needs to remove an fd from us it closes - * the fd first, and only then calls into us. This is - * nasty, since we cannot pass the fd on to epoll() - * anymore. Hence, duplicate the fds here, and keep a - * copy for epoll which we control after use. */ - - fd = fcntl(s, F_DUPFD_CLOEXEC, 3); - if (fd < 0) - return -1; - - if (sd_event_add_io(g->event, &io, fd, events, curl_glue_on_io, g) < 0) - return -1; - - (void) sd_event_source_set_description(io, "curl-io"); - - r = hashmap_put(g->ios, FD_TO_PTR(s), io); - if (r < 0) { - log_oom(); - sd_event_source_unref(io); - return -1; - } - - r = hashmap_put(g->translate_fds, FD_TO_PTR(fd), FD_TO_PTR(s)); - if (r < 0) { - log_oom(); - hashmap_remove(g->ios, FD_TO_PTR(s)); - sd_event_source_unref(io); - return -1; - } - - fd = -1; - } - - return 0; -} - -static int curl_glue_on_timer(sd_event_source *s, uint64_t usec, void *userdata) { - CurlGlue *g = userdata; - int k = 0; - - assert(s); - assert(g); - - if (curl_multi_socket_action(g->curl, CURL_SOCKET_TIMEOUT, 0, &k) != CURLM_OK) { - log_debug("Failed to propagate timeout."); - return -EINVAL; - } - - curl_glue_check_finished(g); - return 0; -} - -static int curl_glue_timer_callback(CURLM *curl, long timeout_ms, void *userdata) { - CurlGlue *g = userdata; - usec_t usec; - - assert(curl); - assert(g); - - if (timeout_ms < 0) { - if (g->timer) { - if (sd_event_source_set_enabled(g->timer, SD_EVENT_OFF) < 0) - return -1; - } - - return 0; - } - - usec = now(clock_boottime_or_monotonic()) + (usec_t) timeout_ms * USEC_PER_MSEC + USEC_PER_MSEC - 1; - - if (g->timer) { - if (sd_event_source_set_time(g->timer, usec) < 0) - return -1; - - if (sd_event_source_set_enabled(g->timer, SD_EVENT_ONESHOT) < 0) - return -1; - } else { - if (sd_event_add_time(g->event, &g->timer, clock_boottime_or_monotonic(), usec, 0, curl_glue_on_timer, g) < 0) - return -1; - - (void) sd_event_source_set_description(g->timer, "curl-timer"); - } - - return 0; -} - -CurlGlue *curl_glue_unref(CurlGlue *g) { - sd_event_source *io; - - if (!g) - return NULL; - - if (g->curl) - curl_multi_cleanup(g->curl); - - while ((io = hashmap_steal_first(g->ios))) { - int fd; - - fd = sd_event_source_get_io_fd(io); - assert(fd >= 0); - - hashmap_remove(g->translate_fds, FD_TO_PTR(fd)); - - safe_close(fd); - sd_event_source_unref(io); - } - - hashmap_free(g->ios); - - sd_event_source_unref(g->timer); - sd_event_unref(g->event); - free(g); - - return NULL; -} - -int curl_glue_new(CurlGlue **glue, sd_event *event) { - _cleanup_(curl_glue_unrefp) CurlGlue *g = NULL; - int r; - - g = new0(CurlGlue, 1); - if (!g) - return -ENOMEM; - - if (event) - g->event = sd_event_ref(event); - else { - r = sd_event_default(&g->event); - if (r < 0) - return r; - } - - g->curl = curl_multi_init(); - if (!g->curl) - return -ENOMEM; - - if (curl_multi_setopt(g->curl, CURLMOPT_SOCKETDATA, g) != CURLM_OK) - return -EINVAL; - - if (curl_multi_setopt(g->curl, CURLMOPT_SOCKETFUNCTION, curl_glue_socket_callback) != CURLM_OK) - return -EINVAL; - - if (curl_multi_setopt(g->curl, CURLMOPT_TIMERDATA, g) != CURLM_OK) - return -EINVAL; - - if (curl_multi_setopt(g->curl, CURLMOPT_TIMERFUNCTION, curl_glue_timer_callback) != CURLM_OK) - return -EINVAL; - - *glue = g; - g = NULL; - - return 0; -} - -int curl_glue_make(CURL **ret, const char *url, void *userdata) { - const char *useragent; - CURL *c; - int r; - - assert(ret); - assert(url); - - c = curl_easy_init(); - if (!c) - return -ENOMEM; - - /* curl_easy_setopt(c, CURLOPT_VERBOSE, 1L); */ - - if (curl_easy_setopt(c, CURLOPT_URL, url) != CURLE_OK) { - r = -EIO; - goto fail; - } - - if (curl_easy_setopt(c, CURLOPT_PRIVATE, userdata) != CURLE_OK) { - r = -EIO; - goto fail; - } - - useragent = strjoina(program_invocation_short_name, "/" PACKAGE_VERSION); - if (curl_easy_setopt(c, CURLOPT_USERAGENT, useragent) != CURLE_OK) { - r = -EIO; - goto fail; - } - - if (curl_easy_setopt(c, CURLOPT_FOLLOWLOCATION, 1L) != CURLE_OK) { - r = -EIO; - goto fail; - } - - *ret = c; - return 0; - -fail: - curl_easy_cleanup(c); - return r; -} - -int curl_glue_add(CurlGlue *g, CURL *c) { - assert(g); - assert(c); - - if (curl_multi_add_handle(g->curl, c) != CURLM_OK) - return -EIO; - - return 0; -} - -void curl_glue_remove_and_free(CurlGlue *g, CURL *c) { - assert(g); - - if (!c) - return; - - if (g->curl) - curl_multi_remove_handle(g->curl, c); - - curl_easy_cleanup(c); -} - -struct curl_slist *curl_slist_new(const char *first, ...) { - struct curl_slist *l; - va_list ap; - - if (!first) - return NULL; - - l = curl_slist_append(NULL, first); - if (!l) - return NULL; - - va_start(ap, first); - - for (;;) { - struct curl_slist *n; - const char *i; - - i = va_arg(ap, const char*); - if (!i) - break; - - n = curl_slist_append(l, i); - if (!n) { - va_end(ap); - curl_slist_free_all(l); - return NULL; - } - - l = n; - } - - va_end(ap); - return l; -} - -int curl_header_strdup(const void *contents, size_t sz, const char *field, char **value) { - const char *p = contents; - size_t l; - char *s; - - l = strlen(field); - if (sz < l) - return 0; - - if (memcmp(p, field, l) != 0) - return 0; - - p += l; - sz -= l; - - if (memchr(p, 0, sz)) - return 0; - - /* Skip over preceeding whitespace */ - while (sz > 0 && strchr(WHITESPACE, p[0])) { - p++; - sz--; - } - - /* Truncate trailing whitespace*/ - while (sz > 0 && strchr(WHITESPACE, p[sz-1])) - sz--; - - s = strndup(p, sz); - if (!s) - return -ENOMEM; - - *value = s; - return 1; -} - -int curl_parse_http_time(const char *t, usec_t *ret) { - const char *e; - locale_t loc; - struct tm tm; - time_t v; - - assert(t); - assert(ret); - - loc = newlocale(LC_TIME_MASK, "C", (locale_t) 0); - if (loc == (locale_t) 0) - return -errno; - - /* RFC822 */ - e = strptime_l(t, "%a, %d %b %Y %H:%M:%S %Z", &tm, loc); - if (!e || *e != 0) - /* RFC 850 */ - e = strptime_l(t, "%A, %d-%b-%y %H:%M:%S %Z", &tm, loc); - if (!e || *e != 0) - /* ANSI C */ - e = strptime_l(t, "%a %b %d %H:%M:%S %Y", &tm, loc); - freelocale(loc); - if (!e || *e != 0) - return -EINVAL; - - v = timegm(&tm); - if (v == (time_t) -1) - return -EINVAL; - - *ret = (usec_t) v * USEC_PER_SEC; - return 0; -} diff --git a/src/grp-import/curl-util.h b/src/grp-import/curl-util.h deleted file mode 100644 index 296b35d09c..0000000000 --- a/src/grp-import/curl-util.h +++ /dev/null @@ -1,56 +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 "basic/hashmap.h" - -typedef struct CurlGlue CurlGlue; - -struct CurlGlue { - sd_event *event; - CURLM *curl; - sd_event_source *timer; - Hashmap *ios; - Hashmap *translate_fds; - - void (*on_finished)(CurlGlue *g, CURL *curl, CURLcode code); - void *userdata; -}; - -int curl_glue_new(CurlGlue **glue, sd_event *event); -CurlGlue* curl_glue_unref(CurlGlue *glue); - -DEFINE_TRIVIAL_CLEANUP_FUNC(CurlGlue*, curl_glue_unref); - -int curl_glue_make(CURL **ret, const char *url, void *userdata); -int curl_glue_add(CurlGlue *g, CURL *c); -void curl_glue_remove_and_free(CurlGlue *g, CURL *c); - -struct curl_slist *curl_slist_new(const char *first, ...) _sentinel_; -int curl_header_strdup(const void *contents, size_t sz, const char *field, char **value); -int curl_parse_http_time(const char *t, usec_t *ret); - -DEFINE_TRIVIAL_CLEANUP_FUNC(CURL*, curl_easy_cleanup); -DEFINE_TRIVIAL_CLEANUP_FUNC(struct curl_slist*, curl_slist_free_all); diff --git a/src/grp-import/org.freedesktop.import1.conf b/src/grp-import/org.freedesktop.import1.conf deleted file mode 100644 index ed2539a03b..0000000000 --- a/src/grp-import/org.freedesktop.import1.conf +++ /dev/null @@ -1,62 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/src/grp-import/org.freedesktop.import1.policy.in b/src/grp-import/org.freedesktop.import1.policy.in deleted file mode 100644 index 85924ed743..0000000000 --- a/src/grp-import/org.freedesktop.import1.policy.in +++ /dev/null @@ -1,49 +0,0 @@ - - - - - - - - The systemd Project - http://www.freedesktop.org/wiki/Software/systemd - - - <_description>Import a VM or container image - <_message>Authentication is required to import a VM or container image - - auth_admin - auth_admin - auth_admin_keep - - - - - <_description>Export a VM or container image - <_message>Authentication is required to export a VM or container image - - auth_admin - auth_admin - auth_admin_keep - - - - - <_description>Download a VM or container image - <_message>Authentication is required to download a VM or container image - - auth_admin - auth_admin - auth_admin_keep - - - - diff --git a/src/grp-import/org.freedesktop.import1.service b/src/grp-import/org.freedesktop.import1.service deleted file mode 100644 index 8fc4c47881..0000000000 --- a/src/grp-import/org.freedesktop.import1.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.import1 -Exec=/bin/false -User=root -SystemdService=dbus-org.freedesktop.import1.service diff --git a/src/grp-import/qcow2-util.c b/src/grp-import/qcow2-util.c deleted file mode 100644 index fa272088d3..0000000000 --- a/src/grp-import/qcow2-util.c +++ /dev/null @@ -1,352 +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 "basic/alloc-util.h" -#include "basic/btrfs-util.h" -#include "qcow2-util.h" -#include "basic/sparse-endian.h" -#include "basic/util.h" - -#define QCOW2_MAGIC 0x514649fb - -#define QCOW2_COPIED (1ULL << 63) -#define QCOW2_COMPRESSED (1ULL << 62) -#define QCOW2_ZERO (1ULL << 0) - -typedef struct _packed_ Header { - be32_t magic; - be32_t version; - - be64_t backing_file_offset; - be32_t backing_file_size; - - be32_t cluster_bits; - be64_t size; - be32_t crypt_method; - - be32_t l1_size; - be64_t l1_table_offset; - - be64_t refcount_table_offset; - be32_t refcount_table_clusters; - - be32_t nb_snapshots; - be64_t snapshots_offset; - - /* The remainder is only present on QCOW3 */ - be64_t incompatible_features; - be64_t compatible_features; - be64_t autoclear_features; - - be32_t refcount_order; - be32_t header_length; -} Header; - -#define HEADER_MAGIC(header) be32toh((header)->magic) -#define HEADER_VERSION(header) be32toh((header)->version) -#define HEADER_CLUSTER_BITS(header) be32toh((header)->cluster_bits) -#define HEADER_CLUSTER_SIZE(header) (1ULL << HEADER_CLUSTER_BITS(header)) -#define HEADER_L2_BITS(header) (HEADER_CLUSTER_BITS(header) - 3) -#define HEADER_SIZE(header) be64toh((header)->size) -#define HEADER_CRYPT_METHOD(header) be32toh((header)->crypt_method) -#define HEADER_L1_SIZE(header) be32toh((header)->l1_size) -#define HEADER_L2_SIZE(header) (HEADER_CLUSTER_SIZE(header)/sizeof(uint64_t)) -#define HEADER_L1_TABLE_OFFSET(header) be64toh((header)->l1_table_offset) - -static uint32_t HEADER_HEADER_LENGTH(const Header *h) { - if (HEADER_VERSION(h) < 3) - return offsetof(Header, incompatible_features); - - return be32toh(h->header_length); -} - -static int copy_cluster( - int sfd, uint64_t soffset, - int dfd, uint64_t doffset, - uint64_t cluster_size, - void *buffer) { - - ssize_t l; - int r; - - r = btrfs_clone_range(sfd, soffset, dfd, doffset, cluster_size); - if (r >= 0) - return r; - - l = pread(sfd, buffer, cluster_size, soffset); - if (l < 0) - return -errno; - if ((uint64_t) l != cluster_size) - return -EIO; - - l = pwrite(dfd, buffer, cluster_size, doffset); - if (l < 0) - return -errno; - if ((uint64_t) l != cluster_size) - return -EIO; - - return 0; -} - -static int decompress_cluster( - int sfd, uint64_t soffset, - int dfd, uint64_t doffset, - uint64_t compressed_size, - uint64_t cluster_size, - void *buffer1, - void *buffer2) { - - _cleanup_free_ void *large_buffer = NULL; - z_stream s = {}; - uint64_t sz; - ssize_t l; - int r; - - if (compressed_size > cluster_size) { - /* The usual cluster buffer doesn't suffice, let's - * allocate a larger one, temporarily */ - - large_buffer = malloc(compressed_size); - if (!large_buffer) - return -ENOMEM; - - buffer1 = large_buffer; - } - - l = pread(sfd, buffer1, compressed_size, soffset); - if (l < 0) - return -errno; - if ((uint64_t) l != compressed_size) - return -EIO; - - s.next_in = buffer1; - s.avail_in = compressed_size; - s.next_out = buffer2; - s.avail_out = cluster_size; - - r = inflateInit2(&s, -12); - if (r != Z_OK) - return -EIO; - - r = inflate(&s, Z_FINISH); - sz = (uint8_t*) s.next_out - (uint8_t*) buffer2; - inflateEnd(&s); - if (r != Z_STREAM_END || sz != cluster_size) - return -EIO; - - l = pwrite(dfd, buffer2, cluster_size, doffset); - if (l < 0) - return -errno; - if ((uint64_t) l != cluster_size) - return -EIO; - - return 0; -} - -static int normalize_offset( - const Header *header, - uint64_t p, - uint64_t *ret, - bool *compressed, - uint64_t *compressed_size) { - - uint64_t q; - - q = be64toh(p); - - if (q & QCOW2_COMPRESSED) { - uint64_t sz, csize_shift, csize_mask; - - if (!compressed) - return -EOPNOTSUPP; - - csize_shift = 64 - 2 - (HEADER_CLUSTER_BITS(header) - 8); - csize_mask = (1ULL << (HEADER_CLUSTER_BITS(header) - 8)) - 1; - sz = (((q >> csize_shift) & csize_mask) + 1) * 512 - (q & 511); - q &= ((1ULL << csize_shift) - 1); - - if (compressed_size) - *compressed_size = sz; - - *compressed = true; - - } else { - if (compressed) { - *compressed = false; - *compressed_size = 0; - } - - if (q & QCOW2_ZERO) { - /* We make no distinction between zero blocks and holes */ - *ret = 0; - return 0; - } - - q &= ~QCOW2_COPIED; - } - - *ret = q; - return q > 0; /* returns positive if not a hole */ -} - -static int verify_header(const Header *header) { - assert(header); - - if (HEADER_MAGIC(header) != QCOW2_MAGIC) - return -EBADMSG; - - if (HEADER_VERSION(header) != 2 && - HEADER_VERSION(header) != 3) - return -EOPNOTSUPP; - - if (HEADER_CRYPT_METHOD(header) != 0) - return -EOPNOTSUPP; - - if (HEADER_CLUSTER_BITS(header) < 9) /* 512K */ - return -EBADMSG; - - if (HEADER_CLUSTER_BITS(header) > 21) /* 2MB */ - return -EBADMSG; - - if (HEADER_SIZE(header) % HEADER_CLUSTER_SIZE(header) != 0) - return -EBADMSG; - - if (HEADER_L1_SIZE(header) > 32*1024*1024) /* 32MB */ - return -EBADMSG; - - if (HEADER_VERSION(header) == 3) { - - if (header->incompatible_features != 0) - return -EOPNOTSUPP; - - if (HEADER_HEADER_LENGTH(header) < sizeof(Header)) - return -EBADMSG; - } - - return 0; -} - -int qcow2_convert(int qcow2_fd, int raw_fd) { - _cleanup_free_ void *buffer1 = NULL, *buffer2 = NULL; - _cleanup_free_ be64_t *l1_table = NULL, *l2_table = NULL; - uint64_t sz, i; - Header header; - ssize_t l; - int r; - - l = pread(qcow2_fd, &header, sizeof(header), 0); - if (l < 0) - return -errno; - if (l != sizeof(header)) - return -EIO; - - r = verify_header(&header); - if (r < 0) - return r; - - l1_table = new(be64_t, HEADER_L1_SIZE(&header)); - if (!l1_table) - return -ENOMEM; - - l2_table = malloc(HEADER_CLUSTER_SIZE(&header)); - if (!l2_table) - return -ENOMEM; - - buffer1 = malloc(HEADER_CLUSTER_SIZE(&header)); - if (!buffer1) - return -ENOMEM; - - buffer2 = malloc(HEADER_CLUSTER_SIZE(&header)); - if (!buffer2) - return -ENOMEM; - - /* Empty the file if it exists, we rely on zero bits */ - if (ftruncate(raw_fd, 0) < 0) - return -errno; - - if (ftruncate(raw_fd, HEADER_SIZE(&header)) < 0) - return -errno; - - sz = sizeof(uint64_t) * HEADER_L1_SIZE(&header); - l = pread(qcow2_fd, l1_table, sz, HEADER_L1_TABLE_OFFSET(&header)); - if (l < 0) - return -errno; - if ((uint64_t) l != sz) - return -EIO; - - for (i = 0; i < HEADER_L1_SIZE(&header); i ++) { - uint64_t l2_begin, j; - - r = normalize_offset(&header, l1_table[i], &l2_begin, NULL, NULL); - if (r < 0) - return r; - if (r == 0) - continue; - - l = pread(qcow2_fd, l2_table, HEADER_CLUSTER_SIZE(&header), l2_begin); - if (l < 0) - return -errno; - if ((uint64_t) l != HEADER_CLUSTER_SIZE(&header)) - return -EIO; - - for (j = 0; j < HEADER_L2_SIZE(&header); j++) { - uint64_t data_begin, p, compressed_size; - bool compressed; - - p = ((i << HEADER_L2_BITS(&header)) + j) << HEADER_CLUSTER_BITS(&header); - - r = normalize_offset(&header, l2_table[j], &data_begin, &compressed, &compressed_size); - if (r < 0) - return r; - if (r == 0) - continue; - - if (compressed) - r = decompress_cluster( - qcow2_fd, data_begin, - raw_fd, p, - compressed_size, HEADER_CLUSTER_SIZE(&header), - buffer1, buffer2); - else - r = copy_cluster( - qcow2_fd, data_begin, - raw_fd, p, - HEADER_CLUSTER_SIZE(&header), buffer1); - if (r < 0) - return r; - } - } - - return 0; -} - -int qcow2_detect(int fd) { - be32_t id; - ssize_t l; - - l = pread(fd, &id, sizeof(id), 0); - if (l < 0) - return -errno; - if (l != sizeof(id)) - return -EIO; - - return htobe32(QCOW2_MAGIC) == id; -} diff --git a/src/grp-import/qcow2-util.h b/src/grp-import/qcow2-util.h deleted file mode 100644 index 6dddac8cdf..0000000000 --- a/src/grp-import/qcow2-util.h +++ /dev/null @@ -1,23 +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 qcow2_detect(int fd); -int qcow2_convert(int qcow2_fd, int raw_fd); diff --git a/src/grp-import/systemd-export/Makefile b/src/grp-import/systemd-export/Makefile deleted file mode 100644 index d0e8781faf..0000000000 --- a/src/grp-import/systemd-export/Makefile +++ /dev/null @@ -1,51 +0,0 @@ -# -*- 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-export - -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) \ - $(BZIP2_CFLAGS) - -systemd_export_LDADD = \ - libshared.la \ - $(XZ_LIBS) \ - $(ZLIB_LIBS) \ - $(BZIP2_LIBS) - -include $(topsrcdir)/build-aux/Makefile.tail.mk diff --git a/src/grp-import/systemd-export/export-raw.c b/src/grp-import/systemd-export/export-raw.c deleted file mode 100644 index 1e3f6e034e..0000000000 --- a/src/grp-import/systemd-export/export-raw.c +++ /dev/null @@ -1,352 +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 - -/* 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 - -#include "basic/alloc-util.h" -#include "basic/btrfs-util.h" -#include "basic/copy.h" -#include "export-raw.h" -#include "basic/fd-util.h" -#include "basic/fileio.h" -#include "import-common.h" -#include "basic/ratelimit.h" -#include "basic/string-util.h" -#include "basic/util.h" - -#define COPY_BUFFER_SIZE (16*1024) - -struct RawExport { - sd_event *event; - - RawExportFinished on_finished; - void *userdata; - - char *path; - - int input_fd; - int output_fd; - - ImportCompress compress; - - sd_event_source *output_event_source; - - void *buffer; - size_t buffer_size; - size_t buffer_allocated; - - uint64_t written_compressed; - uint64_t written_uncompressed; - - unsigned last_percent; - RateLimit progress_rate_limit; - - struct stat st; - - bool eof; - bool tried_reflink; - bool tried_sendfile; -}; - -RawExport *raw_export_unref(RawExport *e) { - if (!e) - return NULL; - - sd_event_source_unref(e->output_event_source); - - import_compress_free(&e->compress); - - sd_event_unref(e->event); - - safe_close(e->input_fd); - - free(e->buffer); - free(e->path); - free(e); - - return NULL; -} - -int raw_export_new( - RawExport **ret, - sd_event *event, - RawExportFinished on_finished, - void *userdata) { - - _cleanup_(raw_export_unrefp) RawExport *e = NULL; - int r; - - assert(ret); - - e = new0(RawExport, 1); - if (!e) - return -ENOMEM; - - e->output_fd = e->input_fd = -1; - e->on_finished = on_finished; - e->userdata = userdata; - - RATELIMIT_INIT(e->progress_rate_limit, 100 * USEC_PER_MSEC, 1); - e->last_percent = (unsigned) -1; - - if (event) - e->event = sd_event_ref(event); - else { - r = sd_event_default(&e->event); - if (r < 0) - return r; - } - - *ret = e; - e = NULL; - - return 0; -} - -static void raw_export_report_progress(RawExport *e) { - unsigned percent; - assert(e); - - if (e->written_uncompressed >= (uint64_t) e->st.st_size) - percent = 100; - else - percent = (unsigned) ((e->written_uncompressed * UINT64_C(100)) / (uint64_t) e->st.st_size); - - if (percent == e->last_percent) - return; - - if (!ratelimit_test(&e->progress_rate_limit)) - return; - - sd_notifyf(false, "X_IMPORT_PROGRESS=%u", percent); - log_info("Exported %u%%.", percent); - - e->last_percent = percent; -} - -static int raw_export_process(RawExport *e) { - ssize_t l; - int r; - - assert(e); - - if (!e->tried_reflink && e->compress.type == IMPORT_COMPRESS_UNCOMPRESSED) { - - /* If we shall take an uncompressed snapshot we can - * reflink source to destination directly. Let's see - * if this works. */ - - r = btrfs_reflink(e->input_fd, e->output_fd); - if (r >= 0) { - r = 0; - goto finish; - } - - e->tried_reflink = true; - } - - if (!e->tried_sendfile && e->compress.type == IMPORT_COMPRESS_UNCOMPRESSED) { - - l = sendfile(e->output_fd, e->input_fd, NULL, COPY_BUFFER_SIZE); - if (l < 0) { - if (errno == EAGAIN) - return 0; - - e->tried_sendfile = true; - } else if (l == 0) { - r = 0; - goto finish; - } else { - e->written_uncompressed += l; - e->written_compressed += l; - - raw_export_report_progress(e); - - return 0; - } - } - - while (e->buffer_size <= 0) { - uint8_t input[COPY_BUFFER_SIZE]; - - if (e->eof) { - r = 0; - goto finish; - } - - l = read(e->input_fd, input, sizeof(input)); - if (l < 0) { - r = log_error_errno(errno, "Failed to read raw file: %m"); - goto finish; - } - - if (l == 0) { - e->eof = true; - r = import_compress_finish(&e->compress, &e->buffer, &e->buffer_size, &e->buffer_allocated); - } else { - e->written_uncompressed += l; - r = import_compress(&e->compress, input, l, &e->buffer, &e->buffer_size, &e->buffer_allocated); - } - if (r < 0) { - r = log_error_errno(r, "Failed to encode: %m"); - goto finish; - } - } - - l = write(e->output_fd, e->buffer, e->buffer_size); - if (l < 0) { - if (errno == EAGAIN) - return 0; - - r = log_error_errno(errno, "Failed to write output file: %m"); - goto finish; - } - - assert((size_t) l <= e->buffer_size); - memmove(e->buffer, (uint8_t*) e->buffer + l, e->buffer_size - l); - e->buffer_size -= l; - e->written_compressed += l; - - raw_export_report_progress(e); - - return 0; - -finish: - if (r >= 0) { - (void) copy_times(e->input_fd, e->output_fd); - (void) copy_xattr(e->input_fd, e->output_fd); - } - - if (e->on_finished) - e->on_finished(e, r, e->userdata); - else - sd_event_exit(e->event, r); - - return 0; -} - -static int raw_export_on_output(sd_event_source *s, int fd, uint32_t revents, void *userdata) { - RawExport *i = userdata; - - return raw_export_process(i); -} - -static int raw_export_on_defer(sd_event_source *s, void *userdata) { - RawExport *i = userdata; - - return raw_export_process(i); -} - -static int reflink_snapshot(int fd, const char *path) { - char *p, *d; - int new_fd, r; - - p = strdupa(path); - d = dirname(p); - - new_fd = open(d, O_TMPFILE|O_CLOEXEC|O_NOCTTY|O_RDWR, 0600); - if (new_fd < 0) { - _cleanup_free_ char *t = NULL; - - r = tempfn_random(path, NULL, &t); - if (r < 0) - return r; - - new_fd = open(t, O_CLOEXEC|O_CREAT|O_NOCTTY|O_RDWR, 0600); - if (new_fd < 0) - return -errno; - - (void) unlink(t); - } - - r = btrfs_reflink(fd, new_fd); - if (r < 0) { - safe_close(new_fd); - return r; - } - - return new_fd; -} - -int raw_export_start(RawExport *e, const char *path, int fd, ImportCompressType compress) { - _cleanup_close_ int sfd = -1, tfd = -1; - int r; - - assert(e); - assert(path); - assert(fd >= 0); - assert(compress < _IMPORT_COMPRESS_TYPE_MAX); - assert(compress != IMPORT_COMPRESS_UNKNOWN); - - if (e->output_fd >= 0) - return -EBUSY; - - r = fd_nonblock(fd, true); - if (r < 0) - return r; - - r = free_and_strdup(&e->path, path); - if (r < 0) - return r; - - sfd = open(path, O_RDONLY|O_CLOEXEC|O_NOCTTY); - if (sfd < 0) - return -errno; - - if (fstat(sfd, &e->st) < 0) - return -errno; - if (!S_ISREG(e->st.st_mode)) - return -ENOTTY; - - /* Try to take a reflink snapshot of the file, if we can t make the export atomic */ - tfd = reflink_snapshot(sfd, path); - if (tfd >= 0) { - e->input_fd = tfd; - tfd = -1; - } else { - e->input_fd = sfd; - sfd = -1; - } - - r = import_compress_init(&e->compress, compress); - if (r < 0) - return r; - - r = sd_event_add_io(e->event, &e->output_event_source, fd, EPOLLOUT, raw_export_on_output, e); - if (r == -EPERM) { - r = sd_event_add_defer(e->event, &e->output_event_source, raw_export_on_defer, e); - if (r < 0) - return r; - - r = sd_event_source_set_enabled(e->output_event_source, SD_EVENT_ON); - } - if (r < 0) - return r; - - e->output_fd = fd; - return r; -} diff --git a/src/grp-import/systemd-export/export-raw.h b/src/grp-import/systemd-export/export-raw.h deleted file mode 100644 index 13d7809b9d..0000000000 --- a/src/grp-import/systemd-export/export-raw.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 "import-compress.h" -#include "basic/macro.h" - -typedef struct RawExport RawExport; - -typedef void (*RawExportFinished)(RawExport *export, int error, void *userdata); - -int raw_export_new(RawExport **export, sd_event *event, RawExportFinished on_finished, void *userdata); -RawExport* raw_export_unref(RawExport *export); - -DEFINE_TRIVIAL_CLEANUP_FUNC(RawExport*, raw_export_unref); - -int raw_export_start(RawExport *export, const char *path, int fd, ImportCompressType compress); diff --git a/src/grp-import/systemd-export/export-tar.c b/src/grp-import/systemd-export/export-tar.c deleted file mode 100644 index 9a8d48dc2e..0000000000 --- a/src/grp-import/systemd-export/export-tar.c +++ /dev/null @@ -1,328 +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 "basic/alloc-util.h" -#include "basic/btrfs-util.h" -#include "export-tar.h" -#include "basic/fd-util.h" -#include "basic/fileio.h" -#include "import-common.h" -#include "basic/process-util.h" -#include "basic/ratelimit.h" -#include "basic/string-util.h" -#include "basic/util.h" - -#define COPY_BUFFER_SIZE (16*1024) - -struct TarExport { - sd_event *event; - - TarExportFinished on_finished; - void *userdata; - - char *path; - char *temp_path; - - int output_fd; - int tar_fd; - - ImportCompress compress; - - sd_event_source *output_event_source; - - void *buffer; - size_t buffer_size; - size_t buffer_allocated; - - uint64_t written_compressed; - uint64_t written_uncompressed; - - pid_t tar_pid; - - struct stat st; - uint64_t quota_referenced; - - unsigned last_percent; - RateLimit progress_rate_limit; - - bool eof; - bool tried_splice; -}; - -TarExport *tar_export_unref(TarExport *e) { - if (!e) - return NULL; - - sd_event_source_unref(e->output_event_source); - - if (e->tar_pid > 1) { - (void) kill_and_sigcont(e->tar_pid, SIGKILL); - (void) wait_for_terminate(e->tar_pid, NULL); - } - - if (e->temp_path) { - (void) btrfs_subvol_remove(e->temp_path, BTRFS_REMOVE_QUOTA); - free(e->temp_path); - } - - import_compress_free(&e->compress); - - sd_event_unref(e->event); - - safe_close(e->tar_fd); - - free(e->buffer); - free(e->path); - free(e); - - return NULL; -} - -int tar_export_new( - TarExport **ret, - sd_event *event, - TarExportFinished on_finished, - void *userdata) { - - _cleanup_(tar_export_unrefp) TarExport *e = NULL; - int r; - - assert(ret); - - e = new0(TarExport, 1); - if (!e) - return -ENOMEM; - - e->output_fd = e->tar_fd = -1; - e->on_finished = on_finished; - e->userdata = userdata; - e->quota_referenced = (uint64_t) -1; - - RATELIMIT_INIT(e->progress_rate_limit, 100 * USEC_PER_MSEC, 1); - e->last_percent = (unsigned) -1; - - if (event) - e->event = sd_event_ref(event); - else { - r = sd_event_default(&e->event); - if (r < 0) - return r; - } - - *ret = e; - e = NULL; - - return 0; -} - -static void tar_export_report_progress(TarExport *e) { - unsigned percent; - assert(e); - - /* Do we have any quota info? If not, we don't know anything about the progress */ - if (e->quota_referenced == (uint64_t) -1) - return; - - if (e->written_uncompressed >= e->quota_referenced) - percent = 100; - else - percent = (unsigned) ((e->written_uncompressed * UINT64_C(100)) / e->quota_referenced); - - if (percent == e->last_percent) - return; - - if (!ratelimit_test(&e->progress_rate_limit)) - return; - - sd_notifyf(false, "X_IMPORT_PROGRESS=%u", percent); - log_info("Exported %u%%.", percent); - - e->last_percent = percent; -} - -static int tar_export_process(TarExport *e) { - ssize_t l; - int r; - - assert(e); - - if (!e->tried_splice && e->compress.type == IMPORT_COMPRESS_UNCOMPRESSED) { - - l = splice(e->tar_fd, NULL, e->output_fd, NULL, COPY_BUFFER_SIZE, 0); - if (l < 0) { - if (errno == EAGAIN) - return 0; - - e->tried_splice = true; - } else if (l == 0) { - r = 0; - goto finish; - } else { - e->written_uncompressed += l; - e->written_compressed += l; - - tar_export_report_progress(e); - - return 0; - } - } - - while (e->buffer_size <= 0) { - uint8_t input[COPY_BUFFER_SIZE]; - - if (e->eof) { - r = 0; - goto finish; - } - - l = read(e->tar_fd, input, sizeof(input)); - if (l < 0) { - r = log_error_errno(errno, "Failed to read tar file: %m"); - goto finish; - } - - if (l == 0) { - e->eof = true; - r = import_compress_finish(&e->compress, &e->buffer, &e->buffer_size, &e->buffer_allocated); - } else { - e->written_uncompressed += l; - r = import_compress(&e->compress, input, l, &e->buffer, &e->buffer_size, &e->buffer_allocated); - } - if (r < 0) { - r = log_error_errno(r, "Failed to encode: %m"); - goto finish; - } - } - - l = write(e->output_fd, e->buffer, e->buffer_size); - if (l < 0) { - if (errno == EAGAIN) - return 0; - - r = log_error_errno(errno, "Failed to write output file: %m"); - goto finish; - } - - assert((size_t) l <= e->buffer_size); - memmove(e->buffer, (uint8_t*) e->buffer + l, e->buffer_size - l); - e->buffer_size -= l; - e->written_compressed += l; - - tar_export_report_progress(e); - - return 0; - -finish: - if (e->on_finished) - e->on_finished(e, r, e->userdata); - else - sd_event_exit(e->event, r); - - return 0; -} - -static int tar_export_on_output(sd_event_source *s, int fd, uint32_t revents, void *userdata) { - TarExport *i = userdata; - - return tar_export_process(i); -} - -static int tar_export_on_defer(sd_event_source *s, void *userdata) { - TarExport *i = userdata; - - return tar_export_process(i); -} - -int tar_export_start(TarExport *e, const char *path, int fd, ImportCompressType compress) { - _cleanup_close_ int sfd = -1; - int r; - - assert(e); - assert(path); - assert(fd >= 0); - assert(compress < _IMPORT_COMPRESS_TYPE_MAX); - assert(compress != IMPORT_COMPRESS_UNKNOWN); - - if (e->output_fd >= 0) - return -EBUSY; - - sfd = open(path, O_DIRECTORY|O_RDONLY|O_NOCTTY|O_CLOEXEC); - if (sfd < 0) - return -errno; - - if (fstat(sfd, &e->st) < 0) - return -errno; - - r = fd_nonblock(fd, true); - if (r < 0) - return r; - - r = free_and_strdup(&e->path, path); - if (r < 0) - return r; - - e->quota_referenced = (uint64_t) -1; - - if (e->st.st_ino == 256) { /* might be a btrfs subvolume? */ - BtrfsQuotaInfo q; - - r = btrfs_subvol_get_subtree_quota_fd(sfd, 0, &q); - if (r >= 0) - e->quota_referenced = q.referenced; - - e->temp_path = mfree(e->temp_path); - - r = tempfn_random(path, NULL, &e->temp_path); - if (r < 0) - return r; - - /* Let's try to make a snapshot, if we can, so that the export is atomic */ - r = btrfs_subvol_snapshot_fd(sfd, e->temp_path, BTRFS_SNAPSHOT_READ_ONLY|BTRFS_SNAPSHOT_RECURSIVE); - if (r < 0) { - log_debug_errno(r, "Couldn't create snapshot %s of %s, not exporting atomically: %m", e->temp_path, path); - e->temp_path = mfree(e->temp_path); - } - } - - r = import_compress_init(&e->compress, compress); - if (r < 0) - return r; - - r = sd_event_add_io(e->event, &e->output_event_source, fd, EPOLLOUT, tar_export_on_output, e); - if (r == -EPERM) { - r = sd_event_add_defer(e->event, &e->output_event_source, tar_export_on_defer, e); - if (r < 0) - return r; - - r = sd_event_source_set_enabled(e->output_event_source, SD_EVENT_ON); - } - if (r < 0) - return r; - - e->tar_fd = import_fork_tar_c(e->temp_path ?: e->path, &e->tar_pid); - if (e->tar_fd < 0) { - e->output_event_source = sd_event_source_unref(e->output_event_source); - return e->tar_fd; - } - - e->output_fd = fd; - return r; -} diff --git a/src/grp-import/systemd-export/export-tar.h b/src/grp-import/systemd-export/export-tar.h deleted file mode 100644 index 0a59033d5b..0000000000 --- a/src/grp-import/systemd-export/export-tar.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 "import-compress.h" -#include "basic/macro.h" - -typedef struct TarExport TarExport; - -typedef void (*TarExportFinished)(TarExport *export, int error, void *userdata); - -int tar_export_new(TarExport **export, sd_event *event, TarExportFinished on_finished, void *userdata); -TarExport* tar_export_unref(TarExport *export); - -DEFINE_TRIVIAL_CLEANUP_FUNC(TarExport*, tar_export_unref); - -int tar_export_start(TarExport *export, const char *path, int fd, ImportCompressType compress); diff --git a/src/grp-import/systemd-export/export.c b/src/grp-import/systemd-export/export.c deleted file mode 100644 index a64b79bba0..0000000000 --- a/src/grp-import/systemd-export/export.c +++ /dev/null @@ -1,320 +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 "basic/alloc-util.h" -#include "export-raw.h" -#include "export-tar.h" -#include "basic/fd-util.h" -#include "basic/fs-util.h" -#include "basic/hostname-util.h" -#include "shared/import-util.h" -#include "shared/machine-image.h" -#include "basic/signal-util.h" -#include "basic/string-util.h" -#include "basic/verbs.h" - -static ImportCompressType arg_compress = IMPORT_COMPRESS_UNKNOWN; - -static void determine_compression_from_filename(const char *p) { - - if (arg_compress != IMPORT_COMPRESS_UNKNOWN) - return; - - if (!p) { - arg_compress = IMPORT_COMPRESS_UNCOMPRESSED; - return; - } - - if (endswith(p, ".xz")) - arg_compress = IMPORT_COMPRESS_XZ; - else if (endswith(p, ".gz")) - arg_compress = IMPORT_COMPRESS_GZIP; - else if (endswith(p, ".bz2")) - arg_compress = IMPORT_COMPRESS_BZIP2; - else - arg_compress = IMPORT_COMPRESS_UNCOMPRESSED; -} - -static int interrupt_signal_handler(sd_event_source *s, const struct signalfd_siginfo *si, void *userdata) { - log_notice("Transfer aborted."); - sd_event_exit(sd_event_source_get_event(s), EINTR); - return 0; -} - -static void on_tar_finished(TarExport *export, int error, void *userdata) { - sd_event *event = userdata; - assert(export); - - if (error == 0) - log_info("Operation completed successfully."); - - sd_event_exit(event, abs(error)); -} - -static int export_tar(int argc, char *argv[], void *userdata) { - _cleanup_(tar_export_unrefp) TarExport *export = NULL; - _cleanup_(sd_event_unrefp) sd_event *event = NULL; - _cleanup_(image_unrefp) Image *image = NULL; - const char *path = NULL, *local = NULL; - _cleanup_close_ int open_fd = -1; - int r, fd; - - if (machine_name_is_valid(argv[1])) { - r = image_find(argv[1], &image); - if (r < 0) - return log_error_errno(r, "Failed to look for machine %s: %m", argv[1]); - if (r == 0) { - log_error("Machine image %s not found.", argv[1]); - return -ENOENT; - } - - local = image->path; - } else - local = argv[1]; - - if (argc >= 3) - path = argv[2]; - if (isempty(path) || streq(path, "-")) - path = NULL; - - determine_compression_from_filename(path); - - if (path) { - open_fd = open(path, O_WRONLY|O_CREAT|O_TRUNC|O_CLOEXEC|O_NOCTTY, 0666); - if (open_fd < 0) - return log_error_errno(errno, "Failed to open tar image for export: %m"); - - fd = open_fd; - - log_info("Exporting '%s', saving to '%s' with compression '%s'.", local, path, import_compress_type_to_string(arg_compress)); - } else { - _cleanup_free_ char *pretty = NULL; - - fd = STDOUT_FILENO; - - (void) readlink_malloc("/proc/self/fd/1", &pretty); - log_info("Exporting '%s', saving to '%s' with compression '%s'.", local, strna(pretty), import_compress_type_to_string(arg_compress)); - } - - r = sd_event_default(&event); - if (r < 0) - return log_error_errno(r, "Failed to allocate event loop: %m"); - - assert_se(sigprocmask_many(SIG_BLOCK, NULL, SIGTERM, SIGINT, -1) >= 0); - (void) sd_event_add_signal(event, NULL, SIGTERM, interrupt_signal_handler, NULL); - (void) sd_event_add_signal(event, NULL, SIGINT, interrupt_signal_handler, NULL); - - r = tar_export_new(&export, event, on_tar_finished, event); - if (r < 0) - return log_error_errno(r, "Failed to allocate exporter: %m"); - - r = tar_export_start(export, local, fd, arg_compress); - if (r < 0) - return log_error_errno(r, "Failed to export image: %m"); - - r = sd_event_loop(event); - if (r < 0) - return log_error_errno(r, "Failed to run event loop: %m"); - - log_info("Exiting."); - return -r; -} - -static void on_raw_finished(RawExport *export, int error, void *userdata) { - sd_event *event = userdata; - assert(export); - - if (error == 0) - log_info("Operation completed successfully."); - - sd_event_exit(event, abs(error)); -} - -static int export_raw(int argc, char *argv[], void *userdata) { - _cleanup_(raw_export_unrefp) RawExport *export = NULL; - _cleanup_(sd_event_unrefp) sd_event *event = NULL; - _cleanup_(image_unrefp) Image *image = NULL; - const char *path = NULL, *local = NULL; - _cleanup_close_ int open_fd = -1; - int r, fd; - - if (machine_name_is_valid(argv[1])) { - r = image_find(argv[1], &image); - if (r < 0) - return log_error_errno(r, "Failed to look for machine %s: %m", argv[1]); - if (r == 0) { - log_error("Machine image %s not found.", argv[1]); - return -ENOENT; - } - - local = image->path; - } else - local = argv[1]; - - if (argc >= 3) - path = argv[2]; - if (isempty(path) || streq(path, "-")) - path = NULL; - - determine_compression_from_filename(path); - - if (path) { - open_fd = open(path, O_WRONLY|O_CREAT|O_TRUNC|O_CLOEXEC|O_NOCTTY, 0666); - if (open_fd < 0) - return log_error_errno(errno, "Failed to open raw image for export: %m"); - - fd = open_fd; - - log_info("Exporting '%s', saving to '%s' with compression '%s'.", local, path, import_compress_type_to_string(arg_compress)); - } else { - _cleanup_free_ char *pretty = NULL; - - fd = STDOUT_FILENO; - - (void) readlink_malloc("/proc/self/fd/1", &pretty); - log_info("Exporting '%s', saving to '%s' with compression '%s'.", local, strna(pretty), import_compress_type_to_string(arg_compress)); - } - - r = sd_event_default(&event); - if (r < 0) - return log_error_errno(r, "Failed to allocate event loop: %m"); - - assert_se(sigprocmask_many(SIG_BLOCK, NULL, SIGTERM, SIGINT, -1) >= 0); - (void) sd_event_add_signal(event, NULL, SIGTERM, interrupt_signal_handler, NULL); - (void) sd_event_add_signal(event, NULL, SIGINT, interrupt_signal_handler, NULL); - - r = raw_export_new(&export, event, on_raw_finished, event); - if (r < 0) - return log_error_errno(r, "Failed to allocate exporter: %m"); - - r = raw_export_start(export, local, fd, arg_compress); - if (r < 0) - return log_error_errno(r, "Failed to export image: %m"); - - r = sd_event_loop(event); - if (r < 0) - return log_error_errno(r, "Failed to run event loop: %m"); - - log_info("Exiting."); - return -r; -} - -static int help(int argc, char *argv[], void *userdata) { - - printf("%s [OPTIONS...] {COMMAND} ...\n\n" - "Export container or virtual machine images.\n\n" - " -h --help Show this help\n" - " --version Show package version\n" - " --format=FORMAT Select format\n\n" - "Commands:\n" - " tar NAME [FILE] Export a TAR image\n" - " raw NAME [FILE] Export a RAW image\n", - program_invocation_short_name); - - return 0; -} - -static int parse_argv(int argc, char *argv[]) { - - enum { - ARG_VERSION = 0x100, - ARG_FORMAT, - }; - - static const struct option options[] = { - { "help", no_argument, NULL, 'h' }, - { "version", no_argument, NULL, ARG_VERSION }, - { "format", required_argument, NULL, ARG_FORMAT }, - {} - }; - - int c; - - assert(argc >= 0); - assert(argv); - - while ((c = getopt_long(argc, argv, "h", options, NULL)) >= 0) - - switch (c) { - - case 'h': - return help(0, NULL, NULL); - - case ARG_VERSION: - return version(); - - case ARG_FORMAT: - if (streq(optarg, "uncompressed")) - arg_compress = IMPORT_COMPRESS_UNCOMPRESSED; - else if (streq(optarg, "xz")) - arg_compress = IMPORT_COMPRESS_XZ; - else if (streq(optarg, "gzip")) - arg_compress = IMPORT_COMPRESS_GZIP; - else if (streq(optarg, "bzip2")) - arg_compress = IMPORT_COMPRESS_BZIP2; - else { - log_error("Unknown format: %s", optarg); - return -EINVAL; - } - break; - - case '?': - return -EINVAL; - - default: - assert_not_reached("Unhandled option"); - } - - return 1; -} - -static int export_main(int argc, char *argv[]) { - - static const Verb verbs[] = { - { "help", VERB_ANY, VERB_ANY, 0, help }, - { "tar", 2, 3, 0, export_tar }, - { "raw", 2, 3, 0, export_raw }, - {} - }; - - return dispatch_verb(argc, argv, verbs, NULL); -} - -int main(int argc, char *argv[]) { - int r; - - setlocale(LC_ALL, ""); - log_parse_environment(); - log_open(); - - r = parse_argv(argc, argv); - if (r <= 0) - goto finish; - - (void) ignore_signals(SIGPIPE, -1); - - r = export_main(argc, argv); - -finish: - return r < 0 ? EXIT_FAILURE : EXIT_SUCCESS; -} diff --git a/src/grp-import/systemd-import/Makefile b/src/grp-import/systemd-import/Makefile deleted file mode 100644 index 301556ffd5..0000000000 --- a/src/grp-import/systemd-import/Makefile +++ /dev/null @@ -1,52 +0,0 @@ -# -*- 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-import -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) \ - $(BZIP2_CFLAGS) - -systemd_import_LDADD = \ - libshared.la \ - $(XZ_LIBS) \ - $(ZLIB_LIBS) \ - $(BZIP2_LIBS) - -include $(topsrcdir)/build-aux/Makefile.tail.mk diff --git a/src/grp-import/systemd-import/import-common.c b/src/grp-import/systemd-import/import-common.c deleted file mode 100644 index c2baef83f8..0000000000 --- a/src/grp-import/systemd-import/import-common.c +++ /dev/null @@ -1,225 +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 "basic/btrfs-util.h" -#include "basic/capability-util.h" -#include "basic/fd-util.h" -#include "import-common.h" -#include "basic/signal-util.h" -#include "basic/util.h" - -int import_make_read_only_fd(int fd) { - int r; - - assert(fd >= 0); - - /* First, let's make this a read-only subvolume if it refers - * to a subvolume */ - r = btrfs_subvol_set_read_only_fd(fd, true); - if (r == -ENOTTY || r == -ENOTDIR || r == -EINVAL) { - struct stat st; - - /* This doesn't refer to a subvolume, or the file - * system isn't even btrfs. In that, case fall back to - * chmod()ing */ - - r = fstat(fd, &st); - if (r < 0) - return log_error_errno(errno, "Failed to stat temporary image: %m"); - - /* Drop "w" flag */ - if (fchmod(fd, st.st_mode & 07555) < 0) - return log_error_errno(errno, "Failed to chmod() final image: %m"); - - return 0; - - } else if (r < 0) - return log_error_errno(r, "Failed to make subvolume read-only: %m"); - - return 0; -} - -int import_make_read_only(const char *path) { - _cleanup_close_ int fd = 1; - - fd = open(path, O_RDONLY|O_NOCTTY|O_CLOEXEC); - if (fd < 0) - return log_error_errno(errno, "Failed to open %s: %m", path); - - return import_make_read_only_fd(fd); -} - -int import_fork_tar_x(const char *path, pid_t *ret) { - _cleanup_close_pair_ int pipefd[2] = { -1, -1 }; - pid_t pid; - int r; - - assert(path); - assert(ret); - - if (pipe2(pipefd, O_CLOEXEC) < 0) - return log_error_errno(errno, "Failed to create pipe for tar: %m"); - - pid = fork(); - if (pid < 0) - return log_error_errno(errno, "Failed to fork off tar: %m"); - - if (pid == 0) { - int null_fd; - uint64_t retain = - (1ULL << CAP_CHOWN) | - (1ULL << CAP_FOWNER) | - (1ULL << CAP_FSETID) | - (1ULL << CAP_MKNOD) | - (1ULL << CAP_SETFCAP) | - (1ULL << CAP_DAC_OVERRIDE); - - /* Child */ - - (void) reset_all_signal_handlers(); - (void) reset_signal_mask(); - assert_se(prctl(PR_SET_PDEATHSIG, SIGTERM) == 0); - - pipefd[1] = safe_close(pipefd[1]); - - if (dup2(pipefd[0], STDIN_FILENO) != STDIN_FILENO) { - log_error_errno(errno, "Failed to dup2() fd: %m"); - _exit(EXIT_FAILURE); - } - - if (pipefd[0] != STDIN_FILENO) - pipefd[0] = safe_close(pipefd[0]); - - null_fd = open("/dev/null", O_WRONLY|O_NOCTTY); - if (null_fd < 0) { - log_error_errno(errno, "Failed to open /dev/null: %m"); - _exit(EXIT_FAILURE); - } - - if (dup2(null_fd, STDOUT_FILENO) != STDOUT_FILENO) { - log_error_errno(errno, "Failed to dup2() fd: %m"); - _exit(EXIT_FAILURE); - } - - if (null_fd != STDOUT_FILENO) - null_fd = safe_close(null_fd); - - fd_cloexec(STDIN_FILENO, false); - fd_cloexec(STDOUT_FILENO, false); - fd_cloexec(STDERR_FILENO, false); - - if (unshare(CLONE_NEWNET) < 0) - log_error_errno(errno, "Failed to lock tar into network namespace, ignoring: %m"); - - r = capability_bounding_set_drop(retain, true); - if (r < 0) - log_error_errno(r, "Failed to drop capabilities, ignoring: %m"); - - execlp("tar", "tar", "--numeric-owner", "-C", path, "-px", "--xattrs", "--xattrs-include=*", NULL); - log_error_errno(errno, "Failed to execute tar: %m"); - _exit(EXIT_FAILURE); - } - - pipefd[0] = safe_close(pipefd[0]); - r = pipefd[1]; - pipefd[1] = -1; - - *ret = pid; - - return r; -} - -int import_fork_tar_c(const char *path, pid_t *ret) { - _cleanup_close_pair_ int pipefd[2] = { -1, -1 }; - pid_t pid; - int r; - - assert(path); - assert(ret); - - if (pipe2(pipefd, O_CLOEXEC) < 0) - return log_error_errno(errno, "Failed to create pipe for tar: %m"); - - pid = fork(); - if (pid < 0) - return log_error_errno(errno, "Failed to fork off tar: %m"); - - if (pid == 0) { - int null_fd; - uint64_t retain = (1ULL << CAP_DAC_OVERRIDE); - - /* Child */ - - (void) reset_all_signal_handlers(); - (void) reset_signal_mask(); - assert_se(prctl(PR_SET_PDEATHSIG, SIGTERM) == 0); - - pipefd[0] = safe_close(pipefd[0]); - - if (dup2(pipefd[1], STDOUT_FILENO) != STDOUT_FILENO) { - log_error_errno(errno, "Failed to dup2() fd: %m"); - _exit(EXIT_FAILURE); - } - - if (pipefd[1] != STDOUT_FILENO) - pipefd[1] = safe_close(pipefd[1]); - - null_fd = open("/dev/null", O_RDONLY|O_NOCTTY); - if (null_fd < 0) { - log_error_errno(errno, "Failed to open /dev/null: %m"); - _exit(EXIT_FAILURE); - } - - if (dup2(null_fd, STDIN_FILENO) != STDIN_FILENO) { - log_error_errno(errno, "Failed to dup2() fd: %m"); - _exit(EXIT_FAILURE); - } - - if (null_fd != STDIN_FILENO) - null_fd = safe_close(null_fd); - - fd_cloexec(STDIN_FILENO, false); - fd_cloexec(STDOUT_FILENO, false); - fd_cloexec(STDERR_FILENO, false); - - if (unshare(CLONE_NEWNET) < 0) - log_error_errno(errno, "Failed to lock tar into network namespace, ignoring: %m"); - - r = capability_bounding_set_drop(retain, true); - if (r < 0) - log_error_errno(r, "Failed to drop capabilities, ignoring: %m"); - - execlp("tar", "tar", "-C", path, "-c", "--xattrs", "--xattrs-include=*", ".", NULL); - log_error_errno(errno, "Failed to execute tar: %m"); - _exit(EXIT_FAILURE); - } - - pipefd[1] = safe_close(pipefd[1]); - r = pipefd[0]; - pipefd[0] = -1; - - *ret = pid; - - return r; -} diff --git a/src/grp-import/systemd-import/import-common.h b/src/grp-import/systemd-import/import-common.h deleted file mode 100644 index 07d3250e71..0000000000 --- a/src/grp-import/systemd-import/import-common.h +++ /dev/null @@ -1,26 +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 import_make_read_only_fd(int fd); -int import_make_read_only(const char *path); - -int import_fork_tar_c(const char *path, pid_t *ret); -int import_fork_tar_x(const char *path, pid_t *ret); diff --git a/src/grp-import/systemd-import/import-compress.c b/src/grp-import/systemd-import/import-compress.c deleted file mode 100644 index 05643cfbaf..0000000000 --- a/src/grp-import/systemd-import/import-compress.c +++ /dev/null @@ -1,469 +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 "import-compress.h" -#include "basic/string-table.h" -#include "basic/util.h" - -void import_compress_free(ImportCompress *c) { - assert(c); - - if (c->type == IMPORT_COMPRESS_XZ) - lzma_end(&c->xz); - else if (c->type == IMPORT_COMPRESS_GZIP) { - if (c->encoding) - deflateEnd(&c->gzip); - else - inflateEnd(&c->gzip); - } else if (c->type == IMPORT_COMPRESS_BZIP2) { - if (c->encoding) - BZ2_bzCompressEnd(&c->bzip2); - else - BZ2_bzDecompressEnd(&c->bzip2); - } - - c->type = IMPORT_COMPRESS_UNKNOWN; -} - -int import_uncompress_detect(ImportCompress *c, const void *data, size_t size) { - static const uint8_t xz_signature[] = { - 0xfd, '7', 'z', 'X', 'Z', 0x00 - }; - static const uint8_t gzip_signature[] = { - 0x1f, 0x8b - }; - static const uint8_t bzip2_signature[] = { - 'B', 'Z', 'h' - }; - - int r; - - assert(c); - - if (c->type != IMPORT_COMPRESS_UNKNOWN) - return 1; - - if (size < MAX3(sizeof(xz_signature), - sizeof(gzip_signature), - sizeof(bzip2_signature))) - return 0; - - assert(data); - - if (memcmp(data, xz_signature, sizeof(xz_signature)) == 0) { - lzma_ret xzr; - - xzr = lzma_stream_decoder(&c->xz, UINT64_MAX, LZMA_TELL_UNSUPPORTED_CHECK); - if (xzr != LZMA_OK) - return -EIO; - - c->type = IMPORT_COMPRESS_XZ; - - } else if (memcmp(data, gzip_signature, sizeof(gzip_signature)) == 0) { - r = inflateInit2(&c->gzip, 15+16); - if (r != Z_OK) - return -EIO; - - c->type = IMPORT_COMPRESS_GZIP; - - } else if (memcmp(data, bzip2_signature, sizeof(bzip2_signature)) == 0) { - r = BZ2_bzDecompressInit(&c->bzip2, 0, 0); - if (r != BZ_OK) - return -EIO; - - c->type = IMPORT_COMPRESS_BZIP2; - } else - c->type = IMPORT_COMPRESS_UNCOMPRESSED; - - c->encoding = false; - - return 1; -} - -int import_uncompress(ImportCompress *c, const void *data, size_t size, ImportCompressCallback callback, void *userdata) { - int r; - - assert(c); - assert(callback); - - r = import_uncompress_detect(c, data, size); - if (r <= 0) - return r; - - if (c->encoding) - return -EINVAL; - - if (size <= 0) - return 1; - - assert(data); - - switch (c->type) { - - case IMPORT_COMPRESS_UNCOMPRESSED: - r = callback(data, size, userdata); - if (r < 0) - return r; - - break; - - case IMPORT_COMPRESS_XZ: - c->xz.next_in = data; - c->xz.avail_in = size; - - while (c->xz.avail_in > 0) { - uint8_t buffer[16 * 1024]; - lzma_ret lzr; - - c->xz.next_out = buffer; - c->xz.avail_out = sizeof(buffer); - - lzr = lzma_code(&c->xz, LZMA_RUN); - if (lzr != LZMA_OK && lzr != LZMA_STREAM_END) - return -EIO; - - r = callback(buffer, sizeof(buffer) - c->xz.avail_out, userdata); - if (r < 0) - return r; - } - - break; - - case IMPORT_COMPRESS_GZIP: - c->gzip.next_in = (void*) data; - c->gzip.avail_in = size; - - while (c->gzip.avail_in > 0) { - uint8_t buffer[16 * 1024]; - - c->gzip.next_out = buffer; - c->gzip.avail_out = sizeof(buffer); - - r = inflate(&c->gzip, Z_NO_FLUSH); - if (r != Z_OK && r != Z_STREAM_END) - return -EIO; - - r = callback(buffer, sizeof(buffer) - c->gzip.avail_out, userdata); - if (r < 0) - return r; - } - - break; - - case IMPORT_COMPRESS_BZIP2: - c->bzip2.next_in = (void*) data; - c->bzip2.avail_in = size; - - while (c->bzip2.avail_in > 0) { - uint8_t buffer[16 * 1024]; - - c->bzip2.next_out = (char*) buffer; - c->bzip2.avail_out = sizeof(buffer); - - r = BZ2_bzDecompress(&c->bzip2); - if (r != BZ_OK && r != BZ_STREAM_END) - return -EIO; - - r = callback(buffer, sizeof(buffer) - c->bzip2.avail_out, userdata); - if (r < 0) - return r; - } - - break; - - default: - assert_not_reached("Unknown compression"); - } - - return 1; -} - -int import_compress_init(ImportCompress *c, ImportCompressType t) { - int r; - - assert(c); - - switch (t) { - - case IMPORT_COMPRESS_XZ: { - lzma_ret xzr; - - xzr = lzma_easy_encoder(&c->xz, LZMA_PRESET_DEFAULT, LZMA_CHECK_CRC64); - if (xzr != LZMA_OK) - return -EIO; - - c->type = IMPORT_COMPRESS_XZ; - break; - } - - case IMPORT_COMPRESS_GZIP: - r = deflateInit2(&c->gzip, Z_DEFAULT_COMPRESSION, Z_DEFLATED, 15 + 16, 8, Z_DEFAULT_STRATEGY); - if (r != Z_OK) - return -EIO; - - c->type = IMPORT_COMPRESS_GZIP; - break; - - case IMPORT_COMPRESS_BZIP2: - r = BZ2_bzCompressInit(&c->bzip2, 9, 0, 0); - if (r != BZ_OK) - return -EIO; - - c->type = IMPORT_COMPRESS_BZIP2; - break; - - case IMPORT_COMPRESS_UNCOMPRESSED: - c->type = IMPORT_COMPRESS_UNCOMPRESSED; - break; - - default: - return -EOPNOTSUPP; - } - - c->encoding = true; - return 0; -} - -static int enlarge_buffer(void **buffer, size_t *buffer_size, size_t *buffer_allocated) { - size_t l; - void *p; - - if (*buffer_allocated > *buffer_size) - return 0; - - l = MAX(16*1024U, (*buffer_size * 2)); - p = realloc(*buffer, l); - if (!p) - return -ENOMEM; - - *buffer = p; - *buffer_allocated = l; - - return 1; -} - -int import_compress(ImportCompress *c, const void *data, size_t size, void **buffer, size_t *buffer_size, size_t *buffer_allocated) { - int r; - - assert(c); - assert(buffer); - assert(buffer_size); - assert(buffer_allocated); - - if (!c->encoding) - return -EINVAL; - - if (size <= 0) - return 0; - - assert(data); - - *buffer_size = 0; - - switch (c->type) { - - case IMPORT_COMPRESS_XZ: - - c->xz.next_in = data; - c->xz.avail_in = size; - - while (c->xz.avail_in > 0) { - lzma_ret lzr; - - r = enlarge_buffer(buffer, buffer_size, buffer_allocated); - if (r < 0) - return r; - - c->xz.next_out = (uint8_t*) *buffer + *buffer_size; - c->xz.avail_out = *buffer_allocated - *buffer_size; - - lzr = lzma_code(&c->xz, LZMA_RUN); - if (lzr != LZMA_OK) - return -EIO; - - *buffer_size += (*buffer_allocated - *buffer_size) - c->xz.avail_out; - } - - break; - - case IMPORT_COMPRESS_GZIP: - - c->gzip.next_in = (void*) data; - c->gzip.avail_in = size; - - while (c->gzip.avail_in > 0) { - r = enlarge_buffer(buffer, buffer_size, buffer_allocated); - if (r < 0) - return r; - - c->gzip.next_out = (uint8_t*) *buffer + *buffer_size; - c->gzip.avail_out = *buffer_allocated - *buffer_size; - - r = deflate(&c->gzip, Z_NO_FLUSH); - if (r != Z_OK) - return -EIO; - - *buffer_size += (*buffer_allocated - *buffer_size) - c->gzip.avail_out; - } - - break; - - case IMPORT_COMPRESS_BZIP2: - - c->bzip2.next_in = (void*) data; - c->bzip2.avail_in = size; - - while (c->bzip2.avail_in > 0) { - r = enlarge_buffer(buffer, buffer_size, buffer_allocated); - if (r < 0) - return r; - - c->bzip2.next_out = (void*) ((uint8_t*) *buffer + *buffer_size); - c->bzip2.avail_out = *buffer_allocated - *buffer_size; - - r = BZ2_bzCompress(&c->bzip2, BZ_RUN); - if (r != BZ_RUN_OK) - return -EIO; - - *buffer_size += (*buffer_allocated - *buffer_size) - c->bzip2.avail_out; - } - - break; - - case IMPORT_COMPRESS_UNCOMPRESSED: - - if (*buffer_allocated < size) { - void *p; - - p = realloc(*buffer, size); - if (!p) - return -ENOMEM; - - *buffer = p; - *buffer_allocated = size; - } - - memcpy(*buffer, data, size); - *buffer_size = size; - break; - - default: - return -EOPNOTSUPP; - } - - return 0; -} - -int import_compress_finish(ImportCompress *c, void **buffer, size_t *buffer_size, size_t *buffer_allocated) { - int r; - - assert(c); - assert(buffer); - assert(buffer_size); - assert(buffer_allocated); - - if (!c->encoding) - return -EINVAL; - - *buffer_size = 0; - - switch (c->type) { - - case IMPORT_COMPRESS_XZ: { - lzma_ret lzr; - - c->xz.avail_in = 0; - - do { - r = enlarge_buffer(buffer, buffer_size, buffer_allocated); - if (r < 0) - return r; - - c->xz.next_out = (uint8_t*) *buffer + *buffer_size; - c->xz.avail_out = *buffer_allocated - *buffer_size; - - lzr = lzma_code(&c->xz, LZMA_FINISH); - if (lzr != LZMA_OK && lzr != LZMA_STREAM_END) - return -EIO; - - *buffer_size += (*buffer_allocated - *buffer_size) - c->xz.avail_out; - } while (lzr != LZMA_STREAM_END); - - break; - } - - case IMPORT_COMPRESS_GZIP: - c->gzip.avail_in = 0; - - do { - r = enlarge_buffer(buffer, buffer_size, buffer_allocated); - if (r < 0) - return r; - - c->gzip.next_out = (uint8_t*) *buffer + *buffer_size; - c->gzip.avail_out = *buffer_allocated - *buffer_size; - - r = deflate(&c->gzip, Z_FINISH); - if (r != Z_OK && r != Z_STREAM_END) - return -EIO; - - *buffer_size += (*buffer_allocated - *buffer_size) - c->gzip.avail_out; - } while (r != Z_STREAM_END); - - break; - - case IMPORT_COMPRESS_BZIP2: - c->bzip2.avail_in = 0; - - do { - r = enlarge_buffer(buffer, buffer_size, buffer_allocated); - if (r < 0) - return r; - - c->bzip2.next_out = (void*) ((uint8_t*) *buffer + *buffer_size); - c->bzip2.avail_out = *buffer_allocated - *buffer_size; - - r = BZ2_bzCompress(&c->bzip2, BZ_FINISH); - if (r != BZ_FINISH_OK && r != BZ_STREAM_END) - return -EIO; - - *buffer_size += (*buffer_allocated - *buffer_size) - c->bzip2.avail_out; - } while (r != BZ_STREAM_END); - - break; - - case IMPORT_COMPRESS_UNCOMPRESSED: - break; - - default: - return -EOPNOTSUPP; - } - - return 0; -} - -static const char* const import_compress_type_table[_IMPORT_COMPRESS_TYPE_MAX] = { - [IMPORT_COMPRESS_UNKNOWN] = "unknown", - [IMPORT_COMPRESS_UNCOMPRESSED] = "uncompressed", - [IMPORT_COMPRESS_XZ] = "xz", - [IMPORT_COMPRESS_GZIP] = "gzip", - [IMPORT_COMPRESS_BZIP2] = "bzip2", -}; - -DEFINE_STRING_TABLE_LOOKUP(import_compress_type, ImportCompressType); diff --git a/src/grp-import/systemd-import/import-compress.h b/src/grp-import/systemd-import/import-compress.h deleted file mode 100644 index 130afb4cd0..0000000000 --- a/src/grp-import/systemd-import/import-compress.h +++ /dev/null @@ -1,61 +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 -#include - -#include "basic/macro.h" - -typedef enum ImportCompressType { - IMPORT_COMPRESS_UNKNOWN, - IMPORT_COMPRESS_UNCOMPRESSED, - IMPORT_COMPRESS_XZ, - IMPORT_COMPRESS_GZIP, - IMPORT_COMPRESS_BZIP2, - _IMPORT_COMPRESS_TYPE_MAX, - _IMPORT_COMPRESS_TYPE_INVALID = -1, -} ImportCompressType; - -typedef struct ImportCompress { - ImportCompressType type; - bool encoding; - union { - lzma_stream xz; - z_stream gzip; - bz_stream bzip2; - }; -} ImportCompress; - -typedef int (*ImportCompressCallback)(const void *data, size_t size, void *userdata); - -void import_compress_free(ImportCompress *c); - -int import_uncompress_detect(ImportCompress *c, const void *data, size_t size); -int import_uncompress(ImportCompress *c, const void *data, size_t size, ImportCompressCallback callback, void *userdata); - -int import_compress_init(ImportCompress *c, ImportCompressType t); -int import_compress(ImportCompress *c, const void *data, size_t size, void **buffer, size_t *buffer_size, size_t *buffer_allocated); -int import_compress_finish(ImportCompress *c, void **buffer, size_t *buffer_size, size_t *buffer_allocated); - -const char* import_compress_type_to_string(ImportCompressType t) _const_; -ImportCompressType import_compress_type_from_string(const char *s) _pure_; diff --git a/src/grp-import/systemd-import/import-pubring.gpg b/src/grp-import/systemd-import/import-pubring.gpg deleted file mode 100644 index be27776896..0000000000 Binary files a/src/grp-import/systemd-import/import-pubring.gpg and /dev/null differ diff --git a/src/grp-import/systemd-import/import-raw.c b/src/grp-import/systemd-import/import-raw.c deleted file mode 100644 index 0bbae2aa8a..0000000000 --- a/src/grp-import/systemd-import/import-raw.c +++ /dev/null @@ -1,467 +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 "basic/alloc-util.h" -#include "basic/btrfs-util.h" -#include "basic/chattr-util.h" -#include "basic/copy.h" -#include "basic/fd-util.h" -#include "basic/fileio.h" -#include "basic/fs-util.h" -#include "basic/hostname-util.h" -#include "import-common.h" -#include "import-compress.h" -#include "import-raw.h" -#include "basic/io-util.h" -#include "shared/machine-pool.h" -#include "basic/mkdir.h" -#include "basic/path-util.h" -#include "qcow2-util.h" -#include "basic/ratelimit.h" -#include "basic/rm-rf.h" -#include "basic/string-util.h" -#include "basic/util.h" - -struct RawImport { - sd_event *event; - - char *image_root; - - RawImportFinished on_finished; - void *userdata; - - char *local; - bool force_local; - bool read_only; - bool grow_machine_directory; - - char *temp_path; - char *final_path; - - int input_fd; - int output_fd; - - ImportCompress compress; - - uint64_t written_since_last_grow; - - sd_event_source *input_event_source; - - uint8_t buffer[16*1024]; - size_t buffer_size; - - uint64_t written_compressed; - uint64_t written_uncompressed; - - struct stat st; - - unsigned last_percent; - RateLimit progress_rate_limit; -}; - -RawImport* raw_import_unref(RawImport *i) { - if (!i) - return NULL; - - sd_event_unref(i->event); - - if (i->temp_path) { - (void) unlink(i->temp_path); - free(i->temp_path); - } - - import_compress_free(&i->compress); - - sd_event_source_unref(i->input_event_source); - - safe_close(i->output_fd); - - free(i->final_path); - free(i->image_root); - free(i->local); - free(i); - - return NULL; -} - -int raw_import_new( - RawImport **ret, - sd_event *event, - const char *image_root, - RawImportFinished on_finished, - void *userdata) { - - _cleanup_(raw_import_unrefp) RawImport *i = NULL; - int r; - - assert(ret); - - i = new0(RawImport, 1); - if (!i) - return -ENOMEM; - - i->input_fd = i->output_fd = -1; - i->on_finished = on_finished; - i->userdata = userdata; - - RATELIMIT_INIT(i->progress_rate_limit, 100 * USEC_PER_MSEC, 1); - i->last_percent = (unsigned) -1; - - i->image_root = strdup(image_root ?: "/var/lib/machines"); - if (!i->image_root) - return -ENOMEM; - - i->grow_machine_directory = path_startswith(i->image_root, "/var/lib/machines"); - - if (event) - i->event = sd_event_ref(event); - else { - r = sd_event_default(&i->event); - if (r < 0) - return r; - } - - *ret = i; - i = NULL; - - return 0; -} - -static void raw_import_report_progress(RawImport *i) { - unsigned percent; - assert(i); - - /* We have no size information, unless the source is a regular file */ - if (!S_ISREG(i->st.st_mode)) - return; - - if (i->written_compressed >= (uint64_t) i->st.st_size) - percent = 100; - else - percent = (unsigned) ((i->written_compressed * UINT64_C(100)) / (uint64_t) i->st.st_size); - - if (percent == i->last_percent) - return; - - if (!ratelimit_test(&i->progress_rate_limit)) - return; - - sd_notifyf(false, "X_IMPORT_PROGRESS=%u", percent); - log_info("Imported %u%%.", percent); - - i->last_percent = percent; -} - -static int raw_import_maybe_convert_qcow2(RawImport *i) { - _cleanup_close_ int converted_fd = -1; - _cleanup_free_ char *t = NULL; - int r; - - assert(i); - - r = qcow2_detect(i->output_fd); - if (r < 0) - return log_error_errno(r, "Failed to detect whether this is a QCOW2 image: %m"); - if (r == 0) - return 0; - - /* This is a QCOW2 image, let's convert it */ - r = tempfn_random(i->final_path, NULL, &t); - if (r < 0) - return log_oom(); - - converted_fd = open(t, O_RDWR|O_CREAT|O_EXCL|O_NOCTTY|O_CLOEXEC, 0664); - if (converted_fd < 0) - return log_error_errno(errno, "Failed to create %s: %m", t); - - r = chattr_fd(converted_fd, FS_NOCOW_FL, FS_NOCOW_FL); - if (r < 0) - log_warning_errno(r, "Failed to set file attributes on %s: %m", t); - - log_info("Unpacking QCOW2 file."); - - r = qcow2_convert(i->output_fd, converted_fd); - if (r < 0) { - unlink(t); - return log_error_errno(r, "Failed to convert qcow2 image: %m"); - } - - (void) unlink(i->temp_path); - free(i->temp_path); - i->temp_path = t; - t = NULL; - - safe_close(i->output_fd); - i->output_fd = converted_fd; - converted_fd = -1; - - return 1; -} - -static int raw_import_finish(RawImport *i) { - int r; - - assert(i); - assert(i->output_fd >= 0); - assert(i->temp_path); - assert(i->final_path); - - /* In case this was a sparse file, make sure the file system is right */ - if (i->written_uncompressed > 0) { - if (ftruncate(i->output_fd, i->written_uncompressed) < 0) - return log_error_errno(errno, "Failed to truncate file: %m"); - } - - r = raw_import_maybe_convert_qcow2(i); - if (r < 0) - return r; - - if (S_ISREG(i->st.st_mode)) { - (void) copy_times(i->input_fd, i->output_fd); - (void) copy_xattr(i->input_fd, i->output_fd); - } - - if (i->read_only) { - r = import_make_read_only_fd(i->output_fd); - if (r < 0) - return r; - } - - if (i->force_local) - (void) rm_rf(i->final_path, REMOVE_ROOT|REMOVE_PHYSICAL|REMOVE_SUBVOLUME); - - r = rename_noreplace(AT_FDCWD, i->temp_path, AT_FDCWD, i->final_path); - if (r < 0) - return log_error_errno(r, "Failed to move image into place: %m"); - - i->temp_path = mfree(i->temp_path); - - return 0; -} - -static int raw_import_open_disk(RawImport *i) { - int r; - - assert(i); - - assert(!i->final_path); - assert(!i->temp_path); - assert(i->output_fd < 0); - - i->final_path = strjoin(i->image_root, "/", i->local, ".raw", NULL); - if (!i->final_path) - return log_oom(); - - r = tempfn_random(i->final_path, NULL, &i->temp_path); - if (r < 0) - return log_oom(); - - (void) mkdir_parents_label(i->temp_path, 0700); - - i->output_fd = open(i->temp_path, O_RDWR|O_CREAT|O_EXCL|O_NOCTTY|O_CLOEXEC, 0664); - if (i->output_fd < 0) - return log_error_errno(errno, "Failed to open destination %s: %m", i->temp_path); - - r = chattr_fd(i->output_fd, FS_NOCOW_FL, FS_NOCOW_FL); - if (r < 0) - log_warning_errno(r, "Failed to set file attributes on %s: %m", i->temp_path); - - return 0; -} - -static int raw_import_try_reflink(RawImport *i) { - off_t p; - int r; - - assert(i); - assert(i->input_fd >= 0); - assert(i->output_fd >= 0); - - if (i->compress.type != IMPORT_COMPRESS_UNCOMPRESSED) - return 0; - - if (!S_ISREG(i->st.st_mode)) - return 0; - - p = lseek(i->input_fd, 0, SEEK_CUR); - if (p == (off_t) -1) - return log_error_errno(errno, "Failed to read file offset of input file: %m"); - - /* Let's only try a btrfs reflink, if we are reading from the beginning of the file */ - if ((uint64_t) p != (uint64_t) i->buffer_size) - return 0; - - r = btrfs_reflink(i->input_fd, i->output_fd); - if (r >= 0) - return 1; - - return 0; -} - -static int raw_import_write(const void *p, size_t sz, void *userdata) { - RawImport *i = userdata; - ssize_t n; - - if (i->grow_machine_directory && i->written_since_last_grow >= GROW_INTERVAL_BYTES) { - i->written_since_last_grow = 0; - grow_machine_directory(); - } - - n = sparse_write(i->output_fd, p, sz, 64); - if (n < 0) - return -errno; - if ((size_t) n < sz) - return -EIO; - - i->written_uncompressed += sz; - i->written_since_last_grow += sz; - - return 0; -} - -static int raw_import_process(RawImport *i) { - ssize_t l; - int r; - - assert(i); - assert(i->buffer_size < sizeof(i->buffer)); - - l = read(i->input_fd, i->buffer + i->buffer_size, sizeof(i->buffer) - i->buffer_size); - if (l < 0) { - if (errno == EAGAIN) - return 0; - - r = log_error_errno(errno, "Failed to read input file: %m"); - goto finish; - } - if (l == 0) { - if (i->compress.type == IMPORT_COMPRESS_UNKNOWN) { - log_error("Premature end of file: %m"); - r = -EIO; - goto finish; - } - - r = raw_import_finish(i); - goto finish; - } - - i->buffer_size += l; - - if (i->compress.type == IMPORT_COMPRESS_UNKNOWN) { - r = import_uncompress_detect(&i->compress, i->buffer, i->buffer_size); - if (r < 0) { - log_error("Failed to detect file compression: %m"); - goto finish; - } - if (r == 0) /* Need more data */ - return 0; - - r = raw_import_open_disk(i); - if (r < 0) - goto finish; - - r = raw_import_try_reflink(i); - if (r < 0) - goto finish; - if (r > 0) { - r = raw_import_finish(i); - goto finish; - } - } - - r = import_uncompress(&i->compress, i->buffer, i->buffer_size, raw_import_write, i); - if (r < 0) { - log_error_errno(r, "Failed to decode and write: %m"); - goto finish; - } - - i->written_compressed += i->buffer_size; - i->buffer_size = 0; - - raw_import_report_progress(i); - - return 0; - -finish: - if (i->on_finished) - i->on_finished(i, r, i->userdata); - else - sd_event_exit(i->event, r); - - return 0; -} - -static int raw_import_on_input(sd_event_source *s, int fd, uint32_t revents, void *userdata) { - RawImport *i = userdata; - - return raw_import_process(i); -} - -static int raw_import_on_defer(sd_event_source *s, void *userdata) { - RawImport *i = userdata; - - return raw_import_process(i); -} - -int raw_import_start(RawImport *i, int fd, const char *local, bool force_local, bool read_only) { - int r; - - assert(i); - assert(fd >= 0); - assert(local); - - if (!machine_name_is_valid(local)) - return -EINVAL; - - if (i->input_fd >= 0) - return -EBUSY; - - r = fd_nonblock(fd, true); - if (r < 0) - return r; - - r = free_and_strdup(&i->local, local); - if (r < 0) - return r; - i->force_local = force_local; - i->read_only = read_only; - - if (fstat(fd, &i->st) < 0) - return -errno; - - r = sd_event_add_io(i->event, &i->input_event_source, fd, EPOLLIN, raw_import_on_input, i); - if (r == -EPERM) { - /* This fd does not support epoll, for example because it is a regular file. Busy read in that case */ - r = sd_event_add_defer(i->event, &i->input_event_source, raw_import_on_defer, i); - if (r < 0) - return r; - - r = sd_event_source_set_enabled(i->input_event_source, SD_EVENT_ON); - } - if (r < 0) - return r; - - i->input_fd = fd; - return r; -} diff --git a/src/grp-import/systemd-import/import-raw.h b/src/grp-import/systemd-import/import-raw.h deleted file mode 100644 index a3bc532d37..0000000000 --- a/src/grp-import/systemd-import/import-raw.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 "shared/import-util.h" -#include "basic/macro.h" - -typedef struct RawImport RawImport; - -typedef void (*RawImportFinished)(RawImport *import, int error, void *userdata); - -int raw_import_new(RawImport **import, sd_event *event, const char *image_root, RawImportFinished on_finished, void *userdata); -RawImport* raw_import_unref(RawImport *import); - -DEFINE_TRIVIAL_CLEANUP_FUNC(RawImport*, raw_import_unref); - -int raw_import_start(RawImport *i, int fd, const char *local, bool force_local, bool read_only); diff --git a/src/grp-import/systemd-import/import-tar.c b/src/grp-import/systemd-import/import-tar.c deleted file mode 100644 index 8848beb11b..0000000000 --- a/src/grp-import/systemd-import/import-tar.c +++ /dev/null @@ -1,388 +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 "basic/alloc-util.h" -#include "basic/btrfs-util.h" -#include "basic/copy.h" -#include "basic/fd-util.h" -#include "basic/fileio.h" -#include "basic/fs-util.h" -#include "basic/hostname-util.h" -#include "import-common.h" -#include "import-compress.h" -#include "import-tar.h" -#include "basic/io-util.h" -#include "shared/machine-pool.h" -#include "basic/mkdir.h" -#include "basic/path-util.h" -#include "basic/process-util.h" -#include "qcow2-util.h" -#include "basic/ratelimit.h" -#include "basic/rm-rf.h" -#include "basic/string-util.h" -#include "basic/util.h" - -struct TarImport { - sd_event *event; - - char *image_root; - - TarImportFinished on_finished; - void *userdata; - - char *local; - bool force_local; - bool read_only; - bool grow_machine_directory; - - char *temp_path; - char *final_path; - - int input_fd; - int tar_fd; - - ImportCompress compress; - - uint64_t written_since_last_grow; - - sd_event_source *input_event_source; - - uint8_t buffer[16*1024]; - size_t buffer_size; - - uint64_t written_compressed; - uint64_t written_uncompressed; - - struct stat st; - - pid_t tar_pid; - - unsigned last_percent; - RateLimit progress_rate_limit; -}; - -TarImport* tar_import_unref(TarImport *i) { - if (!i) - return NULL; - - sd_event_source_unref(i->input_event_source); - - if (i->tar_pid > 1) { - (void) kill_and_sigcont(i->tar_pid, SIGKILL); - (void) wait_for_terminate(i->tar_pid, NULL); - } - - if (i->temp_path) { - (void) rm_rf(i->temp_path, REMOVE_ROOT|REMOVE_PHYSICAL|REMOVE_SUBVOLUME); - free(i->temp_path); - } - - import_compress_free(&i->compress); - - sd_event_unref(i->event); - - safe_close(i->tar_fd); - - free(i->final_path); - free(i->image_root); - free(i->local); - free(i); - - return NULL; -} - -int tar_import_new( - TarImport **ret, - sd_event *event, - const char *image_root, - TarImportFinished on_finished, - void *userdata) { - - _cleanup_(tar_import_unrefp) TarImport *i = NULL; - int r; - - assert(ret); - - i = new0(TarImport, 1); - if (!i) - return -ENOMEM; - - i->input_fd = i->tar_fd = -1; - i->on_finished = on_finished; - i->userdata = userdata; - - RATELIMIT_INIT(i->progress_rate_limit, 100 * USEC_PER_MSEC, 1); - i->last_percent = (unsigned) -1; - - i->image_root = strdup(image_root ?: "/var/lib/machines"); - if (!i->image_root) - return -ENOMEM; - - i->grow_machine_directory = path_startswith(i->image_root, "/var/lib/machines"); - - if (event) - i->event = sd_event_ref(event); - else { - r = sd_event_default(&i->event); - if (r < 0) - return r; - } - - *ret = i; - i = NULL; - - return 0; -} - -static void tar_import_report_progress(TarImport *i) { - unsigned percent; - assert(i); - - /* We have no size information, unless the source is a regular file */ - if (!S_ISREG(i->st.st_mode)) - return; - - if (i->written_compressed >= (uint64_t) i->st.st_size) - percent = 100; - else - percent = (unsigned) ((i->written_compressed * UINT64_C(100)) / (uint64_t) i->st.st_size); - - if (percent == i->last_percent) - return; - - if (!ratelimit_test(&i->progress_rate_limit)) - return; - - sd_notifyf(false, "X_IMPORT_PROGRESS=%u", percent); - log_info("Imported %u%%.", percent); - - i->last_percent = percent; -} - -static int tar_import_finish(TarImport *i) { - int r; - - assert(i); - assert(i->tar_fd >= 0); - assert(i->temp_path); - assert(i->final_path); - - i->tar_fd = safe_close(i->tar_fd); - - if (i->tar_pid > 0) { - r = wait_for_terminate_and_warn("tar", i->tar_pid, true); - i->tar_pid = 0; - if (r < 0) - return r; - } - - if (i->read_only) { - r = import_make_read_only(i->temp_path); - if (r < 0) - return r; - } - - if (i->force_local) - (void) rm_rf(i->final_path, REMOVE_ROOT|REMOVE_PHYSICAL|REMOVE_SUBVOLUME); - - r = rename_noreplace(AT_FDCWD, i->temp_path, AT_FDCWD, i->final_path); - if (r < 0) - return log_error_errno(r, "Failed to move image into place: %m"); - - i->temp_path = mfree(i->temp_path); - - return 0; -} - -static int tar_import_fork_tar(TarImport *i) { - int r; - - assert(i); - - assert(!i->final_path); - assert(!i->temp_path); - assert(i->tar_fd < 0); - - i->final_path = strjoin(i->image_root, "/", i->local, NULL); - if (!i->final_path) - return log_oom(); - - r = tempfn_random(i->final_path, NULL, &i->temp_path); - if (r < 0) - return log_oom(); - - (void) mkdir_parents_label(i->temp_path, 0700); - - r = btrfs_subvol_make(i->temp_path); - if (r == -ENOTTY) { - if (mkdir(i->temp_path, 0755) < 0) - return log_error_errno(errno, "Failed to create directory %s: %m", i->temp_path); - } else if (r < 0) - return log_error_errno(r, "Failed to create subvolume %s: %m", i->temp_path); - else - (void) import_assign_pool_quota_and_warn(i->temp_path); - - i->tar_fd = import_fork_tar_x(i->temp_path, &i->tar_pid); - if (i->tar_fd < 0) - return i->tar_fd; - - return 0; -} - -static int tar_import_write(const void *p, size_t sz, void *userdata) { - TarImport *i = userdata; - int r; - - if (i->grow_machine_directory && i->written_since_last_grow >= GROW_INTERVAL_BYTES) { - i->written_since_last_grow = 0; - grow_machine_directory(); - } - - r = loop_write(i->tar_fd, p, sz, false); - if (r < 0) - return r; - - i->written_uncompressed += sz; - i->written_since_last_grow += sz; - - return 0; -} - -static int tar_import_process(TarImport *i) { - ssize_t l; - int r; - - assert(i); - assert(i->buffer_size < sizeof(i->buffer)); - - l = read(i->input_fd, i->buffer + i->buffer_size, sizeof(i->buffer) - i->buffer_size); - if (l < 0) { - if (errno == EAGAIN) - return 0; - - r = log_error_errno(errno, "Failed to read input file: %m"); - goto finish; - } - if (l == 0) { - if (i->compress.type == IMPORT_COMPRESS_UNKNOWN) { - log_error("Premature end of file: %m"); - r = -EIO; - goto finish; - } - - r = tar_import_finish(i); - goto finish; - } - - i->buffer_size += l; - - if (i->compress.type == IMPORT_COMPRESS_UNKNOWN) { - r = import_uncompress_detect(&i->compress, i->buffer, i->buffer_size); - if (r < 0) { - log_error("Failed to detect file compression: %m"); - goto finish; - } - if (r == 0) /* Need more data */ - return 0; - - r = tar_import_fork_tar(i); - if (r < 0) - goto finish; - } - - r = import_uncompress(&i->compress, i->buffer, i->buffer_size, tar_import_write, i); - if (r < 0) { - log_error_errno(r, "Failed to decode and write: %m"); - goto finish; - } - - i->written_compressed += i->buffer_size; - i->buffer_size = 0; - - tar_import_report_progress(i); - - return 0; - -finish: - if (i->on_finished) - i->on_finished(i, r, i->userdata); - else - sd_event_exit(i->event, r); - - return 0; -} - -static int tar_import_on_input(sd_event_source *s, int fd, uint32_t revents, void *userdata) { - TarImport *i = userdata; - - return tar_import_process(i); -} - -static int tar_import_on_defer(sd_event_source *s, void *userdata) { - TarImport *i = userdata; - - return tar_import_process(i); -} - -int tar_import_start(TarImport *i, int fd, const char *local, bool force_local, bool read_only) { - int r; - - assert(i); - assert(fd >= 0); - assert(local); - - if (!machine_name_is_valid(local)) - return -EINVAL; - - if (i->input_fd >= 0) - return -EBUSY; - - r = fd_nonblock(fd, true); - if (r < 0) - return r; - - r = free_and_strdup(&i->local, local); - if (r < 0) - return r; - i->force_local = force_local; - i->read_only = read_only; - - if (fstat(fd, &i->st) < 0) - return -errno; - - r = sd_event_add_io(i->event, &i->input_event_source, fd, EPOLLIN, tar_import_on_input, i); - if (r == -EPERM) { - /* This fd does not support epoll, for example because it is a regular file. Busy read in that case */ - r = sd_event_add_defer(i->event, &i->input_event_source, tar_import_on_defer, i); - if (r < 0) - return r; - - r = sd_event_source_set_enabled(i->input_event_source, SD_EVENT_ON); - } - if (r < 0) - return r; - - i->input_fd = fd; - return r; -} diff --git a/src/grp-import/systemd-import/import-tar.h b/src/grp-import/systemd-import/import-tar.h deleted file mode 100644 index 90ab082f1a..0000000000 --- a/src/grp-import/systemd-import/import-tar.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 "shared/import-util.h" -#include "basic/macro.h" - -typedef struct TarImport TarImport; - -typedef void (*TarImportFinished)(TarImport *import, int error, void *userdata); - -int tar_import_new(TarImport **import, sd_event *event, const char *image_root, TarImportFinished on_finished, void *userdata); -TarImport* tar_import_unref(TarImport *import); - -DEFINE_TRIVIAL_CLEANUP_FUNC(TarImport*, tar_import_unref); - -int tar_import_start(TarImport *import, int fd, const char *local, bool force_local, bool read_only); diff --git a/src/grp-import/systemd-import/import.c b/src/grp-import/systemd-import/import.c deleted file mode 100644 index 6c76c639c3..0000000000 --- a/src/grp-import/systemd-import/import.c +++ /dev/null @@ -1,337 +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 "basic/alloc-util.h" -#include "basic/fd-util.h" -#include "basic/fs-util.h" -#include "basic/hostname-util.h" -#include "import-raw.h" -#include "import-tar.h" -#include "shared/import-util.h" -#include "shared/machine-image.h" -#include "basic/signal-util.h" -#include "basic/string-util.h" -#include "basic/verbs.h" - -static bool arg_force = false; -static bool arg_read_only = false; -static const char *arg_image_root = "/var/lib/machines"; - -static int interrupt_signal_handler(sd_event_source *s, const struct signalfd_siginfo *si, void *userdata) { - log_notice("Transfer aborted."); - sd_event_exit(sd_event_source_get_event(s), EINTR); - return 0; -} - -static void on_tar_finished(TarImport *import, int error, void *userdata) { - sd_event *event = userdata; - assert(import); - - if (error == 0) - log_info("Operation completed successfully."); - - sd_event_exit(event, abs(error)); -} - -static int import_tar(int argc, char *argv[], void *userdata) { - _cleanup_(tar_import_unrefp) TarImport *import = NULL; - _cleanup_(sd_event_unrefp) sd_event *event = NULL; - const char *path = NULL, *local = NULL; - _cleanup_free_ char *ll = NULL; - _cleanup_close_ int open_fd = -1; - int r, fd; - - 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) { - r = tar_strip_suffixes(local, &ll); - if (r < 0) - return log_oom(); - - local = ll; - - if (!machine_name_is_valid(local)) { - log_error("Local image name '%s' is not valid.", local); - return -EINVAL; - } - - if (!arg_force) { - r = image_find(local, NULL); - if (r < 0) - return log_error_errno(r, "Failed to check whether image '%s' exists: %m", local); - else if (r > 0) { - log_error_errno(EEXIST, "Image '%s' already exists.", local); - return -EEXIST; - } - } - } else - local = "imported"; - - if (path) { - open_fd = open(path, O_RDONLY|O_CLOEXEC|O_NOCTTY); - if (open_fd < 0) - return log_error_errno(errno, "Failed to open tar image to import: %m"); - - fd = open_fd; - - log_info("Importing '%s', saving as '%s'.", path, local); - } else { - _cleanup_free_ char *pretty = NULL; - - fd = STDIN_FILENO; - - (void) readlink_malloc("/proc/self/fd/0", &pretty); - log_info("Importing '%s', saving as '%s'.", strna(pretty), local); - } - - r = sd_event_default(&event); - if (r < 0) - return log_error_errno(r, "Failed to allocate event loop: %m"); - - assert_se(sigprocmask_many(SIG_BLOCK, NULL, SIGTERM, SIGINT, -1) >= 0); - (void) sd_event_add_signal(event, NULL, SIGTERM, interrupt_signal_handler, NULL); - (void) sd_event_add_signal(event, NULL, SIGINT, interrupt_signal_handler, NULL); - - r = tar_import_new(&import, event, arg_image_root, on_tar_finished, event); - if (r < 0) - return log_error_errno(r, "Failed to allocate importer: %m"); - - r = tar_import_start(import, fd, local, arg_force, arg_read_only); - if (r < 0) - return log_error_errno(r, "Failed to import image: %m"); - - r = sd_event_loop(event); - if (r < 0) - return log_error_errno(r, "Failed to run event loop: %m"); - - log_info("Exiting."); - return -r; -} - -static void on_raw_finished(RawImport *import, int error, void *userdata) { - sd_event *event = userdata; - assert(import); - - if (error == 0) - log_info("Operation completed successfully."); - - sd_event_exit(event, abs(error)); -} - -static int import_raw(int argc, char *argv[], void *userdata) { - _cleanup_(raw_import_unrefp) RawImport *import = NULL; - _cleanup_(sd_event_unrefp) sd_event *event = NULL; - const char *path = NULL, *local = NULL; - _cleanup_free_ char *ll = NULL; - _cleanup_close_ int open_fd = -1; - int r, fd; - - 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) { - r = raw_strip_suffixes(local, &ll); - if (r < 0) - return log_oom(); - - local = ll; - - if (!machine_name_is_valid(local)) { - log_error("Local image name '%s' is not valid.", local); - return -EINVAL; - } - - if (!arg_force) { - r = image_find(local, NULL); - if (r < 0) - return log_error_errno(r, "Failed to check whether image '%s' exists: %m", local); - else if (r > 0) { - log_error_errno(EEXIST, "Image '%s' already exists.", local); - return -EEXIST; - } - } - } else - local = "imported"; - - if (path) { - open_fd = open(path, O_RDONLY|O_CLOEXEC|O_NOCTTY); - if (open_fd < 0) - return log_error_errno(errno, "Failed to open raw image to import: %m"); - - fd = open_fd; - - log_info("Importing '%s', saving as '%s'.", path, local); - } else { - _cleanup_free_ char *pretty = NULL; - - fd = STDIN_FILENO; - - (void) readlink_malloc("/proc/self/fd/0", &pretty); - log_info("Importing '%s', saving as '%s'.", pretty, local); - } - - r = sd_event_default(&event); - if (r < 0) - return log_error_errno(r, "Failed to allocate event loop: %m"); - - assert_se(sigprocmask_many(SIG_BLOCK, NULL, SIGTERM, SIGINT, -1) >= 0); - (void) sd_event_add_signal(event, NULL, SIGTERM, interrupt_signal_handler, NULL); - (void) sd_event_add_signal(event, NULL, SIGINT, interrupt_signal_handler, NULL); - - r = raw_import_new(&import, event, arg_image_root, on_raw_finished, event); - if (r < 0) - return log_error_errno(r, "Failed to allocate importer: %m"); - - r = raw_import_start(import, fd, local, arg_force, arg_read_only); - if (r < 0) - return log_error_errno(r, "Failed to import image: %m"); - - r = sd_event_loop(event); - if (r < 0) - return log_error_errno(r, "Failed to run event loop: %m"); - - log_info("Exiting."); - return -r; -} - -static int help(int argc, char *argv[], void *userdata) { - - printf("%s [OPTIONS...] {COMMAND} ...\n\n" - "Import container or virtual machine images.\n\n" - " -h --help Show this help\n" - " --version Show package version\n" - " --force Force creation of image\n" - " --image-root=PATH Image root directory\n" - " --read-only Create a read-only image\n\n" - "Commands:\n" - " tar FILE [NAME] Import a TAR image\n" - " raw FILE [NAME] Import a RAW image\n", - program_invocation_short_name); - - return 0; -} - -static int parse_argv(int argc, char *argv[]) { - - enum { - ARG_VERSION = 0x100, - ARG_FORCE, - ARG_IMAGE_ROOT, - ARG_READ_ONLY, - }; - - static const struct option options[] = { - { "help", no_argument, NULL, 'h' }, - { "version", no_argument, NULL, ARG_VERSION }, - { "force", no_argument, NULL, ARG_FORCE }, - { "image-root", required_argument, NULL, ARG_IMAGE_ROOT }, - { "read-only", no_argument, NULL, ARG_READ_ONLY }, - {} - }; - - int c; - - assert(argc >= 0); - assert(argv); - - while ((c = getopt_long(argc, argv, "h", options, NULL)) >= 0) - - switch (c) { - - case 'h': - return help(0, NULL, NULL); - - case ARG_VERSION: - return version(); - - case ARG_FORCE: - arg_force = true; - break; - - case ARG_IMAGE_ROOT: - arg_image_root = optarg; - break; - - case ARG_READ_ONLY: - arg_read_only = true; - break; - - case '?': - return -EINVAL; - - default: - assert_not_reached("Unhandled option"); - } - - return 1; -} - -static int import_main(int argc, char *argv[]) { - - static const Verb verbs[] = { - { "help", VERB_ANY, VERB_ANY, 0, help }, - { "tar", 2, 3, 0, import_tar }, - { "raw", 2, 3, 0, import_raw }, - {} - }; - - return dispatch_verb(argc, argv, verbs, NULL); -} - -int main(int argc, char *argv[]) { - int r; - - setlocale(LC_ALL, ""); - log_parse_environment(); - log_open(); - - r = parse_argv(argc, argv); - if (r <= 0) - goto finish; - - (void) ignore_signals(SIGPIPE, -1); - - r = import_main(argc, argv); - -finish: - return r < 0 ? EXIT_FAILURE : EXIT_SUCCESS; -} diff --git a/src/grp-import/systemd-importd/Makefile b/src/grp-import/systemd-importd/Makefile deleted file mode 100644 index 7911bfce82..0000000000 --- a/src/grp-import/systemd-importd/Makefile +++ /dev/null @@ -1,39 +0,0 @@ -# -*- 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-importd -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 - -include $(topsrcdir)/build-aux/Makefile.tail.mk diff --git a/src/grp-import/systemd-importd/importd.c b/src/grp-import/systemd-importd/importd.c deleted file mode 100644 index f9fe1038cd..0000000000 --- a/src/grp-import/systemd-importd/importd.c +++ /dev/null @@ -1,1221 +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 "basic/alloc-util.h" -#include "bus-common-errors.h" -#include "shared/bus-util.h" -#include "basic/def.h" -#include "basic/fd-util.h" -#include "basic/hostname-util.h" -#include "shared/import-util.h" -#include "shared/machine-pool.h" -#include "basic/missing.h" -#include "basic/mkdir.h" -#include "basic/parse-util.h" -#include "basic/path-util.h" -#include "basic/process-util.h" -#include "basic/signal-util.h" -#include "basic/socket-util.h" -#include "basic/string-table.h" -#include "basic/strv.h" -#include "basic/syslog-util.h" -#include "basic/user-util.h" -#include "basic/util.h" -#include "basic/web-util.h" - -typedef struct Transfer Transfer; -typedef struct Manager Manager; - -typedef enum TransferType { - TRANSFER_IMPORT_TAR, - TRANSFER_IMPORT_RAW, - TRANSFER_EXPORT_TAR, - TRANSFER_EXPORT_RAW, - TRANSFER_PULL_TAR, - TRANSFER_PULL_RAW, - _TRANSFER_TYPE_MAX, - _TRANSFER_TYPE_INVALID = -1, -} TransferType; - -struct Transfer { - Manager *manager; - - uint32_t id; - char *object_path; - - TransferType type; - ImportVerify verify; - - char *remote; - char *local; - bool force_local; - bool read_only; - - char *format; - - pid_t pid; - - int log_fd; - - char log_message[LINE_MAX]; - size_t log_message_size; - - sd_event_source *pid_event_source; - sd_event_source *log_event_source; - - unsigned n_canceled; - unsigned progress_percent; - - int stdin_fd; - int stdout_fd; -}; - -struct Manager { - sd_event *event; - sd_bus *bus; - - uint32_t current_transfer_id; - Hashmap *transfers; - - Hashmap *polkit_registry; - - int notify_fd; - - sd_event_source *notify_event_source; -}; - -#define TRANSFERS_MAX 64 - -static const char* const transfer_type_table[_TRANSFER_TYPE_MAX] = { - [TRANSFER_IMPORT_TAR] = "import-tar", - [TRANSFER_IMPORT_RAW] = "import-raw", - [TRANSFER_EXPORT_TAR] = "export-tar", - [TRANSFER_EXPORT_RAW] = "export-raw", - [TRANSFER_PULL_TAR] = "pull-tar", - [TRANSFER_PULL_RAW] = "pull-raw", -}; - -DEFINE_PRIVATE_STRING_TABLE_LOOKUP_TO_STRING(transfer_type, TransferType); - -static Transfer *transfer_unref(Transfer *t) { - if (!t) - return NULL; - - if (t->manager) - hashmap_remove(t->manager->transfers, UINT32_TO_PTR(t->id)); - - sd_event_source_unref(t->pid_event_source); - sd_event_source_unref(t->log_event_source); - - free(t->remote); - free(t->local); - free(t->format); - free(t->object_path); - - if (t->pid > 0) { - (void) kill_and_sigcont(t->pid, SIGKILL); - (void) wait_for_terminate(t->pid, NULL); - } - - safe_close(t->log_fd); - safe_close(t->stdin_fd); - safe_close(t->stdout_fd); - - free(t); - return NULL; -} - -DEFINE_TRIVIAL_CLEANUP_FUNC(Transfer*, transfer_unref); - -static int transfer_new(Manager *m, Transfer **ret) { - _cleanup_(transfer_unrefp) Transfer *t = NULL; - uint32_t id; - int r; - - assert(m); - assert(ret); - - if (hashmap_size(m->transfers) >= TRANSFERS_MAX) - return -E2BIG; - - r = hashmap_ensure_allocated(&m->transfers, &trivial_hash_ops); - if (r < 0) - return r; - - t = new0(Transfer, 1); - if (!t) - return -ENOMEM; - - t->type = _TRANSFER_TYPE_INVALID; - t->log_fd = -1; - t->stdin_fd = -1; - t->stdout_fd = -1; - t->verify = _IMPORT_VERIFY_INVALID; - - id = m->current_transfer_id + 1; - - if (asprintf(&t->object_path, "/org/freedesktop/import1/transfer/_%" PRIu32, id) < 0) - return -ENOMEM; - - r = hashmap_put(m->transfers, UINT32_TO_PTR(id), t); - if (r < 0) - return r; - - m->current_transfer_id = id; - - t->manager = m; - t->id = id; - - *ret = t; - t = NULL; - - return 0; -} - -static void transfer_send_log_line(Transfer *t, const char *line) { - int r, priority = LOG_INFO; - - assert(t); - assert(line); - - syslog_parse_priority(&line, &priority, true); - - log_full(priority, "(transfer%" PRIu32 ") %s", t->id, line); - - r = sd_bus_emit_signal( - t->manager->bus, - t->object_path, - "org.freedesktop.import1.Transfer", - "LogMessage", - "us", - priority, - line); - if (r < 0) - log_error_errno(r, "Cannot emit message: %m"); - } - -static void transfer_send_logs(Transfer *t, bool flush) { - assert(t); - - /* Try to send out all log messages, if we can. But if we - * can't we remove the messages from the buffer, but don't - * fail */ - - while (t->log_message_size > 0) { - _cleanup_free_ char *n = NULL; - char *e; - - if (t->log_message_size >= sizeof(t->log_message)) - e = t->log_message + sizeof(t->log_message); - else { - char *a, *b; - - a = memchr(t->log_message, 0, t->log_message_size); - b = memchr(t->log_message, '\n', t->log_message_size); - - if (a && b) - e = a < b ? a : b; - else if (a) - e = a; - else - e = b; - } - - if (!e) { - if (!flush) - return; - - e = t->log_message + t->log_message_size; - } - - n = strndup(t->log_message, e - t->log_message); - - /* Skip over NUL and newlines */ - while ((e < t->log_message + t->log_message_size) && (*e == 0 || *e == '\n')) - e++; - - memmove(t->log_message, e, t->log_message + sizeof(t->log_message) - e); - t->log_message_size -= e - t->log_message; - - if (!n) { - log_oom(); - continue; - } - - if (isempty(n)) - continue; - - transfer_send_log_line(t, n); - } -} - -static int transfer_finalize(Transfer *t, bool success) { - int r; - - assert(t); - - transfer_send_logs(t, true); - - r = sd_bus_emit_signal( - t->manager->bus, - "/org/freedesktop/import1", - "org.freedesktop.import1.Manager", - "TransferRemoved", - "uos", - t->id, - t->object_path, - success ? "done" : - t->n_canceled > 0 ? "canceled" : "failed"); - - if (r < 0) - log_error_errno(r, "Cannot emit message: %m"); - - transfer_unref(t); - return 0; -} - -static int transfer_cancel(Transfer *t) { - int r; - - assert(t); - - r = kill_and_sigcont(t->pid, t->n_canceled < 3 ? SIGTERM : SIGKILL); - if (r < 0) - return r; - - t->n_canceled++; - return 0; -} - -static int transfer_on_pid(sd_event_source *s, const siginfo_t *si, void *userdata) { - Transfer *t = userdata; - bool success = false; - - assert(s); - assert(t); - - if (si->si_code == CLD_EXITED) { - if (si->si_status != 0) - log_error("Import process failed with exit code %i.", si->si_status); - else { - log_debug("Import process succeeded."); - success = true; - } - - } else if (si->si_code == CLD_KILLED || - si->si_code == CLD_DUMPED) - - log_error("Import process terminated by signal %s.", signal_to_string(si->si_status)); - else - log_error("Import process failed due to unknown reason."); - - t->pid = 0; - - return transfer_finalize(t, success); -} - -static int transfer_on_log(sd_event_source *s, int fd, uint32_t revents, void *userdata) { - Transfer *t = userdata; - ssize_t l; - - assert(s); - assert(t); - - l = read(fd, t->log_message + t->log_message_size, sizeof(t->log_message) - t->log_message_size); - if (l <= 0) { - /* EOF/read error. We just close the pipe here, and - * close the watch, waiting for the SIGCHLD to arrive, - * before we do anything else. */ - - if (l < 0) - log_error_errno(errno, "Failed to read log message: %m"); - - t->log_event_source = sd_event_source_unref(t->log_event_source); - return 0; - } - - t->log_message_size += l; - - transfer_send_logs(t, false); - - return 0; -} - -static int transfer_start(Transfer *t) { - _cleanup_close_pair_ int pipefd[2] = { -1, -1 }; - int r; - - assert(t); - assert(t->pid <= 0); - - if (pipe2(pipefd, O_CLOEXEC) < 0) - return -errno; - - t->pid = fork(); - if (t->pid < 0) - return -errno; - if (t->pid == 0) { - const char *cmd[] = { - NULL, /* systemd-import, systemd-export or systemd-pull */ - NULL, /* tar, raw */ - NULL, /* --verify= */ - NULL, /* verify argument */ - NULL, /* maybe --force */ - NULL, /* maybe --read-only */ - NULL, /* if so: the actual URL */ - NULL, /* maybe --format= */ - NULL, /* if so: the actual format */ - NULL, /* remote */ - NULL, /* local */ - NULL - }; - unsigned k = 0; - - /* Child */ - - (void) reset_all_signal_handlers(); - (void) reset_signal_mask(); - assert_se(prctl(PR_SET_PDEATHSIG, SIGTERM) == 0); - - pipefd[0] = safe_close(pipefd[0]); - - if (dup2(pipefd[1], STDERR_FILENO) != STDERR_FILENO) { - log_error_errno(errno, "Failed to dup2() fd: %m"); - _exit(EXIT_FAILURE); - } - - if (t->stdout_fd >= 0) { - if (dup2(t->stdout_fd, STDOUT_FILENO) != STDOUT_FILENO) { - log_error_errno(errno, "Failed to dup2() fd: %m"); - _exit(EXIT_FAILURE); - } - - if (t->stdout_fd != STDOUT_FILENO) - safe_close(t->stdout_fd); - } else { - if (dup2(pipefd[1], STDOUT_FILENO) != STDOUT_FILENO) { - log_error_errno(errno, "Failed to dup2() fd: %m"); - _exit(EXIT_FAILURE); - } - } - - if (pipefd[1] != STDOUT_FILENO && pipefd[1] != STDERR_FILENO) - pipefd[1] = safe_close(pipefd[1]); - - if (t->stdin_fd >= 0) { - if (dup2(t->stdin_fd, STDIN_FILENO) != STDIN_FILENO) { - log_error_errno(errno, "Failed to dup2() fd: %m"); - _exit(EXIT_FAILURE); - } - - if (t->stdin_fd != STDIN_FILENO) - safe_close(t->stdin_fd); - } else { - int null_fd; - - null_fd = open("/dev/null", O_RDONLY|O_NOCTTY); - if (null_fd < 0) { - log_error_errno(errno, "Failed to open /dev/null: %m"); - _exit(EXIT_FAILURE); - } - - if (dup2(null_fd, STDIN_FILENO) != STDIN_FILENO) { - log_error_errno(errno, "Failed to dup2() fd: %m"); - _exit(EXIT_FAILURE); - } - - if (null_fd != STDIN_FILENO) - safe_close(null_fd); - } - - fd_cloexec(STDIN_FILENO, false); - fd_cloexec(STDOUT_FILENO, false); - fd_cloexec(STDERR_FILENO, false); - - setenv("SYSTEMD_LOG_TARGET", "console-prefixed", 1); - setenv("NOTIFY_SOCKET", "/run/systemd/import/notify", 1); - - if (IN_SET(t->type, TRANSFER_IMPORT_TAR, TRANSFER_IMPORT_RAW)) - cmd[k++] = SYSTEMD_IMPORT_PATH; - else if (IN_SET(t->type, TRANSFER_EXPORT_TAR, TRANSFER_EXPORT_RAW)) - cmd[k++] = SYSTEMD_EXPORT_PATH; - else - cmd[k++] = SYSTEMD_PULL_PATH; - - if (IN_SET(t->type, TRANSFER_IMPORT_TAR, TRANSFER_EXPORT_TAR, TRANSFER_PULL_TAR)) - cmd[k++] = "tar"; - else - cmd[k++] = "raw"; - - if (t->verify != _IMPORT_VERIFY_INVALID) { - cmd[k++] = "--verify"; - cmd[k++] = import_verify_to_string(t->verify); - } - - if (t->force_local) - cmd[k++] = "--force"; - if (t->read_only) - cmd[k++] = "--read-only"; - - if (t->format) { - cmd[k++] = "--format"; - cmd[k++] = t->format; - } - - if (!IN_SET(t->type, TRANSFER_EXPORT_TAR, TRANSFER_EXPORT_RAW)) { - if (t->remote) - cmd[k++] = t->remote; - else - cmd[k++] = "-"; - } - - if (t->local) - cmd[k++] = t->local; - cmd[k] = NULL; - - execv(cmd[0], (char * const *) cmd); - log_error_errno(errno, "Failed to execute %s tool: %m", cmd[0]); - _exit(EXIT_FAILURE); - } - - pipefd[1] = safe_close(pipefd[1]); - t->log_fd = pipefd[0]; - pipefd[0] = -1; - - t->stdin_fd = safe_close(t->stdin_fd); - - r = sd_event_add_child(t->manager->event, &t->pid_event_source, t->pid, WEXITED, transfer_on_pid, t); - if (r < 0) - return r; - - r = sd_event_add_io(t->manager->event, &t->log_event_source, t->log_fd, EPOLLIN, transfer_on_log, t); - if (r < 0) - return r; - - /* Make sure always process logging before SIGCHLD */ - r = sd_event_source_set_priority(t->log_event_source, SD_EVENT_PRIORITY_NORMAL -5); - if (r < 0) - return r; - - r = sd_bus_emit_signal( - t->manager->bus, - "/org/freedesktop/import1", - "org.freedesktop.import1.Manager", - "TransferNew", - "uo", - t->id, - t->object_path); - if (r < 0) - return r; - - return 0; -} - -static Manager *manager_unref(Manager *m) { - Transfer *t; - - if (!m) - return NULL; - - sd_event_source_unref(m->notify_event_source); - safe_close(m->notify_fd); - - while ((t = hashmap_first(m->transfers))) - transfer_unref(t); - - hashmap_free(m->transfers); - - bus_verify_polkit_async_registry_free(m->polkit_registry); - - m->bus = sd_bus_flush_close_unref(m->bus); - sd_event_unref(m->event); - - free(m); - return NULL; -} - -DEFINE_TRIVIAL_CLEANUP_FUNC(Manager*, manager_unref); - -static int manager_on_notify(sd_event_source *s, int fd, uint32_t revents, void *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 ucred *ucred = NULL; - Manager *m = userdata; - struct cmsghdr *cmsg; - unsigned percent; - char *p, *e; - Transfer *t; - Iterator i; - ssize_t n; - int r; - - n = recvmsg(fd, &msghdr, MSG_DONTWAIT|MSG_CMSG_CLOEXEC); - if (n < 0) { - if (errno == EAGAIN || errno == EINTR) - return 0; - - return -errno; - } - - cmsg_close_all(&msghdr); - - CMSG_FOREACH(cmsg, &msghdr) - if (cmsg->cmsg_level == SOL_SOCKET && - cmsg->cmsg_type == SCM_CREDENTIALS && - cmsg->cmsg_len == CMSG_LEN(sizeof(struct ucred))) - ucred = (struct ucred*) CMSG_DATA(cmsg); - - if (msghdr.msg_flags & MSG_TRUNC) { - log_warning("Got overly long notification datagram, ignoring."); - return 0; - } - - if (!ucred || ucred->pid <= 0) { - log_warning("Got notification datagram lacking credential information, ignoring."); - return 0; - } - - HASHMAP_FOREACH(t, m->transfers, i) - if (ucred->pid == t->pid) - break; - - if (!t) { - log_warning("Got notification datagram from unexpected peer, ignoring."); - return 0; - } - - buf[n] = 0; - - p = startswith(buf, "X_IMPORT_PROGRESS="); - if (!p) { - p = strstr(buf, "\nX_IMPORT_PROGRESS="); - if (!p) - return 0; - - p += 19; - } - - e = strchrnul(p, '\n'); - *e = 0; - - r = safe_atou(p, &percent); - if (r < 0 || percent > 100) { - log_warning("Got invalid percent value, ignoring."); - return 0; - } - - t->progress_percent = percent; - - log_debug("Got percentage from client: %u%%", percent); - return 0; -} - -static int manager_new(Manager **ret) { - _cleanup_(manager_unrefp) Manager *m = NULL; - static const union sockaddr_union sa = { - .un.sun_family = AF_UNIX, - .un.sun_path = "/run/systemd/import/notify", - }; - static const int one = 1; - int r; - - assert(ret); - - m = new0(Manager, 1); - if (!m) - return -ENOMEM; - - r = sd_event_default(&m->event); - if (r < 0) - return r; - - sd_event_set_watchdog(m->event, true); - - r = sd_bus_default_system(&m->bus); - if (r < 0) - return r; - - m->notify_fd = socket(AF_UNIX, SOCK_DGRAM|SOCK_CLOEXEC|SOCK_NONBLOCK, 0); - if (m->notify_fd < 0) - return -errno; - - (void) mkdir_parents_label(sa.un.sun_path, 0755); - (void) unlink(sa.un.sun_path); - - if (bind(m->notify_fd, &sa.sa, SOCKADDR_UN_LEN(sa.un)) < 0) - return -errno; - - if (setsockopt(m->notify_fd, SOL_SOCKET, SO_PASSCRED, &one, sizeof(one)) < 0) - return -errno; - - r = sd_event_add_io(m->event, &m->notify_event_source, m->notify_fd, EPOLLIN, manager_on_notify, m); - if (r < 0) - return r; - - *ret = m; - m = NULL; - - return 0; -} - -static Transfer *manager_find(Manager *m, TransferType type, const char *remote) { - Transfer *t; - Iterator i; - - assert(m); - assert(type >= 0); - assert(type < _TRANSFER_TYPE_MAX); - - HASHMAP_FOREACH(t, m->transfers, i) { - - if (t->type == type && - streq_ptr(t->remote, remote)) - return t; - } - - return NULL; -} - -static int method_import_tar_or_raw(sd_bus_message *msg, void *userdata, sd_bus_error *error) { - _cleanup_(transfer_unrefp) Transfer *t = NULL; - int fd, force, read_only, r; - const char *local, *object; - Manager *m = userdata; - TransferType type; - uint32_t id; - - assert(msg); - assert(m); - - r = bus_verify_polkit_async( - msg, - CAP_SYS_ADMIN, - "org.freedesktop.import1.import", - NULL, - false, - UID_INVALID, - &m->polkit_registry, - error); - if (r < 0) - return r; - if (r == 0) - return 1; /* Will call us back */ - - r = sd_bus_message_read(msg, "hsbb", &fd, &local, &force, &read_only); - if (r < 0) - return r; - - if (!machine_name_is_valid(local)) - return sd_bus_error_setf(error, SD_BUS_ERROR_INVALID_ARGS, "Local name %s is invalid", local); - - r = setup_machine_directory((uint64_t) -1, error); - if (r < 0) - return r; - - type = streq_ptr(sd_bus_message_get_member(msg), "ImportTar") ? TRANSFER_IMPORT_TAR : TRANSFER_IMPORT_RAW; - - r = transfer_new(m, &t); - if (r < 0) - return r; - - t->type = type; - t->force_local = force; - t->read_only = read_only; - - t->local = strdup(local); - if (!t->local) - return -ENOMEM; - - t->stdin_fd = fcntl(fd, F_DUPFD_CLOEXEC, 3); - if (t->stdin_fd < 0) - return -errno; - - r = transfer_start(t); - if (r < 0) - return r; - - object = t->object_path; - id = t->id; - t = NULL; - - return sd_bus_reply_method_return(msg, "uo", id, object); -} - -static int method_export_tar_or_raw(sd_bus_message *msg, void *userdata, sd_bus_error *error) { - _cleanup_(transfer_unrefp) Transfer *t = NULL; - int fd, r; - const char *local, *object, *format; - Manager *m = userdata; - TransferType type; - uint32_t id; - - assert(msg); - assert(m); - - r = bus_verify_polkit_async( - msg, - CAP_SYS_ADMIN, - "org.freedesktop.import1.export", - NULL, - false, - UID_INVALID, - &m->polkit_registry, - error); - if (r < 0) - return r; - if (r == 0) - return 1; /* Will call us back */ - - r = sd_bus_message_read(msg, "shs", &local, &fd, &format); - if (r < 0) - return r; - - if (!machine_name_is_valid(local)) - return sd_bus_error_setf(error, SD_BUS_ERROR_INVALID_ARGS, "Local name %s is invalid", local); - - type = streq_ptr(sd_bus_message_get_member(msg), "ExportTar") ? TRANSFER_EXPORT_TAR : TRANSFER_EXPORT_RAW; - - r = transfer_new(m, &t); - if (r < 0) - return r; - - t->type = type; - - if (!isempty(format)) { - t->format = strdup(format); - if (!t->format) - return -ENOMEM; - } - - t->local = strdup(local); - if (!t->local) - return -ENOMEM; - - t->stdout_fd = fcntl(fd, F_DUPFD_CLOEXEC, 3); - if (t->stdout_fd < 0) - return -errno; - - r = transfer_start(t); - if (r < 0) - return r; - - object = t->object_path; - id = t->id; - t = NULL; - - return sd_bus_reply_method_return(msg, "uo", id, object); -} - -static int method_pull_tar_or_raw(sd_bus_message *msg, void *userdata, sd_bus_error *error) { - _cleanup_(transfer_unrefp) Transfer *t = NULL; - const char *remote, *local, *verify, *object; - Manager *m = userdata; - ImportVerify v; - TransferType type; - int force, r; - uint32_t id; - - assert(msg); - assert(m); - - r = bus_verify_polkit_async( - msg, - CAP_SYS_ADMIN, - "org.freedesktop.import1.pull", - NULL, - false, - UID_INVALID, - &m->polkit_registry, - error); - if (r < 0) - return r; - if (r == 0) - return 1; /* Will call us back */ - - r = sd_bus_message_read(msg, "sssb", &remote, &local, &verify, &force); - if (r < 0) - return r; - - if (!http_url_is_valid(remote)) - return sd_bus_error_setf(error, SD_BUS_ERROR_INVALID_ARGS, "URL %s is invalid", remote); - - if (isempty(local)) - local = NULL; - else if (!machine_name_is_valid(local)) - return sd_bus_error_setf(error, SD_BUS_ERROR_INVALID_ARGS, "Local name %s is invalid", local); - - if (isempty(verify)) - v = IMPORT_VERIFY_SIGNATURE; - else - v = import_verify_from_string(verify); - if (v < 0) - return sd_bus_error_setf(error, SD_BUS_ERROR_INVALID_ARGS, "Unknown verification mode %s", verify); - - r = setup_machine_directory((uint64_t) -1, error); - if (r < 0) - return r; - - type = streq_ptr(sd_bus_message_get_member(msg), "PullTar") ? TRANSFER_PULL_TAR : TRANSFER_PULL_RAW; - - if (manager_find(m, type, remote)) - return sd_bus_error_setf(error, BUS_ERROR_TRANSFER_IN_PROGRESS, "Transfer for %s already in progress.", remote); - - r = transfer_new(m, &t); - if (r < 0) - return r; - - t->type = type; - t->verify = v; - t->force_local = force; - - t->remote = strdup(remote); - if (!t->remote) - return -ENOMEM; - - if (local) { - t->local = strdup(local); - if (!t->local) - return -ENOMEM; - } - - r = transfer_start(t); - if (r < 0) - return r; - - object = t->object_path; - id = t->id; - t = NULL; - - return sd_bus_reply_method_return(msg, "uo", id, object); -} - -static int method_list_transfers(sd_bus_message *msg, void *userdata, sd_bus_error *error) { - _cleanup_(sd_bus_message_unrefp) sd_bus_message *reply = NULL; - Manager *m = userdata; - Transfer *t; - Iterator i; - int r; - - assert(msg); - assert(m); - - r = sd_bus_message_new_method_return(msg, &reply); - if (r < 0) - return r; - - r = sd_bus_message_open_container(reply, 'a', "(usssdo)"); - if (r < 0) - return r; - - HASHMAP_FOREACH(t, m->transfers, i) { - - r = sd_bus_message_append( - reply, - "(usssdo)", - t->id, - transfer_type_to_string(t->type), - t->remote, - t->local, - (double) t->progress_percent / 100.0, - t->object_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_cancel(sd_bus_message *msg, void *userdata, sd_bus_error *error) { - Transfer *t = userdata; - int r; - - assert(msg); - assert(t); - - r = bus_verify_polkit_async( - msg, - CAP_SYS_ADMIN, - "org.freedesktop.import1.pull", - NULL, - false, - UID_INVALID, - &t->manager->polkit_registry, - error); - if (r < 0) - return r; - if (r == 0) - return 1; /* Will call us back */ - - r = transfer_cancel(t); - if (r < 0) - return r; - - return sd_bus_reply_method_return(msg, NULL); -} - -static int method_cancel_transfer(sd_bus_message *msg, void *userdata, sd_bus_error *error) { - Manager *m = userdata; - Transfer *t; - uint32_t id; - int r; - - assert(msg); - assert(m); - - r = bus_verify_polkit_async( - msg, - CAP_SYS_ADMIN, - "org.freedesktop.import1.pull", - NULL, - false, - UID_INVALID, - &m->polkit_registry, - error); - if (r < 0) - return r; - if (r == 0) - return 1; /* Will call us back */ - - r = sd_bus_message_read(msg, "u", &id); - if (r < 0) - return r; - if (id <= 0) - return sd_bus_error_setf(error, SD_BUS_ERROR_INVALID_ARGS, "Invalid transfer id"); - - t = hashmap_get(m->transfers, UINT32_TO_PTR(id)); - if (!t) - return sd_bus_error_setf(error, BUS_ERROR_NO_SUCH_TRANSFER, "No transfer by id %" PRIu32, id); - - r = transfer_cancel(t); - if (r < 0) - return r; - - return sd_bus_reply_method_return(msg, NULL); -} - -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) { - - Transfer *t = userdata; - - assert(bus); - assert(reply); - assert(t); - - return sd_bus_message_append(reply, "d", (double) t->progress_percent / 100.0); -} - -static BUS_DEFINE_PROPERTY_GET_ENUM(property_get_type, transfer_type, TransferType); -static BUS_DEFINE_PROPERTY_GET_ENUM(property_get_verify, import_verify, ImportVerify); - -static const sd_bus_vtable transfer_vtable[] = { - SD_BUS_VTABLE_START(0), - SD_BUS_PROPERTY("Id", "u", NULL, offsetof(Transfer, id), SD_BUS_VTABLE_PROPERTY_CONST), - SD_BUS_PROPERTY("Local", "s", NULL, offsetof(Transfer, local), SD_BUS_VTABLE_PROPERTY_CONST), - SD_BUS_PROPERTY("Remote", "s", NULL, offsetof(Transfer, remote), SD_BUS_VTABLE_PROPERTY_CONST), - SD_BUS_PROPERTY("Type", "s", property_get_type, offsetof(Transfer, type), SD_BUS_VTABLE_PROPERTY_CONST), - SD_BUS_PROPERTY("Verify", "s", property_get_verify, offsetof(Transfer, verify), SD_BUS_VTABLE_PROPERTY_CONST), - SD_BUS_PROPERTY("Progress", "d", property_get_progress, 0, 0), - SD_BUS_METHOD("Cancel", NULL, NULL, method_cancel, SD_BUS_VTABLE_UNPRIVILEGED), - SD_BUS_SIGNAL("LogMessage", "us", 0), - SD_BUS_VTABLE_END, -}; - -static const sd_bus_vtable manager_vtable[] = { - SD_BUS_VTABLE_START(0), - SD_BUS_METHOD("ImportTar", "hsbb", "uo", method_import_tar_or_raw, SD_BUS_VTABLE_UNPRIVILEGED), - SD_BUS_METHOD("ImportRaw", "hsbb", "uo", method_import_tar_or_raw, SD_BUS_VTABLE_UNPRIVILEGED), - SD_BUS_METHOD("ExportTar", "shs", "uo", method_export_tar_or_raw, SD_BUS_VTABLE_UNPRIVILEGED), - SD_BUS_METHOD("ExportRaw", "shs", "uo", method_export_tar_or_raw, SD_BUS_VTABLE_UNPRIVILEGED), - SD_BUS_METHOD("PullTar", "sssb", "uo", method_pull_tar_or_raw, SD_BUS_VTABLE_UNPRIVILEGED), - SD_BUS_METHOD("PullRaw", "sssb", "uo", method_pull_tar_or_raw, SD_BUS_VTABLE_UNPRIVILEGED), - SD_BUS_METHOD("ListTransfers", NULL, "a(usssdo)", method_list_transfers, SD_BUS_VTABLE_UNPRIVILEGED), - SD_BUS_METHOD("CancelTransfer", "u", NULL, method_cancel_transfer, SD_BUS_VTABLE_UNPRIVILEGED), - SD_BUS_SIGNAL("TransferNew", "uo", 0), - SD_BUS_SIGNAL("TransferRemoved", "uos", 0), - SD_BUS_VTABLE_END, -}; - -static int transfer_object_find(sd_bus *bus, const char *path, const char *interface, void *userdata, void **found, sd_bus_error *error) { - Manager *m = userdata; - Transfer *t; - const char *p; - uint32_t id; - int r; - - assert(bus); - assert(path); - assert(interface); - assert(found); - assert(m); - - p = startswith(path, "/org/freedesktop/import1/transfer/_"); - if (!p) - return 0; - - r = safe_atou32(p, &id); - if (r < 0 || id == 0) - return 0; - - t = hashmap_get(m->transfers, UINT32_TO_PTR(id)); - if (!t) - return 0; - - *found = t; - return 1; -} - -static int transfer_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; - Transfer *t; - unsigned k = 0; - Iterator i; - - l = new0(char*, hashmap_size(m->transfers) + 1); - if (!l) - return -ENOMEM; - - HASHMAP_FOREACH(t, m->transfers, i) { - - l[k] = strdup(t->object_path); - if (!l[k]) - return -ENOMEM; - - k++; - } - - *nodes = l; - l = NULL; - - return 1; -} - -static int manager_add_bus_objects(Manager *m) { - int r; - - assert(m); - - r = sd_bus_add_object_vtable(m->bus, NULL, "/org/freedesktop/import1", "org.freedesktop.import1.Manager", manager_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/import1/transfer", "org.freedesktop.import1.Transfer", transfer_vtable, transfer_object_find, m); - if (r < 0) - return log_error_errno(r, "Failed to register object: %m"); - - r = sd_bus_add_node_enumerator(m->bus, NULL, "/org/freedesktop/import1/transfer", transfer_node_enumerator, m); - if (r < 0) - return log_error_errno(r, "Failed to add transfer enumerator: %m"); - - r = sd_bus_request_name(m->bus, "org.freedesktop.import1", 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; -} - -static bool manager_check_idle(void *userdata) { - Manager *m = userdata; - - return hashmap_isempty(m->transfers); -} - -static int manager_run(Manager *m) { - assert(m); - - return bus_event_loop_with_idle( - m->event, - m->bus, - "org.freedesktop.import1", - DEFAULT_EXIT_USEC, - manager_check_idle, - m); -} - -int main(int argc, char *argv[]) { - _cleanup_(manager_unrefp) Manager *m = 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; - } - - assert_se(sigprocmask_many(SIG_BLOCK, NULL, SIGCHLD, -1) >= 0); - - r = manager_new(&m); - if (r < 0) { - log_error_errno(r, "Failed to allocate manager object: %m"); - goto finish; - } - - r = manager_add_bus_objects(m); - if (r < 0) - goto finish; - - r = manager_run(m); - if (r < 0) { - log_error_errno(r, "Failed to run event loop: %m"); - goto finish; - } - -finish: - return r < 0 ? EXIT_FAILURE : EXIT_SUCCESS; -} diff --git a/src/grp-import/systemd-pull/Makefile b/src/grp-import/systemd-pull/Makefile deleted file mode 100644 index 36505edb5b..0000000000 --- a/src/grp-import/systemd-pull/Makefile +++ /dev/null @@ -1,64 +0,0 @@ -# -*- 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-pull -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/qcow2-util.c \ - src/import/qcow2-util.h - -systemd_pull_CFLAGS = \ - $(AM_CFLAGS) \ - $(LIBCURL_CFLAGS) \ - $(XZ_CFLAGS) \ - $(ZLIB_CFLAGS) \ - $(BZIP2_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) \ - $(BZIP2_LIBS) \ - $(GCRYPT_LIBS) - -include $(topsrcdir)/build-aux/Makefile.tail.mk diff --git a/src/grp-import/systemd-pull/pull-common.c b/src/grp-import/systemd-pull/pull-common.c deleted file mode 100644 index fb167fd93c..0000000000 --- a/src/grp-import/systemd-pull/pull-common.c +++ /dev/null @@ -1,549 +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 "basic/alloc-util.h" -#include "basic/btrfs-util.h" -#include "basic/capability-util.h" -#include "basic/copy.h" -#include "basic/dirent-util.h" -#include "basic/escape.h" -#include "basic/fd-util.h" -#include "basic/io-util.h" -#include "basic/path-util.h" -#include "basic/process-util.h" -#include "pull-common.h" -#include "pull-job.h" -#include "basic/rm-rf.h" -#include "basic/signal-util.h" -#include "basic/siphash24.h" -#include "basic/string-util.h" -#include "basic/strv.h" -#include "basic/util.h" -#include "basic/web-util.h" - -#define FILENAME_ESCAPE "/.#\"\'" -#define HASH_URL_THRESHOLD_LENGTH (_POSIX_PATH_MAX - 16) - -int pull_find_old_etags( - const char *url, - const char *image_root, - int dt, - const char *prefix, - const char *suffix, - char ***etags) { - - _cleanup_free_ char *escaped_url = NULL; - _cleanup_closedir_ DIR *d = NULL; - _cleanup_strv_free_ char **l = NULL; - struct dirent *de; - int r; - - assert(url); - assert(etags); - - if (!image_root) - image_root = "/var/lib/machines"; - - escaped_url = xescape(url, FILENAME_ESCAPE); - if (!escaped_url) - return -ENOMEM; - - d = opendir(image_root); - if (!d) { - if (errno == ENOENT) { - *etags = NULL; - return 0; - } - - return -errno; - } - - FOREACH_DIRENT_ALL(de, d, return -errno) { - const char *a, *b; - char *u; - - if (de->d_type != DT_UNKNOWN && - de->d_type != dt) - continue; - - if (prefix) { - a = startswith(de->d_name, prefix); - if (!a) - continue; - } else - a = de->d_name; - - a = startswith(a, escaped_url); - if (!a) - continue; - - a = startswith(a, "."); - if (!a) - continue; - - if (suffix) { - b = endswith(de->d_name, suffix); - if (!b) - continue; - } else - b = strchr(de->d_name, 0); - - if (a >= b) - continue; - - r = cunescape_length(a, b - a, 0, &u); - if (r < 0) - return r; - - if (!http_etag_is_valid(u)) { - free(u); - continue; - } - - r = strv_consume(&l, u); - if (r < 0) - return r; - } - - *etags = l; - l = NULL; - - return 0; -} - -int pull_make_local_copy(const char *final, const char *image_root, const char *local, bool force_local) { - const char *p; - int r; - - assert(final); - assert(local); - - if (!image_root) - image_root = "/var/lib/machines"; - - p = strjoina(image_root, "/", local); - - if (force_local) - (void) rm_rf(p, REMOVE_ROOT|REMOVE_PHYSICAL|REMOVE_SUBVOLUME); - - r = btrfs_subvol_snapshot(final, p, BTRFS_SNAPSHOT_QUOTA); - if (r == -ENOTTY) { - r = copy_tree(final, p, false); - if (r < 0) - return log_error_errno(r, "Failed to copy image: %m"); - } else if (r < 0) - return log_error_errno(r, "Failed to create local image: %m"); - - log_info("Created new local image '%s'.", local); - - return 0; -} - -static int hash_url(const char *url, char **ret) { - uint64_t h; - static const sd_id128_t k = SD_ID128_ARRAY(df,89,16,87,01,cc,42,30,98,ab,4a,19,a6,a5,63,4f); - - assert(url); - - h = siphash24(url, strlen(url), k.bytes); - if (asprintf(ret, "%"PRIx64, h) < 0) - return -ENOMEM; - - return 0; -} - -int pull_make_path(const char *url, const char *etag, const char *image_root, const char *prefix, const char *suffix, char **ret) { - _cleanup_free_ char *escaped_url = NULL, *escaped_etag = NULL; - char *path; - - assert(url); - assert(ret); - - if (!image_root) - image_root = "/var/lib/machines"; - - escaped_url = xescape(url, FILENAME_ESCAPE); - if (!escaped_url) - return -ENOMEM; - - if (etag) { - escaped_etag = xescape(etag, FILENAME_ESCAPE); - if (!escaped_etag) - return -ENOMEM; - } - - path = strjoin(image_root, "/", strempty(prefix), escaped_url, escaped_etag ? "." : "", - strempty(escaped_etag), strempty(suffix), NULL); - if (!path) - return -ENOMEM; - - /* URLs might make the path longer than the maximum allowed length for a file name. - * When that happens, a URL hash is used instead. Paths returned by this function - * can be later used with tempfn_random() which adds 16 bytes to the resulting name. */ - if (strlen(path) >= HASH_URL_THRESHOLD_LENGTH) { - _cleanup_free_ char *hash = NULL; - int r; - - free(path); - - r = hash_url(url, &hash); - if (r < 0) - return r; - - path = strjoin(image_root, "/", strempty(prefix), hash, escaped_etag ? "." : "", - strempty(escaped_etag), strempty(suffix), NULL); - if (!path) - return -ENOMEM; - } - - *ret = path; - return 0; -} - -int pull_make_settings_job( - PullJob **ret, - const char *url, - CurlGlue *glue, - PullJobFinished on_finished, - void *userdata) { - - _cleanup_free_ char *last_component = NULL, *ll = NULL, *settings_url = NULL; - _cleanup_(pull_job_unrefp) PullJob *job = NULL; - const char *q; - int r; - - assert(ret); - assert(url); - assert(glue); - - r = import_url_last_component(url, &last_component); - if (r < 0) - return r; - - r = tar_strip_suffixes(last_component, &ll); - if (r < 0) - return r; - - q = strjoina(ll, ".nspawn"); - - r = import_url_change_last_component(url, q, &settings_url); - if (r < 0) - return r; - - r = pull_job_new(&job, settings_url, glue, userdata); - if (r < 0) - return r; - - job->on_finished = on_finished; - job->compressed_max = job->uncompressed_max = 1ULL * 1024ULL * 1024ULL; - - *ret = job; - job = NULL; - - return 0; -} - -int pull_make_verification_jobs( - PullJob **ret_checksum_job, - PullJob **ret_signature_job, - ImportVerify verify, - const char *url, - CurlGlue *glue, - PullJobFinished on_finished, - void *userdata) { - - _cleanup_(pull_job_unrefp) PullJob *checksum_job = NULL, *signature_job = NULL; - int r; - - assert(ret_checksum_job); - assert(ret_signature_job); - assert(verify >= 0); - assert(verify < _IMPORT_VERIFY_MAX); - assert(url); - assert(glue); - - if (verify != IMPORT_VERIFY_NO) { - _cleanup_free_ char *checksum_url = NULL; - - /* Queue job for the SHA256SUMS file for the image */ - r = import_url_change_last_component(url, "SHA256SUMS", &checksum_url); - if (r < 0) - return r; - - r = pull_job_new(&checksum_job, checksum_url, glue, userdata); - if (r < 0) - return r; - - checksum_job->on_finished = on_finished; - checksum_job->uncompressed_max = checksum_job->compressed_max = 1ULL * 1024ULL * 1024ULL; - } - - if (verify == IMPORT_VERIFY_SIGNATURE) { - _cleanup_free_ char *signature_url = NULL; - - /* Queue job for the SHA256SUMS.gpg file for the image. */ - r = import_url_change_last_component(url, "SHA256SUMS.gpg", &signature_url); - if (r < 0) - return r; - - r = pull_job_new(&signature_job, signature_url, glue, userdata); - if (r < 0) - return r; - - signature_job->on_finished = on_finished; - signature_job->uncompressed_max = signature_job->compressed_max = 1ULL * 1024ULL * 1024ULL; - } - - *ret_checksum_job = checksum_job; - *ret_signature_job = signature_job; - - checksum_job = signature_job = NULL; - - return 0; -} - -int pull_verify(PullJob *main_job, - PullJob *settings_job, - PullJob *checksum_job, - PullJob *signature_job) { - - _cleanup_close_pair_ int gpg_pipe[2] = { -1, -1 }; - _cleanup_free_ char *fn = NULL; - _cleanup_close_ int sig_file = -1; - const char *p, *line; - char sig_file_path[] = "/tmp/sigXXXXXX", gpg_home[] = "/tmp/gpghomeXXXXXX"; - _cleanup_(sigkill_waitp) pid_t pid = 0; - bool gpg_home_created = false; - int r; - - assert(main_job); - assert(main_job->state == PULL_JOB_DONE); - - if (!checksum_job) - return 0; - - assert(main_job->calc_checksum); - assert(main_job->checksum); - assert(checksum_job->state == PULL_JOB_DONE); - - if (!checksum_job->payload || checksum_job->payload_size <= 0) { - log_error("Checksum is empty, cannot verify."); - return -EBADMSG; - } - - r = import_url_last_component(main_job->url, &fn); - if (r < 0) - return log_oom(); - - if (!filename_is_valid(fn)) { - log_error("Cannot verify checksum, could not determine valid server-side file name."); - return -EBADMSG; - } - - line = strjoina(main_job->checksum, " *", fn, "\n"); - - p = memmem(checksum_job->payload, - checksum_job->payload_size, - line, - strlen(line)); - - if (!p || (p != (char*) checksum_job->payload && p[-1] != '\n')) { - log_error("DOWNLOAD INVALID: Checksum did not check out, payload has been tampered with."); - return -EBADMSG; - } - - log_info("SHA256 checksum of %s is valid.", main_job->url); - - assert(!settings_job || IN_SET(settings_job->state, PULL_JOB_DONE, PULL_JOB_FAILED)); - - if (settings_job && - settings_job->state == PULL_JOB_DONE && - settings_job->error == 0 && - !settings_job->etag_exists) { - - _cleanup_free_ char *settings_fn = NULL; - - assert(settings_job->calc_checksum); - assert(settings_job->checksum); - - r = import_url_last_component(settings_job->url, &settings_fn); - if (r < 0) - return log_oom(); - - if (!filename_is_valid(settings_fn)) { - log_error("Cannot verify checksum, could not determine server-side settings file name."); - return -EBADMSG; - } - - line = strjoina(settings_job->checksum, " *", settings_fn, "\n"); - - p = memmem(checksum_job->payload, - checksum_job->payload_size, - line, - strlen(line)); - - if (!p || (p != (char*) checksum_job->payload && p[-1] != '\n')) { - log_error("DOWNLOAD INVALID: Checksum of settings file did not checkout, settings file has been tampered with."); - return -EBADMSG; - } - - log_info("SHA256 checksum of %s is valid.", settings_job->url); - } - - if (!signature_job) - return 0; - - assert(signature_job->state == PULL_JOB_DONE); - - if (!signature_job->payload || signature_job->payload_size <= 0) { - log_error("Signature is empty, cannot verify."); - return -EBADMSG; - } - - r = pipe2(gpg_pipe, O_CLOEXEC); - if (r < 0) - return log_error_errno(errno, "Failed to create pipe for gpg: %m"); - - sig_file = mkostemp(sig_file_path, O_RDWR); - if (sig_file < 0) - return log_error_errno(errno, "Failed to create temporary file: %m"); - - r = loop_write(sig_file, signature_job->payload, signature_job->payload_size, false); - if (r < 0) { - log_error_errno(r, "Failed to write to temporary file: %m"); - goto finish; - } - - if (!mkdtemp(gpg_home)) { - r = log_error_errno(errno, "Failed to create tempory home for gpg: %m"); - goto finish; - } - - gpg_home_created = true; - - pid = fork(); - if (pid < 0) - return log_error_errno(errno, "Failed to fork off gpg: %m"); - if (pid == 0) { - const char *cmd[] = { - "gpg", - "--no-options", - "--no-default-keyring", - "--no-auto-key-locate", - "--no-auto-check-trustdb", - "--batch", - "--trust-model=always", - NULL, /* --homedir= */ - NULL, /* --keyring= */ - NULL, /* --verify */ - NULL, /* signature file */ - NULL, /* dash */ - NULL /* trailing NULL */ - }; - unsigned k = ELEMENTSOF(cmd) - 6; - int null_fd; - - /* Child */ - - (void) reset_all_signal_handlers(); - (void) reset_signal_mask(); - assert_se(prctl(PR_SET_PDEATHSIG, SIGTERM) == 0); - - gpg_pipe[1] = safe_close(gpg_pipe[1]); - - if (dup2(gpg_pipe[0], STDIN_FILENO) != STDIN_FILENO) { - log_error_errno(errno, "Failed to dup2() fd: %m"); - _exit(EXIT_FAILURE); - } - - if (gpg_pipe[0] != STDIN_FILENO) - gpg_pipe[0] = safe_close(gpg_pipe[0]); - - null_fd = open("/dev/null", O_WRONLY|O_NOCTTY); - if (null_fd < 0) { - log_error_errno(errno, "Failed to open /dev/null: %m"); - _exit(EXIT_FAILURE); - } - - if (dup2(null_fd, STDOUT_FILENO) != STDOUT_FILENO) { - log_error_errno(errno, "Failed to dup2() fd: %m"); - _exit(EXIT_FAILURE); - } - - if (null_fd != STDOUT_FILENO) - null_fd = safe_close(null_fd); - - cmd[k++] = strjoina("--homedir=", gpg_home); - - /* We add the user keyring only to the command line - * arguments, if it's around since gpg fails - * otherwise. */ - if (access(USER_KEYRING_PATH, F_OK) >= 0) - cmd[k++] = "--keyring=" USER_KEYRING_PATH; - else - cmd[k++] = "--keyring=" VENDOR_KEYRING_PATH; - - cmd[k++] = "--verify"; - cmd[k++] = sig_file_path; - cmd[k++] = "-"; - cmd[k++] = NULL; - - fd_cloexec(STDIN_FILENO, false); - fd_cloexec(STDOUT_FILENO, false); - fd_cloexec(STDERR_FILENO, false); - - execvp("gpg2", (char * const *) cmd); - execvp("gpg", (char * const *) cmd); - log_error_errno(errno, "Failed to execute gpg: %m"); - _exit(EXIT_FAILURE); - } - - gpg_pipe[0] = safe_close(gpg_pipe[0]); - - r = loop_write(gpg_pipe[1], checksum_job->payload, checksum_job->payload_size, false); - if (r < 0) { - log_error_errno(r, "Failed to write to pipe: %m"); - goto finish; - } - - gpg_pipe[1] = safe_close(gpg_pipe[1]); - - r = wait_for_terminate_and_warn("gpg", pid, true); - pid = 0; - if (r < 0) - goto finish; - if (r > 0) { - log_error("DOWNLOAD INVALID: Signature verification failed."); - r = -EBADMSG; - } else { - log_info("Signature verification succeeded."); - r = 0; - } - -finish: - if (sig_file >= 0) - (void) unlink(sig_file_path); - - if (gpg_home_created) - (void) rm_rf(gpg_home, REMOVE_ROOT|REMOVE_PHYSICAL); - - return r; -} diff --git a/src/grp-import/systemd-pull/pull-common.h b/src/grp-import/systemd-pull/pull-common.h deleted file mode 100644 index d9cfb6b3c1..0000000000 --- a/src/grp-import/systemd-pull/pull-common.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 "shared/import-util.h" -#include "pull-job.h" - -int pull_make_local_copy(const char *final, const char *root, const char *local, bool force_local); - -int pull_find_old_etags(const char *url, const char *root, int dt, const char *prefix, const char *suffix, char ***etags); - -int pull_make_path(const char *url, const char *etag, const char *image_root, const char *prefix, const char *suffix, char **ret); - -int pull_make_settings_job(PullJob **ret, const char *url, CurlGlue *glue, PullJobFinished on_finished, void *userdata); -int pull_make_verification_jobs(PullJob **ret_checksum_job, PullJob **ret_signature_job, ImportVerify verify, const char *url, CurlGlue *glue, PullJobFinished on_finished, void *userdata); - -int pull_verify(PullJob *main_job, PullJob *settings_job, PullJob *checksum_job, PullJob *signature_job); diff --git a/src/grp-import/systemd-pull/pull-job.c b/src/grp-import/systemd-pull/pull-job.c deleted file mode 100644 index fce08f65d9..0000000000 --- a/src/grp-import/systemd-pull/pull-job.c +++ /dev/null @@ -1,618 +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 "basic/alloc-util.h" -#include "basic/fd-util.h" -#include "basic/hexdecoct.h" -#include "basic/io-util.h" -#include "shared/machine-pool.h" -#include "basic/parse-util.h" -#include "pull-job.h" -#include "basic/string-util.h" -#include "basic/strv.h" -#include "basic/xattr-util.h" - -PullJob* pull_job_unref(PullJob *j) { - if (!j) - return NULL; - - curl_glue_remove_and_free(j->glue, j->curl); - curl_slist_free_all(j->request_header); - - safe_close(j->disk_fd); - - import_compress_free(&j->compress); - - if (j->checksum_context) - gcry_md_close(j->checksum_context); - - free(j->url); - free(j->etag); - strv_free(j->old_etags); - free(j->payload); - free(j->checksum); - - free(j); - - return NULL; -} - -static void pull_job_finish(PullJob *j, int ret) { - assert(j); - - if (j->state == PULL_JOB_DONE || - j->state == PULL_JOB_FAILED) - return; - - if (ret == 0) { - j->state = PULL_JOB_DONE; - j->progress_percent = 100; - log_info("Download of %s complete.", j->url); - } else { - j->state = PULL_JOB_FAILED; - j->error = ret; - } - - if (j->on_finished) - j->on_finished(j); -} - -void pull_job_curl_on_finished(CurlGlue *g, CURL *curl, CURLcode result) { - PullJob *j = NULL; - CURLcode code; - long status; - int r; - - if (curl_easy_getinfo(curl, CURLINFO_PRIVATE, (char **)&j) != CURLE_OK) - return; - - if (!j || j->state == PULL_JOB_DONE || j->state == PULL_JOB_FAILED) - return; - - if (result != CURLE_OK) { - log_error("Transfer failed: %s", curl_easy_strerror(result)); - r = -EIO; - goto finish; - } - - code = curl_easy_getinfo(curl, CURLINFO_RESPONSE_CODE, &status); - if (code != CURLE_OK) { - log_error("Failed to retrieve response code: %s", curl_easy_strerror(code)); - r = -EIO; - goto finish; - } else if (status == 304) { - log_info("Image already downloaded. Skipping download."); - j->etag_exists = true; - r = 0; - goto finish; - } else if (status >= 300) { - log_error("HTTP request to %s failed with code %li.", j->url, status); - r = -EIO; - goto finish; - } else if (status < 200) { - log_error("HTTP request to %s finished with unexpected code %li.", j->url, status); - r = -EIO; - goto finish; - } - - if (j->state != PULL_JOB_RUNNING) { - log_error("Premature connection termination."); - r = -EIO; - goto finish; - } - - if (j->content_length != (uint64_t) -1 && - j->content_length != j->written_compressed) { - log_error("Download truncated."); - r = -EIO; - goto finish; - } - - if (j->checksum_context) { - uint8_t *k; - - k = gcry_md_read(j->checksum_context, GCRY_MD_SHA256); - if (!k) { - log_error("Failed to get checksum."); - r = -EIO; - goto finish; - } - - j->checksum = hexmem(k, gcry_md_get_algo_dlen(GCRY_MD_SHA256)); - if (!j->checksum) { - r = log_oom(); - goto finish; - } - - log_debug("SHA256 of %s is %s.", j->url, j->checksum); - } - - if (j->disk_fd >= 0 && j->allow_sparse) { - /* Make sure the file size is right, in case the file was - * sparse and we just seeked for the last part */ - - if (ftruncate(j->disk_fd, j->written_uncompressed) < 0) { - r = log_error_errno(errno, "Failed to truncate file: %m"); - goto finish; - } - - if (j->etag) - (void) fsetxattr(j->disk_fd, "user.source_etag", j->etag, strlen(j->etag), 0); - if (j->url) - (void) fsetxattr(j->disk_fd, "user.source_url", j->url, strlen(j->url), 0); - - if (j->mtime != 0) { - struct timespec ut[2]; - - timespec_store(&ut[0], j->mtime); - ut[1] = ut[0]; - (void) futimens(j->disk_fd, ut); - - (void) fd_setcrtime(j->disk_fd, j->mtime); - } - } - - r = 0; - -finish: - pull_job_finish(j, r); -} - -static int pull_job_write_uncompressed(const void *p, size_t sz, void *userdata) { - PullJob *j = userdata; - ssize_t n; - - assert(j); - assert(p); - - if (sz <= 0) - return 0; - - if (j->written_uncompressed + sz < j->written_uncompressed) { - log_error("File too large, overflow"); - return -EOVERFLOW; - } - - if (j->written_uncompressed + sz > j->uncompressed_max) { - log_error("File overly large, refusing"); - return -EFBIG; - } - - if (j->disk_fd >= 0) { - - if (j->grow_machine_directory && j->written_since_last_grow >= GROW_INTERVAL_BYTES) { - j->written_since_last_grow = 0; - grow_machine_directory(); - } - - if (j->allow_sparse) - n = sparse_write(j->disk_fd, p, sz, 64); - else - n = write(j->disk_fd, p, sz); - if (n < 0) - return log_error_errno(errno, "Failed to write file: %m"); - if ((size_t) n < sz) { - log_error("Short write"); - return -EIO; - } - } else { - - if (!GREEDY_REALLOC(j->payload, j->payload_allocated, j->payload_size + sz)) - return log_oom(); - - memcpy(j->payload + j->payload_size, p, sz); - j->payload_size += sz; - } - - j->written_uncompressed += sz; - j->written_since_last_grow += sz; - - return 0; -} - -static int pull_job_write_compressed(PullJob *j, void *p, size_t sz) { - int r; - - assert(j); - assert(p); - - if (sz <= 0) - return 0; - - if (j->written_compressed + sz < j->written_compressed) { - log_error("File too large, overflow"); - return -EOVERFLOW; - } - - if (j->written_compressed + sz > j->compressed_max) { - log_error("File overly large, refusing."); - return -EFBIG; - } - - if (j->content_length != (uint64_t) -1 && - j->written_compressed + sz > j->content_length) { - log_error("Content length incorrect."); - return -EFBIG; - } - - if (j->checksum_context) - gcry_md_write(j->checksum_context, p, sz); - - r = import_uncompress(&j->compress, p, sz, pull_job_write_uncompressed, j); - if (r < 0) - return r; - - j->written_compressed += sz; - - return 0; -} - -static int pull_job_open_disk(PullJob *j) { - int r; - - assert(j); - - if (j->on_open_disk) { - r = j->on_open_disk(j); - if (r < 0) - return r; - } - - if (j->disk_fd >= 0) { - /* Check if we can do sparse files */ - - if (lseek(j->disk_fd, SEEK_SET, 0) == 0) - j->allow_sparse = true; - else { - if (errno != ESPIPE) - return log_error_errno(errno, "Failed to seek on file descriptor: %m"); - - j->allow_sparse = false; - } - } - - if (j->calc_checksum) { - if (gcry_md_open(&j->checksum_context, GCRY_MD_SHA256, 0) != 0) { - log_error("Failed to initialize hash context."); - return -EIO; - } - } - - return 0; -} - -static int pull_job_detect_compression(PullJob *j) { - _cleanup_free_ uint8_t *stub = NULL; - size_t stub_size; - - int r; - - assert(j); - - r = import_uncompress_detect(&j->compress, j->payload, j->payload_size); - if (r < 0) - return log_error_errno(r, "Failed to initialize compressor: %m"); - if (r == 0) - return 0; - - log_debug("Stream is compressed: %s", import_compress_type_to_string(j->compress.type)); - - r = pull_job_open_disk(j); - if (r < 0) - return r; - - /* Now, take the payload we read so far, and decompress it */ - stub = j->payload; - stub_size = j->payload_size; - - j->payload = NULL; - j->payload_size = 0; - j->payload_allocated = 0; - - j->state = PULL_JOB_RUNNING; - - r = pull_job_write_compressed(j, stub, stub_size); - if (r < 0) - return r; - - return 0; -} - -static size_t pull_job_write_callback(void *contents, size_t size, size_t nmemb, void *userdata) { - PullJob *j = userdata; - size_t sz = size * nmemb; - int r; - - assert(contents); - assert(j); - - switch (j->state) { - - case PULL_JOB_ANALYZING: - /* Let's first check what it actually is */ - - if (!GREEDY_REALLOC(j->payload, j->payload_allocated, j->payload_size + sz)) { - r = log_oom(); - goto fail; - } - - memcpy(j->payload + j->payload_size, contents, sz); - j->payload_size += sz; - - r = pull_job_detect_compression(j); - if (r < 0) - goto fail; - - break; - - case PULL_JOB_RUNNING: - - r = pull_job_write_compressed(j, contents, sz); - if (r < 0) - goto fail; - - break; - - case PULL_JOB_DONE: - case PULL_JOB_FAILED: - r = -ESTALE; - goto fail; - - default: - assert_not_reached("Impossible state."); - } - - return sz; - -fail: - pull_job_finish(j, r); - return 0; -} - -static size_t pull_job_header_callback(void *contents, size_t size, size_t nmemb, void *userdata) { - PullJob *j = userdata; - size_t sz = size * nmemb; - _cleanup_free_ char *length = NULL, *last_modified = NULL; - char *etag; - int r; - - assert(contents); - assert(j); - - if (j->state == PULL_JOB_DONE || j->state == PULL_JOB_FAILED) { - r = -ESTALE; - goto fail; - } - - assert(j->state == PULL_JOB_ANALYZING); - - r = curl_header_strdup(contents, sz, "ETag:", &etag); - if (r < 0) { - log_oom(); - goto fail; - } - if (r > 0) { - free(j->etag); - j->etag = etag; - - if (strv_contains(j->old_etags, j->etag)) { - log_info("Image already downloaded. Skipping download."); - j->etag_exists = true; - pull_job_finish(j, 0); - return sz; - } - - return sz; - } - - r = curl_header_strdup(contents, sz, "Content-Length:", &length); - if (r < 0) { - log_oom(); - goto fail; - } - if (r > 0) { - (void) safe_atou64(length, &j->content_length); - - if (j->content_length != (uint64_t) -1) { - char bytes[FORMAT_BYTES_MAX]; - - if (j->content_length > j->compressed_max) { - log_error("Content too large."); - r = -EFBIG; - goto fail; - } - - log_info("Downloading %s for %s.", format_bytes(bytes, sizeof(bytes), j->content_length), j->url); - } - - return sz; - } - - r = curl_header_strdup(contents, sz, "Last-Modified:", &last_modified); - if (r < 0) { - log_oom(); - goto fail; - } - if (r > 0) { - (void) curl_parse_http_time(last_modified, &j->mtime); - return sz; - } - - if (j->on_header) { - r = j->on_header(j, contents, sz); - if (r < 0) - goto fail; - } - - return sz; - -fail: - pull_job_finish(j, r); - return 0; -} - -static int pull_job_progress_callback(void *userdata, curl_off_t dltotal, curl_off_t dlnow, curl_off_t ultotal, curl_off_t ulnow) { - PullJob *j = userdata; - unsigned percent; - usec_t n; - - assert(j); - - if (dltotal <= 0) - return 0; - - percent = ((100 * dlnow) / dltotal); - n = now(CLOCK_MONOTONIC); - - if (n > j->last_status_usec + USEC_PER_SEC && - percent != j->progress_percent && - dlnow < dltotal) { - char buf[FORMAT_TIMESPAN_MAX]; - - if (n - j->start_usec > USEC_PER_SEC && dlnow > 0) { - char y[FORMAT_BYTES_MAX]; - usec_t left, done; - - done = n - j->start_usec; - left = (usec_t) (((double) done * (double) dltotal) / dlnow) - done; - - log_info("Got %u%% of %s. %s left at %s/s.", - percent, - j->url, - format_timespan(buf, sizeof(buf), left, USEC_PER_SEC), - format_bytes(y, sizeof(y), (uint64_t) ((double) dlnow / ((double) done / (double) USEC_PER_SEC)))); - } else - log_info("Got %u%% of %s.", percent, j->url); - - j->progress_percent = percent; - j->last_status_usec = n; - - if (j->on_progress) - j->on_progress(j); - } - - return 0; -} - -int pull_job_new(PullJob **ret, const char *url, CurlGlue *glue, void *userdata) { - _cleanup_(pull_job_unrefp) PullJob *j = NULL; - - assert(url); - assert(glue); - assert(ret); - - j = new0(PullJob, 1); - if (!j) - return -ENOMEM; - - j->state = PULL_JOB_INIT; - j->disk_fd = -1; - j->userdata = userdata; - j->glue = glue; - j->content_length = (uint64_t) -1; - j->start_usec = now(CLOCK_MONOTONIC); - j->compressed_max = j->uncompressed_max = 8LLU * 1024LLU * 1024LLU * 1024LLU; /* 8GB */ - - j->url = strdup(url); - if (!j->url) - return -ENOMEM; - - *ret = j; - j = NULL; - - return 0; -} - -int pull_job_begin(PullJob *j) { - int r; - - assert(j); - - if (j->state != PULL_JOB_INIT) - return -EBUSY; - - if (j->grow_machine_directory) - grow_machine_directory(); - - r = curl_glue_make(&j->curl, j->url, j); - if (r < 0) - return r; - - if (!strv_isempty(j->old_etags)) { - _cleanup_free_ char *cc = NULL, *hdr = NULL; - - cc = strv_join(j->old_etags, ", "); - if (!cc) - return -ENOMEM; - - hdr = strappend("If-None-Match: ", cc); - if (!hdr) - return -ENOMEM; - - if (!j->request_header) { - j->request_header = curl_slist_new(hdr, NULL); - if (!j->request_header) - return -ENOMEM; - } else { - struct curl_slist *l; - - l = curl_slist_append(j->request_header, hdr); - if (!l) - return -ENOMEM; - - j->request_header = l; - } - } - - if (j->request_header) { - if (curl_easy_setopt(j->curl, CURLOPT_HTTPHEADER, j->request_header) != CURLE_OK) - return -EIO; - } - - if (curl_easy_setopt(j->curl, CURLOPT_WRITEFUNCTION, pull_job_write_callback) != CURLE_OK) - return -EIO; - - if (curl_easy_setopt(j->curl, CURLOPT_WRITEDATA, j) != CURLE_OK) - return -EIO; - - if (curl_easy_setopt(j->curl, CURLOPT_HEADERFUNCTION, pull_job_header_callback) != CURLE_OK) - return -EIO; - - if (curl_easy_setopt(j->curl, CURLOPT_HEADERDATA, j) != CURLE_OK) - return -EIO; - - if (curl_easy_setopt(j->curl, CURLOPT_XFERINFOFUNCTION, pull_job_progress_callback) != CURLE_OK) - return -EIO; - - if (curl_easy_setopt(j->curl, CURLOPT_XFERINFODATA, j) != CURLE_OK) - return -EIO; - - if (curl_easy_setopt(j->curl, CURLOPT_NOPROGRESS, 0) != CURLE_OK) - return -EIO; - - r = curl_glue_add(j->glue, j->curl); - if (r < 0) - return r; - - j->state = PULL_JOB_ANALYZING; - - return 0; -} diff --git a/src/grp-import/systemd-pull/pull-job.h b/src/grp-import/systemd-pull/pull-job.h deleted file mode 100644 index 80d7370ff1..0000000000 --- a/src/grp-import/systemd-pull/pull-job.h +++ /dev/null @@ -1,106 +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 "curl-util.h" -#include "import-compress.h" -#include "basic/macro.h" - -typedef struct PullJob PullJob; - -typedef void (*PullJobFinished)(PullJob *job); -typedef int (*PullJobOpenDisk)(PullJob *job); -typedef int (*PullJobHeader)(PullJob *job, const char *header, size_t sz); -typedef void (*PullJobProgress)(PullJob *job); - -typedef enum PullJobState { - PULL_JOB_INIT, - PULL_JOB_ANALYZING, /* Still reading into ->payload, to figure out what we have */ - PULL_JOB_RUNNING, /* Writing to destination */ - PULL_JOB_DONE, - PULL_JOB_FAILED, - _PULL_JOB_STATE_MAX, - _PULL_JOB_STATE_INVALID = -1, -} PullJobState; - -#define PULL_JOB_IS_COMPLETE(j) (IN_SET((j)->state, PULL_JOB_DONE, PULL_JOB_FAILED)) - -struct PullJob { - PullJobState state; - int error; - - char *url; - - void *userdata; - PullJobFinished on_finished; - PullJobOpenDisk on_open_disk; - PullJobHeader on_header; - PullJobProgress on_progress; - - CurlGlue *glue; - CURL *curl; - struct curl_slist *request_header; - - char *etag; - char **old_etags; - bool etag_exists; - - uint64_t content_length; - uint64_t written_compressed; - uint64_t written_uncompressed; - - uint64_t uncompressed_max; - uint64_t compressed_max; - - uint8_t *payload; - size_t payload_size; - size_t payload_allocated; - - int disk_fd; - - usec_t mtime; - - ImportCompress compress; - - unsigned progress_percent; - usec_t start_usec; - usec_t last_status_usec; - - bool allow_sparse; - - bool calc_checksum; - gcry_md_hd_t checksum_context; - - char *checksum; - - bool grow_machine_directory; - uint64_t written_since_last_grow; -}; - -int pull_job_new(PullJob **job, const char *url, CurlGlue *glue, void *userdata); -PullJob* pull_job_unref(PullJob *job); - -int pull_job_begin(PullJob *j); - -void pull_job_curl_on_finished(CurlGlue *g, CURL *curl, CURLcode result); - -DEFINE_TRIVIAL_CLEANUP_FUNC(PullJob*, pull_job_unref); diff --git a/src/grp-import/systemd-pull/pull-raw.c b/src/grp-import/systemd-pull/pull-raw.c deleted file mode 100644 index a9777a5838..0000000000 --- a/src/grp-import/systemd-pull/pull-raw.c +++ /dev/null @@ -1,651 +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 "basic/alloc-util.h" -#include "basic/btrfs-util.h" -#include "basic/chattr-util.h" -#include "basic/copy.h" -#include "curl-util.h" -#include "basic/fd-util.h" -#include "basic/fileio.h" -#include "basic/fs-util.h" -#include "basic/hostname-util.h" -#include "import-common.h" -#include "shared/import-util.h" -#include "basic/macro.h" -#include "basic/mkdir.h" -#include "basic/path-util.h" -#include "pull-common.h" -#include "pull-job.h" -#include "pull-raw.h" -#include "qcow2-util.h" -#include "basic/rm-rf.h" -#include "basic/string-util.h" -#include "basic/strv.h" -#include "basic/utf8.h" -#include "basic/util.h" -#include "basic/web-util.h" - -typedef enum RawProgress { - RAW_DOWNLOADING, - RAW_VERIFYING, - RAW_UNPACKING, - RAW_FINALIZING, - RAW_COPYING, -} RawProgress; - -struct RawPull { - sd_event *event; - CurlGlue *glue; - - char *image_root; - - PullJob *raw_job; - PullJob *settings_job; - PullJob *checksum_job; - PullJob *signature_job; - - RawPullFinished on_finished; - void *userdata; - - char *local; - bool force_local; - bool grow_machine_directory; - bool settings; - - char *final_path; - char *temp_path; - - char *settings_path; - char *settings_temp_path; - - ImportVerify verify; -}; - -RawPull* raw_pull_unref(RawPull *i) { - if (!i) - return NULL; - - pull_job_unref(i->raw_job); - pull_job_unref(i->settings_job); - pull_job_unref(i->checksum_job); - pull_job_unref(i->signature_job); - - curl_glue_unref(i->glue); - sd_event_unref(i->event); - - if (i->temp_path) { - (void) unlink(i->temp_path); - free(i->temp_path); - } - - if (i->settings_temp_path) { - (void) unlink(i->settings_temp_path); - free(i->settings_temp_path); - } - - free(i->final_path); - free(i->settings_path); - free(i->image_root); - free(i->local); - free(i); - - return NULL; -} - -int raw_pull_new( - RawPull **ret, - sd_event *event, - const char *image_root, - RawPullFinished on_finished, - void *userdata) { - - _cleanup_(raw_pull_unrefp) RawPull *i = NULL; - int r; - - assert(ret); - - i = new0(RawPull, 1); - if (!i) - return -ENOMEM; - - i->on_finished = on_finished; - i->userdata = userdata; - - i->image_root = strdup(image_root ?: "/var/lib/machines"); - if (!i->image_root) - return -ENOMEM; - - i->grow_machine_directory = path_startswith(i->image_root, "/var/lib/machines"); - - if (event) - i->event = sd_event_ref(event); - else { - r = sd_event_default(&i->event); - if (r < 0) - return r; - } - - r = curl_glue_new(&i->glue, i->event); - if (r < 0) - return r; - - i->glue->on_finished = pull_job_curl_on_finished; - i->glue->userdata = i; - - *ret = i; - i = NULL; - - return 0; -} - -static void raw_pull_report_progress(RawPull *i, RawProgress p) { - unsigned percent; - - assert(i); - - switch (p) { - - case RAW_DOWNLOADING: { - unsigned remain = 80; - - percent = 0; - - if (i->settings_job) { - percent += i->settings_job->progress_percent * 5 / 100; - remain -= 5; - } - - if (i->checksum_job) { - percent += i->checksum_job->progress_percent * 5 / 100; - remain -= 5; - } - - if (i->signature_job) { - percent += i->signature_job->progress_percent * 5 / 100; - remain -= 5; - } - - if (i->raw_job) - percent += i->raw_job->progress_percent * remain / 100; - break; - } - - case RAW_VERIFYING: - percent = 80; - break; - - case RAW_UNPACKING: - percent = 85; - break; - - case RAW_FINALIZING: - percent = 90; - break; - - case RAW_COPYING: - percent = 95; - break; - - default: - assert_not_reached("Unknown progress state"); - } - - sd_notifyf(false, "X_IMPORT_PROGRESS=%u", percent); - log_debug("Combined progress %u%%", percent); -} - -static int raw_pull_maybe_convert_qcow2(RawPull *i) { - _cleanup_close_ int converted_fd = -1; - _cleanup_free_ char *t = NULL; - int r; - - assert(i); - assert(i->raw_job); - - r = qcow2_detect(i->raw_job->disk_fd); - if (r < 0) - return log_error_errno(r, "Failed to detect whether this is a QCOW2 image: %m"); - if (r == 0) - return 0; - - /* This is a QCOW2 image, let's convert it */ - r = tempfn_random(i->final_path, NULL, &t); - if (r < 0) - return log_oom(); - - converted_fd = open(t, O_RDWR|O_CREAT|O_EXCL|O_NOCTTY|O_CLOEXEC, 0664); - if (converted_fd < 0) - return log_error_errno(errno, "Failed to create %s: %m", t); - - r = chattr_fd(converted_fd, FS_NOCOW_FL, FS_NOCOW_FL); - if (r < 0) - log_warning_errno(r, "Failed to set file attributes on %s: %m", t); - - log_info("Unpacking QCOW2 file."); - - r = qcow2_convert(i->raw_job->disk_fd, converted_fd); - if (r < 0) { - unlink(t); - return log_error_errno(r, "Failed to convert qcow2 image: %m"); - } - - (void) unlink(i->temp_path); - free(i->temp_path); - i->temp_path = t; - t = NULL; - - safe_close(i->raw_job->disk_fd); - i->raw_job->disk_fd = converted_fd; - converted_fd = -1; - - return 1; -} - -static int raw_pull_make_local_copy(RawPull *i) { - _cleanup_free_ char *tp = NULL; - _cleanup_close_ int dfd = -1; - const char *p; - int r; - - assert(i); - assert(i->raw_job); - - if (!i->local) - return 0; - - if (!i->final_path) { - r = pull_make_path(i->raw_job->url, i->raw_job->etag, i->image_root, ".raw-", ".raw", &i->final_path); - if (r < 0) - return log_oom(); - } - - if (i->raw_job->etag_exists) { - /* We have downloaded this one previously, reopen it */ - - assert(i->raw_job->disk_fd < 0); - - i->raw_job->disk_fd = open(i->final_path, O_RDONLY|O_NOCTTY|O_CLOEXEC); - if (i->raw_job->disk_fd < 0) - return log_error_errno(errno, "Failed to open vendor image: %m"); - } else { - /* We freshly downloaded the image, use it */ - - assert(i->raw_job->disk_fd >= 0); - - if (lseek(i->raw_job->disk_fd, SEEK_SET, 0) == (off_t) -1) - return log_error_errno(errno, "Failed to seek to beginning of vendor image: %m"); - } - - p = strjoina(i->image_root, "/", i->local, ".raw"); - - if (i->force_local) - (void) rm_rf(p, REMOVE_ROOT|REMOVE_PHYSICAL|REMOVE_SUBVOLUME); - - r = tempfn_random(p, NULL, &tp); - if (r < 0) - return log_oom(); - - dfd = open(tp, O_WRONLY|O_CREAT|O_EXCL|O_NOCTTY|O_CLOEXEC, 0664); - if (dfd < 0) - return log_error_errno(errno, "Failed to create writable copy of image: %m"); - - /* Turn off COW writing. This should greatly improve - * performance on COW file systems like btrfs, since it - * reduces fragmentation caused by not allowing in-place - * writes. */ - r = chattr_fd(dfd, FS_NOCOW_FL, FS_NOCOW_FL); - if (r < 0) - log_warning_errno(r, "Failed to set file attributes on %s: %m", tp); - - r = copy_bytes(i->raw_job->disk_fd, dfd, (uint64_t) -1, true); - if (r < 0) { - unlink(tp); - return log_error_errno(r, "Failed to make writable copy of image: %m"); - } - - (void) copy_times(i->raw_job->disk_fd, dfd); - (void) copy_xattr(i->raw_job->disk_fd, dfd); - - dfd = safe_close(dfd); - - r = rename(tp, p); - if (r < 0) { - r = log_error_errno(errno, "Failed to move writable image into place: %m"); - unlink(tp); - return r; - } - - log_info("Created new local image '%s'.", i->local); - - if (i->settings) { - const char *local_settings; - assert(i->settings_job); - - if (!i->settings_path) { - r = pull_make_path(i->settings_job->url, i->settings_job->etag, i->image_root, ".settings-", NULL, &i->settings_path); - if (r < 0) - return log_oom(); - } - - local_settings = strjoina(i->image_root, "/", i->local, ".nspawn"); - - r = copy_file_atomic(i->settings_path, local_settings, 0644, i->force_local, 0); - if (r == -EEXIST) - log_warning_errno(r, "Settings file %s already exists, not replacing.", local_settings); - else if (r == -ENOENT) - log_debug_errno(r, "Skipping creation of settings file, since none was found."); - else if (r < 0) - log_warning_errno(r, "Failed to copy settings files %s, ignoring: %m", local_settings); - else - log_info("Created new settings file %s.", local_settings); - } - - return 0; -} - -static bool raw_pull_is_done(RawPull *i) { - assert(i); - assert(i->raw_job); - - if (!PULL_JOB_IS_COMPLETE(i->raw_job)) - return false; - if (i->settings_job && !PULL_JOB_IS_COMPLETE(i->settings_job)) - return false; - if (i->checksum_job && !PULL_JOB_IS_COMPLETE(i->checksum_job)) - return false; - if (i->signature_job && !PULL_JOB_IS_COMPLETE(i->signature_job)) - return false; - - return true; -} - -static void raw_pull_job_on_finished(PullJob *j) { - RawPull *i; - int r; - - assert(j); - assert(j->userdata); - - i = j->userdata; - if (j == i->settings_job) { - if (j->error != 0) - log_info_errno(j->error, "Settings file could not be retrieved, proceeding without."); - } else if (j->error != 0) { - if (j == i->checksum_job) - log_error_errno(j->error, "Failed to retrieve SHA256 checksum, cannot verify. (Try --verify=no?)"); - else if (j == i->signature_job) - log_error_errno(j->error, "Failed to retrieve signature file, cannot verify. (Try --verify=no?)"); - else - log_error_errno(j->error, "Failed to retrieve image file. (Wrong URL?)"); - - r = j->error; - goto finish; - } - - /* This is invoked if either the download completed - * successfully, or the download was skipped because we - * already have the etag. In this case ->etag_exists is - * true. - * - * We only do something when we got all three files */ - - if (!raw_pull_is_done(i)) - return; - - if (i->settings_job) - i->settings_job->disk_fd = safe_close(i->settings_job->disk_fd); - - if (!i->raw_job->etag_exists) { - /* This is a new download, verify it, and move it into place */ - assert(i->raw_job->disk_fd >= 0); - - raw_pull_report_progress(i, RAW_VERIFYING); - - r = pull_verify(i->raw_job, i->settings_job, i->checksum_job, i->signature_job); - if (r < 0) - goto finish; - - raw_pull_report_progress(i, RAW_UNPACKING); - - r = raw_pull_maybe_convert_qcow2(i); - if (r < 0) - goto finish; - - raw_pull_report_progress(i, RAW_FINALIZING); - - r = import_make_read_only_fd(i->raw_job->disk_fd); - if (r < 0) - goto finish; - - r = rename_noreplace(AT_FDCWD, i->temp_path, AT_FDCWD, i->final_path); - if (r < 0) { - log_error_errno(r, "Failed to move RAW file into place: %m"); - goto finish; - } - - i->temp_path = mfree(i->temp_path); - - if (i->settings_job && - i->settings_job->error == 0 && - !i->settings_job->etag_exists) { - - assert(i->settings_temp_path); - assert(i->settings_path); - - r = import_make_read_only(i->settings_temp_path); - if (r < 0) - goto finish; - - r = rename_noreplace(AT_FDCWD, i->settings_temp_path, AT_FDCWD, i->settings_path); - if (r < 0) { - log_error_errno(r, "Failed to rename settings file: %m"); - goto finish; - } - - i->settings_temp_path = mfree(i->settings_temp_path); - } - } - - raw_pull_report_progress(i, RAW_COPYING); - - r = raw_pull_make_local_copy(i); - if (r < 0) - goto finish; - - r = 0; - -finish: - if (i->on_finished) - i->on_finished(i, r, i->userdata); - else - sd_event_exit(i->event, r); -} - -static int raw_pull_job_on_open_disk_raw(PullJob *j) { - RawPull *i; - int r; - - assert(j); - assert(j->userdata); - - i = j->userdata; - assert(i->raw_job == j); - assert(!i->final_path); - assert(!i->temp_path); - - r = pull_make_path(j->url, j->etag, i->image_root, ".raw-", ".raw", &i->final_path); - if (r < 0) - return log_oom(); - - r = tempfn_random(i->final_path, NULL, &i->temp_path); - if (r < 0) - return log_oom(); - - (void) mkdir_parents_label(i->temp_path, 0700); - - j->disk_fd = open(i->temp_path, O_RDWR|O_CREAT|O_EXCL|O_NOCTTY|O_CLOEXEC, 0664); - if (j->disk_fd < 0) - return log_error_errno(errno, "Failed to create %s: %m", i->temp_path); - - r = chattr_fd(j->disk_fd, FS_NOCOW_FL, FS_NOCOW_FL); - if (r < 0) - log_warning_errno(r, "Failed to set file attributes on %s: %m", i->temp_path); - - return 0; -} - -static int raw_pull_job_on_open_disk_settings(PullJob *j) { - RawPull *i; - int r; - - assert(j); - assert(j->userdata); - - i = j->userdata; - assert(i->settings_job == j); - assert(!i->settings_path); - assert(!i->settings_temp_path); - - r = pull_make_path(j->url, j->etag, i->image_root, ".settings-", NULL, &i->settings_path); - if (r < 0) - return log_oom(); - - r = tempfn_random(i->settings_path, NULL, &i->settings_temp_path); - if (r < 0) - return log_oom(); - - mkdir_parents_label(i->settings_temp_path, 0700); - - j->disk_fd = open(i->settings_temp_path, O_RDWR|O_CREAT|O_EXCL|O_NOCTTY|O_CLOEXEC, 0664); - if (j->disk_fd < 0) - return log_error_errno(errno, "Failed to create %s: %m", i->settings_temp_path); - - return 0; -} - -static void raw_pull_job_on_progress(PullJob *j) { - RawPull *i; - - assert(j); - assert(j->userdata); - - i = j->userdata; - - raw_pull_report_progress(i, RAW_DOWNLOADING); -} - -int raw_pull_start( - RawPull *i, - const char *url, - const char *local, - bool force_local, - ImportVerify verify, - bool settings) { - - int r; - - assert(i); - assert(verify < _IMPORT_VERIFY_MAX); - assert(verify >= 0); - - if (!http_url_is_valid(url)) - return -EINVAL; - - if (local && !machine_name_is_valid(local)) - return -EINVAL; - - if (i->raw_job) - return -EBUSY; - - r = free_and_strdup(&i->local, local); - if (r < 0) - return r; - - i->force_local = force_local; - i->verify = verify; - i->settings = settings; - - /* Queue job for the image itself */ - r = pull_job_new(&i->raw_job, url, i->glue, i); - if (r < 0) - return r; - - i->raw_job->on_finished = raw_pull_job_on_finished; - i->raw_job->on_open_disk = raw_pull_job_on_open_disk_raw; - i->raw_job->on_progress = raw_pull_job_on_progress; - i->raw_job->calc_checksum = verify != IMPORT_VERIFY_NO; - i->raw_job->grow_machine_directory = i->grow_machine_directory; - - r = pull_find_old_etags(url, i->image_root, DT_REG, ".raw-", ".raw", &i->raw_job->old_etags); - if (r < 0) - return r; - - if (settings) { - r = pull_make_settings_job(&i->settings_job, url, i->glue, raw_pull_job_on_finished, i); - if (r < 0) - return r; - - i->settings_job->on_open_disk = raw_pull_job_on_open_disk_settings; - i->settings_job->on_progress = raw_pull_job_on_progress; - i->settings_job->calc_checksum = verify != IMPORT_VERIFY_NO; - - r = pull_find_old_etags(i->settings_job->url, i->image_root, DT_REG, ".settings-", NULL, &i->settings_job->old_etags); - if (r < 0) - return r; - } - - r = pull_make_verification_jobs(&i->checksum_job, &i->signature_job, verify, url, i->glue, raw_pull_job_on_finished, i); - if (r < 0) - return r; - - r = pull_job_begin(i->raw_job); - if (r < 0) - return r; - - if (i->settings_job) { - r = pull_job_begin(i->settings_job); - if (r < 0) - return r; - } - - if (i->checksum_job) { - i->checksum_job->on_progress = raw_pull_job_on_progress; - - r = pull_job_begin(i->checksum_job); - if (r < 0) - return r; - } - - if (i->signature_job) { - i->signature_job->on_progress = raw_pull_job_on_progress; - - r = pull_job_begin(i->signature_job); - if (r < 0) - return r; - } - - return 0; -} diff --git a/src/grp-import/systemd-pull/pull-raw.h b/src/grp-import/systemd-pull/pull-raw.h deleted file mode 100644 index 84cd146647..0000000000 --- a/src/grp-import/systemd-pull/pull-raw.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 - -#include "shared/import-util.h" -#include "basic/macro.h" - -typedef struct RawPull RawPull; - -typedef void (*RawPullFinished)(RawPull *pull, int error, void *userdata); - -int raw_pull_new(RawPull **pull, sd_event *event, const char *image_root, RawPullFinished on_finished, void *userdata); -RawPull* raw_pull_unref(RawPull *pull); - -DEFINE_TRIVIAL_CLEANUP_FUNC(RawPull*, raw_pull_unref); - -int raw_pull_start(RawPull *pull, const char *url, const char *local, bool force_local, ImportVerify verify, bool settings); diff --git a/src/grp-import/systemd-pull/pull-tar.c b/src/grp-import/systemd-pull/pull-tar.c deleted file mode 100644 index 576bda5a3d..0000000000 --- a/src/grp-import/systemd-pull/pull-tar.c +++ /dev/null @@ -1,563 +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 "basic/alloc-util.h" -#include "basic/btrfs-util.h" -#include "basic/copy.h" -#include "curl-util.h" -#include "basic/fd-util.h" -#include "basic/fileio.h" -#include "basic/fs-util.h" -#include "basic/hostname-util.h" -#include "import-common.h" -#include "shared/import-util.h" -#include "basic/macro.h" -#include "basic/mkdir.h" -#include "basic/path-util.h" -#include "basic/process-util.h" -#include "pull-common.h" -#include "pull-job.h" -#include "pull-tar.h" -#include "basic/rm-rf.h" -#include "basic/string-util.h" -#include "basic/strv.h" -#include "basic/utf8.h" -#include "basic/util.h" -#include "basic/web-util.h" - -typedef enum TarProgress { - TAR_DOWNLOADING, - TAR_VERIFYING, - TAR_FINALIZING, - TAR_COPYING, -} TarProgress; - -struct TarPull { - sd_event *event; - CurlGlue *glue; - - char *image_root; - - PullJob *tar_job; - PullJob *settings_job; - PullJob *checksum_job; - PullJob *signature_job; - - TarPullFinished on_finished; - void *userdata; - - char *local; - bool force_local; - bool grow_machine_directory; - bool settings; - - pid_t tar_pid; - - char *final_path; - char *temp_path; - - char *settings_path; - char *settings_temp_path; - - ImportVerify verify; -}; - -TarPull* tar_pull_unref(TarPull *i) { - if (!i) - return NULL; - - if (i->tar_pid > 1) { - (void) kill_and_sigcont(i->tar_pid, SIGKILL); - (void) wait_for_terminate(i->tar_pid, NULL); - } - - pull_job_unref(i->tar_job); - pull_job_unref(i->settings_job); - pull_job_unref(i->checksum_job); - pull_job_unref(i->signature_job); - - curl_glue_unref(i->glue); - sd_event_unref(i->event); - - if (i->temp_path) { - (void) rm_rf(i->temp_path, REMOVE_ROOT|REMOVE_PHYSICAL|REMOVE_SUBVOLUME); - free(i->temp_path); - } - - if (i->settings_temp_path) { - (void) unlink(i->settings_temp_path); - free(i->settings_temp_path); - } - - free(i->final_path); - free(i->settings_path); - free(i->image_root); - free(i->local); - free(i); - - return NULL; -} - -int tar_pull_new( - TarPull **ret, - sd_event *event, - const char *image_root, - TarPullFinished on_finished, - void *userdata) { - - _cleanup_(tar_pull_unrefp) TarPull *i = NULL; - int r; - - assert(ret); - - i = new0(TarPull, 1); - if (!i) - return -ENOMEM; - - i->on_finished = on_finished; - i->userdata = userdata; - - i->image_root = strdup(image_root ?: "/var/lib/machines"); - if (!i->image_root) - return -ENOMEM; - - i->grow_machine_directory = path_startswith(i->image_root, "/var/lib/machines"); - - if (event) - i->event = sd_event_ref(event); - else { - r = sd_event_default(&i->event); - if (r < 0) - return r; - } - - r = curl_glue_new(&i->glue, i->event); - if (r < 0) - return r; - - i->glue->on_finished = pull_job_curl_on_finished; - i->glue->userdata = i; - - *ret = i; - i = NULL; - - return 0; -} - -static void tar_pull_report_progress(TarPull *i, TarProgress p) { - unsigned percent; - - assert(i); - - switch (p) { - - case TAR_DOWNLOADING: { - unsigned remain = 85; - - percent = 0; - - if (i->settings_job) { - percent += i->settings_job->progress_percent * 5 / 100; - remain -= 5; - } - - if (i->checksum_job) { - percent += i->checksum_job->progress_percent * 5 / 100; - remain -= 5; - } - - if (i->signature_job) { - percent += i->signature_job->progress_percent * 5 / 100; - remain -= 5; - } - - if (i->tar_job) - percent += i->tar_job->progress_percent * remain / 100; - break; - } - - case TAR_VERIFYING: - percent = 85; - break; - - case TAR_FINALIZING: - percent = 90; - break; - - case TAR_COPYING: - percent = 95; - break; - - default: - assert_not_reached("Unknown progress state"); - } - - sd_notifyf(false, "X_IMPORT_PROGRESS=%u", percent); - log_debug("Combined progress %u%%", percent); -} - -static int tar_pull_make_local_copy(TarPull *i) { - int r; - - assert(i); - assert(i->tar_job); - - if (!i->local) - return 0; - - if (!i->final_path) { - r = pull_make_path(i->tar_job->url, i->tar_job->etag, i->image_root, ".tar-", NULL, &i->final_path); - if (r < 0) - return log_oom(); - } - - r = pull_make_local_copy(i->final_path, i->image_root, i->local, i->force_local); - if (r < 0) - return r; - - if (i->settings) { - const char *local_settings; - assert(i->settings_job); - - if (!i->settings_path) { - r = pull_make_path(i->settings_job->url, i->settings_job->etag, i->image_root, ".settings-", NULL, &i->settings_path); - if (r < 0) - return log_oom(); - } - - local_settings = strjoina(i->image_root, "/", i->local, ".nspawn"); - - r = copy_file_atomic(i->settings_path, local_settings, 0664, i->force_local, 0); - if (r == -EEXIST) - log_warning_errno(r, "Settings file %s already exists, not replacing.", local_settings); - else if (r == -ENOENT) - log_debug_errno(r, "Skipping creation of settings file, since none was found."); - else if (r < 0) - log_warning_errno(r, "Failed to copy settings files %s, ignoring: %m", local_settings); - else - log_info("Created new settings file %s.", local_settings); - } - - return 0; -} - -static bool tar_pull_is_done(TarPull *i) { - assert(i); - assert(i->tar_job); - - if (!PULL_JOB_IS_COMPLETE(i->tar_job)) - return false; - if (i->settings_job && !PULL_JOB_IS_COMPLETE(i->settings_job)) - return false; - if (i->checksum_job && !PULL_JOB_IS_COMPLETE(i->checksum_job)) - return false; - if (i->signature_job && !PULL_JOB_IS_COMPLETE(i->signature_job)) - return false; - - return true; -} - -static void tar_pull_job_on_finished(PullJob *j) { - TarPull *i; - int r; - - assert(j); - assert(j->userdata); - - i = j->userdata; - - if (j == i->settings_job) { - if (j->error != 0) - log_info_errno(j->error, "Settings file could not be retrieved, proceeding without."); - } else if (j->error != 0) { - if (j == i->checksum_job) - log_error_errno(j->error, "Failed to retrieve SHA256 checksum, cannot verify. (Try --verify=no?)"); - else if (j == i->signature_job) - log_error_errno(j->error, "Failed to retrieve signature file, cannot verify. (Try --verify=no?)"); - else - log_error_errno(j->error, "Failed to retrieve image file. (Wrong URL?)"); - - r = j->error; - goto finish; - } - - /* This is invoked if either the download completed - * successfully, or the download was skipped because we - * already have the etag. */ - - if (!tar_pull_is_done(i)) - return; - - i->tar_job->disk_fd = safe_close(i->tar_job->disk_fd); - if (i->settings_job) - i->settings_job->disk_fd = safe_close(i->settings_job->disk_fd); - - if (i->tar_pid > 0) { - r = wait_for_terminate_and_warn("tar", i->tar_pid, true); - i->tar_pid = 0; - if (r < 0) - goto finish; - if (r > 0) { - r = -EIO; - goto finish; - } - } - - if (!i->tar_job->etag_exists) { - /* This is a new download, verify it, and move it into place */ - - tar_pull_report_progress(i, TAR_VERIFYING); - - r = pull_verify(i->tar_job, i->settings_job, i->checksum_job, i->signature_job); - if (r < 0) - goto finish; - - tar_pull_report_progress(i, TAR_FINALIZING); - - r = import_make_read_only(i->temp_path); - if (r < 0) - goto finish; - - r = rename_noreplace(AT_FDCWD, i->temp_path, AT_FDCWD, i->final_path); - if (r < 0) { - log_error_errno(r, "Failed to rename to final image name: %m"); - goto finish; - } - - i->temp_path = mfree(i->temp_path); - - if (i->settings_job && - i->settings_job->error == 0 && - !i->settings_job->etag_exists) { - - assert(i->settings_temp_path); - assert(i->settings_path); - - /* Also move the settings file into place, if - * it exist. Note that we do so only if we - * also moved the tar file in place, to keep - * things strictly in sync. */ - - r = import_make_read_only(i->settings_temp_path); - if (r < 0) - goto finish; - - r = rename_noreplace(AT_FDCWD, i->settings_temp_path, AT_FDCWD, i->settings_path); - if (r < 0) { - log_error_errno(r, "Failed to rename settings file: %m"); - goto finish; - } - - i->settings_temp_path = mfree(i->settings_temp_path); - } - } - - tar_pull_report_progress(i, TAR_COPYING); - - r = tar_pull_make_local_copy(i); - if (r < 0) - goto finish; - - r = 0; - -finish: - if (i->on_finished) - i->on_finished(i, r, i->userdata); - else - sd_event_exit(i->event, r); -} - -static int tar_pull_job_on_open_disk_tar(PullJob *j) { - TarPull *i; - int r; - - assert(j); - assert(j->userdata); - - i = j->userdata; - assert(i->tar_job == j); - assert(!i->final_path); - assert(!i->temp_path); - assert(i->tar_pid <= 0); - - r = pull_make_path(j->url, j->etag, i->image_root, ".tar-", NULL, &i->final_path); - if (r < 0) - return log_oom(); - - r = tempfn_random(i->final_path, NULL, &i->temp_path); - if (r < 0) - return log_oom(); - - mkdir_parents_label(i->temp_path, 0700); - - r = btrfs_subvol_make(i->temp_path); - if (r == -ENOTTY) { - if (mkdir(i->temp_path, 0755) < 0) - return log_error_errno(errno, "Failed to create directory %s: %m", i->temp_path); - } else if (r < 0) - return log_error_errno(r, "Failed to create subvolume %s: %m", i->temp_path); - else - (void) import_assign_pool_quota_and_warn(i->temp_path); - - j->disk_fd = import_fork_tar_x(i->temp_path, &i->tar_pid); - if (j->disk_fd < 0) - return j->disk_fd; - - return 0; -} - -static int tar_pull_job_on_open_disk_settings(PullJob *j) { - TarPull *i; - int r; - - assert(j); - assert(j->userdata); - - i = j->userdata; - assert(i->settings_job == j); - assert(!i->settings_path); - assert(!i->settings_temp_path); - - r = pull_make_path(j->url, j->etag, i->image_root, ".settings-", NULL, &i->settings_path); - if (r < 0) - return log_oom(); - - r = tempfn_random(i->settings_path, NULL, &i->settings_temp_path); - if (r < 0) - return log_oom(); - - mkdir_parents_label(i->settings_temp_path, 0700); - - j->disk_fd = open(i->settings_temp_path, O_RDWR|O_CREAT|O_EXCL|O_NOCTTY|O_CLOEXEC, 0664); - if (j->disk_fd < 0) - return log_error_errno(errno, "Failed to create %s: %m", i->settings_temp_path); - - return 0; -} - -static void tar_pull_job_on_progress(PullJob *j) { - TarPull *i; - - assert(j); - assert(j->userdata); - - i = j->userdata; - - tar_pull_report_progress(i, TAR_DOWNLOADING); -} - -int tar_pull_start( - TarPull *i, - const char *url, - const char *local, - bool force_local, - ImportVerify verify, - bool settings) { - - int r; - - assert(i); - assert(verify < _IMPORT_VERIFY_MAX); - assert(verify >= 0); - - if (!http_url_is_valid(url)) - return -EINVAL; - - if (local && !machine_name_is_valid(local)) - return -EINVAL; - - if (i->tar_job) - return -EBUSY; - - r = free_and_strdup(&i->local, local); - if (r < 0) - return r; - - i->force_local = force_local; - i->verify = verify; - i->settings = settings; - - /* Set up download job for TAR file */ - r = pull_job_new(&i->tar_job, url, i->glue, i); - if (r < 0) - return r; - - i->tar_job->on_finished = tar_pull_job_on_finished; - i->tar_job->on_open_disk = tar_pull_job_on_open_disk_tar; - i->tar_job->on_progress = tar_pull_job_on_progress; - i->tar_job->calc_checksum = verify != IMPORT_VERIFY_NO; - i->tar_job->grow_machine_directory = i->grow_machine_directory; - - r = pull_find_old_etags(url, i->image_root, DT_DIR, ".tar-", NULL, &i->tar_job->old_etags); - if (r < 0) - return r; - - /* Set up download job for the settings file (.nspawn) */ - if (settings) { - r = pull_make_settings_job(&i->settings_job, url, i->glue, tar_pull_job_on_finished, i); - if (r < 0) - return r; - - i->settings_job->on_open_disk = tar_pull_job_on_open_disk_settings; - i->settings_job->on_progress = tar_pull_job_on_progress; - i->settings_job->calc_checksum = verify != IMPORT_VERIFY_NO; - - r = pull_find_old_etags(i->settings_job->url, i->image_root, DT_REG, ".settings-", NULL, &i->settings_job->old_etags); - if (r < 0) - return r; - } - - /* Set up download of checksum/signature files */ - r = pull_make_verification_jobs(&i->checksum_job, &i->signature_job, verify, url, i->glue, tar_pull_job_on_finished, i); - if (r < 0) - return r; - - r = pull_job_begin(i->tar_job); - if (r < 0) - return r; - - if (i->settings_job) { - r = pull_job_begin(i->settings_job); - if (r < 0) - return r; - } - - if (i->checksum_job) { - i->checksum_job->on_progress = tar_pull_job_on_progress; - - r = pull_job_begin(i->checksum_job); - if (r < 0) - return r; - } - - if (i->signature_job) { - i->signature_job->on_progress = tar_pull_job_on_progress; - - r = pull_job_begin(i->signature_job); - if (r < 0) - return r; - } - - return 0; -} diff --git a/src/grp-import/systemd-pull/pull-tar.h b/src/grp-import/systemd-pull/pull-tar.h deleted file mode 100644 index addd1f861c..0000000000 --- a/src/grp-import/systemd-pull/pull-tar.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 "shared/import-util.h" -#include "basic/macro.h" - -typedef struct TarPull TarPull; - -typedef void (*TarPullFinished)(TarPull *pull, int error, void *userdata); - -int tar_pull_new(TarPull **pull, sd_event *event, const char *image_root, TarPullFinished on_finished, void *userdata); -TarPull* tar_pull_unref(TarPull *pull); - -DEFINE_TRIVIAL_CLEANUP_FUNC(TarPull*, tar_pull_unref); - -int tar_pull_start(TarPull *pull, const char *url, const char *local, bool force_local, ImportVerify verify, bool settings); diff --git a/src/grp-import/systemd-pull/pull.c b/src/grp-import/systemd-pull/pull.c deleted file mode 100644 index fa2af2fc02..0000000000 --- a/src/grp-import/systemd-pull/pull.c +++ /dev/null @@ -1,337 +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 "basic/alloc-util.h" -#include "basic/hostname-util.h" -#include "shared/import-util.h" -#include "shared/machine-image.h" -#include "basic/parse-util.h" -#include "pull-raw.h" -#include "pull-tar.h" -#include "basic/signal-util.h" -#include "basic/string-util.h" -#include "basic/verbs.h" -#include "basic/web-util.h" - -static bool arg_force = false; -static const char *arg_image_root = "/var/lib/machines"; -static ImportVerify arg_verify = IMPORT_VERIFY_SIGNATURE; -static bool arg_settings = true; - -static int interrupt_signal_handler(sd_event_source *s, const struct signalfd_siginfo *si, void *userdata) { - log_notice("Transfer aborted."); - sd_event_exit(sd_event_source_get_event(s), EINTR); - return 0; -} - -static void on_tar_finished(TarPull *pull, int error, void *userdata) { - sd_event *event = userdata; - assert(pull); - - if (error == 0) - log_info("Operation completed successfully."); - - sd_event_exit(event, abs(error)); -} - -static int pull_tar(int argc, char *argv[], void *userdata) { - _cleanup_(tar_pull_unrefp) TarPull *pull = NULL; - _cleanup_(sd_event_unrefp) sd_event *event = NULL; - const char *url, *local; - _cleanup_free_ char *l = NULL, *ll = NULL; - int r; - - url = argv[1]; - if (!http_url_is_valid(url)) { - log_error("URL '%s' is not valid.", url); - return -EINVAL; - } - - if (argc >= 3) - local = argv[2]; - else { - r = import_url_last_component(url, &l); - if (r < 0) - return log_error_errno(r, "Failed 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 image name '%s' is not valid.", local); - return -EINVAL; - } - - if (!arg_force) { - r = image_find(local, NULL); - if (r < 0) - return log_error_errno(r, "Failed to check whether image '%s' exists: %m", local); - else if (r > 0) { - log_error_errno(EEXIST, "Image '%s' already exists.", local); - return -EEXIST; - } - } - - log_info("Pulling '%s', saving as '%s'.", url, local); - } else - log_info("Pulling '%s'.", url); - - r = sd_event_default(&event); - if (r < 0) - return log_error_errno(r, "Failed to allocate event loop: %m"); - - assert_se(sigprocmask_many(SIG_BLOCK, NULL, SIGTERM, SIGINT, -1) >= 0); - (void) sd_event_add_signal(event, NULL, SIGTERM, interrupt_signal_handler, NULL); - (void) sd_event_add_signal(event, NULL, SIGINT, interrupt_signal_handler, NULL); - - r = tar_pull_new(&pull, event, arg_image_root, on_tar_finished, event); - if (r < 0) - return log_error_errno(r, "Failed to allocate puller: %m"); - - r = tar_pull_start(pull, url, local, arg_force, arg_verify, arg_settings); - if (r < 0) - return log_error_errno(r, "Failed to pull image: %m"); - - r = sd_event_loop(event); - if (r < 0) - return log_error_errno(r, "Failed to run event loop: %m"); - - log_info("Exiting."); - return -r; -} - -static void on_raw_finished(RawPull *pull, int error, void *userdata) { - sd_event *event = userdata; - assert(pull); - - if (error == 0) - log_info("Operation completed successfully."); - - sd_event_exit(event, abs(error)); -} - -static int pull_raw(int argc, char *argv[], void *userdata) { - _cleanup_(raw_pull_unrefp) RawPull *pull = NULL; - _cleanup_(sd_event_unrefp) sd_event *event = NULL; - const char *url, *local; - _cleanup_free_ char *l = NULL, *ll = NULL; - int r; - - url = argv[1]; - if (!http_url_is_valid(url)) { - log_error("URL '%s' is not valid.", url); - return -EINVAL; - } - - if (argc >= 3) - local = argv[2]; - else { - r = import_url_last_component(url, &l); - if (r < 0) - return log_error_errno(r, "Failed 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 image name '%s' is not valid.", local); - return -EINVAL; - } - - if (!arg_force) { - r = image_find(local, NULL); - if (r < 0) - return log_error_errno(r, "Failed to check whether image '%s' exists: %m", local); - else if (r > 0) { - log_error_errno(EEXIST, "Image '%s' already exists.", local); - return -EEXIST; - } - } - - log_info("Pulling '%s', saving as '%s'.", url, local); - } else - log_info("Pulling '%s'.", url); - - r = sd_event_default(&event); - if (r < 0) - return log_error_errno(r, "Failed to allocate event loop: %m"); - - assert_se(sigprocmask_many(SIG_BLOCK, NULL, SIGTERM, SIGINT, -1) >= 0); - (void) sd_event_add_signal(event, NULL, SIGTERM, interrupt_signal_handler, NULL); - (void) sd_event_add_signal(event, NULL, SIGINT, interrupt_signal_handler, NULL); - - r = raw_pull_new(&pull, event, arg_image_root, on_raw_finished, event); - if (r < 0) - return log_error_errno(r, "Failed to allocate puller: %m"); - - r = raw_pull_start(pull, url, local, arg_force, arg_verify, arg_settings); - if (r < 0) - return log_error_errno(r, "Failed to pull image: %m"); - - r = sd_event_loop(event); - if (r < 0) - return log_error_errno(r, "Failed to run event loop: %m"); - - log_info("Exiting."); - return -r; -} - -static int help(int argc, char *argv[], void *userdata) { - - printf("%s [OPTIONS...] {COMMAND} ...\n\n" - "Download container or virtual machine images.\n\n" - " -h --help Show this help\n" - " --version Show package version\n" - " --force Force creation of image\n" - " --verify=MODE Verify downloaded image, one of: 'no',\n" - " 'checksum', 'signature'\n" - " --settings=BOOL Download settings file with image\n" - " --image-root=PATH Image root directory\n\n" - "Commands:\n" - " tar URL [NAME] Download a TAR image\n" - " raw URL [NAME] Download a RAW image\n", - program_invocation_short_name); - - return 0; -} - -static int parse_argv(int argc, char *argv[]) { - - enum { - ARG_VERSION = 0x100, - ARG_FORCE, - ARG_IMAGE_ROOT, - ARG_VERIFY, - ARG_SETTINGS, - }; - - static const struct option options[] = { - { "help", no_argument, NULL, 'h' }, - { "version", no_argument, NULL, ARG_VERSION }, - { "force", no_argument, NULL, ARG_FORCE }, - { "image-root", required_argument, NULL, ARG_IMAGE_ROOT }, - { "verify", required_argument, NULL, ARG_VERIFY }, - { "settings", required_argument, NULL, ARG_SETTINGS }, - {} - }; - - int c, r; - - assert(argc >= 0); - assert(argv); - - while ((c = getopt_long(argc, argv, "h", options, NULL)) >= 0) - - switch (c) { - - case 'h': - return help(0, NULL, NULL); - - case ARG_VERSION: - return version(); - - case ARG_FORCE: - arg_force = true; - break; - - case ARG_IMAGE_ROOT: - arg_image_root = optarg; - break; - - case ARG_VERIFY: - arg_verify = import_verify_from_string(optarg); - if (arg_verify < 0) { - log_error("Invalid verification setting '%s'", optarg); - return -EINVAL; - } - - break; - - case ARG_SETTINGS: - r = parse_boolean(optarg); - if (r < 0) - return log_error_errno(r, "Failed to parse --settings= parameter '%s'", optarg); - - arg_settings = r; - break; - - case '?': - return -EINVAL; - - default: - assert_not_reached("Unhandled option"); - } - - return 1; -} - -static int pull_main(int argc, char *argv[]) { - - static const Verb verbs[] = { - { "help", VERB_ANY, VERB_ANY, 0, help }, - { "tar", 2, 3, 0, pull_tar }, - { "raw", 2, 3, 0, pull_raw }, - {} - }; - - return dispatch_verb(argc, argv, verbs, NULL); -} - -int main(int argc, char *argv[]) { - int r; - - setlocale(LC_ALL, ""); - log_parse_environment(); - log_open(); - - r = parse_argv(argc, argv); - if (r <= 0) - goto finish; - - (void) ignore_signals(SIGPIPE, -1); - - r = pull_main(argc, argv); - -finish: - return r < 0 ? EXIT_FAILURE : EXIT_SUCCESS; -} diff --git a/src/grp-import/test-qcow2.c b/src/grp-import/test-qcow2.c deleted file mode 100644 index 0a256d7f41..0000000000 --- a/src/grp-import/test-qcow2.c +++ /dev/null @@ -1,53 +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 "basic/fd-util.h" -#include "basic/log.h" -#include "qcow2-util.h" -#include "basic/util.h" - -int main(int argc, char *argv[]) { - _cleanup_close_ int sfd = -1, dfd = -1; - int r; - - if (argc != 3) { - log_error("Needs two arguments."); - return EXIT_FAILURE; - } - - sfd = open(argv[1], O_RDONLY|O_CLOEXEC|O_NOCTTY); - if (sfd < 0) { - log_error_errno(errno, "Can't open source file: %m"); - return EXIT_FAILURE; - } - - dfd = open(argv[2], O_WRONLY|O_CREAT|O_CLOEXEC|O_NOCTTY, 0666); - if (dfd < 0) { - log_error_errno(errno, "Can't open destination file: %m"); - return EXIT_FAILURE; - } - - r = qcow2_convert(sfd, dfd); - if (r < 0) { - log_error_errno(r, "Failed to unpack: %m"); - return EXIT_FAILURE; - } - - return EXIT_SUCCESS; -} diff --git a/src/grp-initutils/Makefile b/src/grp-initutils/Makefile new file mode 100644 index 0000000000..8760b1cd32 --- /dev/null +++ b/src/grp-initutils/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 + +at.subdirs += systemd-backlight +at.subdirs += systemd-binfmt +at.subdirs += systemd-detect-virt +at.subdirs += systemd-firstboot +at.subdirs += systemd-fsck +at.subdirs += systemd-quotacheck +at.subdirs += systemd-random-seed +at.subdirs += systemd-rfkill +at.subdirs += systemd-sleep +at.subdirs += systemd-sysctl +at.subdirs += systemd-sysusers +at.subdirs += systemd-tmpfiles +at.subdirs += systemd-update-done +at.subdirs += systemd-update-utmp +at.subdirs += systemd-user-sessions +at.subdirs += systemd-vconsole-setup + +include $(topsrcdir)/build-aux/Makefile.tail.mk diff --git a/src/grp-initutils/systemd-backlight/Makefile b/src/grp-initutils/systemd-backlight/Makefile new file mode 100644 index 0000000000..cf55ac4db9 --- /dev/null +++ b/src/grp-initutils/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 # ENABLE_BACKLIGHT + +EXTRA_DIST += \ + units/systemd-backlight@.service.in + +include $(topsrcdir)/build-aux/Makefile.tail.mk diff --git a/src/grp-initutils/systemd-backlight/backlight.c b/src/grp-initutils/systemd-backlight/backlight.c new file mode 100644 index 0000000000..c7931ac584 --- /dev/null +++ b/src/grp-initutils/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 "basic/alloc-util.h" +#include "basic/def.h" +#include "basic/escape.h" +#include "basic/fileio.h" +#include "basic/mkdir.h" +#include "basic/parse-util.h" +#include "basic/proc-cmdline.h" +#include "basic/string-util.h" +#include "shared/udev-util.h" +#include "basic/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/grp-initutils/systemd-binfmt/Makefile b/src/grp-initutils/systemd-binfmt/Makefile new file mode 100644 index 0000000000..3e5c1ac270 --- /dev/null +++ b/src/grp-initutils/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 # ENABLE_BINFMT + +EXTRA_DIST += \ + units/systemd-binfmt.service.in + +include $(topsrcdir)/build-aux/Makefile.tail.mk diff --git a/src/grp-initutils/systemd-binfmt/binfmt.c b/src/grp-initutils/systemd-binfmt/binfmt.c new file mode 100644 index 0000000000..39bbdb5335 --- /dev/null +++ b/src/grp-initutils/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 "basic/alloc-util.h" +#include "basic/conf-files.h" +#include "basic/def.h" +#include "basic/fd-util.h" +#include "basic/fileio.h" +#include "basic/log.h" +#include "basic/string-util.h" +#include "basic/strv.h" +#include "basic/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/grp-initutils/systemd-detect-virt/Makefile b/src/grp-initutils/systemd-detect-virt/Makefile new file mode 100644 index 0000000000..7b9b9f667b --- /dev/null +++ b/src/grp-initutils/systemd-detect-virt/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 + +bin_PROGRAMS += systemd-detect-virt +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/grp-initutils/systemd-detect-virt/detect-virt.c b/src/grp-initutils/systemd-detect-virt/detect-virt.c new file mode 100644 index 0000000000..06cdab5297 --- /dev/null +++ b/src/grp-initutils/systemd-detect-virt/detect-virt.c @@ -0,0 +1,169 @@ +/*** + This file is part of systemd. + + Copyright 2010 Lennart Poettering + + systemd is free software; you can redistribute it and/or modify it + under the terms of the GNU Lesser General Public License as published by + the Free Software Foundation; either version 2.1 of the License, or + (at your option) any later version. + + systemd is distributed in the hope that it will be useful, but + WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + Lesser General Public License for more details. + + You should have received a copy of the GNU Lesser General Public License + along with systemd; If not, see . +***/ + +#include +#include +#include +#include + +#include "basic/util.h" +#include "basic/virt.h" + +static bool arg_quiet = false; +static enum { + ANY_VIRTUALIZATION, + ONLY_VM, + ONLY_CONTAINER, + ONLY_CHROOT, +} arg_mode = ANY_VIRTUALIZATION; + +static void help(void) { + printf("%s [OPTIONS...]\n\n" + "Detect execution in a virtualized environment.\n\n" + " -h --help Show this help\n" + " --version Show package version\n" + " -c --container Only detect whether we are run in a container\n" + " -v --vm Only detect whether we are run in a VM\n" + " -r --chroot Detect whether we are run in a chroot() environment\n" + " -q --quiet Don't output anything, just set return value\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 }, + { "container", no_argument, NULL, 'c' }, + { "vm", no_argument, NULL, 'v' }, + { "chroot", no_argument, NULL, 'r' }, + { "quiet", no_argument, NULL, 'q' }, + {} + }; + + int c; + + assert(argc >= 0); + assert(argv); + + while ((c = getopt_long(argc, argv, "hqcvr", options, NULL)) >= 0) + + switch (c) { + + case 'h': + help(); + return 0; + + case ARG_VERSION: + return version(); + + case 'q': + arg_quiet = true; + break; + + case 'c': + arg_mode = ONLY_CONTAINER; + break; + + case 'v': + arg_mode = ONLY_VM; + break; + + case 'r': + arg_mode = ONLY_CHROOT; + 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; + + /* This is mostly intended to be used for scripts which want + * to detect whether we are being run in a virtualized + * environment or not */ + + log_parse_environment(); + log_open(); + + r = parse_argv(argc, argv); + if (r <= 0) + return r < 0 ? EXIT_FAILURE : EXIT_SUCCESS; + + switch (arg_mode) { + + case ONLY_VM: + r = detect_vm(); + if (r < 0) { + log_error_errno(r, "Failed to check for VM: %m"); + return EXIT_FAILURE; + } + + break; + + case ONLY_CONTAINER: + r = detect_container(); + if (r < 0) { + log_error_errno(r, "Failed to check for container: %m"); + return EXIT_FAILURE; + } + + break; + + case ONLY_CHROOT: + r = running_in_chroot(); + if (r < 0) { + log_error_errno(r, "Failed to check for chroot() environment: %m"); + return EXIT_FAILURE; + } + + return r ? EXIT_SUCCESS : EXIT_FAILURE; + + case ANY_VIRTUALIZATION: + default: + r = detect_virtualization(); + if (r < 0) { + log_error_errno(r, "Failed to check for virtualization: %m"); + return EXIT_FAILURE; + } + + break; + } + + if (!arg_quiet) + puts(virtualization_to_string(r)); + + return r != VIRTUALIZATION_NONE ? EXIT_SUCCESS : EXIT_FAILURE; +} diff --git a/src/grp-initutils/systemd-firstboot/Makefile b/src/grp-initutils/systemd-firstboot/Makefile new file mode 100644 index 0000000000..b94b344bb9 --- /dev/null +++ b/src/grp-initutils/systemd-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 # ENABLE_FIRSTBOOT + +EXTRA_DIST += \ + units/systemd-firstboot.service.in + +include $(topsrcdir)/build-aux/Makefile.tail.mk diff --git a/src/grp-initutils/systemd-firstboot/firstboot.c b/src/grp-initutils/systemd-firstboot/firstboot.c new file mode 100644 index 0000000000..d2059a943f --- /dev/null +++ b/src/grp-initutils/systemd-firstboot/firstboot.c @@ -0,0 +1,870 @@ +/*** + 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 "basic/alloc-util.h" +#include "basic/copy.h" +#include "basic/fd-util.h" +#include "basic/fileio.h" +#include "basic/fs-util.h" +#include "basic/hostname-util.h" +#include "basic/locale-util.h" +#include "basic/mkdir.h" +#include "basic/parse-util.h" +#include "basic/path-util.h" +#include "basic/random-util.h" +#include "basic/string-util.h" +#include "basic/strv.h" +#include "basic/terminal-util.h" +#include "basic/time-util.h" +#include "basic/umask-util.h" +#include "basic/user-util.h" +#include "shared/ask-password-api.h" + +static char *arg_root = NULL; +static char *arg_locale = NULL; /* $LANG */ +static char *arg_locale_messages = NULL; /* $LC_MESSAGES */ +static char *arg_timezone = NULL; +static char *arg_hostname = NULL; +static sd_id128_t arg_machine_id = {}; +static char *arg_root_password = NULL; +static bool arg_prompt_locale = false; +static bool arg_prompt_timezone = false; +static bool arg_prompt_hostname = false; +static bool arg_prompt_root_password = false; +static bool arg_copy_locale = false; +static bool arg_copy_timezone = false; +static bool arg_copy_root_password = false; + +static bool press_any_key(void) { + char k = 0; + bool need_nl = true; + + printf("-- Press any key to proceed --"); + fflush(stdout); + + (void) read_one_char(stdin, &k, USEC_INFINITY, &need_nl); + + if (need_nl) + putchar('\n'); + + return k != 'q'; +} + +static void print_welcome(void) { + _cleanup_free_ char *pretty_name = NULL; + const char *os_release = NULL; + static bool done = false; + int r; + + if (done) + return; + + os_release = prefix_roota(arg_root, "/etc/os-release"); + r = parse_env_file(os_release, NEWLINE, + "PRETTY_NAME", &pretty_name, + NULL); + if (r == -ENOENT) { + + os_release = prefix_roota(arg_root, "/usr/lib/os-release"); + r = parse_env_file(os_release, NEWLINE, + "PRETTY_NAME", &pretty_name, + NULL); + } + + if (r < 0 && r != -ENOENT) + log_warning_errno(r, "Failed to read os-release file: %m"); + + printf("\nWelcome to your new installation of %s!\nPlease configure a few basic system settings:\n\n", + isempty(pretty_name) ? "GNU/Linux" : pretty_name); + + press_any_key(); + + done = true; +} + +static int show_menu(char **x, unsigned n_columns, unsigned width, unsigned percentage) { + unsigned n, per_column, i, j; + unsigned break_lines, break_modulo; + + assert(n_columns > 0); + + n = strv_length(x); + per_column = (n + n_columns - 1) / n_columns; + + break_lines = lines(); + if (break_lines > 2) + break_lines--; + + /* The first page gets two extra lines, since we want to show + * a title */ + break_modulo = break_lines; + if (break_modulo > 3) + break_modulo -= 3; + + for (i = 0; i < per_column; i++) { + + for (j = 0; j < n_columns; j ++) { + _cleanup_free_ char *e = NULL; + + if (j * per_column + i >= n) + break; + + e = ellipsize(x[j * per_column + i], width, percentage); + if (!e) + return log_oom(); + + printf("%4u) %-*s", j * per_column + i + 1, width, e); + } + + putchar('\n'); + + /* on the first screen we reserve 2 extra lines for the title */ + if (i % break_lines == break_modulo) { + if (!press_any_key()) + return 0; + } + } + + return 0; +} + +static int prompt_loop(const char *text, char **l, bool (*is_valid)(const char *name), char **ret) { + int r; + + assert(text); + assert(is_valid); + assert(ret); + + for (;;) { + _cleanup_free_ char *p = NULL; + unsigned u; + + r = ask_string(&p, "%s %s (empty to skip): ", special_glyph(TRIANGULAR_BULLET), text); + if (r < 0) + return log_error_errno(r, "Failed to query user: %m"); + + if (isempty(p)) { + log_warning("No data entered, skipping."); + return 0; + } + + r = safe_atou(p, &u); + if (r >= 0) { + char *c; + + if (u <= 0 || u > strv_length(l)) { + log_error("Specified entry number out of range."); + continue; + } + + log_info("Selected '%s'.", l[u-1]); + + c = strdup(l[u-1]); + if (!c) + return log_oom(); + + free(*ret); + *ret = c; + return 0; + } + + if (!is_valid(p)) { + log_error("Entered data invalid."); + continue; + } + + free(*ret); + *ret = p; + p = 0; + return 0; + } +} + +static int prompt_locale(void) { + _cleanup_strv_free_ char **locales = NULL; + int r; + + if (arg_locale || arg_locale_messages) + return 0; + + if (!arg_prompt_locale) + return 0; + + r = get_locales(&locales); + if (r < 0) + return log_error_errno(r, "Cannot query locales list: %m"); + + print_welcome(); + + printf("\nAvailable Locales:\n\n"); + r = show_menu(locales, 3, 22, 60); + if (r < 0) + return r; + + putchar('\n'); + + r = prompt_loop("Please enter system locale name or number", locales, locale_is_valid, &arg_locale); + if (r < 0) + return r; + + if (isempty(arg_locale)) + return 0; + + r = prompt_loop("Please enter system message locale name or number", locales, locale_is_valid, &arg_locale_messages); + if (r < 0) + return r; + + return 0; +} + +static int process_locale(void) { + const char *etc_localeconf; + char* locales[3]; + unsigned i = 0; + int r; + + etc_localeconf = prefix_roota(arg_root, "/etc/locale.conf"); + if (laccess(etc_localeconf, F_OK) >= 0) + return 0; + + if (arg_copy_locale && arg_root) { + + mkdir_parents(etc_localeconf, 0755); + r = copy_file("/etc/locale.conf", etc_localeconf, 0, 0644, 0); + if (r != -ENOENT) { + if (r < 0) + return log_error_errno(r, "Failed to copy %s: %m", etc_localeconf); + + log_info("%s copied.", etc_localeconf); + return 0; + } + } + + r = prompt_locale(); + if (r < 0) + return r; + + if (!isempty(arg_locale)) + locales[i++] = strjoina("LANG=", arg_locale); + if (!isempty(arg_locale_messages) && !streq(arg_locale_messages, arg_locale)) + locales[i++] = strjoina("LC_MESSAGES=", arg_locale_messages); + + if (i == 0) + return 0; + + locales[i] = NULL; + + mkdir_parents(etc_localeconf, 0755); + r = write_env_file(etc_localeconf, locales); + if (r < 0) + return log_error_errno(r, "Failed to write %s: %m", etc_localeconf); + + log_info("%s written.", etc_localeconf); + return 0; +} + +static int prompt_timezone(void) { + _cleanup_strv_free_ char **zones = NULL; + int r; + + if (arg_timezone) + return 0; + + if (!arg_prompt_timezone) + return 0; + + r = get_timezones(&zones); + if (r < 0) + return log_error_errno(r, "Cannot query timezone list: %m"); + + print_welcome(); + + printf("\nAvailable Time Zones:\n\n"); + r = show_menu(zones, 3, 22, 30); + if (r < 0) + return r; + + putchar('\n'); + + r = prompt_loop("Please enter timezone name or number", zones, timezone_is_valid, &arg_timezone); + if (r < 0) + return r; + + return 0; +} + +static int process_timezone(void) { + const char *etc_localtime, *e; + int r; + + etc_localtime = prefix_roota(arg_root, "/etc/localtime"); + if (laccess(etc_localtime, F_OK) >= 0) + return 0; + + if (arg_copy_timezone && arg_root) { + _cleanup_free_ char *p = NULL; + + r = readlink_malloc("/etc/localtime", &p); + if (r != -ENOENT) { + if (r < 0) + return log_error_errno(r, "Failed to read host timezone: %m"); + + mkdir_parents(etc_localtime, 0755); + if (symlink(p, etc_localtime) < 0) + return log_error_errno(errno, "Failed to create %s symlink: %m", etc_localtime); + + log_info("%s copied.", etc_localtime); + return 0; + } + } + + r = prompt_timezone(); + if (r < 0) + return r; + + if (isempty(arg_timezone)) + return 0; + + e = strjoina("../usr/share/zoneinfo/", arg_timezone); + + mkdir_parents(etc_localtime, 0755); + if (symlink(e, etc_localtime) < 0) + return log_error_errno(errno, "Failed to create %s symlink: %m", etc_localtime); + + log_info("%s written", etc_localtime); + return 0; +} + +static int prompt_hostname(void) { + int r; + + if (arg_hostname) + return 0; + + if (!arg_prompt_hostname) + return 0; + + print_welcome(); + putchar('\n'); + + for (;;) { + _cleanup_free_ char *h = NULL; + + r = ask_string(&h, "%s Please enter hostname for new system (empty to skip): ", special_glyph(TRIANGULAR_BULLET)); + if (r < 0) + return log_error_errno(r, "Failed to query hostname: %m"); + + if (isempty(h)) { + log_warning("No hostname entered, skipping."); + break; + } + + if (!hostname_is_valid(h, true)) { + log_error("Specified hostname invalid."); + continue; + } + + /* Get rid of the trailing dot that we allow, but don't want to see */ + arg_hostname = hostname_cleanup(h); + h = NULL; + break; + } + + return 0; +} + +static int process_hostname(void) { + const char *etc_hostname; + int r; + + etc_hostname = prefix_roota(arg_root, "/etc/hostname"); + if (laccess(etc_hostname, F_OK) >= 0) + return 0; + + r = prompt_hostname(); + if (r < 0) + return r; + + if (isempty(arg_hostname)) + return 0; + + mkdir_parents(etc_hostname, 0755); + r = write_string_file(etc_hostname, arg_hostname, WRITE_STRING_FILE_CREATE); + if (r < 0) + return log_error_errno(r, "Failed to write %s: %m", etc_hostname); + + log_info("%s written.", etc_hostname); + return 0; +} + +static int process_machine_id(void) { + const char *etc_machine_id; + char id[SD_ID128_STRING_MAX]; + int r; + + etc_machine_id = prefix_roota(arg_root, "/etc/machine-id"); + if (laccess(etc_machine_id, F_OK) >= 0) + return 0; + + if (sd_id128_equal(arg_machine_id, SD_ID128_NULL)) + return 0; + + mkdir_parents(etc_machine_id, 0755); + r = write_string_file(etc_machine_id, sd_id128_to_string(arg_machine_id, id), WRITE_STRING_FILE_CREATE); + if (r < 0) + return log_error_errno(r, "Failed to write machine id: %m"); + + log_info("%s written.", etc_machine_id); + return 0; +} + +static int prompt_root_password(void) { + const char *msg1, *msg2, *etc_shadow; + int r; + + if (arg_root_password) + return 0; + + if (!arg_prompt_root_password) + return 0; + + etc_shadow = prefix_roota(arg_root, "/etc/shadow"); + if (laccess(etc_shadow, F_OK) >= 0) + return 0; + + print_welcome(); + putchar('\n'); + + msg1 = strjoina(special_glyph(TRIANGULAR_BULLET), " Please enter a new root password (empty to skip): "); + msg2 = strjoina(special_glyph(TRIANGULAR_BULLET), " Please enter new root password again: "); + + for (;;) { + _cleanup_string_free_erase_ char *a = NULL, *b = NULL; + + r = ask_password_tty(msg1, NULL, 0, 0, NULL, &a); + if (r < 0) + return log_error_errno(r, "Failed to query root password: %m"); + + if (isempty(a)) { + log_warning("No password entered, skipping."); + break; + } + + r = ask_password_tty(msg2, NULL, 0, 0, NULL, &b); + if (r < 0) + return log_error_errno(r, "Failed to query root password: %m"); + + if (!streq(a, b)) { + log_error("Entered passwords did not match, please try again."); + continue; + } + + arg_root_password = a; + a = NULL; + break; + } + + return 0; +} + +static int write_root_shadow(const char *path, const struct spwd *p) { + _cleanup_fclose_ FILE *f = NULL; + assert(path); + assert(p); + + RUN_WITH_UMASK(0777) + f = fopen(path, "wex"); + if (!f) + return -errno; + + errno = 0; + if (putspent(p, f) != 0) + return errno > 0 ? -errno : -EIO; + + return fflush_and_check(f); +} + +static int process_root_password(void) { + + static const char table[] = + "abcdefghijklmnopqrstuvwxyz" + "ABCDEFGHIJKLMNOPQRSTUVWXYZ" + "0123456789" + "./"; + + struct spwd item = { + .sp_namp = (char*) "root", + .sp_min = -1, + .sp_max = -1, + .sp_warn = -1, + .sp_inact = -1, + .sp_expire = -1, + .sp_flag = (unsigned long) -1, /* this appears to be what everybody does ... */ + }; + + _cleanup_close_ int lock = -1; + char salt[3+16+1+1]; + uint8_t raw[16]; + unsigned i; + char *j; + + const char *etc_shadow; + int r; + + etc_shadow = prefix_roota(arg_root, "/etc/shadow"); + if (laccess(etc_shadow, F_OK) >= 0) + return 0; + + mkdir_parents(etc_shadow, 0755); + + lock = take_etc_passwd_lock(arg_root); + if (lock < 0) + return log_error_errno(lock, "Failed to take a lock: %m"); + + if (arg_copy_root_password && arg_root) { + struct spwd *p; + + errno = 0; + p = getspnam("root"); + if (p || errno != ENOENT) { + if (!p) { + if (!errno) + errno = EIO; + + return log_error_errno(errno, "Failed to find shadow entry for root: %m"); + } + + r = write_root_shadow(etc_shadow, p); + if (r < 0) + return log_error_errno(r, "Failed to write %s: %m", etc_shadow); + + log_info("%s copied.", etc_shadow); + return 0; + } + } + + r = prompt_root_password(); + if (r < 0) + return r; + + if (!arg_root_password) + return 0; + + r = dev_urandom(raw, 16); + if (r < 0) + return log_error_errno(r, "Failed to get salt: %m"); + + /* We only bother with SHA512 hashed passwords, the rest is legacy, and we don't do legacy. */ + assert_cc(sizeof(table) == 64 + 1); + j = stpcpy(salt, "$6$"); + for (i = 0; i < 16; i++) + j[i] = table[raw[i] & 63]; + j[i++] = '$'; + j[i] = 0; + + errno = 0; + item.sp_pwdp = crypt(arg_root_password, salt); + if (!item.sp_pwdp) { + if (!errno) + errno = EINVAL; + + return log_error_errno(errno, "Failed to encrypt password: %m"); + } + + item.sp_lstchg = (long) (now(CLOCK_REALTIME) / USEC_PER_DAY); + + r = write_root_shadow(etc_shadow, &item); + if (r < 0) + return log_error_errno(r, "Failed to write %s: %m", etc_shadow); + + log_info("%s written.", etc_shadow); + return 0; +} + +static void help(void) { + printf("%s [OPTIONS...]\n\n" + "Configures basic settings of the system.\n\n" + " -h --help Show this help\n" + " --version Show package version\n" + " --root=PATH Operate on an alternate filesystem root\n" + " --locale=LOCALE Set primary locale (LANG=)\n" + " --locale-messages=LOCALE Set message locale (LC_MESSAGES=)\n" + " --timezone=TIMEZONE Set timezone\n" + " --hostname=NAME Set host name\n" + " --machine-ID=ID Set machine ID\n" + " --root-password=PASSWORD Set root password\n" + " --root-password-file=FILE Set root password from file\n" + " --prompt-locale Prompt the user for locale settings\n" + " --prompt-timezone Prompt the user for timezone\n" + " --prompt-hostname Prompt the user for hostname\n" + " --prompt-root-password Prompt the user for root password\n" + " --prompt Prompt for all of the above\n" + " --copy-locale Copy locale from host\n" + " --copy-timezone Copy timezone from host\n" + " --copy-root-password Copy root password from host\n" + " --copy Copy locale, timezone, root password\n" + " --setup-machine-id Generate a new random machine ID\n" + , program_invocation_short_name); +} + +static int parse_argv(int argc, char *argv[]) { + + enum { + ARG_VERSION = 0x100, + ARG_ROOT, + ARG_LOCALE, + ARG_LOCALE_MESSAGES, + ARG_TIMEZONE, + ARG_HOSTNAME, + ARG_MACHINE_ID, + ARG_ROOT_PASSWORD, + ARG_ROOT_PASSWORD_FILE, + ARG_PROMPT, + ARG_PROMPT_LOCALE, + ARG_PROMPT_TIMEZONE, + ARG_PROMPT_HOSTNAME, + ARG_PROMPT_ROOT_PASSWORD, + ARG_COPY, + ARG_COPY_LOCALE, + ARG_COPY_TIMEZONE, + ARG_COPY_ROOT_PASSWORD, + ARG_SETUP_MACHINE_ID, + }; + + static const struct option options[] = { + { "help", no_argument, NULL, 'h' }, + { "version", no_argument, NULL, ARG_VERSION }, + { "root", required_argument, NULL, ARG_ROOT }, + { "locale", required_argument, NULL, ARG_LOCALE }, + { "locale-messages", required_argument, NULL, ARG_LOCALE_MESSAGES }, + { "timezone", required_argument, NULL, ARG_TIMEZONE }, + { "hostname", required_argument, NULL, ARG_HOSTNAME }, + { "machine-id", required_argument, NULL, ARG_MACHINE_ID }, + { "root-password", required_argument, NULL, ARG_ROOT_PASSWORD }, + { "root-password-file", required_argument, NULL, ARG_ROOT_PASSWORD_FILE }, + { "prompt", no_argument, NULL, ARG_PROMPT }, + { "prompt-locale", no_argument, NULL, ARG_PROMPT_LOCALE }, + { "prompt-timezone", no_argument, NULL, ARG_PROMPT_TIMEZONE }, + { "prompt-hostname", no_argument, NULL, ARG_PROMPT_HOSTNAME }, + { "prompt-root-password", no_argument, NULL, ARG_PROMPT_ROOT_PASSWORD }, + { "copy", no_argument, NULL, ARG_COPY }, + { "copy-locale", no_argument, NULL, ARG_COPY_LOCALE }, + { "copy-timezone", no_argument, NULL, ARG_COPY_TIMEZONE }, + { "copy-root-password", no_argument, NULL, ARG_COPY_ROOT_PASSWORD }, + { "setup-machine-id", no_argument, NULL, ARG_SETUP_MACHINE_ID }, + {} + }; + + int r, 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_ROOT: + r = parse_path_argument_and_warn(optarg, true, &arg_root); + if (r < 0) + return r; + break; + + case ARG_LOCALE: + if (!locale_is_valid(optarg)) { + log_error("Locale %s is not valid.", optarg); + return -EINVAL; + } + + r = free_and_strdup(&arg_locale, optarg); + if (r < 0) + return log_oom(); + + break; + + case ARG_LOCALE_MESSAGES: + if (!locale_is_valid(optarg)) { + log_error("Locale %s is not valid.", optarg); + return -EINVAL; + } + + r = free_and_strdup(&arg_locale_messages, optarg); + if (r < 0) + return log_oom(); + + break; + + case ARG_TIMEZONE: + if (!timezone_is_valid(optarg)) { + log_error("Timezone %s is not valid.", optarg); + return -EINVAL; + } + + r = free_and_strdup(&arg_timezone, optarg); + if (r < 0) + return log_oom(); + + break; + + case ARG_ROOT_PASSWORD: + r = free_and_strdup(&arg_root_password, optarg); + if (r < 0) + return log_oom(); + break; + + case ARG_ROOT_PASSWORD_FILE: + arg_root_password = mfree(arg_root_password); + + r = read_one_line_file(optarg, &arg_root_password); + if (r < 0) + return log_error_errno(r, "Failed to read %s: %m", optarg); + + break; + + case ARG_HOSTNAME: + if (!hostname_is_valid(optarg, true)) { + log_error("Host name %s is not valid.", optarg); + return -EINVAL; + } + + hostname_cleanup(optarg); + r = free_and_strdup(&arg_hostname, optarg); + if (r < 0) + return log_oom(); + + break; + + case ARG_MACHINE_ID: + if (sd_id128_from_string(optarg, &arg_machine_id) < 0) { + log_error("Failed to parse machine id %s.", optarg); + return -EINVAL; + } + + break; + + case ARG_PROMPT: + arg_prompt_locale = arg_prompt_timezone = arg_prompt_hostname = arg_prompt_root_password = true; + break; + + case ARG_PROMPT_LOCALE: + arg_prompt_locale = true; + break; + + case ARG_PROMPT_TIMEZONE: + arg_prompt_timezone = true; + break; + + case ARG_PROMPT_HOSTNAME: + arg_prompt_hostname = true; + break; + + case ARG_PROMPT_ROOT_PASSWORD: + arg_prompt_root_password = true; + break; + + case ARG_COPY: + arg_copy_locale = arg_copy_timezone = arg_copy_root_password = true; + break; + + case ARG_COPY_LOCALE: + arg_copy_locale = true; + break; + + case ARG_COPY_TIMEZONE: + arg_copy_timezone = true; + break; + + case ARG_COPY_ROOT_PASSWORD: + arg_copy_root_password = true; + break; + + case ARG_SETUP_MACHINE_ID: + + r = sd_id128_randomize(&arg_machine_id); + if (r < 0) + return log_error_errno(r, "Failed to generate randomized machine ID: %m"); + + break; + + case '?': + return -EINVAL; + + default: + assert_not_reached("Unhandled option"); + } + + return 1; +} + +int main(int argc, char *argv[]) { + int r; + + r = parse_argv(argc, argv); + if (r <= 0) + goto finish; + + log_set_target(LOG_TARGET_AUTO); + log_parse_environment(); + log_open(); + + umask(0022); + + r = process_locale(); + if (r < 0) + goto finish; + + r = process_timezone(); + if (r < 0) + goto finish; + + r = process_hostname(); + if (r < 0) + goto finish; + + r = process_machine_id(); + if (r < 0) + goto finish; + + r = process_root_password(); + if (r < 0) + goto finish; + +finish: + free(arg_root); + free(arg_locale); + free(arg_locale_messages); + free(arg_timezone); + free(arg_hostname); + string_erase(arg_root_password); + free(arg_root_password); + + return r < 0 ? EXIT_FAILURE : EXIT_SUCCESS; +} diff --git a/src/grp-initutils/systemd-fsck/Makefile b/src/grp-initutils/systemd-fsck/Makefile new file mode 100644 index 0000000000..f29fba4d20 --- /dev/null +++ b/src/grp-initutils/systemd-fsck/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 + +rootlibexec_PROGRAMS += systemd-fsck +systemd_fsck_SOURCES = \ + src/fsck/fsck.c + +systemd_fsck_LDADD = \ + libshared.la + +include $(topsrcdir)/build-aux/Makefile.tail.mk diff --git a/src/grp-initutils/systemd-fsck/fsck.c b/src/grp-initutils/systemd-fsck/fsck.c new file mode 100644 index 0000000000..4ca018763f --- /dev/null +++ b/src/grp-initutils/systemd-fsck/fsck.c @@ -0,0 +1,487 @@ +/*** + This file is part of systemd. + + Copyright 2010 Lennart Poettering + Copyright 2014 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 +#include +#include +#include +#include + +#include + +#include "basic/alloc-util.h" +#include "basic/fd-util.h" +#include "basic/fs-util.h" +#include "basic/parse-util.h" +#include "basic/path-util.h" +#include "basic/proc-cmdline.h" +#include "basic/process-util.h" +#include "basic/signal-util.h" +#include "basic/socket-util.h" +#include "basic/special.h" +#include "basic/stdio-util.h" +#include "basic/util.h" +#include "sd-bus/bus-common-errors.h" +#include "sd-bus/bus-error.h" +#include "sd-device/device-util.h" +#include "sd-device/sd-device.h" +#include "shared/bus-util.h" + +/* exit codes as defined in fsck(8) */ +enum { + FSCK_SUCCESS = 0, + FSCK_ERROR_CORRECTED = 1, + FSCK_SYSTEM_SHOULD_REBOOT = 2, + FSCK_ERRORS_LEFT_UNCORRECTED = 4, + FSCK_OPERATIONAL_ERROR = 8, + FSCK_USAGE_OR_SYNTAX_ERROR = 16, + FSCK_USER_CANCELLED = 32, + FSCK_SHARED_LIB_ERROR = 128, +}; + +static bool arg_skip = false; +static bool arg_force = false; +static bool arg_show_progress = false; +static const char *arg_repair = "-a"; + +static void start_target(const char *target, const char *mode) { + _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; + + assert(target); + + r = bus_connect_system_systemd(&bus); + if (r < 0) { + log_error_errno(r, "Failed to get D-Bus connection: %m"); + return; + } + + log_info("Running request %s/start/replace", target); + + /* Start these units only if we can replace base.target with it */ + r = sd_bus_call_method(bus, + "org.freedesktop.systemd1", + "/org/freedesktop/systemd1", + "org.freedesktop.systemd1.Manager", + "StartUnitReplace", + &error, + NULL, + "sss", "basic.target", target, mode); + + /* Don't print a warning if we aren't called during startup */ + if (r < 0 && !sd_bus_error_has_name(&error, BUS_ERROR_NO_SUCH_JOB)) + log_error("Failed to start unit: %s", bus_error_message(&error, r)); +} + +static int parse_proc_cmdline_item(const char *key, const char *value) { + int r; + + assert(key); + + if (streq(key, "fsck.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 fsck.mode= parameter '%s'. Ignoring.", value); + + } else if (streq(key, "fsck.repair") && value) { + + if (streq(value, "preen")) + arg_repair = "-a"; + else { + r = parse_boolean(value); + if (r > 0) + arg_repair = "-y"; + else if (r == 0) + arg_repair = "-n"; + else + log_warning("Invalid fsck.repair= parameter '%s'. Ignoring.", value); + } + } + +#ifdef HAVE_SYSV_COMPAT + else if (streq(key, "fastboot") && !value) { + log_warning("Please pass 'fsck.mode=skip' rather than 'fastboot' on the kernel command line."); + arg_skip = true; + + } else if (streq(key, "forcefsck") && !value) { + log_warning("Please pass 'fsck.mode=force' rather than 'forcefsck' on the kernel command line."); + arg_force = true; + } +#endif + + return 0; +} + +static void test_files(void) { + +#ifdef HAVE_SYSV_COMPAT + if (access("/fastboot", F_OK) >= 0) { + log_error("Please pass 'fsck.mode=skip' on the kernel command line rather than creating /fastboot on the root file system."); + arg_skip = true; + } + + if (access("/forcefsck", F_OK) >= 0) { + log_error("Please pass 'fsck.mode=force' on the kernel command line rather than creating /forcefsck on the root file system."); + arg_force = true; + } +#endif + + arg_show_progress = access("/run/systemd/show-status", F_OK) >= 0; +} + +static double percent(int pass, unsigned long cur, unsigned long max) { + /* Values stolen from e2fsck */ + + static const int pass_table[] = { + 0, 70, 90, 92, 95, 100 + }; + + if (pass <= 0) + return 0.0; + + if ((unsigned) pass >= ELEMENTSOF(pass_table) || max == 0) + return 100.0; + + return (double) pass_table[pass-1] + + ((double) pass_table[pass] - (double) pass_table[pass-1]) * + (double) cur / (double) max; +} + +static int process_progress(int fd) { + _cleanup_fclose_ FILE *console = NULL, *f = NULL; + usec_t last = 0; + bool locked = false; + int clear = 0, r; + + /* No progress pipe to process? Then we are a NOP. */ + if (fd < 0) + return 0; + + f = fdopen(fd, "re"); + if (!f) { + safe_close(fd); + return -errno; + } + + console = fopen("/dev/console", "we"); + if (!console) + return -ENOMEM; + + for (;;) { + int pass, m; + unsigned long cur, max; + _cleanup_free_ char *device = NULL; + double p; + usec_t t; + + if (fscanf(f, "%i %lu %lu %ms", &pass, &cur, &max, &device) != 4) { + + if (ferror(f)) + r = log_warning_errno(errno, "Failed to read from progress pipe: %m"); + else if (feof(f)) + r = 0; + else { + log_warning("Failed to parse progress pipe data"); + r = -EBADMSG; + } + break; + } + + /* Only show one progress counter at max */ + if (!locked) { + if (flock(fileno(console), LOCK_EX|LOCK_NB) < 0) + continue; + + locked = true; + } + + /* Only update once every 50ms */ + t = now(CLOCK_MONOTONIC); + if (last + 50 * USEC_PER_MSEC > t) + continue; + + last = t; + + p = percent(pass, cur, max); + fprintf(console, "\r%s: fsck %3.1f%% complete...\r%n", device, p, &m); + fflush(console); + + if (m > clear) + clear = m; + } + + if (clear > 0) { + unsigned j; + + fputc('\r', console); + for (j = 0; j < (unsigned) clear; j++) + fputc(' ', console); + fputc('\r', console); + fflush(console); + } + + return r; +} + +static int fsck_progress_socket(void) { + static const union sockaddr_union sa = { + .un.sun_family = AF_UNIX, + .un.sun_path = "/run/systemd/fsck.progress", + }; + + int fd, r; + + fd = socket(AF_UNIX, SOCK_STREAM, 0); + if (fd < 0) + return log_warning_errno(errno, "socket(): %m"); + + if (connect(fd, &sa.sa, SOCKADDR_UN_LEN(sa.un)) < 0) { + r = log_full_errno(errno == ECONNREFUSED || errno == ENOENT ? LOG_DEBUG : LOG_WARNING, + errno, "Failed to connect to progress socket %s, ignoring: %m", sa.un.sun_path); + safe_close(fd); + return r; + } + + return fd; +} + +int main(int argc, char *argv[]) { + _cleanup_close_pair_ int progress_pipe[2] = { -1, -1 }; + _cleanup_(sd_device_unrefp) sd_device *dev = NULL; + const char *device, *type; + bool root_directory; + siginfo_t status; + struct stat st; + int r; + pid_t pid; + + if (argc > 2) { + log_error("This program expects one or 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 && arg_skip) { + r = 0; + goto finish; + } + + if (argc > 1) { + device = argv[1]; + + if (stat(device, &st) < 0) { + r = log_error_errno(errno, "Failed to stat %s: %m", device); + goto finish; + } + + if (!S_ISBLK(st.st_mode)) { + log_error("%s is not a block device.", device); + r = -EINVAL; + goto finish; + } + + r = sd_device_new_from_devnum(&dev, 'b', st.st_rdev); + if (r < 0) { + log_error_errno(r, "Failed to detect device %s: %m", device); + goto finish; + } + + root_directory = false; + } else { + struct timespec times[2]; + + /* Find root device */ + + if (stat("/", &st) < 0) { + r = log_error_errno(errno, "Failed to stat() the root directory: %m"); + goto finish; + } + + /* Virtual root devices don't need an fsck */ + if (major(st.st_dev) == 0) { + log_debug("Root directory is virtual or btrfs, skipping check."); + r = 0; + goto finish; + } + + /* check if we are already writable */ + times[0] = st.st_atim; + times[1] = st.st_mtim; + + if (utimensat(AT_FDCWD, "/", times, 0) == 0) { + log_info("Root directory is writable, skipping check."); + r = 0; + goto finish; + } + + r = sd_device_new_from_devnum(&dev, 'b', st.st_dev); + if (r < 0) { + log_error_errno(r, "Failed to detect root device: %m"); + goto finish; + } + + r = sd_device_get_devname(dev, &device); + if (r < 0) { + log_error_errno(r, "Failed to detect device node of root directory: %m"); + goto finish; + } + + root_directory = true; + } + + r = sd_device_get_property_value(dev, "ID_FS_TYPE", &type); + if (r >= 0) { + r = fsck_exists(type); + if (r < 0) + log_warning_errno(r, "Couldn't detect if fsck.%s may be used for %s, proceeding: %m", type, device); + else if (r == 0) { + log_info("fsck.%s doesn't exist, not checking file system on %s.", type, device); + goto finish; + } + } + + if (arg_show_progress) { + if (pipe(progress_pipe) < 0) { + r = log_error_errno(errno, "pipe(): %m"); + goto finish; + } + } + + pid = fork(); + if (pid < 0) { + r = log_error_errno(errno, "fork(): %m"); + goto finish; + } + if (pid == 0) { + char dash_c[sizeof("-C")-1 + DECIMAL_STR_MAX(int) + 1]; + int progress_socket = -1; + const char *cmdline[9]; + int i = 0; + + /* Child */ + + (void) reset_all_signal_handlers(); + (void) reset_signal_mask(); + assert_se(prctl(PR_SET_PDEATHSIG, SIGTERM) == 0); + + /* Close the reading side of the progress pipe */ + progress_pipe[0] = safe_close(progress_pipe[0]); + + /* Try to connect to a progress management daemon, if there is one */ + progress_socket = fsck_progress_socket(); + if (progress_socket >= 0) { + /* If this worked we close the progress pipe early, and just use the socket */ + progress_pipe[1] = safe_close(progress_pipe[1]); + xsprintf(dash_c, "-C%i", progress_socket); + } else if (progress_pipe[1] >= 0) { + /* Otherwise if we have the progress pipe to our own local handle, we use it */ + xsprintf(dash_c, "-C%i", progress_pipe[1]); + } else + dash_c[0] = 0; + + cmdline[i++] = "/sbin/fsck"; + cmdline[i++] = arg_repair; + cmdline[i++] = "-T"; + + /* + * Since util-linux v2.25 fsck uses /run/fsck/.lock files. + * The previous versions use flock for the device and conflict with + * udevd, see https://bugs.freedesktop.org/show_bug.cgi?id=79576#c5 + */ + cmdline[i++] = "-l"; + + if (!root_directory) + cmdline[i++] = "-M"; + + if (arg_force) + cmdline[i++] = "-f"; + + if (!isempty(dash_c)) + cmdline[i++] = dash_c; + + cmdline[i++] = device; + cmdline[i++] = NULL; + + execv(cmdline[0], (char**) cmdline); + _exit(FSCK_OPERATIONAL_ERROR); + } + + progress_pipe[1] = safe_close(progress_pipe[1]); + (void) process_progress(progress_pipe[0]); + progress_pipe[0] = -1; + + r = wait_for_terminate(pid, &status); + if (r < 0) { + log_error_errno(r, "waitid(): %m"); + goto finish; + } + + if (status.si_code != CLD_EXITED || (status.si_status & ~1)) { + + if (status.si_code == CLD_KILLED || status.si_code == CLD_DUMPED) + log_error("fsck terminated by signal %s.", signal_to_string(status.si_status)); + else if (status.si_code == CLD_EXITED) + log_error("fsck failed with error code %i.", status.si_status); + else + log_error("fsck failed due to unknown reason."); + + r = -EINVAL; + + if (status.si_code == CLD_EXITED && (status.si_status & FSCK_SYSTEM_SHOULD_REBOOT) && root_directory) + /* System should be rebooted. */ + start_target(SPECIAL_REBOOT_TARGET, "replace-irreversibly"); + else if (status.si_code == CLD_EXITED && (status.si_status & (FSCK_SYSTEM_SHOULD_REBOOT | FSCK_ERRORS_LEFT_UNCORRECTED))) + /* Some other problem */ + start_target(SPECIAL_EMERGENCY_TARGET, "replace"); + else { + log_warning("Ignoring error."); + r = 0; + } + + } else + r = 0; + + if (status.si_code == CLD_EXITED && (status.si_status & FSCK_ERROR_CORRECTED)) + (void) touch("/run/systemd/quotacheck"); + +finish: + return r < 0 ? EXIT_FAILURE : EXIT_SUCCESS; +} diff --git a/src/grp-initutils/systemd-quotacheck/Makefile b/src/grp-initutils/systemd-quotacheck/Makefile new file mode 100644 index 0000000000..979ae07c9e --- /dev/null +++ b/src/grp-initutils/systemd-quotacheck/Makefile @@ -0,0 +1,48 @@ +# -*- 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 # ENABLE_QUOTACHECK + +EXTRA_DIST += \ + units/systemd-quotacheck.service.in + +nodist_systemunit_DATA += \ + units/quotaon.service + +systemd.CPPFLAGS += -DQUOTACHECK=\"$(QUOTACHECK)\" + +include $(topsrcdir)/build-aux/Makefile.tail.mk diff --git a/src/grp-initutils/systemd-quotacheck/quotacheck.c b/src/grp-initutils/systemd-quotacheck/quotacheck.c new file mode 100644 index 0000000000..3c2292b0c4 --- /dev/null +++ b/src/grp-initutils/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 "basic/proc-cmdline.h" +#include "basic/process-util.h" +#include "basic/signal-util.h" +#include "basic/string-util.h" +#include "basic/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/grp-initutils/systemd-random-seed/Makefile b/src/grp-initutils/systemd-random-seed/Makefile new file mode 100644 index 0000000000..65ae60828b --- /dev/null +++ b/src/grp-initutils/systemd-random-seed/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_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 # ENABLE_RANDOMSEED + +EXTRA_DIST += \ + units/systemd-random-seed.service.in + +systemd.CPPFLAGS += -DRANDOM_SEED_DIR=\"$(localstatedir)/lib/systemd/\" +systemd.CPPFLAGS += -DRANDOM_SEED=\"$(localstatedir)/lib/systemd/random-seed\" + +include $(topsrcdir)/build-aux/Makefile.tail.mk diff --git a/src/grp-initutils/systemd-random-seed/random-seed.c b/src/grp-initutils/systemd-random-seed/random-seed.c new file mode 100644 index 0000000000..7fea6069f3 --- /dev/null +++ b/src/grp-initutils/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 "basic/alloc-util.h" +#include "basic/fd-util.h" +#include "basic/io-util.h" +#include "basic/log.h" +#include "basic/mkdir.h" +#include "basic/string-util.h" +#include "basic/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/grp-initutils/systemd-rfkill/Makefile b/src/grp-initutils/systemd-rfkill/Makefile new file mode 100644 index 0000000000..2b4430b712 --- /dev/null +++ b/src/grp-initutils/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 # ENABLE_RFKILL + +EXTRA_DIST += \ + units/systemd-rfkill.service.in + +include $(topsrcdir)/build-aux/Makefile.tail.mk diff --git a/src/grp-initutils/systemd-rfkill/rfkill.c b/src/grp-initutils/systemd-rfkill/rfkill.c new file mode 100644 index 0000000000..d3dd2ef18d --- /dev/null +++ b/src/grp-initutils/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 "basic/alloc-util.h" +#include "basic/escape.h" +#include "basic/fd-util.h" +#include "basic/fileio.h" +#include "basic/io-util.h" +#include "basic/mkdir.h" +#include "basic/parse-util.h" +#include "basic/proc-cmdline.h" +#include "basic/string-table.h" +#include "basic/string-util.h" +#include "shared/udev-util.h" +#include "basic/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/grp-initutils/systemd-sleep/Makefile b/src/grp-initutils/systemd-sleep/Makefile new file mode 100644 index 0000000000..9ac849f817 --- /dev/null +++ b/src/grp-initutils/systemd-sleep/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 + +rootlibexec_PROGRAMS += systemd-sleep +systemd_sleep_SOURCES = \ + src/sleep/sleep.c + +systemd_sleep_LDADD = \ + libshared.la + +systemd.CPPFLAGS += -DSYSTEM_SLEEP_PATH=\"$(systemsleepdir)\" + +include $(topsrcdir)/build-aux/Makefile.tail.mk diff --git a/src/grp-initutils/systemd-sleep/sleep.c b/src/grp-initutils/systemd-sleep/sleep.c new file mode 100644 index 0000000000..100066d4c1 --- /dev/null +++ b/src/grp-initutils/systemd-sleep/sleep.c @@ -0,0 +1,215 @@ +/*** + 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 "basic/def.h" +#include "basic/fd-util.h" +#include "basic/fileio.h" +#include "basic/log.h" +#include "shared/sleep-config.h" +#include "basic/string-util.h" +#include "basic/strv.h" +#include "basic/util.h" + +static char* arg_verb = NULL; + +static int write_mode(char **modes) { + int r = 0; + char **mode; + + STRV_FOREACH(mode, modes) { + int k; + + k = write_string_file("/sys/power/disk", *mode, 0); + if (k == 0) + return 0; + + log_debug_errno(k, "Failed to write '%s' to /sys/power/disk: %m", + *mode); + if (r == 0) + r = k; + } + + if (r < 0) + log_error_errno(r, "Failed to write mode to /sys/power/disk: %m"); + + return r; +} + +static int write_state(FILE **f, char **states) { + char **state; + int r = 0; + + STRV_FOREACH(state, states) { + int k; + + k = write_string_stream(*f, *state, true); + if (k == 0) + return 0; + log_debug_errno(k, "Failed to write '%s' to /sys/power/state: %m", + *state); + if (r == 0) + r = k; + + fclose(*f); + *f = fopen("/sys/power/state", "we"); + if (!*f) + return log_error_errno(errno, "Failed to open /sys/power/state: %m"); + } + + return r; +} + +static int execute(char **modes, char **states) { + + char *arguments[] = { + NULL, + (char*) "pre", + arg_verb, + NULL + }; + static const char* const dirs[] = {SYSTEM_SLEEP_PATH, NULL}; + + int r; + _cleanup_fclose_ FILE *f = NULL; + + /* This file is opened first, so that if we hit an error, + * we can abort before modifying any state. */ + f = fopen("/sys/power/state", "we"); + if (!f) + return log_error_errno(errno, "Failed to open /sys/power/state: %m"); + + /* Configure the hibernation mode */ + r = write_mode(modes); + if (r < 0) + return r; + + execute_directories(dirs, DEFAULT_TIMEOUT_USEC, arguments); + + log_struct(LOG_INFO, + LOG_MESSAGE_ID(SD_MESSAGE_SLEEP_START), + LOG_MESSAGE("Suspending system..."), + "SLEEP=%s", arg_verb, + NULL); + + r = write_state(&f, states); + if (r < 0) + return r; + + log_struct(LOG_INFO, + LOG_MESSAGE_ID(SD_MESSAGE_SLEEP_STOP), + LOG_MESSAGE("System resumed."), + "SLEEP=%s", arg_verb, + NULL); + + arguments[1] = (char*) "post"; + execute_directories(dirs, DEFAULT_TIMEOUT_USEC, arguments); + + return r; +} + +static void help(void) { + printf("%s COMMAND\n\n" + "Suspend the system, hibernate the system, or both.\n\n" + "Commands:\n" + " -h --help Show this help and exit\n" + " --version Print version string and exit\n" + " suspend Suspend the system\n" + " hibernate Hibernate the system\n" + " hybrid-sleep Both hibernate and suspend the system\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; /* done */ + + case ARG_VERSION: + return version(); + + case '?': + return -EINVAL; + + default: + assert_not_reached("Unhandled option"); + } + + if (argc - optind != 1) { + log_error("Usage: %s COMMAND", + program_invocation_short_name); + return -EINVAL; + } + + arg_verb = argv[optind]; + + if (!streq(arg_verb, "suspend") && + !streq(arg_verb, "hibernate") && + !streq(arg_verb, "hybrid-sleep")) { + log_error("Unknown command '%s'.", arg_verb); + return -EINVAL; + } + + return 1 /* work to do */; +} + +int main(int argc, char *argv[]) { + _cleanup_strv_free_ char **modes = NULL, **states = NULL; + int r; + + log_set_target(LOG_TARGET_AUTO); + log_parse_environment(); + log_open(); + + r = parse_argv(argc, argv); + if (r <= 0) + goto finish; + + r = parse_sleep_config(arg_verb, &modes, &states); + if (r < 0) + goto finish; + + r = execute(modes, states); + +finish: + return r < 0 ? EXIT_FAILURE : EXIT_SUCCESS; +} diff --git a/src/grp-initutils/systemd-sysctl/Makefile b/src/grp-initutils/systemd-sysctl/Makefile new file mode 100644 index 0000000000..3af80e7bef --- /dev/null +++ b/src/grp-initutils/systemd-sysctl/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 + +rootlibexec_PROGRAMS += systemd-sysctl +systemd_sysctl_SOURCES = \ + src/sysctl/sysctl.c + +systemd_sysctl_LDADD = \ + libshared.la + +include $(topsrcdir)/build-aux/Makefile.tail.mk diff --git a/src/grp-initutils/systemd-sysctl/sysctl.c b/src/grp-initutils/systemd-sysctl/sysctl.c new file mode 100644 index 0000000000..132420d95d --- /dev/null +++ b/src/grp-initutils/systemd-sysctl/sysctl.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 + +#include "basic/conf-files.h" +#include "basic/def.h" +#include "basic/fd-util.h" +#include "basic/fileio.h" +#include "basic/hashmap.h" +#include "basic/log.h" +#include "basic/path-util.h" +#include "basic/string-util.h" +#include "basic/strv.h" +#include "shared/sysctl-util.h" +#include "basic/util.h" + +static char **arg_prefixes = NULL; + +static const char conf_file_dirs[] = CONF_PATHS_NULSTR("sysctl.d"); + +static int apply_all(Hashmap *sysctl_options) { + char *property, *value; + Iterator i; + int r = 0; + + HASHMAP_FOREACH_KEY(value, property, sysctl_options, i) { + int k; + + k = sysctl_write(property, value); + if (k < 0) { + log_full_errno(k == -ENOENT ? LOG_INFO : LOG_WARNING, k, + "Couldn't write '%s' to '%s', ignoring: %m", value, property); + + if (r == 0 && k != -ENOENT) + r = k; + } + } + + return r; +} + +static int parse_file(Hashmap *sysctl_options, 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("Parsing %s", path); + while (!feof(f)) { + char l[LINE_MAX], *p, *value, *new_value, *property, *existing; + void *v; + 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; + + value = strchr(p, '='); + if (!value) { + log_error("Line is not an assignment in file '%s': %s", path, value); + + if (r == 0) + r = -EINVAL; + continue; + } + + *value = 0; + value++; + + p = sysctl_normalize(strstrip(p)); + value = strstrip(value); + + if (!strv_isempty(arg_prefixes)) { + char **i, *t; + STRV_FOREACH(i, arg_prefixes) { + t = path_startswith(*i, "/proc/sys/"); + if (t == NULL) + t = *i; + if (path_startswith(p, t)) + goto found; + } + /* not found */ + continue; + } + +found: + existing = hashmap_get2(sysctl_options, p, &v); + if (existing) { + if (streq(value, existing)) + continue; + + log_debug("Overwriting earlier assignment of %s in file '%s'.", p, path); + free(hashmap_remove(sysctl_options, p)); + free(v); + } + + property = strdup(p); + if (!property) + return log_oom(); + + new_value = strdup(value); + if (!new_value) { + free(property); + return log_oom(); + } + + k = hashmap_put(sysctl_options, property, new_value); + if (k < 0) { + log_error_errno(k, "Failed to add sysctl variable %s to hashmap: %m", property); + free(property); + free(new_value); + return k; + } + } + + return r; +} + +static void help(void) { + printf("%s [OPTIONS...] [CONFIGURATION FILE...]\n\n" + "Applies kernel sysctl settings.\n\n" + " -h --help Show this help\n" + " --version Show package version\n" + " --prefix=PATH Only apply rules with the specified prefix\n" + , program_invocation_short_name); +} + +static int parse_argv(int argc, char *argv[]) { + + enum { + ARG_VERSION = 0x100, + ARG_PREFIX + }; + + static const struct option options[] = { + { "help", no_argument, NULL, 'h' }, + { "version", no_argument, NULL, ARG_VERSION }, + { "prefix", required_argument, NULL, ARG_PREFIX }, + {} + }; + + 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_PREFIX: { + char *p; + + /* We used to require people to specify absolute paths + * in /proc/sys in the past. This is kinda useless, but + * we need to keep compatibility. We now support any + * sysctl name available. */ + sysctl_normalize(optarg); + + if (startswith(optarg, "/proc/sys")) + p = strdup(optarg); + else + p = strappend("/proc/sys/", optarg); + if (!p) + return log_oom(); + + if (strv_consume(&arg_prefixes, p) < 0) + return log_oom(); + + break; + } + + case '?': + return -EINVAL; + + default: + assert_not_reached("Unhandled option"); + } + + return 1; +} + +int main(int argc, char *argv[]) { + int r = 0, k; + Hashmap *sysctl_options; + + 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); + + sysctl_options = hashmap_new(&string_hash_ops); + if (!sysctl_options) { + r = log_oom(); + goto finish; + } + + r = 0; + + if (argc > optind) { + int i; + + for (i = optind; i < argc; i++) { + k = parse_file(sysctl_options, 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 sysctl.d files: %m"); + goto finish; + } + + STRV_FOREACH(f, files) { + k = parse_file(sysctl_options, *f, true); + if (k < 0 && r == 0) + r = k; + } + } + + k = apply_all(sysctl_options); + if (k < 0 && r == 0) + r = k; + +finish: + hashmap_free_free_free(sysctl_options); + strv_free(arg_prefixes); + + return r < 0 ? EXIT_FAILURE : EXIT_SUCCESS; +} diff --git a/src/grp-initutils/systemd-sysusers/Makefile b/src/grp-initutils/systemd-sysusers/Makefile new file mode 100644 index 0000000000..4753ca75d0 --- /dev/null +++ b/src/grp-initutils/systemd-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 # HAVE_REMOTE + +INSTALL_DIRS += \ + $(sysusersdir) +endif # ENABLE_SYSUSERS + +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/grp-initutils/systemd-sysusers/sysusers.c b/src/grp-initutils/systemd-sysusers/sysusers.c new file mode 100644 index 0000000000..83e526c5ff --- /dev/null +++ b/src/grp-initutils/systemd-sysusers/sysusers.c @@ -0,0 +1,1919 @@ +/*** + 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 "basic/alloc-util.h" +#include "basic/conf-files.h" +#include "basic/copy.h" +#include "basic/def.h" +#include "basic/fd-util.h" +#include "basic/fileio-label.h" +#include "basic/formats-util.h" +#include "basic/hashmap.h" +#include "basic/path-util.h" +#include "basic/selinux-util.h" +#include "basic/smack-util.h" +#include "shared/specifier.h" +#include "basic/string-util.h" +#include "basic/strv.h" +#include "shared/uid-range.h" +#include "basic/user-util.h" +#include "basic/utf8.h" +#include "basic/util.h" + +typedef enum ItemType { + ADD_USER = 'u', + ADD_GROUP = 'g', + ADD_MEMBER = 'm', + ADD_RANGE = 'r', +} ItemType; +typedef struct Item { + ItemType type; + + char *name; + char *uid_path; + char *gid_path; + char *description; + char *home; + + gid_t gid; + uid_t uid; + + bool gid_set:1; + bool uid_set:1; + + bool todo_user:1; + bool todo_group:1; +} Item; + +static char *arg_root = NULL; + +static const char conf_file_dirs[] = CONF_PATHS_NULSTR("sysusers.d"); + +static Hashmap *users = NULL, *groups = NULL; +static Hashmap *todo_uids = NULL, *todo_gids = NULL; +static Hashmap *members = NULL; + +static Hashmap *database_uid = NULL, *database_user = NULL; +static Hashmap *database_gid = NULL, *database_group = NULL; + +static uid_t search_uid = UID_INVALID; +static UidRange *uid_range = NULL; +static unsigned n_uid_range = 0; + +static int load_user_database(void) { + _cleanup_fclose_ FILE *f = NULL; + const char *passwd_path; + struct passwd *pw; + int r; + + passwd_path = prefix_roota(arg_root, "/etc/passwd"); + f = fopen(passwd_path, "re"); + if (!f) + return errno == ENOENT ? 0 : -errno; + + r = hashmap_ensure_allocated(&database_user, &string_hash_ops); + if (r < 0) + return r; + + r = hashmap_ensure_allocated(&database_uid, NULL); + if (r < 0) + return r; + + errno = 0; + while ((pw = fgetpwent(f))) { + char *n; + int k, q; + + n = strdup(pw->pw_name); + if (!n) + return -ENOMEM; + + k = hashmap_put(database_user, n, UID_TO_PTR(pw->pw_uid)); + if (k < 0 && k != -EEXIST) { + free(n); + return k; + } + + q = hashmap_put(database_uid, UID_TO_PTR(pw->pw_uid), n); + if (q < 0 && q != -EEXIST) { + if (k < 0) + free(n); + return q; + } + + if (q < 0 && k < 0) + free(n); + + errno = 0; + } + if (!IN_SET(errno, 0, ENOENT)) + return -errno; + + return 0; +} + +static int load_group_database(void) { + _cleanup_fclose_ FILE *f = NULL; + const char *group_path; + struct group *gr; + int r; + + group_path = prefix_roota(arg_root, "/etc/group"); + f = fopen(group_path, "re"); + if (!f) + return errno == ENOENT ? 0 : -errno; + + r = hashmap_ensure_allocated(&database_group, &string_hash_ops); + if (r < 0) + return r; + + r = hashmap_ensure_allocated(&database_gid, NULL); + if (r < 0) + return r; + + errno = 0; + while ((gr = fgetgrent(f))) { + char *n; + int k, q; + + n = strdup(gr->gr_name); + if (!n) + return -ENOMEM; + + k = hashmap_put(database_group, n, GID_TO_PTR(gr->gr_gid)); + if (k < 0 && k != -EEXIST) { + free(n); + return k; + } + + q = hashmap_put(database_gid, GID_TO_PTR(gr->gr_gid), n); + if (q < 0 && q != -EEXIST) { + if (k < 0) + free(n); + return q; + } + + if (q < 0 && k < 0) + free(n); + + errno = 0; + } + if (!IN_SET(errno, 0, ENOENT)) + return -errno; + + return 0; +} + +static int make_backup(const char *target, const char *x) { + _cleanup_close_ int src = -1; + _cleanup_fclose_ FILE *dst = NULL; + char *backup, *temp; + struct timespec ts[2]; + struct stat st; + int r; + + src = open(x, O_RDONLY|O_CLOEXEC|O_NOCTTY); + if (src < 0) { + if (errno == ENOENT) /* No backup necessary... */ + return 0; + + return -errno; + } + + if (fstat(src, &st) < 0) + return -errno; + + r = fopen_temporary_label(target, x, &dst, &temp); + if (r < 0) + return r; + + r = copy_bytes(src, fileno(dst), (uint64_t) -1, true); + if (r < 0) + goto fail; + + /* Don't fail on chmod() or chown(). If it stays owned by us + * and/or unreadable by others, then it isn't too bad... */ + + backup = strjoina(x, "-"); + + /* Copy over the access mask */ + if (fchmod(fileno(dst), st.st_mode & 07777) < 0) + log_warning_errno(errno, "Failed to change mode on %s: %m", backup); + + if (fchown(fileno(dst), st.st_uid, st.st_gid)< 0) + log_warning_errno(errno, "Failed to change ownership of %s: %m", backup); + + ts[0] = st.st_atim; + ts[1] = st.st_mtim; + if (futimens(fileno(dst), ts) < 0) + log_warning_errno(errno, "Failed to fix access and modification time of %s: %m", backup); + + if (rename(temp, backup) < 0) + goto fail; + + return 0; + +fail: + unlink(temp); + return r; +} + +static int putgrent_with_members(const struct group *gr, FILE *group) { + char **a; + + assert(gr); + assert(group); + + a = hashmap_get(members, gr->gr_name); + if (a) { + _cleanup_strv_free_ char **l = NULL; + bool added = false; + char **i; + + l = strv_copy(gr->gr_mem); + if (!l) + return -ENOMEM; + + STRV_FOREACH(i, a) { + if (strv_find(l, *i)) + continue; + + if (strv_extend(&l, *i) < 0) + return -ENOMEM; + + added = true; + } + + if (added) { + struct group t; + + strv_uniq(l); + strv_sort(l); + + t = *gr; + t.gr_mem = l; + + errno = 0; + if (putgrent(&t, group) != 0) + return errno > 0 ? -errno : -EIO; + + return 1; + } + } + + errno = 0; + if (putgrent(gr, group) != 0) + return errno > 0 ? -errno : -EIO; + + return 0; +} + +static int putsgent_with_members(const struct sgrp *sg, FILE *gshadow) { + char **a; + + assert(sg); + assert(gshadow); + + a = hashmap_get(members, sg->sg_namp); + if (a) { + _cleanup_strv_free_ char **l = NULL; + bool added = false; + char **i; + + l = strv_copy(sg->sg_mem); + if (!l) + return -ENOMEM; + + STRV_FOREACH(i, a) { + if (strv_find(l, *i)) + continue; + + if (strv_extend(&l, *i) < 0) + return -ENOMEM; + + added = true; + } + + if (added) { + struct sgrp t; + + strv_uniq(l); + strv_sort(l); + + t = *sg; + t.sg_mem = l; + + errno = 0; + if (putsgent(&t, gshadow) != 0) + return errno > 0 ? -errno : -EIO; + + return 1; + } + } + + errno = 0; + if (putsgent(sg, gshadow) != 0) + return errno > 0 ? -errno : -EIO; + + return 0; +} + +static int sync_rights(FILE *from, FILE *to) { + struct stat st; + + if (fstat(fileno(from), &st) < 0) + return -errno; + + if (fchmod(fileno(to), st.st_mode & 07777) < 0) + return -errno; + + if (fchown(fileno(to), st.st_uid, st.st_gid) < 0) + return -errno; + + return 0; +} + +static int rename_and_apply_smack(const char *temp_path, const char *dest_path) { + int r = 0; + if (rename(temp_path, dest_path) < 0) + return -errno; + +#ifdef SMACK_RUN_LABEL + r = mac_smack_apply(dest_path, SMACK_ATTR_ACCESS, SMACK_FLOOR_LABEL); + if (r < 0) + return r; +#endif + return r; +} + +static int write_files(void) { + + _cleanup_fclose_ FILE *passwd = NULL, *group = NULL, *shadow = NULL, *gshadow = NULL; + _cleanup_free_ char *passwd_tmp = NULL, *group_tmp = NULL, *shadow_tmp = NULL, *gshadow_tmp = NULL; + const char *passwd_path = NULL, *group_path = NULL, *shadow_path = NULL, *gshadow_path = NULL; + bool group_changed = false; + Iterator iterator; + Item *i; + int r; + + if (hashmap_size(todo_gids) > 0 || hashmap_size(members) > 0) { + _cleanup_fclose_ FILE *original = NULL; + + /* First we update the actual group list file */ + group_path = prefix_roota(arg_root, "/etc/group"); + r = fopen_temporary_label("/etc/group", group_path, &group, &group_tmp); + if (r < 0) + goto finish; + + original = fopen(group_path, "re"); + if (original) { + struct group *gr; + + r = sync_rights(original, group); + if (r < 0) + goto finish; + + errno = 0; + while ((gr = fgetgrent(original))) { + /* Safety checks against name and GID + * collisions. Normally, this should + * be unnecessary, but given that we + * look at the entries anyway here, + * let's make an extra verification + * step that we don't generate + * duplicate entries. */ + + i = hashmap_get(groups, gr->gr_name); + if (i && i->todo_group) { + log_error("%s: Group \"%s\" already exists.", group_path, gr->gr_name); + r = -EEXIST; + goto finish; + } + + if (hashmap_contains(todo_gids, GID_TO_PTR(gr->gr_gid))) { + log_error("%s: Detected collision for GID " GID_FMT ".", group_path, gr->gr_gid); + r = -EEXIST; + goto finish; + } + + r = putgrent_with_members(gr, group); + if (r < 0) + goto finish; + if (r > 0) + group_changed = true; + + errno = 0; + } + if (!IN_SET(errno, 0, ENOENT)) { + r = -errno; + goto finish; + } + + } else if (errno != ENOENT) { + r = -errno; + goto finish; + } else if (fchmod(fileno(group), 0644) < 0) { + r = -errno; + goto finish; + } + + HASHMAP_FOREACH(i, todo_gids, iterator) { + struct group n = { + .gr_name = i->name, + .gr_gid = i->gid, + .gr_passwd = (char*) "x", + }; + + r = putgrent_with_members(&n, group); + if (r < 0) + goto finish; + + group_changed = true; + } + + r = fflush_and_check(group); + if (r < 0) + goto finish; + + if (original) { + fclose(original); + original = NULL; + } + + /* OK, now also update the shadow file for the group list */ + gshadow_path = prefix_roota(arg_root, "/etc/gshadow"); + r = fopen_temporary_label("/etc/gshadow", gshadow_path, &gshadow, &gshadow_tmp); + if (r < 0) + goto finish; + + original = fopen(gshadow_path, "re"); + if (original) { + struct sgrp *sg; + + r = sync_rights(original, gshadow); + if (r < 0) + goto finish; + + errno = 0; + while ((sg = fgetsgent(original))) { + + i = hashmap_get(groups, sg->sg_namp); + if (i && i->todo_group) { + log_error("%s: Group \"%s\" already exists.", gshadow_path, sg->sg_namp); + r = -EEXIST; + goto finish; + } + + r = putsgent_with_members(sg, gshadow); + if (r < 0) + goto finish; + if (r > 0) + group_changed = true; + + errno = 0; + } + if (!IN_SET(errno, 0, ENOENT)) { + r = -errno; + goto finish; + } + + } else if (errno != ENOENT) { + r = -errno; + goto finish; + } else if (fchmod(fileno(gshadow), 0000) < 0) { + r = -errno; + goto finish; + } + + HASHMAP_FOREACH(i, todo_gids, iterator) { + struct sgrp n = { + .sg_namp = i->name, + .sg_passwd = (char*) "!!", + }; + + r = putsgent_with_members(&n, gshadow); + if (r < 0) + goto finish; + + group_changed = true; + } + + r = fflush_and_check(gshadow); + if (r < 0) + goto finish; + } + + if (hashmap_size(todo_uids) > 0) { + _cleanup_fclose_ FILE *original = NULL; + long lstchg; + + /* First we update the user database itself */ + passwd_path = prefix_roota(arg_root, "/etc/passwd"); + r = fopen_temporary_label("/etc/passwd", passwd_path, &passwd, &passwd_tmp); + if (r < 0) + goto finish; + + original = fopen(passwd_path, "re"); + if (original) { + struct passwd *pw; + + r = sync_rights(original, passwd); + if (r < 0) + goto finish; + + errno = 0; + while ((pw = fgetpwent(original))) { + + i = hashmap_get(users, pw->pw_name); + if (i && i->todo_user) { + log_error("%s: User \"%s\" already exists.", passwd_path, pw->pw_name); + r = -EEXIST; + goto finish; + } + + if (hashmap_contains(todo_uids, UID_TO_PTR(pw->pw_uid))) { + log_error("%s: Detected collision for UID " UID_FMT ".", passwd_path, pw->pw_uid); + r = -EEXIST; + goto finish; + } + + errno = 0; + if (putpwent(pw, passwd) < 0) { + r = errno ? -errno : -EIO; + goto finish; + } + + errno = 0; + } + if (!IN_SET(errno, 0, ENOENT)) { + r = -errno; + goto finish; + } + + } else if (errno != ENOENT) { + r = -errno; + goto finish; + } else if (fchmod(fileno(passwd), 0644) < 0) { + r = -errno; + goto finish; + } + + HASHMAP_FOREACH(i, todo_uids, iterator) { + struct passwd n = { + .pw_name = i->name, + .pw_uid = i->uid, + .pw_gid = i->gid, + .pw_gecos = i->description, + + /* "x" means the password is stored in + * the shadow file */ + .pw_passwd = (char*) "x", + + /* We default to the root directory as home */ + .pw_dir = i->home ? i->home : (char*) "/", + + /* Initialize the shell to nologin, + * with one exception: for root we + * patch in something special */ + .pw_shell = i->uid == 0 ? (char*) "/bin/sh" : (char*) "/sbin/nologin", + }; + + errno = 0; + if (putpwent(&n, passwd) != 0) { + r = errno ? -errno : -EIO; + goto finish; + } + } + + r = fflush_and_check(passwd); + if (r < 0) + goto finish; + + if (original) { + fclose(original); + original = NULL; + } + + /* The we update the shadow database */ + shadow_path = prefix_roota(arg_root, "/etc/shadow"); + r = fopen_temporary_label("/etc/shadow", shadow_path, &shadow, &shadow_tmp); + if (r < 0) + goto finish; + + lstchg = (long) (now(CLOCK_REALTIME) / USEC_PER_DAY); + + original = fopen(shadow_path, "re"); + if (original) { + struct spwd *sp; + + r = sync_rights(original, shadow); + if (r < 0) + goto finish; + + errno = 0; + while ((sp = fgetspent(original))) { + + i = hashmap_get(users, sp->sp_namp); + if (i && i->todo_user) { + /* we will update the existing entry */ + sp->sp_lstchg = lstchg; + + /* only the /etc/shadow stage is left, so we can + * safely remove the item from the todo set */ + i->todo_user = false; + hashmap_remove(todo_uids, UID_TO_PTR(i->uid)); + } + + errno = 0; + if (putspent(sp, shadow) < 0) { + r = errno ? -errno : -EIO; + goto finish; + } + + errno = 0; + } + if (!IN_SET(errno, 0, ENOENT)) { + r = -errno; + goto finish; + } + } else if (errno != ENOENT) { + r = -errno; + goto finish; + } else if (fchmod(fileno(shadow), 0000) < 0) { + r = -errno; + goto finish; + } + + HASHMAP_FOREACH(i, todo_uids, iterator) { + struct spwd n = { + .sp_namp = i->name, + .sp_pwdp = (char*) "!!", + .sp_lstchg = lstchg, + .sp_min = -1, + .sp_max = -1, + .sp_warn = -1, + .sp_inact = -1, + .sp_expire = -1, + .sp_flag = (unsigned long) -1, /* this appears to be what everybody does ... */ + }; + + errno = 0; + if (putspent(&n, shadow) != 0) { + r = errno ? -errno : -EIO; + goto finish; + } + } + + r = fflush_and_check(shadow); + if (r < 0) + goto finish; + } + + /* Make a backup of the old files */ + if (group_changed) { + if (group) { + r = make_backup("/etc/group", group_path); + if (r < 0) + goto finish; + } + if (gshadow) { + r = make_backup("/etc/gshadow", gshadow_path); + if (r < 0) + goto finish; + } + } + + if (passwd) { + r = make_backup("/etc/passwd", passwd_path); + if (r < 0) + goto finish; + } + if (shadow) { + r = make_backup("/etc/shadow", shadow_path); + if (r < 0) + goto finish; + } + + /* And make the new files count */ + if (group_changed) { + if (group) { + r = rename_and_apply_smack(group_tmp, group_path); + if (r < 0) + goto finish; + + group_tmp = mfree(group_tmp); + } + if (gshadow) { + r = rename_and_apply_smack(gshadow_tmp, gshadow_path); + if (r < 0) + goto finish; + + gshadow_tmp = mfree(gshadow_tmp); + } + } + + if (passwd) { + r = rename_and_apply_smack(passwd_tmp, passwd_path); + if (r < 0) + goto finish; + + passwd_tmp = mfree(passwd_tmp); + } + if (shadow) { + r = rename_and_apply_smack(shadow_tmp, shadow_path); + if (r < 0) + goto finish; + + shadow_tmp = mfree(shadow_tmp); + } + + r = 0; + +finish: + if (passwd_tmp) + unlink(passwd_tmp); + if (shadow_tmp) + unlink(shadow_tmp); + if (group_tmp) + unlink(group_tmp); + if (gshadow_tmp) + unlink(gshadow_tmp); + + return r; +} + +static int uid_is_ok(uid_t uid, const char *name) { + struct passwd *p; + struct group *g; + const char *n; + Item *i; + + /* Let's see if we already have assigned the UID a second time */ + if (hashmap_get(todo_uids, UID_TO_PTR(uid))) + return 0; + + /* Try to avoid using uids that are already used by a group + * that doesn't have the same name as our new user. */ + i = hashmap_get(todo_gids, GID_TO_PTR(uid)); + if (i && !streq(i->name, name)) + return 0; + + /* Let's check the files directly */ + if (hashmap_contains(database_uid, UID_TO_PTR(uid))) + return 0; + + n = hashmap_get(database_gid, GID_TO_PTR(uid)); + if (n && !streq(n, name)) + return 0; + + /* Let's also check via NSS, to avoid UID clashes over LDAP and such, just in case */ + if (!arg_root) { + errno = 0; + p = getpwuid(uid); + if (p) + return 0; + if (!IN_SET(errno, 0, ENOENT)) + return -errno; + + errno = 0; + g = getgrgid((gid_t) uid); + if (g) { + if (!streq(g->gr_name, name)) + return 0; + } else if (!IN_SET(errno, 0, ENOENT)) + return -errno; + } + + return 1; +} + +static int root_stat(const char *p, struct stat *st) { + const char *fix; + + fix = prefix_roota(arg_root, p); + if (stat(fix, st) < 0) + return -errno; + + return 0; +} + +static int read_id_from_file(Item *i, uid_t *_uid, gid_t *_gid) { + struct stat st; + bool found_uid = false, found_gid = false; + uid_t uid = 0; + gid_t gid = 0; + + assert(i); + + /* First, try to get the gid directly */ + if (_gid && i->gid_path && root_stat(i->gid_path, &st) >= 0) { + gid = st.st_gid; + found_gid = true; + } + + /* Then, try to get the uid directly */ + if ((_uid || (_gid && !found_gid)) + && i->uid_path + && root_stat(i->uid_path, &st) >= 0) { + + uid = st.st_uid; + found_uid = true; + + /* If we need the gid, but had no success yet, also derive it from the uid path */ + if (_gid && !found_gid) { + gid = st.st_gid; + found_gid = true; + } + } + + /* If that didn't work yet, then let's reuse the gid as uid */ + if (_uid && !found_uid && i->gid_path) { + + if (found_gid) { + uid = (uid_t) gid; + found_uid = true; + } else if (root_stat(i->gid_path, &st) >= 0) { + uid = (uid_t) st.st_gid; + found_uid = true; + } + } + + if (_uid) { + if (!found_uid) + return 0; + + *_uid = uid; + } + + if (_gid) { + if (!found_gid) + return 0; + + *_gid = gid; + } + + return 1; +} + +static int add_user(Item *i) { + void *z; + int r; + + assert(i); + + /* Check the database directly */ + z = hashmap_get(database_user, i->name); + if (z) { + log_debug("User %s already exists.", i->name); + i->uid = PTR_TO_UID(z); + i->uid_set = true; + return 0; + } + + if (!arg_root) { + struct passwd *p; + + /* Also check NSS */ + errno = 0; + p = getpwnam(i->name); + if (p) { + log_debug("User %s already exists.", i->name); + i->uid = p->pw_uid; + i->uid_set = true; + + r = free_and_strdup(&i->description, p->pw_gecos); + if (r < 0) + return log_oom(); + + return 0; + } + if (!IN_SET(errno, 0, ENOENT)) + return log_error_errno(errno, "Failed to check if user %s already exists: %m", i->name); + } + + /* Try to use the suggested numeric uid */ + if (i->uid_set) { + r = uid_is_ok(i->uid, i->name); + if (r < 0) + return log_error_errno(r, "Failed to verify uid " UID_FMT ": %m", i->uid); + if (r == 0) { + log_debug("Suggested user ID " UID_FMT " for %s already used.", i->uid, i->name); + i->uid_set = false; + } + } + + /* If that didn't work, try to read it from the specified path */ + if (!i->uid_set) { + uid_t c; + + if (read_id_from_file(i, &c, NULL) > 0) { + + if (c <= 0 || !uid_range_contains(uid_range, n_uid_range, c)) + log_debug("User ID " UID_FMT " of file not suitable for %s.", c, i->name); + else { + r = uid_is_ok(c, i->name); + if (r < 0) + return log_error_errno(r, "Failed to verify uid " UID_FMT ": %m", i->uid); + else if (r > 0) { + i->uid = c; + i->uid_set = true; + } else + log_debug("User ID " UID_FMT " of file for %s is already used.", c, i->name); + } + } + } + + /* Otherwise, try to reuse the group ID */ + if (!i->uid_set && i->gid_set) { + r = uid_is_ok((uid_t) i->gid, i->name); + if (r < 0) + return log_error_errno(r, "Failed to verify uid " UID_FMT ": %m", i->uid); + if (r > 0) { + i->uid = (uid_t) i->gid; + i->uid_set = true; + } + } + + /* And if that didn't work either, let's try to find a free one */ + if (!i->uid_set) { + for (;;) { + r = uid_range_next_lower(uid_range, n_uid_range, &search_uid); + if (r < 0) { + log_error("No free user ID available for %s.", i->name); + return r; + } + + r = uid_is_ok(search_uid, i->name); + if (r < 0) + return log_error_errno(r, "Failed to verify uid " UID_FMT ": %m", i->uid); + else if (r > 0) + break; + } + + i->uid_set = true; + i->uid = search_uid; + } + + r = hashmap_ensure_allocated(&todo_uids, NULL); + if (r < 0) + return log_oom(); + + r = hashmap_put(todo_uids, UID_TO_PTR(i->uid), i); + if (r < 0) + return log_oom(); + + i->todo_user = true; + log_info("Creating user %s (%s) with uid " UID_FMT " and gid " GID_FMT ".", i->name, strna(i->description), i->uid, i->gid); + + return 0; +} + +static int gid_is_ok(gid_t gid) { + struct group *g; + struct passwd *p; + + if (hashmap_get(todo_gids, GID_TO_PTR(gid))) + return 0; + + /* Avoid reusing gids that are already used by a different user */ + if (hashmap_get(todo_uids, UID_TO_PTR(gid))) + return 0; + + if (hashmap_contains(database_gid, GID_TO_PTR(gid))) + return 0; + + if (hashmap_contains(database_uid, UID_TO_PTR(gid))) + return 0; + + if (!arg_root) { + errno = 0; + g = getgrgid(gid); + if (g) + return 0; + if (!IN_SET(errno, 0, ENOENT)) + return -errno; + + errno = 0; + p = getpwuid((uid_t) gid); + if (p) + return 0; + if (!IN_SET(errno, 0, ENOENT)) + return -errno; + } + + return 1; +} + +static int add_group(Item *i) { + void *z; + int r; + + assert(i); + + /* Check the database directly */ + z = hashmap_get(database_group, i->name); + if (z) { + log_debug("Group %s already exists.", i->name); + i->gid = PTR_TO_GID(z); + i->gid_set = true; + return 0; + } + + /* Also check NSS */ + if (!arg_root) { + struct group *g; + + errno = 0; + g = getgrnam(i->name); + if (g) { + log_debug("Group %s already exists.", i->name); + i->gid = g->gr_gid; + i->gid_set = true; + return 0; + } + if (!IN_SET(errno, 0, ENOENT)) + return log_error_errno(errno, "Failed to check if group %s already exists: %m", i->name); + } + + /* Try to use the suggested numeric gid */ + if (i->gid_set) { + r = gid_is_ok(i->gid); + if (r < 0) + return log_error_errno(r, "Failed to verify gid " GID_FMT ": %m", i->gid); + if (r == 0) { + log_debug("Suggested group ID " GID_FMT " for %s already used.", i->gid, i->name); + i->gid_set = false; + } + } + + /* Try to reuse the numeric uid, if there's one */ + if (!i->gid_set && i->uid_set) { + r = gid_is_ok((gid_t) i->uid); + if (r < 0) + return log_error_errno(r, "Failed to verify gid " GID_FMT ": %m", i->gid); + if (r > 0) { + i->gid = (gid_t) i->uid; + i->gid_set = true; + } + } + + /* If that didn't work, try to read it from the specified path */ + if (!i->gid_set) { + gid_t c; + + if (read_id_from_file(i, NULL, &c) > 0) { + + if (c <= 0 || !uid_range_contains(uid_range, n_uid_range, c)) + log_debug("Group ID " GID_FMT " of file not suitable for %s.", c, i->name); + else { + r = gid_is_ok(c); + if (r < 0) + return log_error_errno(r, "Failed to verify gid " GID_FMT ": %m", i->gid); + else if (r > 0) { + i->gid = c; + i->gid_set = true; + } else + log_debug("Group ID " GID_FMT " of file for %s already used.", c, i->name); + } + } + } + + /* And if that didn't work either, let's try to find a free one */ + if (!i->gid_set) { + for (;;) { + /* We look for new GIDs in the UID pool! */ + r = uid_range_next_lower(uid_range, n_uid_range, &search_uid); + if (r < 0) { + log_error("No free group ID available for %s.", i->name); + return r; + } + + r = gid_is_ok(search_uid); + if (r < 0) + return log_error_errno(r, "Failed to verify gid " GID_FMT ": %m", i->gid); + else if (r > 0) + break; + } + + i->gid_set = true; + i->gid = search_uid; + } + + r = hashmap_ensure_allocated(&todo_gids, NULL); + if (r < 0) + return log_oom(); + + r = hashmap_put(todo_gids, GID_TO_PTR(i->gid), i); + if (r < 0) + return log_oom(); + + i->todo_group = true; + log_info("Creating group %s with gid " GID_FMT ".", i->name, i->gid); + + return 0; +} + +static int process_item(Item *i) { + int r; + + assert(i); + + switch (i->type) { + + case ADD_USER: + r = add_group(i); + if (r < 0) + return r; + + return add_user(i); + + case ADD_GROUP: { + Item *j; + + j = hashmap_get(users, i->name); + if (j) { + /* There's already user to be created for this + * name, let's process that in one step */ + + if (i->gid_set) { + j->gid = i->gid; + j->gid_set = true; + } + + if (i->gid_path) { + r = free_and_strdup(&j->gid_path, i->gid_path); + if (r < 0) + return log_oom(); + } + + return 0; + } + + return add_group(i); + } + + default: + assert_not_reached("Unknown item type"); + } +} + +static void item_free(Item *i) { + + if (!i) + return; + + free(i->name); + free(i->uid_path); + free(i->gid_path); + free(i->description); + free(i); +} + +DEFINE_TRIVIAL_CLEANUP_FUNC(Item*, item_free); + +static int add_implicit(void) { + char *g, **l; + Iterator iterator; + int r; + + /* Implicitly create additional users and groups, if they were listed in "m" lines */ + + HASHMAP_FOREACH_KEY(l, g, members, iterator) { + Item *i; + char **m; + + i = hashmap_get(groups, g); + if (!i) { + _cleanup_(item_freep) Item *j = NULL; + + r = hashmap_ensure_allocated(&groups, &string_hash_ops); + if (r < 0) + return log_oom(); + + j = new0(Item, 1); + if (!j) + return log_oom(); + + j->type = ADD_GROUP; + j->name = strdup(g); + if (!j->name) + return log_oom(); + + r = hashmap_put(groups, j->name, j); + if (r < 0) + return log_oom(); + + log_debug("Adding implicit group '%s' due to m line", j->name); + j = NULL; + } + + STRV_FOREACH(m, l) { + + i = hashmap_get(users, *m); + if (!i) { + _cleanup_(item_freep) Item *j = NULL; + + r = hashmap_ensure_allocated(&users, &string_hash_ops); + if (r < 0) + return log_oom(); + + j = new0(Item, 1); + if (!j) + return log_oom(); + + j->type = ADD_USER; + j->name = strdup(*m); + if (!j->name) + return log_oom(); + + r = hashmap_put(users, j->name, j); + if (r < 0) + return log_oom(); + + log_debug("Adding implicit user '%s' due to m line", j->name); + j = NULL; + } + } + } + + return 0; +} + +static bool item_equal(Item *a, Item *b) { + assert(a); + assert(b); + + if (a->type != b->type) + return false; + + if (!streq_ptr(a->name, b->name)) + return false; + + if (!streq_ptr(a->uid_path, b->uid_path)) + return false; + + if (!streq_ptr(a->gid_path, b->gid_path)) + return false; + + if (!streq_ptr(a->description, b->description)) + return false; + + if (a->uid_set != b->uid_set) + return false; + + if (a->uid_set && a->uid != b->uid) + return false; + + if (a->gid_set != b->gid_set) + return false; + + if (a->gid_set && a->gid != b->gid) + return false; + + if (!streq_ptr(a->home, b->home)) + return false; + + return true; +} + +static bool valid_user_group_name(const char *u) { + const char *i; + long sz; + + if (isempty(u)) + return false; + + if (!(u[0] >= 'a' && u[0] <= 'z') && + !(u[0] >= 'A' && u[0] <= 'Z') && + u[0] != '_') + return false; + + for (i = u+1; *i; i++) { + if (!(*i >= 'a' && *i <= 'z') && + !(*i >= 'A' && *i <= 'Z') && + !(*i >= '0' && *i <= '9') && + *i != '_' && + *i != '-') + return false; + } + + sz = sysconf(_SC_LOGIN_NAME_MAX); + assert_se(sz > 0); + + if ((size_t) (i-u) > (size_t) sz) + return false; + + if ((size_t) (i-u) > UT_NAMESIZE - 1) + return false; + + return true; +} + +static bool valid_gecos(const char *d) { + + if (!d) + return false; + + if (!utf8_is_valid(d)) + return false; + + if (string_has_cc(d, NULL)) + return false; + + /* Colons are used as field separators, and hence not OK */ + if (strchr(d, ':')) + return false; + + return true; +} + +static bool valid_home(const char *p) { + + if (isempty(p)) + return false; + + if (!utf8_is_valid(p)) + return false; + + if (string_has_cc(p, NULL)) + return false; + + if (!path_is_absolute(p)) + return false; + + if (!path_is_safe(p)) + return false; + + /* Colons are used as field separators, and hence not OK */ + if (strchr(p, ':')) + return false; + + return true; +} + +static int parse_line(const char *fname, unsigned line, const char *buffer) { + + static const Specifier specifier_table[] = { + { 'm', specifier_machine_id, NULL }, + { 'b', specifier_boot_id, NULL }, + { 'H', specifier_host_name, NULL }, + { 'v', specifier_kernel_release, NULL }, + {} + }; + + _cleanup_free_ char *action = NULL, *name = NULL, *id = NULL, *resolved_name = NULL, *resolved_id = NULL, *description = NULL, *home = NULL; + _cleanup_(item_freep) Item *i = NULL; + Item *existing; + Hashmap *h; + int r; + const char *p; + + assert(fname); + assert(line >= 1); + assert(buffer); + + /* Parse columns */ + p = buffer; + r = extract_many_words(&p, NULL, EXTRACT_QUOTES, &action, &name, &id, &description, &home, NULL); + if (r < 0) { + log_error("[%s:%u] Syntax error.", fname, line); + return r; + } + if (r < 2) { + log_error("[%s:%u] Missing action and name columns.", fname, line); + return -EINVAL; + } + if (!isempty(p)) { + log_error("[%s:%u] Trailing garbage.", fname, line); + return -EINVAL; + } + + /* Verify action */ + if (strlen(action) != 1) { + log_error("[%s:%u] Unknown modifier '%s'", fname, line, action); + return -EINVAL; + } + + if (!IN_SET(action[0], ADD_USER, ADD_GROUP, ADD_MEMBER, ADD_RANGE)) { + log_error("[%s:%u] Unknown command command type '%c'.", fname, line, action[0]); + return -EBADMSG; + } + + /* Verify name */ + if (isempty(name) || streq(name, "-")) + name = mfree(name); + + if (name) { + r = specifier_printf(name, specifier_table, NULL, &resolved_name); + if (r < 0) { + log_error("[%s:%u] Failed to replace specifiers: %s", fname, line, name); + return r; + } + + if (!valid_user_group_name(resolved_name)) { + log_error("[%s:%u] '%s' is not a valid user or group name.", fname, line, resolved_name); + return -EINVAL; + } + } + + /* Verify id */ + if (isempty(id) || streq(id, "-")) + id = mfree(id); + + if (id) { + r = specifier_printf(id, specifier_table, NULL, &resolved_id); + if (r < 0) { + log_error("[%s:%u] Failed to replace specifiers: %s", fname, line, name); + return r; + } + } + + /* Verify description */ + if (isempty(description) || streq(description, "-")) + description = mfree(description); + + if (description) { + if (!valid_gecos(description)) { + log_error("[%s:%u] '%s' is not a valid GECOS field.", fname, line, description); + return -EINVAL; + } + } + + /* Verify home */ + if (isempty(home) || streq(home, "-")) + home = mfree(home); + + if (home) { + if (!valid_home(home)) { + log_error("[%s:%u] '%s' is not a valid home directory field.", fname, line, home); + return -EINVAL; + } + } + + switch (action[0]) { + + case ADD_RANGE: + if (resolved_name) { + log_error("[%s:%u] Lines of type 'r' don't take a name field.", fname, line); + return -EINVAL; + } + + if (!resolved_id) { + log_error("[%s:%u] Lines of type 'r' require a ID range in the third field.", fname, line); + return -EINVAL; + } + + if (description) { + log_error("[%s:%u] Lines of type 'r' don't take a GECOS field.", fname, line); + return -EINVAL; + } + + if (home) { + log_error("[%s:%u] Lines of type 'r' don't take a home directory field.", fname, line); + return -EINVAL; + } + + r = uid_range_add_str(&uid_range, &n_uid_range, resolved_id); + if (r < 0) { + log_error("[%s:%u] Invalid UID range %s.", fname, line, resolved_id); + return -EINVAL; + } + + return 0; + + case ADD_MEMBER: { + char **l; + + /* Try to extend an existing member or group item */ + if (!name) { + log_error("[%s:%u] Lines of type 'm' require a user name in the second field.", fname, line); + return -EINVAL; + } + + if (!resolved_id) { + log_error("[%s:%u] Lines of type 'm' require a group name in the third field.", fname, line); + return -EINVAL; + } + + if (!valid_user_group_name(resolved_id)) { + log_error("[%s:%u] '%s' is not a valid user or group name.", fname, line, resolved_id); + return -EINVAL; + } + + if (description) { + log_error("[%s:%u] Lines of type 'm' don't take a GECOS field.", fname, line); + return -EINVAL; + } + + if (home) { + log_error("[%s:%u] Lines of type 'm' don't take a home directory field.", fname, line); + return -EINVAL; + } + + r = hashmap_ensure_allocated(&members, &string_hash_ops); + if (r < 0) + return log_oom(); + + l = hashmap_get(members, resolved_id); + if (l) { + /* A list for this group name already exists, let's append to it */ + r = strv_push(&l, resolved_name); + if (r < 0) + return log_oom(); + + resolved_name = NULL; + + assert_se(hashmap_update(members, resolved_id, l) >= 0); + } else { + /* No list for this group name exists yet, create one */ + + l = new0(char *, 2); + if (!l) + return -ENOMEM; + + l[0] = resolved_name; + l[1] = NULL; + + r = hashmap_put(members, resolved_id, l); + if (r < 0) { + free(l); + return log_oom(); + } + + resolved_id = resolved_name = NULL; + } + + return 0; + } + + case ADD_USER: + if (!name) { + log_error("[%s:%u] Lines of type 'u' require a user name in the second field.", fname, line); + return -EINVAL; + } + + r = hashmap_ensure_allocated(&users, &string_hash_ops); + if (r < 0) + return log_oom(); + + i = new0(Item, 1); + if (!i) + return log_oom(); + + if (resolved_id) { + if (path_is_absolute(resolved_id)) { + i->uid_path = resolved_id; + resolved_id = NULL; + + path_kill_slashes(i->uid_path); + } else { + r = parse_uid(resolved_id, &i->uid); + if (r < 0) { + log_error("Failed to parse UID: %s", id); + return -EBADMSG; + } + + i->uid_set = true; + } + } + + i->description = description; + description = NULL; + + i->home = home; + home = NULL; + + h = users; + break; + + case ADD_GROUP: + if (!name) { + log_error("[%s:%u] Lines of type 'g' require a user name in the second field.", fname, line); + return -EINVAL; + } + + if (description) { + log_error("[%s:%u] Lines of type 'g' don't take a GECOS field.", fname, line); + return -EINVAL; + } + + if (home) { + log_error("[%s:%u] Lines of type 'g' don't take a home directory field.", fname, line); + return -EINVAL; + } + + r = hashmap_ensure_allocated(&groups, &string_hash_ops); + if (r < 0) + return log_oom(); + + i = new0(Item, 1); + if (!i) + return log_oom(); + + if (resolved_id) { + if (path_is_absolute(resolved_id)) { + i->gid_path = resolved_id; + resolved_id = NULL; + + path_kill_slashes(i->gid_path); + } else { + r = parse_gid(resolved_id, &i->gid); + if (r < 0) { + log_error("Failed to parse GID: %s", id); + return -EBADMSG; + } + + i->gid_set = true; + } + } + + h = groups; + break; + + default: + return -EBADMSG; + } + + i->type = action[0]; + i->name = resolved_name; + resolved_name = NULL; + + existing = hashmap_get(h, i->name); + if (existing) { + + /* Two identical items are fine */ + if (!item_equal(existing, i)) + log_warning("Two or more conflicting lines for %s configured, ignoring.", i->name); + + return 0; + } + + r = hashmap_put(h, i->name, i); + if (r < 0) + return log_oom(); + + i = NULL; + return 0; +} + +static int read_config_file(const char *fn, bool ignore_enoent) { + _cleanup_fclose_ FILE *rf = NULL; + FILE *f = NULL; + char line[LINE_MAX]; + unsigned v = 0; + int r = 0; + + assert(fn); + + if (streq(fn, "-")) + f = stdin; + else { + r = search_and_fopen_nulstr(fn, "re", arg_root, conf_file_dirs, &rf); + if (r < 0) { + if (ignore_enoent && r == -ENOENT) + return 0; + + return log_error_errno(r, "Failed to open '%s', ignoring: %m", fn); + } + + f = rf; + } + + 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; + } + + if (ferror(f)) { + log_error_errno(errno, "Failed to read from file %s: %m", fn); + if (r == 0) + r = -EIO; + } + + return r; +} + +static void free_database(Hashmap *by_name, Hashmap *by_id) { + char *name; + + for (;;) { + name = hashmap_first(by_id); + if (!name) + break; + + hashmap_remove(by_name, name); + + hashmap_steal_first_key(by_id); + free(name); + } + + while ((name = hashmap_steal_first_key(by_name))) + free(name); + + hashmap_free(by_name); + hashmap_free(by_id); +} + +static void help(void) { + printf("%s [OPTIONS...] [CONFIGURATION FILE...]\n\n" + "Creates system user accounts.\n\n" + " -h --help Show this help\n" + " --version Show package version\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_ROOT, + }; + + static const struct option options[] = { + { "help", no_argument, NULL, 'h' }, + { "version", no_argument, NULL, ARG_VERSION }, + { "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_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"); + } + + return 1; +} + +int main(int argc, char *argv[]) { + + _cleanup_close_ int lock = -1; + Iterator iterator; + int r, k; + Item *i; + char *n; + + r = parse_argv(argc, argv); + if (r <= 0) + goto finish; + + log_set_target(LOG_TARGET_AUTO); + log_parse_environment(); + log_open(); + + umask(0022); + + r = mac_selinux_init(); + if (r < 0) { + log_error_errno(r, "SELinux setup failed: %m"); + goto finish; + } + + 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 sysusers.d files: %m"); + goto finish; + } + + STRV_FOREACH(f, files) { + k = read_config_file(*f, true); + if (k < 0 && r == 0) + r = k; + } + } + + if (!uid_range) { + /* Default to default range of 1..SYSTEMD_UID_MAX */ + r = uid_range_add(&uid_range, &n_uid_range, 1, SYSTEM_UID_MAX); + if (r < 0) { + log_oom(); + goto finish; + } + } + + r = add_implicit(); + if (r < 0) + goto finish; + + lock = take_etc_passwd_lock(arg_root); + if (lock < 0) { + log_error_errno(lock, "Failed to take lock: %m"); + goto finish; + } + + r = load_user_database(); + if (r < 0) { + log_error_errno(r, "Failed to load user database: %m"); + goto finish; + } + + r = load_group_database(); + if (r < 0) { + log_error_errno(r, "Failed to read group database: %m"); + goto finish; + } + + HASHMAP_FOREACH(i, groups, iterator) + process_item(i); + + HASHMAP_FOREACH(i, users, iterator) + process_item(i); + + r = write_files(); + if (r < 0) + log_error_errno(r, "Failed to write files: %m"); + +finish: + while ((i = hashmap_steal_first(groups))) + item_free(i); + + while ((i = hashmap_steal_first(users))) + item_free(i); + + while ((n = hashmap_first_key(members))) { + strv_free(hashmap_steal_first(members)); + free(n); + } + + hashmap_free(groups); + hashmap_free(users); + hashmap_free(members); + hashmap_free(todo_uids); + hashmap_free(todo_gids); + + free_database(database_user, database_uid); + free_database(database_group, database_gid); + + free(arg_root); + + return r < 0 ? EXIT_FAILURE : EXIT_SUCCESS; +} diff --git a/src/grp-initutils/systemd-tmpfiles/Makefile b/src/grp-initutils/systemd-tmpfiles/Makefile new file mode 100644 index 0000000000..d12c910883 --- /dev/null +++ b/src/grp-initutils/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 # HAVE_SYSV_COMPAT + +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 # ENABLE_TMPFILES + +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/grp-initutils/systemd-tmpfiles/tmpfiles.c b/src/grp-initutils/systemd-tmpfiles/tmpfiles.c new file mode 100644 index 0000000000..58dd7c0f8c --- /dev/null +++ b/src/grp-initutils/systemd-tmpfiles/tmpfiles.c @@ -0,0 +1,2343 @@ +/*** + 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 "shared/acl-util.h" +#include "basic/alloc-util.h" +#include "basic/btrfs-util.h" +#include "basic/capability-util.h" +#include "basic/chattr-util.h" +#include "basic/conf-files.h" +#include "basic/copy.h" +#include "basic/def.h" +#include "basic/escape.h" +#include "basic/fd-util.h" +#include "basic/fileio.h" +#include "basic/formats-util.h" +#include "basic/fs-util.h" +#include "basic/glob-util.h" +#include "basic/io-util.h" +#include "basic/label.h" +#include "basic/log.h" +#include "basic/macro.h" +#include "basic/missing.h" +#include "basic/mkdir.h" +#include "basic/mount-util.h" +#include "basic/parse-util.h" +#include "basic/path-util.h" +#include "basic/rm-rf.h" +#include "basic/selinux-util.h" +#include "basic/set.h" +#include "shared/specifier.h" +#include "basic/stat-util.h" +#include "basic/stdio-util.h" +#include "basic/string-table.h" +#include "basic/string-util.h" +#include "basic/strv.h" +#include "basic/umask-util.h" +#include "basic/user-util.h" +#include "basic/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', + EMPTY_DIRECTORY = 'e', + 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, + EMPTY_DIRECTORY, + 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, + EMPTY_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_NOFOLLOW|O_CLOEXEC|O_PATH); + 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_NOFOLLOW|O_CLOEXEC|O_PATH); + 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; + + SET_FLAG(value, v, (mode == MODE_ADD || mode == MODE_SET)); + + 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\" (unsupported fs or dir not a subvolume): %m", i->path); + else if (r == -EROFS) + log_debug_errno(r, "Couldn't adjust quota for subvolume \"%s\" (fs is read-only).", i->path); + else if (r == -ENOPROTOOPT) + log_debug_errno(r, "Couldn't adjust quota for subvolume \"%s\" (quota support is disabled).", 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); + } + + /* fall through */ + + case EMPTY_DIRECTORY: + 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) { + assert(i); + + log_debug("Running remove action for entry %c %s", (char) i->type, i->path); + + switch (i->type) { + + case REMOVE_PATH: + case TRUNCATE_DIRECTORY: + case RECURSIVE_REMOVE_PATH: + return glob_item(i, remove_item_instance, false); + + default: + return 0; + } +} + +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) { + 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 EMPTY_DIRECTORY: + case TRUNCATE_DIRECTORY: + case IGNORE_PATH: + case COPY_FILES: + clean_item_instance(i, i->path); + return 0; + case IGNORE_DIRECTORY_PATH: + return glob_item(i, clean_item_instance, false); + default: + return 0; + } +} + +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 EMPTY_DIRECTORY: + 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; + FILE *f; + char line[LINE_MAX]; + Iterator iterator; + unsigned v = 0; + Item *i; + int r; + + assert(fn); + + if (streq(fn, "-")) { + log_debug("Reading config from stdin."); + fn = ""; + f = stdin; + } else { + 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\", ignoring: %m", fn); + return 0; + } + + return log_error_errno(r, "Failed to open '%s': %m", fn); + } + log_debug("Reading config file \"%s\".", fn); + f = _f; + } + + 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(); + + 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/grp-initutils/systemd-update-done/Makefile b/src/grp-initutils/systemd-update-done/Makefile new file mode 100644 index 0000000000..90cb440874 --- /dev/null +++ b/src/grp-initutils/systemd-update-done/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 + +rootlibexec_PROGRAMS += systemd-update-done + +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/grp-initutils/systemd-update-done/update-done.c b/src/grp-initutils/systemd-update-done/update-done.c new file mode 100644 index 0000000000..39d19ec048 --- /dev/null +++ b/src/grp-initutils/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 "basic/fd-util.h" +#include "basic/io-util.h" +#include "basic/selinux-util.h" +#include "basic/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(); + 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/grp-initutils/systemd-update-utmp/Makefile b/src/grp-initutils/systemd-update-utmp/Makefile new file mode 100644 index 0000000000..0107808281 --- /dev/null +++ b/src/grp-initutils/systemd-update-utmp/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_UTMP),) +rootlibexec_PROGRAMS += \ + systemd-update-utmp +endif # HAVE_UTMP + +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/grp-initutils/systemd-update-utmp/update-utmp.c b/src/grp-initutils/systemd-update-utmp/update-utmp.c new file mode 100644 index 0000000000..69264c3a26 --- /dev/null +++ b/src/grp-initutils/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 "basic/alloc-util.h" +#include "sd-bus/bus-error.h" +#include "shared/bus-util.h" +#include "basic/formats-util.h" +#include "basic/log.h" +#include "basic/macro.h" +#include "basic/special.h" +#include "basic/unit-name.h" +#include "basic/util.h" +#include "shared/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/grp-initutils/systemd-user-sessions/Makefile b/src/grp-initutils/systemd-user-sessions/Makefile new file mode 100644 index 0000000000..66fa3b7059 --- /dev/null +++ b/src/grp-initutils/systemd-user-sessions/Makefile @@ -0,0 +1,48 @@ +# -*- 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_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 # HAVE_PAM + +EXTRA_DIST += \ + units/systemd-user-sessions.service.in + +include $(topsrcdir)/build-aux/Makefile.tail.mk diff --git a/src/grp-initutils/systemd-user-sessions/user-sessions.c b/src/grp-initutils/systemd-user-sessions/user-sessions.c new file mode 100644 index 0000000000..4cec29c39e --- /dev/null +++ b/src/grp-initutils/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 "basic/fileio.h" +#include "basic/fileio-label.h" +#include "basic/log.h" +#include "basic/selinux-util.h" +#include "basic/string-util.h" +#include "basic/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(); + + 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/grp-initutils/systemd-vconsole-setup/.gitignore b/src/grp-initutils/systemd-vconsole-setup/.gitignore new file mode 100644 index 0000000000..82741b2fb3 --- /dev/null +++ b/src/grp-initutils/systemd-vconsole-setup/.gitignore @@ -0,0 +1 @@ +/90-vconsole.rules diff --git a/src/grp-initutils/systemd-vconsole-setup/90-vconsole.rules.in b/src/grp-initutils/systemd-vconsole-setup/90-vconsole.rules.in new file mode 100644 index 0000000000..35b9ad5151 --- /dev/null +++ b/src/grp-initutils/systemd-vconsole-setup/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/grp-initutils/systemd-vconsole-setup/Makefile b/src/grp-initutils/systemd-vconsole-setup/Makefile new file mode 100644 index 0000000000..99b369967e --- /dev/null +++ b/src/grp-initutils/systemd-vconsole-setup/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 # ENABLE_VCONSOLE + +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/grp-initutils/systemd-vconsole-setup/vconsole-setup.c b/src/grp-initutils/systemd-vconsole-setup/vconsole-setup.c new file mode 100644 index 0000000000..dd05231982 --- /dev/null +++ b/src/grp-initutils/systemd-vconsole-setup/vconsole-setup.c @@ -0,0 +1,332 @@ +/*** + 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 "basic/alloc-util.h" +#include "basic/fd-util.h" +#include "basic/fileio.h" +#include "basic/io-util.h" +#include "basic/locale-util.h" +#include "basic/log.h" +#include "basic/process-util.h" +#include "basic/signal-util.h" +#include "basic/stdio-util.h" +#include "basic/string-util.h" +#include "basic/terminal-util.h" +#include "basic/util.h" +#include "basic/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; + _cleanup_free_ struct unipair* unipairs = NULL; + int i, r; + + unipairs = new(struct unipair, USHRT_MAX); + if (!unipairs) { + log_oom(); + return; + } + + /* get active, and 16 bit mask of used VT numbers */ + r = ioctl(fd, VT_GETSTATE, &vcs); + if (r < 0) { + log_debug_errno(errno, "VT_GETSTATE failed, ignoring: %m"); + 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/grp-journal-remote/.gitignore b/src/grp-journal-remote/.gitignore deleted file mode 100644 index 06847b65d4..0000000000 --- a/src/grp-journal-remote/.gitignore +++ /dev/null @@ -1,2 +0,0 @@ -/journal-remote.conf -/journal-upload.conf diff --git a/src/grp-journal-remote/Makefile b/src/grp-journal-remote/Makefile deleted file mode 100644 index 7885bea0fc..0000000000 --- a/src/grp-journal-remote/Makefile +++ /dev/null @@ -1,30 +0,0 @@ -# -*- 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 - -at.subdirs += systemd-journa-gatewayd -at.subdirs += systemd-journa-remote -at.subdirs += systemd-journa-upload - -include $(topsrcdir)/build-aux/Makefile.tail.mk diff --git a/src/grp-journal-remote/browse.html b/src/grp-journal-remote/browse.html deleted file mode 100644 index 32848c7673..0000000000 --- a/src/grp-journal-remote/browse.html +++ /dev/null @@ -1,544 +0,0 @@ - - - - Journal - - - - - - - -

- -
-
-
-
-
-
- -
- -      - Only current boot -
- -
- -
- -
- - - - -      - - -
- -
- g: First Page      - ←, k, BACKSPACE: Previous Page      - →, j, SPACE: Next Page      - G: Last Page      - +: More entries      - -: Fewer entries -
- - - - diff --git a/src/grp-journal-remote/log-generator.py b/src/grp-journal-remote/log-generator.py deleted file mode 100755 index fd6964e758..0000000000 --- a/src/grp-journal-remote/log-generator.py +++ /dev/null @@ -1,76 +0,0 @@ -#!/usr/bin/python -from __future__ import print_function -import sys -import argparse - -PARSER = argparse.ArgumentParser() -PARSER.add_argument('n', type=int) -PARSER.add_argument('--dots', action='store_true') -PARSER.add_argument('--data-size', type=int, default=4000) -PARSER.add_argument('--data-type', choices={'random', 'simple'}) -OPTIONS = PARSER.parse_args() - -template = """\ -__CURSOR=s=6863c726210b4560b7048889d8ada5c5;i=3e931;b=f446871715504074bf7049ef0718fa93;m={m:x};t=4fd05c -__REALTIME_TIMESTAMP={realtime_ts} -__MONOTONIC_TIMESTAMP={monotonic_ts} -_BOOT_ID=f446871715504074bf7049ef0718fa93 -_TRANSPORT=syslog -PRIORITY={priority} -SYSLOG_FACILITY={facility} -SYSLOG_IDENTIFIER=/USR/SBIN/CRON -MESSAGE={message} -_UID=0 -_GID=0 -_MACHINE_ID=69121ca41d12c1b69a7960174c27b618 -_HOSTNAME=hostname -SYSLOG_PID=25721 -_PID=25721 -_SOURCE_REALTIME_TIMESTAMP={source_realtime_ts} -DATA={data} -""" - -m = 0x198603b12d7 -realtime_ts = 1404101101501873 -monotonic_ts = 1753961140951 -source_realtime_ts = 1404101101483516 -priority = 3 -facility = 6 - -src = open('/dev/urandom', 'rb') - -bytes = 0 -counter = 0 - -for i in range(OPTIONS.n): - message = repr(src.read(2000)) - if OPTIONS.data_type == 'random': - data = repr(src.read(OPTIONS.data_size)) - else: - # keep the pattern non-repeating so we get a different blob every time - data = '{:0{}}'.format(counter, OPTIONS.data_size) - counter += 1 - - entry = template.format(m=m, - realtime_ts=realtime_ts, - monotonic_ts=monotonic_ts, - source_realtime_ts=source_realtime_ts, - priority=priority, - facility=facility, - message=message, - data=data) - m += 1 - realtime_ts += 1 - monotonic_ts += 1 - source_realtime_ts += 1 - - bytes += len(entry) - - print(entry) - - if OPTIONS.dots: - print('.', file=sys.stderr, end='', flush=True) - -if OPTIONS.dots: - print(file=sys.stderr) -print('Wrote {} bytes'.format(bytes), file=sys.stderr) diff --git a/src/grp-journal-remote/microhttpd-util.c b/src/grp-journal-remote/microhttpd-util.c deleted file mode 100644 index 99d6f32a2a..0000000000 --- a/src/grp-journal-remote/microhttpd-util.c +++ /dev/null @@ -1,327 +0,0 @@ -/*** - This file is part of systemd. - - Copyright 2012 Lennart Poettering - Copyright 2012 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 - -#ifdef HAVE_GNUTLS -#include -#include -#endif - -#include "basic/alloc-util.h" -#include "basic/log.h" -#include "basic/macro.h" -#include "microhttpd-util.h" -#include "basic/string-util.h" -#include "basic/strv.h" -#include "basic/util.h" - -void microhttpd_logger(void *arg, const char *fmt, va_list ap) { - char *f; - - f = strjoina("microhttpd: ", fmt); - - DISABLE_WARNING_FORMAT_NONLITERAL; - log_internalv(LOG_INFO, 0, NULL, 0, NULL, f, ap); - REENABLE_WARNING; -} - - -static int mhd_respond_internal(struct MHD_Connection *connection, - enum MHD_RequestTerminationCode code, - char *buffer, - size_t size, - enum MHD_ResponseMemoryMode mode) { - struct MHD_Response *response; - int r; - - assert(connection); - - response = MHD_create_response_from_buffer(size, buffer, mode); - if (!response) - return MHD_NO; - - log_debug("Queing response %u: %s", code, buffer); - MHD_add_response_header(response, "Content-Type", "text/plain"); - r = MHD_queue_response(connection, code, response); - MHD_destroy_response(response); - - return r; -} - -int mhd_respond(struct MHD_Connection *connection, - enum MHD_RequestTerminationCode code, - const char *message) { - - return mhd_respond_internal(connection, code, - (char*) message, strlen(message), - MHD_RESPMEM_PERSISTENT); -} - -int mhd_respond_oom(struct MHD_Connection *connection) { - return mhd_respond(connection, MHD_HTTP_SERVICE_UNAVAILABLE, "Out of memory.\n"); -} - -int mhd_respondf(struct MHD_Connection *connection, - enum MHD_RequestTerminationCode code, - const char *format, ...) { - - char *m; - int r; - va_list ap; - - assert(connection); - assert(format); - - va_start(ap, format); - r = vasprintf(&m, format, ap); - va_end(ap); - - if (r < 0) - return respond_oom(connection); - - return mhd_respond_internal(connection, code, m, r, MHD_RESPMEM_MUST_FREE); -} - -#ifdef HAVE_GNUTLS - -static struct { - const char *const names[4]; - int level; - bool enabled; -} gnutls_log_map[] = { - { {"0"}, LOG_DEBUG }, - { {"1", "audit"}, LOG_WARNING, true}, /* gnutls session audit */ - { {"2", "assert"}, LOG_DEBUG }, /* gnutls assert log */ - { {"3", "hsk", "ext"}, LOG_DEBUG }, /* gnutls handshake log */ - { {"4", "rec"}, LOG_DEBUG }, /* gnutls record log */ - { {"5", "dtls"}, LOG_DEBUG }, /* gnutls DTLS log */ - { {"6", "buf"}, LOG_DEBUG }, - { {"7", "write", "read"}, LOG_DEBUG }, - { {"8"}, LOG_DEBUG }, - { {"9", "enc", "int"}, LOG_DEBUG }, -}; - -static void log_func_gnutls(int level, const char *message) { - assert_se(message); - - if (0 <= level && level < (int) ELEMENTSOF(gnutls_log_map)) { - if (gnutls_log_map[level].enabled) - log_internal(gnutls_log_map[level].level, 0, NULL, 0, NULL, "gnutls %d/%s: %s", level, gnutls_log_map[level].names[1], message); - } else { - log_debug("Received GNUTLS message with unknown level %d.", level); - log_internal(LOG_DEBUG, 0, NULL, 0, NULL, "gnutls: %s", message); - } -} - -static void log_reset_gnutls_level(void) { - int i; - - for (i = ELEMENTSOF(gnutls_log_map) - 1; i >= 0; i--) - if (gnutls_log_map[i].enabled) { - log_debug("Setting gnutls log level to %d", i); - gnutls_global_set_log_level(i); - break; - } -} - -static int log_enable_gnutls_category(const char *cat) { - unsigned i; - - if (streq(cat, "all")) { - for (i = 0; i < ELEMENTSOF(gnutls_log_map); i++) - gnutls_log_map[i].enabled = true; - log_reset_gnutls_level(); - return 0; - } else - for (i = 0; i < ELEMENTSOF(gnutls_log_map); i++) - if (strv_contains((char**)gnutls_log_map[i].names, cat)) { - gnutls_log_map[i].enabled = true; - log_reset_gnutls_level(); - return 0; - } - log_error("No such log category: %s", cat); - return -EINVAL; -} - -int setup_gnutls_logger(char **categories) { - char **cat; - int r; - - gnutls_global_set_log_function(log_func_gnutls); - - if (categories) { - STRV_FOREACH(cat, categories) { - r = log_enable_gnutls_category(*cat); - if (r < 0) - return r; - } - } else - log_reset_gnutls_level(); - - return 0; -} - -static int verify_cert_authorized(gnutls_session_t session) { - unsigned status; - gnutls_certificate_type_t type; - gnutls_datum_t out; - int r; - - r = gnutls_certificate_verify_peers2(session, &status); - if (r < 0) - return log_error_errno(r, "gnutls_certificate_verify_peers2 failed: %m"); - - type = gnutls_certificate_type_get(session); - r = gnutls_certificate_verification_status_print(status, type, &out, 0); - if (r < 0) - return log_error_errno(r, "gnutls_certificate_verification_status_print failed: %m"); - - log_debug("Certificate status: %s", out.data); - gnutls_free(out.data); - - return status == 0 ? 0 : -EPERM; -} - -static int get_client_cert(gnutls_session_t session, gnutls_x509_crt_t *client_cert) { - const gnutls_datum_t *pcert; - unsigned listsize; - gnutls_x509_crt_t cert; - int r; - - assert(session); - assert(client_cert); - - pcert = gnutls_certificate_get_peers(session, &listsize); - if (!pcert || !listsize) { - log_error("Failed to retrieve certificate chain"); - return -EINVAL; - } - - r = gnutls_x509_crt_init(&cert); - if (r < 0) { - log_error("Failed to initialize client certificate"); - return r; - } - - /* Note that by passing values between 0 and listsize here, you - can get access to the CA's certs */ - r = gnutls_x509_crt_import(cert, &pcert[0], GNUTLS_X509_FMT_DER); - if (r < 0) { - log_error("Failed to import client certificate"); - gnutls_x509_crt_deinit(cert); - return r; - } - - *client_cert = cert; - return 0; -} - -static int get_auth_dn(gnutls_x509_crt_t client_cert, char **buf) { - size_t len = 0; - int r; - - assert(buf); - assert(*buf == NULL); - - r = gnutls_x509_crt_get_dn(client_cert, NULL, &len); - if (r != GNUTLS_E_SHORT_MEMORY_BUFFER) { - log_error("gnutls_x509_crt_get_dn failed"); - return r; - } - - *buf = malloc(len); - if (!*buf) - return log_oom(); - - gnutls_x509_crt_get_dn(client_cert, *buf, &len); - return 0; -} - -static inline void gnutls_x509_crt_deinitp(gnutls_x509_crt_t *p) { - gnutls_x509_crt_deinit(*p); -} - -int check_permissions(struct MHD_Connection *connection, int *code, char **hostname) { - const union MHD_ConnectionInfo *ci; - gnutls_session_t session; - _cleanup_(gnutls_x509_crt_deinitp) gnutls_x509_crt_t client_cert = NULL; - _cleanup_free_ char *buf = NULL; - int r; - - assert(connection); - assert(code); - - *code = 0; - - ci = MHD_get_connection_info(connection, - MHD_CONNECTION_INFO_GNUTLS_SESSION); - if (!ci) { - log_error("MHD_get_connection_info failed: session is unencrypted"); - *code = mhd_respond(connection, MHD_HTTP_FORBIDDEN, - "Encrypted connection is required"); - return -EPERM; - } - session = ci->tls_session; - assert(session); - - r = get_client_cert(session, &client_cert); - if (r < 0) { - *code = mhd_respond(connection, MHD_HTTP_UNAUTHORIZED, - "Authorization through certificate is required"); - return -EPERM; - } - - r = get_auth_dn(client_cert, &buf); - if (r < 0) { - *code = mhd_respond(connection, MHD_HTTP_UNAUTHORIZED, - "Failed to determine distinguished name from certificate"); - return -EPERM; - } - - log_debug("Connection from %s", buf); - - if (hostname) { - *hostname = buf; - buf = NULL; - } - - r = verify_cert_authorized(session); - if (r < 0) { - log_warning("Client is not authorized"); - *code = mhd_respond(connection, MHD_HTTP_UNAUTHORIZED, - "Client certificate not signed by recognized authority"); - } - return r; -} - -#else -int check_permissions(struct MHD_Connection *connection, int *code, char **hostname) { - return -EPERM; -} - -int setup_gnutls_logger(char **categories) { - if (categories) - log_notice("Ignoring specified gnutls logging categories — gnutls not available."); - return 0; -} -#endif diff --git a/src/grp-journal-remote/microhttpd-util.h b/src/grp-journal-remote/microhttpd-util.h deleted file mode 100644 index 178e78f892..0000000000 --- a/src/grp-journal-remote/microhttpd-util.h +++ /dev/null @@ -1,60 +0,0 @@ -#pragma once - -/*** - This file is part of systemd. - - Copyright 2012 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 "basic/macro.h" - -/* Compatiblity with libmicrohttpd < 0.9.38 */ -#ifndef MHD_HTTP_NOT_ACCEPTABLE -#define MHD_HTTP_NOT_ACCEPTABLE MHD_HTTP_METHOD_NOT_ACCEPTABLE -#endif - -#if MHD_VERSION < 0x00094203 -#define MHD_create_response_from_fd_at_offset64 MHD_create_response_from_fd_at_offset -#endif - -void microhttpd_logger(void *arg, const char *fmt, va_list ap) _printf_(2, 0); - -/* respond_oom() must be usable with return, hence this form. */ -#define respond_oom(connection) log_oom(), mhd_respond_oom(connection) - -int mhd_respondf(struct MHD_Connection *connection, - unsigned code, - const char *format, ...) _printf_(3,4); - -int mhd_respond(struct MHD_Connection *connection, - unsigned code, - const char *message); - -int mhd_respond_oom(struct MHD_Connection *connection); - -int check_permissions(struct MHD_Connection *connection, int *code, char **hostname); - -/* Set gnutls internal logging function to a callback which uses our - * own logging framework. - * - * gnutls categories are additionally filtered by our internal log - * level, so it should be set fairly high to capture all potentially - * interesting events without overwhelming detail. - */ -int setup_gnutls_logger(char **categories); diff --git a/src/grp-journal-remote/systemd-journal-gatewayd/Makefile b/src/grp-journal-remote/systemd-journal-gatewayd/Makefile deleted file mode 100644 index 05df34a71d..0000000000 --- a/src/grp-journal-remote/systemd-journal-gatewayd/Makefile +++ /dev/null @@ -1,68 +0,0 @@ -# -*- 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),) -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 # HAVE_GNUTLS - -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 # HAVE_MICROHTTPD - -EXTRA_DIST += \ - units/systemd-journal-gatewayd.service.in - -include $(topsrcdir)/build-aux/Makefile.tail.mk diff --git a/src/grp-journal-remote/systemd-journal-gatewayd/journal-gatewayd.c b/src/grp-journal-remote/systemd-journal-gatewayd/journal-gatewayd.c deleted file mode 100644 index 2e21f9ecb7..0000000000 --- a/src/grp-journal-remote/systemd-journal-gatewayd/journal-gatewayd.c +++ /dev/null @@ -1,1077 +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 -#ifdef HAVE_GNUTLS -#include -#endif -#include -#include -#include -#include - -#include -#include -#include - -#include "basic/alloc-util.h" -#include "shared/bus-util.h" -#include "basic/fd-util.h" -#include "basic/fileio.h" -#include "basic/hostname-util.h" -#include "basic/log.h" -#include "shared/logs-show.h" -#include "microhttpd-util.h" -#include "basic/parse-util.h" -#include "basic/sigbus.h" -#include "basic/util.h" - -#define JOURNAL_WAIT_TIMEOUT (10*USEC_PER_SEC) - -static char *arg_key_pem = NULL; -static char *arg_cert_pem = NULL; -static char *arg_trust_pem = NULL; - -typedef struct RequestMeta { - sd_journal *journal; - - OutputMode mode; - - char *cursor; - int64_t n_skip; - uint64_t n_entries; - bool n_entries_set; - - FILE *tmp; - uint64_t delta, size; - - int argument_parse_error; - - bool follow; - bool discrete; - - uint64_t n_fields; - bool n_fields_set; -} RequestMeta; - -static const char* const mime_types[_OUTPUT_MODE_MAX] = { - [OUTPUT_SHORT] = "text/plain", - [OUTPUT_JSON] = "application/json", - [OUTPUT_JSON_SSE] = "text/event-stream", - [OUTPUT_EXPORT] = "application/vnd.fdo.journal", -}; - -static RequestMeta *request_meta(void **connection_cls) { - RequestMeta *m; - - assert(connection_cls); - if (*connection_cls) - return *connection_cls; - - m = new0(RequestMeta, 1); - if (!m) - return NULL; - - *connection_cls = m; - return m; -} - -static void request_meta_free( - void *cls, - struct MHD_Connection *connection, - void **connection_cls, - enum MHD_RequestTerminationCode toe) { - - RequestMeta *m = *connection_cls; - - if (!m) - return; - - sd_journal_close(m->journal); - - safe_fclose(m->tmp); - - free(m->cursor); - free(m); -} - -static int open_journal(RequestMeta *m) { - assert(m); - - if (m->journal) - return 0; - - return sd_journal_open(&m->journal, SD_JOURNAL_LOCAL_ONLY|SD_JOURNAL_SYSTEM); -} - -static int request_meta_ensure_tmp(RequestMeta *m) { - assert(m); - - if (m->tmp) - rewind(m->tmp); - else { - int fd; - - fd = open_tmpfile_unlinkable("/tmp", O_RDWR|O_CLOEXEC); - if (fd < 0) - return fd; - - m->tmp = fdopen(fd, "w+"); - if (!m->tmp) { - safe_close(fd); - return -errno; - } - } - - return 0; -} - -static ssize_t request_reader_entries( - void *cls, - uint64_t pos, - char *buf, - size_t max) { - - RequestMeta *m = cls; - int r; - size_t n, k; - - assert(m); - assert(buf); - assert(max > 0); - assert(pos >= m->delta); - - pos -= m->delta; - - while (pos >= m->size) { - off_t sz; - - /* End of this entry, so let's serialize the next - * one */ - - if (m->n_entries_set && - m->n_entries <= 0) - return MHD_CONTENT_READER_END_OF_STREAM; - - if (m->n_skip < 0) - r = sd_journal_previous_skip(m->journal, (uint64_t) -m->n_skip + 1); - else if (m->n_skip > 0) - r = sd_journal_next_skip(m->journal, (uint64_t) m->n_skip + 1); - else - r = sd_journal_next(m->journal); - - if (r < 0) { - log_error_errno(r, "Failed to advance journal pointer: %m"); - return MHD_CONTENT_READER_END_WITH_ERROR; - } else if (r == 0) { - - if (m->follow) { - r = sd_journal_wait(m->journal, (uint64_t) JOURNAL_WAIT_TIMEOUT); - if (r < 0) { - log_error_errno(r, "Couldn't wait for journal event: %m"); - return MHD_CONTENT_READER_END_WITH_ERROR; - } - if (r == SD_JOURNAL_NOP) - break; - - continue; - } - - return MHD_CONTENT_READER_END_OF_STREAM; - } - - if (m->discrete) { - assert(m->cursor); - - r = sd_journal_test_cursor(m->journal, m->cursor); - if (r < 0) { - log_error_errno(r, "Failed to test cursor: %m"); - return MHD_CONTENT_READER_END_WITH_ERROR; - } - - if (r == 0) - return MHD_CONTENT_READER_END_OF_STREAM; - } - - pos -= m->size; - m->delta += m->size; - - if (m->n_entries_set) - m->n_entries -= 1; - - m->n_skip = 0; - - r = request_meta_ensure_tmp(m); - if (r < 0) { - log_error_errno(r, "Failed to create temporary file: %m"); - return MHD_CONTENT_READER_END_WITH_ERROR; - } - - r = output_journal(m->tmp, m->journal, m->mode, 0, OUTPUT_FULL_WIDTH, NULL); - if (r < 0) { - log_error_errno(r, "Failed to serialize item: %m"); - return MHD_CONTENT_READER_END_WITH_ERROR; - } - - sz = ftello(m->tmp); - if (sz == (off_t) -1) { - log_error_errno(errno, "Failed to retrieve file position: %m"); - return MHD_CONTENT_READER_END_WITH_ERROR; - } - - m->size = (uint64_t) sz; - } - - if (fseeko(m->tmp, pos, SEEK_SET) < 0) { - log_error_errno(errno, "Failed to seek to position: %m"); - return MHD_CONTENT_READER_END_WITH_ERROR; - } - - n = m->size - pos; - if (n < 1) - return 0; - if (n > max) - n = max; - - errno = 0; - k = fread(buf, 1, n, m->tmp); - if (k != n) { - log_error("Failed to read from file: %s", errno ? strerror(errno) : "Premature EOF"); - return MHD_CONTENT_READER_END_WITH_ERROR; - } - - return (ssize_t) k; -} - -static int request_parse_accept( - RequestMeta *m, - struct MHD_Connection *connection) { - - const char *header; - - assert(m); - assert(connection); - - header = MHD_lookup_connection_value(connection, MHD_HEADER_KIND, "Accept"); - if (!header) - return 0; - - if (streq(header, mime_types[OUTPUT_JSON])) - m->mode = OUTPUT_JSON; - else if (streq(header, mime_types[OUTPUT_JSON_SSE])) - m->mode = OUTPUT_JSON_SSE; - else if (streq(header, mime_types[OUTPUT_EXPORT])) - m->mode = OUTPUT_EXPORT; - else - m->mode = OUTPUT_SHORT; - - return 0; -} - -static int request_parse_range( - RequestMeta *m, - struct MHD_Connection *connection) { - - const char *range, *colon, *colon2; - int r; - - assert(m); - assert(connection); - - range = MHD_lookup_connection_value(connection, MHD_HEADER_KIND, "Range"); - if (!range) - return 0; - - if (!startswith(range, "entries=")) - return 0; - - range += 8; - range += strspn(range, WHITESPACE); - - colon = strchr(range, ':'); - if (!colon) - m->cursor = strdup(range); - else { - const char *p; - - colon2 = strchr(colon + 1, ':'); - if (colon2) { - _cleanup_free_ char *t; - - t = strndup(colon + 1, colon2 - colon - 1); - if (!t) - return -ENOMEM; - - r = safe_atoi64(t, &m->n_skip); - if (r < 0) - return r; - } - - p = (colon2 ? colon2 : colon) + 1; - if (*p) { - r = safe_atou64(p, &m->n_entries); - if (r < 0) - return r; - - if (m->n_entries <= 0) - return -EINVAL; - - m->n_entries_set = true; - } - - m->cursor = strndup(range, colon - range); - } - - if (!m->cursor) - return -ENOMEM; - - m->cursor[strcspn(m->cursor, WHITESPACE)] = 0; - if (isempty(m->cursor)) - m->cursor = mfree(m->cursor); - - return 0; -} - -static int request_parse_arguments_iterator( - void *cls, - enum MHD_ValueKind kind, - const char *key, - const char *value) { - - RequestMeta *m = cls; - _cleanup_free_ char *p = NULL; - int r; - - assert(m); - - if (isempty(key)) { - m->argument_parse_error = -EINVAL; - return MHD_NO; - } - - if (streq(key, "follow")) { - if (isempty(value)) { - m->follow = true; - return MHD_YES; - } - - r = parse_boolean(value); - if (r < 0) { - m->argument_parse_error = r; - return MHD_NO; - } - - m->follow = r; - return MHD_YES; - } - - if (streq(key, "discrete")) { - if (isempty(value)) { - m->discrete = true; - return MHD_YES; - } - - r = parse_boolean(value); - if (r < 0) { - m->argument_parse_error = r; - return MHD_NO; - } - - m->discrete = r; - return MHD_YES; - } - - if (streq(key, "boot")) { - if (isempty(value)) - r = true; - else { - r = parse_boolean(value); - if (r < 0) { - m->argument_parse_error = r; - return MHD_NO; - } - } - - if (r) { - char match[9 + 32 + 1] = "_BOOT_ID="; - sd_id128_t bid; - - r = sd_id128_get_boot(&bid); - if (r < 0) { - log_error_errno(r, "Failed to get boot ID: %m"); - return MHD_NO; - } - - sd_id128_to_string(bid, match + 9); - r = sd_journal_add_match(m->journal, match, sizeof(match)-1); - if (r < 0) { - m->argument_parse_error = r; - return MHD_NO; - } - } - - return MHD_YES; - } - - p = strjoin(key, "=", strempty(value), NULL); - if (!p) { - m->argument_parse_error = log_oom(); - return MHD_NO; - } - - r = sd_journal_add_match(m->journal, p, 0); - if (r < 0) { - m->argument_parse_error = r; - return MHD_NO; - } - - return MHD_YES; -} - -static int request_parse_arguments( - RequestMeta *m, - struct MHD_Connection *connection) { - - assert(m); - assert(connection); - - m->argument_parse_error = 0; - MHD_get_connection_values(connection, MHD_GET_ARGUMENT_KIND, request_parse_arguments_iterator, m); - - return m->argument_parse_error; -} - -static int request_handler_entries( - struct MHD_Connection *connection, - void *connection_cls) { - - struct MHD_Response *response; - RequestMeta *m = connection_cls; - int r; - - assert(connection); - assert(m); - - r = open_journal(m); - if (r < 0) - return mhd_respondf(connection, MHD_HTTP_INTERNAL_SERVER_ERROR, "Failed to open journal: %s\n", strerror(-r)); - - if (request_parse_accept(m, connection) < 0) - return mhd_respond(connection, MHD_HTTP_BAD_REQUEST, "Failed to parse Accept header.\n"); - - if (request_parse_range(m, connection) < 0) - return mhd_respond(connection, MHD_HTTP_BAD_REQUEST, "Failed to parse Range header.\n"); - - if (request_parse_arguments(m, connection) < 0) - return mhd_respond(connection, MHD_HTTP_BAD_REQUEST, "Failed to parse URL arguments.\n"); - - if (m->discrete) { - if (!m->cursor) - return mhd_respond(connection, MHD_HTTP_BAD_REQUEST, "Discrete seeks require a cursor specification.\n"); - - m->n_entries = 1; - m->n_entries_set = true; - } - - if (m->cursor) - r = sd_journal_seek_cursor(m->journal, m->cursor); - else if (m->n_skip >= 0) - r = sd_journal_seek_head(m->journal); - else if (m->n_skip < 0) - r = sd_journal_seek_tail(m->journal); - if (r < 0) - return mhd_respond(connection, MHD_HTTP_BAD_REQUEST, "Failed to seek in journal.\n"); - - response = MHD_create_response_from_callback(MHD_SIZE_UNKNOWN, 4*1024, request_reader_entries, m, NULL); - if (!response) - return respond_oom(connection); - - MHD_add_response_header(response, "Content-Type", mime_types[m->mode]); - - r = MHD_queue_response(connection, MHD_HTTP_OK, response); - MHD_destroy_response(response); - - return r; -} - -static int output_field(FILE *f, OutputMode m, const char *d, size_t l) { - const char *eq; - size_t j; - - eq = memchr(d, '=', l); - if (!eq) - return -EINVAL; - - j = l - (eq - d + 1); - - if (m == OUTPUT_JSON) { - fprintf(f, "{ \"%.*s\" : ", (int) (eq - d), d); - json_escape(f, eq+1, j, OUTPUT_FULL_WIDTH); - fputs(" }\n", f); - } else { - fwrite(eq+1, 1, j, f); - fputc('\n', f); - } - - return 0; -} - -static ssize_t request_reader_fields( - void *cls, - uint64_t pos, - char *buf, - size_t max) { - - RequestMeta *m = cls; - int r; - size_t n, k; - - assert(m); - assert(buf); - assert(max > 0); - assert(pos >= m->delta); - - pos -= m->delta; - - while (pos >= m->size) { - off_t sz; - const void *d; - size_t l; - - /* End of this field, so let's serialize the next - * one */ - - if (m->n_fields_set && - m->n_fields <= 0) - return MHD_CONTENT_READER_END_OF_STREAM; - - r = sd_journal_enumerate_unique(m->journal, &d, &l); - if (r < 0) { - log_error_errno(r, "Failed to advance field index: %m"); - return MHD_CONTENT_READER_END_WITH_ERROR; - } else if (r == 0) - return MHD_CONTENT_READER_END_OF_STREAM; - - pos -= m->size; - m->delta += m->size; - - if (m->n_fields_set) - m->n_fields -= 1; - - r = request_meta_ensure_tmp(m); - if (r < 0) { - log_error_errno(r, "Failed to create temporary file: %m"); - return MHD_CONTENT_READER_END_WITH_ERROR; - } - - r = output_field(m->tmp, m->mode, d, l); - if (r < 0) { - log_error_errno(r, "Failed to serialize item: %m"); - return MHD_CONTENT_READER_END_WITH_ERROR; - } - - sz = ftello(m->tmp); - if (sz == (off_t) -1) { - log_error_errno(errno, "Failed to retrieve file position: %m"); - return MHD_CONTENT_READER_END_WITH_ERROR; - } - - m->size = (uint64_t) sz; - } - - if (fseeko(m->tmp, pos, SEEK_SET) < 0) { - log_error_errno(errno, "Failed to seek to position: %m"); - return MHD_CONTENT_READER_END_WITH_ERROR; - } - - n = m->size - pos; - if (n > max) - n = max; - - errno = 0; - k = fread(buf, 1, n, m->tmp); - if (k != n) { - log_error("Failed to read from file: %s", errno ? strerror(errno) : "Premature EOF"); - return MHD_CONTENT_READER_END_WITH_ERROR; - } - - return (ssize_t) k; -} - -static int request_handler_fields( - struct MHD_Connection *connection, - const char *field, - void *connection_cls) { - - struct MHD_Response *response; - RequestMeta *m = connection_cls; - int r; - - assert(connection); - assert(m); - - r = open_journal(m); - if (r < 0) - return mhd_respondf(connection, MHD_HTTP_INTERNAL_SERVER_ERROR, "Failed to open journal: %s\n", strerror(-r)); - - if (request_parse_accept(m, connection) < 0) - return mhd_respond(connection, MHD_HTTP_BAD_REQUEST, "Failed to parse Accept header.\n"); - - r = sd_journal_query_unique(m->journal, field); - if (r < 0) - return mhd_respond(connection, MHD_HTTP_BAD_REQUEST, "Failed to query unique fields.\n"); - - response = MHD_create_response_from_callback(MHD_SIZE_UNKNOWN, 4*1024, request_reader_fields, m, NULL); - if (!response) - return respond_oom(connection); - - MHD_add_response_header(response, "Content-Type", mime_types[m->mode == OUTPUT_JSON ? OUTPUT_JSON : OUTPUT_SHORT]); - - r = MHD_queue_response(connection, MHD_HTTP_OK, response); - MHD_destroy_response(response); - - return r; -} - -static int request_handler_redirect( - struct MHD_Connection *connection, - const char *target) { - - char *page; - struct MHD_Response *response; - int ret; - - assert(connection); - assert(target); - - if (asprintf(&page, "Please continue to the journal browser.", target) < 0) - return respond_oom(connection); - - response = MHD_create_response_from_buffer(strlen(page), page, MHD_RESPMEM_MUST_FREE); - if (!response) { - free(page); - return respond_oom(connection); - } - - MHD_add_response_header(response, "Content-Type", "text/html"); - MHD_add_response_header(response, "Location", target); - - ret = MHD_queue_response(connection, MHD_HTTP_MOVED_PERMANENTLY, response); - MHD_destroy_response(response); - - return ret; -} - -static int request_handler_file( - struct MHD_Connection *connection, - const char *path, - const char *mime_type) { - - struct MHD_Response *response; - int ret; - _cleanup_close_ int fd = -1; - struct stat st; - - assert(connection); - assert(path); - assert(mime_type); - - fd = open(path, O_RDONLY|O_CLOEXEC); - if (fd < 0) - return mhd_respondf(connection, MHD_HTTP_NOT_FOUND, "Failed to open file %s: %m\n", path); - - if (fstat(fd, &st) < 0) - return mhd_respondf(connection, MHD_HTTP_INTERNAL_SERVER_ERROR, "Failed to stat file: %m\n"); - - response = MHD_create_response_from_fd_at_offset64(st.st_size, fd, 0); - if (!response) - return respond_oom(connection); - - fd = -1; - - MHD_add_response_header(response, "Content-Type", mime_type); - - ret = MHD_queue_response(connection, MHD_HTTP_OK, response); - MHD_destroy_response(response); - - return ret; -} - -static int get_virtualization(char **v) { - _cleanup_(sd_bus_unrefp) sd_bus *bus = NULL; - char *b = NULL; - int r; - - r = sd_bus_default_system(&bus); - if (r < 0) - return r; - - r = sd_bus_get_property_string( - bus, - "org.freedesktop.systemd1", - "/org/freedesktop/systemd1", - "org.freedesktop.systemd1.Manager", - "Virtualization", - NULL, - &b); - if (r < 0) - return r; - - if (isempty(b)) { - free(b); - *v = NULL; - return 0; - } - - *v = b; - return 1; -} - -static int request_handler_machine( - struct MHD_Connection *connection, - void *connection_cls) { - - struct MHD_Response *response; - RequestMeta *m = connection_cls; - int r; - _cleanup_free_ char* hostname = NULL, *os_name = NULL; - uint64_t cutoff_from = 0, cutoff_to = 0, usage = 0; - char *json; - sd_id128_t mid, bid; - _cleanup_free_ char *v = NULL; - - assert(connection); - assert(m); - - r = open_journal(m); - if (r < 0) - return mhd_respondf(connection, MHD_HTTP_INTERNAL_SERVER_ERROR, "Failed to open journal: %s\n", strerror(-r)); - - r = sd_id128_get_machine(&mid); - if (r < 0) - return mhd_respondf(connection, MHD_HTTP_INTERNAL_SERVER_ERROR, "Failed to determine machine ID: %s\n", strerror(-r)); - - r = sd_id128_get_boot(&bid); - if (r < 0) - return mhd_respondf(connection, MHD_HTTP_INTERNAL_SERVER_ERROR, "Failed to determine boot ID: %s\n", strerror(-r)); - - hostname = gethostname_malloc(); - if (!hostname) - return respond_oom(connection); - - r = sd_journal_get_usage(m->journal, &usage); - if (r < 0) - return mhd_respondf(connection, MHD_HTTP_INTERNAL_SERVER_ERROR, "Failed to determine disk usage: %s\n", strerror(-r)); - - r = sd_journal_get_cutoff_realtime_usec(m->journal, &cutoff_from, &cutoff_to); - if (r < 0) - return mhd_respondf(connection, MHD_HTTP_INTERNAL_SERVER_ERROR, "Failed to determine disk usage: %s\n", strerror(-r)); - - if (parse_env_file("/etc/os-release", NEWLINE, "PRETTY_NAME", &os_name, NULL) == -ENOENT) - (void) parse_env_file("/usr/lib/os-release", NEWLINE, "PRETTY_NAME", &os_name, NULL); - - get_virtualization(&v); - - r = asprintf(&json, - "{ \"machine_id\" : \"" SD_ID128_FORMAT_STR "\"," - "\"boot_id\" : \"" SD_ID128_FORMAT_STR "\"," - "\"hostname\" : \"%s\"," - "\"os_pretty_name\" : \"%s\"," - "\"virtualization\" : \"%s\"," - "\"usage\" : \"%"PRIu64"\"," - "\"cutoff_from_realtime\" : \"%"PRIu64"\"," - "\"cutoff_to_realtime\" : \"%"PRIu64"\" }\n", - SD_ID128_FORMAT_VAL(mid), - SD_ID128_FORMAT_VAL(bid), - hostname_cleanup(hostname), - os_name ? os_name : "GNU/Linux", - v ? v : "bare", - usage, - cutoff_from, - cutoff_to); - - if (r < 0) - return respond_oom(connection); - - response = MHD_create_response_from_buffer(strlen(json), json, MHD_RESPMEM_MUST_FREE); - if (!response) { - free(json); - return respond_oom(connection); - } - - MHD_add_response_header(response, "Content-Type", "application/json"); - r = MHD_queue_response(connection, MHD_HTTP_OK, response); - MHD_destroy_response(response); - - return r; -} - -static int request_handler( - void *cls, - struct MHD_Connection *connection, - const char *url, - const char *method, - const char *version, - const char *upload_data, - size_t *upload_data_size, - void **connection_cls) { - int r, code; - - assert(connection); - assert(connection_cls); - assert(url); - assert(method); - - if (!streq(method, "GET")) - return mhd_respond(connection, MHD_HTTP_NOT_ACCEPTABLE, - "Unsupported method.\n"); - - - if (!*connection_cls) { - if (!request_meta(connection_cls)) - return respond_oom(connection); - return MHD_YES; - } - - if (arg_trust_pem) { - r = check_permissions(connection, &code, NULL); - if (r < 0) - return code; - } - - if (streq(url, "/")) - return request_handler_redirect(connection, "/browse"); - - if (streq(url, "/entries")) - return request_handler_entries(connection, *connection_cls); - - if (startswith(url, "/fields/")) - return request_handler_fields(connection, url + 8, *connection_cls); - - if (streq(url, "/browse")) - return request_handler_file(connection, DOCUMENT_ROOT "/browse.html", "text/html"); - - if (streq(url, "/machine")) - return request_handler_machine(connection, *connection_cls); - - return mhd_respond(connection, MHD_HTTP_NOT_FOUND, "Not found.\n"); -} - -static void help(void) { - printf("%s [OPTIONS...] ...\n\n" - "HTTP server for journal events.\n\n" - " -h --help Show this help\n" - " --version Show package version\n" - " --cert=CERT.PEM Server certificate in PEM format\n" - " --key=KEY.PEM Server key in PEM format\n" - " --trust=CERT.PEM Certificat authority certificate in PEM format\n", - program_invocation_short_name); -} - -static int parse_argv(int argc, char *argv[]) { - enum { - ARG_VERSION = 0x100, - ARG_KEY, - ARG_CERT, - ARG_TRUST, - }; - - int r, c; - - static const struct option options[] = { - { "help", no_argument, NULL, 'h' }, - { "version", no_argument, NULL, ARG_VERSION }, - { "key", required_argument, NULL, ARG_KEY }, - { "cert", required_argument, NULL, ARG_CERT }, - { "trust", required_argument, NULL, ARG_TRUST }, - {} - }; - - 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_KEY: - if (arg_key_pem) { - log_error("Key file specified twice"); - return -EINVAL; - } - r = read_full_file(optarg, &arg_key_pem, NULL); - if (r < 0) - return log_error_errno(r, "Failed to read key file: %m"); - assert(arg_key_pem); - break; - - case ARG_CERT: - if (arg_cert_pem) { - log_error("Certificate file specified twice"); - return -EINVAL; - } - r = read_full_file(optarg, &arg_cert_pem, NULL); - if (r < 0) - return log_error_errno(r, "Failed to read certificate file: %m"); - assert(arg_cert_pem); - break; - - case ARG_TRUST: -#ifdef HAVE_GNUTLS - if (arg_trust_pem) { - log_error("CA certificate file specified twice"); - return -EINVAL; - } - r = read_full_file(optarg, &arg_trust_pem, NULL); - if (r < 0) - return log_error_errno(r, "Failed to read CA certificate file: %m"); - assert(arg_trust_pem); - break; -#else - log_error("Option --trust is not available."); -#endif - - case '?': - return -EINVAL; - - default: - assert_not_reached("Unhandled option"); - } - - if (optind < argc) { - log_error("This program does not take arguments."); - return -EINVAL; - } - - if (!!arg_key_pem != !!arg_cert_pem) { - log_error("Certificate and key files must be specified together"); - return -EINVAL; - } - - if (arg_trust_pem && !arg_key_pem) { - log_error("CA certificate can only be used with certificate file"); - return -EINVAL; - } - - return 1; -} - -int main(int argc, char *argv[]) { - struct MHD_Daemon *d = NULL; - int r, n; - - log_set_target(LOG_TARGET_AUTO); - log_parse_environment(); - log_open(); - - r = parse_argv(argc, argv); - if (r < 0) - return EXIT_FAILURE; - if (r == 0) - return EXIT_SUCCESS; - - sigbus_install(); - - r = setup_gnutls_logger(NULL); - if (r < 0) - return EXIT_FAILURE; - - n = sd_listen_fds(1); - if (n < 0) { - log_error_errno(n, "Failed to determine passed sockets: %m"); - goto finish; - } else if (n > 1) { - log_error("Can't listen on more than one socket."); - goto finish; - } else { - struct MHD_OptionItem opts[] = { - { MHD_OPTION_NOTIFY_COMPLETED, - (intptr_t) request_meta_free, NULL }, - { MHD_OPTION_EXTERNAL_LOGGER, - (intptr_t) microhttpd_logger, NULL }, - { MHD_OPTION_END, 0, NULL }, - { MHD_OPTION_END, 0, NULL }, - { MHD_OPTION_END, 0, NULL }, - { MHD_OPTION_END, 0, NULL }, - { MHD_OPTION_END, 0, NULL }}; - int opts_pos = 2; - - /* We force MHD_USE_PIPE_FOR_SHUTDOWN here, in order - * to make sure libmicrohttpd doesn't use shutdown() - * on our listening socket, which would break socket - * re-activation. See - * - * https://lists.gnu.org/archive/html/libmicrohttpd/2015-09/msg00014.html - * https://github.com/systemd/systemd/pull/1286 - */ - - int flags = - MHD_USE_DEBUG | - MHD_USE_DUAL_STACK | - MHD_USE_PIPE_FOR_SHUTDOWN | - MHD_USE_POLL | - MHD_USE_THREAD_PER_CONNECTION; - - if (n > 0) - opts[opts_pos++] = (struct MHD_OptionItem) - {MHD_OPTION_LISTEN_SOCKET, SD_LISTEN_FDS_START}; - if (arg_key_pem) { - assert(arg_cert_pem); - opts[opts_pos++] = (struct MHD_OptionItem) - {MHD_OPTION_HTTPS_MEM_KEY, 0, arg_key_pem}; - opts[opts_pos++] = (struct MHD_OptionItem) - {MHD_OPTION_HTTPS_MEM_CERT, 0, arg_cert_pem}; - flags |= MHD_USE_SSL; - } - if (arg_trust_pem) { - assert(flags & MHD_USE_SSL); - opts[opts_pos++] = (struct MHD_OptionItem) - {MHD_OPTION_HTTPS_MEM_TRUST, 0, arg_trust_pem}; - } - - d = MHD_start_daemon(flags, 19531, - NULL, NULL, - request_handler, NULL, - MHD_OPTION_ARRAY, opts, - MHD_OPTION_END); - } - - if (!d) { - log_error("Failed to start daemon!"); - goto finish; - } - - pause(); - - r = EXIT_SUCCESS; - -finish: - if (d) - MHD_stop_daemon(d); - - return r; -} diff --git a/src/grp-journal-remote/systemd-journal-remote/Makefile b/src/grp-journal-remote/systemd-journal-remote/Makefile deleted file mode 100644 index 05af76afc2..0000000000 --- a/src/grp-journal-remote/systemd-journal-remote/Makefile +++ /dev/null @@ -1,85 +0,0 @@ -# -*- 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 # ENABLE_TMPFILES - -ifneq ($(HAVE_GNUTLS),) -systemd_journal_remote_LDADD += \ - $(GNUTLS_LIBS) -endif # HAVE_GNUTLS - -# 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 # HAVE_MICROHTTPD - -include $(topsrcdir)/build-aux/Makefile.tail.mk diff --git a/src/grp-journal-remote/systemd-journal-remote/journal-remote-parse.c b/src/grp-journal-remote/systemd-journal-remote/journal-remote-parse.c deleted file mode 100644 index ec5358fb97..0000000000 --- a/src/grp-journal-remote/systemd-journal-remote/journal-remote-parse.c +++ /dev/null @@ -1,506 +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 "basic/alloc-util.h" -#include "basic/fd-util.h" -#include "journal-remote-parse.h" -#include "journald-native.h" -#include "basic/parse-util.h" -#include "basic/string-util.h" - -#define LINE_CHUNK 8*1024u - -void source_free(RemoteSource *source) { - if (!source) - return; - - if (source->fd >= 0 && !source->passive_fd) { - log_debug("Closing fd:%d (%s)", source->fd, source->name); - safe_close(source->fd); - } - - free(source->name); - free(source->buf); - iovw_free_contents(&source->iovw); - - log_debug("Writer ref count %i", source->writer->n_ref); - writer_unref(source->writer); - - sd_event_source_unref(source->event); - sd_event_source_unref(source->buffer_event); - - free(source); -} - -/** - * Initialize zero-filled source with given values. On success, takes - * ownerhship of fd and writer, otherwise does not touch them. - */ -RemoteSource* source_new(int fd, bool passive_fd, char *name, Writer *writer) { - - RemoteSource *source; - - log_debug("Creating source for %sfd:%d (%s)", - passive_fd ? "passive " : "", fd, name); - - assert(fd >= 0); - - source = new0(RemoteSource, 1); - if (!source) - return NULL; - - source->fd = fd; - source->passive_fd = passive_fd; - source->name = name; - source->writer = writer; - - return source; -} - -static char* realloc_buffer(RemoteSource *source, size_t size) { - char *b, *old = source->buf; - - b = GREEDY_REALLOC(source->buf, source->size, size); - if (!b) - return NULL; - - iovw_rebase(&source->iovw, old, source->buf); - - return b; -} - -static int get_line(RemoteSource *source, char **line, size_t *size) { - ssize_t n; - char *c = NULL; - - assert(source); - assert(source->state == STATE_LINE); - assert(source->offset <= source->filled); - assert(source->filled <= source->size); - assert(source->buf == NULL || source->size > 0); - assert(source->fd >= 0); - - for (;;) { - if (source->buf) { - size_t start = MAX(source->scanned, source->offset); - - c = memchr(source->buf + start, '\n', - source->filled - start); - if (c != NULL) - break; - } - - source->scanned = source->filled; - if (source->scanned >= DATA_SIZE_MAX) { - log_error("Entry is bigger than %u bytes.", DATA_SIZE_MAX); - return -E2BIG; - } - - if (source->passive_fd) - /* we have to wait for some data to come to us */ - return -EAGAIN; - - /* We know that source->filled is at most DATA_SIZE_MAX, so if - we reallocate it, we'll increase the size at least a bit. */ - assert_cc(DATA_SIZE_MAX < ENTRY_SIZE_MAX); - if (source->size - source->filled < LINE_CHUNK && - !realloc_buffer(source, MIN(source->filled + LINE_CHUNK, ENTRY_SIZE_MAX))) - return log_oom(); - - assert(source->buf); - assert(source->size - source->filled >= LINE_CHUNK || - source->size == ENTRY_SIZE_MAX); - - n = read(source->fd, - source->buf + source->filled, - source->size - source->filled); - if (n < 0) { - if (errno != EAGAIN) - log_error_errno(errno, "read(%d, ..., %zu): %m", - source->fd, - source->size - source->filled); - return -errno; - } else if (n == 0) - return 0; - - source->filled += n; - } - - *line = source->buf + source->offset; - *size = c + 1 - source->buf - source->offset; - source->offset += *size; - - return 1; -} - -int push_data(RemoteSource *source, const char *data, size_t size) { - assert(source); - assert(source->state != STATE_EOF); - - if (!realloc_buffer(source, source->filled + size)) { - log_error("Failed to store received data of size %zu " - "(in addition to existing %zu bytes with %zu filled): %s", - size, source->size, source->filled, strerror(ENOMEM)); - return -ENOMEM; - } - - memcpy(source->buf + source->filled, data, size); - source->filled += size; - - return 0; -} - -static int fill_fixed_size(RemoteSource *source, void **data, size_t size) { - - assert(source); - assert(source->state == STATE_DATA_START || - source->state == STATE_DATA || - source->state == STATE_DATA_FINISH); - assert(size <= DATA_SIZE_MAX); - assert(source->offset <= source->filled); - assert(source->filled <= source->size); - assert(source->buf != NULL || source->size == 0); - assert(source->buf == NULL || source->size > 0); - assert(source->fd >= 0); - assert(data); - - while (source->filled - source->offset < size) { - int n; - - if (source->passive_fd) - /* we have to wait for some data to come to us */ - return -EAGAIN; - - if (!realloc_buffer(source, source->offset + size)) - return log_oom(); - - n = read(source->fd, source->buf + source->filled, - source->size - source->filled); - if (n < 0) { - if (errno != EAGAIN) - log_error_errno(errno, "read(%d, ..., %zu): %m", source->fd, - source->size - source->filled); - return -errno; - } else if (n == 0) - return 0; - - source->filled += n; - } - - *data = source->buf + source->offset; - source->offset += size; - - return 1; -} - -static int get_data_size(RemoteSource *source) { - int r; - void *data; - - assert(source); - assert(source->state == STATE_DATA_START); - assert(source->data_size == 0); - - r = fill_fixed_size(source, &data, sizeof(uint64_t)); - if (r <= 0) - return r; - - source->data_size = le64toh( *(uint64_t *) data ); - if (source->data_size > DATA_SIZE_MAX) { - log_error("Stream declares field with size %zu > DATA_SIZE_MAX = %u", - source->data_size, DATA_SIZE_MAX); - return -EINVAL; - } - if (source->data_size == 0) - log_warning("Binary field with zero length"); - - return 1; -} - -static int get_data_data(RemoteSource *source, void **data) { - int r; - - assert(source); - assert(data); - assert(source->state == STATE_DATA); - - r = fill_fixed_size(source, data, source->data_size); - if (r <= 0) - return r; - - return 1; -} - -static int get_data_newline(RemoteSource *source) { - int r; - char *data; - - assert(source); - assert(source->state == STATE_DATA_FINISH); - - r = fill_fixed_size(source, (void**) &data, 1); - if (r <= 0) - return r; - - assert(data); - if (*data != '\n') { - log_error("expected newline, got '%c'", *data); - return -EINVAL; - } - - return 1; -} - -static int process_dunder(RemoteSource *source, char *line, size_t n) { - const char *timestamp; - int r; - - assert(line); - assert(n > 0); - assert(line[n-1] == '\n'); - - /* XXX: is it worth to support timestamps in extended format? - * We don't produce them, but who knows... */ - - timestamp = startswith(line, "__CURSOR="); - if (timestamp) - /* ignore __CURSOR */ - return 1; - - timestamp = startswith(line, "__REALTIME_TIMESTAMP="); - if (timestamp) { - long long unsigned x; - line[n-1] = '\0'; - r = safe_atollu(timestamp, &x); - if (r < 0) - log_warning("Failed to parse __REALTIME_TIMESTAMP: '%s'", timestamp); - else - source->ts.realtime = x; - return r < 0 ? r : 1; - } - - timestamp = startswith(line, "__MONOTONIC_TIMESTAMP="); - if (timestamp) { - long long unsigned x; - line[n-1] = '\0'; - r = safe_atollu(timestamp, &x); - if (r < 0) - log_warning("Failed to parse __MONOTONIC_TIMESTAMP: '%s'", timestamp); - else - source->ts.monotonic = x; - return r < 0 ? r : 1; - } - - timestamp = startswith(line, "__"); - if (timestamp) { - log_notice("Unknown dunder line %s", line); - return 1; - } - - /* no dunder */ - return 0; -} - -static int process_data(RemoteSource *source) { - int r; - - switch(source->state) { - case STATE_LINE: { - char *line, *sep; - size_t n = 0; - - assert(source->data_size == 0); - - r = get_line(source, &line, &n); - if (r < 0) - return r; - if (r == 0) { - source->state = STATE_EOF; - return r; - } - assert(n > 0); - assert(line[n-1] == '\n'); - - if (n == 1) { - log_trace("Received empty line, event is ready"); - return 1; - } - - r = process_dunder(source, line, n); - if (r != 0) - return r < 0 ? r : 0; - - /* MESSAGE=xxx\n - or - COREDUMP\n - LLLLLLLL0011223344...\n - */ - sep = memchr(line, '=', n); - if (sep) { - /* chomp newline */ - n--; - - r = iovw_put(&source->iovw, line, n); - if (r < 0) - return r; - } else { - /* replace \n with = */ - line[n-1] = '='; - - source->field_len = n; - source->state = STATE_DATA_START; - - /* we cannot put the field in iovec until we have all data */ - } - - log_trace("Received: %.*s (%s)", (int) n, line, sep ? "text" : "binary"); - - return 0; /* continue */ - } - - case STATE_DATA_START: - assert(source->data_size == 0); - - r = get_data_size(source); - // log_debug("get_data_size() -> %d", r); - if (r < 0) - return r; - if (r == 0) { - source->state = STATE_EOF; - return 0; - } - - source->state = source->data_size > 0 ? - STATE_DATA : STATE_DATA_FINISH; - - return 0; /* continue */ - - case STATE_DATA: { - void *data; - char *field; - - assert(source->data_size > 0); - - r = get_data_data(source, &data); - // log_debug("get_data_data() -> %d", r); - if (r < 0) - return r; - if (r == 0) { - source->state = STATE_EOF; - return 0; - } - - assert(data); - - field = (char*) data - sizeof(uint64_t) - source->field_len; - memmove(field + sizeof(uint64_t), field, source->field_len); - - r = iovw_put(&source->iovw, field + sizeof(uint64_t), source->field_len + source->data_size); - if (r < 0) - return r; - - source->state = STATE_DATA_FINISH; - - return 0; /* continue */ - } - - case STATE_DATA_FINISH: - r = get_data_newline(source); - // log_debug("get_data_newline() -> %d", r); - if (r < 0) - return r; - if (r == 0) { - source->state = STATE_EOF; - return 0; - } - - source->data_size = 0; - source->state = STATE_LINE; - - return 0; /* continue */ - default: - assert_not_reached("wtf?"); - } -} - -int process_source(RemoteSource *source, bool compress, bool seal) { - size_t remain, target; - int r; - - assert(source); - assert(source->writer); - - r = process_data(source); - if (r <= 0) - return r; - - /* We have a full event */ - log_trace("Received full event from source@%p fd:%d (%s)", - source, source->fd, source->name); - - if (!source->iovw.count) { - log_warning("Entry with no payload, skipping"); - goto freeing; - } - - assert(source->iovw.iovec); - assert(source->iovw.count); - - r = writer_write(source->writer, &source->iovw, &source->ts, compress, seal); - if (r < 0) - log_error_errno(r, "Failed to write entry of %zu bytes: %m", - iovw_size(&source->iovw)); - else - r = 1; - - freeing: - iovw_free_contents(&source->iovw); - - /* possibly reset buffer position */ - remain = source->filled - source->offset; - - if (remain == 0) /* no brainer */ - source->offset = source->scanned = source->filled = 0; - else if (source->offset > source->size - source->filled && - source->offset > remain) { - memcpy(source->buf, source->buf + source->offset, remain); - source->offset = source->scanned = 0; - source->filled = remain; - } - - target = source->size; - while (target > 16 * LINE_CHUNK && source->filled < target / 2) - target /= 2; - if (target < source->size) { - char *tmp; - - tmp = realloc(source->buf, target); - if (!tmp) - log_warning("Failed to reallocate buffer to (smaller) size %zu", - target); - else { - log_debug("Reallocated buffer from %zu to %zu bytes", - source->size, target); - source->buf = tmp; - source->size = target; - } - } - - return r; -} diff --git a/src/grp-journal-remote/systemd-journal-remote/journal-remote-parse.h b/src/grp-journal-remote/systemd-journal-remote/journal-remote-parse.h deleted file mode 100644 index 4f47ea89d6..0000000000 --- a/src/grp-journal-remote/systemd-journal-remote/journal-remote-parse.h +++ /dev/null @@ -1,69 +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 - -#include "journal-remote-write.h" - -typedef enum { - STATE_LINE = 0, /* waiting to read, or reading line */ - STATE_DATA_START, /* reading binary data header */ - STATE_DATA, /* reading binary data */ - STATE_DATA_FINISH, /* expecting newline */ - STATE_EOF, /* done */ -} source_state; - -typedef struct RemoteSource { - char *name; - int fd; - bool passive_fd; - - char *buf; - size_t size; /* total size of the buffer */ - size_t offset; /* offset to the beginning of live data in the buffer */ - size_t scanned; /* number of bytes since the beginning of data without a newline */ - size_t filled; /* total number of bytes in the buffer */ - - size_t field_len; /* used for binary fields: the field name length */ - size_t data_size; /* and the size of the binary data chunk being processed */ - - struct iovec_wrapper iovw; - - source_state state; - dual_timestamp ts; - - Writer *writer; - - sd_event_source *event; - sd_event_source *buffer_event; -} RemoteSource; - -RemoteSource* source_new(int fd, bool passive_fd, char *name, Writer *writer); - -static inline size_t source_non_empty(RemoteSource *source) { - assert(source); - - return source->filled; -} - -void source_free(RemoteSource *source); -int push_data(RemoteSource *source, const char *data, size_t size); -int process_source(RemoteSource *source, bool compress, bool seal); diff --git a/src/grp-journal-remote/systemd-journal-remote/journal-remote-write.c b/src/grp-journal-remote/systemd-journal-remote/journal-remote-write.c deleted file mode 100644 index 82292fd240..0000000000 --- a/src/grp-journal-remote/systemd-journal-remote/journal-remote-write.c +++ /dev/null @@ -1,168 +0,0 @@ -/*** - This file is part of systemd. - - Copyright 2012 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 "basic/alloc-util.h" -#include "journal-remote.h" - -int iovw_put(struct iovec_wrapper *iovw, void* data, size_t len) { - if (!GREEDY_REALLOC(iovw->iovec, iovw->size_bytes, iovw->count + 1)) - return log_oom(); - - iovw->iovec[iovw->count++] = (struct iovec) {data, len}; - return 0; -} - -void iovw_free_contents(struct iovec_wrapper *iovw) { - iovw->iovec = mfree(iovw->iovec); - iovw->size_bytes = iovw->count = 0; -} - -size_t iovw_size(struct iovec_wrapper *iovw) { - size_t n = 0, i; - - for (i = 0; i < iovw->count; i++) - n += iovw->iovec[i].iov_len; - - return n; -} - -void iovw_rebase(struct iovec_wrapper *iovw, char *old, char *new) { - size_t i; - - for (i = 0; i < iovw->count; i++) - iovw->iovec[i].iov_base = (char*) iovw->iovec[i].iov_base - old + new; -} - -/********************************************************************** - ********************************************************************** - **********************************************************************/ - -static int do_rotate(JournalFile **f, bool compress, bool seal) { - int r = journal_file_rotate(f, compress, seal, NULL); - if (r < 0) { - if (*f) - log_error_errno(r, "Failed to rotate %s: %m", (*f)->path); - else - log_error_errno(r, "Failed to create rotated journal: %m"); - } - - return r; -} - -Writer* writer_new(RemoteServer *server) { - Writer *w; - - w = new0(Writer, 1); - if (!w) - return NULL; - - memset(&w->metrics, 0xFF, sizeof(w->metrics)); - - w->mmap = mmap_cache_new(); - if (!w->mmap) { - free(w); - return NULL; - } - - w->n_ref = 1; - w->server = server; - - return w; -} - -Writer* writer_free(Writer *w) { - if (!w) - return NULL; - - if (w->journal) { - log_debug("Closing journal file %s.", w->journal->path); - journal_file_close(w->journal); - } - - if (w->server && w->hashmap_key) - hashmap_remove(w->server->writers, w->hashmap_key); - - free(w->hashmap_key); - - if (w->mmap) - mmap_cache_unref(w->mmap); - - free(w); - - return NULL; -} - -Writer* writer_unref(Writer *w) { - if (w && (-- w->n_ref <= 0)) - writer_free(w); - - return NULL; -} - -Writer* writer_ref(Writer *w) { - if (w) - assert_se(++ w->n_ref >= 2); - - return w; -} - -int writer_write(Writer *w, - struct iovec_wrapper *iovw, - dual_timestamp *ts, - bool compress, - bool seal) { - int r; - - assert(w); - assert(iovw); - assert(iovw->count > 0); - - if (journal_file_rotate_suggested(w->journal, 0)) { - log_info("%s: Journal header limits reached or header out-of-date, rotating", - w->journal->path); - r = do_rotate(&w->journal, compress, seal); - if (r < 0) - return r; - } - - r = journal_file_append_entry(w->journal, ts, iovw->iovec, iovw->count, - &w->seqnum, NULL, NULL); - if (r >= 0) { - if (w->server) - w->server->event_count += 1; - return 1; - } - - log_debug_errno(r, "%s: Write failed, rotating: %m", w->journal->path); - r = do_rotate(&w->journal, compress, seal); - if (r < 0) - return r; - else - log_debug("%s: Successfully rotated journal", w->journal->path); - - log_debug("Retrying write."); - r = journal_file_append_entry(w->journal, ts, iovw->iovec, iovw->count, - &w->seqnum, NULL, NULL); - if (r < 0) - return r; - - if (w->server) - w->server->event_count += 1; - return 1; -} diff --git a/src/grp-journal-remote/systemd-journal-remote/journal-remote-write.h b/src/grp-journal-remote/systemd-journal-remote/journal-remote-write.h deleted file mode 100644 index 53ba45fc04..0000000000 --- a/src/grp-journal-remote/systemd-journal-remote/journal-remote-write.h +++ /dev/null @@ -1,70 +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 "journal-file.h" - -typedef struct RemoteServer RemoteServer; - -struct iovec_wrapper { - struct iovec *iovec; - size_t size_bytes; - size_t count; -}; - -int iovw_put(struct iovec_wrapper *iovw, void* data, size_t len); -void iovw_free_contents(struct iovec_wrapper *iovw); -size_t iovw_size(struct iovec_wrapper *iovw); -void iovw_rebase(struct iovec_wrapper *iovw, char *old, char *new); - -typedef struct Writer { - JournalFile *journal; - JournalMetrics metrics; - - MMapCache *mmap; - RemoteServer *server; - char *hashmap_key; - - uint64_t seqnum; - - int n_ref; -} Writer; - -Writer* writer_new(RemoteServer* server); -Writer* writer_free(Writer *w); - -Writer* writer_ref(Writer *w); -Writer* writer_unref(Writer *w); - -DEFINE_TRIVIAL_CLEANUP_FUNC(Writer*, writer_unref); -#define _cleanup_writer_unref_ _cleanup_(writer_unrefp) - -int writer_write(Writer *s, - struct iovec_wrapper *iovw, - dual_timestamp *ts, - bool compress, - bool seal); - -typedef enum JournalWriteSplitMode { - JOURNAL_WRITE_SPLIT_NONE, - JOURNAL_WRITE_SPLIT_HOST, - _JOURNAL_WRITE_SPLIT_MAX, - _JOURNAL_WRITE_SPLIT_INVALID = -1 -} JournalWriteSplitMode; diff --git a/src/grp-journal-remote/systemd-journal-remote/journal-remote.c b/src/grp-journal-remote/systemd-journal-remote/journal-remote.c deleted file mode 100644 index 6ea7adabb0..0000000000 --- a/src/grp-journal-remote/systemd-journal-remote/journal-remote.c +++ /dev/null @@ -1,1601 +0,0 @@ -/*** - This file is part of systemd. - - Copyright 2012 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 - -#ifdef HAVE_GNUTLS -#include -#endif - -#include - -#include "basic/alloc-util.h" -#include "shared/conf-parser.h" -#include "basic/def.h" -#include "basic/escape.h" -#include "basic/fd-util.h" -#include "basic/fileio.h" -#include "journal-file.h" -#include "journal-remote-write.h" -#include "journal-remote.h" -#include "journald-native.h" -#include "basic/macro.h" -#include "basic/parse-util.h" -#include "basic/signal-util.h" -#include "basic/socket-util.h" -#include "basic/stat-util.h" -#include "basic/stdio-util.h" -#include "basic/string-table.h" -#include "basic/string-util.h" -#include "basic/strv.h" - -#define REMOTE_JOURNAL_PATH "/var/log/journal/remote" - -#define PRIV_KEY_FILE CERTIFICATE_ROOT "/private/journal-remote.pem" -#define CERT_FILE CERTIFICATE_ROOT "/certs/journal-remote.pem" -#define TRUST_FILE CERTIFICATE_ROOT "/ca/trusted.pem" - -static char* arg_url = NULL; -static char* arg_getter = NULL; -static char* arg_listen_raw = NULL; -static char* arg_listen_http = NULL; -static char* arg_listen_https = NULL; -static char** arg_files = NULL; -static int arg_compress = true; -static int arg_seal = false; -static int http_socket = -1, https_socket = -1; -static char** arg_gnutls_log = NULL; - -static JournalWriteSplitMode arg_split_mode = JOURNAL_WRITE_SPLIT_HOST; -static char* arg_output = NULL; - -static char *arg_key = NULL; -static char *arg_cert = NULL; -static char *arg_trust = NULL; -static bool arg_trust_all = false; - -/********************************************************************** - ********************************************************************** - **********************************************************************/ - -static int spawn_child(const char* child, char** argv) { - int fd[2]; - pid_t parent_pid, child_pid; - int r; - - if (pipe(fd) < 0) - return log_error_errno(errno, "Failed to create pager pipe: %m"); - - parent_pid = getpid(); - - child_pid = fork(); - if (child_pid < 0) { - r = log_error_errno(errno, "Failed to fork: %m"); - safe_close_pair(fd); - return r; - } - - /* In the child */ - if (child_pid == 0) { - - (void) reset_all_signal_handlers(); - (void) reset_signal_mask(); - - r = dup2(fd[1], STDOUT_FILENO); - if (r < 0) { - log_error_errno(errno, "Failed to dup pipe to stdout: %m"); - _exit(EXIT_FAILURE); - } - - safe_close_pair(fd); - - /* 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); - - execvp(child, argv); - log_error_errno(errno, "Failed to exec child %s: %m", child); - _exit(EXIT_FAILURE); - } - - r = close(fd[1]); - if (r < 0) - log_warning_errno(errno, "Failed to close write end of pipe: %m"); - - return fd[0]; -} - -static int spawn_curl(const char* url) { - char **argv = STRV_MAKE("curl", - "-HAccept: application/vnd.fdo.journal", - "--silent", - "--show-error", - url); - int r; - - r = spawn_child("curl", argv); - if (r < 0) - log_error_errno(r, "Failed to spawn curl: %m"); - return r; -} - -static int spawn_getter(const char *getter) { - int r; - _cleanup_strv_free_ char **words = NULL; - - assert(getter); - r = strv_split_extract(&words, getter, WHITESPACE, EXTRACT_QUOTES); - if (r < 0) - return log_error_errno(r, "Failed to split getter option: %m"); - - r = spawn_child(words[0], words); - if (r < 0) - log_error_errno(r, "Failed to spawn getter %s: %m", getter); - - return r; -} - -#define filename_escape(s) xescape((s), "/ ") - -static int open_output(Writer *w, const char* host) { - _cleanup_free_ char *_output = NULL; - const char *output; - int r; - - switch (arg_split_mode) { - case JOURNAL_WRITE_SPLIT_NONE: - output = arg_output ?: REMOTE_JOURNAL_PATH "/remote.journal"; - break; - - case JOURNAL_WRITE_SPLIT_HOST: { - _cleanup_free_ char *name; - - assert(host); - - name = filename_escape(host); - if (!name) - return log_oom(); - - r = asprintf(&_output, "%s/remote-%s.journal", - arg_output ?: REMOTE_JOURNAL_PATH, - name); - if (r < 0) - return log_oom(); - - output = _output; - break; - } - - default: - assert_not_reached("what?"); - } - - r = journal_file_open_reliably(output, - O_RDWR|O_CREAT, 0640, - arg_compress, arg_seal, - &w->metrics, - w->mmap, NULL, - NULL, &w->journal); - if (r < 0) - log_error_errno(r, "Failed to open output journal %s: %m", - output); - else - log_debug("Opened output file %s", w->journal->path); - return r; -} - -/********************************************************************** - ********************************************************************** - **********************************************************************/ - -static int init_writer_hashmap(RemoteServer *s) { - static const struct hash_ops *hash_ops[] = { - [JOURNAL_WRITE_SPLIT_NONE] = NULL, - [JOURNAL_WRITE_SPLIT_HOST] = &string_hash_ops, - }; - - assert(arg_split_mode >= 0 && arg_split_mode < (int) ELEMENTSOF(hash_ops)); - - s->writers = hashmap_new(hash_ops[arg_split_mode]); - if (!s->writers) - return log_oom(); - - return 0; -} - -static int get_writer(RemoteServer *s, const char *host, - Writer **writer) { - const void *key; - _cleanup_writer_unref_ Writer *w = NULL; - int r; - - switch(arg_split_mode) { - case JOURNAL_WRITE_SPLIT_NONE: - key = "one and only"; - break; - - case JOURNAL_WRITE_SPLIT_HOST: - assert(host); - key = host; - break; - - default: - assert_not_reached("what split mode?"); - } - - w = hashmap_get(s->writers, key); - if (w) - writer_ref(w); - else { - w = writer_new(s); - if (!w) - return log_oom(); - - if (arg_split_mode == JOURNAL_WRITE_SPLIT_HOST) { - w->hashmap_key = strdup(key); - if (!w->hashmap_key) - return log_oom(); - } - - r = open_output(w, host); - if (r < 0) - return r; - - r = hashmap_put(s->writers, w->hashmap_key ?: key, w); - if (r < 0) - return r; - } - - *writer = w; - w = NULL; - return 0; -} - -/********************************************************************** - ********************************************************************** - **********************************************************************/ - -/* This should go away as soon as µhttpd allows state to be passed around. */ -static RemoteServer *server; - -static int dispatch_raw_source_event(sd_event_source *event, - int fd, - uint32_t revents, - void *userdata); -static int dispatch_raw_source_until_block(sd_event_source *event, - void *userdata); -static int dispatch_blocking_source_event(sd_event_source *event, - void *userdata); -static int dispatch_raw_connection_event(sd_event_source *event, - int fd, - uint32_t revents, - void *userdata); -static int dispatch_http_event(sd_event_source *event, - int fd, - uint32_t revents, - void *userdata); - -static int get_source_for_fd(RemoteServer *s, - int fd, char *name, RemoteSource **source) { - Writer *writer; - int r; - - /* This takes ownership of name, but only on success. */ - - assert(fd >= 0); - assert(source); - - if (!GREEDY_REALLOC0(s->sources, s->sources_size, fd + 1)) - return log_oom(); - - r = get_writer(s, name, &writer); - if (r < 0) - return log_warning_errno(r, "Failed to get writer for source %s: %m", - name); - - if (s->sources[fd] == NULL) { - s->sources[fd] = source_new(fd, false, name, writer); - if (!s->sources[fd]) { - writer_unref(writer); - return log_oom(); - } - - s->active++; - } - - *source = s->sources[fd]; - return 0; -} - -static int remove_source(RemoteServer *s, int fd) { - RemoteSource *source; - - assert(s); - assert(fd >= 0 && fd < (ssize_t) s->sources_size); - - source = s->sources[fd]; - if (source) { - /* this closes fd too */ - source_free(source); - s->sources[fd] = NULL; - s->active--; - } - - return 0; -} - -static int add_source(RemoteServer *s, int fd, char* name, bool own_name) { - - RemoteSource *source = NULL; - int r; - - /* This takes ownership of name, even on failure, if own_name is true. */ - - assert(s); - assert(fd >= 0); - assert(name); - - if (!own_name) { - name = strdup(name); - if (!name) - return log_oom(); - } - - r = get_source_for_fd(s, fd, name, &source); - if (r < 0) { - log_error_errno(r, "Failed to create source for fd:%d (%s): %m", - fd, name); - free(name); - return r; - } - - r = sd_event_add_io(s->events, &source->event, - fd, EPOLLIN|EPOLLRDHUP|EPOLLPRI, - dispatch_raw_source_event, source); - if (r == 0) { - /* Add additional source for buffer processing. It will be - * enabled later. */ - r = sd_event_add_defer(s->events, &source->buffer_event, - dispatch_raw_source_until_block, source); - if (r == 0) - sd_event_source_set_enabled(source->buffer_event, SD_EVENT_OFF); - } else if (r == -EPERM) { - log_debug("Falling back to sd_event_add_defer for fd:%d (%s)", fd, name); - r = sd_event_add_defer(s->events, &source->event, - dispatch_blocking_source_event, source); - if (r == 0) - sd_event_source_set_enabled(source->event, SD_EVENT_ON); - } - if (r < 0) { - log_error_errno(r, "Failed to register event source for fd:%d: %m", - fd); - goto error; - } - - r = sd_event_source_set_description(source->event, name); - if (r < 0) { - log_error_errno(r, "Failed to set source name for fd:%d: %m", fd); - goto error; - } - - return 1; /* work to do */ - - error: - remove_source(s, fd); - return r; -} - -static int add_raw_socket(RemoteServer *s, int fd) { - int r; - _cleanup_close_ int fd_ = fd; - char name[sizeof("raw-socket-")-1 + DECIMAL_STR_MAX(int) + 1]; - - assert(fd >= 0); - - r = sd_event_add_io(s->events, &s->listen_event, - fd, EPOLLIN, - dispatch_raw_connection_event, s); - if (r < 0) - return r; - - xsprintf(name, "raw-socket-%d", fd); - - r = sd_event_source_set_description(s->listen_event, name); - if (r < 0) - return r; - - fd_ = -1; - s->active++; - return 0; -} - -static int setup_raw_socket(RemoteServer *s, const char *address) { - int fd; - - fd = make_socket_fd(LOG_INFO, address, SOCK_STREAM, SOCK_CLOEXEC); - if (fd < 0) - return fd; - - return add_raw_socket(s, fd); -} - -/********************************************************************** - ********************************************************************** - **********************************************************************/ - -static int request_meta(void **connection_cls, int fd, char *hostname) { - RemoteSource *source; - Writer *writer; - int r; - - assert(connection_cls); - if (*connection_cls) - return 0; - - r = get_writer(server, hostname, &writer); - if (r < 0) - return log_warning_errno(r, "Failed to get writer for source %s: %m", - hostname); - - source = source_new(fd, true, hostname, writer); - if (!source) { - writer_unref(writer); - return log_oom(); - } - - log_debug("Added RemoteSource as connection metadata %p", source); - - *connection_cls = source; - return 0; -} - -static void request_meta_free(void *cls, - struct MHD_Connection *connection, - void **connection_cls, - enum MHD_RequestTerminationCode toe) { - RemoteSource *s; - - assert(connection_cls); - s = *connection_cls; - - if (s) { - log_debug("Cleaning up connection metadata %p", s); - source_free(s); - *connection_cls = NULL; - } -} - -static int process_http_upload( - struct MHD_Connection *connection, - const char *upload_data, - size_t *upload_data_size, - RemoteSource *source) { - - bool finished = false; - size_t remaining; - int r; - - assert(source); - - log_trace("%s: connection %p, %zu bytes", - __func__, connection, *upload_data_size); - - if (*upload_data_size) { - log_trace("Received %zu bytes", *upload_data_size); - - r = push_data(source, upload_data, *upload_data_size); - if (r < 0) - return mhd_respond_oom(connection); - - *upload_data_size = 0; - } else - finished = true; - - for (;;) { - r = process_source(source, arg_compress, arg_seal); - if (r == -EAGAIN) - break; - else if (r < 0) { - log_warning("Failed to process data for connection %p", connection); - if (r == -E2BIG) - return mhd_respondf(connection, - MHD_HTTP_REQUEST_ENTITY_TOO_LARGE, - "Entry is too large, maximum is %u bytes.\n", - DATA_SIZE_MAX); - else - return mhd_respondf(connection, - MHD_HTTP_UNPROCESSABLE_ENTITY, - "Processing failed: %s.", strerror(-r)); - } - } - - if (!finished) - return MHD_YES; - - /* The upload is finished */ - - remaining = source_non_empty(source); - if (remaining > 0) { - log_warning("Premature EOFbyte. %zu bytes lost.", remaining); - return mhd_respondf(connection, MHD_HTTP_EXPECTATION_FAILED, - "Premature EOF. %zu bytes of trailing data not processed.", - remaining); - } - - return mhd_respond(connection, MHD_HTTP_ACCEPTED, "OK.\n"); -}; - -static int request_handler( - void *cls, - struct MHD_Connection *connection, - const char *url, - const char *method, - const char *version, - const char *upload_data, - size_t *upload_data_size, - void **connection_cls) { - - const char *header; - int r, code, fd; - _cleanup_free_ char *hostname = NULL; - - assert(connection); - assert(connection_cls); - assert(url); - assert(method); - - log_trace("Handling a connection %s %s %s", method, url, version); - - if (*connection_cls) - return process_http_upload(connection, - upload_data, upload_data_size, - *connection_cls); - - if (!streq(method, "POST")) - return mhd_respond(connection, MHD_HTTP_NOT_ACCEPTABLE, - "Unsupported method.\n"); - - if (!streq(url, "/upload")) - return mhd_respond(connection, MHD_HTTP_NOT_FOUND, - "Not found.\n"); - - header = MHD_lookup_connection_value(connection, - MHD_HEADER_KIND, "Content-Type"); - if (!header || !streq(header, "application/vnd.fdo.journal")) - return mhd_respond(connection, MHD_HTTP_UNSUPPORTED_MEDIA_TYPE, - "Content-Type: application/vnd.fdo.journal" - " is required.\n"); - - { - const union MHD_ConnectionInfo *ci; - - ci = MHD_get_connection_info(connection, - MHD_CONNECTION_INFO_CONNECTION_FD); - if (!ci) { - log_error("MHD_get_connection_info failed: cannot get remote fd"); - return mhd_respond(connection, MHD_HTTP_INTERNAL_SERVER_ERROR, - "Cannot check remote address"); - } - - fd = ci->connect_fd; - assert(fd >= 0); - } - - if (server->check_trust) { - r = check_permissions(connection, &code, &hostname); - if (r < 0) - return code; - } else { - r = getpeername_pretty(fd, false, &hostname); - if (r < 0) - return mhd_respond(connection, MHD_HTTP_INTERNAL_SERVER_ERROR, - "Cannot check remote hostname"); - } - - assert(hostname); - - r = request_meta(connection_cls, fd, hostname); - if (r == -ENOMEM) - return respond_oom(connection); - else if (r < 0) - return mhd_respond(connection, MHD_HTTP_INTERNAL_SERVER_ERROR, - strerror(-r)); - - hostname = NULL; - return MHD_YES; -} - -static int setup_microhttpd_server(RemoteServer *s, - int fd, - const char *key, - const char *cert, - const char *trust) { - struct MHD_OptionItem opts[] = { - { MHD_OPTION_NOTIFY_COMPLETED, (intptr_t) request_meta_free}, - { MHD_OPTION_EXTERNAL_LOGGER, (intptr_t) microhttpd_logger}, - { MHD_OPTION_LISTEN_SOCKET, fd}, - { MHD_OPTION_CONNECTION_MEMORY_LIMIT, 128*1024}, - { MHD_OPTION_END}, - { MHD_OPTION_END}, - { MHD_OPTION_END}, - { MHD_OPTION_END}}; - int opts_pos = 4; - int flags = - MHD_USE_DEBUG | - MHD_USE_DUAL_STACK | - MHD_USE_EPOLL_LINUX_ONLY | - MHD_USE_PEDANTIC_CHECKS | - MHD_USE_PIPE_FOR_SHUTDOWN; - - const union MHD_DaemonInfo *info; - int r, epoll_fd; - MHDDaemonWrapper *d; - - assert(fd >= 0); - - r = fd_nonblock(fd, true); - if (r < 0) - return log_error_errno(r, "Failed to make fd:%d nonblocking: %m", fd); - - if (key) { - assert(cert); - - opts[opts_pos++] = (struct MHD_OptionItem) - {MHD_OPTION_HTTPS_MEM_KEY, 0, (char*) key}; - opts[opts_pos++] = (struct MHD_OptionItem) - {MHD_OPTION_HTTPS_MEM_CERT, 0, (char*) cert}; - - flags |= MHD_USE_SSL; - - if (trust) - opts[opts_pos++] = (struct MHD_OptionItem) - {MHD_OPTION_HTTPS_MEM_TRUST, 0, (char*) trust}; - } - - d = new(MHDDaemonWrapper, 1); - if (!d) - return log_oom(); - - d->fd = (uint64_t) fd; - - d->daemon = MHD_start_daemon(flags, 0, - NULL, NULL, - request_handler, NULL, - MHD_OPTION_ARRAY, opts, - MHD_OPTION_END); - if (!d->daemon) { - log_error("Failed to start µhttp daemon"); - r = -EINVAL; - goto error; - } - - log_debug("Started MHD %s daemon on fd:%d (wrapper @ %p)", - key ? "HTTPS" : "HTTP", fd, d); - - - info = MHD_get_daemon_info(d->daemon, MHD_DAEMON_INFO_EPOLL_FD_LINUX_ONLY); - if (!info) { - log_error("µhttp returned NULL daemon info"); - r = -EOPNOTSUPP; - goto error; - } - - epoll_fd = info->listen_fd; - if (epoll_fd < 0) { - log_error("µhttp epoll fd is invalid"); - r = -EUCLEAN; - goto error; - } - - r = sd_event_add_io(s->events, &d->event, - epoll_fd, EPOLLIN, - dispatch_http_event, d); - if (r < 0) { - log_error_errno(r, "Failed to add event callback: %m"); - goto error; - } - - r = sd_event_source_set_description(d->event, "epoll-fd"); - if (r < 0) { - log_error_errno(r, "Failed to set source name: %m"); - goto error; - } - - r = hashmap_ensure_allocated(&s->daemons, &uint64_hash_ops); - if (r < 0) { - log_oom(); - goto error; - } - - r = hashmap_put(s->daemons, &d->fd, d); - if (r < 0) { - log_error_errno(r, "Failed to add daemon to hashmap: %m"); - goto error; - } - - s->active++; - return 0; - -error: - MHD_stop_daemon(d->daemon); - free(d->daemon); - free(d); - return r; -} - -static int setup_microhttpd_socket(RemoteServer *s, - const char *address, - const char *key, - const char *cert, - const char *trust) { - int fd; - - fd = make_socket_fd(LOG_DEBUG, address, SOCK_STREAM, SOCK_CLOEXEC); - if (fd < 0) - return fd; - - return setup_microhttpd_server(s, fd, key, cert, trust); -} - -static int dispatch_http_event(sd_event_source *event, - int fd, - uint32_t revents, - void *userdata) { - MHDDaemonWrapper *d = userdata; - int r; - - assert(d); - - r = MHD_run(d->daemon); - if (r == MHD_NO) { - log_error("MHD_run failed!"); - // XXX: unregister daemon - return -EINVAL; - } - - return 1; /* work to do */ -} - -/********************************************************************** - ********************************************************************** - **********************************************************************/ - -static int setup_signals(RemoteServer *s) { - int r; - - assert(s); - - assert_se(sigprocmask_many(SIG_SETMASK, NULL, SIGINT, SIGTERM, -1) >= 0); - - r = sd_event_add_signal(s->events, &s->sigterm_event, SIGTERM, NULL, s); - if (r < 0) - return r; - - r = sd_event_add_signal(s->events, &s->sigint_event, SIGINT, NULL, s); - if (r < 0) - return r; - - return 0; -} - -static int negative_fd(const char *spec) { - /* Return a non-positive number as its inverse, -EINVAL otherwise. */ - - int fd, r; - - r = safe_atoi(spec, &fd); - if (r < 0) - return r; - - if (fd > 0) - return -EINVAL; - else - return -fd; -} - -static int remoteserver_init(RemoteServer *s, - const char* key, - const char* cert, - const char* trust) { - int r, n, fd; - char **file; - - assert(s); - - if ((arg_listen_raw || arg_listen_http) && trust) { - log_error("Option --trust makes all non-HTTPS connections untrusted."); - return -EINVAL; - } - - r = sd_event_default(&s->events); - if (r < 0) - return log_error_errno(r, "Failed to allocate event loop: %m"); - - setup_signals(s); - - assert(server == NULL); - server = s; - - r = init_writer_hashmap(s); - if (r < 0) - return r; - - n = sd_listen_fds(true); - if (n < 0) - return log_error_errno(n, "Failed to read listening file descriptors from environment: %m"); - else - log_debug("Received %d descriptors", n); - - if (MAX(http_socket, https_socket) >= SD_LISTEN_FDS_START + n) { - log_error("Received fewer sockets than expected"); - return -EBADFD; - } - - for (fd = SD_LISTEN_FDS_START; fd < SD_LISTEN_FDS_START + n; fd++) { - if (sd_is_socket(fd, AF_UNSPEC, 0, true)) { - log_debug("Received a listening socket (fd:%d)", fd); - - if (fd == http_socket) - r = setup_microhttpd_server(s, fd, NULL, NULL, NULL); - else if (fd == https_socket) - r = setup_microhttpd_server(s, fd, key, cert, trust); - else - r = add_raw_socket(s, fd); - } else if (sd_is_socket(fd, AF_UNSPEC, 0, false)) { - char *hostname; - - r = getpeername_pretty(fd, false, &hostname); - if (r < 0) - return log_error_errno(r, "Failed to retrieve remote name: %m"); - - log_debug("Received a connection socket (fd:%d) from %s", fd, hostname); - - r = add_source(s, fd, hostname, true); - } else { - log_error("Unknown socket passed on fd:%d", fd); - - return -EINVAL; - } - - if (r < 0) - return log_error_errno(r, "Failed to register socket (fd:%d): %m", - fd); - } - - if (arg_getter) { - log_info("Spawning getter %s...", arg_getter); - fd = spawn_getter(arg_getter); - if (fd < 0) - return fd; - - r = add_source(s, fd, (char*) arg_output, false); - if (r < 0) - return r; - } - - if (arg_url) { - const char *url; - char *hostname, *p; - - if (!strstr(arg_url, "/entries")) { - if (endswith(arg_url, "/")) - url = strjoina(arg_url, "entries"); - else - url = strjoina(arg_url, "/entries"); - } - else - url = strdupa(arg_url); - - log_info("Spawning curl %s...", url); - fd = spawn_curl(url); - if (fd < 0) - return fd; - - hostname = - startswith(arg_url, "https://") ?: - startswith(arg_url, "http://") ?: - arg_url; - - hostname = strdupa(hostname); - if ((p = strchr(hostname, '/'))) - *p = '\0'; - if ((p = strchr(hostname, ':'))) - *p = '\0'; - - r = add_source(s, fd, hostname, false); - if (r < 0) - return r; - } - - if (arg_listen_raw) { - log_debug("Listening on a socket..."); - r = setup_raw_socket(s, arg_listen_raw); - if (r < 0) - return r; - } - - if (arg_listen_http) { - r = setup_microhttpd_socket(s, arg_listen_http, NULL, NULL, NULL); - if (r < 0) - return r; - } - - if (arg_listen_https) { - r = setup_microhttpd_socket(s, arg_listen_https, key, cert, trust); - if (r < 0) - return r; - } - - STRV_FOREACH(file, arg_files) { - const char *output_name; - - if (streq(*file, "-")) { - log_debug("Using standard input as source."); - - fd = STDIN_FILENO; - output_name = "stdin"; - } else { - log_debug("Reading file %s...", *file); - - fd = open(*file, O_RDONLY|O_CLOEXEC|O_NOCTTY|O_NONBLOCK); - if (fd < 0) - return log_error_errno(errno, "Failed to open %s: %m", *file); - output_name = *file; - } - - r = add_source(s, fd, (char*) output_name, false); - if (r < 0) - return r; - } - - if (s->active == 0) { - log_error("Zero sources specified"); - return -EINVAL; - } - - if (arg_split_mode == JOURNAL_WRITE_SPLIT_NONE) { - /* In this case we know what the writer will be - called, so we can create it and verify that we can - create output as expected. */ - r = get_writer(s, NULL, &s->_single_writer); - if (r < 0) - return r; - } - - return 0; -} - -static void server_destroy(RemoteServer *s) { - size_t i; - MHDDaemonWrapper *d; - - while ((d = hashmap_steal_first(s->daemons))) { - MHD_stop_daemon(d->daemon); - sd_event_source_unref(d->event); - free(d); - } - - hashmap_free(s->daemons); - - assert(s->sources_size == 0 || s->sources); - for (i = 0; i < s->sources_size; i++) - remove_source(s, i); - free(s->sources); - - writer_unref(s->_single_writer); - hashmap_free(s->writers); - - sd_event_source_unref(s->sigterm_event); - sd_event_source_unref(s->sigint_event); - sd_event_source_unref(s->listen_event); - sd_event_unref(s->events); - - /* fds that we're listening on remain open... */ -} - -/********************************************************************** - ********************************************************************** - **********************************************************************/ - -static int handle_raw_source(sd_event_source *event, - int fd, - uint32_t revents, - RemoteServer *s) { - - RemoteSource *source; - int r; - - /* Returns 1 if there might be more data pending, - * 0 if data is currently exhausted, negative on error. - */ - - assert(fd >= 0 && fd < (ssize_t) s->sources_size); - source = s->sources[fd]; - assert(source->fd == fd); - - r = process_source(source, arg_compress, arg_seal); - if (source->state == STATE_EOF) { - size_t remaining; - - log_debug("EOF reached with source fd:%d (%s)", - source->fd, source->name); - - remaining = source_non_empty(source); - if (remaining > 0) - log_notice("Premature EOF. %zu bytes lost.", remaining); - remove_source(s, source->fd); - log_debug("%zu active sources remaining", s->active); - return 0; - } else if (r == -E2BIG) { - log_notice_errno(E2BIG, "Entry too big, skipped"); - return 1; - } else if (r == -EAGAIN) { - return 0; - } else if (r < 0) { - log_debug_errno(r, "Closing connection: %m"); - remove_source(server, fd); - return 0; - } else - return 1; -} - -static int dispatch_raw_source_until_block(sd_event_source *event, - void *userdata) { - RemoteSource *source = userdata; - int r; - - /* Make sure event stays around even if source is destroyed */ - sd_event_source_ref(event); - - r = handle_raw_source(event, source->fd, EPOLLIN, server); - if (r != 1) - /* No more data for now */ - sd_event_source_set_enabled(event, SD_EVENT_OFF); - - sd_event_source_unref(event); - - return r; -} - -static int dispatch_raw_source_event(sd_event_source *event, - int fd, - uint32_t revents, - void *userdata) { - RemoteSource *source = userdata; - int r; - - assert(source->event); - assert(source->buffer_event); - - r = handle_raw_source(event, fd, EPOLLIN, server); - if (r == 1) - /* Might have more data. We need to rerun the handler - * until we are sure the buffer is exhausted. */ - sd_event_source_set_enabled(source->buffer_event, SD_EVENT_ON); - - return r; -} - -static int dispatch_blocking_source_event(sd_event_source *event, - void *userdata) { - RemoteSource *source = userdata; - - return handle_raw_source(event, source->fd, EPOLLIN, server); -} - -static int accept_connection(const char* type, int fd, - SocketAddress *addr, char **hostname) { - int fd2, r; - - log_debug("Accepting new %s connection on fd:%d", type, fd); - fd2 = accept4(fd, &addr->sockaddr.sa, &addr->size, SOCK_NONBLOCK|SOCK_CLOEXEC); - if (fd2 < 0) - return log_error_errno(errno, "accept() on fd:%d failed: %m", fd); - - switch(socket_address_family(addr)) { - case AF_INET: - case AF_INET6: { - _cleanup_free_ char *a = NULL; - char *b; - - r = socket_address_print(addr, &a); - if (r < 0) { - log_error_errno(r, "socket_address_print(): %m"); - close(fd2); - return r; - } - - r = socknameinfo_pretty(&addr->sockaddr, addr->size, &b); - if (r < 0) { - log_error_errno(r, "Resolving hostname failed: %m"); - close(fd2); - return r; - } - - log_debug("Accepted %s %s connection from %s", - type, - socket_address_family(addr) == AF_INET ? "IP" : "IPv6", - a); - - *hostname = b; - - return fd2; - }; - default: - log_error("Rejected %s connection with unsupported family %d", - type, socket_address_family(addr)); - close(fd2); - - return -EINVAL; - } -} - -static int dispatch_raw_connection_event(sd_event_source *event, - int fd, - uint32_t revents, - void *userdata) { - RemoteServer *s = userdata; - int fd2; - SocketAddress addr = { - .size = sizeof(union sockaddr_union), - .type = SOCK_STREAM, - }; - char *hostname = NULL; - - fd2 = accept_connection("raw", fd, &addr, &hostname); - if (fd2 < 0) - return fd2; - - return add_source(s, fd2, hostname, true); -} - -/********************************************************************** - ********************************************************************** - **********************************************************************/ - -static const char* const journal_write_split_mode_table[_JOURNAL_WRITE_SPLIT_MAX] = { - [JOURNAL_WRITE_SPLIT_NONE] = "none", - [JOURNAL_WRITE_SPLIT_HOST] = "host", -}; - -DEFINE_PRIVATE_STRING_TABLE_LOOKUP(journal_write_split_mode, JournalWriteSplitMode); -static DEFINE_CONFIG_PARSE_ENUM(config_parse_write_split_mode, - journal_write_split_mode, - JournalWriteSplitMode, - "Failed to parse split mode setting"); - -static int parse_config(void) { - const ConfigTableItem items[] = { - { "Remote", "Seal", config_parse_bool, 0, &arg_seal }, - { "Remote", "SplitMode", config_parse_write_split_mode, 0, &arg_split_mode }, - { "Remote", "ServerKeyFile", config_parse_path, 0, &arg_key }, - { "Remote", "ServerCertificateFile", config_parse_path, 0, &arg_cert }, - { "Remote", "TrustedCertificateFile", config_parse_path, 0, &arg_trust }, - {}}; - - return config_parse_many(PKGSYSCONFDIR "/journal-remote.conf", - CONF_PATHS_NULSTR("systemd/journal-remote.conf.d"), - "Remote\0", config_item_table_lookup, items, - false, NULL); -} - -static void help(void) { - printf("%s [OPTIONS...] {FILE|-}...\n\n" - "Write external journal events to journal file(s).\n\n" - " -h --help Show this help\n" - " --version Show package version\n" - " --url=URL Read events from systemd-journal-gatewayd at URL\n" - " --getter=COMMAND Read events from the output of COMMAND\n" - " --listen-raw=ADDR Listen for connections at ADDR\n" - " --listen-http=ADDR Listen for HTTP connections at ADDR\n" - " --listen-https=ADDR Listen for HTTPS connections at ADDR\n" - " -o --output=FILE|DIR Write output to FILE or DIR/external-*.journal\n" - " --compress[=BOOL] XZ-compress the output journal (default: yes)\n" - " --seal[=BOOL] Use event sealing (default: no)\n" - " --key=FILENAME SSL key in PEM format (default:\n" - " \"" PRIV_KEY_FILE "\")\n" - " --cert=FILENAME SSL certificate in PEM format (default:\n" - " \"" CERT_FILE "\")\n" - " --trust=FILENAME|all SSL CA certificate or disable checking (default:\n" - " \"" TRUST_FILE "\")\n" - " --gnutls-log=CATEGORY...\n" - " Specify a list of gnutls logging categories\n" - " --split-mode=none|host How many output files to create\n" - "\n" - "Note: file descriptors from sd_listen_fds() will be consumed, too.\n" - , program_invocation_short_name); -} - -static int parse_argv(int argc, char *argv[]) { - enum { - ARG_VERSION = 0x100, - ARG_URL, - ARG_LISTEN_RAW, - ARG_LISTEN_HTTP, - ARG_LISTEN_HTTPS, - ARG_GETTER, - ARG_SPLIT_MODE, - ARG_COMPRESS, - ARG_SEAL, - ARG_KEY, - ARG_CERT, - ARG_TRUST, - ARG_GNUTLS_LOG, - }; - - static const struct option options[] = { - { "help", no_argument, NULL, 'h' }, - { "version", no_argument, NULL, ARG_VERSION }, - { "url", required_argument, NULL, ARG_URL }, - { "getter", required_argument, NULL, ARG_GETTER }, - { "listen-raw", required_argument, NULL, ARG_LISTEN_RAW }, - { "listen-http", required_argument, NULL, ARG_LISTEN_HTTP }, - { "listen-https", required_argument, NULL, ARG_LISTEN_HTTPS }, - { "output", required_argument, NULL, 'o' }, - { "split-mode", required_argument, NULL, ARG_SPLIT_MODE }, - { "compress", optional_argument, NULL, ARG_COMPRESS }, - { "seal", optional_argument, NULL, ARG_SEAL }, - { "key", required_argument, NULL, ARG_KEY }, - { "cert", required_argument, NULL, ARG_CERT }, - { "trust", required_argument, NULL, ARG_TRUST }, - { "gnutls-log", required_argument, NULL, ARG_GNUTLS_LOG }, - {} - }; - - int c, r; - bool type_a, type_b; - - assert(argc >= 0); - assert(argv); - - while ((c = getopt_long(argc, argv, "ho:", options, NULL)) >= 0) - switch(c) { - case 'h': - help(); - return 0 /* done */; - - case ARG_VERSION: - return version(); - - case ARG_URL: - if (arg_url) { - log_error("cannot currently set more than one --url"); - return -EINVAL; - } - - arg_url = optarg; - break; - - case ARG_GETTER: - if (arg_getter) { - log_error("cannot currently use --getter more than once"); - return -EINVAL; - } - - arg_getter = optarg; - break; - - case ARG_LISTEN_RAW: - if (arg_listen_raw) { - log_error("cannot currently use --listen-raw more than once"); - return -EINVAL; - } - - arg_listen_raw = optarg; - break; - - case ARG_LISTEN_HTTP: - if (arg_listen_http || http_socket >= 0) { - log_error("cannot currently use --listen-http more than once"); - return -EINVAL; - } - - r = negative_fd(optarg); - if (r >= 0) - http_socket = r; - else - arg_listen_http = optarg; - break; - - case ARG_LISTEN_HTTPS: - if (arg_listen_https || https_socket >= 0) { - log_error("cannot currently use --listen-https more than once"); - return -EINVAL; - } - - r = negative_fd(optarg); - if (r >= 0) - https_socket = r; - else - arg_listen_https = optarg; - - break; - - case ARG_KEY: - if (arg_key) { - log_error("Key file specified twice"); - return -EINVAL; - } - - arg_key = strdup(optarg); - if (!arg_key) - return log_oom(); - - break; - - case ARG_CERT: - if (arg_cert) { - log_error("Certificate file specified twice"); - return -EINVAL; - } - - arg_cert = strdup(optarg); - if (!arg_cert) - return log_oom(); - - break; - - case ARG_TRUST: - if (arg_trust || arg_trust_all) { - log_error("Confusing trusted CA configuration"); - return -EINVAL; - } - - if (streq(optarg, "all")) - arg_trust_all = true; - else { -#ifdef HAVE_GNUTLS - arg_trust = strdup(optarg); - if (!arg_trust) - return log_oom(); -#else - log_error("Option --trust is not available."); - return -EINVAL; -#endif - } - - break; - - case 'o': - if (arg_output) { - log_error("cannot use --output/-o more than once"); - return -EINVAL; - } - - arg_output = optarg; - break; - - case ARG_SPLIT_MODE: - arg_split_mode = journal_write_split_mode_from_string(optarg); - if (arg_split_mode == _JOURNAL_WRITE_SPLIT_INVALID) { - log_error("Invalid split mode: %s", optarg); - return -EINVAL; - } - break; - - case ARG_COMPRESS: - if (optarg) { - r = parse_boolean(optarg); - if (r < 0) { - log_error("Failed to parse --compress= parameter."); - return -EINVAL; - } - - arg_compress = !!r; - } else - arg_compress = true; - - break; - - case ARG_SEAL: - if (optarg) { - r = parse_boolean(optarg); - if (r < 0) { - log_error("Failed to parse --seal= parameter."); - return -EINVAL; - } - - arg_seal = !!r; - } else - arg_seal = true; - - break; - - case ARG_GNUTLS_LOG: { -#ifdef HAVE_GNUTLS - const char* p = optarg; - for (;;) { - _cleanup_free_ char *word = NULL; - - r = extract_first_word(&p, &word, ",", 0); - if (r < 0) - return log_error_errno(r, "Failed to parse --gnutls-log= argument: %m"); - - if (r == 0) - break; - - if (strv_push(&arg_gnutls_log, word) < 0) - return log_oom(); - - word = NULL; - } - break; -#else - log_error("Option --gnutls-log is not available."); - return -EINVAL; -#endif - } - - case '?': - return -EINVAL; - - default: - assert_not_reached("Unknown option code."); - } - - if (optind < argc) - arg_files = argv + optind; - - type_a = arg_getter || !strv_isempty(arg_files); - type_b = arg_url - || arg_listen_raw - || arg_listen_http || arg_listen_https - || sd_listen_fds(false) > 0; - if (type_a && type_b) { - log_error("Cannot use file input or --getter with " - "--arg-listen-... or socket activation."); - return -EINVAL; - } - if (type_a) { - if (!arg_output) { - log_error("Option --output must be specified with file input or --getter."); - return -EINVAL; - } - - arg_split_mode = JOURNAL_WRITE_SPLIT_NONE; - } - - if (arg_split_mode == JOURNAL_WRITE_SPLIT_NONE - && arg_output && is_dir(arg_output, true) > 0) { - log_error("For SplitMode=none, output must be a file."); - return -EINVAL; - } - - if (arg_split_mode == JOURNAL_WRITE_SPLIT_HOST - && arg_output && is_dir(arg_output, true) <= 0) { - log_error("For SplitMode=host, output must be a directory."); - return -EINVAL; - } - - log_debug("Full config: SplitMode=%s Key=%s Cert=%s Trust=%s", - journal_write_split_mode_to_string(arg_split_mode), - strna(arg_key), - strna(arg_cert), - strna(arg_trust)); - - return 1 /* work to do */; -} - -static int load_certificates(char **key, char **cert, char **trust) { - int r; - - r = read_full_file(arg_key ?: PRIV_KEY_FILE, key, NULL); - if (r < 0) - return log_error_errno(r, "Failed to read key from file '%s': %m", - arg_key ?: PRIV_KEY_FILE); - - r = read_full_file(arg_cert ?: CERT_FILE, cert, NULL); - if (r < 0) - return log_error_errno(r, "Failed to read certificate from file '%s': %m", - arg_cert ?: CERT_FILE); - - if (arg_trust_all) - log_info("Certificate checking disabled."); - else { - r = read_full_file(arg_trust ?: TRUST_FILE, trust, NULL); - if (r < 0) - return log_error_errno(r, "Failed to read CA certificate file '%s': %m", - arg_trust ?: TRUST_FILE); - } - - return 0; -} - -int main(int argc, char **argv) { - RemoteServer s = {}; - int r; - _cleanup_free_ char *key = NULL, *cert = NULL, *trust = NULL; - - log_show_color(true); - log_parse_environment(); - - r = parse_config(); - if (r < 0) - return EXIT_FAILURE; - - r = parse_argv(argc, argv); - if (r <= 0) - return r == 0 ? EXIT_SUCCESS : EXIT_FAILURE; - - - if (arg_listen_http || arg_listen_https) { - r = setup_gnutls_logger(arg_gnutls_log); - if (r < 0) - return EXIT_FAILURE; - } - - if (arg_listen_https || https_socket >= 0) - if (load_certificates(&key, &cert, &trust) < 0) - return EXIT_FAILURE; - - if (remoteserver_init(&s, key, cert, trust) < 0) - return EXIT_FAILURE; - - r = sd_event_set_watchdog(s.events, true); - if (r < 0) - log_error_errno(r, "Failed to enable watchdog: %m"); - else - log_debug("Watchdog is %s.", r > 0 ? "enabled" : "disabled"); - - log_debug("%s running as pid "PID_FMT, - program_invocation_short_name, getpid()); - sd_notify(false, - "READY=1\n" - "STATUS=Processing requests..."); - - while (s.active) { - r = sd_event_get_state(s.events); - if (r < 0) - break; - if (r == SD_EVENT_FINISHED) - break; - - r = sd_event_run(s.events, -1); - if (r < 0) { - log_error_errno(r, "Failed to run event loop: %m"); - break; - } - } - - sd_notifyf(false, - "STOPPING=1\n" - "STATUS=Shutting down after writing %" PRIu64 " entries...", s.event_count); - log_info("Finishing after writing %" PRIu64 " entries", s.event_count); - - server_destroy(&s); - - free(arg_key); - free(arg_cert); - free(arg_trust); - - return r >= 0 ? EXIT_SUCCESS : EXIT_FAILURE; -} diff --git a/src/grp-journal-remote/systemd-journal-remote/journal-remote.conf.in b/src/grp-journal-remote/systemd-journal-remote/journal-remote.conf.in deleted file mode 100644 index 7122d63362..0000000000 --- a/src/grp-journal-remote/systemd-journal-remote/journal-remote.conf.in +++ /dev/null @@ -1,6 +0,0 @@ -[Remote] -# Seal=false -# SplitMode=host -# ServerKeyFile=@CERTIFICATEROOT@/private/journal-remote.pem -# ServerCertificateFile=@CERTIFICATEROOT@/certs/journal-remote.pem -# TrustedCertificateFile=@CERTIFICATEROOT@/ca/trusted.pem diff --git a/src/grp-journal-remote/systemd-journal-remote/journal-remote.h b/src/grp-journal-remote/systemd-journal-remote/journal-remote.h deleted file mode 100644 index 2e8d5999b0..0000000000 --- a/src/grp-journal-remote/systemd-journal-remote/journal-remote.h +++ /dev/null @@ -1,52 +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 - -#include "basic/hashmap.h" -#include "journal-remote-parse.h" -#include "journal-remote-write.h" -#include "microhttpd-util.h" - -typedef struct MHDDaemonWrapper MHDDaemonWrapper; - -struct MHDDaemonWrapper { - uint64_t fd; - struct MHD_Daemon *daemon; - - sd_event_source *event; -}; - -struct RemoteServer { - RemoteSource **sources; - size_t sources_size; - size_t active; - - sd_event *events; - sd_event_source *sigterm_event, *sigint_event, *listen_event; - - Hashmap *writers; - Writer *_single_writer; - uint64_t event_count; - - bool check_trust; - Hashmap *daemons; -}; diff --git a/src/grp-journal-remote/systemd-journal-upload/Makefile b/src/grp-journal-remote/systemd-journal-upload/Makefile deleted file mode 100644 index c47fcf0128..0000000000 --- a/src/grp-journal-remote/systemd-journal-upload/Makefile +++ /dev/null @@ -1,54 +0,0 @@ -# -*- 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_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/grp-journal-remote/systemd-journal-upload/journal-upload-journal.c b/src/grp-journal-remote/systemd-journal-upload/journal-upload-journal.c deleted file mode 100644 index eddfc384fa..0000000000 --- a/src/grp-journal-remote/systemd-journal-upload/journal-upload-journal.c +++ /dev/null @@ -1,422 +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 "basic/alloc-util.h" -#include "journal-upload.h" -#include "basic/log.h" -#include "basic/utf8.h" -#include "basic/util.h" -#include - -/** - * Write up to size bytes to buf. Return negative on error, and number of - * bytes written otherwise. The last case is a kind of an error too. - */ -static ssize_t write_entry(char *buf, size_t size, Uploader *u) { - int r; - size_t pos = 0; - - assert(size <= SSIZE_MAX); - - for (;;) { - - switch(u->entry_state) { - case ENTRY_CURSOR: { - u->current_cursor = mfree(u->current_cursor); - - r = sd_journal_get_cursor(u->journal, &u->current_cursor); - if (r < 0) - return log_error_errno(r, "Failed to get cursor: %m"); - - r = snprintf(buf + pos, size - pos, - "__CURSOR=%s\n", u->current_cursor); - if (pos + r > size) - /* not enough space */ - return pos; - - u->entry_state++; - - if (pos + r == size) { - /* exactly one character short, but we don't need it */ - buf[size - 1] = '\n'; - return size; - } - - pos += r; - } /* fall through */ - - case ENTRY_REALTIME: { - usec_t realtime; - - r = sd_journal_get_realtime_usec(u->journal, &realtime); - if (r < 0) - return log_error_errno(r, "Failed to get realtime timestamp: %m"); - - r = snprintf(buf + pos, size - pos, - "__REALTIME_TIMESTAMP="USEC_FMT"\n", realtime); - if (r + pos > size) - /* not enough space */ - return pos; - - u->entry_state++; - - if (r + pos == size) { - /* exactly one character short, but we don't need it */ - buf[size - 1] = '\n'; - return size; - } - - pos += r; - } /* fall through */ - - case ENTRY_MONOTONIC: { - usec_t monotonic; - sd_id128_t boot_id; - - r = sd_journal_get_monotonic_usec(u->journal, &monotonic, &boot_id); - if (r < 0) - return log_error_errno(r, "Failed to get monotonic timestamp: %m"); - - r = snprintf(buf + pos, size - pos, - "__MONOTONIC_TIMESTAMP="USEC_FMT"\n", monotonic); - if (r + pos > size) - /* not enough space */ - return pos; - - u->entry_state++; - - if (r + pos == size) { - /* exactly one character short, but we don't need it */ - buf[size - 1] = '\n'; - return size; - } - - pos += r; - } /* fall through */ - - case ENTRY_BOOT_ID: { - sd_id128_t boot_id; - char sid[33]; - - r = sd_journal_get_monotonic_usec(u->journal, NULL, &boot_id); - if (r < 0) - return log_error_errno(r, "Failed to get monotonic timestamp: %m"); - - r = snprintf(buf + pos, size - pos, - "_BOOT_ID=%s\n", sd_id128_to_string(boot_id, sid)); - if (r + pos > size) - /* not enough space */ - return pos; - - u->entry_state++; - - if (r + pos == size) { - /* exactly one character short, but we don't need it */ - buf[size - 1] = '\n'; - return size; - } - - pos += r; - } /* fall through */ - - case ENTRY_NEW_FIELD: { - u->field_pos = 0; - - r = sd_journal_enumerate_data(u->journal, - &u->field_data, - &u->field_length); - if (r < 0) - return log_error_errno(r, "Failed to move to next field in entry: %m"); - else if (r == 0) { - u->entry_state = ENTRY_OUTRO; - continue; - } - - if (!utf8_is_printable_newline(u->field_data, - u->field_length, false)) { - u->entry_state = ENTRY_BINARY_FIELD_START; - continue; - } - - u->entry_state++; - } /* fall through */ - - case ENTRY_TEXT_FIELD: - case ENTRY_BINARY_FIELD: { - bool done; - size_t tocopy; - - done = size - pos > u->field_length - u->field_pos; - if (done) - tocopy = u->field_length - u->field_pos; - else - tocopy = size - pos; - - memcpy(buf + pos, - (char*) u->field_data + u->field_pos, - tocopy); - - if (done) { - buf[pos + tocopy] = '\n'; - pos += tocopy + 1; - u->entry_state = ENTRY_NEW_FIELD; - continue; - } else { - u->field_pos += tocopy; - return size; - } - } - - case ENTRY_BINARY_FIELD_START: { - const char *c; - size_t len; - - c = memchr(u->field_data, '=', u->field_length); - if (!c || c == u->field_data) { - log_error("Invalid field."); - return -EINVAL; - } - - len = c - (const char*)u->field_data; - - /* need space for label + '\n' */ - if (size - pos < len + 1) - return pos; - - memcpy(buf + pos, u->field_data, len); - buf[pos + len] = '\n'; - pos += len + 1; - - u->field_pos = len + 1; - u->entry_state++; - } /* fall through */ - - case ENTRY_BINARY_FIELD_SIZE: { - uint64_t le64; - - /* need space for uint64_t */ - if (size - pos < 8) - return pos; - - le64 = htole64(u->field_length - u->field_pos); - memcpy(buf + pos, &le64, 8); - pos += 8; - - u->entry_state++; - continue; - } - - case ENTRY_OUTRO: - /* need space for '\n' */ - if (size - pos < 1) - return pos; - - buf[pos++] = '\n'; - u->entry_state++; - u->entries_sent++; - - return pos; - - default: - assert_not_reached("WTF?"); - } - } - assert_not_reached("WTF?"); -} - -static inline void check_update_watchdog(Uploader *u) { - usec_t after; - usec_t elapsed_time; - - if (u->watchdog_usec <= 0) - return; - - after = now(CLOCK_MONOTONIC); - elapsed_time = usec_sub(after, u->watchdog_timestamp); - if (elapsed_time > u->watchdog_usec / 2) { - log_debug("Update watchdog timer"); - sd_notify(false, "WATCHDOG=1"); - u->watchdog_timestamp = after; - } -} - -static size_t journal_input_callback(void *buf, size_t size, size_t nmemb, void *userp) { - Uploader *u = userp; - int r; - sd_journal *j; - size_t filled = 0; - ssize_t w; - - assert(u); - assert(nmemb <= SSIZE_MAX / size); - - check_update_watchdog(u); - - j = u->journal; - - while (j && filled < size * nmemb) { - if (u->entry_state == ENTRY_DONE) { - r = sd_journal_next(j); - if (r < 0) { - log_error_errno(r, "Failed to move to next entry in journal: %m"); - return CURL_READFUNC_ABORT; - } else if (r == 0) { - if (u->input_event) - log_debug("No more entries, waiting for journal."); - else { - log_info("No more entries, closing journal."); - close_journal_input(u); - } - - u->uploading = false; - - break; - } - - u->entry_state = ENTRY_CURSOR; - } - - w = write_entry((char*)buf + filled, size * nmemb - filled, u); - if (w < 0) - return CURL_READFUNC_ABORT; - filled += w; - - if (filled == 0) { - log_error("Buffer space is too small to write entry."); - return CURL_READFUNC_ABORT; - } else if (u->entry_state != ENTRY_DONE) - /* This means that all available space was used up */ - break; - - log_debug("Entry %zu (%s) has been uploaded.", - u->entries_sent, u->current_cursor); - } - - return filled; -} - -void close_journal_input(Uploader *u) { - assert(u); - - if (u->journal) { - log_debug("Closing journal input."); - - sd_journal_close(u->journal); - u->journal = NULL; - } - u->timeout = 0; -} - -static int process_journal_input(Uploader *u, int skip) { - int r; - - if (u->uploading) - return 0; - - r = sd_journal_next_skip(u->journal, skip); - if (r < 0) - return log_error_errno(r, "Failed to skip to next entry: %m"); - else if (r < skip) - return 0; - - /* have data */ - u->entry_state = ENTRY_CURSOR; - return start_upload(u, journal_input_callback, u); -} - -int check_journal_input(Uploader *u) { - if (u->input_event) { - int r; - - r = sd_journal_process(u->journal); - if (r < 0) { - log_error_errno(r, "Failed to process journal: %m"); - close_journal_input(u); - return r; - } - - if (r == SD_JOURNAL_NOP) - return 0; - } - - return process_journal_input(u, 1); -} - -static int dispatch_journal_input(sd_event_source *event, - int fd, - uint32_t revents, - void *userp) { - Uploader *u = userp; - - assert(u); - - if (u->uploading) - return 0; - - log_debug("Detected journal input, checking for new data."); - return check_journal_input(u); -} - -int open_journal_for_upload(Uploader *u, - sd_journal *j, - const char *cursor, - bool after_cursor, - bool follow) { - int fd, r, events; - - u->journal = j; - - sd_journal_set_data_threshold(j, 0); - - if (follow) { - fd = sd_journal_get_fd(j); - if (fd < 0) - return log_error_errno(fd, "sd_journal_get_fd failed: %m"); - - events = sd_journal_get_events(j); - - r = sd_journal_reliable_fd(j); - assert(r >= 0); - if (r > 0) - u->timeout = -1; - else - u->timeout = JOURNAL_UPLOAD_POLL_TIMEOUT; - - r = sd_event_add_io(u->events, &u->input_event, - fd, events, dispatch_journal_input, u); - if (r < 0) - return log_error_errno(r, "Failed to register input event: %m"); - - log_debug("Listening for journal events on fd:%d, timeout %d", - fd, u->timeout == (uint64_t) -1 ? -1 : (int) u->timeout); - } else - log_debug("Not listening for journal events."); - - if (cursor) { - r = sd_journal_seek_cursor(j, cursor); - if (r < 0) - return log_error_errno(r, "Failed to seek to cursor %s: %m", - cursor); - } - - return process_journal_input(u, 1 + !!after_cursor); -} diff --git a/src/grp-journal-remote/systemd-journal-upload/journal-upload.c b/src/grp-journal-remote/systemd-journal-upload/journal-upload.c deleted file mode 100644 index 643f44cd86..0000000000 --- a/src/grp-journal-remote/systemd-journal-upload/journal-upload.c +++ /dev/null @@ -1,880 +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 - -#include "basic/alloc-util.h" -#include "shared/conf-parser.h" -#include "basic/def.h" -#include "basic/fd-util.h" -#include "basic/fileio.h" -#include "basic/formats-util.h" -#include "basic/glob-util.h" -#include "journal-upload.h" -#include "basic/log.h" -#include "basic/mkdir.h" -#include "basic/parse-util.h" -#include "basic/sigbus.h" -#include "basic/signal-util.h" -#include "basic/string-util.h" -#include "basic/util.h" - -#define PRIV_KEY_FILE CERTIFICATE_ROOT "/private/journal-upload.pem" -#define CERT_FILE CERTIFICATE_ROOT "/certs/journal-upload.pem" -#define TRUST_FILE CERTIFICATE_ROOT "/ca/trusted.pem" -#define DEFAULT_PORT 19532 - -static const char* arg_url = NULL; -static const char *arg_key = NULL; -static const char *arg_cert = NULL; -static const char *arg_trust = NULL; -static const char *arg_directory = NULL; -static char **arg_file = NULL; -static const char *arg_cursor = NULL; -static bool arg_after_cursor = false; -static int arg_journal_type = 0; -static const char *arg_machine = NULL; -static bool arg_merge = false; -static int arg_follow = -1; -static const char *arg_save_state = NULL; - -static void close_fd_input(Uploader *u); - -#define SERVER_ANSWER_KEEP 2048 - -#define STATE_FILE "/var/lib/systemd/journal-upload/state" - -#define easy_setopt(curl, opt, value, level, cmd) \ - do { \ - code = curl_easy_setopt(curl, opt, value); \ - if (code) { \ - log_full(level, \ - "curl_easy_setopt " #opt " failed: %s", \ - curl_easy_strerror(code)); \ - cmd; \ - } \ - } while (0) - -static size_t output_callback(char *buf, - size_t size, - size_t nmemb, - void *userp) { - Uploader *u = userp; - - assert(u); - - log_debug("The server answers (%zu bytes): %.*s", - size*nmemb, (int)(size*nmemb), buf); - - if (nmemb && !u->answer) { - u->answer = strndup(buf, size*nmemb); - if (!u->answer) - log_warning_errno(ENOMEM, "Failed to store server answer (%zu bytes): %m", - size*nmemb); - } - - return size * nmemb; -} - -static int check_cursor_updating(Uploader *u) { - _cleanup_free_ char *temp_path = NULL; - _cleanup_fclose_ FILE *f = NULL; - int r; - - if (!u->state_file) - return 0; - - r = mkdir_parents(u->state_file, 0755); - if (r < 0) - return log_error_errno(r, "Cannot create parent directory of state file %s: %m", - u->state_file); - - r = fopen_temporary(u->state_file, &f, &temp_path); - if (r < 0) - return log_error_errno(r, "Cannot save state to %s: %m", - u->state_file); - unlink(temp_path); - - return 0; -} - -static int update_cursor_state(Uploader *u) { - _cleanup_free_ char *temp_path = NULL; - _cleanup_fclose_ FILE *f = NULL; - int r; - - if (!u->state_file || !u->last_cursor) - return 0; - - r = fopen_temporary(u->state_file, &f, &temp_path); - if (r < 0) - goto fail; - - fprintf(f, - "# This is private data. Do not parse.\n" - "LAST_CURSOR=%s\n", - u->last_cursor); - - r = fflush_and_check(f); - if (r < 0) - goto fail; - - if (rename(temp_path, u->state_file) < 0) { - r = -errno; - goto fail; - } - - return 0; - -fail: - if (temp_path) - (void) unlink(temp_path); - - (void) unlink(u->state_file); - - return log_error_errno(r, "Failed to save state %s: %m", u->state_file); -} - -static int load_cursor_state(Uploader *u) { - int r; - - if (!u->state_file) - return 0; - - r = parse_env_file(u->state_file, NEWLINE, - "LAST_CURSOR", &u->last_cursor, - NULL); - - if (r == -ENOENT) - log_debug("State file %s is not present.", u->state_file); - else if (r < 0) - return log_error_errno(r, "Failed to read state file %s: %m", - u->state_file); - else - log_debug("Last cursor was %s", u->last_cursor); - - return 0; -} - - - -int start_upload(Uploader *u, - size_t (*input_callback)(void *ptr, - size_t size, - size_t nmemb, - void *userdata), - void *data) { - CURLcode code; - - assert(u); - assert(input_callback); - - if (!u->header) { - struct curl_slist *h; - - h = curl_slist_append(NULL, "Content-Type: application/vnd.fdo.journal"); - if (!h) - return log_oom(); - - h = curl_slist_append(h, "Transfer-Encoding: chunked"); - if (!h) { - curl_slist_free_all(h); - return log_oom(); - } - - h = curl_slist_append(h, "Accept: text/plain"); - if (!h) { - curl_slist_free_all(h); - return log_oom(); - } - - u->header = h; - } - - if (!u->easy) { - CURL *curl; - - curl = curl_easy_init(); - if (!curl) { - log_error("Call to curl_easy_init failed."); - return -ENOSR; - } - - /* tell it to POST to the URL */ - easy_setopt(curl, CURLOPT_POST, 1L, - LOG_ERR, return -EXFULL); - - easy_setopt(curl, CURLOPT_ERRORBUFFER, u->error, - LOG_ERR, return -EXFULL); - - /* set where to write to */ - easy_setopt(curl, CURLOPT_WRITEFUNCTION, output_callback, - LOG_ERR, return -EXFULL); - - easy_setopt(curl, CURLOPT_WRITEDATA, data, - LOG_ERR, return -EXFULL); - - /* set where to read from */ - easy_setopt(curl, CURLOPT_READFUNCTION, input_callback, - LOG_ERR, return -EXFULL); - - easy_setopt(curl, CURLOPT_READDATA, data, - LOG_ERR, return -EXFULL); - - /* use our special own mime type and chunked transfer */ - easy_setopt(curl, CURLOPT_HTTPHEADER, u->header, - LOG_ERR, return -EXFULL); - - if (_unlikely_(log_get_max_level() >= LOG_DEBUG)) - /* enable verbose for easier tracing */ - easy_setopt(curl, CURLOPT_VERBOSE, 1L, LOG_WARNING, ); - - easy_setopt(curl, CURLOPT_USERAGENT, - "systemd-journal-upload " PACKAGE_STRING, - LOG_WARNING, ); - - if (arg_key || startswith(u->url, "https://")) { - easy_setopt(curl, CURLOPT_SSLKEY, arg_key ?: PRIV_KEY_FILE, - LOG_ERR, return -EXFULL); - easy_setopt(curl, CURLOPT_SSLCERT, arg_cert ?: CERT_FILE, - LOG_ERR, return -EXFULL); - } - - if (streq_ptr(arg_trust, "all")) - easy_setopt(curl, CURLOPT_SSL_VERIFYPEER, 0, - LOG_ERR, return -EUCLEAN); - else if (arg_trust || startswith(u->url, "https://")) - easy_setopt(curl, CURLOPT_CAINFO, arg_trust ?: TRUST_FILE, - LOG_ERR, return -EXFULL); - - if (arg_key || arg_trust) - easy_setopt(curl, CURLOPT_SSLVERSION, CURL_SSLVERSION_TLSv1, - LOG_WARNING, ); - - u->easy = curl; - } else { - /* truncate the potential old error message */ - u->error[0] = '\0'; - - free(u->answer); - u->answer = 0; - } - - /* upload to this place */ - code = curl_easy_setopt(u->easy, CURLOPT_URL, u->url); - if (code) { - log_error("curl_easy_setopt CURLOPT_URL failed: %s", - curl_easy_strerror(code)); - return -EXFULL; - } - - u->uploading = true; - - return 0; -} - -static size_t fd_input_callback(void *buf, size_t size, size_t nmemb, void *userp) { - Uploader *u = userp; - - ssize_t r; - - assert(u); - assert(nmemb <= SSIZE_MAX / size); - - if (u->input < 0) - return 0; - - r = read(u->input, buf, size * nmemb); - log_debug("%s: allowed %zu, read %zd", __func__, size*nmemb, r); - - if (r > 0) - return r; - - u->uploading = false; - if (r == 0) { - log_debug("Reached EOF"); - close_fd_input(u); - return 0; - } else { - log_error_errno(errno, "Aborting transfer after read error on input: %m."); - return CURL_READFUNC_ABORT; - } -} - -static void close_fd_input(Uploader *u) { - assert(u); - - if (u->input >= 0) - close_nointr(u->input); - u->input = -1; - u->timeout = 0; -} - -static int dispatch_fd_input(sd_event_source *event, - int fd, - uint32_t revents, - void *userp) { - Uploader *u = userp; - - assert(u); - assert(fd >= 0); - - if (revents & EPOLLHUP) { - log_debug("Received HUP"); - close_fd_input(u); - return 0; - } - - if (!(revents & EPOLLIN)) { - log_warning("Unexpected poll event %"PRIu32".", revents); - return -EINVAL; - } - - if (u->uploading) { - log_warning("dispatch_fd_input called when uploading, ignoring."); - return 0; - } - - return start_upload(u, fd_input_callback, u); -} - -static int open_file_for_upload(Uploader *u, const char *filename) { - int fd, r = 0; - - if (streq(filename, "-")) - fd = STDIN_FILENO; - else { - fd = open(filename, O_RDONLY|O_CLOEXEC|O_NOCTTY); - if (fd < 0) - return log_error_errno(errno, "Failed to open %s: %m", filename); - } - - u->input = fd; - - if (arg_follow) { - r = sd_event_add_io(u->events, &u->input_event, - fd, EPOLLIN, dispatch_fd_input, u); - if (r < 0) { - if (r != -EPERM || arg_follow > 0) - return log_error_errno(r, "Failed to register input event: %m"); - - /* Normal files should just be consumed without polling. */ - r = start_upload(u, fd_input_callback, u); - } - } - - return r; -} - -static int dispatch_sigterm(sd_event_source *event, - const struct signalfd_siginfo *si, - void *userdata) { - Uploader *u = userdata; - - assert(u); - - log_received_signal(LOG_INFO, si); - - close_fd_input(u); - close_journal_input(u); - - sd_event_exit(u->events, 0); - return 0; -} - -static int setup_signals(Uploader *u) { - int r; - - assert(u); - - assert_se(sigprocmask_many(SIG_SETMASK, NULL, SIGINT, SIGTERM, -1) >= 0); - - r = sd_event_add_signal(u->events, &u->sigterm_event, SIGTERM, dispatch_sigterm, u); - if (r < 0) - return r; - - r = sd_event_add_signal(u->events, &u->sigint_event, SIGINT, dispatch_sigterm, u); - if (r < 0) - return r; - - return 0; -} - -static int setup_uploader(Uploader *u, const char *url, const char *state_file) { - int r; - const char *host, *proto = ""; - - assert(u); - assert(url); - - memzero(u, sizeof(Uploader)); - u->input = -1; - - if (!(host = startswith(url, "http://")) && !(host = startswith(url, "https://"))) { - host = url; - proto = "https://"; - } - - if (strchr(host, ':')) - u->url = strjoin(proto, url, "/upload", NULL); - else { - char *t; - size_t x; - - t = strdupa(url); - x = strlen(t); - while (x > 0 && t[x - 1] == '/') - t[x - 1] = '\0'; - - u->url = strjoin(proto, t, ":" STRINGIFY(DEFAULT_PORT), "/upload", NULL); - } - if (!u->url) - return log_oom(); - - u->state_file = state_file; - - r = sd_event_default(&u->events); - if (r < 0) - return log_error_errno(r, "sd_event_default failed: %m"); - - r = setup_signals(u); - if (r < 0) - return log_error_errno(r, "Failed to set up signals: %m"); - - (void) sd_watchdog_enabled(false, &u->watchdog_usec); - - return load_cursor_state(u); -} - -static void destroy_uploader(Uploader *u) { - assert(u); - - curl_easy_cleanup(u->easy); - curl_slist_free_all(u->header); - free(u->answer); - - free(u->last_cursor); - free(u->current_cursor); - - free(u->url); - - u->input_event = sd_event_source_unref(u->input_event); - - close_fd_input(u); - close_journal_input(u); - - sd_event_source_unref(u->sigterm_event); - sd_event_source_unref(u->sigint_event); - sd_event_unref(u->events); -} - -static int perform_upload(Uploader *u) { - CURLcode code; - long status; - - assert(u); - - u->watchdog_timestamp = now(CLOCK_MONOTONIC); - code = curl_easy_perform(u->easy); - if (code) { - if (u->error[0]) - log_error("Upload to %s failed: %.*s", - u->url, (int) sizeof(u->error), u->error); - else - log_error("Upload to %s failed: %s", - u->url, curl_easy_strerror(code)); - return -EIO; - } - - code = curl_easy_getinfo(u->easy, CURLINFO_RESPONSE_CODE, &status); - if (code) { - log_error("Failed to retrieve response code: %s", - curl_easy_strerror(code)); - return -EUCLEAN; - } - - if (status >= 300) { - log_error("Upload to %s failed with code %ld: %s", - u->url, status, strna(u->answer)); - return -EIO; - } else if (status < 200) { - log_error("Upload to %s finished with unexpected code %ld: %s", - u->url, status, strna(u->answer)); - return -EIO; - } else - log_debug("Upload finished successfully with code %ld: %s", - status, strna(u->answer)); - - free(u->last_cursor); - u->last_cursor = u->current_cursor; - u->current_cursor = NULL; - - return update_cursor_state(u); -} - -static int parse_config(void) { - const ConfigTableItem items[] = { - { "Upload", "URL", config_parse_string, 0, &arg_url }, - { "Upload", "ServerKeyFile", config_parse_path, 0, &arg_key }, - { "Upload", "ServerCertificateFile", config_parse_path, 0, &arg_cert }, - { "Upload", "TrustedCertificateFile", config_parse_path, 0, &arg_trust }, - {}}; - - return config_parse_many(PKGSYSCONFDIR "/journal-upload.conf", - CONF_PATHS_NULSTR("systemd/journal-upload.conf.d"), - "Upload\0", config_item_table_lookup, items, - false, NULL); -} - -static void help(void) { - printf("%s -u URL {FILE|-}...\n\n" - "Upload journal events to a remote server.\n\n" - " -h --help Show this help\n" - " --version Show package version\n" - " -u --url=URL Upload to this address (default port " - STRINGIFY(DEFAULT_PORT) ")\n" - " --key=FILENAME Specify key in PEM format (default:\n" - " \"" PRIV_KEY_FILE "\")\n" - " --cert=FILENAME Specify certificate in PEM format (default:\n" - " \"" CERT_FILE "\")\n" - " --trust=FILENAME|all Specify CA certificate or disable checking (default:\n" - " \"" TRUST_FILE "\")\n" - " --system Use the system journal\n" - " --user Use the user journal for the current user\n" - " -m --merge Use all available journals\n" - " -M --machine=CONTAINER Operate on local container\n" - " -D --directory=PATH Use journal files from directory\n" - " --file=PATH Use this journal file\n" - " --cursor=CURSOR Start at the specified cursor\n" - " --after-cursor=CURSOR Start after the specified cursor\n" - " --follow[=BOOL] Do [not] wait for input\n" - " --save-state[=FILE] Save uploaded cursors (default \n" - " " STATE_FILE ")\n" - " -h --help Show this help and exit\n" - " --version Print version string and exit\n" - , program_invocation_short_name); -} - -static int parse_argv(int argc, char *argv[]) { - enum { - ARG_VERSION = 0x100, - ARG_KEY, - ARG_CERT, - ARG_TRUST, - ARG_USER, - ARG_SYSTEM, - ARG_FILE, - ARG_CURSOR, - ARG_AFTER_CURSOR, - ARG_FOLLOW, - ARG_SAVE_STATE, - }; - - static const struct option options[] = { - { "help", no_argument, NULL, 'h' }, - { "version", no_argument, NULL, ARG_VERSION }, - { "url", required_argument, NULL, 'u' }, - { "key", required_argument, NULL, ARG_KEY }, - { "cert", required_argument, NULL, ARG_CERT }, - { "trust", required_argument, NULL, ARG_TRUST }, - { "system", no_argument, NULL, ARG_SYSTEM }, - { "user", no_argument, NULL, ARG_USER }, - { "merge", no_argument, NULL, 'm' }, - { "machine", required_argument, NULL, 'M' }, - { "directory", required_argument, NULL, 'D' }, - { "file", required_argument, NULL, ARG_FILE }, - { "cursor", required_argument, NULL, ARG_CURSOR }, - { "after-cursor", required_argument, NULL, ARG_AFTER_CURSOR }, - { "follow", optional_argument, NULL, ARG_FOLLOW }, - { "save-state", optional_argument, NULL, ARG_SAVE_STATE }, - {} - }; - - int c, r; - - assert(argc >= 0); - assert(argv); - - opterr = 0; - - while ((c = getopt_long(argc, argv, "hu:mM:D:", options, NULL)) >= 0) - switch(c) { - case 'h': - help(); - return 0 /* done */; - - case ARG_VERSION: - return version(); - - case 'u': - if (arg_url) { - log_error("cannot use more than one --url"); - return -EINVAL; - } - - arg_url = optarg; - break; - - case ARG_KEY: - if (arg_key) { - log_error("cannot use more than one --key"); - return -EINVAL; - } - - arg_key = optarg; - break; - - case ARG_CERT: - if (arg_cert) { - log_error("cannot use more than one --cert"); - return -EINVAL; - } - - arg_cert = optarg; - break; - - case ARG_TRUST: - if (arg_trust) { - log_error("cannot use more than one --trust"); - return -EINVAL; - } - - arg_trust = optarg; - break; - - case ARG_SYSTEM: - arg_journal_type |= SD_JOURNAL_SYSTEM; - break; - - case ARG_USER: - arg_journal_type |= SD_JOURNAL_CURRENT_USER; - break; - - case 'm': - arg_merge = true; - break; - - case 'M': - if (arg_machine) { - log_error("cannot use more than one --machine/-M"); - return -EINVAL; - } - - arg_machine = optarg; - break; - - case 'D': - if (arg_directory) { - log_error("cannot use more than one --directory/-D"); - return -EINVAL; - } - - arg_directory = optarg; - break; - - case ARG_FILE: - r = glob_extend(&arg_file, optarg); - if (r < 0) - return log_error_errno(r, "Failed to add paths: %m"); - break; - - case ARG_CURSOR: - if (arg_cursor) { - log_error("cannot use more than one --cursor/--after-cursor"); - return -EINVAL; - } - - arg_cursor = optarg; - break; - - case ARG_AFTER_CURSOR: - if (arg_cursor) { - log_error("cannot use more than one --cursor/--after-cursor"); - return -EINVAL; - } - - arg_cursor = optarg; - arg_after_cursor = true; - break; - - case ARG_FOLLOW: - if (optarg) { - r = parse_boolean(optarg); - if (r < 0) { - log_error("Failed to parse --follow= parameter."); - return -EINVAL; - } - - arg_follow = !!r; - } else - arg_follow = true; - - break; - - case ARG_SAVE_STATE: - arg_save_state = optarg ?: STATE_FILE; - break; - - case '?': - log_error("Unknown option %s.", argv[optind-1]); - return -EINVAL; - - case ':': - log_error("Missing argument to %s.", argv[optind-1]); - return -EINVAL; - - default: - assert_not_reached("Unhandled option code."); - } - - if (!arg_url) { - log_error("Required --url/-u option missing."); - return -EINVAL; - } - - if (!!arg_key != !!arg_cert) { - log_error("Options --key and --cert must be used together."); - return -EINVAL; - } - - if (optind < argc && (arg_directory || arg_file || arg_machine || arg_journal_type)) { - log_error("Input arguments make no sense with journal input."); - return -EINVAL; - } - - return 1; -} - -static int open_journal(sd_journal **j) { - int r; - - if (arg_directory) - r = sd_journal_open_directory(j, arg_directory, arg_journal_type); - else if (arg_file) - r = sd_journal_open_files(j, (const char**) arg_file, 0); - else if (arg_machine) - r = sd_journal_open_container(j, arg_machine, 0); - else - r = sd_journal_open(j, !arg_merge*SD_JOURNAL_LOCAL_ONLY + arg_journal_type); - if (r < 0) - log_error_errno(r, "Failed to open %s: %m", - arg_directory ? arg_directory : arg_file ? "files" : "journal"); - return r; -} - -int main(int argc, char **argv) { - Uploader u; - int r; - bool use_journal; - - log_show_color(true); - log_parse_environment(); - - r = parse_config(); - if (r < 0) - goto finish; - - r = parse_argv(argc, argv); - if (r <= 0) - goto finish; - - sigbus_install(); - - r = setup_uploader(&u, arg_url, arg_save_state); - if (r < 0) - goto cleanup; - - sd_event_set_watchdog(u.events, true); - - r = check_cursor_updating(&u); - if (r < 0) - goto cleanup; - - log_debug("%s running as pid "PID_FMT, - program_invocation_short_name, getpid()); - - use_journal = optind >= argc; - if (use_journal) { - sd_journal *j; - r = open_journal(&j); - if (r < 0) - goto finish; - r = open_journal_for_upload(&u, j, - arg_cursor ?: u.last_cursor, - arg_cursor ? arg_after_cursor : true, - !!arg_follow); - if (r < 0) - goto finish; - } - - sd_notify(false, - "READY=1\n" - "STATUS=Processing input..."); - - for (;;) { - r = sd_event_get_state(u.events); - if (r < 0) - break; - if (r == SD_EVENT_FINISHED) - break; - - if (use_journal) { - if (!u.journal) - break; - - r = check_journal_input(&u); - } else if (u.input < 0 && !use_journal) { - if (optind >= argc) - break; - - log_debug("Using %s as input.", argv[optind]); - r = open_file_for_upload(&u, argv[optind++]); - } - if (r < 0) - goto cleanup; - - if (u.uploading) { - r = perform_upload(&u); - if (r < 0) - break; - } - - r = sd_event_run(u.events, u.timeout); - if (r < 0) { - log_error_errno(r, "Failed to run event loop: %m"); - break; - } - } - -cleanup: - sd_notify(false, - "STOPPING=1\n" - "STATUS=Shutting down..."); - - destroy_uploader(&u); - -finish: - return r >= 0 ? EXIT_SUCCESS : EXIT_FAILURE; -} diff --git a/src/grp-journal-remote/systemd-journal-upload/journal-upload.conf.in b/src/grp-journal-remote/systemd-journal-upload/journal-upload.conf.in deleted file mode 100644 index c5670682e8..0000000000 --- a/src/grp-journal-remote/systemd-journal-upload/journal-upload.conf.in +++ /dev/null @@ -1,5 +0,0 @@ -[Upload] -# URL= -# ServerKeyFile=@CERTIFICATEROOT@/private/journal-upload.pem -# ServerCertificateFile=@CERTIFICATEROOT@/certs/journal-upload.pem -# TrustedCertificateFile=@CERTIFICATEROOT@/ca/trusted.pem diff --git a/src/grp-journal-remote/systemd-journal-upload/journal-upload.h b/src/grp-journal-remote/systemd-journal-upload/journal-upload.h deleted file mode 100644 index 0b0336c84b..0000000000 --- a/src/grp-journal-remote/systemd-journal-upload/journal-upload.h +++ /dev/null @@ -1,71 +0,0 @@ -#pragma once - -#include - -#include -#include -#include "basic/time-util.h" - -typedef enum { - ENTRY_CURSOR = 0, /* Nothing actually written yet. */ - ENTRY_REALTIME, - ENTRY_MONOTONIC, - ENTRY_BOOT_ID, - ENTRY_NEW_FIELD, /* In between fields. */ - ENTRY_TEXT_FIELD, /* In the middle of a text field. */ - ENTRY_BINARY_FIELD_START, /* Writing the name of a binary field. */ - ENTRY_BINARY_FIELD_SIZE, /* Writing the size of a binary field. */ - ENTRY_BINARY_FIELD, /* In the middle of a binary field. */ - ENTRY_OUTRO, /* Writing '\n' */ - ENTRY_DONE, /* Need to move to a new field. */ -} entry_state; - -typedef struct Uploader { - sd_event *events; - sd_event_source *sigint_event, *sigterm_event; - - char *url; - CURL *easy; - bool uploading; - char error[CURL_ERROR_SIZE]; - struct curl_slist *header; - char *answer; - - sd_event_source *input_event; - uint64_t timeout; - - /* fd stuff */ - int input; - - /* journal stuff */ - sd_journal* journal; - - entry_state entry_state; - const void *field_data; - size_t field_pos, field_length; - - /* general metrics */ - const char *state_file; - - size_t entries_sent; - char *last_cursor, *current_cursor; - usec_t watchdog_timestamp; - usec_t watchdog_usec; -} Uploader; - -#define JOURNAL_UPLOAD_POLL_TIMEOUT (10 * USEC_PER_SEC) - -int start_upload(Uploader *u, - size_t (*input_callback)(void *ptr, - size_t size, - size_t nmemb, - void *userdata), - void *data); - -int open_journal_for_upload(Uploader *u, - sd_journal *j, - const char *cursor, - bool after_cursor, - bool follow); -void close_journal_input(Uploader *u); -int check_journal_input(Uploader *u); diff --git a/src/grp-journal/Makefile b/src/grp-journal/Makefile index fcefb81581..046d93521f 100644 --- a/src/grp-journal/Makefile +++ b/src/grp-journal/Makefile @@ -167,4 +167,8 @@ tests += \ test-compress-benchmark endif # HAVE_COMPRESSION +at.subdirs += journalctl systemd-journald libjournal-core +at.subdirs += systemd-cat +#at.subdirs += grp-remote + include $(topsrcdir)/build-aux/Makefile.tail.mk diff --git a/src/grp-journal/grp-remote/.gitignore b/src/grp-journal/grp-remote/.gitignore new file mode 100644 index 0000000000..06847b65d4 --- /dev/null +++ b/src/grp-journal/grp-remote/.gitignore @@ -0,0 +1,2 @@ +/journal-remote.conf +/journal-upload.conf diff --git a/src/grp-journal/grp-remote/Makefile b/src/grp-journal/grp-remote/Makefile new file mode 100644 index 0000000000..689341d6ae --- /dev/null +++ b/src/grp-journal/grp-remote/Makefile @@ -0,0 +1,30 @@ +# -*- 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 + +at.subdirs += systemd-journal-gatewayd +at.subdirs += systemd-journal-remote +at.subdirs += systemd-journal-upload + +include $(topsrcdir)/build-aux/Makefile.tail.mk diff --git a/src/grp-journal/grp-remote/browse.html b/src/grp-journal/grp-remote/browse.html new file mode 100644 index 0000000000..32848c7673 --- /dev/null +++ b/src/grp-journal/grp-remote/browse.html @@ -0,0 +1,544 @@ + + + + Journal + + + + + + + +

+ +
+
+
+
+
+
+ +
+ +      + Only current boot +
+ +
+ +
+ +
+ + + + +      + + +
+ +
+ g: First Page      + ←, k, BACKSPACE: Previous Page      + →, j, SPACE: Next Page      + G: Last Page      + +: More entries      + -: Fewer entries +
+ + + + diff --git a/src/grp-journal/grp-remote/log-generator.py b/src/grp-journal/grp-remote/log-generator.py new file mode 100755 index 0000000000..fd6964e758 --- /dev/null +++ b/src/grp-journal/grp-remote/log-generator.py @@ -0,0 +1,76 @@ +#!/usr/bin/python +from __future__ import print_function +import sys +import argparse + +PARSER = argparse.ArgumentParser() +PARSER.add_argument('n', type=int) +PARSER.add_argument('--dots', action='store_true') +PARSER.add_argument('--data-size', type=int, default=4000) +PARSER.add_argument('--data-type', choices={'random', 'simple'}) +OPTIONS = PARSER.parse_args() + +template = """\ +__CURSOR=s=6863c726210b4560b7048889d8ada5c5;i=3e931;b=f446871715504074bf7049ef0718fa93;m={m:x};t=4fd05c +__REALTIME_TIMESTAMP={realtime_ts} +__MONOTONIC_TIMESTAMP={monotonic_ts} +_BOOT_ID=f446871715504074bf7049ef0718fa93 +_TRANSPORT=syslog +PRIORITY={priority} +SYSLOG_FACILITY={facility} +SYSLOG_IDENTIFIER=/USR/SBIN/CRON +MESSAGE={message} +_UID=0 +_GID=0 +_MACHINE_ID=69121ca41d12c1b69a7960174c27b618 +_HOSTNAME=hostname +SYSLOG_PID=25721 +_PID=25721 +_SOURCE_REALTIME_TIMESTAMP={source_realtime_ts} +DATA={data} +""" + +m = 0x198603b12d7 +realtime_ts = 1404101101501873 +monotonic_ts = 1753961140951 +source_realtime_ts = 1404101101483516 +priority = 3 +facility = 6 + +src = open('/dev/urandom', 'rb') + +bytes = 0 +counter = 0 + +for i in range(OPTIONS.n): + message = repr(src.read(2000)) + if OPTIONS.data_type == 'random': + data = repr(src.read(OPTIONS.data_size)) + else: + # keep the pattern non-repeating so we get a different blob every time + data = '{:0{}}'.format(counter, OPTIONS.data_size) + counter += 1 + + entry = template.format(m=m, + realtime_ts=realtime_ts, + monotonic_ts=monotonic_ts, + source_realtime_ts=source_realtime_ts, + priority=priority, + facility=facility, + message=message, + data=data) + m += 1 + realtime_ts += 1 + monotonic_ts += 1 + source_realtime_ts += 1 + + bytes += len(entry) + + print(entry) + + if OPTIONS.dots: + print('.', file=sys.stderr, end='', flush=True) + +if OPTIONS.dots: + print(file=sys.stderr) +print('Wrote {} bytes'.format(bytes), file=sys.stderr) diff --git a/src/grp-journal/grp-remote/microhttpd-util.c b/src/grp-journal/grp-remote/microhttpd-util.c new file mode 100644 index 0000000000..99d6f32a2a --- /dev/null +++ b/src/grp-journal/grp-remote/microhttpd-util.c @@ -0,0 +1,327 @@ +/*** + This file is part of systemd. + + Copyright 2012 Lennart Poettering + Copyright 2012 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 + +#ifdef HAVE_GNUTLS +#include +#include +#endif + +#include "basic/alloc-util.h" +#include "basic/log.h" +#include "basic/macro.h" +#include "microhttpd-util.h" +#include "basic/string-util.h" +#include "basic/strv.h" +#include "basic/util.h" + +void microhttpd_logger(void *arg, const char *fmt, va_list ap) { + char *f; + + f = strjoina("microhttpd: ", fmt); + + DISABLE_WARNING_FORMAT_NONLITERAL; + log_internalv(LOG_INFO, 0, NULL, 0, NULL, f, ap); + REENABLE_WARNING; +} + + +static int mhd_respond_internal(struct MHD_Connection *connection, + enum MHD_RequestTerminationCode code, + char *buffer, + size_t size, + enum MHD_ResponseMemoryMode mode) { + struct MHD_Response *response; + int r; + + assert(connection); + + response = MHD_create_response_from_buffer(size, buffer, mode); + if (!response) + return MHD_NO; + + log_debug("Queing response %u: %s", code, buffer); + MHD_add_response_header(response, "Content-Type", "text/plain"); + r = MHD_queue_response(connection, code, response); + MHD_destroy_response(response); + + return r; +} + +int mhd_respond(struct MHD_Connection *connection, + enum MHD_RequestTerminationCode code, + const char *message) { + + return mhd_respond_internal(connection, code, + (char*) message, strlen(message), + MHD_RESPMEM_PERSISTENT); +} + +int mhd_respond_oom(struct MHD_Connection *connection) { + return mhd_respond(connection, MHD_HTTP_SERVICE_UNAVAILABLE, "Out of memory.\n"); +} + +int mhd_respondf(struct MHD_Connection *connection, + enum MHD_RequestTerminationCode code, + const char *format, ...) { + + char *m; + int r; + va_list ap; + + assert(connection); + assert(format); + + va_start(ap, format); + r = vasprintf(&m, format, ap); + va_end(ap); + + if (r < 0) + return respond_oom(connection); + + return mhd_respond_internal(connection, code, m, r, MHD_RESPMEM_MUST_FREE); +} + +#ifdef HAVE_GNUTLS + +static struct { + const char *const names[4]; + int level; + bool enabled; +} gnutls_log_map[] = { + { {"0"}, LOG_DEBUG }, + { {"1", "audit"}, LOG_WARNING, true}, /* gnutls session audit */ + { {"2", "assert"}, LOG_DEBUG }, /* gnutls assert log */ + { {"3", "hsk", "ext"}, LOG_DEBUG }, /* gnutls handshake log */ + { {"4", "rec"}, LOG_DEBUG }, /* gnutls record log */ + { {"5", "dtls"}, LOG_DEBUG }, /* gnutls DTLS log */ + { {"6", "buf"}, LOG_DEBUG }, + { {"7", "write", "read"}, LOG_DEBUG }, + { {"8"}, LOG_DEBUG }, + { {"9", "enc", "int"}, LOG_DEBUG }, +}; + +static void log_func_gnutls(int level, const char *message) { + assert_se(message); + + if (0 <= level && level < (int) ELEMENTSOF(gnutls_log_map)) { + if (gnutls_log_map[level].enabled) + log_internal(gnutls_log_map[level].level, 0, NULL, 0, NULL, "gnutls %d/%s: %s", level, gnutls_log_map[level].names[1], message); + } else { + log_debug("Received GNUTLS message with unknown level %d.", level); + log_internal(LOG_DEBUG, 0, NULL, 0, NULL, "gnutls: %s", message); + } +} + +static void log_reset_gnutls_level(void) { + int i; + + for (i = ELEMENTSOF(gnutls_log_map) - 1; i >= 0; i--) + if (gnutls_log_map[i].enabled) { + log_debug("Setting gnutls log level to %d", i); + gnutls_global_set_log_level(i); + break; + } +} + +static int log_enable_gnutls_category(const char *cat) { + unsigned i; + + if (streq(cat, "all")) { + for (i = 0; i < ELEMENTSOF(gnutls_log_map); i++) + gnutls_log_map[i].enabled = true; + log_reset_gnutls_level(); + return 0; + } else + for (i = 0; i < ELEMENTSOF(gnutls_log_map); i++) + if (strv_contains((char**)gnutls_log_map[i].names, cat)) { + gnutls_log_map[i].enabled = true; + log_reset_gnutls_level(); + return 0; + } + log_error("No such log category: %s", cat); + return -EINVAL; +} + +int setup_gnutls_logger(char **categories) { + char **cat; + int r; + + gnutls_global_set_log_function(log_func_gnutls); + + if (categories) { + STRV_FOREACH(cat, categories) { + r = log_enable_gnutls_category(*cat); + if (r < 0) + return r; + } + } else + log_reset_gnutls_level(); + + return 0; +} + +static int verify_cert_authorized(gnutls_session_t session) { + unsigned status; + gnutls_certificate_type_t type; + gnutls_datum_t out; + int r; + + r = gnutls_certificate_verify_peers2(session, &status); + if (r < 0) + return log_error_errno(r, "gnutls_certificate_verify_peers2 failed: %m"); + + type = gnutls_certificate_type_get(session); + r = gnutls_certificate_verification_status_print(status, type, &out, 0); + if (r < 0) + return log_error_errno(r, "gnutls_certificate_verification_status_print failed: %m"); + + log_debug("Certificate status: %s", out.data); + gnutls_free(out.data); + + return status == 0 ? 0 : -EPERM; +} + +static int get_client_cert(gnutls_session_t session, gnutls_x509_crt_t *client_cert) { + const gnutls_datum_t *pcert; + unsigned listsize; + gnutls_x509_crt_t cert; + int r; + + assert(session); + assert(client_cert); + + pcert = gnutls_certificate_get_peers(session, &listsize); + if (!pcert || !listsize) { + log_error("Failed to retrieve certificate chain"); + return -EINVAL; + } + + r = gnutls_x509_crt_init(&cert); + if (r < 0) { + log_error("Failed to initialize client certificate"); + return r; + } + + /* Note that by passing values between 0 and listsize here, you + can get access to the CA's certs */ + r = gnutls_x509_crt_import(cert, &pcert[0], GNUTLS_X509_FMT_DER); + if (r < 0) { + log_error("Failed to import client certificate"); + gnutls_x509_crt_deinit(cert); + return r; + } + + *client_cert = cert; + return 0; +} + +static int get_auth_dn(gnutls_x509_crt_t client_cert, char **buf) { + size_t len = 0; + int r; + + assert(buf); + assert(*buf == NULL); + + r = gnutls_x509_crt_get_dn(client_cert, NULL, &len); + if (r != GNUTLS_E_SHORT_MEMORY_BUFFER) { + log_error("gnutls_x509_crt_get_dn failed"); + return r; + } + + *buf = malloc(len); + if (!*buf) + return log_oom(); + + gnutls_x509_crt_get_dn(client_cert, *buf, &len); + return 0; +} + +static inline void gnutls_x509_crt_deinitp(gnutls_x509_crt_t *p) { + gnutls_x509_crt_deinit(*p); +} + +int check_permissions(struct MHD_Connection *connection, int *code, char **hostname) { + const union MHD_ConnectionInfo *ci; + gnutls_session_t session; + _cleanup_(gnutls_x509_crt_deinitp) gnutls_x509_crt_t client_cert = NULL; + _cleanup_free_ char *buf = NULL; + int r; + + assert(connection); + assert(code); + + *code = 0; + + ci = MHD_get_connection_info(connection, + MHD_CONNECTION_INFO_GNUTLS_SESSION); + if (!ci) { + log_error("MHD_get_connection_info failed: session is unencrypted"); + *code = mhd_respond(connection, MHD_HTTP_FORBIDDEN, + "Encrypted connection is required"); + return -EPERM; + } + session = ci->tls_session; + assert(session); + + r = get_client_cert(session, &client_cert); + if (r < 0) { + *code = mhd_respond(connection, MHD_HTTP_UNAUTHORIZED, + "Authorization through certificate is required"); + return -EPERM; + } + + r = get_auth_dn(client_cert, &buf); + if (r < 0) { + *code = mhd_respond(connection, MHD_HTTP_UNAUTHORIZED, + "Failed to determine distinguished name from certificate"); + return -EPERM; + } + + log_debug("Connection from %s", buf); + + if (hostname) { + *hostname = buf; + buf = NULL; + } + + r = verify_cert_authorized(session); + if (r < 0) { + log_warning("Client is not authorized"); + *code = mhd_respond(connection, MHD_HTTP_UNAUTHORIZED, + "Client certificate not signed by recognized authority"); + } + return r; +} + +#else +int check_permissions(struct MHD_Connection *connection, int *code, char **hostname) { + return -EPERM; +} + +int setup_gnutls_logger(char **categories) { + if (categories) + log_notice("Ignoring specified gnutls logging categories — gnutls not available."); + return 0; +} +#endif diff --git a/src/grp-journal/grp-remote/microhttpd-util.h b/src/grp-journal/grp-remote/microhttpd-util.h new file mode 100644 index 0000000000..178e78f892 --- /dev/null +++ b/src/grp-journal/grp-remote/microhttpd-util.h @@ -0,0 +1,60 @@ +#pragma once + +/*** + This file is part of systemd. + + Copyright 2012 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 "basic/macro.h" + +/* Compatiblity with libmicrohttpd < 0.9.38 */ +#ifndef MHD_HTTP_NOT_ACCEPTABLE +#define MHD_HTTP_NOT_ACCEPTABLE MHD_HTTP_METHOD_NOT_ACCEPTABLE +#endif + +#if MHD_VERSION < 0x00094203 +#define MHD_create_response_from_fd_at_offset64 MHD_create_response_from_fd_at_offset +#endif + +void microhttpd_logger(void *arg, const char *fmt, va_list ap) _printf_(2, 0); + +/* respond_oom() must be usable with return, hence this form. */ +#define respond_oom(connection) log_oom(), mhd_respond_oom(connection) + +int mhd_respondf(struct MHD_Connection *connection, + unsigned code, + const char *format, ...) _printf_(3,4); + +int mhd_respond(struct MHD_Connection *connection, + unsigned code, + const char *message); + +int mhd_respond_oom(struct MHD_Connection *connection); + +int check_permissions(struct MHD_Connection *connection, int *code, char **hostname); + +/* Set gnutls internal logging function to a callback which uses our + * own logging framework. + * + * gnutls categories are additionally filtered by our internal log + * level, so it should be set fairly high to capture all potentially + * interesting events without overwhelming detail. + */ +int setup_gnutls_logger(char **categories); diff --git a/src/grp-journal/grp-remote/systemd-journal-gatewayd/Makefile b/src/grp-journal/grp-remote/systemd-journal-gatewayd/Makefile new file mode 100644 index 0000000000..05df34a71d --- /dev/null +++ b/src/grp-journal/grp-remote/systemd-journal-gatewayd/Makefile @@ -0,0 +1,68 @@ +# -*- 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),) +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 # HAVE_GNUTLS + +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 # HAVE_MICROHTTPD + +EXTRA_DIST += \ + units/systemd-journal-gatewayd.service.in + +include $(topsrcdir)/build-aux/Makefile.tail.mk diff --git a/src/grp-journal/grp-remote/systemd-journal-gatewayd/journal-gatewayd.c b/src/grp-journal/grp-remote/systemd-journal-gatewayd/journal-gatewayd.c new file mode 100644 index 0000000000..2e21f9ecb7 --- /dev/null +++ b/src/grp-journal/grp-remote/systemd-journal-gatewayd/journal-gatewayd.c @@ -0,0 +1,1077 @@ +/*** + 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 +#ifdef HAVE_GNUTLS +#include +#endif +#include +#include +#include +#include + +#include +#include +#include + +#include "basic/alloc-util.h" +#include "shared/bus-util.h" +#include "basic/fd-util.h" +#include "basic/fileio.h" +#include "basic/hostname-util.h" +#include "basic/log.h" +#include "shared/logs-show.h" +#include "microhttpd-util.h" +#include "basic/parse-util.h" +#include "basic/sigbus.h" +#include "basic/util.h" + +#define JOURNAL_WAIT_TIMEOUT (10*USEC_PER_SEC) + +static char *arg_key_pem = NULL; +static char *arg_cert_pem = NULL; +static char *arg_trust_pem = NULL; + +typedef struct RequestMeta { + sd_journal *journal; + + OutputMode mode; + + char *cursor; + int64_t n_skip; + uint64_t n_entries; + bool n_entries_set; + + FILE *tmp; + uint64_t delta, size; + + int argument_parse_error; + + bool follow; + bool discrete; + + uint64_t n_fields; + bool n_fields_set; +} RequestMeta; + +static const char* const mime_types[_OUTPUT_MODE_MAX] = { + [OUTPUT_SHORT] = "text/plain", + [OUTPUT_JSON] = "application/json", + [OUTPUT_JSON_SSE] = "text/event-stream", + [OUTPUT_EXPORT] = "application/vnd.fdo.journal", +}; + +static RequestMeta *request_meta(void **connection_cls) { + RequestMeta *m; + + assert(connection_cls); + if (*connection_cls) + return *connection_cls; + + m = new0(RequestMeta, 1); + if (!m) + return NULL; + + *connection_cls = m; + return m; +} + +static void request_meta_free( + void *cls, + struct MHD_Connection *connection, + void **connection_cls, + enum MHD_RequestTerminationCode toe) { + + RequestMeta *m = *connection_cls; + + if (!m) + return; + + sd_journal_close(m->journal); + + safe_fclose(m->tmp); + + free(m->cursor); + free(m); +} + +static int open_journal(RequestMeta *m) { + assert(m); + + if (m->journal) + return 0; + + return sd_journal_open(&m->journal, SD_JOURNAL_LOCAL_ONLY|SD_JOURNAL_SYSTEM); +} + +static int request_meta_ensure_tmp(RequestMeta *m) { + assert(m); + + if (m->tmp) + rewind(m->tmp); + else { + int fd; + + fd = open_tmpfile_unlinkable("/tmp", O_RDWR|O_CLOEXEC); + if (fd < 0) + return fd; + + m->tmp = fdopen(fd, "w+"); + if (!m->tmp) { + safe_close(fd); + return -errno; + } + } + + return 0; +} + +static ssize_t request_reader_entries( + void *cls, + uint64_t pos, + char *buf, + size_t max) { + + RequestMeta *m = cls; + int r; + size_t n, k; + + assert(m); + assert(buf); + assert(max > 0); + assert(pos >= m->delta); + + pos -= m->delta; + + while (pos >= m->size) { + off_t sz; + + /* End of this entry, so let's serialize the next + * one */ + + if (m->n_entries_set && + m->n_entries <= 0) + return MHD_CONTENT_READER_END_OF_STREAM; + + if (m->n_skip < 0) + r = sd_journal_previous_skip(m->journal, (uint64_t) -m->n_skip + 1); + else if (m->n_skip > 0) + r = sd_journal_next_skip(m->journal, (uint64_t) m->n_skip + 1); + else + r = sd_journal_next(m->journal); + + if (r < 0) { + log_error_errno(r, "Failed to advance journal pointer: %m"); + return MHD_CONTENT_READER_END_WITH_ERROR; + } else if (r == 0) { + + if (m->follow) { + r = sd_journal_wait(m->journal, (uint64_t) JOURNAL_WAIT_TIMEOUT); + if (r < 0) { + log_error_errno(r, "Couldn't wait for journal event: %m"); + return MHD_CONTENT_READER_END_WITH_ERROR; + } + if (r == SD_JOURNAL_NOP) + break; + + continue; + } + + return MHD_CONTENT_READER_END_OF_STREAM; + } + + if (m->discrete) { + assert(m->cursor); + + r = sd_journal_test_cursor(m->journal, m->cursor); + if (r < 0) { + log_error_errno(r, "Failed to test cursor: %m"); + return MHD_CONTENT_READER_END_WITH_ERROR; + } + + if (r == 0) + return MHD_CONTENT_READER_END_OF_STREAM; + } + + pos -= m->size; + m->delta += m->size; + + if (m->n_entries_set) + m->n_entries -= 1; + + m->n_skip = 0; + + r = request_meta_ensure_tmp(m); + if (r < 0) { + log_error_errno(r, "Failed to create temporary file: %m"); + return MHD_CONTENT_READER_END_WITH_ERROR; + } + + r = output_journal(m->tmp, m->journal, m->mode, 0, OUTPUT_FULL_WIDTH, NULL); + if (r < 0) { + log_error_errno(r, "Failed to serialize item: %m"); + return MHD_CONTENT_READER_END_WITH_ERROR; + } + + sz = ftello(m->tmp); + if (sz == (off_t) -1) { + log_error_errno(errno, "Failed to retrieve file position: %m"); + return MHD_CONTENT_READER_END_WITH_ERROR; + } + + m->size = (uint64_t) sz; + } + + if (fseeko(m->tmp, pos, SEEK_SET) < 0) { + log_error_errno(errno, "Failed to seek to position: %m"); + return MHD_CONTENT_READER_END_WITH_ERROR; + } + + n = m->size - pos; + if (n < 1) + return 0; + if (n > max) + n = max; + + errno = 0; + k = fread(buf, 1, n, m->tmp); + if (k != n) { + log_error("Failed to read from file: %s", errno ? strerror(errno) : "Premature EOF"); + return MHD_CONTENT_READER_END_WITH_ERROR; + } + + return (ssize_t) k; +} + +static int request_parse_accept( + RequestMeta *m, + struct MHD_Connection *connection) { + + const char *header; + + assert(m); + assert(connection); + + header = MHD_lookup_connection_value(connection, MHD_HEADER_KIND, "Accept"); + if (!header) + return 0; + + if (streq(header, mime_types[OUTPUT_JSON])) + m->mode = OUTPUT_JSON; + else if (streq(header, mime_types[OUTPUT_JSON_SSE])) + m->mode = OUTPUT_JSON_SSE; + else if (streq(header, mime_types[OUTPUT_EXPORT])) + m->mode = OUTPUT_EXPORT; + else + m->mode = OUTPUT_SHORT; + + return 0; +} + +static int request_parse_range( + RequestMeta *m, + struct MHD_Connection *connection) { + + const char *range, *colon, *colon2; + int r; + + assert(m); + assert(connection); + + range = MHD_lookup_connection_value(connection, MHD_HEADER_KIND, "Range"); + if (!range) + return 0; + + if (!startswith(range, "entries=")) + return 0; + + range += 8; + range += strspn(range, WHITESPACE); + + colon = strchr(range, ':'); + if (!colon) + m->cursor = strdup(range); + else { + const char *p; + + colon2 = strchr(colon + 1, ':'); + if (colon2) { + _cleanup_free_ char *t; + + t = strndup(colon + 1, colon2 - colon - 1); + if (!t) + return -ENOMEM; + + r = safe_atoi64(t, &m->n_skip); + if (r < 0) + return r; + } + + p = (colon2 ? colon2 : colon) + 1; + if (*p) { + r = safe_atou64(p, &m->n_entries); + if (r < 0) + return r; + + if (m->n_entries <= 0) + return -EINVAL; + + m->n_entries_set = true; + } + + m->cursor = strndup(range, colon - range); + } + + if (!m->cursor) + return -ENOMEM; + + m->cursor[strcspn(m->cursor, WHITESPACE)] = 0; + if (isempty(m->cursor)) + m->cursor = mfree(m->cursor); + + return 0; +} + +static int request_parse_arguments_iterator( + void *cls, + enum MHD_ValueKind kind, + const char *key, + const char *value) { + + RequestMeta *m = cls; + _cleanup_free_ char *p = NULL; + int r; + + assert(m); + + if (isempty(key)) { + m->argument_parse_error = -EINVAL; + return MHD_NO; + } + + if (streq(key, "follow")) { + if (isempty(value)) { + m->follow = true; + return MHD_YES; + } + + r = parse_boolean(value); + if (r < 0) { + m->argument_parse_error = r; + return MHD_NO; + } + + m->follow = r; + return MHD_YES; + } + + if (streq(key, "discrete")) { + if (isempty(value)) { + m->discrete = true; + return MHD_YES; + } + + r = parse_boolean(value); + if (r < 0) { + m->argument_parse_error = r; + return MHD_NO; + } + + m->discrete = r; + return MHD_YES; + } + + if (streq(key, "boot")) { + if (isempty(value)) + r = true; + else { + r = parse_boolean(value); + if (r < 0) { + m->argument_parse_error = r; + return MHD_NO; + } + } + + if (r) { + char match[9 + 32 + 1] = "_BOOT_ID="; + sd_id128_t bid; + + r = sd_id128_get_boot(&bid); + if (r < 0) { + log_error_errno(r, "Failed to get boot ID: %m"); + return MHD_NO; + } + + sd_id128_to_string(bid, match + 9); + r = sd_journal_add_match(m->journal, match, sizeof(match)-1); + if (r < 0) { + m->argument_parse_error = r; + return MHD_NO; + } + } + + return MHD_YES; + } + + p = strjoin(key, "=", strempty(value), NULL); + if (!p) { + m->argument_parse_error = log_oom(); + return MHD_NO; + } + + r = sd_journal_add_match(m->journal, p, 0); + if (r < 0) { + m->argument_parse_error = r; + return MHD_NO; + } + + return MHD_YES; +} + +static int request_parse_arguments( + RequestMeta *m, + struct MHD_Connection *connection) { + + assert(m); + assert(connection); + + m->argument_parse_error = 0; + MHD_get_connection_values(connection, MHD_GET_ARGUMENT_KIND, request_parse_arguments_iterator, m); + + return m->argument_parse_error; +} + +static int request_handler_entries( + struct MHD_Connection *connection, + void *connection_cls) { + + struct MHD_Response *response; + RequestMeta *m = connection_cls; + int r; + + assert(connection); + assert(m); + + r = open_journal(m); + if (r < 0) + return mhd_respondf(connection, MHD_HTTP_INTERNAL_SERVER_ERROR, "Failed to open journal: %s\n", strerror(-r)); + + if (request_parse_accept(m, connection) < 0) + return mhd_respond(connection, MHD_HTTP_BAD_REQUEST, "Failed to parse Accept header.\n"); + + if (request_parse_range(m, connection) < 0) + return mhd_respond(connection, MHD_HTTP_BAD_REQUEST, "Failed to parse Range header.\n"); + + if (request_parse_arguments(m, connection) < 0) + return mhd_respond(connection, MHD_HTTP_BAD_REQUEST, "Failed to parse URL arguments.\n"); + + if (m->discrete) { + if (!m->cursor) + return mhd_respond(connection, MHD_HTTP_BAD_REQUEST, "Discrete seeks require a cursor specification.\n"); + + m->n_entries = 1; + m->n_entries_set = true; + } + + if (m->cursor) + r = sd_journal_seek_cursor(m->journal, m->cursor); + else if (m->n_skip >= 0) + r = sd_journal_seek_head(m->journal); + else if (m->n_skip < 0) + r = sd_journal_seek_tail(m->journal); + if (r < 0) + return mhd_respond(connection, MHD_HTTP_BAD_REQUEST, "Failed to seek in journal.\n"); + + response = MHD_create_response_from_callback(MHD_SIZE_UNKNOWN, 4*1024, request_reader_entries, m, NULL); + if (!response) + return respond_oom(connection); + + MHD_add_response_header(response, "Content-Type", mime_types[m->mode]); + + r = MHD_queue_response(connection, MHD_HTTP_OK, response); + MHD_destroy_response(response); + + return r; +} + +static int output_field(FILE *f, OutputMode m, const char *d, size_t l) { + const char *eq; + size_t j; + + eq = memchr(d, '=', l); + if (!eq) + return -EINVAL; + + j = l - (eq - d + 1); + + if (m == OUTPUT_JSON) { + fprintf(f, "{ \"%.*s\" : ", (int) (eq - d), d); + json_escape(f, eq+1, j, OUTPUT_FULL_WIDTH); + fputs(" }\n", f); + } else { + fwrite(eq+1, 1, j, f); + fputc('\n', f); + } + + return 0; +} + +static ssize_t request_reader_fields( + void *cls, + uint64_t pos, + char *buf, + size_t max) { + + RequestMeta *m = cls; + int r; + size_t n, k; + + assert(m); + assert(buf); + assert(max > 0); + assert(pos >= m->delta); + + pos -= m->delta; + + while (pos >= m->size) { + off_t sz; + const void *d; + size_t l; + + /* End of this field, so let's serialize the next + * one */ + + if (m->n_fields_set && + m->n_fields <= 0) + return MHD_CONTENT_READER_END_OF_STREAM; + + r = sd_journal_enumerate_unique(m->journal, &d, &l); + if (r < 0) { + log_error_errno(r, "Failed to advance field index: %m"); + return MHD_CONTENT_READER_END_WITH_ERROR; + } else if (r == 0) + return MHD_CONTENT_READER_END_OF_STREAM; + + pos -= m->size; + m->delta += m->size; + + if (m->n_fields_set) + m->n_fields -= 1; + + r = request_meta_ensure_tmp(m); + if (r < 0) { + log_error_errno(r, "Failed to create temporary file: %m"); + return MHD_CONTENT_READER_END_WITH_ERROR; + } + + r = output_field(m->tmp, m->mode, d, l); + if (r < 0) { + log_error_errno(r, "Failed to serialize item: %m"); + return MHD_CONTENT_READER_END_WITH_ERROR; + } + + sz = ftello(m->tmp); + if (sz == (off_t) -1) { + log_error_errno(errno, "Failed to retrieve file position: %m"); + return MHD_CONTENT_READER_END_WITH_ERROR; + } + + m->size = (uint64_t) sz; + } + + if (fseeko(m->tmp, pos, SEEK_SET) < 0) { + log_error_errno(errno, "Failed to seek to position: %m"); + return MHD_CONTENT_READER_END_WITH_ERROR; + } + + n = m->size - pos; + if (n > max) + n = max; + + errno = 0; + k = fread(buf, 1, n, m->tmp); + if (k != n) { + log_error("Failed to read from file: %s", errno ? strerror(errno) : "Premature EOF"); + return MHD_CONTENT_READER_END_WITH_ERROR; + } + + return (ssize_t) k; +} + +static int request_handler_fields( + struct MHD_Connection *connection, + const char *field, + void *connection_cls) { + + struct MHD_Response *response; + RequestMeta *m = connection_cls; + int r; + + assert(connection); + assert(m); + + r = open_journal(m); + if (r < 0) + return mhd_respondf(connection, MHD_HTTP_INTERNAL_SERVER_ERROR, "Failed to open journal: %s\n", strerror(-r)); + + if (request_parse_accept(m, connection) < 0) + return mhd_respond(connection, MHD_HTTP_BAD_REQUEST, "Failed to parse Accept header.\n"); + + r = sd_journal_query_unique(m->journal, field); + if (r < 0) + return mhd_respond(connection, MHD_HTTP_BAD_REQUEST, "Failed to query unique fields.\n"); + + response = MHD_create_response_from_callback(MHD_SIZE_UNKNOWN, 4*1024, request_reader_fields, m, NULL); + if (!response) + return respond_oom(connection); + + MHD_add_response_header(response, "Content-Type", mime_types[m->mode == OUTPUT_JSON ? OUTPUT_JSON : OUTPUT_SHORT]); + + r = MHD_queue_response(connection, MHD_HTTP_OK, response); + MHD_destroy_response(response); + + return r; +} + +static int request_handler_redirect( + struct MHD_Connection *connection, + const char *target) { + + char *page; + struct MHD_Response *response; + int ret; + + assert(connection); + assert(target); + + if (asprintf(&page, "Please continue to the journal browser.", target) < 0) + return respond_oom(connection); + + response = MHD_create_response_from_buffer(strlen(page), page, MHD_RESPMEM_MUST_FREE); + if (!response) { + free(page); + return respond_oom(connection); + } + + MHD_add_response_header(response, "Content-Type", "text/html"); + MHD_add_response_header(response, "Location", target); + + ret = MHD_queue_response(connection, MHD_HTTP_MOVED_PERMANENTLY, response); + MHD_destroy_response(response); + + return ret; +} + +static int request_handler_file( + struct MHD_Connection *connection, + const char *path, + const char *mime_type) { + + struct MHD_Response *response; + int ret; + _cleanup_close_ int fd = -1; + struct stat st; + + assert(connection); + assert(path); + assert(mime_type); + + fd = open(path, O_RDONLY|O_CLOEXEC); + if (fd < 0) + return mhd_respondf(connection, MHD_HTTP_NOT_FOUND, "Failed to open file %s: %m\n", path); + + if (fstat(fd, &st) < 0) + return mhd_respondf(connection, MHD_HTTP_INTERNAL_SERVER_ERROR, "Failed to stat file: %m\n"); + + response = MHD_create_response_from_fd_at_offset64(st.st_size, fd, 0); + if (!response) + return respond_oom(connection); + + fd = -1; + + MHD_add_response_header(response, "Content-Type", mime_type); + + ret = MHD_queue_response(connection, MHD_HTTP_OK, response); + MHD_destroy_response(response); + + return ret; +} + +static int get_virtualization(char **v) { + _cleanup_(sd_bus_unrefp) sd_bus *bus = NULL; + char *b = NULL; + int r; + + r = sd_bus_default_system(&bus); + if (r < 0) + return r; + + r = sd_bus_get_property_string( + bus, + "org.freedesktop.systemd1", + "/org/freedesktop/systemd1", + "org.freedesktop.systemd1.Manager", + "Virtualization", + NULL, + &b); + if (r < 0) + return r; + + if (isempty(b)) { + free(b); + *v = NULL; + return 0; + } + + *v = b; + return 1; +} + +static int request_handler_machine( + struct MHD_Connection *connection, + void *connection_cls) { + + struct MHD_Response *response; + RequestMeta *m = connection_cls; + int r; + _cleanup_free_ char* hostname = NULL, *os_name = NULL; + uint64_t cutoff_from = 0, cutoff_to = 0, usage = 0; + char *json; + sd_id128_t mid, bid; + _cleanup_free_ char *v = NULL; + + assert(connection); + assert(m); + + r = open_journal(m); + if (r < 0) + return mhd_respondf(connection, MHD_HTTP_INTERNAL_SERVER_ERROR, "Failed to open journal: %s\n", strerror(-r)); + + r = sd_id128_get_machine(&mid); + if (r < 0) + return mhd_respondf(connection, MHD_HTTP_INTERNAL_SERVER_ERROR, "Failed to determine machine ID: %s\n", strerror(-r)); + + r = sd_id128_get_boot(&bid); + if (r < 0) + return mhd_respondf(connection, MHD_HTTP_INTERNAL_SERVER_ERROR, "Failed to determine boot ID: %s\n", strerror(-r)); + + hostname = gethostname_malloc(); + if (!hostname) + return respond_oom(connection); + + r = sd_journal_get_usage(m->journal, &usage); + if (r < 0) + return mhd_respondf(connection, MHD_HTTP_INTERNAL_SERVER_ERROR, "Failed to determine disk usage: %s\n", strerror(-r)); + + r = sd_journal_get_cutoff_realtime_usec(m->journal, &cutoff_from, &cutoff_to); + if (r < 0) + return mhd_respondf(connection, MHD_HTTP_INTERNAL_SERVER_ERROR, "Failed to determine disk usage: %s\n", strerror(-r)); + + if (parse_env_file("/etc/os-release", NEWLINE, "PRETTY_NAME", &os_name, NULL) == -ENOENT) + (void) parse_env_file("/usr/lib/os-release", NEWLINE, "PRETTY_NAME", &os_name, NULL); + + get_virtualization(&v); + + r = asprintf(&json, + "{ \"machine_id\" : \"" SD_ID128_FORMAT_STR "\"," + "\"boot_id\" : \"" SD_ID128_FORMAT_STR "\"," + "\"hostname\" : \"%s\"," + "\"os_pretty_name\" : \"%s\"," + "\"virtualization\" : \"%s\"," + "\"usage\" : \"%"PRIu64"\"," + "\"cutoff_from_realtime\" : \"%"PRIu64"\"," + "\"cutoff_to_realtime\" : \"%"PRIu64"\" }\n", + SD_ID128_FORMAT_VAL(mid), + SD_ID128_FORMAT_VAL(bid), + hostname_cleanup(hostname), + os_name ? os_name : "GNU/Linux", + v ? v : "bare", + usage, + cutoff_from, + cutoff_to); + + if (r < 0) + return respond_oom(connection); + + response = MHD_create_response_from_buffer(strlen(json), json, MHD_RESPMEM_MUST_FREE); + if (!response) { + free(json); + return respond_oom(connection); + } + + MHD_add_response_header(response, "Content-Type", "application/json"); + r = MHD_queue_response(connection, MHD_HTTP_OK, response); + MHD_destroy_response(response); + + return r; +} + +static int request_handler( + void *cls, + struct MHD_Connection *connection, + const char *url, + const char *method, + const char *version, + const char *upload_data, + size_t *upload_data_size, + void **connection_cls) { + int r, code; + + assert(connection); + assert(connection_cls); + assert(url); + assert(method); + + if (!streq(method, "GET")) + return mhd_respond(connection, MHD_HTTP_NOT_ACCEPTABLE, + "Unsupported method.\n"); + + + if (!*connection_cls) { + if (!request_meta(connection_cls)) + return respond_oom(connection); + return MHD_YES; + } + + if (arg_trust_pem) { + r = check_permissions(connection, &code, NULL); + if (r < 0) + return code; + } + + if (streq(url, "/")) + return request_handler_redirect(connection, "/browse"); + + if (streq(url, "/entries")) + return request_handler_entries(connection, *connection_cls); + + if (startswith(url, "/fields/")) + return request_handler_fields(connection, url + 8, *connection_cls); + + if (streq(url, "/browse")) + return request_handler_file(connection, DOCUMENT_ROOT "/browse.html", "text/html"); + + if (streq(url, "/machine")) + return request_handler_machine(connection, *connection_cls); + + return mhd_respond(connection, MHD_HTTP_NOT_FOUND, "Not found.\n"); +} + +static void help(void) { + printf("%s [OPTIONS...] ...\n\n" + "HTTP server for journal events.\n\n" + " -h --help Show this help\n" + " --version Show package version\n" + " --cert=CERT.PEM Server certificate in PEM format\n" + " --key=KEY.PEM Server key in PEM format\n" + " --trust=CERT.PEM Certificat authority certificate in PEM format\n", + program_invocation_short_name); +} + +static int parse_argv(int argc, char *argv[]) { + enum { + ARG_VERSION = 0x100, + ARG_KEY, + ARG_CERT, + ARG_TRUST, + }; + + int r, c; + + static const struct option options[] = { + { "help", no_argument, NULL, 'h' }, + { "version", no_argument, NULL, ARG_VERSION }, + { "key", required_argument, NULL, ARG_KEY }, + { "cert", required_argument, NULL, ARG_CERT }, + { "trust", required_argument, NULL, ARG_TRUST }, + {} + }; + + 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_KEY: + if (arg_key_pem) { + log_error("Key file specified twice"); + return -EINVAL; + } + r = read_full_file(optarg, &arg_key_pem, NULL); + if (r < 0) + return log_error_errno(r, "Failed to read key file: %m"); + assert(arg_key_pem); + break; + + case ARG_CERT: + if (arg_cert_pem) { + log_error("Certificate file specified twice"); + return -EINVAL; + } + r = read_full_file(optarg, &arg_cert_pem, NULL); + if (r < 0) + return log_error_errno(r, "Failed to read certificate file: %m"); + assert(arg_cert_pem); + break; + + case ARG_TRUST: +#ifdef HAVE_GNUTLS + if (arg_trust_pem) { + log_error("CA certificate file specified twice"); + return -EINVAL; + } + r = read_full_file(optarg, &arg_trust_pem, NULL); + if (r < 0) + return log_error_errno(r, "Failed to read CA certificate file: %m"); + assert(arg_trust_pem); + break; +#else + log_error("Option --trust is not available."); +#endif + + case '?': + return -EINVAL; + + default: + assert_not_reached("Unhandled option"); + } + + if (optind < argc) { + log_error("This program does not take arguments."); + return -EINVAL; + } + + if (!!arg_key_pem != !!arg_cert_pem) { + log_error("Certificate and key files must be specified together"); + return -EINVAL; + } + + if (arg_trust_pem && !arg_key_pem) { + log_error("CA certificate can only be used with certificate file"); + return -EINVAL; + } + + return 1; +} + +int main(int argc, char *argv[]) { + struct MHD_Daemon *d = NULL; + int r, n; + + log_set_target(LOG_TARGET_AUTO); + log_parse_environment(); + log_open(); + + r = parse_argv(argc, argv); + if (r < 0) + return EXIT_FAILURE; + if (r == 0) + return EXIT_SUCCESS; + + sigbus_install(); + + r = setup_gnutls_logger(NULL); + if (r < 0) + return EXIT_FAILURE; + + n = sd_listen_fds(1); + if (n < 0) { + log_error_errno(n, "Failed to determine passed sockets: %m"); + goto finish; + } else if (n > 1) { + log_error("Can't listen on more than one socket."); + goto finish; + } else { + struct MHD_OptionItem opts[] = { + { MHD_OPTION_NOTIFY_COMPLETED, + (intptr_t) request_meta_free, NULL }, + { MHD_OPTION_EXTERNAL_LOGGER, + (intptr_t) microhttpd_logger, NULL }, + { MHD_OPTION_END, 0, NULL }, + { MHD_OPTION_END, 0, NULL }, + { MHD_OPTION_END, 0, NULL }, + { MHD_OPTION_END, 0, NULL }, + { MHD_OPTION_END, 0, NULL }}; + int opts_pos = 2; + + /* We force MHD_USE_PIPE_FOR_SHUTDOWN here, in order + * to make sure libmicrohttpd doesn't use shutdown() + * on our listening socket, which would break socket + * re-activation. See + * + * https://lists.gnu.org/archive/html/libmicrohttpd/2015-09/msg00014.html + * https://github.com/systemd/systemd/pull/1286 + */ + + int flags = + MHD_USE_DEBUG | + MHD_USE_DUAL_STACK | + MHD_USE_PIPE_FOR_SHUTDOWN | + MHD_USE_POLL | + MHD_USE_THREAD_PER_CONNECTION; + + if (n > 0) + opts[opts_pos++] = (struct MHD_OptionItem) + {MHD_OPTION_LISTEN_SOCKET, SD_LISTEN_FDS_START}; + if (arg_key_pem) { + assert(arg_cert_pem); + opts[opts_pos++] = (struct MHD_OptionItem) + {MHD_OPTION_HTTPS_MEM_KEY, 0, arg_key_pem}; + opts[opts_pos++] = (struct MHD_OptionItem) + {MHD_OPTION_HTTPS_MEM_CERT, 0, arg_cert_pem}; + flags |= MHD_USE_SSL; + } + if (arg_trust_pem) { + assert(flags & MHD_USE_SSL); + opts[opts_pos++] = (struct MHD_OptionItem) + {MHD_OPTION_HTTPS_MEM_TRUST, 0, arg_trust_pem}; + } + + d = MHD_start_daemon(flags, 19531, + NULL, NULL, + request_handler, NULL, + MHD_OPTION_ARRAY, opts, + MHD_OPTION_END); + } + + if (!d) { + log_error("Failed to start daemon!"); + goto finish; + } + + pause(); + + r = EXIT_SUCCESS; + +finish: + if (d) + MHD_stop_daemon(d); + + return r; +} diff --git a/src/grp-journal/grp-remote/systemd-journal-remote/Makefile b/src/grp-journal/grp-remote/systemd-journal-remote/Makefile new file mode 100644 index 0000000000..05af76afc2 --- /dev/null +++ b/src/grp-journal/grp-remote/systemd-journal-remote/Makefile @@ -0,0 +1,85 @@ +# -*- 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 # ENABLE_TMPFILES + +ifneq ($(HAVE_GNUTLS),) +systemd_journal_remote_LDADD += \ + $(GNUTLS_LIBS) +endif # HAVE_GNUTLS + +# 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 # HAVE_MICROHTTPD + +include $(topsrcdir)/build-aux/Makefile.tail.mk diff --git a/src/grp-journal/grp-remote/systemd-journal-remote/journal-remote-parse.c b/src/grp-journal/grp-remote/systemd-journal-remote/journal-remote-parse.c new file mode 100644 index 0000000000..ec5358fb97 --- /dev/null +++ b/src/grp-journal/grp-remote/systemd-journal-remote/journal-remote-parse.c @@ -0,0 +1,506 @@ +/*** + 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 "basic/alloc-util.h" +#include "basic/fd-util.h" +#include "journal-remote-parse.h" +#include "journald-native.h" +#include "basic/parse-util.h" +#include "basic/string-util.h" + +#define LINE_CHUNK 8*1024u + +void source_free(RemoteSource *source) { + if (!source) + return; + + if (source->fd >= 0 && !source->passive_fd) { + log_debug("Closing fd:%d (%s)", source->fd, source->name); + safe_close(source->fd); + } + + free(source->name); + free(source->buf); + iovw_free_contents(&source->iovw); + + log_debug("Writer ref count %i", source->writer->n_ref); + writer_unref(source->writer); + + sd_event_source_unref(source->event); + sd_event_source_unref(source->buffer_event); + + free(source); +} + +/** + * Initialize zero-filled source with given values. On success, takes + * ownerhship of fd and writer, otherwise does not touch them. + */ +RemoteSource* source_new(int fd, bool passive_fd, char *name, Writer *writer) { + + RemoteSource *source; + + log_debug("Creating source for %sfd:%d (%s)", + passive_fd ? "passive " : "", fd, name); + + assert(fd >= 0); + + source = new0(RemoteSource, 1); + if (!source) + return NULL; + + source->fd = fd; + source->passive_fd = passive_fd; + source->name = name; + source->writer = writer; + + return source; +} + +static char* realloc_buffer(RemoteSource *source, size_t size) { + char *b, *old = source->buf; + + b = GREEDY_REALLOC(source->buf, source->size, size); + if (!b) + return NULL; + + iovw_rebase(&source->iovw, old, source->buf); + + return b; +} + +static int get_line(RemoteSource *source, char **line, size_t *size) { + ssize_t n; + char *c = NULL; + + assert(source); + assert(source->state == STATE_LINE); + assert(source->offset <= source->filled); + assert(source->filled <= source->size); + assert(source->buf == NULL || source->size > 0); + assert(source->fd >= 0); + + for (;;) { + if (source->buf) { + size_t start = MAX(source->scanned, source->offset); + + c = memchr(source->buf + start, '\n', + source->filled - start); + if (c != NULL) + break; + } + + source->scanned = source->filled; + if (source->scanned >= DATA_SIZE_MAX) { + log_error("Entry is bigger than %u bytes.", DATA_SIZE_MAX); + return -E2BIG; + } + + if (source->passive_fd) + /* we have to wait for some data to come to us */ + return -EAGAIN; + + /* We know that source->filled is at most DATA_SIZE_MAX, so if + we reallocate it, we'll increase the size at least a bit. */ + assert_cc(DATA_SIZE_MAX < ENTRY_SIZE_MAX); + if (source->size - source->filled < LINE_CHUNK && + !realloc_buffer(source, MIN(source->filled + LINE_CHUNK, ENTRY_SIZE_MAX))) + return log_oom(); + + assert(source->buf); + assert(source->size - source->filled >= LINE_CHUNK || + source->size == ENTRY_SIZE_MAX); + + n = read(source->fd, + source->buf + source->filled, + source->size - source->filled); + if (n < 0) { + if (errno != EAGAIN) + log_error_errno(errno, "read(%d, ..., %zu): %m", + source->fd, + source->size - source->filled); + return -errno; + } else if (n == 0) + return 0; + + source->filled += n; + } + + *line = source->buf + source->offset; + *size = c + 1 - source->buf - source->offset; + source->offset += *size; + + return 1; +} + +int push_data(RemoteSource *source, const char *data, size_t size) { + assert(source); + assert(source->state != STATE_EOF); + + if (!realloc_buffer(source, source->filled + size)) { + log_error("Failed to store received data of size %zu " + "(in addition to existing %zu bytes with %zu filled): %s", + size, source->size, source->filled, strerror(ENOMEM)); + return -ENOMEM; + } + + memcpy(source->buf + source->filled, data, size); + source->filled += size; + + return 0; +} + +static int fill_fixed_size(RemoteSource *source, void **data, size_t size) { + + assert(source); + assert(source->state == STATE_DATA_START || + source->state == STATE_DATA || + source->state == STATE_DATA_FINISH); + assert(size <= DATA_SIZE_MAX); + assert(source->offset <= source->filled); + assert(source->filled <= source->size); + assert(source->buf != NULL || source->size == 0); + assert(source->buf == NULL || source->size > 0); + assert(source->fd >= 0); + assert(data); + + while (source->filled - source->offset < size) { + int n; + + if (source->passive_fd) + /* we have to wait for some data to come to us */ + return -EAGAIN; + + if (!realloc_buffer(source, source->offset + size)) + return log_oom(); + + n = read(source->fd, source->buf + source->filled, + source->size - source->filled); + if (n < 0) { + if (errno != EAGAIN) + log_error_errno(errno, "read(%d, ..., %zu): %m", source->fd, + source->size - source->filled); + return -errno; + } else if (n == 0) + return 0; + + source->filled += n; + } + + *data = source->buf + source->offset; + source->offset += size; + + return 1; +} + +static int get_data_size(RemoteSource *source) { + int r; + void *data; + + assert(source); + assert(source->state == STATE_DATA_START); + assert(source->data_size == 0); + + r = fill_fixed_size(source, &data, sizeof(uint64_t)); + if (r <= 0) + return r; + + source->data_size = le64toh( *(uint64_t *) data ); + if (source->data_size > DATA_SIZE_MAX) { + log_error("Stream declares field with size %zu > DATA_SIZE_MAX = %u", + source->data_size, DATA_SIZE_MAX); + return -EINVAL; + } + if (source->data_size == 0) + log_warning("Binary field with zero length"); + + return 1; +} + +static int get_data_data(RemoteSource *source, void **data) { + int r; + + assert(source); + assert(data); + assert(source->state == STATE_DATA); + + r = fill_fixed_size(source, data, source->data_size); + if (r <= 0) + return r; + + return 1; +} + +static int get_data_newline(RemoteSource *source) { + int r; + char *data; + + assert(source); + assert(source->state == STATE_DATA_FINISH); + + r = fill_fixed_size(source, (void**) &data, 1); + if (r <= 0) + return r; + + assert(data); + if (*data != '\n') { + log_error("expected newline, got '%c'", *data); + return -EINVAL; + } + + return 1; +} + +static int process_dunder(RemoteSource *source, char *line, size_t n) { + const char *timestamp; + int r; + + assert(line); + assert(n > 0); + assert(line[n-1] == '\n'); + + /* XXX: is it worth to support timestamps in extended format? + * We don't produce them, but who knows... */ + + timestamp = startswith(line, "__CURSOR="); + if (timestamp) + /* ignore __CURSOR */ + return 1; + + timestamp = startswith(line, "__REALTIME_TIMESTAMP="); + if (timestamp) { + long long unsigned x; + line[n-1] = '\0'; + r = safe_atollu(timestamp, &x); + if (r < 0) + log_warning("Failed to parse __REALTIME_TIMESTAMP: '%s'", timestamp); + else + source->ts.realtime = x; + return r < 0 ? r : 1; + } + + timestamp = startswith(line, "__MONOTONIC_TIMESTAMP="); + if (timestamp) { + long long unsigned x; + line[n-1] = '\0'; + r = safe_atollu(timestamp, &x); + if (r < 0) + log_warning("Failed to parse __MONOTONIC_TIMESTAMP: '%s'", timestamp); + else + source->ts.monotonic = x; + return r < 0 ? r : 1; + } + + timestamp = startswith(line, "__"); + if (timestamp) { + log_notice("Unknown dunder line %s", line); + return 1; + } + + /* no dunder */ + return 0; +} + +static int process_data(RemoteSource *source) { + int r; + + switch(source->state) { + case STATE_LINE: { + char *line, *sep; + size_t n = 0; + + assert(source->data_size == 0); + + r = get_line(source, &line, &n); + if (r < 0) + return r; + if (r == 0) { + source->state = STATE_EOF; + return r; + } + assert(n > 0); + assert(line[n-1] == '\n'); + + if (n == 1) { + log_trace("Received empty line, event is ready"); + return 1; + } + + r = process_dunder(source, line, n); + if (r != 0) + return r < 0 ? r : 0; + + /* MESSAGE=xxx\n + or + COREDUMP\n + LLLLLLLL0011223344...\n + */ + sep = memchr(line, '=', n); + if (sep) { + /* chomp newline */ + n--; + + r = iovw_put(&source->iovw, line, n); + if (r < 0) + return r; + } else { + /* replace \n with = */ + line[n-1] = '='; + + source->field_len = n; + source->state = STATE_DATA_START; + + /* we cannot put the field in iovec until we have all data */ + } + + log_trace("Received: %.*s (%s)", (int) n, line, sep ? "text" : "binary"); + + return 0; /* continue */ + } + + case STATE_DATA_START: + assert(source->data_size == 0); + + r = get_data_size(source); + // log_debug("get_data_size() -> %d", r); + if (r < 0) + return r; + if (r == 0) { + source->state = STATE_EOF; + return 0; + } + + source->state = source->data_size > 0 ? + STATE_DATA : STATE_DATA_FINISH; + + return 0; /* continue */ + + case STATE_DATA: { + void *data; + char *field; + + assert(source->data_size > 0); + + r = get_data_data(source, &data); + // log_debug("get_data_data() -> %d", r); + if (r < 0) + return r; + if (r == 0) { + source->state = STATE_EOF; + return 0; + } + + assert(data); + + field = (char*) data - sizeof(uint64_t) - source->field_len; + memmove(field + sizeof(uint64_t), field, source->field_len); + + r = iovw_put(&source->iovw, field + sizeof(uint64_t), source->field_len + source->data_size); + if (r < 0) + return r; + + source->state = STATE_DATA_FINISH; + + return 0; /* continue */ + } + + case STATE_DATA_FINISH: + r = get_data_newline(source); + // log_debug("get_data_newline() -> %d", r); + if (r < 0) + return r; + if (r == 0) { + source->state = STATE_EOF; + return 0; + } + + source->data_size = 0; + source->state = STATE_LINE; + + return 0; /* continue */ + default: + assert_not_reached("wtf?"); + } +} + +int process_source(RemoteSource *source, bool compress, bool seal) { + size_t remain, target; + int r; + + assert(source); + assert(source->writer); + + r = process_data(source); + if (r <= 0) + return r; + + /* We have a full event */ + log_trace("Received full event from source@%p fd:%d (%s)", + source, source->fd, source->name); + + if (!source->iovw.count) { + log_warning("Entry with no payload, skipping"); + goto freeing; + } + + assert(source->iovw.iovec); + assert(source->iovw.count); + + r = writer_write(source->writer, &source->iovw, &source->ts, compress, seal); + if (r < 0) + log_error_errno(r, "Failed to write entry of %zu bytes: %m", + iovw_size(&source->iovw)); + else + r = 1; + + freeing: + iovw_free_contents(&source->iovw); + + /* possibly reset buffer position */ + remain = source->filled - source->offset; + + if (remain == 0) /* no brainer */ + source->offset = source->scanned = source->filled = 0; + else if (source->offset > source->size - source->filled && + source->offset > remain) { + memcpy(source->buf, source->buf + source->offset, remain); + source->offset = source->scanned = 0; + source->filled = remain; + } + + target = source->size; + while (target > 16 * LINE_CHUNK && source->filled < target / 2) + target /= 2; + if (target < source->size) { + char *tmp; + + tmp = realloc(source->buf, target); + if (!tmp) + log_warning("Failed to reallocate buffer to (smaller) size %zu", + target); + else { + log_debug("Reallocated buffer from %zu to %zu bytes", + source->size, target); + source->buf = tmp; + source->size = target; + } + } + + return r; +} diff --git a/src/grp-journal/grp-remote/systemd-journal-remote/journal-remote-parse.h b/src/grp-journal/grp-remote/systemd-journal-remote/journal-remote-parse.h new file mode 100644 index 0000000000..4f47ea89d6 --- /dev/null +++ b/src/grp-journal/grp-remote/systemd-journal-remote/journal-remote-parse.h @@ -0,0 +1,69 @@ +#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 + +#include "journal-remote-write.h" + +typedef enum { + STATE_LINE = 0, /* waiting to read, or reading line */ + STATE_DATA_START, /* reading binary data header */ + STATE_DATA, /* reading binary data */ + STATE_DATA_FINISH, /* expecting newline */ + STATE_EOF, /* done */ +} source_state; + +typedef struct RemoteSource { + char *name; + int fd; + bool passive_fd; + + char *buf; + size_t size; /* total size of the buffer */ + size_t offset; /* offset to the beginning of live data in the buffer */ + size_t scanned; /* number of bytes since the beginning of data without a newline */ + size_t filled; /* total number of bytes in the buffer */ + + size_t field_len; /* used for binary fields: the field name length */ + size_t data_size; /* and the size of the binary data chunk being processed */ + + struct iovec_wrapper iovw; + + source_state state; + dual_timestamp ts; + + Writer *writer; + + sd_event_source *event; + sd_event_source *buffer_event; +} RemoteSource; + +RemoteSource* source_new(int fd, bool passive_fd, char *name, Writer *writer); + +static inline size_t source_non_empty(RemoteSource *source) { + assert(source); + + return source->filled; +} + +void source_free(RemoteSource *source); +int push_data(RemoteSource *source, const char *data, size_t size); +int process_source(RemoteSource *source, bool compress, bool seal); diff --git a/src/grp-journal/grp-remote/systemd-journal-remote/journal-remote-write.c b/src/grp-journal/grp-remote/systemd-journal-remote/journal-remote-write.c new file mode 100644 index 0000000000..82292fd240 --- /dev/null +++ b/src/grp-journal/grp-remote/systemd-journal-remote/journal-remote-write.c @@ -0,0 +1,168 @@ +/*** + This file is part of systemd. + + Copyright 2012 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 "basic/alloc-util.h" +#include "journal-remote.h" + +int iovw_put(struct iovec_wrapper *iovw, void* data, size_t len) { + if (!GREEDY_REALLOC(iovw->iovec, iovw->size_bytes, iovw->count + 1)) + return log_oom(); + + iovw->iovec[iovw->count++] = (struct iovec) {data, len}; + return 0; +} + +void iovw_free_contents(struct iovec_wrapper *iovw) { + iovw->iovec = mfree(iovw->iovec); + iovw->size_bytes = iovw->count = 0; +} + +size_t iovw_size(struct iovec_wrapper *iovw) { + size_t n = 0, i; + + for (i = 0; i < iovw->count; i++) + n += iovw->iovec[i].iov_len; + + return n; +} + +void iovw_rebase(struct iovec_wrapper *iovw, char *old, char *new) { + size_t i; + + for (i = 0; i < iovw->count; i++) + iovw->iovec[i].iov_base = (char*) iovw->iovec[i].iov_base - old + new; +} + +/********************************************************************** + ********************************************************************** + **********************************************************************/ + +static int do_rotate(JournalFile **f, bool compress, bool seal) { + int r = journal_file_rotate(f, compress, seal, NULL); + if (r < 0) { + if (*f) + log_error_errno(r, "Failed to rotate %s: %m", (*f)->path); + else + log_error_errno(r, "Failed to create rotated journal: %m"); + } + + return r; +} + +Writer* writer_new(RemoteServer *server) { + Writer *w; + + w = new0(Writer, 1); + if (!w) + return NULL; + + memset(&w->metrics, 0xFF, sizeof(w->metrics)); + + w->mmap = mmap_cache_new(); + if (!w->mmap) { + free(w); + return NULL; + } + + w->n_ref = 1; + w->server = server; + + return w; +} + +Writer* writer_free(Writer *w) { + if (!w) + return NULL; + + if (w->journal) { + log_debug("Closing journal file %s.", w->journal->path); + journal_file_close(w->journal); + } + + if (w->server && w->hashmap_key) + hashmap_remove(w->server->writers, w->hashmap_key); + + free(w->hashmap_key); + + if (w->mmap) + mmap_cache_unref(w->mmap); + + free(w); + + return NULL; +} + +Writer* writer_unref(Writer *w) { + if (w && (-- w->n_ref <= 0)) + writer_free(w); + + return NULL; +} + +Writer* writer_ref(Writer *w) { + if (w) + assert_se(++ w->n_ref >= 2); + + return w; +} + +int writer_write(Writer *w, + struct iovec_wrapper *iovw, + dual_timestamp *ts, + bool compress, + bool seal) { + int r; + + assert(w); + assert(iovw); + assert(iovw->count > 0); + + if (journal_file_rotate_suggested(w->journal, 0)) { + log_info("%s: Journal header limits reached or header out-of-date, rotating", + w->journal->path); + r = do_rotate(&w->journal, compress, seal); + if (r < 0) + return r; + } + + r = journal_file_append_entry(w->journal, ts, iovw->iovec, iovw->count, + &w->seqnum, NULL, NULL); + if (r >= 0) { + if (w->server) + w->server->event_count += 1; + return 1; + } + + log_debug_errno(r, "%s: Write failed, rotating: %m", w->journal->path); + r = do_rotate(&w->journal, compress, seal); + if (r < 0) + return r; + else + log_debug("%s: Successfully rotated journal", w->journal->path); + + log_debug("Retrying write."); + r = journal_file_append_entry(w->journal, ts, iovw->iovec, iovw->count, + &w->seqnum, NULL, NULL); + if (r < 0) + return r; + + if (w->server) + w->server->event_count += 1; + return 1; +} diff --git a/src/grp-journal/grp-remote/systemd-journal-remote/journal-remote-write.h b/src/grp-journal/grp-remote/systemd-journal-remote/journal-remote-write.h new file mode 100644 index 0000000000..53ba45fc04 --- /dev/null +++ b/src/grp-journal/grp-remote/systemd-journal-remote/journal-remote-write.h @@ -0,0 +1,70 @@ +#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 "journal-file.h" + +typedef struct RemoteServer RemoteServer; + +struct iovec_wrapper { + struct iovec *iovec; + size_t size_bytes; + size_t count; +}; + +int iovw_put(struct iovec_wrapper *iovw, void* data, size_t len); +void iovw_free_contents(struct iovec_wrapper *iovw); +size_t iovw_size(struct iovec_wrapper *iovw); +void iovw_rebase(struct iovec_wrapper *iovw, char *old, char *new); + +typedef struct Writer { + JournalFile *journal; + JournalMetrics metrics; + + MMapCache *mmap; + RemoteServer *server; + char *hashmap_key; + + uint64_t seqnum; + + int n_ref; +} Writer; + +Writer* writer_new(RemoteServer* server); +Writer* writer_free(Writer *w); + +Writer* writer_ref(Writer *w); +Writer* writer_unref(Writer *w); + +DEFINE_TRIVIAL_CLEANUP_FUNC(Writer*, writer_unref); +#define _cleanup_writer_unref_ _cleanup_(writer_unrefp) + +int writer_write(Writer *s, + struct iovec_wrapper *iovw, + dual_timestamp *ts, + bool compress, + bool seal); + +typedef enum JournalWriteSplitMode { + JOURNAL_WRITE_SPLIT_NONE, + JOURNAL_WRITE_SPLIT_HOST, + _JOURNAL_WRITE_SPLIT_MAX, + _JOURNAL_WRITE_SPLIT_INVALID = -1 +} JournalWriteSplitMode; diff --git a/src/grp-journal/grp-remote/systemd-journal-remote/journal-remote.c b/src/grp-journal/grp-remote/systemd-journal-remote/journal-remote.c new file mode 100644 index 0000000000..6ea7adabb0 --- /dev/null +++ b/src/grp-journal/grp-remote/systemd-journal-remote/journal-remote.c @@ -0,0 +1,1601 @@ +/*** + This file is part of systemd. + + Copyright 2012 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 + +#ifdef HAVE_GNUTLS +#include +#endif + +#include + +#include "basic/alloc-util.h" +#include "shared/conf-parser.h" +#include "basic/def.h" +#include "basic/escape.h" +#include "basic/fd-util.h" +#include "basic/fileio.h" +#include "journal-file.h" +#include "journal-remote-write.h" +#include "journal-remote.h" +#include "journald-native.h" +#include "basic/macro.h" +#include "basic/parse-util.h" +#include "basic/signal-util.h" +#include "basic/socket-util.h" +#include "basic/stat-util.h" +#include "basic/stdio-util.h" +#include "basic/string-table.h" +#include "basic/string-util.h" +#include "basic/strv.h" + +#define REMOTE_JOURNAL_PATH "/var/log/journal/remote" + +#define PRIV_KEY_FILE CERTIFICATE_ROOT "/private/journal-remote.pem" +#define CERT_FILE CERTIFICATE_ROOT "/certs/journal-remote.pem" +#define TRUST_FILE CERTIFICATE_ROOT "/ca/trusted.pem" + +static char* arg_url = NULL; +static char* arg_getter = NULL; +static char* arg_listen_raw = NULL; +static char* arg_listen_http = NULL; +static char* arg_listen_https = NULL; +static char** arg_files = NULL; +static int arg_compress = true; +static int arg_seal = false; +static int http_socket = -1, https_socket = -1; +static char** arg_gnutls_log = NULL; + +static JournalWriteSplitMode arg_split_mode = JOURNAL_WRITE_SPLIT_HOST; +static char* arg_output = NULL; + +static char *arg_key = NULL; +static char *arg_cert = NULL; +static char *arg_trust = NULL; +static bool arg_trust_all = false; + +/********************************************************************** + ********************************************************************** + **********************************************************************/ + +static int spawn_child(const char* child, char** argv) { + int fd[2]; + pid_t parent_pid, child_pid; + int r; + + if (pipe(fd) < 0) + return log_error_errno(errno, "Failed to create pager pipe: %m"); + + parent_pid = getpid(); + + child_pid = fork(); + if (child_pid < 0) { + r = log_error_errno(errno, "Failed to fork: %m"); + safe_close_pair(fd); + return r; + } + + /* In the child */ + if (child_pid == 0) { + + (void) reset_all_signal_handlers(); + (void) reset_signal_mask(); + + r = dup2(fd[1], STDOUT_FILENO); + if (r < 0) { + log_error_errno(errno, "Failed to dup pipe to stdout: %m"); + _exit(EXIT_FAILURE); + } + + safe_close_pair(fd); + + /* 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); + + execvp(child, argv); + log_error_errno(errno, "Failed to exec child %s: %m", child); + _exit(EXIT_FAILURE); + } + + r = close(fd[1]); + if (r < 0) + log_warning_errno(errno, "Failed to close write end of pipe: %m"); + + return fd[0]; +} + +static int spawn_curl(const char* url) { + char **argv = STRV_MAKE("curl", + "-HAccept: application/vnd.fdo.journal", + "--silent", + "--show-error", + url); + int r; + + r = spawn_child("curl", argv); + if (r < 0) + log_error_errno(r, "Failed to spawn curl: %m"); + return r; +} + +static int spawn_getter(const char *getter) { + int r; + _cleanup_strv_free_ char **words = NULL; + + assert(getter); + r = strv_split_extract(&words, getter, WHITESPACE, EXTRACT_QUOTES); + if (r < 0) + return log_error_errno(r, "Failed to split getter option: %m"); + + r = spawn_child(words[0], words); + if (r < 0) + log_error_errno(r, "Failed to spawn getter %s: %m", getter); + + return r; +} + +#define filename_escape(s) xescape((s), "/ ") + +static int open_output(Writer *w, const char* host) { + _cleanup_free_ char *_output = NULL; + const char *output; + int r; + + switch (arg_split_mode) { + case JOURNAL_WRITE_SPLIT_NONE: + output = arg_output ?: REMOTE_JOURNAL_PATH "/remote.journal"; + break; + + case JOURNAL_WRITE_SPLIT_HOST: { + _cleanup_free_ char *name; + + assert(host); + + name = filename_escape(host); + if (!name) + return log_oom(); + + r = asprintf(&_output, "%s/remote-%s.journal", + arg_output ?: REMOTE_JOURNAL_PATH, + name); + if (r < 0) + return log_oom(); + + output = _output; + break; + } + + default: + assert_not_reached("what?"); + } + + r = journal_file_open_reliably(output, + O_RDWR|O_CREAT, 0640, + arg_compress, arg_seal, + &w->metrics, + w->mmap, NULL, + NULL, &w->journal); + if (r < 0) + log_error_errno(r, "Failed to open output journal %s: %m", + output); + else + log_debug("Opened output file %s", w->journal->path); + return r; +} + +/********************************************************************** + ********************************************************************** + **********************************************************************/ + +static int init_writer_hashmap(RemoteServer *s) { + static const struct hash_ops *hash_ops[] = { + [JOURNAL_WRITE_SPLIT_NONE] = NULL, + [JOURNAL_WRITE_SPLIT_HOST] = &string_hash_ops, + }; + + assert(arg_split_mode >= 0 && arg_split_mode < (int) ELEMENTSOF(hash_ops)); + + s->writers = hashmap_new(hash_ops[arg_split_mode]); + if (!s->writers) + return log_oom(); + + return 0; +} + +static int get_writer(RemoteServer *s, const char *host, + Writer **writer) { + const void *key; + _cleanup_writer_unref_ Writer *w = NULL; + int r; + + switch(arg_split_mode) { + case JOURNAL_WRITE_SPLIT_NONE: + key = "one and only"; + break; + + case JOURNAL_WRITE_SPLIT_HOST: + assert(host); + key = host; + break; + + default: + assert_not_reached("what split mode?"); + } + + w = hashmap_get(s->writers, key); + if (w) + writer_ref(w); + else { + w = writer_new(s); + if (!w) + return log_oom(); + + if (arg_split_mode == JOURNAL_WRITE_SPLIT_HOST) { + w->hashmap_key = strdup(key); + if (!w->hashmap_key) + return log_oom(); + } + + r = open_output(w, host); + if (r < 0) + return r; + + r = hashmap_put(s->writers, w->hashmap_key ?: key, w); + if (r < 0) + return r; + } + + *writer = w; + w = NULL; + return 0; +} + +/********************************************************************** + ********************************************************************** + **********************************************************************/ + +/* This should go away as soon as µhttpd allows state to be passed around. */ +static RemoteServer *server; + +static int dispatch_raw_source_event(sd_event_source *event, + int fd, + uint32_t revents, + void *userdata); +static int dispatch_raw_source_until_block(sd_event_source *event, + void *userdata); +static int dispatch_blocking_source_event(sd_event_source *event, + void *userdata); +static int dispatch_raw_connection_event(sd_event_source *event, + int fd, + uint32_t revents, + void *userdata); +static int dispatch_http_event(sd_event_source *event, + int fd, + uint32_t revents, + void *userdata); + +static int get_source_for_fd(RemoteServer *s, + int fd, char *name, RemoteSource **source) { + Writer *writer; + int r; + + /* This takes ownership of name, but only on success. */ + + assert(fd >= 0); + assert(source); + + if (!GREEDY_REALLOC0(s->sources, s->sources_size, fd + 1)) + return log_oom(); + + r = get_writer(s, name, &writer); + if (r < 0) + return log_warning_errno(r, "Failed to get writer for source %s: %m", + name); + + if (s->sources[fd] == NULL) { + s->sources[fd] = source_new(fd, false, name, writer); + if (!s->sources[fd]) { + writer_unref(writer); + return log_oom(); + } + + s->active++; + } + + *source = s->sources[fd]; + return 0; +} + +static int remove_source(RemoteServer *s, int fd) { + RemoteSource *source; + + assert(s); + assert(fd >= 0 && fd < (ssize_t) s->sources_size); + + source = s->sources[fd]; + if (source) { + /* this closes fd too */ + source_free(source); + s->sources[fd] = NULL; + s->active--; + } + + return 0; +} + +static int add_source(RemoteServer *s, int fd, char* name, bool own_name) { + + RemoteSource *source = NULL; + int r; + + /* This takes ownership of name, even on failure, if own_name is true. */ + + assert(s); + assert(fd >= 0); + assert(name); + + if (!own_name) { + name = strdup(name); + if (!name) + return log_oom(); + } + + r = get_source_for_fd(s, fd, name, &source); + if (r < 0) { + log_error_errno(r, "Failed to create source for fd:%d (%s): %m", + fd, name); + free(name); + return r; + } + + r = sd_event_add_io(s->events, &source->event, + fd, EPOLLIN|EPOLLRDHUP|EPOLLPRI, + dispatch_raw_source_event, source); + if (r == 0) { + /* Add additional source for buffer processing. It will be + * enabled later. */ + r = sd_event_add_defer(s->events, &source->buffer_event, + dispatch_raw_source_until_block, source); + if (r == 0) + sd_event_source_set_enabled(source->buffer_event, SD_EVENT_OFF); + } else if (r == -EPERM) { + log_debug("Falling back to sd_event_add_defer for fd:%d (%s)", fd, name); + r = sd_event_add_defer(s->events, &source->event, + dispatch_blocking_source_event, source); + if (r == 0) + sd_event_source_set_enabled(source->event, SD_EVENT_ON); + } + if (r < 0) { + log_error_errno(r, "Failed to register event source for fd:%d: %m", + fd); + goto error; + } + + r = sd_event_source_set_description(source->event, name); + if (r < 0) { + log_error_errno(r, "Failed to set source name for fd:%d: %m", fd); + goto error; + } + + return 1; /* work to do */ + + error: + remove_source(s, fd); + return r; +} + +static int add_raw_socket(RemoteServer *s, int fd) { + int r; + _cleanup_close_ int fd_ = fd; + char name[sizeof("raw-socket-")-1 + DECIMAL_STR_MAX(int) + 1]; + + assert(fd >= 0); + + r = sd_event_add_io(s->events, &s->listen_event, + fd, EPOLLIN, + dispatch_raw_connection_event, s); + if (r < 0) + return r; + + xsprintf(name, "raw-socket-%d", fd); + + r = sd_event_source_set_description(s->listen_event, name); + if (r < 0) + return r; + + fd_ = -1; + s->active++; + return 0; +} + +static int setup_raw_socket(RemoteServer *s, const char *address) { + int fd; + + fd = make_socket_fd(LOG_INFO, address, SOCK_STREAM, SOCK_CLOEXEC); + if (fd < 0) + return fd; + + return add_raw_socket(s, fd); +} + +/********************************************************************** + ********************************************************************** + **********************************************************************/ + +static int request_meta(void **connection_cls, int fd, char *hostname) { + RemoteSource *source; + Writer *writer; + int r; + + assert(connection_cls); + if (*connection_cls) + return 0; + + r = get_writer(server, hostname, &writer); + if (r < 0) + return log_warning_errno(r, "Failed to get writer for source %s: %m", + hostname); + + source = source_new(fd, true, hostname, writer); + if (!source) { + writer_unref(writer); + return log_oom(); + } + + log_debug("Added RemoteSource as connection metadata %p", source); + + *connection_cls = source; + return 0; +} + +static void request_meta_free(void *cls, + struct MHD_Connection *connection, + void **connection_cls, + enum MHD_RequestTerminationCode toe) { + RemoteSource *s; + + assert(connection_cls); + s = *connection_cls; + + if (s) { + log_debug("Cleaning up connection metadata %p", s); + source_free(s); + *connection_cls = NULL; + } +} + +static int process_http_upload( + struct MHD_Connection *connection, + const char *upload_data, + size_t *upload_data_size, + RemoteSource *source) { + + bool finished = false; + size_t remaining; + int r; + + assert(source); + + log_trace("%s: connection %p, %zu bytes", + __func__, connection, *upload_data_size); + + if (*upload_data_size) { + log_trace("Received %zu bytes", *upload_data_size); + + r = push_data(source, upload_data, *upload_data_size); + if (r < 0) + return mhd_respond_oom(connection); + + *upload_data_size = 0; + } else + finished = true; + + for (;;) { + r = process_source(source, arg_compress, arg_seal); + if (r == -EAGAIN) + break; + else if (r < 0) { + log_warning("Failed to process data for connection %p", connection); + if (r == -E2BIG) + return mhd_respondf(connection, + MHD_HTTP_REQUEST_ENTITY_TOO_LARGE, + "Entry is too large, maximum is %u bytes.\n", + DATA_SIZE_MAX); + else + return mhd_respondf(connection, + MHD_HTTP_UNPROCESSABLE_ENTITY, + "Processing failed: %s.", strerror(-r)); + } + } + + if (!finished) + return MHD_YES; + + /* The upload is finished */ + + remaining = source_non_empty(source); + if (remaining > 0) { + log_warning("Premature EOFbyte. %zu bytes lost.", remaining); + return mhd_respondf(connection, MHD_HTTP_EXPECTATION_FAILED, + "Premature EOF. %zu bytes of trailing data not processed.", + remaining); + } + + return mhd_respond(connection, MHD_HTTP_ACCEPTED, "OK.\n"); +}; + +static int request_handler( + void *cls, + struct MHD_Connection *connection, + const char *url, + const char *method, + const char *version, + const char *upload_data, + size_t *upload_data_size, + void **connection_cls) { + + const char *header; + int r, code, fd; + _cleanup_free_ char *hostname = NULL; + + assert(connection); + assert(connection_cls); + assert(url); + assert(method); + + log_trace("Handling a connection %s %s %s", method, url, version); + + if (*connection_cls) + return process_http_upload(connection, + upload_data, upload_data_size, + *connection_cls); + + if (!streq(method, "POST")) + return mhd_respond(connection, MHD_HTTP_NOT_ACCEPTABLE, + "Unsupported method.\n"); + + if (!streq(url, "/upload")) + return mhd_respond(connection, MHD_HTTP_NOT_FOUND, + "Not found.\n"); + + header = MHD_lookup_connection_value(connection, + MHD_HEADER_KIND, "Content-Type"); + if (!header || !streq(header, "application/vnd.fdo.journal")) + return mhd_respond(connection, MHD_HTTP_UNSUPPORTED_MEDIA_TYPE, + "Content-Type: application/vnd.fdo.journal" + " is required.\n"); + + { + const union MHD_ConnectionInfo *ci; + + ci = MHD_get_connection_info(connection, + MHD_CONNECTION_INFO_CONNECTION_FD); + if (!ci) { + log_error("MHD_get_connection_info failed: cannot get remote fd"); + return mhd_respond(connection, MHD_HTTP_INTERNAL_SERVER_ERROR, + "Cannot check remote address"); + } + + fd = ci->connect_fd; + assert(fd >= 0); + } + + if (server->check_trust) { + r = check_permissions(connection, &code, &hostname); + if (r < 0) + return code; + } else { + r = getpeername_pretty(fd, false, &hostname); + if (r < 0) + return mhd_respond(connection, MHD_HTTP_INTERNAL_SERVER_ERROR, + "Cannot check remote hostname"); + } + + assert(hostname); + + r = request_meta(connection_cls, fd, hostname); + if (r == -ENOMEM) + return respond_oom(connection); + else if (r < 0) + return mhd_respond(connection, MHD_HTTP_INTERNAL_SERVER_ERROR, + strerror(-r)); + + hostname = NULL; + return MHD_YES; +} + +static int setup_microhttpd_server(RemoteServer *s, + int fd, + const char *key, + const char *cert, + const char *trust) { + struct MHD_OptionItem opts[] = { + { MHD_OPTION_NOTIFY_COMPLETED, (intptr_t) request_meta_free}, + { MHD_OPTION_EXTERNAL_LOGGER, (intptr_t) microhttpd_logger}, + { MHD_OPTION_LISTEN_SOCKET, fd}, + { MHD_OPTION_CONNECTION_MEMORY_LIMIT, 128*1024}, + { MHD_OPTION_END}, + { MHD_OPTION_END}, + { MHD_OPTION_END}, + { MHD_OPTION_END}}; + int opts_pos = 4; + int flags = + MHD_USE_DEBUG | + MHD_USE_DUAL_STACK | + MHD_USE_EPOLL_LINUX_ONLY | + MHD_USE_PEDANTIC_CHECKS | + MHD_USE_PIPE_FOR_SHUTDOWN; + + const union MHD_DaemonInfo *info; + int r, epoll_fd; + MHDDaemonWrapper *d; + + assert(fd >= 0); + + r = fd_nonblock(fd, true); + if (r < 0) + return log_error_errno(r, "Failed to make fd:%d nonblocking: %m", fd); + + if (key) { + assert(cert); + + opts[opts_pos++] = (struct MHD_OptionItem) + {MHD_OPTION_HTTPS_MEM_KEY, 0, (char*) key}; + opts[opts_pos++] = (struct MHD_OptionItem) + {MHD_OPTION_HTTPS_MEM_CERT, 0, (char*) cert}; + + flags |= MHD_USE_SSL; + + if (trust) + opts[opts_pos++] = (struct MHD_OptionItem) + {MHD_OPTION_HTTPS_MEM_TRUST, 0, (char*) trust}; + } + + d = new(MHDDaemonWrapper, 1); + if (!d) + return log_oom(); + + d->fd = (uint64_t) fd; + + d->daemon = MHD_start_daemon(flags, 0, + NULL, NULL, + request_handler, NULL, + MHD_OPTION_ARRAY, opts, + MHD_OPTION_END); + if (!d->daemon) { + log_error("Failed to start µhttp daemon"); + r = -EINVAL; + goto error; + } + + log_debug("Started MHD %s daemon on fd:%d (wrapper @ %p)", + key ? "HTTPS" : "HTTP", fd, d); + + + info = MHD_get_daemon_info(d->daemon, MHD_DAEMON_INFO_EPOLL_FD_LINUX_ONLY); + if (!info) { + log_error("µhttp returned NULL daemon info"); + r = -EOPNOTSUPP; + goto error; + } + + epoll_fd = info->listen_fd; + if (epoll_fd < 0) { + log_error("µhttp epoll fd is invalid"); + r = -EUCLEAN; + goto error; + } + + r = sd_event_add_io(s->events, &d->event, + epoll_fd, EPOLLIN, + dispatch_http_event, d); + if (r < 0) { + log_error_errno(r, "Failed to add event callback: %m"); + goto error; + } + + r = sd_event_source_set_description(d->event, "epoll-fd"); + if (r < 0) { + log_error_errno(r, "Failed to set source name: %m"); + goto error; + } + + r = hashmap_ensure_allocated(&s->daemons, &uint64_hash_ops); + if (r < 0) { + log_oom(); + goto error; + } + + r = hashmap_put(s->daemons, &d->fd, d); + if (r < 0) { + log_error_errno(r, "Failed to add daemon to hashmap: %m"); + goto error; + } + + s->active++; + return 0; + +error: + MHD_stop_daemon(d->daemon); + free(d->daemon); + free(d); + return r; +} + +static int setup_microhttpd_socket(RemoteServer *s, + const char *address, + const char *key, + const char *cert, + const char *trust) { + int fd; + + fd = make_socket_fd(LOG_DEBUG, address, SOCK_STREAM, SOCK_CLOEXEC); + if (fd < 0) + return fd; + + return setup_microhttpd_server(s, fd, key, cert, trust); +} + +static int dispatch_http_event(sd_event_source *event, + int fd, + uint32_t revents, + void *userdata) { + MHDDaemonWrapper *d = userdata; + int r; + + assert(d); + + r = MHD_run(d->daemon); + if (r == MHD_NO) { + log_error("MHD_run failed!"); + // XXX: unregister daemon + return -EINVAL; + } + + return 1; /* work to do */ +} + +/********************************************************************** + ********************************************************************** + **********************************************************************/ + +static int setup_signals(RemoteServer *s) { + int r; + + assert(s); + + assert_se(sigprocmask_many(SIG_SETMASK, NULL, SIGINT, SIGTERM, -1) >= 0); + + r = sd_event_add_signal(s->events, &s->sigterm_event, SIGTERM, NULL, s); + if (r < 0) + return r; + + r = sd_event_add_signal(s->events, &s->sigint_event, SIGINT, NULL, s); + if (r < 0) + return r; + + return 0; +} + +static int negative_fd(const char *spec) { + /* Return a non-positive number as its inverse, -EINVAL otherwise. */ + + int fd, r; + + r = safe_atoi(spec, &fd); + if (r < 0) + return r; + + if (fd > 0) + return -EINVAL; + else + return -fd; +} + +static int remoteserver_init(RemoteServer *s, + const char* key, + const char* cert, + const char* trust) { + int r, n, fd; + char **file; + + assert(s); + + if ((arg_listen_raw || arg_listen_http) && trust) { + log_error("Option --trust makes all non-HTTPS connections untrusted."); + return -EINVAL; + } + + r = sd_event_default(&s->events); + if (r < 0) + return log_error_errno(r, "Failed to allocate event loop: %m"); + + setup_signals(s); + + assert(server == NULL); + server = s; + + r = init_writer_hashmap(s); + if (r < 0) + return r; + + n = sd_listen_fds(true); + if (n < 0) + return log_error_errno(n, "Failed to read listening file descriptors from environment: %m"); + else + log_debug("Received %d descriptors", n); + + if (MAX(http_socket, https_socket) >= SD_LISTEN_FDS_START + n) { + log_error("Received fewer sockets than expected"); + return -EBADFD; + } + + for (fd = SD_LISTEN_FDS_START; fd < SD_LISTEN_FDS_START + n; fd++) { + if (sd_is_socket(fd, AF_UNSPEC, 0, true)) { + log_debug("Received a listening socket (fd:%d)", fd); + + if (fd == http_socket) + r = setup_microhttpd_server(s, fd, NULL, NULL, NULL); + else if (fd == https_socket) + r = setup_microhttpd_server(s, fd, key, cert, trust); + else + r = add_raw_socket(s, fd); + } else if (sd_is_socket(fd, AF_UNSPEC, 0, false)) { + char *hostname; + + r = getpeername_pretty(fd, false, &hostname); + if (r < 0) + return log_error_errno(r, "Failed to retrieve remote name: %m"); + + log_debug("Received a connection socket (fd:%d) from %s", fd, hostname); + + r = add_source(s, fd, hostname, true); + } else { + log_error("Unknown socket passed on fd:%d", fd); + + return -EINVAL; + } + + if (r < 0) + return log_error_errno(r, "Failed to register socket (fd:%d): %m", + fd); + } + + if (arg_getter) { + log_info("Spawning getter %s...", arg_getter); + fd = spawn_getter(arg_getter); + if (fd < 0) + return fd; + + r = add_source(s, fd, (char*) arg_output, false); + if (r < 0) + return r; + } + + if (arg_url) { + const char *url; + char *hostname, *p; + + if (!strstr(arg_url, "/entries")) { + if (endswith(arg_url, "/")) + url = strjoina(arg_url, "entries"); + else + url = strjoina(arg_url, "/entries"); + } + else + url = strdupa(arg_url); + + log_info("Spawning curl %s...", url); + fd = spawn_curl(url); + if (fd < 0) + return fd; + + hostname = + startswith(arg_url, "https://") ?: + startswith(arg_url, "http://") ?: + arg_url; + + hostname = strdupa(hostname); + if ((p = strchr(hostname, '/'))) + *p = '\0'; + if ((p = strchr(hostname, ':'))) + *p = '\0'; + + r = add_source(s, fd, hostname, false); + if (r < 0) + return r; + } + + if (arg_listen_raw) { + log_debug("Listening on a socket..."); + r = setup_raw_socket(s, arg_listen_raw); + if (r < 0) + return r; + } + + if (arg_listen_http) { + r = setup_microhttpd_socket(s, arg_listen_http, NULL, NULL, NULL); + if (r < 0) + return r; + } + + if (arg_listen_https) { + r = setup_microhttpd_socket(s, arg_listen_https, key, cert, trust); + if (r < 0) + return r; + } + + STRV_FOREACH(file, arg_files) { + const char *output_name; + + if (streq(*file, "-")) { + log_debug("Using standard input as source."); + + fd = STDIN_FILENO; + output_name = "stdin"; + } else { + log_debug("Reading file %s...", *file); + + fd = open(*file, O_RDONLY|O_CLOEXEC|O_NOCTTY|O_NONBLOCK); + if (fd < 0) + return log_error_errno(errno, "Failed to open %s: %m", *file); + output_name = *file; + } + + r = add_source(s, fd, (char*) output_name, false); + if (r < 0) + return r; + } + + if (s->active == 0) { + log_error("Zero sources specified"); + return -EINVAL; + } + + if (arg_split_mode == JOURNAL_WRITE_SPLIT_NONE) { + /* In this case we know what the writer will be + called, so we can create it and verify that we can + create output as expected. */ + r = get_writer(s, NULL, &s->_single_writer); + if (r < 0) + return r; + } + + return 0; +} + +static void server_destroy(RemoteServer *s) { + size_t i; + MHDDaemonWrapper *d; + + while ((d = hashmap_steal_first(s->daemons))) { + MHD_stop_daemon(d->daemon); + sd_event_source_unref(d->event); + free(d); + } + + hashmap_free(s->daemons); + + assert(s->sources_size == 0 || s->sources); + for (i = 0; i < s->sources_size; i++) + remove_source(s, i); + free(s->sources); + + writer_unref(s->_single_writer); + hashmap_free(s->writers); + + sd_event_source_unref(s->sigterm_event); + sd_event_source_unref(s->sigint_event); + sd_event_source_unref(s->listen_event); + sd_event_unref(s->events); + + /* fds that we're listening on remain open... */ +} + +/********************************************************************** + ********************************************************************** + **********************************************************************/ + +static int handle_raw_source(sd_event_source *event, + int fd, + uint32_t revents, + RemoteServer *s) { + + RemoteSource *source; + int r; + + /* Returns 1 if there might be more data pending, + * 0 if data is currently exhausted, negative on error. + */ + + assert(fd >= 0 && fd < (ssize_t) s->sources_size); + source = s->sources[fd]; + assert(source->fd == fd); + + r = process_source(source, arg_compress, arg_seal); + if (source->state == STATE_EOF) { + size_t remaining; + + log_debug("EOF reached with source fd:%d (%s)", + source->fd, source->name); + + remaining = source_non_empty(source); + if (remaining > 0) + log_notice("Premature EOF. %zu bytes lost.", remaining); + remove_source(s, source->fd); + log_debug("%zu active sources remaining", s->active); + return 0; + } else if (r == -E2BIG) { + log_notice_errno(E2BIG, "Entry too big, skipped"); + return 1; + } else if (r == -EAGAIN) { + return 0; + } else if (r < 0) { + log_debug_errno(r, "Closing connection: %m"); + remove_source(server, fd); + return 0; + } else + return 1; +} + +static int dispatch_raw_source_until_block(sd_event_source *event, + void *userdata) { + RemoteSource *source = userdata; + int r; + + /* Make sure event stays around even if source is destroyed */ + sd_event_source_ref(event); + + r = handle_raw_source(event, source->fd, EPOLLIN, server); + if (r != 1) + /* No more data for now */ + sd_event_source_set_enabled(event, SD_EVENT_OFF); + + sd_event_source_unref(event); + + return r; +} + +static int dispatch_raw_source_event(sd_event_source *event, + int fd, + uint32_t revents, + void *userdata) { + RemoteSource *source = userdata; + int r; + + assert(source->event); + assert(source->buffer_event); + + r = handle_raw_source(event, fd, EPOLLIN, server); + if (r == 1) + /* Might have more data. We need to rerun the handler + * until we are sure the buffer is exhausted. */ + sd_event_source_set_enabled(source->buffer_event, SD_EVENT_ON); + + return r; +} + +static int dispatch_blocking_source_event(sd_event_source *event, + void *userdata) { + RemoteSource *source = userdata; + + return handle_raw_source(event, source->fd, EPOLLIN, server); +} + +static int accept_connection(const char* type, int fd, + SocketAddress *addr, char **hostname) { + int fd2, r; + + log_debug("Accepting new %s connection on fd:%d", type, fd); + fd2 = accept4(fd, &addr->sockaddr.sa, &addr->size, SOCK_NONBLOCK|SOCK_CLOEXEC); + if (fd2 < 0) + return log_error_errno(errno, "accept() on fd:%d failed: %m", fd); + + switch(socket_address_family(addr)) { + case AF_INET: + case AF_INET6: { + _cleanup_free_ char *a = NULL; + char *b; + + r = socket_address_print(addr, &a); + if (r < 0) { + log_error_errno(r, "socket_address_print(): %m"); + close(fd2); + return r; + } + + r = socknameinfo_pretty(&addr->sockaddr, addr->size, &b); + if (r < 0) { + log_error_errno(r, "Resolving hostname failed: %m"); + close(fd2); + return r; + } + + log_debug("Accepted %s %s connection from %s", + type, + socket_address_family(addr) == AF_INET ? "IP" : "IPv6", + a); + + *hostname = b; + + return fd2; + }; + default: + log_error("Rejected %s connection with unsupported family %d", + type, socket_address_family(addr)); + close(fd2); + + return -EINVAL; + } +} + +static int dispatch_raw_connection_event(sd_event_source *event, + int fd, + uint32_t revents, + void *userdata) { + RemoteServer *s = userdata; + int fd2; + SocketAddress addr = { + .size = sizeof(union sockaddr_union), + .type = SOCK_STREAM, + }; + char *hostname = NULL; + + fd2 = accept_connection("raw", fd, &addr, &hostname); + if (fd2 < 0) + return fd2; + + return add_source(s, fd2, hostname, true); +} + +/********************************************************************** + ********************************************************************** + **********************************************************************/ + +static const char* const journal_write_split_mode_table[_JOURNAL_WRITE_SPLIT_MAX] = { + [JOURNAL_WRITE_SPLIT_NONE] = "none", + [JOURNAL_WRITE_SPLIT_HOST] = "host", +}; + +DEFINE_PRIVATE_STRING_TABLE_LOOKUP(journal_write_split_mode, JournalWriteSplitMode); +static DEFINE_CONFIG_PARSE_ENUM(config_parse_write_split_mode, + journal_write_split_mode, + JournalWriteSplitMode, + "Failed to parse split mode setting"); + +static int parse_config(void) { + const ConfigTableItem items[] = { + { "Remote", "Seal", config_parse_bool, 0, &arg_seal }, + { "Remote", "SplitMode", config_parse_write_split_mode, 0, &arg_split_mode }, + { "Remote", "ServerKeyFile", config_parse_path, 0, &arg_key }, + { "Remote", "ServerCertificateFile", config_parse_path, 0, &arg_cert }, + { "Remote", "TrustedCertificateFile", config_parse_path, 0, &arg_trust }, + {}}; + + return config_parse_many(PKGSYSCONFDIR "/journal-remote.conf", + CONF_PATHS_NULSTR("systemd/journal-remote.conf.d"), + "Remote\0", config_item_table_lookup, items, + false, NULL); +} + +static void help(void) { + printf("%s [OPTIONS...] {FILE|-}...\n\n" + "Write external journal events to journal file(s).\n\n" + " -h --help Show this help\n" + " --version Show package version\n" + " --url=URL Read events from systemd-journal-gatewayd at URL\n" + " --getter=COMMAND Read events from the output of COMMAND\n" + " --listen-raw=ADDR Listen for connections at ADDR\n" + " --listen-http=ADDR Listen for HTTP connections at ADDR\n" + " --listen-https=ADDR Listen for HTTPS connections at ADDR\n" + " -o --output=FILE|DIR Write output to FILE or DIR/external-*.journal\n" + " --compress[=BOOL] XZ-compress the output journal (default: yes)\n" + " --seal[=BOOL] Use event sealing (default: no)\n" + " --key=FILENAME SSL key in PEM format (default:\n" + " \"" PRIV_KEY_FILE "\")\n" + " --cert=FILENAME SSL certificate in PEM format (default:\n" + " \"" CERT_FILE "\")\n" + " --trust=FILENAME|all SSL CA certificate or disable checking (default:\n" + " \"" TRUST_FILE "\")\n" + " --gnutls-log=CATEGORY...\n" + " Specify a list of gnutls logging categories\n" + " --split-mode=none|host How many output files to create\n" + "\n" + "Note: file descriptors from sd_listen_fds() will be consumed, too.\n" + , program_invocation_short_name); +} + +static int parse_argv(int argc, char *argv[]) { + enum { + ARG_VERSION = 0x100, + ARG_URL, + ARG_LISTEN_RAW, + ARG_LISTEN_HTTP, + ARG_LISTEN_HTTPS, + ARG_GETTER, + ARG_SPLIT_MODE, + ARG_COMPRESS, + ARG_SEAL, + ARG_KEY, + ARG_CERT, + ARG_TRUST, + ARG_GNUTLS_LOG, + }; + + static const struct option options[] = { + { "help", no_argument, NULL, 'h' }, + { "version", no_argument, NULL, ARG_VERSION }, + { "url", required_argument, NULL, ARG_URL }, + { "getter", required_argument, NULL, ARG_GETTER }, + { "listen-raw", required_argument, NULL, ARG_LISTEN_RAW }, + { "listen-http", required_argument, NULL, ARG_LISTEN_HTTP }, + { "listen-https", required_argument, NULL, ARG_LISTEN_HTTPS }, + { "output", required_argument, NULL, 'o' }, + { "split-mode", required_argument, NULL, ARG_SPLIT_MODE }, + { "compress", optional_argument, NULL, ARG_COMPRESS }, + { "seal", optional_argument, NULL, ARG_SEAL }, + { "key", required_argument, NULL, ARG_KEY }, + { "cert", required_argument, NULL, ARG_CERT }, + { "trust", required_argument, NULL, ARG_TRUST }, + { "gnutls-log", required_argument, NULL, ARG_GNUTLS_LOG }, + {} + }; + + int c, r; + bool type_a, type_b; + + assert(argc >= 0); + assert(argv); + + while ((c = getopt_long(argc, argv, "ho:", options, NULL)) >= 0) + switch(c) { + case 'h': + help(); + return 0 /* done */; + + case ARG_VERSION: + return version(); + + case ARG_URL: + if (arg_url) { + log_error("cannot currently set more than one --url"); + return -EINVAL; + } + + arg_url = optarg; + break; + + case ARG_GETTER: + if (arg_getter) { + log_error("cannot currently use --getter more than once"); + return -EINVAL; + } + + arg_getter = optarg; + break; + + case ARG_LISTEN_RAW: + if (arg_listen_raw) { + log_error("cannot currently use --listen-raw more than once"); + return -EINVAL; + } + + arg_listen_raw = optarg; + break; + + case ARG_LISTEN_HTTP: + if (arg_listen_http || http_socket >= 0) { + log_error("cannot currently use --listen-http more than once"); + return -EINVAL; + } + + r = negative_fd(optarg); + if (r >= 0) + http_socket = r; + else + arg_listen_http = optarg; + break; + + case ARG_LISTEN_HTTPS: + if (arg_listen_https || https_socket >= 0) { + log_error("cannot currently use --listen-https more than once"); + return -EINVAL; + } + + r = negative_fd(optarg); + if (r >= 0) + https_socket = r; + else + arg_listen_https = optarg; + + break; + + case ARG_KEY: + if (arg_key) { + log_error("Key file specified twice"); + return -EINVAL; + } + + arg_key = strdup(optarg); + if (!arg_key) + return log_oom(); + + break; + + case ARG_CERT: + if (arg_cert) { + log_error("Certificate file specified twice"); + return -EINVAL; + } + + arg_cert = strdup(optarg); + if (!arg_cert) + return log_oom(); + + break; + + case ARG_TRUST: + if (arg_trust || arg_trust_all) { + log_error("Confusing trusted CA configuration"); + return -EINVAL; + } + + if (streq(optarg, "all")) + arg_trust_all = true; + else { +#ifdef HAVE_GNUTLS + arg_trust = strdup(optarg); + if (!arg_trust) + return log_oom(); +#else + log_error("Option --trust is not available."); + return -EINVAL; +#endif + } + + break; + + case 'o': + if (arg_output) { + log_error("cannot use --output/-o more than once"); + return -EINVAL; + } + + arg_output = optarg; + break; + + case ARG_SPLIT_MODE: + arg_split_mode = journal_write_split_mode_from_string(optarg); + if (arg_split_mode == _JOURNAL_WRITE_SPLIT_INVALID) { + log_error("Invalid split mode: %s", optarg); + return -EINVAL; + } + break; + + case ARG_COMPRESS: + if (optarg) { + r = parse_boolean(optarg); + if (r < 0) { + log_error("Failed to parse --compress= parameter."); + return -EINVAL; + } + + arg_compress = !!r; + } else + arg_compress = true; + + break; + + case ARG_SEAL: + if (optarg) { + r = parse_boolean(optarg); + if (r < 0) { + log_error("Failed to parse --seal= parameter."); + return -EINVAL; + } + + arg_seal = !!r; + } else + arg_seal = true; + + break; + + case ARG_GNUTLS_LOG: { +#ifdef HAVE_GNUTLS + const char* p = optarg; + for (;;) { + _cleanup_free_ char *word = NULL; + + r = extract_first_word(&p, &word, ",", 0); + if (r < 0) + return log_error_errno(r, "Failed to parse --gnutls-log= argument: %m"); + + if (r == 0) + break; + + if (strv_push(&arg_gnutls_log, word) < 0) + return log_oom(); + + word = NULL; + } + break; +#else + log_error("Option --gnutls-log is not available."); + return -EINVAL; +#endif + } + + case '?': + return -EINVAL; + + default: + assert_not_reached("Unknown option code."); + } + + if (optind < argc) + arg_files = argv + optind; + + type_a = arg_getter || !strv_isempty(arg_files); + type_b = arg_url + || arg_listen_raw + || arg_listen_http || arg_listen_https + || sd_listen_fds(false) > 0; + if (type_a && type_b) { + log_error("Cannot use file input or --getter with " + "--arg-listen-... or socket activation."); + return -EINVAL; + } + if (type_a) { + if (!arg_output) { + log_error("Option --output must be specified with file input or --getter."); + return -EINVAL; + } + + arg_split_mode = JOURNAL_WRITE_SPLIT_NONE; + } + + if (arg_split_mode == JOURNAL_WRITE_SPLIT_NONE + && arg_output && is_dir(arg_output, true) > 0) { + log_error("For SplitMode=none, output must be a file."); + return -EINVAL; + } + + if (arg_split_mode == JOURNAL_WRITE_SPLIT_HOST + && arg_output && is_dir(arg_output, true) <= 0) { + log_error("For SplitMode=host, output must be a directory."); + return -EINVAL; + } + + log_debug("Full config: SplitMode=%s Key=%s Cert=%s Trust=%s", + journal_write_split_mode_to_string(arg_split_mode), + strna(arg_key), + strna(arg_cert), + strna(arg_trust)); + + return 1 /* work to do */; +} + +static int load_certificates(char **key, char **cert, char **trust) { + int r; + + r = read_full_file(arg_key ?: PRIV_KEY_FILE, key, NULL); + if (r < 0) + return log_error_errno(r, "Failed to read key from file '%s': %m", + arg_key ?: PRIV_KEY_FILE); + + r = read_full_file(arg_cert ?: CERT_FILE, cert, NULL); + if (r < 0) + return log_error_errno(r, "Failed to read certificate from file '%s': %m", + arg_cert ?: CERT_FILE); + + if (arg_trust_all) + log_info("Certificate checking disabled."); + else { + r = read_full_file(arg_trust ?: TRUST_FILE, trust, NULL); + if (r < 0) + return log_error_errno(r, "Failed to read CA certificate file '%s': %m", + arg_trust ?: TRUST_FILE); + } + + return 0; +} + +int main(int argc, char **argv) { + RemoteServer s = {}; + int r; + _cleanup_free_ char *key = NULL, *cert = NULL, *trust = NULL; + + log_show_color(true); + log_parse_environment(); + + r = parse_config(); + if (r < 0) + return EXIT_FAILURE; + + r = parse_argv(argc, argv); + if (r <= 0) + return r == 0 ? EXIT_SUCCESS : EXIT_FAILURE; + + + if (arg_listen_http || arg_listen_https) { + r = setup_gnutls_logger(arg_gnutls_log); + if (r < 0) + return EXIT_FAILURE; + } + + if (arg_listen_https || https_socket >= 0) + if (load_certificates(&key, &cert, &trust) < 0) + return EXIT_FAILURE; + + if (remoteserver_init(&s, key, cert, trust) < 0) + return EXIT_FAILURE; + + r = sd_event_set_watchdog(s.events, true); + if (r < 0) + log_error_errno(r, "Failed to enable watchdog: %m"); + else + log_debug("Watchdog is %s.", r > 0 ? "enabled" : "disabled"); + + log_debug("%s running as pid "PID_FMT, + program_invocation_short_name, getpid()); + sd_notify(false, + "READY=1\n" + "STATUS=Processing requests..."); + + while (s.active) { + r = sd_event_get_state(s.events); + if (r < 0) + break; + if (r == SD_EVENT_FINISHED) + break; + + r = sd_event_run(s.events, -1); + if (r < 0) { + log_error_errno(r, "Failed to run event loop: %m"); + break; + } + } + + sd_notifyf(false, + "STOPPING=1\n" + "STATUS=Shutting down after writing %" PRIu64 " entries...", s.event_count); + log_info("Finishing after writing %" PRIu64 " entries", s.event_count); + + server_destroy(&s); + + free(arg_key); + free(arg_cert); + free(arg_trust); + + return r >= 0 ? EXIT_SUCCESS : EXIT_FAILURE; +} diff --git a/src/grp-journal/grp-remote/systemd-journal-remote/journal-remote.conf.in b/src/grp-journal/grp-remote/systemd-journal-remote/journal-remote.conf.in new file mode 100644 index 0000000000..7122d63362 --- /dev/null +++ b/src/grp-journal/grp-remote/systemd-journal-remote/journal-remote.conf.in @@ -0,0 +1,6 @@ +[Remote] +# Seal=false +# SplitMode=host +# ServerKeyFile=@CERTIFICATEROOT@/private/journal-remote.pem +# ServerCertificateFile=@CERTIFICATEROOT@/certs/journal-remote.pem +# TrustedCertificateFile=@CERTIFICATEROOT@/ca/trusted.pem diff --git a/src/grp-journal/grp-remote/systemd-journal-remote/journal-remote.h b/src/grp-journal/grp-remote/systemd-journal-remote/journal-remote.h new file mode 100644 index 0000000000..2e8d5999b0 --- /dev/null +++ b/src/grp-journal/grp-remote/systemd-journal-remote/journal-remote.h @@ -0,0 +1,52 @@ +#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 + +#include "basic/hashmap.h" +#include "journal-remote-parse.h" +#include "journal-remote-write.h" +#include "microhttpd-util.h" + +typedef struct MHDDaemonWrapper MHDDaemonWrapper; + +struct MHDDaemonWrapper { + uint64_t fd; + struct MHD_Daemon *daemon; + + sd_event_source *event; +}; + +struct RemoteServer { + RemoteSource **sources; + size_t sources_size; + size_t active; + + sd_event *events; + sd_event_source *sigterm_event, *sigint_event, *listen_event; + + Hashmap *writers; + Writer *_single_writer; + uint64_t event_count; + + bool check_trust; + Hashmap *daemons; +}; diff --git a/src/grp-journal/grp-remote/systemd-journal-upload/Makefile b/src/grp-journal/grp-remote/systemd-journal-upload/Makefile new file mode 100644 index 0000000000..c47fcf0128 --- /dev/null +++ b/src/grp-journal/grp-remote/systemd-journal-upload/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 ($(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/grp-journal/grp-remote/systemd-journal-upload/journal-upload-journal.c b/src/grp-journal/grp-remote/systemd-journal-upload/journal-upload-journal.c new file mode 100644 index 0000000000..eddfc384fa --- /dev/null +++ b/src/grp-journal/grp-remote/systemd-journal-upload/journal-upload-journal.c @@ -0,0 +1,422 @@ +/*** + 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 "basic/alloc-util.h" +#include "journal-upload.h" +#include "basic/log.h" +#include "basic/utf8.h" +#include "basic/util.h" +#include + +/** + * Write up to size bytes to buf. Return negative on error, and number of + * bytes written otherwise. The last case is a kind of an error too. + */ +static ssize_t write_entry(char *buf, size_t size, Uploader *u) { + int r; + size_t pos = 0; + + assert(size <= SSIZE_MAX); + + for (;;) { + + switch(u->entry_state) { + case ENTRY_CURSOR: { + u->current_cursor = mfree(u->current_cursor); + + r = sd_journal_get_cursor(u->journal, &u->current_cursor); + if (r < 0) + return log_error_errno(r, "Failed to get cursor: %m"); + + r = snprintf(buf + pos, size - pos, + "__CURSOR=%s\n", u->current_cursor); + if (pos + r > size) + /* not enough space */ + return pos; + + u->entry_state++; + + if (pos + r == size) { + /* exactly one character short, but we don't need it */ + buf[size - 1] = '\n'; + return size; + } + + pos += r; + } /* fall through */ + + case ENTRY_REALTIME: { + usec_t realtime; + + r = sd_journal_get_realtime_usec(u->journal, &realtime); + if (r < 0) + return log_error_errno(r, "Failed to get realtime timestamp: %m"); + + r = snprintf(buf + pos, size - pos, + "__REALTIME_TIMESTAMP="USEC_FMT"\n", realtime); + if (r + pos > size) + /* not enough space */ + return pos; + + u->entry_state++; + + if (r + pos == size) { + /* exactly one character short, but we don't need it */ + buf[size - 1] = '\n'; + return size; + } + + pos += r; + } /* fall through */ + + case ENTRY_MONOTONIC: { + usec_t monotonic; + sd_id128_t boot_id; + + r = sd_journal_get_monotonic_usec(u->journal, &monotonic, &boot_id); + if (r < 0) + return log_error_errno(r, "Failed to get monotonic timestamp: %m"); + + r = snprintf(buf + pos, size - pos, + "__MONOTONIC_TIMESTAMP="USEC_FMT"\n", monotonic); + if (r + pos > size) + /* not enough space */ + return pos; + + u->entry_state++; + + if (r + pos == size) { + /* exactly one character short, but we don't need it */ + buf[size - 1] = '\n'; + return size; + } + + pos += r; + } /* fall through */ + + case ENTRY_BOOT_ID: { + sd_id128_t boot_id; + char sid[33]; + + r = sd_journal_get_monotonic_usec(u->journal, NULL, &boot_id); + if (r < 0) + return log_error_errno(r, "Failed to get monotonic timestamp: %m"); + + r = snprintf(buf + pos, size - pos, + "_BOOT_ID=%s\n", sd_id128_to_string(boot_id, sid)); + if (r + pos > size) + /* not enough space */ + return pos; + + u->entry_state++; + + if (r + pos == size) { + /* exactly one character short, but we don't need it */ + buf[size - 1] = '\n'; + return size; + } + + pos += r; + } /* fall through */ + + case ENTRY_NEW_FIELD: { + u->field_pos = 0; + + r = sd_journal_enumerate_data(u->journal, + &u->field_data, + &u->field_length); + if (r < 0) + return log_error_errno(r, "Failed to move to next field in entry: %m"); + else if (r == 0) { + u->entry_state = ENTRY_OUTRO; + continue; + } + + if (!utf8_is_printable_newline(u->field_data, + u->field_length, false)) { + u->entry_state = ENTRY_BINARY_FIELD_START; + continue; + } + + u->entry_state++; + } /* fall through */ + + case ENTRY_TEXT_FIELD: + case ENTRY_BINARY_FIELD: { + bool done; + size_t tocopy; + + done = size - pos > u->field_length - u->field_pos; + if (done) + tocopy = u->field_length - u->field_pos; + else + tocopy = size - pos; + + memcpy(buf + pos, + (char*) u->field_data + u->field_pos, + tocopy); + + if (done) { + buf[pos + tocopy] = '\n'; + pos += tocopy + 1; + u->entry_state = ENTRY_NEW_FIELD; + continue; + } else { + u->field_pos += tocopy; + return size; + } + } + + case ENTRY_BINARY_FIELD_START: { + const char *c; + size_t len; + + c = memchr(u->field_data, '=', u->field_length); + if (!c || c == u->field_data) { + log_error("Invalid field."); + return -EINVAL; + } + + len = c - (const char*)u->field_data; + + /* need space for label + '\n' */ + if (size - pos < len + 1) + return pos; + + memcpy(buf + pos, u->field_data, len); + buf[pos + len] = '\n'; + pos += len + 1; + + u->field_pos = len + 1; + u->entry_state++; + } /* fall through */ + + case ENTRY_BINARY_FIELD_SIZE: { + uint64_t le64; + + /* need space for uint64_t */ + if (size - pos < 8) + return pos; + + le64 = htole64(u->field_length - u->field_pos); + memcpy(buf + pos, &le64, 8); + pos += 8; + + u->entry_state++; + continue; + } + + case ENTRY_OUTRO: + /* need space for '\n' */ + if (size - pos < 1) + return pos; + + buf[pos++] = '\n'; + u->entry_state++; + u->entries_sent++; + + return pos; + + default: + assert_not_reached("WTF?"); + } + } + assert_not_reached("WTF?"); +} + +static inline void check_update_watchdog(Uploader *u) { + usec_t after; + usec_t elapsed_time; + + if (u->watchdog_usec <= 0) + return; + + after = now(CLOCK_MONOTONIC); + elapsed_time = usec_sub(after, u->watchdog_timestamp); + if (elapsed_time > u->watchdog_usec / 2) { + log_debug("Update watchdog timer"); + sd_notify(false, "WATCHDOG=1"); + u->watchdog_timestamp = after; + } +} + +static size_t journal_input_callback(void *buf, size_t size, size_t nmemb, void *userp) { + Uploader *u = userp; + int r; + sd_journal *j; + size_t filled = 0; + ssize_t w; + + assert(u); + assert(nmemb <= SSIZE_MAX / size); + + check_update_watchdog(u); + + j = u->journal; + + while (j && filled < size * nmemb) { + if (u->entry_state == ENTRY_DONE) { + r = sd_journal_next(j); + if (r < 0) { + log_error_errno(r, "Failed to move to next entry in journal: %m"); + return CURL_READFUNC_ABORT; + } else if (r == 0) { + if (u->input_event) + log_debug("No more entries, waiting for journal."); + else { + log_info("No more entries, closing journal."); + close_journal_input(u); + } + + u->uploading = false; + + break; + } + + u->entry_state = ENTRY_CURSOR; + } + + w = write_entry((char*)buf + filled, size * nmemb - filled, u); + if (w < 0) + return CURL_READFUNC_ABORT; + filled += w; + + if (filled == 0) { + log_error("Buffer space is too small to write entry."); + return CURL_READFUNC_ABORT; + } else if (u->entry_state != ENTRY_DONE) + /* This means that all available space was used up */ + break; + + log_debug("Entry %zu (%s) has been uploaded.", + u->entries_sent, u->current_cursor); + } + + return filled; +} + +void close_journal_input(Uploader *u) { + assert(u); + + if (u->journal) { + log_debug("Closing journal input."); + + sd_journal_close(u->journal); + u->journal = NULL; + } + u->timeout = 0; +} + +static int process_journal_input(Uploader *u, int skip) { + int r; + + if (u->uploading) + return 0; + + r = sd_journal_next_skip(u->journal, skip); + if (r < 0) + return log_error_errno(r, "Failed to skip to next entry: %m"); + else if (r < skip) + return 0; + + /* have data */ + u->entry_state = ENTRY_CURSOR; + return start_upload(u, journal_input_callback, u); +} + +int check_journal_input(Uploader *u) { + if (u->input_event) { + int r; + + r = sd_journal_process(u->journal); + if (r < 0) { + log_error_errno(r, "Failed to process journal: %m"); + close_journal_input(u); + return r; + } + + if (r == SD_JOURNAL_NOP) + return 0; + } + + return process_journal_input(u, 1); +} + +static int dispatch_journal_input(sd_event_source *event, + int fd, + uint32_t revents, + void *userp) { + Uploader *u = userp; + + assert(u); + + if (u->uploading) + return 0; + + log_debug("Detected journal input, checking for new data."); + return check_journal_input(u); +} + +int open_journal_for_upload(Uploader *u, + sd_journal *j, + const char *cursor, + bool after_cursor, + bool follow) { + int fd, r, events; + + u->journal = j; + + sd_journal_set_data_threshold(j, 0); + + if (follow) { + fd = sd_journal_get_fd(j); + if (fd < 0) + return log_error_errno(fd, "sd_journal_get_fd failed: %m"); + + events = sd_journal_get_events(j); + + r = sd_journal_reliable_fd(j); + assert(r >= 0); + if (r > 0) + u->timeout = -1; + else + u->timeout = JOURNAL_UPLOAD_POLL_TIMEOUT; + + r = sd_event_add_io(u->events, &u->input_event, + fd, events, dispatch_journal_input, u); + if (r < 0) + return log_error_errno(r, "Failed to register input event: %m"); + + log_debug("Listening for journal events on fd:%d, timeout %d", + fd, u->timeout == (uint64_t) -1 ? -1 : (int) u->timeout); + } else + log_debug("Not listening for journal events."); + + if (cursor) { + r = sd_journal_seek_cursor(j, cursor); + if (r < 0) + return log_error_errno(r, "Failed to seek to cursor %s: %m", + cursor); + } + + return process_journal_input(u, 1 + !!after_cursor); +} diff --git a/src/grp-journal/grp-remote/systemd-journal-upload/journal-upload.c b/src/grp-journal/grp-remote/systemd-journal-upload/journal-upload.c new file mode 100644 index 0000000000..643f44cd86 --- /dev/null +++ b/src/grp-journal/grp-remote/systemd-journal-upload/journal-upload.c @@ -0,0 +1,880 @@ +/*** + 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 + +#include "basic/alloc-util.h" +#include "shared/conf-parser.h" +#include "basic/def.h" +#include "basic/fd-util.h" +#include "basic/fileio.h" +#include "basic/formats-util.h" +#include "basic/glob-util.h" +#include "journal-upload.h" +#include "basic/log.h" +#include "basic/mkdir.h" +#include "basic/parse-util.h" +#include "basic/sigbus.h" +#include "basic/signal-util.h" +#include "basic/string-util.h" +#include "basic/util.h" + +#define PRIV_KEY_FILE CERTIFICATE_ROOT "/private/journal-upload.pem" +#define CERT_FILE CERTIFICATE_ROOT "/certs/journal-upload.pem" +#define TRUST_FILE CERTIFICATE_ROOT "/ca/trusted.pem" +#define DEFAULT_PORT 19532 + +static const char* arg_url = NULL; +static const char *arg_key = NULL; +static const char *arg_cert = NULL; +static const char *arg_trust = NULL; +static const char *arg_directory = NULL; +static char **arg_file = NULL; +static const char *arg_cursor = NULL; +static bool arg_after_cursor = false; +static int arg_journal_type = 0; +static const char *arg_machine = NULL; +static bool arg_merge = false; +static int arg_follow = -1; +static const char *arg_save_state = NULL; + +static void close_fd_input(Uploader *u); + +#define SERVER_ANSWER_KEEP 2048 + +#define STATE_FILE "/var/lib/systemd/journal-upload/state" + +#define easy_setopt(curl, opt, value, level, cmd) \ + do { \ + code = curl_easy_setopt(curl, opt, value); \ + if (code) { \ + log_full(level, \ + "curl_easy_setopt " #opt " failed: %s", \ + curl_easy_strerror(code)); \ + cmd; \ + } \ + } while (0) + +static size_t output_callback(char *buf, + size_t size, + size_t nmemb, + void *userp) { + Uploader *u = userp; + + assert(u); + + log_debug("The server answers (%zu bytes): %.*s", + size*nmemb, (int)(size*nmemb), buf); + + if (nmemb && !u->answer) { + u->answer = strndup(buf, size*nmemb); + if (!u->answer) + log_warning_errno(ENOMEM, "Failed to store server answer (%zu bytes): %m", + size*nmemb); + } + + return size * nmemb; +} + +static int check_cursor_updating(Uploader *u) { + _cleanup_free_ char *temp_path = NULL; + _cleanup_fclose_ FILE *f = NULL; + int r; + + if (!u->state_file) + return 0; + + r = mkdir_parents(u->state_file, 0755); + if (r < 0) + return log_error_errno(r, "Cannot create parent directory of state file %s: %m", + u->state_file); + + r = fopen_temporary(u->state_file, &f, &temp_path); + if (r < 0) + return log_error_errno(r, "Cannot save state to %s: %m", + u->state_file); + unlink(temp_path); + + return 0; +} + +static int update_cursor_state(Uploader *u) { + _cleanup_free_ char *temp_path = NULL; + _cleanup_fclose_ FILE *f = NULL; + int r; + + if (!u->state_file || !u->last_cursor) + return 0; + + r = fopen_temporary(u->state_file, &f, &temp_path); + if (r < 0) + goto fail; + + fprintf(f, + "# This is private data. Do not parse.\n" + "LAST_CURSOR=%s\n", + u->last_cursor); + + r = fflush_and_check(f); + if (r < 0) + goto fail; + + if (rename(temp_path, u->state_file) < 0) { + r = -errno; + goto fail; + } + + return 0; + +fail: + if (temp_path) + (void) unlink(temp_path); + + (void) unlink(u->state_file); + + return log_error_errno(r, "Failed to save state %s: %m", u->state_file); +} + +static int load_cursor_state(Uploader *u) { + int r; + + if (!u->state_file) + return 0; + + r = parse_env_file(u->state_file, NEWLINE, + "LAST_CURSOR", &u->last_cursor, + NULL); + + if (r == -ENOENT) + log_debug("State file %s is not present.", u->state_file); + else if (r < 0) + return log_error_errno(r, "Failed to read state file %s: %m", + u->state_file); + else + log_debug("Last cursor was %s", u->last_cursor); + + return 0; +} + + + +int start_upload(Uploader *u, + size_t (*input_callback)(void *ptr, + size_t size, + size_t nmemb, + void *userdata), + void *data) { + CURLcode code; + + assert(u); + assert(input_callback); + + if (!u->header) { + struct curl_slist *h; + + h = curl_slist_append(NULL, "Content-Type: application/vnd.fdo.journal"); + if (!h) + return log_oom(); + + h = curl_slist_append(h, "Transfer-Encoding: chunked"); + if (!h) { + curl_slist_free_all(h); + return log_oom(); + } + + h = curl_slist_append(h, "Accept: text/plain"); + if (!h) { + curl_slist_free_all(h); + return log_oom(); + } + + u->header = h; + } + + if (!u->easy) { + CURL *curl; + + curl = curl_easy_init(); + if (!curl) { + log_error("Call to curl_easy_init failed."); + return -ENOSR; + } + + /* tell it to POST to the URL */ + easy_setopt(curl, CURLOPT_POST, 1L, + LOG_ERR, return -EXFULL); + + easy_setopt(curl, CURLOPT_ERRORBUFFER, u->error, + LOG_ERR, return -EXFULL); + + /* set where to write to */ + easy_setopt(curl, CURLOPT_WRITEFUNCTION, output_callback, + LOG_ERR, return -EXFULL); + + easy_setopt(curl, CURLOPT_WRITEDATA, data, + LOG_ERR, return -EXFULL); + + /* set where to read from */ + easy_setopt(curl, CURLOPT_READFUNCTION, input_callback, + LOG_ERR, return -EXFULL); + + easy_setopt(curl, CURLOPT_READDATA, data, + LOG_ERR, return -EXFULL); + + /* use our special own mime type and chunked transfer */ + easy_setopt(curl, CURLOPT_HTTPHEADER, u->header, + LOG_ERR, return -EXFULL); + + if (_unlikely_(log_get_max_level() >= LOG_DEBUG)) + /* enable verbose for easier tracing */ + easy_setopt(curl, CURLOPT_VERBOSE, 1L, LOG_WARNING, ); + + easy_setopt(curl, CURLOPT_USERAGENT, + "systemd-journal-upload " PACKAGE_STRING, + LOG_WARNING, ); + + if (arg_key || startswith(u->url, "https://")) { + easy_setopt(curl, CURLOPT_SSLKEY, arg_key ?: PRIV_KEY_FILE, + LOG_ERR, return -EXFULL); + easy_setopt(curl, CURLOPT_SSLCERT, arg_cert ?: CERT_FILE, + LOG_ERR, return -EXFULL); + } + + if (streq_ptr(arg_trust, "all")) + easy_setopt(curl, CURLOPT_SSL_VERIFYPEER, 0, + LOG_ERR, return -EUCLEAN); + else if (arg_trust || startswith(u->url, "https://")) + easy_setopt(curl, CURLOPT_CAINFO, arg_trust ?: TRUST_FILE, + LOG_ERR, return -EXFULL); + + if (arg_key || arg_trust) + easy_setopt(curl, CURLOPT_SSLVERSION, CURL_SSLVERSION_TLSv1, + LOG_WARNING, ); + + u->easy = curl; + } else { + /* truncate the potential old error message */ + u->error[0] = '\0'; + + free(u->answer); + u->answer = 0; + } + + /* upload to this place */ + code = curl_easy_setopt(u->easy, CURLOPT_URL, u->url); + if (code) { + log_error("curl_easy_setopt CURLOPT_URL failed: %s", + curl_easy_strerror(code)); + return -EXFULL; + } + + u->uploading = true; + + return 0; +} + +static size_t fd_input_callback(void *buf, size_t size, size_t nmemb, void *userp) { + Uploader *u = userp; + + ssize_t r; + + assert(u); + assert(nmemb <= SSIZE_MAX / size); + + if (u->input < 0) + return 0; + + r = read(u->input, buf, size * nmemb); + log_debug("%s: allowed %zu, read %zd", __func__, size*nmemb, r); + + if (r > 0) + return r; + + u->uploading = false; + if (r == 0) { + log_debug("Reached EOF"); + close_fd_input(u); + return 0; + } else { + log_error_errno(errno, "Aborting transfer after read error on input: %m."); + return CURL_READFUNC_ABORT; + } +} + +static void close_fd_input(Uploader *u) { + assert(u); + + if (u->input >= 0) + close_nointr(u->input); + u->input = -1; + u->timeout = 0; +} + +static int dispatch_fd_input(sd_event_source *event, + int fd, + uint32_t revents, + void *userp) { + Uploader *u = userp; + + assert(u); + assert(fd >= 0); + + if (revents & EPOLLHUP) { + log_debug("Received HUP"); + close_fd_input(u); + return 0; + } + + if (!(revents & EPOLLIN)) { + log_warning("Unexpected poll event %"PRIu32".", revents); + return -EINVAL; + } + + if (u->uploading) { + log_warning("dispatch_fd_input called when uploading, ignoring."); + return 0; + } + + return start_upload(u, fd_input_callback, u); +} + +static int open_file_for_upload(Uploader *u, const char *filename) { + int fd, r = 0; + + if (streq(filename, "-")) + fd = STDIN_FILENO; + else { + fd = open(filename, O_RDONLY|O_CLOEXEC|O_NOCTTY); + if (fd < 0) + return log_error_errno(errno, "Failed to open %s: %m", filename); + } + + u->input = fd; + + if (arg_follow) { + r = sd_event_add_io(u->events, &u->input_event, + fd, EPOLLIN, dispatch_fd_input, u); + if (r < 0) { + if (r != -EPERM || arg_follow > 0) + return log_error_errno(r, "Failed to register input event: %m"); + + /* Normal files should just be consumed without polling. */ + r = start_upload(u, fd_input_callback, u); + } + } + + return r; +} + +static int dispatch_sigterm(sd_event_source *event, + const struct signalfd_siginfo *si, + void *userdata) { + Uploader *u = userdata; + + assert(u); + + log_received_signal(LOG_INFO, si); + + close_fd_input(u); + close_journal_input(u); + + sd_event_exit(u->events, 0); + return 0; +} + +static int setup_signals(Uploader *u) { + int r; + + assert(u); + + assert_se(sigprocmask_many(SIG_SETMASK, NULL, SIGINT, SIGTERM, -1) >= 0); + + r = sd_event_add_signal(u->events, &u->sigterm_event, SIGTERM, dispatch_sigterm, u); + if (r < 0) + return r; + + r = sd_event_add_signal(u->events, &u->sigint_event, SIGINT, dispatch_sigterm, u); + if (r < 0) + return r; + + return 0; +} + +static int setup_uploader(Uploader *u, const char *url, const char *state_file) { + int r; + const char *host, *proto = ""; + + assert(u); + assert(url); + + memzero(u, sizeof(Uploader)); + u->input = -1; + + if (!(host = startswith(url, "http://")) && !(host = startswith(url, "https://"))) { + host = url; + proto = "https://"; + } + + if (strchr(host, ':')) + u->url = strjoin(proto, url, "/upload", NULL); + else { + char *t; + size_t x; + + t = strdupa(url); + x = strlen(t); + while (x > 0 && t[x - 1] == '/') + t[x - 1] = '\0'; + + u->url = strjoin(proto, t, ":" STRINGIFY(DEFAULT_PORT), "/upload", NULL); + } + if (!u->url) + return log_oom(); + + u->state_file = state_file; + + r = sd_event_default(&u->events); + if (r < 0) + return log_error_errno(r, "sd_event_default failed: %m"); + + r = setup_signals(u); + if (r < 0) + return log_error_errno(r, "Failed to set up signals: %m"); + + (void) sd_watchdog_enabled(false, &u->watchdog_usec); + + return load_cursor_state(u); +} + +static void destroy_uploader(Uploader *u) { + assert(u); + + curl_easy_cleanup(u->easy); + curl_slist_free_all(u->header); + free(u->answer); + + free(u->last_cursor); + free(u->current_cursor); + + free(u->url); + + u->input_event = sd_event_source_unref(u->input_event); + + close_fd_input(u); + close_journal_input(u); + + sd_event_source_unref(u->sigterm_event); + sd_event_source_unref(u->sigint_event); + sd_event_unref(u->events); +} + +static int perform_upload(Uploader *u) { + CURLcode code; + long status; + + assert(u); + + u->watchdog_timestamp = now(CLOCK_MONOTONIC); + code = curl_easy_perform(u->easy); + if (code) { + if (u->error[0]) + log_error("Upload to %s failed: %.*s", + u->url, (int) sizeof(u->error), u->error); + else + log_error("Upload to %s failed: %s", + u->url, curl_easy_strerror(code)); + return -EIO; + } + + code = curl_easy_getinfo(u->easy, CURLINFO_RESPONSE_CODE, &status); + if (code) { + log_error("Failed to retrieve response code: %s", + curl_easy_strerror(code)); + return -EUCLEAN; + } + + if (status >= 300) { + log_error("Upload to %s failed with code %ld: %s", + u->url, status, strna(u->answer)); + return -EIO; + } else if (status < 200) { + log_error("Upload to %s finished with unexpected code %ld: %s", + u->url, status, strna(u->answer)); + return -EIO; + } else + log_debug("Upload finished successfully with code %ld: %s", + status, strna(u->answer)); + + free(u->last_cursor); + u->last_cursor = u->current_cursor; + u->current_cursor = NULL; + + return update_cursor_state(u); +} + +static int parse_config(void) { + const ConfigTableItem items[] = { + { "Upload", "URL", config_parse_string, 0, &arg_url }, + { "Upload", "ServerKeyFile", config_parse_path, 0, &arg_key }, + { "Upload", "ServerCertificateFile", config_parse_path, 0, &arg_cert }, + { "Upload", "TrustedCertificateFile", config_parse_path, 0, &arg_trust }, + {}}; + + return config_parse_many(PKGSYSCONFDIR "/journal-upload.conf", + CONF_PATHS_NULSTR("systemd/journal-upload.conf.d"), + "Upload\0", config_item_table_lookup, items, + false, NULL); +} + +static void help(void) { + printf("%s -u URL {FILE|-}...\n\n" + "Upload journal events to a remote server.\n\n" + " -h --help Show this help\n" + " --version Show package version\n" + " -u --url=URL Upload to this address (default port " + STRINGIFY(DEFAULT_PORT) ")\n" + " --key=FILENAME Specify key in PEM format (default:\n" + " \"" PRIV_KEY_FILE "\")\n" + " --cert=FILENAME Specify certificate in PEM format (default:\n" + " \"" CERT_FILE "\")\n" + " --trust=FILENAME|all Specify CA certificate or disable checking (default:\n" + " \"" TRUST_FILE "\")\n" + " --system Use the system journal\n" + " --user Use the user journal for the current user\n" + " -m --merge Use all available journals\n" + " -M --machine=CONTAINER Operate on local container\n" + " -D --directory=PATH Use journal files from directory\n" + " --file=PATH Use this journal file\n" + " --cursor=CURSOR Start at the specified cursor\n" + " --after-cursor=CURSOR Start after the specified cursor\n" + " --follow[=BOOL] Do [not] wait for input\n" + " --save-state[=FILE] Save uploaded cursors (default \n" + " " STATE_FILE ")\n" + " -h --help Show this help and exit\n" + " --version Print version string and exit\n" + , program_invocation_short_name); +} + +static int parse_argv(int argc, char *argv[]) { + enum { + ARG_VERSION = 0x100, + ARG_KEY, + ARG_CERT, + ARG_TRUST, + ARG_USER, + ARG_SYSTEM, + ARG_FILE, + ARG_CURSOR, + ARG_AFTER_CURSOR, + ARG_FOLLOW, + ARG_SAVE_STATE, + }; + + static const struct option options[] = { + { "help", no_argument, NULL, 'h' }, + { "version", no_argument, NULL, ARG_VERSION }, + { "url", required_argument, NULL, 'u' }, + { "key", required_argument, NULL, ARG_KEY }, + { "cert", required_argument, NULL, ARG_CERT }, + { "trust", required_argument, NULL, ARG_TRUST }, + { "system", no_argument, NULL, ARG_SYSTEM }, + { "user", no_argument, NULL, ARG_USER }, + { "merge", no_argument, NULL, 'm' }, + { "machine", required_argument, NULL, 'M' }, + { "directory", required_argument, NULL, 'D' }, + { "file", required_argument, NULL, ARG_FILE }, + { "cursor", required_argument, NULL, ARG_CURSOR }, + { "after-cursor", required_argument, NULL, ARG_AFTER_CURSOR }, + { "follow", optional_argument, NULL, ARG_FOLLOW }, + { "save-state", optional_argument, NULL, ARG_SAVE_STATE }, + {} + }; + + int c, r; + + assert(argc >= 0); + assert(argv); + + opterr = 0; + + while ((c = getopt_long(argc, argv, "hu:mM:D:", options, NULL)) >= 0) + switch(c) { + case 'h': + help(); + return 0 /* done */; + + case ARG_VERSION: + return version(); + + case 'u': + if (arg_url) { + log_error("cannot use more than one --url"); + return -EINVAL; + } + + arg_url = optarg; + break; + + case ARG_KEY: + if (arg_key) { + log_error("cannot use more than one --key"); + return -EINVAL; + } + + arg_key = optarg; + break; + + case ARG_CERT: + if (arg_cert) { + log_error("cannot use more than one --cert"); + return -EINVAL; + } + + arg_cert = optarg; + break; + + case ARG_TRUST: + if (arg_trust) { + log_error("cannot use more than one --trust"); + return -EINVAL; + } + + arg_trust = optarg; + break; + + case ARG_SYSTEM: + arg_journal_type |= SD_JOURNAL_SYSTEM; + break; + + case ARG_USER: + arg_journal_type |= SD_JOURNAL_CURRENT_USER; + break; + + case 'm': + arg_merge = true; + break; + + case 'M': + if (arg_machine) { + log_error("cannot use more than one --machine/-M"); + return -EINVAL; + } + + arg_machine = optarg; + break; + + case 'D': + if (arg_directory) { + log_error("cannot use more than one --directory/-D"); + return -EINVAL; + } + + arg_directory = optarg; + break; + + case ARG_FILE: + r = glob_extend(&arg_file, optarg); + if (r < 0) + return log_error_errno(r, "Failed to add paths: %m"); + break; + + case ARG_CURSOR: + if (arg_cursor) { + log_error("cannot use more than one --cursor/--after-cursor"); + return -EINVAL; + } + + arg_cursor = optarg; + break; + + case ARG_AFTER_CURSOR: + if (arg_cursor) { + log_error("cannot use more than one --cursor/--after-cursor"); + return -EINVAL; + } + + arg_cursor = optarg; + arg_after_cursor = true; + break; + + case ARG_FOLLOW: + if (optarg) { + r = parse_boolean(optarg); + if (r < 0) { + log_error("Failed to parse --follow= parameter."); + return -EINVAL; + } + + arg_follow = !!r; + } else + arg_follow = true; + + break; + + case ARG_SAVE_STATE: + arg_save_state = optarg ?: STATE_FILE; + break; + + case '?': + log_error("Unknown option %s.", argv[optind-1]); + return -EINVAL; + + case ':': + log_error("Missing argument to %s.", argv[optind-1]); + return -EINVAL; + + default: + assert_not_reached("Unhandled option code."); + } + + if (!arg_url) { + log_error("Required --url/-u option missing."); + return -EINVAL; + } + + if (!!arg_key != !!arg_cert) { + log_error("Options --key and --cert must be used together."); + return -EINVAL; + } + + if (optind < argc && (arg_directory || arg_file || arg_machine || arg_journal_type)) { + log_error("Input arguments make no sense with journal input."); + return -EINVAL; + } + + return 1; +} + +static int open_journal(sd_journal **j) { + int r; + + if (arg_directory) + r = sd_journal_open_directory(j, arg_directory, arg_journal_type); + else if (arg_file) + r = sd_journal_open_files(j, (const char**) arg_file, 0); + else if (arg_machine) + r = sd_journal_open_container(j, arg_machine, 0); + else + r = sd_journal_open(j, !arg_merge*SD_JOURNAL_LOCAL_ONLY + arg_journal_type); + if (r < 0) + log_error_errno(r, "Failed to open %s: %m", + arg_directory ? arg_directory : arg_file ? "files" : "journal"); + return r; +} + +int main(int argc, char **argv) { + Uploader u; + int r; + bool use_journal; + + log_show_color(true); + log_parse_environment(); + + r = parse_config(); + if (r < 0) + goto finish; + + r = parse_argv(argc, argv); + if (r <= 0) + goto finish; + + sigbus_install(); + + r = setup_uploader(&u, arg_url, arg_save_state); + if (r < 0) + goto cleanup; + + sd_event_set_watchdog(u.events, true); + + r = check_cursor_updating(&u); + if (r < 0) + goto cleanup; + + log_debug("%s running as pid "PID_FMT, + program_invocation_short_name, getpid()); + + use_journal = optind >= argc; + if (use_journal) { + sd_journal *j; + r = open_journal(&j); + if (r < 0) + goto finish; + r = open_journal_for_upload(&u, j, + arg_cursor ?: u.last_cursor, + arg_cursor ? arg_after_cursor : true, + !!arg_follow); + if (r < 0) + goto finish; + } + + sd_notify(false, + "READY=1\n" + "STATUS=Processing input..."); + + for (;;) { + r = sd_event_get_state(u.events); + if (r < 0) + break; + if (r == SD_EVENT_FINISHED) + break; + + if (use_journal) { + if (!u.journal) + break; + + r = check_journal_input(&u); + } else if (u.input < 0 && !use_journal) { + if (optind >= argc) + break; + + log_debug("Using %s as input.", argv[optind]); + r = open_file_for_upload(&u, argv[optind++]); + } + if (r < 0) + goto cleanup; + + if (u.uploading) { + r = perform_upload(&u); + if (r < 0) + break; + } + + r = sd_event_run(u.events, u.timeout); + if (r < 0) { + log_error_errno(r, "Failed to run event loop: %m"); + break; + } + } + +cleanup: + sd_notify(false, + "STOPPING=1\n" + "STATUS=Shutting down..."); + + destroy_uploader(&u); + +finish: + return r >= 0 ? EXIT_SUCCESS : EXIT_FAILURE; +} diff --git a/src/grp-journal/grp-remote/systemd-journal-upload/journal-upload.conf.in b/src/grp-journal/grp-remote/systemd-journal-upload/journal-upload.conf.in new file mode 100644 index 0000000000..c5670682e8 --- /dev/null +++ b/src/grp-journal/grp-remote/systemd-journal-upload/journal-upload.conf.in @@ -0,0 +1,5 @@ +[Upload] +# URL= +# ServerKeyFile=@CERTIFICATEROOT@/private/journal-upload.pem +# ServerCertificateFile=@CERTIFICATEROOT@/certs/journal-upload.pem +# TrustedCertificateFile=@CERTIFICATEROOT@/ca/trusted.pem diff --git a/src/grp-journal/grp-remote/systemd-journal-upload/journal-upload.h b/src/grp-journal/grp-remote/systemd-journal-upload/journal-upload.h new file mode 100644 index 0000000000..0b0336c84b --- /dev/null +++ b/src/grp-journal/grp-remote/systemd-journal-upload/journal-upload.h @@ -0,0 +1,71 @@ +#pragma once + +#include + +#include +#include +#include "basic/time-util.h" + +typedef enum { + ENTRY_CURSOR = 0, /* Nothing actually written yet. */ + ENTRY_REALTIME, + ENTRY_MONOTONIC, + ENTRY_BOOT_ID, + ENTRY_NEW_FIELD, /* In between fields. */ + ENTRY_TEXT_FIELD, /* In the middle of a text field. */ + ENTRY_BINARY_FIELD_START, /* Writing the name of a binary field. */ + ENTRY_BINARY_FIELD_SIZE, /* Writing the size of a binary field. */ + ENTRY_BINARY_FIELD, /* In the middle of a binary field. */ + ENTRY_OUTRO, /* Writing '\n' */ + ENTRY_DONE, /* Need to move to a new field. */ +} entry_state; + +typedef struct Uploader { + sd_event *events; + sd_event_source *sigint_event, *sigterm_event; + + char *url; + CURL *easy; + bool uploading; + char error[CURL_ERROR_SIZE]; + struct curl_slist *header; + char *answer; + + sd_event_source *input_event; + uint64_t timeout; + + /* fd stuff */ + int input; + + /* journal stuff */ + sd_journal* journal; + + entry_state entry_state; + const void *field_data; + size_t field_pos, field_length; + + /* general metrics */ + const char *state_file; + + size_t entries_sent; + char *last_cursor, *current_cursor; + usec_t watchdog_timestamp; + usec_t watchdog_usec; +} Uploader; + +#define JOURNAL_UPLOAD_POLL_TIMEOUT (10 * USEC_PER_SEC) + +int start_upload(Uploader *u, + size_t (*input_callback)(void *ptr, + size_t size, + size_t nmemb, + void *userdata), + void *data); + +int open_journal_for_upload(Uploader *u, + sd_journal *j, + const char *cursor, + bool after_cursor, + bool follow); +void close_journal_input(Uploader *u); +int check_journal_input(Uploader *u); diff --git a/src/grp-journal/journalctl/Makefile b/src/grp-journal/journalctl/Makefile index c3cdb6b27a..7fc5e64143 100644 --- a/src/grp-journal/journalctl/Makefile +++ b/src/grp-journal/journalctl/Makefile @@ -23,6 +23,9 @@ include $(dir $(lastword $(MAKEFILE_LIST)))/../../../config.mk include $(topsrcdir)/build-aux/Makefile.head.mk +rootbin_PROGRAMS += \ + journalctl + # using _CFLAGS = in the conditional below would suppress AM_CFLAGS journalctl_CFLAGS = \ $(AM_CFLAGS) @@ -46,4 +49,6 @@ journalctl_LDADD += \ $(QRENCODE_LIBS) endif # HAVE_QRENCODE +systemd.CPPFLAGS += -DCATALOG_DATABASE=\"$(catalogstatedir)/database\" + include $(topsrcdir)/build-aux/Makefile.tail.mk diff --git a/src/grp-journal/journalctl/journal-qrcode.c b/src/grp-journal/journalctl/journal-qrcode.c new file mode 100644 index 0000000000..e38730d65c --- /dev/null +++ b/src/grp-journal/journalctl/journal-qrcode.c @@ -0,0 +1,135 @@ +/*** + 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 "journal-qrcode.h" + +#define WHITE_ON_BLACK "\033[40;37;1m" +#define NORMAL "\033[0m" + +static void print_border(FILE *output, unsigned width) { + unsigned x, y; + + /* Four rows of border */ + for (y = 0; y < 4; y += 2) { + fputs(WHITE_ON_BLACK, output); + + for (x = 0; x < 4 + width + 4; x++) + fputs("\342\226\210", output); + + fputs(NORMAL "\n", output); + } +} + +int print_qr_code( + FILE *output, + const void *seed, + size_t seed_size, + uint64_t start, + uint64_t interval, + const char *hn, + sd_id128_t machine) { + + FILE *f; + char *url = NULL; + size_t url_size = 0, i; + QRcode* qr; + unsigned x, y; + + assert(seed); + assert(seed_size > 0); + + f = open_memstream(&url, &url_size); + if (!f) + return -ENOMEM; + + fputs("fss://", f); + + for (i = 0; i < seed_size; i++) { + if (i > 0 && i % 3 == 0) + fputc('-', f); + fprintf(f, "%02x", ((uint8_t*) seed)[i]); + } + + fprintf(f, "/%"PRIx64"-%"PRIx64"?machine=" SD_ID128_FORMAT_STR, + start, + interval, + SD_ID128_FORMAT_VAL(machine)); + + if (hn) + fprintf(f, ";hostname=%s", hn); + + if (ferror(f)) { + fclose(f); + free(url); + return -ENOMEM; + } + + fclose(f); + + qr = QRcode_encodeString(url, 0, QR_ECLEVEL_L, QR_MODE_8, 1); + free(url); + + if (!qr) + return -ENOMEM; + + print_border(output, qr->width); + + for (y = 0; y < (unsigned) qr->width; y += 2) { + const uint8_t *row1, *row2; + + row1 = qr->data + qr->width * y; + row2 = row1 + qr->width; + + fputs(WHITE_ON_BLACK, output); + for (x = 0; x < 4; x++) + fputs("\342\226\210", output); + + for (x = 0; x < (unsigned) qr->width; x ++) { + bool a, b; + + a = row1[x] & 1; + b = (y+1) < (unsigned) qr->width ? (row2[x] & 1) : false; + + if (a && b) + fputc(' ', output); + else if (a) + fputs("\342\226\204", output); + else if (b) + fputs("\342\226\200", output); + else + fputs("\342\226\210", output); + } + + for (x = 0; x < 4; x++) + fputs("\342\226\210", output); + fputs(NORMAL "\n", output); + } + + print_border(output, qr->width); + + QRcode_free(qr); + return 0; +} diff --git a/src/grp-journal/journalctl/journal-qrcode.h b/src/grp-journal/journalctl/journal-qrcode.h new file mode 100644 index 0000000000..34a779d5be --- /dev/null +++ b/src/grp-journal/journalctl/journal-qrcode.h @@ -0,0 +1,27 @@ +#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 + +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/grp-journal/journalctl/journalctl.c b/src/grp-journal/journalctl/journalctl.c index c673fbf78a..85a934e978 100644 --- a/src/grp-journal/journalctl/journalctl.c +++ b/src/grp-journal/journalctl/journalctl.c @@ -36,29 +36,17 @@ #include #include -#include "shared/acl-util.h" #include "basic/alloc-util.h" -#include "bus-error.h" -#include "shared/bus-util.h" -#include "catalog.h" #include "basic/chattr-util.h" #include "basic/fd-util.h" #include "basic/fileio.h" #include "basic/fs-util.h" -#include "fsprg.h" #include "basic/glob-util.h" #include "basic/hostname-util.h" #include "basic/io-util.h" -#include "journal-def.h" -#include "journal-internal.h" -#include "journal-qrcode.h" -#include "journal-vacuum.h" -#include "journal-verify.h" #include "basic/locale-util.h" #include "basic/log.h" -#include "shared/logs-show.h" #include "basic/mkdir.h" -#include "shared/pager.h" #include "basic/parse-util.h" #include "basic/path-util.h" #include "basic/rlimit-util.h" @@ -67,10 +55,23 @@ #include "basic/strv.h" #include "basic/syslog-util.h" #include "basic/terminal-util.h" -#include "udev.h" -#include "shared/udev-util.h" #include "basic/unit-name.h" #include "basic/user-util.h" +#include "sd-bus/bus-error.h" +#include "sd-journal/catalog.h" +#include "sd-journal/fsprg.h" +#include "sd-journal/journal-def.h" +#include "sd-journal/journal-internal.h" +#include "sd-journal/journal-vacuum.h" +#include "sd-journal/journal-verify.h" +#include "shared/acl-util.h" +#include "shared/bus-util.h" +#include "shared/logs-show.h" +#include "shared/pager.h" +#include "shared/udev-util.h" +#include "udev.h" + +#include "journal-qrcode.h" #define DEFAULT_FSS_INTERVAL_USEC (15*USEC_PER_MINUTE) diff --git a/src/grp-journal/libjournal-core/Makefile b/src/grp-journal/libjournal-core/Makefile index d55aebfb49..bcbfd0f8cb 100644 --- a/src/grp-journal/libjournal-core/Makefile +++ b/src/grp-journal/libjournal-core/Makefile @@ -53,4 +53,6 @@ libjournal_core_la_LIBADD = \ noinst_LTLIBRARIES += \ libjournal-core.la +systemd.CPPFLAGS += -DPKGSYSCONFDIR=\"$(pkgsysconfdir)\" + include $(topsrcdir)/build-aux/Makefile.tail.mk diff --git a/src/grp-journal/libjournal-core/cat.c b/src/grp-journal/libjournal-core/cat.c deleted file mode 100644 index 8ab4febd53..0000000000 --- a/src/grp-journal/libjournal-core/cat.c +++ /dev/null @@ -1,161 +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 "basic/fd-util.h" -#include "basic/parse-util.h" -#include "basic/string-util.h" -#include "basic/syslog-util.h" -#include "basic/util.h" - -static const char *arg_identifier = NULL; -static int arg_priority = LOG_INFO; -static bool arg_level_prefix = true; - -static void help(void) { - printf("%s [OPTIONS...] {COMMAND} ...\n\n" - "Execute process with stdout/stderr connected to the journal.\n\n" - " -h --help Show this help\n" - " --version Show package version\n" - " -t --identifier=STRING Set syslog identifier\n" - " -p --priority=PRIORITY Set priority value (0..7)\n" - " --level-prefix=BOOL Control whether level prefix shall be parsed\n" - , program_invocation_short_name); -} - -static int parse_argv(int argc, char *argv[]) { - - enum { - ARG_VERSION = 0x100, - ARG_LEVEL_PREFIX - }; - - static const struct option options[] = { - { "help", no_argument, NULL, 'h' }, - { "version", no_argument, NULL, ARG_VERSION }, - { "identifier", required_argument, NULL, 't' }, - { "priority", required_argument, NULL, 'p' }, - { "level-prefix", required_argument, NULL, ARG_LEVEL_PREFIX }, - {} - }; - - int c; - - assert(argc >= 0); - assert(argv); - - while ((c = getopt_long(argc, argv, "+ht:p:", options, NULL)) >= 0) - - switch (c) { - - case 'h': - help(); - return 0; - - case ARG_VERSION: - return version(); - - case 't': - if (isempty(optarg)) - arg_identifier = NULL; - else - arg_identifier = optarg; - break; - - case 'p': - arg_priority = log_level_from_string(optarg); - if (arg_priority < 0) { - log_error("Failed to parse priority value."); - return -EINVAL; - } - break; - - case ARG_LEVEL_PREFIX: { - int k; - - k = parse_boolean(optarg); - if (k < 0) - return log_error_errno(k, "Failed to parse level prefix value."); - - arg_level_prefix = k; - break; - } - - case '?': - return -EINVAL; - - default: - assert_not_reached("Unhandled option"); - } - - return 1; -} - -int main(int argc, char *argv[]) { - _cleanup_close_ int fd = -1, saved_stderr = -1; - int r; - - log_parse_environment(); - log_open(); - - r = parse_argv(argc, argv); - if (r <= 0) - goto finish; - - fd = sd_journal_stream_fd(arg_identifier, arg_priority, arg_level_prefix); - if (fd < 0) { - r = log_error_errno(fd, "Failed to create stream fd: %m"); - goto finish; - } - - saved_stderr = fcntl(STDERR_FILENO, F_DUPFD_CLOEXEC, 3); - - if (dup3(fd, STDOUT_FILENO, 0) < 0 || - dup3(fd, STDERR_FILENO, 0) < 0) { - r = log_error_errno(errno, "Failed to duplicate fd: %m"); - goto finish; - } - - if (fd >= 3) - safe_close(fd); - fd = -1; - - if (argc <= optind) - (void) execl("/bin/cat", "/bin/cat", NULL); - else - (void) execvp(argv[optind], argv + optind); - r = -errno; - - /* Let's try to restore a working stderr, so we can print the error message */ - if (saved_stderr >= 0) - (void) dup3(saved_stderr, STDERR_FILENO, 0); - - log_error_errno(r, "Failed to execute process: %m"); - -finish: - return r < 0 ? EXIT_FAILURE : EXIT_SUCCESS; -} diff --git a/src/grp-journal/libjournal-core/journal-qrcode.c b/src/grp-journal/libjournal-core/journal-qrcode.c deleted file mode 100644 index e38730d65c..0000000000 --- a/src/grp-journal/libjournal-core/journal-qrcode.c +++ /dev/null @@ -1,135 +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 "journal-qrcode.h" - -#define WHITE_ON_BLACK "\033[40;37;1m" -#define NORMAL "\033[0m" - -static void print_border(FILE *output, unsigned width) { - unsigned x, y; - - /* Four rows of border */ - for (y = 0; y < 4; y += 2) { - fputs(WHITE_ON_BLACK, output); - - for (x = 0; x < 4 + width + 4; x++) - fputs("\342\226\210", output); - - fputs(NORMAL "\n", output); - } -} - -int print_qr_code( - FILE *output, - const void *seed, - size_t seed_size, - uint64_t start, - uint64_t interval, - const char *hn, - sd_id128_t machine) { - - FILE *f; - char *url = NULL; - size_t url_size = 0, i; - QRcode* qr; - unsigned x, y; - - assert(seed); - assert(seed_size > 0); - - f = open_memstream(&url, &url_size); - if (!f) - return -ENOMEM; - - fputs("fss://", f); - - for (i = 0; i < seed_size; i++) { - if (i > 0 && i % 3 == 0) - fputc('-', f); - fprintf(f, "%02x", ((uint8_t*) seed)[i]); - } - - fprintf(f, "/%"PRIx64"-%"PRIx64"?machine=" SD_ID128_FORMAT_STR, - start, - interval, - SD_ID128_FORMAT_VAL(machine)); - - if (hn) - fprintf(f, ";hostname=%s", hn); - - if (ferror(f)) { - fclose(f); - free(url); - return -ENOMEM; - } - - fclose(f); - - qr = QRcode_encodeString(url, 0, QR_ECLEVEL_L, QR_MODE_8, 1); - free(url); - - if (!qr) - return -ENOMEM; - - print_border(output, qr->width); - - for (y = 0; y < (unsigned) qr->width; y += 2) { - const uint8_t *row1, *row2; - - row1 = qr->data + qr->width * y; - row2 = row1 + qr->width; - - fputs(WHITE_ON_BLACK, output); - for (x = 0; x < 4; x++) - fputs("\342\226\210", output); - - for (x = 0; x < (unsigned) qr->width; x ++) { - bool a, b; - - a = row1[x] & 1; - b = (y+1) < (unsigned) qr->width ? (row2[x] & 1) : false; - - if (a && b) - fputc(' ', output); - else if (a) - fputs("\342\226\204", output); - else if (b) - fputs("\342\226\200", output); - else - fputs("\342\226\210", output); - } - - for (x = 0; x < 4; x++) - fputs("\342\226\210", output); - fputs(NORMAL "\n", output); - } - - print_border(output, qr->width); - - QRcode_free(qr); - return 0; -} diff --git a/src/grp-journal/libjournal-core/journal-qrcode.h b/src/grp-journal/libjournal-core/journal-qrcode.h deleted file mode 100644 index 34a779d5be..0000000000 --- a/src/grp-journal/libjournal-core/journal-qrcode.h +++ /dev/null @@ -1,27 +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 - -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/grp-journal/libjournal-core/journald-audit.c b/src/grp-journal/libjournal-core/journald-audit.c index 7abd2f819e..d76a460257 100644 --- a/src/grp-journal/libjournal-core/journald-audit.c +++ b/src/grp-journal/libjournal-core/journald-audit.c @@ -18,7 +18,7 @@ ***/ #include "basic/alloc-util.h" -#include "audit-type.h" +#include "sd-journal/audit-type.h" #include "basic/fd-util.h" #include "basic/hexdecoct.h" #include "basic/io-util.h" diff --git a/src/grp-journal/libjournal-core/journald-gperf.c b/src/grp-journal/libjournal-core/journald-gperf.c new file mode 100644 index 0000000000..d8483a3303 --- /dev/null +++ b/src/grp-journal/libjournal-core/journald-gperf.c @@ -0,0 +1,173 @@ +/* ANSI-C code produced by gperf version 3.0.4 */ +/* Command-line: gperf */ +/* Computed positions: -k'9,18' */ + +#if !((' ' == 32) && ('!' == 33) && ('"' == 34) && ('#' == 35) \ + && ('%' == 37) && ('&' == 38) && ('\'' == 39) && ('(' == 40) \ + && (')' == 41) && ('*' == 42) && ('+' == 43) && (',' == 44) \ + && ('-' == 45) && ('.' == 46) && ('/' == 47) && ('0' == 48) \ + && ('1' == 49) && ('2' == 50) && ('3' == 51) && ('4' == 52) \ + && ('5' == 53) && ('6' == 54) && ('7' == 55) && ('8' == 56) \ + && ('9' == 57) && (':' == 58) && (';' == 59) && ('<' == 60) \ + && ('=' == 61) && ('>' == 62) && ('?' == 63) && ('A' == 65) \ + && ('B' == 66) && ('C' == 67) && ('D' == 68) && ('E' == 69) \ + && ('F' == 70) && ('G' == 71) && ('H' == 72) && ('I' == 73) \ + && ('J' == 74) && ('K' == 75) && ('L' == 76) && ('M' == 77) \ + && ('N' == 78) && ('O' == 79) && ('P' == 80) && ('Q' == 81) \ + && ('R' == 82) && ('S' == 83) && ('T' == 84) && ('U' == 85) \ + && ('V' == 86) && ('W' == 87) && ('X' == 88) && ('Y' == 89) \ + && ('Z' == 90) && ('[' == 91) && ('\\' == 92) && (']' == 93) \ + && ('^' == 94) && ('_' == 95) && ('a' == 97) && ('b' == 98) \ + && ('c' == 99) && ('d' == 100) && ('e' == 101) && ('f' == 102) \ + && ('g' == 103) && ('h' == 104) && ('i' == 105) && ('j' == 106) \ + && ('k' == 107) && ('l' == 108) && ('m' == 109) && ('n' == 110) \ + && ('o' == 111) && ('p' == 112) && ('q' == 113) && ('r' == 114) \ + && ('s' == 115) && ('t' == 116) && ('u' == 117) && ('v' == 118) \ + && ('w' == 119) && ('x' == 120) && ('y' == 121) && ('z' == 122) \ + && ('{' == 123) && ('|' == 124) && ('}' == 125) && ('~' == 126)) +/* The character set is not based on ISO-646. */ +#error "gperf generated tables don't work with this execution character set. Please report a bug to ." +#endif + + +#include +#include +#include "shared/conf-parser.h" +#include "journald-server.h" +#include + +#define TOTAL_KEYWORDS 28 +#define MIN_WORD_LENGTH 12 +#define MAX_WORD_LENGTH 28 +#define MIN_HASH_VALUE 12 +#define MAX_HASH_VALUE 53 +/* maximum key range = 42, duplicates = 0 */ + +#ifdef __GNUC__ +__inline +#else +#ifdef __cplusplus +inline +#endif +#endif +static unsigned int +journald_gperf_hash (register const char *str, register unsigned int len) +{ + static const unsigned char asso_values[] = + { + 54, 54, 54, 54, 54, 54, 54, 54, 54, 54, + 54, 54, 54, 54, 54, 54, 54, 54, 54, 54, + 54, 54, 54, 54, 54, 54, 54, 54, 54, 54, + 54, 54, 54, 54, 54, 54, 54, 54, 54, 54, + 54, 54, 54, 54, 54, 54, 54, 54, 54, 54, + 54, 54, 54, 54, 54, 54, 54, 54, 54, 54, + 54, 54, 54, 54, 54, 54, 10, 0, 54, 54, + 0, 54, 54, 0, 54, 20, 54, 0, 54, 54, + 54, 54, 5, 0, 30, 20, 54, 15, 54, 54, + 54, 54, 54, 54, 54, 54, 54, 15, 54, 0, + 54, 25, 54, 54, 54, 25, 54, 54, 54, 54, + 54, 20, 10, 54, 54, 54, 0, 54, 15, 54, + 0, 5, 54, 54, 54, 54, 54, 54, 54, 54, + 54, 54, 54, 54, 54, 54, 54, 54, 54, 54, + 54, 54, 54, 54, 54, 54, 54, 54, 54, 54, + 54, 54, 54, 54, 54, 54, 54, 54, 54, 54, + 54, 54, 54, 54, 54, 54, 54, 54, 54, 54, + 54, 54, 54, 54, 54, 54, 54, 54, 54, 54, + 54, 54, 54, 54, 54, 54, 54, 54, 54, 54, + 54, 54, 54, 54, 54, 54, 54, 54, 54, 54, + 54, 54, 54, 54, 54, 54, 54, 54, 54, 54, + 54, 54, 54, 54, 54, 54, 54, 54, 54, 54, + 54, 54, 54, 54, 54, 54, 54, 54, 54, 54, + 54, 54, 54, 54, 54, 54, 54, 54, 54, 54, + 54, 54, 54, 54, 54, 54, 54, 54, 54, 54, + 54, 54, 54, 54, 54, 54 + }; + register int hval = len; + + switch (hval) + { + default: + hval += asso_values[(unsigned char)str[17]]; + /*FALLTHROUGH*/ + case 17: + case 16: + case 15: + case 14: + case 13: + case 12: + case 11: + case 10: + case 9: + hval += asso_values[(unsigned char)str[8]]; + break; + } + return hval; +} + +#ifdef __GNUC__ +__inline +#if defined __GNUC_STDC_INLINE__ || defined __GNUC_GNU_INLINE__ +__attribute__ ((__gnu_inline__)) +#endif +#endif +const struct ConfigPerfItem * +journald_gperf_lookup (register const char *str, register unsigned int len) +{ + static const struct ConfigPerfItem wordlist[] = + { + {(char*)0}, {(char*)0}, {(char*)0}, {(char*)0}, + {(char*)0}, {(char*)0}, {(char*)0}, {(char*)0}, + {(char*)0}, {(char*)0}, {(char*)0}, {(char*)0}, + {"Journal.Seal", config_parse_bool, 0, offsetof(Server, seal)}, + {(char*)0}, {(char*)0}, + {"Journal.Storage", config_parse_storage, 0, offsetof(Server, storage)}, + {"Journal.Compress", config_parse_bool, 0, offsetof(Server, compress)}, + {"Journal.SplitMode", config_parse_split_mode, 0, offsetof(Server, split_mode)}, + {"Journal.MaxFileSec", config_parse_sec, 0, offsetof(Server, max_file_usec)}, + {(char*)0}, + {"Journal.MaxLevelKMsg", config_parse_log_level, 0, offsetof(Server, max_level_kmsg)}, + {"Journal.MaxLevelStore", config_parse_log_level, 0, offsetof(Server, max_level_store)}, + {"Journal.SystemMaxFiles", config_parse_uint64, 0, offsetof(Server, system_metrics.n_max_files)}, + {"Journal.ForwardToSyslog", config_parse_bool, 0, offsetof(Server, forward_to_syslog)}, + {"Journal.ForwardToConsole", config_parse_bool, 0, offsetof(Server, forward_to_console)}, + {"Journal.SystemMaxFileSize", config_parse_iec_uint64, 0, offsetof(Server, system_metrics.max_size)}, + {"Journal.RuntimeMaxUse", config_parse_iec_uint64, 0, offsetof(Server, runtime_metrics.max_use)}, + {"Journal.MaxLevelSyslog", config_parse_log_level, 0, offsetof(Server, max_level_syslog)}, + {"Journal.RuntimeMaxFiles", config_parse_uint64, 0, offsetof(Server, runtime_metrics.n_max_files)}, + {(char*)0}, + {"Journal.RateLimitInterval", config_parse_sec, 0, offsetof(Server, rate_limit_interval)}, + {"Journal.RuntimeMaxFileSize", config_parse_iec_uint64, 0, offsetof(Server, runtime_metrics.max_size)}, + {"Journal.SystemKeepFree", config_parse_iec_uint64, 0, offsetof(Server, system_metrics.keep_free)}, + {"Journal.RateLimitIntervalSec",config_parse_sec, 0, offsetof(Server, rate_limit_interval)}, + {(char*)0}, + {"Journal.MaxLevelWall", config_parse_log_level, 0, offsetof(Server, max_level_wall)}, + {"Journal.ForwardToWall", config_parse_bool, 0, offsetof(Server, forward_to_wall)}, + {"Journal.RateLimitBurst", config_parse_unsigned, 0, offsetof(Server, rate_limit_burst)}, + {"Journal.SyncIntervalSec", config_parse_sec, 0, offsetof(Server, sync_interval_usec)}, + {(char*)0}, + {"Journal.SystemMaxUse", config_parse_iec_uint64, 0, offsetof(Server, system_metrics.max_use)}, + {"Journal.ForwardToKMsg", config_parse_bool, 0, offsetof(Server, forward_to_kmsg)}, + {(char*)0}, + {"Journal.MaxLevelConsole", config_parse_log_level, 0, offsetof(Server, max_level_console)}, + {(char*)0}, + {"Journal.TTYPath", config_parse_path, 0, offsetof(Server, tty_path)}, + {(char*)0}, {(char*)0}, + {"Journal.MaxRetentionSec", config_parse_sec, 0, offsetof(Server, max_retention_usec)}, + {(char*)0}, {(char*)0}, {(char*)0}, {(char*)0}, + {"Journal.RuntimeKeepFree", config_parse_iec_uint64, 0, offsetof(Server, runtime_metrics.keep_free)} + }; + + if (len <= MAX_WORD_LENGTH && len >= MIN_WORD_LENGTH) + { + register int key = journald_gperf_hash (str, len); + + if (key <= MAX_HASH_VALUE && key >= 0) + { + register const char *s = wordlist[key].section_and_lvalue; + + if (s && *str == *s && !strcmp (str + 1, s + 1)) + return &wordlist[key]; + } + } + return 0; +} diff --git a/src/grp-journal/libjournal-core/journald-server.c b/src/grp-journal/libjournal-core/journald-server.c index 24b873abeb..4dca4b02b3 100644 --- a/src/grp-journal/libjournal-core/journald-server.c +++ b/src/grp-journal/libjournal-core/journald-server.c @@ -26,16 +26,14 @@ #include #include -#include "libudev.h" +#include #include #include #include -#include "shared/acl-util.h" #include "basic/alloc-util.h" #include "basic/audit-util.h" #include "basic/cgroup-util.h" -#include "shared/conf-parser.h" #include "basic/dirent-util.h" #include "basic/extract-word.h" #include "basic/fd-util.h" @@ -45,17 +43,7 @@ #include "basic/hashmap.h" #include "basic/hostname-util.h" #include "basic/io-util.h" -#include "journal-authenticate.h" -#include "journal-file.h" -#include "journal-internal.h" -#include "journal-vacuum.h" -#include "journald-audit.h" -#include "journald-kmsg.h" -#include "journald-native.h" -#include "journald-rate-limit.h" -#include "journald-server.h" -#include "journald-stream.h" -#include "journald-syslog.h" +#include "basic/log.h" #include "basic/missing.h" #include "basic/mkdir.h" #include "basic/parse-util.h" @@ -69,7 +57,20 @@ #include "basic/string-table.h" #include "basic/string-util.h" #include "basic/user-util.h" -#include "basic/log.h" +#include "sd-journal/journal-authenticate.h" +#include "sd-journal/journal-file.h" +#include "sd-journal/journal-internal.h" +#include "sd-journal/journal-vacuum.h" +#include "shared/acl-util.h" +#include "shared/conf-parser.h" + +#include "journald-audit.h" +#include "journald-kmsg.h" +#include "journald-native.h" +#include "journald-rate-limit.h" +#include "journald-server.h" +#include "journald-stream.h" +#include "journald-syslog.h" #define USER_JOURNALS_MAX 1024 diff --git a/src/grp-journal/systemd-cat/Makefile b/src/grp-journal/systemd-cat/Makefile new file mode 100644 index 0000000000..a6d7eee6ea --- /dev/null +++ b/src/grp-journal/systemd-cat/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 + +systemd_cat_SOURCES = \ + src/journal/cat.c + +systemd_cat_LDADD = \ + libjournal-core.la + + +bin_PROGRAMS += \ + systemd-cat + +include $(topsrcdir)/build-aux/Makefile.tail.mk diff --git a/src/grp-journal/systemd-cat/cat.c b/src/grp-journal/systemd-cat/cat.c new file mode 100644 index 0000000000..8ab4febd53 --- /dev/null +++ b/src/grp-journal/systemd-cat/cat.c @@ -0,0 +1,161 @@ +/*** + 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 "basic/fd-util.h" +#include "basic/parse-util.h" +#include "basic/string-util.h" +#include "basic/syslog-util.h" +#include "basic/util.h" + +static const char *arg_identifier = NULL; +static int arg_priority = LOG_INFO; +static bool arg_level_prefix = true; + +static void help(void) { + printf("%s [OPTIONS...] {COMMAND} ...\n\n" + "Execute process with stdout/stderr connected to the journal.\n\n" + " -h --help Show this help\n" + " --version Show package version\n" + " -t --identifier=STRING Set syslog identifier\n" + " -p --priority=PRIORITY Set priority value (0..7)\n" + " --level-prefix=BOOL Control whether level prefix shall be parsed\n" + , program_invocation_short_name); +} + +static int parse_argv(int argc, char *argv[]) { + + enum { + ARG_VERSION = 0x100, + ARG_LEVEL_PREFIX + }; + + static const struct option options[] = { + { "help", no_argument, NULL, 'h' }, + { "version", no_argument, NULL, ARG_VERSION }, + { "identifier", required_argument, NULL, 't' }, + { "priority", required_argument, NULL, 'p' }, + { "level-prefix", required_argument, NULL, ARG_LEVEL_PREFIX }, + {} + }; + + int c; + + assert(argc >= 0); + assert(argv); + + while ((c = getopt_long(argc, argv, "+ht:p:", options, NULL)) >= 0) + + switch (c) { + + case 'h': + help(); + return 0; + + case ARG_VERSION: + return version(); + + case 't': + if (isempty(optarg)) + arg_identifier = NULL; + else + arg_identifier = optarg; + break; + + case 'p': + arg_priority = log_level_from_string(optarg); + if (arg_priority < 0) { + log_error("Failed to parse priority value."); + return -EINVAL; + } + break; + + case ARG_LEVEL_PREFIX: { + int k; + + k = parse_boolean(optarg); + if (k < 0) + return log_error_errno(k, "Failed to parse level prefix value."); + + arg_level_prefix = k; + break; + } + + case '?': + return -EINVAL; + + default: + assert_not_reached("Unhandled option"); + } + + return 1; +} + +int main(int argc, char *argv[]) { + _cleanup_close_ int fd = -1, saved_stderr = -1; + int r; + + log_parse_environment(); + log_open(); + + r = parse_argv(argc, argv); + if (r <= 0) + goto finish; + + fd = sd_journal_stream_fd(arg_identifier, arg_priority, arg_level_prefix); + if (fd < 0) { + r = log_error_errno(fd, "Failed to create stream fd: %m"); + goto finish; + } + + saved_stderr = fcntl(STDERR_FILENO, F_DUPFD_CLOEXEC, 3); + + if (dup3(fd, STDOUT_FILENO, 0) < 0 || + dup3(fd, STDERR_FILENO, 0) < 0) { + r = log_error_errno(errno, "Failed to duplicate fd: %m"); + goto finish; + } + + if (fd >= 3) + safe_close(fd); + fd = -1; + + if (argc <= optind) + (void) execl("/bin/cat", "/bin/cat", NULL); + else + (void) execvp(argv[optind], argv + optind); + r = -errno; + + /* Let's try to restore a working stderr, so we can print the error message */ + if (saved_stderr >= 0) + (void) dup3(saved_stderr, STDERR_FILENO, 0); + + log_error_errno(r, "Failed to execute process: %m"); + +finish: + return r < 0 ? EXIT_FAILURE : EXIT_SUCCESS; +} diff --git a/src/grp-journal/systemd-journald/Makefile b/src/grp-journal/systemd-journald/Makefile index 755e2d2c36..640528bae4 100644 --- a/src/grp-journal/systemd-journald/Makefile +++ b/src/grp-journal/systemd-journald/Makefile @@ -31,22 +31,9 @@ systemd_journald_LDADD = \ libjournal-core.la \ libshared.la -systemd_cat_SOURCES = \ - src/journal/cat.c - -systemd_cat_LDADD = \ - libjournal-core.la - - rootlibexec_PROGRAMS += \ systemd-journald -rootbin_PROGRAMS += \ - journalctl - -bin_PROGRAMS += \ - systemd-cat - dist_systemunit_DATA += \ units/systemd-journald.socket \ units/systemd-journald-dev-log.socket \ diff --git a/src/grp-journal/systemd-journald/journald.c b/src/grp-journal/systemd-journald/journald.c index 9b4a5d3735..cfeb9ea48e 100644 --- a/src/grp-journal/systemd-journald/journald.c +++ b/src/grp-journal/systemd-journald/journald.c @@ -23,11 +23,12 @@ #include #include "basic/formats-util.h" -#include "journal-authenticate.h" +#include "basic/sigbus.h" +#include "sd-journal/journal-authenticate.h" + #include "journald-kmsg.h" #include "journald-server.h" #include "journald-syslog.h" -#include "basic/sigbus.h" int main(int argc, char *argv[]) { Server server; diff --git a/src/grp-locale/Makefile b/src/grp-locale/Makefile index b34c066ec2..e6ab4a7f48 100644 --- a/src/grp-locale/Makefile +++ b/src/grp-locale/Makefile @@ -87,4 +87,7 @@ polkitpolicy_in_files += \ EXTRA_DIST += \ units/systemd-localed.service.in +systemd.CPPFLAGS += -DSYSTEMD_KBD_MODEL_MAP=\"$(pkgdatadir)/kbd-model-map\" +systemd.CPPFLAGS += -DSYSTEMD_LANGUAGE_FALLBACK_MAP=\"$(pkgdatadir)/language-fallback-map\" + include $(topsrcdir)/build-aux/Makefile.tail.mk diff --git a/src/grp-locale/localectl.c b/src/grp-locale/localectl.c index a159f8ed2e..72ac1c0303 100644 --- a/src/grp-locale/localectl.c +++ b/src/grp-locale/localectl.c @@ -27,18 +27,18 @@ #include -#include "bus-error.h" -#include "shared/bus-util.h" #include "basic/def.h" #include "basic/fd-util.h" #include "basic/fileio.h" #include "basic/locale-util.h" -#include "shared/pager.h" #include "basic/set.h" -#include "shared/spawn-polkit-agent.h" #include "basic/strv.h" #include "basic/util.h" #include "basic/virt.h" +#include "sd-bus/bus-error.h" +#include "shared/bus-util.h" +#include "shared/pager.h" +#include "shared/spawn-polkit-agent.h" static bool arg_no_pager = false; static bool arg_ask_password = true; diff --git a/src/grp-locale/localed.c b/src/grp-locale/localed.c index 513413e2b5..7bb7f1cf34 100644 --- a/src/grp-locale/localed.c +++ b/src/grp-locale/localed.c @@ -30,9 +30,6 @@ #include #include "basic/alloc-util.h" -#include "bus-error.h" -#include "bus-message.h" -#include "shared/bus-util.h" #include "basic/def.h" #include "basic/env-util.h" #include "basic/fd-util.h" @@ -45,6 +42,9 @@ #include "basic/strv.h" #include "basic/user-util.h" #include "basic/util.h" +#include "sd-bus/bus-error.h" +#include "sd-bus/bus-message.h" +#include "shared/bus-util.h" enum { /* We don't list LC_ALL here on purpose. People should be diff --git a/src/grp-machine/Makefile b/src/grp-machine/Makefile index 59142f78bd..15bd8360d0 100644 --- a/src/grp-machine/Makefile +++ b/src/grp-machine/Makefile @@ -26,5 +26,6 @@ include $(topsrcdir)/build-aux/Makefile.head.mk at.subdirs += machinectl systemd-machined at.subdirs += libmachine-core at.subdirs += nss-mymachines +at.subdirs += grp-import include $(topsrcdir)/build-aux/Makefile.tail.mk diff --git a/src/grp-machine/grp-import/.gitignore b/src/grp-machine/grp-import/.gitignore new file mode 100644 index 0000000000..01106e2e68 --- /dev/null +++ b/src/grp-machine/grp-import/.gitignore @@ -0,0 +1 @@ +/org.freedesktop.import1.policy diff --git a/src/grp-machine/grp-import/Makefile b/src/grp-machine/grp-import/Makefile new file mode 100644 index 0000000000..46be93c42f --- /dev/null +++ b/src/grp-machine/grp-import/Makefile @@ -0,0 +1,89 @@ +# -*- 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),) + +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 # HAVE_GCRYPT +endif # HAVE_BZIP2 +endif # HAVE_ZLIB +endif # HAVE_XZ +endif # HAVE_LIBCURL + +endif # ENABLE_IMPORTD + +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/grp-machine/grp-import/curl-util.c b/src/grp-machine/grp-import/curl-util.c new file mode 100644 index 0000000000..d6570a3656 --- /dev/null +++ b/src/grp-machine/grp-import/curl-util.c @@ -0,0 +1,448 @@ +/*** + 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 "basic/alloc-util.h" +#include "curl-util.h" +#include "basic/fd-util.h" +#include "basic/string-util.h" + +static void curl_glue_check_finished(CurlGlue *g) { + CURLMsg *msg; + int k = 0; + + assert(g); + + msg = curl_multi_info_read(g->curl, &k); + if (!msg) + return; + + if (msg->msg != CURLMSG_DONE) + return; + + if (g->on_finished) + g->on_finished(g, msg->easy_handle, msg->data.result); +} + +static int curl_glue_on_io(sd_event_source *s, int fd, uint32_t revents, void *userdata) { + CurlGlue *g = userdata; + int action, k = 0, translated_fd; + + assert(s); + assert(g); + + translated_fd = PTR_TO_FD(hashmap_get(g->translate_fds, FD_TO_PTR(fd))); + + if ((revents & (EPOLLIN|EPOLLOUT)) == (EPOLLIN|EPOLLOUT)) + action = CURL_POLL_INOUT; + else if (revents & EPOLLIN) + action = CURL_POLL_IN; + else if (revents & EPOLLOUT) + action = CURL_POLL_OUT; + else + action = 0; + + if (curl_multi_socket_action(g->curl, translated_fd, action, &k) < 0) { + log_debug("Failed to propagate IO event."); + return -EINVAL; + } + + curl_glue_check_finished(g); + return 0; +} + +static int curl_glue_socket_callback(CURLM *curl, curl_socket_t s, int action, void *userdata, void *socketp) { + sd_event_source *io; + CurlGlue *g = userdata; + uint32_t events = 0; + int r; + + assert(curl); + assert(g); + + io = hashmap_get(g->ios, FD_TO_PTR(s)); + + if (action == CURL_POLL_REMOVE) { + if (io) { + int fd; + + fd = sd_event_source_get_io_fd(io); + assert(fd >= 0); + + sd_event_source_set_enabled(io, SD_EVENT_OFF); + sd_event_source_unref(io); + + hashmap_remove(g->ios, FD_TO_PTR(s)); + hashmap_remove(g->translate_fds, FD_TO_PTR(fd)); + + safe_close(fd); + } + + return 0; + } + + r = hashmap_ensure_allocated(&g->ios, &trivial_hash_ops); + if (r < 0) { + log_oom(); + return -1; + } + + r = hashmap_ensure_allocated(&g->translate_fds, &trivial_hash_ops); + if (r < 0) { + log_oom(); + return -1; + } + + if (action == CURL_POLL_IN) + events = EPOLLIN; + else if (action == CURL_POLL_OUT) + events = EPOLLOUT; + else if (action == CURL_POLL_INOUT) + events = EPOLLIN|EPOLLOUT; + + if (io) { + if (sd_event_source_set_io_events(io, events) < 0) + return -1; + + if (sd_event_source_set_enabled(io, SD_EVENT_ON) < 0) + return -1; + } else { + _cleanup_close_ int fd = -1; + + /* When curl needs to remove an fd from us it closes + * the fd first, and only then calls into us. This is + * nasty, since we cannot pass the fd on to epoll() + * anymore. Hence, duplicate the fds here, and keep a + * copy for epoll which we control after use. */ + + fd = fcntl(s, F_DUPFD_CLOEXEC, 3); + if (fd < 0) + return -1; + + if (sd_event_add_io(g->event, &io, fd, events, curl_glue_on_io, g) < 0) + return -1; + + (void) sd_event_source_set_description(io, "curl-io"); + + r = hashmap_put(g->ios, FD_TO_PTR(s), io); + if (r < 0) { + log_oom(); + sd_event_source_unref(io); + return -1; + } + + r = hashmap_put(g->translate_fds, FD_TO_PTR(fd), FD_TO_PTR(s)); + if (r < 0) { + log_oom(); + hashmap_remove(g->ios, FD_TO_PTR(s)); + sd_event_source_unref(io); + return -1; + } + + fd = -1; + } + + return 0; +} + +static int curl_glue_on_timer(sd_event_source *s, uint64_t usec, void *userdata) { + CurlGlue *g = userdata; + int k = 0; + + assert(s); + assert(g); + + if (curl_multi_socket_action(g->curl, CURL_SOCKET_TIMEOUT, 0, &k) != CURLM_OK) { + log_debug("Failed to propagate timeout."); + return -EINVAL; + } + + curl_glue_check_finished(g); + return 0; +} + +static int curl_glue_timer_callback(CURLM *curl, long timeout_ms, void *userdata) { + CurlGlue *g = userdata; + usec_t usec; + + assert(curl); + assert(g); + + if (timeout_ms < 0) { + if (g->timer) { + if (sd_event_source_set_enabled(g->timer, SD_EVENT_OFF) < 0) + return -1; + } + + return 0; + } + + usec = now(clock_boottime_or_monotonic()) + (usec_t) timeout_ms * USEC_PER_MSEC + USEC_PER_MSEC - 1; + + if (g->timer) { + if (sd_event_source_set_time(g->timer, usec) < 0) + return -1; + + if (sd_event_source_set_enabled(g->timer, SD_EVENT_ONESHOT) < 0) + return -1; + } else { + if (sd_event_add_time(g->event, &g->timer, clock_boottime_or_monotonic(), usec, 0, curl_glue_on_timer, g) < 0) + return -1; + + (void) sd_event_source_set_description(g->timer, "curl-timer"); + } + + return 0; +} + +CurlGlue *curl_glue_unref(CurlGlue *g) { + sd_event_source *io; + + if (!g) + return NULL; + + if (g->curl) + curl_multi_cleanup(g->curl); + + while ((io = hashmap_steal_first(g->ios))) { + int fd; + + fd = sd_event_source_get_io_fd(io); + assert(fd >= 0); + + hashmap_remove(g->translate_fds, FD_TO_PTR(fd)); + + safe_close(fd); + sd_event_source_unref(io); + } + + hashmap_free(g->ios); + + sd_event_source_unref(g->timer); + sd_event_unref(g->event); + free(g); + + return NULL; +} + +int curl_glue_new(CurlGlue **glue, sd_event *event) { + _cleanup_(curl_glue_unrefp) CurlGlue *g = NULL; + int r; + + g = new0(CurlGlue, 1); + if (!g) + return -ENOMEM; + + if (event) + g->event = sd_event_ref(event); + else { + r = sd_event_default(&g->event); + if (r < 0) + return r; + } + + g->curl = curl_multi_init(); + if (!g->curl) + return -ENOMEM; + + if (curl_multi_setopt(g->curl, CURLMOPT_SOCKETDATA, g) != CURLM_OK) + return -EINVAL; + + if (curl_multi_setopt(g->curl, CURLMOPT_SOCKETFUNCTION, curl_glue_socket_callback) != CURLM_OK) + return -EINVAL; + + if (curl_multi_setopt(g->curl, CURLMOPT_TIMERDATA, g) != CURLM_OK) + return -EINVAL; + + if (curl_multi_setopt(g->curl, CURLMOPT_TIMERFUNCTION, curl_glue_timer_callback) != CURLM_OK) + return -EINVAL; + + *glue = g; + g = NULL; + + return 0; +} + +int curl_glue_make(CURL **ret, const char *url, void *userdata) { + const char *useragent; + CURL *c; + int r; + + assert(ret); + assert(url); + + c = curl_easy_init(); + if (!c) + return -ENOMEM; + + /* curl_easy_setopt(c, CURLOPT_VERBOSE, 1L); */ + + if (curl_easy_setopt(c, CURLOPT_URL, url) != CURLE_OK) { + r = -EIO; + goto fail; + } + + if (curl_easy_setopt(c, CURLOPT_PRIVATE, userdata) != CURLE_OK) { + r = -EIO; + goto fail; + } + + useragent = strjoina(program_invocation_short_name, "/" PACKAGE_VERSION); + if (curl_easy_setopt(c, CURLOPT_USERAGENT, useragent) != CURLE_OK) { + r = -EIO; + goto fail; + } + + if (curl_easy_setopt(c, CURLOPT_FOLLOWLOCATION, 1L) != CURLE_OK) { + r = -EIO; + goto fail; + } + + *ret = c; + return 0; + +fail: + curl_easy_cleanup(c); + return r; +} + +int curl_glue_add(CurlGlue *g, CURL *c) { + assert(g); + assert(c); + + if (curl_multi_add_handle(g->curl, c) != CURLM_OK) + return -EIO; + + return 0; +} + +void curl_glue_remove_and_free(CurlGlue *g, CURL *c) { + assert(g); + + if (!c) + return; + + if (g->curl) + curl_multi_remove_handle(g->curl, c); + + curl_easy_cleanup(c); +} + +struct curl_slist *curl_slist_new(const char *first, ...) { + struct curl_slist *l; + va_list ap; + + if (!first) + return NULL; + + l = curl_slist_append(NULL, first); + if (!l) + return NULL; + + va_start(ap, first); + + for (;;) { + struct curl_slist *n; + const char *i; + + i = va_arg(ap, const char*); + if (!i) + break; + + n = curl_slist_append(l, i); + if (!n) { + va_end(ap); + curl_slist_free_all(l); + return NULL; + } + + l = n; + } + + va_end(ap); + return l; +} + +int curl_header_strdup(const void *contents, size_t sz, const char *field, char **value) { + const char *p = contents; + size_t l; + char *s; + + l = strlen(field); + if (sz < l) + return 0; + + if (memcmp(p, field, l) != 0) + return 0; + + p += l; + sz -= l; + + if (memchr(p, 0, sz)) + return 0; + + /* Skip over preceeding whitespace */ + while (sz > 0 && strchr(WHITESPACE, p[0])) { + p++; + sz--; + } + + /* Truncate trailing whitespace*/ + while (sz > 0 && strchr(WHITESPACE, p[sz-1])) + sz--; + + s = strndup(p, sz); + if (!s) + return -ENOMEM; + + *value = s; + return 1; +} + +int curl_parse_http_time(const char *t, usec_t *ret) { + const char *e; + locale_t loc; + struct tm tm; + time_t v; + + assert(t); + assert(ret); + + loc = newlocale(LC_TIME_MASK, "C", (locale_t) 0); + if (loc == (locale_t) 0) + return -errno; + + /* RFC822 */ + e = strptime_l(t, "%a, %d %b %Y %H:%M:%S %Z", &tm, loc); + if (!e || *e != 0) + /* RFC 850 */ + e = strptime_l(t, "%A, %d-%b-%y %H:%M:%S %Z", &tm, loc); + if (!e || *e != 0) + /* ANSI C */ + e = strptime_l(t, "%a %b %d %H:%M:%S %Y", &tm, loc); + freelocale(loc); + if (!e || *e != 0) + return -EINVAL; + + v = timegm(&tm); + if (v == (time_t) -1) + return -EINVAL; + + *ret = (usec_t) v * USEC_PER_SEC; + return 0; +} diff --git a/src/grp-machine/grp-import/curl-util.h b/src/grp-machine/grp-import/curl-util.h new file mode 100644 index 0000000000..296b35d09c --- /dev/null +++ b/src/grp-machine/grp-import/curl-util.h @@ -0,0 +1,56 @@ +#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 "basic/hashmap.h" + +typedef struct CurlGlue CurlGlue; + +struct CurlGlue { + sd_event *event; + CURLM *curl; + sd_event_source *timer; + Hashmap *ios; + Hashmap *translate_fds; + + void (*on_finished)(CurlGlue *g, CURL *curl, CURLcode code); + void *userdata; +}; + +int curl_glue_new(CurlGlue **glue, sd_event *event); +CurlGlue* curl_glue_unref(CurlGlue *glue); + +DEFINE_TRIVIAL_CLEANUP_FUNC(CurlGlue*, curl_glue_unref); + +int curl_glue_make(CURL **ret, const char *url, void *userdata); +int curl_glue_add(CurlGlue *g, CURL *c); +void curl_glue_remove_and_free(CurlGlue *g, CURL *c); + +struct curl_slist *curl_slist_new(const char *first, ...) _sentinel_; +int curl_header_strdup(const void *contents, size_t sz, const char *field, char **value); +int curl_parse_http_time(const char *t, usec_t *ret); + +DEFINE_TRIVIAL_CLEANUP_FUNC(CURL*, curl_easy_cleanup); +DEFINE_TRIVIAL_CLEANUP_FUNC(struct curl_slist*, curl_slist_free_all); diff --git a/src/grp-machine/grp-import/org.freedesktop.import1.conf b/src/grp-machine/grp-import/org.freedesktop.import1.conf new file mode 100644 index 0000000000..ed2539a03b --- /dev/null +++ b/src/grp-machine/grp-import/org.freedesktop.import1.conf @@ -0,0 +1,62 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/src/grp-machine/grp-import/org.freedesktop.import1.policy.in b/src/grp-machine/grp-import/org.freedesktop.import1.policy.in new file mode 100644 index 0000000000..85924ed743 --- /dev/null +++ b/src/grp-machine/grp-import/org.freedesktop.import1.policy.in @@ -0,0 +1,49 @@ + + + + + + + + The systemd Project + http://www.freedesktop.org/wiki/Software/systemd + + + <_description>Import a VM or container image + <_message>Authentication is required to import a VM or container image + + auth_admin + auth_admin + auth_admin_keep + + + + + <_description>Export a VM or container image + <_message>Authentication is required to export a VM or container image + + auth_admin + auth_admin + auth_admin_keep + + + + + <_description>Download a VM or container image + <_message>Authentication is required to download a VM or container image + + auth_admin + auth_admin + auth_admin_keep + + + + diff --git a/src/grp-machine/grp-import/org.freedesktop.import1.service b/src/grp-machine/grp-import/org.freedesktop.import1.service new file mode 100644 index 0000000000..8fc4c47881 --- /dev/null +++ b/src/grp-machine/grp-import/org.freedesktop.import1.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.import1 +Exec=/bin/false +User=root +SystemdService=dbus-org.freedesktop.import1.service diff --git a/src/grp-machine/grp-import/qcow2-util.c b/src/grp-machine/grp-import/qcow2-util.c new file mode 100644 index 0000000000..fa272088d3 --- /dev/null +++ b/src/grp-machine/grp-import/qcow2-util.c @@ -0,0 +1,352 @@ +/*** + 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 "basic/alloc-util.h" +#include "basic/btrfs-util.h" +#include "qcow2-util.h" +#include "basic/sparse-endian.h" +#include "basic/util.h" + +#define QCOW2_MAGIC 0x514649fb + +#define QCOW2_COPIED (1ULL << 63) +#define QCOW2_COMPRESSED (1ULL << 62) +#define QCOW2_ZERO (1ULL << 0) + +typedef struct _packed_ Header { + be32_t magic; + be32_t version; + + be64_t backing_file_offset; + be32_t backing_file_size; + + be32_t cluster_bits; + be64_t size; + be32_t crypt_method; + + be32_t l1_size; + be64_t l1_table_offset; + + be64_t refcount_table_offset; + be32_t refcount_table_clusters; + + be32_t nb_snapshots; + be64_t snapshots_offset; + + /* The remainder is only present on QCOW3 */ + be64_t incompatible_features; + be64_t compatible_features; + be64_t autoclear_features; + + be32_t refcount_order; + be32_t header_length; +} Header; + +#define HEADER_MAGIC(header) be32toh((header)->magic) +#define HEADER_VERSION(header) be32toh((header)->version) +#define HEADER_CLUSTER_BITS(header) be32toh((header)->cluster_bits) +#define HEADER_CLUSTER_SIZE(header) (1ULL << HEADER_CLUSTER_BITS(header)) +#define HEADER_L2_BITS(header) (HEADER_CLUSTER_BITS(header) - 3) +#define HEADER_SIZE(header) be64toh((header)->size) +#define HEADER_CRYPT_METHOD(header) be32toh((header)->crypt_method) +#define HEADER_L1_SIZE(header) be32toh((header)->l1_size) +#define HEADER_L2_SIZE(header) (HEADER_CLUSTER_SIZE(header)/sizeof(uint64_t)) +#define HEADER_L1_TABLE_OFFSET(header) be64toh((header)->l1_table_offset) + +static uint32_t HEADER_HEADER_LENGTH(const Header *h) { + if (HEADER_VERSION(h) < 3) + return offsetof(Header, incompatible_features); + + return be32toh(h->header_length); +} + +static int copy_cluster( + int sfd, uint64_t soffset, + int dfd, uint64_t doffset, + uint64_t cluster_size, + void *buffer) { + + ssize_t l; + int r; + + r = btrfs_clone_range(sfd, soffset, dfd, doffset, cluster_size); + if (r >= 0) + return r; + + l = pread(sfd, buffer, cluster_size, soffset); + if (l < 0) + return -errno; + if ((uint64_t) l != cluster_size) + return -EIO; + + l = pwrite(dfd, buffer, cluster_size, doffset); + if (l < 0) + return -errno; + if ((uint64_t) l != cluster_size) + return -EIO; + + return 0; +} + +static int decompress_cluster( + int sfd, uint64_t soffset, + int dfd, uint64_t doffset, + uint64_t compressed_size, + uint64_t cluster_size, + void *buffer1, + void *buffer2) { + + _cleanup_free_ void *large_buffer = NULL; + z_stream s = {}; + uint64_t sz; + ssize_t l; + int r; + + if (compressed_size > cluster_size) { + /* The usual cluster buffer doesn't suffice, let's + * allocate a larger one, temporarily */ + + large_buffer = malloc(compressed_size); + if (!large_buffer) + return -ENOMEM; + + buffer1 = large_buffer; + } + + l = pread(sfd, buffer1, compressed_size, soffset); + if (l < 0) + return -errno; + if ((uint64_t) l != compressed_size) + return -EIO; + + s.next_in = buffer1; + s.avail_in = compressed_size; + s.next_out = buffer2; + s.avail_out = cluster_size; + + r = inflateInit2(&s, -12); + if (r != Z_OK) + return -EIO; + + r = inflate(&s, Z_FINISH); + sz = (uint8_t*) s.next_out - (uint8_t*) buffer2; + inflateEnd(&s); + if (r != Z_STREAM_END || sz != cluster_size) + return -EIO; + + l = pwrite(dfd, buffer2, cluster_size, doffset); + if (l < 0) + return -errno; + if ((uint64_t) l != cluster_size) + return -EIO; + + return 0; +} + +static int normalize_offset( + const Header *header, + uint64_t p, + uint64_t *ret, + bool *compressed, + uint64_t *compressed_size) { + + uint64_t q; + + q = be64toh(p); + + if (q & QCOW2_COMPRESSED) { + uint64_t sz, csize_shift, csize_mask; + + if (!compressed) + return -EOPNOTSUPP; + + csize_shift = 64 - 2 - (HEADER_CLUSTER_BITS(header) - 8); + csize_mask = (1ULL << (HEADER_CLUSTER_BITS(header) - 8)) - 1; + sz = (((q >> csize_shift) & csize_mask) + 1) * 512 - (q & 511); + q &= ((1ULL << csize_shift) - 1); + + if (compressed_size) + *compressed_size = sz; + + *compressed = true; + + } else { + if (compressed) { + *compressed = false; + *compressed_size = 0; + } + + if (q & QCOW2_ZERO) { + /* We make no distinction between zero blocks and holes */ + *ret = 0; + return 0; + } + + q &= ~QCOW2_COPIED; + } + + *ret = q; + return q > 0; /* returns positive if not a hole */ +} + +static int verify_header(const Header *header) { + assert(header); + + if (HEADER_MAGIC(header) != QCOW2_MAGIC) + return -EBADMSG; + + if (HEADER_VERSION(header) != 2 && + HEADER_VERSION(header) != 3) + return -EOPNOTSUPP; + + if (HEADER_CRYPT_METHOD(header) != 0) + return -EOPNOTSUPP; + + if (HEADER_CLUSTER_BITS(header) < 9) /* 512K */ + return -EBADMSG; + + if (HEADER_CLUSTER_BITS(header) > 21) /* 2MB */ + return -EBADMSG; + + if (HEADER_SIZE(header) % HEADER_CLUSTER_SIZE(header) != 0) + return -EBADMSG; + + if (HEADER_L1_SIZE(header) > 32*1024*1024) /* 32MB */ + return -EBADMSG; + + if (HEADER_VERSION(header) == 3) { + + if (header->incompatible_features != 0) + return -EOPNOTSUPP; + + if (HEADER_HEADER_LENGTH(header) < sizeof(Header)) + return -EBADMSG; + } + + return 0; +} + +int qcow2_convert(int qcow2_fd, int raw_fd) { + _cleanup_free_ void *buffer1 = NULL, *buffer2 = NULL; + _cleanup_free_ be64_t *l1_table = NULL, *l2_table = NULL; + uint64_t sz, i; + Header header; + ssize_t l; + int r; + + l = pread(qcow2_fd, &header, sizeof(header), 0); + if (l < 0) + return -errno; + if (l != sizeof(header)) + return -EIO; + + r = verify_header(&header); + if (r < 0) + return r; + + l1_table = new(be64_t, HEADER_L1_SIZE(&header)); + if (!l1_table) + return -ENOMEM; + + l2_table = malloc(HEADER_CLUSTER_SIZE(&header)); + if (!l2_table) + return -ENOMEM; + + buffer1 = malloc(HEADER_CLUSTER_SIZE(&header)); + if (!buffer1) + return -ENOMEM; + + buffer2 = malloc(HEADER_CLUSTER_SIZE(&header)); + if (!buffer2) + return -ENOMEM; + + /* Empty the file if it exists, we rely on zero bits */ + if (ftruncate(raw_fd, 0) < 0) + return -errno; + + if (ftruncate(raw_fd, HEADER_SIZE(&header)) < 0) + return -errno; + + sz = sizeof(uint64_t) * HEADER_L1_SIZE(&header); + l = pread(qcow2_fd, l1_table, sz, HEADER_L1_TABLE_OFFSET(&header)); + if (l < 0) + return -errno; + if ((uint64_t) l != sz) + return -EIO; + + for (i = 0; i < HEADER_L1_SIZE(&header); i ++) { + uint64_t l2_begin, j; + + r = normalize_offset(&header, l1_table[i], &l2_begin, NULL, NULL); + if (r < 0) + return r; + if (r == 0) + continue; + + l = pread(qcow2_fd, l2_table, HEADER_CLUSTER_SIZE(&header), l2_begin); + if (l < 0) + return -errno; + if ((uint64_t) l != HEADER_CLUSTER_SIZE(&header)) + return -EIO; + + for (j = 0; j < HEADER_L2_SIZE(&header); j++) { + uint64_t data_begin, p, compressed_size; + bool compressed; + + p = ((i << HEADER_L2_BITS(&header)) + j) << HEADER_CLUSTER_BITS(&header); + + r = normalize_offset(&header, l2_table[j], &data_begin, &compressed, &compressed_size); + if (r < 0) + return r; + if (r == 0) + continue; + + if (compressed) + r = decompress_cluster( + qcow2_fd, data_begin, + raw_fd, p, + compressed_size, HEADER_CLUSTER_SIZE(&header), + buffer1, buffer2); + else + r = copy_cluster( + qcow2_fd, data_begin, + raw_fd, p, + HEADER_CLUSTER_SIZE(&header), buffer1); + if (r < 0) + return r; + } + } + + return 0; +} + +int qcow2_detect(int fd) { + be32_t id; + ssize_t l; + + l = pread(fd, &id, sizeof(id), 0); + if (l < 0) + return -errno; + if (l != sizeof(id)) + return -EIO; + + return htobe32(QCOW2_MAGIC) == id; +} diff --git a/src/grp-machine/grp-import/qcow2-util.h b/src/grp-machine/grp-import/qcow2-util.h new file mode 100644 index 0000000000..6dddac8cdf --- /dev/null +++ b/src/grp-machine/grp-import/qcow2-util.h @@ -0,0 +1,23 @@ +#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 qcow2_detect(int fd); +int qcow2_convert(int qcow2_fd, int raw_fd); diff --git a/src/grp-machine/grp-import/systemd-export/Makefile b/src/grp-machine/grp-import/systemd-export/Makefile new file mode 100644 index 0000000000..d0e8781faf --- /dev/null +++ b/src/grp-machine/grp-import/systemd-export/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 + +rootlibexec_PROGRAMS += systemd-export + +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) \ + $(BZIP2_CFLAGS) + +systemd_export_LDADD = \ + libshared.la \ + $(XZ_LIBS) \ + $(ZLIB_LIBS) \ + $(BZIP2_LIBS) + +include $(topsrcdir)/build-aux/Makefile.tail.mk diff --git a/src/grp-machine/grp-import/systemd-export/export-raw.c b/src/grp-machine/grp-import/systemd-export/export-raw.c new file mode 100644 index 0000000000..1e3f6e034e --- /dev/null +++ b/src/grp-machine/grp-import/systemd-export/export-raw.c @@ -0,0 +1,352 @@ +/*** + 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 + +/* 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 + +#include "basic/alloc-util.h" +#include "basic/btrfs-util.h" +#include "basic/copy.h" +#include "export-raw.h" +#include "basic/fd-util.h" +#include "basic/fileio.h" +#include "import-common.h" +#include "basic/ratelimit.h" +#include "basic/string-util.h" +#include "basic/util.h" + +#define COPY_BUFFER_SIZE (16*1024) + +struct RawExport { + sd_event *event; + + RawExportFinished on_finished; + void *userdata; + + char *path; + + int input_fd; + int output_fd; + + ImportCompress compress; + + sd_event_source *output_event_source; + + void *buffer; + size_t buffer_size; + size_t buffer_allocated; + + uint64_t written_compressed; + uint64_t written_uncompressed; + + unsigned last_percent; + RateLimit progress_rate_limit; + + struct stat st; + + bool eof; + bool tried_reflink; + bool tried_sendfile; +}; + +RawExport *raw_export_unref(RawExport *e) { + if (!e) + return NULL; + + sd_event_source_unref(e->output_event_source); + + import_compress_free(&e->compress); + + sd_event_unref(e->event); + + safe_close(e->input_fd); + + free(e->buffer); + free(e->path); + free(e); + + return NULL; +} + +int raw_export_new( + RawExport **ret, + sd_event *event, + RawExportFinished on_finished, + void *userdata) { + + _cleanup_(raw_export_unrefp) RawExport *e = NULL; + int r; + + assert(ret); + + e = new0(RawExport, 1); + if (!e) + return -ENOMEM; + + e->output_fd = e->input_fd = -1; + e->on_finished = on_finished; + e->userdata = userdata; + + RATELIMIT_INIT(e->progress_rate_limit, 100 * USEC_PER_MSEC, 1); + e->last_percent = (unsigned) -1; + + if (event) + e->event = sd_event_ref(event); + else { + r = sd_event_default(&e->event); + if (r < 0) + return r; + } + + *ret = e; + e = NULL; + + return 0; +} + +static void raw_export_report_progress(RawExport *e) { + unsigned percent; + assert(e); + + if (e->written_uncompressed >= (uint64_t) e->st.st_size) + percent = 100; + else + percent = (unsigned) ((e->written_uncompressed * UINT64_C(100)) / (uint64_t) e->st.st_size); + + if (percent == e->last_percent) + return; + + if (!ratelimit_test(&e->progress_rate_limit)) + return; + + sd_notifyf(false, "X_IMPORT_PROGRESS=%u", percent); + log_info("Exported %u%%.", percent); + + e->last_percent = percent; +} + +static int raw_export_process(RawExport *e) { + ssize_t l; + int r; + + assert(e); + + if (!e->tried_reflink && e->compress.type == IMPORT_COMPRESS_UNCOMPRESSED) { + + /* If we shall take an uncompressed snapshot we can + * reflink source to destination directly. Let's see + * if this works. */ + + r = btrfs_reflink(e->input_fd, e->output_fd); + if (r >= 0) { + r = 0; + goto finish; + } + + e->tried_reflink = true; + } + + if (!e->tried_sendfile && e->compress.type == IMPORT_COMPRESS_UNCOMPRESSED) { + + l = sendfile(e->output_fd, e->input_fd, NULL, COPY_BUFFER_SIZE); + if (l < 0) { + if (errno == EAGAIN) + return 0; + + e->tried_sendfile = true; + } else if (l == 0) { + r = 0; + goto finish; + } else { + e->written_uncompressed += l; + e->written_compressed += l; + + raw_export_report_progress(e); + + return 0; + } + } + + while (e->buffer_size <= 0) { + uint8_t input[COPY_BUFFER_SIZE]; + + if (e->eof) { + r = 0; + goto finish; + } + + l = read(e->input_fd, input, sizeof(input)); + if (l < 0) { + r = log_error_errno(errno, "Failed to read raw file: %m"); + goto finish; + } + + if (l == 0) { + e->eof = true; + r = import_compress_finish(&e->compress, &e->buffer, &e->buffer_size, &e->buffer_allocated); + } else { + e->written_uncompressed += l; + r = import_compress(&e->compress, input, l, &e->buffer, &e->buffer_size, &e->buffer_allocated); + } + if (r < 0) { + r = log_error_errno(r, "Failed to encode: %m"); + goto finish; + } + } + + l = write(e->output_fd, e->buffer, e->buffer_size); + if (l < 0) { + if (errno == EAGAIN) + return 0; + + r = log_error_errno(errno, "Failed to write output file: %m"); + goto finish; + } + + assert((size_t) l <= e->buffer_size); + memmove(e->buffer, (uint8_t*) e->buffer + l, e->buffer_size - l); + e->buffer_size -= l; + e->written_compressed += l; + + raw_export_report_progress(e); + + return 0; + +finish: + if (r >= 0) { + (void) copy_times(e->input_fd, e->output_fd); + (void) copy_xattr(e->input_fd, e->output_fd); + } + + if (e->on_finished) + e->on_finished(e, r, e->userdata); + else + sd_event_exit(e->event, r); + + return 0; +} + +static int raw_export_on_output(sd_event_source *s, int fd, uint32_t revents, void *userdata) { + RawExport *i = userdata; + + return raw_export_process(i); +} + +static int raw_export_on_defer(sd_event_source *s, void *userdata) { + RawExport *i = userdata; + + return raw_export_process(i); +} + +static int reflink_snapshot(int fd, const char *path) { + char *p, *d; + int new_fd, r; + + p = strdupa(path); + d = dirname(p); + + new_fd = open(d, O_TMPFILE|O_CLOEXEC|O_NOCTTY|O_RDWR, 0600); + if (new_fd < 0) { + _cleanup_free_ char *t = NULL; + + r = tempfn_random(path, NULL, &t); + if (r < 0) + return r; + + new_fd = open(t, O_CLOEXEC|O_CREAT|O_NOCTTY|O_RDWR, 0600); + if (new_fd < 0) + return -errno; + + (void) unlink(t); + } + + r = btrfs_reflink(fd, new_fd); + if (r < 0) { + safe_close(new_fd); + return r; + } + + return new_fd; +} + +int raw_export_start(RawExport *e, const char *path, int fd, ImportCompressType compress) { + _cleanup_close_ int sfd = -1, tfd = -1; + int r; + + assert(e); + assert(path); + assert(fd >= 0); + assert(compress < _IMPORT_COMPRESS_TYPE_MAX); + assert(compress != IMPORT_COMPRESS_UNKNOWN); + + if (e->output_fd >= 0) + return -EBUSY; + + r = fd_nonblock(fd, true); + if (r < 0) + return r; + + r = free_and_strdup(&e->path, path); + if (r < 0) + return r; + + sfd = open(path, O_RDONLY|O_CLOEXEC|O_NOCTTY); + if (sfd < 0) + return -errno; + + if (fstat(sfd, &e->st) < 0) + return -errno; + if (!S_ISREG(e->st.st_mode)) + return -ENOTTY; + + /* Try to take a reflink snapshot of the file, if we can t make the export atomic */ + tfd = reflink_snapshot(sfd, path); + if (tfd >= 0) { + e->input_fd = tfd; + tfd = -1; + } else { + e->input_fd = sfd; + sfd = -1; + } + + r = import_compress_init(&e->compress, compress); + if (r < 0) + return r; + + r = sd_event_add_io(e->event, &e->output_event_source, fd, EPOLLOUT, raw_export_on_output, e); + if (r == -EPERM) { + r = sd_event_add_defer(e->event, &e->output_event_source, raw_export_on_defer, e); + if (r < 0) + return r; + + r = sd_event_source_set_enabled(e->output_event_source, SD_EVENT_ON); + } + if (r < 0) + return r; + + e->output_fd = fd; + return r; +} diff --git a/src/grp-machine/grp-import/systemd-export/export-raw.h b/src/grp-machine/grp-import/systemd-export/export-raw.h new file mode 100644 index 0000000000..13d7809b9d --- /dev/null +++ b/src/grp-machine/grp-import/systemd-export/export-raw.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 "import-compress.h" +#include "basic/macro.h" + +typedef struct RawExport RawExport; + +typedef void (*RawExportFinished)(RawExport *export, int error, void *userdata); + +int raw_export_new(RawExport **export, sd_event *event, RawExportFinished on_finished, void *userdata); +RawExport* raw_export_unref(RawExport *export); + +DEFINE_TRIVIAL_CLEANUP_FUNC(RawExport*, raw_export_unref); + +int raw_export_start(RawExport *export, const char *path, int fd, ImportCompressType compress); diff --git a/src/grp-machine/grp-import/systemd-export/export-tar.c b/src/grp-machine/grp-import/systemd-export/export-tar.c new file mode 100644 index 0000000000..9a8d48dc2e --- /dev/null +++ b/src/grp-machine/grp-import/systemd-export/export-tar.c @@ -0,0 +1,328 @@ +/*** + 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 "basic/alloc-util.h" +#include "basic/btrfs-util.h" +#include "export-tar.h" +#include "basic/fd-util.h" +#include "basic/fileio.h" +#include "import-common.h" +#include "basic/process-util.h" +#include "basic/ratelimit.h" +#include "basic/string-util.h" +#include "basic/util.h" + +#define COPY_BUFFER_SIZE (16*1024) + +struct TarExport { + sd_event *event; + + TarExportFinished on_finished; + void *userdata; + + char *path; + char *temp_path; + + int output_fd; + int tar_fd; + + ImportCompress compress; + + sd_event_source *output_event_source; + + void *buffer; + size_t buffer_size; + size_t buffer_allocated; + + uint64_t written_compressed; + uint64_t written_uncompressed; + + pid_t tar_pid; + + struct stat st; + uint64_t quota_referenced; + + unsigned last_percent; + RateLimit progress_rate_limit; + + bool eof; + bool tried_splice; +}; + +TarExport *tar_export_unref(TarExport *e) { + if (!e) + return NULL; + + sd_event_source_unref(e->output_event_source); + + if (e->tar_pid > 1) { + (void) kill_and_sigcont(e->tar_pid, SIGKILL); + (void) wait_for_terminate(e->tar_pid, NULL); + } + + if (e->temp_path) { + (void) btrfs_subvol_remove(e->temp_path, BTRFS_REMOVE_QUOTA); + free(e->temp_path); + } + + import_compress_free(&e->compress); + + sd_event_unref(e->event); + + safe_close(e->tar_fd); + + free(e->buffer); + free(e->path); + free(e); + + return NULL; +} + +int tar_export_new( + TarExport **ret, + sd_event *event, + TarExportFinished on_finished, + void *userdata) { + + _cleanup_(tar_export_unrefp) TarExport *e = NULL; + int r; + + assert(ret); + + e = new0(TarExport, 1); + if (!e) + return -ENOMEM; + + e->output_fd = e->tar_fd = -1; + e->on_finished = on_finished; + e->userdata = userdata; + e->quota_referenced = (uint64_t) -1; + + RATELIMIT_INIT(e->progress_rate_limit, 100 * USEC_PER_MSEC, 1); + e->last_percent = (unsigned) -1; + + if (event) + e->event = sd_event_ref(event); + else { + r = sd_event_default(&e->event); + if (r < 0) + return r; + } + + *ret = e; + e = NULL; + + return 0; +} + +static void tar_export_report_progress(TarExport *e) { + unsigned percent; + assert(e); + + /* Do we have any quota info? If not, we don't know anything about the progress */ + if (e->quota_referenced == (uint64_t) -1) + return; + + if (e->written_uncompressed >= e->quota_referenced) + percent = 100; + else + percent = (unsigned) ((e->written_uncompressed * UINT64_C(100)) / e->quota_referenced); + + if (percent == e->last_percent) + return; + + if (!ratelimit_test(&e->progress_rate_limit)) + return; + + sd_notifyf(false, "X_IMPORT_PROGRESS=%u", percent); + log_info("Exported %u%%.", percent); + + e->last_percent = percent; +} + +static int tar_export_process(TarExport *e) { + ssize_t l; + int r; + + assert(e); + + if (!e->tried_splice && e->compress.type == IMPORT_COMPRESS_UNCOMPRESSED) { + + l = splice(e->tar_fd, NULL, e->output_fd, NULL, COPY_BUFFER_SIZE, 0); + if (l < 0) { + if (errno == EAGAIN) + return 0; + + e->tried_splice = true; + } else if (l == 0) { + r = 0; + goto finish; + } else { + e->written_uncompressed += l; + e->written_compressed += l; + + tar_export_report_progress(e); + + return 0; + } + } + + while (e->buffer_size <= 0) { + uint8_t input[COPY_BUFFER_SIZE]; + + if (e->eof) { + r = 0; + goto finish; + } + + l = read(e->tar_fd, input, sizeof(input)); + if (l < 0) { + r = log_error_errno(errno, "Failed to read tar file: %m"); + goto finish; + } + + if (l == 0) { + e->eof = true; + r = import_compress_finish(&e->compress, &e->buffer, &e->buffer_size, &e->buffer_allocated); + } else { + e->written_uncompressed += l; + r = import_compress(&e->compress, input, l, &e->buffer, &e->buffer_size, &e->buffer_allocated); + } + if (r < 0) { + r = log_error_errno(r, "Failed to encode: %m"); + goto finish; + } + } + + l = write(e->output_fd, e->buffer, e->buffer_size); + if (l < 0) { + if (errno == EAGAIN) + return 0; + + r = log_error_errno(errno, "Failed to write output file: %m"); + goto finish; + } + + assert((size_t) l <= e->buffer_size); + memmove(e->buffer, (uint8_t*) e->buffer + l, e->buffer_size - l); + e->buffer_size -= l; + e->written_compressed += l; + + tar_export_report_progress(e); + + return 0; + +finish: + if (e->on_finished) + e->on_finished(e, r, e->userdata); + else + sd_event_exit(e->event, r); + + return 0; +} + +static int tar_export_on_output(sd_event_source *s, int fd, uint32_t revents, void *userdata) { + TarExport *i = userdata; + + return tar_export_process(i); +} + +static int tar_export_on_defer(sd_event_source *s, void *userdata) { + TarExport *i = userdata; + + return tar_export_process(i); +} + +int tar_export_start(TarExport *e, const char *path, int fd, ImportCompressType compress) { + _cleanup_close_ int sfd = -1; + int r; + + assert(e); + assert(path); + assert(fd >= 0); + assert(compress < _IMPORT_COMPRESS_TYPE_MAX); + assert(compress != IMPORT_COMPRESS_UNKNOWN); + + if (e->output_fd >= 0) + return -EBUSY; + + sfd = open(path, O_DIRECTORY|O_RDONLY|O_NOCTTY|O_CLOEXEC); + if (sfd < 0) + return -errno; + + if (fstat(sfd, &e->st) < 0) + return -errno; + + r = fd_nonblock(fd, true); + if (r < 0) + return r; + + r = free_and_strdup(&e->path, path); + if (r < 0) + return r; + + e->quota_referenced = (uint64_t) -1; + + if (e->st.st_ino == 256) { /* might be a btrfs subvolume? */ + BtrfsQuotaInfo q; + + r = btrfs_subvol_get_subtree_quota_fd(sfd, 0, &q); + if (r >= 0) + e->quota_referenced = q.referenced; + + e->temp_path = mfree(e->temp_path); + + r = tempfn_random(path, NULL, &e->temp_path); + if (r < 0) + return r; + + /* Let's try to make a snapshot, if we can, so that the export is atomic */ + r = btrfs_subvol_snapshot_fd(sfd, e->temp_path, BTRFS_SNAPSHOT_READ_ONLY|BTRFS_SNAPSHOT_RECURSIVE); + if (r < 0) { + log_debug_errno(r, "Couldn't create snapshot %s of %s, not exporting atomically: %m", e->temp_path, path); + e->temp_path = mfree(e->temp_path); + } + } + + r = import_compress_init(&e->compress, compress); + if (r < 0) + return r; + + r = sd_event_add_io(e->event, &e->output_event_source, fd, EPOLLOUT, tar_export_on_output, e); + if (r == -EPERM) { + r = sd_event_add_defer(e->event, &e->output_event_source, tar_export_on_defer, e); + if (r < 0) + return r; + + r = sd_event_source_set_enabled(e->output_event_source, SD_EVENT_ON); + } + if (r < 0) + return r; + + e->tar_fd = import_fork_tar_c(e->temp_path ?: e->path, &e->tar_pid); + if (e->tar_fd < 0) { + e->output_event_source = sd_event_source_unref(e->output_event_source); + return e->tar_fd; + } + + e->output_fd = fd; + return r; +} diff --git a/src/grp-machine/grp-import/systemd-export/export-tar.h b/src/grp-machine/grp-import/systemd-export/export-tar.h new file mode 100644 index 0000000000..0a59033d5b --- /dev/null +++ b/src/grp-machine/grp-import/systemd-export/export-tar.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 "import-compress.h" +#include "basic/macro.h" + +typedef struct TarExport TarExport; + +typedef void (*TarExportFinished)(TarExport *export, int error, void *userdata); + +int tar_export_new(TarExport **export, sd_event *event, TarExportFinished on_finished, void *userdata); +TarExport* tar_export_unref(TarExport *export); + +DEFINE_TRIVIAL_CLEANUP_FUNC(TarExport*, tar_export_unref); + +int tar_export_start(TarExport *export, const char *path, int fd, ImportCompressType compress); diff --git a/src/grp-machine/grp-import/systemd-export/export.c b/src/grp-machine/grp-import/systemd-export/export.c new file mode 100644 index 0000000000..a64b79bba0 --- /dev/null +++ b/src/grp-machine/grp-import/systemd-export/export.c @@ -0,0 +1,320 @@ +/*** + 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 "basic/alloc-util.h" +#include "export-raw.h" +#include "export-tar.h" +#include "basic/fd-util.h" +#include "basic/fs-util.h" +#include "basic/hostname-util.h" +#include "shared/import-util.h" +#include "shared/machine-image.h" +#include "basic/signal-util.h" +#include "basic/string-util.h" +#include "basic/verbs.h" + +static ImportCompressType arg_compress = IMPORT_COMPRESS_UNKNOWN; + +static void determine_compression_from_filename(const char *p) { + + if (arg_compress != IMPORT_COMPRESS_UNKNOWN) + return; + + if (!p) { + arg_compress = IMPORT_COMPRESS_UNCOMPRESSED; + return; + } + + if (endswith(p, ".xz")) + arg_compress = IMPORT_COMPRESS_XZ; + else if (endswith(p, ".gz")) + arg_compress = IMPORT_COMPRESS_GZIP; + else if (endswith(p, ".bz2")) + arg_compress = IMPORT_COMPRESS_BZIP2; + else + arg_compress = IMPORT_COMPRESS_UNCOMPRESSED; +} + +static int interrupt_signal_handler(sd_event_source *s, const struct signalfd_siginfo *si, void *userdata) { + log_notice("Transfer aborted."); + sd_event_exit(sd_event_source_get_event(s), EINTR); + return 0; +} + +static void on_tar_finished(TarExport *export, int error, void *userdata) { + sd_event *event = userdata; + assert(export); + + if (error == 0) + log_info("Operation completed successfully."); + + sd_event_exit(event, abs(error)); +} + +static int export_tar(int argc, char *argv[], void *userdata) { + _cleanup_(tar_export_unrefp) TarExport *export = NULL; + _cleanup_(sd_event_unrefp) sd_event *event = NULL; + _cleanup_(image_unrefp) Image *image = NULL; + const char *path = NULL, *local = NULL; + _cleanup_close_ int open_fd = -1; + int r, fd; + + if (machine_name_is_valid(argv[1])) { + r = image_find(argv[1], &image); + if (r < 0) + return log_error_errno(r, "Failed to look for machine %s: %m", argv[1]); + if (r == 0) { + log_error("Machine image %s not found.", argv[1]); + return -ENOENT; + } + + local = image->path; + } else + local = argv[1]; + + if (argc >= 3) + path = argv[2]; + if (isempty(path) || streq(path, "-")) + path = NULL; + + determine_compression_from_filename(path); + + if (path) { + open_fd = open(path, O_WRONLY|O_CREAT|O_TRUNC|O_CLOEXEC|O_NOCTTY, 0666); + if (open_fd < 0) + return log_error_errno(errno, "Failed to open tar image for export: %m"); + + fd = open_fd; + + log_info("Exporting '%s', saving to '%s' with compression '%s'.", local, path, import_compress_type_to_string(arg_compress)); + } else { + _cleanup_free_ char *pretty = NULL; + + fd = STDOUT_FILENO; + + (void) readlink_malloc("/proc/self/fd/1", &pretty); + log_info("Exporting '%s', saving to '%s' with compression '%s'.", local, strna(pretty), import_compress_type_to_string(arg_compress)); + } + + r = sd_event_default(&event); + if (r < 0) + return log_error_errno(r, "Failed to allocate event loop: %m"); + + assert_se(sigprocmask_many(SIG_BLOCK, NULL, SIGTERM, SIGINT, -1) >= 0); + (void) sd_event_add_signal(event, NULL, SIGTERM, interrupt_signal_handler, NULL); + (void) sd_event_add_signal(event, NULL, SIGINT, interrupt_signal_handler, NULL); + + r = tar_export_new(&export, event, on_tar_finished, event); + if (r < 0) + return log_error_errno(r, "Failed to allocate exporter: %m"); + + r = tar_export_start(export, local, fd, arg_compress); + if (r < 0) + return log_error_errno(r, "Failed to export image: %m"); + + r = sd_event_loop(event); + if (r < 0) + return log_error_errno(r, "Failed to run event loop: %m"); + + log_info("Exiting."); + return -r; +} + +static void on_raw_finished(RawExport *export, int error, void *userdata) { + sd_event *event = userdata; + assert(export); + + if (error == 0) + log_info("Operation completed successfully."); + + sd_event_exit(event, abs(error)); +} + +static int export_raw(int argc, char *argv[], void *userdata) { + _cleanup_(raw_export_unrefp) RawExport *export = NULL; + _cleanup_(sd_event_unrefp) sd_event *event = NULL; + _cleanup_(image_unrefp) Image *image = NULL; + const char *path = NULL, *local = NULL; + _cleanup_close_ int open_fd = -1; + int r, fd; + + if (machine_name_is_valid(argv[1])) { + r = image_find(argv[1], &image); + if (r < 0) + return log_error_errno(r, "Failed to look for machine %s: %m", argv[1]); + if (r == 0) { + log_error("Machine image %s not found.", argv[1]); + return -ENOENT; + } + + local = image->path; + } else + local = argv[1]; + + if (argc >= 3) + path = argv[2]; + if (isempty(path) || streq(path, "-")) + path = NULL; + + determine_compression_from_filename(path); + + if (path) { + open_fd = open(path, O_WRONLY|O_CREAT|O_TRUNC|O_CLOEXEC|O_NOCTTY, 0666); + if (open_fd < 0) + return log_error_errno(errno, "Failed to open raw image for export: %m"); + + fd = open_fd; + + log_info("Exporting '%s', saving to '%s' with compression '%s'.", local, path, import_compress_type_to_string(arg_compress)); + } else { + _cleanup_free_ char *pretty = NULL; + + fd = STDOUT_FILENO; + + (void) readlink_malloc("/proc/self/fd/1", &pretty); + log_info("Exporting '%s', saving to '%s' with compression '%s'.", local, strna(pretty), import_compress_type_to_string(arg_compress)); + } + + r = sd_event_default(&event); + if (r < 0) + return log_error_errno(r, "Failed to allocate event loop: %m"); + + assert_se(sigprocmask_many(SIG_BLOCK, NULL, SIGTERM, SIGINT, -1) >= 0); + (void) sd_event_add_signal(event, NULL, SIGTERM, interrupt_signal_handler, NULL); + (void) sd_event_add_signal(event, NULL, SIGINT, interrupt_signal_handler, NULL); + + r = raw_export_new(&export, event, on_raw_finished, event); + if (r < 0) + return log_error_errno(r, "Failed to allocate exporter: %m"); + + r = raw_export_start(export, local, fd, arg_compress); + if (r < 0) + return log_error_errno(r, "Failed to export image: %m"); + + r = sd_event_loop(event); + if (r < 0) + return log_error_errno(r, "Failed to run event loop: %m"); + + log_info("Exiting."); + return -r; +} + +static int help(int argc, char *argv[], void *userdata) { + + printf("%s [OPTIONS...] {COMMAND} ...\n\n" + "Export container or virtual machine images.\n\n" + " -h --help Show this help\n" + " --version Show package version\n" + " --format=FORMAT Select format\n\n" + "Commands:\n" + " tar NAME [FILE] Export a TAR image\n" + " raw NAME [FILE] Export a RAW image\n", + program_invocation_short_name); + + return 0; +} + +static int parse_argv(int argc, char *argv[]) { + + enum { + ARG_VERSION = 0x100, + ARG_FORMAT, + }; + + static const struct option options[] = { + { "help", no_argument, NULL, 'h' }, + { "version", no_argument, NULL, ARG_VERSION }, + { "format", required_argument, NULL, ARG_FORMAT }, + {} + }; + + int c; + + assert(argc >= 0); + assert(argv); + + while ((c = getopt_long(argc, argv, "h", options, NULL)) >= 0) + + switch (c) { + + case 'h': + return help(0, NULL, NULL); + + case ARG_VERSION: + return version(); + + case ARG_FORMAT: + if (streq(optarg, "uncompressed")) + arg_compress = IMPORT_COMPRESS_UNCOMPRESSED; + else if (streq(optarg, "xz")) + arg_compress = IMPORT_COMPRESS_XZ; + else if (streq(optarg, "gzip")) + arg_compress = IMPORT_COMPRESS_GZIP; + else if (streq(optarg, "bzip2")) + arg_compress = IMPORT_COMPRESS_BZIP2; + else { + log_error("Unknown format: %s", optarg); + return -EINVAL; + } + break; + + case '?': + return -EINVAL; + + default: + assert_not_reached("Unhandled option"); + } + + return 1; +} + +static int export_main(int argc, char *argv[]) { + + static const Verb verbs[] = { + { "help", VERB_ANY, VERB_ANY, 0, help }, + { "tar", 2, 3, 0, export_tar }, + { "raw", 2, 3, 0, export_raw }, + {} + }; + + return dispatch_verb(argc, argv, verbs, NULL); +} + +int main(int argc, char *argv[]) { + int r; + + setlocale(LC_ALL, ""); + log_parse_environment(); + log_open(); + + r = parse_argv(argc, argv); + if (r <= 0) + goto finish; + + (void) ignore_signals(SIGPIPE, -1); + + r = export_main(argc, argv); + +finish: + return r < 0 ? EXIT_FAILURE : EXIT_SUCCESS; +} diff --git a/src/grp-machine/grp-import/systemd-import/Makefile b/src/grp-machine/grp-import/systemd-import/Makefile new file mode 100644 index 0000000000..301556ffd5 --- /dev/null +++ b/src/grp-machine/grp-import/systemd-import/Makefile @@ -0,0 +1,52 @@ +# -*- 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-import +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) \ + $(BZIP2_CFLAGS) + +systemd_import_LDADD = \ + libshared.la \ + $(XZ_LIBS) \ + $(ZLIB_LIBS) \ + $(BZIP2_LIBS) + +include $(topsrcdir)/build-aux/Makefile.tail.mk diff --git a/src/grp-machine/grp-import/systemd-import/import-common.c b/src/grp-machine/grp-import/systemd-import/import-common.c new file mode 100644 index 0000000000..c2baef83f8 --- /dev/null +++ b/src/grp-machine/grp-import/systemd-import/import-common.c @@ -0,0 +1,225 @@ +/*** + 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 "basic/btrfs-util.h" +#include "basic/capability-util.h" +#include "basic/fd-util.h" +#include "import-common.h" +#include "basic/signal-util.h" +#include "basic/util.h" + +int import_make_read_only_fd(int fd) { + int r; + + assert(fd >= 0); + + /* First, let's make this a read-only subvolume if it refers + * to a subvolume */ + r = btrfs_subvol_set_read_only_fd(fd, true); + if (r == -ENOTTY || r == -ENOTDIR || r == -EINVAL) { + struct stat st; + + /* This doesn't refer to a subvolume, or the file + * system isn't even btrfs. In that, case fall back to + * chmod()ing */ + + r = fstat(fd, &st); + if (r < 0) + return log_error_errno(errno, "Failed to stat temporary image: %m"); + + /* Drop "w" flag */ + if (fchmod(fd, st.st_mode & 07555) < 0) + return log_error_errno(errno, "Failed to chmod() final image: %m"); + + return 0; + + } else if (r < 0) + return log_error_errno(r, "Failed to make subvolume read-only: %m"); + + return 0; +} + +int import_make_read_only(const char *path) { + _cleanup_close_ int fd = 1; + + fd = open(path, O_RDONLY|O_NOCTTY|O_CLOEXEC); + if (fd < 0) + return log_error_errno(errno, "Failed to open %s: %m", path); + + return import_make_read_only_fd(fd); +} + +int import_fork_tar_x(const char *path, pid_t *ret) { + _cleanup_close_pair_ int pipefd[2] = { -1, -1 }; + pid_t pid; + int r; + + assert(path); + assert(ret); + + if (pipe2(pipefd, O_CLOEXEC) < 0) + return log_error_errno(errno, "Failed to create pipe for tar: %m"); + + pid = fork(); + if (pid < 0) + return log_error_errno(errno, "Failed to fork off tar: %m"); + + if (pid == 0) { + int null_fd; + uint64_t retain = + (1ULL << CAP_CHOWN) | + (1ULL << CAP_FOWNER) | + (1ULL << CAP_FSETID) | + (1ULL << CAP_MKNOD) | + (1ULL << CAP_SETFCAP) | + (1ULL << CAP_DAC_OVERRIDE); + + /* Child */ + + (void) reset_all_signal_handlers(); + (void) reset_signal_mask(); + assert_se(prctl(PR_SET_PDEATHSIG, SIGTERM) == 0); + + pipefd[1] = safe_close(pipefd[1]); + + if (dup2(pipefd[0], STDIN_FILENO) != STDIN_FILENO) { + log_error_errno(errno, "Failed to dup2() fd: %m"); + _exit(EXIT_FAILURE); + } + + if (pipefd[0] != STDIN_FILENO) + pipefd[0] = safe_close(pipefd[0]); + + null_fd = open("/dev/null", O_WRONLY|O_NOCTTY); + if (null_fd < 0) { + log_error_errno(errno, "Failed to open /dev/null: %m"); + _exit(EXIT_FAILURE); + } + + if (dup2(null_fd, STDOUT_FILENO) != STDOUT_FILENO) { + log_error_errno(errno, "Failed to dup2() fd: %m"); + _exit(EXIT_FAILURE); + } + + if (null_fd != STDOUT_FILENO) + null_fd = safe_close(null_fd); + + fd_cloexec(STDIN_FILENO, false); + fd_cloexec(STDOUT_FILENO, false); + fd_cloexec(STDERR_FILENO, false); + + if (unshare(CLONE_NEWNET) < 0) + log_error_errno(errno, "Failed to lock tar into network namespace, ignoring: %m"); + + r = capability_bounding_set_drop(retain, true); + if (r < 0) + log_error_errno(r, "Failed to drop capabilities, ignoring: %m"); + + execlp("tar", "tar", "--numeric-owner", "-C", path, "-px", "--xattrs", "--xattrs-include=*", NULL); + log_error_errno(errno, "Failed to execute tar: %m"); + _exit(EXIT_FAILURE); + } + + pipefd[0] = safe_close(pipefd[0]); + r = pipefd[1]; + pipefd[1] = -1; + + *ret = pid; + + return r; +} + +int import_fork_tar_c(const char *path, pid_t *ret) { + _cleanup_close_pair_ int pipefd[2] = { -1, -1 }; + pid_t pid; + int r; + + assert(path); + assert(ret); + + if (pipe2(pipefd, O_CLOEXEC) < 0) + return log_error_errno(errno, "Failed to create pipe for tar: %m"); + + pid = fork(); + if (pid < 0) + return log_error_errno(errno, "Failed to fork off tar: %m"); + + if (pid == 0) { + int null_fd; + uint64_t retain = (1ULL << CAP_DAC_OVERRIDE); + + /* Child */ + + (void) reset_all_signal_handlers(); + (void) reset_signal_mask(); + assert_se(prctl(PR_SET_PDEATHSIG, SIGTERM) == 0); + + pipefd[0] = safe_close(pipefd[0]); + + if (dup2(pipefd[1], STDOUT_FILENO) != STDOUT_FILENO) { + log_error_errno(errno, "Failed to dup2() fd: %m"); + _exit(EXIT_FAILURE); + } + + if (pipefd[1] != STDOUT_FILENO) + pipefd[1] = safe_close(pipefd[1]); + + null_fd = open("/dev/null", O_RDONLY|O_NOCTTY); + if (null_fd < 0) { + log_error_errno(errno, "Failed to open /dev/null: %m"); + _exit(EXIT_FAILURE); + } + + if (dup2(null_fd, STDIN_FILENO) != STDIN_FILENO) { + log_error_errno(errno, "Failed to dup2() fd: %m"); + _exit(EXIT_FAILURE); + } + + if (null_fd != STDIN_FILENO) + null_fd = safe_close(null_fd); + + fd_cloexec(STDIN_FILENO, false); + fd_cloexec(STDOUT_FILENO, false); + fd_cloexec(STDERR_FILENO, false); + + if (unshare(CLONE_NEWNET) < 0) + log_error_errno(errno, "Failed to lock tar into network namespace, ignoring: %m"); + + r = capability_bounding_set_drop(retain, true); + if (r < 0) + log_error_errno(r, "Failed to drop capabilities, ignoring: %m"); + + execlp("tar", "tar", "-C", path, "-c", "--xattrs", "--xattrs-include=*", ".", NULL); + log_error_errno(errno, "Failed to execute tar: %m"); + _exit(EXIT_FAILURE); + } + + pipefd[1] = safe_close(pipefd[1]); + r = pipefd[0]; + pipefd[0] = -1; + + *ret = pid; + + return r; +} diff --git a/src/grp-machine/grp-import/systemd-import/import-common.h b/src/grp-machine/grp-import/systemd-import/import-common.h new file mode 100644 index 0000000000..07d3250e71 --- /dev/null +++ b/src/grp-machine/grp-import/systemd-import/import-common.h @@ -0,0 +1,26 @@ +#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 import_make_read_only_fd(int fd); +int import_make_read_only(const char *path); + +int import_fork_tar_c(const char *path, pid_t *ret); +int import_fork_tar_x(const char *path, pid_t *ret); diff --git a/src/grp-machine/grp-import/systemd-import/import-compress.c b/src/grp-machine/grp-import/systemd-import/import-compress.c new file mode 100644 index 0000000000..05643cfbaf --- /dev/null +++ b/src/grp-machine/grp-import/systemd-import/import-compress.c @@ -0,0 +1,469 @@ +/*** + 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 "import-compress.h" +#include "basic/string-table.h" +#include "basic/util.h" + +void import_compress_free(ImportCompress *c) { + assert(c); + + if (c->type == IMPORT_COMPRESS_XZ) + lzma_end(&c->xz); + else if (c->type == IMPORT_COMPRESS_GZIP) { + if (c->encoding) + deflateEnd(&c->gzip); + else + inflateEnd(&c->gzip); + } else if (c->type == IMPORT_COMPRESS_BZIP2) { + if (c->encoding) + BZ2_bzCompressEnd(&c->bzip2); + else + BZ2_bzDecompressEnd(&c->bzip2); + } + + c->type = IMPORT_COMPRESS_UNKNOWN; +} + +int import_uncompress_detect(ImportCompress *c, const void *data, size_t size) { + static const uint8_t xz_signature[] = { + 0xfd, '7', 'z', 'X', 'Z', 0x00 + }; + static const uint8_t gzip_signature[] = { + 0x1f, 0x8b + }; + static const uint8_t bzip2_signature[] = { + 'B', 'Z', 'h' + }; + + int r; + + assert(c); + + if (c->type != IMPORT_COMPRESS_UNKNOWN) + return 1; + + if (size < MAX3(sizeof(xz_signature), + sizeof(gzip_signature), + sizeof(bzip2_signature))) + return 0; + + assert(data); + + if (memcmp(data, xz_signature, sizeof(xz_signature)) == 0) { + lzma_ret xzr; + + xzr = lzma_stream_decoder(&c->xz, UINT64_MAX, LZMA_TELL_UNSUPPORTED_CHECK); + if (xzr != LZMA_OK) + return -EIO; + + c->type = IMPORT_COMPRESS_XZ; + + } else if (memcmp(data, gzip_signature, sizeof(gzip_signature)) == 0) { + r = inflateInit2(&c->gzip, 15+16); + if (r != Z_OK) + return -EIO; + + c->type = IMPORT_COMPRESS_GZIP; + + } else if (memcmp(data, bzip2_signature, sizeof(bzip2_signature)) == 0) { + r = BZ2_bzDecompressInit(&c->bzip2, 0, 0); + if (r != BZ_OK) + return -EIO; + + c->type = IMPORT_COMPRESS_BZIP2; + } else + c->type = IMPORT_COMPRESS_UNCOMPRESSED; + + c->encoding = false; + + return 1; +} + +int import_uncompress(ImportCompress *c, const void *data, size_t size, ImportCompressCallback callback, void *userdata) { + int r; + + assert(c); + assert(callback); + + r = import_uncompress_detect(c, data, size); + if (r <= 0) + return r; + + if (c->encoding) + return -EINVAL; + + if (size <= 0) + return 1; + + assert(data); + + switch (c->type) { + + case IMPORT_COMPRESS_UNCOMPRESSED: + r = callback(data, size, userdata); + if (r < 0) + return r; + + break; + + case IMPORT_COMPRESS_XZ: + c->xz.next_in = data; + c->xz.avail_in = size; + + while (c->xz.avail_in > 0) { + uint8_t buffer[16 * 1024]; + lzma_ret lzr; + + c->xz.next_out = buffer; + c->xz.avail_out = sizeof(buffer); + + lzr = lzma_code(&c->xz, LZMA_RUN); + if (lzr != LZMA_OK && lzr != LZMA_STREAM_END) + return -EIO; + + r = callback(buffer, sizeof(buffer) - c->xz.avail_out, userdata); + if (r < 0) + return r; + } + + break; + + case IMPORT_COMPRESS_GZIP: + c->gzip.next_in = (void*) data; + c->gzip.avail_in = size; + + while (c->gzip.avail_in > 0) { + uint8_t buffer[16 * 1024]; + + c->gzip.next_out = buffer; + c->gzip.avail_out = sizeof(buffer); + + r = inflate(&c->gzip, Z_NO_FLUSH); + if (r != Z_OK && r != Z_STREAM_END) + return -EIO; + + r = callback(buffer, sizeof(buffer) - c->gzip.avail_out, userdata); + if (r < 0) + return r; + } + + break; + + case IMPORT_COMPRESS_BZIP2: + c->bzip2.next_in = (void*) data; + c->bzip2.avail_in = size; + + while (c->bzip2.avail_in > 0) { + uint8_t buffer[16 * 1024]; + + c->bzip2.next_out = (char*) buffer; + c->bzip2.avail_out = sizeof(buffer); + + r = BZ2_bzDecompress(&c->bzip2); + if (r != BZ_OK && r != BZ_STREAM_END) + return -EIO; + + r = callback(buffer, sizeof(buffer) - c->bzip2.avail_out, userdata); + if (r < 0) + return r; + } + + break; + + default: + assert_not_reached("Unknown compression"); + } + + return 1; +} + +int import_compress_init(ImportCompress *c, ImportCompressType t) { + int r; + + assert(c); + + switch (t) { + + case IMPORT_COMPRESS_XZ: { + lzma_ret xzr; + + xzr = lzma_easy_encoder(&c->xz, LZMA_PRESET_DEFAULT, LZMA_CHECK_CRC64); + if (xzr != LZMA_OK) + return -EIO; + + c->type = IMPORT_COMPRESS_XZ; + break; + } + + case IMPORT_COMPRESS_GZIP: + r = deflateInit2(&c->gzip, Z_DEFAULT_COMPRESSION, Z_DEFLATED, 15 + 16, 8, Z_DEFAULT_STRATEGY); + if (r != Z_OK) + return -EIO; + + c->type = IMPORT_COMPRESS_GZIP; + break; + + case IMPORT_COMPRESS_BZIP2: + r = BZ2_bzCompressInit(&c->bzip2, 9, 0, 0); + if (r != BZ_OK) + return -EIO; + + c->type = IMPORT_COMPRESS_BZIP2; + break; + + case IMPORT_COMPRESS_UNCOMPRESSED: + c->type = IMPORT_COMPRESS_UNCOMPRESSED; + break; + + default: + return -EOPNOTSUPP; + } + + c->encoding = true; + return 0; +} + +static int enlarge_buffer(void **buffer, size_t *buffer_size, size_t *buffer_allocated) { + size_t l; + void *p; + + if (*buffer_allocated > *buffer_size) + return 0; + + l = MAX(16*1024U, (*buffer_size * 2)); + p = realloc(*buffer, l); + if (!p) + return -ENOMEM; + + *buffer = p; + *buffer_allocated = l; + + return 1; +} + +int import_compress(ImportCompress *c, const void *data, size_t size, void **buffer, size_t *buffer_size, size_t *buffer_allocated) { + int r; + + assert(c); + assert(buffer); + assert(buffer_size); + assert(buffer_allocated); + + if (!c->encoding) + return -EINVAL; + + if (size <= 0) + return 0; + + assert(data); + + *buffer_size = 0; + + switch (c->type) { + + case IMPORT_COMPRESS_XZ: + + c->xz.next_in = data; + c->xz.avail_in = size; + + while (c->xz.avail_in > 0) { + lzma_ret lzr; + + r = enlarge_buffer(buffer, buffer_size, buffer_allocated); + if (r < 0) + return r; + + c->xz.next_out = (uint8_t*) *buffer + *buffer_size; + c->xz.avail_out = *buffer_allocated - *buffer_size; + + lzr = lzma_code(&c->xz, LZMA_RUN); + if (lzr != LZMA_OK) + return -EIO; + + *buffer_size += (*buffer_allocated - *buffer_size) - c->xz.avail_out; + } + + break; + + case IMPORT_COMPRESS_GZIP: + + c->gzip.next_in = (void*) data; + c->gzip.avail_in = size; + + while (c->gzip.avail_in > 0) { + r = enlarge_buffer(buffer, buffer_size, buffer_allocated); + if (r < 0) + return r; + + c->gzip.next_out = (uint8_t*) *buffer + *buffer_size; + c->gzip.avail_out = *buffer_allocated - *buffer_size; + + r = deflate(&c->gzip, Z_NO_FLUSH); + if (r != Z_OK) + return -EIO; + + *buffer_size += (*buffer_allocated - *buffer_size) - c->gzip.avail_out; + } + + break; + + case IMPORT_COMPRESS_BZIP2: + + c->bzip2.next_in = (void*) data; + c->bzip2.avail_in = size; + + while (c->bzip2.avail_in > 0) { + r = enlarge_buffer(buffer, buffer_size, buffer_allocated); + if (r < 0) + return r; + + c->bzip2.next_out = (void*) ((uint8_t*) *buffer + *buffer_size); + c->bzip2.avail_out = *buffer_allocated - *buffer_size; + + r = BZ2_bzCompress(&c->bzip2, BZ_RUN); + if (r != BZ_RUN_OK) + return -EIO; + + *buffer_size += (*buffer_allocated - *buffer_size) - c->bzip2.avail_out; + } + + break; + + case IMPORT_COMPRESS_UNCOMPRESSED: + + if (*buffer_allocated < size) { + void *p; + + p = realloc(*buffer, size); + if (!p) + return -ENOMEM; + + *buffer = p; + *buffer_allocated = size; + } + + memcpy(*buffer, data, size); + *buffer_size = size; + break; + + default: + return -EOPNOTSUPP; + } + + return 0; +} + +int import_compress_finish(ImportCompress *c, void **buffer, size_t *buffer_size, size_t *buffer_allocated) { + int r; + + assert(c); + assert(buffer); + assert(buffer_size); + assert(buffer_allocated); + + if (!c->encoding) + return -EINVAL; + + *buffer_size = 0; + + switch (c->type) { + + case IMPORT_COMPRESS_XZ: { + lzma_ret lzr; + + c->xz.avail_in = 0; + + do { + r = enlarge_buffer(buffer, buffer_size, buffer_allocated); + if (r < 0) + return r; + + c->xz.next_out = (uint8_t*) *buffer + *buffer_size; + c->xz.avail_out = *buffer_allocated - *buffer_size; + + lzr = lzma_code(&c->xz, LZMA_FINISH); + if (lzr != LZMA_OK && lzr != LZMA_STREAM_END) + return -EIO; + + *buffer_size += (*buffer_allocated - *buffer_size) - c->xz.avail_out; + } while (lzr != LZMA_STREAM_END); + + break; + } + + case IMPORT_COMPRESS_GZIP: + c->gzip.avail_in = 0; + + do { + r = enlarge_buffer(buffer, buffer_size, buffer_allocated); + if (r < 0) + return r; + + c->gzip.next_out = (uint8_t*) *buffer + *buffer_size; + c->gzip.avail_out = *buffer_allocated - *buffer_size; + + r = deflate(&c->gzip, Z_FINISH); + if (r != Z_OK && r != Z_STREAM_END) + return -EIO; + + *buffer_size += (*buffer_allocated - *buffer_size) - c->gzip.avail_out; + } while (r != Z_STREAM_END); + + break; + + case IMPORT_COMPRESS_BZIP2: + c->bzip2.avail_in = 0; + + do { + r = enlarge_buffer(buffer, buffer_size, buffer_allocated); + if (r < 0) + return r; + + c->bzip2.next_out = (void*) ((uint8_t*) *buffer + *buffer_size); + c->bzip2.avail_out = *buffer_allocated - *buffer_size; + + r = BZ2_bzCompress(&c->bzip2, BZ_FINISH); + if (r != BZ_FINISH_OK && r != BZ_STREAM_END) + return -EIO; + + *buffer_size += (*buffer_allocated - *buffer_size) - c->bzip2.avail_out; + } while (r != BZ_STREAM_END); + + break; + + case IMPORT_COMPRESS_UNCOMPRESSED: + break; + + default: + return -EOPNOTSUPP; + } + + return 0; +} + +static const char* const import_compress_type_table[_IMPORT_COMPRESS_TYPE_MAX] = { + [IMPORT_COMPRESS_UNKNOWN] = "unknown", + [IMPORT_COMPRESS_UNCOMPRESSED] = "uncompressed", + [IMPORT_COMPRESS_XZ] = "xz", + [IMPORT_COMPRESS_GZIP] = "gzip", + [IMPORT_COMPRESS_BZIP2] = "bzip2", +}; + +DEFINE_STRING_TABLE_LOOKUP(import_compress_type, ImportCompressType); diff --git a/src/grp-machine/grp-import/systemd-import/import-compress.h b/src/grp-machine/grp-import/systemd-import/import-compress.h new file mode 100644 index 0000000000..130afb4cd0 --- /dev/null +++ b/src/grp-machine/grp-import/systemd-import/import-compress.h @@ -0,0 +1,61 @@ +#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 + +#include "basic/macro.h" + +typedef enum ImportCompressType { + IMPORT_COMPRESS_UNKNOWN, + IMPORT_COMPRESS_UNCOMPRESSED, + IMPORT_COMPRESS_XZ, + IMPORT_COMPRESS_GZIP, + IMPORT_COMPRESS_BZIP2, + _IMPORT_COMPRESS_TYPE_MAX, + _IMPORT_COMPRESS_TYPE_INVALID = -1, +} ImportCompressType; + +typedef struct ImportCompress { + ImportCompressType type; + bool encoding; + union { + lzma_stream xz; + z_stream gzip; + bz_stream bzip2; + }; +} ImportCompress; + +typedef int (*ImportCompressCallback)(const void *data, size_t size, void *userdata); + +void import_compress_free(ImportCompress *c); + +int import_uncompress_detect(ImportCompress *c, const void *data, size_t size); +int import_uncompress(ImportCompress *c, const void *data, size_t size, ImportCompressCallback callback, void *userdata); + +int import_compress_init(ImportCompress *c, ImportCompressType t); +int import_compress(ImportCompress *c, const void *data, size_t size, void **buffer, size_t *buffer_size, size_t *buffer_allocated); +int import_compress_finish(ImportCompress *c, void **buffer, size_t *buffer_size, size_t *buffer_allocated); + +const char* import_compress_type_to_string(ImportCompressType t) _const_; +ImportCompressType import_compress_type_from_string(const char *s) _pure_; diff --git a/src/grp-machine/grp-import/systemd-import/import-pubring.gpg b/src/grp-machine/grp-import/systemd-import/import-pubring.gpg new file mode 100644 index 0000000000..be27776896 Binary files /dev/null and b/src/grp-machine/grp-import/systemd-import/import-pubring.gpg differ diff --git a/src/grp-machine/grp-import/systemd-import/import-raw.c b/src/grp-machine/grp-import/systemd-import/import-raw.c new file mode 100644 index 0000000000..0bbae2aa8a --- /dev/null +++ b/src/grp-machine/grp-import/systemd-import/import-raw.c @@ -0,0 +1,467 @@ +/*** + 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 "basic/alloc-util.h" +#include "basic/btrfs-util.h" +#include "basic/chattr-util.h" +#include "basic/copy.h" +#include "basic/fd-util.h" +#include "basic/fileio.h" +#include "basic/fs-util.h" +#include "basic/hostname-util.h" +#include "import-common.h" +#include "import-compress.h" +#include "import-raw.h" +#include "basic/io-util.h" +#include "shared/machine-pool.h" +#include "basic/mkdir.h" +#include "basic/path-util.h" +#include "qcow2-util.h" +#include "basic/ratelimit.h" +#include "basic/rm-rf.h" +#include "basic/string-util.h" +#include "basic/util.h" + +struct RawImport { + sd_event *event; + + char *image_root; + + RawImportFinished on_finished; + void *userdata; + + char *local; + bool force_local; + bool read_only; + bool grow_machine_directory; + + char *temp_path; + char *final_path; + + int input_fd; + int output_fd; + + ImportCompress compress; + + uint64_t written_since_last_grow; + + sd_event_source *input_event_source; + + uint8_t buffer[16*1024]; + size_t buffer_size; + + uint64_t written_compressed; + uint64_t written_uncompressed; + + struct stat st; + + unsigned last_percent; + RateLimit progress_rate_limit; +}; + +RawImport* raw_import_unref(RawImport *i) { + if (!i) + return NULL; + + sd_event_unref(i->event); + + if (i->temp_path) { + (void) unlink(i->temp_path); + free(i->temp_path); + } + + import_compress_free(&i->compress); + + sd_event_source_unref(i->input_event_source); + + safe_close(i->output_fd); + + free(i->final_path); + free(i->image_root); + free(i->local); + free(i); + + return NULL; +} + +int raw_import_new( + RawImport **ret, + sd_event *event, + const char *image_root, + RawImportFinished on_finished, + void *userdata) { + + _cleanup_(raw_import_unrefp) RawImport *i = NULL; + int r; + + assert(ret); + + i = new0(RawImport, 1); + if (!i) + return -ENOMEM; + + i->input_fd = i->output_fd = -1; + i->on_finished = on_finished; + i->userdata = userdata; + + RATELIMIT_INIT(i->progress_rate_limit, 100 * USEC_PER_MSEC, 1); + i->last_percent = (unsigned) -1; + + i->image_root = strdup(image_root ?: "/var/lib/machines"); + if (!i->image_root) + return -ENOMEM; + + i->grow_machine_directory = path_startswith(i->image_root, "/var/lib/machines"); + + if (event) + i->event = sd_event_ref(event); + else { + r = sd_event_default(&i->event); + if (r < 0) + return r; + } + + *ret = i; + i = NULL; + + return 0; +} + +static void raw_import_report_progress(RawImport *i) { + unsigned percent; + assert(i); + + /* We have no size information, unless the source is a regular file */ + if (!S_ISREG(i->st.st_mode)) + return; + + if (i->written_compressed >= (uint64_t) i->st.st_size) + percent = 100; + else + percent = (unsigned) ((i->written_compressed * UINT64_C(100)) / (uint64_t) i->st.st_size); + + if (percent == i->last_percent) + return; + + if (!ratelimit_test(&i->progress_rate_limit)) + return; + + sd_notifyf(false, "X_IMPORT_PROGRESS=%u", percent); + log_info("Imported %u%%.", percent); + + i->last_percent = percent; +} + +static int raw_import_maybe_convert_qcow2(RawImport *i) { + _cleanup_close_ int converted_fd = -1; + _cleanup_free_ char *t = NULL; + int r; + + assert(i); + + r = qcow2_detect(i->output_fd); + if (r < 0) + return log_error_errno(r, "Failed to detect whether this is a QCOW2 image: %m"); + if (r == 0) + return 0; + + /* This is a QCOW2 image, let's convert it */ + r = tempfn_random(i->final_path, NULL, &t); + if (r < 0) + return log_oom(); + + converted_fd = open(t, O_RDWR|O_CREAT|O_EXCL|O_NOCTTY|O_CLOEXEC, 0664); + if (converted_fd < 0) + return log_error_errno(errno, "Failed to create %s: %m", t); + + r = chattr_fd(converted_fd, FS_NOCOW_FL, FS_NOCOW_FL); + if (r < 0) + log_warning_errno(r, "Failed to set file attributes on %s: %m", t); + + log_info("Unpacking QCOW2 file."); + + r = qcow2_convert(i->output_fd, converted_fd); + if (r < 0) { + unlink(t); + return log_error_errno(r, "Failed to convert qcow2 image: %m"); + } + + (void) unlink(i->temp_path); + free(i->temp_path); + i->temp_path = t; + t = NULL; + + safe_close(i->output_fd); + i->output_fd = converted_fd; + converted_fd = -1; + + return 1; +} + +static int raw_import_finish(RawImport *i) { + int r; + + assert(i); + assert(i->output_fd >= 0); + assert(i->temp_path); + assert(i->final_path); + + /* In case this was a sparse file, make sure the file system is right */ + if (i->written_uncompressed > 0) { + if (ftruncate(i->output_fd, i->written_uncompressed) < 0) + return log_error_errno(errno, "Failed to truncate file: %m"); + } + + r = raw_import_maybe_convert_qcow2(i); + if (r < 0) + return r; + + if (S_ISREG(i->st.st_mode)) { + (void) copy_times(i->input_fd, i->output_fd); + (void) copy_xattr(i->input_fd, i->output_fd); + } + + if (i->read_only) { + r = import_make_read_only_fd(i->output_fd); + if (r < 0) + return r; + } + + if (i->force_local) + (void) rm_rf(i->final_path, REMOVE_ROOT|REMOVE_PHYSICAL|REMOVE_SUBVOLUME); + + r = rename_noreplace(AT_FDCWD, i->temp_path, AT_FDCWD, i->final_path); + if (r < 0) + return log_error_errno(r, "Failed to move image into place: %m"); + + i->temp_path = mfree(i->temp_path); + + return 0; +} + +static int raw_import_open_disk(RawImport *i) { + int r; + + assert(i); + + assert(!i->final_path); + assert(!i->temp_path); + assert(i->output_fd < 0); + + i->final_path = strjoin(i->image_root, "/", i->local, ".raw", NULL); + if (!i->final_path) + return log_oom(); + + r = tempfn_random(i->final_path, NULL, &i->temp_path); + if (r < 0) + return log_oom(); + + (void) mkdir_parents_label(i->temp_path, 0700); + + i->output_fd = open(i->temp_path, O_RDWR|O_CREAT|O_EXCL|O_NOCTTY|O_CLOEXEC, 0664); + if (i->output_fd < 0) + return log_error_errno(errno, "Failed to open destination %s: %m", i->temp_path); + + r = chattr_fd(i->output_fd, FS_NOCOW_FL, FS_NOCOW_FL); + if (r < 0) + log_warning_errno(r, "Failed to set file attributes on %s: %m", i->temp_path); + + return 0; +} + +static int raw_import_try_reflink(RawImport *i) { + off_t p; + int r; + + assert(i); + assert(i->input_fd >= 0); + assert(i->output_fd >= 0); + + if (i->compress.type != IMPORT_COMPRESS_UNCOMPRESSED) + return 0; + + if (!S_ISREG(i->st.st_mode)) + return 0; + + p = lseek(i->input_fd, 0, SEEK_CUR); + if (p == (off_t) -1) + return log_error_errno(errno, "Failed to read file offset of input file: %m"); + + /* Let's only try a btrfs reflink, if we are reading from the beginning of the file */ + if ((uint64_t) p != (uint64_t) i->buffer_size) + return 0; + + r = btrfs_reflink(i->input_fd, i->output_fd); + if (r >= 0) + return 1; + + return 0; +} + +static int raw_import_write(const void *p, size_t sz, void *userdata) { + RawImport *i = userdata; + ssize_t n; + + if (i->grow_machine_directory && i->written_since_last_grow >= GROW_INTERVAL_BYTES) { + i->written_since_last_grow = 0; + grow_machine_directory(); + } + + n = sparse_write(i->output_fd, p, sz, 64); + if (n < 0) + return -errno; + if ((size_t) n < sz) + return -EIO; + + i->written_uncompressed += sz; + i->written_since_last_grow += sz; + + return 0; +} + +static int raw_import_process(RawImport *i) { + ssize_t l; + int r; + + assert(i); + assert(i->buffer_size < sizeof(i->buffer)); + + l = read(i->input_fd, i->buffer + i->buffer_size, sizeof(i->buffer) - i->buffer_size); + if (l < 0) { + if (errno == EAGAIN) + return 0; + + r = log_error_errno(errno, "Failed to read input file: %m"); + goto finish; + } + if (l == 0) { + if (i->compress.type == IMPORT_COMPRESS_UNKNOWN) { + log_error("Premature end of file: %m"); + r = -EIO; + goto finish; + } + + r = raw_import_finish(i); + goto finish; + } + + i->buffer_size += l; + + if (i->compress.type == IMPORT_COMPRESS_UNKNOWN) { + r = import_uncompress_detect(&i->compress, i->buffer, i->buffer_size); + if (r < 0) { + log_error("Failed to detect file compression: %m"); + goto finish; + } + if (r == 0) /* Need more data */ + return 0; + + r = raw_import_open_disk(i); + if (r < 0) + goto finish; + + r = raw_import_try_reflink(i); + if (r < 0) + goto finish; + if (r > 0) { + r = raw_import_finish(i); + goto finish; + } + } + + r = import_uncompress(&i->compress, i->buffer, i->buffer_size, raw_import_write, i); + if (r < 0) { + log_error_errno(r, "Failed to decode and write: %m"); + goto finish; + } + + i->written_compressed += i->buffer_size; + i->buffer_size = 0; + + raw_import_report_progress(i); + + return 0; + +finish: + if (i->on_finished) + i->on_finished(i, r, i->userdata); + else + sd_event_exit(i->event, r); + + return 0; +} + +static int raw_import_on_input(sd_event_source *s, int fd, uint32_t revents, void *userdata) { + RawImport *i = userdata; + + return raw_import_process(i); +} + +static int raw_import_on_defer(sd_event_source *s, void *userdata) { + RawImport *i = userdata; + + return raw_import_process(i); +} + +int raw_import_start(RawImport *i, int fd, const char *local, bool force_local, bool read_only) { + int r; + + assert(i); + assert(fd >= 0); + assert(local); + + if (!machine_name_is_valid(local)) + return -EINVAL; + + if (i->input_fd >= 0) + return -EBUSY; + + r = fd_nonblock(fd, true); + if (r < 0) + return r; + + r = free_and_strdup(&i->local, local); + if (r < 0) + return r; + i->force_local = force_local; + i->read_only = read_only; + + if (fstat(fd, &i->st) < 0) + return -errno; + + r = sd_event_add_io(i->event, &i->input_event_source, fd, EPOLLIN, raw_import_on_input, i); + if (r == -EPERM) { + /* This fd does not support epoll, for example because it is a regular file. Busy read in that case */ + r = sd_event_add_defer(i->event, &i->input_event_source, raw_import_on_defer, i); + if (r < 0) + return r; + + r = sd_event_source_set_enabled(i->input_event_source, SD_EVENT_ON); + } + if (r < 0) + return r; + + i->input_fd = fd; + return r; +} diff --git a/src/grp-machine/grp-import/systemd-import/import-raw.h b/src/grp-machine/grp-import/systemd-import/import-raw.h new file mode 100644 index 0000000000..a3bc532d37 --- /dev/null +++ b/src/grp-machine/grp-import/systemd-import/import-raw.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 "shared/import-util.h" +#include "basic/macro.h" + +typedef struct RawImport RawImport; + +typedef void (*RawImportFinished)(RawImport *import, int error, void *userdata); + +int raw_import_new(RawImport **import, sd_event *event, const char *image_root, RawImportFinished on_finished, void *userdata); +RawImport* raw_import_unref(RawImport *import); + +DEFINE_TRIVIAL_CLEANUP_FUNC(RawImport*, raw_import_unref); + +int raw_import_start(RawImport *i, int fd, const char *local, bool force_local, bool read_only); diff --git a/src/grp-machine/grp-import/systemd-import/import-tar.c b/src/grp-machine/grp-import/systemd-import/import-tar.c new file mode 100644 index 0000000000..8848beb11b --- /dev/null +++ b/src/grp-machine/grp-import/systemd-import/import-tar.c @@ -0,0 +1,388 @@ +/*** + 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 "basic/alloc-util.h" +#include "basic/btrfs-util.h" +#include "basic/copy.h" +#include "basic/fd-util.h" +#include "basic/fileio.h" +#include "basic/fs-util.h" +#include "basic/hostname-util.h" +#include "import-common.h" +#include "import-compress.h" +#include "import-tar.h" +#include "basic/io-util.h" +#include "shared/machine-pool.h" +#include "basic/mkdir.h" +#include "basic/path-util.h" +#include "basic/process-util.h" +#include "qcow2-util.h" +#include "basic/ratelimit.h" +#include "basic/rm-rf.h" +#include "basic/string-util.h" +#include "basic/util.h" + +struct TarImport { + sd_event *event; + + char *image_root; + + TarImportFinished on_finished; + void *userdata; + + char *local; + bool force_local; + bool read_only; + bool grow_machine_directory; + + char *temp_path; + char *final_path; + + int input_fd; + int tar_fd; + + ImportCompress compress; + + uint64_t written_since_last_grow; + + sd_event_source *input_event_source; + + uint8_t buffer[16*1024]; + size_t buffer_size; + + uint64_t written_compressed; + uint64_t written_uncompressed; + + struct stat st; + + pid_t tar_pid; + + unsigned last_percent; + RateLimit progress_rate_limit; +}; + +TarImport* tar_import_unref(TarImport *i) { + if (!i) + return NULL; + + sd_event_source_unref(i->input_event_source); + + if (i->tar_pid > 1) { + (void) kill_and_sigcont(i->tar_pid, SIGKILL); + (void) wait_for_terminate(i->tar_pid, NULL); + } + + if (i->temp_path) { + (void) rm_rf(i->temp_path, REMOVE_ROOT|REMOVE_PHYSICAL|REMOVE_SUBVOLUME); + free(i->temp_path); + } + + import_compress_free(&i->compress); + + sd_event_unref(i->event); + + safe_close(i->tar_fd); + + free(i->final_path); + free(i->image_root); + free(i->local); + free(i); + + return NULL; +} + +int tar_import_new( + TarImport **ret, + sd_event *event, + const char *image_root, + TarImportFinished on_finished, + void *userdata) { + + _cleanup_(tar_import_unrefp) TarImport *i = NULL; + int r; + + assert(ret); + + i = new0(TarImport, 1); + if (!i) + return -ENOMEM; + + i->input_fd = i->tar_fd = -1; + i->on_finished = on_finished; + i->userdata = userdata; + + RATELIMIT_INIT(i->progress_rate_limit, 100 * USEC_PER_MSEC, 1); + i->last_percent = (unsigned) -1; + + i->image_root = strdup(image_root ?: "/var/lib/machines"); + if (!i->image_root) + return -ENOMEM; + + i->grow_machine_directory = path_startswith(i->image_root, "/var/lib/machines"); + + if (event) + i->event = sd_event_ref(event); + else { + r = sd_event_default(&i->event); + if (r < 0) + return r; + } + + *ret = i; + i = NULL; + + return 0; +} + +static void tar_import_report_progress(TarImport *i) { + unsigned percent; + assert(i); + + /* We have no size information, unless the source is a regular file */ + if (!S_ISREG(i->st.st_mode)) + return; + + if (i->written_compressed >= (uint64_t) i->st.st_size) + percent = 100; + else + percent = (unsigned) ((i->written_compressed * UINT64_C(100)) / (uint64_t) i->st.st_size); + + if (percent == i->last_percent) + return; + + if (!ratelimit_test(&i->progress_rate_limit)) + return; + + sd_notifyf(false, "X_IMPORT_PROGRESS=%u", percent); + log_info("Imported %u%%.", percent); + + i->last_percent = percent; +} + +static int tar_import_finish(TarImport *i) { + int r; + + assert(i); + assert(i->tar_fd >= 0); + assert(i->temp_path); + assert(i->final_path); + + i->tar_fd = safe_close(i->tar_fd); + + if (i->tar_pid > 0) { + r = wait_for_terminate_and_warn("tar", i->tar_pid, true); + i->tar_pid = 0; + if (r < 0) + return r; + } + + if (i->read_only) { + r = import_make_read_only(i->temp_path); + if (r < 0) + return r; + } + + if (i->force_local) + (void) rm_rf(i->final_path, REMOVE_ROOT|REMOVE_PHYSICAL|REMOVE_SUBVOLUME); + + r = rename_noreplace(AT_FDCWD, i->temp_path, AT_FDCWD, i->final_path); + if (r < 0) + return log_error_errno(r, "Failed to move image into place: %m"); + + i->temp_path = mfree(i->temp_path); + + return 0; +} + +static int tar_import_fork_tar(TarImport *i) { + int r; + + assert(i); + + assert(!i->final_path); + assert(!i->temp_path); + assert(i->tar_fd < 0); + + i->final_path = strjoin(i->image_root, "/", i->local, NULL); + if (!i->final_path) + return log_oom(); + + r = tempfn_random(i->final_path, NULL, &i->temp_path); + if (r < 0) + return log_oom(); + + (void) mkdir_parents_label(i->temp_path, 0700); + + r = btrfs_subvol_make(i->temp_path); + if (r == -ENOTTY) { + if (mkdir(i->temp_path, 0755) < 0) + return log_error_errno(errno, "Failed to create directory %s: %m", i->temp_path); + } else if (r < 0) + return log_error_errno(r, "Failed to create subvolume %s: %m", i->temp_path); + else + (void) import_assign_pool_quota_and_warn(i->temp_path); + + i->tar_fd = import_fork_tar_x(i->temp_path, &i->tar_pid); + if (i->tar_fd < 0) + return i->tar_fd; + + return 0; +} + +static int tar_import_write(const void *p, size_t sz, void *userdata) { + TarImport *i = userdata; + int r; + + if (i->grow_machine_directory && i->written_since_last_grow >= GROW_INTERVAL_BYTES) { + i->written_since_last_grow = 0; + grow_machine_directory(); + } + + r = loop_write(i->tar_fd, p, sz, false); + if (r < 0) + return r; + + i->written_uncompressed += sz; + i->written_since_last_grow += sz; + + return 0; +} + +static int tar_import_process(TarImport *i) { + ssize_t l; + int r; + + assert(i); + assert(i->buffer_size < sizeof(i->buffer)); + + l = read(i->input_fd, i->buffer + i->buffer_size, sizeof(i->buffer) - i->buffer_size); + if (l < 0) { + if (errno == EAGAIN) + return 0; + + r = log_error_errno(errno, "Failed to read input file: %m"); + goto finish; + } + if (l == 0) { + if (i->compress.type == IMPORT_COMPRESS_UNKNOWN) { + log_error("Premature end of file: %m"); + r = -EIO; + goto finish; + } + + r = tar_import_finish(i); + goto finish; + } + + i->buffer_size += l; + + if (i->compress.type == IMPORT_COMPRESS_UNKNOWN) { + r = import_uncompress_detect(&i->compress, i->buffer, i->buffer_size); + if (r < 0) { + log_error("Failed to detect file compression: %m"); + goto finish; + } + if (r == 0) /* Need more data */ + return 0; + + r = tar_import_fork_tar(i); + if (r < 0) + goto finish; + } + + r = import_uncompress(&i->compress, i->buffer, i->buffer_size, tar_import_write, i); + if (r < 0) { + log_error_errno(r, "Failed to decode and write: %m"); + goto finish; + } + + i->written_compressed += i->buffer_size; + i->buffer_size = 0; + + tar_import_report_progress(i); + + return 0; + +finish: + if (i->on_finished) + i->on_finished(i, r, i->userdata); + else + sd_event_exit(i->event, r); + + return 0; +} + +static int tar_import_on_input(sd_event_source *s, int fd, uint32_t revents, void *userdata) { + TarImport *i = userdata; + + return tar_import_process(i); +} + +static int tar_import_on_defer(sd_event_source *s, void *userdata) { + TarImport *i = userdata; + + return tar_import_process(i); +} + +int tar_import_start(TarImport *i, int fd, const char *local, bool force_local, bool read_only) { + int r; + + assert(i); + assert(fd >= 0); + assert(local); + + if (!machine_name_is_valid(local)) + return -EINVAL; + + if (i->input_fd >= 0) + return -EBUSY; + + r = fd_nonblock(fd, true); + if (r < 0) + return r; + + r = free_and_strdup(&i->local, local); + if (r < 0) + return r; + i->force_local = force_local; + i->read_only = read_only; + + if (fstat(fd, &i->st) < 0) + return -errno; + + r = sd_event_add_io(i->event, &i->input_event_source, fd, EPOLLIN, tar_import_on_input, i); + if (r == -EPERM) { + /* This fd does not support epoll, for example because it is a regular file. Busy read in that case */ + r = sd_event_add_defer(i->event, &i->input_event_source, tar_import_on_defer, i); + if (r < 0) + return r; + + r = sd_event_source_set_enabled(i->input_event_source, SD_EVENT_ON); + } + if (r < 0) + return r; + + i->input_fd = fd; + return r; +} diff --git a/src/grp-machine/grp-import/systemd-import/import-tar.h b/src/grp-machine/grp-import/systemd-import/import-tar.h new file mode 100644 index 0000000000..90ab082f1a --- /dev/null +++ b/src/grp-machine/grp-import/systemd-import/import-tar.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 "shared/import-util.h" +#include "basic/macro.h" + +typedef struct TarImport TarImport; + +typedef void (*TarImportFinished)(TarImport *import, int error, void *userdata); + +int tar_import_new(TarImport **import, sd_event *event, const char *image_root, TarImportFinished on_finished, void *userdata); +TarImport* tar_import_unref(TarImport *import); + +DEFINE_TRIVIAL_CLEANUP_FUNC(TarImport*, tar_import_unref); + +int tar_import_start(TarImport *import, int fd, const char *local, bool force_local, bool read_only); diff --git a/src/grp-machine/grp-import/systemd-import/import.c b/src/grp-machine/grp-import/systemd-import/import.c new file mode 100644 index 0000000000..6c76c639c3 --- /dev/null +++ b/src/grp-machine/grp-import/systemd-import/import.c @@ -0,0 +1,337 @@ +/*** + 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 "basic/alloc-util.h" +#include "basic/fd-util.h" +#include "basic/fs-util.h" +#include "basic/hostname-util.h" +#include "import-raw.h" +#include "import-tar.h" +#include "shared/import-util.h" +#include "shared/machine-image.h" +#include "basic/signal-util.h" +#include "basic/string-util.h" +#include "basic/verbs.h" + +static bool arg_force = false; +static bool arg_read_only = false; +static const char *arg_image_root = "/var/lib/machines"; + +static int interrupt_signal_handler(sd_event_source *s, const struct signalfd_siginfo *si, void *userdata) { + log_notice("Transfer aborted."); + sd_event_exit(sd_event_source_get_event(s), EINTR); + return 0; +} + +static void on_tar_finished(TarImport *import, int error, void *userdata) { + sd_event *event = userdata; + assert(import); + + if (error == 0) + log_info("Operation completed successfully."); + + sd_event_exit(event, abs(error)); +} + +static int import_tar(int argc, char *argv[], void *userdata) { + _cleanup_(tar_import_unrefp) TarImport *import = NULL; + _cleanup_(sd_event_unrefp) sd_event *event = NULL; + const char *path = NULL, *local = NULL; + _cleanup_free_ char *ll = NULL; + _cleanup_close_ int open_fd = -1; + int r, fd; + + 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) { + r = tar_strip_suffixes(local, &ll); + if (r < 0) + return log_oom(); + + local = ll; + + if (!machine_name_is_valid(local)) { + log_error("Local image name '%s' is not valid.", local); + return -EINVAL; + } + + if (!arg_force) { + r = image_find(local, NULL); + if (r < 0) + return log_error_errno(r, "Failed to check whether image '%s' exists: %m", local); + else if (r > 0) { + log_error_errno(EEXIST, "Image '%s' already exists.", local); + return -EEXIST; + } + } + } else + local = "imported"; + + if (path) { + open_fd = open(path, O_RDONLY|O_CLOEXEC|O_NOCTTY); + if (open_fd < 0) + return log_error_errno(errno, "Failed to open tar image to import: %m"); + + fd = open_fd; + + log_info("Importing '%s', saving as '%s'.", path, local); + } else { + _cleanup_free_ char *pretty = NULL; + + fd = STDIN_FILENO; + + (void) readlink_malloc("/proc/self/fd/0", &pretty); + log_info("Importing '%s', saving as '%s'.", strna(pretty), local); + } + + r = sd_event_default(&event); + if (r < 0) + return log_error_errno(r, "Failed to allocate event loop: %m"); + + assert_se(sigprocmask_many(SIG_BLOCK, NULL, SIGTERM, SIGINT, -1) >= 0); + (void) sd_event_add_signal(event, NULL, SIGTERM, interrupt_signal_handler, NULL); + (void) sd_event_add_signal(event, NULL, SIGINT, interrupt_signal_handler, NULL); + + r = tar_import_new(&import, event, arg_image_root, on_tar_finished, event); + if (r < 0) + return log_error_errno(r, "Failed to allocate importer: %m"); + + r = tar_import_start(import, fd, local, arg_force, arg_read_only); + if (r < 0) + return log_error_errno(r, "Failed to import image: %m"); + + r = sd_event_loop(event); + if (r < 0) + return log_error_errno(r, "Failed to run event loop: %m"); + + log_info("Exiting."); + return -r; +} + +static void on_raw_finished(RawImport *import, int error, void *userdata) { + sd_event *event = userdata; + assert(import); + + if (error == 0) + log_info("Operation completed successfully."); + + sd_event_exit(event, abs(error)); +} + +static int import_raw(int argc, char *argv[], void *userdata) { + _cleanup_(raw_import_unrefp) RawImport *import = NULL; + _cleanup_(sd_event_unrefp) sd_event *event = NULL; + const char *path = NULL, *local = NULL; + _cleanup_free_ char *ll = NULL; + _cleanup_close_ int open_fd = -1; + int r, fd; + + 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) { + r = raw_strip_suffixes(local, &ll); + if (r < 0) + return log_oom(); + + local = ll; + + if (!machine_name_is_valid(local)) { + log_error("Local image name '%s' is not valid.", local); + return -EINVAL; + } + + if (!arg_force) { + r = image_find(local, NULL); + if (r < 0) + return log_error_errno(r, "Failed to check whether image '%s' exists: %m", local); + else if (r > 0) { + log_error_errno(EEXIST, "Image '%s' already exists.", local); + return -EEXIST; + } + } + } else + local = "imported"; + + if (path) { + open_fd = open(path, O_RDONLY|O_CLOEXEC|O_NOCTTY); + if (open_fd < 0) + return log_error_errno(errno, "Failed to open raw image to import: %m"); + + fd = open_fd; + + log_info("Importing '%s', saving as '%s'.", path, local); + } else { + _cleanup_free_ char *pretty = NULL; + + fd = STDIN_FILENO; + + (void) readlink_malloc("/proc/self/fd/0", &pretty); + log_info("Importing '%s', saving as '%s'.", pretty, local); + } + + r = sd_event_default(&event); + if (r < 0) + return log_error_errno(r, "Failed to allocate event loop: %m"); + + assert_se(sigprocmask_many(SIG_BLOCK, NULL, SIGTERM, SIGINT, -1) >= 0); + (void) sd_event_add_signal(event, NULL, SIGTERM, interrupt_signal_handler, NULL); + (void) sd_event_add_signal(event, NULL, SIGINT, interrupt_signal_handler, NULL); + + r = raw_import_new(&import, event, arg_image_root, on_raw_finished, event); + if (r < 0) + return log_error_errno(r, "Failed to allocate importer: %m"); + + r = raw_import_start(import, fd, local, arg_force, arg_read_only); + if (r < 0) + return log_error_errno(r, "Failed to import image: %m"); + + r = sd_event_loop(event); + if (r < 0) + return log_error_errno(r, "Failed to run event loop: %m"); + + log_info("Exiting."); + return -r; +} + +static int help(int argc, char *argv[], void *userdata) { + + printf("%s [OPTIONS...] {COMMAND} ...\n\n" + "Import container or virtual machine images.\n\n" + " -h --help Show this help\n" + " --version Show package version\n" + " --force Force creation of image\n" + " --image-root=PATH Image root directory\n" + " --read-only Create a read-only image\n\n" + "Commands:\n" + " tar FILE [NAME] Import a TAR image\n" + " raw FILE [NAME] Import a RAW image\n", + program_invocation_short_name); + + return 0; +} + +static int parse_argv(int argc, char *argv[]) { + + enum { + ARG_VERSION = 0x100, + ARG_FORCE, + ARG_IMAGE_ROOT, + ARG_READ_ONLY, + }; + + static const struct option options[] = { + { "help", no_argument, NULL, 'h' }, + { "version", no_argument, NULL, ARG_VERSION }, + { "force", no_argument, NULL, ARG_FORCE }, + { "image-root", required_argument, NULL, ARG_IMAGE_ROOT }, + { "read-only", no_argument, NULL, ARG_READ_ONLY }, + {} + }; + + int c; + + assert(argc >= 0); + assert(argv); + + while ((c = getopt_long(argc, argv, "h", options, NULL)) >= 0) + + switch (c) { + + case 'h': + return help(0, NULL, NULL); + + case ARG_VERSION: + return version(); + + case ARG_FORCE: + arg_force = true; + break; + + case ARG_IMAGE_ROOT: + arg_image_root = optarg; + break; + + case ARG_READ_ONLY: + arg_read_only = true; + break; + + case '?': + return -EINVAL; + + default: + assert_not_reached("Unhandled option"); + } + + return 1; +} + +static int import_main(int argc, char *argv[]) { + + static const Verb verbs[] = { + { "help", VERB_ANY, VERB_ANY, 0, help }, + { "tar", 2, 3, 0, import_tar }, + { "raw", 2, 3, 0, import_raw }, + {} + }; + + return dispatch_verb(argc, argv, verbs, NULL); +} + +int main(int argc, char *argv[]) { + int r; + + setlocale(LC_ALL, ""); + log_parse_environment(); + log_open(); + + r = parse_argv(argc, argv); + if (r <= 0) + goto finish; + + (void) ignore_signals(SIGPIPE, -1); + + r = import_main(argc, argv); + +finish: + return r < 0 ? EXIT_FAILURE : EXIT_SUCCESS; +} diff --git a/src/grp-machine/grp-import/systemd-importd/Makefile b/src/grp-machine/grp-import/systemd-importd/Makefile new file mode 100644 index 0000000000..7911bfce82 --- /dev/null +++ b/src/grp-machine/grp-import/systemd-importd/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 + +rootlibexec_PROGRAMS += systemd-importd +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 + +include $(topsrcdir)/build-aux/Makefile.tail.mk diff --git a/src/grp-machine/grp-import/systemd-importd/importd.c b/src/grp-machine/grp-import/systemd-importd/importd.c new file mode 100644 index 0000000000..f9fe1038cd --- /dev/null +++ b/src/grp-machine/grp-import/systemd-importd/importd.c @@ -0,0 +1,1221 @@ +/*** + 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 "basic/alloc-util.h" +#include "bus-common-errors.h" +#include "shared/bus-util.h" +#include "basic/def.h" +#include "basic/fd-util.h" +#include "basic/hostname-util.h" +#include "shared/import-util.h" +#include "shared/machine-pool.h" +#include "basic/missing.h" +#include "basic/mkdir.h" +#include "basic/parse-util.h" +#include "basic/path-util.h" +#include "basic/process-util.h" +#include "basic/signal-util.h" +#include "basic/socket-util.h" +#include "basic/string-table.h" +#include "basic/strv.h" +#include "basic/syslog-util.h" +#include "basic/user-util.h" +#include "basic/util.h" +#include "basic/web-util.h" + +typedef struct Transfer Transfer; +typedef struct Manager Manager; + +typedef enum TransferType { + TRANSFER_IMPORT_TAR, + TRANSFER_IMPORT_RAW, + TRANSFER_EXPORT_TAR, + TRANSFER_EXPORT_RAW, + TRANSFER_PULL_TAR, + TRANSFER_PULL_RAW, + _TRANSFER_TYPE_MAX, + _TRANSFER_TYPE_INVALID = -1, +} TransferType; + +struct Transfer { + Manager *manager; + + uint32_t id; + char *object_path; + + TransferType type; + ImportVerify verify; + + char *remote; + char *local; + bool force_local; + bool read_only; + + char *format; + + pid_t pid; + + int log_fd; + + char log_message[LINE_MAX]; + size_t log_message_size; + + sd_event_source *pid_event_source; + sd_event_source *log_event_source; + + unsigned n_canceled; + unsigned progress_percent; + + int stdin_fd; + int stdout_fd; +}; + +struct Manager { + sd_event *event; + sd_bus *bus; + + uint32_t current_transfer_id; + Hashmap *transfers; + + Hashmap *polkit_registry; + + int notify_fd; + + sd_event_source *notify_event_source; +}; + +#define TRANSFERS_MAX 64 + +static const char* const transfer_type_table[_TRANSFER_TYPE_MAX] = { + [TRANSFER_IMPORT_TAR] = "import-tar", + [TRANSFER_IMPORT_RAW] = "import-raw", + [TRANSFER_EXPORT_TAR] = "export-tar", + [TRANSFER_EXPORT_RAW] = "export-raw", + [TRANSFER_PULL_TAR] = "pull-tar", + [TRANSFER_PULL_RAW] = "pull-raw", +}; + +DEFINE_PRIVATE_STRING_TABLE_LOOKUP_TO_STRING(transfer_type, TransferType); + +static Transfer *transfer_unref(Transfer *t) { + if (!t) + return NULL; + + if (t->manager) + hashmap_remove(t->manager->transfers, UINT32_TO_PTR(t->id)); + + sd_event_source_unref(t->pid_event_source); + sd_event_source_unref(t->log_event_source); + + free(t->remote); + free(t->local); + free(t->format); + free(t->object_path); + + if (t->pid > 0) { + (void) kill_and_sigcont(t->pid, SIGKILL); + (void) wait_for_terminate(t->pid, NULL); + } + + safe_close(t->log_fd); + safe_close(t->stdin_fd); + safe_close(t->stdout_fd); + + free(t); + return NULL; +} + +DEFINE_TRIVIAL_CLEANUP_FUNC(Transfer*, transfer_unref); + +static int transfer_new(Manager *m, Transfer **ret) { + _cleanup_(transfer_unrefp) Transfer *t = NULL; + uint32_t id; + int r; + + assert(m); + assert(ret); + + if (hashmap_size(m->transfers) >= TRANSFERS_MAX) + return -E2BIG; + + r = hashmap_ensure_allocated(&m->transfers, &trivial_hash_ops); + if (r < 0) + return r; + + t = new0(Transfer, 1); + if (!t) + return -ENOMEM; + + t->type = _TRANSFER_TYPE_INVALID; + t->log_fd = -1; + t->stdin_fd = -1; + t->stdout_fd = -1; + t->verify = _IMPORT_VERIFY_INVALID; + + id = m->current_transfer_id + 1; + + if (asprintf(&t->object_path, "/org/freedesktop/import1/transfer/_%" PRIu32, id) < 0) + return -ENOMEM; + + r = hashmap_put(m->transfers, UINT32_TO_PTR(id), t); + if (r < 0) + return r; + + m->current_transfer_id = id; + + t->manager = m; + t->id = id; + + *ret = t; + t = NULL; + + return 0; +} + +static void transfer_send_log_line(Transfer *t, const char *line) { + int r, priority = LOG_INFO; + + assert(t); + assert(line); + + syslog_parse_priority(&line, &priority, true); + + log_full(priority, "(transfer%" PRIu32 ") %s", t->id, line); + + r = sd_bus_emit_signal( + t->manager->bus, + t->object_path, + "org.freedesktop.import1.Transfer", + "LogMessage", + "us", + priority, + line); + if (r < 0) + log_error_errno(r, "Cannot emit message: %m"); + } + +static void transfer_send_logs(Transfer *t, bool flush) { + assert(t); + + /* Try to send out all log messages, if we can. But if we + * can't we remove the messages from the buffer, but don't + * fail */ + + while (t->log_message_size > 0) { + _cleanup_free_ char *n = NULL; + char *e; + + if (t->log_message_size >= sizeof(t->log_message)) + e = t->log_message + sizeof(t->log_message); + else { + char *a, *b; + + a = memchr(t->log_message, 0, t->log_message_size); + b = memchr(t->log_message, '\n', t->log_message_size); + + if (a && b) + e = a < b ? a : b; + else if (a) + e = a; + else + e = b; + } + + if (!e) { + if (!flush) + return; + + e = t->log_message + t->log_message_size; + } + + n = strndup(t->log_message, e - t->log_message); + + /* Skip over NUL and newlines */ + while ((e < t->log_message + t->log_message_size) && (*e == 0 || *e == '\n')) + e++; + + memmove(t->log_message, e, t->log_message + sizeof(t->log_message) - e); + t->log_message_size -= e - t->log_message; + + if (!n) { + log_oom(); + continue; + } + + if (isempty(n)) + continue; + + transfer_send_log_line(t, n); + } +} + +static int transfer_finalize(Transfer *t, bool success) { + int r; + + assert(t); + + transfer_send_logs(t, true); + + r = sd_bus_emit_signal( + t->manager->bus, + "/org/freedesktop/import1", + "org.freedesktop.import1.Manager", + "TransferRemoved", + "uos", + t->id, + t->object_path, + success ? "done" : + t->n_canceled > 0 ? "canceled" : "failed"); + + if (r < 0) + log_error_errno(r, "Cannot emit message: %m"); + + transfer_unref(t); + return 0; +} + +static int transfer_cancel(Transfer *t) { + int r; + + assert(t); + + r = kill_and_sigcont(t->pid, t->n_canceled < 3 ? SIGTERM : SIGKILL); + if (r < 0) + return r; + + t->n_canceled++; + return 0; +} + +static int transfer_on_pid(sd_event_source *s, const siginfo_t *si, void *userdata) { + Transfer *t = userdata; + bool success = false; + + assert(s); + assert(t); + + if (si->si_code == CLD_EXITED) { + if (si->si_status != 0) + log_error("Import process failed with exit code %i.", si->si_status); + else { + log_debug("Import process succeeded."); + success = true; + } + + } else if (si->si_code == CLD_KILLED || + si->si_code == CLD_DUMPED) + + log_error("Import process terminated by signal %s.", signal_to_string(si->si_status)); + else + log_error("Import process failed due to unknown reason."); + + t->pid = 0; + + return transfer_finalize(t, success); +} + +static int transfer_on_log(sd_event_source *s, int fd, uint32_t revents, void *userdata) { + Transfer *t = userdata; + ssize_t l; + + assert(s); + assert(t); + + l = read(fd, t->log_message + t->log_message_size, sizeof(t->log_message) - t->log_message_size); + if (l <= 0) { + /* EOF/read error. We just close the pipe here, and + * close the watch, waiting for the SIGCHLD to arrive, + * before we do anything else. */ + + if (l < 0) + log_error_errno(errno, "Failed to read log message: %m"); + + t->log_event_source = sd_event_source_unref(t->log_event_source); + return 0; + } + + t->log_message_size += l; + + transfer_send_logs(t, false); + + return 0; +} + +static int transfer_start(Transfer *t) { + _cleanup_close_pair_ int pipefd[2] = { -1, -1 }; + int r; + + assert(t); + assert(t->pid <= 0); + + if (pipe2(pipefd, O_CLOEXEC) < 0) + return -errno; + + t->pid = fork(); + if (t->pid < 0) + return -errno; + if (t->pid == 0) { + const char *cmd[] = { + NULL, /* systemd-import, systemd-export or systemd-pull */ + NULL, /* tar, raw */ + NULL, /* --verify= */ + NULL, /* verify argument */ + NULL, /* maybe --force */ + NULL, /* maybe --read-only */ + NULL, /* if so: the actual URL */ + NULL, /* maybe --format= */ + NULL, /* if so: the actual format */ + NULL, /* remote */ + NULL, /* local */ + NULL + }; + unsigned k = 0; + + /* Child */ + + (void) reset_all_signal_handlers(); + (void) reset_signal_mask(); + assert_se(prctl(PR_SET_PDEATHSIG, SIGTERM) == 0); + + pipefd[0] = safe_close(pipefd[0]); + + if (dup2(pipefd[1], STDERR_FILENO) != STDERR_FILENO) { + log_error_errno(errno, "Failed to dup2() fd: %m"); + _exit(EXIT_FAILURE); + } + + if (t->stdout_fd >= 0) { + if (dup2(t->stdout_fd, STDOUT_FILENO) != STDOUT_FILENO) { + log_error_errno(errno, "Failed to dup2() fd: %m"); + _exit(EXIT_FAILURE); + } + + if (t->stdout_fd != STDOUT_FILENO) + safe_close(t->stdout_fd); + } else { + if (dup2(pipefd[1], STDOUT_FILENO) != STDOUT_FILENO) { + log_error_errno(errno, "Failed to dup2() fd: %m"); + _exit(EXIT_FAILURE); + } + } + + if (pipefd[1] != STDOUT_FILENO && pipefd[1] != STDERR_FILENO) + pipefd[1] = safe_close(pipefd[1]); + + if (t->stdin_fd >= 0) { + if (dup2(t->stdin_fd, STDIN_FILENO) != STDIN_FILENO) { + log_error_errno(errno, "Failed to dup2() fd: %m"); + _exit(EXIT_FAILURE); + } + + if (t->stdin_fd != STDIN_FILENO) + safe_close(t->stdin_fd); + } else { + int null_fd; + + null_fd = open("/dev/null", O_RDONLY|O_NOCTTY); + if (null_fd < 0) { + log_error_errno(errno, "Failed to open /dev/null: %m"); + _exit(EXIT_FAILURE); + } + + if (dup2(null_fd, STDIN_FILENO) != STDIN_FILENO) { + log_error_errno(errno, "Failed to dup2() fd: %m"); + _exit(EXIT_FAILURE); + } + + if (null_fd != STDIN_FILENO) + safe_close(null_fd); + } + + fd_cloexec(STDIN_FILENO, false); + fd_cloexec(STDOUT_FILENO, false); + fd_cloexec(STDERR_FILENO, false); + + setenv("SYSTEMD_LOG_TARGET", "console-prefixed", 1); + setenv("NOTIFY_SOCKET", "/run/systemd/import/notify", 1); + + if (IN_SET(t->type, TRANSFER_IMPORT_TAR, TRANSFER_IMPORT_RAW)) + cmd[k++] = SYSTEMD_IMPORT_PATH; + else if (IN_SET(t->type, TRANSFER_EXPORT_TAR, TRANSFER_EXPORT_RAW)) + cmd[k++] = SYSTEMD_EXPORT_PATH; + else + cmd[k++] = SYSTEMD_PULL_PATH; + + if (IN_SET(t->type, TRANSFER_IMPORT_TAR, TRANSFER_EXPORT_TAR, TRANSFER_PULL_TAR)) + cmd[k++] = "tar"; + else + cmd[k++] = "raw"; + + if (t->verify != _IMPORT_VERIFY_INVALID) { + cmd[k++] = "--verify"; + cmd[k++] = import_verify_to_string(t->verify); + } + + if (t->force_local) + cmd[k++] = "--force"; + if (t->read_only) + cmd[k++] = "--read-only"; + + if (t->format) { + cmd[k++] = "--format"; + cmd[k++] = t->format; + } + + if (!IN_SET(t->type, TRANSFER_EXPORT_TAR, TRANSFER_EXPORT_RAW)) { + if (t->remote) + cmd[k++] = t->remote; + else + cmd[k++] = "-"; + } + + if (t->local) + cmd[k++] = t->local; + cmd[k] = NULL; + + execv(cmd[0], (char * const *) cmd); + log_error_errno(errno, "Failed to execute %s tool: %m", cmd[0]); + _exit(EXIT_FAILURE); + } + + pipefd[1] = safe_close(pipefd[1]); + t->log_fd = pipefd[0]; + pipefd[0] = -1; + + t->stdin_fd = safe_close(t->stdin_fd); + + r = sd_event_add_child(t->manager->event, &t->pid_event_source, t->pid, WEXITED, transfer_on_pid, t); + if (r < 0) + return r; + + r = sd_event_add_io(t->manager->event, &t->log_event_source, t->log_fd, EPOLLIN, transfer_on_log, t); + if (r < 0) + return r; + + /* Make sure always process logging before SIGCHLD */ + r = sd_event_source_set_priority(t->log_event_source, SD_EVENT_PRIORITY_NORMAL -5); + if (r < 0) + return r; + + r = sd_bus_emit_signal( + t->manager->bus, + "/org/freedesktop/import1", + "org.freedesktop.import1.Manager", + "TransferNew", + "uo", + t->id, + t->object_path); + if (r < 0) + return r; + + return 0; +} + +static Manager *manager_unref(Manager *m) { + Transfer *t; + + if (!m) + return NULL; + + sd_event_source_unref(m->notify_event_source); + safe_close(m->notify_fd); + + while ((t = hashmap_first(m->transfers))) + transfer_unref(t); + + hashmap_free(m->transfers); + + bus_verify_polkit_async_registry_free(m->polkit_registry); + + m->bus = sd_bus_flush_close_unref(m->bus); + sd_event_unref(m->event); + + free(m); + return NULL; +} + +DEFINE_TRIVIAL_CLEANUP_FUNC(Manager*, manager_unref); + +static int manager_on_notify(sd_event_source *s, int fd, uint32_t revents, void *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 ucred *ucred = NULL; + Manager *m = userdata; + struct cmsghdr *cmsg; + unsigned percent; + char *p, *e; + Transfer *t; + Iterator i; + ssize_t n; + int r; + + n = recvmsg(fd, &msghdr, MSG_DONTWAIT|MSG_CMSG_CLOEXEC); + if (n < 0) { + if (errno == EAGAIN || errno == EINTR) + return 0; + + return -errno; + } + + cmsg_close_all(&msghdr); + + CMSG_FOREACH(cmsg, &msghdr) + if (cmsg->cmsg_level == SOL_SOCKET && + cmsg->cmsg_type == SCM_CREDENTIALS && + cmsg->cmsg_len == CMSG_LEN(sizeof(struct ucred))) + ucred = (struct ucred*) CMSG_DATA(cmsg); + + if (msghdr.msg_flags & MSG_TRUNC) { + log_warning("Got overly long notification datagram, ignoring."); + return 0; + } + + if (!ucred || ucred->pid <= 0) { + log_warning("Got notification datagram lacking credential information, ignoring."); + return 0; + } + + HASHMAP_FOREACH(t, m->transfers, i) + if (ucred->pid == t->pid) + break; + + if (!t) { + log_warning("Got notification datagram from unexpected peer, ignoring."); + return 0; + } + + buf[n] = 0; + + p = startswith(buf, "X_IMPORT_PROGRESS="); + if (!p) { + p = strstr(buf, "\nX_IMPORT_PROGRESS="); + if (!p) + return 0; + + p += 19; + } + + e = strchrnul(p, '\n'); + *e = 0; + + r = safe_atou(p, &percent); + if (r < 0 || percent > 100) { + log_warning("Got invalid percent value, ignoring."); + return 0; + } + + t->progress_percent = percent; + + log_debug("Got percentage from client: %u%%", percent); + return 0; +} + +static int manager_new(Manager **ret) { + _cleanup_(manager_unrefp) Manager *m = NULL; + static const union sockaddr_union sa = { + .un.sun_family = AF_UNIX, + .un.sun_path = "/run/systemd/import/notify", + }; + static const int one = 1; + int r; + + assert(ret); + + m = new0(Manager, 1); + if (!m) + return -ENOMEM; + + r = sd_event_default(&m->event); + if (r < 0) + return r; + + sd_event_set_watchdog(m->event, true); + + r = sd_bus_default_system(&m->bus); + if (r < 0) + return r; + + m->notify_fd = socket(AF_UNIX, SOCK_DGRAM|SOCK_CLOEXEC|SOCK_NONBLOCK, 0); + if (m->notify_fd < 0) + return -errno; + + (void) mkdir_parents_label(sa.un.sun_path, 0755); + (void) unlink(sa.un.sun_path); + + if (bind(m->notify_fd, &sa.sa, SOCKADDR_UN_LEN(sa.un)) < 0) + return -errno; + + if (setsockopt(m->notify_fd, SOL_SOCKET, SO_PASSCRED, &one, sizeof(one)) < 0) + return -errno; + + r = sd_event_add_io(m->event, &m->notify_event_source, m->notify_fd, EPOLLIN, manager_on_notify, m); + if (r < 0) + return r; + + *ret = m; + m = NULL; + + return 0; +} + +static Transfer *manager_find(Manager *m, TransferType type, const char *remote) { + Transfer *t; + Iterator i; + + assert(m); + assert(type >= 0); + assert(type < _TRANSFER_TYPE_MAX); + + HASHMAP_FOREACH(t, m->transfers, i) { + + if (t->type == type && + streq_ptr(t->remote, remote)) + return t; + } + + return NULL; +} + +static int method_import_tar_or_raw(sd_bus_message *msg, void *userdata, sd_bus_error *error) { + _cleanup_(transfer_unrefp) Transfer *t = NULL; + int fd, force, read_only, r; + const char *local, *object; + Manager *m = userdata; + TransferType type; + uint32_t id; + + assert(msg); + assert(m); + + r = bus_verify_polkit_async( + msg, + CAP_SYS_ADMIN, + "org.freedesktop.import1.import", + NULL, + false, + UID_INVALID, + &m->polkit_registry, + error); + if (r < 0) + return r; + if (r == 0) + return 1; /* Will call us back */ + + r = sd_bus_message_read(msg, "hsbb", &fd, &local, &force, &read_only); + if (r < 0) + return r; + + if (!machine_name_is_valid(local)) + return sd_bus_error_setf(error, SD_BUS_ERROR_INVALID_ARGS, "Local name %s is invalid", local); + + r = setup_machine_directory((uint64_t) -1, error); + if (r < 0) + return r; + + type = streq_ptr(sd_bus_message_get_member(msg), "ImportTar") ? TRANSFER_IMPORT_TAR : TRANSFER_IMPORT_RAW; + + r = transfer_new(m, &t); + if (r < 0) + return r; + + t->type = type; + t->force_local = force; + t->read_only = read_only; + + t->local = strdup(local); + if (!t->local) + return -ENOMEM; + + t->stdin_fd = fcntl(fd, F_DUPFD_CLOEXEC, 3); + if (t->stdin_fd < 0) + return -errno; + + r = transfer_start(t); + if (r < 0) + return r; + + object = t->object_path; + id = t->id; + t = NULL; + + return sd_bus_reply_method_return(msg, "uo", id, object); +} + +static int method_export_tar_or_raw(sd_bus_message *msg, void *userdata, sd_bus_error *error) { + _cleanup_(transfer_unrefp) Transfer *t = NULL; + int fd, r; + const char *local, *object, *format; + Manager *m = userdata; + TransferType type; + uint32_t id; + + assert(msg); + assert(m); + + r = bus_verify_polkit_async( + msg, + CAP_SYS_ADMIN, + "org.freedesktop.import1.export", + NULL, + false, + UID_INVALID, + &m->polkit_registry, + error); + if (r < 0) + return r; + if (r == 0) + return 1; /* Will call us back */ + + r = sd_bus_message_read(msg, "shs", &local, &fd, &format); + if (r < 0) + return r; + + if (!machine_name_is_valid(local)) + return sd_bus_error_setf(error, SD_BUS_ERROR_INVALID_ARGS, "Local name %s is invalid", local); + + type = streq_ptr(sd_bus_message_get_member(msg), "ExportTar") ? TRANSFER_EXPORT_TAR : TRANSFER_EXPORT_RAW; + + r = transfer_new(m, &t); + if (r < 0) + return r; + + t->type = type; + + if (!isempty(format)) { + t->format = strdup(format); + if (!t->format) + return -ENOMEM; + } + + t->local = strdup(local); + if (!t->local) + return -ENOMEM; + + t->stdout_fd = fcntl(fd, F_DUPFD_CLOEXEC, 3); + if (t->stdout_fd < 0) + return -errno; + + r = transfer_start(t); + if (r < 0) + return r; + + object = t->object_path; + id = t->id; + t = NULL; + + return sd_bus_reply_method_return(msg, "uo", id, object); +} + +static int method_pull_tar_or_raw(sd_bus_message *msg, void *userdata, sd_bus_error *error) { + _cleanup_(transfer_unrefp) Transfer *t = NULL; + const char *remote, *local, *verify, *object; + Manager *m = userdata; + ImportVerify v; + TransferType type; + int force, r; + uint32_t id; + + assert(msg); + assert(m); + + r = bus_verify_polkit_async( + msg, + CAP_SYS_ADMIN, + "org.freedesktop.import1.pull", + NULL, + false, + UID_INVALID, + &m->polkit_registry, + error); + if (r < 0) + return r; + if (r == 0) + return 1; /* Will call us back */ + + r = sd_bus_message_read(msg, "sssb", &remote, &local, &verify, &force); + if (r < 0) + return r; + + if (!http_url_is_valid(remote)) + return sd_bus_error_setf(error, SD_BUS_ERROR_INVALID_ARGS, "URL %s is invalid", remote); + + if (isempty(local)) + local = NULL; + else if (!machine_name_is_valid(local)) + return sd_bus_error_setf(error, SD_BUS_ERROR_INVALID_ARGS, "Local name %s is invalid", local); + + if (isempty(verify)) + v = IMPORT_VERIFY_SIGNATURE; + else + v = import_verify_from_string(verify); + if (v < 0) + return sd_bus_error_setf(error, SD_BUS_ERROR_INVALID_ARGS, "Unknown verification mode %s", verify); + + r = setup_machine_directory((uint64_t) -1, error); + if (r < 0) + return r; + + type = streq_ptr(sd_bus_message_get_member(msg), "PullTar") ? TRANSFER_PULL_TAR : TRANSFER_PULL_RAW; + + if (manager_find(m, type, remote)) + return sd_bus_error_setf(error, BUS_ERROR_TRANSFER_IN_PROGRESS, "Transfer for %s already in progress.", remote); + + r = transfer_new(m, &t); + if (r < 0) + return r; + + t->type = type; + t->verify = v; + t->force_local = force; + + t->remote = strdup(remote); + if (!t->remote) + return -ENOMEM; + + if (local) { + t->local = strdup(local); + if (!t->local) + return -ENOMEM; + } + + r = transfer_start(t); + if (r < 0) + return r; + + object = t->object_path; + id = t->id; + t = NULL; + + return sd_bus_reply_method_return(msg, "uo", id, object); +} + +static int method_list_transfers(sd_bus_message *msg, void *userdata, sd_bus_error *error) { + _cleanup_(sd_bus_message_unrefp) sd_bus_message *reply = NULL; + Manager *m = userdata; + Transfer *t; + Iterator i; + int r; + + assert(msg); + assert(m); + + r = sd_bus_message_new_method_return(msg, &reply); + if (r < 0) + return r; + + r = sd_bus_message_open_container(reply, 'a', "(usssdo)"); + if (r < 0) + return r; + + HASHMAP_FOREACH(t, m->transfers, i) { + + r = sd_bus_message_append( + reply, + "(usssdo)", + t->id, + transfer_type_to_string(t->type), + t->remote, + t->local, + (double) t->progress_percent / 100.0, + t->object_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_cancel(sd_bus_message *msg, void *userdata, sd_bus_error *error) { + Transfer *t = userdata; + int r; + + assert(msg); + assert(t); + + r = bus_verify_polkit_async( + msg, + CAP_SYS_ADMIN, + "org.freedesktop.import1.pull", + NULL, + false, + UID_INVALID, + &t->manager->polkit_registry, + error); + if (r < 0) + return r; + if (r == 0) + return 1; /* Will call us back */ + + r = transfer_cancel(t); + if (r < 0) + return r; + + return sd_bus_reply_method_return(msg, NULL); +} + +static int method_cancel_transfer(sd_bus_message *msg, void *userdata, sd_bus_error *error) { + Manager *m = userdata; + Transfer *t; + uint32_t id; + int r; + + assert(msg); + assert(m); + + r = bus_verify_polkit_async( + msg, + CAP_SYS_ADMIN, + "org.freedesktop.import1.pull", + NULL, + false, + UID_INVALID, + &m->polkit_registry, + error); + if (r < 0) + return r; + if (r == 0) + return 1; /* Will call us back */ + + r = sd_bus_message_read(msg, "u", &id); + if (r < 0) + return r; + if (id <= 0) + return sd_bus_error_setf(error, SD_BUS_ERROR_INVALID_ARGS, "Invalid transfer id"); + + t = hashmap_get(m->transfers, UINT32_TO_PTR(id)); + if (!t) + return sd_bus_error_setf(error, BUS_ERROR_NO_SUCH_TRANSFER, "No transfer by id %" PRIu32, id); + + r = transfer_cancel(t); + if (r < 0) + return r; + + return sd_bus_reply_method_return(msg, NULL); +} + +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) { + + Transfer *t = userdata; + + assert(bus); + assert(reply); + assert(t); + + return sd_bus_message_append(reply, "d", (double) t->progress_percent / 100.0); +} + +static BUS_DEFINE_PROPERTY_GET_ENUM(property_get_type, transfer_type, TransferType); +static BUS_DEFINE_PROPERTY_GET_ENUM(property_get_verify, import_verify, ImportVerify); + +static const sd_bus_vtable transfer_vtable[] = { + SD_BUS_VTABLE_START(0), + SD_BUS_PROPERTY("Id", "u", NULL, offsetof(Transfer, id), SD_BUS_VTABLE_PROPERTY_CONST), + SD_BUS_PROPERTY("Local", "s", NULL, offsetof(Transfer, local), SD_BUS_VTABLE_PROPERTY_CONST), + SD_BUS_PROPERTY("Remote", "s", NULL, offsetof(Transfer, remote), SD_BUS_VTABLE_PROPERTY_CONST), + SD_BUS_PROPERTY("Type", "s", property_get_type, offsetof(Transfer, type), SD_BUS_VTABLE_PROPERTY_CONST), + SD_BUS_PROPERTY("Verify", "s", property_get_verify, offsetof(Transfer, verify), SD_BUS_VTABLE_PROPERTY_CONST), + SD_BUS_PROPERTY("Progress", "d", property_get_progress, 0, 0), + SD_BUS_METHOD("Cancel", NULL, NULL, method_cancel, SD_BUS_VTABLE_UNPRIVILEGED), + SD_BUS_SIGNAL("LogMessage", "us", 0), + SD_BUS_VTABLE_END, +}; + +static const sd_bus_vtable manager_vtable[] = { + SD_BUS_VTABLE_START(0), + SD_BUS_METHOD("ImportTar", "hsbb", "uo", method_import_tar_or_raw, SD_BUS_VTABLE_UNPRIVILEGED), + SD_BUS_METHOD("ImportRaw", "hsbb", "uo", method_import_tar_or_raw, SD_BUS_VTABLE_UNPRIVILEGED), + SD_BUS_METHOD("ExportTar", "shs", "uo", method_export_tar_or_raw, SD_BUS_VTABLE_UNPRIVILEGED), + SD_BUS_METHOD("ExportRaw", "shs", "uo", method_export_tar_or_raw, SD_BUS_VTABLE_UNPRIVILEGED), + SD_BUS_METHOD("PullTar", "sssb", "uo", method_pull_tar_or_raw, SD_BUS_VTABLE_UNPRIVILEGED), + SD_BUS_METHOD("PullRaw", "sssb", "uo", method_pull_tar_or_raw, SD_BUS_VTABLE_UNPRIVILEGED), + SD_BUS_METHOD("ListTransfers", NULL, "a(usssdo)", method_list_transfers, SD_BUS_VTABLE_UNPRIVILEGED), + SD_BUS_METHOD("CancelTransfer", "u", NULL, method_cancel_transfer, SD_BUS_VTABLE_UNPRIVILEGED), + SD_BUS_SIGNAL("TransferNew", "uo", 0), + SD_BUS_SIGNAL("TransferRemoved", "uos", 0), + SD_BUS_VTABLE_END, +}; + +static int transfer_object_find(sd_bus *bus, const char *path, const char *interface, void *userdata, void **found, sd_bus_error *error) { + Manager *m = userdata; + Transfer *t; + const char *p; + uint32_t id; + int r; + + assert(bus); + assert(path); + assert(interface); + assert(found); + assert(m); + + p = startswith(path, "/org/freedesktop/import1/transfer/_"); + if (!p) + return 0; + + r = safe_atou32(p, &id); + if (r < 0 || id == 0) + return 0; + + t = hashmap_get(m->transfers, UINT32_TO_PTR(id)); + if (!t) + return 0; + + *found = t; + return 1; +} + +static int transfer_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; + Transfer *t; + unsigned k = 0; + Iterator i; + + l = new0(char*, hashmap_size(m->transfers) + 1); + if (!l) + return -ENOMEM; + + HASHMAP_FOREACH(t, m->transfers, i) { + + l[k] = strdup(t->object_path); + if (!l[k]) + return -ENOMEM; + + k++; + } + + *nodes = l; + l = NULL; + + return 1; +} + +static int manager_add_bus_objects(Manager *m) { + int r; + + assert(m); + + r = sd_bus_add_object_vtable(m->bus, NULL, "/org/freedesktop/import1", "org.freedesktop.import1.Manager", manager_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/import1/transfer", "org.freedesktop.import1.Transfer", transfer_vtable, transfer_object_find, m); + if (r < 0) + return log_error_errno(r, "Failed to register object: %m"); + + r = sd_bus_add_node_enumerator(m->bus, NULL, "/org/freedesktop/import1/transfer", transfer_node_enumerator, m); + if (r < 0) + return log_error_errno(r, "Failed to add transfer enumerator: %m"); + + r = sd_bus_request_name(m->bus, "org.freedesktop.import1", 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; +} + +static bool manager_check_idle(void *userdata) { + Manager *m = userdata; + + return hashmap_isempty(m->transfers); +} + +static int manager_run(Manager *m) { + assert(m); + + return bus_event_loop_with_idle( + m->event, + m->bus, + "org.freedesktop.import1", + DEFAULT_EXIT_USEC, + manager_check_idle, + m); +} + +int main(int argc, char *argv[]) { + _cleanup_(manager_unrefp) Manager *m = 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; + } + + assert_se(sigprocmask_many(SIG_BLOCK, NULL, SIGCHLD, -1) >= 0); + + r = manager_new(&m); + if (r < 0) { + log_error_errno(r, "Failed to allocate manager object: %m"); + goto finish; + } + + r = manager_add_bus_objects(m); + if (r < 0) + goto finish; + + r = manager_run(m); + if (r < 0) { + log_error_errno(r, "Failed to run event loop: %m"); + goto finish; + } + +finish: + return r < 0 ? EXIT_FAILURE : EXIT_SUCCESS; +} diff --git a/src/grp-machine/grp-import/systemd-pull/Makefile b/src/grp-machine/grp-import/systemd-pull/Makefile new file mode 100644 index 0000000000..36505edb5b --- /dev/null +++ b/src/grp-machine/grp-import/systemd-pull/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 + +rootlibexec_PROGRAMS += systemd-pull +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/qcow2-util.c \ + src/import/qcow2-util.h + +systemd_pull_CFLAGS = \ + $(AM_CFLAGS) \ + $(LIBCURL_CFLAGS) \ + $(XZ_CFLAGS) \ + $(ZLIB_CFLAGS) \ + $(BZIP2_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) \ + $(BZIP2_LIBS) \ + $(GCRYPT_LIBS) + +include $(topsrcdir)/build-aux/Makefile.tail.mk diff --git a/src/grp-machine/grp-import/systemd-pull/pull-common.c b/src/grp-machine/grp-import/systemd-pull/pull-common.c new file mode 100644 index 0000000000..fb167fd93c --- /dev/null +++ b/src/grp-machine/grp-import/systemd-pull/pull-common.c @@ -0,0 +1,549 @@ +/*** + 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 "basic/alloc-util.h" +#include "basic/btrfs-util.h" +#include "basic/capability-util.h" +#include "basic/copy.h" +#include "basic/dirent-util.h" +#include "basic/escape.h" +#include "basic/fd-util.h" +#include "basic/io-util.h" +#include "basic/path-util.h" +#include "basic/process-util.h" +#include "pull-common.h" +#include "pull-job.h" +#include "basic/rm-rf.h" +#include "basic/signal-util.h" +#include "basic/siphash24.h" +#include "basic/string-util.h" +#include "basic/strv.h" +#include "basic/util.h" +#include "basic/web-util.h" + +#define FILENAME_ESCAPE "/.#\"\'" +#define HASH_URL_THRESHOLD_LENGTH (_POSIX_PATH_MAX - 16) + +int pull_find_old_etags( + const char *url, + const char *image_root, + int dt, + const char *prefix, + const char *suffix, + char ***etags) { + + _cleanup_free_ char *escaped_url = NULL; + _cleanup_closedir_ DIR *d = NULL; + _cleanup_strv_free_ char **l = NULL; + struct dirent *de; + int r; + + assert(url); + assert(etags); + + if (!image_root) + image_root = "/var/lib/machines"; + + escaped_url = xescape(url, FILENAME_ESCAPE); + if (!escaped_url) + return -ENOMEM; + + d = opendir(image_root); + if (!d) { + if (errno == ENOENT) { + *etags = NULL; + return 0; + } + + return -errno; + } + + FOREACH_DIRENT_ALL(de, d, return -errno) { + const char *a, *b; + char *u; + + if (de->d_type != DT_UNKNOWN && + de->d_type != dt) + continue; + + if (prefix) { + a = startswith(de->d_name, prefix); + if (!a) + continue; + } else + a = de->d_name; + + a = startswith(a, escaped_url); + if (!a) + continue; + + a = startswith(a, "."); + if (!a) + continue; + + if (suffix) { + b = endswith(de->d_name, suffix); + if (!b) + continue; + } else + b = strchr(de->d_name, 0); + + if (a >= b) + continue; + + r = cunescape_length(a, b - a, 0, &u); + if (r < 0) + return r; + + if (!http_etag_is_valid(u)) { + free(u); + continue; + } + + r = strv_consume(&l, u); + if (r < 0) + return r; + } + + *etags = l; + l = NULL; + + return 0; +} + +int pull_make_local_copy(const char *final, const char *image_root, const char *local, bool force_local) { + const char *p; + int r; + + assert(final); + assert(local); + + if (!image_root) + image_root = "/var/lib/machines"; + + p = strjoina(image_root, "/", local); + + if (force_local) + (void) rm_rf(p, REMOVE_ROOT|REMOVE_PHYSICAL|REMOVE_SUBVOLUME); + + r = btrfs_subvol_snapshot(final, p, BTRFS_SNAPSHOT_QUOTA); + if (r == -ENOTTY) { + r = copy_tree(final, p, false); + if (r < 0) + return log_error_errno(r, "Failed to copy image: %m"); + } else if (r < 0) + return log_error_errno(r, "Failed to create local image: %m"); + + log_info("Created new local image '%s'.", local); + + return 0; +} + +static int hash_url(const char *url, char **ret) { + uint64_t h; + static const sd_id128_t k = SD_ID128_ARRAY(df,89,16,87,01,cc,42,30,98,ab,4a,19,a6,a5,63,4f); + + assert(url); + + h = siphash24(url, strlen(url), k.bytes); + if (asprintf(ret, "%"PRIx64, h) < 0) + return -ENOMEM; + + return 0; +} + +int pull_make_path(const char *url, const char *etag, const char *image_root, const char *prefix, const char *suffix, char **ret) { + _cleanup_free_ char *escaped_url = NULL, *escaped_etag = NULL; + char *path; + + assert(url); + assert(ret); + + if (!image_root) + image_root = "/var/lib/machines"; + + escaped_url = xescape(url, FILENAME_ESCAPE); + if (!escaped_url) + return -ENOMEM; + + if (etag) { + escaped_etag = xescape(etag, FILENAME_ESCAPE); + if (!escaped_etag) + return -ENOMEM; + } + + path = strjoin(image_root, "/", strempty(prefix), escaped_url, escaped_etag ? "." : "", + strempty(escaped_etag), strempty(suffix), NULL); + if (!path) + return -ENOMEM; + + /* URLs might make the path longer than the maximum allowed length for a file name. + * When that happens, a URL hash is used instead. Paths returned by this function + * can be later used with tempfn_random() which adds 16 bytes to the resulting name. */ + if (strlen(path) >= HASH_URL_THRESHOLD_LENGTH) { + _cleanup_free_ char *hash = NULL; + int r; + + free(path); + + r = hash_url(url, &hash); + if (r < 0) + return r; + + path = strjoin(image_root, "/", strempty(prefix), hash, escaped_etag ? "." : "", + strempty(escaped_etag), strempty(suffix), NULL); + if (!path) + return -ENOMEM; + } + + *ret = path; + return 0; +} + +int pull_make_settings_job( + PullJob **ret, + const char *url, + CurlGlue *glue, + PullJobFinished on_finished, + void *userdata) { + + _cleanup_free_ char *last_component = NULL, *ll = NULL, *settings_url = NULL; + _cleanup_(pull_job_unrefp) PullJob *job = NULL; + const char *q; + int r; + + assert(ret); + assert(url); + assert(glue); + + r = import_url_last_component(url, &last_component); + if (r < 0) + return r; + + r = tar_strip_suffixes(last_component, &ll); + if (r < 0) + return r; + + q = strjoina(ll, ".nspawn"); + + r = import_url_change_last_component(url, q, &settings_url); + if (r < 0) + return r; + + r = pull_job_new(&job, settings_url, glue, userdata); + if (r < 0) + return r; + + job->on_finished = on_finished; + job->compressed_max = job->uncompressed_max = 1ULL * 1024ULL * 1024ULL; + + *ret = job; + job = NULL; + + return 0; +} + +int pull_make_verification_jobs( + PullJob **ret_checksum_job, + PullJob **ret_signature_job, + ImportVerify verify, + const char *url, + CurlGlue *glue, + PullJobFinished on_finished, + void *userdata) { + + _cleanup_(pull_job_unrefp) PullJob *checksum_job = NULL, *signature_job = NULL; + int r; + + assert(ret_checksum_job); + assert(ret_signature_job); + assert(verify >= 0); + assert(verify < _IMPORT_VERIFY_MAX); + assert(url); + assert(glue); + + if (verify != IMPORT_VERIFY_NO) { + _cleanup_free_ char *checksum_url = NULL; + + /* Queue job for the SHA256SUMS file for the image */ + r = import_url_change_last_component(url, "SHA256SUMS", &checksum_url); + if (r < 0) + return r; + + r = pull_job_new(&checksum_job, checksum_url, glue, userdata); + if (r < 0) + return r; + + checksum_job->on_finished = on_finished; + checksum_job->uncompressed_max = checksum_job->compressed_max = 1ULL * 1024ULL * 1024ULL; + } + + if (verify == IMPORT_VERIFY_SIGNATURE) { + _cleanup_free_ char *signature_url = NULL; + + /* Queue job for the SHA256SUMS.gpg file for the image. */ + r = import_url_change_last_component(url, "SHA256SUMS.gpg", &signature_url); + if (r < 0) + return r; + + r = pull_job_new(&signature_job, signature_url, glue, userdata); + if (r < 0) + return r; + + signature_job->on_finished = on_finished; + signature_job->uncompressed_max = signature_job->compressed_max = 1ULL * 1024ULL * 1024ULL; + } + + *ret_checksum_job = checksum_job; + *ret_signature_job = signature_job; + + checksum_job = signature_job = NULL; + + return 0; +} + +int pull_verify(PullJob *main_job, + PullJob *settings_job, + PullJob *checksum_job, + PullJob *signature_job) { + + _cleanup_close_pair_ int gpg_pipe[2] = { -1, -1 }; + _cleanup_free_ char *fn = NULL; + _cleanup_close_ int sig_file = -1; + const char *p, *line; + char sig_file_path[] = "/tmp/sigXXXXXX", gpg_home[] = "/tmp/gpghomeXXXXXX"; + _cleanup_(sigkill_waitp) pid_t pid = 0; + bool gpg_home_created = false; + int r; + + assert(main_job); + assert(main_job->state == PULL_JOB_DONE); + + if (!checksum_job) + return 0; + + assert(main_job->calc_checksum); + assert(main_job->checksum); + assert(checksum_job->state == PULL_JOB_DONE); + + if (!checksum_job->payload || checksum_job->payload_size <= 0) { + log_error("Checksum is empty, cannot verify."); + return -EBADMSG; + } + + r = import_url_last_component(main_job->url, &fn); + if (r < 0) + return log_oom(); + + if (!filename_is_valid(fn)) { + log_error("Cannot verify checksum, could not determine valid server-side file name."); + return -EBADMSG; + } + + line = strjoina(main_job->checksum, " *", fn, "\n"); + + p = memmem(checksum_job->payload, + checksum_job->payload_size, + line, + strlen(line)); + + if (!p || (p != (char*) checksum_job->payload && p[-1] != '\n')) { + log_error("DOWNLOAD INVALID: Checksum did not check out, payload has been tampered with."); + return -EBADMSG; + } + + log_info("SHA256 checksum of %s is valid.", main_job->url); + + assert(!settings_job || IN_SET(settings_job->state, PULL_JOB_DONE, PULL_JOB_FAILED)); + + if (settings_job && + settings_job->state == PULL_JOB_DONE && + settings_job->error == 0 && + !settings_job->etag_exists) { + + _cleanup_free_ char *settings_fn = NULL; + + assert(settings_job->calc_checksum); + assert(settings_job->checksum); + + r = import_url_last_component(settings_job->url, &settings_fn); + if (r < 0) + return log_oom(); + + if (!filename_is_valid(settings_fn)) { + log_error("Cannot verify checksum, could not determine server-side settings file name."); + return -EBADMSG; + } + + line = strjoina(settings_job->checksum, " *", settings_fn, "\n"); + + p = memmem(checksum_job->payload, + checksum_job->payload_size, + line, + strlen(line)); + + if (!p || (p != (char*) checksum_job->payload && p[-1] != '\n')) { + log_error("DOWNLOAD INVALID: Checksum of settings file did not checkout, settings file has been tampered with."); + return -EBADMSG; + } + + log_info("SHA256 checksum of %s is valid.", settings_job->url); + } + + if (!signature_job) + return 0; + + assert(signature_job->state == PULL_JOB_DONE); + + if (!signature_job->payload || signature_job->payload_size <= 0) { + log_error("Signature is empty, cannot verify."); + return -EBADMSG; + } + + r = pipe2(gpg_pipe, O_CLOEXEC); + if (r < 0) + return log_error_errno(errno, "Failed to create pipe for gpg: %m"); + + sig_file = mkostemp(sig_file_path, O_RDWR); + if (sig_file < 0) + return log_error_errno(errno, "Failed to create temporary file: %m"); + + r = loop_write(sig_file, signature_job->payload, signature_job->payload_size, false); + if (r < 0) { + log_error_errno(r, "Failed to write to temporary file: %m"); + goto finish; + } + + if (!mkdtemp(gpg_home)) { + r = log_error_errno(errno, "Failed to create tempory home for gpg: %m"); + goto finish; + } + + gpg_home_created = true; + + pid = fork(); + if (pid < 0) + return log_error_errno(errno, "Failed to fork off gpg: %m"); + if (pid == 0) { + const char *cmd[] = { + "gpg", + "--no-options", + "--no-default-keyring", + "--no-auto-key-locate", + "--no-auto-check-trustdb", + "--batch", + "--trust-model=always", + NULL, /* --homedir= */ + NULL, /* --keyring= */ + NULL, /* --verify */ + NULL, /* signature file */ + NULL, /* dash */ + NULL /* trailing NULL */ + }; + unsigned k = ELEMENTSOF(cmd) - 6; + int null_fd; + + /* Child */ + + (void) reset_all_signal_handlers(); + (void) reset_signal_mask(); + assert_se(prctl(PR_SET_PDEATHSIG, SIGTERM) == 0); + + gpg_pipe[1] = safe_close(gpg_pipe[1]); + + if (dup2(gpg_pipe[0], STDIN_FILENO) != STDIN_FILENO) { + log_error_errno(errno, "Failed to dup2() fd: %m"); + _exit(EXIT_FAILURE); + } + + if (gpg_pipe[0] != STDIN_FILENO) + gpg_pipe[0] = safe_close(gpg_pipe[0]); + + null_fd = open("/dev/null", O_WRONLY|O_NOCTTY); + if (null_fd < 0) { + log_error_errno(errno, "Failed to open /dev/null: %m"); + _exit(EXIT_FAILURE); + } + + if (dup2(null_fd, STDOUT_FILENO) != STDOUT_FILENO) { + log_error_errno(errno, "Failed to dup2() fd: %m"); + _exit(EXIT_FAILURE); + } + + if (null_fd != STDOUT_FILENO) + null_fd = safe_close(null_fd); + + cmd[k++] = strjoina("--homedir=", gpg_home); + + /* We add the user keyring only to the command line + * arguments, if it's around since gpg fails + * otherwise. */ + if (access(USER_KEYRING_PATH, F_OK) >= 0) + cmd[k++] = "--keyring=" USER_KEYRING_PATH; + else + cmd[k++] = "--keyring=" VENDOR_KEYRING_PATH; + + cmd[k++] = "--verify"; + cmd[k++] = sig_file_path; + cmd[k++] = "-"; + cmd[k++] = NULL; + + fd_cloexec(STDIN_FILENO, false); + fd_cloexec(STDOUT_FILENO, false); + fd_cloexec(STDERR_FILENO, false); + + execvp("gpg2", (char * const *) cmd); + execvp("gpg", (char * const *) cmd); + log_error_errno(errno, "Failed to execute gpg: %m"); + _exit(EXIT_FAILURE); + } + + gpg_pipe[0] = safe_close(gpg_pipe[0]); + + r = loop_write(gpg_pipe[1], checksum_job->payload, checksum_job->payload_size, false); + if (r < 0) { + log_error_errno(r, "Failed to write to pipe: %m"); + goto finish; + } + + gpg_pipe[1] = safe_close(gpg_pipe[1]); + + r = wait_for_terminate_and_warn("gpg", pid, true); + pid = 0; + if (r < 0) + goto finish; + if (r > 0) { + log_error("DOWNLOAD INVALID: Signature verification failed."); + r = -EBADMSG; + } else { + log_info("Signature verification succeeded."); + r = 0; + } + +finish: + if (sig_file >= 0) + (void) unlink(sig_file_path); + + if (gpg_home_created) + (void) rm_rf(gpg_home, REMOVE_ROOT|REMOVE_PHYSICAL); + + return r; +} diff --git a/src/grp-machine/grp-import/systemd-pull/pull-common.h b/src/grp-machine/grp-import/systemd-pull/pull-common.h new file mode 100644 index 0000000000..d9cfb6b3c1 --- /dev/null +++ b/src/grp-machine/grp-import/systemd-pull/pull-common.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 "shared/import-util.h" +#include "pull-job.h" + +int pull_make_local_copy(const char *final, const char *root, const char *local, bool force_local); + +int pull_find_old_etags(const char *url, const char *root, int dt, const char *prefix, const char *suffix, char ***etags); + +int pull_make_path(const char *url, const char *etag, const char *image_root, const char *prefix, const char *suffix, char **ret); + +int pull_make_settings_job(PullJob **ret, const char *url, CurlGlue *glue, PullJobFinished on_finished, void *userdata); +int pull_make_verification_jobs(PullJob **ret_checksum_job, PullJob **ret_signature_job, ImportVerify verify, const char *url, CurlGlue *glue, PullJobFinished on_finished, void *userdata); + +int pull_verify(PullJob *main_job, PullJob *settings_job, PullJob *checksum_job, PullJob *signature_job); diff --git a/src/grp-machine/grp-import/systemd-pull/pull-job.c b/src/grp-machine/grp-import/systemd-pull/pull-job.c new file mode 100644 index 0000000000..fce08f65d9 --- /dev/null +++ b/src/grp-machine/grp-import/systemd-pull/pull-job.c @@ -0,0 +1,618 @@ +/*** + 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 "basic/alloc-util.h" +#include "basic/fd-util.h" +#include "basic/hexdecoct.h" +#include "basic/io-util.h" +#include "shared/machine-pool.h" +#include "basic/parse-util.h" +#include "pull-job.h" +#include "basic/string-util.h" +#include "basic/strv.h" +#include "basic/xattr-util.h" + +PullJob* pull_job_unref(PullJob *j) { + if (!j) + return NULL; + + curl_glue_remove_and_free(j->glue, j->curl); + curl_slist_free_all(j->request_header); + + safe_close(j->disk_fd); + + import_compress_free(&j->compress); + + if (j->checksum_context) + gcry_md_close(j->checksum_context); + + free(j->url); + free(j->etag); + strv_free(j->old_etags); + free(j->payload); + free(j->checksum); + + free(j); + + return NULL; +} + +static void pull_job_finish(PullJob *j, int ret) { + assert(j); + + if (j->state == PULL_JOB_DONE || + j->state == PULL_JOB_FAILED) + return; + + if (ret == 0) { + j->state = PULL_JOB_DONE; + j->progress_percent = 100; + log_info("Download of %s complete.", j->url); + } else { + j->state = PULL_JOB_FAILED; + j->error = ret; + } + + if (j->on_finished) + j->on_finished(j); +} + +void pull_job_curl_on_finished(CurlGlue *g, CURL *curl, CURLcode result) { + PullJob *j = NULL; + CURLcode code; + long status; + int r; + + if (curl_easy_getinfo(curl, CURLINFO_PRIVATE, (char **)&j) != CURLE_OK) + return; + + if (!j || j->state == PULL_JOB_DONE || j->state == PULL_JOB_FAILED) + return; + + if (result != CURLE_OK) { + log_error("Transfer failed: %s", curl_easy_strerror(result)); + r = -EIO; + goto finish; + } + + code = curl_easy_getinfo(curl, CURLINFO_RESPONSE_CODE, &status); + if (code != CURLE_OK) { + log_error("Failed to retrieve response code: %s", curl_easy_strerror(code)); + r = -EIO; + goto finish; + } else if (status == 304) { + log_info("Image already downloaded. Skipping download."); + j->etag_exists = true; + r = 0; + goto finish; + } else if (status >= 300) { + log_error("HTTP request to %s failed with code %li.", j->url, status); + r = -EIO; + goto finish; + } else if (status < 200) { + log_error("HTTP request to %s finished with unexpected code %li.", j->url, status); + r = -EIO; + goto finish; + } + + if (j->state != PULL_JOB_RUNNING) { + log_error("Premature connection termination."); + r = -EIO; + goto finish; + } + + if (j->content_length != (uint64_t) -1 && + j->content_length != j->written_compressed) { + log_error("Download truncated."); + r = -EIO; + goto finish; + } + + if (j->checksum_context) { + uint8_t *k; + + k = gcry_md_read(j->checksum_context, GCRY_MD_SHA256); + if (!k) { + log_error("Failed to get checksum."); + r = -EIO; + goto finish; + } + + j->checksum = hexmem(k, gcry_md_get_algo_dlen(GCRY_MD_SHA256)); + if (!j->checksum) { + r = log_oom(); + goto finish; + } + + log_debug("SHA256 of %s is %s.", j->url, j->checksum); + } + + if (j->disk_fd >= 0 && j->allow_sparse) { + /* Make sure the file size is right, in case the file was + * sparse and we just seeked for the last part */ + + if (ftruncate(j->disk_fd, j->written_uncompressed) < 0) { + r = log_error_errno(errno, "Failed to truncate file: %m"); + goto finish; + } + + if (j->etag) + (void) fsetxattr(j->disk_fd, "user.source_etag", j->etag, strlen(j->etag), 0); + if (j->url) + (void) fsetxattr(j->disk_fd, "user.source_url", j->url, strlen(j->url), 0); + + if (j->mtime != 0) { + struct timespec ut[2]; + + timespec_store(&ut[0], j->mtime); + ut[1] = ut[0]; + (void) futimens(j->disk_fd, ut); + + (void) fd_setcrtime(j->disk_fd, j->mtime); + } + } + + r = 0; + +finish: + pull_job_finish(j, r); +} + +static int pull_job_write_uncompressed(const void *p, size_t sz, void *userdata) { + PullJob *j = userdata; + ssize_t n; + + assert(j); + assert(p); + + if (sz <= 0) + return 0; + + if (j->written_uncompressed + sz < j->written_uncompressed) { + log_error("File too large, overflow"); + return -EOVERFLOW; + } + + if (j->written_uncompressed + sz > j->uncompressed_max) { + log_error("File overly large, refusing"); + return -EFBIG; + } + + if (j->disk_fd >= 0) { + + if (j->grow_machine_directory && j->written_since_last_grow >= GROW_INTERVAL_BYTES) { + j->written_since_last_grow = 0; + grow_machine_directory(); + } + + if (j->allow_sparse) + n = sparse_write(j->disk_fd, p, sz, 64); + else + n = write(j->disk_fd, p, sz); + if (n < 0) + return log_error_errno(errno, "Failed to write file: %m"); + if ((size_t) n < sz) { + log_error("Short write"); + return -EIO; + } + } else { + + if (!GREEDY_REALLOC(j->payload, j->payload_allocated, j->payload_size + sz)) + return log_oom(); + + memcpy(j->payload + j->payload_size, p, sz); + j->payload_size += sz; + } + + j->written_uncompressed += sz; + j->written_since_last_grow += sz; + + return 0; +} + +static int pull_job_write_compressed(PullJob *j, void *p, size_t sz) { + int r; + + assert(j); + assert(p); + + if (sz <= 0) + return 0; + + if (j->written_compressed + sz < j->written_compressed) { + log_error("File too large, overflow"); + return -EOVERFLOW; + } + + if (j->written_compressed + sz > j->compressed_max) { + log_error("File overly large, refusing."); + return -EFBIG; + } + + if (j->content_length != (uint64_t) -1 && + j->written_compressed + sz > j->content_length) { + log_error("Content length incorrect."); + return -EFBIG; + } + + if (j->checksum_context) + gcry_md_write(j->checksum_context, p, sz); + + r = import_uncompress(&j->compress, p, sz, pull_job_write_uncompressed, j); + if (r < 0) + return r; + + j->written_compressed += sz; + + return 0; +} + +static int pull_job_open_disk(PullJob *j) { + int r; + + assert(j); + + if (j->on_open_disk) { + r = j->on_open_disk(j); + if (r < 0) + return r; + } + + if (j->disk_fd >= 0) { + /* Check if we can do sparse files */ + + if (lseek(j->disk_fd, SEEK_SET, 0) == 0) + j->allow_sparse = true; + else { + if (errno != ESPIPE) + return log_error_errno(errno, "Failed to seek on file descriptor: %m"); + + j->allow_sparse = false; + } + } + + if (j->calc_checksum) { + if (gcry_md_open(&j->checksum_context, GCRY_MD_SHA256, 0) != 0) { + log_error("Failed to initialize hash context."); + return -EIO; + } + } + + return 0; +} + +static int pull_job_detect_compression(PullJob *j) { + _cleanup_free_ uint8_t *stub = NULL; + size_t stub_size; + + int r; + + assert(j); + + r = import_uncompress_detect(&j->compress, j->payload, j->payload_size); + if (r < 0) + return log_error_errno(r, "Failed to initialize compressor: %m"); + if (r == 0) + return 0; + + log_debug("Stream is compressed: %s", import_compress_type_to_string(j->compress.type)); + + r = pull_job_open_disk(j); + if (r < 0) + return r; + + /* Now, take the payload we read so far, and decompress it */ + stub = j->payload; + stub_size = j->payload_size; + + j->payload = NULL; + j->payload_size = 0; + j->payload_allocated = 0; + + j->state = PULL_JOB_RUNNING; + + r = pull_job_write_compressed(j, stub, stub_size); + if (r < 0) + return r; + + return 0; +} + +static size_t pull_job_write_callback(void *contents, size_t size, size_t nmemb, void *userdata) { + PullJob *j = userdata; + size_t sz = size * nmemb; + int r; + + assert(contents); + assert(j); + + switch (j->state) { + + case PULL_JOB_ANALYZING: + /* Let's first check what it actually is */ + + if (!GREEDY_REALLOC(j->payload, j->payload_allocated, j->payload_size + sz)) { + r = log_oom(); + goto fail; + } + + memcpy(j->payload + j->payload_size, contents, sz); + j->payload_size += sz; + + r = pull_job_detect_compression(j); + if (r < 0) + goto fail; + + break; + + case PULL_JOB_RUNNING: + + r = pull_job_write_compressed(j, contents, sz); + if (r < 0) + goto fail; + + break; + + case PULL_JOB_DONE: + case PULL_JOB_FAILED: + r = -ESTALE; + goto fail; + + default: + assert_not_reached("Impossible state."); + } + + return sz; + +fail: + pull_job_finish(j, r); + return 0; +} + +static size_t pull_job_header_callback(void *contents, size_t size, size_t nmemb, void *userdata) { + PullJob *j = userdata; + size_t sz = size * nmemb; + _cleanup_free_ char *length = NULL, *last_modified = NULL; + char *etag; + int r; + + assert(contents); + assert(j); + + if (j->state == PULL_JOB_DONE || j->state == PULL_JOB_FAILED) { + r = -ESTALE; + goto fail; + } + + assert(j->state == PULL_JOB_ANALYZING); + + r = curl_header_strdup(contents, sz, "ETag:", &etag); + if (r < 0) { + log_oom(); + goto fail; + } + if (r > 0) { + free(j->etag); + j->etag = etag; + + if (strv_contains(j->old_etags, j->etag)) { + log_info("Image already downloaded. Skipping download."); + j->etag_exists = true; + pull_job_finish(j, 0); + return sz; + } + + return sz; + } + + r = curl_header_strdup(contents, sz, "Content-Length:", &length); + if (r < 0) { + log_oom(); + goto fail; + } + if (r > 0) { + (void) safe_atou64(length, &j->content_length); + + if (j->content_length != (uint64_t) -1) { + char bytes[FORMAT_BYTES_MAX]; + + if (j->content_length > j->compressed_max) { + log_error("Content too large."); + r = -EFBIG; + goto fail; + } + + log_info("Downloading %s for %s.", format_bytes(bytes, sizeof(bytes), j->content_length), j->url); + } + + return sz; + } + + r = curl_header_strdup(contents, sz, "Last-Modified:", &last_modified); + if (r < 0) { + log_oom(); + goto fail; + } + if (r > 0) { + (void) curl_parse_http_time(last_modified, &j->mtime); + return sz; + } + + if (j->on_header) { + r = j->on_header(j, contents, sz); + if (r < 0) + goto fail; + } + + return sz; + +fail: + pull_job_finish(j, r); + return 0; +} + +static int pull_job_progress_callback(void *userdata, curl_off_t dltotal, curl_off_t dlnow, curl_off_t ultotal, curl_off_t ulnow) { + PullJob *j = userdata; + unsigned percent; + usec_t n; + + assert(j); + + if (dltotal <= 0) + return 0; + + percent = ((100 * dlnow) / dltotal); + n = now(CLOCK_MONOTONIC); + + if (n > j->last_status_usec + USEC_PER_SEC && + percent != j->progress_percent && + dlnow < dltotal) { + char buf[FORMAT_TIMESPAN_MAX]; + + if (n - j->start_usec > USEC_PER_SEC && dlnow > 0) { + char y[FORMAT_BYTES_MAX]; + usec_t left, done; + + done = n - j->start_usec; + left = (usec_t) (((double) done * (double) dltotal) / dlnow) - done; + + log_info("Got %u%% of %s. %s left at %s/s.", + percent, + j->url, + format_timespan(buf, sizeof(buf), left, USEC_PER_SEC), + format_bytes(y, sizeof(y), (uint64_t) ((double) dlnow / ((double) done / (double) USEC_PER_SEC)))); + } else + log_info("Got %u%% of %s.", percent, j->url); + + j->progress_percent = percent; + j->last_status_usec = n; + + if (j->on_progress) + j->on_progress(j); + } + + return 0; +} + +int pull_job_new(PullJob **ret, const char *url, CurlGlue *glue, void *userdata) { + _cleanup_(pull_job_unrefp) PullJob *j = NULL; + + assert(url); + assert(glue); + assert(ret); + + j = new0(PullJob, 1); + if (!j) + return -ENOMEM; + + j->state = PULL_JOB_INIT; + j->disk_fd = -1; + j->userdata = userdata; + j->glue = glue; + j->content_length = (uint64_t) -1; + j->start_usec = now(CLOCK_MONOTONIC); + j->compressed_max = j->uncompressed_max = 8LLU * 1024LLU * 1024LLU * 1024LLU; /* 8GB */ + + j->url = strdup(url); + if (!j->url) + return -ENOMEM; + + *ret = j; + j = NULL; + + return 0; +} + +int pull_job_begin(PullJob *j) { + int r; + + assert(j); + + if (j->state != PULL_JOB_INIT) + return -EBUSY; + + if (j->grow_machine_directory) + grow_machine_directory(); + + r = curl_glue_make(&j->curl, j->url, j); + if (r < 0) + return r; + + if (!strv_isempty(j->old_etags)) { + _cleanup_free_ char *cc = NULL, *hdr = NULL; + + cc = strv_join(j->old_etags, ", "); + if (!cc) + return -ENOMEM; + + hdr = strappend("If-None-Match: ", cc); + if (!hdr) + return -ENOMEM; + + if (!j->request_header) { + j->request_header = curl_slist_new(hdr, NULL); + if (!j->request_header) + return -ENOMEM; + } else { + struct curl_slist *l; + + l = curl_slist_append(j->request_header, hdr); + if (!l) + return -ENOMEM; + + j->request_header = l; + } + } + + if (j->request_header) { + if (curl_easy_setopt(j->curl, CURLOPT_HTTPHEADER, j->request_header) != CURLE_OK) + return -EIO; + } + + if (curl_easy_setopt(j->curl, CURLOPT_WRITEFUNCTION, pull_job_write_callback) != CURLE_OK) + return -EIO; + + if (curl_easy_setopt(j->curl, CURLOPT_WRITEDATA, j) != CURLE_OK) + return -EIO; + + if (curl_easy_setopt(j->curl, CURLOPT_HEADERFUNCTION, pull_job_header_callback) != CURLE_OK) + return -EIO; + + if (curl_easy_setopt(j->curl, CURLOPT_HEADERDATA, j) != CURLE_OK) + return -EIO; + + if (curl_easy_setopt(j->curl, CURLOPT_XFERINFOFUNCTION, pull_job_progress_callback) != CURLE_OK) + return -EIO; + + if (curl_easy_setopt(j->curl, CURLOPT_XFERINFODATA, j) != CURLE_OK) + return -EIO; + + if (curl_easy_setopt(j->curl, CURLOPT_NOPROGRESS, 0) != CURLE_OK) + return -EIO; + + r = curl_glue_add(j->glue, j->curl); + if (r < 0) + return r; + + j->state = PULL_JOB_ANALYZING; + + return 0; +} diff --git a/src/grp-machine/grp-import/systemd-pull/pull-job.h b/src/grp-machine/grp-import/systemd-pull/pull-job.h new file mode 100644 index 0000000000..80d7370ff1 --- /dev/null +++ b/src/grp-machine/grp-import/systemd-pull/pull-job.h @@ -0,0 +1,106 @@ +#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 "curl-util.h" +#include "import-compress.h" +#include "basic/macro.h" + +typedef struct PullJob PullJob; + +typedef void (*PullJobFinished)(PullJob *job); +typedef int (*PullJobOpenDisk)(PullJob *job); +typedef int (*PullJobHeader)(PullJob *job, const char *header, size_t sz); +typedef void (*PullJobProgress)(PullJob *job); + +typedef enum PullJobState { + PULL_JOB_INIT, + PULL_JOB_ANALYZING, /* Still reading into ->payload, to figure out what we have */ + PULL_JOB_RUNNING, /* Writing to destination */ + PULL_JOB_DONE, + PULL_JOB_FAILED, + _PULL_JOB_STATE_MAX, + _PULL_JOB_STATE_INVALID = -1, +} PullJobState; + +#define PULL_JOB_IS_COMPLETE(j) (IN_SET((j)->state, PULL_JOB_DONE, PULL_JOB_FAILED)) + +struct PullJob { + PullJobState state; + int error; + + char *url; + + void *userdata; + PullJobFinished on_finished; + PullJobOpenDisk on_open_disk; + PullJobHeader on_header; + PullJobProgress on_progress; + + CurlGlue *glue; + CURL *curl; + struct curl_slist *request_header; + + char *etag; + char **old_etags; + bool etag_exists; + + uint64_t content_length; + uint64_t written_compressed; + uint64_t written_uncompressed; + + uint64_t uncompressed_max; + uint64_t compressed_max; + + uint8_t *payload; + size_t payload_size; + size_t payload_allocated; + + int disk_fd; + + usec_t mtime; + + ImportCompress compress; + + unsigned progress_percent; + usec_t start_usec; + usec_t last_status_usec; + + bool allow_sparse; + + bool calc_checksum; + gcry_md_hd_t checksum_context; + + char *checksum; + + bool grow_machine_directory; + uint64_t written_since_last_grow; +}; + +int pull_job_new(PullJob **job, const char *url, CurlGlue *glue, void *userdata); +PullJob* pull_job_unref(PullJob *job); + +int pull_job_begin(PullJob *j); + +void pull_job_curl_on_finished(CurlGlue *g, CURL *curl, CURLcode result); + +DEFINE_TRIVIAL_CLEANUP_FUNC(PullJob*, pull_job_unref); diff --git a/src/grp-machine/grp-import/systemd-pull/pull-raw.c b/src/grp-machine/grp-import/systemd-pull/pull-raw.c new file mode 100644 index 0000000000..a9777a5838 --- /dev/null +++ b/src/grp-machine/grp-import/systemd-pull/pull-raw.c @@ -0,0 +1,651 @@ +/*** + 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 "basic/alloc-util.h" +#include "basic/btrfs-util.h" +#include "basic/chattr-util.h" +#include "basic/copy.h" +#include "curl-util.h" +#include "basic/fd-util.h" +#include "basic/fileio.h" +#include "basic/fs-util.h" +#include "basic/hostname-util.h" +#include "import-common.h" +#include "shared/import-util.h" +#include "basic/macro.h" +#include "basic/mkdir.h" +#include "basic/path-util.h" +#include "pull-common.h" +#include "pull-job.h" +#include "pull-raw.h" +#include "qcow2-util.h" +#include "basic/rm-rf.h" +#include "basic/string-util.h" +#include "basic/strv.h" +#include "basic/utf8.h" +#include "basic/util.h" +#include "basic/web-util.h" + +typedef enum RawProgress { + RAW_DOWNLOADING, + RAW_VERIFYING, + RAW_UNPACKING, + RAW_FINALIZING, + RAW_COPYING, +} RawProgress; + +struct RawPull { + sd_event *event; + CurlGlue *glue; + + char *image_root; + + PullJob *raw_job; + PullJob *settings_job; + PullJob *checksum_job; + PullJob *signature_job; + + RawPullFinished on_finished; + void *userdata; + + char *local; + bool force_local; + bool grow_machine_directory; + bool settings; + + char *final_path; + char *temp_path; + + char *settings_path; + char *settings_temp_path; + + ImportVerify verify; +}; + +RawPull* raw_pull_unref(RawPull *i) { + if (!i) + return NULL; + + pull_job_unref(i->raw_job); + pull_job_unref(i->settings_job); + pull_job_unref(i->checksum_job); + pull_job_unref(i->signature_job); + + curl_glue_unref(i->glue); + sd_event_unref(i->event); + + if (i->temp_path) { + (void) unlink(i->temp_path); + free(i->temp_path); + } + + if (i->settings_temp_path) { + (void) unlink(i->settings_temp_path); + free(i->settings_temp_path); + } + + free(i->final_path); + free(i->settings_path); + free(i->image_root); + free(i->local); + free(i); + + return NULL; +} + +int raw_pull_new( + RawPull **ret, + sd_event *event, + const char *image_root, + RawPullFinished on_finished, + void *userdata) { + + _cleanup_(raw_pull_unrefp) RawPull *i = NULL; + int r; + + assert(ret); + + i = new0(RawPull, 1); + if (!i) + return -ENOMEM; + + i->on_finished = on_finished; + i->userdata = userdata; + + i->image_root = strdup(image_root ?: "/var/lib/machines"); + if (!i->image_root) + return -ENOMEM; + + i->grow_machine_directory = path_startswith(i->image_root, "/var/lib/machines"); + + if (event) + i->event = sd_event_ref(event); + else { + r = sd_event_default(&i->event); + if (r < 0) + return r; + } + + r = curl_glue_new(&i->glue, i->event); + if (r < 0) + return r; + + i->glue->on_finished = pull_job_curl_on_finished; + i->glue->userdata = i; + + *ret = i; + i = NULL; + + return 0; +} + +static void raw_pull_report_progress(RawPull *i, RawProgress p) { + unsigned percent; + + assert(i); + + switch (p) { + + case RAW_DOWNLOADING: { + unsigned remain = 80; + + percent = 0; + + if (i->settings_job) { + percent += i->settings_job->progress_percent * 5 / 100; + remain -= 5; + } + + if (i->checksum_job) { + percent += i->checksum_job->progress_percent * 5 / 100; + remain -= 5; + } + + if (i->signature_job) { + percent += i->signature_job->progress_percent * 5 / 100; + remain -= 5; + } + + if (i->raw_job) + percent += i->raw_job->progress_percent * remain / 100; + break; + } + + case RAW_VERIFYING: + percent = 80; + break; + + case RAW_UNPACKING: + percent = 85; + break; + + case RAW_FINALIZING: + percent = 90; + break; + + case RAW_COPYING: + percent = 95; + break; + + default: + assert_not_reached("Unknown progress state"); + } + + sd_notifyf(false, "X_IMPORT_PROGRESS=%u", percent); + log_debug("Combined progress %u%%", percent); +} + +static int raw_pull_maybe_convert_qcow2(RawPull *i) { + _cleanup_close_ int converted_fd = -1; + _cleanup_free_ char *t = NULL; + int r; + + assert(i); + assert(i->raw_job); + + r = qcow2_detect(i->raw_job->disk_fd); + if (r < 0) + return log_error_errno(r, "Failed to detect whether this is a QCOW2 image: %m"); + if (r == 0) + return 0; + + /* This is a QCOW2 image, let's convert it */ + r = tempfn_random(i->final_path, NULL, &t); + if (r < 0) + return log_oom(); + + converted_fd = open(t, O_RDWR|O_CREAT|O_EXCL|O_NOCTTY|O_CLOEXEC, 0664); + if (converted_fd < 0) + return log_error_errno(errno, "Failed to create %s: %m", t); + + r = chattr_fd(converted_fd, FS_NOCOW_FL, FS_NOCOW_FL); + if (r < 0) + log_warning_errno(r, "Failed to set file attributes on %s: %m", t); + + log_info("Unpacking QCOW2 file."); + + r = qcow2_convert(i->raw_job->disk_fd, converted_fd); + if (r < 0) { + unlink(t); + return log_error_errno(r, "Failed to convert qcow2 image: %m"); + } + + (void) unlink(i->temp_path); + free(i->temp_path); + i->temp_path = t; + t = NULL; + + safe_close(i->raw_job->disk_fd); + i->raw_job->disk_fd = converted_fd; + converted_fd = -1; + + return 1; +} + +static int raw_pull_make_local_copy(RawPull *i) { + _cleanup_free_ char *tp = NULL; + _cleanup_close_ int dfd = -1; + const char *p; + int r; + + assert(i); + assert(i->raw_job); + + if (!i->local) + return 0; + + if (!i->final_path) { + r = pull_make_path(i->raw_job->url, i->raw_job->etag, i->image_root, ".raw-", ".raw", &i->final_path); + if (r < 0) + return log_oom(); + } + + if (i->raw_job->etag_exists) { + /* We have downloaded this one previously, reopen it */ + + assert(i->raw_job->disk_fd < 0); + + i->raw_job->disk_fd = open(i->final_path, O_RDONLY|O_NOCTTY|O_CLOEXEC); + if (i->raw_job->disk_fd < 0) + return log_error_errno(errno, "Failed to open vendor image: %m"); + } else { + /* We freshly downloaded the image, use it */ + + assert(i->raw_job->disk_fd >= 0); + + if (lseek(i->raw_job->disk_fd, SEEK_SET, 0) == (off_t) -1) + return log_error_errno(errno, "Failed to seek to beginning of vendor image: %m"); + } + + p = strjoina(i->image_root, "/", i->local, ".raw"); + + if (i->force_local) + (void) rm_rf(p, REMOVE_ROOT|REMOVE_PHYSICAL|REMOVE_SUBVOLUME); + + r = tempfn_random(p, NULL, &tp); + if (r < 0) + return log_oom(); + + dfd = open(tp, O_WRONLY|O_CREAT|O_EXCL|O_NOCTTY|O_CLOEXEC, 0664); + if (dfd < 0) + return log_error_errno(errno, "Failed to create writable copy of image: %m"); + + /* Turn off COW writing. This should greatly improve + * performance on COW file systems like btrfs, since it + * reduces fragmentation caused by not allowing in-place + * writes. */ + r = chattr_fd(dfd, FS_NOCOW_FL, FS_NOCOW_FL); + if (r < 0) + log_warning_errno(r, "Failed to set file attributes on %s: %m", tp); + + r = copy_bytes(i->raw_job->disk_fd, dfd, (uint64_t) -1, true); + if (r < 0) { + unlink(tp); + return log_error_errno(r, "Failed to make writable copy of image: %m"); + } + + (void) copy_times(i->raw_job->disk_fd, dfd); + (void) copy_xattr(i->raw_job->disk_fd, dfd); + + dfd = safe_close(dfd); + + r = rename(tp, p); + if (r < 0) { + r = log_error_errno(errno, "Failed to move writable image into place: %m"); + unlink(tp); + return r; + } + + log_info("Created new local image '%s'.", i->local); + + if (i->settings) { + const char *local_settings; + assert(i->settings_job); + + if (!i->settings_path) { + r = pull_make_path(i->settings_job->url, i->settings_job->etag, i->image_root, ".settings-", NULL, &i->settings_path); + if (r < 0) + return log_oom(); + } + + local_settings = strjoina(i->image_root, "/", i->local, ".nspawn"); + + r = copy_file_atomic(i->settings_path, local_settings, 0644, i->force_local, 0); + if (r == -EEXIST) + log_warning_errno(r, "Settings file %s already exists, not replacing.", local_settings); + else if (r == -ENOENT) + log_debug_errno(r, "Skipping creation of settings file, since none was found."); + else if (r < 0) + log_warning_errno(r, "Failed to copy settings files %s, ignoring: %m", local_settings); + else + log_info("Created new settings file %s.", local_settings); + } + + return 0; +} + +static bool raw_pull_is_done(RawPull *i) { + assert(i); + assert(i->raw_job); + + if (!PULL_JOB_IS_COMPLETE(i->raw_job)) + return false; + if (i->settings_job && !PULL_JOB_IS_COMPLETE(i->settings_job)) + return false; + if (i->checksum_job && !PULL_JOB_IS_COMPLETE(i->checksum_job)) + return false; + if (i->signature_job && !PULL_JOB_IS_COMPLETE(i->signature_job)) + return false; + + return true; +} + +static void raw_pull_job_on_finished(PullJob *j) { + RawPull *i; + int r; + + assert(j); + assert(j->userdata); + + i = j->userdata; + if (j == i->settings_job) { + if (j->error != 0) + log_info_errno(j->error, "Settings file could not be retrieved, proceeding without."); + } else if (j->error != 0) { + if (j == i->checksum_job) + log_error_errno(j->error, "Failed to retrieve SHA256 checksum, cannot verify. (Try --verify=no?)"); + else if (j == i->signature_job) + log_error_errno(j->error, "Failed to retrieve signature file, cannot verify. (Try --verify=no?)"); + else + log_error_errno(j->error, "Failed to retrieve image file. (Wrong URL?)"); + + r = j->error; + goto finish; + } + + /* This is invoked if either the download completed + * successfully, or the download was skipped because we + * already have the etag. In this case ->etag_exists is + * true. + * + * We only do something when we got all three files */ + + if (!raw_pull_is_done(i)) + return; + + if (i->settings_job) + i->settings_job->disk_fd = safe_close(i->settings_job->disk_fd); + + if (!i->raw_job->etag_exists) { + /* This is a new download, verify it, and move it into place */ + assert(i->raw_job->disk_fd >= 0); + + raw_pull_report_progress(i, RAW_VERIFYING); + + r = pull_verify(i->raw_job, i->settings_job, i->checksum_job, i->signature_job); + if (r < 0) + goto finish; + + raw_pull_report_progress(i, RAW_UNPACKING); + + r = raw_pull_maybe_convert_qcow2(i); + if (r < 0) + goto finish; + + raw_pull_report_progress(i, RAW_FINALIZING); + + r = import_make_read_only_fd(i->raw_job->disk_fd); + if (r < 0) + goto finish; + + r = rename_noreplace(AT_FDCWD, i->temp_path, AT_FDCWD, i->final_path); + if (r < 0) { + log_error_errno(r, "Failed to move RAW file into place: %m"); + goto finish; + } + + i->temp_path = mfree(i->temp_path); + + if (i->settings_job && + i->settings_job->error == 0 && + !i->settings_job->etag_exists) { + + assert(i->settings_temp_path); + assert(i->settings_path); + + r = import_make_read_only(i->settings_temp_path); + if (r < 0) + goto finish; + + r = rename_noreplace(AT_FDCWD, i->settings_temp_path, AT_FDCWD, i->settings_path); + if (r < 0) { + log_error_errno(r, "Failed to rename settings file: %m"); + goto finish; + } + + i->settings_temp_path = mfree(i->settings_temp_path); + } + } + + raw_pull_report_progress(i, RAW_COPYING); + + r = raw_pull_make_local_copy(i); + if (r < 0) + goto finish; + + r = 0; + +finish: + if (i->on_finished) + i->on_finished(i, r, i->userdata); + else + sd_event_exit(i->event, r); +} + +static int raw_pull_job_on_open_disk_raw(PullJob *j) { + RawPull *i; + int r; + + assert(j); + assert(j->userdata); + + i = j->userdata; + assert(i->raw_job == j); + assert(!i->final_path); + assert(!i->temp_path); + + r = pull_make_path(j->url, j->etag, i->image_root, ".raw-", ".raw", &i->final_path); + if (r < 0) + return log_oom(); + + r = tempfn_random(i->final_path, NULL, &i->temp_path); + if (r < 0) + return log_oom(); + + (void) mkdir_parents_label(i->temp_path, 0700); + + j->disk_fd = open(i->temp_path, O_RDWR|O_CREAT|O_EXCL|O_NOCTTY|O_CLOEXEC, 0664); + if (j->disk_fd < 0) + return log_error_errno(errno, "Failed to create %s: %m", i->temp_path); + + r = chattr_fd(j->disk_fd, FS_NOCOW_FL, FS_NOCOW_FL); + if (r < 0) + log_warning_errno(r, "Failed to set file attributes on %s: %m", i->temp_path); + + return 0; +} + +static int raw_pull_job_on_open_disk_settings(PullJob *j) { + RawPull *i; + int r; + + assert(j); + assert(j->userdata); + + i = j->userdata; + assert(i->settings_job == j); + assert(!i->settings_path); + assert(!i->settings_temp_path); + + r = pull_make_path(j->url, j->etag, i->image_root, ".settings-", NULL, &i->settings_path); + if (r < 0) + return log_oom(); + + r = tempfn_random(i->settings_path, NULL, &i->settings_temp_path); + if (r < 0) + return log_oom(); + + mkdir_parents_label(i->settings_temp_path, 0700); + + j->disk_fd = open(i->settings_temp_path, O_RDWR|O_CREAT|O_EXCL|O_NOCTTY|O_CLOEXEC, 0664); + if (j->disk_fd < 0) + return log_error_errno(errno, "Failed to create %s: %m", i->settings_temp_path); + + return 0; +} + +static void raw_pull_job_on_progress(PullJob *j) { + RawPull *i; + + assert(j); + assert(j->userdata); + + i = j->userdata; + + raw_pull_report_progress(i, RAW_DOWNLOADING); +} + +int raw_pull_start( + RawPull *i, + const char *url, + const char *local, + bool force_local, + ImportVerify verify, + bool settings) { + + int r; + + assert(i); + assert(verify < _IMPORT_VERIFY_MAX); + assert(verify >= 0); + + if (!http_url_is_valid(url)) + return -EINVAL; + + if (local && !machine_name_is_valid(local)) + return -EINVAL; + + if (i->raw_job) + return -EBUSY; + + r = free_and_strdup(&i->local, local); + if (r < 0) + return r; + + i->force_local = force_local; + i->verify = verify; + i->settings = settings; + + /* Queue job for the image itself */ + r = pull_job_new(&i->raw_job, url, i->glue, i); + if (r < 0) + return r; + + i->raw_job->on_finished = raw_pull_job_on_finished; + i->raw_job->on_open_disk = raw_pull_job_on_open_disk_raw; + i->raw_job->on_progress = raw_pull_job_on_progress; + i->raw_job->calc_checksum = verify != IMPORT_VERIFY_NO; + i->raw_job->grow_machine_directory = i->grow_machine_directory; + + r = pull_find_old_etags(url, i->image_root, DT_REG, ".raw-", ".raw", &i->raw_job->old_etags); + if (r < 0) + return r; + + if (settings) { + r = pull_make_settings_job(&i->settings_job, url, i->glue, raw_pull_job_on_finished, i); + if (r < 0) + return r; + + i->settings_job->on_open_disk = raw_pull_job_on_open_disk_settings; + i->settings_job->on_progress = raw_pull_job_on_progress; + i->settings_job->calc_checksum = verify != IMPORT_VERIFY_NO; + + r = pull_find_old_etags(i->settings_job->url, i->image_root, DT_REG, ".settings-", NULL, &i->settings_job->old_etags); + if (r < 0) + return r; + } + + r = pull_make_verification_jobs(&i->checksum_job, &i->signature_job, verify, url, i->glue, raw_pull_job_on_finished, i); + if (r < 0) + return r; + + r = pull_job_begin(i->raw_job); + if (r < 0) + return r; + + if (i->settings_job) { + r = pull_job_begin(i->settings_job); + if (r < 0) + return r; + } + + if (i->checksum_job) { + i->checksum_job->on_progress = raw_pull_job_on_progress; + + r = pull_job_begin(i->checksum_job); + if (r < 0) + return r; + } + + if (i->signature_job) { + i->signature_job->on_progress = raw_pull_job_on_progress; + + r = pull_job_begin(i->signature_job); + if (r < 0) + return r; + } + + return 0; +} diff --git a/src/grp-machine/grp-import/systemd-pull/pull-raw.h b/src/grp-machine/grp-import/systemd-pull/pull-raw.h new file mode 100644 index 0000000000..84cd146647 --- /dev/null +++ b/src/grp-machine/grp-import/systemd-pull/pull-raw.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 + +#include "shared/import-util.h" +#include "basic/macro.h" + +typedef struct RawPull RawPull; + +typedef void (*RawPullFinished)(RawPull *pull, int error, void *userdata); + +int raw_pull_new(RawPull **pull, sd_event *event, const char *image_root, RawPullFinished on_finished, void *userdata); +RawPull* raw_pull_unref(RawPull *pull); + +DEFINE_TRIVIAL_CLEANUP_FUNC(RawPull*, raw_pull_unref); + +int raw_pull_start(RawPull *pull, const char *url, const char *local, bool force_local, ImportVerify verify, bool settings); diff --git a/src/grp-machine/grp-import/systemd-pull/pull-tar.c b/src/grp-machine/grp-import/systemd-pull/pull-tar.c new file mode 100644 index 0000000000..576bda5a3d --- /dev/null +++ b/src/grp-machine/grp-import/systemd-pull/pull-tar.c @@ -0,0 +1,563 @@ +/*** + 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 "basic/alloc-util.h" +#include "basic/btrfs-util.h" +#include "basic/copy.h" +#include "curl-util.h" +#include "basic/fd-util.h" +#include "basic/fileio.h" +#include "basic/fs-util.h" +#include "basic/hostname-util.h" +#include "import-common.h" +#include "shared/import-util.h" +#include "basic/macro.h" +#include "basic/mkdir.h" +#include "basic/path-util.h" +#include "basic/process-util.h" +#include "pull-common.h" +#include "pull-job.h" +#include "pull-tar.h" +#include "basic/rm-rf.h" +#include "basic/string-util.h" +#include "basic/strv.h" +#include "basic/utf8.h" +#include "basic/util.h" +#include "basic/web-util.h" + +typedef enum TarProgress { + TAR_DOWNLOADING, + TAR_VERIFYING, + TAR_FINALIZING, + TAR_COPYING, +} TarProgress; + +struct TarPull { + sd_event *event; + CurlGlue *glue; + + char *image_root; + + PullJob *tar_job; + PullJob *settings_job; + PullJob *checksum_job; + PullJob *signature_job; + + TarPullFinished on_finished; + void *userdata; + + char *local; + bool force_local; + bool grow_machine_directory; + bool settings; + + pid_t tar_pid; + + char *final_path; + char *temp_path; + + char *settings_path; + char *settings_temp_path; + + ImportVerify verify; +}; + +TarPull* tar_pull_unref(TarPull *i) { + if (!i) + return NULL; + + if (i->tar_pid > 1) { + (void) kill_and_sigcont(i->tar_pid, SIGKILL); + (void) wait_for_terminate(i->tar_pid, NULL); + } + + pull_job_unref(i->tar_job); + pull_job_unref(i->settings_job); + pull_job_unref(i->checksum_job); + pull_job_unref(i->signature_job); + + curl_glue_unref(i->glue); + sd_event_unref(i->event); + + if (i->temp_path) { + (void) rm_rf(i->temp_path, REMOVE_ROOT|REMOVE_PHYSICAL|REMOVE_SUBVOLUME); + free(i->temp_path); + } + + if (i->settings_temp_path) { + (void) unlink(i->settings_temp_path); + free(i->settings_temp_path); + } + + free(i->final_path); + free(i->settings_path); + free(i->image_root); + free(i->local); + free(i); + + return NULL; +} + +int tar_pull_new( + TarPull **ret, + sd_event *event, + const char *image_root, + TarPullFinished on_finished, + void *userdata) { + + _cleanup_(tar_pull_unrefp) TarPull *i = NULL; + int r; + + assert(ret); + + i = new0(TarPull, 1); + if (!i) + return -ENOMEM; + + i->on_finished = on_finished; + i->userdata = userdata; + + i->image_root = strdup(image_root ?: "/var/lib/machines"); + if (!i->image_root) + return -ENOMEM; + + i->grow_machine_directory = path_startswith(i->image_root, "/var/lib/machines"); + + if (event) + i->event = sd_event_ref(event); + else { + r = sd_event_default(&i->event); + if (r < 0) + return r; + } + + r = curl_glue_new(&i->glue, i->event); + if (r < 0) + return r; + + i->glue->on_finished = pull_job_curl_on_finished; + i->glue->userdata = i; + + *ret = i; + i = NULL; + + return 0; +} + +static void tar_pull_report_progress(TarPull *i, TarProgress p) { + unsigned percent; + + assert(i); + + switch (p) { + + case TAR_DOWNLOADING: { + unsigned remain = 85; + + percent = 0; + + if (i->settings_job) { + percent += i->settings_job->progress_percent * 5 / 100; + remain -= 5; + } + + if (i->checksum_job) { + percent += i->checksum_job->progress_percent * 5 / 100; + remain -= 5; + } + + if (i->signature_job) { + percent += i->signature_job->progress_percent * 5 / 100; + remain -= 5; + } + + if (i->tar_job) + percent += i->tar_job->progress_percent * remain / 100; + break; + } + + case TAR_VERIFYING: + percent = 85; + break; + + case TAR_FINALIZING: + percent = 90; + break; + + case TAR_COPYING: + percent = 95; + break; + + default: + assert_not_reached("Unknown progress state"); + } + + sd_notifyf(false, "X_IMPORT_PROGRESS=%u", percent); + log_debug("Combined progress %u%%", percent); +} + +static int tar_pull_make_local_copy(TarPull *i) { + int r; + + assert(i); + assert(i->tar_job); + + if (!i->local) + return 0; + + if (!i->final_path) { + r = pull_make_path(i->tar_job->url, i->tar_job->etag, i->image_root, ".tar-", NULL, &i->final_path); + if (r < 0) + return log_oom(); + } + + r = pull_make_local_copy(i->final_path, i->image_root, i->local, i->force_local); + if (r < 0) + return r; + + if (i->settings) { + const char *local_settings; + assert(i->settings_job); + + if (!i->settings_path) { + r = pull_make_path(i->settings_job->url, i->settings_job->etag, i->image_root, ".settings-", NULL, &i->settings_path); + if (r < 0) + return log_oom(); + } + + local_settings = strjoina(i->image_root, "/", i->local, ".nspawn"); + + r = copy_file_atomic(i->settings_path, local_settings, 0664, i->force_local, 0); + if (r == -EEXIST) + log_warning_errno(r, "Settings file %s already exists, not replacing.", local_settings); + else if (r == -ENOENT) + log_debug_errno(r, "Skipping creation of settings file, since none was found."); + else if (r < 0) + log_warning_errno(r, "Failed to copy settings files %s, ignoring: %m", local_settings); + else + log_info("Created new settings file %s.", local_settings); + } + + return 0; +} + +static bool tar_pull_is_done(TarPull *i) { + assert(i); + assert(i->tar_job); + + if (!PULL_JOB_IS_COMPLETE(i->tar_job)) + return false; + if (i->settings_job && !PULL_JOB_IS_COMPLETE(i->settings_job)) + return false; + if (i->checksum_job && !PULL_JOB_IS_COMPLETE(i->checksum_job)) + return false; + if (i->signature_job && !PULL_JOB_IS_COMPLETE(i->signature_job)) + return false; + + return true; +} + +static void tar_pull_job_on_finished(PullJob *j) { + TarPull *i; + int r; + + assert(j); + assert(j->userdata); + + i = j->userdata; + + if (j == i->settings_job) { + if (j->error != 0) + log_info_errno(j->error, "Settings file could not be retrieved, proceeding without."); + } else if (j->error != 0) { + if (j == i->checksum_job) + log_error_errno(j->error, "Failed to retrieve SHA256 checksum, cannot verify. (Try --verify=no?)"); + else if (j == i->signature_job) + log_error_errno(j->error, "Failed to retrieve signature file, cannot verify. (Try --verify=no?)"); + else + log_error_errno(j->error, "Failed to retrieve image file. (Wrong URL?)"); + + r = j->error; + goto finish; + } + + /* This is invoked if either the download completed + * successfully, or the download was skipped because we + * already have the etag. */ + + if (!tar_pull_is_done(i)) + return; + + i->tar_job->disk_fd = safe_close(i->tar_job->disk_fd); + if (i->settings_job) + i->settings_job->disk_fd = safe_close(i->settings_job->disk_fd); + + if (i->tar_pid > 0) { + r = wait_for_terminate_and_warn("tar", i->tar_pid, true); + i->tar_pid = 0; + if (r < 0) + goto finish; + if (r > 0) { + r = -EIO; + goto finish; + } + } + + if (!i->tar_job->etag_exists) { + /* This is a new download, verify it, and move it into place */ + + tar_pull_report_progress(i, TAR_VERIFYING); + + r = pull_verify(i->tar_job, i->settings_job, i->checksum_job, i->signature_job); + if (r < 0) + goto finish; + + tar_pull_report_progress(i, TAR_FINALIZING); + + r = import_make_read_only(i->temp_path); + if (r < 0) + goto finish; + + r = rename_noreplace(AT_FDCWD, i->temp_path, AT_FDCWD, i->final_path); + if (r < 0) { + log_error_errno(r, "Failed to rename to final image name: %m"); + goto finish; + } + + i->temp_path = mfree(i->temp_path); + + if (i->settings_job && + i->settings_job->error == 0 && + !i->settings_job->etag_exists) { + + assert(i->settings_temp_path); + assert(i->settings_path); + + /* Also move the settings file into place, if + * it exist. Note that we do so only if we + * also moved the tar file in place, to keep + * things strictly in sync. */ + + r = import_make_read_only(i->settings_temp_path); + if (r < 0) + goto finish; + + r = rename_noreplace(AT_FDCWD, i->settings_temp_path, AT_FDCWD, i->settings_path); + if (r < 0) { + log_error_errno(r, "Failed to rename settings file: %m"); + goto finish; + } + + i->settings_temp_path = mfree(i->settings_temp_path); + } + } + + tar_pull_report_progress(i, TAR_COPYING); + + r = tar_pull_make_local_copy(i); + if (r < 0) + goto finish; + + r = 0; + +finish: + if (i->on_finished) + i->on_finished(i, r, i->userdata); + else + sd_event_exit(i->event, r); +} + +static int tar_pull_job_on_open_disk_tar(PullJob *j) { + TarPull *i; + int r; + + assert(j); + assert(j->userdata); + + i = j->userdata; + assert(i->tar_job == j); + assert(!i->final_path); + assert(!i->temp_path); + assert(i->tar_pid <= 0); + + r = pull_make_path(j->url, j->etag, i->image_root, ".tar-", NULL, &i->final_path); + if (r < 0) + return log_oom(); + + r = tempfn_random(i->final_path, NULL, &i->temp_path); + if (r < 0) + return log_oom(); + + mkdir_parents_label(i->temp_path, 0700); + + r = btrfs_subvol_make(i->temp_path); + if (r == -ENOTTY) { + if (mkdir(i->temp_path, 0755) < 0) + return log_error_errno(errno, "Failed to create directory %s: %m", i->temp_path); + } else if (r < 0) + return log_error_errno(r, "Failed to create subvolume %s: %m", i->temp_path); + else + (void) import_assign_pool_quota_and_warn(i->temp_path); + + j->disk_fd = import_fork_tar_x(i->temp_path, &i->tar_pid); + if (j->disk_fd < 0) + return j->disk_fd; + + return 0; +} + +static int tar_pull_job_on_open_disk_settings(PullJob *j) { + TarPull *i; + int r; + + assert(j); + assert(j->userdata); + + i = j->userdata; + assert(i->settings_job == j); + assert(!i->settings_path); + assert(!i->settings_temp_path); + + r = pull_make_path(j->url, j->etag, i->image_root, ".settings-", NULL, &i->settings_path); + if (r < 0) + return log_oom(); + + r = tempfn_random(i->settings_path, NULL, &i->settings_temp_path); + if (r < 0) + return log_oom(); + + mkdir_parents_label(i->settings_temp_path, 0700); + + j->disk_fd = open(i->settings_temp_path, O_RDWR|O_CREAT|O_EXCL|O_NOCTTY|O_CLOEXEC, 0664); + if (j->disk_fd < 0) + return log_error_errno(errno, "Failed to create %s: %m", i->settings_temp_path); + + return 0; +} + +static void tar_pull_job_on_progress(PullJob *j) { + TarPull *i; + + assert(j); + assert(j->userdata); + + i = j->userdata; + + tar_pull_report_progress(i, TAR_DOWNLOADING); +} + +int tar_pull_start( + TarPull *i, + const char *url, + const char *local, + bool force_local, + ImportVerify verify, + bool settings) { + + int r; + + assert(i); + assert(verify < _IMPORT_VERIFY_MAX); + assert(verify >= 0); + + if (!http_url_is_valid(url)) + return -EINVAL; + + if (local && !machine_name_is_valid(local)) + return -EINVAL; + + if (i->tar_job) + return -EBUSY; + + r = free_and_strdup(&i->local, local); + if (r < 0) + return r; + + i->force_local = force_local; + i->verify = verify; + i->settings = settings; + + /* Set up download job for TAR file */ + r = pull_job_new(&i->tar_job, url, i->glue, i); + if (r < 0) + return r; + + i->tar_job->on_finished = tar_pull_job_on_finished; + i->tar_job->on_open_disk = tar_pull_job_on_open_disk_tar; + i->tar_job->on_progress = tar_pull_job_on_progress; + i->tar_job->calc_checksum = verify != IMPORT_VERIFY_NO; + i->tar_job->grow_machine_directory = i->grow_machine_directory; + + r = pull_find_old_etags(url, i->image_root, DT_DIR, ".tar-", NULL, &i->tar_job->old_etags); + if (r < 0) + return r; + + /* Set up download job for the settings file (.nspawn) */ + if (settings) { + r = pull_make_settings_job(&i->settings_job, url, i->glue, tar_pull_job_on_finished, i); + if (r < 0) + return r; + + i->settings_job->on_open_disk = tar_pull_job_on_open_disk_settings; + i->settings_job->on_progress = tar_pull_job_on_progress; + i->settings_job->calc_checksum = verify != IMPORT_VERIFY_NO; + + r = pull_find_old_etags(i->settings_job->url, i->image_root, DT_REG, ".settings-", NULL, &i->settings_job->old_etags); + if (r < 0) + return r; + } + + /* Set up download of checksum/signature files */ + r = pull_make_verification_jobs(&i->checksum_job, &i->signature_job, verify, url, i->glue, tar_pull_job_on_finished, i); + if (r < 0) + return r; + + r = pull_job_begin(i->tar_job); + if (r < 0) + return r; + + if (i->settings_job) { + r = pull_job_begin(i->settings_job); + if (r < 0) + return r; + } + + if (i->checksum_job) { + i->checksum_job->on_progress = tar_pull_job_on_progress; + + r = pull_job_begin(i->checksum_job); + if (r < 0) + return r; + } + + if (i->signature_job) { + i->signature_job->on_progress = tar_pull_job_on_progress; + + r = pull_job_begin(i->signature_job); + if (r < 0) + return r; + } + + return 0; +} diff --git a/src/grp-machine/grp-import/systemd-pull/pull-tar.h b/src/grp-machine/grp-import/systemd-pull/pull-tar.h new file mode 100644 index 0000000000..addd1f861c --- /dev/null +++ b/src/grp-machine/grp-import/systemd-pull/pull-tar.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 "shared/import-util.h" +#include "basic/macro.h" + +typedef struct TarPull TarPull; + +typedef void (*TarPullFinished)(TarPull *pull, int error, void *userdata); + +int tar_pull_new(TarPull **pull, sd_event *event, const char *image_root, TarPullFinished on_finished, void *userdata); +TarPull* tar_pull_unref(TarPull *pull); + +DEFINE_TRIVIAL_CLEANUP_FUNC(TarPull*, tar_pull_unref); + +int tar_pull_start(TarPull *pull, const char *url, const char *local, bool force_local, ImportVerify verify, bool settings); diff --git a/src/grp-machine/grp-import/systemd-pull/pull.c b/src/grp-machine/grp-import/systemd-pull/pull.c new file mode 100644 index 0000000000..fa2af2fc02 --- /dev/null +++ b/src/grp-machine/grp-import/systemd-pull/pull.c @@ -0,0 +1,337 @@ +/*** + 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 "basic/alloc-util.h" +#include "basic/hostname-util.h" +#include "shared/import-util.h" +#include "shared/machine-image.h" +#include "basic/parse-util.h" +#include "pull-raw.h" +#include "pull-tar.h" +#include "basic/signal-util.h" +#include "basic/string-util.h" +#include "basic/verbs.h" +#include "basic/web-util.h" + +static bool arg_force = false; +static const char *arg_image_root = "/var/lib/machines"; +static ImportVerify arg_verify = IMPORT_VERIFY_SIGNATURE; +static bool arg_settings = true; + +static int interrupt_signal_handler(sd_event_source *s, const struct signalfd_siginfo *si, void *userdata) { + log_notice("Transfer aborted."); + sd_event_exit(sd_event_source_get_event(s), EINTR); + return 0; +} + +static void on_tar_finished(TarPull *pull, int error, void *userdata) { + sd_event *event = userdata; + assert(pull); + + if (error == 0) + log_info("Operation completed successfully."); + + sd_event_exit(event, abs(error)); +} + +static int pull_tar(int argc, char *argv[], void *userdata) { + _cleanup_(tar_pull_unrefp) TarPull *pull = NULL; + _cleanup_(sd_event_unrefp) sd_event *event = NULL; + const char *url, *local; + _cleanup_free_ char *l = NULL, *ll = NULL; + int r; + + url = argv[1]; + if (!http_url_is_valid(url)) { + log_error("URL '%s' is not valid.", url); + return -EINVAL; + } + + if (argc >= 3) + local = argv[2]; + else { + r = import_url_last_component(url, &l); + if (r < 0) + return log_error_errno(r, "Failed 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 image name '%s' is not valid.", local); + return -EINVAL; + } + + if (!arg_force) { + r = image_find(local, NULL); + if (r < 0) + return log_error_errno(r, "Failed to check whether image '%s' exists: %m", local); + else if (r > 0) { + log_error_errno(EEXIST, "Image '%s' already exists.", local); + return -EEXIST; + } + } + + log_info("Pulling '%s', saving as '%s'.", url, local); + } else + log_info("Pulling '%s'.", url); + + r = sd_event_default(&event); + if (r < 0) + return log_error_errno(r, "Failed to allocate event loop: %m"); + + assert_se(sigprocmask_many(SIG_BLOCK, NULL, SIGTERM, SIGINT, -1) >= 0); + (void) sd_event_add_signal(event, NULL, SIGTERM, interrupt_signal_handler, NULL); + (void) sd_event_add_signal(event, NULL, SIGINT, interrupt_signal_handler, NULL); + + r = tar_pull_new(&pull, event, arg_image_root, on_tar_finished, event); + if (r < 0) + return log_error_errno(r, "Failed to allocate puller: %m"); + + r = tar_pull_start(pull, url, local, arg_force, arg_verify, arg_settings); + if (r < 0) + return log_error_errno(r, "Failed to pull image: %m"); + + r = sd_event_loop(event); + if (r < 0) + return log_error_errno(r, "Failed to run event loop: %m"); + + log_info("Exiting."); + return -r; +} + +static void on_raw_finished(RawPull *pull, int error, void *userdata) { + sd_event *event = userdata; + assert(pull); + + if (error == 0) + log_info("Operation completed successfully."); + + sd_event_exit(event, abs(error)); +} + +static int pull_raw(int argc, char *argv[], void *userdata) { + _cleanup_(raw_pull_unrefp) RawPull *pull = NULL; + _cleanup_(sd_event_unrefp) sd_event *event = NULL; + const char *url, *local; + _cleanup_free_ char *l = NULL, *ll = NULL; + int r; + + url = argv[1]; + if (!http_url_is_valid(url)) { + log_error("URL '%s' is not valid.", url); + return -EINVAL; + } + + if (argc >= 3) + local = argv[2]; + else { + r = import_url_last_component(url, &l); + if (r < 0) + return log_error_errno(r, "Failed 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 image name '%s' is not valid.", local); + return -EINVAL; + } + + if (!arg_force) { + r = image_find(local, NULL); + if (r < 0) + return log_error_errno(r, "Failed to check whether image '%s' exists: %m", local); + else if (r > 0) { + log_error_errno(EEXIST, "Image '%s' already exists.", local); + return -EEXIST; + } + } + + log_info("Pulling '%s', saving as '%s'.", url, local); + } else + log_info("Pulling '%s'.", url); + + r = sd_event_default(&event); + if (r < 0) + return log_error_errno(r, "Failed to allocate event loop: %m"); + + assert_se(sigprocmask_many(SIG_BLOCK, NULL, SIGTERM, SIGINT, -1) >= 0); + (void) sd_event_add_signal(event, NULL, SIGTERM, interrupt_signal_handler, NULL); + (void) sd_event_add_signal(event, NULL, SIGINT, interrupt_signal_handler, NULL); + + r = raw_pull_new(&pull, event, arg_image_root, on_raw_finished, event); + if (r < 0) + return log_error_errno(r, "Failed to allocate puller: %m"); + + r = raw_pull_start(pull, url, local, arg_force, arg_verify, arg_settings); + if (r < 0) + return log_error_errno(r, "Failed to pull image: %m"); + + r = sd_event_loop(event); + if (r < 0) + return log_error_errno(r, "Failed to run event loop: %m"); + + log_info("Exiting."); + return -r; +} + +static int help(int argc, char *argv[], void *userdata) { + + printf("%s [OPTIONS...] {COMMAND} ...\n\n" + "Download container or virtual machine images.\n\n" + " -h --help Show this help\n" + " --version Show package version\n" + " --force Force creation of image\n" + " --verify=MODE Verify downloaded image, one of: 'no',\n" + " 'checksum', 'signature'\n" + " --settings=BOOL Download settings file with image\n" + " --image-root=PATH Image root directory\n\n" + "Commands:\n" + " tar URL [NAME] Download a TAR image\n" + " raw URL [NAME] Download a RAW image\n", + program_invocation_short_name); + + return 0; +} + +static int parse_argv(int argc, char *argv[]) { + + enum { + ARG_VERSION = 0x100, + ARG_FORCE, + ARG_IMAGE_ROOT, + ARG_VERIFY, + ARG_SETTINGS, + }; + + static const struct option options[] = { + { "help", no_argument, NULL, 'h' }, + { "version", no_argument, NULL, ARG_VERSION }, + { "force", no_argument, NULL, ARG_FORCE }, + { "image-root", required_argument, NULL, ARG_IMAGE_ROOT }, + { "verify", required_argument, NULL, ARG_VERIFY }, + { "settings", required_argument, NULL, ARG_SETTINGS }, + {} + }; + + int c, r; + + assert(argc >= 0); + assert(argv); + + while ((c = getopt_long(argc, argv, "h", options, NULL)) >= 0) + + switch (c) { + + case 'h': + return help(0, NULL, NULL); + + case ARG_VERSION: + return version(); + + case ARG_FORCE: + arg_force = true; + break; + + case ARG_IMAGE_ROOT: + arg_image_root = optarg; + break; + + case ARG_VERIFY: + arg_verify = import_verify_from_string(optarg); + if (arg_verify < 0) { + log_error("Invalid verification setting '%s'", optarg); + return -EINVAL; + } + + break; + + case ARG_SETTINGS: + r = parse_boolean(optarg); + if (r < 0) + return log_error_errno(r, "Failed to parse --settings= parameter '%s'", optarg); + + arg_settings = r; + break; + + case '?': + return -EINVAL; + + default: + assert_not_reached("Unhandled option"); + } + + return 1; +} + +static int pull_main(int argc, char *argv[]) { + + static const Verb verbs[] = { + { "help", VERB_ANY, VERB_ANY, 0, help }, + { "tar", 2, 3, 0, pull_tar }, + { "raw", 2, 3, 0, pull_raw }, + {} + }; + + return dispatch_verb(argc, argv, verbs, NULL); +} + +int main(int argc, char *argv[]) { + int r; + + setlocale(LC_ALL, ""); + log_parse_environment(); + log_open(); + + r = parse_argv(argc, argv); + if (r <= 0) + goto finish; + + (void) ignore_signals(SIGPIPE, -1); + + r = pull_main(argc, argv); + +finish: + return r < 0 ? EXIT_FAILURE : EXIT_SUCCESS; +} diff --git a/src/grp-machine/grp-import/test-qcow2.c b/src/grp-machine/grp-import/test-qcow2.c new file mode 100644 index 0000000000..0a256d7f41 --- /dev/null +++ b/src/grp-machine/grp-import/test-qcow2.c @@ -0,0 +1,53 @@ +/*** + 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 "basic/fd-util.h" +#include "basic/log.h" +#include "qcow2-util.h" +#include "basic/util.h" + +int main(int argc, char *argv[]) { + _cleanup_close_ int sfd = -1, dfd = -1; + int r; + + if (argc != 3) { + log_error("Needs two arguments."); + return EXIT_FAILURE; + } + + sfd = open(argv[1], O_RDONLY|O_CLOEXEC|O_NOCTTY); + if (sfd < 0) { + log_error_errno(errno, "Can't open source file: %m"); + return EXIT_FAILURE; + } + + dfd = open(argv[2], O_WRONLY|O_CREAT|O_CLOEXEC|O_NOCTTY, 0666); + if (dfd < 0) { + log_error_errno(errno, "Can't open destination file: %m"); + return EXIT_FAILURE; + } + + r = qcow2_convert(sfd, dfd); + if (r < 0) { + log_error_errno(r, "Failed to unpack: %m"); + return EXIT_FAILURE; + } + + return EXIT_SUCCESS; +} diff --git a/src/grp-system/Makefile b/src/grp-system/Makefile index ea8b031596..3ef4fd8c0e 100644 --- a/src/grp-system/Makefile +++ b/src/grp-system/Makefile @@ -23,6 +23,7 @@ include $(dir $(lastword $(MAKEFILE_LIST)))/../../config.mk include $(topsrcdir)/build-aux/Makefile.head.mk -at.subdirs += systemctl systemd +at.subdirs += systemctl systemd libcore +at.subdirs += grp-utils include $(topsrcdir)/build-aux/Makefile.tail.mk diff --git a/src/grp-system/grp-utils/Makefile b/src/grp-system/grp-utils/Makefile new file mode 100644 index 0000000000..9573b5750c --- /dev/null +++ b/src/grp-system/grp-utils/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 + +at.subdirs += systemd-analyze +at.subdirs += systemd-delta +at.subdirs += systemd-fstab-generator +at.subdirs += systemd-run +at.subdirs += systemd-sysv-generator + +include $(topsrcdir)/build-aux/Makefile.tail.mk diff --git a/src/grp-system/grp-utils/systemd-analyze/.gitignore b/src/grp-system/grp-utils/systemd-analyze/.gitignore new file mode 100644 index 0000000000..752ea236c8 --- /dev/null +++ b/src/grp-system/grp-utils/systemd-analyze/.gitignore @@ -0,0 +1 @@ +/systemd-analyze diff --git a/src/grp-system/grp-utils/systemd-analyze/Makefile b/src/grp-system/grp-utils/systemd-analyze/Makefile new file mode 100644 index 0000000000..9d647e5a52 --- /dev/null +++ b/src/grp-system/grp-utils/systemd-analyze/Makefile @@ -0,0 +1,40 @@ +# -*- 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 + +bin_PROGRAMS += systemd-analyze +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/grp-system/grp-utils/systemd-analyze/analyze-verify.c b/src/grp-system/grp-utils/systemd-analyze/analyze-verify.c new file mode 100644 index 0000000000..7553574f53 --- /dev/null +++ b/src/grp-system/grp-utils/systemd-analyze/analyze-verify.c @@ -0,0 +1,304 @@ +/*** + 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 "basic/alloc-util.h" +#include "analyze-verify.h" +#include "sd-bus/bus-error.h" +#include "shared/bus-util.h" +#include "basic/log.h" +#include "manager.h" +#include "shared/pager.h" +#include "basic/path-util.h" +#include "basic/strv.h" +#include "basic/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, UnitFileScope scope, bool check_man) { + _cleanup_(sd_bus_error_free) sd_bus_error err = SD_BUS_ERROR_NULL; + _cleanup_free_ char *var = NULL; + Manager *m = NULL; + FILE *serial = NULL; + FDSet *fdset = 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(scope, 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/grp-system/grp-utils/systemd-analyze/analyze-verify.h b/src/grp-system/grp-utils/systemd-analyze/analyze-verify.h new file mode 100644 index 0000000000..7b89007fd0 --- /dev/null +++ b/src/grp-system/grp-utils/systemd-analyze/analyze-verify.h @@ -0,0 +1,26 @@ +#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 + +#include "shared/path-lookup.h" + +int verify_units(char **filenames, UnitFileScope scope, bool check_man); diff --git a/src/grp-system/grp-utils/systemd-analyze/analyze.c b/src/grp-system/grp-utils/systemd-analyze/analyze.c new file mode 100644 index 0000000000..1ce700ccba --- /dev/null +++ b/src/grp-system/grp-utils/systemd-analyze/analyze.c @@ -0,0 +1,1485 @@ +/*** + 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 "basic/alloc-util.h" +#include "analyze-verify.h" +#include "sd-bus/bus-error.h" +#include "shared/bus-unit-util.h" +#include "shared/bus-util.h" +#include "basic/glob-util.h" +#include "basic/hashmap.h" +#include "basic/locale-util.h" +#include "basic/log.h" +#include "shared/pager.h" +#include "basic/parse-util.h" +#include "basic/special.h" +#include "basic/strv.h" +#include "basic/strxcpyx.h" +#include "basic/terminal-util.h" +#include "basic/unit-name.h" +#include "basic/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 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", special_glyph(branches & (1 << (i-1)) ? TREE_VERTICAL : TREE_SPACE)); + + printf("%s", special_glyph(last ? TREE_RIGHT : 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(arg_no_pager, false); + + 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(arg_no_pager, false); + + 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(arg_no_pager, false); + + 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(arg_no_pager, false); + + 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 ? UNIT_FILE_USER : UNIT_FILE_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/grp-system/grp-utils/systemd-delta/Makefile b/src/grp-system/grp-utils/systemd-delta/Makefile new file mode 100644 index 0000000000..4f5610d27a --- /dev/null +++ b/src/grp-system/grp-utils/systemd-delta/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 + +bin_PROGRAMS += systemd-delta +systemd_delta_SOURCES = \ + src/delta/delta.c + +systemd_delta_LDADD = \ + libshared.la + +include $(topsrcdir)/build-aux/Makefile.tail.mk diff --git a/src/grp-system/grp-utils/systemd-delta/delta.c b/src/grp-system/grp-utils/systemd-delta/delta.c new file mode 100644 index 0000000000..dd571e7195 --- /dev/null +++ b/src/grp-system/grp-utils/systemd-delta/delta.c @@ -0,0 +1,635 @@ +/*** + 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 "basic/alloc-util.h" +#include "basic/dirent-util.h" +#include "basic/fd-util.h" +#include "basic/fs-util.h" +#include "basic/hashmap.h" +#include "basic/locale-util.h" +#include "basic/log.h" +#include "shared/pager.h" +#include "basic/parse-util.h" +#include "basic/path-util.h" +#include "basic/process-util.h" +#include "basic/signal-util.h" +#include "basic/stat-util.h" +#include "basic/string-util.h" +#include "basic/strv.h" +#include "basic/terminal-util.h" +#include "basic/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 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, special_glyph(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, special_glyph(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, special_glyph(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, special_glyph(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, special_glyph(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, special_glyph(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, special_glyph(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, special_glyph(ARROW), basename(p), special_glyph(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), special_glyph(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), special_glyph(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(arg_no_pager, false); + + 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/grp-system/grp-utils/systemd-fstab-generator/Makefile b/src/grp-system/grp-utils/systemd-fstab-generator/Makefile new file mode 100644 index 0000000000..4f19808477 --- /dev/null +++ b/src/grp-system/grp-utils/systemd-fstab-generator/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 + +systemgenerator_PROGRAMS += systemd-fstab-generator +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/grp-system/grp-utils/systemd-fstab-generator/fstab-generator.c b/src/grp-system/grp-utils/systemd-fstab-generator/fstab-generator.c new file mode 100644 index 0000000000..bedf469f93 --- /dev/null +++ b/src/grp-system/grp-utils/systemd-fstab-generator/fstab-generator.c @@ -0,0 +1,713 @@ +/*** + 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 "basic/alloc-util.h" +#include "basic/fd-util.h" +#include "basic/fileio.h" +#include "shared/fstab-util.h" +#include "shared/generator.h" +#include "basic/log.h" +#include "basic/mkdir.h" +#include "mount-setup.h" +#include "basic/mount-util.h" +#include "basic/parse-util.h" +#include "basic/path-util.h" +#include "basic/proc-cmdline.h" +#include "basic/special.h" +#include "basic/stat-util.h" +#include "basic/string-util.h" +#include "basic/strv.h" +#include "basic/unit-name.h" +#include "basic/util.h" +#include "basic/virt.h" + +static const char *arg_dest = "/tmp"; +static bool arg_fstab_enabled = true; +static char *arg_root_what = NULL; +static char *arg_root_fstype = NULL; +static char *arg_root_options = NULL; +static int arg_root_rw = -1; +static char *arg_usr_what = NULL; +static char *arg_usr_fstype = NULL; +static char *arg_usr_options = NULL; + +static int add_swap( + const char *what, + struct mntent *me, + bool noauto, + bool nofail) { + + _cleanup_free_ char *name = NULL, *unit = NULL, *lnk = NULL; + _cleanup_fclose_ FILE *f = NULL; + int r; + + assert(what); + assert(me); + + if (access("/proc/swaps", F_OK) < 0) { + log_info("Swap not supported, ignoring fstab swap entry for %s.", what); + return 0; + } + + if (detect_container() > 0) { + log_info("Running in a container, ignoring fstab swap entry for %s.", what); + return 0; + } + + r = unit_name_from_path(what, ".swap", &name); + if (r < 0) + return log_error_errno(r, "Failed to generate unit name: %m"); + + unit = strjoin(arg_dest, "/", name, NULL); + if (!unit) + return log_oom(); + + f = fopen(unit, "wxe"); + if (!f) { + if (errno == EEXIST) + log_error("Failed to create swap unit file %s, as it already exists. Duplicate entry in /etc/fstab?", unit); + else + log_error_errno(errno, "Failed to create unit file %s: %m", unit); + return -errno; + } + + fprintf(f, + "# Automatically generated by systemd-fstab-generator\n\n" + "[Unit]\n" + "SourcePath=/etc/fstab\n" + "Documentation=man:fstab(5) man:systemd-fstab-generator(8)\n\n" + "[Swap]\n" + "What=%s\n", + what); + + if (!isempty(me->mnt_opts) && !streq(me->mnt_opts, "defaults")) + fprintf(f, "Options=%s\n", me->mnt_opts); + + r = fflush_and_check(f); + if (r < 0) + return log_error_errno(r, "Failed to write unit file %s: %m", unit); + + /* use what as where, to have a nicer error message */ + r = generator_write_timeouts(arg_dest, what, what, me->mnt_opts, NULL); + if (r < 0) + return r; + + if (!noauto) { + lnk = strjoin(arg_dest, "/" SPECIAL_SWAP_TARGET, + nofail ? ".wants/" : ".requires/", name, NULL); + if (!lnk) + return log_oom(); + + mkdir_parents_label(lnk, 0755); + if (symlink(unit, lnk) < 0) + return log_error_errno(errno, "Failed to create symlink %s: %m", lnk); + } + + return 0; +} + +static bool mount_is_network(struct mntent *me) { + assert(me); + + return fstab_test_option(me->mnt_opts, "_netdev\0") || + fstype_is_network(me->mnt_type); +} + +static bool mount_in_initrd(struct mntent *me) { + assert(me); + + return fstab_test_option(me->mnt_opts, "x-initrd.mount\0") || + streq(me->mnt_dir, "/usr"); +} + +static int write_idle_timeout(FILE *f, const char *where, const char *opts) { + _cleanup_free_ char *timeout = NULL; + char timespan[FORMAT_TIMESPAN_MAX]; + usec_t u; + int r; + + r = fstab_filter_options(opts, "x-systemd.idle-timeout\0", NULL, &timeout, NULL); + if (r < 0) + return log_warning_errno(r, "Failed to parse options: %m"); + if (r == 0) + return 0; + + r = parse_sec(timeout, &u); + if (r < 0) { + log_warning("Failed to parse timeout for %s, ignoring: %s", where, timeout); + return 0; + } + + fprintf(f, "TimeoutIdleSec=%s\n", format_timespan(timespan, sizeof(timespan), u, 0)); + + return 0; +} + +static int write_requires_after(FILE *f, const char *opts) { + _cleanup_strv_free_ char **names = NULL, **units = NULL; + _cleanup_free_ char *res = NULL; + char **s; + int r; + + assert(f); + assert(opts); + + r = fstab_extract_values(opts, "x-systemd.requires", &names); + if (r < 0) + return log_warning_errno(r, "Failed to parse options: %m"); + if (r == 0) + return 0; + + STRV_FOREACH(s, names) { + char *x; + + r = unit_name_mangle_with_suffix(*s, UNIT_NAME_NOGLOB, ".mount", &x); + if (r < 0) + return log_error_errno(r, "Failed to generate unit name: %m"); + r = strv_consume(&units, x); + if (r < 0) + return log_oom(); + } + + if (units) { + res = strv_join(units, " "); + if (!res) + return log_oom(); + fprintf(f, "After=%1$s\nRequires=%1$s\n", res); + } + + return 0; +} + +static int write_requires_mounts_for(FILE *f, const char *opts) { + _cleanup_strv_free_ char **paths = NULL; + _cleanup_free_ char *res = NULL; + int r; + + assert(f); + assert(opts); + + r = fstab_extract_values(opts, "x-systemd.requires-mounts-for", &paths); + if (r < 0) + return log_warning_errno(r, "Failed to parse options: %m"); + if (r == 0) + return 0; + + res = strv_join(paths, " "); + if (!res) + return log_oom(); + + fprintf(f, "RequiresMountsFor=%s\n", res); + + return 0; +} + +static int add_mount( + const char *what, + const char *where, + const char *fstype, + const char *opts, + int passno, + bool noauto, + bool nofail, + bool automount, + const char *post, + const char *source) { + + _cleanup_free_ char + *name = NULL, *unit = NULL, *lnk = NULL, + *automount_name = NULL, *automount_unit = NULL, + *filtered = NULL; + _cleanup_fclose_ FILE *f = NULL; + int r; + + assert(what); + assert(where); + assert(opts); + assert(post); + assert(source); + + if (streq_ptr(fstype, "autofs")) + return 0; + + if (!is_path(where)) { + log_warning("Mount point %s is not a valid path, ignoring.", where); + return 0; + } + + if (mount_point_is_api(where) || + mount_point_ignore(where)) + return 0; + + if (path_equal(where, "/")) { + if (noauto) + log_warning("Ignoring \"noauto\" for root device"); + if (nofail) + log_warning("Ignoring \"nofail\" for root device"); + if (automount) + log_warning("Ignoring automount option for root device"); + + noauto = nofail = automount = false; + } + + r = unit_name_from_path(where, ".mount", &name); + if (r < 0) + return log_error_errno(r, "Failed to generate unit name: %m"); + + unit = strjoin(arg_dest, "/", name, NULL); + if (!unit) + return log_oom(); + + f = fopen(unit, "wxe"); + if (!f) { + if (errno == EEXIST) + log_error("Failed to create mount unit file %s, as it already exists. Duplicate entry in /etc/fstab?", unit); + else + log_error_errno(errno, "Failed to create unit file %s: %m", unit); + return -errno; + } + + fprintf(f, + "# Automatically generated by systemd-fstab-generator\n\n" + "[Unit]\n" + "SourcePath=%s\n" + "Documentation=man:fstab(5) man:systemd-fstab-generator(8)\n", + source); + + if (!noauto && !nofail && !automount) + fprintf(f, "Before=%s\n", post); + + if (!automount && opts) { + r = write_requires_after(f, opts); + if (r < 0) + return r; + r = write_requires_mounts_for(f, opts); + if (r < 0) + return r; + } + + if (passno != 0) { + r = generator_write_fsck_deps(f, arg_dest, what, where, fstype); + if (r < 0) + return r; + } + + fprintf(f, + "\n" + "[Mount]\n" + "What=%s\n" + "Where=%s\n", + what, + where); + + if (!isempty(fstype) && !streq(fstype, "auto")) + fprintf(f, "Type=%s\n", fstype); + + r = generator_write_timeouts(arg_dest, what, where, opts, &filtered); + if (r < 0) + return r; + + if (!isempty(filtered) && !streq(filtered, "defaults")) + fprintf(f, "Options=%s\n", filtered); + + r = fflush_and_check(f); + if (r < 0) + return log_error_errno(r, "Failed to write unit file %s: %m", unit); + + if (!noauto && !automount) { + lnk = strjoin(arg_dest, "/", post, nofail ? ".wants/" : ".requires/", name, NULL); + if (!lnk) + return log_oom(); + + mkdir_parents_label(lnk, 0755); + if (symlink(unit, lnk) < 0) + return log_error_errno(errno, "Failed to create symlink %s: %m", lnk); + } + + if (automount) { + r = unit_name_from_path(where, ".automount", &automount_name); + if (r < 0) + return log_error_errno(r, "Failed to generate unit name: %m"); + + automount_unit = strjoin(arg_dest, "/", automount_name, NULL); + if (!automount_unit) + return log_oom(); + + fclose(f); + f = fopen(automount_unit, "wxe"); + if (!f) + return log_error_errno(errno, "Failed to create unit file %s: %m", automount_unit); + + fprintf(f, + "# Automatically generated by systemd-fstab-generator\n\n" + "[Unit]\n" + "SourcePath=%s\n" + "Documentation=man:fstab(5) man:systemd-fstab-generator(8)\n", + source); + + fprintf(f, "Before=%s\n", post); + + if (opts) { + r = write_requires_after(f, opts); + if (r < 0) + return r; + r = write_requires_mounts_for(f, opts); + if (r < 0) + return r; + } + + fprintf(f, + "\n" + "[Automount]\n" + "Where=%s\n", + where); + + r = write_idle_timeout(f, where, opts); + if (r < 0) + return r; + + r = fflush_and_check(f); + if (r < 0) + return log_error_errno(r, "Failed to write unit file %s: %m", automount_unit); + + free(lnk); + lnk = strjoin(arg_dest, "/", post, nofail ? ".wants/" : ".requires/", automount_name, NULL); + if (!lnk) + return log_oom(); + + mkdir_parents_label(lnk, 0755); + if (symlink(automount_unit, lnk) < 0) + return log_error_errno(errno, "Failed to create symlink %s: %m", lnk); + } + + return 0; +} + +static int parse_fstab(bool initrd) { + _cleanup_endmntent_ FILE *f = NULL; + const char *fstab_path; + struct mntent *me; + int r = 0; + + fstab_path = initrd ? "/sysroot/etc/fstab" : "/etc/fstab"; + f = setmntent(fstab_path, "re"); + if (!f) { + if (errno == ENOENT) + return 0; + + log_error_errno(errno, "Failed to open %s: %m", fstab_path); + return -errno; + } + + while ((me = getmntent(f))) { + _cleanup_free_ char *where = NULL, *what = NULL; + bool noauto, nofail; + int k; + + if (initrd && !mount_in_initrd(me)) + continue; + + what = fstab_node_to_udev_node(me->mnt_fsname); + if (!what) + return log_oom(); + + if (is_device_path(what) && path_is_read_only_fs("sys") > 0) { + log_info("Running in a container, ignoring fstab device entry for %s.", what); + continue; + } + + where = initrd ? strappend("/sysroot/", me->mnt_dir) : strdup(me->mnt_dir); + if (!where) + return log_oom(); + + if (is_path(where)) + path_kill_slashes(where); + + noauto = fstab_test_yes_no_option(me->mnt_opts, "noauto\0" "auto\0"); + nofail = fstab_test_yes_no_option(me->mnt_opts, "nofail\0" "fail\0"); + log_debug("Found entry what=%s where=%s type=%s nofail=%s noauto=%s", + what, where, me->mnt_type, + yes_no(noauto), yes_no(nofail)); + + if (streq(me->mnt_type, "swap")) + k = add_swap(what, me, noauto, nofail); + else { + bool automount; + const char *post; + + automount = fstab_test_option(me->mnt_opts, + "comment=systemd.automount\0" + "x-systemd.automount\0"); + if (initrd) + post = SPECIAL_INITRD_FS_TARGET; + else if (mount_is_network(me)) + post = SPECIAL_REMOTE_FS_TARGET; + else + post = SPECIAL_LOCAL_FS_TARGET; + + k = add_mount(what, + where, + me->mnt_type, + me->mnt_opts, + me->mnt_passno, + noauto, + nofail, + automount, + post, + fstab_path); + } + + if (k < 0) + r = k; + } + + return r; +} + +static int add_sysroot_mount(void) { + _cleanup_free_ char *what = NULL; + const char *opts; + int r; + + if (isempty(arg_root_what)) { + log_debug("Could not find a root= entry on the kernel command line."); + return 0; + } + + what = fstab_node_to_udev_node(arg_root_what); + if (!what) + return log_oom(); + + if (!arg_root_options) + opts = arg_root_rw > 0 ? "rw" : "ro"; + else if (arg_root_rw >= 0 || + !fstab_test_option(arg_root_options, "ro\0" "rw\0")) + opts = strjoina(arg_root_options, ",", arg_root_rw > 0 ? "rw" : "ro"); + else + opts = arg_root_options; + + log_debug("Found entry what=%s where=/sysroot type=%s", what, strna(arg_root_fstype)); + + if (is_device_path(what)) { + r = generator_write_initrd_root_device_deps(arg_dest, what); + if (r < 0) + return r; + } + + return add_mount(what, + "/sysroot", + arg_root_fstype, + opts, + is_device_path(what) ? 1 : 0, + false, + false, + false, + SPECIAL_INITRD_ROOT_FS_TARGET, + "/proc/cmdline"); +} + +static int add_sysroot_usr_mount(void) { + _cleanup_free_ char *what = NULL; + const char *opts; + + if (!arg_usr_what && !arg_usr_fstype && !arg_usr_options) + return 0; + + if (arg_root_what && !arg_usr_what) { + arg_usr_what = strdup(arg_root_what); + + if (!arg_usr_what) + return log_oom(); + } + + if (arg_root_fstype && !arg_usr_fstype) { + arg_usr_fstype = strdup(arg_root_fstype); + + if (!arg_usr_fstype) + return log_oom(); + } + + if (arg_root_options && !arg_usr_options) { + arg_usr_options = strdup(arg_root_options); + + if (!arg_usr_options) + return log_oom(); + } + + if (!arg_usr_what) + return 0; + + what = fstab_node_to_udev_node(arg_usr_what); + if (!path_is_absolute(what)) { + log_debug("Skipping entry what=%s where=/sysroot/usr type=%s", what, strna(arg_usr_fstype)); + return -1; + } + + if (!arg_usr_options) + opts = arg_root_rw > 0 ? "rw" : "ro"; + else if (!fstab_test_option(arg_usr_options, "ro\0" "rw\0")) + opts = strjoina(arg_usr_options, ",", arg_root_rw > 0 ? "rw" : "ro"); + else + opts = arg_usr_options; + + log_debug("Found entry what=%s where=/sysroot/usr type=%s", what, strna(arg_usr_fstype)); + return add_mount(what, + "/sysroot/usr", + arg_usr_fstype, + opts, + 1, + false, + false, + false, + SPECIAL_INITRD_FS_TARGET, + "/proc/cmdline"); +} + +static int parse_proc_cmdline_item(const char *key, const char *value) { + int r; + + /* root=, usr=, usrfstype= and roofstype= may occur more than once, the last + * instance should take precedence. In the case of multiple rootflags= + * or usrflags= the arguments should be concatenated */ + + if (STR_IN_SET(key, "fstab", "rd.fstab") && value) { + + r = parse_boolean(value); + if (r < 0) + log_warning("Failed to parse fstab switch %s. Ignoring.", value); + else + arg_fstab_enabled = r; + + } else if (streq(key, "root") && value) { + + if (free_and_strdup(&arg_root_what, value) < 0) + return log_oom(); + + } else if (streq(key, "rootfstype") && value) { + + if (free_and_strdup(&arg_root_fstype, value) < 0) + return log_oom(); + + } else if (streq(key, "rootflags") && value) { + char *o; + + o = arg_root_options ? + strjoin(arg_root_options, ",", value, NULL) : + strdup(value); + if (!o) + return log_oom(); + + free(arg_root_options); + arg_root_options = o; + + } else if (streq(key, "mount.usr") && value) { + + if (free_and_strdup(&arg_usr_what, value) < 0) + return log_oom(); + + } else if (streq(key, "mount.usrfstype") && value) { + + if (free_and_strdup(&arg_usr_fstype, value) < 0) + return log_oom(); + + } else if (streq(key, "mount.usrflags") && value) { + char *o; + + o = arg_usr_options ? + strjoin(arg_usr_options, ",", value, NULL) : + strdup(value); + if (!o) + return log_oom(); + + free(arg_usr_options); + arg_usr_options = o; + + } else if (streq(key, "rw") && !value) + arg_root_rw = true; + else if (streq(key, "ro") && !value) + arg_root_rw = false; + + return 0; +} + +int main(int argc, char *argv[]) { + int r = 0; + + 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); + + r = parse_proc_cmdline(parse_proc_cmdline_item); + if (r < 0) + log_warning_errno(r, "Failed to parse kernel command line, ignoring: %m"); + + /* Always honour root= and usr= in the kernel command line if we are in an initrd */ + if (in_initrd()) { + r = add_sysroot_mount(); + if (r == 0) + r = add_sysroot_usr_mount(); + } + + /* Honour /etc/fstab only when that's enabled */ + if (arg_fstab_enabled) { + int k; + + log_debug("Parsing /etc/fstab"); + + /* Parse the local /etc/fstab, possibly from the initrd */ + k = parse_fstab(false); + if (k < 0) + r = k; + + /* If running in the initrd also parse the /etc/fstab from the host */ + if (in_initrd()) { + log_debug("Parsing /sysroot/etc/fstab"); + + k = parse_fstab(true); + if (k < 0) + r = k; + } + } + + free(arg_root_what); + free(arg_root_fstype); + free(arg_root_options); + + free(arg_usr_what); + free(arg_usr_fstype); + free(arg_usr_options); + + return r < 0 ? EXIT_FAILURE : EXIT_SUCCESS; +} diff --git a/src/grp-system/grp-utils/systemd-fstab-generator/mount-setup.c b/src/grp-system/grp-utils/systemd-fstab-generator/mount-setup.c new file mode 120000 index 0000000000..7f7ff15b46 --- /dev/null +++ b/src/grp-system/grp-utils/systemd-fstab-generator/mount-setup.c @@ -0,0 +1 @@ +../../libcore/mount-setup.c \ No newline at end of file diff --git a/src/grp-system/grp-utils/systemd-fstab-generator/mount-setup.h b/src/grp-system/grp-utils/systemd-fstab-generator/mount-setup.h new file mode 120000 index 0000000000..50721d8bfc --- /dev/null +++ b/src/grp-system/grp-utils/systemd-fstab-generator/mount-setup.h @@ -0,0 +1 @@ +../../libcore/mount-setup.h \ No newline at end of file diff --git a/src/grp-system/grp-utils/systemd-run/Makefile b/src/grp-system/grp-utils/systemd-run/Makefile new file mode 100644 index 0000000000..17d21167bc --- /dev/null +++ b/src/grp-system/grp-utils/systemd-run/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 + +bin_PROGRAMS += systemd-run +systemd_run_SOURCES = \ + src/run/run.c + +systemd_run_LDADD = \ + libshared.la + +include $(topsrcdir)/build-aux/Makefile.tail.mk diff --git a/src/grp-system/grp-utils/systemd-run/run.c b/src/grp-system/grp-utils/systemd-run/run.c new file mode 100644 index 0000000000..9ff2ba8003 --- /dev/null +++ b/src/grp-system/grp-utils/systemd-run/run.c @@ -0,0 +1,1261 @@ +/*** + 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 "basic/alloc-util.h" +#include "sd-bus/bus-error.h" +#include "shared/bus-unit-util.h" +#include "shared/bus-util.h" +#include "basic/calendarspec.h" +#include "basic/env-util.h" +#include "basic/fd-util.h" +#include "basic/formats-util.h" +#include "basic/parse-util.h" +#include "basic/path-util.h" +#include "shared/ptyfwd.h" +#include "basic/signal-util.h" +#include "shared/spawn-polkit-agent.h" +#include "basic/strv.h" +#include "basic/terminal-util.h" +#include "basic/unit-name.h" +#include "basic/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 a timer option is specified and the unit specified with\n" + "the --unit option exists, the 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" + " -E --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_USER, + ARG_SYSTEM, + ARG_SCOPE, + ARG_UNIT, + ARG_DESCRIPTION, + ARG_SLICE, + ARG_SEND_SIGHUP, + ARG_SERVICE_TYPE, + ARG_EXEC_USER, + ARG_EXEC_GROUP, + ARG_NICE, + 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, + ARG_NO_ASK_PASSWORD, + }; + + 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, 'E' }, + { "property", required_argument, NULL, 'p' }, + { "tty", no_argument, NULL, 't' }, /* deprecated */ + { "pty", 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 'E': + 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 = transient_cgroup_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; + _cleanup_(sd_bus_message_unrefp) sd_bus_message *pty_reply = 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, + &pty_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(pty_reply, "hs", &master, &s); + if (r < 0) + return bus_log_parse_error(r); + + 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/grp-system/grp-utils/systemd-sysv-generator/Makefile b/src/grp-system/grp-utils/systemd-sysv-generator/Makefile new file mode 100644 index 0000000000..10dd75abc6 --- /dev/null +++ b/src/grp-system/grp-utils/systemd-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/grp-system/grp-utils/systemd-sysv-generator/sysv-generator.c b/src/grp-system/grp-utils/systemd-sysv-generator/sysv-generator.c new file mode 100644 index 0000000000..183f93e9e7 --- /dev/null +++ b/src/grp-system/grp-utils/systemd-sysv-generator/sysv-generator.c @@ -0,0 +1,1039 @@ +/*** + This file is part of systemd. + + Copyright 2014 Thomas H.P. Andersen + Copyright 2010 Lennart Poettering + Copyright 2011 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 "basic/alloc-util.h" +#include "basic/dirent-util.h" +#include "basic/fd-util.h" +#include "basic/fileio.h" +#include "basic/hashmap.h" +#include "basic/hexdecoct.h" +#include "shared/install.h" +#include "basic/log.h" +#include "basic/mkdir.h" +#include "shared/path-lookup.h" +#include "basic/path-util.h" +#include "basic/set.h" +#include "basic/special.h" +#include "basic/stat-util.h" +#include "basic/string-util.h" +#include "basic/strv.h" +#include "basic/unit-name.h" +#include "basic/util.h" + +typedef enum RunlevelType { + RUNLEVEL_UP, + RUNLEVEL_DOWN +} RunlevelType; + +static const struct { + const char *path; + const char *target; + const RunlevelType type; +} rcnd_table[] = { + /* Standard SysV runlevels for start-up */ + { "rc1.d", SPECIAL_RESCUE_TARGET, RUNLEVEL_UP }, + { "rc2.d", SPECIAL_MULTI_USER_TARGET, RUNLEVEL_UP }, + { "rc3.d", SPECIAL_MULTI_USER_TARGET, RUNLEVEL_UP }, + { "rc4.d", SPECIAL_MULTI_USER_TARGET, RUNLEVEL_UP }, + { "rc5.d", SPECIAL_GRAPHICAL_TARGET, RUNLEVEL_UP }, + + /* Standard SysV runlevels for shutdown */ + { "rc0.d", SPECIAL_POWEROFF_TARGET, RUNLEVEL_DOWN }, + { "rc6.d", SPECIAL_REBOOT_TARGET, RUNLEVEL_DOWN } + + /* Note that the order here matters, as we read the + directories in this order, and we want to make sure that + sysv_start_priority is known when we first load the + unit. And that value we only know from S links. Hence + UP must be read before DOWN */ +}; + +static const char *arg_dest = "/tmp"; + +typedef struct SysvStub { + char *name; + char *path; + char *description; + int sysv_start_priority; + char *pid_file; + char **before; + char **after; + char **wants; + char **wanted_by; + char **conflicts; + bool has_lsb; + bool reload; + bool loaded; +} SysvStub; + +static void free_sysvstub(SysvStub *s) { + if (!s) + return; + + free(s->name); + free(s->path); + free(s->description); + free(s->pid_file); + strv_free(s->before); + strv_free(s->after); + strv_free(s->wants); + strv_free(s->wanted_by); + strv_free(s->conflicts); + free(s); +} + +DEFINE_TRIVIAL_CLEANUP_FUNC(SysvStub*, free_sysvstub); + +static void free_sysvstub_hashmapp(Hashmap **h) { + SysvStub *stub; + + while ((stub = hashmap_steal_first(*h))) + free_sysvstub(stub); + + hashmap_free(*h); +} + +static int add_symlink(const char *service, const char *where) { + const char *from, *to; + int r; + + assert(service); + assert(where); + + from = strjoina(arg_dest, "/", service); + to = strjoina(arg_dest, "/", where, ".wants/", service); + + mkdir_parents_label(to, 0755); + + r = symlink(from, to); + if (r < 0) { + if (errno == EEXIST) + return 0; + + return -errno; + } + + return 1; +} + +static int add_alias(const char *service, const char *alias) { + const char *link; + int r; + + assert(service); + assert(alias); + + link = strjoina(arg_dest, "/", alias); + + r = symlink(service, link); + if (r < 0) { + if (errno == EEXIST) + return 0; + + return -errno; + } + + return 1; +} + +static int generate_unit_file(SysvStub *s) { + _cleanup_fclose_ FILE *f = NULL; + const char *unit; + char **p; + int r; + + assert(s); + + if (!s->loaded) + return 0; + + unit = strjoina(arg_dest, "/", s->name); + + /* We might already have a symlink with the same name from a Provides:, + * or from backup files like /etc/init.d/foo.bak. Real scripts always win, + * so remove an existing link */ + if (is_symlink(unit) > 0) { + log_warning("Overwriting existing symlink %s with real service.", unit); + (void) unlink(unit); + } + + f = fopen(unit, "wxe"); + if (!f) + return log_error_errno(errno, "Failed to create unit file %s: %m", unit); + + fprintf(f, + "# Automatically generated by systemd-sysv-generator\n\n" + "[Unit]\n" + "Documentation=man:systemd-sysv-generator(8)\n" + "SourcePath=%s\n", + s->path); + + if (s->description) + fprintf(f, "Description=%s\n", s->description); + + STRV_FOREACH(p, s->before) + fprintf(f, "Before=%s\n", *p); + STRV_FOREACH(p, s->after) + fprintf(f, "After=%s\n", *p); + STRV_FOREACH(p, s->wants) + fprintf(f, "Wants=%s\n", *p); + STRV_FOREACH(p, s->conflicts) + fprintf(f, "Conflicts=%s\n", *p); + + fprintf(f, + "\n[Service]\n" + "Type=forking\n" + "Restart=no\n" + "TimeoutSec=5min\n" + "IgnoreSIGPIPE=no\n" + "KillMode=process\n" + "GuessMainPID=no\n" + "RemainAfterExit=%s\n", + yes_no(!s->pid_file)); + + if (s->pid_file) + fprintf(f, "PIDFile=%s\n", s->pid_file); + + fprintf(f, + "ExecStart=%s start\n" + "ExecStop=%s stop\n", + s->path, s->path); + + if (s->reload) + fprintf(f, "ExecReload=%s reload\n", s->path); + + r = fflush_and_check(f); + if (r < 0) + return log_error_errno(r, "Failed to write unit %s: %m", unit); + + STRV_FOREACH(p, s->wanted_by) { + r = add_symlink(s->name, *p); + if (r < 0) + log_warning_errno(r, "Failed to create 'Wants' symlink to %s, ignoring: %m", *p); + } + + return 1; +} + +static bool usage_contains_reload(const char *line) { + return (strcasestr(line, "{reload|") || + strcasestr(line, "{reload}") || + strcasestr(line, "{reload\"") || + strcasestr(line, "|reload|") || + strcasestr(line, "|reload}") || + strcasestr(line, "|reload\"")); +} + +static char *sysv_translate_name(const char *name) { + _cleanup_free_ char *c = NULL; + char *res; + + c = strdup(name); + if (!c) + return NULL; + + res = endswith(c, ".sh"); + if (res) + *res = 0; + + if (unit_name_mangle(c, UNIT_NAME_NOGLOB, &res) < 0) + return NULL; + + return res; +} + +static int sysv_translate_facility(const char *name, const char *filename, char **ret) { + + /* We silently ignore the $ prefix here. According to the LSB + * spec it simply indicates whether something is a + * standardized name or a distribution-specific one. Since we + * just follow what already exists and do not introduce new + * uses or names we don't care who introduced a new name. */ + + static const char * const table[] = { + /* LSB defined facilities */ + "local_fs", NULL, + "network", SPECIAL_NETWORK_ONLINE_TARGET, + "named", SPECIAL_NSS_LOOKUP_TARGET, + "portmap", SPECIAL_RPCBIND_TARGET, + "remote_fs", SPECIAL_REMOTE_FS_TARGET, + "syslog", NULL, + "time", SPECIAL_TIME_SYNC_TARGET, + }; + + char *filename_no_sh, *e, *m; + const char *n; + unsigned i; + int r; + + assert(name); + assert(filename); + assert(ret); + + n = *name == '$' ? name + 1 : name; + + for (i = 0; i < ELEMENTSOF(table); i += 2) { + if (!streq(table[i], n)) + continue; + + if (!table[i+1]) + return 0; + + m = strdup(table[i+1]); + if (!m) + return log_oom(); + + *ret = m; + return 1; + } + + /* If we don't know this name, fallback heuristics to figure + * out whether something is a target or a service alias. */ + + /* Facilities starting with $ are most likely targets */ + if (*name == '$') { + r = unit_name_build(n, NULL, ".target", ret); + if (r < 0) + return log_error_errno(r, "Failed to build name: %m"); + + return r; + } + + /* Strip ".sh" suffix from file name for comparison */ + filename_no_sh = strdupa(filename); + e = endswith(filename_no_sh, ".sh"); + if (e) { + *e = '\0'; + filename = filename_no_sh; + } + + /* Names equaling the file name of the services are redundant */ + if (streq_ptr(n, filename)) + return 0; + + /* Everything else we assume to be normal service names */ + m = sysv_translate_name(n); + if (!m) + return log_oom(); + + *ret = m; + return 1; +} + +static int handle_provides(SysvStub *s, unsigned line, const char *full_text, const char *text) { + int r; + + assert(s); + assert(full_text); + assert(text); + + for (;;) { + _cleanup_free_ char *word = NULL, *m = NULL; + + r = extract_first_word(&text, &word, NULL, EXTRACT_QUOTES|EXTRACT_RELAX); + if (r < 0) + return log_error_errno(r, "Failed to parse word from provides string: %m"); + if (r == 0) + break; + + r = sysv_translate_facility(word, basename(s->path), &m); + if (r <= 0) /* continue on error */ + continue; + + switch (unit_name_to_type(m)) { + + case UNIT_SERVICE: + log_debug("Adding Provides: alias '%s' for '%s'", m, s->name); + r = add_alias(s->name, m); + if (r < 0) + log_warning_errno(r, "[%s:%u] Failed to add LSB Provides name %s, ignoring: %m", s->path, line, m); + break; + + case UNIT_TARGET: + + /* NB: SysV targets which are provided by a + * service are pulled in by the services, as + * an indication that the generic service is + * now available. This is strictly one-way. + * The targets do NOT pull in SysV services! */ + + r = strv_extend(&s->before, m); + if (r < 0) + return log_oom(); + + r = strv_extend(&s->wants, m); + if (r < 0) + return log_oom(); + + if (streq(m, SPECIAL_NETWORK_ONLINE_TARGET)) { + r = strv_extend(&s->before, SPECIAL_NETWORK_TARGET); + if (r < 0) + return log_oom(); + } + + break; + + case _UNIT_TYPE_INVALID: + log_warning("Unit name '%s' is invalid", m); + break; + + default: + log_warning("Unknown unit type for unit '%s'", m); + } + } + + return 0; +} + +static int handle_dependencies(SysvStub *s, unsigned line, const char *full_text, const char *text) { + int r; + + assert(s); + assert(full_text); + assert(text); + + for (;;) { + _cleanup_free_ char *word = NULL, *m = NULL; + bool is_before; + + r = extract_first_word(&text, &word, NULL, EXTRACT_QUOTES|EXTRACT_RELAX); + if (r < 0) + return log_error_errno(r, "Failed to parse word from provides string: %m"); + if (r == 0) + break; + + r = sysv_translate_facility(word, basename(s->path), &m); + if (r <= 0) /* continue on error */ + continue; + + is_before = startswith_no_case(full_text, "X-Start-Before:"); + + if (streq(m, SPECIAL_NETWORK_ONLINE_TARGET) && !is_before) { + /* the network-online target is special, as it needs to be actively pulled in */ + r = strv_extend(&s->after, m); + if (r < 0) + return log_oom(); + + r = strv_extend(&s->wants, m); + } else + r = strv_extend(is_before ? &s->before : &s->after, m); + if (r < 0) + return log_oom(); + } + + return 0; +} + +static int load_sysv(SysvStub *s) { + _cleanup_fclose_ FILE *f; + unsigned line = 0; + int r; + enum { + NORMAL, + DESCRIPTION, + LSB, + LSB_DESCRIPTION, + USAGE_CONTINUATION + } state = NORMAL; + _cleanup_free_ char *short_description = NULL, *long_description = NULL, *chkconfig_description = NULL; + char *description; + bool supports_reload = false; + char l[LINE_MAX]; + + assert(s); + + f = fopen(s->path, "re"); + if (!f) { + if (errno == ENOENT) + return 0; + + return log_error_errno(errno, "Failed to open %s: %m", s->path); + } + + log_debug("Loading SysV script %s", s->path); + + FOREACH_LINE(l, f, goto fail) { + char *t; + + line++; + + t = strstrip(l); + if (*t != '#') { + /* Try to figure out whether this init script supports + * the reload operation. This heuristic looks for + * "Usage" lines which include the reload option. */ + if ( state == USAGE_CONTINUATION || + (state == NORMAL && strcasestr(t, "usage"))) { + if (usage_contains_reload(t)) { + supports_reload = true; + state = NORMAL; + } else if (t[strlen(t)-1] == '\\') + state = USAGE_CONTINUATION; + else + state = NORMAL; + } + + continue; + } + + if (state == NORMAL && streq(t, "### BEGIN INIT INFO")) { + state = LSB; + s->has_lsb = true; + continue; + } + + if ((state == LSB_DESCRIPTION || state == LSB) && streq(t, "### END INIT INFO")) { + state = NORMAL; + continue; + } + + t++; + t += strspn(t, WHITESPACE); + + if (state == NORMAL) { + + /* Try to parse Red Hat style description */ + + if (startswith_no_case(t, "description:")) { + + size_t k; + const char *j; + + k = strlen(t); + if (k > 0 && t[k-1] == '\\') { + state = DESCRIPTION; + t[k-1] = 0; + } + + j = strstrip(t+12); + if (isempty(j)) + j = NULL; + + r = free_and_strdup(&chkconfig_description, j); + if (r < 0) + return log_oom(); + + } else if (startswith_no_case(t, "pidfile:")) { + const char *fn; + + state = NORMAL; + + fn = strstrip(t+8); + if (!path_is_absolute(fn)) { + log_error("[%s:%u] PID file not absolute. Ignoring.", s->path, line); + continue; + } + + r = free_and_strdup(&s->pid_file, fn); + if (r < 0) + return log_oom(); + } + + } else if (state == DESCRIPTION) { + + /* Try to parse Red Hat style description + * continuation */ + + size_t k; + char *j; + + k = strlen(t); + if (k > 0 && t[k-1] == '\\') + t[k-1] = 0; + else + state = NORMAL; + + j = strstrip(t); + if (!isempty(j)) { + char *d = NULL; + + if (chkconfig_description) + d = strjoin(chkconfig_description, " ", j, NULL); + else + d = strdup(j); + if (!d) + return log_oom(); + + free(chkconfig_description); + chkconfig_description = d; + } + + } else if (state == LSB || state == LSB_DESCRIPTION) { + + if (startswith_no_case(t, "Provides:")) { + state = LSB; + + r = handle_provides(s, line, t, t + 9); + if (r < 0) + return r; + + } else if (startswith_no_case(t, "Required-Start:") || + startswith_no_case(t, "Should-Start:") || + startswith_no_case(t, "X-Start-Before:") || + startswith_no_case(t, "X-Start-After:")) { + + state = LSB; + + r = handle_dependencies(s, line, t, strchr(t, ':') + 1); + if (r < 0) + return r; + + } else if (startswith_no_case(t, "Description:")) { + const char *j; + + state = LSB_DESCRIPTION; + + j = strstrip(t+12); + if (isempty(j)) + j = NULL; + + r = free_and_strdup(&long_description, j); + if (r < 0) + return log_oom(); + + } else if (startswith_no_case(t, "Short-Description:")) { + const char *j; + + state = LSB; + + j = strstrip(t+18); + if (isempty(j)) + j = NULL; + + r = free_and_strdup(&short_description, j); + if (r < 0) + return log_oom(); + + } else if (state == LSB_DESCRIPTION) { + + if (startswith(l, "#\t") || startswith(l, "# ")) { + const char *j; + + j = strstrip(t); + if (!isempty(j)) { + char *d = NULL; + + if (long_description) + d = strjoin(long_description, " ", t, NULL); + else + d = strdup(j); + if (!d) + return log_oom(); + + free(long_description); + long_description = d; + } + + } else + state = LSB; + } + } + } + + s->reload = supports_reload; + + /* We use the long description only if + * no short description is set. */ + + if (short_description) + description = short_description; + else if (chkconfig_description) + description = chkconfig_description; + else if (long_description) + description = long_description; + else + description = NULL; + + if (description) { + char *d; + + d = strappend(s->has_lsb ? "LSB: " : "SYSV: ", description); + if (!d) + return log_oom(); + + s->description = d; + } + + s->loaded = true; + return 0; + +fail: + return log_error_errno(errno, "Failed to read configuration file '%s': %m", s->path); +} + +static int fix_order(SysvStub *s, Hashmap *all_services) { + SysvStub *other; + Iterator j; + int r; + + assert(s); + + if (!s->loaded) + return 0; + + if (s->sysv_start_priority < 0) + return 0; + + HASHMAP_FOREACH(other, all_services, j) { + if (s == other) + continue; + + if (!other->loaded) + continue; + + if (other->sysv_start_priority < 0) + continue; + + /* If both units have modern headers we don't care + * about the priorities */ + if (s->has_lsb && other->has_lsb) + continue; + + if (other->sysv_start_priority < s->sysv_start_priority) { + r = strv_extend(&s->after, other->name); + if (r < 0) + return log_oom(); + + } else if (other->sysv_start_priority > s->sysv_start_priority) { + r = strv_extend(&s->before, other->name); + if (r < 0) + return log_oom(); + } else + continue; + + /* FIXME: Maybe we should compare the name here lexicographically? */ + } + + return 0; +} + +static int acquire_search_path(const char *def, const char *envvar, char ***ret) { + _cleanup_strv_free_ char **l = NULL; + const char *e; + int r; + + assert(def); + assert(envvar); + + e = getenv(envvar); + if (e) { + r = path_split_and_make_absolute(e, &l); + if (r < 0) + return log_error_errno(r, "Failed to make $%s search path absolute: %m", envvar); + } + + if (strv_isempty(l)) { + strv_free(l); + + l = strv_new(def, NULL); + if (!l) + return log_oom(); + } + + if (!path_strv_resolve_uniq(l, NULL)) + return log_oom(); + + *ret = l; + l = NULL; + + return 0; +} + +static int enumerate_sysv(const LookupPaths *lp, Hashmap *all_services) { + _cleanup_strv_free_ char **sysvinit_path = NULL; + char **path; + int r; + + assert(lp); + + r = acquire_search_path(SYSTEM_SYSVINIT_PATH, "SYSTEMD_SYSVINIT_PATH", &sysvinit_path); + if (r < 0) + return r; + + STRV_FOREACH(path, sysvinit_path) { + _cleanup_closedir_ DIR *d = NULL; + struct dirent *de; + + d = opendir(*path); + if (!d) { + if (errno != ENOENT) + log_warning_errno(errno, "Opening %s failed, ignoring: %m", *path); + continue; + } + + FOREACH_DIRENT(de, d, log_error_errno(errno, "Failed to enumerate directory %s, ignoring: %m", *path)) { + _cleanup_free_ char *fpath = NULL, *name = NULL; + _cleanup_(free_sysvstubp) SysvStub *service = NULL; + struct stat st; + + if (fstatat(dirfd(d), de->d_name, &st, 0) < 0) { + log_warning_errno(errno, "stat() failed on %s/%s, ignoring: %m", *path, de->d_name); + continue; + } + + if (!(st.st_mode & S_IXUSR)) + continue; + + if (!S_ISREG(st.st_mode)) + continue; + + name = sysv_translate_name(de->d_name); + if (!name) + return log_oom(); + + if (hashmap_contains(all_services, name)) + continue; + + r = unit_file_exists(UNIT_FILE_SYSTEM, lp, name); + if (r < 0 && !IN_SET(r, -ELOOP, -ERFKILL, -EADDRNOTAVAIL)) { + log_debug_errno(r, "Failed to detect whether %s exists, skipping: %m", name); + continue; + } else if (r != 0) { + log_debug("Native unit for %s already exists, skipping.", name); + continue; + } + + fpath = strjoin(*path, "/", de->d_name, NULL); + if (!fpath) + return log_oom(); + + service = new0(SysvStub, 1); + if (!service) + return log_oom(); + + service->sysv_start_priority = -1; + service->name = name; + service->path = fpath; + name = fpath = NULL; + + r = hashmap_put(all_services, service->name, service); + if (r < 0) + return log_oom(); + + service = NULL; + } + } + + return 0; +} + +static int set_dependencies_from_rcnd(const LookupPaths *lp, Hashmap *all_services) { + Set *runlevel_services[ELEMENTSOF(rcnd_table)] = {}; + _cleanup_set_free_ Set *shutdown_services = NULL; + _cleanup_strv_free_ char **sysvrcnd_path = NULL; + SysvStub *service; + unsigned i; + Iterator j; + char **p; + int r; + + assert(lp); + + r = acquire_search_path(SYSTEM_SYSVRCND_PATH, "SYSTEMD_SYSVRCND_PATH", &sysvrcnd_path); + if (r < 0) + return r; + + STRV_FOREACH(p, sysvrcnd_path) { + for (i = 0; i < ELEMENTSOF(rcnd_table); i ++) { + + _cleanup_closedir_ DIR *d = NULL; + _cleanup_free_ char *path = NULL; + struct dirent *de; + + path = strjoin(*p, "/", rcnd_table[i].path, NULL); + if (!path) { + r = log_oom(); + goto finish; + } + + d = opendir(path); + if (!d) { + if (errno != ENOENT) + log_warning_errno(errno, "Opening %s failed, ignoring: %m", path); + + continue; + } + + FOREACH_DIRENT(de, d, log_error_errno(errno, "Failed to enumerate directory %s, ignoring: %m", path)) { + _cleanup_free_ char *name = NULL, *fpath = NULL; + int a, b; + + if (de->d_name[0] != 'S' && de->d_name[0] != 'K') + continue; + + if (strlen(de->d_name) < 4) + continue; + + a = undecchar(de->d_name[1]); + b = undecchar(de->d_name[2]); + + if (a < 0 || b < 0) + continue; + + fpath = strjoin(*p, "/", de->d_name, NULL); + if (!fpath) { + r = log_oom(); + goto finish; + } + + name = sysv_translate_name(de->d_name + 3); + if (!name) { + r = log_oom(); + goto finish; + } + + service = hashmap_get(all_services, name); + if (!service) { + log_debug("Ignoring %s symlink in %s, not generating %s.", de->d_name, rcnd_table[i].path, name); + continue; + } + + if (de->d_name[0] == 'S') { + + if (rcnd_table[i].type == RUNLEVEL_UP) + service->sysv_start_priority = MAX(a*10 + b, service->sysv_start_priority); + + r = set_ensure_allocated(&runlevel_services[i], NULL); + if (r < 0) { + log_oom(); + goto finish; + } + + r = set_put(runlevel_services[i], service); + if (r < 0) { + log_oom(); + goto finish; + } + + } else if (de->d_name[0] == 'K' && + (rcnd_table[i].type == RUNLEVEL_DOWN)) { + + r = set_ensure_allocated(&shutdown_services, NULL); + if (r < 0) { + log_oom(); + goto finish; + } + + r = set_put(shutdown_services, service); + if (r < 0) { + log_oom(); + goto finish; + } + } + } + } + } + + + for (i = 0; i < ELEMENTSOF(rcnd_table); i ++) + SET_FOREACH(service, runlevel_services[i], j) { + r = strv_extend(&service->before, rcnd_table[i].target); + if (r < 0) { + log_oom(); + goto finish; + } + r = strv_extend(&service->wanted_by, rcnd_table[i].target); + if (r < 0) { + log_oom(); + goto finish; + } + } + + SET_FOREACH(service, shutdown_services, j) { + r = strv_extend(&service->before, SPECIAL_SHUTDOWN_TARGET); + if (r < 0) { + log_oom(); + goto finish; + } + r = strv_extend(&service->conflicts, SPECIAL_SHUTDOWN_TARGET); + if (r < 0) { + log_oom(); + goto finish; + } + } + + r = 0; + +finish: + for (i = 0; i < ELEMENTSOF(rcnd_table); i++) + set_free(runlevel_services[i]); + + return r; +} + +int main(int argc, char *argv[]) { + _cleanup_(free_sysvstub_hashmapp) Hashmap *all_services = NULL; + _cleanup_lookup_paths_free_ LookupPaths lp = {}; + SysvStub *service; + Iterator j; + int r; + + if (argc > 1 && argc != 4) { + log_error("This program takes three or no arguments."); + return EXIT_FAILURE; + } + + if (argc > 1) + arg_dest = argv[3]; + + log_set_target(LOG_TARGET_SAFE); + log_parse_environment(); + log_open(); + + umask(0022); + + r = lookup_paths_init(&lp, UNIT_FILE_SYSTEM, LOOKUP_PATHS_EXCLUDE_GENERATED, NULL); + if (r < 0) { + log_error_errno(r, "Failed to find lookup paths: %m"); + goto finish; + } + + all_services = hashmap_new(&string_hash_ops); + if (!all_services) { + r = log_oom(); + goto finish; + } + + r = enumerate_sysv(&lp, all_services); + if (r < 0) + goto finish; + + r = set_dependencies_from_rcnd(&lp, all_services); + if (r < 0) + goto finish; + + HASHMAP_FOREACH(service, all_services, j) + (void) load_sysv(service); + + HASHMAP_FOREACH(service, all_services, j) { + (void) fix_order(service, all_services); + (void) generate_unit_file(service); + } + + r = 0; + +finish: + return r < 0 ? EXIT_FAILURE : EXIT_SUCCESS; +} diff --git a/src/grp-system/libcore/.gitignore b/src/grp-system/libcore/.gitignore new file mode 100644 index 0000000000..465b4fcc20 --- /dev/null +++ b/src/grp-system/libcore/.gitignore @@ -0,0 +1,3 @@ +/macros.systemd +/triggers.systemd +/systemd.pc diff --git a/src/grp-system/libcore/Makefile b/src/grp-system/libcore/Makefile new file mode 100644 index 0000000000..a93e1e5be0 --- /dev/null +++ b/src/grp-system/libcore/Makefile @@ -0,0 +1,175 @@ +# -*- 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-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: $(outdir)/load-fragment-gperf.gperf + $(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 ";" }' < $< > $@ + +systemd.CPPFLAGS += -I$(topsrcdir)/src/libudev/src +systemd.CPPFLAGS += -DMOUNT_PATH=\"$(MOUNT_PATH)\" +systemd.CPPFLAGS += -DUMOUNT_PATH=\"$(UMOUNT_PATH)\" +systemd.CPPFLAGS += -DSYSTEMD_CGROUP_AGENT_PATH=\"$(libexecdir)/systemd-cgroups-agent\" +systemd.CPPFLAGS += -DSYSTEMD_BINARY_PATH=\"$(libexecdir)/systemd\" + +include $(topsrcdir)/build-aux/Makefile.tail.mk diff --git a/src/grp-system/libcore/audit-fd.c b/src/grp-system/libcore/audit-fd.c new file mode 100644 index 0000000000..8ea525eda3 --- /dev/null +++ b/src/grp-system/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 "basic/fd-util.h" +#include "basic/log.h" +#include "basic/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/grp-system/libcore/audit-fd.h b/src/grp-system/libcore/audit-fd.h new file mode 100644 index 0000000000..0eccb59210 --- /dev/null +++ b/src/grp-system/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/grp-system/libcore/automount.c b/src/grp-system/libcore/automount.c new file mode 100644 index 0000000000..145ed5ab5d --- /dev/null +++ b/src/grp-system/libcore/automount.c @@ -0,0 +1,1108 @@ +/*** + This file is part of systemd. + + Copyright 2010 Lennart Poettering + + systemd is free software; you can redistribute it and/or modify it + under the terms of the GNU Lesser General Public License as published by + the Free Software Foundation; either version 2.1 of the License, or + (at your option) any later version. + + systemd is distributed in the hope that it will be useful, but + WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + Lesser General Public License for more details. + + You 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 "basic/alloc-util.h" +#include "basic/async.h" +#include "automount.h" +#include "sd-bus/bus-error.h" +#include "shared/bus-util.h" +#include "dbus-automount.h" +#include "basic/fd-util.h" +#include "basic/formats-util.h" +#include "basic/io-util.h" +#include "basic/label.h" +#include "basic/mkdir.h" +#include "basic/mount-util.h" +#include "mount.h" +#include "basic/parse-util.h" +#include "basic/path-util.h" +#include "basic/process-util.h" +#include "basic/special.h" +#include "basic/stdio-util.h" +#include "basic/string-table.h" +#include "basic/string-util.h" +#include "basic/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 int automount_start_expire(Automount *a); +static void automount_stop_expire(Automount *a); +static int automount_send_ready(Automount *a, Set *tokens, int status); + +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 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 (!MANAGER_IS_SYSTEM(UNIT(a)->manager)) + 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_RUNNING) + automount_stop_expire(a); + + 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 != 0) { + 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 != 0) + 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 void automount_trigger_notify(Unit *u, Unit *other) { + Automount *a = AUTOMOUNT(u); + int r; + + assert(a); + assert(other); + + /* Filter out invocations with bogus state */ + if (other->load_state != UNIT_LOADED || other->type != UNIT_MOUNT) + return; + + /* Don't propagate state changes from the mount if we are already down */ + if (!IN_SET(a->state, AUTOMOUNT_WAITING, AUTOMOUNT_RUNNING)) + return; + + /* Propagate start limit hit state */ + if (other->start_limit_hit) { + automount_enter_dead(a, AUTOMOUNT_FAILURE_MOUNT_START_LIMIT_HIT); + return; + } + + /* Don't propagate anything if there's still a job queued */ + if (other->job) + return; + + /* The mount is successfully established */ + if (IN_SET(MOUNT(other)->state, MOUNT_MOUNTED, MOUNT_REMOUNTING)) { + (void) 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"); + + automount_set_state(a, AUTOMOUNT_RUNNING); + } + + /* The mount is in some unhappy state now, let's unfreeze any waiting clients */ + if (IN_SET(MOUNT(other)->state, + MOUNT_DEAD, MOUNT_UNMOUNTING, + MOUNT_MOUNTING_SIGTERM, MOUNT_MOUNTING_SIGKILL, + MOUNT_REMOUNTING_SIGTERM, MOUNT_REMOUNTING_SIGKILL, + MOUNT_UNMOUNTING_SIGTERM, MOUNT_UNMOUNTING_SIGKILL, + MOUNT_FAILED)) { + + (void) automount_send_ready(a, a->tokens, -ENODEV); + + automount_set_state(a, AUTOMOUNT_WAITING); + } +} + +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_stop_expire(Automount *a) { + assert(a); + + if (!a->expire_event_source) + return; + + (void) sd_event_source_set_enabled(a->expire_event_source, SD_EVENT_OFF); +} + +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; + int r; + + 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; + } + + r = unit_start_limit_test(u); + if (r < 0) { + automount_enter_dead(a, AUTOMOUNT_FAILURE_START_LIMIT_HIT); + return r; + } + + 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); + + automount_stop_expire(a); + + 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", + [AUTOMOUNT_FAILURE_START_LIMIT_HIT] = "start-limit-hit", + [AUTOMOUNT_FAILURE_MOUNT_START_LIMIT_HIT] = "mount-start-limit-hit", +}; + +DEFINE_STRING_TABLE_LOOKUP(automount_result, AutomountResult); + +const UnitVTable automount_vtable = { + .object_size = sizeof(Automount), + + .sections = + "Unit\0" + "Automount\0" + "Install\0", + + .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, + + .trigger_notify = automount_trigger_notify, + + .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/grp-system/libcore/automount.h b/src/grp-system/libcore/automount.h new file mode 100644 index 0000000000..76a201178e --- /dev/null +++ b/src/grp-system/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_FAILURE_START_LIMIT_HIT, + AUTOMOUNT_FAILURE_MOUNT_START_LIMIT_HIT, + _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; + +const char* automount_result_to_string(AutomountResult i) _const_; +AutomountResult automount_result_from_string(const char *s) _pure_; diff --git a/src/grp-system/libcore/bus-policy.c b/src/grp-system/libcore/bus-policy.c new file mode 100644 index 0000000000..cc228bc42f --- /dev/null +++ b/src/grp-system/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 "basic/alloc-util.h" +#include "sd-bus/bus-kernel.h" +#include "bus-policy.h" +#include "sd-bus/kdbus.h" +#include "basic/string-table.h" +#include "basic/user-util.h" +#include "basic/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/grp-system/libcore/bus-policy.h b/src/grp-system/libcore/bus-policy.h new file mode 100644 index 0000000000..a6d19c1e9a --- /dev/null +++ b/src/grp-system/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 "sd-bus/kdbus.h" +#include "basic/list.h" +#include "basic/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/grp-system/libcore/busname.c b/src/grp-system/libcore/busname.c new file mode 100644 index 0000000000..765fd7a442 --- /dev/null +++ b/src/grp-system/libcore/busname.c @@ -0,0 +1,1082 @@ +/*** + 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 "basic/alloc-util.h" +#include "sd-bus/bus-internal.h" +#include "sd-bus/bus-kernel.h" +#include "bus-policy.h" +#include "shared/bus-util.h" +#include "busname.h" +#include "dbus-busname.h" +#include "basic/fd-util.h" +#include "basic/formats-util.h" +#include "sd-bus/kdbus.h" +#include "basic/parse-util.h" +#include "basic/process-util.h" +#include "service.h" +#include "basic/signal-util.h" +#include "basic/special.h" +#include "basic/string-table.h" +#include "basic/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 (MANAGER_IS_SYSTEM(UNIT(n)->manager)) { + 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 = MANAGER_IS_SYSTEM(UNIT(n)->manager) ? "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); + int r; + + 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)); + + r = unit_start_limit_test(u); + if (r < 0) { + busname_enter_dead(n, BUSNAME_FAILURE_START_LIMIT_HIT); + return r; + } + + 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 int busname_control_pid(Unit *u) { + BusName *n = BUSNAME(u); + + assert(n); + + return n->control_pid; +} + +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_START_LIMIT_HIT] = "start-limit-hit", + [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", + + .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, + + .control_pid = busname_control_pid, + + .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/grp-system/libcore/busname.h b/src/grp-system/libcore/busname.h new file mode 100644 index 0000000000..a8562db458 --- /dev/null +++ b/src/grp-system/libcore/busname.h @@ -0,0 +1,69 @@ +#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" +#include "bus-policy.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_START_LIMIT_HIT, + 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/grp-system/libcore/cgroup.c b/src/grp-system/libcore/cgroup.c new file mode 100644 index 0000000000..43d494cee7 --- /dev/null +++ b/src/grp-system/libcore/cgroup.c @@ -0,0 +1,1895 @@ +/*** + 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 "basic/alloc-util.h" +#include "basic/cgroup-util.h" +#include "cgroup.h" +#include "basic/fd-util.h" +#include "basic/fileio.h" +#include "basic/fs-util.h" +#include "basic/parse-util.h" +#include "basic/path-util.h" +#include "basic/process-util.h" +#include "basic/special.h" +#include "basic/string-table.h" +#include "basic/string-util.h" +#include "basic/stdio-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->io_weight = CGROUP_WEIGHT_INVALID; + c->startup_io_weight = CGROUP_WEIGHT_INVALID; + + 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_io_device_weight(CGroupContext *c, CGroupIODeviceWeight *w) { + assert(c); + assert(w); + + LIST_REMOVE(device_weights, c->io_device_weights, w); + free(w->path); + free(w); +} + +void cgroup_context_free_io_device_limit(CGroupContext *c, CGroupIODeviceLimit *l) { + assert(c); + assert(l); + + LIST_REMOVE(device_limits, c->io_device_limits, l); + free(l->path); + free(l); +} + +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->io_device_weights) + cgroup_context_free_io_device_weight(c, c->io_device_weights); + + while (c->io_device_limits) + cgroup_context_free_io_device_limit(c, c->io_device_limits); + + 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) { + CGroupIODeviceLimit *il; + CGroupIODeviceWeight *iw; + CGroupBlockIODeviceBandwidth *b; + CGroupBlockIODeviceWeight *w; + CGroupDeviceAllow *a; + char u[FORMAT_TIMESPAN_MAX]; + + assert(c); + assert(f); + + prefix = strempty(prefix); + + fprintf(f, + "%sCPUAccounting=%s\n" + "%sIOAccounting=%s\n" + "%sBlockIOAccounting=%s\n" + "%sMemoryAccounting=%s\n" + "%sTasksAccounting=%s\n" + "%sCPUShares=%" PRIu64 "\n" + "%sStartupCPUShares=%" PRIu64 "\n" + "%sCPUQuotaPerSecSec=%s\n" + "%sIOWeight=%" PRIu64 "\n" + "%sStartupIOWeight=%" PRIu64 "\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->io_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->io_weight, + prefix, c->startup_io_weight, + 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, iw, c->io_device_weights) + fprintf(f, + "%sIODeviceWeight=%s %" PRIu64, + prefix, + iw->path, + iw->weight); + + LIST_FOREACH(device_limits, il, c->io_device_limits) { + char buf[FORMAT_BYTES_MAX]; + CGroupIOLimitType type; + + for (type = 0; type < _CGROUP_IO_LIMIT_TYPE_MAX; type++) + if (il->limits[type] != cgroup_io_limit_defaults[type]) + fprintf(f, + "%s%s=%s %s\n", + prefix, + cgroup_io_limit_type_to_string(type), + il->path, + format_bytes(buf, sizeof(buf), il->limits[type])); + } + + 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]; + + if (b->rbps != CGROUP_LIMIT_MAX) + fprintf(f, + "%sBlockIOReadBandwidth=%s %s\n", + prefix, + b->path, + format_bytes(buf, sizeof(buf), b->rbps)); + if (b->wbps != CGROUP_LIMIT_MAX) + fprintf(f, + "%sBlockIOWriteBandwidth=%s %s\n", + prefix, + b->path, + format_bytes(buf, sizeof(buf), b->wbps)); + } +} + +static int lookup_block_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; +} + +static bool cgroup_context_has_io_config(CGroupContext *c) { + return c->io_accounting || + c->io_weight != CGROUP_WEIGHT_INVALID || + c->startup_io_weight != CGROUP_WEIGHT_INVALID || + c->io_device_weights || + c->io_device_limits; +} + +static bool cgroup_context_has_blockio_config(CGroupContext *c) { + return 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; +} + +static uint64_t cgroup_context_io_weight(CGroupContext *c, ManagerState state) { + if (IN_SET(state, MANAGER_STARTING, MANAGER_INITIALIZING) && + c->startup_io_weight != CGROUP_WEIGHT_INVALID) + return c->startup_io_weight; + else if (c->io_weight != CGROUP_WEIGHT_INVALID) + return c->io_weight; + else + return CGROUP_WEIGHT_DEFAULT; +} + +static uint64_t cgroup_context_blkio_weight(CGroupContext *c, ManagerState state) { + if (IN_SET(state, MANAGER_STARTING, MANAGER_INITIALIZING) && + c->startup_blockio_weight != CGROUP_BLKIO_WEIGHT_INVALID) + return c->startup_blockio_weight; + else if (c->blockio_weight != CGROUP_BLKIO_WEIGHT_INVALID) + return c->blockio_weight; + else + return CGROUP_BLKIO_WEIGHT_DEFAULT; +} + +static uint64_t cgroup_weight_blkio_to_io(uint64_t blkio_weight) { + return CLAMP(blkio_weight * CGROUP_WEIGHT_DEFAULT / CGROUP_BLKIO_WEIGHT_DEFAULT, + CGROUP_WEIGHT_MIN, CGROUP_WEIGHT_MAX); +} + +static uint64_t cgroup_weight_io_to_blkio(uint64_t io_weight) { + return CLAMP(io_weight * CGROUP_BLKIO_WEIGHT_DEFAULT / CGROUP_WEIGHT_DEFAULT, + CGROUP_BLKIO_WEIGHT_MIN, CGROUP_BLKIO_WEIGHT_MAX); +} + +static void cgroup_apply_io_device_weight(const char *path, const char *dev_path, uint64_t io_weight) { + char buf[DECIMAL_STR_MAX(dev_t)*2+2+DECIMAL_STR_MAX(uint64_t)+1]; + dev_t dev; + int r; + + r = lookup_block_device(dev_path, &dev); + if (r < 0) + return; + + xsprintf(buf, "%u:%u %" PRIu64 "\n", major(dev), minor(dev), io_weight); + r = cg_set_attribute("io", path, "io.weight", buf); + if (r < 0) + log_full_errno(IN_SET(r, -ENOENT, -EROFS, -EACCES) ? LOG_DEBUG : LOG_WARNING, r, + "Failed to set io.weight on %s: %m", path); +} + +static void cgroup_apply_blkio_device_weight(const char *path, const char *dev_path, uint64_t blkio_weight) { + char buf[DECIMAL_STR_MAX(dev_t)*2+2+DECIMAL_STR_MAX(uint64_t)+1]; + dev_t dev; + int r; + + r = lookup_block_device(dev_path, &dev); + if (r < 0) + return; + + xsprintf(buf, "%u:%u %" PRIu64 "\n", major(dev), minor(dev), blkio_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); +} + +static unsigned cgroup_apply_io_device_limit(const char *path, const char *dev_path, uint64_t *limits) { + char limit_bufs[_CGROUP_IO_LIMIT_TYPE_MAX][DECIMAL_STR_MAX(uint64_t)]; + char buf[DECIMAL_STR_MAX(dev_t)*2+2+(6+DECIMAL_STR_MAX(uint64_t)+1)*4]; + CGroupIOLimitType type; + dev_t dev; + unsigned n = 0; + int r; + + r = lookup_block_device(dev_path, &dev); + if (r < 0) + return 0; + + for (type = 0; type < _CGROUP_IO_LIMIT_TYPE_MAX; type++) { + if (limits[type] != cgroup_io_limit_defaults[type]) { + xsprintf(limit_bufs[type], "%" PRIu64, limits[type]); + n++; + } else { + xsprintf(limit_bufs[type], "%s", limits[type] == CGROUP_LIMIT_MAX ? "max" : "0"); + } + } + + xsprintf(buf, "%u:%u rbps=%s wbps=%s riops=%s wiops=%s\n", major(dev), minor(dev), + limit_bufs[CGROUP_IO_RBPS_MAX], limit_bufs[CGROUP_IO_WBPS_MAX], + limit_bufs[CGROUP_IO_RIOPS_MAX], limit_bufs[CGROUP_IO_WIOPS_MAX]); + r = cg_set_attribute("io", path, "io.max", buf); + if (r < 0) + log_full_errno(IN_SET(r, -ENOENT, -EROFS, -EACCES) ? LOG_DEBUG : LOG_WARNING, r, + "Failed to set io.max on %s: %m", path); + return n; +} + +static unsigned cgroup_apply_blkio_device_limit(const char *path, const char *dev_path, uint64_t rbps, uint64_t wbps) { + char buf[DECIMAL_STR_MAX(dev_t)*2+2+DECIMAL_STR_MAX(uint64_t)+1]; + dev_t dev; + unsigned n = 0; + int r; + + r = lookup_block_device(dev_path, &dev); + if (r < 0) + return 0; + + if (rbps != CGROUP_LIMIT_MAX) + n++; + sprintf(buf, "%u:%u %" PRIu64 "\n", major(dev), minor(dev), rbps); + r = cg_set_attribute("blkio", path, "blkio.throttle.read_bps_device", buf); + if (r < 0) + log_full_errno(IN_SET(r, -ENOENT, -EROFS, -EACCES) ? LOG_DEBUG : LOG_WARNING, r, + "Failed to set blkio.throttle.read_bps_device on %s: %m", path); + + if (wbps != CGROUP_LIMIT_MAX) + n++; + sprintf(buf, "%u:%u %" PRIu64 "\n", major(dev), minor(dev), wbps); + r = cg_set_attribute("blkio", path, "blkio.throttle.write_bps_device", buf); + if (r < 0) + log_full_errno(IN_SET(r, -ENOENT, -EROFS, -EACCES) ? LOG_DEBUG : LOG_WARNING, r, + "Failed to set blkio.throttle.write_bps_device on %s: %m", path); + + return n; +} + +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_IO) { + bool has_io = cgroup_context_has_io_config(c); + bool has_blockio = cgroup_context_has_blockio_config(c); + + if (!is_root) { + char buf[8+DECIMAL_STR_MAX(uint64_t)+1]; + uint64_t weight; + + if (has_io) + weight = cgroup_context_io_weight(c, state); + else if (has_blockio) + weight = cgroup_weight_blkio_to_io(cgroup_context_blkio_weight(c, state)); + else + weight = CGROUP_WEIGHT_DEFAULT; + + xsprintf(buf, "default %" PRIu64 "\n", weight); + r = cg_set_attribute("io", path, "io.weight", buf); + if (r < 0) + log_full_errno(IN_SET(r, -ENOENT, -EROFS, -EACCES) ? LOG_DEBUG : LOG_WARNING, r, + "Failed to set io.weight on %s: %m", path); + + if (has_io) { + CGroupIODeviceWeight *w; + + /* FIXME: no way to reset this list */ + LIST_FOREACH(device_weights, w, c->io_device_weights) + cgroup_apply_io_device_weight(path, w->path, w->weight); + } else if (has_blockio) { + CGroupBlockIODeviceWeight *w; + + /* FIXME: no way to reset this list */ + LIST_FOREACH(device_weights, w, c->blockio_device_weights) + cgroup_apply_io_device_weight(path, w->path, cgroup_weight_blkio_to_io(w->weight)); + } + } + + /* Apply limits and free ones without config. */ + if (has_io) { + CGroupIODeviceLimit *l, *next; + + LIST_FOREACH_SAFE(device_limits, l, next, c->io_device_limits) { + if (!cgroup_apply_io_device_limit(path, l->path, l->limits)) + cgroup_context_free_io_device_limit(c, l); + } + } else if (has_blockio) { + CGroupBlockIODeviceBandwidth *b, *next; + + LIST_FOREACH_SAFE(device_bandwidths, b, next, c->blockio_device_bandwidths) { + uint64_t limits[_CGROUP_IO_LIMIT_TYPE_MAX]; + CGroupIOLimitType type; + + for (type = 0; type < _CGROUP_IO_LIMIT_TYPE_MAX; type++) + limits[type] = cgroup_io_limit_defaults[type]; + + limits[CGROUP_IO_RBPS_MAX] = b->rbps; + limits[CGROUP_IO_WBPS_MAX] = b->wbps; + + if (!cgroup_apply_io_device_limit(path, b->path, limits)) + cgroup_context_free_blockio_device_bandwidth(c, b); + } + } + } + + if (mask & CGROUP_MASK_BLKIO) { + bool has_io = cgroup_context_has_io_config(c); + bool has_blockio = cgroup_context_has_blockio_config(c); + + if (!is_root) { + char buf[DECIMAL_STR_MAX(uint64_t)+1]; + uint64_t weight; + + if (has_blockio) + weight = cgroup_context_blkio_weight(c, state); + else if (has_io) + weight = cgroup_weight_io_to_blkio(cgroup_context_io_weight(c, state)); + else + weight = CGROUP_BLKIO_WEIGHT_DEFAULT; + + xsprintf(buf, "%" PRIu64 "\n", weight); + 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); + + if (has_blockio) { + CGroupBlockIODeviceWeight *w; + + /* FIXME: no way to reset this list */ + LIST_FOREACH(device_weights, w, c->blockio_device_weights) + cgroup_apply_blkio_device_weight(path, w->path, w->weight); + } else if (has_io) { + CGroupIODeviceWeight *w; + + /* FIXME: no way to reset this list */ + LIST_FOREACH(device_weights, w, c->io_device_weights) + cgroup_apply_blkio_device_weight(path, w->path, cgroup_weight_io_to_blkio(w->weight)); + } + } + + /* Apply limits and free ones without config. */ + if (has_blockio) { + CGroupBlockIODeviceBandwidth *b, *next; + + LIST_FOREACH_SAFE(device_bandwidths, b, next, c->blockio_device_bandwidths) { + if (!cgroup_apply_blkio_device_limit(path, b->path, b->rbps, b->wbps)) + cgroup_context_free_blockio_device_bandwidth(c, b); + } + } else if (has_io) { + CGroupIODeviceLimit *l, *next; + + LIST_FOREACH_SAFE(device_limits, l, next, c->io_device_limits) { + if (!cgroup_apply_blkio_device_limit(path, l->path, l->limits[CGROUP_IO_RBPS_MAX], l->limits[CGROUP_IO_WBPS_MAX])) + cgroup_context_free_io_device_limit(c, l); + } + } + } + + 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 (cgroup_context_has_io_config(c) || cgroup_context_has_blockio_config(c)) + mask |= CGROUP_MASK_IO | 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 *events = 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.events", &events); + if (r < 0) + return log_oom(); + + u->cgroup_inotify_wd = inotify_add_watch(u->manager->cgroup_inotify_fd, events, 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; + u->cgroup_enabled_mask = enable_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, CGroupMask enable_mask) { + assert(u); + + return u->cgroup_realized && u->cgroup_realized_mask == target_mask && u->cgroup_enabled_mask == enable_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); + enable_mask = unit_get_enable_mask(u); + + if (unit_has_mask_realized(u, target_mask, enable_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 */ + 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), unit_get_enable_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; + u->cgroup_enabled_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 && MANAGER_IS_SYSTEM(m)) { + 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"); + + /* Process cgroup empty notifications early, but after service notifications and SIGCHLD. Also + * see handling of cgroup agent notifications, for the classic cgroup hierarchy support. */ + r = sd_event_source_set_priority(m->cgroup_inotify_event_source, SD_EVENT_PRIORITY_NORMAL-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 (MANAGER_IS_SYSTEM(m)) { + + /* 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); + + log_debug("Got cgroup empty notification for: %s", 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; + + /* always invalidate compat pairs together */ + if (m & (CGROUP_MASK_IO | CGROUP_MASK_BLKIO)) + m |= CGROUP_MASK_IO | CGROUP_MASK_BLKIO; + + 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_IO|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/grp-system/libcore/cgroup.h b/src/grp-system/libcore/cgroup.h new file mode 100644 index 0000000000..69ff9832bf --- /dev/null +++ b/src/grp-system/libcore/cgroup.h @@ -0,0 +1,181 @@ +#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 "basic/list.h" +#include "basic/time-util.h" +#include "basic/cgroup-util.h" + +typedef struct CGroupContext CGroupContext; +typedef struct CGroupDeviceAllow CGroupDeviceAllow; +typedef struct CGroupIODeviceWeight CGroupIODeviceWeight; +typedef struct CGroupIODeviceLimit CGroupIODeviceLimit; +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 CGroupIODeviceWeight { + LIST_FIELDS(CGroupIODeviceWeight, device_weights); + char *path; + uint64_t weight; +}; + +struct CGroupIODeviceLimit { + LIST_FIELDS(CGroupIODeviceLimit, device_limits); + char *path; + uint64_t limits[_CGROUP_IO_LIMIT_TYPE_MAX]; +}; + +struct CGroupBlockIODeviceWeight { + LIST_FIELDS(CGroupBlockIODeviceWeight, device_weights); + char *path; + uint64_t weight; +}; + +struct CGroupBlockIODeviceBandwidth { + LIST_FIELDS(CGroupBlockIODeviceBandwidth, device_bandwidths); + char *path; + uint64_t rbps; + uint64_t wbps; +}; + +struct CGroupContext { + bool cpu_accounting; + bool io_accounting; + bool blockio_accounting; + bool memory_accounting; + bool tasks_accounting; + + /* For unified hierarchy */ + uint64_t io_weight; + uint64_t startup_io_weight; + LIST_HEAD(CGroupIODeviceWeight, io_device_weights); + LIST_HEAD(CGroupIODeviceLimit, io_device_limits); + + /* For legacy hierarchies */ + 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); + + /* Common */ + uint64_t tasks_max; + + bool delegate; +}; + +#include "basic/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_io_device_weight(CGroupContext *c, CGroupIODeviceWeight *w); +void cgroup_context_free_io_device_limit(CGroupContext *c, CGroupIODeviceLimit *l); +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/grp-system/libcore/dbus-automount.c b/src/grp-system/libcore/dbus-automount.c new file mode 100644 index 0000000000..bc44dc1c0a --- /dev/null +++ b/src/grp-system/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 "shared/bus-util.h" +#include "dbus-automount.h" +#include "basic/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/grp-system/libcore/dbus-automount.h b/src/grp-system/libcore/dbus-automount.h new file mode 100644 index 0000000000..7b51eb973a --- /dev/null +++ b/src/grp-system/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/grp-system/libcore/dbus-busname.c b/src/grp-system/libcore/dbus-busname.c new file mode 100644 index 0000000000..80e7484f46 --- /dev/null +++ b/src/grp-system/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 "shared/bus-util.h" +#include "busname.h" +#include "dbus-busname.h" +#include "basic/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/grp-system/libcore/dbus-busname.h b/src/grp-system/libcore/dbus-busname.h new file mode 100644 index 0000000000..8643d1a404 --- /dev/null +++ b/src/grp-system/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/grp-system/libcore/dbus-cgroup.c b/src/grp-system/libcore/dbus-cgroup.c new file mode 100644 index 0000000000..86f7c626c4 --- /dev/null +++ b/src/grp-system/libcore/dbus-cgroup.c @@ -0,0 +1,1004 @@ +/*** + 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 "basic/alloc-util.h" +#include "shared/bus-util.h" +#include "basic/cgroup-util.h" +#include "cgroup.h" +#include "dbus-cgroup.h" +#include "basic/fd-util.h" +#include "basic/fileio.h" +#include "basic/path-util.h" + +static BUS_DEFINE_PROPERTY_GET_ENUM(property_get_cgroup_device_policy, cgroup_device_policy, CGroupDevicePolicy); + +static int property_get_io_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; + CGroupIODeviceWeight *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->io_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_io_device_limits( + 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; + CGroupIODeviceLimit *l; + 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_limits, l, c->io_device_limits) { + CGroupIOLimitType type; + + type = cgroup_io_limit_type_from_string(property); + if (type < 0 || l->limits[type] == cgroup_io_limit_defaults[type]) + continue; + + r = sd_bus_message_append(reply, "(st)", l->path, l->limits[type]); + if (r < 0) + return r; + } + + return sd_bus_message_close_container(reply); +} + +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) { + uint64_t v; + + if (streq(property, "BlockIOReadBandwidth")) + v = b->rbps; + else + v = b->wbps; + + if (v == CGROUP_LIMIT_MAX) + continue; + + r = sd_bus_message_append(reply, "(st)", b->path, v); + 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("IOAccounting", "b", bus_property_get_bool, offsetof(CGroupContext, io_accounting), 0), + SD_BUS_PROPERTY("IOWeight", "t", NULL, offsetof(CGroupContext, io_weight), 0), + SD_BUS_PROPERTY("StartupIOWeight", "t", NULL, offsetof(CGroupContext, startup_io_weight), 0), + SD_BUS_PROPERTY("IODeviceWeight", "a(st)", property_get_io_device_weight, 0, 0), + SD_BUS_PROPERTY("IOReadBandwidthMax", "a(st)", property_get_io_device_limits, 0, 0), + SD_BUS_PROPERTY("IOWriteBandwidthMax", "a(st)", property_get_io_device_limits, 0, 0), + SD_BUS_PROPERTY("IOReadIOPSMax", "a(st)", property_get_io_device_limits, 0, 0), + SD_BUS_PROPERTY("IOWriteIOPSMax", "a(st)", property_get_io_device_limits, 0, 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) { + + CGroupIOLimitType iol_type; + 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, "IOAccounting")) { + int b; + + r = sd_bus_message_read(message, "b", &b); + if (r < 0) + return r; + + if (mode != UNIT_CHECK) { + c->io_accounting = b; + unit_invalidate_cgroup(u, CGROUP_MASK_IO); + unit_write_drop_in_private(u, mode, name, b ? "IOAccounting=yes" : "IOAccounting=no"); + } + + return 1; + + } else if (streq(name, "IOWeight")) { + uint64_t weight; + + r = sd_bus_message_read(message, "t", &weight); + if (r < 0) + return r; + + if (!CGROUP_WEIGHT_IS_OK(weight)) + return sd_bus_error_set_errnof(error, EINVAL, "IOWeight value out of range"); + + if (mode != UNIT_CHECK) { + c->io_weight = weight; + unit_invalidate_cgroup(u, CGROUP_MASK_IO); + + if (weight == CGROUP_WEIGHT_INVALID) + unit_write_drop_in_private(u, mode, name, "IOWeight="); + else + unit_write_drop_in_private_format(u, mode, name, "IOWeight=%" PRIu64, weight); + } + + return 1; + + } else if (streq(name, "StartupIOWeight")) { + uint64_t weight; + + r = sd_bus_message_read(message, "t", &weight); + if (r < 0) + return r; + + if (CGROUP_WEIGHT_IS_OK(weight)) + return sd_bus_error_set_errnof(error, EINVAL, "StartupIOWeight value out of range"); + + if (mode != UNIT_CHECK) { + c->startup_io_weight = weight; + unit_invalidate_cgroup(u, CGROUP_MASK_IO); + + if (weight == CGROUP_WEIGHT_INVALID) + unit_write_drop_in_private(u, mode, name, "StartupIOWeight="); + else + unit_write_drop_in_private_format(u, mode, name, "StartupIOWeight=%" PRIu64, weight); + } + + return 1; + + } else if ((iol_type = cgroup_io_limit_type_from_string(name)) >= 0) { + const char *path; + unsigned n = 0; + uint64_t u64; + + 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) { + CGroupIODeviceLimit *a = NULL, *b; + + LIST_FOREACH(device_limits, b, c->io_device_limits) { + if (path_equal(path, b->path)) { + a = b; + break; + } + } + + if (!a) { + CGroupIOLimitType type; + + a = new0(CGroupIODeviceLimit, 1); + if (!a) + return -ENOMEM; + + a->path = strdup(path); + if (!a->path) { + free(a); + return -ENOMEM; + } + + for (type = 0; type < _CGROUP_IO_LIMIT_TYPE_MAX; type++) + a->limits[type] = cgroup_io_limit_defaults[type]; + + LIST_PREPEND(device_limits, c->io_device_limits, a); + } + + a->limits[iol_type] = u64; + } + + n++; + } + if (r < 0) + return r; + + r = sd_bus_message_exit_container(message); + if (r < 0) + return r; + + if (mode != UNIT_CHECK) { + CGroupIODeviceLimit *a; + _cleanup_free_ char *buf = NULL; + _cleanup_fclose_ FILE *f = NULL; + size_t size = 0; + + if (n == 0) { + LIST_FOREACH(device_limits, a, c->io_device_limits) + a->limits[iol_type] = cgroup_io_limit_defaults[iol_type]; + } + + unit_invalidate_cgroup(u, CGROUP_MASK_IO); + + f = open_memstream(&buf, &size); + if (!f) + return -ENOMEM; + + fprintf(f, "%s=\n", name); + LIST_FOREACH(device_limits, a, c->io_device_limits) + if (a->limits[iol_type] != cgroup_io_limit_defaults[iol_type]) + fprintf(f, "%s=%s %" PRIu64 "\n", name, a->path, a->limits[iol_type]); + + 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, "IODeviceWeight")) { + 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_WEIGHT_IS_OK(weight) || weight == CGROUP_WEIGHT_INVALID) + return sd_bus_error_set_errnof(error, EINVAL, "IODeviceWeight out of range"); + + if (mode != UNIT_CHECK) { + CGroupIODeviceWeight *a = NULL, *b; + + LIST_FOREACH(device_weights, b, c->io_device_weights) { + if (path_equal(b->path, path)) { + a = b; + break; + } + } + + if (!a) { + a = new0(CGroupIODeviceWeight, 1); + if (!a) + return -ENOMEM; + + a->path = strdup(path); + if (!a->path) { + free(a); + return -ENOMEM; + } + LIST_PREPEND(device_weights,c->io_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; + CGroupIODeviceWeight *a; + size_t size = 0; + + if (n == 0) { + while (c->io_device_weights) + cgroup_context_free_io_device_weight(c, c->io_device_weights); + } + + unit_invalidate_cgroup(u, CGROUP_MASK_IO); + + f = open_memstream(&buf, &size); + if (!f) + return -ENOMEM; + + fputs("IODeviceWeight=\n", f); + LIST_FOREACH(device_weights, a, c->io_device_weights) + fprintf(f, "IODeviceWeight=%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, "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)) { + a = b; + break; + } + } + + if (!a) { + a = new0(CGroupBlockIODeviceBandwidth, 1); + if (!a) + return -ENOMEM; + + a->rbps = CGROUP_LIMIT_MAX; + a->wbps = CGROUP_LIMIT_MAX; + a->path = strdup(path); + if (!a->path) { + free(a); + return -ENOMEM; + } + + LIST_PREPEND(device_bandwidths, c->blockio_device_bandwidths, a); + } + + if (read) + a->rbps = u64; + else + a->wbps = 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; + _cleanup_free_ char *buf = NULL; + _cleanup_fclose_ FILE *f = NULL; + size_t size = 0; + + if (n == 0) { + LIST_FOREACH(device_bandwidths, a, c->blockio_device_bandwidths) { + if (read) + a->rbps = CGROUP_LIMIT_MAX; + else + a->wbps = CGROUP_LIMIT_MAX; + } + } + + 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->rbps != CGROUP_LIMIT_MAX) + fprintf(f, "BlockIOReadBandwidth=%s %" PRIu64 "\n", a->path, a->rbps); + } else { + fputs("BlockIOWriteBandwidth=\n", f); + LIST_FOREACH(device_bandwidths, a, c->blockio_device_bandwidths) + if (a->wbps != CGROUP_LIMIT_MAX) + fprintf(f, "BlockIOWriteBandwidth=%s %" PRIu64 "\n", a->path, a->wbps); + } + + 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/grp-system/libcore/dbus-cgroup.h b/src/grp-system/libcore/dbus-cgroup.h new file mode 100644 index 0000000000..84d0f1ba04 --- /dev/null +++ b/src/grp-system/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/grp-system/libcore/dbus-device.c b/src/grp-system/libcore/dbus-device.c new file mode 100644 index 0000000000..e1a12224d3 --- /dev/null +++ b/src/grp-system/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/grp-system/libcore/dbus-device.h b/src/grp-system/libcore/dbus-device.h new file mode 100644 index 0000000000..eb1d8c3278 --- /dev/null +++ b/src/grp-system/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/grp-system/libcore/dbus-execute.c b/src/grp-system/libcore/dbus-execute.c new file mode 100644 index 0000000000..b5530d945e --- /dev/null +++ b/src/grp-system/libcore/dbus-execute.c @@ -0,0 +1,1545 @@ +/*** + This file is part of systemd. + + Copyright 2010 Lennart Poettering + + systemd is free software; you can redistribute it and/or modify it + under the terms of the GNU Lesser General Public License as published by + the Free Software Foundation; either version 2.1 of the License, or + (at your option) any later version. + + systemd is distributed in the hope that it will be useful, but + WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + Lesser General Public 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 "basic/af-list.h" +#include "basic/alloc-util.h" +#include "shared/bus-util.h" +#include "basic/capability-util.h" +#include "dbus-execute.h" +#include "basic/env-util.h" +#include "execute.h" +#include "basic/fd-util.h" +#include "basic/fileio.h" +#include "basic/ioprio.h" +#include "basic/missing.h" +#include "namespace.h" +#include "basic/parse-util.h" +#include "basic/path-util.h" +#include "basic/process-util.h" +#include "basic/rlimit-util.h" +#ifdef HAVE_SECCOMP +#include "shared/seccomp-util.h" +#endif +#include "basic/strv.h" +#include "basic/syslog-util.h" +#include "basic/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_empty_string( + 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", ""); +} + +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_empty_string, 0, SD_BUS_VTABLE_PROPERTY_CONST|SD_BUS_VTABLE_HIDDEN), + 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 if (free_and_strdup(&c->user, uu) < 0) + return -ENOMEM; + + 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 if (free_and_strdup(&c->group, gg) < 0) + return -ENOMEM; + + 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 if (free_and_strdup(&c->syslog_identifier, id) < 0) + return -ENOMEM; + + 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; + + } else if (streq(name, "SELinuxContext")) { + const char *s; + + r = sd_bus_message_read(message, "s", &s); + if (r < 0) + return r; + + if (mode != UNIT_CHECK) { + if (isempty(s)) + c->selinux_context = mfree(c->selinux_context); + else if (free_and_strdup(&c->selinux_context, s) < 0) + return -ENOMEM; + + unit_write_drop_in_private_format(u, mode, name, "%s=%s\n", name, strempty(s)); + } + + 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/grp-system/libcore/dbus-execute.h b/src/grp-system/libcore/dbus-execute.h new file mode 100644 index 0000000000..bdfef41db4 --- /dev/null +++ b/src/grp-system/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/grp-system/libcore/dbus-job.c b/src/grp-system/libcore/dbus-job.c new file mode 100644 index 0000000000..dde6aa28ef --- /dev/null +++ b/src/grp-system/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 "basic/alloc-util.h" +#include "dbus-job.h" +#include "dbus.h" +#include "job.h" +#include "basic/log.h" +#include "selinux-access.h" +#include "basic/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, false); + + 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/grp-system/libcore/dbus-job.h b/src/grp-system/libcore/dbus-job.h new file mode 100644 index 0000000000..95664cb90c --- /dev/null +++ b/src/grp-system/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/grp-system/libcore/dbus-kill.c b/src/grp-system/libcore/dbus-kill.c new file mode 100644 index 0000000000..cd76682298 --- /dev/null +++ b/src/grp-system/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 "shared/bus-util.h" +#include "dbus-kill.h" +#include "kill.h" +#include "basic/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 sd_bus_error_setf(error, SD_BUS_ERROR_INVALID_ARGS, "Kill mode '%s' not known.", m); + + 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 (!SIGNAL_VALID(sig)) + 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/grp-system/libcore/dbus-kill.h b/src/grp-system/libcore/dbus-kill.h new file mode 100644 index 0000000000..b32ce9d223 --- /dev/null +++ b/src/grp-system/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/grp-system/libcore/dbus-manager.c b/src/grp-system/libcore/dbus-manager.c new file mode 100644 index 0000000000..59f8a7908a --- /dev/null +++ b/src/grp-system/libcore/dbus-manager.c @@ -0,0 +1,2305 @@ +/*** + This file is part of systemd. + + Copyright 2010 Lennart Poettering + + systemd is free software; you can redistribute it and/or modify it + under the terms of the GNU Lesser General Public License as published by + the Free Software Foundation; either version 2.1 of the License, or + (at your option) any later version. + + systemd is distributed in the hope that it will be useful, but + WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + Lesser General Public License for more details. + + You should have received a copy of the GNU Lesser General Public License + along with systemd; If not, see . +***/ + +#include +#include +#include + +#include "basic/alloc-util.h" +#include "basic/architecture.h" +#include "basic/build.h" +#include "sd-bus/bus-common-errors.h" +#include "basic/clock-util.h" +#include "dbus-execute.h" +#include "dbus-job.h" +#include "dbus-manager.h" +#include "dbus-unit.h" +#include "dbus.h" +#include "basic/env-util.h" +#include "basic/fd-util.h" +#include "basic/fileio.h" +#include "basic/formats-util.h" +#include "shared/install.h" +#include "basic/log.h" +#include "basic/path-util.h" +#include "selinux-access.h" +#include "basic/stat-util.h" +#include "basic/string-util.h" +#include "basic/strv.h" +#include "basic/syslog-util.h" +#include "basic/virt.h" +#include "shared/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(NULL) > 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 reply_unit_info(sd_bus_message *reply, Unit *u) { + _cleanup_free_ char *unit_path = NULL, *job_path = NULL; + Unit *following; + + following = unit_following(u); + + 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; + } + + return 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 : "/"); +} + +static int method_list_units_by_names(sd_bus_message *message, void *userdata, sd_bus_error *error) { + _cleanup_(sd_bus_message_unrefp) sd_bus_message *reply = NULL; + Manager *m = userdata; + int r; + char **unit; + _cleanup_strv_free_ char **units = NULL; + + assert(message); + assert(m); + + r = sd_bus_message_read_strv(message, &units); + 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; + + STRV_FOREACH(unit, units) { + Unit *u; + + if (!unit_name_is_valid(*unit, UNIT_NAME_ANY)) + continue; + + r = manager_load_unit(m, *unit, NULL, error, &u); + if (r < 0) + return r; + + r = reply_unit_info(reply, u); + 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_get_unit_processes(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_get_processes(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, char **patterns) { + _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) { + if (k != u->id) + continue; + + 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; + + if (!strv_isempty(patterns) && + !strv_fnmatch_or_empty(patterns, u->id, FNM_NOESCAPE)) + continue; + + r = reply_unit_info(reply, u); + 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, 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, NULL); +} + +static int method_list_units_by_patterns(sd_bus_message *message, void *userdata, sd_bus_error *error) { + _cleanup_strv_free_ char **states = NULL; + _cleanup_strv_free_ char **patterns = NULL; + int r; + + r = sd_bus_message_read_strv(message, &states); + if (r < 0) + return r; + + r = sd_bus_message_read_strv(message, &patterns); + if (r < 0) + return r; + + return list_units_filtered(message, userdata, error, states, patterns); +} + +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 (!MANAGER_IS_SYSTEM(m)) + 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 (!MANAGER_IS_SYSTEM(m)) + 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 (!MANAGER_IS_SYSTEM(m)) + 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 (!MANAGER_IS_SYSTEM(m)) + 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 (!MANAGER_IS_SYSTEM(m)) + 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 (MANAGER_IS_SYSTEM(m) && 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 list_unit_files_by_patterns(sd_bus_message *message, void *userdata, sd_bus_error *error, char **states, char **patterns) { + _cleanup_(sd_bus_message_unrefp) sd_bus_message *reply = NULL; + Manager *m = userdata; + 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->unit_file_scope, NULL, h, states, patterns); + 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_list_unit_files(sd_bus_message *message, void *userdata, sd_bus_error *error) { + return list_unit_files_by_patterns(message, userdata, error, NULL, NULL); +} + +static int method_list_unit_files_by_patterns(sd_bus_message *message, void *userdata, sd_bus_error *error) { + _cleanup_strv_free_ char **states = NULL; + _cleanup_strv_free_ char **patterns = NULL; + int r; + + r = sd_bus_message_read_strv(message, &states); + if (r < 0) + return r; + + r = sd_bus_message_read_strv(message, &patterns); + if (r < 0) + return r; + + return list_unit_files_by_patterns(message, userdata, error, states, patterns); +} + +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; + 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; + + r = unit_file_get_state(m->unit_file_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; + 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 = unit_file_get_default(m->unit_file_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 (unit_file_changes_have_modification(changes, n_changes)) { + 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++) + if (changes[i].type >= 0) { + const char *change = unit_file_change_type_to_string(changes[i].type); + assert(change != NULL); + + r = sd_bus_message_append( + reply, "(sss)", + change, + 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; +} + +/* Create an error reply, using the error information from changes[] + * if possible, and fall back to generating an error from error code c. + * The error message only describes the first error. + * + * Coordinate with unit_file_dump_changes() in install.c. + */ +static int install_error( + sd_bus_error *error, + int c, + UnitFileChange *changes, + unsigned n_changes) { + int r; + unsigned i; + assert(c < 0); + + for (i = 0; i < n_changes; i++) + switch(changes[i].type) { + case 0 ... INT_MAX: + continue; + case -EEXIST: + if (changes[i].source) + r = sd_bus_error_setf(error, BUS_ERROR_UNIT_EXISTS, + "File %s already exists and is a symlink to %s.", + changes[i].path, changes[i].source); + else + r = sd_bus_error_setf(error, BUS_ERROR_UNIT_EXISTS, + "File %s already exists.", + changes[i].path); + goto found; + case -ERFKILL: + r = sd_bus_error_setf(error, BUS_ERROR_UNIT_MASKED, + "Unit file %s is masked.", changes[i].path); + goto found; + case -EADDRNOTAVAIL: + r = sd_bus_error_setf(error, BUS_ERROR_UNIT_GENERATED, + "Unit %s is transient or generated.", changes[i].path); + goto found; + case -ELOOP: + r = sd_bus_error_setf(error, BUS_ERROR_UNIT_LINKED, + "Refusing to operate on linked unit file %s", changes[i].path); + goto found; + default: + r = sd_bus_error_set_errnof(error, changes[i].type, "File %s: %m", changes[i].path); + goto found; + } + + r = c; + found: + unit_file_changes_free(changes, n_changes); + return r; +} + +static int method_enable_unit_files_generic( + sd_bus_message *message, + Manager *m, + 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; + 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 */ + + r = call(m->unit_file_scope, runtime, NULL, l, force, &changes, &n_changes); + if (r < 0) + return install_error(error, r, changes, n_changes); + + 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, 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, 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, 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, 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, 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; + 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 */ + + r = unit_file_preset(m->unit_file_scope, runtime, NULL, l, mm, force, &changes, &n_changes); + if (r < 0) + return install_error(error, r, changes, n_changes); + + 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, + 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; + 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; + + 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(m->unit_file_scope, runtime, NULL, l, &changes, &n_changes); + if (r < 0) + return install_error(error, r, changes, n_changes); + + 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, 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, unit_file_unmask, error); +} + +static int method_revert_unit_files(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; + int r; + + assert(message); + assert(m); + + r = sd_bus_message_read_strv(message, &l); + 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 */ + + r = unit_file_revert(m->unit_file_scope, NULL, l, &changes, &n_changes); + if (r < 0) + return install_error(error, r, changes, n_changes); + + return reply_unit_file_changes_and_free(m, message, -1, changes, n_changes); +} + +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; + 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 */ + + r = unit_file_set_default(m->unit_file_scope, NULL, name, force, &changes, &n_changes); + if (r < 0) + return install_error(error, r, changes, n_changes); + + 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; + 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 */ + + r = unit_file_preset_all(m->unit_file_scope, runtime, NULL, mm, force, &changes, &n_changes); + if (r < 0) + return install_error(error, r, changes, n_changes); + + 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; + int runtime, force, r; + char *target, *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; + + r = unit_file_add_dependency(m->unit_file_scope, runtime, NULL, l, target, dep, force, &changes, &n_changes); + if (r < 0) + return install_error(error, r, changes, n_changes); + + 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.search_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("DefaultStartLimitIntervalSec", "t", bus_property_get_usec, offsetof(Manager, default_start_limit_interval), 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_VTABLE_HIDDEN), /* obsolete alias name */ + 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("GetUnitProcesses", "s", "a(sus)", method_get_unit_processes, 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("ListUnitsByPatterns", "asas", "a(ssssssouso)", method_list_units_by_patterns, SD_BUS_VTABLE_UNPRIVILEGED), + SD_BUS_METHOD("ListUnitsByNames", "as", "a(ssssssouso)", method_list_units_by_names, 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("ListUnitFilesByPatterns", "asas", "a(ss)", method_list_unit_files_by_patterns, 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("RevertUnitFiles", "as", "a(sss)", method_revert_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/grp-system/libcore/dbus-manager.h b/src/grp-system/libcore/dbus-manager.h new file mode 100644 index 0000000000..36a2e9481b --- /dev/null +++ b/src/grp-system/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/grp-system/libcore/dbus-mount.c b/src/grp-system/libcore/dbus-mount.c new file mode 100644 index 0000000000..e6e824b6b9 --- /dev/null +++ b/src/grp-system/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 "shared/bus-util.h" +#include "dbus-cgroup.h" +#include "dbus-execute.h" +#include "dbus-kill.h" +#include "dbus-mount.h" +#include "mount.h" +#include "basic/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/grp-system/libcore/dbus-mount.h b/src/grp-system/libcore/dbus-mount.h new file mode 100644 index 0000000000..f9844e449d --- /dev/null +++ b/src/grp-system/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/grp-system/libcore/dbus-path.c b/src/grp-system/libcore/dbus-path.c new file mode 100644 index 0000000000..2f2a65d4c1 --- /dev/null +++ b/src/grp-system/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 "shared/bus-util.h" +#include "dbus-path.h" +#include "path.h" +#include "basic/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/grp-system/libcore/dbus-path.h b/src/grp-system/libcore/dbus-path.h new file mode 100644 index 0000000000..d3c19e0c2b --- /dev/null +++ b/src/grp-system/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/grp-system/libcore/dbus-scope.c b/src/grp-system/libcore/dbus-scope.c new file mode 100644 index 0000000000..3a9c3b98ec --- /dev/null +++ b/src/grp-system/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 "basic/alloc-util.h" +#include "sd-bus/bus-common-errors.h" +#include "sd-bus/bus-internal.h" +#include "shared/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/grp-system/libcore/dbus-scope.h b/src/grp-system/libcore/dbus-scope.h new file mode 100644 index 0000000000..f96ddef0cf --- /dev/null +++ b/src/grp-system/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/grp-system/libcore/dbus-service.c b/src/grp-system/libcore/dbus-service.c new file mode 100644 index 0000000000..463d9da4e1 --- /dev/null +++ b/src/grp-system/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 "basic/alloc-util.h" +#include "basic/async.h" +#include "shared/bus-util.h" +#include "dbus-cgroup.h" +#include "dbus-execute.h" +#include "dbus-kill.h" +#include "dbus-service.h" +#include "basic/fd-util.h" +#include "basic/fileio.h" +#include "basic/path-util.h" +#include "service.h" +#include "basic/string-util.h" +#include "basic/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/grp-system/libcore/dbus-service.h b/src/grp-system/libcore/dbus-service.h new file mode 100644 index 0000000000..291959325c --- /dev/null +++ b/src/grp-system/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/grp-system/libcore/dbus-slice.c b/src/grp-system/libcore/dbus-slice.c new file mode 100644 index 0000000000..e37f50b283 --- /dev/null +++ b/src/grp-system/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/grp-system/libcore/dbus-slice.h b/src/grp-system/libcore/dbus-slice.h new file mode 100644 index 0000000000..8e4cabbf8a --- /dev/null +++ b/src/grp-system/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/grp-system/libcore/dbus-socket.c b/src/grp-system/libcore/dbus-socket.c new file mode 100644 index 0000000000..53c9911991 --- /dev/null +++ b/src/grp-system/libcore/dbus-socket.c @@ -0,0 +1,184 @@ +/*** + This file is part of systemd. + + Copyright 2010 Lennart Poettering + + systemd is free software; you can redistribute it and/or modify it + under the terms of the GNU Lesser General Public License as published by + the Free Software Foundation; either version 2.1 of the License, or + (at your option) any later version. + + systemd is distributed in the hope that it will be useful, but + WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + Lesser General Public License for more details. + + You should have received a copy of the GNU Lesser General Public License + along with systemd; If not, see . +***/ + +#include "basic/alloc-util.h" +#include "shared/bus-util.h" +#include "dbus-cgroup.h" +#include "dbus-execute.h" +#include "dbus-socket.h" +#include "socket.h" +#include "basic/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), + SD_BUS_PROPERTY("TriggerLimitIntervalUSec", "t", bus_property_get_usec, offsetof(Socket, trigger_limit.interval), SD_BUS_VTABLE_PROPERTY_CONST), + SD_BUS_PROPERTY("TriggerLimitBurst", "u", bus_property_get_unsigned, offsetof(Socket, trigger_limit.burst), 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/grp-system/libcore/dbus-socket.h b/src/grp-system/libcore/dbus-socket.h new file mode 100644 index 0000000000..a31906feea --- /dev/null +++ b/src/grp-system/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/grp-system/libcore/dbus-swap.c b/src/grp-system/libcore/dbus-swap.c new file mode 100644 index 0000000000..2cfc8bd496 --- /dev/null +++ b/src/grp-system/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 "shared/bus-util.h" +#include "dbus-cgroup.h" +#include "dbus-execute.h" +#include "dbus-swap.h" +#include "basic/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/grp-system/libcore/dbus-swap.h b/src/grp-system/libcore/dbus-swap.h new file mode 100644 index 0000000000..19151fb771 --- /dev/null +++ b/src/grp-system/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/grp-system/libcore/dbus-target.c b/src/grp-system/libcore/dbus-target.c new file mode 100644 index 0000000000..6858b1ce72 --- /dev/null +++ b/src/grp-system/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/grp-system/libcore/dbus-target.h b/src/grp-system/libcore/dbus-target.h new file mode 100644 index 0000000000..c97a9d626e --- /dev/null +++ b/src/grp-system/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/grp-system/libcore/dbus-timer.c b/src/grp-system/libcore/dbus-timer.c new file mode 100644 index 0000000000..be84522036 --- /dev/null +++ b/src/grp-system/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 "basic/alloc-util.h" +#include "shared/bus-util.h" +#include "dbus-timer.h" +#include "basic/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_or_monotonic()); + + 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/grp-system/libcore/dbus-timer.h b/src/grp-system/libcore/dbus-timer.h new file mode 100644 index 0000000000..505fb5df72 --- /dev/null +++ b/src/grp-system/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/grp-system/libcore/dbus-unit.c b/src/grp-system/libcore/dbus-unit.c new file mode 100644 index 0000000000..dd5d6e4c5e --- /dev/null +++ b/src/grp-system/libcore/dbus-unit.c @@ -0,0 +1,1423 @@ +/*** + This file is part of systemd. + + Copyright 2010 Lennart Poettering + + systemd is free software; you can redistribute it and/or modify it + under the terms of the GNU Lesser General Public License as published by + the Free Software Foundation; either version 2.1 of the License, or + (at your option) any later version. + + systemd is distributed in the hope that it will be useful, but + WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + Lesser General Public License for more details. + + You should have received a copy of the GNU Lesser General Public License + along with systemd; If not, see . +***/ + +#include + +#include "basic/alloc-util.h" +#include "sd-bus/bus-common-errors.h" +#include "basic/cgroup-util.h" +#include "dbus-unit.h" +#include "dbus.h" +#include "basic/fd-util.h" +#include "basic/locale-util.h" +#include "basic/log.h" +#include "basic/process-util.h" +#include "selinux-access.h" +#include "basic/signal-util.h" +#include "basic/special.h" +#include "basic/string-util.h" +#include "basic/strv.h" +#include "basic/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 (!SIGNAL_VALID(signo)) + 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("StartLimitIntervalSec", "t", bus_property_get_usec, offsetof(Unit, start_limit.interval), 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_VTABLE_HIDDEN), /* obsolete alias name */ + 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); +} + +static int append_process(sd_bus_message *reply, const char *p, pid_t pid, Set *pids) { + _cleanup_free_ char *buf = NULL, *cmdline = NULL; + int r; + + assert(reply); + assert(pid > 0); + + r = set_put(pids, PID_TO_PTR(pid)); + if (r == -EEXIST || r == 0) + return 0; + if (r < 0) + return r; + + if (!p) { + r = cg_pid_get_path(SYSTEMD_CGROUP_CONTROLLER, pid, &buf); + if (r == -ESRCH) + return 0; + if (r < 0) + return r; + + p = buf; + } + + (void) get_process_cmdline(pid, 0, true, &cmdline); + + return sd_bus_message_append(reply, + "(sus)", + p, + (uint32_t) pid, + cmdline); +} + +static int append_cgroup(sd_bus_message *reply, const char *p, Set *pids) { + _cleanup_closedir_ DIR *d = NULL; + _cleanup_fclose_ FILE *f = NULL; + int r; + + assert(reply); + assert(p); + + r = cg_enumerate_processes(SYSTEMD_CGROUP_CONTROLLER, p, &f); + if (r == ENOENT) + return 0; + if (r < 0) + return r; + + for (;;) { + pid_t pid; + + r = cg_read_pid(f, &pid); + if (r < 0) + return r; + if (r == 0) + break; + + if (is_kernel_thread(pid) > 0) + continue; + + r = append_process(reply, p, pid, pids); + if (r < 0) + return r; + } + + r = cg_enumerate_subgroups(SYSTEMD_CGROUP_CONTROLLER, p, &d); + if (r == -ENOENT) + return 0; + if (r < 0) + return r; + + for (;;) { + _cleanup_free_ char *g = NULL, *j = NULL; + + r = cg_read_subgroup(d, &g); + if (r < 0) + return r; + if (r == 0) + break; + + j = strjoin(p, "/", g, NULL); + if (!j) + return -ENOMEM; + + r = append_cgroup(reply, j, pids); + if (r < 0) + return r; + } + + return 0; +} + +int bus_unit_method_get_processes(sd_bus_message *message, void *userdata, sd_bus_error *error) { + _cleanup_(sd_bus_message_unrefp) sd_bus_message *reply = NULL; + _cleanup_(set_freep) Set *pids = NULL; + Unit *u = userdata; + pid_t pid; + int r; + + assert(message); + + pids = set_new(NULL); + if (!pids) + return -ENOMEM; + + r = sd_bus_message_new_method_return(message, &reply); + if (r < 0) + return r; + + r = sd_bus_message_open_container(reply, 'a', "(sus)"); + if (r < 0) + return r; + + if (u->cgroup_path) { + r = append_cgroup(reply, u->cgroup_path, pids); + if (r < 0) + return r; + } + + /* The main and control pids might live outside of the cgroup, hence fetch them separately */ + pid = unit_main_pid(u); + if (pid > 0) { + r = append_process(reply, NULL, pid, pids); + if (r < 0) + return r; + } + + pid = unit_control_pid(u); + if (pid > 0) { + r = append_process(reply, NULL, pid, pids); + if (r < 0) + return r; + } + + r = sd_bus_message_close_container(reply); + if (r < 0) + return r; + + return sd_bus_send(NULL, reply, NULL); +} + +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_METHOD("GetProcesses", NULL, "a(sus)", bus_unit_method_get_processes, SD_BUS_VTABLE_UNPRIVILEGED), + 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); + + /* Note that we do not dispatch the load queue here yet, as we don't want our own transient unit to be + * loaded while we are still setting it up. Or in other words, we use manager_load_unit_prepare() + * instead of manager_load_unit() on purpose, here. */ + r = manager_load_unit_prepare(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) { + assert(u); + + 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/grp-system/libcore/dbus-unit.h b/src/grp-system/libcore/dbus-unit.h new file mode 100644 index 0000000000..758045a47c --- /dev/null +++ b/src/grp-system/libcore/dbus-unit.h @@ -0,0 +1,41 @@ +#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_method_get_processes(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/grp-system/libcore/dbus.c b/src/grp-system/libcore/dbus.c new file mode 100644 index 0000000000..d1d6248041 --- /dev/null +++ b/src/grp-system/libcore/dbus.c @@ -0,0 +1,1243 @@ +/*** + This file is part of systemd. + + Copyright 2010 Lennart Poettering + + systemd is free software; you can redistribute it and/or modify it + under the terms of the GNU Lesser General Public License as published by + the Free Software Foundation; either version 2.1 of the License, or + (at your option) any later version. + + systemd is distributed in the hope that it will be useful, but + WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + Lesser General Public License for more details. + + You should have received a copy of the GNU Lesser General Public License + along with systemd; If not, see . +***/ + +#include +#include +#include + +#include + +#include "basic/alloc-util.h" +#include "sd-bus/bus-common-errors.h" +#include "sd-bus/bus-error.h" +#include "sd-bus/bus-internal.h" +#include "shared/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 "basic/fd-util.h" +#include "basic/log.h" +#include "basic/missing.h" +#include "basic/mkdir.h" +#include "selinux-access.h" +#include "basic/special.h" +#include "basic/string-util.h" +#include "basic/strv.h" +#include "basic/strxcpyx.h" +#include "basic/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; +} + +int bus_forward_agent_released(Manager *m, const char *path) { + int r; + + assert(m); + assert(path); + + if (!MANAGER_IS_SYSTEM(m)) + return 0; + + if (!m->system_bus) + return 0; + + /* If we are running a system instance we forward the agent message on the system bus, so that the user + * instances get notified about this, too */ + + r = sd_bus_emit_signal(m->system_bus, + "/org/freedesktop/systemd1/agent", + "org.freedesktop.systemd1.Agent", + "Released", + "s", path); + if (r < 0) + return log_warning_errno(r, "Failed to propagate agent release message: %m"); + + return 1; +} + +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; + Manager *m = userdata; + const char *cgroup; + uid_t sender_uid; + int r; + + assert(message); + assert(m); + + /* 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); + 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; + } + + 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 (MANAGER_IS_SYSTEM(m) && m->system_bus) + bus = sd_bus_ref(m->system_bus); + else { + if (MANAGER_IS_SYSTEM(m)) + 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); + + /* if we are a user instance we get the Released message via the system bus */ + if (MANAGER_IS_USER(m)) { + 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 (MANAGER_IS_SYSTEM(m) && 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 (MANAGER_IS_SYSTEM(m)) { + + /* We want the private bus only when running as init */ + if (getpid() != 1) + return 0; + + strcpy(sa.un.sun_path, "/run/systemd/private"); + salen = SOCKADDR_UN_LEN(sa.un); + } 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 (!MANAGER_IS_SYSTEM(m)) + 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/grp-system/libcore/dbus.h b/src/grp-system/libcore/dbus.h new file mode 100644 index 0000000000..6baaffbd75 --- /dev/null +++ b/src/grp-system/libcore/dbus.h @@ -0,0 +1,44 @@ +#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); + +int bus_forward_agent_released(Manager *m, const char *path); diff --git a/src/grp-system/libcore/device.c b/src/grp-system/libcore/device.c new file mode 100644 index 0000000000..10e9e9ae76 --- /dev/null +++ b/src/grp-system/libcore/device.c @@ -0,0 +1,876 @@ +/*** + This file is part of systemd. + + Copyright 2010 Lennart Poettering + + systemd is free software; you can redistribute it and/or modify it + under the terms of the GNU Lesser General Public License as published by + the Free Software Foundation; either version 2.1 of the License, or + (at your option) any later version. + + systemd is distributed in the hope that it will be useful, but + WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + Lesser General Public License for 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 "basic/alloc-util.h" +#include "dbus-device.h" +#include "device.h" +#include "basic/log.h" +#include "basic/parse-util.h" +#include "basic/path-util.h" +#include "basic/stat-util.h" +#include "basic/string-util.h" +#include "swap.h" +#include "shared/udev-util.h" +#include "basic/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 = MANAGER_IS_USER(u->manager) ? "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 (dev && 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_debug(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", + + .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/grp-system/libcore/device.h b/src/grp-system/libcore/device.h new file mode 100644 index 0000000000..184a1a349b --- /dev/null +++ b/src/grp-system/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/grp-system/libcore/execute.c b/src/grp-system/libcore/execute.c new file mode 100644 index 0000000000..e7628bceb3 --- /dev/null +++ b/src/grp-system/libcore/execute.c @@ -0,0 +1,3092 @@ +/*** + This file is part of systemd. + + Copyright 2010 Lennart Poettering + + systemd is free software; you can redistribute it and/or modify it + under the terms of the GNU Lesser General Public License as published by + the Free Software Foundation; either version 2.1 of the License, or + (at your option) any later version. + + systemd is distributed in the hope that it will be useful, but + WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + Lesser General Public License for more details. + + You 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 + +#ifdef HAVE_PAM +#include +#endif + +#ifdef HAVE_SELINUX +#include +#endif + +#ifdef HAVE_SECCOMP +#include +#endif + +#ifdef HAVE_APPARMOR +#include +#endif + +#include + +#include "basic/af-list.h" +#include "basic/alloc-util.h" +#ifdef HAVE_APPARMOR +#include "shared/apparmor-util.h" +#endif +#include "basic/async.h" +#include "basic/barrier.h" +#include "basic/cap-list.h" +#include "basic/capability-util.h" +#include "basic/def.h" +#include "basic/env-util.h" +#include "basic/errno-list.h" +#include "execute.h" +#include "basic/exit-status.h" +#include "basic/fd-util.h" +#include "basic/fileio.h" +#include "basic/formats-util.h" +#include "basic/fs-util.h" +#include "basic/glob-util.h" +#include "basic/io-util.h" +#include "basic/ioprio.h" +#include "basic/log.h" +#include "basic/macro.h" +#include "basic/missing.h" +#include "basic/mkdir.h" +#include "namespace.h" +#include "basic/parse-util.h" +#include "basic/path-util.h" +#include "basic/process-util.h" +#include "basic/rlimit-util.h" +#include "basic/rm-rf.h" +#ifdef HAVE_SECCOMP +#include "shared/seccomp-util.h" +#endif +#include "basic/securebits.h" +#include "basic/selinux-util.h" +#include "basic/signal-util.h" +#include "basic/smack-util.h" +#include "basic/string-table.h" +#include "basic/string-util.h" +#include "basic/strv.h" +#include "basic/syslog-util.h" +#include "basic/terminal-util.h" +#include "unit.h" +#include "basic/user-util.h" +#include "basic/util.h" +#include "shared/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, SOCKADDR_UN_LEN(sa.un)); + 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 look up) the uid and make sure we keep the + * capabilities while doing so. */ + + if (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<= 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 (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 (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 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, + 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) { + + bool use_address_families = context->address_families_whitelist || + !set_isempty(context->address_families); + bool use_syscall_filter = context->syscall_whitelist || + !set_isempty(context->syscall_filter) || + !set_isempty(context->syscall_archs); + 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->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<no_new_privileges || + (!have_effective_cap(CAP_SYS_ADMIN) && (use_address_families || use_syscall_filter))) + if (prctl(PR_SET_NO_NEW_PRIVS, 1, 0, 0, 0) < 0) { + *exit_status = EXIT_NO_NEW_PRIVILEGES; + return -errno; + } + +#ifdef HAVE_SECCOMP + if (use_address_families) { + r = apply_address_families(context); + if (r < 0) { + *exit_status = EXIT_ADDRESS_FAMILIES; + return r; + } + } + + if (use_syscall_filter) { + 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); + + 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); +} + +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->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/grp-system/libcore/execute.h b/src/grp-system/libcore/execute.h new file mode 100644 index 0000000000..190a56f443 --- /dev/null +++ b/src/grp-system/libcore/execute.h @@ -0,0 +1,287 @@ +#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 "basic/fdset.h" +#include "basic/list.h" +#include "basic/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; + 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; +}; + +#include "basic/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; + + 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/grp-system/libcore/failure-action.c b/src/grp-system/libcore/failure-action.c new file mode 100644 index 0000000000..be76c15a37 --- /dev/null +++ b/src/grp-system/libcore/failure-action.c @@ -0,0 +1,127 @@ +/*** + 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 "sd-bus/bus-error.h" +#include "shared/bus-util.h" +#include "failure-action.h" +#include "basic/special.h" +#include "basic/string-table.h" +#include "basic/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 (!MANAGER_IS_SYSTEM(m)) { + /* 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."); + + (void) update_reboot_parameter_and_warn(reboot_arg); + (void) manager_add_job_by_name_and_warn(m, JOB_START, SPECIAL_REBOOT_TARGET, JOB_REPLACE_IRREVERSIBLY, NULL); + + break; + + case FAILURE_ACTION_REBOOT_FORCE: + log_and_status(m, "Forcibly rebooting as result of failure."); + + (void) update_reboot_parameter_and_warn(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 (!isempty(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_warning_errno(errno, "Failed to reboot with parameter, retrying without: %m"); + } + + 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_IRREVERSIBLY, 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/grp-system/libcore/failure-action.h b/src/grp-system/libcore/failure-action.h new file mode 100644 index 0000000000..96c6b51cb2 --- /dev/null +++ b/src/grp-system/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 "basic/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/grp-system/libcore/hostname-setup.c b/src/grp-system/libcore/hostname-setup.c new file mode 100644 index 0000000000..0085c0d405 --- /dev/null +++ b/src/grp-system/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 "basic/alloc-util.h" +#include "basic/fileio.h" +#include "hostname-setup.h" +#include "basic/hostname-util.h" +#include "basic/log.h" +#include "basic/macro.h" +#include "basic/string-util.h" +#include "basic/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/grp-system/libcore/hostname-setup.h b/src/grp-system/libcore/hostname-setup.h new file mode 100644 index 0000000000..73e8c75c71 --- /dev/null +++ b/src/grp-system/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/grp-system/libcore/ima-setup.c b/src/grp-system/libcore/ima-setup.c new file mode 100644 index 0000000000..c6eb358c9d --- /dev/null +++ b/src/grp-system/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 "basic/fd-util.h" +#include "basic/fileio.h" +#include "ima-setup.h" +#include "basic/log.h" +#include "basic/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/grp-system/libcore/ima-setup.h b/src/grp-system/libcore/ima-setup.h new file mode 100644 index 0000000000..472b58cb00 --- /dev/null +++ b/src/grp-system/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/grp-system/libcore/job.c b/src/grp-system/libcore/job.c new file mode 100644 index 0000000000..1a24a7f152 --- /dev/null +++ b/src/grp-system/libcore/job.c @@ -0,0 +1,1259 @@ +/*** + This file is part of systemd. + + Copyright 2010 Lennart Poettering + + systemd is free software; you can redistribute it and/or modify it + under the terms of the GNU Lesser General Public License as published by + the Free Software Foundation; either version 2.1 of the License, or + (at your option) any later version. + + systemd is distributed in the hope that it will be useful, but + WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + Lesser General Public License for more details. + + You should have received a copy of the GNU Lesser General Public License + along with systemd; If not, see . +***/ + +#include + +#include +#include + +#include "basic/alloc-util.h" +#include "basic/async.h" +#include "dbus-job.h" +#include "dbus.h" +#include "basic/escape.h" +#include "job.h" +#include "basic/log.h" +#include "basic/macro.h" +#include "basic/parse-util.h" +#include "basic/set.h" +#include "basic/special.h" +#include "basic/stdio-util.h" +#include "basic/string-table.h" +#include "basic/string-util.h" +#include "basic/strv.h" +#include "basic/terminal-util.h" +#include "unit.h" +#include "basic/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 (!MANAGER_IS_RELOADING(j->manager)) + 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, 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, true); + else if (r == -EBADR) + r = job_finish_and_invalidate(j, JOB_SKIPPED, true, false); + else if (r == -ENOEXEC) + r = job_finish_and_invalidate(j, JOB_INVALID, true, false); + else if (r == -EPROTO) + r = job_finish_and_invalidate(j, JOB_ASSERT, true, false); + else if (r == -EOPNOTSUPP) + r = job_finish_and_invalidate(j, JOB_UNSUPPORTED, true, false); + else if (r == -EAGAIN) + job_set_state(j, JOB_WAITING); + else if (r < 0) + r = job_finish_and_invalidate(j, JOB_FAILED, true, false); + } + + 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 stopping %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 struct { + const char *color, *word; + } const statuses[_JOB_RESULT_MAX] = { + [JOB_DONE] = {ANSI_GREEN, " OK "}, + [JOB_TIMEOUT] = {ANSI_HIGHLIGHT_RED, " TIME "}, + [JOB_FAILED] = {ANSI_HIGHLIGHT_RED, "FAILED"}, + [JOB_DEPENDENCY] = {ANSI_HIGHLIGHT_YELLOW, "DEPEND"}, + [JOB_SKIPPED] = {ANSI_HIGHLIGHT, " INFO "}, + [JOB_ASSERT] = {ANSI_HIGHLIGHT_YELLOW, "ASSERT"}, + [JOB_UNSUPPORTED] = {ANSI_HIGHLIGHT_YELLOW, "UNSUPP"}, + }; + + const char *format; + const char *status; + + 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 (log_get_show_color()) + status = strjoina(statuses[result].color, statuses[result].word, ANSI_NORMAL); + else + status = statuses[result].word; + + if (result != JOB_DONE) + manager_flip_auto_status(u->manager, true); + + DISABLE_WARNING_FORMAT_NONLITERAL; + unit_status_printf(u, status, 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, false); + } +} + +int job_finish_and_invalidate(Job *j, JobResult result, bool recursive, bool already) { + 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)); + + /* If this job did nothing to respective unit we don't log the status message */ + if (!already) + 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, false); + + 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 (!MANAGER_IS_SYSTEM(j->unit->manager)) + 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/grp-system/libcore/job.h b/src/grp-system/libcore/job.h new file mode 100644 index 0000000000..f88e5a0782 --- /dev/null +++ b/src/grp-system/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 "basic/list.h" +#include "basic/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, bool already); + +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/grp-system/libcore/kill.c b/src/grp-system/libcore/kill.c new file mode 100644 index 0000000000..22e5bdb4fe --- /dev/null +++ b/src/grp-system/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 "basic/signal-util.h" +#include "basic/string-table.h" +#include "basic/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/grp-system/libcore/kill.h b/src/grp-system/libcore/kill.h new file mode 100644 index 0000000000..529e4c5377 --- /dev/null +++ b/src/grp-system/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 "basic/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/grp-system/libcore/killall.c b/src/grp-system/libcore/killall.c new file mode 100644 index 0000000000..03bed30704 --- /dev/null +++ b/src/grp-system/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 "basic/alloc-util.h" +#include "basic/fd-util.h" +#include "basic/formats-util.h" +#include "killall.h" +#include "basic/parse-util.h" +#include "basic/process-util.h" +#include "basic/set.h" +#include "basic/string-util.h" +#include "basic/terminal-util.h" +#include "basic/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/grp-system/libcore/killall.h b/src/grp-system/libcore/killall.h new file mode 100644 index 0000000000..acc2439f00 --- /dev/null +++ b/src/grp-system/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/grp-system/libcore/kmod-setup.c b/src/grp-system/libcore/kmod-setup.c new file mode 100644 index 0000000000..6ddf1137ec --- /dev/null +++ b/src/grp-system/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 "shared/bus-util.h" +#include "basic/capability-util.h" +#include "kmod-setup.h" +#include "basic/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/grp-system/libcore/kmod-setup.h b/src/grp-system/libcore/kmod-setup.h new file mode 100644 index 0000000000..685f4df301 --- /dev/null +++ b/src/grp-system/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/grp-system/libcore/linux/auto_dev-ioctl.h b/src/grp-system/libcore/linux/auto_dev-ioctl.h new file mode 100644 index 0000000000..aeaeb3ea7a --- /dev/null +++ b/src/grp-system/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/grp-system/libcore/load-dropin.c b/src/grp-system/libcore/load-dropin.c new file mode 100644 index 0000000000..bb6cf0f989 --- /dev/null +++ b/src/grp-system/libcore/load-dropin.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 "shared/conf-parser.h" +#include "load-dropin.h" +#include "load-fragment.h" +#include "basic/log.h" +#include "basic/strv.h" +#include "basic/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) { + _cleanup_strv_free_ char **l = NULL; + 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.search_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); + } + } + + r = unit_find_dropin_paths(u, &l); + if (r <= 0) + return 0; + + if (!u->dropin_paths) { + u->dropin_paths = l; + l = NULL; + } else { + r = strv_extend_strv(&u->dropin_paths, l, true); + if (r < 0) + return log_oom(); + } + + 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/grp-system/libcore/load-dropin.h b/src/grp-system/libcore/load-dropin.h new file mode 100644 index 0000000000..067661b1ff --- /dev/null +++ b/src/grp-system/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 "shared/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.search_path, + u->manager->unit_path_cache, + u->names, + paths); +} + +int unit_load_dropin(Unit *u); diff --git a/src/grp-system/libcore/load-fragment-gperf.gperf.m4 b/src/grp-system/libcore/load-fragment-gperf.gperf.m4 new file mode 100644 index 0000000000..c9f6e6acf7 --- /dev/null +++ b/src/grp-system/libcore/load-fragment-gperf.gperf.m4 @@ -0,0 +1,391 @@ +%{ +#include +#include "shared/conf-parser.h" +#include "load-fragment.h" +#include "basic/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_warn_compat, DISABLED_LEGACY, 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.IOAccounting, config_parse_bool, 0, offsetof($1, cgroup_context.io_accounting) +$1.IOWeight, config_parse_io_weight, 0, offsetof($1, cgroup_context.io_weight) +$1.StartupIOWeight, config_parse_io_weight, 0, offsetof($1, cgroup_context.startup_io_weight) +$1.IODeviceWeight, config_parse_io_device_weight, 0, offsetof($1, cgroup_context) +$1.IOReadBandwidthMax, config_parse_io_limit, 0, offsetof($1, cgroup_context) +$1.IOWriteBandwidthMax, config_parse_io_limit, 0, offsetof($1, cgroup_context) +$1.IOReadIOPSMax, config_parse_io_limit, 0, offsetof($1, cgroup_context) +$1.IOWriteIOPSMax, config_parse_io_limit, 0, offsetof($1, cgroup_context) +$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.StartLimitIntervalSec, config_parse_sec, 0, offsetof(Unit, start_limit.interval) +m4_dnl The following is a legacy alias name for compatibility +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) +m4_dnl The following three only exist for compatibility, they moved into Unit, see above +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_warn_compat, DISABLED_LEGACY, 0 +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 +Socket.TriggerLimitIntervalSec, config_parse_sec, 0, offsetof(Socket, trigger_limit.interval) +Socket.TriggerLimitBurst, config_parse_unsigned, 0, offsetof(Socket, trigger_limit.burst) +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/grp-system/libcore/load-fragment.c b/src/grp-system/libcore/load-fragment.c new file mode 100644 index 0000000000..6d9100b189 --- /dev/null +++ b/src/grp-system/libcore/load-fragment.c @@ -0,0 +1,4083 @@ +/*** + 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 "basic/af-list.h" +#include "basic/alloc-util.h" +#include "sd-bus/bus-error.h" +#include "sd-bus/bus-internal.h" +#include "shared/bus-util.h" +#include "basic/cap-list.h" +#include "basic/capability-util.h" +#include "cgroup.h" +#include "shared/conf-parser.h" +#include "basic/cpu-set-util.h" +#include "basic/env-util.h" +#include "basic/errno-list.h" +#include "basic/escape.h" +#include "basic/fd-util.h" +#include "basic/fs-util.h" +#include "basic/ioprio.h" +#include "load-fragment.h" +#include "basic/log.h" +#include "basic/missing.h" +#include "basic/parse-util.h" +#include "basic/path-util.h" +#include "basic/process-util.h" +#include "basic/rlimit-util.h" +#ifdef HAVE_SECCOMP +#include "shared/seccomp-util.h" +#endif +#include "basic/securebits.h" +#include "basic/signal-util.h" +#include "basic/stat-util.h" +#include "basic/string-util.h" +#include "basic/strv.h" +#include "basic/unit-name.h" +#include "unit-printf.h" +#include "unit.h" +#include "basic/utf8.h" +#include "basic/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, "*")) { + if (!ifname_valid(rvalue)) { + log_syntax(unit, LOG_ERR, filename, line, 0, "Interface name is invalid, ignoring: %s", rvalue); + return 0; + } + + 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_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_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 && MANAGER_IS_USER(u->manager)) + 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, *t = NULL; + CGroupContext *c = data; + CGroupDeviceAllow *a; + const char *m = NULL; + size_t n; + int r; + + if (isempty(rvalue)) { + while (c->device_allow) + cgroup_context_free_device_allow(c, c->device_allow); + + return 0; + } + + r = unit_full_printf(userdata, rvalue, &t); + if(r < 0) { + log_syntax(unit, LOG_WARNING, filename, line, r, + "Failed to resolve specifiers in %s, ignoring: %m", + rvalue); + } + + n = strcspn(t, WHITESPACE); + + path = strndup(t, 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 = t + n + strspn(t + 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_io_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_weight_parse(rvalue, weight); + if (r < 0) { + log_syntax(unit, LOG_ERR, filename, line, r, "IO weight '%s' invalid. Ignoring.", rvalue); + return 0; + } + + return 0; +} + +int config_parse_io_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; + CGroupIODeviceWeight *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->io_device_weights) + cgroup_context_free_io_device_weight(c, c->io_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_weight_parse(weight, &u); + if (r < 0) { + log_syntax(unit, LOG_ERR, filename, line, r, "IO weight '%s' invalid. Ignoring.", weight); + return 0; + } + + assert(u != CGROUP_WEIGHT_INVALID); + + w = new0(CGroupIODeviceWeight, 1); + if (!w) + return log_oom(); + + w->path = path; + path = NULL; + + w->weight = u; + + LIST_PREPEND(device_weights, c->io_device_weights, w); + return 0; +} + +int config_parse_io_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) { + + _cleanup_free_ char *path = NULL; + CGroupIODeviceLimit *l = NULL, *t; + CGroupContext *c = data; + CGroupIOLimitType type; + const char *limit; + uint64_t num; + size_t n; + int r; + + assert(filename); + assert(lvalue); + assert(rvalue); + + type = cgroup_io_limit_type_from_string(lvalue); + assert(type >= 0); + + if (isempty(rvalue)) { + LIST_FOREACH(device_limits, l, c->io_device_limits) + l->limits[type] = cgroup_io_limit_defaults[type]; + return 0; + } + + n = strcspn(rvalue, WHITESPACE); + limit = rvalue + n; + limit += strspn(limit, WHITESPACE); + + if (!*limit) { + 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; + } + + if (streq("max", limit)) { + num = CGROUP_LIMIT_MAX; + } else { + r = parse_size(limit, 1000, &num); + if (r < 0 || num <= 0) { + log_syntax(unit, LOG_ERR, filename, line, r, "IO Limit '%s' invalid. Ignoring.", rvalue); + return 0; + } + } + + LIST_FOREACH(device_limits, t, c->io_device_limits) { + if (path_equal(path, t->path)) { + l = t; + break; + } + } + + if (!l) { + CGroupIOLimitType ttype; + + l = new0(CGroupIODeviceLimit, 1); + if (!l) + return log_oom(); + + l->path = path; + path = NULL; + for (ttype = 0; ttype < _CGROUP_IO_LIMIT_TYPE_MAX; ttype++) + l->limits[ttype] = cgroup_io_limit_defaults[ttype]; + + LIST_PREPEND(device_limits, c->io_device_limits, l); + } + + l->limits[type] = num; + + 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 = NULL, *t; + 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)) { + LIST_FOREACH(device_bandwidths, b, c->blockio_device_bandwidths) { + b->rbps = CGROUP_LIMIT_MAX; + b->wbps = CGROUP_LIMIT_MAX; + } + 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; + } + + LIST_FOREACH(device_bandwidths, t, c->blockio_device_bandwidths) { + if (path_equal(path, t->path)) { + b = t; + break; + } + } + + if (!t) { + b = new0(CGroupBlockIODeviceBandwidth, 1); + if (!b) + return log_oom(); + + b->path = path; + path = NULL; + b->rbps = CGROUP_LIMIT_MAX; + b->wbps = CGROUP_LIMIT_MAX; + + LIST_PREPEND(device_bandwidths, c->blockio_device_bandwidths, b); + } + + if (read) + b->rbps = bytes; + else + b->wbps = bytes; + + 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) { + char *id = NULL; + unsigned c = 0; + int fd, r; + FILE *f; + + 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 */ + + /* If the symlink name we are looking at is unit template, then + we must search for instance of this template */ + if (unit_name_is_valid(k, UNIT_NAME_TEMPLATE)) { + _cleanup_free_ char *instance = NULL; + + r = unit_name_replace_instance(k, (*u)->instance, &instance); + if (r < 0) + return r; + + other = manager_get_unit((*u)->manager, instance); + } else + 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) { + _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; + int r; + + 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.search_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) + break; + filename = mfree(filename); + if (r != -ENOENT) + return r; + + /* Empty the symlink names for the next run */ + set_clear_free(symlink_names); + } + } + + if (!filename) + /* Hmm, no suitable file found? */ + return 0; + + if (!unit_type_may_alias(u->type) && set_size(symlink_names) > 1) { + log_unit_warning(u, "Unit type of %s does not support alias names, refusing loading via symlink.", u->id); + return -ELOOP; + } + + 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; + u->fragment_mtime = 0; + } else { + u->load_state = UNIT_LOADED; + u->fragment_mtime = timespec_load(&st.st_mtim); + + /* 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; + + 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_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_io_limit, "LIMIT" }, + { config_parse_io_weight, "WEIGHT" }, + { config_parse_io_device_weight, "DEVICEWEIGHT" }, + { 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/grp-system/libcore/load-fragment.h b/src/grp-system/libcore/load-fragment.h new file mode 100644 index 0000000000..b36a2e3a02 --- /dev/null +++ b/src/grp-system/libcore/load-fragment.h @@ -0,0 +1,123 @@ +#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_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_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_io_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_io_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_io_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_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/grp-system/libcore/locale-setup.c b/src/grp-system/libcore/locale-setup.c new file mode 100644 index 0000000000..1c9660cdf4 --- /dev/null +++ b/src/grp-system/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 "basic/env-util.h" +#include "basic/fileio.h" +#include "locale-setup.h" +#include "basic/locale-util.h" +#include "basic/string-util.h" +#include "basic/strv.h" +#include "basic/util.h" +#include "basic/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/grp-system/libcore/locale-setup.h b/src/grp-system/libcore/locale-setup.h new file mode 100644 index 0000000000..3b97497afe --- /dev/null +++ b/src/grp-system/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/grp-system/libcore/loopback-setup.c b/src/grp-system/libcore/loopback-setup.c new file mode 100644 index 0000000000..f92e346f8c --- /dev/null +++ b/src/grp-system/libcore/loopback-setup.c @@ -0,0 +1,89 @@ +/*** + This file is part of systemd. + + Copyright 2010 Lennart Poettering + + systemd is free software; you can redistribute it and/or modify it + under the terms of the GNU Lesser General Public License as published by + the Free Software Foundation; either version 2.1 of the License, or + (at your option) any later version. + + systemd is distributed in the hope that it will be useful, but + WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + Lesser General Public License for more details. + + You should have received a copy of the GNU Lesser General Public License + along with systemd; If not, see . +***/ + +#include +#include + +#include "basic/missing.h" +#include "sd-netlink/sd-netlink.h" + +#include "loopback-setup.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/grp-system/libcore/loopback-setup.h b/src/grp-system/libcore/loopback-setup.h new file mode 100644 index 0000000000..e7547b8a26 --- /dev/null +++ b/src/grp-system/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/grp-system/libcore/machine-id-setup.c b/src/grp-system/libcore/machine-id-setup.c new file mode 100644 index 0000000000..702cc90e89 --- /dev/null +++ b/src/grp-system/libcore/machine-id-setup.c @@ -0,0 +1,364 @@ +/*** + This file is part of systemd. + + Copyright 2010 Lennart Poettering + + systemd is free software; you can redistribute it and/or modify it + under the terms of the GNU Lesser General Public License as published by + the Free Software Foundation; either version 2.1 of the License, or + (at your option) any later version. + + systemd is distributed in the hope that it will be useful, but + WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + Lesser General Public License for more details. + + You 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 "basic/alloc-util.h" +#include "basic/fd-util.h" +#include "basic/fileio.h" +#include "basic/fs-util.h" +#include "basic/hexdecoct.h" +#include "basic/io-util.h" +#include "basic/log.h" +#include "machine-id-setup.h" +#include "basic/macro.h" +#include "basic/mkdir.h" +#include "basic/mount-util.h" +#include "basic/path-util.h" +#include "basic/process-util.h" +#include "basic/stat-util.h" +#include "basic/string-util.h" +#include "basic/umask-util.h" +#include "basic/util.h" +#include "basic/virt.h" + +static int shorten_uuid(char destination[34], const char source[36]) { + unsigned i, j; + + assert(destination); + assert(source); + + /* Converts a UUID into a machine ID, by lowercasing it and + * removing dashes. Validates everything. */ + + for (i = 0, j = 0; i < 36 && j < 32; i++) { + int t; + + t = unhexchar(source[i]); + if (t < 0) + continue; + + destination[j++] = hexchar(t); + } + + if (i != 36 || j != 32) + return -EINVAL; + + destination[32] = '\n'; + destination[33] = 0; + return 0; +} + +static int read_machine_id(int fd, char id[34]) { + char id_to_validate[34]; + int r; + + assert(fd >= 0); + assert(id); + + /* Reads a machine ID from a file, validates it, and returns + * it. The returned ID ends in a newline. */ + + r = loop_read_exact(fd, id_to_validate, 33, false); + if (r < 0) + return r; + + if (id_to_validate[32] != '\n') + return -EINVAL; + + id_to_validate[32] = 0; + + if (!id128_is_valid(id_to_validate)) + return -EINVAL; + + memcpy(id, id_to_validate, 32); + id[32] = '\n'; + id[33] = 0; + return 0; +} + +static int write_machine_id(int fd, const char id[34]) { + int r; + + assert(fd >= 0); + assert(id); + + if (lseek(fd, 0, SEEK_SET) < 0) + return -errno; + + r = loop_write(fd, id, 33, false); + if (r < 0) + return r; + + if (fsync(fd) < 0) + return -errno; + + return 0; +} + +static int generate_machine_id(char id[34], const char *root) { + int fd, r; + unsigned char *p; + sd_id128_t buf; + char *q; + const char *dbus_machine_id; + + assert(id); + + dbus_machine_id = prefix_roota(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; + + etc_machine_id = prefix_roota(root, "/etc/machine-id"); + run_machine_id = prefix_roota(root, "/run/machine-id"); + + RUN_WITH_UMASK(0000) { + /* We create this 0444, to indicate that this isn't really + * 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; + + etc_machine_id = prefix_roota(root, "/etc/machine-id"); + + r = path_is_mount_point(etc_machine_id, 0); + if (r < 0) + return log_error_errno(r, "Failed to determine whether %s is a mount point: %m", etc_machine_id); + if (r == 0) { + log_debug("%s is is not a mount point. Nothing to do.", etc_machine_id); + 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/grp-system/libcore/machine-id-setup.h b/src/grp-system/libcore/machine-id-setup.h new file mode 100644 index 0000000000..a7e7678ed9 --- /dev/null +++ b/src/grp-system/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/grp-system/libcore/manager.c b/src/grp-system/libcore/manager.c new file mode 100644 index 0000000000..9019967300 --- /dev/null +++ b/src/grp-system/libcore/manager.c @@ -0,0 +1,3134 @@ +/*** + This file is part of systemd. + + Copyright 2010 Lennart Poettering + + systemd is free software; you can redistribute it and/or modify it + under the terms of the GNU Lesser General Public License as published by + the Free Software Foundation; either version 2.1 of the License, or + (at your option) any later version. + + systemd is distributed in the hope that it will be useful, but + WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + Lesser General Public License for more details. + + You 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 "basic/alloc-util.h" +#include "audit-fd.h" +#include "shared/boot-timestamps.h" +#include "sd-bus/bus-common-errors.h" +#include "sd-bus/bus-error.h" +#include "sd-bus/bus-kernel.h" +#include "shared/bus-util.h" +#include "dbus-job.h" +#include "dbus-manager.h" +#include "dbus-unit.h" +#include "dbus.h" +#include "basic/dirent-util.h" +#include "basic/env-util.h" +#include "basic/escape.h" +#include "basic/exit-status.h" +#include "basic/fd-util.h" +#include "basic/fileio.h" +#include "basic/fs-util.h" +#include "basic/hashmap.h" +#include "basic/io-util.h" +#include "locale-setup.h" +#include "basic/log.h" +#include "basic/macro.h" +#include "manager.h" +#include "basic/missing.h" +#include "basic/mkdir.h" +#include "basic/mkdir.h" +#include "basic/parse-util.h" +#include "shared/path-lookup.h" +#include "basic/path-util.h" +#include "basic/process-util.h" +#include "basic/ratelimit.h" +#include "basic/rm-rf.h" +#include "basic/signal-util.h" +#include "basic/special.h" +#include "basic/stat-util.h" +#include "basic/string-table.h" +#include "basic/string-util.h" +#include "basic/strv.h" +#include "basic/terminal-util.h" +#include "basic/time-util.h" +#include "transaction.h" +#include "basic/umask-util.h" +#include "basic/unit-name.h" +#include "basic/util.h" +#include "basic/virt.h" +#include "shared/watchdog.h" + +#define NOTIFY_RCVBUF_SIZE (8*1024*1024) +#define CGROUPS_AGENT_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_cgroups_agent_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_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. Also, process this before handling cgroup notifications, so that we always collect child exit + * status information before detecting that there's no process in a cgroup. */ + r = sd_event_source_set_priority(m->signal_event_source, SD_EVENT_PRIORITY_NORMAL-6); + if (r < 0) + return r; + + if (MANAGER_IS_SYSTEM(m)) + 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 (MANAGER_IS_SYSTEM(m)) { + /* 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(UnitFileScope scope, bool test_run, Manager **_m) { + Manager *m; + int r; + + assert(_m); + assert(IN_SET(scope, UNIT_FILE_SYSTEM, UNIT_FILE_USER)); + + m = new0(Manager, 1); + if (!m) + return -ENOMEM; + + m->unit_file_scope = scope; + 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); + +#ifdef ENABLE_EFI + if (MANAGER_IS_SYSTEM(m) && detect_container() <= 0) + boot_timestamps(&m->userspace_timestamp, &m->firmware_timestamp, &m->loader_timestamp); +#endif + + /* Prepare log fields we can use for structured logging */ + if (MANAGER_IS_SYSTEM(m)) { + m->unit_log_field = "UNIT="; + m->unit_log_format_string = "UNIT=%s"; + } else { + m->unit_log_field = "USER_UNIT="; + m->unit_log_format_string = "USER_UNIT=%s"; + } + + 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->cgroups_agent_fd = m->signal_fd = m->time_change_fd = + m->dev_autofs_fd = m->private_listen_fd = m->kdbus_fd = m->cgroup_inotify_fd = + m->ask_password_inotify_fd = -1; + + m->current_job_id = 1; /* start as id #1, so that we can leave #0 around as "null-like" value */ + + 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; + const char *e; + + /* 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); + + e = manager_get_runtime_prefix(m); + if (!e) { + log_error("Failed to determine runtime prefix."); + 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, SOCKADDR_UN_LEN(sa.un)); + 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 notification messages 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_cgroups_agent(Manager *m) { + + static const union sockaddr_union sa = { + .un.sun_family = AF_UNIX, + .un.sun_path = "/run/systemd/cgroups-agent", + }; + int r; + + /* This creates a listening socket we receive cgroups agent messages on. We do not use D-Bus for delivering + * these messages from the cgroups agent binary to PID 1, as the cgroups agent binary is very short-living, and + * each instance of it needs a new D-Bus connection. Since D-Bus connections are SOCK_STREAM/AF_UNIX, on + * overloaded systems the backlog of the D-Bus socket becomes relevant, as not more than the configured number + * of D-Bus connections may be queued until the kernel will start dropping further incoming connections, + * possibly resulting in lost cgroups agent messages. To avoid this, we'll use a private SOCK_DGRAM/AF_UNIX + * socket, where no backlog is relevant as communication may take place without an actual connect() cycle, and + * we thus won't lose messages. + * + * Note that PID 1 will forward the agent message to system bus, so that the user systemd instance may listen + * to it. The system instance hence listens on this special socket, but the user instances listen on the system + * bus for these messages. */ + + if (m->test_run) + return 0; + + if (!MANAGER_IS_SYSTEM(m)) + return 0; + + if (cg_unified() > 0) /* We don't need this anymore on the unified hierarchy */ + return 0; + + if (m->cgroups_agent_fd < 0) { + _cleanup_close_ int fd = -1; + + /* First free all secondary fields */ + m->cgroups_agent_event_source = sd_event_source_unref(m->cgroups_agent_event_source); + + fd = socket(AF_UNIX, SOCK_DGRAM|SOCK_CLOEXEC|SOCK_NONBLOCK, 0); + if (fd < 0) + return log_error_errno(errno, "Failed to allocate cgroups agent socket: %m"); + + fd_inc_rcvbuf(fd, CGROUPS_AGENT_RCVBUF_SIZE); + + (void) unlink(sa.un.sun_path); + + /* Only allow root to connect to this socket */ + RUN_WITH_UMASK(0077) + r = bind(fd, &sa.sa, SOCKADDR_UN_LEN(sa.un)); + if (r < 0) + return log_error_errno(errno, "bind(%s) failed: %m", sa.un.sun_path); + + m->cgroups_agent_fd = fd; + fd = -1; + } + + if (!m->cgroups_agent_event_source) { + r = sd_event_add_io(m->event, &m->cgroups_agent_event_source, m->cgroups_agent_fd, EPOLLIN, manager_dispatch_cgroups_agent_fd, m); + if (r < 0) + return log_error_errno(r, "Failed to allocate cgroups agent event source: %m"); + + /* Process cgroups notifications early, but after having processed service notification messages or + * SIGCHLD signals, so that a cgroup running empty is always just the last safety net of notification, + * and we collected the metadata the notification and SIGCHLD stuff offers first. Also see handling of + * cgroup inotify for the unified cgroup stuff. */ + r = sd_event_source_set_priority(m->cgroups_agent_event_source, SD_EVENT_PRIORITY_NORMAL-5); + if (r < 0) + return log_error_errno(r, "Failed to set priority of cgroups agent event source: %m"); + + (void) sd_event_source_set_description(m->cgroups_agent_event_source, "manager-cgroups-agent"); + } + + return 0; +} + +static int manager_setup_kdbus(Manager *m) { + _cleanup_free_ char *p = NULL; + + assert(m); + + if (m->test_run || m->kdbus_fd >= 0) + return 0; + if (!is_kdbus_available()) + return -ESOCKTNOSUPPORT; + + m->kdbus_fd = bus_kernel_create_bus( + MANAGER_IS_SYSTEM(m) ? "system" : "user", + MANAGER_IS_SYSTEM(m), &p); + + if (m->kdbus_fd < 0) + return log_debug_errno(m->kdbus_fd, "Failed to set up kdbus: %m"); + + log_debug("Successfully set up kdbus on %s", p); + + return 0; +} + +static int manager_connect_bus(Manager *m, bool reexecuting) { + bool try_bus_connect; + + assert(m); + + if (m->test_run) + return 0; + + try_bus_connect = + m->kdbus_fd >= 0 || + reexecuting || + (MANAGER_IS_USER(m) && 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); + + lookup_paths_flush_generator(&m->lookup_paths); + + 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->cgroups_agent_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->cgroups_agent_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; + 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) { + r = -ENOMEM; + goto fail; + } + + /* 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.search_path) { + _cleanup_closedir_ DIR *d = NULL; + struct dirent *de; + + d = opendir(*i); + if (!d) { + if (errno != ENOENT) + log_warning_errno(errno, "Failed to open directory %s, ignoring: %m", *i); + continue; + } + + FOREACH_DIRENT(de, d, r = -errno; goto fail) { + char *p; + + 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; + } + } + + return; + +fail: + log_warning_errno(r, "Failed to build unit path cache, proceeding without: %m"); + m->unit_path_cache = set_free_free(m->unit_path_cache); +} + +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); + + r = lookup_paths_init(&m->lookup_paths, m->unit_file_scope, 0, NULL); + if (r < 0) + return r; + + /* Make sure the transient directory always exists, so that it remains in the search path */ + r = mkdir_p_label(m->lookup_paths.transient, 0755); + if (r < 0) + return r; + + dual_timestamp_get(&m->generators_start_timestamp); + r = manager_run_generators(m); + dual_timestamp_get(&m->generators_finish_timestamp); + if (r < 0) + return r; + + lookup_paths_reduce(&m->lookup_paths); + 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; + + q = manager_setup_cgroups_agent(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)) { + if (unit_name_is_valid(name, UNIT_NAME_TEMPLATE)) + return sd_bus_error_setf(e, SD_BUS_ERROR_INVALID_ARGS, "Unit name %s is missing the instance name.", name); + + 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, 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 int manager_dispatch_cgroups_agent_fd(sd_event_source *source, int fd, uint32_t revents, void *userdata) { + Manager *m = userdata; + char buf[PATH_MAX+1]; + ssize_t n; + + n = recv(fd, buf, sizeof(buf), 0); + if (n < 0) + return log_error_errno(errno, "Failed to read cgroups agent message: %m"); + if (n == 0) { + log_error("Got zero-length cgroups agent message, ignoring."); + return 0; + } + if ((size_t) n >= sizeof(buf)) { + log_error("Got overly long cgroups agent message, ignoring."); + return 0; + } + + if (memchr(buf, 0, n)) { + log_error("Got cgroups agent message with embedded NUL byte, ignoring."); + return 0; + } + buf[n] = 0; + + manager_notify_cgroup_empty(m, buf); + bus_forward_agent_released(m, buf); + + return 0; +} + +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); + + if (UNIT_VTABLE(u)->sigchld_event) + 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 && MANAGER_IS_USER(m)) + ? LOG_DEBUG : LOG_INFO, + &sfsi); + + switch (sfsi.ssi_signo) { + + case SIGCHLD: + sigchld = true; + break; + + case SIGTERM: + if (MANAGER_IS_SYSTEM(m)) { + /* This is for compatibility with the + * original sysvinit */ + m->exit_code = MANAGER_REEXECUTE; + break; + } + + /* Fall through */ + + case SIGINT: + if (MANAGER_IS_SYSTEM(m)) { + + /* 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 (MANAGER_IS_SYSTEM(m)) + manager_start_target(m, SPECIAL_KBREQUEST_TARGET, JOB_REPLACE); + + /* This is a nop on non-init */ + break; + + case SIGPWR: + if (MANAGER_IS_SYSTEM(m)) + 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 (MANAGER_IS_USER(m)) { + 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->runtime_watchdog != USEC_INFINITY && MANAGER_IS_SYSTEM(m)) + 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->runtime_watchdog != USEC_INFINITY && MANAGER_IS_SYSTEM(m)) { + 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 (!MANAGER_IS_SYSTEM(m)) + 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 (MANAGER_IS_RELOADING(m)) + 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) { + static const union sockaddr_union sa = PLYMOUTH_SOCKET; + _cleanup_free_ char *message = NULL; + _cleanup_close_ int fd = -1; + int n = 0; + + /* Don't generate plymouth events if the service was already + * started and we're just deserializing */ + if (MANAGER_IS_RELOADING(m)) + return; + + if (!MANAGER_IS_SYSTEM(m)) + 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, SOCKADDR_UN_LEN(sa.un)) < 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 = MANAGER_IS_SYSTEM(m) ? "/run/systemd" : "/tmp"; + fd = open_tmpfile_unlinkable(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->cgroups_agent_fd >= 0) { + int copy; + + copy = fdset_put_dup(fds, m->cgroups_agent_fd); + if (copy < 0) + return copy; + + fprintf(f, "cgroups-agent-fd=%i\n", copy); + } + + 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, "cgroups-agent-fd=")) { + int fd; + + if (safe_atoi(l + 17, &fd) < 0 || fd < 0 || !fdset_contains(fds, fd)) + log_debug("Failed to parse cgroups agent fd: %s", l + 10); + else { + m->cgroups_agent_event_source = sd_event_source_unref(m->cgroups_agent_event_source); + safe_close(m->cgroups_agent_fd); + m->cgroups_agent_fd = fdset_remove(fds, fd); + } + + } 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); + lookup_paths_flush_generator(&m->lookup_paths); + lookup_paths_free(&m->lookup_paths); + + q = lookup_paths_init(&m->lookup_paths, m->unit_file_scope, 0, NULL); + if (q < 0 && r >= 0) + r = q; + + /* Find new unit paths */ + q = manager_run_generators(m); + if (q < 0 && r >= 0) + r = q; + + lookup_paths_reduce(&m->lookup_paths); + 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; + + q = manager_setup_cgroups_agent(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; +} + +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 (MANAGER_IS_SYSTEM(m) && 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 (MANAGER_IS_RELOADING(m)) + 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 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_binary_paths(m->unit_file_scope); + 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) { + if (access(*path, F_OK) >= 0) + goto found; + if (errno != ENOENT) + log_warning_errno(errno, "Failed to open generator directory %s: %m", *path); + } + + return 0; + + found: + r = lookup_paths_mkdir_generator(&m->lookup_paths); + if (r < 0) + goto finish; + + argv[0] = NULL; /* Leave this empty, execute_directory() will fill something in */ + argv[1] = m->lookup_paths.generator; + argv[2] = m->lookup_paths.generator_early; + argv[3] = m->lookup_paths.generator_late; + argv[4] = NULL; + + RUN_WITH_UMASK(0022) + execute_directories((const char* const*) paths, DEFAULT_TIMEOUT_USEC, (char**) argv); + +finish: + lookup_paths_trim_generator(&m->lookup_paths); + return r; +} + +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 (!MANAGER_IS_SYSTEM(m)) + 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 (!MANAGER_IS_SYSTEM(m)) + 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 (!MANAGER_IS_SYSTEM(m)) + 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 (!MANAGER_IS_SYSTEM(m)) + 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 MANAGER_IS_SYSTEM(m) ? + "/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/grp-system/libcore/manager.h b/src/grp-system/libcore/manager.h new file mode 100644 index 0000000000..87186b6e40 --- /dev/null +++ b/src/grp-system/libcore/manager.h @@ -0,0 +1,379 @@ +#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 "basic/cgroup-util.h" +#include "basic/fdset.h" +#include "basic/hashmap.h" +#include "basic/list.h" +#include "basic/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 "shared/path-lookup.h" +#include "show-status.h" +#include "basic/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 cgroups_agent_fd; + sd_event_source *cgroups_agent_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; + + UnitFileScope unit_file_scope; + 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; + + 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 */ + 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_io_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; /* tri-state */ +}; + +#define MANAGER_IS_SYSTEM(m) ((m)->unit_file_scope == UNIT_FILE_SYSTEM) +#define MANAGER_IS_USER(m) ((m)->unit_file_scope != UNIT_FILE_SYSTEM) + +#define MANAGER_IS_RELOADING(m) ((m)->n_reloading > 0) + +int manager_new(UnitFileScope scope, 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); + +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/grp-system/libcore/mount-setup.c b/src/grp-system/libcore/mount-setup.c new file mode 100644 index 0000000000..09df45c567 --- /dev/null +++ b/src/grp-system/libcore/mount-setup.c @@ -0,0 +1,413 @@ +/*** + This file is part of systemd. + + Copyright 2010 Lennart Poettering + + systemd is free software; you can redistribute it and/or modify it + under the terms of the GNU Lesser General Public License as published by + the Free Software Foundation; either version 2.1 of the License, or + (at your option) any later version. + + systemd is distributed in the hope that it will be useful, but + WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + Lesser General Public License for more details. + + You should have received a copy of the GNU Lesser General Public License + along with systemd; If not, see . +***/ + +#include +#include +#include +#include +#include + +#include "basic/alloc-util.h" +#include "shared/bus-util.h" +#include "basic/cgroup-util.h" +#include "shared/dev-setup.h" +#include "shared/efivars.h" +#include "basic/label.h" +#include "basic/log.h" +#include "basic/macro.h" +#include "basic/missing.h" +#include "basic/mkdir.h" +#include "mount-setup.h" +#include "basic/mount-util.h" +#include "basic/path-util.h" +#include "basic/set.h" +#include "basic/smack-util.h" +#include "basic/strv.h" +#include "basic/user-util.h" +#include "basic/util.h" +#include "basic/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", "cgroup2", NULL, 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("/dev/shm", 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/grp-system/libcore/mount-setup.h b/src/grp-system/libcore/mount-setup.h new file mode 100644 index 0000000000..647bd770ae --- /dev/null +++ b/src/grp-system/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/grp-system/libcore/mount.c b/src/grp-system/libcore/mount.c new file mode 100644 index 0000000000..ab6efd4a9c --- /dev/null +++ b/src/grp-system/libcore/mount.c @@ -0,0 +1,1881 @@ +/*** + This file is part of systemd. + + Copyright 2010 Lennart Poettering + + systemd is free software; you can redistribute it and/or modify it + under the terms of the GNU Lesser General Public License as published by + the Free Software Foundation; either version 2.1 of the License, or + (at your option) any later version. + + systemd is distributed in the hope that it will be useful, but + WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + Lesser General Public License for more details. + + You should have received a copy of the GNU Lesser General Public License + along with systemd; If not, see . +***/ + +#include +#include +#include +#include + +#include + +#include "basic/alloc-util.h" +#include "dbus-mount.h" +#include "basic/escape.h" +#include "basic/exit-status.h" +#include "basic/formats-util.h" +#include "shared/fstab-util.h" +#include "basic/log.h" +#include "manager.h" +#include "basic/mkdir.h" +#include "mount-setup.h" +#include "basic/mount-util.h" +#include "mount.h" +#include "basic/parse-util.h" +#include "basic/path-util.h" +#include "basic/process-util.h" +#include "basic/special.h" +#include "basic/string-table.h" +#include "basic/string-util.h" +#include "basic/strv.h" +#include "basic/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_loop(const MountParameters *p) { + assert(p); + + if (fstab_test_option(p->options, "loop\0")) + return true; + + return false; +} + +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 mount_is_automount(const MountParameters *p) { + assert(p); + + return fstab_test_option(p->options, + "comment=systemd.automount\0" + "x-systemd.automount\0"); +} + +static bool mount_state_active(MountState state) { + return IN_SET(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); +} + +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 or a loop mount) to be + * available. */ + pm = get_mount_parameters_fragment(m); + if (pm && pm->what && + path_is_absolute(pm->what) && + (mount_is_bind(pm) || mount_is_loop(pm) || !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) && !mount_is_automount(p) && MANAGER_IS_SYSTEM(UNIT(m)->manager)) + 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 (!MANAGER_IS_SYSTEM(UNIT(m)->manager)) + 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_IN_SET(m->where, "/", "/usr") || + path_startswith(m->where, "/run/initramfs")) + 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 (!MANAGER_IS_SYSTEM(UNIT(m)->manager)) + return 0; + + /* We do not add any default dependencies to /, /usr or + * /run/initramfs/, 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_IN_SET(m->where, "/", "/usr") || + path_startswith(m->where, "/run/initramfs") || + 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 void mount_set_state(Mount *m, MountState state) { + MountState old_state; + assert(m); + + old_state = m->state; + m->state = state; + + if (!mount_state_active(state)) { + 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; + } + + 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) && + mount_state_active(new_state)) { + + 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, + .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); + int r; + + 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); + + r = unit_start_limit_test(u); + if (r < 0) { + mount_enter_dead(m, MOUNT_FAILURE_START_LIMIT_HIT); + return r; + } + + 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 (MANAGER_IS_SYSTEM(m)) { + 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 (MANAGER_IS_SYSTEM(m) && + 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 int mount_control_pid(Unit *u) { + Mount *m = MOUNT(u); + + assert(m); + + return m->control_pid; +} + +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", + [MOUNT_FAILURE_START_LIMIT_HIT] = "start-limit-hit", +}; + +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", + + .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, + + .control_pid = mount_control_pid, + + .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/grp-system/libcore/mount.h b/src/grp-system/libcore/mount.h new file mode 100644 index 0000000000..da529c44f4 --- /dev/null +++ b/src/grp-system/libcore/mount.h @@ -0,0 +1,108 @@ +#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_FAILURE_START_LIMIT_HIT, + _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/grp-system/libcore/namespace.c b/src/grp-system/libcore/namespace.c new file mode 100644 index 0000000000..f7c5364437 --- /dev/null +++ b/src/grp-system/libcore/namespace.c @@ -0,0 +1,650 @@ +/*** + This file is part of systemd. + + Copyright 2010 Lennart Poettering + + systemd is free software; you can redistribute it and/or modify it + under the terms of the GNU Lesser General Public License as published by + the Free Software Foundation; either version 2.1 of the License, or + (at your option) any later version. + + systemd is distributed in the hope that it will be useful, but + WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + Lesser General Public License for more details. + + You 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 "basic/alloc-util.h" +#include "shared/dev-setup.h" +#include "basic/fd-util.h" +#include "loopback-setup.h" +#include "basic/missing.h" +#include "basic/mkdir.h" +#include "basic/mount-util.h" +#include "namespace.h" +#include "basic/path-util.h" +#include "basic/selinux-util.h" +#include "basic/socket-util.h" +#include "basic/string-table.h" +#include "basic/string-util.h" +#include "basic/strv.h" +#include "basic/umask-util.h" +#include "basic/user-util.h" +#include "basic/util.h" + +#define DEV_MOUNT_OPTIONS (MS_NOSUID|MS_STRICTATIME|MS_NOEXEC) + +typedef enum MountMode { + /* This is ordered by priority! */ + INACCESSIBLE, + READONLY, + PRIVATE_TMP, + PRIVATE_VAR_TMP, + PRIVATE_DEV, + 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", DEV_MOUNT_OPTIONS, "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); + + /* Unmount everything in old /dev */ + umount_recursive(m->path, 0); + 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 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); + + 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); + if (r == 0 && m->mode == PRIVATE_DEV) /* can be readonly but the submounts can't*/ + r = mount(NULL, m->path, NULL, MS_REMOUNT|DEV_MOUNT_OPTIONS|MS_RDONLY, NULL); + } 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, + 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 + + 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 (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/grp-system/libcore/namespace.h b/src/grp-system/libcore/namespace.h new file mode 100644 index 0000000000..03097327dd --- /dev/null +++ b/src/grp-system/libcore/namespace.h @@ -0,0 +1,63 @@ +#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 "basic/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, + 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/grp-system/libcore/path.c b/src/grp-system/libcore/path.c new file mode 100644 index 0000000000..782e085174 --- /dev/null +++ b/src/grp-system/libcore/path.c @@ -0,0 +1,784 @@ +/*** + This file is part of systemd. + + Copyright 2010 Lennart Poettering + + systemd is free software; you can redistribute it and/or modify it + under the terms of the GNU Lesser General Public License as published by + the Free Software Foundation; either version 2.1 of the License, or + (at your option) any later version. + + systemd is distributed in the hope that it will be useful, but + WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + Lesser General Public License for more details. + + 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/bus-error.h" +#include "shared/bus-util.h" +#include "dbus-path.h" +#include "basic/fd-util.h" +#include "basic/fs-util.h" +#include "basic/glob-util.h" +#include "basic/macro.h" +#include "basic/mkdir.h" +#include "path.h" +#include "basic/special.h" +#include "basic/stat-util.h" +#include "basic/string-table.h" +#include "basic/string-util.h" +#include "basic/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'; + + (void) 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 (MANAGER_IS_SYSTEM(UNIT(p)->manager)) { + 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, PATH_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; + int r; + + 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; + } + + r = unit_start_limit_test(u); + if (r < 0) { + path_enter_dead(p, PATH_FAILURE_START_LIMIT_HIT); + return r; + } + + 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", + [PATH_FAILURE_START_LIMIT_HIT] = "start-limit-hit", +}; + +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/grp-system/libcore/path.h b/src/grp-system/libcore/path.h new file mode 100644 index 0000000000..4230c8fb99 --- /dev/null +++ b/src/grp-system/libcore/path.h @@ -0,0 +1,93 @@ +#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_FAILURE_START_LIMIT_HIT, + _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/grp-system/libcore/scope.c b/src/grp-system/libcore/scope.c new file mode 100644 index 0000000000..baf1051375 --- /dev/null +++ b/src/grp-system/libcore/scope.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 "basic/alloc-util.h" +#include "dbus-scope.h" +#include "load-dropin.h" +#include "basic/log.h" +#include "scope.h" +#include "basic/special.h" +#include "basic/string-table.h" +#include "basic/string-util.h" +#include "basic/strv.h" +#include "basic/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; + u->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(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(u->manager)) + /* Refuse to load non-transient scope units, but allow them while reloading. */ + return -ENOENT; + + r = unit_load_fragment_and_dropin_optional(u); + if (r < 0) + return r; + + if (u->load_state == UNIT_LOADED) { + 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(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", + + .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/grp-system/libcore/scope.h b/src/grp-system/libcore/scope.h new file mode 100644 index 0000000000..2dc86325c5 --- /dev/null +++ b/src/grp-system/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/grp-system/libcore/selinux-access.c b/src/grp-system/libcore/selinux-access.c new file mode 100644 index 0000000000..a259e128c1 --- /dev/null +++ b/src/grp-system/libcore/selinux-access.c @@ -0,0 +1,283 @@ +/*** + 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 "basic/alloc-util.h" +#include "audit-fd.h" +#include "shared/bus-util.h" +#include "basic/log.h" +#include "basic/path-util.h" +#include "basic/selinux-util.h" +#include "basic/stdio-util.h" +#include "basic/strv.h" +#include "basic/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; + const char *fmt2; + +#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 + + fmt2 = strjoina("selinux: ", fmt); + + va_start(ap, fmt); + log_internalv(LOG_AUTH | callback_type_to_priority(type), 0, __FILE__, __LINE__, __FUNCTION__, fmt2, 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/grp-system/libcore/selinux-access.h b/src/grp-system/libcore/selinux-access.h new file mode 100644 index 0000000000..1b1a8418ec --- /dev/null +++ b/src/grp-system/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 "shared/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/grp-system/libcore/selinux-setup.c b/src/grp-system/libcore/selinux-setup.c new file mode 100644 index 0000000000..37a143bce4 --- /dev/null +++ b/src/grp-system/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 "basic/log.h" +#include "basic/macro.h" +#include "selinux-setup.h" +#include "basic/selinux-util.h" +#include "basic/string-util.h" +#include "basic/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_raw(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/grp-system/libcore/selinux-setup.h b/src/grp-system/libcore/selinux-setup.h new file mode 100644 index 0000000000..7b613249b0 --- /dev/null +++ b/src/grp-system/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/grp-system/libcore/service.c b/src/grp-system/libcore/service.c new file mode 100644 index 0000000000..2845a841c3 --- /dev/null +++ b/src/grp-system/libcore/service.c @@ -0,0 +1,3353 @@ +/*** + This file is part of systemd. + + Copyright 2010 Lennart Poettering + + systemd is free software; you can redistribute it and/or modify it + under the terms of the GNU Lesser General Public License as published by + the Free Software Foundation; either version 2.1 of the License, or + (at your option) any later version. + + systemd is distributed in the hope that it will be useful, but + WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + Lesser General Public License for more details. + + You should have received a copy of the GNU Lesser General Public License + along with systemd; If not, see . +***/ + +#include +#include +#include + +#include "basic/alloc-util.h" +#include "basic/async.h" +#include "sd-bus/bus-error.h" +#include "sd-bus/bus-kernel.h" +#include "shared/bus-util.h" +#include "dbus-service.h" +#include "basic/def.h" +#include "basic/env-util.h" +#include "basic/escape.h" +#include "basic/exit-status.h" +#include "basic/fd-util.h" +#include "basic/fileio.h" +#include "basic/formats-util.h" +#include "basic/fs-util.h" +#include "load-dropin.h" +#include "load-fragment.h" +#include "basic/log.h" +#include "manager.h" +#include "basic/parse-util.h" +#include "basic/path-util.h" +#include "basic/process-util.h" +#include "service.h" +#include "basic/signal-util.h" +#include "basic/special.h" +#include "basic/string-table.h" +#include "basic/string-util.h" +#include "basic/strv.h" +#include "basic/unit-name.h" +#include "unit-printf.h" +#include "unit.h" +#include "basic/utf8.h" +#include "basic/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->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; +} + +void service_close_socket_fd(Service *s) { + assert(s); + + /* Undo the effect of service_set_socket_fd(). */ + + s->socket_fd = asynchronous_close(s->socket_fd); + + if (UNIT_ISSET(s->accept_socket)) { + 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); + + service_close_socket_fd(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 (MANAGER_IS_SYSTEM(UNIT(s)->manager)) { + /* 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 void 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; + + if (!s->guess_main_pid) + return; + + assert(s->main_pid <= 0); + + if (unit_search_main_pid(UNIT(s), &pid) < 0) + return; + + log_unit_debug(UNIT(s), "Main PID guessed: "PID_FMT, pid); + if (service_set_main_pid(s, pid) < 0) + return; + + 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); +} + +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); + + 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 && !MANAGER_IS_RELOADING(UNIT(s)->manager)) + 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_ 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, + .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 (!MANAGER_IS_SYSTEM(UNIT(s)->manager)) + 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; + + 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.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); + int r; + + 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)); + + /* Make sure we don't enter a busy loop of some kind. */ + r = unit_start_limit_test(u); + if (r < 0) { + service_enter_dead(s, SERVICE_FAILURE_START_LIMIT_HIT, false); + return r; + } + + 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; + + 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); + } + } + + 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, "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 + 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 + service_search_main_pid(s); + + service_enter_running(s, SERVICE_SUCCESS); + break; + + case SERVICE_RELOAD: + if (f == SERVICE_SUCCESS) + if (service_load_pid_file(s, true) < 0) + 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. We take ownership of the passed fd on success. */ + + 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; + } + + r = unit_add_two_dependencies(UNIT(sock), UNIT_BEFORE, UNIT_TRIGGERS, UNIT(s), false); + 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 0; +} + +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 int service_main_pid(Unit *u) { + Service *s = SERVICE(u); + + assert(s); + + return s->main_pid; +} + +static int service_control_pid(Unit *u) { + Service *s = SERVICE(u); + + assert(s); + + return s->control_pid; +} + +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", + [SERVICE_FAILURE_START_LIMIT_HIT] = "start-limit-hit", +}; + +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, + + .main_pid = service_main_pid, + .control_pid = service_control_pid, + + .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/grp-system/libcore/service.h b/src/grp-system/libcore/service.h new file mode 100644 index 0000000000..66e2c387fe --- /dev/null +++ b/src/grp-system/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 "basic/exit-status.h" +#include "kill.h" +#include "path.h" +#include "basic/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, /* a bit of a misnomer, just our catch-all error for errnos we didn't expect */ + SERVICE_FAILURE_TIMEOUT, + SERVICE_FAILURE_EXIT_CODE, + SERVICE_FAILURE_SIGNAL, + SERVICE_FAILURE_CORE_DUMP, + SERVICE_FAILURE_WATCHDOG, + SERVICE_FAILURE_START_LIMIT_HIT, + _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; + + 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); +void service_close_socket_fd(Service *s); + +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/grp-system/libcore/show-status.c b/src/grp-system/libcore/show-status.c new file mode 100644 index 0000000000..03a3117f53 --- /dev/null +++ b/src/grp-system/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 "basic/alloc-util.h" +#include "basic/fd-util.h" +#include "basic/io-util.h" +#include "basic/parse-util.h" +#include "show-status.h" +#include "basic/string-util.h" +#include "basic/terminal-util.h" +#include "basic/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/grp-system/libcore/show-status.h b/src/grp-system/libcore/show-status.h new file mode 100644 index 0000000000..56c56eb358 --- /dev/null +++ b/src/grp-system/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 "basic/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/grp-system/libcore/slice.c b/src/grp-system/libcore/slice.c new file mode 100644 index 0000000000..d9b3fa9525 --- /dev/null +++ b/src/grp-system/libcore/slice.c @@ -0,0 +1,346 @@ +/*** + 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 "basic/alloc-util.h" +#include "dbus-slice.h" +#include "basic/log.h" +#include "slice.h" +#include "basic/special.h" +#include "basic/string-util.h" +#include "basic/strv.h" +#include "basic/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_init(Unit *u) { + assert(u); + assert(u->load_state == UNIT_STUB); + + u->ignore_on_isolate = true; +} + +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); + assert(u->load_state == UNIT_STUB); + + 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", + + .can_transient = true, + + .init = slice_init, + .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/grp-system/libcore/slice.h b/src/grp-system/libcore/slice.h new file mode 100644 index 0000000000..c9f3f61067 --- /dev/null +++ b/src/grp-system/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/grp-system/libcore/smack-setup.c b/src/grp-system/libcore/smack-setup.c new file mode 100644 index 0000000000..16eb234202 --- /dev/null +++ b/src/grp-system/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 "basic/alloc-util.h" +#include "basic/dirent-util.h" +#include "basic/fd-util.h" +#include "basic/fileio.h" +#include "basic/log.h" +#include "basic/macro.h" +#include "smack-setup.h" +#include "basic/string-util.h" +#include "basic/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/grp-system/libcore/smack-setup.h b/src/grp-system/libcore/smack-setup.h new file mode 100644 index 0000000000..78164c85e6 --- /dev/null +++ b/src/grp-system/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/grp-system/libcore/socket.c b/src/grp-system/libcore/socket.c new file mode 100644 index 0000000000..7ee5edfef8 --- /dev/null +++ b/src/grp-system/libcore/socket.c @@ -0,0 +1,2992 @@ +/*** + This file is part of systemd. + + Copyright 2010 Lennart Poettering + + systemd is free software; you can redistribute it and/or modify it + under the terms of the GNU Lesser General Public License as published by + the Free Software Foundation; either version 2.1 of the License, or + (at your option) any later version. + + systemd is distributed in the hope that it will be useful, but + WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + Lesser General Public License for more details. + + You 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 "basic/alloc-util.h" +#include "sd-bus/bus-error.h" +#include "shared/bus-util.h" +#include "basic/copy.h" +#include "dbus-socket.h" +#include "basic/def.h" +#include "basic/exit-status.h" +#include "basic/fd-util.h" +#include "basic/formats-util.h" +#include "basic/io-util.h" +#include "basic/label.h" +#include "basic/log.h" +#include "basic/missing.h" +#include "basic/mkdir.h" +#include "basic/parse-util.h" +#include "basic/path-util.h" +#include "basic/process-util.h" +#include "basic/selinux-util.h" +#include "basic/signal-util.h" +#include "basic/smack-util.h" +#include "socket.h" +#include "basic/special.h" +#include "basic/string-table.h" +#include "basic/string-util.h" +#include "basic/strv.h" +#include "basic/unit-name.h" +#include "unit-printf.h" +#include "unit.h" +#include "basic/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; + + s->trigger_limit.interval = USEC_INFINITY; + s->trigger_limit.burst = (unsigned) -1; +} + +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; + + 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 (MANAGER_IS_SYSTEM(UNIT(s)->manager)) { + 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); + + /* Pick defaults for the trigger limit, if nothing was explicitly configured. We pick a relatively high limit + * in Accept=yes mode, and a lower limit for Accept=no. Reason: in Accept=yes mode we are invoking accept() + * ourselves before the trigger limit can hit, thus incoming connections are taken off the socket queue quickly + * and reliably. This is different for Accept=no, where the spawned service has to take the incoming traffic + * off the queues, which it might not necessarily do. Moreover, while Accept=no services are supposed to + * process whatever is queued in one go, and thus should normally never have to be started frequently. This is + * different for Accept=yes where each connection is processed by a new service instance, and thus frequent + * service starts are typical. */ + + if (s->trigger_limit.interval == USEC_INFINITY) + s->trigger_limit.interval = 2 * USEC_PER_SEC; + + if (s->trigger_limit.burst == (unsigned) -1) { + if (s->accept) + s->trigger_limit.burst = 200; + else + s->trigger_limit.burst = 20; + } + + 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, + "%sSocketUser: %s\n" + "%sSocketGroup: %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); + } + + fprintf(f, + "%sTriggerLimitIntervalSec: %s\n" + "%sTriggerLimitBurst: %u\n", + prefix, format_timespan(time_string, FORMAT_TIMESPAN_MAX, s->trigger_limit.interval, USEC_PER_SEC), + prefix, s->trigger_limit.burst); + + 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) { + bool was_open; + + was_open = p->fd >= 0; + + p->event_source = sd_event_source_unref(p->event_source); + 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 normally delete sockets in the file system before we create a new one, not after we + * stopped using one! That all said, if the user explicitly requested this, we'll delete them here + * anyway, but only then. */ + + if (!was_open || !s->remove_on_stop) + continue; + + switch (p->type) { + + case SOCKET_FIFO: + (void) unlink(p->path); + break; + + case SOCKET_MQUEUE: + (void) mq_unlink(p->path); + break; + + case SOCKET_SOCKET: + (void) socket_address_unlink(&p->address); + break; + + default: + break; + } + } + + if (s->remove_on_stop) + STRV_FOREACH(i, s->symlinks) + (void) 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_determine_selinux_label(Socket *s, char **ret) { + ExecCommand *c; + int r; + + assert(s); + assert(ret); + + if (s->selinux_context_from_net) { + /* If this is requested, get label from the network label */ + + r = mac_selinux_get_our_label(ret); + if (r == -EOPNOTSUPP) + goto no_label; + + } else { + /* Otherwise, get it from the executable we are about to start */ + r = socket_instantiate_service(s); + if (r < 0) + return r; + + if (!UNIT_ISSET(s->service)) + goto no_label; + + c = SERVICE(UNIT_DEREF(s->service))->exec_command[SERVICE_EXEC_START]; + if (!c) + goto no_label; + + r = mac_selinux_get_create_label_from_exe(c->path, ret); + if (r == -EPERM || r == -EOPNOTSUPP) + goto no_label; + } + + return r; + +no_label: + *ret = NULL; + return 0; +} + +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. */ + + r = socket_determine_selinux_label(s, &label); + if (r < 0) + goto rollback; + + know_label = true; + } + + /* Apply the socket protocol */ + switch (p->address.type) { + + case SOCK_STREAM: + case SOCK_SEQPACKET: + if (s->socket_protocol == IPPROTO_SCTP) + p->address.protocol = s->socket_protocol; + break; + + case SOCK_DGRAM: + if (s->socket_protocol == IPPROTO_UDPLITE) + p->address.protocol = s->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: { + _cleanup_free_ char *ep = NULL; + + ep = path_make_absolute("ep0", p->path); + + p->fd = usbffs_address_create(ep); + 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; +} + +enum { + SOCKET_OPEN_NONE, + SOCKET_OPEN_SOME, + SOCKET_OPEN_ALL, +}; + +static int socket_check_open(Socket *s) { + bool have_open = false, have_closed = false; + SocketPort *p; + + assert(s); + + LIST_FOREACH(port, p, s->ports) { + if (p->fd < 0) + have_closed = true; + else + have_open = true; + + if (have_open && have_closed) + return SOCKET_OPEN_SOME; + } + + if (have_open) + return SOCKET_OPEN_ALL; + + return SOCKET_OPEN_NONE; +} + +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)) { + + /* Originally, we used to simply reopen all sockets here that we didn't have file descriptors + * for. However, this is problematic, as we won't traverse throught the SOCKET_START_CHOWN state for + * them, and thus the UID/GID wouldn't be right. Hence, instead simply check if we have all fds open, + * and if there's a mismatch, warn loudly. */ + + r = socket_check_open(s); + if (r == SOCKET_OPEN_NONE) + log_unit_warning(UNIT(s), + "Socket unit configuration has changed while unit has been running, " + "no open socket file descriptor left. " + "The socket unit is not functional until restarted."); + else if (r == SOCKET_OPEN_SOME) + log_unit_warning(UNIT(s), + "Socket unit configuration has changed while unit has been running, " + "and some socket file descriptors have not been opened yet. " + "The socket unit is not fully functional until restarted."); + } + + 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, + .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 flush_ports(Socket *s) { + SocketPort *p; + + /* Flush all incoming traffic, regardless if actual bytes or new connections, so that this socket isn't busy + * anymore */ + + LIST_FOREACH(port, p, s->ports) { + if (p->fd < 0) + continue; + + (void) flush_accept(p->fd); + (void) flush_fd(p->fd); + } +} + +static void socket_enter_running(Socket *s, int cfd) { + _cleanup_(sd_bus_error_free) sd_bus_error error = SD_BUS_ERROR_NULL; + int r; + + /* Note that this call takes possession of the connection fd passed. It either has to assign it somewhere or + * close it. */ + + 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) + cfd = safe_close(cfd); + else + flush_ports(s); + + return; + } + + if (!ratelimit_test(&s->trigger_limit)) { + safe_close(cfd); + log_unit_warning(UNIT(s), "Trigger limit hit, refusing further activation."); + socket_enter_stop_pre(s, SOCKET_FAILURE_TRIGGER_LIMIT_HIT); + 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), refusing connection attempt.", 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. */ + log_unit_debug(UNIT(s), "Got ENOTCONN on incoming socket, assuming aborted connection attempt, ignoring."); + 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_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; /* We passed ownership of the fd to the service now. Forget it here. */ + s->n_connections++; + + r = manager_add_job(UNIT(s)->manager, JOB_START, UNIT(service), JOB_REPLACE, &error, NULL); + if (r < 0) { + /* We failed to activate the new service, but it still exists. Let's make sure the service + * closes and forgets the connection fd again, immediately. */ + service_close_socket_fd(service); + 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); + int r; + + 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); + + r = unit_start_limit_test(u); + if (r < 0) { + socket_enter_dead(s, SOCKET_FAILURE_START_LIMIT_HIT); + return r; + } + + 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); + + /* Filter out invocations with bogus state */ + if (other->load_state != UNIT_LOADED || other->type != UNIT_SERVICE) + return; + + /* Don't propagate state changes from the service if we are already down */ + if (!IN_SET(s->state, SOCKET_RUNNING, SOCKET_LISTENING)) + return; + + /* We don't care for the service state if we are in Accept=yes mode */ + if (s->accept) + return; + + /* Propagate start limit hit state */ + if (other->start_limit_hit) { + socket_enter_stop_pre(s, SOCKET_FAILURE_SERVICE_START_LIMIT_HIT); + return; + } + + /* Don't propagate anything if there's still a job queued */ + if (other->job) + 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 int socket_control_pid(Unit *u) { + Socket *s = SOCKET(u); + + assert(s); + + return s->control_pid; +} + +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_START_LIMIT_HIT] = "start-limit-hit", + [SOCKET_FAILURE_TRIGGER_LIMIT_HIT] = "trigger-limit-hit", + [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, + + .control_pid = socket_control_pid, + + .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/grp-system/libcore/socket.h b/src/grp-system/libcore/socket.h new file mode 100644 index 0000000000..4f25eabd5d --- /dev/null +++ b/src/grp-system/libcore/socket.h @@ -0,0 +1,185 @@ +#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 "basic/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_START_LIMIT_HIT, + SOCKET_FAILURE_TRIGGER_LIMIT_HIT, + 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; + + RateLimit trigger_limit; +}; + +/* 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/grp-system/libcore/swap.c b/src/grp-system/libcore/swap.c new file mode 100644 index 0000000000..c08057a9fe --- /dev/null +++ b/src/grp-system/libcore/swap.c @@ -0,0 +1,1532 @@ +/*** + This file is part of systemd. + + Copyright 2010 Lennart Poettering + + systemd is free software; you can redistribute it and/or modify it + under the terms of the GNU Lesser General Public License as published by + the Free Software Foundation; either version 2.1 of the License, or + (at your option) any later version. + + systemd is distributed in the hope that it will be useful, but + WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + Lesser General Public License for more details. + + 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 "basic/alloc-util.h" +#include "dbus-swap.h" +#include "basic/escape.h" +#include "basic/exit-status.h" +#include "basic/fd-util.h" +#include "basic/formats-util.h" +#include "shared/fstab-util.h" +#include "basic/parse-util.h" +#include "basic/path-util.h" +#include "basic/process-util.h" +#include "basic/special.h" +#include "basic/string-table.h" +#include "basic/string-util.h" +#include "swap.h" +#include "shared/udev-util.h" +#include "basic/unit-name.h" +#include "unit.h" +#include "basic/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, MANAGER_IS_SYSTEM(UNIT(s)->manager), 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 (!MANAGER_IS_SYSTEM(UNIT(s)->manager)) + 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, + .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; + int r; + + 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; + + r = unit_start_limit_test(u); + if (r < 0) { + swap_enter_dead(s, SWAP_FAILURE_START_LIMIT_HIT); + return r; + } + + 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 int swap_control_pid(Unit *u) { + Swap *s = SWAP(u); + + assert(s); + + return s->control_pid; +} + +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", + [SWAP_FAILURE_START_LIMIT_HIT] = "start-limit-hit", +}; + +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", + + .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, + + .control_pid = swap_control_pid, + + .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/grp-system/libcore/swap.h b/src/grp-system/libcore/swap.h new file mode 100644 index 0000000000..fbf66debdc --- /dev/null +++ b/src/grp-system/libcore/swap.h @@ -0,0 +1,110 @@ +#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_FAILURE_START_LIMIT_HIT, + _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/grp-system/libcore/target.c b/src/grp-system/libcore/target.c new file mode 100644 index 0000000000..25630b80e5 --- /dev/null +++ b/src/grp-system/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 "basic/log.h" +#include "basic/special.h" +#include "basic/string-util.h" +#include "basic/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/grp-system/libcore/target.h b/src/grp-system/libcore/target.h new file mode 100644 index 0000000000..339aea154e --- /dev/null +++ b/src/grp-system/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/grp-system/libcore/timer.c b/src/grp-system/libcore/timer.c new file mode 100644 index 0000000000..45eec02dee --- /dev/null +++ b/src/grp-system/libcore/timer.c @@ -0,0 +1,859 @@ +/*** + This file is part of systemd. + + Copyright 2010 Lennart Poettering + + systemd is free software; you can redistribute it and/or modify it + under the terms of the GNU Lesser General Public License as published by + the Free Software Foundation; either version 2.1 of the License, or + (at your option) any later version. + + systemd is distributed in the hope that it will be useful, but + WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + Lesser General Public License for more details. + + You should have received a copy of the GNU Lesser General Public License + along with systemd; If not, see . +***/ + +#include + +#include "basic/alloc-util.h" +#include "sd-bus/bus-error.h" +#include "shared/bus-util.h" +#include "dbus-timer.h" +#include "basic/fs-util.h" +#include "basic/parse-util.h" +#include "basic/random-util.h" +#include "basic/special.h" +#include "basic/string-table.h" +#include "basic/string-util.h" +#include "timer.h" +#include "basic/unit-name.h" +#include "unit.h" +#include "basic/user-util.h" +#include "basic/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 (MANAGER_IS_SYSTEM(UNIT(t)->manager)) { + 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 (MANAGER_IS_SYSTEM(UNIT(t)->manager)) { + + 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_or_monotonic()); + 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_or_monotonic() : 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; + int r; + + 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; + } + + r = unit_start_limit_test(u); + if (r < 0) { + timer_enter_dead(t, TIMER_FAILURE_START_LIMIT_HIT); + return r; + } + + 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", + [TIMER_FAILURE_START_LIMIT_HIT] = "start-limit-hit", +}; + +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/grp-system/libcore/timer.h b/src/grp-system/libcore/timer.h new file mode 100644 index 0000000000..3773ec47e1 --- /dev/null +++ b/src/grp-system/libcore/timer.h @@ -0,0 +1,89 @@ +#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 "basic/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_FAILURE_START_LIMIT_HIT, + _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/grp-system/libcore/transaction.c b/src/grp-system/libcore/transaction.c new file mode 100644 index 0000000000..a0ff69e2dc --- /dev/null +++ b/src/grp-system/libcore/transaction.c @@ -0,0 +1,1099 @@ +/*** + This file is part of systemd. + + Copyright 2010 Lennart Poettering + + systemd is free software; you can redistribute it and/or modify it + under the terms of the GNU Lesser General Public License as published by + the Free Software Foundation; either version 2.1 of the License, or + (at your option) any later version. + + systemd is distributed in the hope that it will be useful, but + WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + Lesser General Public License for more details. + + You should have received a copy of the GNU Lesser General Public License + along with systemd; If not, see . +***/ + +#include +#include + +#include "basic/alloc-util.h" +#include "sd-bus/bus-common-errors.h" +#include "sd-bus/bus-error.h" +#include "basic/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) { + const char *status; + /* 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)); + + if (log_get_show_color()) + status = ANSI_HIGHLIGHT_RED " SKIP " ANSI_NORMAL; + else + status = " SKIP "; + + unit_status_printf(delete->unit, status, + "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, 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 (MANAGER_IS_RELOADING(unit->manager)) + 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, -ERFKILL, -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/grp-system/libcore/transaction.h b/src/grp-system/libcore/transaction.h new file mode 100644 index 0000000000..73489af0e6 --- /dev/null +++ b/src/grp-system/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 "basic/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/grp-system/libcore/unit-printf.c b/src/grp-system/libcore/unit-printf.c new file mode 100644 index 0000000000..c64d45e251 --- /dev/null +++ b/src/grp-system/libcore/unit-printf.c @@ -0,0 +1,304 @@ +/*** + This file is part of systemd. + + Copyright 2010 Lennart Poettering + + systemd is free software; you can redistribute it and/or modify it + under the terms of the GNU Lesser General Public License as published by + the Free Software Foundation; either version 2.1 of the License, or + (at your option) any later version. + + systemd is distributed in the hope that it will be useful, but + WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + Lesser General Public License for more details. + + You should have received a copy of the GNU Lesser General Public License + along with systemd; If not, see . +***/ + +#include "basic/alloc-util.h" +#include "basic/cgroup-util.h" +#include "basic/formats-util.h" +#include "basic/macro.h" +#include "shared/specifier.h" +#include "basic/string-util.h" +#include "basic/strv.h" +#include "basic/unit-name.h" +#include "unit-printf.h" +#include "unit.h" +#include "basic/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); + + e = manager_get_runtime_prefix(u->manager); + 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/grp-system/libcore/unit-printf.h b/src/grp-system/libcore/unit-printf.h new file mode 100644 index 0000000000..4fc8531228 --- /dev/null +++ b/src/grp-system/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/grp-system/libcore/unit.c b/src/grp-system/libcore/unit.c new file mode 100644 index 0000000000..3caa8baa26 --- /dev/null +++ b/src/grp-system/libcore/unit.c @@ -0,0 +1,3818 @@ +/*** + This file is part of systemd. + + Copyright 2010 Lennart Poettering + + systemd is free software; you can redistribute it and/or modify it + under the terms of the GNU Lesser General Public License as published by + the Free Software Foundation; either version 2.1 of the License, or + (at your option) any later version. + + systemd is distributed in the hope that it will be useful, but + WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + Lesser General Public License for more details. + + You 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 "basic/alloc-util.h" +#include "sd-bus/bus-common-errors.h" +#include "shared/bus-util.h" +#include "basic/cgroup-util.h" +#include "dbus-unit.h" +#include "dbus.h" +#include "shared/dropin.h" +#include "basic/escape.h" +#include "execute.h" +#include "basic/fileio-label.h" +#include "basic/formats-util.h" +#include "load-dropin.h" +#include "load-fragment.h" +#include "basic/log.h" +#include "basic/macro.h" +#include "basic/missing.h" +#include "basic/mkdir.h" +#include "basic/parse-util.h" +#include "basic/path-util.h" +#include "basic/process-util.h" +#include "basic/set.h" +#include "basic/signal-util.h" +#include "basic/special.h" +#include "basic/stat-util.h" +#include "basic/stdio-util.h" +#include "basic/string-util.h" +#include "basic/strv.h" +#include "basic/umask-util.h" +#include "basic/unit-name.h" +#include "unit.h" +#include "basic/user-util.h" +#include "basic/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->io_accounting = u->manager->default_io_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_type_may_template(t)) + 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_type_may_alias(t) && !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, *pp = NULL; + + p = dirname_malloc(*i); /* Get the drop-in directory from the drop-in file */ + if (!p) + continue; + + pp = dirname_malloc(p); /* Get the config directory from the drop-in directory */ + if (!pp) + continue; + + /* Only drop transient drop-ins */ + if (!path_equal(u->manager->lookup_paths.transient, pp)) + continue; + + (void) unlink(*i); + (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->transient_file) + fclose(u->transient_file); + + if (!MANAGER_IS_RELOADING(u->manager)) + 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 (!unit_type_may_alias(u->type)) /* Merging only applies to unit names that support aliases */ + return -EEXIST; + + 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) { + _cleanup_free_ char *s = NULL; + Unit *other; + 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, &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 (!MANAGER_IS_SYSTEM(u->manager)) + 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\tState 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_io_weight == CGROUP_WEIGHT_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 (u->transient_file) { + r = fflush_and_check(u->transient_file); + if (r < 0) + goto fail; + + fclose(u->transient_file); + u->transient_file = NULL; + + u->fragment_mtime = now(CLOCK_REALTIME); + } + + 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); +} + +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; + + 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; + + /* 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 (!MANAGER_IS_RELOADING(m)) { + 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, false); + 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, false); + } + + 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, false); + 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, false); + } + } + + 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, false); + else if (u->job->state == JOB_RUNNING && ns != UNIT_DEACTIVATING) { + unexpected = true; + job_finish_and_invalidate(u->job, JOB_FAILED, true, false); + } + + break; + + default: + assert_not_reached("Job type unknown"); + } + + } else + unexpected = true; + + if (!MANAGER_IS_RELOADING(m)) { + + /* 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) && + !MANAGER_IS_RELOADING(m)) { + /* 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) && + !MANAGER_IS_RELOADING(m)) { + + /* 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 (!MANAGER_IS_RELOADING(u->manager)) { + /* 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; + + /* Disallow slice changes if @u is already bound to cgroups */ + if (UNIT_ISSET(u->slice) && u->cgroup_realized) + return -EBUSY; + + unit_ref_unset(&u->slice); + 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 (MANAGER_IS_SYSTEM(u->manager)) + b = strjoin("system-", escaped, ".slice", NULL); + else + b = strappend(escaped, ".slice"); + if (!b) + return -ENOMEM; + + slice_name = b; + } else + slice_name = + MANAGER_IS_SYSTEM(u->manager) && !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, "'"); + + 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, + MANAGER_IS_SYSTEM(u->manager) ? 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; +} + +static bool fragment_mtime_newer(const char *path, usec_t mtime) { + struct stat st; + + if (!path) + return false; + + if (stat(path, &st) < 0) + /* What, cannot access this anymore? */ + return true; + + if (mtime > 0) + /* For non-empty files check the mtime */ + return timespec_load(&st.st_mtim) > mtime; + else if (!null_or_empty(&st)) + /* For masked files check if they are still so */ + return true; + + return false; +} + +bool unit_need_daemon_reload(Unit *u) { + _cleanup_strv_free_ char **t = NULL; + char **path; + + assert(u); + + if (fragment_mtime_newer(u->fragment_path, u->fragment_mtime)) + return true; + + if (fragment_mtime_newer(u->source_path, u->source_mtime)) + return true; + + (void) unit_find_dropin_paths(u, &t); + if (!strv_equal(u->dropin_paths, t)) + return true; + + STRV_FOREACH(path, u->dropin_paths) + if (fragment_mtime_newer(*path, u->dropin_mtime)) + return true; + + return false; +} + +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(SIGNAL_VALID(signo)); + + 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->unit_file_scope, + 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->unit_file_scope, + 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; + + /* We are about to drop a reference to the unit, make sure the garbage collection has a look at it as it might + * be unreferenced now. */ + unit_add_to_gc_queue(ref->unit); + + 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 (MANAGER_IS_USER(u->manager) && + !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 (MANAGER_IS_USER(u->manager) && + (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 const char* unit_drop_in_dir(Unit *u, UnitSetPropertiesMode mode) { + assert(u); + + if (!IN_SET(mode, UNIT_RUNTIME, UNIT_PERSISTENT)) + return NULL; + + if (u->transient) /* Redirect drop-ins for transient units always into the transient directory. */ + return u->manager->lookup_paths.transient; + + if (mode == UNIT_RUNTIME) + return u->manager->lookup_paths.runtime_control; + + if (mode == UNIT_PERSISTENT) + return u->manager->lookup_paths.persistent_control; + + return NULL; +} + +int unit_write_drop_in(Unit *u, UnitSetPropertiesMode mode, const char *name, const char *data) { + _cleanup_free_ char *p = NULL, *q = NULL; + const char *dir, *prefixed; + int r; + + assert(u); + + if (u->transient_file) { + /* When this is a transient unit file in creation, then let's not create a new drop-in but instead + * write to the transient unit file. */ + fputs(data, u->transient_file); + return 0; + } + + if (!IN_SET(mode, UNIT_PERSISTENT, UNIT_RUNTIME)) + return 0; + + dir = unit_drop_in_dir(u, mode); + if (!dir) + return -EINVAL; + + prefixed = strjoina("# This is a drop-in unit file extension, created via \"systemctl set-property\" or an equivalent operation. Do not edit.\n", + data); + + r = drop_in_file(dir, u->id, 50, name, &p, &q); + if (r < 0) + return r; + + (void) mkdir_p(p, 0755); + r = write_string_file_atomic_label(q, prefixed); + if (r < 0) + return r; + + r = strv_push(&u->dropin_paths, q); + if (r < 0) + return r; + q = NULL; + + 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) { + const char *ndata; + + 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 = strjoina("[", UNIT_VTABLE(u)->private_section, "]\n", data); + + 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) { + FILE *f; + char *path; + + assert(u); + + if (!UNIT_VTABLE(u)->can_transient) + return -EOPNOTSUPP; + + path = strjoin(u->manager->lookup_paths.transient, "/", u->id, NULL); + if (!path) + return -ENOMEM; + + /* Let's open the file we'll write the transient settings into. This file is kept open as long as we are + * creating the transient, and is closed in unit_load(), as soon as we start loading the file. */ + + RUN_WITH_UMASK(0022) { + f = fopen(path, "we"); + if (!f) { + free(path); + return -errno; + } + } + + if (u->transient_file) + fclose(u->transient_file); + u->transient_file = f; + + free(u->fragment_path); + u->fragment_path = 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; + + u->load_state = UNIT_STUB; + u->load_error = 0; + u->transient = true; + + unit_add_to_dbus_queue(u); + unit_add_to_gc_queue(u); + unit_add_to_load_queue(u); + + fputs("# This is a transient unit file, created programmatically via the systemd API. Do not edit.\n", + u->transient_file); + + 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); +} + +pid_t unit_control_pid(Unit *u) { + assert(u); + + if (UNIT_VTABLE(u)->control_pid) + return UNIT_VTABLE(u)->control_pid(u); + + return 0; +} + +pid_t unit_main_pid(Unit *u) { + assert(u); + + if (UNIT_VTABLE(u)->main_pid) + return UNIT_VTABLE(u)->main_pid(u); + + return 0; +} diff --git a/src/grp-system/libcore/unit.h b/src/grp-system/libcore/unit.h new file mode 100644 index 0000000000..df1174b837 --- /dev/null +++ b/src/grp-system/libcore/unit.h @@ -0,0 +1,639 @@ +#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 "shared/condition.h" +#include "failure-action.h" +#include "shared/install.h" +#include "basic/list.h" +#include "basic/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 this is a transient unit we are currently writing, this is where we are writing it to */ + FILE *transient_file; + + /* 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_enabled_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); + + /* Returns the main PID if there is any defined, or 0. */ + pid_t (*main_pid)(Unit *u); + + /* Returns the main PID if there is any defined, or 0. */ + pid_t (*control_pid)(Unit *u); + + /* 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; + + /* 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); + +pid_t unit_control_pid(Unit *u); +pid_t unit_main_pid(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); + +int unit_start_limit_test(Unit *u); + +/* 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/grp-system/systemctl/Makefile b/src/grp-system/systemctl/Makefile index 24585cc12b..43269c66c7 100644 --- a/src/grp-system/systemctl/Makefile +++ b/src/grp-system/systemctl/Makefile @@ -30,4 +30,8 @@ systemctl_SOURCES = \ systemctl_LDADD = \ libshared.la +systemd.CPPFLAGS += -DROOTLIBEXECDIR=\"$(rootlibexecdir)\" +systemd.CPPFLAGS += -DSYSTEM_SYSVINIT_PATH=\"$(SYSTEM_SYSVINIT_PATH)\" +systemd.CPPFLAGS += -DSYSTEMD_BINARY_PATH=\"$(rootlibexecdir)/systemd\" + include $(topsrcdir)/build-aux/Makefile.tail.mk diff --git a/src/grp-system/systemctl/systemctl.c b/src/grp-system/systemctl/systemctl.c index b9b1c8400b..0808ecafe3 100644 --- a/src/grp-system/systemctl/systemctl.c +++ b/src/grp-system/systemctl/systemctl.c @@ -36,16 +36,8 @@ #include #include "basic/alloc-util.h" -#include "bus-common-errors.h" -#include "bus-error.h" -#include "bus-message.h" -#include "shared/bus-unit-util.h" -#include "shared/bus-util.h" -#include "shared/cgroup-show.h" #include "basic/cgroup-util.h" #include "basic/copy.h" -#include "shared/dropin.h" -#include "shared/efivars.h" #include "basic/env-util.h" #include "basic/exit-status.h" #include "basic/fd-util.h" @@ -54,26 +46,19 @@ #include "basic/fs-util.h" #include "basic/glob-util.h" #include "basic/hostname-util.h" -#include "shared/initreq.h" -#include "shared/install.h" #include "basic/io-util.h" #include "basic/list.h" #include "basic/locale-util.h" #include "basic/log.h" -#include "shared/logs-show.h" #include "basic/macro.h" #include "basic/mkdir.h" -#include "shared/pager.h" #include "basic/parse-util.h" -#include "shared/path-lookup.h" #include "basic/path-util.h" #include "basic/process-util.h" #include "basic/rlimit-util.h" #include "basic/set.h" #include "basic/signal-util.h" #include "basic/socket-util.h" -#include "shared/spawn-ask-password-agent.h" -#include "shared/spawn-polkit-agent.h" #include "basic/special.h" #include "basic/stat-util.h" #include "basic/strv.h" @@ -81,9 +66,24 @@ #include "basic/unit-name.h" #include "basic/user-util.h" #include "basic/util.h" -#include "shared/utmp-wtmp.h" #include "basic/verbs.h" #include "basic/virt.h" +#include "sd-bus/bus-common-errors.h" +#include "sd-bus/bus-error.h" +#include "sd-bus/bus-message.h" +#include "shared/bus-unit-util.h" +#include "shared/bus-util.h" +#include "shared/cgroup-show.h" +#include "shared/dropin.h" +#include "shared/efivars.h" +#include "shared/initreq.h" +#include "shared/install.h" +#include "shared/logs-show.h" +#include "shared/pager.h" +#include "shared/path-lookup.h" +#include "shared/spawn-ask-password-agent.h" +#include "shared/spawn-polkit-agent.h" +#include "shared/utmp-wtmp.h" static char **arg_types = NULL; static char **arg_states = NULL; diff --git a/src/grp-system/systemd/Makefile b/src/grp-system/systemd/Makefile index cfef258c6d..8184c85da8 100644 --- a/src/grp-system/systemd/Makefile +++ b/src/grp-system/systemd/Makefile @@ -68,4 +68,10 @@ dist_systemunit_DATA_busnames += \ BUSNAMES_TARGET_WANTS += \ org.freedesktop.systemd1.busname +systemd.CPPFLAGS += -DPKGSYSCONFDIR=\"$(pkgsysconfdir)\" + +systemd.CPPFLAGS += -DSYSTEMD_BINARY_PATH=\"$(rootlibexecdir)/systemd\" +systemd.CPPFLAGS += -DSYSTEMD_SHUTDOWN_BINARY_PATH=\"$(rootlibexecdir)/systemd-shutdown\" +systemd.CPPFLAGS += -DSYSTEMCTL_BINARY_PATH=\"$(rootbindir)/systemctl\" + include $(topsrcdir)/build-aux/Makefile.tail.mk diff --git a/src/grp-system/systemd/main.c b/src/grp-system/systemd/main.c index 31206f21dc..aa7c126fcf 100644 --- a/src/grp-system/systemd/main.c +++ b/src/grp-system/systemd/main.c @@ -41,13 +41,9 @@ #include "basic/alloc-util.h" #include "basic/architecture.h" #include "basic/build.h" -#include "bus-error.h" -#include "shared/bus-util.h" #include "basic/capability-util.h" #include "basic/clock-util.h" -#include "shared/conf-parser.h" #include "basic/cpu-set-util.h" -#include "dbus-manager.h" #include "basic/def.h" #include "basic/env-util.h" #include "basic/fd-util.h" @@ -55,36 +51,40 @@ #include "basic/fileio.h" #include "basic/formats-util.h" #include "basic/fs-util.h" -#include "hostname-setup.h" -#include "ima-setup.h" -#include "killall.h" -#include "kmod-setup.h" -#include "load-fragment.h" #include "basic/log.h" -#include "loopback-setup.h" -#include "machine-id-setup.h" -#include "manager.h" #include "basic/missing.h" -#include "mount-setup.h" -#include "shared/pager.h" #include "basic/parse-util.h" #include "basic/proc-cmdline.h" #include "basic/process-util.h" #include "basic/rlimit-util.h" -#include "selinux-setup.h" #include "basic/selinux-util.h" #include "basic/signal-util.h" -#include "smack-setup.h" #include "basic/special.h" #include "basic/stat-util.h" #include "basic/stdio-util.h" #include "basic/strv.h" -#include "shared/switch-root.h" #include "basic/terminal-util.h" #include "basic/umask-util.h" #include "basic/user-util.h" #include "basic/virt.h" +#include "dbus-manager.h" +#include "hostname-setup.h" +#include "ima-setup.h" +#include "killall.h" +#include "kmod-setup.h" +#include "load-fragment.h" +#include "loopback-setup.h" +#include "machine-id-setup.h" +#include "manager.h" +#include "mount-setup.h" +#include "sd-bus/bus-error.h" +#include "selinux-setup.h" +#include "shared/bus-util.h" +#include "shared/conf-parser.h" +#include "shared/pager.h" +#include "shared/switch-root.h" #include "shared/watchdog.h" +#include "smack-setup.h" static enum { ACTION_RUN, diff --git a/src/grp-udev/systemd-hwdb/Makefile b/src/grp-udev/systemd-hwdb/Makefile new file mode 100644 index 0000000000..bf14199ffe --- /dev/null +++ b/src/grp-udev/systemd-hwdb/Makefile @@ -0,0 +1,78 @@ +# -*- 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_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 # ENABLE_HWDB + +EXTRA_DIST += \ + units/systemd-hwdb-update.service.in \ + hwdb/ids-update.pl \ + hwdb/sdio.ids + +systemd.CPPFLAGS += -DUDEVLIBEXECDIR=\"$(udevlibexecdir)\" + +include $(topsrcdir)/build-aux/Makefile.tail.mk diff --git a/src/grp-udev/systemd-hwdb/hwdb.c b/src/grp-udev/systemd-hwdb/hwdb.c new file mode 100644 index 0000000000..0e96716b0f --- /dev/null +++ b/src/grp-udev/systemd-hwdb/hwdb.c @@ -0,0 +1,739 @@ +/*** + 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 + +#include "basic/alloc-util.h" +#include "basic/conf-files.h" +#include "basic/fd-util.h" +#include "basic/fileio.h" +#include "basic/fs-util.h" +#include "basic/mkdir.h" +#include "basic/strbuf.h" +#include "basic/string-util.h" +#include "basic/strv.h" +#include "basic/util.h" +#include "basic/verbs.h" +#include "sd-hwdb/hwdb-internal.h" +#include "sd-hwdb/hwdb-util.h" + +/* + * Generic udev properties, key/value database based on modalias strings. + * Uses a Patricia/radix trie to index all matches for efficient lookup. + */ + +static const char *arg_hwdb_bin_dir = "/etc/udev"; +static const char *arg_root = ""; + +static const char * const conf_file_dirs[] = { + "/etc/udev/hwdb.d", + UDEVLIBEXECDIR "/hwdb.d", + NULL +}; + +/* in-memory trie objects */ +struct trie { + struct trie_node *root; + struct strbuf *strings; + + size_t nodes_count; + size_t children_count; + size_t values_count; +}; + +struct trie_node { + /* prefix, common part for all children of this node */ + size_t prefix_off; + + /* sorted array of pointers to children nodes */ + struct trie_child_entry *children; + uint8_t children_count; + + /* sorted array of key/value pairs */ + struct trie_value_entry *values; + size_t values_count; +}; + +/* children array item with char (0-255) index */ +struct trie_child_entry { + uint8_t c; + struct trie_node *child; +}; + +/* value array item with key/value pairs */ +struct trie_value_entry { + size_t key_off; + size_t value_off; +}; + +static int trie_children_cmp(const void *v1, const void *v2) { + const struct trie_child_entry *n1 = v1; + const struct trie_child_entry *n2 = v2; + + return n1->c - n2->c; +} + +static int node_add_child(struct trie *trie, struct trie_node *node, struct trie_node *node_child, uint8_t c) { + struct trie_child_entry *child; + + /* extend array, add new entry, sort for bisection */ + child = realloc(node->children, (node->children_count + 1) * sizeof(struct trie_child_entry)); + if (!child) + return -ENOMEM; + + node->children = child; + trie->children_count++; + node->children[node->children_count].c = c; + node->children[node->children_count].child = node_child; + node->children_count++; + qsort(node->children, node->children_count, sizeof(struct trie_child_entry), trie_children_cmp); + trie->nodes_count++; + + return 0; +} + +static struct trie_node *node_lookup(const struct trie_node *node, uint8_t c) { + struct trie_child_entry *child; + struct trie_child_entry search; + + search.c = c; + child = bsearch(&search, node->children, node->children_count, sizeof(struct trie_child_entry), trie_children_cmp); + if (child) + return child->child; + return NULL; +} + +static void trie_node_cleanup(struct trie_node *node) { + size_t i; + + for (i = 0; i < node->children_count; i++) + trie_node_cleanup(node->children[i].child); + free(node->children); + free(node->values); + free(node); +} + +static void trie_free(struct trie *trie) { + if (!trie) + return; + + if (trie->root) + trie_node_cleanup(trie->root); + + strbuf_cleanup(trie->strings); + free(trie); +} + +DEFINE_TRIVIAL_CLEANUP_FUNC(struct trie*, trie_free); + +static int trie_values_cmp(const void *v1, const void *v2, void *arg) { + const struct trie_value_entry *val1 = v1; + const struct trie_value_entry *val2 = v2; + struct trie *trie = arg; + + return strcmp(trie->strings->buf + val1->key_off, + trie->strings->buf + val2->key_off); +} + +static int trie_node_add_value(struct trie *trie, struct trie_node *node, + const char *key, const char *value) { + ssize_t k, v; + struct trie_value_entry *val; + + k = strbuf_add_string(trie->strings, key, strlen(key)); + if (k < 0) + return k; + v = strbuf_add_string(trie->strings, value, strlen(value)); + if (v < 0) + return v; + + if (node->values_count) { + struct trie_value_entry search = { + .key_off = k, + .value_off = v, + }; + + val = xbsearch_r(&search, node->values, node->values_count, sizeof(struct trie_value_entry), trie_values_cmp, trie); + if (val) { + /* replace existing earlier key with new value */ + val->value_off = v; + return 0; + } + } + + /* extend array, add new entry, sort for bisection */ + val = realloc(node->values, (node->values_count + 1) * sizeof(struct trie_value_entry)); + if (!val) + return -ENOMEM; + trie->values_count++; + node->values = val; + node->values[node->values_count].key_off = k; + node->values[node->values_count].value_off = v; + node->values_count++; + qsort_r(node->values, node->values_count, sizeof(struct trie_value_entry), trie_values_cmp, trie); + return 0; +} + +static int trie_insert(struct trie *trie, struct trie_node *node, const char *search, + const char *key, const char *value) { + size_t i = 0; + int err = 0; + + for (;;) { + size_t p; + uint8_t c; + struct trie_node *child; + + for (p = 0; (c = trie->strings->buf[node->prefix_off + p]); p++) { + _cleanup_free_ char *s = NULL; + ssize_t off; + _cleanup_free_ struct trie_node *new_child = NULL; + + if (c == search[i + p]) + continue; + + /* split node */ + new_child = new0(struct trie_node, 1); + if (!new_child) + return -ENOMEM; + + /* move values from parent to child */ + new_child->prefix_off = node->prefix_off + p+1; + new_child->children = node->children; + new_child->children_count = node->children_count; + new_child->values = node->values; + new_child->values_count = node->values_count; + + /* update parent; use strdup() because the source gets realloc()d */ + s = strndup(trie->strings->buf + node->prefix_off, p); + if (!s) + return -ENOMEM; + + off = strbuf_add_string(trie->strings, s, p); + if (off < 0) + return off; + + node->prefix_off = off; + node->children = NULL; + node->children_count = 0; + node->values = NULL; + node->values_count = 0; + err = node_add_child(trie, node, new_child, c); + if (err < 0) + return err; + + new_child = NULL; /* avoid cleanup */ + break; + } + i += p; + + c = search[i]; + if (c == '\0') + return trie_node_add_value(trie, node, key, value); + + child = node_lookup(node, c); + if (!child) { + ssize_t off; + + /* new child */ + child = new0(struct trie_node, 1); + if (!child) + return -ENOMEM; + + off = strbuf_add_string(trie->strings, search + i+1, strlen(search + i+1)); + if (off < 0) { + free(child); + return off; + } + + child->prefix_off = off; + err = node_add_child(trie, node, child, c); + if (err < 0) { + free(child); + return err; + } + + return trie_node_add_value(trie, child, key, value); + } + + node = child; + i++; + } +} + +struct trie_f { + FILE *f; + struct trie *trie; + uint64_t strings_off; + + uint64_t nodes_count; + uint64_t children_count; + uint64_t values_count; +}; + +/* calculate the storage space for the nodes, children arrays, value arrays */ +static void trie_store_nodes_size(struct trie_f *trie, struct trie_node *node) { + uint64_t i; + + for (i = 0; i < node->children_count; i++) + trie_store_nodes_size(trie, node->children[i].child); + + trie->strings_off += sizeof(struct trie_node_f); + for (i = 0; i < node->children_count; i++) + trie->strings_off += sizeof(struct trie_child_entry_f); + for (i = 0; i < node->values_count; i++) + trie->strings_off += sizeof(struct trie_value_entry_f); +} + +static int64_t trie_store_nodes(struct trie_f *trie, struct trie_node *node) { + uint64_t i; + struct trie_node_f n = { + .prefix_off = htole64(trie->strings_off + node->prefix_off), + .children_count = node->children_count, + .values_count = htole64(node->values_count), + }; + struct trie_child_entry_f *children = NULL; + int64_t node_off; + + if (node->children_count) { + children = new0(struct trie_child_entry_f, node->children_count); + if (!children) + return -ENOMEM; + } + + /* post-order recursion */ + for (i = 0; i < node->children_count; i++) { + int64_t child_off; + + child_off = trie_store_nodes(trie, node->children[i].child); + if (child_off < 0) { + free(children); + return child_off; + } + children[i].c = node->children[i].c; + children[i].child_off = htole64(child_off); + } + + /* write node */ + node_off = ftello(trie->f); + fwrite(&n, sizeof(struct trie_node_f), 1, trie->f); + trie->nodes_count++; + + /* append children array */ + if (node->children_count) { + fwrite(children, sizeof(struct trie_child_entry_f), node->children_count, trie->f); + trie->children_count += node->children_count; + free(children); + } + + /* append values array */ + for (i = 0; i < node->values_count; i++) { + struct trie_value_entry_f v = { + .key_off = htole64(trie->strings_off + node->values[i].key_off), + .value_off = htole64(trie->strings_off + node->values[i].value_off), + }; + + fwrite(&v, sizeof(struct trie_value_entry_f), 1, trie->f); + trie->values_count++; + } + + return node_off; +} + +static int trie_store(struct trie *trie, const char *filename) { + struct trie_f t = { + .trie = trie, + }; + _cleanup_free_ char *filename_tmp = NULL; + int64_t pos; + int64_t root_off; + int64_t size; + struct trie_header_f h = { + .signature = HWDB_SIG, + .tool_version = htole64(atoi(VERSION)), + .header_size = htole64(sizeof(struct trie_header_f)), + .node_size = htole64(sizeof(struct trie_node_f)), + .child_entry_size = htole64(sizeof(struct trie_child_entry_f)), + .value_entry_size = htole64(sizeof(struct trie_value_entry_f)), + }; + int err; + + /* calculate size of header, nodes, children entries, value entries */ + t.strings_off = sizeof(struct trie_header_f); + trie_store_nodes_size(&t, trie->root); + + err = fopen_temporary(filename , &t.f, &filename_tmp); + if (err < 0) + return err; + fchmod(fileno(t.f), 0444); + + /* write nodes */ + err = fseeko(t.f, sizeof(struct trie_header_f), SEEK_SET); + if (err < 0) { + fclose(t.f); + unlink_noerrno(filename_tmp); + return -errno; + } + root_off = trie_store_nodes(&t, trie->root); + h.nodes_root_off = htole64(root_off); + pos = ftello(t.f); + h.nodes_len = htole64(pos - sizeof(struct trie_header_f)); + + /* write string buffer */ + fwrite(trie->strings->buf, trie->strings->len, 1, t.f); + h.strings_len = htole64(trie->strings->len); + + /* write header */ + size = ftello(t.f); + h.file_size = htole64(size); + err = fseeko(t.f, 0, SEEK_SET); + if (err < 0) { + fclose(t.f); + unlink_noerrno(filename_tmp); + return -errno; + } + fwrite(&h, sizeof(struct trie_header_f), 1, t.f); + err = ferror(t.f); + if (err) + err = -errno; + fclose(t.f); + if (err < 0 || rename(filename_tmp, filename) < 0) { + unlink_noerrno(filename_tmp); + return err < 0 ? err : -errno; + } + + log_debug("=== trie on-disk ==="); + log_debug("size: %8"PRIi64" bytes", size); + log_debug("header: %8zu bytes", sizeof(struct trie_header_f)); + log_debug("nodes: %8"PRIu64" bytes (%8"PRIu64")", + t.nodes_count * sizeof(struct trie_node_f), t.nodes_count); + log_debug("child pointers: %8"PRIu64" bytes (%8"PRIu64")", + t.children_count * sizeof(struct trie_child_entry_f), t.children_count); + log_debug("value pointers: %8"PRIu64" bytes (%8"PRIu64")", + t.values_count * sizeof(struct trie_value_entry_f), t.values_count); + log_debug("string store: %8zu bytes", trie->strings->len); + log_debug("strings start: %8"PRIu64, t.strings_off); + + return 0; +} + +static int insert_data(struct trie *trie, char **match_list, char *line, const char *filename) { + char *value, **entry; + + value = strchr(line, '='); + if (!value) { + log_error("Error, key/value pair expected but got '%s' in '%s':", line, filename); + return -EINVAL; + } + + value[0] = '\0'; + value++; + + /* libudev requires properties to start with a space */ + while (isblank(line[0]) && isblank(line[1])) + line++; + + if (line[0] == '\0' || value[0] == '\0') { + log_error("Error, empty key or value '%s' in '%s':", line, filename); + return -EINVAL; + } + + STRV_FOREACH(entry, match_list) + trie_insert(trie, trie->root, *entry, line, value); + + return 0; +} + +static int import_file(struct trie *trie, const char *filename) { + enum { + HW_NONE, + HW_MATCH, + HW_DATA, + } state = HW_NONE; + _cleanup_fclose_ FILE *f = NULL; + char line[LINE_MAX]; + _cleanup_strv_free_ char **match_list = NULL; + char *match = NULL; + int r; + + f = fopen(filename, "re"); + if (!f) + return -errno; + + while (fgets(line, sizeof(line), f)) { + size_t len; + char *pos; + + /* comment line */ + if (line[0] == '#') + continue; + + /* strip trailing comment */ + pos = strchr(line, '#'); + if (pos) + pos[0] = '\0'; + + /* strip trailing whitespace */ + len = strlen(line); + while (len > 0 && isspace(line[len-1])) + len--; + line[len] = '\0'; + + switch (state) { + case HW_NONE: + if (len == 0) + break; + + if (line[0] == ' ') { + log_error("Error, MATCH expected but got '%s' in '%s':", line, filename); + break; + } + + /* start of record, first match */ + state = HW_MATCH; + + match = strdup(line); + if (!match) + return -ENOMEM; + + r = strv_consume(&match_list, match); + if (r < 0) + return r; + + break; + + case HW_MATCH: + if (len == 0) { + log_error("Error, DATA expected but got empty line in '%s':", filename); + state = HW_NONE; + strv_clear(match_list); + break; + } + + /* another match */ + if (line[0] != ' ') { + match = strdup(line); + if (!match) + return -ENOMEM; + + r = strv_consume(&match_list, match); + if (r < 0) + return r; + + break; + } + + /* first data */ + state = HW_DATA; + insert_data(trie, match_list, line, filename); + break; + + case HW_DATA: + /* end of record */ + if (len == 0) { + state = HW_NONE; + strv_clear(match_list); + break; + } + + if (line[0] != ' ') { + log_error("Error, DATA expected but got '%s' in '%s':", line, filename); + state = HW_NONE; + strv_clear(match_list); + break; + } + + insert_data(trie, match_list, line, filename); + break; + }; + } + + return 0; +} + +static int hwdb_query(int argc, char *argv[], void *userdata) { + _cleanup_(sd_hwdb_unrefp) sd_hwdb *hwdb = NULL; + const char *key, *value; + const char *modalias; + int r; + + assert(argc >= 2); + assert(argv); + + modalias = argv[1]; + + r = sd_hwdb_new(&hwdb); + if (r < 0) + return r; + + SD_HWDB_FOREACH_PROPERTY(hwdb, modalias, key, value) + printf("%s=%s\n", key, value); + + return 0; +} + +static int hwdb_update(int argc, char *argv[], void *userdata) { + _cleanup_free_ char *hwdb_bin = NULL; + _cleanup_(trie_freep) struct trie *trie = NULL; + char **files, **f; + int r; + + trie = new0(struct trie, 1); + if (!trie) + return -ENOMEM; + + /* string store */ + trie->strings = strbuf_new(); + if (!trie->strings) + return -ENOMEM; + + /* index */ + trie->root = new0(struct trie_node, 1); + if (!trie->root) + return -ENOMEM; + + trie->nodes_count++; + + r = conf_files_list_strv(&files, ".hwdb", arg_root, conf_file_dirs); + if (r < 0) + return log_error_errno(r, "failed to enumerate hwdb files: %m"); + + STRV_FOREACH(f, files) { + log_debug("reading file '%s'", *f); + import_file(trie, *f); + } + strv_free(files); + + strbuf_complete(trie->strings); + + log_debug("=== trie in-memory ==="); + log_debug("nodes: %8zu bytes (%8zu)", + trie->nodes_count * sizeof(struct trie_node), trie->nodes_count); + log_debug("children arrays: %8zu bytes (%8zu)", + trie->children_count * sizeof(struct trie_child_entry), trie->children_count); + log_debug("values arrays: %8zu bytes (%8zu)", + trie->values_count * sizeof(struct trie_value_entry), trie->values_count); + log_debug("strings: %8zu bytes", + trie->strings->len); + log_debug("strings incoming: %8zu bytes (%8zu)", + trie->strings->in_len, trie->strings->in_count); + log_debug("strings dedup'ed: %8zu bytes (%8zu)", + trie->strings->dedup_len, trie->strings->dedup_count); + + hwdb_bin = strjoin(arg_root, "/", arg_hwdb_bin_dir, "/hwdb.bin", NULL); + if (!hwdb_bin) + return -ENOMEM; + + mkdir_parents(hwdb_bin, 0755); + r = trie_store(trie, hwdb_bin); + if (r < 0) + return log_error_errno(r, "Failure writing database %s: %m", hwdb_bin); + + return 0; +} + +static void help(void) { + printf("Usage: %s OPTIONS COMMAND\n\n" + "Update or query the hardware database.\n\n" + " -h --help Show this help\n" + " --version Show package version\n" + " --usr Generate in " UDEVLIBEXECDIR " instead of /etc/udev\n" + " -r --root=PATH Alternative root path in the filesystem\n\n" + "Commands:\n" + " update Update the hwdb database\n" + " query MODALIAS Query database and print result\n", + program_invocation_short_name); +} + +static int parse_argv(int argc, char *argv[]) { + enum { + ARG_VERSION = 0x100, + ARG_USR, + }; + + static const struct option options[] = { + { "help", no_argument, NULL, 'h' }, + { "version", no_argument, NULL, ARG_VERSION }, + { "usr", no_argument, NULL, ARG_USR }, + { "root", required_argument, NULL, 'r' }, + {} + }; + + int c; + + assert(argc >= 0); + assert(argv); + + while ((c = getopt_long(argc, argv, "ut:r:h", options, NULL)) >= 0) { + switch(c) { + + case 'h': + help(); + return 0; + + case ARG_VERSION: + return version(); + + case ARG_USR: + arg_hwdb_bin_dir = UDEVLIBEXECDIR; + break; + + case 'r': + arg_root = optarg; + break; + + case '?': + return -EINVAL; + + default: + assert_not_reached("Unknown option"); + } + } + + return 1; +} + +static int hwdb_main(int argc, char *argv[]) { + const Verb verbs[] = { + { "update", 1, 1, 0, hwdb_update }, + { "query", 2, 2, 0, hwdb_query }, + {}, + }; + + return dispatch_verb(argc, argv, verbs, NULL); +} + +int main (int argc, char *argv[]) { + int r; + + log_parse_environment(); + log_open(); + + r = parse_argv(argc, argv); + if (r <= 0) + goto finish; + + r = hwdb_main(argc, argv); + +finish: + return r < 0 ? EXIT_FAILURE : EXIT_SUCCESS; +} diff --git a/src/grp-utils/Makefile b/src/grp-utils/Makefile new file mode 100644 index 0000000000..eda1a32f4b --- /dev/null +++ b/src/grp-utils/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 + +at.subdirs += systemd-ac-power +at.subdirs += systemd-escape +at.subdirs += systemd-notify +at.subdirs += systemd-path +at.subdirs += systemd-socket-activate + +include $(topsrcdir)/build-aux/Makefile.tail.mk diff --git a/src/grp-utils/systemd-ac-power/Makefile b/src/grp-utils/systemd-ac-power/Makefile new file mode 100644 index 0000000000..ec35048162 --- /dev/null +++ b/src/grp-utils/systemd-ac-power/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 + +rootlibexec_PROGRAMS += systemd-ac-power +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/grp-utils/systemd-ac-power/ac-power.c b/src/grp-utils/systemd-ac-power/ac-power.c new file mode 100644 index 0000000000..945c318f66 --- /dev/null +++ b/src/grp-utils/systemd-ac-power/ac-power.c @@ -0,0 +1,35 @@ +/*** + This file is part of systemd. + + Copyright 2010 Lennart Poettering + + systemd is free software; you can redistribute it and/or modify it + under the terms of the GNU Lesser General Public License as published by + the Free Software Foundation; either version 2.1 of the License, or + (at your option) any later version. + + systemd is distributed in the hope that it will be useful, but + WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + Lesser General Public License for more details. + + You should have received a copy of the GNU Lesser General Public License + along with systemd; If not, see . +***/ + +#include "basic/util.h" + +int main(int argc, char *argv[]) { + int r; + + /* This is mostly intended to be used for scripts which want + * to detect whether AC power is plugged in or not. */ + + r = on_ac_power(); + if (r < 0) { + log_error_errno(r, "Failed to read AC status: %m"); + return EXIT_FAILURE; + } + + return r != 0 ? EXIT_SUCCESS : EXIT_FAILURE; +} diff --git a/src/grp-utils/systemd-escape/Makefile b/src/grp-utils/systemd-escape/Makefile new file mode 100644 index 0000000000..4eb2fe356a --- /dev/null +++ b/src/grp-utils/systemd-escape/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 + +rootbin_PROGRAMS += systemd-escape + +systemd_escape_SOURCES = \ + src/escape/escape.c + +systemd_escape_LDADD = \ + libshared.la + +include $(topsrcdir)/build-aux/Makefile.tail.mk diff --git a/src/grp-utils/systemd-escape/escape.c b/src/grp-utils/systemd-escape/escape.c new file mode 100644 index 0000000000..479ea0e87c --- /dev/null +++ b/src/grp-utils/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 "basic/alloc-util.h" +#include "basic/log.h" +#include "basic/string-util.h" +#include "basic/strv.h" +#include "basic/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/grp-utils/systemd-notify/Makefile b/src/grp-utils/systemd-notify/Makefile new file mode 100644 index 0000000000..60e1336e72 --- /dev/null +++ b/src/grp-utils/systemd-notify/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 + +rootbin_PROGRAMS += systemd-notify +systemd_notify_SOURCES = \ + src/notify/notify.c + +systemd_notify_LDADD = \ + libshared.la + +include $(topsrcdir)/build-aux/Makefile.tail.mk diff --git a/src/grp-utils/systemd-notify/notify.c b/src/grp-utils/systemd-notify/notify.c new file mode 100644 index 0000000000..4db49508b3 --- /dev/null +++ b/src/grp-utils/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 "basic/alloc-util.h" +#include "basic/env-util.h" +#include "basic/formats-util.h" +#include "basic/log.h" +#include "basic/parse-util.h" +#include "basic/string-util.h" +#include "basic/strv.h" +#include "basic/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/grp-utils/systemd-path/Makefile b/src/grp-utils/systemd-path/Makefile new file mode 100644 index 0000000000..f1a5ccd8bb --- /dev/null +++ b/src/grp-utils/systemd-path/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 + +bin_PROGRAMS += systemd-path + +systemd_path_SOURCES = \ + sd-path.c \ + src/path/path.c + +systemd_path_LDADD = \ + libbasic.la + +systemd.CPPFLAGS += -DLIBDIR=\"$(libdir)\" +systemd.CPPFLAGS += -DROOTLIBDIR=\"$(rootlibdir)\" + +include $(topsrcdir)/build-aux/Makefile.tail.mk diff --git a/src/grp-utils/systemd-path/_sd-common.h b/src/grp-utils/systemd-path/_sd-common.h new file mode 120000 index 0000000000..d2b5d6f4e4 --- /dev/null +++ b/src/grp-utils/systemd-path/_sd-common.h @@ -0,0 +1 @@ +../../libsystemd/include/systemd/_sd-common.h \ No newline at end of file diff --git a/src/grp-utils/systemd-path/path.c b/src/grp-utils/systemd-path/path.c new file mode 100644 index 0000000000..aa63884365 --- /dev/null +++ b/src/grp-utils/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 "sd-path.h" + +#include "basic/alloc-util.h" +#include "basic/log.h" +#include "basic/macro.h" +#include "basic/string-util.h" +#include "basic/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/grp-utils/systemd-path/sd-path.c b/src/grp-utils/systemd-path/sd-path.c new file mode 100644 index 0000000000..b904b56c01 --- /dev/null +++ b/src/grp-utils/systemd-path/sd-path.c @@ -0,0 +1,638 @@ +/*** + 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 "basic/alloc-util.h" +#include "basic/architecture.h" +#include "basic/fd-util.h" +#include "basic/fileio.h" +#include "basic/missing.h" +#include "basic/path-util.h" +#include "basic/string-util.h" +#include "basic/strv.h" +#include "basic/user-util.h" +#include "basic/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; + _cleanup_free_ const char *fn = NULL; + const char *c = NULL; + char line[LINE_MAX]; + size_t n; + int r; + + assert(field); + assert(buffer); + assert(ret); + + r = from_home_dir("XDG_CONFIG_HOME", ".config", &b, &c); + if (r < 0) + return r; + + fn = strappend(c, "/user-dirs.dirs"); + if (!fn) + return -ENOMEM; + + 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/grp-utils/systemd-path/sd-path.h b/src/grp-utils/systemd-path/sd-path.h new file mode 100644 index 0000000000..be6abdcd03 --- /dev/null +++ b/src/grp-utils/systemd-path/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/grp-utils/systemd-socket-activate/Makefile b/src/grp-utils/systemd-socket-activate/Makefile new file mode 100644 index 0000000000..7d69a80d36 --- /dev/null +++ b/src/grp-utils/systemd-socket-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 + + +bin_PROGRAMS += \ + systemd-socket-activate + +systemd_socket_activate_SOURCES = \ + src/activate/activate.c + +systemd_socket_activate_LDADD = \ + libshared.la + +include $(topsrcdir)/build-aux/Makefile.tail.mk diff --git a/src/grp-utils/systemd-socket-activate/activate.c b/src/grp-utils/systemd-socket-activate/activate.c new file mode 100644 index 0000000000..2ad205af17 --- /dev/null +++ b/src/grp-utils/systemd-socket-activate/activate.c @@ -0,0 +1,545 @@ +/*** + 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 "basic/alloc-util.h" +#include "basic/escape.h" +#include "basic/fd-util.h" +#include "basic/log.h" +#include "basic/macro.h" +#include "basic/signal-util.h" +#include "basic/socket-util.h" +#include "basic/string-util.h" +#include "basic/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 char **arg_fdnames = 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; + 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_fdnames) { + _cleanup_free_ char *names = NULL; + size_t len; + char *e; + int i; + + len = strv_length(arg_fdnames); + if (len == 1) + for (i = 1; i < n_fds; i++) { + r = strv_extend(&arg_fdnames, arg_fdnames[0]); + if (r < 0) + return log_error_errno(r, "Failed to extend strv: %m"); + } + else if (len != (unsigned) n_fds) + log_warning("The number of fd names is different than number of fds: %zu vs %d", + len, n_fds); + + names = strv_join(arg_fdnames, ":"); + if (!names) + return log_oom(); + + e = strappend("LISTEN_FDNAMES=", names); + if (!e) + return log_oom(); + + 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) { + PROTECT_ERRNO; + + for (;;) { + siginfo_t si; + int r; + + si.si_pid = 0; + r = waitid(P_ALL, 0, &si, WEXITED|WNOHANG); + if (r < 0) { + if (errno != ECHILD) + log_error_errno(errno, "Failed to reap children: %m"); + return; + } + if (si.si_pid == 0) + return; + + log_info("Child %d died with code %d", si.si_pid, si.si_status); + } +} + +static int install_chld_handler(void) { + static const struct sigaction act = { + .sa_flags = SA_NOCLDSTOP, + .sa_handler = 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" + " --fdname=NAME[:NAME...] Specify names for file descriptors\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:aE:d", 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: { + _cleanup_strv_free_ char **names; + char **s; + + names = strv_split(optarg, ":"); + if (!names) + return log_oom(); + + STRV_FOREACH(s, names) + if (!fdname_is_valid(*s)) { + _cleanup_free_ char *esc; + + esc = cescape(*s); + log_warning("File descriptor name \"%s\" is not valid.", esc); + } + + /* Empty optargs means one empty name */ + r = strv_extend_strv(&arg_fdnames, + strv_isempty(names) ? STRV_MAKE("") : names, + false); + if (r < 0) + return log_error_errno(r, "strv_extend_strv: %m"); + 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/kernel-install/50-depmod.install b/src/kernel-install/50-depmod.install deleted file mode 100644 index 68c24bed7a..0000000000 --- a/src/kernel-install/50-depmod.install +++ /dev/null @@ -1,8 +0,0 @@ -#!/bin/bash -# -*- mode: shell-script; indent-tabs-mode: nil; sh-basic-offset: 4; -*- -# ex: ts=8 sw=4 sts=4 et filetype=sh - -[[ $1 == "add" ]] || exit 0 -[[ $2 ]] || exit 1 - -exec depmod -a "$2" diff --git a/src/kernel-install/90-loaderentry.install b/src/kernel-install/90-loaderentry.install deleted file mode 100644 index 6e94e12f94..0000000000 --- a/src/kernel-install/90-loaderentry.install +++ /dev/null @@ -1,88 +0,0 @@ -#!/bin/bash -# -*- mode: shell-script; indent-tabs-mode: nil; sh-basic-offset: 4; -*- -# ex: ts=8 sw=4 sts=4 et filetype=sh - -COMMAND="$1" -KERNEL_VERSION="$2" -BOOT_DIR_ABS="$3" -KERNEL_IMAGE="$4" - -if [[ -f /etc/machine-id ]]; then - read MACHINE_ID < /etc/machine-id -fi - -if ! [[ $MACHINE_ID ]]; then - exit 1 -fi - -BOOT_DIR="/$MACHINE_ID/$KERNEL_VERSION" -LOADER_ENTRY="/boot/loader/entries/$MACHINE_ID-$KERNEL_VERSION.conf" - -if [[ $COMMAND == remove ]]; then - exec rm -f "$LOADER_ENTRY" -fi - -if ! [[ $COMMAND == add ]]; then - exit 1 -fi - -if ! [[ $KERNEL_IMAGE ]]; then - exit 1 -fi - -if [[ -f /etc/os-release ]]; then - . /etc/os-release -elif [[ -f /usr/lib/os-release ]]; then - . /usr/lib/os-release -fi - -if ! [[ $PRETTY_NAME ]]; then - PRETTY_NAME="GNU/Linux $KERNEL_VERSION" -fi - -declare -a BOOT_OPTIONS - -if [[ -f /etc/kernel/cmdline ]]; then - read -r -d '' -a BOOT_OPTIONS < /etc/kernel/cmdline -fi - -if ! [[ ${BOOT_OPTIONS[*]} ]]; then - read -r -d '' -a line < /proc/cmdline - for i in "${line[@]}"; do - [[ "${i#initrd=*}" != "$i" ]] && continue - BOOT_OPTIONS+=("$i") - done -fi - -if ! [[ ${BOOT_OPTIONS[*]} ]]; then - echo "Could not determine the kernel command line parameters." >&2 - echo "Please specify the kernel command line in /etc/kernel/cmdline!" >&2 - exit 1 -fi - -cp "$KERNEL_IMAGE" "$BOOT_DIR_ABS/linux" && - chown root:root "$BOOT_DIR_ABS/linux" && - chmod 0644 "$BOOT_DIR_ABS/linux" || { - echo "Could not copy '$KERNEL_IMAGE to '$BOOT_DIR_ABS/linux'." >&2 - exit 1 -} - -mkdir -p "${LOADER_ENTRY%/*}" || { - echo "Could not create loader entry directory '${LOADER_ENTRY%/*}'." >&2 - exit 1 -} - -{ - echo "title $PRETTY_NAME" - echo "version $KERNEL_VERSION" - echo "machine-id $MACHINE_ID" - echo "options ${BOOT_OPTIONS[*]}" - echo "linux $BOOT_DIR/linux" - [[ -f $BOOT_DIR_ABS/initrd ]] && \ - echo "initrd $BOOT_DIR/initrd" - : -} > "$LOADER_ENTRY" || { - echo "Could not create loader entry '$LOADER_ENTRY'." >&2 - exit 1 -} -exit 0 diff --git a/src/kernel-install/Makefile b/src/kernel-install/Makefile deleted file mode 100644 index d50b684d01..0000000000 --- a/src/kernel-install/Makefile +++ /dev/null @@ -1,33 +0,0 @@ -# -*- 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 deleted file mode 100644 index 7cd2494cf7..0000000000 --- a/src/kernel-install/bash-completion_kernel-install +++ /dev/null @@ -1,50 +0,0 @@ -# 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 b/src/kernel-install/kernel-install deleted file mode 100644 index 3ae1d77e33..0000000000 --- a/src/kernel-install/kernel-install +++ /dev/null @@ -1,136 +0,0 @@ -#!/bin/bash -# -*- mode: shell-script; indent-tabs-mode: nil; sh-basic-offset: 4; -*- -# ex: ts=8 sw=4 sts=4 et filetype=sh -# -# This file is part of systemd. -# -# 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 . - -usage() -{ - echo "Usage:" - echo " $0 add KERNEL-VERSION KERNEL-IMAGE" - echo " $0 remove KERNEL-VERSION" -} - -dropindirs_sort() -{ - local suffix=$1; shift - local -a files - local f d i - - readarray -t files < <( - for d in "$@"; do - for i in "$d/"*"$suffix"; do - if [[ -e "$i" ]]; then - echo "${i##*/}" - fi - done - done | sort -Vu - ) - - for f in "${files[@]}"; do - for d in "$@"; do - if [[ -e "$d/$f" ]]; then - echo "$d/$f" - continue 2 - fi - done - done -} - -export LC_COLLATE=C - -for i in "$@"; do - if [ "$i" == "--help" -o "$i" == "-h" ]; then - usage - exit 0 - fi -done - -if [[ "${0##*/}" == 'installkernel' ]]; then - COMMAND='add' -else - COMMAND="$1" - shift -fi - -KERNEL_VERSION="$1" -KERNEL_IMAGE="$2" - -if [[ -f /etc/machine-id ]]; then - read MACHINE_ID < /etc/machine-id -fi - -if ! [[ $MACHINE_ID ]]; then - echo "Could not determine your machine ID from /etc/machine-id." >&2 - echo "Please run 'systemd-machine-id-setup' as root. See man:machine-id(5)" >&2 - exit 1 -fi - -if [[ ! $COMMAND ]] || [[ ! $KERNEL_VERSION ]]; then - echo "Not enough arguments" >&2 - exit 1 -fi - -BOOT_DIR_ABS="/boot/$MACHINE_ID/$KERNEL_VERSION" -ret=0 - -readarray -t PLUGINS < <( - dropindirs_sort ".install" \ - "/etc/kernel/install.d" \ - "/usr/lib/kernel/install.d" -) - -case $COMMAND in - add) - if [[ ! "$KERNEL_IMAGE" ]]; then - echo "Command 'add' requires an argument" >&2 - exit 1 - fi - - mkdir -p "$BOOT_DIR_ABS" || { - echo "Could not create boot directory '$BOOT_DIR_ABS'." >&2 - exit 1 - } - - for f in "${PLUGINS[@]}"; do - if [[ -x $f ]]; then - "$f" add "$KERNEL_VERSION" "$BOOT_DIR_ABS" "$KERNEL_IMAGE" - ((ret+=$?)) - fi - done - ;; - - remove) - for f in "${PLUGINS[@]}"; do - if [[ -x $f ]]; then - "$f" remove "$KERNEL_VERSION" "$BOOT_DIR_ABS" - ((ret+=$?)) - fi - done - - rm -rf "$BOOT_DIR_ABS" - ((ret+=$?)) - ;; - - *) - echo "Unknown command '$COMMAND'" >&2 - exit 1 - ;; -esac - -exit $ret diff --git a/src/kernel-install/kernel-install.xml b/src/kernel-install/kernel-install.xml deleted file mode 100644 index eb519188a6..0000000000 --- a/src/kernel-install/kernel-install.xml +++ /dev/null @@ -1,192 +0,0 @@ - - - - - - - - - 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 deleted file mode 100644 index 4fdd3a4ae7..0000000000 --- a/src/kernel-install/zsh-completion_kernel-install +++ /dev/null @@ -1,26 +0,0 @@ -#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/libcore/.gitignore b/src/libcore/.gitignore deleted file mode 100644 index 465b4fcc20..0000000000 --- a/src/libcore/.gitignore +++ /dev/null @@ -1,3 +0,0 @@ -/macros.systemd -/triggers.systemd -/systemd.pc diff --git a/src/libcore/Makefile b/src/libcore/Makefile deleted file mode 100644 index 23032ea6f7..0000000000 --- a/src/libcore/Makefile +++ /dev/null @@ -1,175 +0,0 @@ -# -*- 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-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: $(outdir)/load-fragment-gperf.gperf - $(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 ";" }' < $< > $@ - -systemd.CPPFLAGS += -I$(topsrcdir)/src/libudev/src -systemd.CPPFLAGS += -DMOUNT_PATH=\"$(MOUNT_PATH)\" -systemd.CPPFLAGS += -DUMOUNT_PATH=\"$(UMOUNT_PATH)\" -systemd.CPPFLAGS += -DSYSTEMD_CGROUP_AGENT_PATH=\"$(libexecdir)/systemd-cgroups-agent\" -systemd.CPPFLAGS += -DSYSTEMD_BINARY_PATH=\"$(libexecdir)/systemd\" - -include $(topsrcdir)/build-aux/Makefile.tail.mk diff --git a/src/libcore/audit-fd.c b/src/libcore/audit-fd.c deleted file mode 100644 index 8ea525eda3..0000000000 --- a/src/libcore/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 "basic/fd-util.h" -#include "basic/log.h" -#include "basic/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 deleted file mode 100644 index 0eccb59210..0000000000 --- a/src/libcore/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/libcore/automount.c b/src/libcore/automount.c deleted file mode 100644 index 145ed5ab5d..0000000000 --- a/src/libcore/automount.c +++ /dev/null @@ -1,1108 +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 "basic/alloc-util.h" -#include "basic/async.h" -#include "automount.h" -#include "sd-bus/bus-error.h" -#include "shared/bus-util.h" -#include "dbus-automount.h" -#include "basic/fd-util.h" -#include "basic/formats-util.h" -#include "basic/io-util.h" -#include "basic/label.h" -#include "basic/mkdir.h" -#include "basic/mount-util.h" -#include "mount.h" -#include "basic/parse-util.h" -#include "basic/path-util.h" -#include "basic/process-util.h" -#include "basic/special.h" -#include "basic/stdio-util.h" -#include "basic/string-table.h" -#include "basic/string-util.h" -#include "basic/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 int automount_start_expire(Automount *a); -static void automount_stop_expire(Automount *a); -static int automount_send_ready(Automount *a, Set *tokens, int status); - -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 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 (!MANAGER_IS_SYSTEM(UNIT(a)->manager)) - 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_RUNNING) - automount_stop_expire(a); - - 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 != 0) { - 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 != 0) - 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 void automount_trigger_notify(Unit *u, Unit *other) { - Automount *a = AUTOMOUNT(u); - int r; - - assert(a); - assert(other); - - /* Filter out invocations with bogus state */ - if (other->load_state != UNIT_LOADED || other->type != UNIT_MOUNT) - return; - - /* Don't propagate state changes from the mount if we are already down */ - if (!IN_SET(a->state, AUTOMOUNT_WAITING, AUTOMOUNT_RUNNING)) - return; - - /* Propagate start limit hit state */ - if (other->start_limit_hit) { - automount_enter_dead(a, AUTOMOUNT_FAILURE_MOUNT_START_LIMIT_HIT); - return; - } - - /* Don't propagate anything if there's still a job queued */ - if (other->job) - return; - - /* The mount is successfully established */ - if (IN_SET(MOUNT(other)->state, MOUNT_MOUNTED, MOUNT_REMOUNTING)) { - (void) 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"); - - automount_set_state(a, AUTOMOUNT_RUNNING); - } - - /* The mount is in some unhappy state now, let's unfreeze any waiting clients */ - if (IN_SET(MOUNT(other)->state, - MOUNT_DEAD, MOUNT_UNMOUNTING, - MOUNT_MOUNTING_SIGTERM, MOUNT_MOUNTING_SIGKILL, - MOUNT_REMOUNTING_SIGTERM, MOUNT_REMOUNTING_SIGKILL, - MOUNT_UNMOUNTING_SIGTERM, MOUNT_UNMOUNTING_SIGKILL, - MOUNT_FAILED)) { - - (void) automount_send_ready(a, a->tokens, -ENODEV); - - automount_set_state(a, AUTOMOUNT_WAITING); - } -} - -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_stop_expire(Automount *a) { - assert(a); - - if (!a->expire_event_source) - return; - - (void) sd_event_source_set_enabled(a->expire_event_source, SD_EVENT_OFF); -} - -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; - int r; - - 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; - } - - r = unit_start_limit_test(u); - if (r < 0) { - automount_enter_dead(a, AUTOMOUNT_FAILURE_START_LIMIT_HIT); - return r; - } - - 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); - - automount_stop_expire(a); - - 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", - [AUTOMOUNT_FAILURE_START_LIMIT_HIT] = "start-limit-hit", - [AUTOMOUNT_FAILURE_MOUNT_START_LIMIT_HIT] = "mount-start-limit-hit", -}; - -DEFINE_STRING_TABLE_LOOKUP(automount_result, AutomountResult); - -const UnitVTable automount_vtable = { - .object_size = sizeof(Automount), - - .sections = - "Unit\0" - "Automount\0" - "Install\0", - - .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, - - .trigger_notify = automount_trigger_notify, - - .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 deleted file mode 100644 index 76a201178e..0000000000 --- a/src/libcore/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_FAILURE_START_LIMIT_HIT, - AUTOMOUNT_FAILURE_MOUNT_START_LIMIT_HIT, - _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; - -const char* automount_result_to_string(AutomountResult i) _const_; -AutomountResult automount_result_from_string(const char *s) _pure_; diff --git a/src/libcore/bus-policy.c b/src/libcore/bus-policy.c deleted file mode 100644 index cc228bc42f..0000000000 --- a/src/libcore/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 "basic/alloc-util.h" -#include "sd-bus/bus-kernel.h" -#include "bus-policy.h" -#include "sd-bus/kdbus.h" -#include "basic/string-table.h" -#include "basic/user-util.h" -#include "basic/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 deleted file mode 100644 index a6d19c1e9a..0000000000 --- a/src/libcore/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 "sd-bus/kdbus.h" -#include "basic/list.h" -#include "basic/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 deleted file mode 100644 index 765fd7a442..0000000000 --- a/src/libcore/busname.c +++ /dev/null @@ -1,1082 +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 "basic/alloc-util.h" -#include "sd-bus/bus-internal.h" -#include "sd-bus/bus-kernel.h" -#include "bus-policy.h" -#include "shared/bus-util.h" -#include "busname.h" -#include "dbus-busname.h" -#include "basic/fd-util.h" -#include "basic/formats-util.h" -#include "sd-bus/kdbus.h" -#include "basic/parse-util.h" -#include "basic/process-util.h" -#include "service.h" -#include "basic/signal-util.h" -#include "basic/special.h" -#include "basic/string-table.h" -#include "basic/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 (MANAGER_IS_SYSTEM(UNIT(n)->manager)) { - 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 = MANAGER_IS_SYSTEM(UNIT(n)->manager) ? "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); - int r; - - 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)); - - r = unit_start_limit_test(u); - if (r < 0) { - busname_enter_dead(n, BUSNAME_FAILURE_START_LIMIT_HIT); - return r; - } - - 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 int busname_control_pid(Unit *u) { - BusName *n = BUSNAME(u); - - assert(n); - - return n->control_pid; -} - -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_START_LIMIT_HIT] = "start-limit-hit", - [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", - - .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, - - .control_pid = busname_control_pid, - - .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 deleted file mode 100644 index a8562db458..0000000000 --- a/src/libcore/busname.h +++ /dev/null @@ -1,69 +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" -#include "bus-policy.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_START_LIMIT_HIT, - 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 deleted file mode 100644 index 43d494cee7..0000000000 --- a/src/libcore/cgroup.c +++ /dev/null @@ -1,1895 +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 "basic/alloc-util.h" -#include "basic/cgroup-util.h" -#include "cgroup.h" -#include "basic/fd-util.h" -#include "basic/fileio.h" -#include "basic/fs-util.h" -#include "basic/parse-util.h" -#include "basic/path-util.h" -#include "basic/process-util.h" -#include "basic/special.h" -#include "basic/string-table.h" -#include "basic/string-util.h" -#include "basic/stdio-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->io_weight = CGROUP_WEIGHT_INVALID; - c->startup_io_weight = CGROUP_WEIGHT_INVALID; - - 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_io_device_weight(CGroupContext *c, CGroupIODeviceWeight *w) { - assert(c); - assert(w); - - LIST_REMOVE(device_weights, c->io_device_weights, w); - free(w->path); - free(w); -} - -void cgroup_context_free_io_device_limit(CGroupContext *c, CGroupIODeviceLimit *l) { - assert(c); - assert(l); - - LIST_REMOVE(device_limits, c->io_device_limits, l); - free(l->path); - free(l); -} - -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->io_device_weights) - cgroup_context_free_io_device_weight(c, c->io_device_weights); - - while (c->io_device_limits) - cgroup_context_free_io_device_limit(c, c->io_device_limits); - - 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) { - CGroupIODeviceLimit *il; - CGroupIODeviceWeight *iw; - CGroupBlockIODeviceBandwidth *b; - CGroupBlockIODeviceWeight *w; - CGroupDeviceAllow *a; - char u[FORMAT_TIMESPAN_MAX]; - - assert(c); - assert(f); - - prefix = strempty(prefix); - - fprintf(f, - "%sCPUAccounting=%s\n" - "%sIOAccounting=%s\n" - "%sBlockIOAccounting=%s\n" - "%sMemoryAccounting=%s\n" - "%sTasksAccounting=%s\n" - "%sCPUShares=%" PRIu64 "\n" - "%sStartupCPUShares=%" PRIu64 "\n" - "%sCPUQuotaPerSecSec=%s\n" - "%sIOWeight=%" PRIu64 "\n" - "%sStartupIOWeight=%" PRIu64 "\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->io_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->io_weight, - prefix, c->startup_io_weight, - 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, iw, c->io_device_weights) - fprintf(f, - "%sIODeviceWeight=%s %" PRIu64, - prefix, - iw->path, - iw->weight); - - LIST_FOREACH(device_limits, il, c->io_device_limits) { - char buf[FORMAT_BYTES_MAX]; - CGroupIOLimitType type; - - for (type = 0; type < _CGROUP_IO_LIMIT_TYPE_MAX; type++) - if (il->limits[type] != cgroup_io_limit_defaults[type]) - fprintf(f, - "%s%s=%s %s\n", - prefix, - cgroup_io_limit_type_to_string(type), - il->path, - format_bytes(buf, sizeof(buf), il->limits[type])); - } - - 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]; - - if (b->rbps != CGROUP_LIMIT_MAX) - fprintf(f, - "%sBlockIOReadBandwidth=%s %s\n", - prefix, - b->path, - format_bytes(buf, sizeof(buf), b->rbps)); - if (b->wbps != CGROUP_LIMIT_MAX) - fprintf(f, - "%sBlockIOWriteBandwidth=%s %s\n", - prefix, - b->path, - format_bytes(buf, sizeof(buf), b->wbps)); - } -} - -static int lookup_block_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; -} - -static bool cgroup_context_has_io_config(CGroupContext *c) { - return c->io_accounting || - c->io_weight != CGROUP_WEIGHT_INVALID || - c->startup_io_weight != CGROUP_WEIGHT_INVALID || - c->io_device_weights || - c->io_device_limits; -} - -static bool cgroup_context_has_blockio_config(CGroupContext *c) { - return 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; -} - -static uint64_t cgroup_context_io_weight(CGroupContext *c, ManagerState state) { - if (IN_SET(state, MANAGER_STARTING, MANAGER_INITIALIZING) && - c->startup_io_weight != CGROUP_WEIGHT_INVALID) - return c->startup_io_weight; - else if (c->io_weight != CGROUP_WEIGHT_INVALID) - return c->io_weight; - else - return CGROUP_WEIGHT_DEFAULT; -} - -static uint64_t cgroup_context_blkio_weight(CGroupContext *c, ManagerState state) { - if (IN_SET(state, MANAGER_STARTING, MANAGER_INITIALIZING) && - c->startup_blockio_weight != CGROUP_BLKIO_WEIGHT_INVALID) - return c->startup_blockio_weight; - else if (c->blockio_weight != CGROUP_BLKIO_WEIGHT_INVALID) - return c->blockio_weight; - else - return CGROUP_BLKIO_WEIGHT_DEFAULT; -} - -static uint64_t cgroup_weight_blkio_to_io(uint64_t blkio_weight) { - return CLAMP(blkio_weight * CGROUP_WEIGHT_DEFAULT / CGROUP_BLKIO_WEIGHT_DEFAULT, - CGROUP_WEIGHT_MIN, CGROUP_WEIGHT_MAX); -} - -static uint64_t cgroup_weight_io_to_blkio(uint64_t io_weight) { - return CLAMP(io_weight * CGROUP_BLKIO_WEIGHT_DEFAULT / CGROUP_WEIGHT_DEFAULT, - CGROUP_BLKIO_WEIGHT_MIN, CGROUP_BLKIO_WEIGHT_MAX); -} - -static void cgroup_apply_io_device_weight(const char *path, const char *dev_path, uint64_t io_weight) { - char buf[DECIMAL_STR_MAX(dev_t)*2+2+DECIMAL_STR_MAX(uint64_t)+1]; - dev_t dev; - int r; - - r = lookup_block_device(dev_path, &dev); - if (r < 0) - return; - - xsprintf(buf, "%u:%u %" PRIu64 "\n", major(dev), minor(dev), io_weight); - r = cg_set_attribute("io", path, "io.weight", buf); - if (r < 0) - log_full_errno(IN_SET(r, -ENOENT, -EROFS, -EACCES) ? LOG_DEBUG : LOG_WARNING, r, - "Failed to set io.weight on %s: %m", path); -} - -static void cgroup_apply_blkio_device_weight(const char *path, const char *dev_path, uint64_t blkio_weight) { - char buf[DECIMAL_STR_MAX(dev_t)*2+2+DECIMAL_STR_MAX(uint64_t)+1]; - dev_t dev; - int r; - - r = lookup_block_device(dev_path, &dev); - if (r < 0) - return; - - xsprintf(buf, "%u:%u %" PRIu64 "\n", major(dev), minor(dev), blkio_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); -} - -static unsigned cgroup_apply_io_device_limit(const char *path, const char *dev_path, uint64_t *limits) { - char limit_bufs[_CGROUP_IO_LIMIT_TYPE_MAX][DECIMAL_STR_MAX(uint64_t)]; - char buf[DECIMAL_STR_MAX(dev_t)*2+2+(6+DECIMAL_STR_MAX(uint64_t)+1)*4]; - CGroupIOLimitType type; - dev_t dev; - unsigned n = 0; - int r; - - r = lookup_block_device(dev_path, &dev); - if (r < 0) - return 0; - - for (type = 0; type < _CGROUP_IO_LIMIT_TYPE_MAX; type++) { - if (limits[type] != cgroup_io_limit_defaults[type]) { - xsprintf(limit_bufs[type], "%" PRIu64, limits[type]); - n++; - } else { - xsprintf(limit_bufs[type], "%s", limits[type] == CGROUP_LIMIT_MAX ? "max" : "0"); - } - } - - xsprintf(buf, "%u:%u rbps=%s wbps=%s riops=%s wiops=%s\n", major(dev), minor(dev), - limit_bufs[CGROUP_IO_RBPS_MAX], limit_bufs[CGROUP_IO_WBPS_MAX], - limit_bufs[CGROUP_IO_RIOPS_MAX], limit_bufs[CGROUP_IO_WIOPS_MAX]); - r = cg_set_attribute("io", path, "io.max", buf); - if (r < 0) - log_full_errno(IN_SET(r, -ENOENT, -EROFS, -EACCES) ? LOG_DEBUG : LOG_WARNING, r, - "Failed to set io.max on %s: %m", path); - return n; -} - -static unsigned cgroup_apply_blkio_device_limit(const char *path, const char *dev_path, uint64_t rbps, uint64_t wbps) { - char buf[DECIMAL_STR_MAX(dev_t)*2+2+DECIMAL_STR_MAX(uint64_t)+1]; - dev_t dev; - unsigned n = 0; - int r; - - r = lookup_block_device(dev_path, &dev); - if (r < 0) - return 0; - - if (rbps != CGROUP_LIMIT_MAX) - n++; - sprintf(buf, "%u:%u %" PRIu64 "\n", major(dev), minor(dev), rbps); - r = cg_set_attribute("blkio", path, "blkio.throttle.read_bps_device", buf); - if (r < 0) - log_full_errno(IN_SET(r, -ENOENT, -EROFS, -EACCES) ? LOG_DEBUG : LOG_WARNING, r, - "Failed to set blkio.throttle.read_bps_device on %s: %m", path); - - if (wbps != CGROUP_LIMIT_MAX) - n++; - sprintf(buf, "%u:%u %" PRIu64 "\n", major(dev), minor(dev), wbps); - r = cg_set_attribute("blkio", path, "blkio.throttle.write_bps_device", buf); - if (r < 0) - log_full_errno(IN_SET(r, -ENOENT, -EROFS, -EACCES) ? LOG_DEBUG : LOG_WARNING, r, - "Failed to set blkio.throttle.write_bps_device on %s: %m", path); - - return n; -} - -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_IO) { - bool has_io = cgroup_context_has_io_config(c); - bool has_blockio = cgroup_context_has_blockio_config(c); - - if (!is_root) { - char buf[8+DECIMAL_STR_MAX(uint64_t)+1]; - uint64_t weight; - - if (has_io) - weight = cgroup_context_io_weight(c, state); - else if (has_blockio) - weight = cgroup_weight_blkio_to_io(cgroup_context_blkio_weight(c, state)); - else - weight = CGROUP_WEIGHT_DEFAULT; - - xsprintf(buf, "default %" PRIu64 "\n", weight); - r = cg_set_attribute("io", path, "io.weight", buf); - if (r < 0) - log_full_errno(IN_SET(r, -ENOENT, -EROFS, -EACCES) ? LOG_DEBUG : LOG_WARNING, r, - "Failed to set io.weight on %s: %m", path); - - if (has_io) { - CGroupIODeviceWeight *w; - - /* FIXME: no way to reset this list */ - LIST_FOREACH(device_weights, w, c->io_device_weights) - cgroup_apply_io_device_weight(path, w->path, w->weight); - } else if (has_blockio) { - CGroupBlockIODeviceWeight *w; - - /* FIXME: no way to reset this list */ - LIST_FOREACH(device_weights, w, c->blockio_device_weights) - cgroup_apply_io_device_weight(path, w->path, cgroup_weight_blkio_to_io(w->weight)); - } - } - - /* Apply limits and free ones without config. */ - if (has_io) { - CGroupIODeviceLimit *l, *next; - - LIST_FOREACH_SAFE(device_limits, l, next, c->io_device_limits) { - if (!cgroup_apply_io_device_limit(path, l->path, l->limits)) - cgroup_context_free_io_device_limit(c, l); - } - } else if (has_blockio) { - CGroupBlockIODeviceBandwidth *b, *next; - - LIST_FOREACH_SAFE(device_bandwidths, b, next, c->blockio_device_bandwidths) { - uint64_t limits[_CGROUP_IO_LIMIT_TYPE_MAX]; - CGroupIOLimitType type; - - for (type = 0; type < _CGROUP_IO_LIMIT_TYPE_MAX; type++) - limits[type] = cgroup_io_limit_defaults[type]; - - limits[CGROUP_IO_RBPS_MAX] = b->rbps; - limits[CGROUP_IO_WBPS_MAX] = b->wbps; - - if (!cgroup_apply_io_device_limit(path, b->path, limits)) - cgroup_context_free_blockio_device_bandwidth(c, b); - } - } - } - - if (mask & CGROUP_MASK_BLKIO) { - bool has_io = cgroup_context_has_io_config(c); - bool has_blockio = cgroup_context_has_blockio_config(c); - - if (!is_root) { - char buf[DECIMAL_STR_MAX(uint64_t)+1]; - uint64_t weight; - - if (has_blockio) - weight = cgroup_context_blkio_weight(c, state); - else if (has_io) - weight = cgroup_weight_io_to_blkio(cgroup_context_io_weight(c, state)); - else - weight = CGROUP_BLKIO_WEIGHT_DEFAULT; - - xsprintf(buf, "%" PRIu64 "\n", weight); - 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); - - if (has_blockio) { - CGroupBlockIODeviceWeight *w; - - /* FIXME: no way to reset this list */ - LIST_FOREACH(device_weights, w, c->blockio_device_weights) - cgroup_apply_blkio_device_weight(path, w->path, w->weight); - } else if (has_io) { - CGroupIODeviceWeight *w; - - /* FIXME: no way to reset this list */ - LIST_FOREACH(device_weights, w, c->io_device_weights) - cgroup_apply_blkio_device_weight(path, w->path, cgroup_weight_io_to_blkio(w->weight)); - } - } - - /* Apply limits and free ones without config. */ - if (has_blockio) { - CGroupBlockIODeviceBandwidth *b, *next; - - LIST_FOREACH_SAFE(device_bandwidths, b, next, c->blockio_device_bandwidths) { - if (!cgroup_apply_blkio_device_limit(path, b->path, b->rbps, b->wbps)) - cgroup_context_free_blockio_device_bandwidth(c, b); - } - } else if (has_io) { - CGroupIODeviceLimit *l, *next; - - LIST_FOREACH_SAFE(device_limits, l, next, c->io_device_limits) { - if (!cgroup_apply_blkio_device_limit(path, l->path, l->limits[CGROUP_IO_RBPS_MAX], l->limits[CGROUP_IO_WBPS_MAX])) - cgroup_context_free_io_device_limit(c, l); - } - } - } - - 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 (cgroup_context_has_io_config(c) || cgroup_context_has_blockio_config(c)) - mask |= CGROUP_MASK_IO | 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 *events = 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.events", &events); - if (r < 0) - return log_oom(); - - u->cgroup_inotify_wd = inotify_add_watch(u->manager->cgroup_inotify_fd, events, 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; - u->cgroup_enabled_mask = enable_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, CGroupMask enable_mask) { - assert(u); - - return u->cgroup_realized && u->cgroup_realized_mask == target_mask && u->cgroup_enabled_mask == enable_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); - enable_mask = unit_get_enable_mask(u); - - if (unit_has_mask_realized(u, target_mask, enable_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 */ - 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), unit_get_enable_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; - u->cgroup_enabled_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 && MANAGER_IS_SYSTEM(m)) { - 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"); - - /* Process cgroup empty notifications early, but after service notifications and SIGCHLD. Also - * see handling of cgroup agent notifications, for the classic cgroup hierarchy support. */ - r = sd_event_source_set_priority(m->cgroup_inotify_event_source, SD_EVENT_PRIORITY_NORMAL-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 (MANAGER_IS_SYSTEM(m)) { - - /* 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); - - log_debug("Got cgroup empty notification for: %s", 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; - - /* always invalidate compat pairs together */ - if (m & (CGROUP_MASK_IO | CGROUP_MASK_BLKIO)) - m |= CGROUP_MASK_IO | CGROUP_MASK_BLKIO; - - 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_IO|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 deleted file mode 100644 index 69ff9832bf..0000000000 --- a/src/libcore/cgroup.h +++ /dev/null @@ -1,181 +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 "basic/list.h" -#include "basic/time-util.h" -#include "basic/cgroup-util.h" - -typedef struct CGroupContext CGroupContext; -typedef struct CGroupDeviceAllow CGroupDeviceAllow; -typedef struct CGroupIODeviceWeight CGroupIODeviceWeight; -typedef struct CGroupIODeviceLimit CGroupIODeviceLimit; -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 CGroupIODeviceWeight { - LIST_FIELDS(CGroupIODeviceWeight, device_weights); - char *path; - uint64_t weight; -}; - -struct CGroupIODeviceLimit { - LIST_FIELDS(CGroupIODeviceLimit, device_limits); - char *path; - uint64_t limits[_CGROUP_IO_LIMIT_TYPE_MAX]; -}; - -struct CGroupBlockIODeviceWeight { - LIST_FIELDS(CGroupBlockIODeviceWeight, device_weights); - char *path; - uint64_t weight; -}; - -struct CGroupBlockIODeviceBandwidth { - LIST_FIELDS(CGroupBlockIODeviceBandwidth, device_bandwidths); - char *path; - uint64_t rbps; - uint64_t wbps; -}; - -struct CGroupContext { - bool cpu_accounting; - bool io_accounting; - bool blockio_accounting; - bool memory_accounting; - bool tasks_accounting; - - /* For unified hierarchy */ - uint64_t io_weight; - uint64_t startup_io_weight; - LIST_HEAD(CGroupIODeviceWeight, io_device_weights); - LIST_HEAD(CGroupIODeviceLimit, io_device_limits); - - /* For legacy hierarchies */ - 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); - - /* Common */ - uint64_t tasks_max; - - bool delegate; -}; - -#include "basic/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_io_device_weight(CGroupContext *c, CGroupIODeviceWeight *w); -void cgroup_context_free_io_device_limit(CGroupContext *c, CGroupIODeviceLimit *l); -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 deleted file mode 100644 index bc44dc1c0a..0000000000 --- a/src/libcore/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 "shared/bus-util.h" -#include "dbus-automount.h" -#include "basic/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 deleted file mode 100644 index 7b51eb973a..0000000000 --- a/src/libcore/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/libcore/dbus-busname.c b/src/libcore/dbus-busname.c deleted file mode 100644 index 80e7484f46..0000000000 --- a/src/libcore/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 "shared/bus-util.h" -#include "busname.h" -#include "dbus-busname.h" -#include "basic/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 deleted file mode 100644 index 8643d1a404..0000000000 --- a/src/libcore/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/libcore/dbus-cgroup.c b/src/libcore/dbus-cgroup.c deleted file mode 100644 index 86f7c626c4..0000000000 --- a/src/libcore/dbus-cgroup.c +++ /dev/null @@ -1,1004 +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 "basic/alloc-util.h" -#include "shared/bus-util.h" -#include "basic/cgroup-util.h" -#include "cgroup.h" -#include "dbus-cgroup.h" -#include "basic/fd-util.h" -#include "basic/fileio.h" -#include "basic/path-util.h" - -static BUS_DEFINE_PROPERTY_GET_ENUM(property_get_cgroup_device_policy, cgroup_device_policy, CGroupDevicePolicy); - -static int property_get_io_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; - CGroupIODeviceWeight *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->io_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_io_device_limits( - 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; - CGroupIODeviceLimit *l; - 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_limits, l, c->io_device_limits) { - CGroupIOLimitType type; - - type = cgroup_io_limit_type_from_string(property); - if (type < 0 || l->limits[type] == cgroup_io_limit_defaults[type]) - continue; - - r = sd_bus_message_append(reply, "(st)", l->path, l->limits[type]); - if (r < 0) - return r; - } - - return sd_bus_message_close_container(reply); -} - -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) { - uint64_t v; - - if (streq(property, "BlockIOReadBandwidth")) - v = b->rbps; - else - v = b->wbps; - - if (v == CGROUP_LIMIT_MAX) - continue; - - r = sd_bus_message_append(reply, "(st)", b->path, v); - 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("IOAccounting", "b", bus_property_get_bool, offsetof(CGroupContext, io_accounting), 0), - SD_BUS_PROPERTY("IOWeight", "t", NULL, offsetof(CGroupContext, io_weight), 0), - SD_BUS_PROPERTY("StartupIOWeight", "t", NULL, offsetof(CGroupContext, startup_io_weight), 0), - SD_BUS_PROPERTY("IODeviceWeight", "a(st)", property_get_io_device_weight, 0, 0), - SD_BUS_PROPERTY("IOReadBandwidthMax", "a(st)", property_get_io_device_limits, 0, 0), - SD_BUS_PROPERTY("IOWriteBandwidthMax", "a(st)", property_get_io_device_limits, 0, 0), - SD_BUS_PROPERTY("IOReadIOPSMax", "a(st)", property_get_io_device_limits, 0, 0), - SD_BUS_PROPERTY("IOWriteIOPSMax", "a(st)", property_get_io_device_limits, 0, 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) { - - CGroupIOLimitType iol_type; - 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, "IOAccounting")) { - int b; - - r = sd_bus_message_read(message, "b", &b); - if (r < 0) - return r; - - if (mode != UNIT_CHECK) { - c->io_accounting = b; - unit_invalidate_cgroup(u, CGROUP_MASK_IO); - unit_write_drop_in_private(u, mode, name, b ? "IOAccounting=yes" : "IOAccounting=no"); - } - - return 1; - - } else if (streq(name, "IOWeight")) { - uint64_t weight; - - r = sd_bus_message_read(message, "t", &weight); - if (r < 0) - return r; - - if (!CGROUP_WEIGHT_IS_OK(weight)) - return sd_bus_error_set_errnof(error, EINVAL, "IOWeight value out of range"); - - if (mode != UNIT_CHECK) { - c->io_weight = weight; - unit_invalidate_cgroup(u, CGROUP_MASK_IO); - - if (weight == CGROUP_WEIGHT_INVALID) - unit_write_drop_in_private(u, mode, name, "IOWeight="); - else - unit_write_drop_in_private_format(u, mode, name, "IOWeight=%" PRIu64, weight); - } - - return 1; - - } else if (streq(name, "StartupIOWeight")) { - uint64_t weight; - - r = sd_bus_message_read(message, "t", &weight); - if (r < 0) - return r; - - if (CGROUP_WEIGHT_IS_OK(weight)) - return sd_bus_error_set_errnof(error, EINVAL, "StartupIOWeight value out of range"); - - if (mode != UNIT_CHECK) { - c->startup_io_weight = weight; - unit_invalidate_cgroup(u, CGROUP_MASK_IO); - - if (weight == CGROUP_WEIGHT_INVALID) - unit_write_drop_in_private(u, mode, name, "StartupIOWeight="); - else - unit_write_drop_in_private_format(u, mode, name, "StartupIOWeight=%" PRIu64, weight); - } - - return 1; - - } else if ((iol_type = cgroup_io_limit_type_from_string(name)) >= 0) { - const char *path; - unsigned n = 0; - uint64_t u64; - - 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) { - CGroupIODeviceLimit *a = NULL, *b; - - LIST_FOREACH(device_limits, b, c->io_device_limits) { - if (path_equal(path, b->path)) { - a = b; - break; - } - } - - if (!a) { - CGroupIOLimitType type; - - a = new0(CGroupIODeviceLimit, 1); - if (!a) - return -ENOMEM; - - a->path = strdup(path); - if (!a->path) { - free(a); - return -ENOMEM; - } - - for (type = 0; type < _CGROUP_IO_LIMIT_TYPE_MAX; type++) - a->limits[type] = cgroup_io_limit_defaults[type]; - - LIST_PREPEND(device_limits, c->io_device_limits, a); - } - - a->limits[iol_type] = u64; - } - - n++; - } - if (r < 0) - return r; - - r = sd_bus_message_exit_container(message); - if (r < 0) - return r; - - if (mode != UNIT_CHECK) { - CGroupIODeviceLimit *a; - _cleanup_free_ char *buf = NULL; - _cleanup_fclose_ FILE *f = NULL; - size_t size = 0; - - if (n == 0) { - LIST_FOREACH(device_limits, a, c->io_device_limits) - a->limits[iol_type] = cgroup_io_limit_defaults[iol_type]; - } - - unit_invalidate_cgroup(u, CGROUP_MASK_IO); - - f = open_memstream(&buf, &size); - if (!f) - return -ENOMEM; - - fprintf(f, "%s=\n", name); - LIST_FOREACH(device_limits, a, c->io_device_limits) - if (a->limits[iol_type] != cgroup_io_limit_defaults[iol_type]) - fprintf(f, "%s=%s %" PRIu64 "\n", name, a->path, a->limits[iol_type]); - - 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, "IODeviceWeight")) { - 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_WEIGHT_IS_OK(weight) || weight == CGROUP_WEIGHT_INVALID) - return sd_bus_error_set_errnof(error, EINVAL, "IODeviceWeight out of range"); - - if (mode != UNIT_CHECK) { - CGroupIODeviceWeight *a = NULL, *b; - - LIST_FOREACH(device_weights, b, c->io_device_weights) { - if (path_equal(b->path, path)) { - a = b; - break; - } - } - - if (!a) { - a = new0(CGroupIODeviceWeight, 1); - if (!a) - return -ENOMEM; - - a->path = strdup(path); - if (!a->path) { - free(a); - return -ENOMEM; - } - LIST_PREPEND(device_weights,c->io_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; - CGroupIODeviceWeight *a; - size_t size = 0; - - if (n == 0) { - while (c->io_device_weights) - cgroup_context_free_io_device_weight(c, c->io_device_weights); - } - - unit_invalidate_cgroup(u, CGROUP_MASK_IO); - - f = open_memstream(&buf, &size); - if (!f) - return -ENOMEM; - - fputs("IODeviceWeight=\n", f); - LIST_FOREACH(device_weights, a, c->io_device_weights) - fprintf(f, "IODeviceWeight=%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, "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)) { - a = b; - break; - } - } - - if (!a) { - a = new0(CGroupBlockIODeviceBandwidth, 1); - if (!a) - return -ENOMEM; - - a->rbps = CGROUP_LIMIT_MAX; - a->wbps = CGROUP_LIMIT_MAX; - a->path = strdup(path); - if (!a->path) { - free(a); - return -ENOMEM; - } - - LIST_PREPEND(device_bandwidths, c->blockio_device_bandwidths, a); - } - - if (read) - a->rbps = u64; - else - a->wbps = 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; - _cleanup_free_ char *buf = NULL; - _cleanup_fclose_ FILE *f = NULL; - size_t size = 0; - - if (n == 0) { - LIST_FOREACH(device_bandwidths, a, c->blockio_device_bandwidths) { - if (read) - a->rbps = CGROUP_LIMIT_MAX; - else - a->wbps = CGROUP_LIMIT_MAX; - } - } - - 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->rbps != CGROUP_LIMIT_MAX) - fprintf(f, "BlockIOReadBandwidth=%s %" PRIu64 "\n", a->path, a->rbps); - } else { - fputs("BlockIOWriteBandwidth=\n", f); - LIST_FOREACH(device_bandwidths, a, c->blockio_device_bandwidths) - if (a->wbps != CGROUP_LIMIT_MAX) - fprintf(f, "BlockIOWriteBandwidth=%s %" PRIu64 "\n", a->path, a->wbps); - } - - 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 deleted file mode 100644 index 84d0f1ba04..0000000000 --- a/src/libcore/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 - -#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 deleted file mode 100644 index e1a12224d3..0000000000 --- a/src/libcore/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/libcore/dbus-device.h b/src/libcore/dbus-device.h deleted file mode 100644 index eb1d8c3278..0000000000 --- a/src/libcore/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/libcore/dbus-execute.c b/src/libcore/dbus-execute.c deleted file mode 100644 index b5530d945e..0000000000 --- a/src/libcore/dbus-execute.c +++ /dev/null @@ -1,1545 +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 "basic/af-list.h" -#include "basic/alloc-util.h" -#include "shared/bus-util.h" -#include "basic/capability-util.h" -#include "dbus-execute.h" -#include "basic/env-util.h" -#include "execute.h" -#include "basic/fd-util.h" -#include "basic/fileio.h" -#include "basic/ioprio.h" -#include "basic/missing.h" -#include "namespace.h" -#include "basic/parse-util.h" -#include "basic/path-util.h" -#include "basic/process-util.h" -#include "basic/rlimit-util.h" -#ifdef HAVE_SECCOMP -#include "shared/seccomp-util.h" -#endif -#include "basic/strv.h" -#include "basic/syslog-util.h" -#include "basic/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_empty_string( - 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", ""); -} - -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_empty_string, 0, SD_BUS_VTABLE_PROPERTY_CONST|SD_BUS_VTABLE_HIDDEN), - 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 if (free_and_strdup(&c->user, uu) < 0) - return -ENOMEM; - - 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 if (free_and_strdup(&c->group, gg) < 0) - return -ENOMEM; - - 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 if (free_and_strdup(&c->syslog_identifier, id) < 0) - return -ENOMEM; - - 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; - - } else if (streq(name, "SELinuxContext")) { - const char *s; - - r = sd_bus_message_read(message, "s", &s); - if (r < 0) - return r; - - if (mode != UNIT_CHECK) { - if (isempty(s)) - c->selinux_context = mfree(c->selinux_context); - else if (free_and_strdup(&c->selinux_context, s) < 0) - return -ENOMEM; - - unit_write_drop_in_private_format(u, mode, name, "%s=%s\n", name, strempty(s)); - } - - 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 deleted file mode 100644 index bdfef41db4..0000000000 --- a/src/libcore/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 - -#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 deleted file mode 100644 index dde6aa28ef..0000000000 --- a/src/libcore/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 - -#include "basic/alloc-util.h" -#include "dbus-job.h" -#include "dbus.h" -#include "job.h" -#include "basic/log.h" -#include "selinux-access.h" -#include "basic/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, false); - - 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 deleted file mode 100644 index 95664cb90c..0000000000 --- a/src/libcore/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 - -#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 deleted file mode 100644 index cd76682298..0000000000 --- a/src/libcore/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 "shared/bus-util.h" -#include "dbus-kill.h" -#include "kill.h" -#include "basic/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 sd_bus_error_setf(error, SD_BUS_ERROR_INVALID_ARGS, "Kill mode '%s' not known.", m); - - 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 (!SIGNAL_VALID(sig)) - 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 deleted file mode 100644 index b32ce9d223..0000000000 --- a/src/libcore/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 - -#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 deleted file mode 100644 index 59f8a7908a..0000000000 --- a/src/libcore/dbus-manager.c +++ /dev/null @@ -1,2305 +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 "basic/alloc-util.h" -#include "basic/architecture.h" -#include "basic/build.h" -#include "sd-bus/bus-common-errors.h" -#include "basic/clock-util.h" -#include "dbus-execute.h" -#include "dbus-job.h" -#include "dbus-manager.h" -#include "dbus-unit.h" -#include "dbus.h" -#include "basic/env-util.h" -#include "basic/fd-util.h" -#include "basic/fileio.h" -#include "basic/formats-util.h" -#include "shared/install.h" -#include "basic/log.h" -#include "basic/path-util.h" -#include "selinux-access.h" -#include "basic/stat-util.h" -#include "basic/string-util.h" -#include "basic/strv.h" -#include "basic/syslog-util.h" -#include "basic/virt.h" -#include "shared/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(NULL) > 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 reply_unit_info(sd_bus_message *reply, Unit *u) { - _cleanup_free_ char *unit_path = NULL, *job_path = NULL; - Unit *following; - - following = unit_following(u); - - 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; - } - - return 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 : "/"); -} - -static int method_list_units_by_names(sd_bus_message *message, void *userdata, sd_bus_error *error) { - _cleanup_(sd_bus_message_unrefp) sd_bus_message *reply = NULL; - Manager *m = userdata; - int r; - char **unit; - _cleanup_strv_free_ char **units = NULL; - - assert(message); - assert(m); - - r = sd_bus_message_read_strv(message, &units); - 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; - - STRV_FOREACH(unit, units) { - Unit *u; - - if (!unit_name_is_valid(*unit, UNIT_NAME_ANY)) - continue; - - r = manager_load_unit(m, *unit, NULL, error, &u); - if (r < 0) - return r; - - r = reply_unit_info(reply, u); - 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_get_unit_processes(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_get_processes(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, char **patterns) { - _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) { - if (k != u->id) - continue; - - 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; - - if (!strv_isempty(patterns) && - !strv_fnmatch_or_empty(patterns, u->id, FNM_NOESCAPE)) - continue; - - r = reply_unit_info(reply, u); - 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, 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, NULL); -} - -static int method_list_units_by_patterns(sd_bus_message *message, void *userdata, sd_bus_error *error) { - _cleanup_strv_free_ char **states = NULL; - _cleanup_strv_free_ char **patterns = NULL; - int r; - - r = sd_bus_message_read_strv(message, &states); - if (r < 0) - return r; - - r = sd_bus_message_read_strv(message, &patterns); - if (r < 0) - return r; - - return list_units_filtered(message, userdata, error, states, patterns); -} - -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 (!MANAGER_IS_SYSTEM(m)) - 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 (!MANAGER_IS_SYSTEM(m)) - 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 (!MANAGER_IS_SYSTEM(m)) - 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 (!MANAGER_IS_SYSTEM(m)) - 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 (!MANAGER_IS_SYSTEM(m)) - 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 (MANAGER_IS_SYSTEM(m) && 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 list_unit_files_by_patterns(sd_bus_message *message, void *userdata, sd_bus_error *error, char **states, char **patterns) { - _cleanup_(sd_bus_message_unrefp) sd_bus_message *reply = NULL; - Manager *m = userdata; - 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->unit_file_scope, NULL, h, states, patterns); - 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_list_unit_files(sd_bus_message *message, void *userdata, sd_bus_error *error) { - return list_unit_files_by_patterns(message, userdata, error, NULL, NULL); -} - -static int method_list_unit_files_by_patterns(sd_bus_message *message, void *userdata, sd_bus_error *error) { - _cleanup_strv_free_ char **states = NULL; - _cleanup_strv_free_ char **patterns = NULL; - int r; - - r = sd_bus_message_read_strv(message, &states); - if (r < 0) - return r; - - r = sd_bus_message_read_strv(message, &patterns); - if (r < 0) - return r; - - return list_unit_files_by_patterns(message, userdata, error, states, patterns); -} - -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; - 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; - - r = unit_file_get_state(m->unit_file_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; - 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 = unit_file_get_default(m->unit_file_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 (unit_file_changes_have_modification(changes, n_changes)) { - 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++) - if (changes[i].type >= 0) { - const char *change = unit_file_change_type_to_string(changes[i].type); - assert(change != NULL); - - r = sd_bus_message_append( - reply, "(sss)", - change, - 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; -} - -/* Create an error reply, using the error information from changes[] - * if possible, and fall back to generating an error from error code c. - * The error message only describes the first error. - * - * Coordinate with unit_file_dump_changes() in install.c. - */ -static int install_error( - sd_bus_error *error, - int c, - UnitFileChange *changes, - unsigned n_changes) { - int r; - unsigned i; - assert(c < 0); - - for (i = 0; i < n_changes; i++) - switch(changes[i].type) { - case 0 ... INT_MAX: - continue; - case -EEXIST: - if (changes[i].source) - r = sd_bus_error_setf(error, BUS_ERROR_UNIT_EXISTS, - "File %s already exists and is a symlink to %s.", - changes[i].path, changes[i].source); - else - r = sd_bus_error_setf(error, BUS_ERROR_UNIT_EXISTS, - "File %s already exists.", - changes[i].path); - goto found; - case -ERFKILL: - r = sd_bus_error_setf(error, BUS_ERROR_UNIT_MASKED, - "Unit file %s is masked.", changes[i].path); - goto found; - case -EADDRNOTAVAIL: - r = sd_bus_error_setf(error, BUS_ERROR_UNIT_GENERATED, - "Unit %s is transient or generated.", changes[i].path); - goto found; - case -ELOOP: - r = sd_bus_error_setf(error, BUS_ERROR_UNIT_LINKED, - "Refusing to operate on linked unit file %s", changes[i].path); - goto found; - default: - r = sd_bus_error_set_errnof(error, changes[i].type, "File %s: %m", changes[i].path); - goto found; - } - - r = c; - found: - unit_file_changes_free(changes, n_changes); - return r; -} - -static int method_enable_unit_files_generic( - sd_bus_message *message, - Manager *m, - 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; - 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 */ - - r = call(m->unit_file_scope, runtime, NULL, l, force, &changes, &n_changes); - if (r < 0) - return install_error(error, r, changes, n_changes); - - 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, 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, 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, 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, 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, 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; - 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 */ - - r = unit_file_preset(m->unit_file_scope, runtime, NULL, l, mm, force, &changes, &n_changes); - if (r < 0) - return install_error(error, r, changes, n_changes); - - 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, - 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; - 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; - - 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(m->unit_file_scope, runtime, NULL, l, &changes, &n_changes); - if (r < 0) - return install_error(error, r, changes, n_changes); - - 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, 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, unit_file_unmask, error); -} - -static int method_revert_unit_files(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; - int r; - - assert(message); - assert(m); - - r = sd_bus_message_read_strv(message, &l); - 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 */ - - r = unit_file_revert(m->unit_file_scope, NULL, l, &changes, &n_changes); - if (r < 0) - return install_error(error, r, changes, n_changes); - - return reply_unit_file_changes_and_free(m, message, -1, changes, n_changes); -} - -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; - 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 */ - - r = unit_file_set_default(m->unit_file_scope, NULL, name, force, &changes, &n_changes); - if (r < 0) - return install_error(error, r, changes, n_changes); - - 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; - 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 */ - - r = unit_file_preset_all(m->unit_file_scope, runtime, NULL, mm, force, &changes, &n_changes); - if (r < 0) - return install_error(error, r, changes, n_changes); - - 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; - int runtime, force, r; - char *target, *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; - - r = unit_file_add_dependency(m->unit_file_scope, runtime, NULL, l, target, dep, force, &changes, &n_changes); - if (r < 0) - return install_error(error, r, changes, n_changes); - - 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.search_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("DefaultStartLimitIntervalSec", "t", bus_property_get_usec, offsetof(Manager, default_start_limit_interval), 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_VTABLE_HIDDEN), /* obsolete alias name */ - 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("GetUnitProcesses", "s", "a(sus)", method_get_unit_processes, 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("ListUnitsByPatterns", "asas", "a(ssssssouso)", method_list_units_by_patterns, SD_BUS_VTABLE_UNPRIVILEGED), - SD_BUS_METHOD("ListUnitsByNames", "as", "a(ssssssouso)", method_list_units_by_names, 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("ListUnitFilesByPatterns", "asas", "a(ss)", method_list_unit_files_by_patterns, 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("RevertUnitFiles", "as", "a(sss)", method_revert_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 deleted file mode 100644 index 36a2e9481b..0000000000 --- a/src/libcore/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/libcore/dbus-mount.c b/src/libcore/dbus-mount.c deleted file mode 100644 index e6e824b6b9..0000000000 --- a/src/libcore/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 "shared/bus-util.h" -#include "dbus-cgroup.h" -#include "dbus-execute.h" -#include "dbus-kill.h" -#include "dbus-mount.h" -#include "mount.h" -#include "basic/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 deleted file mode 100644 index f9844e449d..0000000000 --- a/src/libcore/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 - -#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 deleted file mode 100644 index 2f2a65d4c1..0000000000 --- a/src/libcore/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 "shared/bus-util.h" -#include "dbus-path.h" -#include "path.h" -#include "basic/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 deleted file mode 100644 index d3c19e0c2b..0000000000 --- a/src/libcore/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/libcore/dbus-scope.c b/src/libcore/dbus-scope.c deleted file mode 100644 index 3a9c3b98ec..0000000000 --- a/src/libcore/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 "basic/alloc-util.h" -#include "sd-bus/bus-common-errors.h" -#include "sd-bus/bus-internal.h" -#include "shared/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 deleted file mode 100644 index f96ddef0cf..0000000000 --- a/src/libcore/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 - -#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 deleted file mode 100644 index 463d9da4e1..0000000000 --- a/src/libcore/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 "basic/alloc-util.h" -#include "basic/async.h" -#include "shared/bus-util.h" -#include "dbus-cgroup.h" -#include "dbus-execute.h" -#include "dbus-kill.h" -#include "dbus-service.h" -#include "basic/fd-util.h" -#include "basic/fileio.h" -#include "basic/path-util.h" -#include "service.h" -#include "basic/string-util.h" -#include "basic/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 deleted file mode 100644 index 291959325c..0000000000 --- a/src/libcore/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 - -#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 deleted file mode 100644 index e37f50b283..0000000000 --- a/src/libcore/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/libcore/dbus-slice.h b/src/libcore/dbus-slice.h deleted file mode 100644 index 8e4cabbf8a..0000000000 --- a/src/libcore/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 - -#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 deleted file mode 100644 index 53c9911991..0000000000 --- a/src/libcore/dbus-socket.c +++ /dev/null @@ -1,184 +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 "basic/alloc-util.h" -#include "shared/bus-util.h" -#include "dbus-cgroup.h" -#include "dbus-execute.h" -#include "dbus-socket.h" -#include "socket.h" -#include "basic/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), - SD_BUS_PROPERTY("TriggerLimitIntervalUSec", "t", bus_property_get_usec, offsetof(Socket, trigger_limit.interval), SD_BUS_VTABLE_PROPERTY_CONST), - SD_BUS_PROPERTY("TriggerLimitBurst", "u", bus_property_get_unsigned, offsetof(Socket, trigger_limit.burst), 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 deleted file mode 100644 index a31906feea..0000000000 --- a/src/libcore/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 - -#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 deleted file mode 100644 index 2cfc8bd496..0000000000 --- a/src/libcore/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 "shared/bus-util.h" -#include "dbus-cgroup.h" -#include "dbus-execute.h" -#include "dbus-swap.h" -#include "basic/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 deleted file mode 100644 index 19151fb771..0000000000 --- a/src/libcore/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 - -#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 deleted file mode 100644 index 6858b1ce72..0000000000 --- a/src/libcore/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/libcore/dbus-target.h b/src/libcore/dbus-target.h deleted file mode 100644 index c97a9d626e..0000000000 --- a/src/libcore/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 - -extern const sd_bus_vtable bus_target_vtable[]; diff --git a/src/libcore/dbus-timer.c b/src/libcore/dbus-timer.c deleted file mode 100644 index be84522036..0000000000 --- a/src/libcore/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 "basic/alloc-util.h" -#include "shared/bus-util.h" -#include "dbus-timer.h" -#include "basic/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_or_monotonic()); - - 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 deleted file mode 100644 index 505fb5df72..0000000000 --- a/src/libcore/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 - -#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 deleted file mode 100644 index dd5d6e4c5e..0000000000 --- a/src/libcore/dbus-unit.c +++ /dev/null @@ -1,1423 +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 "basic/alloc-util.h" -#include "sd-bus/bus-common-errors.h" -#include "basic/cgroup-util.h" -#include "dbus-unit.h" -#include "dbus.h" -#include "basic/fd-util.h" -#include "basic/locale-util.h" -#include "basic/log.h" -#include "basic/process-util.h" -#include "selinux-access.h" -#include "basic/signal-util.h" -#include "basic/special.h" -#include "basic/string-util.h" -#include "basic/strv.h" -#include "basic/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 (!SIGNAL_VALID(signo)) - 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("StartLimitIntervalSec", "t", bus_property_get_usec, offsetof(Unit, start_limit.interval), 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_VTABLE_HIDDEN), /* obsolete alias name */ - 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); -} - -static int append_process(sd_bus_message *reply, const char *p, pid_t pid, Set *pids) { - _cleanup_free_ char *buf = NULL, *cmdline = NULL; - int r; - - assert(reply); - assert(pid > 0); - - r = set_put(pids, PID_TO_PTR(pid)); - if (r == -EEXIST || r == 0) - return 0; - if (r < 0) - return r; - - if (!p) { - r = cg_pid_get_path(SYSTEMD_CGROUP_CONTROLLER, pid, &buf); - if (r == -ESRCH) - return 0; - if (r < 0) - return r; - - p = buf; - } - - (void) get_process_cmdline(pid, 0, true, &cmdline); - - return sd_bus_message_append(reply, - "(sus)", - p, - (uint32_t) pid, - cmdline); -} - -static int append_cgroup(sd_bus_message *reply, const char *p, Set *pids) { - _cleanup_closedir_ DIR *d = NULL; - _cleanup_fclose_ FILE *f = NULL; - int r; - - assert(reply); - assert(p); - - r = cg_enumerate_processes(SYSTEMD_CGROUP_CONTROLLER, p, &f); - if (r == ENOENT) - return 0; - if (r < 0) - return r; - - for (;;) { - pid_t pid; - - r = cg_read_pid(f, &pid); - if (r < 0) - return r; - if (r == 0) - break; - - if (is_kernel_thread(pid) > 0) - continue; - - r = append_process(reply, p, pid, pids); - if (r < 0) - return r; - } - - r = cg_enumerate_subgroups(SYSTEMD_CGROUP_CONTROLLER, p, &d); - if (r == -ENOENT) - return 0; - if (r < 0) - return r; - - for (;;) { - _cleanup_free_ char *g = NULL, *j = NULL; - - r = cg_read_subgroup(d, &g); - if (r < 0) - return r; - if (r == 0) - break; - - j = strjoin(p, "/", g, NULL); - if (!j) - return -ENOMEM; - - r = append_cgroup(reply, j, pids); - if (r < 0) - return r; - } - - return 0; -} - -int bus_unit_method_get_processes(sd_bus_message *message, void *userdata, sd_bus_error *error) { - _cleanup_(sd_bus_message_unrefp) sd_bus_message *reply = NULL; - _cleanup_(set_freep) Set *pids = NULL; - Unit *u = userdata; - pid_t pid; - int r; - - assert(message); - - pids = set_new(NULL); - if (!pids) - return -ENOMEM; - - r = sd_bus_message_new_method_return(message, &reply); - if (r < 0) - return r; - - r = sd_bus_message_open_container(reply, 'a', "(sus)"); - if (r < 0) - return r; - - if (u->cgroup_path) { - r = append_cgroup(reply, u->cgroup_path, pids); - if (r < 0) - return r; - } - - /* The main and control pids might live outside of the cgroup, hence fetch them separately */ - pid = unit_main_pid(u); - if (pid > 0) { - r = append_process(reply, NULL, pid, pids); - if (r < 0) - return r; - } - - pid = unit_control_pid(u); - if (pid > 0) { - r = append_process(reply, NULL, pid, pids); - if (r < 0) - return r; - } - - r = sd_bus_message_close_container(reply); - if (r < 0) - return r; - - return sd_bus_send(NULL, reply, NULL); -} - -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_METHOD("GetProcesses", NULL, "a(sus)", bus_unit_method_get_processes, SD_BUS_VTABLE_UNPRIVILEGED), - 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); - - /* Note that we do not dispatch the load queue here yet, as we don't want our own transient unit to be - * loaded while we are still setting it up. Or in other words, we use manager_load_unit_prepare() - * instead of manager_load_unit() on purpose, here. */ - r = manager_load_unit_prepare(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) { - assert(u); - - 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 deleted file mode 100644 index 758045a47c..0000000000 --- a/src/libcore/dbus-unit.h +++ /dev/null @@ -1,41 +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 "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_method_get_processes(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 deleted file mode 100644 index d1d6248041..0000000000 --- a/src/libcore/dbus.c +++ /dev/null @@ -1,1243 +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 "basic/alloc-util.h" -#include "sd-bus/bus-common-errors.h" -#include "sd-bus/bus-error.h" -#include "sd-bus/bus-internal.h" -#include "shared/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 "basic/fd-util.h" -#include "basic/log.h" -#include "basic/missing.h" -#include "basic/mkdir.h" -#include "selinux-access.h" -#include "basic/special.h" -#include "basic/string-util.h" -#include "basic/strv.h" -#include "basic/strxcpyx.h" -#include "basic/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; -} - -int bus_forward_agent_released(Manager *m, const char *path) { - int r; - - assert(m); - assert(path); - - if (!MANAGER_IS_SYSTEM(m)) - return 0; - - if (!m->system_bus) - return 0; - - /* If we are running a system instance we forward the agent message on the system bus, so that the user - * instances get notified about this, too */ - - r = sd_bus_emit_signal(m->system_bus, - "/org/freedesktop/systemd1/agent", - "org.freedesktop.systemd1.Agent", - "Released", - "s", path); - if (r < 0) - return log_warning_errno(r, "Failed to propagate agent release message: %m"); - - return 1; -} - -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; - Manager *m = userdata; - const char *cgroup; - uid_t sender_uid; - int r; - - assert(message); - assert(m); - - /* 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); - 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; - } - - 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 (MANAGER_IS_SYSTEM(m) && m->system_bus) - bus = sd_bus_ref(m->system_bus); - else { - if (MANAGER_IS_SYSTEM(m)) - 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); - - /* if we are a user instance we get the Released message via the system bus */ - if (MANAGER_IS_USER(m)) { - 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 (MANAGER_IS_SYSTEM(m) && 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 (MANAGER_IS_SYSTEM(m)) { - - /* We want the private bus only when running as init */ - if (getpid() != 1) - return 0; - - strcpy(sa.un.sun_path, "/run/systemd/private"); - salen = SOCKADDR_UN_LEN(sa.un); - } 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 (!MANAGER_IS_SYSTEM(m)) - 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 deleted file mode 100644 index 6baaffbd75..0000000000 --- a/src/libcore/dbus.h +++ /dev/null @@ -1,44 +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); - -int bus_forward_agent_released(Manager *m, const char *path); diff --git a/src/libcore/device.c b/src/libcore/device.c deleted file mode 100644 index 10e9e9ae76..0000000000 --- a/src/libcore/device.c +++ /dev/null @@ -1,876 +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 "basic/alloc-util.h" -#include "dbus-device.h" -#include "device.h" -#include "basic/log.h" -#include "basic/parse-util.h" -#include "basic/path-util.h" -#include "basic/stat-util.h" -#include "basic/string-util.h" -#include "swap.h" -#include "shared/udev-util.h" -#include "basic/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 = MANAGER_IS_USER(u->manager) ? "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 (dev && 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_debug(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", - - .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 deleted file mode 100644 index 184a1a349b..0000000000 --- a/src/libcore/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/libcore/execute.c b/src/libcore/execute.c deleted file mode 100644 index e7628bceb3..0000000000 --- a/src/libcore/execute.c +++ /dev/null @@ -1,3092 +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 - -#ifdef HAVE_PAM -#include -#endif - -#ifdef HAVE_SELINUX -#include -#endif - -#ifdef HAVE_SECCOMP -#include -#endif - -#ifdef HAVE_APPARMOR -#include -#endif - -#include - -#include "basic/af-list.h" -#include "basic/alloc-util.h" -#ifdef HAVE_APPARMOR -#include "shared/apparmor-util.h" -#endif -#include "basic/async.h" -#include "basic/barrier.h" -#include "basic/cap-list.h" -#include "basic/capability-util.h" -#include "basic/def.h" -#include "basic/env-util.h" -#include "basic/errno-list.h" -#include "execute.h" -#include "basic/exit-status.h" -#include "basic/fd-util.h" -#include "basic/fileio.h" -#include "basic/formats-util.h" -#include "basic/fs-util.h" -#include "basic/glob-util.h" -#include "basic/io-util.h" -#include "basic/ioprio.h" -#include "basic/log.h" -#include "basic/macro.h" -#include "basic/missing.h" -#include "basic/mkdir.h" -#include "namespace.h" -#include "basic/parse-util.h" -#include "basic/path-util.h" -#include "basic/process-util.h" -#include "basic/rlimit-util.h" -#include "basic/rm-rf.h" -#ifdef HAVE_SECCOMP -#include "shared/seccomp-util.h" -#endif -#include "basic/securebits.h" -#include "basic/selinux-util.h" -#include "basic/signal-util.h" -#include "basic/smack-util.h" -#include "basic/string-table.h" -#include "basic/string-util.h" -#include "basic/strv.h" -#include "basic/syslog-util.h" -#include "basic/terminal-util.h" -#include "unit.h" -#include "basic/user-util.h" -#include "basic/util.h" -#include "shared/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, SOCKADDR_UN_LEN(sa.un)); - 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 look up) the uid and make sure we keep the - * capabilities while doing so. */ - - if (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<= 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 (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 (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 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, - 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) { - - bool use_address_families = context->address_families_whitelist || - !set_isempty(context->address_families); - bool use_syscall_filter = context->syscall_whitelist || - !set_isempty(context->syscall_filter) || - !set_isempty(context->syscall_archs); - 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->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<no_new_privileges || - (!have_effective_cap(CAP_SYS_ADMIN) && (use_address_families || use_syscall_filter))) - if (prctl(PR_SET_NO_NEW_PRIVS, 1, 0, 0, 0) < 0) { - *exit_status = EXIT_NO_NEW_PRIVILEGES; - return -errno; - } - -#ifdef HAVE_SECCOMP - if (use_address_families) { - r = apply_address_families(context); - if (r < 0) { - *exit_status = EXIT_ADDRESS_FAMILIES; - return r; - } - } - - if (use_syscall_filter) { - 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); - - 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); -} - -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->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 deleted file mode 100644 index 190a56f443..0000000000 --- a/src/libcore/execute.h +++ /dev/null @@ -1,287 +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 "basic/fdset.h" -#include "basic/list.h" -#include "basic/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; - 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; -}; - -#include "basic/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; - - 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 deleted file mode 100644 index be76c15a37..0000000000 --- a/src/libcore/failure-action.c +++ /dev/null @@ -1,127 +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 "sd-bus/bus-error.h" -#include "shared/bus-util.h" -#include "failure-action.h" -#include "basic/special.h" -#include "basic/string-table.h" -#include "basic/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 (!MANAGER_IS_SYSTEM(m)) { - /* 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."); - - (void) update_reboot_parameter_and_warn(reboot_arg); - (void) manager_add_job_by_name_and_warn(m, JOB_START, SPECIAL_REBOOT_TARGET, JOB_REPLACE_IRREVERSIBLY, NULL); - - break; - - case FAILURE_ACTION_REBOOT_FORCE: - log_and_status(m, "Forcibly rebooting as result of failure."); - - (void) update_reboot_parameter_and_warn(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 (!isempty(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_warning_errno(errno, "Failed to reboot with parameter, retrying without: %m"); - } - - 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_IRREVERSIBLY, 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 deleted file mode 100644 index 96c6b51cb2..0000000000 --- a/src/libcore/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 "basic/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 deleted file mode 100644 index 0085c0d405..0000000000 --- a/src/libcore/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 "basic/alloc-util.h" -#include "basic/fileio.h" -#include "hostname-setup.h" -#include "basic/hostname-util.h" -#include "basic/log.h" -#include "basic/macro.h" -#include "basic/string-util.h" -#include "basic/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 deleted file mode 100644 index 73e8c75c71..0000000000 --- a/src/libcore/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/libcore/ima-setup.c b/src/libcore/ima-setup.c deleted file mode 100644 index c6eb358c9d..0000000000 --- a/src/libcore/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 "basic/fd-util.h" -#include "basic/fileio.h" -#include "ima-setup.h" -#include "basic/log.h" -#include "basic/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 deleted file mode 100644 index 472b58cb00..0000000000 --- a/src/libcore/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/libcore/job.c b/src/libcore/job.c deleted file mode 100644 index 1a24a7f152..0000000000 --- a/src/libcore/job.c +++ /dev/null @@ -1,1259 +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 "basic/alloc-util.h" -#include "basic/async.h" -#include "dbus-job.h" -#include "dbus.h" -#include "basic/escape.h" -#include "job.h" -#include "basic/log.h" -#include "basic/macro.h" -#include "basic/parse-util.h" -#include "basic/set.h" -#include "basic/special.h" -#include "basic/stdio-util.h" -#include "basic/string-table.h" -#include "basic/string-util.h" -#include "basic/strv.h" -#include "basic/terminal-util.h" -#include "unit.h" -#include "basic/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 (!MANAGER_IS_RELOADING(j->manager)) - 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, 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, true); - else if (r == -EBADR) - r = job_finish_and_invalidate(j, JOB_SKIPPED, true, false); - else if (r == -ENOEXEC) - r = job_finish_and_invalidate(j, JOB_INVALID, true, false); - else if (r == -EPROTO) - r = job_finish_and_invalidate(j, JOB_ASSERT, true, false); - else if (r == -EOPNOTSUPP) - r = job_finish_and_invalidate(j, JOB_UNSUPPORTED, true, false); - else if (r == -EAGAIN) - job_set_state(j, JOB_WAITING); - else if (r < 0) - r = job_finish_and_invalidate(j, JOB_FAILED, true, false); - } - - 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 stopping %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 struct { - const char *color, *word; - } const statuses[_JOB_RESULT_MAX] = { - [JOB_DONE] = {ANSI_GREEN, " OK "}, - [JOB_TIMEOUT] = {ANSI_HIGHLIGHT_RED, " TIME "}, - [JOB_FAILED] = {ANSI_HIGHLIGHT_RED, "FAILED"}, - [JOB_DEPENDENCY] = {ANSI_HIGHLIGHT_YELLOW, "DEPEND"}, - [JOB_SKIPPED] = {ANSI_HIGHLIGHT, " INFO "}, - [JOB_ASSERT] = {ANSI_HIGHLIGHT_YELLOW, "ASSERT"}, - [JOB_UNSUPPORTED] = {ANSI_HIGHLIGHT_YELLOW, "UNSUPP"}, - }; - - const char *format; - const char *status; - - 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 (log_get_show_color()) - status = strjoina(statuses[result].color, statuses[result].word, ANSI_NORMAL); - else - status = statuses[result].word; - - if (result != JOB_DONE) - manager_flip_auto_status(u->manager, true); - - DISABLE_WARNING_FORMAT_NONLITERAL; - unit_status_printf(u, status, 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, false); - } -} - -int job_finish_and_invalidate(Job *j, JobResult result, bool recursive, bool already) { - 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)); - - /* If this job did nothing to respective unit we don't log the status message */ - if (!already) - 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, false); - - 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 (!MANAGER_IS_SYSTEM(j->unit->manager)) - 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 deleted file mode 100644 index f88e5a0782..0000000000 --- a/src/libcore/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 - -#include "basic/list.h" -#include "basic/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, bool already); - -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 deleted file mode 100644 index 22e5bdb4fe..0000000000 --- a/src/libcore/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 "basic/signal-util.h" -#include "basic/string-table.h" -#include "basic/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 deleted file mode 100644 index 529e4c5377..0000000000 --- a/src/libcore/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 "basic/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 deleted file mode 100644 index 03bed30704..0000000000 --- a/src/libcore/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 "basic/alloc-util.h" -#include "basic/fd-util.h" -#include "basic/formats-util.h" -#include "killall.h" -#include "basic/parse-util.h" -#include "basic/process-util.h" -#include "basic/set.h" -#include "basic/string-util.h" -#include "basic/terminal-util.h" -#include "basic/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 deleted file mode 100644 index acc2439f00..0000000000 --- a/src/libcore/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/libcore/kmod-setup.c b/src/libcore/kmod-setup.c deleted file mode 100644 index 6ddf1137ec..0000000000 --- a/src/libcore/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 "shared/bus-util.h" -#include "basic/capability-util.h" -#include "kmod-setup.h" -#include "basic/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 deleted file mode 100644 index 685f4df301..0000000000 --- a/src/libcore/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/libcore/linux/auto_dev-ioctl.h b/src/libcore/linux/auto_dev-ioctl.h deleted file mode 100644 index aeaeb3ea7a..0000000000 --- a/src/libcore/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/libcore/load-dropin.c b/src/libcore/load-dropin.c deleted file mode 100644 index bb6cf0f989..0000000000 --- a/src/libcore/load-dropin.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 "shared/conf-parser.h" -#include "load-dropin.h" -#include "load-fragment.h" -#include "basic/log.h" -#include "basic/strv.h" -#include "basic/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) { - _cleanup_strv_free_ char **l = NULL; - 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.search_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); - } - } - - r = unit_find_dropin_paths(u, &l); - if (r <= 0) - return 0; - - if (!u->dropin_paths) { - u->dropin_paths = l; - l = NULL; - } else { - r = strv_extend_strv(&u->dropin_paths, l, true); - if (r < 0) - return log_oom(); - } - - 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 deleted file mode 100644 index 067661b1ff..0000000000 --- a/src/libcore/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 "shared/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.search_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 deleted file mode 100644 index c9f6e6acf7..0000000000 --- a/src/libcore/load-fragment-gperf.gperf.m4 +++ /dev/null @@ -1,391 +0,0 @@ -%{ -#include -#include "shared/conf-parser.h" -#include "load-fragment.h" -#include "basic/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_warn_compat, DISABLED_LEGACY, 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.IOAccounting, config_parse_bool, 0, offsetof($1, cgroup_context.io_accounting) -$1.IOWeight, config_parse_io_weight, 0, offsetof($1, cgroup_context.io_weight) -$1.StartupIOWeight, config_parse_io_weight, 0, offsetof($1, cgroup_context.startup_io_weight) -$1.IODeviceWeight, config_parse_io_device_weight, 0, offsetof($1, cgroup_context) -$1.IOReadBandwidthMax, config_parse_io_limit, 0, offsetof($1, cgroup_context) -$1.IOWriteBandwidthMax, config_parse_io_limit, 0, offsetof($1, cgroup_context) -$1.IOReadIOPSMax, config_parse_io_limit, 0, offsetof($1, cgroup_context) -$1.IOWriteIOPSMax, config_parse_io_limit, 0, offsetof($1, cgroup_context) -$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.StartLimitIntervalSec, config_parse_sec, 0, offsetof(Unit, start_limit.interval) -m4_dnl The following is a legacy alias name for compatibility -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) -m4_dnl The following three only exist for compatibility, they moved into Unit, see above -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_warn_compat, DISABLED_LEGACY, 0 -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 -Socket.TriggerLimitIntervalSec, config_parse_sec, 0, offsetof(Socket, trigger_limit.interval) -Socket.TriggerLimitBurst, config_parse_unsigned, 0, offsetof(Socket, trigger_limit.burst) -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 deleted file mode 100644 index 6d9100b189..0000000000 --- a/src/libcore/load-fragment.c +++ /dev/null @@ -1,4083 +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 "basic/af-list.h" -#include "basic/alloc-util.h" -#include "sd-bus/bus-error.h" -#include "sd-bus/bus-internal.h" -#include "shared/bus-util.h" -#include "basic/cap-list.h" -#include "basic/capability-util.h" -#include "cgroup.h" -#include "shared/conf-parser.h" -#include "basic/cpu-set-util.h" -#include "basic/env-util.h" -#include "basic/errno-list.h" -#include "basic/escape.h" -#include "basic/fd-util.h" -#include "basic/fs-util.h" -#include "basic/ioprio.h" -#include "load-fragment.h" -#include "basic/log.h" -#include "basic/missing.h" -#include "basic/parse-util.h" -#include "basic/path-util.h" -#include "basic/process-util.h" -#include "basic/rlimit-util.h" -#ifdef HAVE_SECCOMP -#include "shared/seccomp-util.h" -#endif -#include "basic/securebits.h" -#include "basic/signal-util.h" -#include "basic/stat-util.h" -#include "basic/string-util.h" -#include "basic/strv.h" -#include "basic/unit-name.h" -#include "unit-printf.h" -#include "unit.h" -#include "basic/utf8.h" -#include "basic/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, "*")) { - if (!ifname_valid(rvalue)) { - log_syntax(unit, LOG_ERR, filename, line, 0, "Interface name is invalid, ignoring: %s", rvalue); - return 0; - } - - 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_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_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 && MANAGER_IS_USER(u->manager)) - 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, *t = NULL; - CGroupContext *c = data; - CGroupDeviceAllow *a; - const char *m = NULL; - size_t n; - int r; - - if (isempty(rvalue)) { - while (c->device_allow) - cgroup_context_free_device_allow(c, c->device_allow); - - return 0; - } - - r = unit_full_printf(userdata, rvalue, &t); - if(r < 0) { - log_syntax(unit, LOG_WARNING, filename, line, r, - "Failed to resolve specifiers in %s, ignoring: %m", - rvalue); - } - - n = strcspn(t, WHITESPACE); - - path = strndup(t, 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 = t + n + strspn(t + 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_io_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_weight_parse(rvalue, weight); - if (r < 0) { - log_syntax(unit, LOG_ERR, filename, line, r, "IO weight '%s' invalid. Ignoring.", rvalue); - return 0; - } - - return 0; -} - -int config_parse_io_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; - CGroupIODeviceWeight *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->io_device_weights) - cgroup_context_free_io_device_weight(c, c->io_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_weight_parse(weight, &u); - if (r < 0) { - log_syntax(unit, LOG_ERR, filename, line, r, "IO weight '%s' invalid. Ignoring.", weight); - return 0; - } - - assert(u != CGROUP_WEIGHT_INVALID); - - w = new0(CGroupIODeviceWeight, 1); - if (!w) - return log_oom(); - - w->path = path; - path = NULL; - - w->weight = u; - - LIST_PREPEND(device_weights, c->io_device_weights, w); - return 0; -} - -int config_parse_io_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) { - - _cleanup_free_ char *path = NULL; - CGroupIODeviceLimit *l = NULL, *t; - CGroupContext *c = data; - CGroupIOLimitType type; - const char *limit; - uint64_t num; - size_t n; - int r; - - assert(filename); - assert(lvalue); - assert(rvalue); - - type = cgroup_io_limit_type_from_string(lvalue); - assert(type >= 0); - - if (isempty(rvalue)) { - LIST_FOREACH(device_limits, l, c->io_device_limits) - l->limits[type] = cgroup_io_limit_defaults[type]; - return 0; - } - - n = strcspn(rvalue, WHITESPACE); - limit = rvalue + n; - limit += strspn(limit, WHITESPACE); - - if (!*limit) { - 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; - } - - if (streq("max", limit)) { - num = CGROUP_LIMIT_MAX; - } else { - r = parse_size(limit, 1000, &num); - if (r < 0 || num <= 0) { - log_syntax(unit, LOG_ERR, filename, line, r, "IO Limit '%s' invalid. Ignoring.", rvalue); - return 0; - } - } - - LIST_FOREACH(device_limits, t, c->io_device_limits) { - if (path_equal(path, t->path)) { - l = t; - break; - } - } - - if (!l) { - CGroupIOLimitType ttype; - - l = new0(CGroupIODeviceLimit, 1); - if (!l) - return log_oom(); - - l->path = path; - path = NULL; - for (ttype = 0; ttype < _CGROUP_IO_LIMIT_TYPE_MAX; ttype++) - l->limits[ttype] = cgroup_io_limit_defaults[ttype]; - - LIST_PREPEND(device_limits, c->io_device_limits, l); - } - - l->limits[type] = num; - - 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 = NULL, *t; - 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)) { - LIST_FOREACH(device_bandwidths, b, c->blockio_device_bandwidths) { - b->rbps = CGROUP_LIMIT_MAX; - b->wbps = CGROUP_LIMIT_MAX; - } - 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; - } - - LIST_FOREACH(device_bandwidths, t, c->blockio_device_bandwidths) { - if (path_equal(path, t->path)) { - b = t; - break; - } - } - - if (!t) { - b = new0(CGroupBlockIODeviceBandwidth, 1); - if (!b) - return log_oom(); - - b->path = path; - path = NULL; - b->rbps = CGROUP_LIMIT_MAX; - b->wbps = CGROUP_LIMIT_MAX; - - LIST_PREPEND(device_bandwidths, c->blockio_device_bandwidths, b); - } - - if (read) - b->rbps = bytes; - else - b->wbps = bytes; - - 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) { - char *id = NULL; - unsigned c = 0; - int fd, r; - FILE *f; - - 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 */ - - /* If the symlink name we are looking at is unit template, then - we must search for instance of this template */ - if (unit_name_is_valid(k, UNIT_NAME_TEMPLATE)) { - _cleanup_free_ char *instance = NULL; - - r = unit_name_replace_instance(k, (*u)->instance, &instance); - if (r < 0) - return r; - - other = manager_get_unit((*u)->manager, instance); - } else - 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) { - _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; - int r; - - 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.search_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) - break; - filename = mfree(filename); - if (r != -ENOENT) - return r; - - /* Empty the symlink names for the next run */ - set_clear_free(symlink_names); - } - } - - if (!filename) - /* Hmm, no suitable file found? */ - return 0; - - if (!unit_type_may_alias(u->type) && set_size(symlink_names) > 1) { - log_unit_warning(u, "Unit type of %s does not support alias names, refusing loading via symlink.", u->id); - return -ELOOP; - } - - 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; - u->fragment_mtime = 0; - } else { - u->load_state = UNIT_LOADED; - u->fragment_mtime = timespec_load(&st.st_mtim); - - /* 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; - - 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_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_io_limit, "LIMIT" }, - { config_parse_io_weight, "WEIGHT" }, - { config_parse_io_device_weight, "DEVICEWEIGHT" }, - { 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 deleted file mode 100644 index b36a2e3a02..0000000000 --- a/src/libcore/load-fragment.h +++ /dev/null @@ -1,123 +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_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_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_io_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_io_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_io_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_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 deleted file mode 100644 index 1c9660cdf4..0000000000 --- a/src/libcore/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 "basic/env-util.h" -#include "basic/fileio.h" -#include "locale-setup.h" -#include "basic/locale-util.h" -#include "basic/string-util.h" -#include "basic/strv.h" -#include "basic/util.h" -#include "basic/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 deleted file mode 100644 index 3b97497afe..0000000000 --- a/src/libcore/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/libcore/loopback-setup.c b/src/libcore/loopback-setup.c deleted file mode 100644 index f92e346f8c..0000000000 --- a/src/libcore/loopback-setup.c +++ /dev/null @@ -1,89 +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 "basic/missing.h" -#include "sd-netlink/sd-netlink.h" - -#include "loopback-setup.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 deleted file mode 100644 index e7547b8a26..0000000000 --- a/src/libcore/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/libcore/machine-id-setup.c b/src/libcore/machine-id-setup.c deleted file mode 100644 index 702cc90e89..0000000000 --- a/src/libcore/machine-id-setup.c +++ /dev/null @@ -1,364 +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 "basic/alloc-util.h" -#include "basic/fd-util.h" -#include "basic/fileio.h" -#include "basic/fs-util.h" -#include "basic/hexdecoct.h" -#include "basic/io-util.h" -#include "basic/log.h" -#include "machine-id-setup.h" -#include "basic/macro.h" -#include "basic/mkdir.h" -#include "basic/mount-util.h" -#include "basic/path-util.h" -#include "basic/process-util.h" -#include "basic/stat-util.h" -#include "basic/string-util.h" -#include "basic/umask-util.h" -#include "basic/util.h" -#include "basic/virt.h" - -static int shorten_uuid(char destination[34], const char source[36]) { - unsigned i, j; - - assert(destination); - assert(source); - - /* Converts a UUID into a machine ID, by lowercasing it and - * removing dashes. Validates everything. */ - - for (i = 0, j = 0; i < 36 && j < 32; i++) { - int t; - - t = unhexchar(source[i]); - if (t < 0) - continue; - - destination[j++] = hexchar(t); - } - - if (i != 36 || j != 32) - return -EINVAL; - - destination[32] = '\n'; - destination[33] = 0; - return 0; -} - -static int read_machine_id(int fd, char id[34]) { - char id_to_validate[34]; - int r; - - assert(fd >= 0); - assert(id); - - /* Reads a machine ID from a file, validates it, and returns - * it. The returned ID ends in a newline. */ - - r = loop_read_exact(fd, id_to_validate, 33, false); - if (r < 0) - return r; - - if (id_to_validate[32] != '\n') - return -EINVAL; - - id_to_validate[32] = 0; - - if (!id128_is_valid(id_to_validate)) - return -EINVAL; - - memcpy(id, id_to_validate, 32); - id[32] = '\n'; - id[33] = 0; - return 0; -} - -static int write_machine_id(int fd, const char id[34]) { - int r; - - assert(fd >= 0); - assert(id); - - if (lseek(fd, 0, SEEK_SET) < 0) - return -errno; - - r = loop_write(fd, id, 33, false); - if (r < 0) - return r; - - if (fsync(fd) < 0) - return -errno; - - return 0; -} - -static int generate_machine_id(char id[34], const char *root) { - int fd, r; - unsigned char *p; - sd_id128_t buf; - char *q; - const char *dbus_machine_id; - - assert(id); - - dbus_machine_id = prefix_roota(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; - - etc_machine_id = prefix_roota(root, "/etc/machine-id"); - run_machine_id = prefix_roota(root, "/run/machine-id"); - - RUN_WITH_UMASK(0000) { - /* We create this 0444, to indicate that this isn't really - * 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; - - etc_machine_id = prefix_roota(root, "/etc/machine-id"); - - r = path_is_mount_point(etc_machine_id, 0); - if (r < 0) - return log_error_errno(r, "Failed to determine whether %s is a mount point: %m", etc_machine_id); - if (r == 0) { - log_debug("%s is is not a mount point. Nothing to do.", etc_machine_id); - 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 deleted file mode 100644 index a7e7678ed9..0000000000 --- a/src/libcore/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/libcore/manager.c b/src/libcore/manager.c deleted file mode 100644 index 9019967300..0000000000 --- a/src/libcore/manager.c +++ /dev/null @@ -1,3134 +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 -#include - -#include "basic/alloc-util.h" -#include "audit-fd.h" -#include "shared/boot-timestamps.h" -#include "sd-bus/bus-common-errors.h" -#include "sd-bus/bus-error.h" -#include "sd-bus/bus-kernel.h" -#include "shared/bus-util.h" -#include "dbus-job.h" -#include "dbus-manager.h" -#include "dbus-unit.h" -#include "dbus.h" -#include "basic/dirent-util.h" -#include "basic/env-util.h" -#include "basic/escape.h" -#include "basic/exit-status.h" -#include "basic/fd-util.h" -#include "basic/fileio.h" -#include "basic/fs-util.h" -#include "basic/hashmap.h" -#include "basic/io-util.h" -#include "locale-setup.h" -#include "basic/log.h" -#include "basic/macro.h" -#include "manager.h" -#include "basic/missing.h" -#include "basic/mkdir.h" -#include "basic/mkdir.h" -#include "basic/parse-util.h" -#include "shared/path-lookup.h" -#include "basic/path-util.h" -#include "basic/process-util.h" -#include "basic/ratelimit.h" -#include "basic/rm-rf.h" -#include "basic/signal-util.h" -#include "basic/special.h" -#include "basic/stat-util.h" -#include "basic/string-table.h" -#include "basic/string-util.h" -#include "basic/strv.h" -#include "basic/terminal-util.h" -#include "basic/time-util.h" -#include "transaction.h" -#include "basic/umask-util.h" -#include "basic/unit-name.h" -#include "basic/util.h" -#include "basic/virt.h" -#include "shared/watchdog.h" - -#define NOTIFY_RCVBUF_SIZE (8*1024*1024) -#define CGROUPS_AGENT_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_cgroups_agent_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_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. Also, process this before handling cgroup notifications, so that we always collect child exit - * status information before detecting that there's no process in a cgroup. */ - r = sd_event_source_set_priority(m->signal_event_source, SD_EVENT_PRIORITY_NORMAL-6); - if (r < 0) - return r; - - if (MANAGER_IS_SYSTEM(m)) - 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 (MANAGER_IS_SYSTEM(m)) { - /* 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(UnitFileScope scope, bool test_run, Manager **_m) { - Manager *m; - int r; - - assert(_m); - assert(IN_SET(scope, UNIT_FILE_SYSTEM, UNIT_FILE_USER)); - - m = new0(Manager, 1); - if (!m) - return -ENOMEM; - - m->unit_file_scope = scope; - 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); - -#ifdef ENABLE_EFI - if (MANAGER_IS_SYSTEM(m) && detect_container() <= 0) - boot_timestamps(&m->userspace_timestamp, &m->firmware_timestamp, &m->loader_timestamp); -#endif - - /* Prepare log fields we can use for structured logging */ - if (MANAGER_IS_SYSTEM(m)) { - m->unit_log_field = "UNIT="; - m->unit_log_format_string = "UNIT=%s"; - } else { - m->unit_log_field = "USER_UNIT="; - m->unit_log_format_string = "USER_UNIT=%s"; - } - - 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->cgroups_agent_fd = m->signal_fd = m->time_change_fd = - m->dev_autofs_fd = m->private_listen_fd = m->kdbus_fd = m->cgroup_inotify_fd = - m->ask_password_inotify_fd = -1; - - m->current_job_id = 1; /* start as id #1, so that we can leave #0 around as "null-like" value */ - - 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; - const char *e; - - /* 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); - - e = manager_get_runtime_prefix(m); - if (!e) { - log_error("Failed to determine runtime prefix."); - 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, SOCKADDR_UN_LEN(sa.un)); - 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 notification messages 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_cgroups_agent(Manager *m) { - - static const union sockaddr_union sa = { - .un.sun_family = AF_UNIX, - .un.sun_path = "/run/systemd/cgroups-agent", - }; - int r; - - /* This creates a listening socket we receive cgroups agent messages on. We do not use D-Bus for delivering - * these messages from the cgroups agent binary to PID 1, as the cgroups agent binary is very short-living, and - * each instance of it needs a new D-Bus connection. Since D-Bus connections are SOCK_STREAM/AF_UNIX, on - * overloaded systems the backlog of the D-Bus socket becomes relevant, as not more than the configured number - * of D-Bus connections may be queued until the kernel will start dropping further incoming connections, - * possibly resulting in lost cgroups agent messages. To avoid this, we'll use a private SOCK_DGRAM/AF_UNIX - * socket, where no backlog is relevant as communication may take place without an actual connect() cycle, and - * we thus won't lose messages. - * - * Note that PID 1 will forward the agent message to system bus, so that the user systemd instance may listen - * to it. The system instance hence listens on this special socket, but the user instances listen on the system - * bus for these messages. */ - - if (m->test_run) - return 0; - - if (!MANAGER_IS_SYSTEM(m)) - return 0; - - if (cg_unified() > 0) /* We don't need this anymore on the unified hierarchy */ - return 0; - - if (m->cgroups_agent_fd < 0) { - _cleanup_close_ int fd = -1; - - /* First free all secondary fields */ - m->cgroups_agent_event_source = sd_event_source_unref(m->cgroups_agent_event_source); - - fd = socket(AF_UNIX, SOCK_DGRAM|SOCK_CLOEXEC|SOCK_NONBLOCK, 0); - if (fd < 0) - return log_error_errno(errno, "Failed to allocate cgroups agent socket: %m"); - - fd_inc_rcvbuf(fd, CGROUPS_AGENT_RCVBUF_SIZE); - - (void) unlink(sa.un.sun_path); - - /* Only allow root to connect to this socket */ - RUN_WITH_UMASK(0077) - r = bind(fd, &sa.sa, SOCKADDR_UN_LEN(sa.un)); - if (r < 0) - return log_error_errno(errno, "bind(%s) failed: %m", sa.un.sun_path); - - m->cgroups_agent_fd = fd; - fd = -1; - } - - if (!m->cgroups_agent_event_source) { - r = sd_event_add_io(m->event, &m->cgroups_agent_event_source, m->cgroups_agent_fd, EPOLLIN, manager_dispatch_cgroups_agent_fd, m); - if (r < 0) - return log_error_errno(r, "Failed to allocate cgroups agent event source: %m"); - - /* Process cgroups notifications early, but after having processed service notification messages or - * SIGCHLD signals, so that a cgroup running empty is always just the last safety net of notification, - * and we collected the metadata the notification and SIGCHLD stuff offers first. Also see handling of - * cgroup inotify for the unified cgroup stuff. */ - r = sd_event_source_set_priority(m->cgroups_agent_event_source, SD_EVENT_PRIORITY_NORMAL-5); - if (r < 0) - return log_error_errno(r, "Failed to set priority of cgroups agent event source: %m"); - - (void) sd_event_source_set_description(m->cgroups_agent_event_source, "manager-cgroups-agent"); - } - - return 0; -} - -static int manager_setup_kdbus(Manager *m) { - _cleanup_free_ char *p = NULL; - - assert(m); - - if (m->test_run || m->kdbus_fd >= 0) - return 0; - if (!is_kdbus_available()) - return -ESOCKTNOSUPPORT; - - m->kdbus_fd = bus_kernel_create_bus( - MANAGER_IS_SYSTEM(m) ? "system" : "user", - MANAGER_IS_SYSTEM(m), &p); - - if (m->kdbus_fd < 0) - return log_debug_errno(m->kdbus_fd, "Failed to set up kdbus: %m"); - - log_debug("Successfully set up kdbus on %s", p); - - return 0; -} - -static int manager_connect_bus(Manager *m, bool reexecuting) { - bool try_bus_connect; - - assert(m); - - if (m->test_run) - return 0; - - try_bus_connect = - m->kdbus_fd >= 0 || - reexecuting || - (MANAGER_IS_USER(m) && 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); - - lookup_paths_flush_generator(&m->lookup_paths); - - 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->cgroups_agent_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->cgroups_agent_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; - 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) { - r = -ENOMEM; - goto fail; - } - - /* 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.search_path) { - _cleanup_closedir_ DIR *d = NULL; - struct dirent *de; - - d = opendir(*i); - if (!d) { - if (errno != ENOENT) - log_warning_errno(errno, "Failed to open directory %s, ignoring: %m", *i); - continue; - } - - FOREACH_DIRENT(de, d, r = -errno; goto fail) { - char *p; - - 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; - } - } - - return; - -fail: - log_warning_errno(r, "Failed to build unit path cache, proceeding without: %m"); - m->unit_path_cache = set_free_free(m->unit_path_cache); -} - -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); - - r = lookup_paths_init(&m->lookup_paths, m->unit_file_scope, 0, NULL); - if (r < 0) - return r; - - /* Make sure the transient directory always exists, so that it remains in the search path */ - r = mkdir_p_label(m->lookup_paths.transient, 0755); - if (r < 0) - return r; - - dual_timestamp_get(&m->generators_start_timestamp); - r = manager_run_generators(m); - dual_timestamp_get(&m->generators_finish_timestamp); - if (r < 0) - return r; - - lookup_paths_reduce(&m->lookup_paths); - 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; - - q = manager_setup_cgroups_agent(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)) { - if (unit_name_is_valid(name, UNIT_NAME_TEMPLATE)) - return sd_bus_error_setf(e, SD_BUS_ERROR_INVALID_ARGS, "Unit name %s is missing the instance name.", name); - - 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, 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 int manager_dispatch_cgroups_agent_fd(sd_event_source *source, int fd, uint32_t revents, void *userdata) { - Manager *m = userdata; - char buf[PATH_MAX+1]; - ssize_t n; - - n = recv(fd, buf, sizeof(buf), 0); - if (n < 0) - return log_error_errno(errno, "Failed to read cgroups agent message: %m"); - if (n == 0) { - log_error("Got zero-length cgroups agent message, ignoring."); - return 0; - } - if ((size_t) n >= sizeof(buf)) { - log_error("Got overly long cgroups agent message, ignoring."); - return 0; - } - - if (memchr(buf, 0, n)) { - log_error("Got cgroups agent message with embedded NUL byte, ignoring."); - return 0; - } - buf[n] = 0; - - manager_notify_cgroup_empty(m, buf); - bus_forward_agent_released(m, buf); - - return 0; -} - -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); - - if (UNIT_VTABLE(u)->sigchld_event) - 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 && MANAGER_IS_USER(m)) - ? LOG_DEBUG : LOG_INFO, - &sfsi); - - switch (sfsi.ssi_signo) { - - case SIGCHLD: - sigchld = true; - break; - - case SIGTERM: - if (MANAGER_IS_SYSTEM(m)) { - /* This is for compatibility with the - * original sysvinit */ - m->exit_code = MANAGER_REEXECUTE; - break; - } - - /* Fall through */ - - case SIGINT: - if (MANAGER_IS_SYSTEM(m)) { - - /* 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 (MANAGER_IS_SYSTEM(m)) - manager_start_target(m, SPECIAL_KBREQUEST_TARGET, JOB_REPLACE); - - /* This is a nop on non-init */ - break; - - case SIGPWR: - if (MANAGER_IS_SYSTEM(m)) - 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 (MANAGER_IS_USER(m)) { - 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->runtime_watchdog != USEC_INFINITY && MANAGER_IS_SYSTEM(m)) - 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->runtime_watchdog != USEC_INFINITY && MANAGER_IS_SYSTEM(m)) { - 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 (!MANAGER_IS_SYSTEM(m)) - 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 (MANAGER_IS_RELOADING(m)) - 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) { - static const union sockaddr_union sa = PLYMOUTH_SOCKET; - _cleanup_free_ char *message = NULL; - _cleanup_close_ int fd = -1; - int n = 0; - - /* Don't generate plymouth events if the service was already - * started and we're just deserializing */ - if (MANAGER_IS_RELOADING(m)) - return; - - if (!MANAGER_IS_SYSTEM(m)) - 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, SOCKADDR_UN_LEN(sa.un)) < 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 = MANAGER_IS_SYSTEM(m) ? "/run/systemd" : "/tmp"; - fd = open_tmpfile_unlinkable(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->cgroups_agent_fd >= 0) { - int copy; - - copy = fdset_put_dup(fds, m->cgroups_agent_fd); - if (copy < 0) - return copy; - - fprintf(f, "cgroups-agent-fd=%i\n", copy); - } - - 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, "cgroups-agent-fd=")) { - int fd; - - if (safe_atoi(l + 17, &fd) < 0 || fd < 0 || !fdset_contains(fds, fd)) - log_debug("Failed to parse cgroups agent fd: %s", l + 10); - else { - m->cgroups_agent_event_source = sd_event_source_unref(m->cgroups_agent_event_source); - safe_close(m->cgroups_agent_fd); - m->cgroups_agent_fd = fdset_remove(fds, fd); - } - - } 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); - lookup_paths_flush_generator(&m->lookup_paths); - lookup_paths_free(&m->lookup_paths); - - q = lookup_paths_init(&m->lookup_paths, m->unit_file_scope, 0, NULL); - if (q < 0 && r >= 0) - r = q; - - /* Find new unit paths */ - q = manager_run_generators(m); - if (q < 0 && r >= 0) - r = q; - - lookup_paths_reduce(&m->lookup_paths); - 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; - - q = manager_setup_cgroups_agent(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; -} - -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 (MANAGER_IS_SYSTEM(m) && 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 (MANAGER_IS_RELOADING(m)) - 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 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_binary_paths(m->unit_file_scope); - 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) { - if (access(*path, F_OK) >= 0) - goto found; - if (errno != ENOENT) - log_warning_errno(errno, "Failed to open generator directory %s: %m", *path); - } - - return 0; - - found: - r = lookup_paths_mkdir_generator(&m->lookup_paths); - if (r < 0) - goto finish; - - argv[0] = NULL; /* Leave this empty, execute_directory() will fill something in */ - argv[1] = m->lookup_paths.generator; - argv[2] = m->lookup_paths.generator_early; - argv[3] = m->lookup_paths.generator_late; - argv[4] = NULL; - - RUN_WITH_UMASK(0022) - execute_directories((const char* const*) paths, DEFAULT_TIMEOUT_USEC, (char**) argv); - -finish: - lookup_paths_trim_generator(&m->lookup_paths); - return r; -} - -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 (!MANAGER_IS_SYSTEM(m)) - 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 (!MANAGER_IS_SYSTEM(m)) - 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 (!MANAGER_IS_SYSTEM(m)) - 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 (!MANAGER_IS_SYSTEM(m)) - 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 MANAGER_IS_SYSTEM(m) ? - "/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 deleted file mode 100644 index 87186b6e40..0000000000 --- a/src/libcore/manager.h +++ /dev/null @@ -1,379 +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 "basic/cgroup-util.h" -#include "basic/fdset.h" -#include "basic/hashmap.h" -#include "basic/list.h" -#include "basic/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 "shared/path-lookup.h" -#include "show-status.h" -#include "basic/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 cgroups_agent_fd; - sd_event_source *cgroups_agent_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; - - UnitFileScope unit_file_scope; - 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; - - 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 */ - 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_io_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; /* tri-state */ -}; - -#define MANAGER_IS_SYSTEM(m) ((m)->unit_file_scope == UNIT_FILE_SYSTEM) -#define MANAGER_IS_USER(m) ((m)->unit_file_scope != UNIT_FILE_SYSTEM) - -#define MANAGER_IS_RELOADING(m) ((m)->n_reloading > 0) - -int manager_new(UnitFileScope scope, 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); - -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 deleted file mode 100644 index 09df45c567..0000000000 --- a/src/libcore/mount-setup.c +++ /dev/null @@ -1,413 +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 "basic/alloc-util.h" -#include "shared/bus-util.h" -#include "basic/cgroup-util.h" -#include "shared/dev-setup.h" -#include "shared/efivars.h" -#include "basic/label.h" -#include "basic/log.h" -#include "basic/macro.h" -#include "basic/missing.h" -#include "basic/mkdir.h" -#include "mount-setup.h" -#include "basic/mount-util.h" -#include "basic/path-util.h" -#include "basic/set.h" -#include "basic/smack-util.h" -#include "basic/strv.h" -#include "basic/user-util.h" -#include "basic/util.h" -#include "basic/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", "cgroup2", NULL, 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("/dev/shm", 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 deleted file mode 100644 index 647bd770ae..0000000000 --- a/src/libcore/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/libcore/mount.c b/src/libcore/mount.c deleted file mode 100644 index ab6efd4a9c..0000000000 --- a/src/libcore/mount.c +++ /dev/null @@ -1,1881 +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 "basic/alloc-util.h" -#include "dbus-mount.h" -#include "basic/escape.h" -#include "basic/exit-status.h" -#include "basic/formats-util.h" -#include "shared/fstab-util.h" -#include "basic/log.h" -#include "manager.h" -#include "basic/mkdir.h" -#include "mount-setup.h" -#include "basic/mount-util.h" -#include "mount.h" -#include "basic/parse-util.h" -#include "basic/path-util.h" -#include "basic/process-util.h" -#include "basic/special.h" -#include "basic/string-table.h" -#include "basic/string-util.h" -#include "basic/strv.h" -#include "basic/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_loop(const MountParameters *p) { - assert(p); - - if (fstab_test_option(p->options, "loop\0")) - return true; - - return false; -} - -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 mount_is_automount(const MountParameters *p) { - assert(p); - - return fstab_test_option(p->options, - "comment=systemd.automount\0" - "x-systemd.automount\0"); -} - -static bool mount_state_active(MountState state) { - return IN_SET(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); -} - -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 or a loop mount) to be - * available. */ - pm = get_mount_parameters_fragment(m); - if (pm && pm->what && - path_is_absolute(pm->what) && - (mount_is_bind(pm) || mount_is_loop(pm) || !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) && !mount_is_automount(p) && MANAGER_IS_SYSTEM(UNIT(m)->manager)) - 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 (!MANAGER_IS_SYSTEM(UNIT(m)->manager)) - 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_IN_SET(m->where, "/", "/usr") || - path_startswith(m->where, "/run/initramfs")) - 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 (!MANAGER_IS_SYSTEM(UNIT(m)->manager)) - return 0; - - /* We do not add any default dependencies to /, /usr or - * /run/initramfs/, 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_IN_SET(m->where, "/", "/usr") || - path_startswith(m->where, "/run/initramfs") || - 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 void mount_set_state(Mount *m, MountState state) { - MountState old_state; - assert(m); - - old_state = m->state; - m->state = state; - - if (!mount_state_active(state)) { - 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; - } - - 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) && - mount_state_active(new_state)) { - - 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, - .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); - int r; - - 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); - - r = unit_start_limit_test(u); - if (r < 0) { - mount_enter_dead(m, MOUNT_FAILURE_START_LIMIT_HIT); - return r; - } - - 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 (MANAGER_IS_SYSTEM(m)) { - 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 (MANAGER_IS_SYSTEM(m) && - 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 int mount_control_pid(Unit *u) { - Mount *m = MOUNT(u); - - assert(m); - - return m->control_pid; -} - -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", - [MOUNT_FAILURE_START_LIMIT_HIT] = "start-limit-hit", -}; - -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", - - .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, - - .control_pid = mount_control_pid, - - .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 deleted file mode 100644 index da529c44f4..0000000000 --- a/src/libcore/mount.h +++ /dev/null @@ -1,108 +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_FAILURE_START_LIMIT_HIT, - _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 deleted file mode 100644 index f7c5364437..0000000000 --- a/src/libcore/namespace.c +++ /dev/null @@ -1,650 +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 "basic/alloc-util.h" -#include "shared/dev-setup.h" -#include "basic/fd-util.h" -#include "loopback-setup.h" -#include "basic/missing.h" -#include "basic/mkdir.h" -#include "basic/mount-util.h" -#include "namespace.h" -#include "basic/path-util.h" -#include "basic/selinux-util.h" -#include "basic/socket-util.h" -#include "basic/string-table.h" -#include "basic/string-util.h" -#include "basic/strv.h" -#include "basic/umask-util.h" -#include "basic/user-util.h" -#include "basic/util.h" - -#define DEV_MOUNT_OPTIONS (MS_NOSUID|MS_STRICTATIME|MS_NOEXEC) - -typedef enum MountMode { - /* This is ordered by priority! */ - INACCESSIBLE, - READONLY, - PRIVATE_TMP, - PRIVATE_VAR_TMP, - PRIVATE_DEV, - 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", DEV_MOUNT_OPTIONS, "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); - - /* Unmount everything in old /dev */ - umount_recursive(m->path, 0); - 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 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); - - 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); - if (r == 0 && m->mode == PRIVATE_DEV) /* can be readonly but the submounts can't*/ - r = mount(NULL, m->path, NULL, MS_REMOUNT|DEV_MOUNT_OPTIONS|MS_RDONLY, NULL); - } 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, - 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 + - 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 (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 deleted file mode 100644 index 03097327dd..0000000000 --- a/src/libcore/namespace.h +++ /dev/null @@ -1,63 +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 "basic/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, - 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 deleted file mode 100644 index 782e085174..0000000000 --- a/src/libcore/path.c +++ /dev/null @@ -1,784 +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-bus/bus-error.h" -#include "shared/bus-util.h" -#include "dbus-path.h" -#include "basic/fd-util.h" -#include "basic/fs-util.h" -#include "basic/glob-util.h" -#include "basic/macro.h" -#include "basic/mkdir.h" -#include "path.h" -#include "basic/special.h" -#include "basic/stat-util.h" -#include "basic/string-table.h" -#include "basic/string-util.h" -#include "basic/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'; - - (void) 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 (MANAGER_IS_SYSTEM(UNIT(p)->manager)) { - 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, PATH_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; - int r; - - 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; - } - - r = unit_start_limit_test(u); - if (r < 0) { - path_enter_dead(p, PATH_FAILURE_START_LIMIT_HIT); - return r; - } - - 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", - [PATH_FAILURE_START_LIMIT_HIT] = "start-limit-hit", -}; - -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 deleted file mode 100644 index 4230c8fb99..0000000000 --- a/src/libcore/path.h +++ /dev/null @@ -1,93 +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_FAILURE_START_LIMIT_HIT, - _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 deleted file mode 100644 index baf1051375..0000000000 --- a/src/libcore/scope.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 "basic/alloc-util.h" -#include "dbus-scope.h" -#include "load-dropin.h" -#include "basic/log.h" -#include "scope.h" -#include "basic/special.h" -#include "basic/string-table.h" -#include "basic/string-util.h" -#include "basic/strv.h" -#include "basic/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; - u->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(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(u->manager)) - /* Refuse to load non-transient scope units, but allow them while reloading. */ - return -ENOENT; - - r = unit_load_fragment_and_dropin_optional(u); - if (r < 0) - return r; - - if (u->load_state == UNIT_LOADED) { - 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(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", - - .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 deleted file mode 100644 index 2dc86325c5..0000000000 --- a/src/libcore/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/libcore/selinux-access.c b/src/libcore/selinux-access.c deleted file mode 100644 index a259e128c1..0000000000 --- a/src/libcore/selinux-access.c +++ /dev/null @@ -1,283 +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 - -#include "basic/alloc-util.h" -#include "audit-fd.h" -#include "shared/bus-util.h" -#include "basic/log.h" -#include "basic/path-util.h" -#include "basic/selinux-util.h" -#include "basic/stdio-util.h" -#include "basic/strv.h" -#include "basic/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; - const char *fmt2; - -#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 - - fmt2 = strjoina("selinux: ", fmt); - - va_start(ap, fmt); - log_internalv(LOG_AUTH | callback_type_to_priority(type), 0, __FILE__, __LINE__, __FUNCTION__, fmt2, 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 deleted file mode 100644 index 1b1a8418ec..0000000000 --- a/src/libcore/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 - -#include "shared/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 deleted file mode 100644 index 37a143bce4..0000000000 --- a/src/libcore/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 "basic/log.h" -#include "basic/macro.h" -#include "selinux-setup.h" -#include "basic/selinux-util.h" -#include "basic/string-util.h" -#include "basic/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_raw(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 deleted file mode 100644 index 7b613249b0..0000000000 --- a/src/libcore/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/libcore/service.c b/src/libcore/service.c deleted file mode 100644 index 2845a841c3..0000000000 --- a/src/libcore/service.c +++ /dev/null @@ -1,3353 +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 "basic/alloc-util.h" -#include "basic/async.h" -#include "sd-bus/bus-error.h" -#include "sd-bus/bus-kernel.h" -#include "shared/bus-util.h" -#include "dbus-service.h" -#include "basic/def.h" -#include "basic/env-util.h" -#include "basic/escape.h" -#include "basic/exit-status.h" -#include "basic/fd-util.h" -#include "basic/fileio.h" -#include "basic/formats-util.h" -#include "basic/fs-util.h" -#include "load-dropin.h" -#include "load-fragment.h" -#include "basic/log.h" -#include "manager.h" -#include "basic/parse-util.h" -#include "basic/path-util.h" -#include "basic/process-util.h" -#include "service.h" -#include "basic/signal-util.h" -#include "basic/special.h" -#include "basic/string-table.h" -#include "basic/string-util.h" -#include "basic/strv.h" -#include "basic/unit-name.h" -#include "unit-printf.h" -#include "unit.h" -#include "basic/utf8.h" -#include "basic/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->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; -} - -void service_close_socket_fd(Service *s) { - assert(s); - - /* Undo the effect of service_set_socket_fd(). */ - - s->socket_fd = asynchronous_close(s->socket_fd); - - if (UNIT_ISSET(s->accept_socket)) { - 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); - - service_close_socket_fd(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 (MANAGER_IS_SYSTEM(UNIT(s)->manager)) { - /* 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 void 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; - - if (!s->guess_main_pid) - return; - - assert(s->main_pid <= 0); - - if (unit_search_main_pid(UNIT(s), &pid) < 0) - return; - - log_unit_debug(UNIT(s), "Main PID guessed: "PID_FMT, pid); - if (service_set_main_pid(s, pid) < 0) - return; - - 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); -} - -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); - - 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 && !MANAGER_IS_RELOADING(UNIT(s)->manager)) - 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_ 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, - .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 (!MANAGER_IS_SYSTEM(UNIT(s)->manager)) - 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; - - 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.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); - int r; - - 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)); - - /* Make sure we don't enter a busy loop of some kind. */ - r = unit_start_limit_test(u); - if (r < 0) { - service_enter_dead(s, SERVICE_FAILURE_START_LIMIT_HIT, false); - return r; - } - - 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; - - 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); - } - } - - 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, "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 - 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 - service_search_main_pid(s); - - service_enter_running(s, SERVICE_SUCCESS); - break; - - case SERVICE_RELOAD: - if (f == SERVICE_SUCCESS) - if (service_load_pid_file(s, true) < 0) - 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. We take ownership of the passed fd on success. */ - - 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; - } - - r = unit_add_two_dependencies(UNIT(sock), UNIT_BEFORE, UNIT_TRIGGERS, UNIT(s), false); - 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 0; -} - -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 int service_main_pid(Unit *u) { - Service *s = SERVICE(u); - - assert(s); - - return s->main_pid; -} - -static int service_control_pid(Unit *u) { - Service *s = SERVICE(u); - - assert(s); - - return s->control_pid; -} - -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", - [SERVICE_FAILURE_START_LIMIT_HIT] = "start-limit-hit", -}; - -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, - - .main_pid = service_main_pid, - .control_pid = service_control_pid, - - .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 deleted file mode 100644 index 66e2c387fe..0000000000 --- a/src/libcore/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 "basic/exit-status.h" -#include "kill.h" -#include "path.h" -#include "basic/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, /* a bit of a misnomer, just our catch-all error for errnos we didn't expect */ - SERVICE_FAILURE_TIMEOUT, - SERVICE_FAILURE_EXIT_CODE, - SERVICE_FAILURE_SIGNAL, - SERVICE_FAILURE_CORE_DUMP, - SERVICE_FAILURE_WATCHDOG, - SERVICE_FAILURE_START_LIMIT_HIT, - _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; - - 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); -void service_close_socket_fd(Service *s); - -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 deleted file mode 100644 index 03a3117f53..0000000000 --- a/src/libcore/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 "basic/alloc-util.h" -#include "basic/fd-util.h" -#include "basic/io-util.h" -#include "basic/parse-util.h" -#include "show-status.h" -#include "basic/string-util.h" -#include "basic/terminal-util.h" -#include "basic/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 deleted file mode 100644 index 56c56eb358..0000000000 --- a/src/libcore/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 "basic/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/slice.c b/src/libcore/slice.c deleted file mode 100644 index d9b3fa9525..0000000000 --- a/src/libcore/slice.c +++ /dev/null @@ -1,346 +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 "basic/alloc-util.h" -#include "dbus-slice.h" -#include "basic/log.h" -#include "slice.h" -#include "basic/special.h" -#include "basic/string-util.h" -#include "basic/strv.h" -#include "basic/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_init(Unit *u) { - assert(u); - assert(u->load_state == UNIT_STUB); - - u->ignore_on_isolate = true; -} - -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); - assert(u->load_state == UNIT_STUB); - - 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", - - .can_transient = true, - - .init = slice_init, - .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 deleted file mode 100644 index c9f3f61067..0000000000 --- a/src/libcore/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/libcore/smack-setup.c b/src/libcore/smack-setup.c deleted file mode 100644 index 16eb234202..0000000000 --- a/src/libcore/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 "basic/alloc-util.h" -#include "basic/dirent-util.h" -#include "basic/fd-util.h" -#include "basic/fileio.h" -#include "basic/log.h" -#include "basic/macro.h" -#include "smack-setup.h" -#include "basic/string-util.h" -#include "basic/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 deleted file mode 100644 index 78164c85e6..0000000000 --- a/src/libcore/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/libcore/socket.c b/src/libcore/socket.c deleted file mode 100644 index 7ee5edfef8..0000000000 --- a/src/libcore/socket.c +++ /dev/null @@ -1,2992 +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 "basic/alloc-util.h" -#include "sd-bus/bus-error.h" -#include "shared/bus-util.h" -#include "basic/copy.h" -#include "dbus-socket.h" -#include "basic/def.h" -#include "basic/exit-status.h" -#include "basic/fd-util.h" -#include "basic/formats-util.h" -#include "basic/io-util.h" -#include "basic/label.h" -#include "basic/log.h" -#include "basic/missing.h" -#include "basic/mkdir.h" -#include "basic/parse-util.h" -#include "basic/path-util.h" -#include "basic/process-util.h" -#include "basic/selinux-util.h" -#include "basic/signal-util.h" -#include "basic/smack-util.h" -#include "socket.h" -#include "basic/special.h" -#include "basic/string-table.h" -#include "basic/string-util.h" -#include "basic/strv.h" -#include "basic/unit-name.h" -#include "unit-printf.h" -#include "unit.h" -#include "basic/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; - - s->trigger_limit.interval = USEC_INFINITY; - s->trigger_limit.burst = (unsigned) -1; -} - -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; - - 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 (MANAGER_IS_SYSTEM(UNIT(s)->manager)) { - 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); - - /* Pick defaults for the trigger limit, if nothing was explicitly configured. We pick a relatively high limit - * in Accept=yes mode, and a lower limit for Accept=no. Reason: in Accept=yes mode we are invoking accept() - * ourselves before the trigger limit can hit, thus incoming connections are taken off the socket queue quickly - * and reliably. This is different for Accept=no, where the spawned service has to take the incoming traffic - * off the queues, which it might not necessarily do. Moreover, while Accept=no services are supposed to - * process whatever is queued in one go, and thus should normally never have to be started frequently. This is - * different for Accept=yes where each connection is processed by a new service instance, and thus frequent - * service starts are typical. */ - - if (s->trigger_limit.interval == USEC_INFINITY) - s->trigger_limit.interval = 2 * USEC_PER_SEC; - - if (s->trigger_limit.burst == (unsigned) -1) { - if (s->accept) - s->trigger_limit.burst = 200; - else - s->trigger_limit.burst = 20; - } - - 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, - "%sSocketUser: %s\n" - "%sSocketGroup: %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); - } - - fprintf(f, - "%sTriggerLimitIntervalSec: %s\n" - "%sTriggerLimitBurst: %u\n", - prefix, format_timespan(time_string, FORMAT_TIMESPAN_MAX, s->trigger_limit.interval, USEC_PER_SEC), - prefix, s->trigger_limit.burst); - - 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) { - bool was_open; - - was_open = p->fd >= 0; - - p->event_source = sd_event_source_unref(p->event_source); - 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 normally delete sockets in the file system before we create a new one, not after we - * stopped using one! That all said, if the user explicitly requested this, we'll delete them here - * anyway, but only then. */ - - if (!was_open || !s->remove_on_stop) - continue; - - switch (p->type) { - - case SOCKET_FIFO: - (void) unlink(p->path); - break; - - case SOCKET_MQUEUE: - (void) mq_unlink(p->path); - break; - - case SOCKET_SOCKET: - (void) socket_address_unlink(&p->address); - break; - - default: - break; - } - } - - if (s->remove_on_stop) - STRV_FOREACH(i, s->symlinks) - (void) 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_determine_selinux_label(Socket *s, char **ret) { - ExecCommand *c; - int r; - - assert(s); - assert(ret); - - if (s->selinux_context_from_net) { - /* If this is requested, get label from the network label */ - - r = mac_selinux_get_our_label(ret); - if (r == -EOPNOTSUPP) - goto no_label; - - } else { - /* Otherwise, get it from the executable we are about to start */ - r = socket_instantiate_service(s); - if (r < 0) - return r; - - if (!UNIT_ISSET(s->service)) - goto no_label; - - c = SERVICE(UNIT_DEREF(s->service))->exec_command[SERVICE_EXEC_START]; - if (!c) - goto no_label; - - r = mac_selinux_get_create_label_from_exe(c->path, ret); - if (r == -EPERM || r == -EOPNOTSUPP) - goto no_label; - } - - return r; - -no_label: - *ret = NULL; - return 0; -} - -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. */ - - r = socket_determine_selinux_label(s, &label); - if (r < 0) - goto rollback; - - know_label = true; - } - - /* Apply the socket protocol */ - switch (p->address.type) { - - case SOCK_STREAM: - case SOCK_SEQPACKET: - if (s->socket_protocol == IPPROTO_SCTP) - p->address.protocol = s->socket_protocol; - break; - - case SOCK_DGRAM: - if (s->socket_protocol == IPPROTO_UDPLITE) - p->address.protocol = s->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: { - _cleanup_free_ char *ep = NULL; - - ep = path_make_absolute("ep0", p->path); - - p->fd = usbffs_address_create(ep); - 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; -} - -enum { - SOCKET_OPEN_NONE, - SOCKET_OPEN_SOME, - SOCKET_OPEN_ALL, -}; - -static int socket_check_open(Socket *s) { - bool have_open = false, have_closed = false; - SocketPort *p; - - assert(s); - - LIST_FOREACH(port, p, s->ports) { - if (p->fd < 0) - have_closed = true; - else - have_open = true; - - if (have_open && have_closed) - return SOCKET_OPEN_SOME; - } - - if (have_open) - return SOCKET_OPEN_ALL; - - return SOCKET_OPEN_NONE; -} - -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)) { - - /* Originally, we used to simply reopen all sockets here that we didn't have file descriptors - * for. However, this is problematic, as we won't traverse throught the SOCKET_START_CHOWN state for - * them, and thus the UID/GID wouldn't be right. Hence, instead simply check if we have all fds open, - * and if there's a mismatch, warn loudly. */ - - r = socket_check_open(s); - if (r == SOCKET_OPEN_NONE) - log_unit_warning(UNIT(s), - "Socket unit configuration has changed while unit has been running, " - "no open socket file descriptor left. " - "The socket unit is not functional until restarted."); - else if (r == SOCKET_OPEN_SOME) - log_unit_warning(UNIT(s), - "Socket unit configuration has changed while unit has been running, " - "and some socket file descriptors have not been opened yet. " - "The socket unit is not fully functional until restarted."); - } - - 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, - .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 flush_ports(Socket *s) { - SocketPort *p; - - /* Flush all incoming traffic, regardless if actual bytes or new connections, so that this socket isn't busy - * anymore */ - - LIST_FOREACH(port, p, s->ports) { - if (p->fd < 0) - continue; - - (void) flush_accept(p->fd); - (void) flush_fd(p->fd); - } -} - -static void socket_enter_running(Socket *s, int cfd) { - _cleanup_(sd_bus_error_free) sd_bus_error error = SD_BUS_ERROR_NULL; - int r; - - /* Note that this call takes possession of the connection fd passed. It either has to assign it somewhere or - * close it. */ - - 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) - cfd = safe_close(cfd); - else - flush_ports(s); - - return; - } - - if (!ratelimit_test(&s->trigger_limit)) { - safe_close(cfd); - log_unit_warning(UNIT(s), "Trigger limit hit, refusing further activation."); - socket_enter_stop_pre(s, SOCKET_FAILURE_TRIGGER_LIMIT_HIT); - 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), refusing connection attempt.", 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. */ - log_unit_debug(UNIT(s), "Got ENOTCONN on incoming socket, assuming aborted connection attempt, ignoring."); - 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_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; /* We passed ownership of the fd to the service now. Forget it here. */ - s->n_connections++; - - r = manager_add_job(UNIT(s)->manager, JOB_START, UNIT(service), JOB_REPLACE, &error, NULL); - if (r < 0) { - /* We failed to activate the new service, but it still exists. Let's make sure the service - * closes and forgets the connection fd again, immediately. */ - service_close_socket_fd(service); - 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); - int r; - - 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); - - r = unit_start_limit_test(u); - if (r < 0) { - socket_enter_dead(s, SOCKET_FAILURE_START_LIMIT_HIT); - return r; - } - - 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); - - /* Filter out invocations with bogus state */ - if (other->load_state != UNIT_LOADED || other->type != UNIT_SERVICE) - return; - - /* Don't propagate state changes from the service if we are already down */ - if (!IN_SET(s->state, SOCKET_RUNNING, SOCKET_LISTENING)) - return; - - /* We don't care for the service state if we are in Accept=yes mode */ - if (s->accept) - return; - - /* Propagate start limit hit state */ - if (other->start_limit_hit) { - socket_enter_stop_pre(s, SOCKET_FAILURE_SERVICE_START_LIMIT_HIT); - return; - } - - /* Don't propagate anything if there's still a job queued */ - if (other->job) - 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 int socket_control_pid(Unit *u) { - Socket *s = SOCKET(u); - - assert(s); - - return s->control_pid; -} - -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_START_LIMIT_HIT] = "start-limit-hit", - [SOCKET_FAILURE_TRIGGER_LIMIT_HIT] = "trigger-limit-hit", - [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, - - .control_pid = socket_control_pid, - - .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 deleted file mode 100644 index 4f25eabd5d..0000000000 --- a/src/libcore/socket.h +++ /dev/null @@ -1,185 +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 "basic/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_START_LIMIT_HIT, - SOCKET_FAILURE_TRIGGER_LIMIT_HIT, - 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; - - RateLimit trigger_limit; -}; - -/* 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 deleted file mode 100644 index c08057a9fe..0000000000 --- a/src/libcore/swap.c +++ /dev/null @@ -1,1532 +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 "basic/alloc-util.h" -#include "dbus-swap.h" -#include "basic/escape.h" -#include "basic/exit-status.h" -#include "basic/fd-util.h" -#include "basic/formats-util.h" -#include "shared/fstab-util.h" -#include "basic/parse-util.h" -#include "basic/path-util.h" -#include "basic/process-util.h" -#include "basic/special.h" -#include "basic/string-table.h" -#include "basic/string-util.h" -#include "swap.h" -#include "shared/udev-util.h" -#include "basic/unit-name.h" -#include "unit.h" -#include "basic/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, MANAGER_IS_SYSTEM(UNIT(s)->manager), 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 (!MANAGER_IS_SYSTEM(UNIT(s)->manager)) - 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, - .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; - int r; - - 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; - - r = unit_start_limit_test(u); - if (r < 0) { - swap_enter_dead(s, SWAP_FAILURE_START_LIMIT_HIT); - return r; - } - - 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 int swap_control_pid(Unit *u) { - Swap *s = SWAP(u); - - assert(s); - - return s->control_pid; -} - -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", - [SWAP_FAILURE_START_LIMIT_HIT] = "start-limit-hit", -}; - -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", - - .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, - - .control_pid = swap_control_pid, - - .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 deleted file mode 100644 index fbf66debdc..0000000000 --- a/src/libcore/swap.h +++ /dev/null @@ -1,110 +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_FAILURE_START_LIMIT_HIT, - _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 deleted file mode 100644 index 25630b80e5..0000000000 --- a/src/libcore/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 "basic/log.h" -#include "basic/special.h" -#include "basic/string-util.h" -#include "basic/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 deleted file mode 100644 index 339aea154e..0000000000 --- a/src/libcore/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/libcore/timer.c b/src/libcore/timer.c deleted file mode 100644 index 45eec02dee..0000000000 --- a/src/libcore/timer.c +++ /dev/null @@ -1,859 +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 "basic/alloc-util.h" -#include "sd-bus/bus-error.h" -#include "shared/bus-util.h" -#include "dbus-timer.h" -#include "basic/fs-util.h" -#include "basic/parse-util.h" -#include "basic/random-util.h" -#include "basic/special.h" -#include "basic/string-table.h" -#include "basic/string-util.h" -#include "timer.h" -#include "basic/unit-name.h" -#include "unit.h" -#include "basic/user-util.h" -#include "basic/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 (MANAGER_IS_SYSTEM(UNIT(t)->manager)) { - 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 (MANAGER_IS_SYSTEM(UNIT(t)->manager)) { - - 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_or_monotonic()); - 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_or_monotonic() : 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; - int r; - - 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; - } - - r = unit_start_limit_test(u); - if (r < 0) { - timer_enter_dead(t, TIMER_FAILURE_START_LIMIT_HIT); - return r; - } - - 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", - [TIMER_FAILURE_START_LIMIT_HIT] = "start-limit-hit", -}; - -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 deleted file mode 100644 index 3773ec47e1..0000000000 --- a/src/libcore/timer.h +++ /dev/null @@ -1,89 +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 "basic/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_FAILURE_START_LIMIT_HIT, - _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 deleted file mode 100644 index a0ff69e2dc..0000000000 --- a/src/libcore/transaction.c +++ /dev/null @@ -1,1099 +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 "basic/alloc-util.h" -#include "sd-bus/bus-common-errors.h" -#include "sd-bus/bus-error.h" -#include "basic/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) { - const char *status; - /* 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)); - - if (log_get_show_color()) - status = ANSI_HIGHLIGHT_RED " SKIP " ANSI_NORMAL; - else - status = " SKIP "; - - unit_status_printf(delete->unit, status, - "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, 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 (MANAGER_IS_RELOADING(unit->manager)) - 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, -ERFKILL, -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 deleted file mode 100644 index 73489af0e6..0000000000 --- a/src/libcore/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 "basic/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/unit-printf.c b/src/libcore/unit-printf.c deleted file mode 100644 index c64d45e251..0000000000 --- a/src/libcore/unit-printf.c +++ /dev/null @@ -1,304 +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 "basic/alloc-util.h" -#include "basic/cgroup-util.h" -#include "basic/formats-util.h" -#include "basic/macro.h" -#include "shared/specifier.h" -#include "basic/string-util.h" -#include "basic/strv.h" -#include "basic/unit-name.h" -#include "unit-printf.h" -#include "unit.h" -#include "basic/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); - - e = manager_get_runtime_prefix(u->manager); - 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 deleted file mode 100644 index 4fc8531228..0000000000 --- a/src/libcore/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/libcore/unit.c b/src/libcore/unit.c deleted file mode 100644 index 3caa8baa26..0000000000 --- a/src/libcore/unit.c +++ /dev/null @@ -1,3818 +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 "basic/alloc-util.h" -#include "sd-bus/bus-common-errors.h" -#include "shared/bus-util.h" -#include "basic/cgroup-util.h" -#include "dbus-unit.h" -#include "dbus.h" -#include "shared/dropin.h" -#include "basic/escape.h" -#include "execute.h" -#include "basic/fileio-label.h" -#include "basic/formats-util.h" -#include "load-dropin.h" -#include "load-fragment.h" -#include "basic/log.h" -#include "basic/macro.h" -#include "basic/missing.h" -#include "basic/mkdir.h" -#include "basic/parse-util.h" -#include "basic/path-util.h" -#include "basic/process-util.h" -#include "basic/set.h" -#include "basic/signal-util.h" -#include "basic/special.h" -#include "basic/stat-util.h" -#include "basic/stdio-util.h" -#include "basic/string-util.h" -#include "basic/strv.h" -#include "basic/umask-util.h" -#include "basic/unit-name.h" -#include "unit.h" -#include "basic/user-util.h" -#include "basic/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->io_accounting = u->manager->default_io_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_type_may_template(t)) - 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_type_may_alias(t) && !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, *pp = NULL; - - p = dirname_malloc(*i); /* Get the drop-in directory from the drop-in file */ - if (!p) - continue; - - pp = dirname_malloc(p); /* Get the config directory from the drop-in directory */ - if (!pp) - continue; - - /* Only drop transient drop-ins */ - if (!path_equal(u->manager->lookup_paths.transient, pp)) - continue; - - (void) unlink(*i); - (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->transient_file) - fclose(u->transient_file); - - if (!MANAGER_IS_RELOADING(u->manager)) - 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 (!unit_type_may_alias(u->type)) /* Merging only applies to unit names that support aliases */ - return -EEXIST; - - 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) { - _cleanup_free_ char *s = NULL; - Unit *other; - 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, &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 (!MANAGER_IS_SYSTEM(u->manager)) - 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\tState 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_io_weight == CGROUP_WEIGHT_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 (u->transient_file) { - r = fflush_and_check(u->transient_file); - if (r < 0) - goto fail; - - fclose(u->transient_file); - u->transient_file = NULL; - - u->fragment_mtime = now(CLOCK_REALTIME); - } - - 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); -} - -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; - - 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; - - /* 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 (!MANAGER_IS_RELOADING(m)) { - 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, false); - 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, false); - } - - 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, false); - 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, false); - } - } - - 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, false); - else if (u->job->state == JOB_RUNNING && ns != UNIT_DEACTIVATING) { - unexpected = true; - job_finish_and_invalidate(u->job, JOB_FAILED, true, false); - } - - break; - - default: - assert_not_reached("Job type unknown"); - } - - } else - unexpected = true; - - if (!MANAGER_IS_RELOADING(m)) { - - /* 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) && - !MANAGER_IS_RELOADING(m)) { - /* 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) && - !MANAGER_IS_RELOADING(m)) { - - /* 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 (!MANAGER_IS_RELOADING(u->manager)) { - /* 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; - - /* Disallow slice changes if @u is already bound to cgroups */ - if (UNIT_ISSET(u->slice) && u->cgroup_realized) - return -EBUSY; - - unit_ref_unset(&u->slice); - 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 (MANAGER_IS_SYSTEM(u->manager)) - b = strjoin("system-", escaped, ".slice", NULL); - else - b = strappend(escaped, ".slice"); - if (!b) - return -ENOMEM; - - slice_name = b; - } else - slice_name = - MANAGER_IS_SYSTEM(u->manager) && !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, "'"); - - 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, - MANAGER_IS_SYSTEM(u->manager) ? 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; -} - -static bool fragment_mtime_newer(const char *path, usec_t mtime) { - struct stat st; - - if (!path) - return false; - - if (stat(path, &st) < 0) - /* What, cannot access this anymore? */ - return true; - - if (mtime > 0) - /* For non-empty files check the mtime */ - return timespec_load(&st.st_mtim) > mtime; - else if (!null_or_empty(&st)) - /* For masked files check if they are still so */ - return true; - - return false; -} - -bool unit_need_daemon_reload(Unit *u) { - _cleanup_strv_free_ char **t = NULL; - char **path; - - assert(u); - - if (fragment_mtime_newer(u->fragment_path, u->fragment_mtime)) - return true; - - if (fragment_mtime_newer(u->source_path, u->source_mtime)) - return true; - - (void) unit_find_dropin_paths(u, &t); - if (!strv_equal(u->dropin_paths, t)) - return true; - - STRV_FOREACH(path, u->dropin_paths) - if (fragment_mtime_newer(*path, u->dropin_mtime)) - return true; - - return false; -} - -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(SIGNAL_VALID(signo)); - - 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->unit_file_scope, - 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->unit_file_scope, - 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; - - /* We are about to drop a reference to the unit, make sure the garbage collection has a look at it as it might - * be unreferenced now. */ - unit_add_to_gc_queue(ref->unit); - - 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 (MANAGER_IS_USER(u->manager) && - !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 (MANAGER_IS_USER(u->manager) && - (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 const char* unit_drop_in_dir(Unit *u, UnitSetPropertiesMode mode) { - assert(u); - - if (!IN_SET(mode, UNIT_RUNTIME, UNIT_PERSISTENT)) - return NULL; - - if (u->transient) /* Redirect drop-ins for transient units always into the transient directory. */ - return u->manager->lookup_paths.transient; - - if (mode == UNIT_RUNTIME) - return u->manager->lookup_paths.runtime_control; - - if (mode == UNIT_PERSISTENT) - return u->manager->lookup_paths.persistent_control; - - return NULL; -} - -int unit_write_drop_in(Unit *u, UnitSetPropertiesMode mode, const char *name, const char *data) { - _cleanup_free_ char *p = NULL, *q = NULL; - const char *dir, *prefixed; - int r; - - assert(u); - - if (u->transient_file) { - /* When this is a transient unit file in creation, then let's not create a new drop-in but instead - * write to the transient unit file. */ - fputs(data, u->transient_file); - return 0; - } - - if (!IN_SET(mode, UNIT_PERSISTENT, UNIT_RUNTIME)) - return 0; - - dir = unit_drop_in_dir(u, mode); - if (!dir) - return -EINVAL; - - prefixed = strjoina("# This is a drop-in unit file extension, created via \"systemctl set-property\" or an equivalent operation. Do not edit.\n", - data); - - r = drop_in_file(dir, u->id, 50, name, &p, &q); - if (r < 0) - return r; - - (void) mkdir_p(p, 0755); - r = write_string_file_atomic_label(q, prefixed); - if (r < 0) - return r; - - r = strv_push(&u->dropin_paths, q); - if (r < 0) - return r; - q = NULL; - - 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) { - const char *ndata; - - 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 = strjoina("[", UNIT_VTABLE(u)->private_section, "]\n", data); - - 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) { - FILE *f; - char *path; - - assert(u); - - if (!UNIT_VTABLE(u)->can_transient) - return -EOPNOTSUPP; - - path = strjoin(u->manager->lookup_paths.transient, "/", u->id, NULL); - if (!path) - return -ENOMEM; - - /* Let's open the file we'll write the transient settings into. This file is kept open as long as we are - * creating the transient, and is closed in unit_load(), as soon as we start loading the file. */ - - RUN_WITH_UMASK(0022) { - f = fopen(path, "we"); - if (!f) { - free(path); - return -errno; - } - } - - if (u->transient_file) - fclose(u->transient_file); - u->transient_file = f; - - free(u->fragment_path); - u->fragment_path = 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; - - u->load_state = UNIT_STUB; - u->load_error = 0; - u->transient = true; - - unit_add_to_dbus_queue(u); - unit_add_to_gc_queue(u); - unit_add_to_load_queue(u); - - fputs("# This is a transient unit file, created programmatically via the systemd API. Do not edit.\n", - u->transient_file); - - 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); -} - -pid_t unit_control_pid(Unit *u) { - assert(u); - - if (UNIT_VTABLE(u)->control_pid) - return UNIT_VTABLE(u)->control_pid(u); - - return 0; -} - -pid_t unit_main_pid(Unit *u) { - assert(u); - - if (UNIT_VTABLE(u)->main_pid) - return UNIT_VTABLE(u)->main_pid(u); - - return 0; -} diff --git a/src/libcore/unit.h b/src/libcore/unit.h deleted file mode 100644 index df1174b837..0000000000 --- a/src/libcore/unit.h +++ /dev/null @@ -1,639 +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 "shared/condition.h" -#include "failure-action.h" -#include "shared/install.h" -#include "basic/list.h" -#include "basic/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 this is a transient unit we are currently writing, this is where we are writing it to */ - FILE *transient_file; - - /* 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_enabled_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); - - /* Returns the main PID if there is any defined, or 0. */ - pid_t (*main_pid)(Unit *u); - - /* Returns the main PID if there is any defined, or 0. */ - pid_t (*control_pid)(Unit *u); - - /* 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; - - /* 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); - -pid_t unit_control_pid(Unit *u); -pid_t unit_main_pid(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); - -int unit_start_limit_test(Unit *u); - -/* 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/libsystemd-network/include/systemd-network/_sd-common.h b/src/libsystemd-network/include/systemd-network/_sd-common.h new file mode 120000 index 0000000000..653327c267 --- /dev/null +++ b/src/libsystemd-network/include/systemd-network/_sd-common.h @@ -0,0 +1 @@ +../../../libsystemd/include/systemd/_sd-common.h \ No newline at end of file diff --git a/src/libsystemd-network/include/systemd-network/sd-dhcp-client.h b/src/libsystemd-network/include/systemd-network/sd-dhcp-client.h index ca254e5f9c..cc51933b1b 100644 --- a/src/libsystemd-network/include/systemd-network/sd-dhcp-client.h +++ b/src/libsystemd-network/include/systemd-network/sd-dhcp-client.h @@ -28,7 +28,7 @@ #include "systemd-network/sd-dhcp-lease.h" #include -#include +#include "_sd-common.h" _SD_BEGIN_DECLARATIONS; diff --git a/src/libsystemd-network/include/systemd-network/sd-dhcp-lease.h b/src/libsystemd-network/include/systemd-network/sd-dhcp-lease.h index 014288b8c1..2f565ca825 100644 --- a/src/libsystemd-network/include/systemd-network/sd-dhcp-lease.h +++ b/src/libsystemd-network/include/systemd-network/sd-dhcp-lease.h @@ -26,7 +26,7 @@ #include #include -#include +#include "_sd-common.h" _SD_BEGIN_DECLARATIONS; diff --git a/src/libsystemd-network/include/systemd-network/sd-dhcp-server.h b/src/libsystemd-network/include/systemd-network/sd-dhcp-server.h index e433d8562e..bbb2bb203c 100644 --- a/src/libsystemd-network/include/systemd-network/sd-dhcp-server.h +++ b/src/libsystemd-network/include/systemd-network/sd-dhcp-server.h @@ -26,7 +26,7 @@ #include -#include +#include "_sd-common.h" _SD_BEGIN_DECLARATIONS; diff --git a/src/libsystemd-network/include/systemd-network/sd-dhcp6-client.h b/src/libsystemd-network/include/systemd-network/sd-dhcp6-client.h index 37243ef707..dd6cb32aae 100644 --- a/src/libsystemd-network/include/systemd-network/sd-dhcp6-client.h +++ b/src/libsystemd-network/include/systemd-network/sd-dhcp6-client.h @@ -27,7 +27,7 @@ #include "systemd-network/sd-dhcp6-lease.h" #include -#include +#include "_sd-common.h" _SD_BEGIN_DECLARATIONS; diff --git a/src/libsystemd-network/include/systemd-network/sd-dhcp6-lease.h b/src/libsystemd-network/include/systemd-network/sd-dhcp6-lease.h index 3e633548d0..184fbb8e0d 100644 --- a/src/libsystemd-network/include/systemd-network/sd-dhcp6-lease.h +++ b/src/libsystemd-network/include/systemd-network/sd-dhcp6-lease.h @@ -24,7 +24,7 @@ #include #include -#include +#include "_sd-common.h" _SD_BEGIN_DECLARATIONS; diff --git a/src/libsystemd-network/include/systemd-network/sd-ipv4acd.h b/src/libsystemd-network/include/systemd-network/sd-ipv4acd.h index 8246b5b267..93db7a4a6c 100644 --- a/src/libsystemd-network/include/systemd-network/sd-ipv4acd.h +++ b/src/libsystemd-network/include/systemd-network/sd-ipv4acd.h @@ -26,7 +26,7 @@ #include -#include +#include "_sd-common.h" _SD_BEGIN_DECLARATIONS; diff --git a/src/libsystemd-network/include/systemd-network/sd-ipv4ll.h b/src/libsystemd-network/include/systemd-network/sd-ipv4ll.h index ab7233a856..9167623167 100644 --- a/src/libsystemd-network/include/systemd-network/sd-ipv4ll.h +++ b/src/libsystemd-network/include/systemd-network/sd-ipv4ll.h @@ -25,7 +25,7 @@ #include -#include +#include "_sd-common.h" _SD_BEGIN_DECLARATIONS; diff --git a/src/libsystemd-network/include/systemd-network/sd-lldp.h b/src/libsystemd-network/include/systemd-network/sd-lldp.h index b611fdd37c..391e7c2a2e 100644 --- a/src/libsystemd-network/include/systemd-network/sd-lldp.h +++ b/src/libsystemd-network/include/systemd-network/sd-lldp.h @@ -26,7 +26,7 @@ #include -#include +#include "_sd-common.h" _SD_BEGIN_DECLARATIONS; diff --git a/src/libsystemd-network/include/systemd-network/sd-ndisc.h b/src/libsystemd-network/include/systemd-network/sd-ndisc.h index 42ef961dac..c77a435d17 100644 --- a/src/libsystemd-network/include/systemd-network/sd-ndisc.h +++ b/src/libsystemd-network/include/systemd-network/sd-ndisc.h @@ -25,7 +25,7 @@ #include -#include +#include "_sd-common.h" _SD_BEGIN_DECLARATIONS; diff --git a/src/libsystemd/Makefile b/src/libsystemd/Makefile index a193f4592d..05d928f493 100644 --- a/src/libsystemd/Makefile +++ b/src/libsystemd/Makefile @@ -64,7 +64,7 @@ pkginclude_HEADERS += \ src/systemd/sd-id128.h \ src/systemd/sd-daemon.h -lib_LTLIBRARIES += \ +rootlib_LTLIBRARIES += \ libsystemd.la # ------------------------------------------------------------------------------ diff --git a/src/libudev/src/Makefile b/src/libudev/src/Makefile index 77fc1fe6aa..2bc5227cfb 100644 --- a/src/libudev/src/Makefile +++ b/src/libudev/src/Makefile @@ -30,7 +30,7 @@ LIBUDEV_AGE=6 include_HEADERS += \ src/libudev/libudev.h -lib_LTLIBRARIES += \ +rootlib_LTLIBRARIES += \ libudev.la libudev_la_SOURCES =\ diff --git a/src/systemd-ac-power/Makefile b/src/systemd-ac-power/Makefile deleted file mode 100644 index aa8912d711..0000000000 --- a/src/systemd-ac-power/Makefile +++ /dev/null @@ -1,33 +0,0 @@ -# -*- 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-ac-power -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/systemd-ac-power/ac-power.c b/src/systemd-ac-power/ac-power.c deleted file mode 100644 index 945c318f66..0000000000 --- a/src/systemd-ac-power/ac-power.c +++ /dev/null @@ -1,35 +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 "basic/util.h" - -int main(int argc, char *argv[]) { - int r; - - /* This is mostly intended to be used for scripts which want - * to detect whether AC power is plugged in or not. */ - - r = on_ac_power(); - if (r < 0) { - log_error_errno(r, "Failed to read AC status: %m"); - return EXIT_FAILURE; - } - - return r != 0 ? EXIT_SUCCESS : EXIT_FAILURE; -} diff --git a/src/systemd-activate/Makefile b/src/systemd-activate/Makefile deleted file mode 100644 index 5e2299f00e..0000000000 --- a/src/systemd-activate/Makefile +++ /dev/null @@ -1,36 +0,0 @@ -# -*- 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 - - -bin_PROGRAMS += \ - systemd-socket-activate - -systemd_socket_activate_SOURCES = \ - src/activate/activate.c - -systemd_socket_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 deleted file mode 100644 index 2ad205af17..0000000000 --- a/src/systemd-activate/activate.c +++ /dev/null @@ -1,545 +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 "basic/alloc-util.h" -#include "basic/escape.h" -#include "basic/fd-util.h" -#include "basic/log.h" -#include "basic/macro.h" -#include "basic/signal-util.h" -#include "basic/socket-util.h" -#include "basic/string-util.h" -#include "basic/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 char **arg_fdnames = 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; - 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_fdnames) { - _cleanup_free_ char *names = NULL; - size_t len; - char *e; - int i; - - len = strv_length(arg_fdnames); - if (len == 1) - for (i = 1; i < n_fds; i++) { - r = strv_extend(&arg_fdnames, arg_fdnames[0]); - if (r < 0) - return log_error_errno(r, "Failed to extend strv: %m"); - } - else if (len != (unsigned) n_fds) - log_warning("The number of fd names is different than number of fds: %zu vs %d", - len, n_fds); - - names = strv_join(arg_fdnames, ":"); - if (!names) - return log_oom(); - - e = strappend("LISTEN_FDNAMES=", names); - if (!e) - return log_oom(); - - 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) { - PROTECT_ERRNO; - - for (;;) { - siginfo_t si; - int r; - - si.si_pid = 0; - r = waitid(P_ALL, 0, &si, WEXITED|WNOHANG); - if (r < 0) { - if (errno != ECHILD) - log_error_errno(errno, "Failed to reap children: %m"); - return; - } - if (si.si_pid == 0) - return; - - log_info("Child %d died with code %d", si.si_pid, si.si_status); - } -} - -static int install_chld_handler(void) { - static const struct sigaction act = { - .sa_flags = SA_NOCLDSTOP, - .sa_handler = 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" - " --fdname=NAME[:NAME...] Specify names for file descriptors\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:aE:d", 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: { - _cleanup_strv_free_ char **names; - char **s; - - names = strv_split(optarg, ":"); - if (!names) - return log_oom(); - - STRV_FOREACH(s, names) - if (!fdname_is_valid(*s)) { - _cleanup_free_ char *esc; - - esc = cescape(*s); - log_warning("File descriptor name \"%s\" is not valid.", esc); - } - - /* Empty optargs means one empty name */ - r = strv_extend_strv(&arg_fdnames, - strv_isempty(names) ? STRV_MAKE("") : names, - false); - if (r < 0) - return log_error_errno(r, "strv_extend_strv: %m"); - 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-analyze/.gitignore b/src/systemd-analyze/.gitignore deleted file mode 100644 index 752ea236c8..0000000000 --- a/src/systemd-analyze/.gitignore +++ /dev/null @@ -1 +0,0 @@ -/systemd-analyze diff --git a/src/systemd-analyze/Makefile b/src/systemd-analyze/Makefile deleted file mode 100644 index 2e4e7c0bb1..0000000000 --- a/src/systemd-analyze/Makefile +++ /dev/null @@ -1,40 +0,0 @@ -# -*- 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 - -bin_PROGRAMS += systemd-analyze -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 deleted file mode 100644 index 7553574f53..0000000000 --- a/src/systemd-analyze/analyze-verify.c +++ /dev/null @@ -1,304 +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 "basic/alloc-util.h" -#include "analyze-verify.h" -#include "sd-bus/bus-error.h" -#include "shared/bus-util.h" -#include "basic/log.h" -#include "manager.h" -#include "shared/pager.h" -#include "basic/path-util.h" -#include "basic/strv.h" -#include "basic/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, UnitFileScope scope, bool check_man) { - _cleanup_(sd_bus_error_free) sd_bus_error err = SD_BUS_ERROR_NULL; - _cleanup_free_ char *var = NULL; - Manager *m = NULL; - FILE *serial = NULL; - FDSet *fdset = 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(scope, 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 deleted file mode 100644 index 7b89007fd0..0000000000 --- a/src/systemd-analyze/analyze-verify.h +++ /dev/null @@ -1,26 +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 - -#include "shared/path-lookup.h" - -int verify_units(char **filenames, UnitFileScope scope, bool check_man); diff --git a/src/systemd-analyze/analyze.c b/src/systemd-analyze/analyze.c deleted file mode 100644 index 1ce700ccba..0000000000 --- a/src/systemd-analyze/analyze.c +++ /dev/null @@ -1,1485 +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 - -#include "basic/alloc-util.h" -#include "analyze-verify.h" -#include "sd-bus/bus-error.h" -#include "shared/bus-unit-util.h" -#include "shared/bus-util.h" -#include "basic/glob-util.h" -#include "basic/hashmap.h" -#include "basic/locale-util.h" -#include "basic/log.h" -#include "shared/pager.h" -#include "basic/parse-util.h" -#include "basic/special.h" -#include "basic/strv.h" -#include "basic/strxcpyx.h" -#include "basic/terminal-util.h" -#include "basic/unit-name.h" -#include "basic/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 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", special_glyph(branches & (1 << (i-1)) ? TREE_VERTICAL : TREE_SPACE)); - - printf("%s", special_glyph(last ? TREE_RIGHT : 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(arg_no_pager, false); - - 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(arg_no_pager, false); - - 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(arg_no_pager, false); - - 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(arg_no_pager, false); - - 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 ? UNIT_FILE_USER : UNIT_FILE_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-delta/Makefile b/src/systemd-delta/Makefile deleted file mode 100644 index 62c18cd8d4..0000000000 --- a/src/systemd-delta/Makefile +++ /dev/null @@ -1,33 +0,0 @@ -# -*- 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 - -bin_PROGRAMS += systemd-delta -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 deleted file mode 100644 index dd571e7195..0000000000 --- a/src/systemd-delta/delta.c +++ /dev/null @@ -1,635 +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 "basic/alloc-util.h" -#include "basic/dirent-util.h" -#include "basic/fd-util.h" -#include "basic/fs-util.h" -#include "basic/hashmap.h" -#include "basic/locale-util.h" -#include "basic/log.h" -#include "shared/pager.h" -#include "basic/parse-util.h" -#include "basic/path-util.h" -#include "basic/process-util.h" -#include "basic/signal-util.h" -#include "basic/stat-util.h" -#include "basic/string-util.h" -#include "basic/strv.h" -#include "basic/terminal-util.h" -#include "basic/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 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, special_glyph(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, special_glyph(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, special_glyph(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, special_glyph(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, special_glyph(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, special_glyph(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, special_glyph(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, special_glyph(ARROW), basename(p), special_glyph(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), special_glyph(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), special_glyph(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(arg_no_pager, false); - - 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 deleted file mode 100644 index f094a7e2a0..0000000000 --- a/src/systemd-escape/Makefile +++ /dev/null @@ -1,34 +0,0 @@ -# -*- 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 - -rootbin_PROGRAMS += systemd-escape - -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 deleted file mode 100644 index 479ea0e87c..0000000000 --- a/src/systemd-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 "basic/alloc-util.h" -#include "basic/log.h" -#include "basic/string-util.h" -#include "basic/strv.h" -#include "basic/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-firstboot/Makefile b/src/systemd-firstboot/Makefile deleted file mode 100644 index 09d9ebb1d7..0000000000 --- a/src/systemd-firstboot/Makefile +++ /dev/null @@ -1,47 +0,0 @@ -# -*- 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 # ENABLE_FIRSTBOOT - -EXTRA_DIST += \ - units/systemd-firstboot.service.in - -include $(topsrcdir)/build-aux/Makefile.tail.mk diff --git a/src/systemd-firstboot/firstboot.c b/src/systemd-firstboot/firstboot.c deleted file mode 100644 index 46e82a2a7b..0000000000 --- a/src/systemd-firstboot/firstboot.c +++ /dev/null @@ -1,870 +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 "basic/alloc-util.h" -#include "shared/ask-password-api.h" -#include "basic/copy.h" -#include "basic/fd-util.h" -#include "basic/fileio.h" -#include "basic/fs-util.h" -#include "basic/hostname-util.h" -#include "basic/locale-util.h" -#include "basic/mkdir.h" -#include "basic/parse-util.h" -#include "basic/path-util.h" -#include "basic/random-util.h" -#include "basic/string-util.h" -#include "basic/strv.h" -#include "basic/terminal-util.h" -#include "basic/time-util.h" -#include "basic/umask-util.h" -#include "basic/user-util.h" - -static char *arg_root = NULL; -static char *arg_locale = NULL; /* $LANG */ -static char *arg_locale_messages = NULL; /* $LC_MESSAGES */ -static char *arg_timezone = NULL; -static char *arg_hostname = NULL; -static sd_id128_t arg_machine_id = {}; -static char *arg_root_password = NULL; -static bool arg_prompt_locale = false; -static bool arg_prompt_timezone = false; -static bool arg_prompt_hostname = false; -static bool arg_prompt_root_password = false; -static bool arg_copy_locale = false; -static bool arg_copy_timezone = false; -static bool arg_copy_root_password = false; - -static bool press_any_key(void) { - char k = 0; - bool need_nl = true; - - printf("-- Press any key to proceed --"); - fflush(stdout); - - (void) read_one_char(stdin, &k, USEC_INFINITY, &need_nl); - - if (need_nl) - putchar('\n'); - - return k != 'q'; -} - -static void print_welcome(void) { - _cleanup_free_ char *pretty_name = NULL; - const char *os_release = NULL; - static bool done = false; - int r; - - if (done) - return; - - os_release = prefix_roota(arg_root, "/etc/os-release"); - r = parse_env_file(os_release, NEWLINE, - "PRETTY_NAME", &pretty_name, - NULL); - if (r == -ENOENT) { - - os_release = prefix_roota(arg_root, "/usr/lib/os-release"); - r = parse_env_file(os_release, NEWLINE, - "PRETTY_NAME", &pretty_name, - NULL); - } - - if (r < 0 && r != -ENOENT) - log_warning_errno(r, "Failed to read os-release file: %m"); - - printf("\nWelcome to your new installation of %s!\nPlease configure a few basic system settings:\n\n", - isempty(pretty_name) ? "GNU/Linux" : pretty_name); - - press_any_key(); - - done = true; -} - -static int show_menu(char **x, unsigned n_columns, unsigned width, unsigned percentage) { - unsigned n, per_column, i, j; - unsigned break_lines, break_modulo; - - assert(n_columns > 0); - - n = strv_length(x); - per_column = (n + n_columns - 1) / n_columns; - - break_lines = lines(); - if (break_lines > 2) - break_lines--; - - /* The first page gets two extra lines, since we want to show - * a title */ - break_modulo = break_lines; - if (break_modulo > 3) - break_modulo -= 3; - - for (i = 0; i < per_column; i++) { - - for (j = 0; j < n_columns; j ++) { - _cleanup_free_ char *e = NULL; - - if (j * per_column + i >= n) - break; - - e = ellipsize(x[j * per_column + i], width, percentage); - if (!e) - return log_oom(); - - printf("%4u) %-*s", j * per_column + i + 1, width, e); - } - - putchar('\n'); - - /* on the first screen we reserve 2 extra lines for the title */ - if (i % break_lines == break_modulo) { - if (!press_any_key()) - return 0; - } - } - - return 0; -} - -static int prompt_loop(const char *text, char **l, bool (*is_valid)(const char *name), char **ret) { - int r; - - assert(text); - assert(is_valid); - assert(ret); - - for (;;) { - _cleanup_free_ char *p = NULL; - unsigned u; - - r = ask_string(&p, "%s %s (empty to skip): ", special_glyph(TRIANGULAR_BULLET), text); - if (r < 0) - return log_error_errno(r, "Failed to query user: %m"); - - if (isempty(p)) { - log_warning("No data entered, skipping."); - return 0; - } - - r = safe_atou(p, &u); - if (r >= 0) { - char *c; - - if (u <= 0 || u > strv_length(l)) { - log_error("Specified entry number out of range."); - continue; - } - - log_info("Selected '%s'.", l[u-1]); - - c = strdup(l[u-1]); - if (!c) - return log_oom(); - - free(*ret); - *ret = c; - return 0; - } - - if (!is_valid(p)) { - log_error("Entered data invalid."); - continue; - } - - free(*ret); - *ret = p; - p = 0; - return 0; - } -} - -static int prompt_locale(void) { - _cleanup_strv_free_ char **locales = NULL; - int r; - - if (arg_locale || arg_locale_messages) - return 0; - - if (!arg_prompt_locale) - return 0; - - r = get_locales(&locales); - if (r < 0) - return log_error_errno(r, "Cannot query locales list: %m"); - - print_welcome(); - - printf("\nAvailable Locales:\n\n"); - r = show_menu(locales, 3, 22, 60); - if (r < 0) - return r; - - putchar('\n'); - - r = prompt_loop("Please enter system locale name or number", locales, locale_is_valid, &arg_locale); - if (r < 0) - return r; - - if (isempty(arg_locale)) - return 0; - - r = prompt_loop("Please enter system message locale name or number", locales, locale_is_valid, &arg_locale_messages); - if (r < 0) - return r; - - return 0; -} - -static int process_locale(void) { - const char *etc_localeconf; - char* locales[3]; - unsigned i = 0; - int r; - - etc_localeconf = prefix_roota(arg_root, "/etc/locale.conf"); - if (laccess(etc_localeconf, F_OK) >= 0) - return 0; - - if (arg_copy_locale && arg_root) { - - mkdir_parents(etc_localeconf, 0755); - r = copy_file("/etc/locale.conf", etc_localeconf, 0, 0644, 0); - if (r != -ENOENT) { - if (r < 0) - return log_error_errno(r, "Failed to copy %s: %m", etc_localeconf); - - log_info("%s copied.", etc_localeconf); - return 0; - } - } - - r = prompt_locale(); - if (r < 0) - return r; - - if (!isempty(arg_locale)) - locales[i++] = strjoina("LANG=", arg_locale); - if (!isempty(arg_locale_messages) && !streq(arg_locale_messages, arg_locale)) - locales[i++] = strjoina("LC_MESSAGES=", arg_locale_messages); - - if (i == 0) - return 0; - - locales[i] = NULL; - - mkdir_parents(etc_localeconf, 0755); - r = write_env_file(etc_localeconf, locales); - if (r < 0) - return log_error_errno(r, "Failed to write %s: %m", etc_localeconf); - - log_info("%s written.", etc_localeconf); - return 0; -} - -static int prompt_timezone(void) { - _cleanup_strv_free_ char **zones = NULL; - int r; - - if (arg_timezone) - return 0; - - if (!arg_prompt_timezone) - return 0; - - r = get_timezones(&zones); - if (r < 0) - return log_error_errno(r, "Cannot query timezone list: %m"); - - print_welcome(); - - printf("\nAvailable Time Zones:\n\n"); - r = show_menu(zones, 3, 22, 30); - if (r < 0) - return r; - - putchar('\n'); - - r = prompt_loop("Please enter timezone name or number", zones, timezone_is_valid, &arg_timezone); - if (r < 0) - return r; - - return 0; -} - -static int process_timezone(void) { - const char *etc_localtime, *e; - int r; - - etc_localtime = prefix_roota(arg_root, "/etc/localtime"); - if (laccess(etc_localtime, F_OK) >= 0) - return 0; - - if (arg_copy_timezone && arg_root) { - _cleanup_free_ char *p = NULL; - - r = readlink_malloc("/etc/localtime", &p); - if (r != -ENOENT) { - if (r < 0) - return log_error_errno(r, "Failed to read host timezone: %m"); - - mkdir_parents(etc_localtime, 0755); - if (symlink(p, etc_localtime) < 0) - return log_error_errno(errno, "Failed to create %s symlink: %m", etc_localtime); - - log_info("%s copied.", etc_localtime); - return 0; - } - } - - r = prompt_timezone(); - if (r < 0) - return r; - - if (isempty(arg_timezone)) - return 0; - - e = strjoina("../usr/share/zoneinfo/", arg_timezone); - - mkdir_parents(etc_localtime, 0755); - if (symlink(e, etc_localtime) < 0) - return log_error_errno(errno, "Failed to create %s symlink: %m", etc_localtime); - - log_info("%s written", etc_localtime); - return 0; -} - -static int prompt_hostname(void) { - int r; - - if (arg_hostname) - return 0; - - if (!arg_prompt_hostname) - return 0; - - print_welcome(); - putchar('\n'); - - for (;;) { - _cleanup_free_ char *h = NULL; - - r = ask_string(&h, "%s Please enter hostname for new system (empty to skip): ", special_glyph(TRIANGULAR_BULLET)); - if (r < 0) - return log_error_errno(r, "Failed to query hostname: %m"); - - if (isempty(h)) { - log_warning("No hostname entered, skipping."); - break; - } - - if (!hostname_is_valid(h, true)) { - log_error("Specified hostname invalid."); - continue; - } - - /* Get rid of the trailing dot that we allow, but don't want to see */ - arg_hostname = hostname_cleanup(h); - h = NULL; - break; - } - - return 0; -} - -static int process_hostname(void) { - const char *etc_hostname; - int r; - - etc_hostname = prefix_roota(arg_root, "/etc/hostname"); - if (laccess(etc_hostname, F_OK) >= 0) - return 0; - - r = prompt_hostname(); - if (r < 0) - return r; - - if (isempty(arg_hostname)) - return 0; - - mkdir_parents(etc_hostname, 0755); - r = write_string_file(etc_hostname, arg_hostname, WRITE_STRING_FILE_CREATE); - if (r < 0) - return log_error_errno(r, "Failed to write %s: %m", etc_hostname); - - log_info("%s written.", etc_hostname); - return 0; -} - -static int process_machine_id(void) { - const char *etc_machine_id; - char id[SD_ID128_STRING_MAX]; - int r; - - etc_machine_id = prefix_roota(arg_root, "/etc/machine-id"); - if (laccess(etc_machine_id, F_OK) >= 0) - return 0; - - if (sd_id128_equal(arg_machine_id, SD_ID128_NULL)) - return 0; - - mkdir_parents(etc_machine_id, 0755); - r = write_string_file(etc_machine_id, sd_id128_to_string(arg_machine_id, id), WRITE_STRING_FILE_CREATE); - if (r < 0) - return log_error_errno(r, "Failed to write machine id: %m"); - - log_info("%s written.", etc_machine_id); - return 0; -} - -static int prompt_root_password(void) { - const char *msg1, *msg2, *etc_shadow; - int r; - - if (arg_root_password) - return 0; - - if (!arg_prompt_root_password) - return 0; - - etc_shadow = prefix_roota(arg_root, "/etc/shadow"); - if (laccess(etc_shadow, F_OK) >= 0) - return 0; - - print_welcome(); - putchar('\n'); - - msg1 = strjoina(special_glyph(TRIANGULAR_BULLET), " Please enter a new root password (empty to skip): "); - msg2 = strjoina(special_glyph(TRIANGULAR_BULLET), " Please enter new root password again: "); - - for (;;) { - _cleanup_string_free_erase_ char *a = NULL, *b = NULL; - - r = ask_password_tty(msg1, NULL, 0, 0, NULL, &a); - if (r < 0) - return log_error_errno(r, "Failed to query root password: %m"); - - if (isempty(a)) { - log_warning("No password entered, skipping."); - break; - } - - r = ask_password_tty(msg2, NULL, 0, 0, NULL, &b); - if (r < 0) - return log_error_errno(r, "Failed to query root password: %m"); - - if (!streq(a, b)) { - log_error("Entered passwords did not match, please try again."); - continue; - } - - arg_root_password = a; - a = NULL; - break; - } - - return 0; -} - -static int write_root_shadow(const char *path, const struct spwd *p) { - _cleanup_fclose_ FILE *f = NULL; - assert(path); - assert(p); - - RUN_WITH_UMASK(0777) - f = fopen(path, "wex"); - if (!f) - return -errno; - - errno = 0; - if (putspent(p, f) != 0) - return errno > 0 ? -errno : -EIO; - - return fflush_and_check(f); -} - -static int process_root_password(void) { - - static const char table[] = - "abcdefghijklmnopqrstuvwxyz" - "ABCDEFGHIJKLMNOPQRSTUVWXYZ" - "0123456789" - "./"; - - struct spwd item = { - .sp_namp = (char*) "root", - .sp_min = -1, - .sp_max = -1, - .sp_warn = -1, - .sp_inact = -1, - .sp_expire = -1, - .sp_flag = (unsigned long) -1, /* this appears to be what everybody does ... */ - }; - - _cleanup_close_ int lock = -1; - char salt[3+16+1+1]; - uint8_t raw[16]; - unsigned i; - char *j; - - const char *etc_shadow; - int r; - - etc_shadow = prefix_roota(arg_root, "/etc/shadow"); - if (laccess(etc_shadow, F_OK) >= 0) - return 0; - - mkdir_parents(etc_shadow, 0755); - - lock = take_etc_passwd_lock(arg_root); - if (lock < 0) - return log_error_errno(lock, "Failed to take a lock: %m"); - - if (arg_copy_root_password && arg_root) { - struct spwd *p; - - errno = 0; - p = getspnam("root"); - if (p || errno != ENOENT) { - if (!p) { - if (!errno) - errno = EIO; - - return log_error_errno(errno, "Failed to find shadow entry for root: %m"); - } - - r = write_root_shadow(etc_shadow, p); - if (r < 0) - return log_error_errno(r, "Failed to write %s: %m", etc_shadow); - - log_info("%s copied.", etc_shadow); - return 0; - } - } - - r = prompt_root_password(); - if (r < 0) - return r; - - if (!arg_root_password) - return 0; - - r = dev_urandom(raw, 16); - if (r < 0) - return log_error_errno(r, "Failed to get salt: %m"); - - /* We only bother with SHA512 hashed passwords, the rest is legacy, and we don't do legacy. */ - assert_cc(sizeof(table) == 64 + 1); - j = stpcpy(salt, "$6$"); - for (i = 0; i < 16; i++) - j[i] = table[raw[i] & 63]; - j[i++] = '$'; - j[i] = 0; - - errno = 0; - item.sp_pwdp = crypt(arg_root_password, salt); - if (!item.sp_pwdp) { - if (!errno) - errno = EINVAL; - - return log_error_errno(errno, "Failed to encrypt password: %m"); - } - - item.sp_lstchg = (long) (now(CLOCK_REALTIME) / USEC_PER_DAY); - - r = write_root_shadow(etc_shadow, &item); - if (r < 0) - return log_error_errno(r, "Failed to write %s: %m", etc_shadow); - - log_info("%s written.", etc_shadow); - return 0; -} - -static void help(void) { - printf("%s [OPTIONS...]\n\n" - "Configures basic settings of the system.\n\n" - " -h --help Show this help\n" - " --version Show package version\n" - " --root=PATH Operate on an alternate filesystem root\n" - " --locale=LOCALE Set primary locale (LANG=)\n" - " --locale-messages=LOCALE Set message locale (LC_MESSAGES=)\n" - " --timezone=TIMEZONE Set timezone\n" - " --hostname=NAME Set host name\n" - " --machine-ID=ID Set machine ID\n" - " --root-password=PASSWORD Set root password\n" - " --root-password-file=FILE Set root password from file\n" - " --prompt-locale Prompt the user for locale settings\n" - " --prompt-timezone Prompt the user for timezone\n" - " --prompt-hostname Prompt the user for hostname\n" - " --prompt-root-password Prompt the user for root password\n" - " --prompt Prompt for all of the above\n" - " --copy-locale Copy locale from host\n" - " --copy-timezone Copy timezone from host\n" - " --copy-root-password Copy root password from host\n" - " --copy Copy locale, timezone, root password\n" - " --setup-machine-id Generate a new random machine ID\n" - , program_invocation_short_name); -} - -static int parse_argv(int argc, char *argv[]) { - - enum { - ARG_VERSION = 0x100, - ARG_ROOT, - ARG_LOCALE, - ARG_LOCALE_MESSAGES, - ARG_TIMEZONE, - ARG_HOSTNAME, - ARG_MACHINE_ID, - ARG_ROOT_PASSWORD, - ARG_ROOT_PASSWORD_FILE, - ARG_PROMPT, - ARG_PROMPT_LOCALE, - ARG_PROMPT_TIMEZONE, - ARG_PROMPT_HOSTNAME, - ARG_PROMPT_ROOT_PASSWORD, - ARG_COPY, - ARG_COPY_LOCALE, - ARG_COPY_TIMEZONE, - ARG_COPY_ROOT_PASSWORD, - ARG_SETUP_MACHINE_ID, - }; - - static const struct option options[] = { - { "help", no_argument, NULL, 'h' }, - { "version", no_argument, NULL, ARG_VERSION }, - { "root", required_argument, NULL, ARG_ROOT }, - { "locale", required_argument, NULL, ARG_LOCALE }, - { "locale-messages", required_argument, NULL, ARG_LOCALE_MESSAGES }, - { "timezone", required_argument, NULL, ARG_TIMEZONE }, - { "hostname", required_argument, NULL, ARG_HOSTNAME }, - { "machine-id", required_argument, NULL, ARG_MACHINE_ID }, - { "root-password", required_argument, NULL, ARG_ROOT_PASSWORD }, - { "root-password-file", required_argument, NULL, ARG_ROOT_PASSWORD_FILE }, - { "prompt", no_argument, NULL, ARG_PROMPT }, - { "prompt-locale", no_argument, NULL, ARG_PROMPT_LOCALE }, - { "prompt-timezone", no_argument, NULL, ARG_PROMPT_TIMEZONE }, - { "prompt-hostname", no_argument, NULL, ARG_PROMPT_HOSTNAME }, - { "prompt-root-password", no_argument, NULL, ARG_PROMPT_ROOT_PASSWORD }, - { "copy", no_argument, NULL, ARG_COPY }, - { "copy-locale", no_argument, NULL, ARG_COPY_LOCALE }, - { "copy-timezone", no_argument, NULL, ARG_COPY_TIMEZONE }, - { "copy-root-password", no_argument, NULL, ARG_COPY_ROOT_PASSWORD }, - { "setup-machine-id", no_argument, NULL, ARG_SETUP_MACHINE_ID }, - {} - }; - - int r, 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_ROOT: - r = parse_path_argument_and_warn(optarg, true, &arg_root); - if (r < 0) - return r; - break; - - case ARG_LOCALE: - if (!locale_is_valid(optarg)) { - log_error("Locale %s is not valid.", optarg); - return -EINVAL; - } - - r = free_and_strdup(&arg_locale, optarg); - if (r < 0) - return log_oom(); - - break; - - case ARG_LOCALE_MESSAGES: - if (!locale_is_valid(optarg)) { - log_error("Locale %s is not valid.", optarg); - return -EINVAL; - } - - r = free_and_strdup(&arg_locale_messages, optarg); - if (r < 0) - return log_oom(); - - break; - - case ARG_TIMEZONE: - if (!timezone_is_valid(optarg)) { - log_error("Timezone %s is not valid.", optarg); - return -EINVAL; - } - - r = free_and_strdup(&arg_timezone, optarg); - if (r < 0) - return log_oom(); - - break; - - case ARG_ROOT_PASSWORD: - r = free_and_strdup(&arg_root_password, optarg); - if (r < 0) - return log_oom(); - break; - - case ARG_ROOT_PASSWORD_FILE: - arg_root_password = mfree(arg_root_password); - - r = read_one_line_file(optarg, &arg_root_password); - if (r < 0) - return log_error_errno(r, "Failed to read %s: %m", optarg); - - break; - - case ARG_HOSTNAME: - if (!hostname_is_valid(optarg, true)) { - log_error("Host name %s is not valid.", optarg); - return -EINVAL; - } - - hostname_cleanup(optarg); - r = free_and_strdup(&arg_hostname, optarg); - if (r < 0) - return log_oom(); - - break; - - case ARG_MACHINE_ID: - if (sd_id128_from_string(optarg, &arg_machine_id) < 0) { - log_error("Failed to parse machine id %s.", optarg); - return -EINVAL; - } - - break; - - case ARG_PROMPT: - arg_prompt_locale = arg_prompt_timezone = arg_prompt_hostname = arg_prompt_root_password = true; - break; - - case ARG_PROMPT_LOCALE: - arg_prompt_locale = true; - break; - - case ARG_PROMPT_TIMEZONE: - arg_prompt_timezone = true; - break; - - case ARG_PROMPT_HOSTNAME: - arg_prompt_hostname = true; - break; - - case ARG_PROMPT_ROOT_PASSWORD: - arg_prompt_root_password = true; - break; - - case ARG_COPY: - arg_copy_locale = arg_copy_timezone = arg_copy_root_password = true; - break; - - case ARG_COPY_LOCALE: - arg_copy_locale = true; - break; - - case ARG_COPY_TIMEZONE: - arg_copy_timezone = true; - break; - - case ARG_COPY_ROOT_PASSWORD: - arg_copy_root_password = true; - break; - - case ARG_SETUP_MACHINE_ID: - - r = sd_id128_randomize(&arg_machine_id); - if (r < 0) - return log_error_errno(r, "Failed to generate randomized machine ID: %m"); - - break; - - case '?': - return -EINVAL; - - default: - assert_not_reached("Unhandled option"); - } - - return 1; -} - -int main(int argc, char *argv[]) { - int r; - - r = parse_argv(argc, argv); - if (r <= 0) - goto finish; - - log_set_target(LOG_TARGET_AUTO); - log_parse_environment(); - log_open(); - - umask(0022); - - r = process_locale(); - if (r < 0) - goto finish; - - r = process_timezone(); - if (r < 0) - goto finish; - - r = process_hostname(); - if (r < 0) - goto finish; - - r = process_machine_id(); - if (r < 0) - goto finish; - - r = process_root_password(); - if (r < 0) - goto finish; - -finish: - free(arg_root); - free(arg_locale); - free(arg_locale_messages); - free(arg_timezone); - free(arg_hostname); - string_erase(arg_root_password); - free(arg_root_password); - - return r < 0 ? EXIT_FAILURE : EXIT_SUCCESS; -} diff --git a/src/systemd-fsck/Makefile b/src/systemd-fsck/Makefile deleted file mode 100644 index cc4e21055c..0000000000 --- a/src/systemd-fsck/Makefile +++ /dev/null @@ -1,33 +0,0 @@ -# -*- 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-fsck -systemd_fsck_SOURCES = \ - src/fsck/fsck.c - -systemd_fsck_LDADD = \ - libshared.la - -include $(topsrcdir)/build-aux/Makefile.tail.mk diff --git a/src/systemd-fsck/fsck.c b/src/systemd-fsck/fsck.c deleted file mode 100644 index 4ca018763f..0000000000 --- a/src/systemd-fsck/fsck.c +++ /dev/null @@ -1,487 +0,0 @@ -/*** - This file is part of systemd. - - Copyright 2010 Lennart Poettering - Copyright 2014 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 -#include -#include -#include -#include - -#include - -#include "basic/alloc-util.h" -#include "basic/fd-util.h" -#include "basic/fs-util.h" -#include "basic/parse-util.h" -#include "basic/path-util.h" -#include "basic/proc-cmdline.h" -#include "basic/process-util.h" -#include "basic/signal-util.h" -#include "basic/socket-util.h" -#include "basic/special.h" -#include "basic/stdio-util.h" -#include "basic/util.h" -#include "sd-bus/bus-common-errors.h" -#include "sd-bus/bus-error.h" -#include "sd-device/device-util.h" -#include "sd-device/sd-device.h" -#include "shared/bus-util.h" - -/* exit codes as defined in fsck(8) */ -enum { - FSCK_SUCCESS = 0, - FSCK_ERROR_CORRECTED = 1, - FSCK_SYSTEM_SHOULD_REBOOT = 2, - FSCK_ERRORS_LEFT_UNCORRECTED = 4, - FSCK_OPERATIONAL_ERROR = 8, - FSCK_USAGE_OR_SYNTAX_ERROR = 16, - FSCK_USER_CANCELLED = 32, - FSCK_SHARED_LIB_ERROR = 128, -}; - -static bool arg_skip = false; -static bool arg_force = false; -static bool arg_show_progress = false; -static const char *arg_repair = "-a"; - -static void start_target(const char *target, const char *mode) { - _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; - - assert(target); - - r = bus_connect_system_systemd(&bus); - if (r < 0) { - log_error_errno(r, "Failed to get D-Bus connection: %m"); - return; - } - - log_info("Running request %s/start/replace", target); - - /* Start these units only if we can replace base.target with it */ - r = sd_bus_call_method(bus, - "org.freedesktop.systemd1", - "/org/freedesktop/systemd1", - "org.freedesktop.systemd1.Manager", - "StartUnitReplace", - &error, - NULL, - "sss", "basic.target", target, mode); - - /* Don't print a warning if we aren't called during startup */ - if (r < 0 && !sd_bus_error_has_name(&error, BUS_ERROR_NO_SUCH_JOB)) - log_error("Failed to start unit: %s", bus_error_message(&error, r)); -} - -static int parse_proc_cmdline_item(const char *key, const char *value) { - int r; - - assert(key); - - if (streq(key, "fsck.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 fsck.mode= parameter '%s'. Ignoring.", value); - - } else if (streq(key, "fsck.repair") && value) { - - if (streq(value, "preen")) - arg_repair = "-a"; - else { - r = parse_boolean(value); - if (r > 0) - arg_repair = "-y"; - else if (r == 0) - arg_repair = "-n"; - else - log_warning("Invalid fsck.repair= parameter '%s'. Ignoring.", value); - } - } - -#ifdef HAVE_SYSV_COMPAT - else if (streq(key, "fastboot") && !value) { - log_warning("Please pass 'fsck.mode=skip' rather than 'fastboot' on the kernel command line."); - arg_skip = true; - - } else if (streq(key, "forcefsck") && !value) { - log_warning("Please pass 'fsck.mode=force' rather than 'forcefsck' on the kernel command line."); - arg_force = true; - } -#endif - - return 0; -} - -static void test_files(void) { - -#ifdef HAVE_SYSV_COMPAT - if (access("/fastboot", F_OK) >= 0) { - log_error("Please pass 'fsck.mode=skip' on the kernel command line rather than creating /fastboot on the root file system."); - arg_skip = true; - } - - if (access("/forcefsck", F_OK) >= 0) { - log_error("Please pass 'fsck.mode=force' on the kernel command line rather than creating /forcefsck on the root file system."); - arg_force = true; - } -#endif - - arg_show_progress = access("/run/systemd/show-status", F_OK) >= 0; -} - -static double percent(int pass, unsigned long cur, unsigned long max) { - /* Values stolen from e2fsck */ - - static const int pass_table[] = { - 0, 70, 90, 92, 95, 100 - }; - - if (pass <= 0) - return 0.0; - - if ((unsigned) pass >= ELEMENTSOF(pass_table) || max == 0) - return 100.0; - - return (double) pass_table[pass-1] + - ((double) pass_table[pass] - (double) pass_table[pass-1]) * - (double) cur / (double) max; -} - -static int process_progress(int fd) { - _cleanup_fclose_ FILE *console = NULL, *f = NULL; - usec_t last = 0; - bool locked = false; - int clear = 0, r; - - /* No progress pipe to process? Then we are a NOP. */ - if (fd < 0) - return 0; - - f = fdopen(fd, "re"); - if (!f) { - safe_close(fd); - return -errno; - } - - console = fopen("/dev/console", "we"); - if (!console) - return -ENOMEM; - - for (;;) { - int pass, m; - unsigned long cur, max; - _cleanup_free_ char *device = NULL; - double p; - usec_t t; - - if (fscanf(f, "%i %lu %lu %ms", &pass, &cur, &max, &device) != 4) { - - if (ferror(f)) - r = log_warning_errno(errno, "Failed to read from progress pipe: %m"); - else if (feof(f)) - r = 0; - else { - log_warning("Failed to parse progress pipe data"); - r = -EBADMSG; - } - break; - } - - /* Only show one progress counter at max */ - if (!locked) { - if (flock(fileno(console), LOCK_EX|LOCK_NB) < 0) - continue; - - locked = true; - } - - /* Only update once every 50ms */ - t = now(CLOCK_MONOTONIC); - if (last + 50 * USEC_PER_MSEC > t) - continue; - - last = t; - - p = percent(pass, cur, max); - fprintf(console, "\r%s: fsck %3.1f%% complete...\r%n", device, p, &m); - fflush(console); - - if (m > clear) - clear = m; - } - - if (clear > 0) { - unsigned j; - - fputc('\r', console); - for (j = 0; j < (unsigned) clear; j++) - fputc(' ', console); - fputc('\r', console); - fflush(console); - } - - return r; -} - -static int fsck_progress_socket(void) { - static const union sockaddr_union sa = { - .un.sun_family = AF_UNIX, - .un.sun_path = "/run/systemd/fsck.progress", - }; - - int fd, r; - - fd = socket(AF_UNIX, SOCK_STREAM, 0); - if (fd < 0) - return log_warning_errno(errno, "socket(): %m"); - - if (connect(fd, &sa.sa, SOCKADDR_UN_LEN(sa.un)) < 0) { - r = log_full_errno(errno == ECONNREFUSED || errno == ENOENT ? LOG_DEBUG : LOG_WARNING, - errno, "Failed to connect to progress socket %s, ignoring: %m", sa.un.sun_path); - safe_close(fd); - return r; - } - - return fd; -} - -int main(int argc, char *argv[]) { - _cleanup_close_pair_ int progress_pipe[2] = { -1, -1 }; - _cleanup_(sd_device_unrefp) sd_device *dev = NULL; - const char *device, *type; - bool root_directory; - siginfo_t status; - struct stat st; - int r; - pid_t pid; - - if (argc > 2) { - log_error("This program expects one or 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 && arg_skip) { - r = 0; - goto finish; - } - - if (argc > 1) { - device = argv[1]; - - if (stat(device, &st) < 0) { - r = log_error_errno(errno, "Failed to stat %s: %m", device); - goto finish; - } - - if (!S_ISBLK(st.st_mode)) { - log_error("%s is not a block device.", device); - r = -EINVAL; - goto finish; - } - - r = sd_device_new_from_devnum(&dev, 'b', st.st_rdev); - if (r < 0) { - log_error_errno(r, "Failed to detect device %s: %m", device); - goto finish; - } - - root_directory = false; - } else { - struct timespec times[2]; - - /* Find root device */ - - if (stat("/", &st) < 0) { - r = log_error_errno(errno, "Failed to stat() the root directory: %m"); - goto finish; - } - - /* Virtual root devices don't need an fsck */ - if (major(st.st_dev) == 0) { - log_debug("Root directory is virtual or btrfs, skipping check."); - r = 0; - goto finish; - } - - /* check if we are already writable */ - times[0] = st.st_atim; - times[1] = st.st_mtim; - - if (utimensat(AT_FDCWD, "/", times, 0) == 0) { - log_info("Root directory is writable, skipping check."); - r = 0; - goto finish; - } - - r = sd_device_new_from_devnum(&dev, 'b', st.st_dev); - if (r < 0) { - log_error_errno(r, "Failed to detect root device: %m"); - goto finish; - } - - r = sd_device_get_devname(dev, &device); - if (r < 0) { - log_error_errno(r, "Failed to detect device node of root directory: %m"); - goto finish; - } - - root_directory = true; - } - - r = sd_device_get_property_value(dev, "ID_FS_TYPE", &type); - if (r >= 0) { - r = fsck_exists(type); - if (r < 0) - log_warning_errno(r, "Couldn't detect if fsck.%s may be used for %s, proceeding: %m", type, device); - else if (r == 0) { - log_info("fsck.%s doesn't exist, not checking file system on %s.", type, device); - goto finish; - } - } - - if (arg_show_progress) { - if (pipe(progress_pipe) < 0) { - r = log_error_errno(errno, "pipe(): %m"); - goto finish; - } - } - - pid = fork(); - if (pid < 0) { - r = log_error_errno(errno, "fork(): %m"); - goto finish; - } - if (pid == 0) { - char dash_c[sizeof("-C")-1 + DECIMAL_STR_MAX(int) + 1]; - int progress_socket = -1; - const char *cmdline[9]; - int i = 0; - - /* Child */ - - (void) reset_all_signal_handlers(); - (void) reset_signal_mask(); - assert_se(prctl(PR_SET_PDEATHSIG, SIGTERM) == 0); - - /* Close the reading side of the progress pipe */ - progress_pipe[0] = safe_close(progress_pipe[0]); - - /* Try to connect to a progress management daemon, if there is one */ - progress_socket = fsck_progress_socket(); - if (progress_socket >= 0) { - /* If this worked we close the progress pipe early, and just use the socket */ - progress_pipe[1] = safe_close(progress_pipe[1]); - xsprintf(dash_c, "-C%i", progress_socket); - } else if (progress_pipe[1] >= 0) { - /* Otherwise if we have the progress pipe to our own local handle, we use it */ - xsprintf(dash_c, "-C%i", progress_pipe[1]); - } else - dash_c[0] = 0; - - cmdline[i++] = "/sbin/fsck"; - cmdline[i++] = arg_repair; - cmdline[i++] = "-T"; - - /* - * Since util-linux v2.25 fsck uses /run/fsck/.lock files. - * The previous versions use flock for the device and conflict with - * udevd, see https://bugs.freedesktop.org/show_bug.cgi?id=79576#c5 - */ - cmdline[i++] = "-l"; - - if (!root_directory) - cmdline[i++] = "-M"; - - if (arg_force) - cmdline[i++] = "-f"; - - if (!isempty(dash_c)) - cmdline[i++] = dash_c; - - cmdline[i++] = device; - cmdline[i++] = NULL; - - execv(cmdline[0], (char**) cmdline); - _exit(FSCK_OPERATIONAL_ERROR); - } - - progress_pipe[1] = safe_close(progress_pipe[1]); - (void) process_progress(progress_pipe[0]); - progress_pipe[0] = -1; - - r = wait_for_terminate(pid, &status); - if (r < 0) { - log_error_errno(r, "waitid(): %m"); - goto finish; - } - - if (status.si_code != CLD_EXITED || (status.si_status & ~1)) { - - if (status.si_code == CLD_KILLED || status.si_code == CLD_DUMPED) - log_error("fsck terminated by signal %s.", signal_to_string(status.si_status)); - else if (status.si_code == CLD_EXITED) - log_error("fsck failed with error code %i.", status.si_status); - else - log_error("fsck failed due to unknown reason."); - - r = -EINVAL; - - if (status.si_code == CLD_EXITED && (status.si_status & FSCK_SYSTEM_SHOULD_REBOOT) && root_directory) - /* System should be rebooted. */ - start_target(SPECIAL_REBOOT_TARGET, "replace-irreversibly"); - else if (status.si_code == CLD_EXITED && (status.si_status & (FSCK_SYSTEM_SHOULD_REBOOT | FSCK_ERRORS_LEFT_UNCORRECTED))) - /* Some other problem */ - start_target(SPECIAL_EMERGENCY_TARGET, "replace"); - else { - log_warning("Ignoring error."); - r = 0; - } - - } else - r = 0; - - if (status.si_code == CLD_EXITED && (status.si_status & FSCK_ERROR_CORRECTED)) - (void) touch("/run/systemd/quotacheck"); - -finish: - return r < 0 ? EXIT_FAILURE : EXIT_SUCCESS; -} diff --git a/src/systemd-fstab-generator/Makefile b/src/systemd-fstab-generator/Makefile deleted file mode 100644 index 0bfc70e464..0000000000 --- a/src/systemd-fstab-generator/Makefile +++ /dev/null @@ -1,34 +0,0 @@ -# -*- 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-fstab-generator -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/systemd-fstab-generator/fstab-generator.c b/src/systemd-fstab-generator/fstab-generator.c deleted file mode 100644 index bedf469f93..0000000000 --- a/src/systemd-fstab-generator/fstab-generator.c +++ /dev/null @@ -1,713 +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 "basic/alloc-util.h" -#include "basic/fd-util.h" -#include "basic/fileio.h" -#include "shared/fstab-util.h" -#include "shared/generator.h" -#include "basic/log.h" -#include "basic/mkdir.h" -#include "mount-setup.h" -#include "basic/mount-util.h" -#include "basic/parse-util.h" -#include "basic/path-util.h" -#include "basic/proc-cmdline.h" -#include "basic/special.h" -#include "basic/stat-util.h" -#include "basic/string-util.h" -#include "basic/strv.h" -#include "basic/unit-name.h" -#include "basic/util.h" -#include "basic/virt.h" - -static const char *arg_dest = "/tmp"; -static bool arg_fstab_enabled = true; -static char *arg_root_what = NULL; -static char *arg_root_fstype = NULL; -static char *arg_root_options = NULL; -static int arg_root_rw = -1; -static char *arg_usr_what = NULL; -static char *arg_usr_fstype = NULL; -static char *arg_usr_options = NULL; - -static int add_swap( - const char *what, - struct mntent *me, - bool noauto, - bool nofail) { - - _cleanup_free_ char *name = NULL, *unit = NULL, *lnk = NULL; - _cleanup_fclose_ FILE *f = NULL; - int r; - - assert(what); - assert(me); - - if (access("/proc/swaps", F_OK) < 0) { - log_info("Swap not supported, ignoring fstab swap entry for %s.", what); - return 0; - } - - if (detect_container() > 0) { - log_info("Running in a container, ignoring fstab swap entry for %s.", what); - return 0; - } - - r = unit_name_from_path(what, ".swap", &name); - if (r < 0) - return log_error_errno(r, "Failed to generate unit name: %m"); - - unit = strjoin(arg_dest, "/", name, NULL); - if (!unit) - return log_oom(); - - f = fopen(unit, "wxe"); - if (!f) { - if (errno == EEXIST) - log_error("Failed to create swap unit file %s, as it already exists. Duplicate entry in /etc/fstab?", unit); - else - log_error_errno(errno, "Failed to create unit file %s: %m", unit); - return -errno; - } - - fprintf(f, - "# Automatically generated by systemd-fstab-generator\n\n" - "[Unit]\n" - "SourcePath=/etc/fstab\n" - "Documentation=man:fstab(5) man:systemd-fstab-generator(8)\n\n" - "[Swap]\n" - "What=%s\n", - what); - - if (!isempty(me->mnt_opts) && !streq(me->mnt_opts, "defaults")) - fprintf(f, "Options=%s\n", me->mnt_opts); - - r = fflush_and_check(f); - if (r < 0) - return log_error_errno(r, "Failed to write unit file %s: %m", unit); - - /* use what as where, to have a nicer error message */ - r = generator_write_timeouts(arg_dest, what, what, me->mnt_opts, NULL); - if (r < 0) - return r; - - if (!noauto) { - lnk = strjoin(arg_dest, "/" SPECIAL_SWAP_TARGET, - nofail ? ".wants/" : ".requires/", name, NULL); - if (!lnk) - return log_oom(); - - mkdir_parents_label(lnk, 0755); - if (symlink(unit, lnk) < 0) - return log_error_errno(errno, "Failed to create symlink %s: %m", lnk); - } - - return 0; -} - -static bool mount_is_network(struct mntent *me) { - assert(me); - - return fstab_test_option(me->mnt_opts, "_netdev\0") || - fstype_is_network(me->mnt_type); -} - -static bool mount_in_initrd(struct mntent *me) { - assert(me); - - return fstab_test_option(me->mnt_opts, "x-initrd.mount\0") || - streq(me->mnt_dir, "/usr"); -} - -static int write_idle_timeout(FILE *f, const char *where, const char *opts) { - _cleanup_free_ char *timeout = NULL; - char timespan[FORMAT_TIMESPAN_MAX]; - usec_t u; - int r; - - r = fstab_filter_options(opts, "x-systemd.idle-timeout\0", NULL, &timeout, NULL); - if (r < 0) - return log_warning_errno(r, "Failed to parse options: %m"); - if (r == 0) - return 0; - - r = parse_sec(timeout, &u); - if (r < 0) { - log_warning("Failed to parse timeout for %s, ignoring: %s", where, timeout); - return 0; - } - - fprintf(f, "TimeoutIdleSec=%s\n", format_timespan(timespan, sizeof(timespan), u, 0)); - - return 0; -} - -static int write_requires_after(FILE *f, const char *opts) { - _cleanup_strv_free_ char **names = NULL, **units = NULL; - _cleanup_free_ char *res = NULL; - char **s; - int r; - - assert(f); - assert(opts); - - r = fstab_extract_values(opts, "x-systemd.requires", &names); - if (r < 0) - return log_warning_errno(r, "Failed to parse options: %m"); - if (r == 0) - return 0; - - STRV_FOREACH(s, names) { - char *x; - - r = unit_name_mangle_with_suffix(*s, UNIT_NAME_NOGLOB, ".mount", &x); - if (r < 0) - return log_error_errno(r, "Failed to generate unit name: %m"); - r = strv_consume(&units, x); - if (r < 0) - return log_oom(); - } - - if (units) { - res = strv_join(units, " "); - if (!res) - return log_oom(); - fprintf(f, "After=%1$s\nRequires=%1$s\n", res); - } - - return 0; -} - -static int write_requires_mounts_for(FILE *f, const char *opts) { - _cleanup_strv_free_ char **paths = NULL; - _cleanup_free_ char *res = NULL; - int r; - - assert(f); - assert(opts); - - r = fstab_extract_values(opts, "x-systemd.requires-mounts-for", &paths); - if (r < 0) - return log_warning_errno(r, "Failed to parse options: %m"); - if (r == 0) - return 0; - - res = strv_join(paths, " "); - if (!res) - return log_oom(); - - fprintf(f, "RequiresMountsFor=%s\n", res); - - return 0; -} - -static int add_mount( - const char *what, - const char *where, - const char *fstype, - const char *opts, - int passno, - bool noauto, - bool nofail, - bool automount, - const char *post, - const char *source) { - - _cleanup_free_ char - *name = NULL, *unit = NULL, *lnk = NULL, - *automount_name = NULL, *automount_unit = NULL, - *filtered = NULL; - _cleanup_fclose_ FILE *f = NULL; - int r; - - assert(what); - assert(where); - assert(opts); - assert(post); - assert(source); - - if (streq_ptr(fstype, "autofs")) - return 0; - - if (!is_path(where)) { - log_warning("Mount point %s is not a valid path, ignoring.", where); - return 0; - } - - if (mount_point_is_api(where) || - mount_point_ignore(where)) - return 0; - - if (path_equal(where, "/")) { - if (noauto) - log_warning("Ignoring \"noauto\" for root device"); - if (nofail) - log_warning("Ignoring \"nofail\" for root device"); - if (automount) - log_warning("Ignoring automount option for root device"); - - noauto = nofail = automount = false; - } - - r = unit_name_from_path(where, ".mount", &name); - if (r < 0) - return log_error_errno(r, "Failed to generate unit name: %m"); - - unit = strjoin(arg_dest, "/", name, NULL); - if (!unit) - return log_oom(); - - f = fopen(unit, "wxe"); - if (!f) { - if (errno == EEXIST) - log_error("Failed to create mount unit file %s, as it already exists. Duplicate entry in /etc/fstab?", unit); - else - log_error_errno(errno, "Failed to create unit file %s: %m", unit); - return -errno; - } - - fprintf(f, - "# Automatically generated by systemd-fstab-generator\n\n" - "[Unit]\n" - "SourcePath=%s\n" - "Documentation=man:fstab(5) man:systemd-fstab-generator(8)\n", - source); - - if (!noauto && !nofail && !automount) - fprintf(f, "Before=%s\n", post); - - if (!automount && opts) { - r = write_requires_after(f, opts); - if (r < 0) - return r; - r = write_requires_mounts_for(f, opts); - if (r < 0) - return r; - } - - if (passno != 0) { - r = generator_write_fsck_deps(f, arg_dest, what, where, fstype); - if (r < 0) - return r; - } - - fprintf(f, - "\n" - "[Mount]\n" - "What=%s\n" - "Where=%s\n", - what, - where); - - if (!isempty(fstype) && !streq(fstype, "auto")) - fprintf(f, "Type=%s\n", fstype); - - r = generator_write_timeouts(arg_dest, what, where, opts, &filtered); - if (r < 0) - return r; - - if (!isempty(filtered) && !streq(filtered, "defaults")) - fprintf(f, "Options=%s\n", filtered); - - r = fflush_and_check(f); - if (r < 0) - return log_error_errno(r, "Failed to write unit file %s: %m", unit); - - if (!noauto && !automount) { - lnk = strjoin(arg_dest, "/", post, nofail ? ".wants/" : ".requires/", name, NULL); - if (!lnk) - return log_oom(); - - mkdir_parents_label(lnk, 0755); - if (symlink(unit, lnk) < 0) - return log_error_errno(errno, "Failed to create symlink %s: %m", lnk); - } - - if (automount) { - r = unit_name_from_path(where, ".automount", &automount_name); - if (r < 0) - return log_error_errno(r, "Failed to generate unit name: %m"); - - automount_unit = strjoin(arg_dest, "/", automount_name, NULL); - if (!automount_unit) - return log_oom(); - - fclose(f); - f = fopen(automount_unit, "wxe"); - if (!f) - return log_error_errno(errno, "Failed to create unit file %s: %m", automount_unit); - - fprintf(f, - "# Automatically generated by systemd-fstab-generator\n\n" - "[Unit]\n" - "SourcePath=%s\n" - "Documentation=man:fstab(5) man:systemd-fstab-generator(8)\n", - source); - - fprintf(f, "Before=%s\n", post); - - if (opts) { - r = write_requires_after(f, opts); - if (r < 0) - return r; - r = write_requires_mounts_for(f, opts); - if (r < 0) - return r; - } - - fprintf(f, - "\n" - "[Automount]\n" - "Where=%s\n", - where); - - r = write_idle_timeout(f, where, opts); - if (r < 0) - return r; - - r = fflush_and_check(f); - if (r < 0) - return log_error_errno(r, "Failed to write unit file %s: %m", automount_unit); - - free(lnk); - lnk = strjoin(arg_dest, "/", post, nofail ? ".wants/" : ".requires/", automount_name, NULL); - if (!lnk) - return log_oom(); - - mkdir_parents_label(lnk, 0755); - if (symlink(automount_unit, lnk) < 0) - return log_error_errno(errno, "Failed to create symlink %s: %m", lnk); - } - - return 0; -} - -static int parse_fstab(bool initrd) { - _cleanup_endmntent_ FILE *f = NULL; - const char *fstab_path; - struct mntent *me; - int r = 0; - - fstab_path = initrd ? "/sysroot/etc/fstab" : "/etc/fstab"; - f = setmntent(fstab_path, "re"); - if (!f) { - if (errno == ENOENT) - return 0; - - log_error_errno(errno, "Failed to open %s: %m", fstab_path); - return -errno; - } - - while ((me = getmntent(f))) { - _cleanup_free_ char *where = NULL, *what = NULL; - bool noauto, nofail; - int k; - - if (initrd && !mount_in_initrd(me)) - continue; - - what = fstab_node_to_udev_node(me->mnt_fsname); - if (!what) - return log_oom(); - - if (is_device_path(what) && path_is_read_only_fs("sys") > 0) { - log_info("Running in a container, ignoring fstab device entry for %s.", what); - continue; - } - - where = initrd ? strappend("/sysroot/", me->mnt_dir) : strdup(me->mnt_dir); - if (!where) - return log_oom(); - - if (is_path(where)) - path_kill_slashes(where); - - noauto = fstab_test_yes_no_option(me->mnt_opts, "noauto\0" "auto\0"); - nofail = fstab_test_yes_no_option(me->mnt_opts, "nofail\0" "fail\0"); - log_debug("Found entry what=%s where=%s type=%s nofail=%s noauto=%s", - what, where, me->mnt_type, - yes_no(noauto), yes_no(nofail)); - - if (streq(me->mnt_type, "swap")) - k = add_swap(what, me, noauto, nofail); - else { - bool automount; - const char *post; - - automount = fstab_test_option(me->mnt_opts, - "comment=systemd.automount\0" - "x-systemd.automount\0"); - if (initrd) - post = SPECIAL_INITRD_FS_TARGET; - else if (mount_is_network(me)) - post = SPECIAL_REMOTE_FS_TARGET; - else - post = SPECIAL_LOCAL_FS_TARGET; - - k = add_mount(what, - where, - me->mnt_type, - me->mnt_opts, - me->mnt_passno, - noauto, - nofail, - automount, - post, - fstab_path); - } - - if (k < 0) - r = k; - } - - return r; -} - -static int add_sysroot_mount(void) { - _cleanup_free_ char *what = NULL; - const char *opts; - int r; - - if (isempty(arg_root_what)) { - log_debug("Could not find a root= entry on the kernel command line."); - return 0; - } - - what = fstab_node_to_udev_node(arg_root_what); - if (!what) - return log_oom(); - - if (!arg_root_options) - opts = arg_root_rw > 0 ? "rw" : "ro"; - else if (arg_root_rw >= 0 || - !fstab_test_option(arg_root_options, "ro\0" "rw\0")) - opts = strjoina(arg_root_options, ",", arg_root_rw > 0 ? "rw" : "ro"); - else - opts = arg_root_options; - - log_debug("Found entry what=%s where=/sysroot type=%s", what, strna(arg_root_fstype)); - - if (is_device_path(what)) { - r = generator_write_initrd_root_device_deps(arg_dest, what); - if (r < 0) - return r; - } - - return add_mount(what, - "/sysroot", - arg_root_fstype, - opts, - is_device_path(what) ? 1 : 0, - false, - false, - false, - SPECIAL_INITRD_ROOT_FS_TARGET, - "/proc/cmdline"); -} - -static int add_sysroot_usr_mount(void) { - _cleanup_free_ char *what = NULL; - const char *opts; - - if (!arg_usr_what && !arg_usr_fstype && !arg_usr_options) - return 0; - - if (arg_root_what && !arg_usr_what) { - arg_usr_what = strdup(arg_root_what); - - if (!arg_usr_what) - return log_oom(); - } - - if (arg_root_fstype && !arg_usr_fstype) { - arg_usr_fstype = strdup(arg_root_fstype); - - if (!arg_usr_fstype) - return log_oom(); - } - - if (arg_root_options && !arg_usr_options) { - arg_usr_options = strdup(arg_root_options); - - if (!arg_usr_options) - return log_oom(); - } - - if (!arg_usr_what) - return 0; - - what = fstab_node_to_udev_node(arg_usr_what); - if (!path_is_absolute(what)) { - log_debug("Skipping entry what=%s where=/sysroot/usr type=%s", what, strna(arg_usr_fstype)); - return -1; - } - - if (!arg_usr_options) - opts = arg_root_rw > 0 ? "rw" : "ro"; - else if (!fstab_test_option(arg_usr_options, "ro\0" "rw\0")) - opts = strjoina(arg_usr_options, ",", arg_root_rw > 0 ? "rw" : "ro"); - else - opts = arg_usr_options; - - log_debug("Found entry what=%s where=/sysroot/usr type=%s", what, strna(arg_usr_fstype)); - return add_mount(what, - "/sysroot/usr", - arg_usr_fstype, - opts, - 1, - false, - false, - false, - SPECIAL_INITRD_FS_TARGET, - "/proc/cmdline"); -} - -static int parse_proc_cmdline_item(const char *key, const char *value) { - int r; - - /* root=, usr=, usrfstype= and roofstype= may occur more than once, the last - * instance should take precedence. In the case of multiple rootflags= - * or usrflags= the arguments should be concatenated */ - - if (STR_IN_SET(key, "fstab", "rd.fstab") && value) { - - r = parse_boolean(value); - if (r < 0) - log_warning("Failed to parse fstab switch %s. Ignoring.", value); - else - arg_fstab_enabled = r; - - } else if (streq(key, "root") && value) { - - if (free_and_strdup(&arg_root_what, value) < 0) - return log_oom(); - - } else if (streq(key, "rootfstype") && value) { - - if (free_and_strdup(&arg_root_fstype, value) < 0) - return log_oom(); - - } else if (streq(key, "rootflags") && value) { - char *o; - - o = arg_root_options ? - strjoin(arg_root_options, ",", value, NULL) : - strdup(value); - if (!o) - return log_oom(); - - free(arg_root_options); - arg_root_options = o; - - } else if (streq(key, "mount.usr") && value) { - - if (free_and_strdup(&arg_usr_what, value) < 0) - return log_oom(); - - } else if (streq(key, "mount.usrfstype") && value) { - - if (free_and_strdup(&arg_usr_fstype, value) < 0) - return log_oom(); - - } else if (streq(key, "mount.usrflags") && value) { - char *o; - - o = arg_usr_options ? - strjoin(arg_usr_options, ",", value, NULL) : - strdup(value); - if (!o) - return log_oom(); - - free(arg_usr_options); - arg_usr_options = o; - - } else if (streq(key, "rw") && !value) - arg_root_rw = true; - else if (streq(key, "ro") && !value) - arg_root_rw = false; - - return 0; -} - -int main(int argc, char *argv[]) { - int r = 0; - - 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); - - r = parse_proc_cmdline(parse_proc_cmdline_item); - if (r < 0) - log_warning_errno(r, "Failed to parse kernel command line, ignoring: %m"); - - /* Always honour root= and usr= in the kernel command line if we are in an initrd */ - if (in_initrd()) { - r = add_sysroot_mount(); - if (r == 0) - r = add_sysroot_usr_mount(); - } - - /* Honour /etc/fstab only when that's enabled */ - if (arg_fstab_enabled) { - int k; - - log_debug("Parsing /etc/fstab"); - - /* Parse the local /etc/fstab, possibly from the initrd */ - k = parse_fstab(false); - if (k < 0) - r = k; - - /* If running in the initrd also parse the /etc/fstab from the host */ - if (in_initrd()) { - log_debug("Parsing /sysroot/etc/fstab"); - - k = parse_fstab(true); - if (k < 0) - r = k; - } - } - - free(arg_root_what); - free(arg_root_fstype); - free(arg_root_options); - - free(arg_usr_what); - free(arg_usr_fstype); - free(arg_usr_options); - - return r < 0 ? EXIT_FAILURE : EXIT_SUCCESS; -} diff --git a/src/systemd-fstab-generator/mount-setup.c b/src/systemd-fstab-generator/mount-setup.c deleted file mode 120000 index a4ab487157..0000000000 --- a/src/systemd-fstab-generator/mount-setup.c +++ /dev/null @@ -1 +0,0 @@ -../libcore/mount-setup.c \ No newline at end of file diff --git a/src/systemd-fstab-generator/mount-setup.h b/src/systemd-fstab-generator/mount-setup.h deleted file mode 120000 index 1f984851f8..0000000000 --- a/src/systemd-fstab-generator/mount-setup.h +++ /dev/null @@ -1 +0,0 @@ -../libcore/mount-setup.h \ No newline at end of file diff --git a/src/systemd-hwdb/Makefile b/src/systemd-hwdb/Makefile deleted file mode 100644 index bf14199ffe..0000000000 --- a/src/systemd-hwdb/Makefile +++ /dev/null @@ -1,78 +0,0 @@ -# -*- 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_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 # ENABLE_HWDB - -EXTRA_DIST += \ - units/systemd-hwdb-update.service.in \ - hwdb/ids-update.pl \ - hwdb/sdio.ids - -systemd.CPPFLAGS += -DUDEVLIBEXECDIR=\"$(udevlibexecdir)\" - -include $(topsrcdir)/build-aux/Makefile.tail.mk diff --git a/src/systemd-hwdb/hwdb.c b/src/systemd-hwdb/hwdb.c deleted file mode 100644 index 0e96716b0f..0000000000 --- a/src/systemd-hwdb/hwdb.c +++ /dev/null @@ -1,739 +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 - -#include "basic/alloc-util.h" -#include "basic/conf-files.h" -#include "basic/fd-util.h" -#include "basic/fileio.h" -#include "basic/fs-util.h" -#include "basic/mkdir.h" -#include "basic/strbuf.h" -#include "basic/string-util.h" -#include "basic/strv.h" -#include "basic/util.h" -#include "basic/verbs.h" -#include "sd-hwdb/hwdb-internal.h" -#include "sd-hwdb/hwdb-util.h" - -/* - * Generic udev properties, key/value database based on modalias strings. - * Uses a Patricia/radix trie to index all matches for efficient lookup. - */ - -static const char *arg_hwdb_bin_dir = "/etc/udev"; -static const char *arg_root = ""; - -static const char * const conf_file_dirs[] = { - "/etc/udev/hwdb.d", - UDEVLIBEXECDIR "/hwdb.d", - NULL -}; - -/* in-memory trie objects */ -struct trie { - struct trie_node *root; - struct strbuf *strings; - - size_t nodes_count; - size_t children_count; - size_t values_count; -}; - -struct trie_node { - /* prefix, common part for all children of this node */ - size_t prefix_off; - - /* sorted array of pointers to children nodes */ - struct trie_child_entry *children; - uint8_t children_count; - - /* sorted array of key/value pairs */ - struct trie_value_entry *values; - size_t values_count; -}; - -/* children array item with char (0-255) index */ -struct trie_child_entry { - uint8_t c; - struct trie_node *child; -}; - -/* value array item with key/value pairs */ -struct trie_value_entry { - size_t key_off; - size_t value_off; -}; - -static int trie_children_cmp(const void *v1, const void *v2) { - const struct trie_child_entry *n1 = v1; - const struct trie_child_entry *n2 = v2; - - return n1->c - n2->c; -} - -static int node_add_child(struct trie *trie, struct trie_node *node, struct trie_node *node_child, uint8_t c) { - struct trie_child_entry *child; - - /* extend array, add new entry, sort for bisection */ - child = realloc(node->children, (node->children_count + 1) * sizeof(struct trie_child_entry)); - if (!child) - return -ENOMEM; - - node->children = child; - trie->children_count++; - node->children[node->children_count].c = c; - node->children[node->children_count].child = node_child; - node->children_count++; - qsort(node->children, node->children_count, sizeof(struct trie_child_entry), trie_children_cmp); - trie->nodes_count++; - - return 0; -} - -static struct trie_node *node_lookup(const struct trie_node *node, uint8_t c) { - struct trie_child_entry *child; - struct trie_child_entry search; - - search.c = c; - child = bsearch(&search, node->children, node->children_count, sizeof(struct trie_child_entry), trie_children_cmp); - if (child) - return child->child; - return NULL; -} - -static void trie_node_cleanup(struct trie_node *node) { - size_t i; - - for (i = 0; i < node->children_count; i++) - trie_node_cleanup(node->children[i].child); - free(node->children); - free(node->values); - free(node); -} - -static void trie_free(struct trie *trie) { - if (!trie) - return; - - if (trie->root) - trie_node_cleanup(trie->root); - - strbuf_cleanup(trie->strings); - free(trie); -} - -DEFINE_TRIVIAL_CLEANUP_FUNC(struct trie*, trie_free); - -static int trie_values_cmp(const void *v1, const void *v2, void *arg) { - const struct trie_value_entry *val1 = v1; - const struct trie_value_entry *val2 = v2; - struct trie *trie = arg; - - return strcmp(trie->strings->buf + val1->key_off, - trie->strings->buf + val2->key_off); -} - -static int trie_node_add_value(struct trie *trie, struct trie_node *node, - const char *key, const char *value) { - ssize_t k, v; - struct trie_value_entry *val; - - k = strbuf_add_string(trie->strings, key, strlen(key)); - if (k < 0) - return k; - v = strbuf_add_string(trie->strings, value, strlen(value)); - if (v < 0) - return v; - - if (node->values_count) { - struct trie_value_entry search = { - .key_off = k, - .value_off = v, - }; - - val = xbsearch_r(&search, node->values, node->values_count, sizeof(struct trie_value_entry), trie_values_cmp, trie); - if (val) { - /* replace existing earlier key with new value */ - val->value_off = v; - return 0; - } - } - - /* extend array, add new entry, sort for bisection */ - val = realloc(node->values, (node->values_count + 1) * sizeof(struct trie_value_entry)); - if (!val) - return -ENOMEM; - trie->values_count++; - node->values = val; - node->values[node->values_count].key_off = k; - node->values[node->values_count].value_off = v; - node->values_count++; - qsort_r(node->values, node->values_count, sizeof(struct trie_value_entry), trie_values_cmp, trie); - return 0; -} - -static int trie_insert(struct trie *trie, struct trie_node *node, const char *search, - const char *key, const char *value) { - size_t i = 0; - int err = 0; - - for (;;) { - size_t p; - uint8_t c; - struct trie_node *child; - - for (p = 0; (c = trie->strings->buf[node->prefix_off + p]); p++) { - _cleanup_free_ char *s = NULL; - ssize_t off; - _cleanup_free_ struct trie_node *new_child = NULL; - - if (c == search[i + p]) - continue; - - /* split node */ - new_child = new0(struct trie_node, 1); - if (!new_child) - return -ENOMEM; - - /* move values from parent to child */ - new_child->prefix_off = node->prefix_off + p+1; - new_child->children = node->children; - new_child->children_count = node->children_count; - new_child->values = node->values; - new_child->values_count = node->values_count; - - /* update parent; use strdup() because the source gets realloc()d */ - s = strndup(trie->strings->buf + node->prefix_off, p); - if (!s) - return -ENOMEM; - - off = strbuf_add_string(trie->strings, s, p); - if (off < 0) - return off; - - node->prefix_off = off; - node->children = NULL; - node->children_count = 0; - node->values = NULL; - node->values_count = 0; - err = node_add_child(trie, node, new_child, c); - if (err < 0) - return err; - - new_child = NULL; /* avoid cleanup */ - break; - } - i += p; - - c = search[i]; - if (c == '\0') - return trie_node_add_value(trie, node, key, value); - - child = node_lookup(node, c); - if (!child) { - ssize_t off; - - /* new child */ - child = new0(struct trie_node, 1); - if (!child) - return -ENOMEM; - - off = strbuf_add_string(trie->strings, search + i+1, strlen(search + i+1)); - if (off < 0) { - free(child); - return off; - } - - child->prefix_off = off; - err = node_add_child(trie, node, child, c); - if (err < 0) { - free(child); - return err; - } - - return trie_node_add_value(trie, child, key, value); - } - - node = child; - i++; - } -} - -struct trie_f { - FILE *f; - struct trie *trie; - uint64_t strings_off; - - uint64_t nodes_count; - uint64_t children_count; - uint64_t values_count; -}; - -/* calculate the storage space for the nodes, children arrays, value arrays */ -static void trie_store_nodes_size(struct trie_f *trie, struct trie_node *node) { - uint64_t i; - - for (i = 0; i < node->children_count; i++) - trie_store_nodes_size(trie, node->children[i].child); - - trie->strings_off += sizeof(struct trie_node_f); - for (i = 0; i < node->children_count; i++) - trie->strings_off += sizeof(struct trie_child_entry_f); - for (i = 0; i < node->values_count; i++) - trie->strings_off += sizeof(struct trie_value_entry_f); -} - -static int64_t trie_store_nodes(struct trie_f *trie, struct trie_node *node) { - uint64_t i; - struct trie_node_f n = { - .prefix_off = htole64(trie->strings_off + node->prefix_off), - .children_count = node->children_count, - .values_count = htole64(node->values_count), - }; - struct trie_child_entry_f *children = NULL; - int64_t node_off; - - if (node->children_count) { - children = new0(struct trie_child_entry_f, node->children_count); - if (!children) - return -ENOMEM; - } - - /* post-order recursion */ - for (i = 0; i < node->children_count; i++) { - int64_t child_off; - - child_off = trie_store_nodes(trie, node->children[i].child); - if (child_off < 0) { - free(children); - return child_off; - } - children[i].c = node->children[i].c; - children[i].child_off = htole64(child_off); - } - - /* write node */ - node_off = ftello(trie->f); - fwrite(&n, sizeof(struct trie_node_f), 1, trie->f); - trie->nodes_count++; - - /* append children array */ - if (node->children_count) { - fwrite(children, sizeof(struct trie_child_entry_f), node->children_count, trie->f); - trie->children_count += node->children_count; - free(children); - } - - /* append values array */ - for (i = 0; i < node->values_count; i++) { - struct trie_value_entry_f v = { - .key_off = htole64(trie->strings_off + node->values[i].key_off), - .value_off = htole64(trie->strings_off + node->values[i].value_off), - }; - - fwrite(&v, sizeof(struct trie_value_entry_f), 1, trie->f); - trie->values_count++; - } - - return node_off; -} - -static int trie_store(struct trie *trie, const char *filename) { - struct trie_f t = { - .trie = trie, - }; - _cleanup_free_ char *filename_tmp = NULL; - int64_t pos; - int64_t root_off; - int64_t size; - struct trie_header_f h = { - .signature = HWDB_SIG, - .tool_version = htole64(atoi(VERSION)), - .header_size = htole64(sizeof(struct trie_header_f)), - .node_size = htole64(sizeof(struct trie_node_f)), - .child_entry_size = htole64(sizeof(struct trie_child_entry_f)), - .value_entry_size = htole64(sizeof(struct trie_value_entry_f)), - }; - int err; - - /* calculate size of header, nodes, children entries, value entries */ - t.strings_off = sizeof(struct trie_header_f); - trie_store_nodes_size(&t, trie->root); - - err = fopen_temporary(filename , &t.f, &filename_tmp); - if (err < 0) - return err; - fchmod(fileno(t.f), 0444); - - /* write nodes */ - err = fseeko(t.f, sizeof(struct trie_header_f), SEEK_SET); - if (err < 0) { - fclose(t.f); - unlink_noerrno(filename_tmp); - return -errno; - } - root_off = trie_store_nodes(&t, trie->root); - h.nodes_root_off = htole64(root_off); - pos = ftello(t.f); - h.nodes_len = htole64(pos - sizeof(struct trie_header_f)); - - /* write string buffer */ - fwrite(trie->strings->buf, trie->strings->len, 1, t.f); - h.strings_len = htole64(trie->strings->len); - - /* write header */ - size = ftello(t.f); - h.file_size = htole64(size); - err = fseeko(t.f, 0, SEEK_SET); - if (err < 0) { - fclose(t.f); - unlink_noerrno(filename_tmp); - return -errno; - } - fwrite(&h, sizeof(struct trie_header_f), 1, t.f); - err = ferror(t.f); - if (err) - err = -errno; - fclose(t.f); - if (err < 0 || rename(filename_tmp, filename) < 0) { - unlink_noerrno(filename_tmp); - return err < 0 ? err : -errno; - } - - log_debug("=== trie on-disk ==="); - log_debug("size: %8"PRIi64" bytes", size); - log_debug("header: %8zu bytes", sizeof(struct trie_header_f)); - log_debug("nodes: %8"PRIu64" bytes (%8"PRIu64")", - t.nodes_count * sizeof(struct trie_node_f), t.nodes_count); - log_debug("child pointers: %8"PRIu64" bytes (%8"PRIu64")", - t.children_count * sizeof(struct trie_child_entry_f), t.children_count); - log_debug("value pointers: %8"PRIu64" bytes (%8"PRIu64")", - t.values_count * sizeof(struct trie_value_entry_f), t.values_count); - log_debug("string store: %8zu bytes", trie->strings->len); - log_debug("strings start: %8"PRIu64, t.strings_off); - - return 0; -} - -static int insert_data(struct trie *trie, char **match_list, char *line, const char *filename) { - char *value, **entry; - - value = strchr(line, '='); - if (!value) { - log_error("Error, key/value pair expected but got '%s' in '%s':", line, filename); - return -EINVAL; - } - - value[0] = '\0'; - value++; - - /* libudev requires properties to start with a space */ - while (isblank(line[0]) && isblank(line[1])) - line++; - - if (line[0] == '\0' || value[0] == '\0') { - log_error("Error, empty key or value '%s' in '%s':", line, filename); - return -EINVAL; - } - - STRV_FOREACH(entry, match_list) - trie_insert(trie, trie->root, *entry, line, value); - - return 0; -} - -static int import_file(struct trie *trie, const char *filename) { - enum { - HW_NONE, - HW_MATCH, - HW_DATA, - } state = HW_NONE; - _cleanup_fclose_ FILE *f = NULL; - char line[LINE_MAX]; - _cleanup_strv_free_ char **match_list = NULL; - char *match = NULL; - int r; - - f = fopen(filename, "re"); - if (!f) - return -errno; - - while (fgets(line, sizeof(line), f)) { - size_t len; - char *pos; - - /* comment line */ - if (line[0] == '#') - continue; - - /* strip trailing comment */ - pos = strchr(line, '#'); - if (pos) - pos[0] = '\0'; - - /* strip trailing whitespace */ - len = strlen(line); - while (len > 0 && isspace(line[len-1])) - len--; - line[len] = '\0'; - - switch (state) { - case HW_NONE: - if (len == 0) - break; - - if (line[0] == ' ') { - log_error("Error, MATCH expected but got '%s' in '%s':", line, filename); - break; - } - - /* start of record, first match */ - state = HW_MATCH; - - match = strdup(line); - if (!match) - return -ENOMEM; - - r = strv_consume(&match_list, match); - if (r < 0) - return r; - - break; - - case HW_MATCH: - if (len == 0) { - log_error("Error, DATA expected but got empty line in '%s':", filename); - state = HW_NONE; - strv_clear(match_list); - break; - } - - /* another match */ - if (line[0] != ' ') { - match = strdup(line); - if (!match) - return -ENOMEM; - - r = strv_consume(&match_list, match); - if (r < 0) - return r; - - break; - } - - /* first data */ - state = HW_DATA; - insert_data(trie, match_list, line, filename); - break; - - case HW_DATA: - /* end of record */ - if (len == 0) { - state = HW_NONE; - strv_clear(match_list); - break; - } - - if (line[0] != ' ') { - log_error("Error, DATA expected but got '%s' in '%s':", line, filename); - state = HW_NONE; - strv_clear(match_list); - break; - } - - insert_data(trie, match_list, line, filename); - break; - }; - } - - return 0; -} - -static int hwdb_query(int argc, char *argv[], void *userdata) { - _cleanup_(sd_hwdb_unrefp) sd_hwdb *hwdb = NULL; - const char *key, *value; - const char *modalias; - int r; - - assert(argc >= 2); - assert(argv); - - modalias = argv[1]; - - r = sd_hwdb_new(&hwdb); - if (r < 0) - return r; - - SD_HWDB_FOREACH_PROPERTY(hwdb, modalias, key, value) - printf("%s=%s\n", key, value); - - return 0; -} - -static int hwdb_update(int argc, char *argv[], void *userdata) { - _cleanup_free_ char *hwdb_bin = NULL; - _cleanup_(trie_freep) struct trie *trie = NULL; - char **files, **f; - int r; - - trie = new0(struct trie, 1); - if (!trie) - return -ENOMEM; - - /* string store */ - trie->strings = strbuf_new(); - if (!trie->strings) - return -ENOMEM; - - /* index */ - trie->root = new0(struct trie_node, 1); - if (!trie->root) - return -ENOMEM; - - trie->nodes_count++; - - r = conf_files_list_strv(&files, ".hwdb", arg_root, conf_file_dirs); - if (r < 0) - return log_error_errno(r, "failed to enumerate hwdb files: %m"); - - STRV_FOREACH(f, files) { - log_debug("reading file '%s'", *f); - import_file(trie, *f); - } - strv_free(files); - - strbuf_complete(trie->strings); - - log_debug("=== trie in-memory ==="); - log_debug("nodes: %8zu bytes (%8zu)", - trie->nodes_count * sizeof(struct trie_node), trie->nodes_count); - log_debug("children arrays: %8zu bytes (%8zu)", - trie->children_count * sizeof(struct trie_child_entry), trie->children_count); - log_debug("values arrays: %8zu bytes (%8zu)", - trie->values_count * sizeof(struct trie_value_entry), trie->values_count); - log_debug("strings: %8zu bytes", - trie->strings->len); - log_debug("strings incoming: %8zu bytes (%8zu)", - trie->strings->in_len, trie->strings->in_count); - log_debug("strings dedup'ed: %8zu bytes (%8zu)", - trie->strings->dedup_len, trie->strings->dedup_count); - - hwdb_bin = strjoin(arg_root, "/", arg_hwdb_bin_dir, "/hwdb.bin", NULL); - if (!hwdb_bin) - return -ENOMEM; - - mkdir_parents(hwdb_bin, 0755); - r = trie_store(trie, hwdb_bin); - if (r < 0) - return log_error_errno(r, "Failure writing database %s: %m", hwdb_bin); - - return 0; -} - -static void help(void) { - printf("Usage: %s OPTIONS COMMAND\n\n" - "Update or query the hardware database.\n\n" - " -h --help Show this help\n" - " --version Show package version\n" - " --usr Generate in " UDEVLIBEXECDIR " instead of /etc/udev\n" - " -r --root=PATH Alternative root path in the filesystem\n\n" - "Commands:\n" - " update Update the hwdb database\n" - " query MODALIAS Query database and print result\n", - program_invocation_short_name); -} - -static int parse_argv(int argc, char *argv[]) { - enum { - ARG_VERSION = 0x100, - ARG_USR, - }; - - static const struct option options[] = { - { "help", no_argument, NULL, 'h' }, - { "version", no_argument, NULL, ARG_VERSION }, - { "usr", no_argument, NULL, ARG_USR }, - { "root", required_argument, NULL, 'r' }, - {} - }; - - int c; - - assert(argc >= 0); - assert(argv); - - while ((c = getopt_long(argc, argv, "ut:r:h", options, NULL)) >= 0) { - switch(c) { - - case 'h': - help(); - return 0; - - case ARG_VERSION: - return version(); - - case ARG_USR: - arg_hwdb_bin_dir = UDEVLIBEXECDIR; - break; - - case 'r': - arg_root = optarg; - break; - - case '?': - return -EINVAL; - - default: - assert_not_reached("Unknown option"); - } - } - - return 1; -} - -static int hwdb_main(int argc, char *argv[]) { - const Verb verbs[] = { - { "update", 1, 1, 0, hwdb_update }, - { "query", 2, 2, 0, hwdb_query }, - {}, - }; - - return dispatch_verb(argc, argv, verbs, NULL); -} - -int main (int argc, char *argv[]) { - int r; - - log_parse_environment(); - log_open(); - - r = parse_argv(argc, argv); - if (r <= 0) - goto finish; - - r = hwdb_main(argc, argv); - -finish: - return r < 0 ? EXIT_FAILURE : EXIT_SUCCESS; -} diff --git a/src/systemd-machine-id-setup/machine-id-setup.c b/src/systemd-machine-id-setup/machine-id-setup.c index 2f0f2d8cf1..78f80e2b73 120000 --- a/src/systemd-machine-id-setup/machine-id-setup.c +++ b/src/systemd-machine-id-setup/machine-id-setup.c @@ -1 +1 @@ -../libcore/machine-id-setup.c \ No newline at end of file +../grp-system/libcore/machine-id-setup.c \ No newline at end of file diff --git a/src/systemd-machine-id-setup/machine-id-setup.h b/src/systemd-machine-id-setup/machine-id-setup.h index 6b34cb74f4..d2659724ce 120000 --- a/src/systemd-machine-id-setup/machine-id-setup.h +++ b/src/systemd-machine-id-setup/machine-id-setup.h @@ -1 +1 @@ -../libcore/machine-id-setup.h \ No newline at end of file +../grp-system/libcore/machine-id-setup.h \ No newline at end of file diff --git a/src/systemd-notify/Makefile b/src/systemd-notify/Makefile deleted file mode 100644 index 06a6604e5b..0000000000 --- a/src/systemd-notify/Makefile +++ /dev/null @@ -1,33 +0,0 @@ -# -*- 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 - -rootbin_PROGRAMS += systemd-notify -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 deleted file mode 100644 index 4db49508b3..0000000000 --- a/src/systemd-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 - -#include "basic/alloc-util.h" -#include "basic/env-util.h" -#include "basic/formats-util.h" -#include "basic/log.h" -#include "basic/parse-util.h" -#include "basic/string-util.h" -#include "basic/strv.h" -#include "basic/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/loopback-setup.c b/src/systemd-nspawn/loopback-setup.c index 1078ace0bf..da633f4936 120000 --- a/src/systemd-nspawn/loopback-setup.c +++ b/src/systemd-nspawn/loopback-setup.c @@ -1 +1 @@ -../libcore/loopback-setup.c \ No newline at end of file +../grp-system/libcore/loopback-setup.c \ No newline at end of file diff --git a/src/systemd-nspawn/loopback-setup.h b/src/systemd-nspawn/loopback-setup.h index 18fc7663a2..dc051ab1bd 120000 --- a/src/systemd-nspawn/loopback-setup.h +++ b/src/systemd-nspawn/loopback-setup.h @@ -1 +1 @@ -../libcore/loopback-setup.h \ No newline at end of file +../grp-system/libcore/loopback-setup.h \ No newline at end of file diff --git a/src/systemd-nspawn/machine-id-setup.c b/src/systemd-nspawn/machine-id-setup.c index 2f0f2d8cf1..78f80e2b73 120000 --- a/src/systemd-nspawn/machine-id-setup.c +++ b/src/systemd-nspawn/machine-id-setup.c @@ -1 +1 @@ -../libcore/machine-id-setup.c \ No newline at end of file +../grp-system/libcore/machine-id-setup.c \ No newline at end of file diff --git a/src/systemd-nspawn/machine-id-setup.h b/src/systemd-nspawn/machine-id-setup.h index 6b34cb74f4..d2659724ce 120000 --- a/src/systemd-nspawn/machine-id-setup.h +++ b/src/systemd-nspawn/machine-id-setup.h @@ -1 +1 @@ -../libcore/machine-id-setup.h \ No newline at end of file +../grp-system/libcore/machine-id-setup.h \ No newline at end of file diff --git a/src/systemd-nspawn/mount-setup.c b/src/systemd-nspawn/mount-setup.c index a4ab487157..67cb74c218 120000 --- a/src/systemd-nspawn/mount-setup.c +++ b/src/systemd-nspawn/mount-setup.c @@ -1 +1 @@ -../libcore/mount-setup.c \ No newline at end of file +../grp-system/libcore/mount-setup.c \ No newline at end of file diff --git a/src/systemd-nspawn/mount-setup.h b/src/systemd-nspawn/mount-setup.h index 1f984851f8..bae54ba700 120000 --- a/src/systemd-nspawn/mount-setup.h +++ b/src/systemd-nspawn/mount-setup.h @@ -1 +1 @@ -../libcore/mount-setup.h \ No newline at end of file +../grp-system/libcore/mount-setup.h \ No newline at end of file diff --git a/src/systemd-path/Makefile b/src/systemd-path/Makefile deleted file mode 100644 index 4835742fd6..0000000000 --- a/src/systemd-path/Makefile +++ /dev/null @@ -1,38 +0,0 @@ -# -*- 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 - -bin_PROGRAMS += systemd-path - -systemd_path_SOURCES = \ - sd-path.c \ - src/path/path.c - -systemd_path_LDADD = \ - libbasic.la - -systemd.CPPFLAGS += -DLIBDIR=\"$(libdir)\" -systemd.CPPFLAGS += -DROOTLIBDIR=\"$(rootlibdir)\" - -include $(topsrcdir)/build-aux/Makefile.tail.mk diff --git a/src/systemd-path/_sd-common.h b/src/systemd-path/_sd-common.h deleted file mode 120000 index 89aca2e4a7..0000000000 --- a/src/systemd-path/_sd-common.h +++ /dev/null @@ -1 +0,0 @@ -../libsystemd/include/systemd/_sd-common.h \ No newline at end of file diff --git a/src/systemd-path/path.c b/src/systemd-path/path.c deleted file mode 100644 index aa63884365..0000000000 --- a/src/systemd-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 "basic/alloc-util.h" -#include "basic/log.h" -#include "basic/macro.h" -#include "basic/string-util.h" -#include "basic/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-path/sd-path.c b/src/systemd-path/sd-path.c deleted file mode 100644 index b904b56c01..0000000000 --- a/src/systemd-path/sd-path.c +++ /dev/null @@ -1,638 +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 "basic/alloc-util.h" -#include "basic/architecture.h" -#include "basic/fd-util.h" -#include "basic/fileio.h" -#include "basic/missing.h" -#include "basic/path-util.h" -#include "basic/string-util.h" -#include "basic/strv.h" -#include "basic/user-util.h" -#include "basic/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; - _cleanup_free_ const char *fn = NULL; - const char *c = NULL; - char line[LINE_MAX]; - size_t n; - int r; - - assert(field); - assert(buffer); - assert(ret); - - r = from_home_dir("XDG_CONFIG_HOME", ".config", &b, &c); - if (r < 0) - return r; - - fn = strappend(c, "/user-dirs.dirs"); - if (!fn) - return -ENOMEM; - - 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/systemd-path/sd-path.h b/src/systemd-path/sd-path.h deleted file mode 100644 index be6abdcd03..0000000000 --- a/src/systemd-path/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-remount-fs/mount-setup.c b/src/systemd-remount-fs/mount-setup.c index a4ab487157..67cb74c218 120000 --- a/src/systemd-remount-fs/mount-setup.c +++ b/src/systemd-remount-fs/mount-setup.c @@ -1 +1 @@ -../libcore/mount-setup.c \ No newline at end of file +../grp-system/libcore/mount-setup.c \ No newline at end of file diff --git a/src/systemd-remount-fs/mount-setup.h b/src/systemd-remount-fs/mount-setup.h index 1f984851f8..bae54ba700 120000 --- a/src/systemd-remount-fs/mount-setup.h +++ b/src/systemd-remount-fs/mount-setup.h @@ -1 +1 @@ -../libcore/mount-setup.h \ No newline at end of file +../grp-system/libcore/mount-setup.h \ No newline at end of file diff --git a/src/systemd-run/Makefile b/src/systemd-run/Makefile deleted file mode 100644 index a8edcf66c1..0000000000 --- a/src/systemd-run/Makefile +++ /dev/null @@ -1,33 +0,0 @@ -# -*- 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 - -bin_PROGRAMS += systemd-run -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 deleted file mode 100644 index 9ff2ba8003..0000000000 --- a/src/systemd-run/run.c +++ /dev/null @@ -1,1261 +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 "basic/alloc-util.h" -#include "sd-bus/bus-error.h" -#include "shared/bus-unit-util.h" -#include "shared/bus-util.h" -#include "basic/calendarspec.h" -#include "basic/env-util.h" -#include "basic/fd-util.h" -#include "basic/formats-util.h" -#include "basic/parse-util.h" -#include "basic/path-util.h" -#include "shared/ptyfwd.h" -#include "basic/signal-util.h" -#include "shared/spawn-polkit-agent.h" -#include "basic/strv.h" -#include "basic/terminal-util.h" -#include "basic/unit-name.h" -#include "basic/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 a timer option is specified and the unit specified with\n" - "the --unit option exists, the 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" - " -E --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_USER, - ARG_SYSTEM, - ARG_SCOPE, - ARG_UNIT, - ARG_DESCRIPTION, - ARG_SLICE, - ARG_SEND_SIGHUP, - ARG_SERVICE_TYPE, - ARG_EXEC_USER, - ARG_EXEC_GROUP, - ARG_NICE, - 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, - ARG_NO_ASK_PASSWORD, - }; - - 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, 'E' }, - { "property", required_argument, NULL, 'p' }, - { "tty", no_argument, NULL, 't' }, /* deprecated */ - { "pty", 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 'E': - 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 = transient_cgroup_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; - _cleanup_(sd_bus_message_unrefp) sd_bus_message *pty_reply = 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, - &pty_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(pty_reply, "hs", &master, &s); - if (r < 0) - return bus_log_parse_error(r); - - 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/killall.c b/src/systemd-shutdown/killall.c index 5ddb36306b..84c06d47ee 120000 --- a/src/systemd-shutdown/killall.c +++ b/src/systemd-shutdown/killall.c @@ -1 +1 @@ -../libcore/killall.c \ No newline at end of file +../grp-system/libcore/killall.c \ No newline at end of file diff --git a/src/systemd-shutdown/killall.h b/src/systemd-shutdown/killall.h index 60b334fe91..10ac8c905d 120000 --- a/src/systemd-shutdown/killall.h +++ b/src/systemd-shutdown/killall.h @@ -1 +1 @@ -../libcore/killall.h \ No newline at end of file +../grp-system/libcore/killall.h \ No newline at end of file diff --git a/src/systemd-shutdown/mount-setup.c b/src/systemd-shutdown/mount-setup.c index a4ab487157..67cb74c218 120000 --- a/src/systemd-shutdown/mount-setup.c +++ b/src/systemd-shutdown/mount-setup.c @@ -1 +1 @@ -../libcore/mount-setup.c \ No newline at end of file +../grp-system/libcore/mount-setup.c \ No newline at end of file diff --git a/src/systemd-shutdown/mount-setup.h b/src/systemd-shutdown/mount-setup.h index 1f984851f8..bae54ba700 120000 --- a/src/systemd-shutdown/mount-setup.h +++ b/src/systemd-shutdown/mount-setup.h @@ -1 +1 @@ -../libcore/mount-setup.h \ No newline at end of file +../grp-system/libcore/mount-setup.h \ No newline at end of file diff --git a/src/systemd-sysctl/Makefile b/src/systemd-sysctl/Makefile deleted file mode 100644 index 39ce08d5e3..0000000000 --- a/src/systemd-sysctl/Makefile +++ /dev/null @@ -1,33 +0,0 @@ -# -*- 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-sysctl -systemd_sysctl_SOURCES = \ - src/sysctl/sysctl.c - -systemd_sysctl_LDADD = \ - libshared.la - -include $(topsrcdir)/build-aux/Makefile.tail.mk diff --git a/src/systemd-sysctl/sysctl.c b/src/systemd-sysctl/sysctl.c deleted file mode 100644 index 132420d95d..0000000000 --- a/src/systemd-sysctl/sysctl.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 -#include - -#include "basic/conf-files.h" -#include "basic/def.h" -#include "basic/fd-util.h" -#include "basic/fileio.h" -#include "basic/hashmap.h" -#include "basic/log.h" -#include "basic/path-util.h" -#include "basic/string-util.h" -#include "basic/strv.h" -#include "shared/sysctl-util.h" -#include "basic/util.h" - -static char **arg_prefixes = NULL; - -static const char conf_file_dirs[] = CONF_PATHS_NULSTR("sysctl.d"); - -static int apply_all(Hashmap *sysctl_options) { - char *property, *value; - Iterator i; - int r = 0; - - HASHMAP_FOREACH_KEY(value, property, sysctl_options, i) { - int k; - - k = sysctl_write(property, value); - if (k < 0) { - log_full_errno(k == -ENOENT ? LOG_INFO : LOG_WARNING, k, - "Couldn't write '%s' to '%s', ignoring: %m", value, property); - - if (r == 0 && k != -ENOENT) - r = k; - } - } - - return r; -} - -static int parse_file(Hashmap *sysctl_options, 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("Parsing %s", path); - while (!feof(f)) { - char l[LINE_MAX], *p, *value, *new_value, *property, *existing; - void *v; - 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; - - value = strchr(p, '='); - if (!value) { - log_error("Line is not an assignment in file '%s': %s", path, value); - - if (r == 0) - r = -EINVAL; - continue; - } - - *value = 0; - value++; - - p = sysctl_normalize(strstrip(p)); - value = strstrip(value); - - if (!strv_isempty(arg_prefixes)) { - char **i, *t; - STRV_FOREACH(i, arg_prefixes) { - t = path_startswith(*i, "/proc/sys/"); - if (t == NULL) - t = *i; - if (path_startswith(p, t)) - goto found; - } - /* not found */ - continue; - } - -found: - existing = hashmap_get2(sysctl_options, p, &v); - if (existing) { - if (streq(value, existing)) - continue; - - log_debug("Overwriting earlier assignment of %s in file '%s'.", p, path); - free(hashmap_remove(sysctl_options, p)); - free(v); - } - - property = strdup(p); - if (!property) - return log_oom(); - - new_value = strdup(value); - if (!new_value) { - free(property); - return log_oom(); - } - - k = hashmap_put(sysctl_options, property, new_value); - if (k < 0) { - log_error_errno(k, "Failed to add sysctl variable %s to hashmap: %m", property); - free(property); - free(new_value); - return k; - } - } - - return r; -} - -static void help(void) { - printf("%s [OPTIONS...] [CONFIGURATION FILE...]\n\n" - "Applies kernel sysctl settings.\n\n" - " -h --help Show this help\n" - " --version Show package version\n" - " --prefix=PATH Only apply rules with the specified prefix\n" - , program_invocation_short_name); -} - -static int parse_argv(int argc, char *argv[]) { - - enum { - ARG_VERSION = 0x100, - ARG_PREFIX - }; - - static const struct option options[] = { - { "help", no_argument, NULL, 'h' }, - { "version", no_argument, NULL, ARG_VERSION }, - { "prefix", required_argument, NULL, ARG_PREFIX }, - {} - }; - - 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_PREFIX: { - char *p; - - /* We used to require people to specify absolute paths - * in /proc/sys in the past. This is kinda useless, but - * we need to keep compatibility. We now support any - * sysctl name available. */ - sysctl_normalize(optarg); - - if (startswith(optarg, "/proc/sys")) - p = strdup(optarg); - else - p = strappend("/proc/sys/", optarg); - if (!p) - return log_oom(); - - if (strv_consume(&arg_prefixes, p) < 0) - return log_oom(); - - break; - } - - case '?': - return -EINVAL; - - default: - assert_not_reached("Unhandled option"); - } - - return 1; -} - -int main(int argc, char *argv[]) { - int r = 0, k; - Hashmap *sysctl_options; - - 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); - - sysctl_options = hashmap_new(&string_hash_ops); - if (!sysctl_options) { - r = log_oom(); - goto finish; - } - - r = 0; - - if (argc > optind) { - int i; - - for (i = optind; i < argc; i++) { - k = parse_file(sysctl_options, 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 sysctl.d files: %m"); - goto finish; - } - - STRV_FOREACH(f, files) { - k = parse_file(sysctl_options, *f, true); - if (k < 0 && r == 0) - r = k; - } - } - - k = apply_all(sysctl_options); - if (k < 0 && r == 0) - r = k; - -finish: - hashmap_free_free_free(sysctl_options); - strv_free(arg_prefixes); - - return r < 0 ? EXIT_FAILURE : EXIT_SUCCESS; -} diff --git a/src/systemd-sysusers/Makefile b/src/systemd-sysusers/Makefile deleted file mode 100644 index d18e41b1d5..0000000000 --- a/src/systemd-sysusers/Makefile +++ /dev/null @@ -1,61 +0,0 @@ -# -*- 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 # HAVE_REMOTE - -INSTALL_DIRS += \ - $(sysusersdir) -endif # ENABLE_SYSUSERS - -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/systemd-sysusers/sysusers.c b/src/systemd-sysusers/sysusers.c deleted file mode 100644 index 83e526c5ff..0000000000 --- a/src/systemd-sysusers/sysusers.c +++ /dev/null @@ -1,1919 +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 "basic/alloc-util.h" -#include "basic/conf-files.h" -#include "basic/copy.h" -#include "basic/def.h" -#include "basic/fd-util.h" -#include "basic/fileio-label.h" -#include "basic/formats-util.h" -#include "basic/hashmap.h" -#include "basic/path-util.h" -#include "basic/selinux-util.h" -#include "basic/smack-util.h" -#include "shared/specifier.h" -#include "basic/string-util.h" -#include "basic/strv.h" -#include "shared/uid-range.h" -#include "basic/user-util.h" -#include "basic/utf8.h" -#include "basic/util.h" - -typedef enum ItemType { - ADD_USER = 'u', - ADD_GROUP = 'g', - ADD_MEMBER = 'm', - ADD_RANGE = 'r', -} ItemType; -typedef struct Item { - ItemType type; - - char *name; - char *uid_path; - char *gid_path; - char *description; - char *home; - - gid_t gid; - uid_t uid; - - bool gid_set:1; - bool uid_set:1; - - bool todo_user:1; - bool todo_group:1; -} Item; - -static char *arg_root = NULL; - -static const char conf_file_dirs[] = CONF_PATHS_NULSTR("sysusers.d"); - -static Hashmap *users = NULL, *groups = NULL; -static Hashmap *todo_uids = NULL, *todo_gids = NULL; -static Hashmap *members = NULL; - -static Hashmap *database_uid = NULL, *database_user = NULL; -static Hashmap *database_gid = NULL, *database_group = NULL; - -static uid_t search_uid = UID_INVALID; -static UidRange *uid_range = NULL; -static unsigned n_uid_range = 0; - -static int load_user_database(void) { - _cleanup_fclose_ FILE *f = NULL; - const char *passwd_path; - struct passwd *pw; - int r; - - passwd_path = prefix_roota(arg_root, "/etc/passwd"); - f = fopen(passwd_path, "re"); - if (!f) - return errno == ENOENT ? 0 : -errno; - - r = hashmap_ensure_allocated(&database_user, &string_hash_ops); - if (r < 0) - return r; - - r = hashmap_ensure_allocated(&database_uid, NULL); - if (r < 0) - return r; - - errno = 0; - while ((pw = fgetpwent(f))) { - char *n; - int k, q; - - n = strdup(pw->pw_name); - if (!n) - return -ENOMEM; - - k = hashmap_put(database_user, n, UID_TO_PTR(pw->pw_uid)); - if (k < 0 && k != -EEXIST) { - free(n); - return k; - } - - q = hashmap_put(database_uid, UID_TO_PTR(pw->pw_uid), n); - if (q < 0 && q != -EEXIST) { - if (k < 0) - free(n); - return q; - } - - if (q < 0 && k < 0) - free(n); - - errno = 0; - } - if (!IN_SET(errno, 0, ENOENT)) - return -errno; - - return 0; -} - -static int load_group_database(void) { - _cleanup_fclose_ FILE *f = NULL; - const char *group_path; - struct group *gr; - int r; - - group_path = prefix_roota(arg_root, "/etc/group"); - f = fopen(group_path, "re"); - if (!f) - return errno == ENOENT ? 0 : -errno; - - r = hashmap_ensure_allocated(&database_group, &string_hash_ops); - if (r < 0) - return r; - - r = hashmap_ensure_allocated(&database_gid, NULL); - if (r < 0) - return r; - - errno = 0; - while ((gr = fgetgrent(f))) { - char *n; - int k, q; - - n = strdup(gr->gr_name); - if (!n) - return -ENOMEM; - - k = hashmap_put(database_group, n, GID_TO_PTR(gr->gr_gid)); - if (k < 0 && k != -EEXIST) { - free(n); - return k; - } - - q = hashmap_put(database_gid, GID_TO_PTR(gr->gr_gid), n); - if (q < 0 && q != -EEXIST) { - if (k < 0) - free(n); - return q; - } - - if (q < 0 && k < 0) - free(n); - - errno = 0; - } - if (!IN_SET(errno, 0, ENOENT)) - return -errno; - - return 0; -} - -static int make_backup(const char *target, const char *x) { - _cleanup_close_ int src = -1; - _cleanup_fclose_ FILE *dst = NULL; - char *backup, *temp; - struct timespec ts[2]; - struct stat st; - int r; - - src = open(x, O_RDONLY|O_CLOEXEC|O_NOCTTY); - if (src < 0) { - if (errno == ENOENT) /* No backup necessary... */ - return 0; - - return -errno; - } - - if (fstat(src, &st) < 0) - return -errno; - - r = fopen_temporary_label(target, x, &dst, &temp); - if (r < 0) - return r; - - r = copy_bytes(src, fileno(dst), (uint64_t) -1, true); - if (r < 0) - goto fail; - - /* Don't fail on chmod() or chown(). If it stays owned by us - * and/or unreadable by others, then it isn't too bad... */ - - backup = strjoina(x, "-"); - - /* Copy over the access mask */ - if (fchmod(fileno(dst), st.st_mode & 07777) < 0) - log_warning_errno(errno, "Failed to change mode on %s: %m", backup); - - if (fchown(fileno(dst), st.st_uid, st.st_gid)< 0) - log_warning_errno(errno, "Failed to change ownership of %s: %m", backup); - - ts[0] = st.st_atim; - ts[1] = st.st_mtim; - if (futimens(fileno(dst), ts) < 0) - log_warning_errno(errno, "Failed to fix access and modification time of %s: %m", backup); - - if (rename(temp, backup) < 0) - goto fail; - - return 0; - -fail: - unlink(temp); - return r; -} - -static int putgrent_with_members(const struct group *gr, FILE *group) { - char **a; - - assert(gr); - assert(group); - - a = hashmap_get(members, gr->gr_name); - if (a) { - _cleanup_strv_free_ char **l = NULL; - bool added = false; - char **i; - - l = strv_copy(gr->gr_mem); - if (!l) - return -ENOMEM; - - STRV_FOREACH(i, a) { - if (strv_find(l, *i)) - continue; - - if (strv_extend(&l, *i) < 0) - return -ENOMEM; - - added = true; - } - - if (added) { - struct group t; - - strv_uniq(l); - strv_sort(l); - - t = *gr; - t.gr_mem = l; - - errno = 0; - if (putgrent(&t, group) != 0) - return errno > 0 ? -errno : -EIO; - - return 1; - } - } - - errno = 0; - if (putgrent(gr, group) != 0) - return errno > 0 ? -errno : -EIO; - - return 0; -} - -static int putsgent_with_members(const struct sgrp *sg, FILE *gshadow) { - char **a; - - assert(sg); - assert(gshadow); - - a = hashmap_get(members, sg->sg_namp); - if (a) { - _cleanup_strv_free_ char **l = NULL; - bool added = false; - char **i; - - l = strv_copy(sg->sg_mem); - if (!l) - return -ENOMEM; - - STRV_FOREACH(i, a) { - if (strv_find(l, *i)) - continue; - - if (strv_extend(&l, *i) < 0) - return -ENOMEM; - - added = true; - } - - if (added) { - struct sgrp t; - - strv_uniq(l); - strv_sort(l); - - t = *sg; - t.sg_mem = l; - - errno = 0; - if (putsgent(&t, gshadow) != 0) - return errno > 0 ? -errno : -EIO; - - return 1; - } - } - - errno = 0; - if (putsgent(sg, gshadow) != 0) - return errno > 0 ? -errno : -EIO; - - return 0; -} - -static int sync_rights(FILE *from, FILE *to) { - struct stat st; - - if (fstat(fileno(from), &st) < 0) - return -errno; - - if (fchmod(fileno(to), st.st_mode & 07777) < 0) - return -errno; - - if (fchown(fileno(to), st.st_uid, st.st_gid) < 0) - return -errno; - - return 0; -} - -static int rename_and_apply_smack(const char *temp_path, const char *dest_path) { - int r = 0; - if (rename(temp_path, dest_path) < 0) - return -errno; - -#ifdef SMACK_RUN_LABEL - r = mac_smack_apply(dest_path, SMACK_ATTR_ACCESS, SMACK_FLOOR_LABEL); - if (r < 0) - return r; -#endif - return r; -} - -static int write_files(void) { - - _cleanup_fclose_ FILE *passwd = NULL, *group = NULL, *shadow = NULL, *gshadow = NULL; - _cleanup_free_ char *passwd_tmp = NULL, *group_tmp = NULL, *shadow_tmp = NULL, *gshadow_tmp = NULL; - const char *passwd_path = NULL, *group_path = NULL, *shadow_path = NULL, *gshadow_path = NULL; - bool group_changed = false; - Iterator iterator; - Item *i; - int r; - - if (hashmap_size(todo_gids) > 0 || hashmap_size(members) > 0) { - _cleanup_fclose_ FILE *original = NULL; - - /* First we update the actual group list file */ - group_path = prefix_roota(arg_root, "/etc/group"); - r = fopen_temporary_label("/etc/group", group_path, &group, &group_tmp); - if (r < 0) - goto finish; - - original = fopen(group_path, "re"); - if (original) { - struct group *gr; - - r = sync_rights(original, group); - if (r < 0) - goto finish; - - errno = 0; - while ((gr = fgetgrent(original))) { - /* Safety checks against name and GID - * collisions. Normally, this should - * be unnecessary, but given that we - * look at the entries anyway here, - * let's make an extra verification - * step that we don't generate - * duplicate entries. */ - - i = hashmap_get(groups, gr->gr_name); - if (i && i->todo_group) { - log_error("%s: Group \"%s\" already exists.", group_path, gr->gr_name); - r = -EEXIST; - goto finish; - } - - if (hashmap_contains(todo_gids, GID_TO_PTR(gr->gr_gid))) { - log_error("%s: Detected collision for GID " GID_FMT ".", group_path, gr->gr_gid); - r = -EEXIST; - goto finish; - } - - r = putgrent_with_members(gr, group); - if (r < 0) - goto finish; - if (r > 0) - group_changed = true; - - errno = 0; - } - if (!IN_SET(errno, 0, ENOENT)) { - r = -errno; - goto finish; - } - - } else if (errno != ENOENT) { - r = -errno; - goto finish; - } else if (fchmod(fileno(group), 0644) < 0) { - r = -errno; - goto finish; - } - - HASHMAP_FOREACH(i, todo_gids, iterator) { - struct group n = { - .gr_name = i->name, - .gr_gid = i->gid, - .gr_passwd = (char*) "x", - }; - - r = putgrent_with_members(&n, group); - if (r < 0) - goto finish; - - group_changed = true; - } - - r = fflush_and_check(group); - if (r < 0) - goto finish; - - if (original) { - fclose(original); - original = NULL; - } - - /* OK, now also update the shadow file for the group list */ - gshadow_path = prefix_roota(arg_root, "/etc/gshadow"); - r = fopen_temporary_label("/etc/gshadow", gshadow_path, &gshadow, &gshadow_tmp); - if (r < 0) - goto finish; - - original = fopen(gshadow_path, "re"); - if (original) { - struct sgrp *sg; - - r = sync_rights(original, gshadow); - if (r < 0) - goto finish; - - errno = 0; - while ((sg = fgetsgent(original))) { - - i = hashmap_get(groups, sg->sg_namp); - if (i && i->todo_group) { - log_error("%s: Group \"%s\" already exists.", gshadow_path, sg->sg_namp); - r = -EEXIST; - goto finish; - } - - r = putsgent_with_members(sg, gshadow); - if (r < 0) - goto finish; - if (r > 0) - group_changed = true; - - errno = 0; - } - if (!IN_SET(errno, 0, ENOENT)) { - r = -errno; - goto finish; - } - - } else if (errno != ENOENT) { - r = -errno; - goto finish; - } else if (fchmod(fileno(gshadow), 0000) < 0) { - r = -errno; - goto finish; - } - - HASHMAP_FOREACH(i, todo_gids, iterator) { - struct sgrp n = { - .sg_namp = i->name, - .sg_passwd = (char*) "!!", - }; - - r = putsgent_with_members(&n, gshadow); - if (r < 0) - goto finish; - - group_changed = true; - } - - r = fflush_and_check(gshadow); - if (r < 0) - goto finish; - } - - if (hashmap_size(todo_uids) > 0) { - _cleanup_fclose_ FILE *original = NULL; - long lstchg; - - /* First we update the user database itself */ - passwd_path = prefix_roota(arg_root, "/etc/passwd"); - r = fopen_temporary_label("/etc/passwd", passwd_path, &passwd, &passwd_tmp); - if (r < 0) - goto finish; - - original = fopen(passwd_path, "re"); - if (original) { - struct passwd *pw; - - r = sync_rights(original, passwd); - if (r < 0) - goto finish; - - errno = 0; - while ((pw = fgetpwent(original))) { - - i = hashmap_get(users, pw->pw_name); - if (i && i->todo_user) { - log_error("%s: User \"%s\" already exists.", passwd_path, pw->pw_name); - r = -EEXIST; - goto finish; - } - - if (hashmap_contains(todo_uids, UID_TO_PTR(pw->pw_uid))) { - log_error("%s: Detected collision for UID " UID_FMT ".", passwd_path, pw->pw_uid); - r = -EEXIST; - goto finish; - } - - errno = 0; - if (putpwent(pw, passwd) < 0) { - r = errno ? -errno : -EIO; - goto finish; - } - - errno = 0; - } - if (!IN_SET(errno, 0, ENOENT)) { - r = -errno; - goto finish; - } - - } else if (errno != ENOENT) { - r = -errno; - goto finish; - } else if (fchmod(fileno(passwd), 0644) < 0) { - r = -errno; - goto finish; - } - - HASHMAP_FOREACH(i, todo_uids, iterator) { - struct passwd n = { - .pw_name = i->name, - .pw_uid = i->uid, - .pw_gid = i->gid, - .pw_gecos = i->description, - - /* "x" means the password is stored in - * the shadow file */ - .pw_passwd = (char*) "x", - - /* We default to the root directory as home */ - .pw_dir = i->home ? i->home : (char*) "/", - - /* Initialize the shell to nologin, - * with one exception: for root we - * patch in something special */ - .pw_shell = i->uid == 0 ? (char*) "/bin/sh" : (char*) "/sbin/nologin", - }; - - errno = 0; - if (putpwent(&n, passwd) != 0) { - r = errno ? -errno : -EIO; - goto finish; - } - } - - r = fflush_and_check(passwd); - if (r < 0) - goto finish; - - if (original) { - fclose(original); - original = NULL; - } - - /* The we update the shadow database */ - shadow_path = prefix_roota(arg_root, "/etc/shadow"); - r = fopen_temporary_label("/etc/shadow", shadow_path, &shadow, &shadow_tmp); - if (r < 0) - goto finish; - - lstchg = (long) (now(CLOCK_REALTIME) / USEC_PER_DAY); - - original = fopen(shadow_path, "re"); - if (original) { - struct spwd *sp; - - r = sync_rights(original, shadow); - if (r < 0) - goto finish; - - errno = 0; - while ((sp = fgetspent(original))) { - - i = hashmap_get(users, sp->sp_namp); - if (i && i->todo_user) { - /* we will update the existing entry */ - sp->sp_lstchg = lstchg; - - /* only the /etc/shadow stage is left, so we can - * safely remove the item from the todo set */ - i->todo_user = false; - hashmap_remove(todo_uids, UID_TO_PTR(i->uid)); - } - - errno = 0; - if (putspent(sp, shadow) < 0) { - r = errno ? -errno : -EIO; - goto finish; - } - - errno = 0; - } - if (!IN_SET(errno, 0, ENOENT)) { - r = -errno; - goto finish; - } - } else if (errno != ENOENT) { - r = -errno; - goto finish; - } else if (fchmod(fileno(shadow), 0000) < 0) { - r = -errno; - goto finish; - } - - HASHMAP_FOREACH(i, todo_uids, iterator) { - struct spwd n = { - .sp_namp = i->name, - .sp_pwdp = (char*) "!!", - .sp_lstchg = lstchg, - .sp_min = -1, - .sp_max = -1, - .sp_warn = -1, - .sp_inact = -1, - .sp_expire = -1, - .sp_flag = (unsigned long) -1, /* this appears to be what everybody does ... */ - }; - - errno = 0; - if (putspent(&n, shadow) != 0) { - r = errno ? -errno : -EIO; - goto finish; - } - } - - r = fflush_and_check(shadow); - if (r < 0) - goto finish; - } - - /* Make a backup of the old files */ - if (group_changed) { - if (group) { - r = make_backup("/etc/group", group_path); - if (r < 0) - goto finish; - } - if (gshadow) { - r = make_backup("/etc/gshadow", gshadow_path); - if (r < 0) - goto finish; - } - } - - if (passwd) { - r = make_backup("/etc/passwd", passwd_path); - if (r < 0) - goto finish; - } - if (shadow) { - r = make_backup("/etc/shadow", shadow_path); - if (r < 0) - goto finish; - } - - /* And make the new files count */ - if (group_changed) { - if (group) { - r = rename_and_apply_smack(group_tmp, group_path); - if (r < 0) - goto finish; - - group_tmp = mfree(group_tmp); - } - if (gshadow) { - r = rename_and_apply_smack(gshadow_tmp, gshadow_path); - if (r < 0) - goto finish; - - gshadow_tmp = mfree(gshadow_tmp); - } - } - - if (passwd) { - r = rename_and_apply_smack(passwd_tmp, passwd_path); - if (r < 0) - goto finish; - - passwd_tmp = mfree(passwd_tmp); - } - if (shadow) { - r = rename_and_apply_smack(shadow_tmp, shadow_path); - if (r < 0) - goto finish; - - shadow_tmp = mfree(shadow_tmp); - } - - r = 0; - -finish: - if (passwd_tmp) - unlink(passwd_tmp); - if (shadow_tmp) - unlink(shadow_tmp); - if (group_tmp) - unlink(group_tmp); - if (gshadow_tmp) - unlink(gshadow_tmp); - - return r; -} - -static int uid_is_ok(uid_t uid, const char *name) { - struct passwd *p; - struct group *g; - const char *n; - Item *i; - - /* Let's see if we already have assigned the UID a second time */ - if (hashmap_get(todo_uids, UID_TO_PTR(uid))) - return 0; - - /* Try to avoid using uids that are already used by a group - * that doesn't have the same name as our new user. */ - i = hashmap_get(todo_gids, GID_TO_PTR(uid)); - if (i && !streq(i->name, name)) - return 0; - - /* Let's check the files directly */ - if (hashmap_contains(database_uid, UID_TO_PTR(uid))) - return 0; - - n = hashmap_get(database_gid, GID_TO_PTR(uid)); - if (n && !streq(n, name)) - return 0; - - /* Let's also check via NSS, to avoid UID clashes over LDAP and such, just in case */ - if (!arg_root) { - errno = 0; - p = getpwuid(uid); - if (p) - return 0; - if (!IN_SET(errno, 0, ENOENT)) - return -errno; - - errno = 0; - g = getgrgid((gid_t) uid); - if (g) { - if (!streq(g->gr_name, name)) - return 0; - } else if (!IN_SET(errno, 0, ENOENT)) - return -errno; - } - - return 1; -} - -static int root_stat(const char *p, struct stat *st) { - const char *fix; - - fix = prefix_roota(arg_root, p); - if (stat(fix, st) < 0) - return -errno; - - return 0; -} - -static int read_id_from_file(Item *i, uid_t *_uid, gid_t *_gid) { - struct stat st; - bool found_uid = false, found_gid = false; - uid_t uid = 0; - gid_t gid = 0; - - assert(i); - - /* First, try to get the gid directly */ - if (_gid && i->gid_path && root_stat(i->gid_path, &st) >= 0) { - gid = st.st_gid; - found_gid = true; - } - - /* Then, try to get the uid directly */ - if ((_uid || (_gid && !found_gid)) - && i->uid_path - && root_stat(i->uid_path, &st) >= 0) { - - uid = st.st_uid; - found_uid = true; - - /* If we need the gid, but had no success yet, also derive it from the uid path */ - if (_gid && !found_gid) { - gid = st.st_gid; - found_gid = true; - } - } - - /* If that didn't work yet, then let's reuse the gid as uid */ - if (_uid && !found_uid && i->gid_path) { - - if (found_gid) { - uid = (uid_t) gid; - found_uid = true; - } else if (root_stat(i->gid_path, &st) >= 0) { - uid = (uid_t) st.st_gid; - found_uid = true; - } - } - - if (_uid) { - if (!found_uid) - return 0; - - *_uid = uid; - } - - if (_gid) { - if (!found_gid) - return 0; - - *_gid = gid; - } - - return 1; -} - -static int add_user(Item *i) { - void *z; - int r; - - assert(i); - - /* Check the database directly */ - z = hashmap_get(database_user, i->name); - if (z) { - log_debug("User %s already exists.", i->name); - i->uid = PTR_TO_UID(z); - i->uid_set = true; - return 0; - } - - if (!arg_root) { - struct passwd *p; - - /* Also check NSS */ - errno = 0; - p = getpwnam(i->name); - if (p) { - log_debug("User %s already exists.", i->name); - i->uid = p->pw_uid; - i->uid_set = true; - - r = free_and_strdup(&i->description, p->pw_gecos); - if (r < 0) - return log_oom(); - - return 0; - } - if (!IN_SET(errno, 0, ENOENT)) - return log_error_errno(errno, "Failed to check if user %s already exists: %m", i->name); - } - - /* Try to use the suggested numeric uid */ - if (i->uid_set) { - r = uid_is_ok(i->uid, i->name); - if (r < 0) - return log_error_errno(r, "Failed to verify uid " UID_FMT ": %m", i->uid); - if (r == 0) { - log_debug("Suggested user ID " UID_FMT " for %s already used.", i->uid, i->name); - i->uid_set = false; - } - } - - /* If that didn't work, try to read it from the specified path */ - if (!i->uid_set) { - uid_t c; - - if (read_id_from_file(i, &c, NULL) > 0) { - - if (c <= 0 || !uid_range_contains(uid_range, n_uid_range, c)) - log_debug("User ID " UID_FMT " of file not suitable for %s.", c, i->name); - else { - r = uid_is_ok(c, i->name); - if (r < 0) - return log_error_errno(r, "Failed to verify uid " UID_FMT ": %m", i->uid); - else if (r > 0) { - i->uid = c; - i->uid_set = true; - } else - log_debug("User ID " UID_FMT " of file for %s is already used.", c, i->name); - } - } - } - - /* Otherwise, try to reuse the group ID */ - if (!i->uid_set && i->gid_set) { - r = uid_is_ok((uid_t) i->gid, i->name); - if (r < 0) - return log_error_errno(r, "Failed to verify uid " UID_FMT ": %m", i->uid); - if (r > 0) { - i->uid = (uid_t) i->gid; - i->uid_set = true; - } - } - - /* And if that didn't work either, let's try to find a free one */ - if (!i->uid_set) { - for (;;) { - r = uid_range_next_lower(uid_range, n_uid_range, &search_uid); - if (r < 0) { - log_error("No free user ID available for %s.", i->name); - return r; - } - - r = uid_is_ok(search_uid, i->name); - if (r < 0) - return log_error_errno(r, "Failed to verify uid " UID_FMT ": %m", i->uid); - else if (r > 0) - break; - } - - i->uid_set = true; - i->uid = search_uid; - } - - r = hashmap_ensure_allocated(&todo_uids, NULL); - if (r < 0) - return log_oom(); - - r = hashmap_put(todo_uids, UID_TO_PTR(i->uid), i); - if (r < 0) - return log_oom(); - - i->todo_user = true; - log_info("Creating user %s (%s) with uid " UID_FMT " and gid " GID_FMT ".", i->name, strna(i->description), i->uid, i->gid); - - return 0; -} - -static int gid_is_ok(gid_t gid) { - struct group *g; - struct passwd *p; - - if (hashmap_get(todo_gids, GID_TO_PTR(gid))) - return 0; - - /* Avoid reusing gids that are already used by a different user */ - if (hashmap_get(todo_uids, UID_TO_PTR(gid))) - return 0; - - if (hashmap_contains(database_gid, GID_TO_PTR(gid))) - return 0; - - if (hashmap_contains(database_uid, UID_TO_PTR(gid))) - return 0; - - if (!arg_root) { - errno = 0; - g = getgrgid(gid); - if (g) - return 0; - if (!IN_SET(errno, 0, ENOENT)) - return -errno; - - errno = 0; - p = getpwuid((uid_t) gid); - if (p) - return 0; - if (!IN_SET(errno, 0, ENOENT)) - return -errno; - } - - return 1; -} - -static int add_group(Item *i) { - void *z; - int r; - - assert(i); - - /* Check the database directly */ - z = hashmap_get(database_group, i->name); - if (z) { - log_debug("Group %s already exists.", i->name); - i->gid = PTR_TO_GID(z); - i->gid_set = true; - return 0; - } - - /* Also check NSS */ - if (!arg_root) { - struct group *g; - - errno = 0; - g = getgrnam(i->name); - if (g) { - log_debug("Group %s already exists.", i->name); - i->gid = g->gr_gid; - i->gid_set = true; - return 0; - } - if (!IN_SET(errno, 0, ENOENT)) - return log_error_errno(errno, "Failed to check if group %s already exists: %m", i->name); - } - - /* Try to use the suggested numeric gid */ - if (i->gid_set) { - r = gid_is_ok(i->gid); - if (r < 0) - return log_error_errno(r, "Failed to verify gid " GID_FMT ": %m", i->gid); - if (r == 0) { - log_debug("Suggested group ID " GID_FMT " for %s already used.", i->gid, i->name); - i->gid_set = false; - } - } - - /* Try to reuse the numeric uid, if there's one */ - if (!i->gid_set && i->uid_set) { - r = gid_is_ok((gid_t) i->uid); - if (r < 0) - return log_error_errno(r, "Failed to verify gid " GID_FMT ": %m", i->gid); - if (r > 0) { - i->gid = (gid_t) i->uid; - i->gid_set = true; - } - } - - /* If that didn't work, try to read it from the specified path */ - if (!i->gid_set) { - gid_t c; - - if (read_id_from_file(i, NULL, &c) > 0) { - - if (c <= 0 || !uid_range_contains(uid_range, n_uid_range, c)) - log_debug("Group ID " GID_FMT " of file not suitable for %s.", c, i->name); - else { - r = gid_is_ok(c); - if (r < 0) - return log_error_errno(r, "Failed to verify gid " GID_FMT ": %m", i->gid); - else if (r > 0) { - i->gid = c; - i->gid_set = true; - } else - log_debug("Group ID " GID_FMT " of file for %s already used.", c, i->name); - } - } - } - - /* And if that didn't work either, let's try to find a free one */ - if (!i->gid_set) { - for (;;) { - /* We look for new GIDs in the UID pool! */ - r = uid_range_next_lower(uid_range, n_uid_range, &search_uid); - if (r < 0) { - log_error("No free group ID available for %s.", i->name); - return r; - } - - r = gid_is_ok(search_uid); - if (r < 0) - return log_error_errno(r, "Failed to verify gid " GID_FMT ": %m", i->gid); - else if (r > 0) - break; - } - - i->gid_set = true; - i->gid = search_uid; - } - - r = hashmap_ensure_allocated(&todo_gids, NULL); - if (r < 0) - return log_oom(); - - r = hashmap_put(todo_gids, GID_TO_PTR(i->gid), i); - if (r < 0) - return log_oom(); - - i->todo_group = true; - log_info("Creating group %s with gid " GID_FMT ".", i->name, i->gid); - - return 0; -} - -static int process_item(Item *i) { - int r; - - assert(i); - - switch (i->type) { - - case ADD_USER: - r = add_group(i); - if (r < 0) - return r; - - return add_user(i); - - case ADD_GROUP: { - Item *j; - - j = hashmap_get(users, i->name); - if (j) { - /* There's already user to be created for this - * name, let's process that in one step */ - - if (i->gid_set) { - j->gid = i->gid; - j->gid_set = true; - } - - if (i->gid_path) { - r = free_and_strdup(&j->gid_path, i->gid_path); - if (r < 0) - return log_oom(); - } - - return 0; - } - - return add_group(i); - } - - default: - assert_not_reached("Unknown item type"); - } -} - -static void item_free(Item *i) { - - if (!i) - return; - - free(i->name); - free(i->uid_path); - free(i->gid_path); - free(i->description); - free(i); -} - -DEFINE_TRIVIAL_CLEANUP_FUNC(Item*, item_free); - -static int add_implicit(void) { - char *g, **l; - Iterator iterator; - int r; - - /* Implicitly create additional users and groups, if they were listed in "m" lines */ - - HASHMAP_FOREACH_KEY(l, g, members, iterator) { - Item *i; - char **m; - - i = hashmap_get(groups, g); - if (!i) { - _cleanup_(item_freep) Item *j = NULL; - - r = hashmap_ensure_allocated(&groups, &string_hash_ops); - if (r < 0) - return log_oom(); - - j = new0(Item, 1); - if (!j) - return log_oom(); - - j->type = ADD_GROUP; - j->name = strdup(g); - if (!j->name) - return log_oom(); - - r = hashmap_put(groups, j->name, j); - if (r < 0) - return log_oom(); - - log_debug("Adding implicit group '%s' due to m line", j->name); - j = NULL; - } - - STRV_FOREACH(m, l) { - - i = hashmap_get(users, *m); - if (!i) { - _cleanup_(item_freep) Item *j = NULL; - - r = hashmap_ensure_allocated(&users, &string_hash_ops); - if (r < 0) - return log_oom(); - - j = new0(Item, 1); - if (!j) - return log_oom(); - - j->type = ADD_USER; - j->name = strdup(*m); - if (!j->name) - return log_oom(); - - r = hashmap_put(users, j->name, j); - if (r < 0) - return log_oom(); - - log_debug("Adding implicit user '%s' due to m line", j->name); - j = NULL; - } - } - } - - return 0; -} - -static bool item_equal(Item *a, Item *b) { - assert(a); - assert(b); - - if (a->type != b->type) - return false; - - if (!streq_ptr(a->name, b->name)) - return false; - - if (!streq_ptr(a->uid_path, b->uid_path)) - return false; - - if (!streq_ptr(a->gid_path, b->gid_path)) - return false; - - if (!streq_ptr(a->description, b->description)) - return false; - - if (a->uid_set != b->uid_set) - return false; - - if (a->uid_set && a->uid != b->uid) - return false; - - if (a->gid_set != b->gid_set) - return false; - - if (a->gid_set && a->gid != b->gid) - return false; - - if (!streq_ptr(a->home, b->home)) - return false; - - return true; -} - -static bool valid_user_group_name(const char *u) { - const char *i; - long sz; - - if (isempty(u)) - return false; - - if (!(u[0] >= 'a' && u[0] <= 'z') && - !(u[0] >= 'A' && u[0] <= 'Z') && - u[0] != '_') - return false; - - for (i = u+1; *i; i++) { - if (!(*i >= 'a' && *i <= 'z') && - !(*i >= 'A' && *i <= 'Z') && - !(*i >= '0' && *i <= '9') && - *i != '_' && - *i != '-') - return false; - } - - sz = sysconf(_SC_LOGIN_NAME_MAX); - assert_se(sz > 0); - - if ((size_t) (i-u) > (size_t) sz) - return false; - - if ((size_t) (i-u) > UT_NAMESIZE - 1) - return false; - - return true; -} - -static bool valid_gecos(const char *d) { - - if (!d) - return false; - - if (!utf8_is_valid(d)) - return false; - - if (string_has_cc(d, NULL)) - return false; - - /* Colons are used as field separators, and hence not OK */ - if (strchr(d, ':')) - return false; - - return true; -} - -static bool valid_home(const char *p) { - - if (isempty(p)) - return false; - - if (!utf8_is_valid(p)) - return false; - - if (string_has_cc(p, NULL)) - return false; - - if (!path_is_absolute(p)) - return false; - - if (!path_is_safe(p)) - return false; - - /* Colons are used as field separators, and hence not OK */ - if (strchr(p, ':')) - return false; - - return true; -} - -static int parse_line(const char *fname, unsigned line, const char *buffer) { - - static const Specifier specifier_table[] = { - { 'm', specifier_machine_id, NULL }, - { 'b', specifier_boot_id, NULL }, - { 'H', specifier_host_name, NULL }, - { 'v', specifier_kernel_release, NULL }, - {} - }; - - _cleanup_free_ char *action = NULL, *name = NULL, *id = NULL, *resolved_name = NULL, *resolved_id = NULL, *description = NULL, *home = NULL; - _cleanup_(item_freep) Item *i = NULL; - Item *existing; - Hashmap *h; - int r; - const char *p; - - assert(fname); - assert(line >= 1); - assert(buffer); - - /* Parse columns */ - p = buffer; - r = extract_many_words(&p, NULL, EXTRACT_QUOTES, &action, &name, &id, &description, &home, NULL); - if (r < 0) { - log_error("[%s:%u] Syntax error.", fname, line); - return r; - } - if (r < 2) { - log_error("[%s:%u] Missing action and name columns.", fname, line); - return -EINVAL; - } - if (!isempty(p)) { - log_error("[%s:%u] Trailing garbage.", fname, line); - return -EINVAL; - } - - /* Verify action */ - if (strlen(action) != 1) { - log_error("[%s:%u] Unknown modifier '%s'", fname, line, action); - return -EINVAL; - } - - if (!IN_SET(action[0], ADD_USER, ADD_GROUP, ADD_MEMBER, ADD_RANGE)) { - log_error("[%s:%u] Unknown command command type '%c'.", fname, line, action[0]); - return -EBADMSG; - } - - /* Verify name */ - if (isempty(name) || streq(name, "-")) - name = mfree(name); - - if (name) { - r = specifier_printf(name, specifier_table, NULL, &resolved_name); - if (r < 0) { - log_error("[%s:%u] Failed to replace specifiers: %s", fname, line, name); - return r; - } - - if (!valid_user_group_name(resolved_name)) { - log_error("[%s:%u] '%s' is not a valid user or group name.", fname, line, resolved_name); - return -EINVAL; - } - } - - /* Verify id */ - if (isempty(id) || streq(id, "-")) - id = mfree(id); - - if (id) { - r = specifier_printf(id, specifier_table, NULL, &resolved_id); - if (r < 0) { - log_error("[%s:%u] Failed to replace specifiers: %s", fname, line, name); - return r; - } - } - - /* Verify description */ - if (isempty(description) || streq(description, "-")) - description = mfree(description); - - if (description) { - if (!valid_gecos(description)) { - log_error("[%s:%u] '%s' is not a valid GECOS field.", fname, line, description); - return -EINVAL; - } - } - - /* Verify home */ - if (isempty(home) || streq(home, "-")) - home = mfree(home); - - if (home) { - if (!valid_home(home)) { - log_error("[%s:%u] '%s' is not a valid home directory field.", fname, line, home); - return -EINVAL; - } - } - - switch (action[0]) { - - case ADD_RANGE: - if (resolved_name) { - log_error("[%s:%u] Lines of type 'r' don't take a name field.", fname, line); - return -EINVAL; - } - - if (!resolved_id) { - log_error("[%s:%u] Lines of type 'r' require a ID range in the third field.", fname, line); - return -EINVAL; - } - - if (description) { - log_error("[%s:%u] Lines of type 'r' don't take a GECOS field.", fname, line); - return -EINVAL; - } - - if (home) { - log_error("[%s:%u] Lines of type 'r' don't take a home directory field.", fname, line); - return -EINVAL; - } - - r = uid_range_add_str(&uid_range, &n_uid_range, resolved_id); - if (r < 0) { - log_error("[%s:%u] Invalid UID range %s.", fname, line, resolved_id); - return -EINVAL; - } - - return 0; - - case ADD_MEMBER: { - char **l; - - /* Try to extend an existing member or group item */ - if (!name) { - log_error("[%s:%u] Lines of type 'm' require a user name in the second field.", fname, line); - return -EINVAL; - } - - if (!resolved_id) { - log_error("[%s:%u] Lines of type 'm' require a group name in the third field.", fname, line); - return -EINVAL; - } - - if (!valid_user_group_name(resolved_id)) { - log_error("[%s:%u] '%s' is not a valid user or group name.", fname, line, resolved_id); - return -EINVAL; - } - - if (description) { - log_error("[%s:%u] Lines of type 'm' don't take a GECOS field.", fname, line); - return -EINVAL; - } - - if (home) { - log_error("[%s:%u] Lines of type 'm' don't take a home directory field.", fname, line); - return -EINVAL; - } - - r = hashmap_ensure_allocated(&members, &string_hash_ops); - if (r < 0) - return log_oom(); - - l = hashmap_get(members, resolved_id); - if (l) { - /* A list for this group name already exists, let's append to it */ - r = strv_push(&l, resolved_name); - if (r < 0) - return log_oom(); - - resolved_name = NULL; - - assert_se(hashmap_update(members, resolved_id, l) >= 0); - } else { - /* No list for this group name exists yet, create one */ - - l = new0(char *, 2); - if (!l) - return -ENOMEM; - - l[0] = resolved_name; - l[1] = NULL; - - r = hashmap_put(members, resolved_id, l); - if (r < 0) { - free(l); - return log_oom(); - } - - resolved_id = resolved_name = NULL; - } - - return 0; - } - - case ADD_USER: - if (!name) { - log_error("[%s:%u] Lines of type 'u' require a user name in the second field.", fname, line); - return -EINVAL; - } - - r = hashmap_ensure_allocated(&users, &string_hash_ops); - if (r < 0) - return log_oom(); - - i = new0(Item, 1); - if (!i) - return log_oom(); - - if (resolved_id) { - if (path_is_absolute(resolved_id)) { - i->uid_path = resolved_id; - resolved_id = NULL; - - path_kill_slashes(i->uid_path); - } else { - r = parse_uid(resolved_id, &i->uid); - if (r < 0) { - log_error("Failed to parse UID: %s", id); - return -EBADMSG; - } - - i->uid_set = true; - } - } - - i->description = description; - description = NULL; - - i->home = home; - home = NULL; - - h = users; - break; - - case ADD_GROUP: - if (!name) { - log_error("[%s:%u] Lines of type 'g' require a user name in the second field.", fname, line); - return -EINVAL; - } - - if (description) { - log_error("[%s:%u] Lines of type 'g' don't take a GECOS field.", fname, line); - return -EINVAL; - } - - if (home) { - log_error("[%s:%u] Lines of type 'g' don't take a home directory field.", fname, line); - return -EINVAL; - } - - r = hashmap_ensure_allocated(&groups, &string_hash_ops); - if (r < 0) - return log_oom(); - - i = new0(Item, 1); - if (!i) - return log_oom(); - - if (resolved_id) { - if (path_is_absolute(resolved_id)) { - i->gid_path = resolved_id; - resolved_id = NULL; - - path_kill_slashes(i->gid_path); - } else { - r = parse_gid(resolved_id, &i->gid); - if (r < 0) { - log_error("Failed to parse GID: %s", id); - return -EBADMSG; - } - - i->gid_set = true; - } - } - - h = groups; - break; - - default: - return -EBADMSG; - } - - i->type = action[0]; - i->name = resolved_name; - resolved_name = NULL; - - existing = hashmap_get(h, i->name); - if (existing) { - - /* Two identical items are fine */ - if (!item_equal(existing, i)) - log_warning("Two or more conflicting lines for %s configured, ignoring.", i->name); - - return 0; - } - - r = hashmap_put(h, i->name, i); - if (r < 0) - return log_oom(); - - i = NULL; - return 0; -} - -static int read_config_file(const char *fn, bool ignore_enoent) { - _cleanup_fclose_ FILE *rf = NULL; - FILE *f = NULL; - char line[LINE_MAX]; - unsigned v = 0; - int r = 0; - - assert(fn); - - if (streq(fn, "-")) - f = stdin; - else { - r = search_and_fopen_nulstr(fn, "re", arg_root, conf_file_dirs, &rf); - if (r < 0) { - if (ignore_enoent && r == -ENOENT) - return 0; - - return log_error_errno(r, "Failed to open '%s', ignoring: %m", fn); - } - - f = rf; - } - - 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; - } - - if (ferror(f)) { - log_error_errno(errno, "Failed to read from file %s: %m", fn); - if (r == 0) - r = -EIO; - } - - return r; -} - -static void free_database(Hashmap *by_name, Hashmap *by_id) { - char *name; - - for (;;) { - name = hashmap_first(by_id); - if (!name) - break; - - hashmap_remove(by_name, name); - - hashmap_steal_first_key(by_id); - free(name); - } - - while ((name = hashmap_steal_first_key(by_name))) - free(name); - - hashmap_free(by_name); - hashmap_free(by_id); -} - -static void help(void) { - printf("%s [OPTIONS...] [CONFIGURATION FILE...]\n\n" - "Creates system user accounts.\n\n" - " -h --help Show this help\n" - " --version Show package version\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_ROOT, - }; - - static const struct option options[] = { - { "help", no_argument, NULL, 'h' }, - { "version", no_argument, NULL, ARG_VERSION }, - { "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_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"); - } - - return 1; -} - -int main(int argc, char *argv[]) { - - _cleanup_close_ int lock = -1; - Iterator iterator; - int r, k; - Item *i; - char *n; - - r = parse_argv(argc, argv); - if (r <= 0) - goto finish; - - log_set_target(LOG_TARGET_AUTO); - log_parse_environment(); - log_open(); - - umask(0022); - - r = mac_selinux_init(); - if (r < 0) { - log_error_errno(r, "SELinux setup failed: %m"); - goto finish; - } - - 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 sysusers.d files: %m"); - goto finish; - } - - STRV_FOREACH(f, files) { - k = read_config_file(*f, true); - if (k < 0 && r == 0) - r = k; - } - } - - if (!uid_range) { - /* Default to default range of 1..SYSTEMD_UID_MAX */ - r = uid_range_add(&uid_range, &n_uid_range, 1, SYSTEM_UID_MAX); - if (r < 0) { - log_oom(); - goto finish; - } - } - - r = add_implicit(); - if (r < 0) - goto finish; - - lock = take_etc_passwd_lock(arg_root); - if (lock < 0) { - log_error_errno(lock, "Failed to take lock: %m"); - goto finish; - } - - r = load_user_database(); - if (r < 0) { - log_error_errno(r, "Failed to load user database: %m"); - goto finish; - } - - r = load_group_database(); - if (r < 0) { - log_error_errno(r, "Failed to read group database: %m"); - goto finish; - } - - HASHMAP_FOREACH(i, groups, iterator) - process_item(i); - - HASHMAP_FOREACH(i, users, iterator) - process_item(i); - - r = write_files(); - if (r < 0) - log_error_errno(r, "Failed to write files: %m"); - -finish: - while ((i = hashmap_steal_first(groups))) - item_free(i); - - while ((i = hashmap_steal_first(users))) - item_free(i); - - while ((n = hashmap_first_key(members))) { - strv_free(hashmap_steal_first(members)); - free(n); - } - - hashmap_free(groups); - hashmap_free(users); - hashmap_free(members); - hashmap_free(todo_uids); - hashmap_free(todo_gids); - - free_database(database_user, database_uid); - free_database(database_group, database_gid); - - free(arg_root); - - return r < 0 ? EXIT_FAILURE : EXIT_SUCCESS; -} diff --git a/src/systemd-sysv-generator/Makefile b/src/systemd-sysv-generator/Makefile deleted file mode 100644 index f6de146408..0000000000 --- a/src/systemd-sysv-generator/Makefile +++ /dev/null @@ -1,32 +0,0 @@ -# -*- 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/systemd-sysv-generator/sysv-generator.c b/src/systemd-sysv-generator/sysv-generator.c deleted file mode 100644 index 183f93e9e7..0000000000 --- a/src/systemd-sysv-generator/sysv-generator.c +++ /dev/null @@ -1,1039 +0,0 @@ -/*** - This file is part of systemd. - - Copyright 2014 Thomas H.P. Andersen - Copyright 2010 Lennart Poettering - Copyright 2011 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 "basic/alloc-util.h" -#include "basic/dirent-util.h" -#include "basic/fd-util.h" -#include "basic/fileio.h" -#include "basic/hashmap.h" -#include "basic/hexdecoct.h" -#include "shared/install.h" -#include "basic/log.h" -#include "basic/mkdir.h" -#include "shared/path-lookup.h" -#include "basic/path-util.h" -#include "basic/set.h" -#include "basic/special.h" -#include "basic/stat-util.h" -#include "basic/string-util.h" -#include "basic/strv.h" -#include "basic/unit-name.h" -#include "basic/util.h" - -typedef enum RunlevelType { - RUNLEVEL_UP, - RUNLEVEL_DOWN -} RunlevelType; - -static const struct { - const char *path; - const char *target; - const RunlevelType type; -} rcnd_table[] = { - /* Standard SysV runlevels for start-up */ - { "rc1.d", SPECIAL_RESCUE_TARGET, RUNLEVEL_UP }, - { "rc2.d", SPECIAL_MULTI_USER_TARGET, RUNLEVEL_UP }, - { "rc3.d", SPECIAL_MULTI_USER_TARGET, RUNLEVEL_UP }, - { "rc4.d", SPECIAL_MULTI_USER_TARGET, RUNLEVEL_UP }, - { "rc5.d", SPECIAL_GRAPHICAL_TARGET, RUNLEVEL_UP }, - - /* Standard SysV runlevels for shutdown */ - { "rc0.d", SPECIAL_POWEROFF_TARGET, RUNLEVEL_DOWN }, - { "rc6.d", SPECIAL_REBOOT_TARGET, RUNLEVEL_DOWN } - - /* Note that the order here matters, as we read the - directories in this order, and we want to make sure that - sysv_start_priority is known when we first load the - unit. And that value we only know from S links. Hence - UP must be read before DOWN */ -}; - -static const char *arg_dest = "/tmp"; - -typedef struct SysvStub { - char *name; - char *path; - char *description; - int sysv_start_priority; - char *pid_file; - char **before; - char **after; - char **wants; - char **wanted_by; - char **conflicts; - bool has_lsb; - bool reload; - bool loaded; -} SysvStub; - -static void free_sysvstub(SysvStub *s) { - if (!s) - return; - - free(s->name); - free(s->path); - free(s->description); - free(s->pid_file); - strv_free(s->before); - strv_free(s->after); - strv_free(s->wants); - strv_free(s->wanted_by); - strv_free(s->conflicts); - free(s); -} - -DEFINE_TRIVIAL_CLEANUP_FUNC(SysvStub*, free_sysvstub); - -static void free_sysvstub_hashmapp(Hashmap **h) { - SysvStub *stub; - - while ((stub = hashmap_steal_first(*h))) - free_sysvstub(stub); - - hashmap_free(*h); -} - -static int add_symlink(const char *service, const char *where) { - const char *from, *to; - int r; - - assert(service); - assert(where); - - from = strjoina(arg_dest, "/", service); - to = strjoina(arg_dest, "/", where, ".wants/", service); - - mkdir_parents_label(to, 0755); - - r = symlink(from, to); - if (r < 0) { - if (errno == EEXIST) - return 0; - - return -errno; - } - - return 1; -} - -static int add_alias(const char *service, const char *alias) { - const char *link; - int r; - - assert(service); - assert(alias); - - link = strjoina(arg_dest, "/", alias); - - r = symlink(service, link); - if (r < 0) { - if (errno == EEXIST) - return 0; - - return -errno; - } - - return 1; -} - -static int generate_unit_file(SysvStub *s) { - _cleanup_fclose_ FILE *f = NULL; - const char *unit; - char **p; - int r; - - assert(s); - - if (!s->loaded) - return 0; - - unit = strjoina(arg_dest, "/", s->name); - - /* We might already have a symlink with the same name from a Provides:, - * or from backup files like /etc/init.d/foo.bak. Real scripts always win, - * so remove an existing link */ - if (is_symlink(unit) > 0) { - log_warning("Overwriting existing symlink %s with real service.", unit); - (void) unlink(unit); - } - - f = fopen(unit, "wxe"); - if (!f) - return log_error_errno(errno, "Failed to create unit file %s: %m", unit); - - fprintf(f, - "# Automatically generated by systemd-sysv-generator\n\n" - "[Unit]\n" - "Documentation=man:systemd-sysv-generator(8)\n" - "SourcePath=%s\n", - s->path); - - if (s->description) - fprintf(f, "Description=%s\n", s->description); - - STRV_FOREACH(p, s->before) - fprintf(f, "Before=%s\n", *p); - STRV_FOREACH(p, s->after) - fprintf(f, "After=%s\n", *p); - STRV_FOREACH(p, s->wants) - fprintf(f, "Wants=%s\n", *p); - STRV_FOREACH(p, s->conflicts) - fprintf(f, "Conflicts=%s\n", *p); - - fprintf(f, - "\n[Service]\n" - "Type=forking\n" - "Restart=no\n" - "TimeoutSec=5min\n" - "IgnoreSIGPIPE=no\n" - "KillMode=process\n" - "GuessMainPID=no\n" - "RemainAfterExit=%s\n", - yes_no(!s->pid_file)); - - if (s->pid_file) - fprintf(f, "PIDFile=%s\n", s->pid_file); - - fprintf(f, - "ExecStart=%s start\n" - "ExecStop=%s stop\n", - s->path, s->path); - - if (s->reload) - fprintf(f, "ExecReload=%s reload\n", s->path); - - r = fflush_and_check(f); - if (r < 0) - return log_error_errno(r, "Failed to write unit %s: %m", unit); - - STRV_FOREACH(p, s->wanted_by) { - r = add_symlink(s->name, *p); - if (r < 0) - log_warning_errno(r, "Failed to create 'Wants' symlink to %s, ignoring: %m", *p); - } - - return 1; -} - -static bool usage_contains_reload(const char *line) { - return (strcasestr(line, "{reload|") || - strcasestr(line, "{reload}") || - strcasestr(line, "{reload\"") || - strcasestr(line, "|reload|") || - strcasestr(line, "|reload}") || - strcasestr(line, "|reload\"")); -} - -static char *sysv_translate_name(const char *name) { - _cleanup_free_ char *c = NULL; - char *res; - - c = strdup(name); - if (!c) - return NULL; - - res = endswith(c, ".sh"); - if (res) - *res = 0; - - if (unit_name_mangle(c, UNIT_NAME_NOGLOB, &res) < 0) - return NULL; - - return res; -} - -static int sysv_translate_facility(const char *name, const char *filename, char **ret) { - - /* We silently ignore the $ prefix here. According to the LSB - * spec it simply indicates whether something is a - * standardized name or a distribution-specific one. Since we - * just follow what already exists and do not introduce new - * uses or names we don't care who introduced a new name. */ - - static const char * const table[] = { - /* LSB defined facilities */ - "local_fs", NULL, - "network", SPECIAL_NETWORK_ONLINE_TARGET, - "named", SPECIAL_NSS_LOOKUP_TARGET, - "portmap", SPECIAL_RPCBIND_TARGET, - "remote_fs", SPECIAL_REMOTE_FS_TARGET, - "syslog", NULL, - "time", SPECIAL_TIME_SYNC_TARGET, - }; - - char *filename_no_sh, *e, *m; - const char *n; - unsigned i; - int r; - - assert(name); - assert(filename); - assert(ret); - - n = *name == '$' ? name + 1 : name; - - for (i = 0; i < ELEMENTSOF(table); i += 2) { - if (!streq(table[i], n)) - continue; - - if (!table[i+1]) - return 0; - - m = strdup(table[i+1]); - if (!m) - return log_oom(); - - *ret = m; - return 1; - } - - /* If we don't know this name, fallback heuristics to figure - * out whether something is a target or a service alias. */ - - /* Facilities starting with $ are most likely targets */ - if (*name == '$') { - r = unit_name_build(n, NULL, ".target", ret); - if (r < 0) - return log_error_errno(r, "Failed to build name: %m"); - - return r; - } - - /* Strip ".sh" suffix from file name for comparison */ - filename_no_sh = strdupa(filename); - e = endswith(filename_no_sh, ".sh"); - if (e) { - *e = '\0'; - filename = filename_no_sh; - } - - /* Names equaling the file name of the services are redundant */ - if (streq_ptr(n, filename)) - return 0; - - /* Everything else we assume to be normal service names */ - m = sysv_translate_name(n); - if (!m) - return log_oom(); - - *ret = m; - return 1; -} - -static int handle_provides(SysvStub *s, unsigned line, const char *full_text, const char *text) { - int r; - - assert(s); - assert(full_text); - assert(text); - - for (;;) { - _cleanup_free_ char *word = NULL, *m = NULL; - - r = extract_first_word(&text, &word, NULL, EXTRACT_QUOTES|EXTRACT_RELAX); - if (r < 0) - return log_error_errno(r, "Failed to parse word from provides string: %m"); - if (r == 0) - break; - - r = sysv_translate_facility(word, basename(s->path), &m); - if (r <= 0) /* continue on error */ - continue; - - switch (unit_name_to_type(m)) { - - case UNIT_SERVICE: - log_debug("Adding Provides: alias '%s' for '%s'", m, s->name); - r = add_alias(s->name, m); - if (r < 0) - log_warning_errno(r, "[%s:%u] Failed to add LSB Provides name %s, ignoring: %m", s->path, line, m); - break; - - case UNIT_TARGET: - - /* NB: SysV targets which are provided by a - * service are pulled in by the services, as - * an indication that the generic service is - * now available. This is strictly one-way. - * The targets do NOT pull in SysV services! */ - - r = strv_extend(&s->before, m); - if (r < 0) - return log_oom(); - - r = strv_extend(&s->wants, m); - if (r < 0) - return log_oom(); - - if (streq(m, SPECIAL_NETWORK_ONLINE_TARGET)) { - r = strv_extend(&s->before, SPECIAL_NETWORK_TARGET); - if (r < 0) - return log_oom(); - } - - break; - - case _UNIT_TYPE_INVALID: - log_warning("Unit name '%s' is invalid", m); - break; - - default: - log_warning("Unknown unit type for unit '%s'", m); - } - } - - return 0; -} - -static int handle_dependencies(SysvStub *s, unsigned line, const char *full_text, const char *text) { - int r; - - assert(s); - assert(full_text); - assert(text); - - for (;;) { - _cleanup_free_ char *word = NULL, *m = NULL; - bool is_before; - - r = extract_first_word(&text, &word, NULL, EXTRACT_QUOTES|EXTRACT_RELAX); - if (r < 0) - return log_error_errno(r, "Failed to parse word from provides string: %m"); - if (r == 0) - break; - - r = sysv_translate_facility(word, basename(s->path), &m); - if (r <= 0) /* continue on error */ - continue; - - is_before = startswith_no_case(full_text, "X-Start-Before:"); - - if (streq(m, SPECIAL_NETWORK_ONLINE_TARGET) && !is_before) { - /* the network-online target is special, as it needs to be actively pulled in */ - r = strv_extend(&s->after, m); - if (r < 0) - return log_oom(); - - r = strv_extend(&s->wants, m); - } else - r = strv_extend(is_before ? &s->before : &s->after, m); - if (r < 0) - return log_oom(); - } - - return 0; -} - -static int load_sysv(SysvStub *s) { - _cleanup_fclose_ FILE *f; - unsigned line = 0; - int r; - enum { - NORMAL, - DESCRIPTION, - LSB, - LSB_DESCRIPTION, - USAGE_CONTINUATION - } state = NORMAL; - _cleanup_free_ char *short_description = NULL, *long_description = NULL, *chkconfig_description = NULL; - char *description; - bool supports_reload = false; - char l[LINE_MAX]; - - assert(s); - - f = fopen(s->path, "re"); - if (!f) { - if (errno == ENOENT) - return 0; - - return log_error_errno(errno, "Failed to open %s: %m", s->path); - } - - log_debug("Loading SysV script %s", s->path); - - FOREACH_LINE(l, f, goto fail) { - char *t; - - line++; - - t = strstrip(l); - if (*t != '#') { - /* Try to figure out whether this init script supports - * the reload operation. This heuristic looks for - * "Usage" lines which include the reload option. */ - if ( state == USAGE_CONTINUATION || - (state == NORMAL && strcasestr(t, "usage"))) { - if (usage_contains_reload(t)) { - supports_reload = true; - state = NORMAL; - } else if (t[strlen(t)-1] == '\\') - state = USAGE_CONTINUATION; - else - state = NORMAL; - } - - continue; - } - - if (state == NORMAL && streq(t, "### BEGIN INIT INFO")) { - state = LSB; - s->has_lsb = true; - continue; - } - - if ((state == LSB_DESCRIPTION || state == LSB) && streq(t, "### END INIT INFO")) { - state = NORMAL; - continue; - } - - t++; - t += strspn(t, WHITESPACE); - - if (state == NORMAL) { - - /* Try to parse Red Hat style description */ - - if (startswith_no_case(t, "description:")) { - - size_t k; - const char *j; - - k = strlen(t); - if (k > 0 && t[k-1] == '\\') { - state = DESCRIPTION; - t[k-1] = 0; - } - - j = strstrip(t+12); - if (isempty(j)) - j = NULL; - - r = free_and_strdup(&chkconfig_description, j); - if (r < 0) - return log_oom(); - - } else if (startswith_no_case(t, "pidfile:")) { - const char *fn; - - state = NORMAL; - - fn = strstrip(t+8); - if (!path_is_absolute(fn)) { - log_error("[%s:%u] PID file not absolute. Ignoring.", s->path, line); - continue; - } - - r = free_and_strdup(&s->pid_file, fn); - if (r < 0) - return log_oom(); - } - - } else if (state == DESCRIPTION) { - - /* Try to parse Red Hat style description - * continuation */ - - size_t k; - char *j; - - k = strlen(t); - if (k > 0 && t[k-1] == '\\') - t[k-1] = 0; - else - state = NORMAL; - - j = strstrip(t); - if (!isempty(j)) { - char *d = NULL; - - if (chkconfig_description) - d = strjoin(chkconfig_description, " ", j, NULL); - else - d = strdup(j); - if (!d) - return log_oom(); - - free(chkconfig_description); - chkconfig_description = d; - } - - } else if (state == LSB || state == LSB_DESCRIPTION) { - - if (startswith_no_case(t, "Provides:")) { - state = LSB; - - r = handle_provides(s, line, t, t + 9); - if (r < 0) - return r; - - } else if (startswith_no_case(t, "Required-Start:") || - startswith_no_case(t, "Should-Start:") || - startswith_no_case(t, "X-Start-Before:") || - startswith_no_case(t, "X-Start-After:")) { - - state = LSB; - - r = handle_dependencies(s, line, t, strchr(t, ':') + 1); - if (r < 0) - return r; - - } else if (startswith_no_case(t, "Description:")) { - const char *j; - - state = LSB_DESCRIPTION; - - j = strstrip(t+12); - if (isempty(j)) - j = NULL; - - r = free_and_strdup(&long_description, j); - if (r < 0) - return log_oom(); - - } else if (startswith_no_case(t, "Short-Description:")) { - const char *j; - - state = LSB; - - j = strstrip(t+18); - if (isempty(j)) - j = NULL; - - r = free_and_strdup(&short_description, j); - if (r < 0) - return log_oom(); - - } else if (state == LSB_DESCRIPTION) { - - if (startswith(l, "#\t") || startswith(l, "# ")) { - const char *j; - - j = strstrip(t); - if (!isempty(j)) { - char *d = NULL; - - if (long_description) - d = strjoin(long_description, " ", t, NULL); - else - d = strdup(j); - if (!d) - return log_oom(); - - free(long_description); - long_description = d; - } - - } else - state = LSB; - } - } - } - - s->reload = supports_reload; - - /* We use the long description only if - * no short description is set. */ - - if (short_description) - description = short_description; - else if (chkconfig_description) - description = chkconfig_description; - else if (long_description) - description = long_description; - else - description = NULL; - - if (description) { - char *d; - - d = strappend(s->has_lsb ? "LSB: " : "SYSV: ", description); - if (!d) - return log_oom(); - - s->description = d; - } - - s->loaded = true; - return 0; - -fail: - return log_error_errno(errno, "Failed to read configuration file '%s': %m", s->path); -} - -static int fix_order(SysvStub *s, Hashmap *all_services) { - SysvStub *other; - Iterator j; - int r; - - assert(s); - - if (!s->loaded) - return 0; - - if (s->sysv_start_priority < 0) - return 0; - - HASHMAP_FOREACH(other, all_services, j) { - if (s == other) - continue; - - if (!other->loaded) - continue; - - if (other->sysv_start_priority < 0) - continue; - - /* If both units have modern headers we don't care - * about the priorities */ - if (s->has_lsb && other->has_lsb) - continue; - - if (other->sysv_start_priority < s->sysv_start_priority) { - r = strv_extend(&s->after, other->name); - if (r < 0) - return log_oom(); - - } else if (other->sysv_start_priority > s->sysv_start_priority) { - r = strv_extend(&s->before, other->name); - if (r < 0) - return log_oom(); - } else - continue; - - /* FIXME: Maybe we should compare the name here lexicographically? */ - } - - return 0; -} - -static int acquire_search_path(const char *def, const char *envvar, char ***ret) { - _cleanup_strv_free_ char **l = NULL; - const char *e; - int r; - - assert(def); - assert(envvar); - - e = getenv(envvar); - if (e) { - r = path_split_and_make_absolute(e, &l); - if (r < 0) - return log_error_errno(r, "Failed to make $%s search path absolute: %m", envvar); - } - - if (strv_isempty(l)) { - strv_free(l); - - l = strv_new(def, NULL); - if (!l) - return log_oom(); - } - - if (!path_strv_resolve_uniq(l, NULL)) - return log_oom(); - - *ret = l; - l = NULL; - - return 0; -} - -static int enumerate_sysv(const LookupPaths *lp, Hashmap *all_services) { - _cleanup_strv_free_ char **sysvinit_path = NULL; - char **path; - int r; - - assert(lp); - - r = acquire_search_path(SYSTEM_SYSVINIT_PATH, "SYSTEMD_SYSVINIT_PATH", &sysvinit_path); - if (r < 0) - return r; - - STRV_FOREACH(path, sysvinit_path) { - _cleanup_closedir_ DIR *d = NULL; - struct dirent *de; - - d = opendir(*path); - if (!d) { - if (errno != ENOENT) - log_warning_errno(errno, "Opening %s failed, ignoring: %m", *path); - continue; - } - - FOREACH_DIRENT(de, d, log_error_errno(errno, "Failed to enumerate directory %s, ignoring: %m", *path)) { - _cleanup_free_ char *fpath = NULL, *name = NULL; - _cleanup_(free_sysvstubp) SysvStub *service = NULL; - struct stat st; - - if (fstatat(dirfd(d), de->d_name, &st, 0) < 0) { - log_warning_errno(errno, "stat() failed on %s/%s, ignoring: %m", *path, de->d_name); - continue; - } - - if (!(st.st_mode & S_IXUSR)) - continue; - - if (!S_ISREG(st.st_mode)) - continue; - - name = sysv_translate_name(de->d_name); - if (!name) - return log_oom(); - - if (hashmap_contains(all_services, name)) - continue; - - r = unit_file_exists(UNIT_FILE_SYSTEM, lp, name); - if (r < 0 && !IN_SET(r, -ELOOP, -ERFKILL, -EADDRNOTAVAIL)) { - log_debug_errno(r, "Failed to detect whether %s exists, skipping: %m", name); - continue; - } else if (r != 0) { - log_debug("Native unit for %s already exists, skipping.", name); - continue; - } - - fpath = strjoin(*path, "/", de->d_name, NULL); - if (!fpath) - return log_oom(); - - service = new0(SysvStub, 1); - if (!service) - return log_oom(); - - service->sysv_start_priority = -1; - service->name = name; - service->path = fpath; - name = fpath = NULL; - - r = hashmap_put(all_services, service->name, service); - if (r < 0) - return log_oom(); - - service = NULL; - } - } - - return 0; -} - -static int set_dependencies_from_rcnd(const LookupPaths *lp, Hashmap *all_services) { - Set *runlevel_services[ELEMENTSOF(rcnd_table)] = {}; - _cleanup_set_free_ Set *shutdown_services = NULL; - _cleanup_strv_free_ char **sysvrcnd_path = NULL; - SysvStub *service; - unsigned i; - Iterator j; - char **p; - int r; - - assert(lp); - - r = acquire_search_path(SYSTEM_SYSVRCND_PATH, "SYSTEMD_SYSVRCND_PATH", &sysvrcnd_path); - if (r < 0) - return r; - - STRV_FOREACH(p, sysvrcnd_path) { - for (i = 0; i < ELEMENTSOF(rcnd_table); i ++) { - - _cleanup_closedir_ DIR *d = NULL; - _cleanup_free_ char *path = NULL; - struct dirent *de; - - path = strjoin(*p, "/", rcnd_table[i].path, NULL); - if (!path) { - r = log_oom(); - goto finish; - } - - d = opendir(path); - if (!d) { - if (errno != ENOENT) - log_warning_errno(errno, "Opening %s failed, ignoring: %m", path); - - continue; - } - - FOREACH_DIRENT(de, d, log_error_errno(errno, "Failed to enumerate directory %s, ignoring: %m", path)) { - _cleanup_free_ char *name = NULL, *fpath = NULL; - int a, b; - - if (de->d_name[0] != 'S' && de->d_name[0] != 'K') - continue; - - if (strlen(de->d_name) < 4) - continue; - - a = undecchar(de->d_name[1]); - b = undecchar(de->d_name[2]); - - if (a < 0 || b < 0) - continue; - - fpath = strjoin(*p, "/", de->d_name, NULL); - if (!fpath) { - r = log_oom(); - goto finish; - } - - name = sysv_translate_name(de->d_name + 3); - if (!name) { - r = log_oom(); - goto finish; - } - - service = hashmap_get(all_services, name); - if (!service) { - log_debug("Ignoring %s symlink in %s, not generating %s.", de->d_name, rcnd_table[i].path, name); - continue; - } - - if (de->d_name[0] == 'S') { - - if (rcnd_table[i].type == RUNLEVEL_UP) - service->sysv_start_priority = MAX(a*10 + b, service->sysv_start_priority); - - r = set_ensure_allocated(&runlevel_services[i], NULL); - if (r < 0) { - log_oom(); - goto finish; - } - - r = set_put(runlevel_services[i], service); - if (r < 0) { - log_oom(); - goto finish; - } - - } else if (de->d_name[0] == 'K' && - (rcnd_table[i].type == RUNLEVEL_DOWN)) { - - r = set_ensure_allocated(&shutdown_services, NULL); - if (r < 0) { - log_oom(); - goto finish; - } - - r = set_put(shutdown_services, service); - if (r < 0) { - log_oom(); - goto finish; - } - } - } - } - } - - - for (i = 0; i < ELEMENTSOF(rcnd_table); i ++) - SET_FOREACH(service, runlevel_services[i], j) { - r = strv_extend(&service->before, rcnd_table[i].target); - if (r < 0) { - log_oom(); - goto finish; - } - r = strv_extend(&service->wanted_by, rcnd_table[i].target); - if (r < 0) { - log_oom(); - goto finish; - } - } - - SET_FOREACH(service, shutdown_services, j) { - r = strv_extend(&service->before, SPECIAL_SHUTDOWN_TARGET); - if (r < 0) { - log_oom(); - goto finish; - } - r = strv_extend(&service->conflicts, SPECIAL_SHUTDOWN_TARGET); - if (r < 0) { - log_oom(); - goto finish; - } - } - - r = 0; - -finish: - for (i = 0; i < ELEMENTSOF(rcnd_table); i++) - set_free(runlevel_services[i]); - - return r; -} - -int main(int argc, char *argv[]) { - _cleanup_(free_sysvstub_hashmapp) Hashmap *all_services = NULL; - _cleanup_lookup_paths_free_ LookupPaths lp = {}; - SysvStub *service; - Iterator j; - int r; - - if (argc > 1 && argc != 4) { - log_error("This program takes three or no arguments."); - return EXIT_FAILURE; - } - - if (argc > 1) - arg_dest = argv[3]; - - log_set_target(LOG_TARGET_SAFE); - log_parse_environment(); - log_open(); - - umask(0022); - - r = lookup_paths_init(&lp, UNIT_FILE_SYSTEM, LOOKUP_PATHS_EXCLUDE_GENERATED, NULL); - if (r < 0) { - log_error_errno(r, "Failed to find lookup paths: %m"); - goto finish; - } - - all_services = hashmap_new(&string_hash_ops); - if (!all_services) { - r = log_oom(); - goto finish; - } - - r = enumerate_sysv(&lp, all_services); - if (r < 0) - goto finish; - - r = set_dependencies_from_rcnd(&lp, all_services); - if (r < 0) - goto finish; - - HASHMAP_FOREACH(service, all_services, j) - (void) load_sysv(service); - - HASHMAP_FOREACH(service, all_services, j) { - (void) fix_order(service, all_services); - (void) generate_unit_file(service); - } - - r = 0; - -finish: - return r < 0 ? EXIT_FAILURE : EXIT_SUCCESS; -} diff --git a/src/systemd-timesyncd/timesyncd.c b/src/systemd-timesyncd/timesyncd.c index 0a3a08b46c..7a357c2296 100644 --- a/src/systemd-timesyncd/timesyncd.c +++ b/src/systemd-timesyncd/timesyncd.c @@ -24,11 +24,12 @@ #include "basic/clock-util.h" #include "basic/fd-util.h" #include "basic/fs-util.h" -#include "sd-network/network-util.h" #include "basic/signal-util.h" +#include "basic/user-util.h" +#include "sd-network/network-util.h" + #include "timesyncd-conf.h" #include "timesyncd-manager.h" -#include "basic/user-util.h" static int load_clock_timestamp(uid_t uid, gid_t gid) { _cleanup_close_ int fd = -1; diff --git a/src/systemd-tmpfiles/Makefile b/src/systemd-tmpfiles/Makefile deleted file mode 100644 index 1e72e31215..0000000000 --- a/src/systemd-tmpfiles/Makefile +++ /dev/null @@ -1,84 +0,0 @@ -# -*- 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 # HAVE_SYSV_COMPAT - -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 # ENABLE_TMPFILES - -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 deleted file mode 100644 index 58dd7c0f8c..0000000000 --- a/src/systemd-tmpfiles/tmpfiles.c +++ /dev/null @@ -1,2343 +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 "shared/acl-util.h" -#include "basic/alloc-util.h" -#include "basic/btrfs-util.h" -#include "basic/capability-util.h" -#include "basic/chattr-util.h" -#include "basic/conf-files.h" -#include "basic/copy.h" -#include "basic/def.h" -#include "basic/escape.h" -#include "basic/fd-util.h" -#include "basic/fileio.h" -#include "basic/formats-util.h" -#include "basic/fs-util.h" -#include "basic/glob-util.h" -#include "basic/io-util.h" -#include "basic/label.h" -#include "basic/log.h" -#include "basic/macro.h" -#include "basic/missing.h" -#include "basic/mkdir.h" -#include "basic/mount-util.h" -#include "basic/parse-util.h" -#include "basic/path-util.h" -#include "basic/rm-rf.h" -#include "basic/selinux-util.h" -#include "basic/set.h" -#include "shared/specifier.h" -#include "basic/stat-util.h" -#include "basic/stdio-util.h" -#include "basic/string-table.h" -#include "basic/string-util.h" -#include "basic/strv.h" -#include "basic/umask-util.h" -#include "basic/user-util.h" -#include "basic/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', - EMPTY_DIRECTORY = 'e', - 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, - EMPTY_DIRECTORY, - 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, - EMPTY_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_NOFOLLOW|O_CLOEXEC|O_PATH); - 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_NOFOLLOW|O_CLOEXEC|O_PATH); - 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; - - SET_FLAG(value, v, (mode == MODE_ADD || mode == MODE_SET)); - - 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\" (unsupported fs or dir not a subvolume): %m", i->path); - else if (r == -EROFS) - log_debug_errno(r, "Couldn't adjust quota for subvolume \"%s\" (fs is read-only).", i->path); - else if (r == -ENOPROTOOPT) - log_debug_errno(r, "Couldn't adjust quota for subvolume \"%s\" (quota support is disabled).", 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); - } - - /* fall through */ - - case EMPTY_DIRECTORY: - 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) { - assert(i); - - log_debug("Running remove action for entry %c %s", (char) i->type, i->path); - - switch (i->type) { - - case REMOVE_PATH: - case TRUNCATE_DIRECTORY: - case RECURSIVE_REMOVE_PATH: - return glob_item(i, remove_item_instance, false); - - default: - return 0; - } -} - -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) { - 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 EMPTY_DIRECTORY: - case TRUNCATE_DIRECTORY: - case IGNORE_PATH: - case COPY_FILES: - clean_item_instance(i, i->path); - return 0; - case IGNORE_DIRECTORY_PATH: - return glob_item(i, clean_item_instance, false); - default: - return 0; - } -} - -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 EMPTY_DIRECTORY: - 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; - FILE *f; - char line[LINE_MAX]; - Iterator iterator; - unsigned v = 0; - Item *i; - int r; - - assert(fn); - - if (streq(fn, "-")) { - log_debug("Reading config from stdin."); - fn = ""; - f = stdin; - } else { - 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\", ignoring: %m", fn); - return 0; - } - - return log_error_errno(r, "Failed to open '%s': %m", fn); - } - log_debug("Reading config file \"%s\".", fn); - f = _f; - } - - 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(); - - 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-update-done/Makefile b/src/systemd-update-done/Makefile deleted file mode 100644 index 5045982316..0000000000 --- a/src/systemd-update-done/Makefile +++ /dev/null @@ -1,34 +0,0 @@ -# -*- 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-update-done - -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 deleted file mode 100644 index 39d19ec048..0000000000 --- a/src/systemd-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 "basic/fd-util.h" -#include "basic/io-util.h" -#include "basic/selinux-util.h" -#include "basic/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(); - 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 deleted file mode 100644 index 56f8d1f4b5..0000000000 --- a/src/systemd-update-utmp/Makefile +++ /dev/null @@ -1,42 +0,0 @@ -# -*- 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_UTMP),) -rootlibexec_PROGRAMS += \ - systemd-update-utmp -endif # HAVE_UTMP - -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 deleted file mode 100644 index 69264c3a26..0000000000 --- a/src/systemd-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 - -#include "basic/alloc-util.h" -#include "sd-bus/bus-error.h" -#include "shared/bus-util.h" -#include "basic/formats-util.h" -#include "basic/log.h" -#include "basic/macro.h" -#include "basic/special.h" -#include "basic/unit-name.h" -#include "basic/util.h" -#include "shared/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; -} -- cgit v1.2.3-54-g00ecf