From b30e2f4c18ad81b04e4314fd191a5d458553773c Mon Sep 17 00:00:00 2001 From: Kay Sievers Date: Wed, 11 Apr 2012 12:59:52 +0200 Subject: move libsystemd_core.la sources into core/ --- Makefile.am | 239 +-- po/POTFILES.skip | 26 +- src/ask-password-api.h | 33 - src/automount.c | 888 ---------- src/automount.h | 76 - src/build.h | 69 - src/bus-errors.h | 58 - src/cgroup-attr.c | 102 -- src/cgroup-attr.h | 49 - src/cgroup.c | 556 ------- src/cgroup.h | 94 -- src/condition.c | 323 ---- src/condition.h | 69 - src/core/ask-password-api.h | 33 + src/core/automount.c | 888 ++++++++++ src/core/automount.h | 76 + src/core/build.h | 69 + src/core/bus-errors.h | 58 + src/core/cgroup-attr.c | 102 ++ src/core/cgroup-attr.h | 49 + src/core/cgroup.c | 556 +++++++ src/core/cgroup.h | 94 ++ src/core/condition.c | 323 ++++ src/core/condition.h | 69 + src/core/dbus-automount.c | 72 + src/core/dbus-automount.h | 34 + src/core/dbus-device.c | 65 + src/core/dbus-device.h | 34 + src/core/dbus-execute.c | 422 +++++ src/core/dbus-execute.h | 125 ++ src/core/dbus-job.c | 354 ++++ src/core/dbus-job.h | 36 + src/core/dbus-loop.h | 30 + src/core/dbus-manager.c | 1544 ++++++++++++++++++ src/core/dbus-manager.h | 31 + src/core/dbus-mount.c | 168 ++ src/core/dbus-mount.h | 34 + src/core/dbus-path.c | 119 ++ src/core/dbus-path.h | 35 + src/core/dbus-service.c | 169 ++ src/core/dbus-service.h | 34 + src/core/dbus-snapshot.c | 93 ++ src/core/dbus-snapshot.h | 33 + src/core/dbus-socket.c | 139 ++ src/core/dbus-socket.h | 34 + src/core/dbus-swap.c | 111 ++ src/core/dbus-swap.h | 35 + src/core/dbus-target.c | 55 + src/core/dbus-target.h | 33 + src/core/dbus-timer.c | 137 ++ src/core/dbus-timer.h | 34 + src/core/dbus-unit.c | 850 ++++++++++ src/core/dbus-unit.h | 139 ++ src/core/dbus.c | 1483 +++++++++++++++++ src/core/dbus.h | 53 + src/core/device.c | 616 +++++++ src/core/device.h | 59 + src/core/execute.c | 2113 ++++++++++++++++++++++++ src/core/execute.h | 233 +++ src/core/fdset.c | 167 ++ src/core/fdset.h | 40 + src/core/ima-setup.c | 115 ++ src/core/ima-setup.h | 29 + src/core/initreq.h | 77 + src/core/job.c | 701 ++++++++ src/core/job.h | 200 +++ src/core/kmod-setup.c | 96 ++ src/core/kmod-setup.h | 27 + src/core/load-dropin.c | 150 ++ src/core/load-dropin.h | 31 + src/core/load-fragment.c | 2445 ++++++++++++++++++++++++++++ src/core/load-fragment.h | 91 ++ src/core/locale-setup.c | 251 +++ src/core/locale-setup.h | 27 + src/core/manager.c | 3235 ++++++++++++++++++++++++++++++++++++ src/core/manager.h | 304 ++++ src/core/mount.c | 1930 ++++++++++++++++++++++ src/core/mount.h | 124 ++ src/core/namespace.c | 346 ++++ src/core/namespace.h | 34 + src/core/path.c | 771 +++++++++ src/core/path.h | 113 ++ src/core/polkit.h | 36 + src/core/securebits.h | 45 + src/core/selinux-setup.c | 112 ++ src/core/selinux-setup.h | 29 + src/core/service.c | 3797 +++++++++++++++++++++++++++++++++++++++++++ src/core/service.h | 220 +++ src/core/snapshot.c | 309 ++++ src/core/snapshot.h | 53 + src/core/socket.c | 2218 +++++++++++++++++++++++++ src/core/socket.h | 173 ++ src/core/spawn-agent.h | 28 + src/core/special.h | 88 + src/core/swap.c | 1415 ++++++++++++++++ src/core/swap.h | 128 ++ src/core/sysfs-show.h | 27 + src/core/target.c | 224 +++ src/core/target.h | 47 + src/core/tcpwrap.c | 68 + src/core/tcpwrap.h | 29 + src/core/timer.c | 520 ++++++ src/core/timer.h | 93 ++ src/core/unit.c | 2676 ++++++++++++++++++++++++++++++ src/core/unit.h | 562 +++++++ src/dbus-automount.c | 72 - src/dbus-automount.h | 34 - src/dbus-device.c | 65 - src/dbus-device.h | 34 - src/dbus-execute.c | 422 ----- src/dbus-execute.h | 125 -- src/dbus-job.c | 354 ---- src/dbus-job.h | 36 - src/dbus-loop.h | 30 - src/dbus-manager.c | 1544 ------------------ src/dbus-manager.h | 31 - src/dbus-mount.c | 168 -- src/dbus-mount.h | 34 - src/dbus-path.c | 119 -- src/dbus-path.h | 35 - src/dbus-service.c | 169 -- src/dbus-service.h | 34 - src/dbus-snapshot.c | 93 -- src/dbus-snapshot.h | 33 - src/dbus-socket.c | 139 -- src/dbus-socket.h | 34 - src/dbus-swap.c | 111 -- src/dbus-swap.h | 35 - src/dbus-target.c | 55 - src/dbus-target.h | 33 - src/dbus-timer.c | 137 -- src/dbus-timer.h | 34 - src/dbus-unit.c | 850 ---------- src/dbus-unit.h | 139 -- src/dbus.c | 1483 ----------------- src/dbus.h | 53 - src/device.c | 616 ------- src/device.h | 59 - src/execute.c | 2113 ------------------------ src/execute.h | 233 --- src/fdset.c | 167 -- src/fdset.h | 40 - src/ima-setup.c | 115 -- src/ima-setup.h | 29 - src/initreq.h | 77 - src/job.c | 701 -------- src/job.h | 200 --- src/kmod-setup.c | 96 -- src/kmod-setup.h | 27 - src/load-dropin.c | 150 -- src/load-dropin.h | 31 - src/load-fragment.c | 2445 ---------------------------- src/load-fragment.h | 91 -- src/locale-setup.c | 251 --- src/locale-setup.h | 27 - src/manager.c | 3235 ------------------------------------ src/manager.h | 304 ---- src/mount.c | 1930 ---------------------- src/mount.h | 124 -- src/namespace.c | 346 ---- src/namespace.h | 34 - src/path.c | 771 --------- src/path.h | 113 -- src/polkit.h | 36 - src/securebits.h | 45 - src/selinux-setup.c | 112 -- src/selinux-setup.h | 29 - src/service.c | 3797 ------------------------------------------- src/service.h | 220 --- src/snapshot.c | 309 ---- src/snapshot.h | 53 - src/socket.c | 2218 ------------------------- src/socket.h | 173 -- src/spawn-agent.h | 28 - src/special.h | 88 - src/swap.c | 1415 ---------------- src/swap.h | 128 -- src/sysfs-show.h | 27 - src/target.c | 224 --- src/target.h | 47 - src/tcpwrap.c | 68 - src/tcpwrap.h | 29 - src/timer.c | 520 ------ src/timer.h | 93 -- src/unit.c | 2676 ------------------------------ src/unit.h | 562 ------- 186 files changed, 36208 insertions(+), 36205 deletions(-) delete mode 100644 src/ask-password-api.h delete mode 100644 src/automount.c delete mode 100644 src/automount.h delete mode 100644 src/build.h delete mode 100644 src/bus-errors.h delete mode 100644 src/cgroup-attr.c delete mode 100644 src/cgroup-attr.h delete mode 100644 src/cgroup.c delete mode 100644 src/cgroup.h delete mode 100644 src/condition.c delete mode 100644 src/condition.h create mode 100644 src/core/ask-password-api.h create mode 100644 src/core/automount.c create mode 100644 src/core/automount.h create mode 100644 src/core/build.h create mode 100644 src/core/bus-errors.h create mode 100644 src/core/cgroup-attr.c create mode 100644 src/core/cgroup-attr.h create mode 100644 src/core/cgroup.c create mode 100644 src/core/cgroup.h create mode 100644 src/core/condition.c create mode 100644 src/core/condition.h create mode 100644 src/core/dbus-automount.c create mode 100644 src/core/dbus-automount.h create mode 100644 src/core/dbus-device.c create mode 100644 src/core/dbus-device.h create mode 100644 src/core/dbus-execute.c create mode 100644 src/core/dbus-execute.h create mode 100644 src/core/dbus-job.c create mode 100644 src/core/dbus-job.h create mode 100644 src/core/dbus-loop.h create mode 100644 src/core/dbus-manager.c create mode 100644 src/core/dbus-manager.h create mode 100644 src/core/dbus-mount.c create mode 100644 src/core/dbus-mount.h create mode 100644 src/core/dbus-path.c create mode 100644 src/core/dbus-path.h create mode 100644 src/core/dbus-service.c create mode 100644 src/core/dbus-service.h create mode 100644 src/core/dbus-snapshot.c create mode 100644 src/core/dbus-snapshot.h create mode 100644 src/core/dbus-socket.c create mode 100644 src/core/dbus-socket.h create mode 100644 src/core/dbus-swap.c create mode 100644 src/core/dbus-swap.h create mode 100644 src/core/dbus-target.c create mode 100644 src/core/dbus-target.h create mode 100644 src/core/dbus-timer.c create mode 100644 src/core/dbus-timer.h create mode 100644 src/core/dbus-unit.c create mode 100644 src/core/dbus-unit.h create mode 100644 src/core/dbus.c create mode 100644 src/core/dbus.h create mode 100644 src/core/device.c create mode 100644 src/core/device.h create mode 100644 src/core/execute.c create mode 100644 src/core/execute.h create mode 100644 src/core/fdset.c create mode 100644 src/core/fdset.h create mode 100644 src/core/ima-setup.c create mode 100644 src/core/ima-setup.h create mode 100644 src/core/initreq.h create mode 100644 src/core/job.c create mode 100644 src/core/job.h create mode 100644 src/core/kmod-setup.c create mode 100644 src/core/kmod-setup.h create mode 100644 src/core/load-dropin.c create mode 100644 src/core/load-dropin.h create mode 100644 src/core/load-fragment.c create mode 100644 src/core/load-fragment.h create mode 100644 src/core/locale-setup.c create mode 100644 src/core/locale-setup.h create mode 100644 src/core/manager.c create mode 100644 src/core/manager.h create mode 100644 src/core/mount.c create mode 100644 src/core/mount.h create mode 100644 src/core/namespace.c create mode 100644 src/core/namespace.h create mode 100644 src/core/path.c create mode 100644 src/core/path.h create mode 100644 src/core/polkit.h create mode 100644 src/core/securebits.h create mode 100644 src/core/selinux-setup.c create mode 100644 src/core/selinux-setup.h create mode 100644 src/core/service.c create mode 100644 src/core/service.h create mode 100644 src/core/snapshot.c create mode 100644 src/core/snapshot.h create mode 100644 src/core/socket.c create mode 100644 src/core/socket.h create mode 100644 src/core/spawn-agent.h create mode 100644 src/core/special.h create mode 100644 src/core/swap.c create mode 100644 src/core/swap.h create mode 100644 src/core/sysfs-show.h create mode 100644 src/core/target.c create mode 100644 src/core/target.h create mode 100644 src/core/tcpwrap.c create mode 100644 src/core/tcpwrap.h create mode 100644 src/core/timer.c create mode 100644 src/core/timer.h create mode 100644 src/core/unit.c create mode 100644 src/core/unit.h delete mode 100644 src/dbus-automount.c delete mode 100644 src/dbus-automount.h delete mode 100644 src/dbus-device.c delete mode 100644 src/dbus-device.h delete mode 100644 src/dbus-execute.c delete mode 100644 src/dbus-execute.h delete mode 100644 src/dbus-job.c delete mode 100644 src/dbus-job.h delete mode 100644 src/dbus-loop.h delete mode 100644 src/dbus-manager.c delete mode 100644 src/dbus-manager.h delete mode 100644 src/dbus-mount.c delete mode 100644 src/dbus-mount.h delete mode 100644 src/dbus-path.c delete mode 100644 src/dbus-path.h delete mode 100644 src/dbus-service.c delete mode 100644 src/dbus-service.h delete mode 100644 src/dbus-snapshot.c delete mode 100644 src/dbus-snapshot.h delete mode 100644 src/dbus-socket.c delete mode 100644 src/dbus-socket.h delete mode 100644 src/dbus-swap.c delete mode 100644 src/dbus-swap.h delete mode 100644 src/dbus-target.c delete mode 100644 src/dbus-target.h delete mode 100644 src/dbus-timer.c delete mode 100644 src/dbus-timer.h delete mode 100644 src/dbus-unit.c delete mode 100644 src/dbus-unit.h delete mode 100644 src/dbus.c delete mode 100644 src/dbus.h delete mode 100644 src/device.c delete mode 100644 src/device.h delete mode 100644 src/execute.c delete mode 100644 src/execute.h delete mode 100644 src/fdset.c delete mode 100644 src/fdset.h delete mode 100644 src/ima-setup.c delete mode 100644 src/ima-setup.h delete mode 100644 src/initreq.h delete mode 100644 src/job.c delete mode 100644 src/job.h delete mode 100644 src/kmod-setup.c delete mode 100644 src/kmod-setup.h delete mode 100644 src/load-dropin.c delete mode 100644 src/load-dropin.h delete mode 100644 src/load-fragment.c delete mode 100644 src/load-fragment.h delete mode 100644 src/locale-setup.c delete mode 100644 src/locale-setup.h delete mode 100644 src/manager.c delete mode 100644 src/manager.h delete mode 100644 src/mount.c delete mode 100644 src/mount.h delete mode 100644 src/namespace.c delete mode 100644 src/namespace.h delete mode 100644 src/path.c delete mode 100644 src/path.h delete mode 100644 src/polkit.h delete mode 100644 src/securebits.h delete mode 100644 src/selinux-setup.c delete mode 100644 src/selinux-setup.h delete mode 100644 src/service.c delete mode 100644 src/service.h delete mode 100644 src/snapshot.c delete mode 100644 src/snapshot.h delete mode 100644 src/socket.c delete mode 100644 src/socket.h delete mode 100644 src/spawn-agent.h delete mode 100644 src/special.h delete mode 100644 src/swap.c delete mode 100644 src/swap.h delete mode 100644 src/sysfs-show.h delete mode 100644 src/target.c delete mode 100644 src/target.h delete mode 100644 src/tcpwrap.c delete mode 100644 src/tcpwrap.h delete mode 100644 src/timer.c delete mode 100644 src/timer.h delete mode 100644 src/unit.c delete mode 100644 src/unit.h diff --git a/Makefile.am b/Makefile.am index 07b30ebd1e..4ed1f12dcc 100644 --- a/Makefile.am +++ b/Makefile.am @@ -126,6 +126,7 @@ AM_CPPFLAGS = \ -I $(top_srcdir)/src/login \ -I $(top_srcdir)/src/journal \ -I $(top_srcdir)/src/systemd \ + -I $(top_srcdir)/src/core \ -I $(top_srcdir)/src/udev AM_CFLAGS = $(WARNINGFLAGS) @@ -647,128 +648,124 @@ noinst_LTLIBRARIES += \ libsystemd-core.la libsystemd_core_la_SOURCES = \ - src/unit.c \ - src/unit.h \ - src/job.c \ - src/job.h \ - src/manager.c \ - src/manager.h \ - src/path-lookup.c \ - src/path-lookup.h \ - src/load-fragment.c \ - src/load-fragment.h \ - src/service.c \ - src/service.h \ - src/automount.c \ - src/automount.h \ - src/mount.c \ - src/mount.h \ - src/swap.c \ - src/swap.h \ - src/device.c \ - src/device.h \ - src/target.c \ - src/target.h \ - src/snapshot.c \ - src/snapshot.h \ - src/socket.c \ - src/socket.h \ - src/timer.c \ - src/timer.h \ - src/path.c \ - src/path.h \ - src/load-dropin.c \ - src/load-dropin.h \ - src/execute.c \ - src/execute.h \ - src/utmp-wtmp.c \ - src/utmp-wtmp.h \ - src/dbus.c \ - src/dbus.h \ - src/dbus-manager.c \ - src/dbus-manager.h \ - src/dbus-unit.c \ - src/dbus-unit.h \ - src/dbus-job.c \ - src/dbus-job.h \ - src/dbus-service.c \ - src/dbus-service.h \ - src/dbus-socket.c \ - src/dbus-socket.h \ - src/dbus-timer.c \ - src/dbus-timer.h \ - src/dbus-target.c \ - src/dbus-target.h \ - src/dbus-mount.c \ - src/dbus-mount.h \ - src/dbus-automount.c \ - src/dbus-automount.h \ - src/dbus-swap.c \ - src/dbus-swap.h \ - src/dbus-snapshot.c \ - src/dbus-snapshot.h \ - src/dbus-device.c \ - src/dbus-device.h \ - src/dbus-execute.c \ - src/dbus-execute.h \ - src/dbus-path.c \ - src/dbus-path.h \ - src/cgroup.c \ - src/cgroup.h \ - src/mount-setup.c \ - src/mount-setup.h \ - src/hostname-setup.c \ - src/hostname-setup.h \ - src/selinux-setup.c \ - src/selinux-setup.h \ - src/ima-setup.c \ - src/ima-setup.h \ - src/loopback-setup.h \ - src/loopback-setup.c \ - src/kmod-setup.c \ - src/kmod-setup.h \ - src/locale-setup.h \ - src/locale-setup.c \ - src/machine-id-setup.c \ - src/machine-id-setup.h \ - src/fdset.c \ - src/fdset.h \ - src/condition.c \ - src/condition.h \ + src/def.h \ + src/missing.h \ src/dbus-common.c \ src/dbus-common.h \ - src/install.c \ - src/install.h \ + src/watchdog.c \ + src/watchdog.h \ + src/loopback-setup.h \ + src/loopback-setup.c \ + src/hostname-setup.c \ + src/hostname-setup.h \ src/specifier.c \ src/specifier.h \ - src/namespace.c \ - src/namespace.h \ + src/install.c \ + src/install.h \ + src/path-lookup.c \ + src/path-lookup.h \ src/unit-name.c \ src/unit-name.h \ - src/tcpwrap.c \ - src/tcpwrap.h \ - src/cgroup-attr.c \ - src/cgroup-attr.h \ - src/watchdog.c \ - src/watchdog.h \ - src/def.h \ - src/missing.h \ - src/securebits.h \ + src/utmp-wtmp.c \ + src/utmp-wtmp.h \ + src/machine-id-setup.c \ + src/machine-id-setup.h \ + src/mount-setup.c \ + src/mount-setup.h \ src/linux/auto_dev-ioctl.h \ src/linux/fanotify.h \ - src/initreq.h \ - src/special.h \ - src/dbus-common.h \ - src/bus-errors.h \ - src/cgroup-show.h \ - src/build.h \ - src/umount.h \ - src/ask-password-api.h \ - src/sysfs-show.h \ - src/polkit.h \ - src/dbus-loop.h \ - src/spawn-agent.h \ - src/logs-show.h + src/core/unit.c \ + src/core/unit.h \ + src/core/job.c \ + src/core/job.h \ + src/core/manager.c \ + src/core/manager.h \ + src/core/load-fragment.c \ + src/core/load-fragment.h \ + src/core/service.c \ + src/core/service.h \ + src/core/automount.c \ + src/core/automount.h \ + src/core/mount.c \ + src/core/mount.h \ + src/core/swap.c \ + src/core/swap.h \ + src/core/device.c \ + src/core/device.h \ + src/core/target.c \ + src/core/target.h \ + src/core/snapshot.c \ + src/core/snapshot.h \ + src/core/socket.c \ + src/core/socket.h \ + src/core/timer.c \ + src/core/timer.h \ + src/core/path.c \ + src/core/path.h \ + src/core/load-dropin.c \ + src/core/load-dropin.h \ + src/core/execute.c \ + src/core/execute.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-timer.c \ + src/core/dbus-timer.h \ + src/core/dbus-target.c \ + src/core/dbus-target.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-snapshot.c \ + src/core/dbus-snapshot.h \ + src/core/dbus-device.c \ + src/core/dbus-device.h \ + src/core/dbus-execute.c \ + src/core/dbus-execute.h \ + src/core/dbus-path.c \ + src/core/dbus-path.h \ + src/core/cgroup.c \ + src/core/cgroup.h \ + src/core/selinux-setup.c \ + src/core/selinux-setup.h \ + src/core/ima-setup.c \ + src/core/ima-setup.h \ + src/core/kmod-setup.c \ + src/core/kmod-setup.h \ + src/core/locale-setup.h \ + src/core/locale-setup.c \ + src/core/fdset.c \ + src/core/fdset.h \ + src/core/condition.c \ + src/core/condition.h \ + src/core/namespace.c \ + src/core/namespace.h \ + src/core/tcpwrap.c \ + src/core/tcpwrap.h \ + src/core/cgroup-attr.c \ + src/core/cgroup-attr.h \ + src/core/securebits.h \ + src/core/initreq.h \ + src/core/special.h \ + src/core/bus-errors.h \ + src/core/build.h \ + src/core/ask-password-api.h \ + src/core/sysfs-show.h \ + src/core/polkit.h \ + src/core/dbus-loop.h \ + src/core/spawn-agent.h nodist_libsystemd_core_la_SOURCES = \ src/load-fragment-gperf.c \ @@ -948,6 +945,7 @@ pkginclude_HEADERS += \ systemd_shutdown_SOURCES = \ src/mount-setup.c \ src/umount.c \ + src/umount.h \ src/shutdown.c \ src/watchdog.c \ src/watchdog.h @@ -1075,10 +1073,12 @@ systemctl_SOURCES = \ src/dbus-common.c \ src/path-lookup.c \ src/cgroup-show.c \ + src/cgroup-show.h \ src/unit-name.c \ src/install.c \ src/spawn-agent.c \ - src/logs-show.c + src/logs-show.c \ + src/logs-show.h systemctl_CFLAGS = \ $(AM_CFLAGS) \ @@ -1120,7 +1120,8 @@ systemd_reply_password_LDADD = \ # ------------------------------------------------------------------------------ systemd_cgls_SOURCES = \ src/cgls.c \ - src/cgroup-show.c + src/cgroup-show.c \ + src/cgroup-show.h systemd_cgls_LDADD = \ libsystemd-shared.la @@ -1971,7 +1972,8 @@ systemd_cat_LDADD = \ journalctl_SOURCES = \ src/journal/journalctl.c \ - src/logs-show.c + src/logs-show.c \ + src/logs-show.h journalctl_LDADD = \ libsystemd-shared.la \ @@ -2585,7 +2587,8 @@ loginctl_SOURCES = \ src/login/loginctl.c \ src/login/sysfs-show.c \ src/dbus-common.c \ - src/cgroup-show.c + src/cgroup-show.c \ + src/cgroup-show.h loginctl_CFLAGS = \ $(AM_CFLAGS) \ diff --git a/po/POTFILES.skip b/po/POTFILES.skip index 5a5d027493..2acacbb58b 100644 --- a/po/POTFILES.skip +++ b/po/POTFILES.skip @@ -1,16 +1,16 @@ -src/dbus-automount.c -src/dbus-device.c -src/dbus-job.c -src/dbus-manager.c -src/dbus-mount.c -src/dbus-path.c -src/dbus-service.c -src/dbus-snapshot.c -src/dbus-socket.c -src/dbus-swap.c -src/dbus-target.c -src/dbus-timer.c -src/dbus-unit.c +src/core/dbus-automount.c +src/core/dbus-device.c +src/core/dbus-job.c +src/core/dbus-manager.c +src/core/dbus-mount.c +src/core/dbus-path.c +src/core/dbus-service.c +src/core/dbus-snapshot.c +src/core/dbus-socket.c +src/core/dbus-swap.c +src/core/dbus-target.c +src/core/dbus-timer.c +src/core/dbus-unit.c src/hostname/hostnamed.c src/locale/localed.c src/org.freedesktop.systemd1.policy.in.in diff --git a/src/ask-password-api.h b/src/ask-password-api.h deleted file mode 100644 index fec8625a0f..0000000000 --- a/src/ask-password-api.h +++ /dev/null @@ -1,33 +0,0 @@ -/*-*- Mode: C; c-basic-offset: 8; indent-tabs-mode: nil -*-*/ - -#ifndef fooaskpasswordapihfoo -#define fooaskpasswordapihfoo - -/*** - This file is part of systemd. - - Copyright 2010 Lennart Poettering - - systemd is free software; you can redistribute it and/or modify it - under the terms of the GNU General Public License as published by - the Free Software Foundation; either version 2 of the License, or - (at your option) any later version. - - systemd is distributed in the hope that 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 General Public License - along with systemd; If not, see . -***/ - -#include "util.h" - -int ask_password_tty(const char *message, usec_t until, const char *flag_file, char **_passphrase); - -int ask_password_agent(const char *message, const char *icon, usec_t until, bool accept_cached, char ***_passphrases); - -int ask_password_auto(const char *message, const char *icon, usec_t until, bool accept_cached, char ***_passphrases); - -#endif diff --git a/src/automount.c b/src/automount.c deleted file mode 100644 index 6857a6fd76..0000000000 --- a/src/automount.c +++ /dev/null @@ -1,888 +0,0 @@ -/*-*- Mode: C; c-basic-offset: 8; indent-tabs-mode: nil -*-*/ - -/*** - This file is part of systemd. - - Copyright 2010 Lennart Poettering - - systemd is free software; you can redistribute it and/or modify it - under the terms of the GNU General Public License as published by - the Free Software Foundation; either version 2 of the License, or - (at your option) any later version. - - systemd is distributed in the hope that 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 General Public License - along with systemd; If not, see . -***/ - -#include -#include -#include -#include -#include -#include -#include -#include -#include - -#include "unit.h" -#include "automount.h" -#include "load-fragment.h" -#include "load-dropin.h" -#include "unit-name.h" -#include "dbus-automount.h" -#include "bus-errors.h" -#include "special.h" -#include "label.h" -#include "mkdir.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 -}; - -static int open_dev_autofs(Manager *m); - -static void automount_init(Unit *u) { - Automount *a = AUTOMOUNT(u); - - assert(u); - assert(u->load_state == UNIT_STUB); - - a->pipe_watch.fd = a->pipe_fd = -1; - a->pipe_watch.type = WATCH_INVALID; - - a->directory_mode = 0755; - - UNIT(a)->ignore_on_isolate = true; -} - -static void repeat_unmout(const char *path) { - assert(path); - - for (;;) { - /* If there are multiple mounts on a mount point, this - * removes them all */ - - if (umount2(path, MNT_DETACH) >= 0) - continue; - - if (errno != EINVAL) - log_error("Failed to unmount: %m"); - - break; - } -} - -static void unmount_autofs(Automount *a) { - assert(a); - - if (a->pipe_fd < 0) - return; - - automount_send_ready(a, -EHOSTDOWN); - - unit_unwatch_fd(UNIT(a), &a->pipe_watch); - close_nointr_nofail(a->pipe_fd); - a->pipe_fd = -1; - - /* 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)) - repeat_unmout(a->where); -} - -static void automount_done(Unit *u) { - Automount *a = AUTOMOUNT(u); - - assert(a); - - unmount_autofs(a); - unit_ref_unset(&a->mount); - - free(a->where); - a->where = NULL; - - set_free(a->tokens); - a->tokens = NULL; -} - -int automount_add_one_mount_link(Automount *a, Mount *m) { - int r; - - assert(a); - assert(m); - - if (UNIT(a)->load_state != UNIT_LOADED || - UNIT(m)->load_state != UNIT_LOADED) - return 0; - - if (!path_startswith(a->where, m->where)) - return 0; - - if (path_equal(a->where, m->where)) - return 0; - - if ((r = unit_add_two_dependencies(UNIT(a), UNIT_AFTER, UNIT_REQUIRES, UNIT(m), true)) < 0) - return r; - - return 0; -} - -static int automount_add_mount_links(Automount *a) { - Unit *other; - int r; - - assert(a); - - LIST_FOREACH(units_by_type, other, UNIT(a)->manager->units_by_type[UNIT_MOUNT]) - if ((r = automount_add_one_mount_link(a, MOUNT(other))) < 0) - return r; - - return 0; -} - -static int automount_add_default_dependencies(Automount *a) { - int r; - - assert(a); - - if (UNIT(a)->manager->running_as == MANAGER_SYSTEM) { - - if ((r = unit_add_dependency_by_name(UNIT(a), UNIT_BEFORE, SPECIAL_BASIC_TARGET, NULL, true)) < 0) - return r; - - if ((r = unit_add_two_dependencies_by_name(UNIT(a), UNIT_BEFORE, UNIT_CONFLICTS, SPECIAL_UMOUNT_TARGET, NULL, true)) < 0) - return r; - } - - return 0; -} - -static int automount_verify(Automount *a) { - bool b; - char *e; - assert(a); - - if (UNIT(a)->load_state != UNIT_LOADED) - return 0; - - if (path_equal(a->where, "/")) { - log_error("Cannot have an automount unit for the root directory. Refusing."); - return -EINVAL; - } - - if (!(e = unit_name_from_path(a->where, ".automount"))) - return -ENOMEM; - - b = unit_has_name(UNIT(a), e); - free(e); - - if (!b) { - log_error("%s's Where setting doesn't match unit name. Refusing.", UNIT(a)->id); - return -EINVAL; - } - - return 0; -} - -static int automount_load(Unit *u) { - int r; - Automount *a = AUTOMOUNT(u); - - assert(u); - assert(u->load_state == UNIT_STUB); - - /* Load a .automount file */ - if ((r = unit_load_fragment_and_dropin_optional(u)) < 0) - return r; - - if (u->load_state == UNIT_LOADED) { - Unit *x; - - if (!a->where) - if (!(a->where = unit_name_to_path(u->id))) - return -ENOMEM; - - path_kill_slashes(a->where); - - if ((r = automount_add_mount_links(a)) < 0) - return r; - - r = unit_load_related_unit(u, ".mount", &x); - if (r < 0) - return r; - - unit_ref_set(&a->mount, x); - - r = unit_add_two_dependencies(u, UNIT_BEFORE, UNIT_TRIGGERS, UNIT_DEREF(a->mount), true); - if (r < 0) - return r; - - if (UNIT(a)->default_dependencies) - if ((r = automount_add_default_dependencies(a)) < 0) - return r; - } - - return automount_verify(a); -} - -static void automount_set_state(Automount *a, AutomountState state) { - AutomountState old_state; - assert(a); - - old_state = a->state; - a->state = state; - - if (state != AUTOMOUNT_WAITING && - state != AUTOMOUNT_RUNNING) - unmount_autofs(a); - - if (state != old_state) - log_debug("%s changed %s -> %s", - UNIT(a)->id, - 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) { - - if ((r = open_dev_autofs(u->manager)) < 0) - return r; - - if (a->deserialized_state == AUTOMOUNT_WAITING || - a->deserialized_state == AUTOMOUNT_RUNNING) { - - assert(a->pipe_fd >= 0); - - if ((r = unit_watch_fd(UNIT(a), a->pipe_fd, EPOLLIN, &a->pipe_watch)) < 0) - return r; - } - - automount_set_state(a, a->deserialized_state); - } - - return 0; -} - -static void automount_dump(Unit *u, FILE *f, const char *prefix) { - Automount *a = AUTOMOUNT(u); - - assert(a); - - fprintf(f, - "%sAutomount State: %s\n" - "%sResult: %s\n" - "%sWhere: %s\n" - "%sDirectoryMode: %04o\n", - prefix, automount_state_to_string(a->state), - prefix, automount_result_to_string(a->result), - prefix, a->where, - prefix, a->directory_mode); -} - -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); - - if ((m->dev_autofs_fd = open("/dev/autofs", O_CLOEXEC|O_RDONLY)) < 0) { - log_error("Failed to open /dev/autofs: %s", strerror(errno)); - return -errno; - } - - init_autofs_dev_ioctl(¶m); - if (ioctl(m->dev_autofs_fd, AUTOFS_DEV_IOCTL_VERSION, ¶m) < 0) { - close_nointr_nofail(m->dev_autofs_fd); - m->dev_autofs_fd = -1; - 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; - int r; - - assert(dev_autofs_fd >= 0); - assert(where); - - l = sizeof(struct autofs_dev_ioctl) + strlen(where) + 1; - - if (!(param = malloc(l))) - return -ENOMEM; - - 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) { - r = -errno; - goto finish; - } - - if (param->ioctlfd < 0) { - r = -EIO; - goto finish; - } - - fd_cloexec(param->ioctlfd, true); - r = param->ioctlfd; - -finish: - free(param); - return r; -} - -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, time_t sec) { - struct autofs_dev_ioctl param; - - assert(dev_autofs_fd >= 0); - assert(ioctl_fd >= 0); - - init_autofs_dev_ioctl(¶m); - param.ioctlfd = ioctl_fd; - param.timeout.timeout = sec; - - if (ioctl(dev_autofs_fd, AUTOFS_DEV_IOCTL_TIMEOUT, ¶m) < 0) - return -errno; - - return 0; -} - -static int autofs_send_ready(int dev_autofs_fd, int ioctl_fd, uint32_t token, int status) { - struct autofs_dev_ioctl param; - - assert(dev_autofs_fd >= 0); - assert(ioctl_fd >= 0); - - init_autofs_dev_ioctl(¶m); - param.ioctlfd = ioctl_fd; - - if (status) { - param.fail.token = token; - param.fail.status = status; - } else - param.ready.token = token; - - if (ioctl(dev_autofs_fd, status ? AUTOFS_DEV_IOCTL_FAIL : AUTOFS_DEV_IOCTL_READY, ¶m) < 0) - return -errno; - - return 0; -} - -int automount_send_ready(Automount *a, int status) { - int ioctl_fd, r; - unsigned token; - - assert(a); - assert(status <= 0); - - if (set_isempty(a->tokens)) - return 0; - - if ((ioctl_fd = open_ioctl_fd(UNIT(a)->manager->dev_autofs_fd, a->where, a->dev_id)) < 0) { - r = ioctl_fd; - goto fail; - } - - if (status) - log_debug("Sending failure: %s", strerror(-status)); - else - log_debug("Sending success."); - - r = 0; - - /* Autofs thankfully does not hand out 0 as a token */ - while ((token = PTR_TO_UINT(set_steal_first(a->tokens)))) { - int k; - - /* Autofs fun fact II: - * - * if you pass a positive status code here, the kernel will - * freeze! Yay! */ - - if ((k = autofs_send_ready(UNIT(a)->manager->dev_autofs_fd, - ioctl_fd, - token, - status)) < 0) - r = k; - } - -fail: - if (ioctl_fd >= 0) - close_nointr_nofail(ioctl_fd); - - return r; -} - -static void automount_enter_waiting(Automount *a) { - int p[2] = { -1, -1 }; - char name[32], options[128]; - bool mounted = false; - int r, ioctl_fd = -1, dev_autofs_fd; - struct stat st; - - assert(a); - assert(a->pipe_fd < 0); - assert(a->where); - - if (a->tokens) - set_clear(a->tokens); - - if ((dev_autofs_fd = open_dev_autofs(UNIT(a)->manager)) < 0) { - r = dev_autofs_fd; - goto fail; - } - - /* We knowingly ignore the results of this call */ - mkdir_p(a->where, 0555); - - if (pipe2(p, O_NONBLOCK|O_CLOEXEC) < 0) { - r = -errno; - goto fail; - } - - snprintf(options, sizeof(options), "fd=%i,pgrp=%u,minproto=5,maxproto=5,direct", p[1], (unsigned) getpgrp()); - char_array_0(options); - - snprintf(name, sizeof(name), "systemd-%u", (unsigned) getpid()); - char_array_0(name); - - if (mount(name, a->where, "autofs", 0, options) < 0) { - r = -errno; - goto fail; - } - - mounted = true; - - close_nointr_nofail(p[1]); - p[1] = -1; - - if (stat(a->where, &st) < 0) { - r = -errno; - goto fail; - } - - if ((ioctl_fd = open_ioctl_fd(dev_autofs_fd, a->where, st.st_dev)) < 0) { - r = ioctl_fd; - goto fail; - } - - if ((r = autofs_protocol(dev_autofs_fd, ioctl_fd)) < 0) - goto fail; - - if ((r = autofs_set_timeout(dev_autofs_fd, ioctl_fd, 300)) < 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. */ - - close_nointr_nofail(ioctl_fd); - ioctl_fd = -1; - - if ((r = unit_watch_fd(UNIT(a), p[0], EPOLLIN, &a->pipe_watch)) < 0) - goto fail; - - a->pipe_fd = p[0]; - a->dev_id = st.st_dev; - - automount_set_state(a, AUTOMOUNT_WAITING); - - return; - -fail: - assert_se(close_pipe(p) == 0); - - if (ioctl_fd >= 0) - close_nointr_nofail(ioctl_fd); - - if (mounted) - repeat_unmout(a->where); - - log_error("Failed to initialize automounter: %s", strerror(-r)); - automount_enter_dead(a, AUTOMOUNT_FAILURE_RESOURCES); -} - -static void automount_enter_runnning(Automount *a) { - int r; - struct stat st; - DBusError error; - - assert(a); - assert(UNIT_DEREF(a->mount)); - - dbus_error_init(&error); - - /* We don't take mount requests anymore if we are supposed to - * shut down anyway */ - if (unit_pending_inactive(UNIT(a))) { - log_debug("Suppressing automount request on %s since unit stop is scheduled.", UNIT(a)->id); - automount_send_ready(a, -EHOSTDOWN); - return; - } - - mkdir_p(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_warning("%s failed to stat automount point: %m", UNIT(a)->id); - goto fail; - } - - if (!S_ISDIR(st.st_mode) || st.st_dev != a->dev_id) - log_info("%s's automount point already active?", UNIT(a)->id); - else if ((r = manager_add_job(UNIT(a)->manager, JOB_START, UNIT_DEREF(a->mount), JOB_REPLACE, true, &error, NULL)) < 0) { - log_warning("%s failed to queue mount startup job: %s", UNIT(a)->id, bus_error(&error, r)); - goto fail; - } - - automount_set_state(a, AUTOMOUNT_RUNNING); - return; - -fail: - automount_enter_dead(a, AUTOMOUNT_FAILURE_RESOURCES); - dbus_error_free(&error); -} - -static int automount_start(Unit *u) { - Automount *a = AUTOMOUNT(u); - - assert(a); - - assert(a->state == AUTOMOUNT_DEAD || a->state == AUTOMOUNT_FAILED); - - if (path_is_mount_point(a->where, false)) { - log_error("Path %s is already a mount point, refusing start for %s", a->where, u->id); - return -EEXIST; - } - - if (UNIT_DEREF(a->mount)->load_state != UNIT_LOADED) - return -ENOENT; - - a->result = AUTOMOUNT_SUCCESS; - automount_enter_waiting(a); - return 0; -} - -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 0; -} - -static int automount_serialize(Unit *u, FILE *f, FDSet *fds) { - Automount *a = AUTOMOUNT(u); - void *p; - Iterator i; - - 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)); - - if (a->pipe_fd >= 0) { - int copy; - - if ((copy = fdset_put_dup(fds, a->pipe_fd)) < 0) - return copy; - - unit_serialize_item_format(u, f, "pipe-fd", "%i", copy); - } - - 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; - - if ((state = automount_state_from_string(value)) < 0) - log_debug("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_debug("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_debug("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_debug("Failed to parse token value %s", value); - else { - if (!a->tokens) - if (!(a->tokens = set_new(trivial_hash_func, trivial_compare_func))) - return -ENOMEM; - - if ((r = set_put(a->tokens, UINT_TO_PTR(token))) < 0) - return r; - } - } else if (streq(key, "pipe-fd")) { - int fd; - - if (safe_atoi(value, &fd) < 0 || fd < 0 || !fdset_contains(fds, fd)) - log_debug("Failed to parse pipe-fd value %s", value); - else { - if (a->pipe_fd >= 0) - close_nointr_nofail(a->pipe_fd); - - a->pipe_fd = fdset_remove(fds, fd); - } - } else - log_debug("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) { - Automount *a = AUTOMOUNT(u); - - assert(a); - - if (!UNIT_DEREF(a->mount)) - return false; - - return UNIT_VTABLE(UNIT_DEREF(a->mount))->check_gc(UNIT_DEREF(a->mount)); -} - -static void automount_fd_event(Unit *u, int fd, uint32_t events, Watch *w) { - Automount *a = AUTOMOUNT(u); - union autofs_v5_packet_union packet; - ssize_t l; - int r; - - assert(a); - assert(fd == a->pipe_fd); - - if (events != EPOLLIN) { - log_error("Got invalid poll event on pipe."); - goto fail; - } - - if ((l = loop_read(a->pipe_fd, &packet, sizeof(packet), true)) != sizeof(packet)) { - log_error("Invalid read from pipe: %s", l < 0 ? strerror(-l) : "short read"); - goto fail; - } - - switch (packet.hdr.type) { - - case autofs_ptype_missing_direct: - - if (packet.v5_packet.pid > 0) { - char *p = NULL; - - get_process_comm(packet.v5_packet.pid, &p); - log_debug("Got direct mount request for %s, triggered by %lu (%s)", packet.v5_packet.name, (unsigned long) packet.v5_packet.pid, strna(p)); - free(p); - - } else - log_debug("Got direct mount request for %s", packet.v5_packet.name); - - if (!a->tokens) - if (!(a->tokens = set_new(trivial_hash_func, trivial_compare_func))) { - log_error("Failed to allocate token set."); - goto fail; - } - - if ((r = set_put(a->tokens, UINT_TO_PTR(packet.v5_packet.wait_queue_token))) < 0) { - log_error("Failed to remember token: %s", strerror(-r)); - goto fail; - } - - automount_enter_runnning(a); - break; - - default: - log_error("Received unknown automount request %i", packet.hdr.type); - break; - } - - return; - -fail: - automount_enter_dead(a, AUTOMOUNT_FAILURE_RESOURCES); -} - -static void automount_shutdown(Manager *m) { - assert(m); - - if (m->dev_autofs_fd >= 0) - close_nointr_nofail(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 const char* const automount_state_table[_AUTOMOUNT_STATE_MAX] = { - [AUTOMOUNT_DEAD] = "dead", - [AUTOMOUNT_WAITING] = "waiting", - [AUTOMOUNT_RUNNING] = "running", - [AUTOMOUNT_FAILED] = "failed" -}; - -DEFINE_STRING_TABLE_LOOKUP(automount_state, AutomountState); - -static const char* const automount_result_table[_AUTOMOUNT_RESULT_MAX] = { - [AUTOMOUNT_SUCCESS] = "success", - [AUTOMOUNT_FAILURE_RESOURCES] = "resources" -}; - -DEFINE_STRING_TABLE_LOOKUP(automount_result, AutomountResult); - -const UnitVTable automount_vtable = { - .suffix = ".automount", - .object_size = sizeof(Automount), - .sections = - "Unit\0" - "Automount\0" - "Install\0", - - .no_alias = true, - .no_instances = true, - - .init = automount_init, - .load = automount_load, - .done = automount_done, - - .coldplug = automount_coldplug, - - .dump = automount_dump, - - .start = automount_start, - .stop = automount_stop, - - .serialize = automount_serialize, - .deserialize_item = automount_deserialize_item, - - .active_state = automount_active_state, - .sub_state_to_string = automount_sub_state_to_string, - - .check_gc = automount_check_gc, - - .fd_event = automount_fd_event, - - .reset_failed = automount_reset_failed, - - .bus_interface = "org.freedesktop.systemd1.Automount", - .bus_message_handler = bus_automount_message_handler, - .bus_invalidating_properties = bus_automount_invalidating_properties, - - .shutdown = automount_shutdown -}; diff --git a/src/automount.h b/src/automount.h deleted file mode 100644 index 19baee208b..0000000000 --- a/src/automount.h +++ /dev/null @@ -1,76 +0,0 @@ -/*-*- Mode: C; c-basic-offset: 8; indent-tabs-mode: nil -*-*/ - -#ifndef fooautomounthfoo -#define fooautomounthfoo - -/*** - This file is part of systemd. - - Copyright 2010 Lennart Poettering - - systemd is free software; you can redistribute it and/or modify it - under the terms of the GNU General Public License as published by - the Free Software Foundation; either version 2 of the License, or - (at your option) any later version. - - systemd is distributed in the hope that 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 General Public License - along with systemd; If not, see . -***/ - -typedef struct Automount Automount; - -#include "unit.h" - -typedef enum AutomountState { - AUTOMOUNT_DEAD, - AUTOMOUNT_WAITING, - AUTOMOUNT_RUNNING, - AUTOMOUNT_FAILED, - _AUTOMOUNT_STATE_MAX, - _AUTOMOUNT_STATE_INVALID = -1 -} AutomountState; - -typedef enum AutomountResult { - AUTOMOUNT_SUCCESS, - AUTOMOUNT_FAILURE_RESOURCES, - _AUTOMOUNT_RESULT_MAX, - _AUTOMOUNT_RESULT_INVALID = -1 -} AutomountResult; - -struct Automount { - Unit meta; - - AutomountState state, deserialized_state; - - char *where; - - UnitRef mount; - - int pipe_fd; - mode_t directory_mode; - Watch pipe_watch; - dev_t dev_id; - - Set *tokens; - - AutomountResult result; -}; - -extern const UnitVTable automount_vtable; - -int automount_send_ready(Automount *a, int status); - -int automount_add_one_mount_link(Automount *a, Mount *m); - -const char* automount_state_to_string(AutomountState i); -AutomountState automount_state_from_string(const char *s); - -const char* automount_result_to_string(AutomountResult i); -AutomountResult automount_result_from_string(const char *s); - -#endif diff --git a/src/build.h b/src/build.h deleted file mode 100644 index 0619013141..0000000000 --- a/src/build.h +++ /dev/null @@ -1,69 +0,0 @@ -/*-*- Mode: C; c-basic-offset: 8; indent-tabs-mode: nil -*-*/ - -#ifndef foobuildhfoo -#define foobuildhfoo - -/*** - This file is part of systemd. - - Copyright 2010 Lennart Poettering - - systemd is free software; you can redistribute it and/or modify it - under the terms of the GNU General Public License as published by - the Free Software Foundation; either version 2 of the License, or - (at your option) any later version. - - systemd is distributed in the hope that 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 General Public License - along with systemd; If not, see . -***/ - -#ifdef HAVE_PAM -#define _PAM_FEATURE_ "+PAM" -#else -#define _PAM_FEATURE_ "-PAM" -#endif - -#ifdef HAVE_LIBWRAP -#define _LIBWRAP_FEATURE_ "+LIBWRAP" -#else -#define _LIBWRAP_FEATURE_ "-LIBWRAP" -#endif - -#ifdef HAVE_AUDIT -#define _AUDIT_FEATURE_ "+AUDIT" -#else -#define _AUDIT_FEATURE_ "-AUDIT" -#endif - -#ifdef HAVE_SELINUX -#define _SELINUX_FEATURE_ "+SELINUX" -#else -#define _SELINUX_FEATURE_ "-SELINUX" -#endif - -#ifdef HAVE_IMA -#define _IMA_FEATURE_ "+IMA" -#else -#define _IMA_FEATURE_ "-IMA" -#endif - -#ifdef HAVE_SYSV_COMPAT -#define _SYSVINIT_FEATURE_ "+SYSVINIT" -#else -#define _SYSVINIT_FEATURE_ "-SYSVINIT" -#endif - -#ifdef HAVE_LIBCRYPTSETUP -#define _LIBCRYPTSETUP_FEATURE_ "+LIBCRYPTSETUP" -#else -#define _LIBCRYPTSETUP_FEATURE_ "-LIBCRYPTSETUP" -#endif - -#define SYSTEMD_FEATURES _PAM_FEATURE_ " " _LIBWRAP_FEATURE_ " " _AUDIT_FEATURE_ " " _SELINUX_FEATURE_ " " _IMA_FEATURE_ " " _SYSVINIT_FEATURE_ " " _LIBCRYPTSETUP_FEATURE_ - -#endif diff --git a/src/bus-errors.h b/src/bus-errors.h deleted file mode 100644 index 82d4e99eef..0000000000 --- a/src/bus-errors.h +++ /dev/null @@ -1,58 +0,0 @@ -/*-*- Mode: C; c-basic-offset: 8; indent-tabs-mode: nil -*-*/ - -#ifndef foobuserrorshfoo -#define foobuserrorshfoo - -/*** - This file is part of systemd. - - Copyright 2010 Lennart Poettering - - systemd is free software; you can redistribute it and/or modify it - under the terms of the GNU General Public License as published by - the Free Software Foundation; either version 2 of the License, or - (at your option) any later version. - - systemd is distributed in the hope that 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 General Public License - along with systemd; If not, see . -***/ - -#include -#include - -#define BUS_ERROR_NO_SUCH_UNIT "org.freedesktop.systemd1.NoSuchUnit" -#define BUS_ERROR_NO_SUCH_JOB "org.freedesktop.systemd1.NoSuchJob" -#define BUS_ERROR_NOT_SUBSCRIBED "org.freedesktop.systemd1.NotSubscribed" -#define BUS_ERROR_INVALID_PATH "org.freedesktop.systemd1.InvalidPath" -#define BUS_ERROR_INVALID_NAME "org.freedesktop.systemd1.InvalidName" -#define BUS_ERROR_UNIT_TYPE_MISMATCH "org.freedesktop.systemd1.UnitTypeMismatch" -#define BUS_ERROR_UNIT_EXISTS "org.freedesktop.systemd1.UnitExists" -#define BUS_ERROR_NOT_SUPPORTED "org.freedesktop.systemd1.NotSupported" -#define BUS_ERROR_INVALID_JOB_MODE "org.freedesktop.systemd1.InvalidJobMode" -#define BUS_ERROR_ONLY_BY_DEPENDENCY "org.freedesktop.systemd1.OnlyByDependency" -#define BUS_ERROR_NO_ISOLATION "org.freedesktop.systemd1.NoIsolation" -#define BUS_ERROR_LOAD_FAILED "org.freedesktop.systemd1.LoadFailed" -#define BUS_ERROR_MASKED "org.freedesktop.systemd1.Masked" -#define BUS_ERROR_JOB_TYPE_NOT_APPLICABLE "org.freedesktop.systemd1.JobTypeNotApplicable" -#define BUS_ERROR_TRANSACTION_IS_DESTRUCTIVE "org.freedesktop.systemd1.TransactionIsDestructive" -#define BUS_ERROR_TRANSACTION_JOBS_CONFLICTING "org.freedesktop.systemd1.TransactionJobsConflicting" -#define BUS_ERROR_TRANSACTION_ORDER_IS_CYCLIC "org.freedesktop.systemd1.TransactionOrderIsCyclic" -#define BUS_ERROR_SHUTTING_DOWN "org.freedesktop.systemd1.ShuttingDown" -#define BUS_ERROR_NO_SUCH_PROCESS "org.freedesktop.systemd1.NoSuchProcess" - -static inline const char *bus_error(const DBusError *e, int r) { - if (e && e->message) - return e->message; - - if (r >= 0) - return strerror(r); - - return strerror(-r); -} - -#endif diff --git a/src/cgroup-attr.c b/src/cgroup-attr.c deleted file mode 100644 index 474a6865c4..0000000000 --- a/src/cgroup-attr.c +++ /dev/null @@ -1,102 +0,0 @@ -/*-*- Mode: C; c-basic-offset: 8; indent-tabs-mode: nil -*-*/ - -/*** - This file is part of systemd. - - Copyright 2011 Lennart Poettering - - systemd is free software; you can redistribute it and/or modify it - under the terms of the GNU General Public License as published by - the Free Software Foundation; either version 2 of the License, or - (at your option) any later version. - - systemd is distributed in the hope that 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 General Public License - along with systemd; If not, see . -***/ - -#include "cgroup-attr.h" -#include "cgroup-util.h" -#include "list.h" - -int cgroup_attribute_apply(CGroupAttribute *a, CGroupBonding *b) { - int r; - char *path = NULL; - char *v = NULL; - - assert(a); - - b = cgroup_bonding_find_list(b, a->controller); - if (!b) - return 0; - - if (a->map_callback) { - r = a->map_callback(a->controller, a->name, a->value, &v); - if (r < 0) - return r; - } - - r = cg_get_path(a->controller, b->path, a->name, &path); - if (r < 0) { - free(v); - return r; - } - - r = write_one_line_file(path, v ? v : a->value); - if (r < 0) - log_warning("Failed to write '%s' to %s: %s", v ? v : a->value, path, strerror(-r)); - - free(path); - free(v); - - return r; -} - -int cgroup_attribute_apply_list(CGroupAttribute *first, CGroupBonding *b) { - CGroupAttribute *a; - int r = 0; - - LIST_FOREACH(by_unit, a, first) { - int k; - - k = cgroup_attribute_apply(a, b); - if (r == 0) - r = k; - } - - return r; -} - -CGroupAttribute *cgroup_attribute_find_list(CGroupAttribute *first, const char *controller, const char *name) { - CGroupAttribute *a; - - assert(controller); - assert(name); - - LIST_FOREACH(by_unit, a, first) - if (streq(a->controller, controller) && - streq(a->name, name)) - return a; - - return NULL; -} - -static void cgroup_attribute_free(CGroupAttribute *a) { - assert(a); - - free(a->controller); - free(a->name); - free(a->value); - free(a); -} - -void cgroup_attribute_free_list(CGroupAttribute *first) { - CGroupAttribute *a, *n; - - LIST_FOREACH_SAFE(by_unit, a, n, first) - cgroup_attribute_free(a); -} diff --git a/src/cgroup-attr.h b/src/cgroup-attr.h deleted file mode 100644 index 63a73b8101..0000000000 --- a/src/cgroup-attr.h +++ /dev/null @@ -1,49 +0,0 @@ -/*-*- Mode: C; c-basic-offset: 8; indent-tabs-mode: nil -*-*/ - -#ifndef foocgroupattrhfoo -#define foocgroupattrhfoo - -/*** - This file is part of systemd. - - Copyright 2011 Lennart Poettering - - systemd is free software; you can redistribute it and/or modify it - under the terms of the GNU General Public License as published by - the Free Software Foundation; either version 2 of the License, or - (at your option) any later version. - - systemd is distributed in the hope that 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 General Public License - along with systemd; If not, see . -***/ - -typedef struct CGroupAttribute CGroupAttribute; - -typedef int (*CGroupAttributeMapCallback)(const char *controller, const char*name, const char *value, char **ret); - -#include "unit.h" -#include "cgroup.h" - -struct CGroupAttribute { - char *controller; - char *name; - char *value; - - CGroupAttributeMapCallback map_callback; - - LIST_FIELDS(CGroupAttribute, by_unit); -}; - -int cgroup_attribute_apply(CGroupAttribute *a, CGroupBonding *b); -int cgroup_attribute_apply_list(CGroupAttribute *first, CGroupBonding *b); - -CGroupAttribute *cgroup_attribute_find_list(CGroupAttribute *first, const char *controller, const char *name); - -void cgroup_attribute_free_list(CGroupAttribute *first); - -#endif diff --git a/src/cgroup.c b/src/cgroup.c deleted file mode 100644 index 1f6139e25f..0000000000 --- a/src/cgroup.c +++ /dev/null @@ -1,556 +0,0 @@ -/*-*- Mode: C; c-basic-offset: 8; indent-tabs-mode: nil -*-*/ - -/*** - This file is part of systemd. - - Copyright 2010 Lennart Poettering - - systemd is free software; you can redistribute it and/or modify it - under the terms of the GNU General Public License as published by - the Free Software Foundation; either version 2 of the License, or - (at your option) any later version. - - systemd is distributed in the hope that 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 General Public License - along with systemd; If not, see . -***/ - -#include -#include -#include -#include -#include -#include -#include - -#include "cgroup.h" -#include "cgroup-util.h" -#include "log.h" - -int cgroup_bonding_realize(CGroupBonding *b) { - int r; - - assert(b); - assert(b->path); - assert(b->controller); - - r = cg_create(b->controller, b->path); - if (r < 0) { - log_warning("Failed to create cgroup %s:%s: %s", b->controller, b->path, strerror(-r)); - return r; - } - - b->realized = true; - - return 0; -} - -int cgroup_bonding_realize_list(CGroupBonding *first) { - CGroupBonding *b; - int r; - - LIST_FOREACH(by_unit, b, first) - if ((r = cgroup_bonding_realize(b)) < 0 && b->essential) - return r; - - return 0; -} - -void cgroup_bonding_free(CGroupBonding *b, bool trim) { - assert(b); - - if (b->unit) { - CGroupBonding *f; - - LIST_REMOVE(CGroupBonding, by_unit, b->unit->cgroup_bondings, b); - - if (streq(b->controller, SYSTEMD_CGROUP_CONTROLLER)) { - assert_se(f = hashmap_get(b->unit->manager->cgroup_bondings, b->path)); - LIST_REMOVE(CGroupBonding, by_path, f, b); - - if (f) - hashmap_replace(b->unit->manager->cgroup_bondings, b->path, f); - else - hashmap_remove(b->unit->manager->cgroup_bondings, b->path); - } - } - - if (b->realized && b->ours && trim) - cg_trim(b->controller, b->path, false); - - free(b->controller); - free(b->path); - free(b); -} - -void cgroup_bonding_free_list(CGroupBonding *first, bool remove_or_trim) { - CGroupBonding *b, *n; - - LIST_FOREACH_SAFE(by_unit, b, n, first) - cgroup_bonding_free(b, remove_or_trim); -} - -void cgroup_bonding_trim(CGroupBonding *b, bool delete_root) { - assert(b); - - if (b->realized && b->ours) - cg_trim(b->controller, b->path, delete_root); -} - -void cgroup_bonding_trim_list(CGroupBonding *first, bool delete_root) { - CGroupBonding *b; - - LIST_FOREACH(by_unit, b, first) - cgroup_bonding_trim(b, delete_root); -} - -int cgroup_bonding_install(CGroupBonding *b, pid_t pid) { - int r; - - assert(b); - assert(pid >= 0); - - if ((r = cg_create_and_attach(b->controller, b->path, pid)) < 0) - return r; - - b->realized = true; - return 0; -} - -int cgroup_bonding_install_list(CGroupBonding *first, pid_t pid) { - CGroupBonding *b; - int r; - - LIST_FOREACH(by_unit, b, first) - if ((r = cgroup_bonding_install(b, pid)) < 0 && b->essential) - return r; - - return 0; -} - -int cgroup_bonding_set_group_access(CGroupBonding *b, mode_t mode, uid_t uid, gid_t gid) { - assert(b); - - if (!b->realized) - return -EINVAL; - - return cg_set_group_access(b->controller, b->path, mode, uid, gid); -} - -int cgroup_bonding_set_group_access_list(CGroupBonding *first, mode_t mode, uid_t uid, gid_t gid) { - CGroupBonding *b; - int r; - - LIST_FOREACH(by_unit, b, first) { - r = cgroup_bonding_set_group_access(b, mode, uid, gid); - if (r < 0) - return r; - } - - return 0; -} - -int cgroup_bonding_set_task_access(CGroupBonding *b, mode_t mode, uid_t uid, gid_t gid, int sticky) { - assert(b); - - if (!b->realized) - return -EINVAL; - - return cg_set_task_access(b->controller, b->path, mode, uid, gid, sticky); -} - -int cgroup_bonding_set_task_access_list(CGroupBonding *first, mode_t mode, uid_t uid, gid_t gid, int sticky) { - CGroupBonding *b; - int r; - - LIST_FOREACH(by_unit, b, first) { - r = cgroup_bonding_set_task_access(b, mode, uid, gid, sticky); - if (r < 0) - return r; - } - - return 0; -} - -int cgroup_bonding_kill(CGroupBonding *b, int sig, bool sigcont, Set *s) { - assert(b); - assert(sig >= 0); - - /* Don't kill cgroups that aren't ours */ - if (!b->ours) - return 0; - - return cg_kill_recursive(b->controller, b->path, sig, sigcont, true, false, s); -} - -int cgroup_bonding_kill_list(CGroupBonding *first, int sig, bool sigcont, Set *s) { - CGroupBonding *b; - Set *allocated_set = NULL; - int ret = -EAGAIN, r; - - if (!first) - return 0; - - if (!s) - if (!(s = allocated_set = set_new(trivial_hash_func, trivial_compare_func))) - return -ENOMEM; - - LIST_FOREACH(by_unit, b, first) { - if ((r = cgroup_bonding_kill(b, sig, sigcont, s)) < 0) { - if (r == -EAGAIN || r == -ESRCH) - continue; - - ret = r; - goto finish; - } - - if (ret < 0 || r > 0) - ret = r; - } - -finish: - if (allocated_set) - set_free(allocated_set); - - return ret; -} - -/* Returns 1 if the group is empty, 0 if it is not, -EAGAIN if we - * cannot know */ -int cgroup_bonding_is_empty(CGroupBonding *b) { - int r; - - assert(b); - - if ((r = cg_is_empty_recursive(b->controller, b->path, true)) < 0) - return r; - - /* If it is empty it is empty */ - if (r > 0) - return 1; - - /* It's not only us using this cgroup, so we just don't know */ - return b->ours ? 0 : -EAGAIN; -} - -int cgroup_bonding_is_empty_list(CGroupBonding *first) { - CGroupBonding *b; - - LIST_FOREACH(by_unit, b, first) { - int r; - - if ((r = cgroup_bonding_is_empty(b)) < 0) { - /* If this returned -EAGAIN, then we don't know if the - * group is empty, so let's see if another group can - * tell us */ - - if (r != -EAGAIN) - return r; - } else - return r; - } - - return -EAGAIN; -} - -int manager_setup_cgroup(Manager *m) { - char *current = NULL, *path = NULL; - int r; - char suffix[32]; - - assert(m); - - /* 0. Be nice to Ingo Molnar #628004 */ - if (path_is_mount_point("/sys/fs/cgroup/systemd", false) <= 0) { - log_warning("No control group support available, not creating root group."); - return 0; - } - - /* 1. Determine hierarchy */ - if ((r = cg_get_by_pid(SYSTEMD_CGROUP_CONTROLLER, 0, ¤t)) < 0) { - log_error("Cannot determine cgroup we are running in: %s", strerror(-r)); - goto finish; - } - - if (m->running_as == MANAGER_SYSTEM) - strcpy(suffix, "/system"); - else { - snprintf(suffix, sizeof(suffix), "/systemd-%lu", (unsigned long) getpid()); - char_array_0(suffix); - } - - free(m->cgroup_hierarchy); - if (endswith(current, suffix)) { - /* We probably got reexecuted and can continue to use our root cgroup */ - m->cgroup_hierarchy = current; - current = NULL; - - } else { - /* We need a new root cgroup */ - m->cgroup_hierarchy = NULL; - if (asprintf(&m->cgroup_hierarchy, "%s%s", streq(current, "/") ? "" : current, suffix) < 0) { - log_error("Out of memory"); - r = -ENOMEM; - goto finish; - } - } - - /* 2. Show data */ - if ((r = cg_get_path(SYSTEMD_CGROUP_CONTROLLER, m->cgroup_hierarchy, NULL, &path)) < 0) { - log_error("Cannot find cgroup mount point: %s", strerror(-r)); - goto finish; - } - - log_debug("Using cgroup controller " SYSTEMD_CGROUP_CONTROLLER ". File system hierarchy is at %s.", path); - - /* 3. Install agent */ - if ((r = cg_install_release_agent(SYSTEMD_CGROUP_CONTROLLER, SYSTEMD_CGROUP_AGENT_PATH)) < 0) - log_warning("Failed to install release agent, ignoring: %s", strerror(-r)); - else if (r > 0) - log_debug("Installed release agent."); - else - log_debug("Release agent already installed."); - - /* 4. Realize the group */ - if ((r = cg_create_and_attach(SYSTEMD_CGROUP_CONTROLLER, m->cgroup_hierarchy, 0)) < 0) { - log_error("Failed to create root cgroup hierarchy: %s", strerror(-r)); - goto finish; - } - - /* 5. And pin it, so that it cannot be unmounted */ - if (m->pin_cgroupfs_fd >= 0) - close_nointr_nofail(m->pin_cgroupfs_fd); - - if ((m->pin_cgroupfs_fd = open(path, O_RDONLY|O_CLOEXEC|O_DIRECTORY|O_NOCTTY|O_NONBLOCK)) < 0) { - log_error("Failed to open pin file: %m"); - r = -errno; - goto finish; - } - - log_debug("Created root group."); - -finish: - free(current); - free(path); - - return r; -} - -void manager_shutdown_cgroup(Manager *m, bool delete) { - assert(m); - - if (delete && m->cgroup_hierarchy) - cg_delete(SYSTEMD_CGROUP_CONTROLLER, m->cgroup_hierarchy); - - if (m->pin_cgroupfs_fd >= 0) { - close_nointr_nofail(m->pin_cgroupfs_fd); - m->pin_cgroupfs_fd = -1; - } - - free(m->cgroup_hierarchy); - m->cgroup_hierarchy = NULL; -} - -int cgroup_bonding_get(Manager *m, const char *cgroup, CGroupBonding **bonding) { - CGroupBonding *b; - char *p; - - assert(m); - assert(cgroup); - assert(bonding); - - b = hashmap_get(m->cgroup_bondings, cgroup); - if (b) { - *bonding = b; - return 1; - } - - p = strdup(cgroup); - if (!p) - return -ENOMEM; - - for (;;) { - char *e; - - e = strrchr(p, '/'); - if (!e || e == p) { - free(p); - *bonding = NULL; - return 0; - } - - *e = 0; - - b = hashmap_get(m->cgroup_bondings, p); - if (b) { - free(p); - *bonding = b; - return 1; - } - } -} - -int cgroup_notify_empty(Manager *m, const char *group) { - CGroupBonding *l, *b; - int r; - - assert(m); - assert(group); - - r = cgroup_bonding_get(m, group, &l); - if (r <= 0) - return r; - - LIST_FOREACH(by_path, b, l) { - int t; - - if (!b->unit) - continue; - - t = cgroup_bonding_is_empty_list(b); - if (t < 0) { - - /* If we don't know, we don't know */ - if (t != -EAGAIN) - log_warning("Failed to check whether cgroup is empty: %s", strerror(errno)); - - continue; - } - - if (t > 0) { - /* If it is empty, let's delete it */ - cgroup_bonding_trim_list(b->unit->cgroup_bondings, true); - - if (UNIT_VTABLE(b->unit)->cgroup_notify_empty) - UNIT_VTABLE(b->unit)->cgroup_notify_empty(b->unit); - } - } - - return 0; -} - -Unit* cgroup_unit_by_pid(Manager *m, pid_t pid) { - CGroupBonding *l, *b; - char *group = NULL; - - assert(m); - - if (pid <= 1) - return NULL; - - if (cg_get_by_pid(SYSTEMD_CGROUP_CONTROLLER, pid, &group) < 0) - return NULL; - - l = hashmap_get(m->cgroup_bondings, group); - - if (!l) { - char *slash; - - while ((slash = strrchr(group, '/'))) { - if (slash == group) - break; - - *slash = 0; - - if ((l = hashmap_get(m->cgroup_bondings, group))) - break; - } - } - - free(group); - - LIST_FOREACH(by_path, b, l) { - - if (!b->unit) - continue; - - if (b->ours) - return b->unit; - } - - return NULL; -} - -CGroupBonding *cgroup_bonding_find_list(CGroupBonding *first, const char *controller) { - CGroupBonding *b; - - assert(controller); - - LIST_FOREACH(by_unit, b, first) - if (streq(b->controller, controller)) - return b; - - return NULL; -} - -char *cgroup_bonding_to_string(CGroupBonding *b) { - char *r; - - assert(b); - - if (asprintf(&r, "%s:%s", b->controller, b->path) < 0) - return NULL; - - return r; -} - -pid_t cgroup_bonding_search_main_pid(CGroupBonding *b) { - FILE *f; - pid_t pid = 0, npid, mypid; - - assert(b); - - if (!b->ours) - return 0; - - if (cg_enumerate_processes(b->controller, b->path, &f) < 0) - return 0; - - 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_parent_of_pid(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. */ - pid = 0; - break; - } - - pid = npid; - } - - fclose(f); - - return pid; -} - -pid_t cgroup_bonding_search_main_pid_list(CGroupBonding *first) { - CGroupBonding *b; - pid_t pid; - - /* Try to find a main pid from this cgroup, but checking if - * there's only one PID in the cgroup and returning it. Later - * on we might want to add additional, smarter heuristics - * here. */ - - LIST_FOREACH(by_unit, b, first) - if ((pid = cgroup_bonding_search_main_pid(b)) != 0) - return pid; - - return 0; - -} diff --git a/src/cgroup.h b/src/cgroup.h deleted file mode 100644 index 5faa7dc0f7..0000000000 --- a/src/cgroup.h +++ /dev/null @@ -1,94 +0,0 @@ -/*-*- Mode: C; c-basic-offset: 8; indent-tabs-mode: nil -*-*/ - -#ifndef foocgrouphfoo -#define foocgrouphfoo - -/*** - This file is part of systemd. - - Copyright 2010 Lennart Poettering - - systemd is free software; you can redistribute it and/or modify it - under the terms of the GNU General Public License as published by - the Free Software Foundation; either version 2 of the License, or - (at your option) any later version. - - systemd is distributed in the hope that 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 General Public License - along with systemd; If not, see . -***/ - -typedef struct CGroupBonding CGroupBonding; - -#include "unit.h" - -/* Binds a cgroup to a name */ -struct CGroupBonding { - char *controller; - char *path; - - Unit *unit; - - /* For the Unit::cgroup_bondings list */ - LIST_FIELDS(CGroupBonding, by_unit); - - /* For the Manager::cgroup_bondings hashmap */ - LIST_FIELDS(CGroupBonding, by_path); - - /* When shutting down, remove cgroup? Are our own tasks the - * only ones in this group?*/ - bool ours:1; - - /* If we cannot create this group, or add a process to it, is this fatal? */ - bool essential:1; - - /* This cgroup is realized */ - bool realized:1; -}; - -int cgroup_bonding_realize(CGroupBonding *b); -int cgroup_bonding_realize_list(CGroupBonding *first); - -void cgroup_bonding_free(CGroupBonding *b, bool trim); -void cgroup_bonding_free_list(CGroupBonding *first, bool trim); - -int cgroup_bonding_install(CGroupBonding *b, pid_t pid); -int cgroup_bonding_install_list(CGroupBonding *first, pid_t pid); - -int cgroup_bonding_set_group_access(CGroupBonding *b, mode_t mode, uid_t uid, gid_t gid); -int cgroup_bonding_set_group_access_list(CGroupBonding *b, mode_t mode, uid_t uid, gid_t gid); - -int cgroup_bonding_set_task_access(CGroupBonding *b, mode_t mode, uid_t uid, gid_t gid, int sticky); -int cgroup_bonding_set_task_access_list(CGroupBonding *b, mode_t mode, uid_t uid, gid_t gid, int sticky); - -int cgroup_bonding_kill(CGroupBonding *b, int sig, bool sigcont, Set *s); -int cgroup_bonding_kill_list(CGroupBonding *first, int sig, bool sigcont, Set *s); - -void cgroup_bonding_trim(CGroupBonding *first, bool delete_root); -void cgroup_bonding_trim_list(CGroupBonding *first, bool delete_root); - -int cgroup_bonding_is_empty(CGroupBonding *b); -int cgroup_bonding_is_empty_list(CGroupBonding *first); - -CGroupBonding *cgroup_bonding_find_list(CGroupBonding *first, const char *controller); - -char *cgroup_bonding_to_string(CGroupBonding *b); - -pid_t cgroup_bonding_search_main_pid(CGroupBonding *b); -pid_t cgroup_bonding_search_main_pid_list(CGroupBonding *b); - -#include "manager.h" - -int manager_setup_cgroup(Manager *m); -void manager_shutdown_cgroup(Manager *m, bool delete); - -int cgroup_bonding_get(Manager *m, const char *cgroup, CGroupBonding **bonding); -int cgroup_notify_empty(Manager *m, const char *group); - -Unit* cgroup_unit_by_pid(Manager *m, pid_t pid); - -#endif diff --git a/src/condition.c b/src/condition.c deleted file mode 100644 index 2b51a16f17..0000000000 --- a/src/condition.c +++ /dev/null @@ -1,323 +0,0 @@ -/*-*- Mode: C; c-basic-offset: 8; indent-tabs-mode: nil -*-*/ - -/*** - This file is part of systemd. - - Copyright 2010 Lennart Poettering - - systemd is free software; you can redistribute it and/or modify it - under the terms of the GNU General Public License as published by - the Free Software Foundation; either version 2 of the License, or - (at your option) any later version. - - systemd is distributed in the hope that 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 General Public License - along with systemd; If not, see . -***/ - -#include -#include -#include -#include -#include - -#ifdef HAVE_SELINUX -#include -#endif - -#include "util.h" -#include "condition.h" -#include "virt.h" - -Condition* condition_new(ConditionType type, const char *parameter, bool trigger, bool negate) { - Condition *c; - - assert(type < _CONDITION_TYPE_MAX); - - c = new0(Condition, 1); - if (!c) - return NULL; - - c->type = type; - c->trigger = trigger; - c->negate = negate; - - if (parameter) { - c->parameter = strdup(parameter); - if (!c->parameter) { - free(c); - return NULL; - } - } - - return c; -} - -void condition_free(Condition *c) { - assert(c); - - free(c->parameter); - free(c); -} - -void condition_free_list(Condition *first) { - Condition *c, *n; - - LIST_FOREACH_SAFE(conditions, c, n, first) - condition_free(c); -} - -static bool test_kernel_command_line(const char *parameter) { - char *line, *w, *state, *word = NULL; - bool equal; - int r; - size_t l, pl; - bool found = false; - - assert(parameter); - - if (detect_container(NULL) > 0) - return false; - - r = read_one_line_file("/proc/cmdline", &line); - if (r < 0) { - log_warning("Failed to read /proc/cmdline, ignoring: %s", strerror(-r)); - return false; - } - - equal = !!strchr(parameter, '='); - pl = strlen(parameter); - - FOREACH_WORD_QUOTED(w, l, line, state) { - - free(word); - word = strndup(w, l); - if (!word) - break; - - if (equal) { - if (streq(word, parameter)) { - found = true; - break; - } - } else { - if (startswith(word, parameter) && (word[pl] == '=' || word[pl] == 0)) { - found = true; - break; - } - } - - } - - free(word); - free(line); - - return found; -} - -static bool test_virtualization(const char *parameter) { - int b; - Virtualization v; - const char *id; - - assert(parameter); - - v = detect_virtualization(&id); - if (v < 0) { - log_warning("Failed to detect virtualization, ignoring: %s", strerror(-v)); - return false; - } - - /* First, compare with yes/no */ - b = parse_boolean(parameter); - - if (v > 0 && b > 0) - return true; - - if (v == 0 && b == 0) - return true; - - /* Then, compare categorization */ - if (v == VIRTUALIZATION_VM && streq(parameter, "vm")) - return true; - - if (v == VIRTUALIZATION_CONTAINER && streq(parameter, "container")) - return true; - - /* Finally compare id */ - return v > 0 && streq(parameter, id); -} - -static bool test_security(const char *parameter) { -#ifdef HAVE_SELINUX - if (streq(parameter, "selinux")) - return is_selinux_enabled() > 0; -#endif - return false; -} - -static bool test_capability(const char *parameter) { - cap_value_t value; - FILE *f; - char line[LINE_MAX]; - unsigned long long capabilities = (unsigned long long) -1; - - /* If it's an invalid capability, we don't have it */ - - if (cap_from_name(parameter, &value) < 0) - return false; - - /* If it's a valid capability we default to assume - * that we have it */ - - f = fopen("/proc/self/status", "re"); - if (!f) - return true; - - while (fgets(line, sizeof(line), f)) { - truncate_nl(line); - - if (startswith(line, "CapBnd:")) { - (void) sscanf(line+7, "%llx", &capabilities); - break; - } - } - - fclose(f); - - return !!(capabilities & (1ULL << value)); -} - -bool condition_test(Condition *c) { - assert(c); - - switch(c->type) { - - case CONDITION_PATH_EXISTS: - return (access(c->parameter, F_OK) >= 0) == !c->negate; - - case CONDITION_PATH_EXISTS_GLOB: - return (glob_exists(c->parameter) > 0) == !c->negate; - - case CONDITION_PATH_IS_DIRECTORY: { - struct stat st; - - if (stat(c->parameter, &st) < 0) - return c->negate; - return S_ISDIR(st.st_mode) == !c->negate; - } - - case CONDITION_PATH_IS_SYMBOLIC_LINK: { - struct stat st; - - if (lstat(c->parameter, &st) < 0) - return c->negate; - return S_ISLNK(st.st_mode) == !c->negate; - } - - case CONDITION_PATH_IS_MOUNT_POINT: - return (path_is_mount_point(c->parameter, true) > 0) == !c->negate; - - case CONDITION_DIRECTORY_NOT_EMPTY: { - int k; - - k = dir_is_empty(c->parameter); - return !(k == -ENOENT || k > 0) == !c->negate; - } - - case CONDITION_FILE_IS_EXECUTABLE: { - struct stat st; - - if (stat(c->parameter, &st) < 0) - return c->negate; - - return (S_ISREG(st.st_mode) && (st.st_mode & 0111)) == !c->negate; - } - - case CONDITION_KERNEL_COMMAND_LINE: - return test_kernel_command_line(c->parameter) == !c->negate; - - case CONDITION_VIRTUALIZATION: - return test_virtualization(c->parameter) == !c->negate; - - case CONDITION_SECURITY: - return test_security(c->parameter) == !c->negate; - - case CONDITION_CAPABILITY: - return test_capability(c->parameter) == !c->negate; - - case CONDITION_NULL: - return !c->negate; - - default: - assert_not_reached("Invalid condition type."); - } -} - -bool condition_test_list(Condition *first) { - Condition *c; - int triggered = -1; - - /* 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) { - bool b; - - b = condition_test(c); - - if (!c->trigger && !b) - return false; - - if (c->trigger && triggered <= 0) - triggered = b; - } - - return triggered != 0; -} - -void condition_dump(Condition *c, FILE *f, const char *prefix) { - assert(c); - assert(f); - - if (!prefix) - prefix = ""; - - fprintf(f, - "%s\t%s: %s%s%s\n", - prefix, - condition_type_to_string(c->type), - c->trigger ? "|" : "", - c->negate ? "!" : "", - c->parameter); -} - -void condition_dump_list(Condition *first, FILE *f, const char *prefix) { - Condition *c; - - LIST_FOREACH(conditions, c, first) - condition_dump(c, f, prefix); -} - -static const char* const condition_type_table[_CONDITION_TYPE_MAX] = { - [CONDITION_PATH_EXISTS] = "ConditionPathExists", - [CONDITION_PATH_EXISTS_GLOB] = "ConditionPathExistsGlob", - [CONDITION_PATH_IS_DIRECTORY] = "ConditionPathIsDirectory", - [CONDITION_PATH_IS_SYMBOLIC_LINK] = "ConditionPathIsSymbolicLink", - [CONDITION_PATH_IS_MOUNT_POINT] = "ConditionPathIsMountPoint", - [CONDITION_DIRECTORY_NOT_EMPTY] = "ConditionDirectoryNotEmpty", - [CONDITION_KERNEL_COMMAND_LINE] = "ConditionKernelCommandLine", - [CONDITION_VIRTUALIZATION] = "ConditionVirtualization", - [CONDITION_SECURITY] = "ConditionSecurity", - [CONDITION_NULL] = "ConditionNull" -}; - -DEFINE_STRING_TABLE_LOOKUP(condition_type, ConditionType); diff --git a/src/condition.h b/src/condition.h deleted file mode 100644 index 71b1c6761e..0000000000 --- a/src/condition.h +++ /dev/null @@ -1,69 +0,0 @@ -/*-*- Mode: C; c-basic-offset: 8; indent-tabs-mode: nil -*-*/ - -#ifndef fooconditionhfoo -#define fooconditionhfoo - -/*** - This file is part of systemd. - - Copyright 2010 Lennart Poettering - - systemd is free software; you can redistribute it and/or modify it - under the terms of the GNU General Public License as published by - the Free Software Foundation; either version 2 of the License, or - (at your option) any later version. - - systemd is distributed in the hope that 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 General Public License - along with systemd; If not, see . -***/ - -#include - -#include "list.h" - -typedef enum ConditionType { - CONDITION_PATH_EXISTS, - CONDITION_PATH_EXISTS_GLOB, - CONDITION_PATH_IS_DIRECTORY, - CONDITION_PATH_IS_SYMBOLIC_LINK, - CONDITION_PATH_IS_MOUNT_POINT, - CONDITION_DIRECTORY_NOT_EMPTY, - CONDITION_FILE_IS_EXECUTABLE, - CONDITION_KERNEL_COMMAND_LINE, - CONDITION_VIRTUALIZATION, - CONDITION_SECURITY, - CONDITION_CAPABILITY, - CONDITION_NULL, - _CONDITION_TYPE_MAX, - _CONDITION_TYPE_INVALID = -1 -} ConditionType; - -typedef struct Condition { - ConditionType type; - char *parameter; - - bool trigger:1; - bool negate:1; - - LIST_FIELDS(struct Condition, conditions); -} Condition; - -Condition* condition_new(ConditionType type, const char *parameter, bool trigger, bool negate); -void condition_free(Condition *c); -void condition_free_list(Condition *c); - -bool condition_test(Condition *c); -bool condition_test_list(Condition *c); - -void condition_dump(Condition *c, FILE *f, const char *prefix); -void condition_dump_list(Condition *c, FILE *f, const char *prefix); - -const char* condition_type_to_string(ConditionType t); -int condition_type_from_string(const char *s); - -#endif diff --git a/src/core/ask-password-api.h b/src/core/ask-password-api.h new file mode 100644 index 0000000000..fec8625a0f --- /dev/null +++ b/src/core/ask-password-api.h @@ -0,0 +1,33 @@ +/*-*- Mode: C; c-basic-offset: 8; indent-tabs-mode: nil -*-*/ + +#ifndef fooaskpasswordapihfoo +#define fooaskpasswordapihfoo + +/*** + This file is part of systemd. + + Copyright 2010 Lennart Poettering + + systemd is free software; you can redistribute it and/or modify it + under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + systemd is distributed in the hope that 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 General Public License + along with systemd; If not, see . +***/ + +#include "util.h" + +int ask_password_tty(const char *message, usec_t until, const char *flag_file, char **_passphrase); + +int ask_password_agent(const char *message, const char *icon, usec_t until, bool accept_cached, char ***_passphrases); + +int ask_password_auto(const char *message, const char *icon, usec_t until, bool accept_cached, char ***_passphrases); + +#endif diff --git a/src/core/automount.c b/src/core/automount.c new file mode 100644 index 0000000000..6857a6fd76 --- /dev/null +++ b/src/core/automount.c @@ -0,0 +1,888 @@ +/*-*- Mode: C; c-basic-offset: 8; indent-tabs-mode: nil -*-*/ + +/*** + This file is part of systemd. + + Copyright 2010 Lennart Poettering + + systemd is free software; you can redistribute it and/or modify it + under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + systemd is distributed in the hope that 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 General Public License + along with systemd; If not, see . +***/ + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "unit.h" +#include "automount.h" +#include "load-fragment.h" +#include "load-dropin.h" +#include "unit-name.h" +#include "dbus-automount.h" +#include "bus-errors.h" +#include "special.h" +#include "label.h" +#include "mkdir.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 +}; + +static int open_dev_autofs(Manager *m); + +static void automount_init(Unit *u) { + Automount *a = AUTOMOUNT(u); + + assert(u); + assert(u->load_state == UNIT_STUB); + + a->pipe_watch.fd = a->pipe_fd = -1; + a->pipe_watch.type = WATCH_INVALID; + + a->directory_mode = 0755; + + UNIT(a)->ignore_on_isolate = true; +} + +static void repeat_unmout(const char *path) { + assert(path); + + for (;;) { + /* If there are multiple mounts on a mount point, this + * removes them all */ + + if (umount2(path, MNT_DETACH) >= 0) + continue; + + if (errno != EINVAL) + log_error("Failed to unmount: %m"); + + break; + } +} + +static void unmount_autofs(Automount *a) { + assert(a); + + if (a->pipe_fd < 0) + return; + + automount_send_ready(a, -EHOSTDOWN); + + unit_unwatch_fd(UNIT(a), &a->pipe_watch); + close_nointr_nofail(a->pipe_fd); + a->pipe_fd = -1; + + /* 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)) + repeat_unmout(a->where); +} + +static void automount_done(Unit *u) { + Automount *a = AUTOMOUNT(u); + + assert(a); + + unmount_autofs(a); + unit_ref_unset(&a->mount); + + free(a->where); + a->where = NULL; + + set_free(a->tokens); + a->tokens = NULL; +} + +int automount_add_one_mount_link(Automount *a, Mount *m) { + int r; + + assert(a); + assert(m); + + if (UNIT(a)->load_state != UNIT_LOADED || + UNIT(m)->load_state != UNIT_LOADED) + return 0; + + if (!path_startswith(a->where, m->where)) + return 0; + + if (path_equal(a->where, m->where)) + return 0; + + if ((r = unit_add_two_dependencies(UNIT(a), UNIT_AFTER, UNIT_REQUIRES, UNIT(m), true)) < 0) + return r; + + return 0; +} + +static int automount_add_mount_links(Automount *a) { + Unit *other; + int r; + + assert(a); + + LIST_FOREACH(units_by_type, other, UNIT(a)->manager->units_by_type[UNIT_MOUNT]) + if ((r = automount_add_one_mount_link(a, MOUNT(other))) < 0) + return r; + + return 0; +} + +static int automount_add_default_dependencies(Automount *a) { + int r; + + assert(a); + + if (UNIT(a)->manager->running_as == MANAGER_SYSTEM) { + + if ((r = unit_add_dependency_by_name(UNIT(a), UNIT_BEFORE, SPECIAL_BASIC_TARGET, NULL, true)) < 0) + return r; + + if ((r = unit_add_two_dependencies_by_name(UNIT(a), UNIT_BEFORE, UNIT_CONFLICTS, SPECIAL_UMOUNT_TARGET, NULL, true)) < 0) + return r; + } + + return 0; +} + +static int automount_verify(Automount *a) { + bool b; + char *e; + assert(a); + + if (UNIT(a)->load_state != UNIT_LOADED) + return 0; + + if (path_equal(a->where, "/")) { + log_error("Cannot have an automount unit for the root directory. Refusing."); + return -EINVAL; + } + + if (!(e = unit_name_from_path(a->where, ".automount"))) + return -ENOMEM; + + b = unit_has_name(UNIT(a), e); + free(e); + + if (!b) { + log_error("%s's Where setting doesn't match unit name. Refusing.", UNIT(a)->id); + return -EINVAL; + } + + return 0; +} + +static int automount_load(Unit *u) { + int r; + Automount *a = AUTOMOUNT(u); + + assert(u); + assert(u->load_state == UNIT_STUB); + + /* Load a .automount file */ + if ((r = unit_load_fragment_and_dropin_optional(u)) < 0) + return r; + + if (u->load_state == UNIT_LOADED) { + Unit *x; + + if (!a->where) + if (!(a->where = unit_name_to_path(u->id))) + return -ENOMEM; + + path_kill_slashes(a->where); + + if ((r = automount_add_mount_links(a)) < 0) + return r; + + r = unit_load_related_unit(u, ".mount", &x); + if (r < 0) + return r; + + unit_ref_set(&a->mount, x); + + r = unit_add_two_dependencies(u, UNIT_BEFORE, UNIT_TRIGGERS, UNIT_DEREF(a->mount), true); + if (r < 0) + return r; + + if (UNIT(a)->default_dependencies) + if ((r = automount_add_default_dependencies(a)) < 0) + return r; + } + + return automount_verify(a); +} + +static void automount_set_state(Automount *a, AutomountState state) { + AutomountState old_state; + assert(a); + + old_state = a->state; + a->state = state; + + if (state != AUTOMOUNT_WAITING && + state != AUTOMOUNT_RUNNING) + unmount_autofs(a); + + if (state != old_state) + log_debug("%s changed %s -> %s", + UNIT(a)->id, + 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) { + + if ((r = open_dev_autofs(u->manager)) < 0) + return r; + + if (a->deserialized_state == AUTOMOUNT_WAITING || + a->deserialized_state == AUTOMOUNT_RUNNING) { + + assert(a->pipe_fd >= 0); + + if ((r = unit_watch_fd(UNIT(a), a->pipe_fd, EPOLLIN, &a->pipe_watch)) < 0) + return r; + } + + automount_set_state(a, a->deserialized_state); + } + + return 0; +} + +static void automount_dump(Unit *u, FILE *f, const char *prefix) { + Automount *a = AUTOMOUNT(u); + + assert(a); + + fprintf(f, + "%sAutomount State: %s\n" + "%sResult: %s\n" + "%sWhere: %s\n" + "%sDirectoryMode: %04o\n", + prefix, automount_state_to_string(a->state), + prefix, automount_result_to_string(a->result), + prefix, a->where, + prefix, a->directory_mode); +} + +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); + + if ((m->dev_autofs_fd = open("/dev/autofs", O_CLOEXEC|O_RDONLY)) < 0) { + log_error("Failed to open /dev/autofs: %s", strerror(errno)); + return -errno; + } + + init_autofs_dev_ioctl(¶m); + if (ioctl(m->dev_autofs_fd, AUTOFS_DEV_IOCTL_VERSION, ¶m) < 0) { + close_nointr_nofail(m->dev_autofs_fd); + m->dev_autofs_fd = -1; + 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; + int r; + + assert(dev_autofs_fd >= 0); + assert(where); + + l = sizeof(struct autofs_dev_ioctl) + strlen(where) + 1; + + if (!(param = malloc(l))) + return -ENOMEM; + + 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) { + r = -errno; + goto finish; + } + + if (param->ioctlfd < 0) { + r = -EIO; + goto finish; + } + + fd_cloexec(param->ioctlfd, true); + r = param->ioctlfd; + +finish: + free(param); + return r; +} + +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, time_t sec) { + struct autofs_dev_ioctl param; + + assert(dev_autofs_fd >= 0); + assert(ioctl_fd >= 0); + + init_autofs_dev_ioctl(¶m); + param.ioctlfd = ioctl_fd; + param.timeout.timeout = sec; + + if (ioctl(dev_autofs_fd, AUTOFS_DEV_IOCTL_TIMEOUT, ¶m) < 0) + return -errno; + + return 0; +} + +static int autofs_send_ready(int dev_autofs_fd, int ioctl_fd, uint32_t token, int status) { + struct autofs_dev_ioctl param; + + assert(dev_autofs_fd >= 0); + assert(ioctl_fd >= 0); + + init_autofs_dev_ioctl(¶m); + param.ioctlfd = ioctl_fd; + + if (status) { + param.fail.token = token; + param.fail.status = status; + } else + param.ready.token = token; + + if (ioctl(dev_autofs_fd, status ? AUTOFS_DEV_IOCTL_FAIL : AUTOFS_DEV_IOCTL_READY, ¶m) < 0) + return -errno; + + return 0; +} + +int automount_send_ready(Automount *a, int status) { + int ioctl_fd, r; + unsigned token; + + assert(a); + assert(status <= 0); + + if (set_isempty(a->tokens)) + return 0; + + if ((ioctl_fd = open_ioctl_fd(UNIT(a)->manager->dev_autofs_fd, a->where, a->dev_id)) < 0) { + r = ioctl_fd; + goto fail; + } + + if (status) + log_debug("Sending failure: %s", strerror(-status)); + else + log_debug("Sending success."); + + r = 0; + + /* Autofs thankfully does not hand out 0 as a token */ + while ((token = PTR_TO_UINT(set_steal_first(a->tokens)))) { + int k; + + /* Autofs fun fact II: + * + * if you pass a positive status code here, the kernel will + * freeze! Yay! */ + + if ((k = autofs_send_ready(UNIT(a)->manager->dev_autofs_fd, + ioctl_fd, + token, + status)) < 0) + r = k; + } + +fail: + if (ioctl_fd >= 0) + close_nointr_nofail(ioctl_fd); + + return r; +} + +static void automount_enter_waiting(Automount *a) { + int p[2] = { -1, -1 }; + char name[32], options[128]; + bool mounted = false; + int r, ioctl_fd = -1, dev_autofs_fd; + struct stat st; + + assert(a); + assert(a->pipe_fd < 0); + assert(a->where); + + if (a->tokens) + set_clear(a->tokens); + + if ((dev_autofs_fd = open_dev_autofs(UNIT(a)->manager)) < 0) { + r = dev_autofs_fd; + goto fail; + } + + /* We knowingly ignore the results of this call */ + mkdir_p(a->where, 0555); + + if (pipe2(p, O_NONBLOCK|O_CLOEXEC) < 0) { + r = -errno; + goto fail; + } + + snprintf(options, sizeof(options), "fd=%i,pgrp=%u,minproto=5,maxproto=5,direct", p[1], (unsigned) getpgrp()); + char_array_0(options); + + snprintf(name, sizeof(name), "systemd-%u", (unsigned) getpid()); + char_array_0(name); + + if (mount(name, a->where, "autofs", 0, options) < 0) { + r = -errno; + goto fail; + } + + mounted = true; + + close_nointr_nofail(p[1]); + p[1] = -1; + + if (stat(a->where, &st) < 0) { + r = -errno; + goto fail; + } + + if ((ioctl_fd = open_ioctl_fd(dev_autofs_fd, a->where, st.st_dev)) < 0) { + r = ioctl_fd; + goto fail; + } + + if ((r = autofs_protocol(dev_autofs_fd, ioctl_fd)) < 0) + goto fail; + + if ((r = autofs_set_timeout(dev_autofs_fd, ioctl_fd, 300)) < 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. */ + + close_nointr_nofail(ioctl_fd); + ioctl_fd = -1; + + if ((r = unit_watch_fd(UNIT(a), p[0], EPOLLIN, &a->pipe_watch)) < 0) + goto fail; + + a->pipe_fd = p[0]; + a->dev_id = st.st_dev; + + automount_set_state(a, AUTOMOUNT_WAITING); + + return; + +fail: + assert_se(close_pipe(p) == 0); + + if (ioctl_fd >= 0) + close_nointr_nofail(ioctl_fd); + + if (mounted) + repeat_unmout(a->where); + + log_error("Failed to initialize automounter: %s", strerror(-r)); + automount_enter_dead(a, AUTOMOUNT_FAILURE_RESOURCES); +} + +static void automount_enter_runnning(Automount *a) { + int r; + struct stat st; + DBusError error; + + assert(a); + assert(UNIT_DEREF(a->mount)); + + dbus_error_init(&error); + + /* We don't take mount requests anymore if we are supposed to + * shut down anyway */ + if (unit_pending_inactive(UNIT(a))) { + log_debug("Suppressing automount request on %s since unit stop is scheduled.", UNIT(a)->id); + automount_send_ready(a, -EHOSTDOWN); + return; + } + + mkdir_p(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_warning("%s failed to stat automount point: %m", UNIT(a)->id); + goto fail; + } + + if (!S_ISDIR(st.st_mode) || st.st_dev != a->dev_id) + log_info("%s's automount point already active?", UNIT(a)->id); + else if ((r = manager_add_job(UNIT(a)->manager, JOB_START, UNIT_DEREF(a->mount), JOB_REPLACE, true, &error, NULL)) < 0) { + log_warning("%s failed to queue mount startup job: %s", UNIT(a)->id, bus_error(&error, r)); + goto fail; + } + + automount_set_state(a, AUTOMOUNT_RUNNING); + return; + +fail: + automount_enter_dead(a, AUTOMOUNT_FAILURE_RESOURCES); + dbus_error_free(&error); +} + +static int automount_start(Unit *u) { + Automount *a = AUTOMOUNT(u); + + assert(a); + + assert(a->state == AUTOMOUNT_DEAD || a->state == AUTOMOUNT_FAILED); + + if (path_is_mount_point(a->where, false)) { + log_error("Path %s is already a mount point, refusing start for %s", a->where, u->id); + return -EEXIST; + } + + if (UNIT_DEREF(a->mount)->load_state != UNIT_LOADED) + return -ENOENT; + + a->result = AUTOMOUNT_SUCCESS; + automount_enter_waiting(a); + return 0; +} + +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 0; +} + +static int automount_serialize(Unit *u, FILE *f, FDSet *fds) { + Automount *a = AUTOMOUNT(u); + void *p; + Iterator i; + + 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)); + + if (a->pipe_fd >= 0) { + int copy; + + if ((copy = fdset_put_dup(fds, a->pipe_fd)) < 0) + return copy; + + unit_serialize_item_format(u, f, "pipe-fd", "%i", copy); + } + + 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; + + if ((state = automount_state_from_string(value)) < 0) + log_debug("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_debug("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_debug("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_debug("Failed to parse token value %s", value); + else { + if (!a->tokens) + if (!(a->tokens = set_new(trivial_hash_func, trivial_compare_func))) + return -ENOMEM; + + if ((r = set_put(a->tokens, UINT_TO_PTR(token))) < 0) + return r; + } + } else if (streq(key, "pipe-fd")) { + int fd; + + if (safe_atoi(value, &fd) < 0 || fd < 0 || !fdset_contains(fds, fd)) + log_debug("Failed to parse pipe-fd value %s", value); + else { + if (a->pipe_fd >= 0) + close_nointr_nofail(a->pipe_fd); + + a->pipe_fd = fdset_remove(fds, fd); + } + } else + log_debug("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) { + Automount *a = AUTOMOUNT(u); + + assert(a); + + if (!UNIT_DEREF(a->mount)) + return false; + + return UNIT_VTABLE(UNIT_DEREF(a->mount))->check_gc(UNIT_DEREF(a->mount)); +} + +static void automount_fd_event(Unit *u, int fd, uint32_t events, Watch *w) { + Automount *a = AUTOMOUNT(u); + union autofs_v5_packet_union packet; + ssize_t l; + int r; + + assert(a); + assert(fd == a->pipe_fd); + + if (events != EPOLLIN) { + log_error("Got invalid poll event on pipe."); + goto fail; + } + + if ((l = loop_read(a->pipe_fd, &packet, sizeof(packet), true)) != sizeof(packet)) { + log_error("Invalid read from pipe: %s", l < 0 ? strerror(-l) : "short read"); + goto fail; + } + + switch (packet.hdr.type) { + + case autofs_ptype_missing_direct: + + if (packet.v5_packet.pid > 0) { + char *p = NULL; + + get_process_comm(packet.v5_packet.pid, &p); + log_debug("Got direct mount request for %s, triggered by %lu (%s)", packet.v5_packet.name, (unsigned long) packet.v5_packet.pid, strna(p)); + free(p); + + } else + log_debug("Got direct mount request for %s", packet.v5_packet.name); + + if (!a->tokens) + if (!(a->tokens = set_new(trivial_hash_func, trivial_compare_func))) { + log_error("Failed to allocate token set."); + goto fail; + } + + if ((r = set_put(a->tokens, UINT_TO_PTR(packet.v5_packet.wait_queue_token))) < 0) { + log_error("Failed to remember token: %s", strerror(-r)); + goto fail; + } + + automount_enter_runnning(a); + break; + + default: + log_error("Received unknown automount request %i", packet.hdr.type); + break; + } + + return; + +fail: + automount_enter_dead(a, AUTOMOUNT_FAILURE_RESOURCES); +} + +static void automount_shutdown(Manager *m) { + assert(m); + + if (m->dev_autofs_fd >= 0) + close_nointr_nofail(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 const char* const automount_state_table[_AUTOMOUNT_STATE_MAX] = { + [AUTOMOUNT_DEAD] = "dead", + [AUTOMOUNT_WAITING] = "waiting", + [AUTOMOUNT_RUNNING] = "running", + [AUTOMOUNT_FAILED] = "failed" +}; + +DEFINE_STRING_TABLE_LOOKUP(automount_state, AutomountState); + +static const char* const automount_result_table[_AUTOMOUNT_RESULT_MAX] = { + [AUTOMOUNT_SUCCESS] = "success", + [AUTOMOUNT_FAILURE_RESOURCES] = "resources" +}; + +DEFINE_STRING_TABLE_LOOKUP(automount_result, AutomountResult); + +const UnitVTable automount_vtable = { + .suffix = ".automount", + .object_size = sizeof(Automount), + .sections = + "Unit\0" + "Automount\0" + "Install\0", + + .no_alias = true, + .no_instances = true, + + .init = automount_init, + .load = automount_load, + .done = automount_done, + + .coldplug = automount_coldplug, + + .dump = automount_dump, + + .start = automount_start, + .stop = automount_stop, + + .serialize = automount_serialize, + .deserialize_item = automount_deserialize_item, + + .active_state = automount_active_state, + .sub_state_to_string = automount_sub_state_to_string, + + .check_gc = automount_check_gc, + + .fd_event = automount_fd_event, + + .reset_failed = automount_reset_failed, + + .bus_interface = "org.freedesktop.systemd1.Automount", + .bus_message_handler = bus_automount_message_handler, + .bus_invalidating_properties = bus_automount_invalidating_properties, + + .shutdown = automount_shutdown +}; diff --git a/src/core/automount.h b/src/core/automount.h new file mode 100644 index 0000000000..19baee208b --- /dev/null +++ b/src/core/automount.h @@ -0,0 +1,76 @@ +/*-*- Mode: C; c-basic-offset: 8; indent-tabs-mode: nil -*-*/ + +#ifndef fooautomounthfoo +#define fooautomounthfoo + +/*** + This file is part of systemd. + + Copyright 2010 Lennart Poettering + + systemd is free software; you can redistribute it and/or modify it + under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + systemd is distributed in the hope that 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 General Public License + along with systemd; If not, see . +***/ + +typedef struct Automount Automount; + +#include "unit.h" + +typedef enum AutomountState { + AUTOMOUNT_DEAD, + AUTOMOUNT_WAITING, + AUTOMOUNT_RUNNING, + AUTOMOUNT_FAILED, + _AUTOMOUNT_STATE_MAX, + _AUTOMOUNT_STATE_INVALID = -1 +} AutomountState; + +typedef enum AutomountResult { + AUTOMOUNT_SUCCESS, + AUTOMOUNT_FAILURE_RESOURCES, + _AUTOMOUNT_RESULT_MAX, + _AUTOMOUNT_RESULT_INVALID = -1 +} AutomountResult; + +struct Automount { + Unit meta; + + AutomountState state, deserialized_state; + + char *where; + + UnitRef mount; + + int pipe_fd; + mode_t directory_mode; + Watch pipe_watch; + dev_t dev_id; + + Set *tokens; + + AutomountResult result; +}; + +extern const UnitVTable automount_vtable; + +int automount_send_ready(Automount *a, int status); + +int automount_add_one_mount_link(Automount *a, Mount *m); + +const char* automount_state_to_string(AutomountState i); +AutomountState automount_state_from_string(const char *s); + +const char* automount_result_to_string(AutomountResult i); +AutomountResult automount_result_from_string(const char *s); + +#endif diff --git a/src/core/build.h b/src/core/build.h new file mode 100644 index 0000000000..0619013141 --- /dev/null +++ b/src/core/build.h @@ -0,0 +1,69 @@ +/*-*- Mode: C; c-basic-offset: 8; indent-tabs-mode: nil -*-*/ + +#ifndef foobuildhfoo +#define foobuildhfoo + +/*** + This file is part of systemd. + + Copyright 2010 Lennart Poettering + + systemd is free software; you can redistribute it and/or modify it + under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + systemd is distributed in the hope that 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 General Public License + along with systemd; If not, see . +***/ + +#ifdef HAVE_PAM +#define _PAM_FEATURE_ "+PAM" +#else +#define _PAM_FEATURE_ "-PAM" +#endif + +#ifdef HAVE_LIBWRAP +#define _LIBWRAP_FEATURE_ "+LIBWRAP" +#else +#define _LIBWRAP_FEATURE_ "-LIBWRAP" +#endif + +#ifdef HAVE_AUDIT +#define _AUDIT_FEATURE_ "+AUDIT" +#else +#define _AUDIT_FEATURE_ "-AUDIT" +#endif + +#ifdef HAVE_SELINUX +#define _SELINUX_FEATURE_ "+SELINUX" +#else +#define _SELINUX_FEATURE_ "-SELINUX" +#endif + +#ifdef HAVE_IMA +#define _IMA_FEATURE_ "+IMA" +#else +#define _IMA_FEATURE_ "-IMA" +#endif + +#ifdef HAVE_SYSV_COMPAT +#define _SYSVINIT_FEATURE_ "+SYSVINIT" +#else +#define _SYSVINIT_FEATURE_ "-SYSVINIT" +#endif + +#ifdef HAVE_LIBCRYPTSETUP +#define _LIBCRYPTSETUP_FEATURE_ "+LIBCRYPTSETUP" +#else +#define _LIBCRYPTSETUP_FEATURE_ "-LIBCRYPTSETUP" +#endif + +#define SYSTEMD_FEATURES _PAM_FEATURE_ " " _LIBWRAP_FEATURE_ " " _AUDIT_FEATURE_ " " _SELINUX_FEATURE_ " " _IMA_FEATURE_ " " _SYSVINIT_FEATURE_ " " _LIBCRYPTSETUP_FEATURE_ + +#endif diff --git a/src/core/bus-errors.h b/src/core/bus-errors.h new file mode 100644 index 0000000000..82d4e99eef --- /dev/null +++ b/src/core/bus-errors.h @@ -0,0 +1,58 @@ +/*-*- Mode: C; c-basic-offset: 8; indent-tabs-mode: nil -*-*/ + +#ifndef foobuserrorshfoo +#define foobuserrorshfoo + +/*** + This file is part of systemd. + + Copyright 2010 Lennart Poettering + + systemd is free software; you can redistribute it and/or modify it + under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + systemd is distributed in the hope that 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 General Public License + along with systemd; If not, see . +***/ + +#include +#include + +#define BUS_ERROR_NO_SUCH_UNIT "org.freedesktop.systemd1.NoSuchUnit" +#define BUS_ERROR_NO_SUCH_JOB "org.freedesktop.systemd1.NoSuchJob" +#define BUS_ERROR_NOT_SUBSCRIBED "org.freedesktop.systemd1.NotSubscribed" +#define BUS_ERROR_INVALID_PATH "org.freedesktop.systemd1.InvalidPath" +#define BUS_ERROR_INVALID_NAME "org.freedesktop.systemd1.InvalidName" +#define BUS_ERROR_UNIT_TYPE_MISMATCH "org.freedesktop.systemd1.UnitTypeMismatch" +#define BUS_ERROR_UNIT_EXISTS "org.freedesktop.systemd1.UnitExists" +#define BUS_ERROR_NOT_SUPPORTED "org.freedesktop.systemd1.NotSupported" +#define BUS_ERROR_INVALID_JOB_MODE "org.freedesktop.systemd1.InvalidJobMode" +#define BUS_ERROR_ONLY_BY_DEPENDENCY "org.freedesktop.systemd1.OnlyByDependency" +#define BUS_ERROR_NO_ISOLATION "org.freedesktop.systemd1.NoIsolation" +#define BUS_ERROR_LOAD_FAILED "org.freedesktop.systemd1.LoadFailed" +#define BUS_ERROR_MASKED "org.freedesktop.systemd1.Masked" +#define BUS_ERROR_JOB_TYPE_NOT_APPLICABLE "org.freedesktop.systemd1.JobTypeNotApplicable" +#define BUS_ERROR_TRANSACTION_IS_DESTRUCTIVE "org.freedesktop.systemd1.TransactionIsDestructive" +#define BUS_ERROR_TRANSACTION_JOBS_CONFLICTING "org.freedesktop.systemd1.TransactionJobsConflicting" +#define BUS_ERROR_TRANSACTION_ORDER_IS_CYCLIC "org.freedesktop.systemd1.TransactionOrderIsCyclic" +#define BUS_ERROR_SHUTTING_DOWN "org.freedesktop.systemd1.ShuttingDown" +#define BUS_ERROR_NO_SUCH_PROCESS "org.freedesktop.systemd1.NoSuchProcess" + +static inline const char *bus_error(const DBusError *e, int r) { + if (e && e->message) + return e->message; + + if (r >= 0) + return strerror(r); + + return strerror(-r); +} + +#endif diff --git a/src/core/cgroup-attr.c b/src/core/cgroup-attr.c new file mode 100644 index 0000000000..474a6865c4 --- /dev/null +++ b/src/core/cgroup-attr.c @@ -0,0 +1,102 @@ +/*-*- Mode: C; c-basic-offset: 8; indent-tabs-mode: nil -*-*/ + +/*** + This file is part of systemd. + + Copyright 2011 Lennart Poettering + + systemd is free software; you can redistribute it and/or modify it + under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + systemd is distributed in the hope that 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 General Public License + along with systemd; If not, see . +***/ + +#include "cgroup-attr.h" +#include "cgroup-util.h" +#include "list.h" + +int cgroup_attribute_apply(CGroupAttribute *a, CGroupBonding *b) { + int r; + char *path = NULL; + char *v = NULL; + + assert(a); + + b = cgroup_bonding_find_list(b, a->controller); + if (!b) + return 0; + + if (a->map_callback) { + r = a->map_callback(a->controller, a->name, a->value, &v); + if (r < 0) + return r; + } + + r = cg_get_path(a->controller, b->path, a->name, &path); + if (r < 0) { + free(v); + return r; + } + + r = write_one_line_file(path, v ? v : a->value); + if (r < 0) + log_warning("Failed to write '%s' to %s: %s", v ? v : a->value, path, strerror(-r)); + + free(path); + free(v); + + return r; +} + +int cgroup_attribute_apply_list(CGroupAttribute *first, CGroupBonding *b) { + CGroupAttribute *a; + int r = 0; + + LIST_FOREACH(by_unit, a, first) { + int k; + + k = cgroup_attribute_apply(a, b); + if (r == 0) + r = k; + } + + return r; +} + +CGroupAttribute *cgroup_attribute_find_list(CGroupAttribute *first, const char *controller, const char *name) { + CGroupAttribute *a; + + assert(controller); + assert(name); + + LIST_FOREACH(by_unit, a, first) + if (streq(a->controller, controller) && + streq(a->name, name)) + return a; + + return NULL; +} + +static void cgroup_attribute_free(CGroupAttribute *a) { + assert(a); + + free(a->controller); + free(a->name); + free(a->value); + free(a); +} + +void cgroup_attribute_free_list(CGroupAttribute *first) { + CGroupAttribute *a, *n; + + LIST_FOREACH_SAFE(by_unit, a, n, first) + cgroup_attribute_free(a); +} diff --git a/src/core/cgroup-attr.h b/src/core/cgroup-attr.h new file mode 100644 index 0000000000..63a73b8101 --- /dev/null +++ b/src/core/cgroup-attr.h @@ -0,0 +1,49 @@ +/*-*- Mode: C; c-basic-offset: 8; indent-tabs-mode: nil -*-*/ + +#ifndef foocgroupattrhfoo +#define foocgroupattrhfoo + +/*** + This file is part of systemd. + + Copyright 2011 Lennart Poettering + + systemd is free software; you can redistribute it and/or modify it + under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + systemd is distributed in the hope that 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 General Public License + along with systemd; If not, see . +***/ + +typedef struct CGroupAttribute CGroupAttribute; + +typedef int (*CGroupAttributeMapCallback)(const char *controller, const char*name, const char *value, char **ret); + +#include "unit.h" +#include "cgroup.h" + +struct CGroupAttribute { + char *controller; + char *name; + char *value; + + CGroupAttributeMapCallback map_callback; + + LIST_FIELDS(CGroupAttribute, by_unit); +}; + +int cgroup_attribute_apply(CGroupAttribute *a, CGroupBonding *b); +int cgroup_attribute_apply_list(CGroupAttribute *first, CGroupBonding *b); + +CGroupAttribute *cgroup_attribute_find_list(CGroupAttribute *first, const char *controller, const char *name); + +void cgroup_attribute_free_list(CGroupAttribute *first); + +#endif diff --git a/src/core/cgroup.c b/src/core/cgroup.c new file mode 100644 index 0000000000..1f6139e25f --- /dev/null +++ b/src/core/cgroup.c @@ -0,0 +1,556 @@ +/*-*- Mode: C; c-basic-offset: 8; indent-tabs-mode: nil -*-*/ + +/*** + This file is part of systemd. + + Copyright 2010 Lennart Poettering + + systemd is free software; you can redistribute it and/or modify it + under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + systemd is distributed in the hope that 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 General Public License + along with systemd; If not, see . +***/ + +#include +#include +#include +#include +#include +#include +#include + +#include "cgroup.h" +#include "cgroup-util.h" +#include "log.h" + +int cgroup_bonding_realize(CGroupBonding *b) { + int r; + + assert(b); + assert(b->path); + assert(b->controller); + + r = cg_create(b->controller, b->path); + if (r < 0) { + log_warning("Failed to create cgroup %s:%s: %s", b->controller, b->path, strerror(-r)); + return r; + } + + b->realized = true; + + return 0; +} + +int cgroup_bonding_realize_list(CGroupBonding *first) { + CGroupBonding *b; + int r; + + LIST_FOREACH(by_unit, b, first) + if ((r = cgroup_bonding_realize(b)) < 0 && b->essential) + return r; + + return 0; +} + +void cgroup_bonding_free(CGroupBonding *b, bool trim) { + assert(b); + + if (b->unit) { + CGroupBonding *f; + + LIST_REMOVE(CGroupBonding, by_unit, b->unit->cgroup_bondings, b); + + if (streq(b->controller, SYSTEMD_CGROUP_CONTROLLER)) { + assert_se(f = hashmap_get(b->unit->manager->cgroup_bondings, b->path)); + LIST_REMOVE(CGroupBonding, by_path, f, b); + + if (f) + hashmap_replace(b->unit->manager->cgroup_bondings, b->path, f); + else + hashmap_remove(b->unit->manager->cgroup_bondings, b->path); + } + } + + if (b->realized && b->ours && trim) + cg_trim(b->controller, b->path, false); + + free(b->controller); + free(b->path); + free(b); +} + +void cgroup_bonding_free_list(CGroupBonding *first, bool remove_or_trim) { + CGroupBonding *b, *n; + + LIST_FOREACH_SAFE(by_unit, b, n, first) + cgroup_bonding_free(b, remove_or_trim); +} + +void cgroup_bonding_trim(CGroupBonding *b, bool delete_root) { + assert(b); + + if (b->realized && b->ours) + cg_trim(b->controller, b->path, delete_root); +} + +void cgroup_bonding_trim_list(CGroupBonding *first, bool delete_root) { + CGroupBonding *b; + + LIST_FOREACH(by_unit, b, first) + cgroup_bonding_trim(b, delete_root); +} + +int cgroup_bonding_install(CGroupBonding *b, pid_t pid) { + int r; + + assert(b); + assert(pid >= 0); + + if ((r = cg_create_and_attach(b->controller, b->path, pid)) < 0) + return r; + + b->realized = true; + return 0; +} + +int cgroup_bonding_install_list(CGroupBonding *first, pid_t pid) { + CGroupBonding *b; + int r; + + LIST_FOREACH(by_unit, b, first) + if ((r = cgroup_bonding_install(b, pid)) < 0 && b->essential) + return r; + + return 0; +} + +int cgroup_bonding_set_group_access(CGroupBonding *b, mode_t mode, uid_t uid, gid_t gid) { + assert(b); + + if (!b->realized) + return -EINVAL; + + return cg_set_group_access(b->controller, b->path, mode, uid, gid); +} + +int cgroup_bonding_set_group_access_list(CGroupBonding *first, mode_t mode, uid_t uid, gid_t gid) { + CGroupBonding *b; + int r; + + LIST_FOREACH(by_unit, b, first) { + r = cgroup_bonding_set_group_access(b, mode, uid, gid); + if (r < 0) + return r; + } + + return 0; +} + +int cgroup_bonding_set_task_access(CGroupBonding *b, mode_t mode, uid_t uid, gid_t gid, int sticky) { + assert(b); + + if (!b->realized) + return -EINVAL; + + return cg_set_task_access(b->controller, b->path, mode, uid, gid, sticky); +} + +int cgroup_bonding_set_task_access_list(CGroupBonding *first, mode_t mode, uid_t uid, gid_t gid, int sticky) { + CGroupBonding *b; + int r; + + LIST_FOREACH(by_unit, b, first) { + r = cgroup_bonding_set_task_access(b, mode, uid, gid, sticky); + if (r < 0) + return r; + } + + return 0; +} + +int cgroup_bonding_kill(CGroupBonding *b, int sig, bool sigcont, Set *s) { + assert(b); + assert(sig >= 0); + + /* Don't kill cgroups that aren't ours */ + if (!b->ours) + return 0; + + return cg_kill_recursive(b->controller, b->path, sig, sigcont, true, false, s); +} + +int cgroup_bonding_kill_list(CGroupBonding *first, int sig, bool sigcont, Set *s) { + CGroupBonding *b; + Set *allocated_set = NULL; + int ret = -EAGAIN, r; + + if (!first) + return 0; + + if (!s) + if (!(s = allocated_set = set_new(trivial_hash_func, trivial_compare_func))) + return -ENOMEM; + + LIST_FOREACH(by_unit, b, first) { + if ((r = cgroup_bonding_kill(b, sig, sigcont, s)) < 0) { + if (r == -EAGAIN || r == -ESRCH) + continue; + + ret = r; + goto finish; + } + + if (ret < 0 || r > 0) + ret = r; + } + +finish: + if (allocated_set) + set_free(allocated_set); + + return ret; +} + +/* Returns 1 if the group is empty, 0 if it is not, -EAGAIN if we + * cannot know */ +int cgroup_bonding_is_empty(CGroupBonding *b) { + int r; + + assert(b); + + if ((r = cg_is_empty_recursive(b->controller, b->path, true)) < 0) + return r; + + /* If it is empty it is empty */ + if (r > 0) + return 1; + + /* It's not only us using this cgroup, so we just don't know */ + return b->ours ? 0 : -EAGAIN; +} + +int cgroup_bonding_is_empty_list(CGroupBonding *first) { + CGroupBonding *b; + + LIST_FOREACH(by_unit, b, first) { + int r; + + if ((r = cgroup_bonding_is_empty(b)) < 0) { + /* If this returned -EAGAIN, then we don't know if the + * group is empty, so let's see if another group can + * tell us */ + + if (r != -EAGAIN) + return r; + } else + return r; + } + + return -EAGAIN; +} + +int manager_setup_cgroup(Manager *m) { + char *current = NULL, *path = NULL; + int r; + char suffix[32]; + + assert(m); + + /* 0. Be nice to Ingo Molnar #628004 */ + if (path_is_mount_point("/sys/fs/cgroup/systemd", false) <= 0) { + log_warning("No control group support available, not creating root group."); + return 0; + } + + /* 1. Determine hierarchy */ + if ((r = cg_get_by_pid(SYSTEMD_CGROUP_CONTROLLER, 0, ¤t)) < 0) { + log_error("Cannot determine cgroup we are running in: %s", strerror(-r)); + goto finish; + } + + if (m->running_as == MANAGER_SYSTEM) + strcpy(suffix, "/system"); + else { + snprintf(suffix, sizeof(suffix), "/systemd-%lu", (unsigned long) getpid()); + char_array_0(suffix); + } + + free(m->cgroup_hierarchy); + if (endswith(current, suffix)) { + /* We probably got reexecuted and can continue to use our root cgroup */ + m->cgroup_hierarchy = current; + current = NULL; + + } else { + /* We need a new root cgroup */ + m->cgroup_hierarchy = NULL; + if (asprintf(&m->cgroup_hierarchy, "%s%s", streq(current, "/") ? "" : current, suffix) < 0) { + log_error("Out of memory"); + r = -ENOMEM; + goto finish; + } + } + + /* 2. Show data */ + if ((r = cg_get_path(SYSTEMD_CGROUP_CONTROLLER, m->cgroup_hierarchy, NULL, &path)) < 0) { + log_error("Cannot find cgroup mount point: %s", strerror(-r)); + goto finish; + } + + log_debug("Using cgroup controller " SYSTEMD_CGROUP_CONTROLLER ". File system hierarchy is at %s.", path); + + /* 3. Install agent */ + if ((r = cg_install_release_agent(SYSTEMD_CGROUP_CONTROLLER, SYSTEMD_CGROUP_AGENT_PATH)) < 0) + log_warning("Failed to install release agent, ignoring: %s", strerror(-r)); + else if (r > 0) + log_debug("Installed release agent."); + else + log_debug("Release agent already installed."); + + /* 4. Realize the group */ + if ((r = cg_create_and_attach(SYSTEMD_CGROUP_CONTROLLER, m->cgroup_hierarchy, 0)) < 0) { + log_error("Failed to create root cgroup hierarchy: %s", strerror(-r)); + goto finish; + } + + /* 5. And pin it, so that it cannot be unmounted */ + if (m->pin_cgroupfs_fd >= 0) + close_nointr_nofail(m->pin_cgroupfs_fd); + + if ((m->pin_cgroupfs_fd = open(path, O_RDONLY|O_CLOEXEC|O_DIRECTORY|O_NOCTTY|O_NONBLOCK)) < 0) { + log_error("Failed to open pin file: %m"); + r = -errno; + goto finish; + } + + log_debug("Created root group."); + +finish: + free(current); + free(path); + + return r; +} + +void manager_shutdown_cgroup(Manager *m, bool delete) { + assert(m); + + if (delete && m->cgroup_hierarchy) + cg_delete(SYSTEMD_CGROUP_CONTROLLER, m->cgroup_hierarchy); + + if (m->pin_cgroupfs_fd >= 0) { + close_nointr_nofail(m->pin_cgroupfs_fd); + m->pin_cgroupfs_fd = -1; + } + + free(m->cgroup_hierarchy); + m->cgroup_hierarchy = NULL; +} + +int cgroup_bonding_get(Manager *m, const char *cgroup, CGroupBonding **bonding) { + CGroupBonding *b; + char *p; + + assert(m); + assert(cgroup); + assert(bonding); + + b = hashmap_get(m->cgroup_bondings, cgroup); + if (b) { + *bonding = b; + return 1; + } + + p = strdup(cgroup); + if (!p) + return -ENOMEM; + + for (;;) { + char *e; + + e = strrchr(p, '/'); + if (!e || e == p) { + free(p); + *bonding = NULL; + return 0; + } + + *e = 0; + + b = hashmap_get(m->cgroup_bondings, p); + if (b) { + free(p); + *bonding = b; + return 1; + } + } +} + +int cgroup_notify_empty(Manager *m, const char *group) { + CGroupBonding *l, *b; + int r; + + assert(m); + assert(group); + + r = cgroup_bonding_get(m, group, &l); + if (r <= 0) + return r; + + LIST_FOREACH(by_path, b, l) { + int t; + + if (!b->unit) + continue; + + t = cgroup_bonding_is_empty_list(b); + if (t < 0) { + + /* If we don't know, we don't know */ + if (t != -EAGAIN) + log_warning("Failed to check whether cgroup is empty: %s", strerror(errno)); + + continue; + } + + if (t > 0) { + /* If it is empty, let's delete it */ + cgroup_bonding_trim_list(b->unit->cgroup_bondings, true); + + if (UNIT_VTABLE(b->unit)->cgroup_notify_empty) + UNIT_VTABLE(b->unit)->cgroup_notify_empty(b->unit); + } + } + + return 0; +} + +Unit* cgroup_unit_by_pid(Manager *m, pid_t pid) { + CGroupBonding *l, *b; + char *group = NULL; + + assert(m); + + if (pid <= 1) + return NULL; + + if (cg_get_by_pid(SYSTEMD_CGROUP_CONTROLLER, pid, &group) < 0) + return NULL; + + l = hashmap_get(m->cgroup_bondings, group); + + if (!l) { + char *slash; + + while ((slash = strrchr(group, '/'))) { + if (slash == group) + break; + + *slash = 0; + + if ((l = hashmap_get(m->cgroup_bondings, group))) + break; + } + } + + free(group); + + LIST_FOREACH(by_path, b, l) { + + if (!b->unit) + continue; + + if (b->ours) + return b->unit; + } + + return NULL; +} + +CGroupBonding *cgroup_bonding_find_list(CGroupBonding *first, const char *controller) { + CGroupBonding *b; + + assert(controller); + + LIST_FOREACH(by_unit, b, first) + if (streq(b->controller, controller)) + return b; + + return NULL; +} + +char *cgroup_bonding_to_string(CGroupBonding *b) { + char *r; + + assert(b); + + if (asprintf(&r, "%s:%s", b->controller, b->path) < 0) + return NULL; + + return r; +} + +pid_t cgroup_bonding_search_main_pid(CGroupBonding *b) { + FILE *f; + pid_t pid = 0, npid, mypid; + + assert(b); + + if (!b->ours) + return 0; + + if (cg_enumerate_processes(b->controller, b->path, &f) < 0) + return 0; + + 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_parent_of_pid(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. */ + pid = 0; + break; + } + + pid = npid; + } + + fclose(f); + + return pid; +} + +pid_t cgroup_bonding_search_main_pid_list(CGroupBonding *first) { + CGroupBonding *b; + pid_t pid; + + /* Try to find a main pid from this cgroup, but checking if + * there's only one PID in the cgroup and returning it. Later + * on we might want to add additional, smarter heuristics + * here. */ + + LIST_FOREACH(by_unit, b, first) + if ((pid = cgroup_bonding_search_main_pid(b)) != 0) + return pid; + + return 0; + +} diff --git a/src/core/cgroup.h b/src/core/cgroup.h new file mode 100644 index 0000000000..5faa7dc0f7 --- /dev/null +++ b/src/core/cgroup.h @@ -0,0 +1,94 @@ +/*-*- Mode: C; c-basic-offset: 8; indent-tabs-mode: nil -*-*/ + +#ifndef foocgrouphfoo +#define foocgrouphfoo + +/*** + This file is part of systemd. + + Copyright 2010 Lennart Poettering + + systemd is free software; you can redistribute it and/or modify it + under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + systemd is distributed in the hope that 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 General Public License + along with systemd; If not, see . +***/ + +typedef struct CGroupBonding CGroupBonding; + +#include "unit.h" + +/* Binds a cgroup to a name */ +struct CGroupBonding { + char *controller; + char *path; + + Unit *unit; + + /* For the Unit::cgroup_bondings list */ + LIST_FIELDS(CGroupBonding, by_unit); + + /* For the Manager::cgroup_bondings hashmap */ + LIST_FIELDS(CGroupBonding, by_path); + + /* When shutting down, remove cgroup? Are our own tasks the + * only ones in this group?*/ + bool ours:1; + + /* If we cannot create this group, or add a process to it, is this fatal? */ + bool essential:1; + + /* This cgroup is realized */ + bool realized:1; +}; + +int cgroup_bonding_realize(CGroupBonding *b); +int cgroup_bonding_realize_list(CGroupBonding *first); + +void cgroup_bonding_free(CGroupBonding *b, bool trim); +void cgroup_bonding_free_list(CGroupBonding *first, bool trim); + +int cgroup_bonding_install(CGroupBonding *b, pid_t pid); +int cgroup_bonding_install_list(CGroupBonding *first, pid_t pid); + +int cgroup_bonding_set_group_access(CGroupBonding *b, mode_t mode, uid_t uid, gid_t gid); +int cgroup_bonding_set_group_access_list(CGroupBonding *b, mode_t mode, uid_t uid, gid_t gid); + +int cgroup_bonding_set_task_access(CGroupBonding *b, mode_t mode, uid_t uid, gid_t gid, int sticky); +int cgroup_bonding_set_task_access_list(CGroupBonding *b, mode_t mode, uid_t uid, gid_t gid, int sticky); + +int cgroup_bonding_kill(CGroupBonding *b, int sig, bool sigcont, Set *s); +int cgroup_bonding_kill_list(CGroupBonding *first, int sig, bool sigcont, Set *s); + +void cgroup_bonding_trim(CGroupBonding *first, bool delete_root); +void cgroup_bonding_trim_list(CGroupBonding *first, bool delete_root); + +int cgroup_bonding_is_empty(CGroupBonding *b); +int cgroup_bonding_is_empty_list(CGroupBonding *first); + +CGroupBonding *cgroup_bonding_find_list(CGroupBonding *first, const char *controller); + +char *cgroup_bonding_to_string(CGroupBonding *b); + +pid_t cgroup_bonding_search_main_pid(CGroupBonding *b); +pid_t cgroup_bonding_search_main_pid_list(CGroupBonding *b); + +#include "manager.h" + +int manager_setup_cgroup(Manager *m); +void manager_shutdown_cgroup(Manager *m, bool delete); + +int cgroup_bonding_get(Manager *m, const char *cgroup, CGroupBonding **bonding); +int cgroup_notify_empty(Manager *m, const char *group); + +Unit* cgroup_unit_by_pid(Manager *m, pid_t pid); + +#endif diff --git a/src/core/condition.c b/src/core/condition.c new file mode 100644 index 0000000000..2b51a16f17 --- /dev/null +++ b/src/core/condition.c @@ -0,0 +1,323 @@ +/*-*- Mode: C; c-basic-offset: 8; indent-tabs-mode: nil -*-*/ + +/*** + This file is part of systemd. + + Copyright 2010 Lennart Poettering + + systemd is free software; you can redistribute it and/or modify it + under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + systemd is distributed in the hope that 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 General Public License + along with systemd; If not, see . +***/ + +#include +#include +#include +#include +#include + +#ifdef HAVE_SELINUX +#include +#endif + +#include "util.h" +#include "condition.h" +#include "virt.h" + +Condition* condition_new(ConditionType type, const char *parameter, bool trigger, bool negate) { + Condition *c; + + assert(type < _CONDITION_TYPE_MAX); + + c = new0(Condition, 1); + if (!c) + return NULL; + + c->type = type; + c->trigger = trigger; + c->negate = negate; + + if (parameter) { + c->parameter = strdup(parameter); + if (!c->parameter) { + free(c); + return NULL; + } + } + + return c; +} + +void condition_free(Condition *c) { + assert(c); + + free(c->parameter); + free(c); +} + +void condition_free_list(Condition *first) { + Condition *c, *n; + + LIST_FOREACH_SAFE(conditions, c, n, first) + condition_free(c); +} + +static bool test_kernel_command_line(const char *parameter) { + char *line, *w, *state, *word = NULL; + bool equal; + int r; + size_t l, pl; + bool found = false; + + assert(parameter); + + if (detect_container(NULL) > 0) + return false; + + r = read_one_line_file("/proc/cmdline", &line); + if (r < 0) { + log_warning("Failed to read /proc/cmdline, ignoring: %s", strerror(-r)); + return false; + } + + equal = !!strchr(parameter, '='); + pl = strlen(parameter); + + FOREACH_WORD_QUOTED(w, l, line, state) { + + free(word); + word = strndup(w, l); + if (!word) + break; + + if (equal) { + if (streq(word, parameter)) { + found = true; + break; + } + } else { + if (startswith(word, parameter) && (word[pl] == '=' || word[pl] == 0)) { + found = true; + break; + } + } + + } + + free(word); + free(line); + + return found; +} + +static bool test_virtualization(const char *parameter) { + int b; + Virtualization v; + const char *id; + + assert(parameter); + + v = detect_virtualization(&id); + if (v < 0) { + log_warning("Failed to detect virtualization, ignoring: %s", strerror(-v)); + return false; + } + + /* First, compare with yes/no */ + b = parse_boolean(parameter); + + if (v > 0 && b > 0) + return true; + + if (v == 0 && b == 0) + return true; + + /* Then, compare categorization */ + if (v == VIRTUALIZATION_VM && streq(parameter, "vm")) + return true; + + if (v == VIRTUALIZATION_CONTAINER && streq(parameter, "container")) + return true; + + /* Finally compare id */ + return v > 0 && streq(parameter, id); +} + +static bool test_security(const char *parameter) { +#ifdef HAVE_SELINUX + if (streq(parameter, "selinux")) + return is_selinux_enabled() > 0; +#endif + return false; +} + +static bool test_capability(const char *parameter) { + cap_value_t value; + FILE *f; + char line[LINE_MAX]; + unsigned long long capabilities = (unsigned long long) -1; + + /* If it's an invalid capability, we don't have it */ + + if (cap_from_name(parameter, &value) < 0) + return false; + + /* If it's a valid capability we default to assume + * that we have it */ + + f = fopen("/proc/self/status", "re"); + if (!f) + return true; + + while (fgets(line, sizeof(line), f)) { + truncate_nl(line); + + if (startswith(line, "CapBnd:")) { + (void) sscanf(line+7, "%llx", &capabilities); + break; + } + } + + fclose(f); + + return !!(capabilities & (1ULL << value)); +} + +bool condition_test(Condition *c) { + assert(c); + + switch(c->type) { + + case CONDITION_PATH_EXISTS: + return (access(c->parameter, F_OK) >= 0) == !c->negate; + + case CONDITION_PATH_EXISTS_GLOB: + return (glob_exists(c->parameter) > 0) == !c->negate; + + case CONDITION_PATH_IS_DIRECTORY: { + struct stat st; + + if (stat(c->parameter, &st) < 0) + return c->negate; + return S_ISDIR(st.st_mode) == !c->negate; + } + + case CONDITION_PATH_IS_SYMBOLIC_LINK: { + struct stat st; + + if (lstat(c->parameter, &st) < 0) + return c->negate; + return S_ISLNK(st.st_mode) == !c->negate; + } + + case CONDITION_PATH_IS_MOUNT_POINT: + return (path_is_mount_point(c->parameter, true) > 0) == !c->negate; + + case CONDITION_DIRECTORY_NOT_EMPTY: { + int k; + + k = dir_is_empty(c->parameter); + return !(k == -ENOENT || k > 0) == !c->negate; + } + + case CONDITION_FILE_IS_EXECUTABLE: { + struct stat st; + + if (stat(c->parameter, &st) < 0) + return c->negate; + + return (S_ISREG(st.st_mode) && (st.st_mode & 0111)) == !c->negate; + } + + case CONDITION_KERNEL_COMMAND_LINE: + return test_kernel_command_line(c->parameter) == !c->negate; + + case CONDITION_VIRTUALIZATION: + return test_virtualization(c->parameter) == !c->negate; + + case CONDITION_SECURITY: + return test_security(c->parameter) == !c->negate; + + case CONDITION_CAPABILITY: + return test_capability(c->parameter) == !c->negate; + + case CONDITION_NULL: + return !c->negate; + + default: + assert_not_reached("Invalid condition type."); + } +} + +bool condition_test_list(Condition *first) { + Condition *c; + int triggered = -1; + + /* 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) { + bool b; + + b = condition_test(c); + + if (!c->trigger && !b) + return false; + + if (c->trigger && triggered <= 0) + triggered = b; + } + + return triggered != 0; +} + +void condition_dump(Condition *c, FILE *f, const char *prefix) { + assert(c); + assert(f); + + if (!prefix) + prefix = ""; + + fprintf(f, + "%s\t%s: %s%s%s\n", + prefix, + condition_type_to_string(c->type), + c->trigger ? "|" : "", + c->negate ? "!" : "", + c->parameter); +} + +void condition_dump_list(Condition *first, FILE *f, const char *prefix) { + Condition *c; + + LIST_FOREACH(conditions, c, first) + condition_dump(c, f, prefix); +} + +static const char* const condition_type_table[_CONDITION_TYPE_MAX] = { + [CONDITION_PATH_EXISTS] = "ConditionPathExists", + [CONDITION_PATH_EXISTS_GLOB] = "ConditionPathExistsGlob", + [CONDITION_PATH_IS_DIRECTORY] = "ConditionPathIsDirectory", + [CONDITION_PATH_IS_SYMBOLIC_LINK] = "ConditionPathIsSymbolicLink", + [CONDITION_PATH_IS_MOUNT_POINT] = "ConditionPathIsMountPoint", + [CONDITION_DIRECTORY_NOT_EMPTY] = "ConditionDirectoryNotEmpty", + [CONDITION_KERNEL_COMMAND_LINE] = "ConditionKernelCommandLine", + [CONDITION_VIRTUALIZATION] = "ConditionVirtualization", + [CONDITION_SECURITY] = "ConditionSecurity", + [CONDITION_NULL] = "ConditionNull" +}; + +DEFINE_STRING_TABLE_LOOKUP(condition_type, ConditionType); diff --git a/src/core/condition.h b/src/core/condition.h new file mode 100644 index 0000000000..71b1c6761e --- /dev/null +++ b/src/core/condition.h @@ -0,0 +1,69 @@ +/*-*- Mode: C; c-basic-offset: 8; indent-tabs-mode: nil -*-*/ + +#ifndef fooconditionhfoo +#define fooconditionhfoo + +/*** + This file is part of systemd. + + Copyright 2010 Lennart Poettering + + systemd is free software; you can redistribute it and/or modify it + under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + systemd is distributed in the hope that 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 General Public License + along with systemd; If not, see . +***/ + +#include + +#include "list.h" + +typedef enum ConditionType { + CONDITION_PATH_EXISTS, + CONDITION_PATH_EXISTS_GLOB, + CONDITION_PATH_IS_DIRECTORY, + CONDITION_PATH_IS_SYMBOLIC_LINK, + CONDITION_PATH_IS_MOUNT_POINT, + CONDITION_DIRECTORY_NOT_EMPTY, + CONDITION_FILE_IS_EXECUTABLE, + CONDITION_KERNEL_COMMAND_LINE, + CONDITION_VIRTUALIZATION, + CONDITION_SECURITY, + CONDITION_CAPABILITY, + CONDITION_NULL, + _CONDITION_TYPE_MAX, + _CONDITION_TYPE_INVALID = -1 +} ConditionType; + +typedef struct Condition { + ConditionType type; + char *parameter; + + bool trigger:1; + bool negate:1; + + LIST_FIELDS(struct Condition, conditions); +} Condition; + +Condition* condition_new(ConditionType type, const char *parameter, bool trigger, bool negate); +void condition_free(Condition *c); +void condition_free_list(Condition *c); + +bool condition_test(Condition *c); +bool condition_test_list(Condition *c); + +void condition_dump(Condition *c, FILE *f, const char *prefix); +void condition_dump_list(Condition *c, FILE *f, const char *prefix); + +const char* condition_type_to_string(ConditionType t); +int condition_type_from_string(const char *s); + +#endif diff --git a/src/core/dbus-automount.c b/src/core/dbus-automount.c new file mode 100644 index 0000000000..8e45f81fcf --- /dev/null +++ b/src/core/dbus-automount.c @@ -0,0 +1,72 @@ +/*-*- Mode: C; c-basic-offset: 8; indent-tabs-mode: nil -*-*/ + +/*** + This file is part of systemd. + + Copyright 2010 Lennart Poettering + + systemd is free software; you can redistribute it and/or modify it + under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + systemd is distributed in the hope that 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 General Public License + along with systemd; If not, see . +***/ + +#include + +#include "dbus-unit.h" +#include "dbus-automount.h" +#include "dbus-common.h" + +#define BUS_AUTOMOUNT_INTERFACE \ + " \n" \ + " \n" \ + " \n" \ + " \n" \ + " \n" + +#define INTROSPECTION \ + DBUS_INTROSPECT_1_0_XML_DOCTYPE_DECL_NODE \ + "\n" \ + BUS_UNIT_INTERFACE \ + BUS_AUTOMOUNT_INTERFACE \ + BUS_PROPERTIES_INTERFACE \ + BUS_PEER_INTERFACE \ + BUS_INTROSPECTABLE_INTERFACE \ + "\n" + +#define INTERFACES_LIST \ + BUS_UNIT_INTERFACES_LIST \ + "org.freedesktop.systemd1.Automount\0" + +const char bus_automount_interface[] _introspect_("Automount") = BUS_AUTOMOUNT_INTERFACE; + +const char bus_automount_invalidating_properties[] = + "Result\0"; + +static DEFINE_BUS_PROPERTY_APPEND_ENUM(bus_automount_append_automount_result, automount_result, AutomountResult); + +static const BusProperty bus_automount_properties[] = { + { "Where", bus_property_append_string, "s", offsetof(Automount, where), true }, + { "DirectoryMode", bus_property_append_mode, "u", offsetof(Automount, directory_mode) }, + { "Result", bus_automount_append_automount_result, "s", offsetof(Automount, result) }, + { NULL, } +}; + +DBusHandlerResult bus_automount_message_handler(Unit *u, DBusConnection *c, DBusMessage *message) { + Automount *am = AUTOMOUNT(u); + const BusBoundProperties bps[] = { + { "org.freedesktop.systemd1.Unit", bus_unit_properties, u }, + { "org.freedesktop.systemd1.Automount", bus_automount_properties, am }, + { NULL, } + }; + + return bus_default_message_handler(c, message, INTROSPECTION, INTERFACES_LIST, bps); +} diff --git a/src/core/dbus-automount.h b/src/core/dbus-automount.h new file mode 100644 index 0000000000..2fc8345048 --- /dev/null +++ b/src/core/dbus-automount.h @@ -0,0 +1,34 @@ +/*-*- Mode: C; c-basic-offset: 8; indent-tabs-mode: nil -*-*/ + +#ifndef foodbusautomounthfoo +#define foodbusautomounthfoo + +/*** + This file is part of systemd. + + Copyright 2010 Lennart Poettering + + systemd is free software; you can redistribute it and/or modify it + under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + systemd is distributed in the hope that 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 General Public License + along with systemd; If not, see . +***/ + +#include + +#include "unit.h" + +DBusHandlerResult bus_automount_message_handler(Unit *u, DBusConnection *c, DBusMessage *message); + +extern const char bus_automount_interface[]; +extern const char bus_automount_invalidating_properties[]; + +#endif diff --git a/src/core/dbus-device.c b/src/core/dbus-device.c new file mode 100644 index 0000000000..b39fb9d381 --- /dev/null +++ b/src/core/dbus-device.c @@ -0,0 +1,65 @@ +/*-*- Mode: C; c-basic-offset: 8; indent-tabs-mode: nil -*-*/ + +/*** + This file is part of systemd. + + Copyright 2010 Lennart Poettering + + systemd is free software; you can redistribute it and/or modify it + under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + systemd is distributed in the hope that 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 General Public License + along with systemd; If not, see . +***/ + +#include "dbus-unit.h" +#include "dbus-device.h" +#include "dbus-common.h" + +#define BUS_DEVICE_INTERFACE \ + " \n" \ + " \n" \ + " \n" + +#define INTROSPECTION \ + DBUS_INTROSPECT_1_0_XML_DOCTYPE_DECL_NODE \ + "\n" \ + BUS_UNIT_INTERFACE \ + BUS_DEVICE_INTERFACE \ + BUS_PROPERTIES_INTERFACE \ + BUS_PEER_INTERFACE \ + BUS_INTROSPECTABLE_INTERFACE \ + "\n" + +#define INTERFACES_LIST \ + BUS_UNIT_INTERFACES_LIST \ + "org.freedesktop.systemd1.Device\0" + +const char bus_device_interface[] _introspect_("Device") = BUS_DEVICE_INTERFACE; + +const char bus_device_invalidating_properties[] = + "SysFSPath\0"; + +static const BusProperty bus_device_properties[] = { + { "SysFSPath", bus_property_append_string, "s", offsetof(Device, sysfs), true }, + { NULL, } +}; + + +DBusHandlerResult bus_device_message_handler(Unit *u, DBusConnection *c, DBusMessage *message) { + Device *d = DEVICE(u); + const BusBoundProperties bps[] = { + { "org.freedesktop.systemd1.Unit", bus_unit_properties, u }, + { "org.freedesktop.systemd1.Device", bus_device_properties, d }, + { NULL, } + }; + + return bus_default_message_handler(c, message, INTROSPECTION, INTERFACES_LIST, bps); +} diff --git a/src/core/dbus-device.h b/src/core/dbus-device.h new file mode 100644 index 0000000000..fba270b4e6 --- /dev/null +++ b/src/core/dbus-device.h @@ -0,0 +1,34 @@ +/*-*- Mode: C; c-basic-offset: 8; indent-tabs-mode: nil -*-*/ + +#ifndef foodbusdevicehfoo +#define foodbusdevicehfoo + +/*** + This file is part of systemd. + + Copyright 2010 Lennart Poettering + + systemd is free software; you can redistribute it and/or modify it + under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + systemd is distributed in the hope that 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 General Public License + along with systemd; If not, see . +***/ + +#include + +#include "unit.h" + +DBusHandlerResult bus_device_message_handler(Unit *u, DBusConnection *c, DBusMessage *message); + +extern const char bus_device_interface[]; +extern const char bus_device_invalidating_properties[]; + +#endif diff --git a/src/core/dbus-execute.c b/src/core/dbus-execute.c new file mode 100644 index 0000000000..1fd2b21336 --- /dev/null +++ b/src/core/dbus-execute.c @@ -0,0 +1,422 @@ +/*-*- Mode: C; c-basic-offset: 8; indent-tabs-mode: nil -*-*/ + +/*** + This file is part of systemd. + + Copyright 2010 Lennart Poettering + + systemd is free software; you can redistribute it and/or modify it + under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + systemd is distributed in the hope that 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 General Public License + along with systemd; If not, see . +***/ + +#include +#include +#include + +#include "dbus-execute.h" +#include "missing.h" +#include "ioprio.h" +#include "strv.h" +#include "dbus-common.h" + +DEFINE_BUS_PROPERTY_APPEND_ENUM(bus_execute_append_kill_mode, kill_mode, KillMode); + +DEFINE_BUS_PROPERTY_APPEND_ENUM(bus_execute_append_input, exec_input, ExecInput); +DEFINE_BUS_PROPERTY_APPEND_ENUM(bus_execute_append_output, exec_output, ExecOutput); + +int bus_execute_append_env_files(DBusMessageIter *i, const char *property, void *data) { + char **env_files = data, **j; + DBusMessageIter sub, sub2; + + assert(i); + assert(property); + + if (!dbus_message_iter_open_container(i, DBUS_TYPE_ARRAY, "(sb)", &sub)) + return -ENOMEM; + + STRV_FOREACH(j, env_files) { + dbus_bool_t b = false; + char *fn = *j; + + if (fn[0] == '-') { + b = true; + fn++; + } + + if (!dbus_message_iter_open_container(&sub, DBUS_TYPE_STRUCT, NULL, &sub2) || + !dbus_message_iter_append_basic(&sub2, DBUS_TYPE_STRING, &fn) || + !dbus_message_iter_append_basic(&sub2, DBUS_TYPE_BOOLEAN, &b) || + !dbus_message_iter_close_container(&sub, &sub2)) + return -ENOMEM; + } + + if (!dbus_message_iter_close_container(i, &sub)) + return -ENOMEM; + + return 0; +} + +int bus_execute_append_oom_score_adjust(DBusMessageIter *i, const char *property, void *data) { + ExecContext *c = data; + int32_t n; + + assert(i); + assert(property); + assert(c); + + if (c->oom_score_adjust_set) + n = c->oom_score_adjust; + else { + char *t; + + n = 0; + if (read_one_line_file("/proc/self/oom_score_adj", &t) >= 0) { + safe_atoi(t, &n); + free(t); + } else if (read_one_line_file("/proc/self/oom_adj", &t) >= 0) { + safe_atoi(t, &n); + free(t); + + if (n == OOM_ADJUST_MAX) + n = OOM_SCORE_ADJ_MAX; + else + n = (n * OOM_SCORE_ADJ_MAX) / -OOM_DISABLE; + } + } + + if (!dbus_message_iter_append_basic(i, DBUS_TYPE_INT32, &n)) + return -ENOMEM; + + return 0; +} + +int bus_execute_append_nice(DBusMessageIter *i, const char *property, void *data) { + ExecContext *c = data; + int32_t n; + + assert(i); + assert(property); + assert(c); + + if (c->nice_set) + n = c->nice; + else + n = getpriority(PRIO_PROCESS, 0); + + if (!dbus_message_iter_append_basic(i, DBUS_TYPE_INT32, &n)) + return -ENOMEM; + + return 0; +} + +int bus_execute_append_ioprio(DBusMessageIter *i, const char *property, void *data) { + ExecContext *c = data; + int32_t n; + + assert(i); + assert(property); + assert(c); + + if (c->ioprio_set) + n = c->ioprio; + else + n = ioprio_get(IOPRIO_WHO_PROCESS, 0); + + if (!dbus_message_iter_append_basic(i, DBUS_TYPE_INT32, &n)) + return -ENOMEM; + + return 0; +} + +int bus_execute_append_cpu_sched_policy(DBusMessageIter *i, const char *property, void *data) { + ExecContext *c = data; + int32_t n; + + assert(i); + assert(property); + assert(c); + + if (c->cpu_sched_set) + n = c->cpu_sched_policy; + else + n = sched_getscheduler(0); + + if (!dbus_message_iter_append_basic(i, DBUS_TYPE_INT32, &n)) + return -ENOMEM; + + return 0; +} + +int bus_execute_append_cpu_sched_priority(DBusMessageIter *i, const char *property, void *data) { + ExecContext *c = data; + int32_t n; + + assert(i); + assert(property); + assert(c); + + if (c->cpu_sched_set) + n = c->cpu_sched_priority; + else { + struct sched_param p; + n = 0; + + zero(p); + if (sched_getparam(0, &p) >= 0) + n = p.sched_priority; + } + + if (!dbus_message_iter_append_basic(i, DBUS_TYPE_INT32, &n)) + return -ENOMEM; + + return 0; +} + +int bus_execute_append_affinity(DBusMessageIter *i, const char *property, void *data) { + ExecContext *c = data; + dbus_bool_t b; + DBusMessageIter sub; + + assert(i); + assert(property); + assert(c); + + if (!dbus_message_iter_open_container(i, DBUS_TYPE_ARRAY, "y", &sub)) + return -ENOMEM; + + if (c->cpuset) + b = dbus_message_iter_append_fixed_array(&sub, DBUS_TYPE_BYTE, &c->cpuset, CPU_ALLOC_SIZE(c->cpuset_ncpus)); + else + b = dbus_message_iter_append_fixed_array(&sub, DBUS_TYPE_BYTE, &c->cpuset, 0); + + if (!b) + return -ENOMEM; + + if (!dbus_message_iter_close_container(i, &sub)) + return -ENOMEM; + + return 0; +} + +int bus_execute_append_timer_slack_nsec(DBusMessageIter *i, const char *property, void *data) { + ExecContext *c = data; + uint64_t u; + + assert(i); + assert(property); + assert(c); + + if (c->timer_slack_nsec_set) + u = (uint64_t) c->timer_slack_nsec; + else + u = (uint64_t) prctl(PR_GET_TIMERSLACK); + + if (!dbus_message_iter_append_basic(i, DBUS_TYPE_UINT64, &u)) + return -ENOMEM; + + return 0; +} + +int bus_execute_append_capability_bs(DBusMessageIter *i, const char *property, void *data) { + ExecContext *c = data; + uint64_t normal, inverted; + + assert(i); + assert(property); + assert(c); + + /* We store this negated internally, to match the kernel, but + * we expose it normalized. */ + + normal = *(uint64_t*) data; + inverted = ~normal; + + return bus_property_append_uint64(i, property, &inverted); +} + +int bus_execute_append_capabilities(DBusMessageIter *i, const char *property, void *data) { + ExecContext *c = data; + char *t = NULL; + const char *s; + dbus_bool_t b; + + assert(i); + assert(property); + assert(c); + + if (c->capabilities) + s = t = cap_to_text(c->capabilities, NULL); + else + s = ""; + + if (!s) + return -ENOMEM; + + b = dbus_message_iter_append_basic(i, DBUS_TYPE_STRING, &s); + + if (t) + cap_free(t); + + if (!b) + return -ENOMEM; + + return 0; +} + +int bus_execute_append_rlimits(DBusMessageIter *i, const char *property, void *data) { + ExecContext *c = data; + int r; + uint64_t u; + + assert(i); + assert(property); + assert(c); + + assert_se((r = rlimit_from_string(property)) >= 0); + + if (c->rlimit[r]) + u = (uint64_t) c->rlimit[r]->rlim_max; + else { + struct rlimit rl; + + zero(rl); + getrlimit(r, &rl); + + u = (uint64_t) rl.rlim_max; + } + + if (!dbus_message_iter_append_basic(i, DBUS_TYPE_UINT64, &u)) + return -ENOMEM; + + return 0; +} + +int bus_execute_append_command(DBusMessageIter *i, const char *property, void *data) { + ExecCommand *c = data; + DBusMessageIter sub, sub2, sub3; + + assert(i); + assert(property); + + if (!dbus_message_iter_open_container(i, DBUS_TYPE_ARRAY, "(sasbttttuii)", &sub)) + return -ENOMEM; + + LIST_FOREACH(command, c, c) { + char **l; + uint32_t pid; + int32_t code, status; + dbus_bool_t b; + + if (!c->path) + continue; + + if (!dbus_message_iter_open_container(&sub, DBUS_TYPE_STRUCT, NULL, &sub2) || + !dbus_message_iter_append_basic(&sub2, DBUS_TYPE_STRING, &c->path) || + !dbus_message_iter_open_container(&sub2, DBUS_TYPE_ARRAY, "s", &sub3)) + return -ENOMEM; + + STRV_FOREACH(l, c->argv) + if (!dbus_message_iter_append_basic(&sub3, DBUS_TYPE_STRING, l)) + return -ENOMEM; + + pid = (uint32_t) c->exec_status.pid; + code = (int32_t) c->exec_status.code; + status = (int32_t) c->exec_status.status; + + b = !!c->ignore; + + if (!dbus_message_iter_close_container(&sub2, &sub3) || + !dbus_message_iter_append_basic(&sub2, DBUS_TYPE_BOOLEAN, &b) || + !dbus_message_iter_append_basic(&sub2, DBUS_TYPE_UINT64, &c->exec_status.start_timestamp.realtime) || + !dbus_message_iter_append_basic(&sub2, DBUS_TYPE_UINT64, &c->exec_status.start_timestamp.monotonic) || + !dbus_message_iter_append_basic(&sub2, DBUS_TYPE_UINT64, &c->exec_status.exit_timestamp.realtime) || + !dbus_message_iter_append_basic(&sub2, DBUS_TYPE_UINT64, &c->exec_status.exit_timestamp.monotonic) || + !dbus_message_iter_append_basic(&sub2, DBUS_TYPE_UINT32, &pid) || + !dbus_message_iter_append_basic(&sub2, DBUS_TYPE_INT32, &code) || + !dbus_message_iter_append_basic(&sub2, DBUS_TYPE_INT32, &status)) + return -ENOMEM; + + if (!dbus_message_iter_close_container(&sub, &sub2)) + return -ENOMEM; + } + + if (!dbus_message_iter_close_container(i, &sub)) + return -ENOMEM; + + return 0; +} + +const BusProperty bus_exec_context_properties[] = { + { "Environment", bus_property_append_strv, "as", offsetof(ExecContext, environment), true }, + { "EnvironmentFiles", bus_execute_append_env_files, "a(sb)", offsetof(ExecContext, environment_files), true }, + { "UMask", bus_property_append_mode, "u", offsetof(ExecContext, umask) }, + { "LimitCPU", bus_execute_append_rlimits, "t", 0 }, + { "LimitFSIZE", bus_execute_append_rlimits, "t", 0 }, + { "LimitDATA", bus_execute_append_rlimits, "t", 0 }, + { "LimitSTACK", bus_execute_append_rlimits, "t", 0 }, + { "LimitCORE", bus_execute_append_rlimits, "t", 0 }, + { "LimitRSS", bus_execute_append_rlimits, "t", 0 }, + { "LimitNOFILE", bus_execute_append_rlimits, "t", 0 }, + { "LimitAS", bus_execute_append_rlimits, "t", 0 }, + { "LimitNPROC", bus_execute_append_rlimits, "t", 0 }, + { "LimitMEMLOCK", bus_execute_append_rlimits, "t", 0 }, + { "LimitLOCKS", bus_execute_append_rlimits, "t", 0 }, + { "LimitSIGPENDING", bus_execute_append_rlimits, "t", 0 }, + { "LimitMSGQUEUE", bus_execute_append_rlimits, "t", 0 }, + { "LimitNICE", bus_execute_append_rlimits, "t", 0 }, + { "LimitRTPRIO", bus_execute_append_rlimits, "t", 0 }, + { "LimitRTTIME", bus_execute_append_rlimits, "t", 0 }, + { "WorkingDirectory", bus_property_append_string, "s", offsetof(ExecContext, working_directory), true }, + { "RootDirectory", bus_property_append_string, "s", offsetof(ExecContext, root_directory), true }, + { "OOMScoreAdjust", bus_execute_append_oom_score_adjust, "i", 0 }, + { "Nice", bus_execute_append_nice, "i", 0 }, + { "IOScheduling", bus_execute_append_ioprio, "i", 0 }, + { "CPUSchedulingPolicy", bus_execute_append_cpu_sched_policy, "i", 0 }, + { "CPUSchedulingPriority", bus_execute_append_cpu_sched_priority, "i", 0 }, + { "CPUAffinity", bus_execute_append_affinity, "ay", 0 }, + { "TimerSlackNSec", bus_execute_append_timer_slack_nsec, "t", 0 }, + { "CPUSchedulingResetOnFork", bus_property_append_bool, "b", offsetof(ExecContext, cpu_sched_reset_on_fork) }, + { "NonBlocking", bus_property_append_bool, "b", offsetof(ExecContext, non_blocking) }, + { "StandardInput", bus_execute_append_input, "s", offsetof(ExecContext, std_input) }, + { "StandardOutput", bus_execute_append_output, "s", offsetof(ExecContext, std_output) }, + { "StandardError", bus_execute_append_output, "s", offsetof(ExecContext, std_error) }, + { "TTYPath", bus_property_append_string, "s", offsetof(ExecContext, tty_path), true }, + { "TTYReset", bus_property_append_bool, "b", offsetof(ExecContext, tty_reset) }, + { "TTYVHangup", bus_property_append_bool, "b", offsetof(ExecContext, tty_vhangup) }, + { "TTYVTDisallocate", bus_property_append_bool, "b", offsetof(ExecContext, tty_vt_disallocate) }, + { "SyslogPriority", bus_property_append_int, "i", offsetof(ExecContext, syslog_priority) }, + { "SyslogIdentifier", bus_property_append_string, "s", offsetof(ExecContext, syslog_identifier), true }, + { "SyslogLevelPrefix", bus_property_append_bool, "b", offsetof(ExecContext, syslog_level_prefix) }, + { "Capabilities", bus_execute_append_capabilities, "s", 0 }, + { "SecureBits", bus_property_append_int, "i", offsetof(ExecContext, secure_bits) }, + { "CapabilityBoundingSet", bus_execute_append_capability_bs, "t", offsetof(ExecContext, capability_bounding_set_drop) }, + { "User", bus_property_append_string, "s", offsetof(ExecContext, user), true }, + { "Group", bus_property_append_string, "s", offsetof(ExecContext, group), true }, + { "SupplementaryGroups", bus_property_append_strv, "as", offsetof(ExecContext, supplementary_groups), true }, + { "TCPWrapName", bus_property_append_string, "s", offsetof(ExecContext, tcpwrap_name), true }, + { "PAMName", bus_property_append_string, "s", offsetof(ExecContext, pam_name), true }, + { "ReadWriteDirectories", bus_property_append_strv, "as", offsetof(ExecContext, read_write_dirs), true }, + { "ReadOnlyDirectories", bus_property_append_strv, "as", offsetof(ExecContext, read_only_dirs), true }, + { "InaccessibleDirectories", bus_property_append_strv, "as", offsetof(ExecContext, inaccessible_dirs), true }, + { "MountFlags", bus_property_append_ul, "t", offsetof(ExecContext, mount_flags) }, + { "PrivateTmp", bus_property_append_bool, "b", offsetof(ExecContext, private_tmp) }, + { "PrivateNetwork", bus_property_append_bool, "b", offsetof(ExecContext, private_network) }, + { "SameProcessGroup", bus_property_append_bool, "b", offsetof(ExecContext, same_pgrp) }, + { "KillMode", bus_execute_append_kill_mode, "s", offsetof(ExecContext, kill_mode) }, + { "KillSignal", bus_property_append_int, "i", offsetof(ExecContext, kill_signal) }, + { "UtmpIdentifier", bus_property_append_string, "s", offsetof(ExecContext, utmp_id), true }, + { "ControlGroupModify", bus_property_append_bool, "b", offsetof(ExecContext, control_group_modify) }, + { "ControlGroupPersistent", bus_property_append_tristate_false, "b", offsetof(ExecContext, control_group_persistent) }, + { "IgnoreSIGPIPE", bus_property_append_bool, "b", offsetof(ExecContext, ignore_sigpipe ) }, + { NULL, } +}; diff --git a/src/core/dbus-execute.h b/src/core/dbus-execute.h new file mode 100644 index 0000000000..03cd69d126 --- /dev/null +++ b/src/core/dbus-execute.h @@ -0,0 +1,125 @@ +/*-*- Mode: C; c-basic-offset: 8; indent-tabs-mode: nil -*-*/ + +#ifndef foodbusexecutehfoo +#define foodbusexecutehfoo + +/*** + This file is part of systemd. + + Copyright 2010 Lennart Poettering + + systemd is free software; you can redistribute it and/or modify it + under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + systemd is distributed in the hope that 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 General Public License + along with systemd; If not, see . +***/ + +#include + +#include "manager.h" +#include "dbus-common.h" + +#define BUS_EXEC_STATUS_INTERFACE(prefix) \ + " \n" \ + " \n" \ + " \n" \ + " \n" \ + " \n" \ + " \n" \ + " \n" + +#define BUS_EXEC_CONTEXT_INTERFACE \ + " \n" \ + " \n" \ + " \n" \ + " \n" \ + " \n" \ + " \n" \ + " \n" \ + " \n" \ + " \n" \ + " \n" \ + " \n" \ + " \n" \ + " \n" \ + " \n" \ + " \n" \ + " \n" \ + " \n" \ + " \n" \ + " \n" \ + " \n" \ + " \n" \ + " \n" \ + " \n" \ + " \n" \ + " \n" \ + " \n" \ + " \n" \ + " \n" \ + " \n" \ + " \n" \ + " \n" \ + " \n" \ + " \n" \ + " \n" \ + " \n" \ + " \n" \ + " \n" \ + " \n" \ + " \n" \ + " \n" \ + " \n" \ + " \n" \ + " \n" \ + " \n" \ + " \n" \ + " \n" \ + " \n" \ + " \n" \ + " \n" \ + " \n" \ + " \n" \ + " \n" \ + " \n" \ + " \n" \ + " \n" \ + " \n" \ + " \n" \ + " \n" \ + " \n" \ + " \n" + +#define BUS_EXEC_COMMAND_INTERFACE(name) \ + " \n" + +extern const BusProperty bus_exec_context_properties[]; + +#define BUS_EXEC_COMMAND_PROPERTY(name, command, indirect) \ + { name, bus_execute_append_command, "a(sasbttttuii)", (command), (indirect), NULL } + +int bus_execute_append_output(DBusMessageIter *i, const char *property, void *data); +int bus_execute_append_input(DBusMessageIter *i, const char *property, void *data); +int bus_execute_append_oom_score_adjust(DBusMessageIter *i, const char *property, void *data); +int bus_execute_append_nice(DBusMessageIter *i, const char *property, void *data); +int bus_execute_append_ioprio(DBusMessageIter *i, const char *property, void *data); +int bus_execute_append_cpu_sched_policy(DBusMessageIter *i, const char *property, void *data); +int bus_execute_append_cpu_sched_priority(DBusMessageIter *i, const char *property, void *data); +int bus_execute_append_affinity(DBusMessageIter *i, const char *property, void *data); +int bus_execute_append_timer_slack_nsec(DBusMessageIter *i, const char *property, void *data); +int bus_execute_append_capabilities(DBusMessageIter *i, const char *property, void *data); +int bus_execute_append_capability_bs(DBusMessageIter *i, const char *property, void *data); +int bus_execute_append_rlimits(DBusMessageIter *i, const char *property, void *data); +int bus_execute_append_command(DBusMessageIter *u, const char *property, void *data); +int bus_execute_append_kill_mode(DBusMessageIter *i, const char *property, void *data); +int bus_execute_append_env_files(DBusMessageIter *i, const char *property, void *data); + +#endif diff --git a/src/core/dbus-job.c b/src/core/dbus-job.c new file mode 100644 index 0000000000..ab6d610243 --- /dev/null +++ b/src/core/dbus-job.c @@ -0,0 +1,354 @@ +/*-*- Mode: C; c-basic-offset: 8; indent-tabs-mode: nil -*-*/ + +/*** + This file is part of systemd. + + Copyright 2010 Lennart Poettering + + systemd is free software; you can redistribute it and/or modify it + under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + systemd is distributed in the hope that 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 General Public License + along with systemd; If not, see . +***/ + +#include + +#include "dbus.h" +#include "log.h" +#include "dbus-job.h" +#include "dbus-common.h" + +#define BUS_JOB_INTERFACE \ + " \n" \ + " \n" \ + " \n" \ + " \n" \ + " \n" \ + " \n" \ + " \n" + +#define INTROSPECTION \ + DBUS_INTROSPECT_1_0_XML_DOCTYPE_DECL_NODE \ + "\n" \ + BUS_JOB_INTERFACE \ + BUS_PROPERTIES_INTERFACE \ + BUS_PEER_INTERFACE \ + BUS_INTROSPECTABLE_INTERFACE \ + "\n" + +const char bus_job_interface[] _introspect_("Job") = BUS_JOB_INTERFACE; + +#define INTERFACES_LIST \ + BUS_GENERIC_INTERFACES_LIST \ + "org.freedesktop.systemd1.Job\0" + +#define INVALIDATING_PROPERTIES \ + "State\0" + +static DEFINE_BUS_PROPERTY_APPEND_ENUM(bus_job_append_state, job_state, JobState); +static DEFINE_BUS_PROPERTY_APPEND_ENUM(bus_job_append_type, job_type, JobType); + +static int bus_job_append_unit(DBusMessageIter *i, const char *property, void *data) { + Job *j = data; + DBusMessageIter sub; + char *p; + + assert(i); + assert(property); + assert(j); + + if (!dbus_message_iter_open_container(i, DBUS_TYPE_STRUCT, NULL, &sub)) + return -ENOMEM; + + if (!(p = unit_dbus_path(j->unit))) + return -ENOMEM; + + if (!dbus_message_iter_append_basic(&sub, DBUS_TYPE_STRING, &j->unit->id) || + !dbus_message_iter_append_basic(&sub, DBUS_TYPE_OBJECT_PATH, &p)) { + free(p); + return -ENOMEM; + } + + free(p); + + if (!dbus_message_iter_close_container(i, &sub)) + return -ENOMEM; + + return 0; +} + +static const BusProperty bus_job_properties[] = { + { "Id", bus_property_append_uint32, "u", offsetof(Job, id) }, + { "State", bus_job_append_state, "s", offsetof(Job, state) }, + { "JobType", bus_job_append_type, "s", offsetof(Job, type) }, + { "Unit", bus_job_append_unit, "(so)", 0 }, + { NULL, } +}; + +static DBusHandlerResult bus_job_message_dispatch(Job *j, DBusConnection *connection, DBusMessage *message) { + DBusMessage *reply = NULL; + + if (dbus_message_is_method_call(message, "org.freedesktop.systemd1.Job", "Cancel")) { + if (!(reply = dbus_message_new_method_return(message))) + goto oom; + + job_finish_and_invalidate(j, JOB_CANCELED); + + } else { + const BusBoundProperties bps[] = { + { "org.freedesktop.systemd1.Job", bus_job_properties, j }, + { NULL, } + }; + return bus_default_message_handler(connection, message, INTROSPECTION, INTERFACES_LIST, bps); + } + + if (reply) { + if (!dbus_connection_send(connection, reply, NULL)) + goto oom; + + dbus_message_unref(reply); + } + + return DBUS_HANDLER_RESULT_HANDLED; + +oom: + if (reply) + dbus_message_unref(reply); + + return DBUS_HANDLER_RESULT_NEED_MEMORY; +} + +static DBusHandlerResult bus_job_message_handler(DBusConnection *connection, DBusMessage *message, void *data) { + Manager *m = data; + Job *j; + int r; + DBusMessage *reply; + + assert(connection); + assert(message); + assert(m); + + if (streq(dbus_message_get_path(message), "/org/freedesktop/systemd1/job")) { + /* Be nice to gdbus and return introspection data for our mid-level paths */ + + if (dbus_message_is_method_call(message, "org.freedesktop.DBus.Introspectable", "Introspect")) { + char *introspection = NULL; + FILE *f; + Iterator i; + size_t size; + + if (!(reply = dbus_message_new_method_return(message))) + goto oom; + + /* We roll our own introspection code here, instead of + * relying on bus_default_message_handler() because we + * need to generate our introspection string + * dynamically. */ + + if (!(f = open_memstream(&introspection, &size))) + goto oom; + + fputs(DBUS_INTROSPECT_1_0_XML_DOCTYPE_DECL_NODE + "\n", f); + + fputs(BUS_INTROSPECTABLE_INTERFACE, f); + fputs(BUS_PEER_INTERFACE, f); + + HASHMAP_FOREACH(j, m->jobs, i) + fprintf(f, "", (unsigned long) j->id); + + fputs("\n", f); + + if (ferror(f)) { + fclose(f); + free(introspection); + goto oom; + } + + fclose(f); + + if (!introspection) + goto oom; + + if (!dbus_message_append_args(reply, DBUS_TYPE_STRING, &introspection, DBUS_TYPE_INVALID)) { + free(introspection); + goto oom; + } + + free(introspection); + + if (!dbus_connection_send(connection, reply, NULL)) + goto oom; + + dbus_message_unref(reply); + + return DBUS_HANDLER_RESULT_HANDLED; + } + + return DBUS_HANDLER_RESULT_NOT_YET_HANDLED; + } + + if ((r = manager_get_job_from_dbus_path(m, dbus_message_get_path(message), &j)) < 0) { + + if (r == -ENOMEM) + return DBUS_HANDLER_RESULT_NEED_MEMORY; + + if (r == -ENOENT) { + DBusError e; + + dbus_error_init(&e); + dbus_set_error_const(&e, DBUS_ERROR_UNKNOWN_OBJECT, "Unknown job"); + return bus_send_error_reply(connection, message, &e, r); + } + + return bus_send_error_reply(connection, message, NULL, r); + } + + return bus_job_message_dispatch(j, connection, message); + +oom: + if (reply) + dbus_message_unref(reply); + + return DBUS_HANDLER_RESULT_NEED_MEMORY; +} + +const DBusObjectPathVTable bus_job_vtable = { + .message_function = bus_job_message_handler +}; + +static int job_send_message(Job *j, DBusMessage *m) { + int r; + + assert(j); + assert(m); + + if (bus_has_subscriber(j->manager)) { + if ((r = bus_broadcast(j->manager, m)) < 0) + return r; + + } else if (j->bus_client) { + /* If nobody is subscribed, we just send the message + * to the client which created the job */ + + assert(j->bus); + + if (!dbus_message_set_destination(m, j->bus_client)) + return -ENOMEM; + + if (!dbus_connection_send(j->bus, m, NULL)) + return -ENOMEM; + } + + return 0; +} + +void bus_job_send_change_signal(Job *j) { + char *p = NULL; + DBusMessage *m = NULL; + + assert(j); + + if (j->in_dbus_queue) { + LIST_REMOVE(Job, dbus_queue, j->manager->dbus_job_queue, j); + j->in_dbus_queue = false; + } + + if (!bus_has_subscriber(j->manager) && !j->bus_client) { + j->sent_dbus_new_signal = true; + return; + } + + if (!(p = job_dbus_path(j))) + goto oom; + + if (j->sent_dbus_new_signal) { + /* Send a properties changed signal */ + + if (!(m = bus_properties_changed_new(p, "org.freedesktop.systemd1.Job", INVALIDATING_PROPERTIES))) + goto oom; + + } else { + /* Send a new signal */ + + if (!(m = dbus_message_new_signal("/org/freedesktop/systemd1", "org.freedesktop.systemd1.Manager", "JobNew"))) + goto oom; + + if (!dbus_message_append_args(m, + DBUS_TYPE_UINT32, &j->id, + DBUS_TYPE_OBJECT_PATH, &p, + DBUS_TYPE_INVALID)) + goto oom; + } + + if (job_send_message(j, m) < 0) + goto oom; + + free(p); + dbus_message_unref(m); + + j->sent_dbus_new_signal = true; + + return; + +oom: + free(p); + + if (m) + dbus_message_unref(m); + + log_error("Failed to allocate job change signal."); +} + +void bus_job_send_removed_signal(Job *j) { + char *p = NULL; + DBusMessage *m = NULL; + const char *r; + + assert(j); + + if (!bus_has_subscriber(j->manager) && !j->bus_client) + return; + + if (!j->sent_dbus_new_signal) + bus_job_send_change_signal(j); + + if (!(p = job_dbus_path(j))) + goto oom; + + if (!(m = dbus_message_new_signal("/org/freedesktop/systemd1", "org.freedesktop.systemd1.Manager", "JobRemoved"))) + goto oom; + + r = job_result_to_string(j->result); + + if (!dbus_message_append_args(m, + DBUS_TYPE_UINT32, &j->id, + DBUS_TYPE_OBJECT_PATH, &p, + DBUS_TYPE_STRING, &r, + DBUS_TYPE_INVALID)) + goto oom; + + if (job_send_message(j, m) < 0) + goto oom; + + free(p); + dbus_message_unref(m); + + return; + +oom: + free(p); + + if (m) + dbus_message_unref(m); + + log_error("Failed to allocate job remove signal."); +} diff --git a/src/core/dbus-job.h b/src/core/dbus-job.h new file mode 100644 index 0000000000..103c2ff3ec --- /dev/null +++ b/src/core/dbus-job.h @@ -0,0 +1,36 @@ +/*-*- Mode: C; c-basic-offset: 8; indent-tabs-mode: nil -*-*/ + +#ifndef foodbusjobhfoo +#define foodbusjobhfoo + +/*** + This file is part of systemd. + + Copyright 2010 Lennart Poettering + + systemd is free software; you can redistribute it and/or modify it + under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + systemd is distributed in the hope that 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 General Public License + along with systemd; If not, see . +***/ + +#include + +#include "job.h" + +void bus_job_send_change_signal(Job *j); +void bus_job_send_removed_signal(Job *j); + +extern const DBusObjectPathVTable bus_job_vtable; + +extern const char bus_job_interface[]; + +#endif diff --git a/src/core/dbus-loop.h b/src/core/dbus-loop.h new file mode 100644 index 0000000000..0bbdfe5925 --- /dev/null +++ b/src/core/dbus-loop.h @@ -0,0 +1,30 @@ +/*-*- Mode: C; c-basic-offset: 8; indent-tabs-mode: nil -*-*/ + +#ifndef foodbusloophfoo +#define foodbusloophfoo + +/*** + This file is part of systemd. + + Copyright 2011 Lennart Poettering + + systemd is free software; you can redistribute it and/or modify it + under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + systemd is distributed in the hope that 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 General Public License + along with systemd; If not, see . +***/ + +#include + +int bus_loop_open(DBusConnection *c); +int bus_loop_dispatch(int fd); + +#endif diff --git a/src/core/dbus-manager.c b/src/core/dbus-manager.c new file mode 100644 index 0000000000..3bf0c07b88 --- /dev/null +++ b/src/core/dbus-manager.c @@ -0,0 +1,1544 @@ +/*-*- Mode: C; c-basic-offset: 8; indent-tabs-mode: nil -*-*/ + +/*** + This file is part of systemd. + + Copyright 2010 Lennart Poettering + + systemd is free software; you can redistribute it and/or modify it + under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + systemd is distributed in the hope that 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 General Public License + along with systemd; If not, see . +***/ + +#include +#include + +#include "dbus.h" +#include "log.h" +#include "dbus-manager.h" +#include "strv.h" +#include "bus-errors.h" +#include "build.h" +#include "dbus-common.h" +#include "install.h" + +#define BUS_MANAGER_INTERFACE_BEGIN \ + " \n" + +#define BUS_MANAGER_INTERFACE_METHODS \ + " \n" \ + " \n" \ + " \n" \ + " \n" \ + " \n" \ + " \n" \ + " \n" \ + " \n" \ + " \n" \ + " \n" \ + " \n" \ + " \n" \ + " \n" \ + " \n" \ + " \n" \ + " \n" \ + " \n" \ + " \n" \ + " \n" \ + " \n" \ + " \n" \ + " \n" \ + " \n" \ + " \n" \ + " \n" \ + " \n" \ + " \n" \ + " \n" \ + " \n" \ + " \n" \ + " \n" \ + " \n" \ + " \n" \ + " \n" \ + " \n" \ + " \n" \ + " \n" \ + " \n" \ + " \n" \ + " \n" \ + " \n" \ + " \n" \ + " \n" \ + " \n" \ + " \n" \ + " \n" \ + " \n" \ + " \n" \ + " \n" \ + " \n" \ + " \n" \ + " \n" \ + " \n" \ + " \n" \ + " \n" \ + " \n" \ + " \n" \ + " \n" \ + " \n" \ + " \n" \ + " \n" \ + " \n" \ + " \n" \ + " \n" \ + " \n" \ + " \n" \ + " \n" \ + " \n" \ + " \n" \ + " \n" \ + " \n" \ + " \n" \ + " \n" \ + " \n" \ + " \n" \ + " \n" \ + " \n" \ + " \n" \ + " \n" \ + " \n" \ + " \n" \ + " \n" \ + " \n" \ + " \n" \ + " \n" \ + " \n" \ + " \n" \ + " \n" \ + " \n" \ + " \n" \ + " \n" \ + " \n" \ + " \n" \ + " \n" \ + " \n" \ + " \n" \ + " \n" \ + " \n" \ + " \n" \ + " \n" \ + " \n" \ + " \n" \ + " \n" \ + " \n" \ + " \n" \ + " \n" \ + " \n" \ + " \n" \ + " \n" \ + " \n" \ + " \n" \ + " \n" \ + " \n" \ + " \n" \ + " \n" \ + " \n" \ + " \n" \ + " \n" \ + " \n" \ + " \n" \ + " \n" \ + " \n" \ + " \n" \ + " \n" \ + " \n" \ + " \n" \ + " \n" \ + " \n" \ + " \n" \ + " \n" \ + " \n" \ + " \n" \ + " \n" \ + " \n" \ + " \n" \ + " \n" \ + " \n" \ + " \n" \ + " \n" \ + " \n" \ + " \n" \ + " \n" \ + " \n" \ + " \n" \ + " \n" \ + " \n" \ + " \n" \ + " \n" \ + " \n" + +#define BUS_MANAGER_INTERFACE_SIGNALS \ + " \n" \ + " \n" \ + " \n" \ + " \n" \ + " \n" \ + " \n" \ + " \n" \ + " \n" \ + " \n" \ + " \n" \ + " \n" \ + " \n" \ + " \n" \ + " \n" \ + " \n" \ + " \n" \ + " " \ + " \n" \ + " \n" \ + " \n" \ + " \n" \ + " \n" \ + " " \ + " \n" + +#define BUS_MANAGER_INTERFACE_PROPERTIES_GENERAL \ + " \n" \ + " \n" \ + " \n" \ + " \n" \ + " \n" \ + " \n" \ + " \n" \ + " \n" \ + " \n" \ + " \n" \ + " \n" \ + " \n" \ + " \n" \ + " \n" \ + " \n" \ + " \n" \ + " \n" \ + " \n" \ + " \n" \ + " \n" \ + " \n" \ + " \n" \ + " \n" \ + " \n" \ + " \n" \ + " \n" \ + " \n" \ + " \n" \ + " \n" \ + " \n" \ + " \n" + +#ifdef HAVE_SYSV_COMPAT +#define BUS_MANAGER_INTERFACE_PROPERTIES_SYSV \ + " \n" \ + " \n" \ + " \n" +#else +#define BUS_MANAGER_INTERFACE_PROPERTIES_SYSV +#endif + +#define BUS_MANAGER_INTERFACE_END \ + " \n" + +#define BUS_MANAGER_INTERFACE \ + BUS_MANAGER_INTERFACE_BEGIN \ + BUS_MANAGER_INTERFACE_METHODS \ + BUS_MANAGER_INTERFACE_SIGNALS \ + BUS_MANAGER_INTERFACE_PROPERTIES_GENERAL \ + BUS_MANAGER_INTERFACE_PROPERTIES_SYSV \ + BUS_MANAGER_INTERFACE_END + +#define INTROSPECTION_BEGIN \ + DBUS_INTROSPECT_1_0_XML_DOCTYPE_DECL_NODE \ + "\n" \ + BUS_MANAGER_INTERFACE \ + BUS_PROPERTIES_INTERFACE \ + BUS_PEER_INTERFACE \ + BUS_INTROSPECTABLE_INTERFACE + +#define INTROSPECTION_END \ + "\n" + +#define INTERFACES_LIST \ + BUS_GENERIC_INTERFACES_LIST \ + "org.freedesktop.systemd1.Manager\0" + +const char bus_manager_interface[] _introspect_("Manager") = BUS_MANAGER_INTERFACE; + +static DEFINE_BUS_PROPERTY_APPEND_ENUM(bus_manager_append_running_as, manager_running_as, ManagerRunningAs); +static DEFINE_BUS_PROPERTY_APPEND_ENUM(bus_manager_append_exec_output, exec_output, ExecOutput); + +static int bus_manager_append_tainted(DBusMessageIter *i, const char *property, void *data) { + const char *t; + Manager *m = data; + char buf[LINE_MAX] = "", *e = buf, *p = NULL; + + assert(i); + assert(property); + assert(m); + + if (m->taint_usr) + e = stpcpy(e, "usr-separate-fs "); + + if (readlink_malloc("/etc/mtab", &p) < 0) + e = stpcpy(e, "etc-mtab-not-symlink "); + else + free(p); + + if (access("/proc/cgroups", F_OK) < 0) + stpcpy(e, "cgroups-missing "); + + t = strstrip(buf); + + if (!dbus_message_iter_append_basic(i, DBUS_TYPE_STRING, &t)) + return -ENOMEM; + + return 0; +} + +static int bus_manager_append_log_target(DBusMessageIter *i, const char *property, void *data) { + const char *t; + + assert(i); + assert(property); + + t = log_target_to_string(log_get_target()); + + if (!dbus_message_iter_append_basic(i, DBUS_TYPE_STRING, &t)) + return -ENOMEM; + + return 0; +} + +static int bus_manager_set_log_target(DBusMessageIter *i, const char *property, void *data) { + const char *t; + + assert(i); + assert(property); + + dbus_message_iter_get_basic(i, &t); + + return log_set_target_from_string(t); +} + +static int bus_manager_append_log_level(DBusMessageIter *i, const char *property, void *data) { + const char *t; + + assert(i); + assert(property); + + t = log_level_to_string(log_get_max_level()); + + if (!dbus_message_iter_append_basic(i, DBUS_TYPE_STRING, &t)) + return -ENOMEM; + + return 0; +} + +static int bus_manager_set_log_level(DBusMessageIter *i, const char *property, void *data) { + const char *t; + + assert(i); + assert(property); + + dbus_message_iter_get_basic(i, &t); + + return log_set_max_level_from_string(t); +} + +static int bus_manager_append_n_names(DBusMessageIter *i, const char *property, void *data) { + Manager *m = data; + uint32_t u; + + assert(i); + assert(property); + assert(m); + + u = hashmap_size(m->units); + + if (!dbus_message_iter_append_basic(i, DBUS_TYPE_UINT32, &u)) + return -ENOMEM; + + return 0; +} + +static int bus_manager_append_n_jobs(DBusMessageIter *i, const char *property, void *data) { + Manager *m = data; + uint32_t u; + + assert(i); + assert(property); + assert(m); + + u = hashmap_size(m->jobs); + + if (!dbus_message_iter_append_basic(i, DBUS_TYPE_UINT32, &u)) + return -ENOMEM; + + return 0; +} + +static int bus_manager_append_progress(DBusMessageIter *i, const char *property, void *data) { + double d; + Manager *m = data; + + assert(i); + assert(property); + 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); + + if (!dbus_message_iter_append_basic(i, DBUS_TYPE_DOUBLE, &d)) + return -ENOMEM; + + return 0; +} + +static const char *message_get_sender_with_fallback(DBusMessage *m) { + const char *s; + + assert(m); + + if ((s = dbus_message_get_sender(m))) + return s; + + /* When the message came in from a direct connection the + * message will have no sender. We fix that here. */ + + return ":no-sender"; +} + +static DBusMessage *message_from_file_changes( + DBusMessage *m, + UnitFileChange *changes, + unsigned n_changes, + int carries_install_info) { + + DBusMessageIter iter, sub, sub2; + DBusMessage *reply; + unsigned i; + + reply = dbus_message_new_method_return(m); + if (!reply) + return NULL; + + dbus_message_iter_init_append(reply, &iter); + + if (carries_install_info >= 0) { + dbus_bool_t b; + + b = !!carries_install_info; + if (!dbus_message_iter_append_basic(&iter, DBUS_TYPE_BOOLEAN, &b)) + goto oom; + } + + if (!dbus_message_iter_open_container(&iter, DBUS_TYPE_ARRAY, "(sss)", &sub)) + goto oom; + + for (i = 0; i < n_changes; i++) { + const char *type, *path, *source; + + type = unit_file_change_type_to_string(changes[i].type); + path = strempty(changes[i].path); + source = strempty(changes[i].source); + + if (!dbus_message_iter_open_container(&sub, DBUS_TYPE_STRUCT, NULL, &sub2) || + !dbus_message_iter_append_basic(&sub2, DBUS_TYPE_STRING, &type) || + !dbus_message_iter_append_basic(&sub2, DBUS_TYPE_STRING, &path) || + !dbus_message_iter_append_basic(&sub2, DBUS_TYPE_STRING, &source) || + !dbus_message_iter_close_container(&sub, &sub2)) + goto oom; + } + + if (!dbus_message_iter_close_container(&iter, &sub)) + goto oom; + + return reply; + +oom: + dbus_message_unref(reply); + return NULL; +} + +static int bus_manager_send_unit_files_changed(Manager *m) { + DBusMessage *s; + int r; + + s = dbus_message_new_signal("/org/freedesktop/systemd1", "org.freedesktop.systemd1.Manager", "UnitFilesChanged"); + if (!s) + return -ENOMEM; + + r = bus_broadcast(m, s); + dbus_message_unref(s); + + return r; +} + +static const char systemd_property_string[] = + PACKAGE_STRING "\0" + DISTRIBUTION "\0" + SYSTEMD_FEATURES; + +static const BusProperty bus_systemd_properties[] = { + { "Version", bus_property_append_string, "s", 0 }, + { "Distribution", bus_property_append_string, "s", sizeof(PACKAGE_STRING) }, + { "Features", bus_property_append_string, "s", sizeof(PACKAGE_STRING) + sizeof(DISTRIBUTION) }, + { NULL, } +}; + +static const BusProperty bus_manager_properties[] = { + { "RunningAs", bus_manager_append_running_as, "s", offsetof(Manager, running_as) }, + { "Tainted", bus_manager_append_tainted, "s", 0 }, + { "InitRDTimestamp", bus_property_append_uint64, "t", offsetof(Manager, initrd_timestamp.realtime) }, + { "InitRDTimestampMonotonic", bus_property_append_uint64, "t", offsetof(Manager, initrd_timestamp.monotonic) }, + { "StartupTimestamp", bus_property_append_uint64, "t", offsetof(Manager, startup_timestamp.realtime) }, + { "StartupTimestampMonotonic", bus_property_append_uint64, "t", offsetof(Manager, startup_timestamp.monotonic) }, + { "FinishTimestamp", bus_property_append_uint64, "t", offsetof(Manager, finish_timestamp.realtime) }, + { "FinishTimestampMonotonic", bus_property_append_uint64, "t", offsetof(Manager, finish_timestamp.monotonic) }, + { "LogLevel", bus_manager_append_log_level, "s", 0, 0, bus_manager_set_log_level }, + { "LogTarget", bus_manager_append_log_target, "s", 0, 0, bus_manager_set_log_target }, + { "NNames", bus_manager_append_n_names, "u", 0 }, + { "NJobs", bus_manager_append_n_jobs, "u", 0 }, + { "NInstalledJobs",bus_property_append_uint32, "u", offsetof(Manager, n_installed_jobs) }, + { "NFailedJobs", bus_property_append_uint32, "u", offsetof(Manager, n_failed_jobs) }, + { "Progress", bus_manager_append_progress, "d", 0 }, + { "Environment", bus_property_append_strv, "as", offsetof(Manager, environment), true }, + { "ConfirmSpawn", bus_property_append_bool, "b", offsetof(Manager, confirm_spawn) }, + { "ShowStatus", bus_property_append_bool, "b", offsetof(Manager, show_status) }, + { "UnitPath", bus_property_append_strv, "as", offsetof(Manager, lookup_paths.unit_path), true }, + { "NotifySocket", bus_property_append_string, "s", offsetof(Manager, notify_socket), true }, + { "ControlGroupHierarchy", bus_property_append_string, "s", offsetof(Manager, cgroup_hierarchy), true }, + { "MountAuto", bus_property_append_bool, "b", offsetof(Manager, mount_auto) }, + { "SwapAuto", bus_property_append_bool, "b", offsetof(Manager, swap_auto) }, + { "DefaultControllers", bus_property_append_strv, "as", offsetof(Manager, default_controllers), true }, + { "DefaultStandardOutput", bus_manager_append_exec_output, "s", offsetof(Manager, default_std_output) }, + { "DefaultStandardError", bus_manager_append_exec_output, "s", offsetof(Manager, default_std_error) }, + { "RuntimeWatchdogUSec", bus_property_append_usec, "t", offsetof(Manager, runtime_watchdog), }, + { "ShutdownWatchdogUSec", bus_property_append_usec, "t", offsetof(Manager, shutdown_watchdog), }, +#ifdef HAVE_SYSV_COMPAT + { "SysVConsole", bus_property_append_bool, "b", offsetof(Manager, sysv_console) }, + { "SysVInitPath", bus_property_append_strv, "as", offsetof(Manager, lookup_paths.sysvinit_path), true }, + { "SysVRcndPath", bus_property_append_strv, "as", offsetof(Manager, lookup_paths.sysvrcnd_path), true }, +#endif + { NULL, } +}; + +static DBusHandlerResult bus_manager_message_handler(DBusConnection *connection, DBusMessage *message, void *data) { + Manager *m = data; + + int r; + DBusError error; + DBusMessage *reply = NULL; + char * path = NULL; + JobType job_type = _JOB_TYPE_INVALID; + bool reload_if_possible = false; + const char *member; + + assert(connection); + assert(message); + assert(m); + + dbus_error_init(&error); + + member = dbus_message_get_member(message); + + if (dbus_message_is_method_call(message, "org.freedesktop.systemd1.Manager", "GetUnit")) { + const char *name; + Unit *u; + + if (!dbus_message_get_args( + message, + &error, + DBUS_TYPE_STRING, &name, + DBUS_TYPE_INVALID)) + return bus_send_error_reply(connection, message, &error, -EINVAL); + + if (!(u = manager_get_unit(m, name))) { + dbus_set_error(&error, BUS_ERROR_NO_SUCH_UNIT, "Unit %s is not loaded.", name); + return bus_send_error_reply(connection, message, &error, -ENOENT); + } + + if (!(reply = dbus_message_new_method_return(message))) + goto oom; + + if (!(path = unit_dbus_path(u))) + goto oom; + + if (!dbus_message_append_args( + reply, + DBUS_TYPE_OBJECT_PATH, &path, + DBUS_TYPE_INVALID)) + goto oom; + } else if (dbus_message_is_method_call(message, "org.freedesktop.systemd1.Manager", "GetUnitByPID")) { + Unit *u; + uint32_t pid; + + if (!dbus_message_get_args( + message, + &error, + DBUS_TYPE_UINT32, &pid, + DBUS_TYPE_INVALID)) + return bus_send_error_reply(connection, message, &error, -EINVAL); + + if (!(u = cgroup_unit_by_pid(m, (pid_t) pid))) { + dbus_set_error(&error, BUS_ERROR_NO_SUCH_UNIT, "No unit for PID %lu is loaded.", (unsigned long) pid); + return bus_send_error_reply(connection, message, &error, -ENOENT); + } + + if (!(reply = dbus_message_new_method_return(message))) + goto oom; + + if (!(path = unit_dbus_path(u))) + goto oom; + + if (!dbus_message_append_args( + reply, + DBUS_TYPE_OBJECT_PATH, &path, + DBUS_TYPE_INVALID)) + goto oom; + } else if (dbus_message_is_method_call(message, "org.freedesktop.systemd1.Manager", "LoadUnit")) { + const char *name; + Unit *u; + + if (!dbus_message_get_args( + message, + &error, + DBUS_TYPE_STRING, &name, + DBUS_TYPE_INVALID)) + return bus_send_error_reply(connection, message, &error, -EINVAL); + + if ((r = manager_load_unit(m, name, NULL, &error, &u)) < 0) + return bus_send_error_reply(connection, message, &error, r); + + if (!(reply = dbus_message_new_method_return(message))) + goto oom; + + if (!(path = unit_dbus_path(u))) + goto oom; + + if (!dbus_message_append_args( + reply, + DBUS_TYPE_OBJECT_PATH, &path, + DBUS_TYPE_INVALID)) + goto oom; + + } else if (dbus_message_is_method_call(message, "org.freedesktop.systemd1.Manager", "StartUnit")) + job_type = JOB_START; + else if (dbus_message_is_method_call(message, "org.freedesktop.systemd1.Manager", "StartUnitReplace")) + job_type = JOB_START; + else if (dbus_message_is_method_call(message, "org.freedesktop.systemd1.Manager", "StopUnit")) + job_type = JOB_STOP; + else if (dbus_message_is_method_call(message, "org.freedesktop.systemd1.Manager", "ReloadUnit")) + job_type = JOB_RELOAD; + else if (dbus_message_is_method_call(message, "org.freedesktop.systemd1.Manager", "RestartUnit")) + job_type = JOB_RESTART; + else if (dbus_message_is_method_call(message, "org.freedesktop.systemd1.Manager", "TryRestartUnit")) + job_type = JOB_TRY_RESTART; + else if (dbus_message_is_method_call(message, "org.freedesktop.systemd1.Manager", "ReloadOrRestartUnit")) { + reload_if_possible = true; + job_type = JOB_RESTART; + } else if (dbus_message_is_method_call(message, "org.freedesktop.systemd1.Manager", "ReloadOrTryRestartUnit")) { + reload_if_possible = true; + job_type = JOB_TRY_RESTART; + } else if (dbus_message_is_method_call(message, "org.freedesktop.systemd1.Manager", "KillUnit")) { + const char *name, *swho, *smode; + int32_t signo; + Unit *u; + KillMode mode; + KillWho who; + + if (!dbus_message_get_args( + message, + &error, + DBUS_TYPE_STRING, &name, + DBUS_TYPE_STRING, &swho, + DBUS_TYPE_STRING, &smode, + DBUS_TYPE_INT32, &signo, + DBUS_TYPE_INVALID)) + return bus_send_error_reply(connection, message, &error, -EINVAL); + + if (isempty(swho)) + who = KILL_ALL; + else { + who = kill_who_from_string(swho); + if (who < 0) + return bus_send_error_reply(connection, message, &error, -EINVAL); + } + + if (isempty(smode)) + mode = KILL_CONTROL_GROUP; + else { + mode = kill_mode_from_string(smode); + if (mode < 0) + return bus_send_error_reply(connection, message, &error, -EINVAL); + } + + if (signo <= 0 || signo >= _NSIG) + return bus_send_error_reply(connection, message, &error, -EINVAL); + + if (!(u = manager_get_unit(m, name))) { + dbus_set_error(&error, BUS_ERROR_NO_SUCH_UNIT, "Unit %s is not loaded.", name); + return bus_send_error_reply(connection, message, &error, -ENOENT); + } + + if ((r = unit_kill(u, who, mode, signo, &error)) < 0) + return bus_send_error_reply(connection, message, &error, r); + + if (!(reply = dbus_message_new_method_return(message))) + goto oom; + + } else if (dbus_message_is_method_call(message, "org.freedesktop.systemd1.Manager", "GetJob")) { + uint32_t id; + Job *j; + + if (!dbus_message_get_args( + message, + &error, + DBUS_TYPE_UINT32, &id, + DBUS_TYPE_INVALID)) + return bus_send_error_reply(connection, message, &error, -EINVAL); + + if (!(j = manager_get_job(m, id))) { + dbus_set_error(&error, BUS_ERROR_NO_SUCH_JOB, "Job %u does not exist.", (unsigned) id); + return bus_send_error_reply(connection, message, &error, -ENOENT); + } + + if (!(reply = dbus_message_new_method_return(message))) + goto oom; + + if (!(path = job_dbus_path(j))) + goto oom; + + if (!dbus_message_append_args( + reply, + DBUS_TYPE_OBJECT_PATH, &path, + DBUS_TYPE_INVALID)) + goto oom; + + } else if (dbus_message_is_method_call(message, "org.freedesktop.systemd1.Manager", "ClearJobs")) { + + manager_clear_jobs(m); + + if (!(reply = dbus_message_new_method_return(message))) + goto oom; + + } else if (dbus_message_is_method_call(message, "org.freedesktop.systemd1.Manager", "ResetFailed")) { + + manager_reset_failed(m); + + if (!(reply = dbus_message_new_method_return(message))) + goto oom; + + } else if (dbus_message_is_method_call(message, "org.freedesktop.systemd1.Manager", "ResetFailedUnit")) { + const char *name; + Unit *u; + + if (!dbus_message_get_args( + message, + &error, + DBUS_TYPE_STRING, &name, + DBUS_TYPE_INVALID)) + return bus_send_error_reply(connection, message, &error, -EINVAL); + + if (!(u = manager_get_unit(m, name))) { + dbus_set_error(&error, BUS_ERROR_NO_SUCH_UNIT, "Unit %s is not loaded.", name); + return bus_send_error_reply(connection, message, &error, -ENOENT); + } + + unit_reset_failed(u); + + if (!(reply = dbus_message_new_method_return(message))) + goto oom; + + } else if (dbus_message_is_method_call(message, "org.freedesktop.systemd1.Manager", "ListUnits")) { + DBusMessageIter iter, sub; + Iterator i; + Unit *u; + const char *k; + + if (!(reply = dbus_message_new_method_return(message))) + goto oom; + + dbus_message_iter_init_append(reply, &iter); + + if (!dbus_message_iter_open_container(&iter, DBUS_TYPE_ARRAY, "(ssssssouso)", &sub)) + goto oom; + + HASHMAP_FOREACH_KEY(u, k, m->units, i) { + char *u_path, *j_path; + const char *description, *load_state, *active_state, *sub_state, *sjob_type, *following; + DBusMessageIter sub2; + uint32_t job_id; + Unit *f; + + if (k != u->id) + continue; + + if (!dbus_message_iter_open_container(&sub, DBUS_TYPE_STRUCT, NULL, &sub2)) + goto oom; + + description = unit_description(u); + load_state = unit_load_state_to_string(u->load_state); + active_state = unit_active_state_to_string(unit_active_state(u)); + sub_state = unit_sub_state_to_string(u); + + f = unit_following(u); + following = f ? f->id : ""; + + if (!(u_path = unit_dbus_path(u))) + goto oom; + + if (u->job) { + job_id = (uint32_t) u->job->id; + + if (!(j_path = job_dbus_path(u->job))) { + free(u_path); + goto oom; + } + + sjob_type = job_type_to_string(u->job->type); + } else { + job_id = 0; + j_path = u_path; + sjob_type = ""; + } + + if (!dbus_message_iter_append_basic(&sub2, DBUS_TYPE_STRING, &u->id) || + !dbus_message_iter_append_basic(&sub2, DBUS_TYPE_STRING, &description) || + !dbus_message_iter_append_basic(&sub2, DBUS_TYPE_STRING, &load_state) || + !dbus_message_iter_append_basic(&sub2, DBUS_TYPE_STRING, &active_state) || + !dbus_message_iter_append_basic(&sub2, DBUS_TYPE_STRING, &sub_state) || + !dbus_message_iter_append_basic(&sub2, DBUS_TYPE_STRING, &following) || + !dbus_message_iter_append_basic(&sub2, DBUS_TYPE_OBJECT_PATH, &u_path) || + !dbus_message_iter_append_basic(&sub2, DBUS_TYPE_UINT32, &job_id) || + !dbus_message_iter_append_basic(&sub2, DBUS_TYPE_STRING, &sjob_type) || + !dbus_message_iter_append_basic(&sub2, DBUS_TYPE_OBJECT_PATH, &j_path)) { + free(u_path); + if (u->job) + free(j_path); + goto oom; + } + + free(u_path); + if (u->job) + free(j_path); + + if (!dbus_message_iter_close_container(&sub, &sub2)) + goto oom; + } + + if (!dbus_message_iter_close_container(&iter, &sub)) + goto oom; + + } else if (dbus_message_is_method_call(message, "org.freedesktop.systemd1.Manager", "ListJobs")) { + DBusMessageIter iter, sub; + Iterator i; + Job *j; + + if (!(reply = dbus_message_new_method_return(message))) + goto oom; + + dbus_message_iter_init_append(reply, &iter); + + if (!dbus_message_iter_open_container(&iter, DBUS_TYPE_ARRAY, "(usssoo)", &sub)) + goto oom; + + HASHMAP_FOREACH(j, m->jobs, i) { + char *u_path, *j_path; + const char *state, *type; + uint32_t id; + DBusMessageIter sub2; + + if (!dbus_message_iter_open_container(&sub, DBUS_TYPE_STRUCT, NULL, &sub2)) + goto oom; + + id = (uint32_t) j->id; + state = job_state_to_string(j->state); + type = job_type_to_string(j->type); + + if (!(j_path = job_dbus_path(j))) + goto oom; + + if (!(u_path = unit_dbus_path(j->unit))) { + free(j_path); + goto oom; + } + + if (!dbus_message_iter_append_basic(&sub2, DBUS_TYPE_UINT32, &id) || + !dbus_message_iter_append_basic(&sub2, DBUS_TYPE_STRING, &j->unit->id) || + !dbus_message_iter_append_basic(&sub2, DBUS_TYPE_STRING, &type) || + !dbus_message_iter_append_basic(&sub2, DBUS_TYPE_STRING, &state) || + !dbus_message_iter_append_basic(&sub2, DBUS_TYPE_OBJECT_PATH, &j_path) || + !dbus_message_iter_append_basic(&sub2, DBUS_TYPE_OBJECT_PATH, &u_path)) { + free(j_path); + free(u_path); + goto oom; + } + + free(j_path); + free(u_path); + + if (!dbus_message_iter_close_container(&sub, &sub2)) + goto oom; + } + + if (!dbus_message_iter_close_container(&iter, &sub)) + goto oom; + + } else if (dbus_message_is_method_call(message, "org.freedesktop.systemd1.Manager", "Subscribe")) { + char *client; + Set *s; + + if (!(s = BUS_CONNECTION_SUBSCRIBED(m, connection))) { + if (!(s = set_new(string_hash_func, string_compare_func))) + goto oom; + + if (!(dbus_connection_set_data(connection, m->subscribed_data_slot, s, NULL))) { + set_free(s); + goto oom; + } + } + + if (!(client = strdup(message_get_sender_with_fallback(message)))) + goto oom; + + if ((r = set_put(s, client)) < 0) { + free(client); + return bus_send_error_reply(connection, message, NULL, r); + } + + if (!(reply = dbus_message_new_method_return(message))) + goto oom; + + } else if (dbus_message_is_method_call(message, "org.freedesktop.systemd1.Manager", "Unsubscribe")) { + char *client; + + if (!(client = set_remove(BUS_CONNECTION_SUBSCRIBED(m, connection), (char*) message_get_sender_with_fallback(message)))) { + dbus_set_error(&error, BUS_ERROR_NOT_SUBSCRIBED, "Client is not subscribed."); + return bus_send_error_reply(connection, message, &error, -ENOENT); + } + + free(client); + + if (!(reply = dbus_message_new_method_return(message))) + goto oom; + + } else if (dbus_message_is_method_call(message, "org.freedesktop.systemd1.Manager", "Dump")) { + FILE *f; + char *dump = NULL; + size_t size; + + if (!(reply = dbus_message_new_method_return(message))) + goto oom; + + if (!(f = open_memstream(&dump, &size))) + goto oom; + + manager_dump_units(m, f, NULL); + manager_dump_jobs(m, f, NULL); + + if (ferror(f)) { + fclose(f); + free(dump); + goto oom; + } + + fclose(f); + + if (!dbus_message_append_args(reply, DBUS_TYPE_STRING, &dump, DBUS_TYPE_INVALID)) { + free(dump); + goto oom; + } + + free(dump); + } else if (dbus_message_is_method_call(message, "org.freedesktop.systemd1.Manager", "CreateSnapshot")) { + const char *name; + dbus_bool_t cleanup; + Snapshot *s; + + if (!dbus_message_get_args( + message, + &error, + DBUS_TYPE_STRING, &name, + DBUS_TYPE_BOOLEAN, &cleanup, + DBUS_TYPE_INVALID)) + return bus_send_error_reply(connection, message, &error, -EINVAL); + + if (name && name[0] == 0) + name = NULL; + + if ((r = snapshot_create(m, name, cleanup, &error, &s)) < 0) + return bus_send_error_reply(connection, message, &error, r); + + if (!(reply = dbus_message_new_method_return(message))) + goto oom; + + if (!(path = unit_dbus_path(UNIT(s)))) + goto oom; + + if (!dbus_message_append_args( + reply, + DBUS_TYPE_OBJECT_PATH, &path, + DBUS_TYPE_INVALID)) + goto oom; + + } else if (dbus_message_is_method_call(message, "org.freedesktop.DBus.Introspectable", "Introspect")) { + char *introspection = NULL; + FILE *f; + Iterator i; + Unit *u; + Job *j; + const char *k; + size_t size; + + if (!(reply = dbus_message_new_method_return(message))) + goto oom; + + /* We roll our own introspection code here, instead of + * relying on bus_default_message_handler() because we + * need to generate our introspection string + * dynamically. */ + + if (!(f = open_memstream(&introspection, &size))) + goto oom; + + fputs(INTROSPECTION_BEGIN, f); + + HASHMAP_FOREACH_KEY(u, k, m->units, i) { + char *p; + + if (k != u->id) + continue; + + if (!(p = bus_path_escape(k))) { + fclose(f); + free(introspection); + goto oom; + } + + fprintf(f, "", p); + free(p); + } + + HASHMAP_FOREACH(j, m->jobs, i) + fprintf(f, "", (unsigned long) j->id); + + fputs(INTROSPECTION_END, f); + + if (ferror(f)) { + fclose(f); + free(introspection); + goto oom; + } + + fclose(f); + + if (!introspection) + goto oom; + + if (!dbus_message_append_args(reply, DBUS_TYPE_STRING, &introspection, DBUS_TYPE_INVALID)) { + free(introspection); + goto oom; + } + + free(introspection); + + } else if (dbus_message_is_method_call(message, "org.freedesktop.systemd1.Manager", "Reload")) { + + assert(!m->queued_message); + + /* 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. */ + + if (!(m->queued_message = dbus_message_new_method_return(message))) + goto oom; + + m->queued_message_connection = connection; + m->exit_code = MANAGER_RELOAD; + + } else if (dbus_message_is_method_call(message, "org.freedesktop.systemd1.Manager", "Reexecute")) { + + /* We don't send a reply back here, the client should + * just wait for us disconnecting. */ + + m->exit_code = MANAGER_REEXECUTE; + + } else if (dbus_message_is_method_call(message, "org.freedesktop.systemd1.Manager", "Exit")) { + + if (m->running_as == MANAGER_SYSTEM) { + dbus_set_error(&error, BUS_ERROR_NOT_SUPPORTED, "Exit is only supported for user service managers."); + return bus_send_error_reply(connection, message, &error, -ENOTSUP); + } + + if (!(reply = dbus_message_new_method_return(message))) + goto oom; + + m->exit_code = MANAGER_EXIT; + + } else if (dbus_message_is_method_call(message, "org.freedesktop.systemd1.Manager", "Reboot")) { + + if (m->running_as != MANAGER_SYSTEM) { + dbus_set_error(&error, BUS_ERROR_NOT_SUPPORTED, "Reboot is only supported for system managers."); + return bus_send_error_reply(connection, message, &error, -ENOTSUP); + } + + if (!(reply = dbus_message_new_method_return(message))) + goto oom; + + m->exit_code = MANAGER_REBOOT; + + } else if (dbus_message_is_method_call(message, "org.freedesktop.systemd1.Manager", "PowerOff")) { + + if (m->running_as != MANAGER_SYSTEM) { + dbus_set_error(&error, BUS_ERROR_NOT_SUPPORTED, "Powering off is only supported for system managers."); + return bus_send_error_reply(connection, message, &error, -ENOTSUP); + } + + if (!(reply = dbus_message_new_method_return(message))) + goto oom; + + m->exit_code = MANAGER_POWEROFF; + + } else if (dbus_message_is_method_call(message, "org.freedesktop.systemd1.Manager", "Halt")) { + + if (m->running_as != MANAGER_SYSTEM) { + dbus_set_error(&error, BUS_ERROR_NOT_SUPPORTED, "Halting is only supported for system managers."); + return bus_send_error_reply(connection, message, &error, -ENOTSUP); + } + + if (!(reply = dbus_message_new_method_return(message))) + goto oom; + + m->exit_code = MANAGER_HALT; + + } else if (dbus_message_is_method_call(message, "org.freedesktop.systemd1.Manager", "KExec")) { + + if (m->running_as != MANAGER_SYSTEM) { + dbus_set_error(&error, BUS_ERROR_NOT_SUPPORTED, "kexec is only supported for system managers."); + return bus_send_error_reply(connection, message, &error, -ENOTSUP); + } + + if (!(reply = dbus_message_new_method_return(message))) + goto oom; + + m->exit_code = MANAGER_KEXEC; + + } else if (dbus_message_is_method_call(message, "org.freedesktop.systemd1.Manager", "SetEnvironment")) { + char **l = NULL, **e = NULL; + + if ((r = bus_parse_strv(message, &l)) < 0) { + if (r == -ENOMEM) + goto oom; + + return bus_send_error_reply(connection, message, NULL, r); + } + + e = strv_env_merge(2, m->environment, l); + strv_free(l); + + if (!e) + goto oom; + + if (!(reply = dbus_message_new_method_return(message))) { + strv_free(e); + goto oom; + } + + strv_free(m->environment); + m->environment = e; + + } else if (dbus_message_is_method_call(message, "org.freedesktop.systemd1.Manager", "UnsetEnvironment")) { + char **l = NULL, **e = NULL; + + if ((r = bus_parse_strv(message, &l)) < 0) { + if (r == -ENOMEM) + goto oom; + + return bus_send_error_reply(connection, message, NULL, r); + } + + e = strv_env_delete(m->environment, 1, l); + strv_free(l); + + if (!e) + goto oom; + + if (!(reply = dbus_message_new_method_return(message))) { + strv_free(e); + goto oom; + } + + strv_free(m->environment); + m->environment = e; + + } else if (dbus_message_is_method_call(message, "org.freedesktop.systemd1.Manager", "UnsetAndSetEnvironment")) { + char **l_set = NULL, **l_unset = NULL, **e = NULL, **f = NULL; + DBusMessageIter iter; + + if (!dbus_message_iter_init(message, &iter)) + goto oom; + + r = bus_parse_strv_iter(&iter, &l_unset); + if (r < 0) { + if (r == -ENOMEM) + goto oom; + + return bus_send_error_reply(connection, message, NULL, r); + } + + if (!dbus_message_iter_next(&iter)) { + strv_free(l_unset); + return bus_send_error_reply(connection, message, NULL, -EINVAL); + } + + r = bus_parse_strv_iter(&iter, &l_set); + if (r < 0) { + strv_free(l_unset); + if (r == -ENOMEM) + goto oom; + + return bus_send_error_reply(connection, message, NULL, r); + } + + e = strv_env_delete(m->environment, 1, l_unset); + strv_free(l_unset); + + if (!e) { + strv_free(l_set); + goto oom; + } + + f = strv_env_merge(2, e, l_set); + strv_free(l_set); + strv_free(e); + + if (!f) + goto oom; + + if (!(reply = dbus_message_new_method_return(message))) { + strv_free(f); + goto oom; + } + + strv_free(m->environment); + m->environment = f; + } else if (dbus_message_is_method_call(message, "org.freedesktop.systemd1.Manager", "ListUnitFiles")) { + DBusMessageIter iter, sub, sub2; + Hashmap *h; + Iterator i; + UnitFileList *item; + + reply = dbus_message_new_method_return(message); + if (!reply) + goto oom; + + h = hashmap_new(string_hash_func, string_compare_func); + if (!h) + goto oom; + + r = unit_file_get_list(m->running_as == MANAGER_SYSTEM ? UNIT_FILE_SYSTEM : UNIT_FILE_USER, NULL, h); + if (r < 0) { + unit_file_list_free(h); + dbus_message_unref(reply); + return bus_send_error_reply(connection, message, NULL, r); + } + + dbus_message_iter_init_append(reply, &iter); + + if (!dbus_message_iter_open_container(&iter, DBUS_TYPE_ARRAY, "(ss)", &sub)) { + unit_file_list_free(h); + goto oom; + } + + HASHMAP_FOREACH(item, h, i) { + const char *state; + + state = unit_file_state_to_string(item->state); + assert(state); + + if (!dbus_message_iter_open_container(&sub, DBUS_TYPE_STRUCT, NULL, &sub2) || + !dbus_message_iter_append_basic(&sub2, DBUS_TYPE_STRING, &item->path) || + !dbus_message_iter_append_basic(&sub2, DBUS_TYPE_STRING, &state) || + !dbus_message_iter_close_container(&sub, &sub2)) { + unit_file_list_free(h); + goto oom; + } + } + + unit_file_list_free(h); + + if (!dbus_message_iter_close_container(&iter, &sub)) + goto oom; + + } else if (dbus_message_is_method_call(message, "org.freedesktop.systemd1.Manager", "GetUnitFileState")) { + const char *name; + UnitFileState state; + const char *s; + + if (!dbus_message_get_args( + message, + &error, + DBUS_TYPE_STRING, &name, + DBUS_TYPE_INVALID)) + return bus_send_error_reply(connection, message, &error, -EINVAL); + + state = unit_file_get_state(m->running_as == MANAGER_SYSTEM ? UNIT_FILE_SYSTEM : UNIT_FILE_USER, NULL, name); + if (state < 0) + return bus_send_error_reply(connection, message, NULL, state); + + s = unit_file_state_to_string(state); + assert(s); + + reply = dbus_message_new_method_return(message); + if (!reply) + goto oom; + + if (!dbus_message_append_args( + reply, + DBUS_TYPE_STRING, &s, + DBUS_TYPE_INVALID)) + goto oom; + } else if (dbus_message_is_method_call(message, "org.freedesktop.systemd1.Manager", "EnableUnitFiles") || + dbus_message_is_method_call(message, "org.freedesktop.systemd1.Manager", "ReenableUnitFiles") || + dbus_message_is_method_call(message, "org.freedesktop.systemd1.Manager", "LinkUnitFiles") || + dbus_message_is_method_call(message, "org.freedesktop.systemd1.Manager", "PresetUnitFiles") || + dbus_message_is_method_call(message, "org.freedesktop.systemd1.Manager", "MaskUnitFiles")) { + + char **l = NULL; + DBusMessageIter iter; + UnitFileScope scope = m->running_as == MANAGER_SYSTEM ? UNIT_FILE_SYSTEM : UNIT_FILE_USER; + UnitFileChange *changes = NULL; + unsigned n_changes = 0; + dbus_bool_t runtime, force; + int carries_install_info = -1; + + if (!dbus_message_iter_init(message, &iter)) + goto oom; + + r = bus_parse_strv_iter(&iter, &l); + if (r < 0) { + if (r == -ENOMEM) + goto oom; + + return bus_send_error_reply(connection, message, NULL, r); + } + + if (!dbus_message_iter_next(&iter) || + bus_iter_get_basic_and_next(&iter, DBUS_TYPE_BOOLEAN, &runtime, true) < 0 || + bus_iter_get_basic_and_next(&iter, DBUS_TYPE_BOOLEAN, &force, false) < 0) { + strv_free(l); + return bus_send_error_reply(connection, message, NULL, -EIO); + } + + if (streq(member, "EnableUnitFiles")) { + r = unit_file_enable(scope, runtime, NULL, l, force, &changes, &n_changes); + carries_install_info = r; + } else if (streq(member, "ReenableUnitFiles")) { + r = unit_file_reenable(scope, runtime, NULL, l, force, &changes, &n_changes); + carries_install_info = r; + } else if (streq(member, "LinkUnitFiles")) + r = unit_file_link(scope, runtime, NULL, l, force, &changes, &n_changes); + else if (streq(member, "PresetUnitFiles")) { + r = unit_file_preset(scope, runtime, NULL, l, force, &changes, &n_changes); + carries_install_info = r; + } else if (streq(member, "MaskUnitFiles")) + r = unit_file_mask(scope, runtime, NULL, l, force, &changes, &n_changes); + else + assert_not_reached("Uh? Wrong method"); + + strv_free(l); + bus_manager_send_unit_files_changed(m); + + if (r < 0) { + unit_file_changes_free(changes, n_changes); + return bus_send_error_reply(connection, message, NULL, r); + } + + reply = message_from_file_changes(message, changes, n_changes, carries_install_info); + unit_file_changes_free(changes, n_changes); + + if (!reply) + goto oom; + + } else if (dbus_message_is_method_call(message, "org.freedesktop.systemd1.Manager", "DisableUnitFiles") || + dbus_message_is_method_call(message, "org.freedesktop.systemd1.Manager", "UnmaskUnitFiles")) { + + char **l = NULL; + DBusMessageIter iter; + UnitFileScope scope = m->running_as == MANAGER_SYSTEM ? UNIT_FILE_SYSTEM : UNIT_FILE_USER; + UnitFileChange *changes = NULL; + unsigned n_changes = 0; + dbus_bool_t runtime; + + if (!dbus_message_iter_init(message, &iter)) + goto oom; + + r = bus_parse_strv_iter(&iter, &l); + if (r < 0) { + if (r == -ENOMEM) + goto oom; + + return bus_send_error_reply(connection, message, NULL, r); + } + + if (!dbus_message_iter_next(&iter) || + bus_iter_get_basic_and_next(&iter, DBUS_TYPE_BOOLEAN, &runtime, false) < 0) { + strv_free(l); + return bus_send_error_reply(connection, message, NULL, -EIO); + } + + if (streq(member, "DisableUnitFiles")) + r = unit_file_disable(scope, runtime, NULL, l, &changes, &n_changes); + else if (streq(member, "UnmaskUnitFiles")) + r = unit_file_unmask(scope, runtime, NULL, l, &changes, &n_changes); + else + assert_not_reached("Uh? Wrong method"); + + strv_free(l); + bus_manager_send_unit_files_changed(m); + + if (r < 0) { + unit_file_changes_free(changes, n_changes); + return bus_send_error_reply(connection, message, NULL, r); + } + + reply = message_from_file_changes(message, changes, n_changes, -1); + unit_file_changes_free(changes, n_changes); + + if (!reply) + goto oom; + + } else { + const BusBoundProperties bps[] = { + { "org.freedesktop.systemd1.Manager", bus_systemd_properties, systemd_property_string }, + { "org.freedesktop.systemd1.Manager", bus_manager_properties, m }, + { NULL, } + }; + return bus_default_message_handler(connection, message, NULL, INTERFACES_LIST, bps); + } + + if (job_type != _JOB_TYPE_INVALID) { + const char *name, *smode, *old_name = NULL; + JobMode mode; + Job *j; + Unit *u; + bool b; + + if (dbus_message_is_method_call(message, "org.freedesktop.systemd1.Manager", "StartUnitReplace")) + b = dbus_message_get_args( + message, + &error, + DBUS_TYPE_STRING, &old_name, + DBUS_TYPE_STRING, &name, + DBUS_TYPE_STRING, &smode, + DBUS_TYPE_INVALID); + else + b = dbus_message_get_args( + message, + &error, + DBUS_TYPE_STRING, &name, + DBUS_TYPE_STRING, &smode, + DBUS_TYPE_INVALID); + + if (!b) + return bus_send_error_reply(connection, message, &error, -EINVAL); + + if (old_name) + if (!(u = manager_get_unit(m, old_name)) || + !u->job || + u->job->type != JOB_START) { + dbus_set_error(&error, BUS_ERROR_NO_SUCH_JOB, "No job queued for unit %s", old_name); + return bus_send_error_reply(connection, message, &error, -ENOENT); + } + + + if ((mode = job_mode_from_string(smode)) == _JOB_MODE_INVALID) { + dbus_set_error(&error, BUS_ERROR_INVALID_JOB_MODE, "Job mode %s is invalid.", smode); + return bus_send_error_reply(connection, message, &error, -EINVAL); + } + + if ((r = manager_load_unit(m, name, NULL, &error, &u)) < 0) + return bus_send_error_reply(connection, message, &error, r); + + if (reload_if_possible && unit_can_reload(u)) { + if (job_type == JOB_RESTART) + job_type = JOB_RELOAD_OR_START; + else if (job_type == JOB_TRY_RESTART) + job_type = JOB_RELOAD; + } + + if ((job_type == JOB_START && u->refuse_manual_start) || + (job_type == JOB_STOP && u->refuse_manual_stop) || + ((job_type == JOB_RESTART || job_type == JOB_TRY_RESTART) && + (u->refuse_manual_start || u->refuse_manual_stop))) { + dbus_set_error(&error, BUS_ERROR_ONLY_BY_DEPENDENCY, "Operation refused, may be requested by dependency only."); + return bus_send_error_reply(connection, message, &error, -EPERM); + } + + if ((r = manager_add_job(m, job_type, u, mode, true, &error, &j)) < 0) + return bus_send_error_reply(connection, message, &error, r); + + if (!(j->bus_client = strdup(message_get_sender_with_fallback(message)))) + goto oom; + + j->bus = connection; + + if (!(reply = dbus_message_new_method_return(message))) + goto oom; + + if (!(path = job_dbus_path(j))) + goto oom; + + if (!dbus_message_append_args( + reply, + DBUS_TYPE_OBJECT_PATH, &path, + DBUS_TYPE_INVALID)) + goto oom; + } + + if (reply) { + if (!dbus_connection_send(connection, reply, NULL)) + goto oom; + + dbus_message_unref(reply); + } + + free(path); + + return DBUS_HANDLER_RESULT_HANDLED; + +oom: + free(path); + + if (reply) + dbus_message_unref(reply); + + dbus_error_free(&error); + + return DBUS_HANDLER_RESULT_NEED_MEMORY; +} + +const DBusObjectPathVTable bus_manager_vtable = { + .message_function = bus_manager_message_handler +}; diff --git a/src/core/dbus-manager.h b/src/core/dbus-manager.h new file mode 100644 index 0000000000..2eb2fbbed6 --- /dev/null +++ b/src/core/dbus-manager.h @@ -0,0 +1,31 @@ +/*-*- Mode: C; c-basic-offset: 8; indent-tabs-mode: nil -*-*/ + +#ifndef foodbusmanagerhfoo +#define foodbusmanagerhfoo + +/*** + This file is part of systemd. + + Copyright 2010 Lennart Poettering + + systemd is free software; you can redistribute it and/or modify it + under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + systemd is distributed in the hope that 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 General Public License + along with systemd; If not, see . +***/ + +#include + +extern const DBusObjectPathVTable bus_manager_vtable; + +extern const char bus_manager_interface[]; + +#endif diff --git a/src/core/dbus-mount.c b/src/core/dbus-mount.c new file mode 100644 index 0000000000..35d6ea7a1d --- /dev/null +++ b/src/core/dbus-mount.c @@ -0,0 +1,168 @@ +/*-*- Mode: C; c-basic-offset: 8; indent-tabs-mode: nil -*-*/ + +/*** + This file is part of systemd. + + Copyright 2010 Lennart Poettering + + systemd is free software; you can redistribute it and/or modify it + under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + systemd is distributed in the hope that 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 General Public License + along with systemd; If not, see . +***/ + +#include + +#include "dbus-unit.h" +#include "dbus-mount.h" +#include "dbus-execute.h" +#include "dbus-common.h" + +#define BUS_MOUNT_INTERFACE \ + " \n" \ + " \n" \ + " \n" \ + " \n" \ + " \n" \ + " \n" \ + BUS_EXEC_COMMAND_INTERFACE("ExecMount") \ + BUS_EXEC_COMMAND_INTERFACE("ExecUnmount") \ + BUS_EXEC_COMMAND_INTERFACE("ExecRemount") \ + BUS_EXEC_CONTEXT_INTERFACE \ + " \n" \ + " \n" \ + " \n" \ + " \n" + +#define INTROSPECTION \ + DBUS_INTROSPECT_1_0_XML_DOCTYPE_DECL_NODE \ + "\n" \ + BUS_UNIT_INTERFACE \ + BUS_MOUNT_INTERFACE \ + BUS_PROPERTIES_INTERFACE \ + BUS_PEER_INTERFACE \ + BUS_INTROSPECTABLE_INTERFACE \ + "\n" + +#define INTERFACES_LIST \ + BUS_UNIT_INTERFACES_LIST \ + "org.freedesktop.systemd1.Mount\0" + +const char bus_mount_interface[] _introspect_("Mount") = BUS_MOUNT_INTERFACE; + +const char bus_mount_invalidating_properties[] = + "What\0" + "Options\0" + "Type\0" + "ExecMount\0" + "ExecUnmount\0" + "ExecRemount\0" + "ControlPID\0" + "Result\0"; + +static int bus_mount_append_what(DBusMessageIter *i, const char *property, void *data) { + Mount *m = data; + const char *d; + + assert(i); + assert(property); + 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 if (m->from_etc_fstab && m->parameters_etc_fstab.what) + d = m->parameters_etc_fstab.what; + else + d = ""; + + if (!dbus_message_iter_append_basic(i, DBUS_TYPE_STRING, &d)) + return -ENOMEM; + + return 0; +} + +static int bus_mount_append_options(DBusMessageIter *i, const char *property, void *data) { + Mount *m = data; + const char *d; + + assert(i); + assert(property); + 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 if (m->from_etc_fstab && m->parameters_etc_fstab.options) + d = m->parameters_etc_fstab.options; + else + d = ""; + + if (!dbus_message_iter_append_basic(i, DBUS_TYPE_STRING, &d)) + return -ENOMEM; + + return 0; +} + +static int bus_mount_append_type(DBusMessageIter *i, const char *property, void *data) { + Mount *m = data; + const char *d; + + assert(i); + assert(property); + 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 if (m->from_etc_fstab && m->parameters_etc_fstab.fstype) + d = m->parameters_etc_fstab.fstype; + else + d = ""; + + if (!dbus_message_iter_append_basic(i, DBUS_TYPE_STRING, &d)) + return -ENOMEM; + + return 0; +} + +static DEFINE_BUS_PROPERTY_APPEND_ENUM(bus_mount_append_mount_result, mount_result, MountResult); + +static const BusProperty bus_mount_properties[] = { + { "Where", bus_property_append_string, "s", offsetof(Mount, where), true }, + { "What", bus_mount_append_what, "s", 0 }, + { "Options", bus_mount_append_options, "s", 0 }, + { "Type", bus_mount_append_type, "s", 0 }, + { "TimeoutUSec", bus_property_append_usec, "t", offsetof(Mount, timeout_usec) }, + BUS_EXEC_COMMAND_PROPERTY("ExecMount", offsetof(Mount, exec_command[MOUNT_EXEC_MOUNT]), false), + BUS_EXEC_COMMAND_PROPERTY("ExecUnmount", offsetof(Mount, exec_command[MOUNT_EXEC_UNMOUNT]), false), + BUS_EXEC_COMMAND_PROPERTY("ExecRemount", offsetof(Mount, exec_command[MOUNT_EXEC_REMOUNT]), false), + { "ControlPID", bus_property_append_pid, "u", offsetof(Mount, control_pid) }, + { "DirectoryMode", bus_property_append_mode, "u", offsetof(Mount, directory_mode) }, + { "Result", bus_mount_append_mount_result, "s", offsetof(Mount, result) }, + { NULL, } +}; + +DBusHandlerResult bus_mount_message_handler(Unit *u, DBusConnection *c, DBusMessage *message) { + Mount *m = MOUNT(u); + + const BusBoundProperties bps[] = { + { "org.freedesktop.systemd1.Unit", bus_unit_properties, u }, + { "org.freedesktop.systemd1.Mount", bus_mount_properties, m }, + { "org.freedesktop.systemd1.Mount", bus_exec_context_properties, &m->exec_context }, + { NULL, } + }; + + return bus_default_message_handler(c, message, INTROSPECTION, INTERFACES_LIST, bps ); +} diff --git a/src/core/dbus-mount.h b/src/core/dbus-mount.h new file mode 100644 index 0000000000..b5613fa7b6 --- /dev/null +++ b/src/core/dbus-mount.h @@ -0,0 +1,34 @@ +/*-*- Mode: C; c-basic-offset: 8; indent-tabs-mode: nil -*-*/ + +#ifndef foodbusmounthfoo +#define foodbusmounthfoo + +/*** + This file is part of systemd. + + Copyright 2010 Lennart Poettering + + systemd is free software; you can redistribute it and/or modify it + under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + systemd is distributed in the hope that 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 General Public License + along with systemd; If not, see . +***/ + +#include + +#include "unit.h" + +DBusHandlerResult bus_mount_message_handler(Unit *u, DBusConnection *c, DBusMessage *message); + +extern const char bus_mount_interface[]; +extern const char bus_mount_invalidating_properties[]; + +#endif diff --git a/src/core/dbus-path.c b/src/core/dbus-path.c new file mode 100644 index 0000000000..5506784c38 --- /dev/null +++ b/src/core/dbus-path.c @@ -0,0 +1,119 @@ +/*-*- Mode: C; c-basic-offset: 8; indent-tabs-mode: nil -*-*/ + +/*** + This file is part of systemd. + + Copyright 2010 Lennart Poettering + + systemd is free software; you can redistribute it and/or modify it + under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + systemd is distributed in the hope that 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 General Public License + along with systemd; If not, see . +***/ + +#include + +#include "dbus-unit.h" +#include "dbus-path.h" +#include "dbus-execute.h" +#include "dbus-common.h" + +#define BUS_PATH_INTERFACE \ + " \n" \ + " \n" \ + " \n" \ + " \n" \ + " \n" \ + " \n" \ + " \n" + +#define INTROSPECTION \ + DBUS_INTROSPECT_1_0_XML_DOCTYPE_DECL_NODE \ + "\n" \ + BUS_UNIT_INTERFACE \ + BUS_PATH_INTERFACE \ + BUS_PROPERTIES_INTERFACE \ + BUS_PEER_INTERFACE \ + BUS_INTROSPECTABLE_INTERFACE \ + "\n" + +#define INTERFACES_LIST \ + BUS_UNIT_INTERFACES_LIST \ + "org.freedesktop.systemd1.Path\0" + +const char bus_path_interface[] _introspect_("Path") = BUS_PATH_INTERFACE; + +const char bus_path_invalidating_properties[] = + "Result\0"; + +static int bus_path_append_paths(DBusMessageIter *i, const char *property, void *data) { + Path *p = data; + DBusMessageIter sub, sub2; + PathSpec *k; + + assert(i); + assert(property); + assert(p); + + if (!dbus_message_iter_open_container(i, DBUS_TYPE_ARRAY, "(ss)", &sub)) + return -ENOMEM; + + LIST_FOREACH(spec, k, p->specs) { + const char *t = path_type_to_string(k->type); + + if (!dbus_message_iter_open_container(&sub, DBUS_TYPE_STRUCT, NULL, &sub2) || + !dbus_message_iter_append_basic(&sub2, DBUS_TYPE_STRING, &t) || + !dbus_message_iter_append_basic(&sub2, DBUS_TYPE_STRING, &k->path) || + !dbus_message_iter_close_container(&sub, &sub2)) + return -ENOMEM; + } + + if (!dbus_message_iter_close_container(i, &sub)) + return -ENOMEM; + + return 0; +} + +static int bus_path_append_unit(DBusMessageIter *i, const char *property, void *data) { + Unit *u = data; + Path *p = PATH(u); + const char *t; + + assert(i); + assert(property); + assert(u); + + t = UNIT_DEREF(p->unit) ? UNIT_DEREF(p->unit)->id : ""; + + return dbus_message_iter_append_basic(i, DBUS_TYPE_STRING, &t) ? 0 : -ENOMEM; +} + +static DEFINE_BUS_PROPERTY_APPEND_ENUM(bus_path_append_path_result, path_result, PathResult); + +static const BusProperty bus_path_properties[] = { + { "Unit", bus_path_append_unit, "s", 0 }, + { "Paths", bus_path_append_paths, "a(ss)", 0 }, + { "MakeDirectory", bus_property_append_bool, "b", offsetof(Path, make_directory) }, + { "DirectoryMode", bus_property_append_mode, "u", offsetof(Path, directory_mode) }, + { "Result", bus_path_append_path_result, "s", offsetof(Path, result) }, + { NULL, } +}; + +DBusHandlerResult bus_path_message_handler(Unit *u, DBusConnection *c, DBusMessage *message) { + Path *p = PATH(u); + const BusBoundProperties bps[] = { + { "org.freedesktop.systemd1.Unit", bus_unit_properties, u }, + { "org.freedesktop.systemd1.Path", bus_path_properties, p }, + { NULL, } + }; + + return bus_default_message_handler(c, message, INTROSPECTION, INTERFACES_LIST, bps); +} diff --git a/src/core/dbus-path.h b/src/core/dbus-path.h new file mode 100644 index 0000000000..2888400a13 --- /dev/null +++ b/src/core/dbus-path.h @@ -0,0 +1,35 @@ +/*-*- Mode: C; c-basic-offset: 8; indent-tabs-mode: nil -*-*/ + +#ifndef foodbuspathhfoo +#define foodbuspathhfoo + +/*** + This file is part of systemd. + + Copyright 2010 Lennart Poettering + + systemd is free software; you can redistribute it and/or modify it + under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + systemd is distributed in the hope that 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 General Public License + along with systemd; If not, see . +***/ + +#include + +#include "unit.h" + +DBusHandlerResult bus_path_message_handler(Unit *u, DBusConnection *c, DBusMessage *message); + +extern const char bus_path_interface[]; + +extern const char bus_path_invalidating_properties[]; + +#endif diff --git a/src/core/dbus-service.c b/src/core/dbus-service.c new file mode 100644 index 0000000000..d840415417 --- /dev/null +++ b/src/core/dbus-service.c @@ -0,0 +1,169 @@ +/*-*- Mode: C; c-basic-offset: 8; indent-tabs-mode: nil -*-*/ + +/*** + This file is part of systemd. + + Copyright 2010 Lennart Poettering + + systemd is free software; you can redistribute it and/or modify it + under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + systemd is distributed in the hope that 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 General Public License + along with systemd; If not, see . +***/ + +#include + +#include "dbus-unit.h" +#include "dbus-execute.h" +#include "dbus-service.h" +#include "dbus-common.h" + +#ifdef HAVE_SYSV_COMPAT +#define BUS_SERVICE_SYSV_INTERFACE_FRAGMENT \ + " \n" \ + " \n" \ + " \n" +#else +#define BUS_SERVICE_SYSV_INTERFACE_FRAGMENT "" +#endif + +#define BUS_SERVICE_INTERFACE \ + " \n" \ + " \n" \ + " \n" \ + " \n" \ + " \n" \ + " \n" \ + " \n" \ + " \n" \ + " \n" \ + " \n" \ + " \n" \ + " \n" \ + " \n" \ + BUS_EXEC_COMMAND_INTERFACE("ExecStartPre") \ + BUS_EXEC_COMMAND_INTERFACE("ExecStart") \ + BUS_EXEC_COMMAND_INTERFACE("ExecStartPost") \ + BUS_EXEC_COMMAND_INTERFACE("ExecReload") \ + BUS_EXEC_COMMAND_INTERFACE("ExecStop") \ + BUS_EXEC_COMMAND_INTERFACE("ExecStopPost") \ + BUS_EXEC_CONTEXT_INTERFACE \ + " \n" \ + " \n" \ + " \n" \ + BUS_EXEC_STATUS_INTERFACE("ExecMain") \ + " \n" \ + " \n" \ + " \n" \ + " \n" \ + " \n" \ + " \n" \ + BUS_SERVICE_SYSV_INTERFACE_FRAGMENT \ + " \n" + +#define INTROSPECTION \ + DBUS_INTROSPECT_1_0_XML_DOCTYPE_DECL_NODE \ + "\n" \ + BUS_UNIT_INTERFACE \ + BUS_SERVICE_INTERFACE \ + BUS_PROPERTIES_INTERFACE \ + BUS_PEER_INTERFACE \ + BUS_INTROSPECTABLE_INTERFACE \ + "\n" + +#define INTERFACES_LIST \ + BUS_UNIT_INTERFACES_LIST \ + "org.freedesktop.systemd1.Service\0" + +const char bus_service_interface[] _introspect_("Service") = BUS_SERVICE_INTERFACE; + +const char bus_service_invalidating_properties[] = + "ExecStartPre\0" + "ExecStart\0" + "ExecStartPost\0" + "ExecReload\0" + "ExecStop\0" + "ExecStopPost\0" + "ExecMain\0" + "WatchdogTimestamp\0" + "WatchdogTimestampMonotonic\0" + "MainPID\0" + "ControlPID\0" + "StatusText\0" + "Result\0"; + +static DEFINE_BUS_PROPERTY_APPEND_ENUM(bus_service_append_type, service_type, ServiceType); +static DEFINE_BUS_PROPERTY_APPEND_ENUM(bus_service_append_restart, service_restart, ServiceRestart); +static DEFINE_BUS_PROPERTY_APPEND_ENUM(bus_service_append_notify_access, notify_access, NotifyAccess); +static DEFINE_BUS_PROPERTY_APPEND_ENUM(bus_service_append_service_result, service_result, ServiceResult); +static DEFINE_BUS_PROPERTY_APPEND_ENUM(bus_service_append_start_limit_action, start_limit_action, StartLimitAction); +static DEFINE_BUS_PROPERTY_SET_ENUM(bus_service_set_start_limit_action, start_limit_action, StartLimitAction); + +static const BusProperty bus_exec_main_status_properties[] = { + { "ExecMainStartTimestamp", bus_property_append_usec, "t", offsetof(ExecStatus, start_timestamp.realtime) }, + { "ExecMainStartTimestampMonotonic",bus_property_append_usec, "t", offsetof(ExecStatus, start_timestamp.monotonic) }, + { "ExecMainExitTimestamp", bus_property_append_usec, "t", offsetof(ExecStatus, start_timestamp.realtime) }, + { "ExecMainExitTimestampMonotonic", bus_property_append_usec, "t", offsetof(ExecStatus, start_timestamp.monotonic) }, + { "ExecMainPID", bus_property_append_pid, "u", offsetof(ExecStatus, pid) }, + { "ExecMainCode", bus_property_append_int, "i", offsetof(ExecStatus, code) }, + { "ExecMainStatus", bus_property_append_int, "i", offsetof(ExecStatus, status) }, + { NULL, } +}; + +static const BusProperty bus_service_properties[] = { + { "Type", bus_service_append_type, "s", offsetof(Service, type) }, + { "Restart", bus_service_append_restart, "s", offsetof(Service, restart) }, + { "PIDFile", bus_property_append_string, "s", offsetof(Service, pid_file), true }, + { "NotifyAccess", bus_service_append_notify_access, "s", offsetof(Service, notify_access) }, + { "RestartUSec", bus_property_append_usec, "t", offsetof(Service, restart_usec) }, + { "TimeoutUSec", bus_property_append_usec, "t", offsetof(Service, timeout_usec) }, + { "WatchdogUSec", bus_property_append_usec, "t", offsetof(Service, watchdog_usec) }, + { "WatchdogTimestamp", bus_property_append_usec, "t", offsetof(Service, watchdog_timestamp.realtime) }, + { "WatchdogTimestampMonotonic",bus_property_append_usec, "t", offsetof(Service, watchdog_timestamp.monotonic) }, + { "StartLimitInterval", bus_property_append_usec, "t", offsetof(Service, start_limit.interval) }, + { "StartLimitBurst", bus_property_append_uint32, "u", offsetof(Service, start_limit.burst) }, + { "StartLimitAction", bus_service_append_start_limit_action,"s", offsetof(Service, start_limit_action), false, bus_service_set_start_limit_action}, + BUS_EXEC_COMMAND_PROPERTY("ExecStartPre", offsetof(Service, exec_command[SERVICE_EXEC_START_PRE]), true ), + BUS_EXEC_COMMAND_PROPERTY("ExecStart", offsetof(Service, exec_command[SERVICE_EXEC_START]), true ), + BUS_EXEC_COMMAND_PROPERTY("ExecStartPost", offsetof(Service, exec_command[SERVICE_EXEC_START_POST]), true ), + BUS_EXEC_COMMAND_PROPERTY("ExecReload", offsetof(Service, exec_command[SERVICE_EXEC_RELOAD]), true ), + BUS_EXEC_COMMAND_PROPERTY("ExecStop", offsetof(Service, exec_command[SERVICE_EXEC_STOP]), true ), + BUS_EXEC_COMMAND_PROPERTY("ExecStopPost", offsetof(Service, exec_command[SERVICE_EXEC_STOP_POST]), true ), + { "PermissionsStartOnly", bus_property_append_bool, "b", offsetof(Service, permissions_start_only) }, + { "RootDirectoryStartOnly", bus_property_append_bool, "b", offsetof(Service, root_directory_start_only) }, + { "RemainAfterExit", bus_property_append_bool, "b", offsetof(Service, remain_after_exit) }, + { "GuessMainPID", bus_property_append_bool, "b", offsetof(Service, guess_main_pid) }, + { "MainPID", bus_property_append_pid, "u", offsetof(Service, main_pid) }, + { "ControlPID", bus_property_append_pid, "u", offsetof(Service, control_pid) }, + { "BusName", bus_property_append_string, "s", offsetof(Service, bus_name), true }, + { "StatusText", bus_property_append_string, "s", offsetof(Service, status_text), true }, +#ifdef HAVE_SYSV_COMPAT + { "SysVRunLevels", bus_property_append_string, "s", offsetof(Service, sysv_runlevels), true }, + { "SysVStartPriority", bus_property_append_int, "i", offsetof(Service, sysv_start_priority) }, + { "SysVPath", bus_property_append_string, "s", offsetof(Service, sysv_path), true }, +#endif + { "FsckPassNo", bus_property_append_int, "i", offsetof(Service, fsck_passno) }, + { "Result", bus_service_append_service_result,"s", offsetof(Service, result) }, + { NULL, } +}; + +DBusHandlerResult bus_service_message_handler(Unit *u, DBusConnection *connection, DBusMessage *message) { + Service *s = SERVICE(u); + const BusBoundProperties bps[] = { + { "org.freedesktop.systemd1.Unit", bus_unit_properties, u }, + { "org.freedesktop.systemd1.Service", bus_service_properties, s }, + { "org.freedesktop.systemd1.Service", bus_exec_context_properties, &s->exec_context }, + { "org.freedesktop.systemd1.Service", bus_exec_main_status_properties, &s->main_exec_status }, + { NULL, } + }; + + return bus_default_message_handler(connection, message, INTROSPECTION, INTERFACES_LIST, bps); +} diff --git a/src/core/dbus-service.h b/src/core/dbus-service.h new file mode 100644 index 0000000000..d6eab65c52 --- /dev/null +++ b/src/core/dbus-service.h @@ -0,0 +1,34 @@ +/*-*- Mode: C; c-basic-offset: 8; indent-tabs-mode: nil -*-*/ + +#ifndef foodbusservicehfoo +#define foodbusservicehfoo + +/*** + This file is part of systemd. + + Copyright 2010 Lennart Poettering + + systemd is free software; you can redistribute it and/or modify it + under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + systemd is distributed in the hope that 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 General Public License + along with systemd; If not, see . +***/ + +#include + +#include "unit.h" + +DBusHandlerResult bus_service_message_handler(Unit *u, DBusConnection *c, DBusMessage *message); + +extern const char bus_service_interface[]; +extern const char bus_service_invalidating_properties[]; + +#endif diff --git a/src/core/dbus-snapshot.c b/src/core/dbus-snapshot.c new file mode 100644 index 0000000000..e69388a524 --- /dev/null +++ b/src/core/dbus-snapshot.c @@ -0,0 +1,93 @@ +/*-*- Mode: C; c-basic-offset: 8; indent-tabs-mode: nil -*-*/ + +/*** + This file is part of systemd. + + Copyright 2010 Lennart Poettering + + systemd is free software; you can redistribute it and/or modify it + under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + systemd is distributed in the hope that 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 General Public License + along with systemd; If not, see . +***/ + +#include "dbus-unit.h" +#include "dbus-snapshot.h" +#include "dbus-common.h" + +#define BUS_SNAPSHOT_INTERFACE \ + " \n" \ + " \n" \ + " \n" \ + " \n" + +#define INTROSPECTION \ + DBUS_INTROSPECT_1_0_XML_DOCTYPE_DECL_NODE \ + "\n" \ + BUS_UNIT_INTERFACE \ + BUS_SNAPSHOT_INTERFACE \ + BUS_PROPERTIES_INTERFACE \ + BUS_PEER_INTERFACE \ + BUS_INTROSPECTABLE_INTERFACE \ + "\n" + +#define INTERFACES_LIST \ + BUS_UNIT_INTERFACES_LIST \ + "org.freedesktop.systemd1.Snapshot\0" + +const char bus_snapshot_interface[] _introspect_("Snapshot") = BUS_SNAPSHOT_INTERFACE; + +static const BusProperty bus_snapshot_properties[] = { + { "Cleanup", bus_property_append_bool, "b", offsetof(Snapshot, cleanup) }, + { NULL, } +}; + +DBusHandlerResult bus_snapshot_message_handler(Unit *u, DBusConnection *c, DBusMessage *message) { + Snapshot *s = SNAPSHOT(u); + + DBusMessage *reply = NULL; + DBusError error; + + dbus_error_init(&error); + + if (dbus_message_is_method_call(message, "org.freedesktop.systemd1.Snapshot", "Remove")) { + + snapshot_remove(SNAPSHOT(u)); + + if (!(reply = dbus_message_new_method_return(message))) + goto oom; + + } else { + const BusBoundProperties bps[] = { + { "org.freedesktop.systemd1.Unit", bus_unit_properties, u }, + { "org.freedesktop.systemd1.Snapshot", bus_snapshot_properties, s }, + { NULL, } + }; + return bus_default_message_handler(c, message, INTROSPECTION, INTERFACES_LIST, bps); + } + + if (reply) { + if (!dbus_connection_send(c, reply, NULL)) + goto oom; + + dbus_message_unref(reply); + } + + return DBUS_HANDLER_RESULT_HANDLED; + +oom: + if (reply) + dbus_message_unref(reply); + + dbus_error_free(&error); + + return DBUS_HANDLER_RESULT_NEED_MEMORY; +} diff --git a/src/core/dbus-snapshot.h b/src/core/dbus-snapshot.h new file mode 100644 index 0000000000..0b82279f1c --- /dev/null +++ b/src/core/dbus-snapshot.h @@ -0,0 +1,33 @@ +/*-*- Mode: C; c-basic-offset: 8; indent-tabs-mode: nil -*-*/ + +#ifndef foodbussnapshothfoo +#define foodbussnapshothfoo + +/*** + This file is part of systemd. + + Copyright 2010 Lennart Poettering + + systemd is free software; you can redistribute it and/or modify it + under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + systemd is distributed in the hope that 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 General Public License + along with systemd; If not, see . +***/ + +#include + +#include "unit.h" + +DBusHandlerResult bus_snapshot_message_handler(Unit *u, DBusConnection *c, DBusMessage *message); + +extern const char bus_snapshot_interface[]; + +#endif diff --git a/src/core/dbus-socket.c b/src/core/dbus-socket.c new file mode 100644 index 0000000000..2e3342cb55 --- /dev/null +++ b/src/core/dbus-socket.c @@ -0,0 +1,139 @@ +/*-*- Mode: C; c-basic-offset: 8; indent-tabs-mode: nil -*-*/ + +/*** + This file is part of systemd. + + Copyright 2010 Lennart Poettering + + systemd is free software; you can redistribute it and/or modify it + under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + systemd is distributed in the hope that 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 General Public License + along with systemd; If not, see . +***/ + +#include + +#include "dbus-unit.h" +#include "dbus-socket.h" +#include "dbus-execute.h" +#include "dbus-common.h" + +#define BUS_SOCKET_INTERFACE \ + " \n" \ + " \n" \ + " \n" \ + " \n" \ + BUS_EXEC_COMMAND_INTERFACE("ExecStartPre") \ + BUS_EXEC_COMMAND_INTERFACE("ExecStartPost") \ + BUS_EXEC_COMMAND_INTERFACE("ExecStopPre") \ + BUS_EXEC_COMMAND_INTERFACE("ExecStopPost") \ + BUS_EXEC_CONTEXT_INTERFACE \ + " \n" \ + " \n" \ + " \n" \ + " \n" \ + " \n" \ + " \n" \ + " \n" \ + " \n" \ + " \n" \ + " \n" \ + " \n" \ + " \n" \ + " \n" \ + " \n" \ + " \n" \ + " \n" \ + " \n" \ + " \n" \ + " \n" \ + " \n" \ + " \n" \ + " \n" \ + " \n" \ + " \n" \ + " \n" \ + +#define INTROSPECTION \ + DBUS_INTROSPECT_1_0_XML_DOCTYPE_DECL_NODE \ + "\n" \ + BUS_UNIT_INTERFACE \ + BUS_SOCKET_INTERFACE \ + BUS_PROPERTIES_INTERFACE \ + BUS_PEER_INTERFACE \ + BUS_INTROSPECTABLE_INTERFACE \ + "\n" + +#define INTERFACES_LIST \ + BUS_UNIT_INTERFACES_LIST \ + "org.freedesktop.systemd1.Socket\0" + +const char bus_socket_interface[] _introspect_("Socket") = BUS_SOCKET_INTERFACE; + +const char bus_socket_invalidating_properties[] = + "ExecStartPre\0" + "ExecStartPost\0" + "ExecStopPre\0" + "ExecStopPost\0" + "ControlPID\0" + "NAccepted\0" + "NConnections\0" + "Result\0"; + +static DEFINE_BUS_PROPERTY_APPEND_ENUM(bus_socket_append_bind_ipv6_only, socket_address_bind_ipv6_only, SocketAddressBindIPv6Only); +static DEFINE_BUS_PROPERTY_APPEND_ENUM(bus_socket_append_socket_result, socket_result, SocketResult); + +static const BusProperty bus_socket_properties[] = { + { "BindIPv6Only", bus_socket_append_bind_ipv6_only, "s", offsetof(Socket, bind_ipv6_only) }, + { "Backlog", bus_property_append_unsigned, "u", offsetof(Socket, backlog) }, + { "TimeoutUSec", bus_property_append_usec, "t", offsetof(Socket, timeout_usec) }, + BUS_EXEC_COMMAND_PROPERTY("ExecStartPre", offsetof(Socket, exec_command[SOCKET_EXEC_START_PRE]), true ), + BUS_EXEC_COMMAND_PROPERTY("ExecStartPost", offsetof(Socket, exec_command[SOCKET_EXEC_START_POST]), true ), + BUS_EXEC_COMMAND_PROPERTY("ExecStopPre", offsetof(Socket, exec_command[SOCKET_EXEC_STOP_PRE]), true ), + BUS_EXEC_COMMAND_PROPERTY("ExecStopPost", offsetof(Socket, exec_command[SOCKET_EXEC_STOP_POST]), true ), + { "ControlPID", bus_property_append_pid, "u", offsetof(Socket, control_pid) }, + { "BindToDevice", bus_property_append_string, "s", offsetof(Socket, bind_to_device), true }, + { "DirectoryMode", bus_property_append_mode, "u", offsetof(Socket, directory_mode) }, + { "SocketMode", bus_property_append_mode, "u", offsetof(Socket, socket_mode) }, + { "Accept", bus_property_append_bool, "b", offsetof(Socket, accept) }, + { "KeepAlive", bus_property_append_bool, "b", offsetof(Socket, keep_alive) }, + { "Priority", bus_property_append_int, "i", offsetof(Socket, priority) }, + { "ReceiveBuffer", bus_property_append_size, "t", offsetof(Socket, receive_buffer) }, + { "SendBuffer", bus_property_append_size, "t", offsetof(Socket, send_buffer) }, + { "IPTOS", bus_property_append_int, "i", offsetof(Socket, ip_tos) }, + { "IPTTL", bus_property_append_int, "i", offsetof(Socket, ip_ttl) }, + { "PipeSize", bus_property_append_size, "t", offsetof(Socket, pipe_size) }, + { "FreeBind", bus_property_append_bool, "b", offsetof(Socket, free_bind) }, + { "Transparent", bus_property_append_bool, "b", offsetof(Socket, transparent) }, + { "Broadcast", bus_property_append_bool, "b", offsetof(Socket, broadcast) }, + { "PassCredentials",bus_property_append_bool, "b", offsetof(Socket, pass_cred) }, + { "PassSecurity", bus_property_append_bool, "b", offsetof(Socket, pass_sec) }, + { "Mark", bus_property_append_int, "i", offsetof(Socket, mark) }, + { "MaxConnections", bus_property_append_unsigned, "u", offsetof(Socket, max_connections) }, + { "NConnections", bus_property_append_unsigned, "u", offsetof(Socket, n_connections) }, + { "NAccepted", bus_property_append_unsigned, "u", offsetof(Socket, n_accepted) }, + { "MessageQueueMaxMessages", bus_property_append_long, "x", offsetof(Socket, mq_maxmsg) }, + { "MessageQueueMessageSize", bus_property_append_long, "x", offsetof(Socket, mq_msgsize) }, + { "Result", bus_socket_append_socket_result, "s", offsetof(Socket, result) }, + { NULL, } +}; + +DBusHandlerResult bus_socket_message_handler(Unit *u, DBusConnection *c, DBusMessage *message) { + Socket *s = SOCKET(u); + const BusBoundProperties bps[] = { + { "org.freedesktop.systemd1.Unit", bus_unit_properties, u }, + { "org.freedesktop.systemd1.Socket", bus_socket_properties, s }, + { "org.freedesktop.systemd1.Socket", bus_exec_context_properties, &s->exec_context }, + { NULL, } + }; + + return bus_default_message_handler(c, message, INTROSPECTION, INTERFACES_LIST, bps); +} diff --git a/src/core/dbus-socket.h b/src/core/dbus-socket.h new file mode 100644 index 0000000000..069a2f5df0 --- /dev/null +++ b/src/core/dbus-socket.h @@ -0,0 +1,34 @@ +/*-*- Mode: C; c-basic-offset: 8; indent-tabs-mode: nil -*-*/ + +#ifndef foodbussockethfoo +#define foodbussockethfoo + +/*** + This file is part of systemd. + + Copyright 2010 Lennart Poettering + + systemd is free software; you can redistribute it and/or modify it + under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + systemd is distributed in the hope that 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 General Public License + along with systemd; If not, see . +***/ + +#include + +#include "unit.h" + +DBusHandlerResult bus_socket_message_handler(Unit *u, DBusConnection *c, DBusMessage *message); + +extern const char bus_socket_interface[]; +extern const char bus_socket_invalidating_properties[]; + +#endif diff --git a/src/core/dbus-swap.c b/src/core/dbus-swap.c new file mode 100644 index 0000000000..09cd1e8b9c --- /dev/null +++ b/src/core/dbus-swap.c @@ -0,0 +1,111 @@ +/*-*- Mode: C; c-basic-offset: 8; indent-tabs-mode: nil -*-*/ + +/*** + 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 General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + systemd is distributed in the hope that 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 General Public License + along with systemd; If not, see . +***/ + +#include + +#include "dbus-unit.h" +#include "dbus-swap.h" +#include "dbus-execute.h" +#include "dbus-common.h" + +#define BUS_SWAP_INTERFACE \ + " \n" \ + " \n" \ + " \n" \ + " \n" \ + BUS_EXEC_COMMAND_INTERFACE("ExecActivate") \ + BUS_EXEC_COMMAND_INTERFACE("ExecDeactivate") \ + BUS_EXEC_CONTEXT_INTERFACE \ + " \n" \ + " \n" \ + " \n" + +#define INTROSPECTION \ + DBUS_INTROSPECT_1_0_XML_DOCTYPE_DECL_NODE \ + "\n" \ + BUS_UNIT_INTERFACE \ + BUS_SWAP_INTERFACE \ + BUS_PROPERTIES_INTERFACE \ + BUS_PEER_INTERFACE \ + BUS_INTROSPECTABLE_INTERFACE \ + "\n" + +#define INTERFACES_LIST \ + BUS_UNIT_INTERFACES_LIST \ + "org.freedesktop.systemd1.Swap\0" + +const char bus_swap_interface[] _introspect_("Swap") = BUS_SWAP_INTERFACE; + +const char bus_swap_invalidating_properties[] = + "What\0" + "Priority\0" + "ExecActivate\0" + "ExecDeactivate\0" + "ControlPID\0" + "Result\0"; + +static int bus_swap_append_priority(DBusMessageIter *i, const char *property, void *data) { + Swap *s = data; + dbus_int32_t j; + + assert(i); + assert(property); + assert(s); + + if (s->from_proc_swaps) + j = s->parameters_proc_swaps.priority; + else if (s->from_fragment) + j = s->parameters_fragment.priority; + else if (s->from_etc_fstab) + j = s->parameters_etc_fstab.priority; + else + j = -1; + + if (!dbus_message_iter_append_basic(i, DBUS_TYPE_INT32, &j)) + return -ENOMEM; + + return 0; +} + +static DEFINE_BUS_PROPERTY_APPEND_ENUM(bus_swap_append_swap_result, swap_result, SwapResult); + +static const BusProperty bus_swap_properties[] = { + { "What", bus_property_append_string, "s", offsetof(Swap, what), true }, + { "Priority", bus_swap_append_priority, "i", 0 }, + BUS_EXEC_COMMAND_PROPERTY("ExecActivate", offsetof(Swap, exec_command[SWAP_EXEC_ACTIVATE]), false), + BUS_EXEC_COMMAND_PROPERTY("ExecDeactivate", offsetof(Swap, exec_command[SWAP_EXEC_DEACTIVATE]), false), + { "ControlPID", bus_property_append_pid, "u", offsetof(Swap, control_pid) }, + { "Result", bus_swap_append_swap_result,"s", offsetof(Swap, result) }, + { NULL, } +}; + +DBusHandlerResult bus_swap_message_handler(Unit *u, DBusConnection *c, DBusMessage *message) { + Swap *s = SWAP(u); + const BusBoundProperties bps[] = { + { "org.freedesktop.systemd1.Unit", bus_unit_properties, u }, + { "org.freedesktop.systemd1.Swap", bus_swap_properties, s }, + { "org.freedesktop.systemd1.Swap", bus_exec_context_properties, &s->exec_context }, + { NULL, } + }; + + return bus_default_message_handler(c, message, INTROSPECTION, INTERFACES_LIST, bps); +} diff --git a/src/core/dbus-swap.h b/src/core/dbus-swap.h new file mode 100644 index 0000000000..15b914726b --- /dev/null +++ b/src/core/dbus-swap.h @@ -0,0 +1,35 @@ +/*-*- Mode: C; c-basic-offset: 8; indent-tabs-mode: nil -*-*/ + +#ifndef foodbusswaphfoo +#define foodbusswaphfoo + +/*** + 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 General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + systemd is distributed in the hope that 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 General Public License + along with systemd; If not, see . +***/ + +#include + +#include "unit.h" + +DBusHandlerResult bus_swap_message_handler(Unit *u, DBusConnection *c, DBusMessage *message); + +extern const char bus_swap_interface[]; +extern const char bus_swap_invalidating_properties[]; + +#endif diff --git a/src/core/dbus-target.c b/src/core/dbus-target.c new file mode 100644 index 0000000000..55cf862de0 --- /dev/null +++ b/src/core/dbus-target.c @@ -0,0 +1,55 @@ +/*-*- Mode: C; c-basic-offset: 8; indent-tabs-mode: nil -*-*/ + +/*** + This file is part of systemd. + + Copyright 2010 Lennart Poettering + + systemd is free software; you can redistribute it and/or modify it + under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + systemd is distributed in the hope that 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 General Public License + along with systemd; If not, see . +***/ + +#include + +#include "dbus-unit.h" +#include "dbus-target.h" +#include "dbus-common.h" + +#define BUS_TARGET_INTERFACE \ + " \n" \ + " \n" + +#define INTROSPECTION \ + DBUS_INTROSPECT_1_0_XML_DOCTYPE_DECL_NODE \ + "\n" \ + BUS_UNIT_INTERFACE \ + BUS_TARGET_INTERFACE \ + BUS_PROPERTIES_INTERFACE \ + BUS_PEER_INTERFACE \ + BUS_INTROSPECTABLE_INTERFACE \ + "\n" + +#define INTERFACES_LIST \ + BUS_UNIT_INTERFACES_LIST \ + "org.freedesktop.systemd1.Target\0" + +const char bus_target_interface[] _introspect_("Target") = BUS_TARGET_INTERFACE; + +DBusHandlerResult bus_target_message_handler(Unit *u, DBusConnection *c, DBusMessage *message) { + const BusBoundProperties bps[] = { + { "org.freedesktop.systemd1.Unit", bus_unit_properties, u }, + { NULL, } + }; + + return bus_default_message_handler(c, message, INTROSPECTION, INTERFACES_LIST, bps); +} diff --git a/src/core/dbus-target.h b/src/core/dbus-target.h new file mode 100644 index 0000000000..13d38764a2 --- /dev/null +++ b/src/core/dbus-target.h @@ -0,0 +1,33 @@ +/*-*- Mode: C; c-basic-offset: 8; indent-tabs-mode: nil -*-*/ + +#ifndef foodbustargethfoo +#define foodbustargethfoo + +/*** + This file is part of systemd. + + Copyright 2010 Lennart Poettering + + systemd is free software; you can redistribute it and/or modify it + under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + systemd is distributed in the hope that 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 General Public License + along with systemd; If not, see . +***/ + +#include + +#include "unit.h" + +DBusHandlerResult bus_target_message_handler(Unit *u, DBusConnection *c, DBusMessage *message); + +extern const char bus_target_interface[]; + +#endif diff --git a/src/core/dbus-timer.c b/src/core/dbus-timer.c new file mode 100644 index 0000000000..b396aed047 --- /dev/null +++ b/src/core/dbus-timer.c @@ -0,0 +1,137 @@ +/*-*- Mode: C; c-basic-offset: 8; indent-tabs-mode: nil -*-*/ + +/*** + This file is part of systemd. + + Copyright 2010 Lennart Poettering + + systemd is free software; you can redistribute it and/or modify it + under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + systemd is distributed in the hope that 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 General Public License + along with systemd; If not, see . +***/ + +#include + +#include "dbus-unit.h" +#include "dbus-timer.h" +#include "dbus-execute.h" +#include "dbus-common.h" + +#define BUS_TIMER_INTERFACE \ + " \n" \ + " \n" \ + " \n" \ + " \n" \ + " \n" \ + " \n" + +#define INTROSPECTION \ + DBUS_INTROSPECT_1_0_XML_DOCTYPE_DECL_NODE \ + "\n" \ + BUS_UNIT_INTERFACE \ + BUS_TIMER_INTERFACE \ + BUS_PROPERTIES_INTERFACE \ + BUS_PEER_INTERFACE \ + BUS_INTROSPECTABLE_INTERFACE \ + "\n" + +#define INTERFACES_LIST \ + BUS_UNIT_INTERFACES_LIST \ + "org.freedesktop.systemd1.Timer\0" + +const char bus_timer_interface[] _introspect_("Timer") = BUS_TIMER_INTERFACE; + +const char bus_timer_invalidating_properties[] = + "Timers\0" + "NextElapseUSec\0" + "Result\0"; + +static int bus_timer_append_timers(DBusMessageIter *i, const char *property, void *data) { + Timer *p = data; + DBusMessageIter sub, sub2; + TimerValue *k; + + assert(i); + assert(property); + assert(p); + + if (!dbus_message_iter_open_container(i, DBUS_TYPE_ARRAY, "(stt)", &sub)) + return -ENOMEM; + + LIST_FOREACH(value, k, p->values) { + char *buf; + const char *t; + size_t l; + bool b; + + t = timer_base_to_string(k->base); + assert(endswith(t, "Sec")); + + /* s/Sec/USec/ */ + l = strlen(t); + if (!(buf = new(char, l+2))) + return -ENOMEM; + + memcpy(buf, t, l-3); + memcpy(buf+l-3, "USec", 5); + + b = dbus_message_iter_open_container(&sub, DBUS_TYPE_STRUCT, NULL, &sub2) && + dbus_message_iter_append_basic(&sub2, DBUS_TYPE_STRING, &buf) && + dbus_message_iter_append_basic(&sub2, DBUS_TYPE_UINT64, &k->value) && + dbus_message_iter_append_basic(&sub2, DBUS_TYPE_UINT64, &k->next_elapse) && + dbus_message_iter_close_container(&sub, &sub2); + + free(buf); + if (!b) + return -ENOMEM; + } + + if (!dbus_message_iter_close_container(i, &sub)) + return -ENOMEM; + + return 0; +} + +static int bus_timer_append_unit(DBusMessageIter *i, const char *property, void *data) { + Unit *u = data; + Timer *timer = TIMER(u); + const char *t; + + assert(i); + assert(property); + assert(u); + + t = UNIT_DEREF(timer->unit) ? UNIT_DEREF(timer->unit)->id : ""; + + return dbus_message_iter_append_basic(i, DBUS_TYPE_STRING, &t) ? 0 : -ENOMEM; +} + +static DEFINE_BUS_PROPERTY_APPEND_ENUM(bus_timer_append_timer_result, timer_result, TimerResult); + +static const BusProperty bus_timer_properties[] = { + { "Unit", bus_timer_append_unit, "s", 0 }, + { "Timers", bus_timer_append_timers, "a(stt)", 0 }, + { "NextElapseUSec", bus_property_append_usec, "t", offsetof(Timer, next_elapse) }, + { "Result", bus_timer_append_timer_result,"s", offsetof(Timer, result) }, + { NULL, } +}; + +DBusHandlerResult bus_timer_message_handler(Unit *u, DBusConnection *c, DBusMessage *message) { + Timer *t = TIMER(u); + const BusBoundProperties bps[] = { + { "org.freedesktop.systemd1.Unit", bus_unit_properties, u }, + { "org.freedesktop.systemd1.Timer", bus_timer_properties, t }, + { NULL, } + }; + + return bus_default_message_handler(c, message, INTROSPECTION, INTERFACES_LIST, bps); +} diff --git a/src/core/dbus-timer.h b/src/core/dbus-timer.h new file mode 100644 index 0000000000..e692e12fcb --- /dev/null +++ b/src/core/dbus-timer.h @@ -0,0 +1,34 @@ +/*-*- Mode: C; c-basic-offset: 8; indent-tabs-mode: nil -*-*/ + +#ifndef foodbustimerhfoo +#define foodbustimerhfoo + +/*** + This file is part of systemd. + + Copyright 2010 Lennart Poettering + + systemd is free software; you can redistribute it and/or modify it + under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + systemd is distributed in the hope that 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 General Public License + along with systemd; If not, see . +***/ + +#include + +#include "unit.h" + +DBusHandlerResult bus_timer_message_handler(Unit *u, DBusConnection *c, DBusMessage *message); + +extern const char bus_timer_interface[]; +extern const char bus_timer_invalidating_properties[]; + +#endif diff --git a/src/core/dbus-unit.c b/src/core/dbus-unit.c new file mode 100644 index 0000000000..c7532c7255 --- /dev/null +++ b/src/core/dbus-unit.c @@ -0,0 +1,850 @@ +/*-*- Mode: C; c-basic-offset: 8; indent-tabs-mode: nil -*-*/ + +/*** + This file is part of systemd. + + Copyright 2010 Lennart Poettering + + systemd is free software; you can redistribute it and/or modify it + under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + systemd is distributed in the hope that 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 General Public License + along with systemd; If not, see . +***/ + +#include + +#include "dbus.h" +#include "log.h" +#include "dbus-unit.h" +#include "bus-errors.h" +#include "dbus-common.h" + +const char bus_unit_interface[] _introspect_("Unit") = BUS_UNIT_INTERFACE; + +#define INVALIDATING_PROPERTIES \ + "LoadState\0" \ + "ActiveState\0" \ + "SubState\0" \ + "InactiveExitTimestamp\0" \ + "ActiveEnterTimestamp\0" \ + "ActiveExitTimestamp\0" \ + "InactiveEnterTimestamp\0" \ + "Job\0" \ + "NeedDaemonReload\0" + +static int bus_unit_append_names(DBusMessageIter *i, const char *property, void *data) { + char *t; + Iterator j; + DBusMessageIter sub; + Unit *u = data; + + if (!dbus_message_iter_open_container(i, DBUS_TYPE_ARRAY, "s", &sub)) + return -ENOMEM; + + SET_FOREACH(t, u->names, j) + if (!dbus_message_iter_append_basic(&sub, DBUS_TYPE_STRING, &t)) + return -ENOMEM; + + if (!dbus_message_iter_close_container(i, &sub)) + return -ENOMEM; + + return 0; +} + +static int bus_unit_append_following(DBusMessageIter *i, const char *property, void *data) { + Unit *u = data, *f; + const char *d; + + assert(i); + assert(property); + assert(u); + + f = unit_following(u); + d = f ? f->id : ""; + + if (!dbus_message_iter_append_basic(i, DBUS_TYPE_STRING, &d)) + return -ENOMEM; + + return 0; +} + +static int bus_unit_append_dependencies(DBusMessageIter *i, const char *property, void *data) { + Unit *u; + Iterator j; + DBusMessageIter sub; + Set *s = data; + + if (!dbus_message_iter_open_container(i, DBUS_TYPE_ARRAY, "s", &sub)) + return -ENOMEM; + + SET_FOREACH(u, s, j) + if (!dbus_message_iter_append_basic(&sub, DBUS_TYPE_STRING, &u->id)) + return -ENOMEM; + + if (!dbus_message_iter_close_container(i, &sub)) + return -ENOMEM; + + return 0; +} + +static int bus_unit_append_description(DBusMessageIter *i, const char *property, void *data) { + Unit *u = data; + const char *d; + + assert(i); + assert(property); + assert(u); + + d = unit_description(u); + + if (!dbus_message_iter_append_basic(i, DBUS_TYPE_STRING, &d)) + return -ENOMEM; + + return 0; +} + +static DEFINE_BUS_PROPERTY_APPEND_ENUM(bus_unit_append_load_state, unit_load_state, UnitLoadState); + +static int bus_unit_append_active_state(DBusMessageIter *i, const char *property, void *data) { + Unit *u = data; + const char *state; + + assert(i); + assert(property); + assert(u); + + state = unit_active_state_to_string(unit_active_state(u)); + + if (!dbus_message_iter_append_basic(i, DBUS_TYPE_STRING, &state)) + return -ENOMEM; + + return 0; +} + +static int bus_unit_append_sub_state(DBusMessageIter *i, const char *property, void *data) { + Unit *u = data; + const char *state; + + assert(i); + assert(property); + assert(u); + + state = unit_sub_state_to_string(u); + + if (!dbus_message_iter_append_basic(i, DBUS_TYPE_STRING, &state)) + return -ENOMEM; + + return 0; +} + +static int bus_unit_append_file_state(DBusMessageIter *i, const char *property, void *data) { + Unit *u = data; + const char *state; + + assert(i); + assert(property); + assert(u); + + state = strempty(unit_file_state_to_string(unit_get_unit_file_state(u))); + + if (!dbus_message_iter_append_basic(i, DBUS_TYPE_STRING, &state)) + return -ENOMEM; + + return 0; +} + +static int bus_unit_append_can_start(DBusMessageIter *i, const char *property, void *data) { + Unit *u = data; + dbus_bool_t b; + + assert(i); + assert(property); + assert(u); + + b = unit_can_start(u) && + !u->refuse_manual_start; + + if (!dbus_message_iter_append_basic(i, DBUS_TYPE_BOOLEAN, &b)) + return -ENOMEM; + + return 0; +} + +static int bus_unit_append_can_stop(DBusMessageIter *i, const char *property, void *data) { + Unit *u = data; + dbus_bool_t b; + + assert(i); + assert(property); + assert(u); + + /* On the lower levels we assume that every unit we can start + * we can also stop */ + + b = unit_can_start(u) && + !u->refuse_manual_stop; + + if (!dbus_message_iter_append_basic(i, DBUS_TYPE_BOOLEAN, &b)) + return -ENOMEM; + + return 0; +} + +static int bus_unit_append_can_reload(DBusMessageIter *i, const char *property, void *data) { + Unit *u = data; + dbus_bool_t b; + + assert(i); + assert(property); + assert(u); + + b = unit_can_reload(u); + + if (!dbus_message_iter_append_basic(i, DBUS_TYPE_BOOLEAN, &b)) + return -ENOMEM; + + return 0; +} + +static int bus_unit_append_can_isolate(DBusMessageIter *i, const char *property, void *data) { + Unit *u = data; + dbus_bool_t b; + + assert(i); + assert(property); + assert(u); + + b = unit_can_isolate(u) && + !u->refuse_manual_start; + + if (!dbus_message_iter_append_basic(i, DBUS_TYPE_BOOLEAN, &b)) + return -ENOMEM; + + return 0; +} + +static int bus_unit_append_job(DBusMessageIter *i, const char *property, void *data) { + Unit *u = data; + DBusMessageIter sub; + char *p; + + assert(i); + assert(property); + assert(u); + + if (!dbus_message_iter_open_container(i, DBUS_TYPE_STRUCT, NULL, &sub)) + return -ENOMEM; + + if (u->job) { + + if (!(p = job_dbus_path(u->job))) + return -ENOMEM; + + if (!dbus_message_iter_append_basic(&sub, DBUS_TYPE_UINT32, &u->job->id) || + !dbus_message_iter_append_basic(&sub, DBUS_TYPE_OBJECT_PATH, &p)) { + free(p); + return -ENOMEM; + } + } else { + uint32_t id = 0; + + /* No job, so let's fill in some placeholder + * data. Since we need to fill in a valid path we + * simple point to ourselves. */ + + if (!(p = unit_dbus_path(u))) + return -ENOMEM; + + if (!dbus_message_iter_append_basic(&sub, DBUS_TYPE_UINT32, &id) || + !dbus_message_iter_append_basic(&sub, DBUS_TYPE_OBJECT_PATH, &p)) { + free(p); + return -ENOMEM; + } + } + + free(p); + + if (!dbus_message_iter_close_container(i, &sub)) + return -ENOMEM; + + return 0; +} + +static int bus_unit_append_default_cgroup(DBusMessageIter *i, const char *property, void *data) { + Unit *u = data; + char *t; + CGroupBonding *cgb; + bool success; + + assert(i); + assert(property); + assert(u); + + if ((cgb = unit_get_default_cgroup(u))) { + if (!(t = cgroup_bonding_to_string(cgb))) + return -ENOMEM; + } else + t = (char*) ""; + + success = dbus_message_iter_append_basic(i, DBUS_TYPE_STRING, &t); + + if (cgb) + free(t); + + return success ? 0 : -ENOMEM; +} + +static int bus_unit_append_cgroups(DBusMessageIter *i, const char *property, void *data) { + Unit *u = data; + CGroupBonding *cgb; + DBusMessageIter sub; + + if (!dbus_message_iter_open_container(i, DBUS_TYPE_ARRAY, "s", &sub)) + return -ENOMEM; + + LIST_FOREACH(by_unit, cgb, u->cgroup_bondings) { + char *t; + bool success; + + if (!(t = cgroup_bonding_to_string(cgb))) + return -ENOMEM; + + success = dbus_message_iter_append_basic(&sub, DBUS_TYPE_STRING, &t); + free(t); + + if (!success) + return -ENOMEM; + } + + if (!dbus_message_iter_close_container(i, &sub)) + return -ENOMEM; + + return 0; +} + +static int bus_unit_append_cgroup_attrs(DBusMessageIter *i, const char *property, void *data) { + Unit *u = data; + CGroupAttribute *a; + DBusMessageIter sub, sub2; + + if (!dbus_message_iter_open_container(i, DBUS_TYPE_ARRAY, "(sss)", &sub)) + return -ENOMEM; + + LIST_FOREACH(by_unit, a, u->cgroup_attributes) { + char *v = NULL; + bool success; + + if (a->map_callback) + a->map_callback(a->controller, a->name, a->value, &v); + + success = + dbus_message_iter_open_container(&sub, DBUS_TYPE_STRUCT, NULL, &sub2) && + dbus_message_iter_append_basic(&sub2, DBUS_TYPE_STRING, &a->controller) && + dbus_message_iter_append_basic(&sub2, DBUS_TYPE_STRING, &a->name) && + dbus_message_iter_append_basic(&sub2, DBUS_TYPE_STRING, v ? &v : &a->value) && + dbus_message_iter_close_container(&sub, &sub2); + + free(v); + + if (!success) + return -ENOMEM; + } + + if (!dbus_message_iter_close_container(i, &sub)) + return -ENOMEM; + + return 0; +} + +static int bus_unit_append_need_daemon_reload(DBusMessageIter *i, const char *property, void *data) { + Unit *u = data; + dbus_bool_t b; + + assert(i); + assert(property); + assert(u); + + b = unit_need_daemon_reload(u); + + if (!dbus_message_iter_append_basic(i, DBUS_TYPE_BOOLEAN, &b)) + return -ENOMEM; + + return 0; +} + +static int bus_unit_append_load_error(DBusMessageIter *i, const char *property, void *data) { + Unit *u = data; + const char *name, *message; + DBusMessageIter sub; + + assert(i); + assert(property); + assert(u); + + if (u->load_error != 0) { + name = bus_errno_to_dbus(u->load_error); + message = strempty(strerror(-u->load_error)); + } else + name = message = ""; + + if (!dbus_message_iter_open_container(i, DBUS_TYPE_STRUCT, NULL, &sub) || + !dbus_message_iter_append_basic(&sub, DBUS_TYPE_STRING, &name) || + !dbus_message_iter_append_basic(&sub, DBUS_TYPE_STRING, &message) || + !dbus_message_iter_close_container(i, &sub)) + return -ENOMEM; + + return 0; +} + +static DBusHandlerResult bus_unit_message_dispatch(Unit *u, DBusConnection *connection, DBusMessage *message) { + DBusMessage *reply = NULL; + Manager *m = u->manager; + DBusError error; + JobType job_type = _JOB_TYPE_INVALID; + char *path = NULL; + bool reload_if_possible = false; + + dbus_error_init(&error); + + if (dbus_message_is_method_call(message, "org.freedesktop.systemd1.Unit", "Start")) + job_type = JOB_START; + else if (dbus_message_is_method_call(message, "org.freedesktop.systemd1.Unit", "Stop")) + job_type = JOB_STOP; + else if (dbus_message_is_method_call(message, "org.freedesktop.systemd1.Unit", "Reload")) + job_type = JOB_RELOAD; + else if (dbus_message_is_method_call(message, "org.freedesktop.systemd1.Unit", "Restart")) + job_type = JOB_RESTART; + else if (dbus_message_is_method_call(message, "org.freedesktop.systemd1.Unit", "TryRestart")) + job_type = JOB_TRY_RESTART; + else if (dbus_message_is_method_call(message, "org.freedesktop.systemd1.Unit", "ReloadOrRestart")) { + reload_if_possible = true; + job_type = JOB_RESTART; + } else if (dbus_message_is_method_call(message, "org.freedesktop.systemd1.Unit", "ReloadOrTryRestart")) { + reload_if_possible = true; + job_type = JOB_TRY_RESTART; + } else if (dbus_message_is_method_call(message, "org.freedesktop.systemd1.Unit", "Kill")) { + const char *swho, *smode; + int32_t signo; + KillMode mode; + KillWho who; + int r; + + if (!dbus_message_get_args( + message, + &error, + DBUS_TYPE_STRING, &swho, + DBUS_TYPE_STRING, &smode, + DBUS_TYPE_INT32, &signo, + DBUS_TYPE_INVALID)) + return bus_send_error_reply(connection, message, &error, -EINVAL); + + if (isempty(swho)) + who = KILL_ALL; + else { + who = kill_who_from_string(swho); + if (who < 0) + return bus_send_error_reply(connection, message, &error, -EINVAL); + } + + if (isempty(smode)) + mode = KILL_CONTROL_GROUP; + else { + mode = kill_mode_from_string(smode); + if (mode < 0) + return bus_send_error_reply(connection, message, &error, -EINVAL); + } + + if (signo <= 0 || signo >= _NSIG) + return bus_send_error_reply(connection, message, &error, -EINVAL); + + if ((r = unit_kill(u, who, mode, signo, &error)) < 0) + return bus_send_error_reply(connection, message, &error, r); + + if (!(reply = dbus_message_new_method_return(message))) + goto oom; + + } else if (dbus_message_is_method_call(message, "org.freedesktop.systemd1.Unit", "ResetFailed")) { + + unit_reset_failed(u); + + if (!(reply = dbus_message_new_method_return(message))) + goto oom; + + } else if (UNIT_VTABLE(u)->bus_message_handler) + return UNIT_VTABLE(u)->bus_message_handler(u, connection, message); + else + return DBUS_HANDLER_RESULT_NOT_YET_HANDLED; + + if (job_type != _JOB_TYPE_INVALID) { + const char *smode; + JobMode mode; + Job *j; + int r; + + if ((job_type == JOB_START && u->refuse_manual_start) || + (job_type == JOB_STOP && u->refuse_manual_stop) || + ((job_type == JOB_RESTART || job_type == JOB_TRY_RESTART) && + (u->refuse_manual_start || u->refuse_manual_stop))) { + dbus_set_error(&error, BUS_ERROR_ONLY_BY_DEPENDENCY, "Operation refused, may be requested by dependency only."); + return bus_send_error_reply(connection, message, &error, -EPERM); + } + + if (!dbus_message_get_args( + message, + &error, + DBUS_TYPE_STRING, &smode, + DBUS_TYPE_INVALID)) + return bus_send_error_reply(connection, message, &error, -EINVAL); + + if (reload_if_possible && unit_can_reload(u)) { + if (job_type == JOB_RESTART) + job_type = JOB_RELOAD_OR_START; + else if (job_type == JOB_TRY_RESTART) + job_type = JOB_RELOAD; + } + + if ((mode = job_mode_from_string(smode)) == _JOB_MODE_INVALID) { + dbus_set_error(&error, BUS_ERROR_INVALID_JOB_MODE, "Job mode %s is invalid.", smode); + return bus_send_error_reply(connection, message, &error, -EINVAL); + } + + if ((r = manager_add_job(m, job_type, u, mode, true, &error, &j)) < 0) + return bus_send_error_reply(connection, message, &error, r); + + if (!(reply = dbus_message_new_method_return(message))) + goto oom; + + if (!(path = job_dbus_path(j))) + goto oom; + + if (!dbus_message_append_args( + reply, + DBUS_TYPE_OBJECT_PATH, &path, + DBUS_TYPE_INVALID)) + goto oom; + } + + if (reply) { + if (!dbus_connection_send(connection, reply, NULL)) + goto oom; + + dbus_message_unref(reply); + } + + free(path); + + return DBUS_HANDLER_RESULT_HANDLED; + +oom: + free(path); + + if (reply) + dbus_message_unref(reply); + + dbus_error_free(&error); + + return DBUS_HANDLER_RESULT_NEED_MEMORY; +} + +static DBusHandlerResult bus_unit_message_handler(DBusConnection *connection, DBusMessage *message, void *data) { + Manager *m = data; + Unit *u; + int r; + DBusMessage *reply; + + assert(connection); + assert(message); + assert(m); + + if (streq(dbus_message_get_path(message), "/org/freedesktop/systemd1/unit")) { + /* Be nice to gdbus and return introspection data for our mid-level paths */ + + if (dbus_message_is_method_call(message, "org.freedesktop.DBus.Introspectable", "Introspect")) { + char *introspection = NULL; + FILE *f; + Iterator i; + const char *k; + size_t size; + + if (!(reply = dbus_message_new_method_return(message))) + goto oom; + + /* We roll our own introspection code here, instead of + * relying on bus_default_message_handler() because we + * need to generate our introspection string + * dynamically. */ + + if (!(f = open_memstream(&introspection, &size))) + goto oom; + + fputs(DBUS_INTROSPECT_1_0_XML_DOCTYPE_DECL_NODE + "\n", f); + + fputs(BUS_INTROSPECTABLE_INTERFACE, f); + fputs(BUS_PEER_INTERFACE, f); + + HASHMAP_FOREACH_KEY(u, k, m->units, i) { + char *p; + + if (k != u->id) + continue; + + if (!(p = bus_path_escape(k))) { + fclose(f); + free(introspection); + goto oom; + } + + fprintf(f, "", p); + free(p); + } + + fputs("\n", f); + + if (ferror(f)) { + fclose(f); + free(introspection); + goto oom; + } + + fclose(f); + + if (!introspection) + goto oom; + + if (!dbus_message_append_args(reply, DBUS_TYPE_STRING, &introspection, DBUS_TYPE_INVALID)) { + free(introspection); + goto oom; + } + + free(introspection); + + if (!dbus_connection_send(connection, reply, NULL)) + goto oom; + + dbus_message_unref(reply); + + return DBUS_HANDLER_RESULT_HANDLED; + } + + return DBUS_HANDLER_RESULT_NOT_YET_HANDLED; + } + + if ((r = manager_get_unit_from_dbus_path(m, dbus_message_get_path(message), &u)) < 0) { + + if (r == -ENOMEM) + return DBUS_HANDLER_RESULT_NEED_MEMORY; + + if (r == -ENOENT) { + DBusError e; + + dbus_error_init(&e); + dbus_set_error_const(&e, DBUS_ERROR_UNKNOWN_OBJECT, "Unknown unit"); + return bus_send_error_reply(connection, message, &e, r); + } + + return bus_send_error_reply(connection, message, NULL, r); + } + + return bus_unit_message_dispatch(u, connection, message); + +oom: + if (reply) + dbus_message_unref(reply); + + return DBUS_HANDLER_RESULT_NEED_MEMORY; +} + +const DBusObjectPathVTable bus_unit_vtable = { + .message_function = bus_unit_message_handler +}; + +void bus_unit_send_change_signal(Unit *u) { + char *p = NULL; + DBusMessage *m = NULL; + + assert(u); + + if (u->in_dbus_queue) { + LIST_REMOVE(Unit, dbus_queue, u->manager->dbus_unit_queue, u); + u->in_dbus_queue = false; + } + + if (!u->id) + return; + + if (!bus_has_subscriber(u->manager)) { + u->sent_dbus_new_signal = true; + return; + } + + if (!(p = unit_dbus_path(u))) + goto oom; + + if (u->sent_dbus_new_signal) { + /* 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 + * behaviour if needed. */ + + if (UNIT_VTABLE(u)->bus_invalidating_properties) { + + if (!(m = bus_properties_changed_new(p, + UNIT_VTABLE(u)->bus_interface, + UNIT_VTABLE(u)->bus_invalidating_properties))) + goto oom; + + if (bus_broadcast(u->manager, m) < 0) + goto oom; + + dbus_message_unref(m); + } + + if (!(m = bus_properties_changed_new(p, "org.freedesktop.systemd1.Unit", INVALIDATING_PROPERTIES))) + goto oom; + + } else { + /* Send a new signal */ + + if (!(m = dbus_message_new_signal("/org/freedesktop/systemd1", "org.freedesktop.systemd1.Manager", "UnitNew"))) + goto oom; + + if (!dbus_message_append_args(m, + DBUS_TYPE_STRING, &u->id, + DBUS_TYPE_OBJECT_PATH, &p, + DBUS_TYPE_INVALID)) + goto oom; + } + + if (bus_broadcast(u->manager, m) < 0) + goto oom; + + free(p); + dbus_message_unref(m); + + u->sent_dbus_new_signal = true; + + return; + +oom: + free(p); + + if (m) + dbus_message_unref(m); + + log_error("Failed to allocate unit change/new signal."); +} + +void bus_unit_send_removed_signal(Unit *u) { + char *p = NULL; + DBusMessage *m = NULL; + + assert(u); + + if (!bus_has_subscriber(u->manager)) + return; + + if (!u->sent_dbus_new_signal) + bus_unit_send_change_signal(u); + + if (!u->id) + return; + + if (!(p = unit_dbus_path(u))) + goto oom; + + if (!(m = dbus_message_new_signal("/org/freedesktop/systemd1", "org.freedesktop.systemd1.Manager", "UnitRemoved"))) + goto oom; + + if (!dbus_message_append_args(m, + DBUS_TYPE_STRING, &u->id, + DBUS_TYPE_OBJECT_PATH, &p, + DBUS_TYPE_INVALID)) + goto oom; + + if (bus_broadcast(u->manager, m) < 0) + goto oom; + + free(p); + dbus_message_unref(m); + + return; + +oom: + free(p); + + if (m) + dbus_message_unref(m); + + log_error("Failed to allocate unit remove signal."); +} + +const BusProperty bus_unit_properties[] = { + { "Id", bus_property_append_string, "s", offsetof(Unit, id), true }, + { "Names", bus_unit_append_names, "as", 0 }, + { "Following", bus_unit_append_following, "s", 0 }, + { "Requires", bus_unit_append_dependencies, "as", offsetof(Unit, dependencies[UNIT_REQUIRES]), true }, + { "RequiresOverridable", bus_unit_append_dependencies, "as", offsetof(Unit, dependencies[UNIT_REQUIRES_OVERRIDABLE]), true }, + { "Requisite", bus_unit_append_dependencies, "as", offsetof(Unit, dependencies[UNIT_REQUISITE]), true }, + { "RequisiteOverridable", bus_unit_append_dependencies, "as", offsetof(Unit, dependencies[UNIT_REQUISITE_OVERRIDABLE]), true }, + { "Wants", bus_unit_append_dependencies, "as", offsetof(Unit, dependencies[UNIT_WANTS]), true }, + { "BindTo", bus_unit_append_dependencies, "as", offsetof(Unit, dependencies[UNIT_BIND_TO]), true }, + { "RequiredBy", bus_unit_append_dependencies, "as", offsetof(Unit, dependencies[UNIT_REQUIRED_BY]), true }, + { "RequiredByOverridable",bus_unit_append_dependencies, "as", offsetof(Unit, dependencies[UNIT_REQUIRED_BY_OVERRIDABLE]), true }, + { "WantedBy", bus_unit_append_dependencies, "as", offsetof(Unit, dependencies[UNIT_WANTED_BY]), true }, + { "BoundBy", bus_unit_append_dependencies, "as", offsetof(Unit, dependencies[UNIT_BOUND_BY]), true }, + { "Conflicts", bus_unit_append_dependencies, "as", offsetof(Unit, dependencies[UNIT_CONFLICTS]), true }, + { "ConflictedBy", bus_unit_append_dependencies, "as", offsetof(Unit, dependencies[UNIT_CONFLICTED_BY]), true }, + { "Before", bus_unit_append_dependencies, "as", offsetof(Unit, dependencies[UNIT_BEFORE]), true }, + { "After", bus_unit_append_dependencies, "as", offsetof(Unit, dependencies[UNIT_AFTER]), true }, + { "OnFailure", bus_unit_append_dependencies, "as", offsetof(Unit, dependencies[UNIT_ON_FAILURE]), true }, + { "Triggers", bus_unit_append_dependencies, "as", offsetof(Unit, dependencies[UNIT_TRIGGERS]), true }, + { "TriggeredBy", bus_unit_append_dependencies, "as", offsetof(Unit, dependencies[UNIT_TRIGGERED_BY]), true }, + { "PropagateReloadTo", bus_unit_append_dependencies, "as", offsetof(Unit, dependencies[UNIT_PROPAGATE_RELOAD_TO]), true }, + { "PropagateReloadFrom", bus_unit_append_dependencies, "as", offsetof(Unit, dependencies[UNIT_PROPAGATE_RELOAD_FROM]), true }, + { "Description", bus_unit_append_description, "s", 0 }, + { "LoadState", bus_unit_append_load_state, "s", offsetof(Unit, load_state) }, + { "ActiveState", bus_unit_append_active_state, "s", 0 }, + { "SubState", bus_unit_append_sub_state, "s", 0 }, + { "FragmentPath", bus_property_append_string, "s", offsetof(Unit, fragment_path), true }, + { "UnitFileState", bus_unit_append_file_state, "s", 0 }, + { "InactiveExitTimestamp",bus_property_append_usec, "t", offsetof(Unit, inactive_exit_timestamp.realtime) }, + { "InactiveExitTimestampMonotonic", bus_property_append_usec, "t", offsetof(Unit, inactive_exit_timestamp.monotonic) }, + { "ActiveEnterTimestamp", bus_property_append_usec, "t", offsetof(Unit, active_enter_timestamp.realtime) }, + { "ActiveEnterTimestampMonotonic", bus_property_append_usec, "t", offsetof(Unit, active_enter_timestamp.monotonic) }, + { "ActiveExitTimestamp", bus_property_append_usec, "t", offsetof(Unit, active_exit_timestamp.realtime) }, + { "ActiveExitTimestampMonotonic", bus_property_append_usec, "t", offsetof(Unit, active_exit_timestamp.monotonic) }, + { "InactiveEnterTimestamp", bus_property_append_usec, "t", offsetof(Unit, inactive_enter_timestamp.realtime) }, + { "InactiveEnterTimestampMonotonic",bus_property_append_usec, "t", offsetof(Unit, inactive_enter_timestamp.monotonic) }, + { "CanStart", bus_unit_append_can_start, "b", 0 }, + { "CanStop", bus_unit_append_can_stop, "b", 0 }, + { "CanReload", bus_unit_append_can_reload, "b", 0 }, + { "CanIsolate", bus_unit_append_can_isolate, "b", 0 }, + { "Job", bus_unit_append_job, "(uo)", 0 }, + { "StopWhenUnneeded", bus_property_append_bool, "b", offsetof(Unit, stop_when_unneeded) }, + { "RefuseManualStart", bus_property_append_bool, "b", offsetof(Unit, refuse_manual_start) }, + { "RefuseManualStop", bus_property_append_bool, "b", offsetof(Unit, refuse_manual_stop) }, + { "AllowIsolate", bus_property_append_bool, "b", offsetof(Unit, allow_isolate) }, + { "DefaultDependencies", bus_property_append_bool, "b", offsetof(Unit, default_dependencies) }, + { "OnFailureIsolate", bus_property_append_bool, "b", offsetof(Unit, on_failure_isolate) }, + { "IgnoreOnIsolate", bus_property_append_bool, "b", offsetof(Unit, ignore_on_isolate) }, + { "IgnoreOnSnapshot", bus_property_append_bool, "b", offsetof(Unit, ignore_on_snapshot) }, + { "DefaultControlGroup", bus_unit_append_default_cgroup, "s", 0 }, + { "ControlGroup", bus_unit_append_cgroups, "as", 0 }, + { "ControlGroupAttributes", bus_unit_append_cgroup_attrs,"a(sss)", 0 }, + { "NeedDaemonReload", bus_unit_append_need_daemon_reload, "b", 0 }, + { "JobTimeoutUSec", bus_property_append_usec, "t", offsetof(Unit, job_timeout) }, + { "ConditionTimestamp", bus_property_append_usec, "t", offsetof(Unit, condition_timestamp.realtime) }, + { "ConditionTimestampMonotonic", bus_property_append_usec, "t", offsetof(Unit, condition_timestamp.monotonic) }, + { "ConditionResult", bus_property_append_bool, "b", offsetof(Unit, condition_result) }, + { "LoadError", bus_unit_append_load_error, "(ss)", 0 }, + { NULL, } +}; diff --git a/src/core/dbus-unit.h b/src/core/dbus-unit.h new file mode 100644 index 0000000000..4f19a808bf --- /dev/null +++ b/src/core/dbus-unit.h @@ -0,0 +1,139 @@ +/*-*- Mode: C; c-basic-offset: 8; indent-tabs-mode: nil -*-*/ + +#ifndef foodbusunithfoo +#define foodbusunithfoo + +/*** + This file is part of systemd. + + Copyright 2010 Lennart Poettering + + systemd is free software; you can redistribute it and/or modify it + under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + systemd is distributed in the hope that 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 General Public License + along with systemd; If not, see . +***/ + +#include + +#include "manager.h" +#include "dbus-common.h" + +#define BUS_UNIT_INTERFACE \ + " \n" \ + " \n" \ + " \n" \ + " \n" \ + " \n" \ + " \n" \ + " \n" \ + " \n" \ + " \n" \ + " \n" \ + " \n" \ + " \n" \ + " \n" \ + " \n" \ + " \n" \ + " \n" \ + " \n" \ + " \n" \ + " \n" \ + " \n" \ + " \n" \ + " \n" \ + " \n" \ + " \n" \ + " \n" \ + " \n" \ + " \n" \ + " \n" \ + " \n" \ + " \n" \ + " \n" \ + " \n" \ + " \n" \ + " \n" \ + " \n" \ + " \n" \ + " \n" \ + " \n" \ + " \n" \ + " \n" \ + " \n" \ + " \n" \ + " \n" \ + " \n" \ + " \n" \ + " \n" \ + " \n" \ + " \n" \ + " \n" \ + " \n" \ + " \n" \ + " \n" \ + " \n" \ + " \n" \ + " \n" \ + " \n" \ + " \n" \ + " \n" \ + " \n" \ + " \n" \ + " \n" \ + " \n" \ + " \n" \ + " \n" \ + " \n" \ + " \n" \ + " \n" \ + " \n" \ + " \n" \ + " \n" \ + " \n" \ + " \n" \ + " \n" \ + " \n" \ + " \n" \ + " \n" \ + " \n" \ + " \n" \ + " \n" \ + " \n" \ + " \n" \ + " \n" \ + " \n" \ + " \n" \ + " \n" \ + " \n" \ + " \n" \ + " \n" \ + " \n" \ + " \n" \ + " \n" \ + " \n" \ + " \n" \ + " \n" + +#define BUS_UNIT_INTERFACES_LIST \ + BUS_GENERIC_INTERFACES_LIST \ + "org.freedesktop.systemd1.Unit\0" + +extern const BusProperty bus_unit_properties[]; + +void bus_unit_send_change_signal(Unit *u); +void bus_unit_send_removed_signal(Unit *u); + +extern const DBusObjectPathVTable bus_unit_vtable; + +extern const char bus_unit_interface[]; + +#endif diff --git a/src/core/dbus.c b/src/core/dbus.c new file mode 100644 index 0000000000..ddf91f225a --- /dev/null +++ b/src/core/dbus.c @@ -0,0 +1,1483 @@ +/*-*- Mode: C; c-basic-offset: 8; indent-tabs-mode: nil -*-*/ + +/*** + This file is part of systemd. + + Copyright 2010 Lennart Poettering + + systemd is free software; you can redistribute it and/or modify it + under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + systemd is distributed in the hope that 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 General Public License + along with systemd; If not, see . +***/ + +#include +#include +#include +#include +#include + +#include "dbus.h" +#include "log.h" +#include "strv.h" +#include "cgroup.h" +#include "mkdir.h" +#include "dbus-unit.h" +#include "dbus-job.h" +#include "dbus-manager.h" +#include "dbus-service.h" +#include "dbus-socket.h" +#include "dbus-target.h" +#include "dbus-device.h" +#include "dbus-mount.h" +#include "dbus-automount.h" +#include "dbus-snapshot.h" +#include "dbus-swap.h" +#include "dbus-timer.h" +#include "dbus-path.h" +#include "bus-errors.h" +#include "special.h" +#include "dbus-common.h" + +#define CONNECTIONS_MAX 52 + +/* Well-known address (http://dbus.freedesktop.org/doc/dbus-specification.html#message-bus-types) */ +#define DBUS_SYSTEM_BUS_DEFAULT_ADDRESS "unix:path=/var/run/dbus/system_bus_socket" +/* Only used as a fallback */ +#define DBUS_SESSION_BUS_DEFAULT_ADDRESS "autolaunch:" + +static const char bus_properties_interface[] = BUS_PROPERTIES_INTERFACE; +static const char bus_introspectable_interface[] = BUS_INTROSPECTABLE_INTERFACE; + +const char *const bus_interface_table[] = { + "org.freedesktop.DBus.Properties", bus_properties_interface, + "org.freedesktop.DBus.Introspectable", bus_introspectable_interface, + "org.freedesktop.systemd1.Manager", bus_manager_interface, + "org.freedesktop.systemd1.Job", bus_job_interface, + "org.freedesktop.systemd1.Unit", bus_unit_interface, + "org.freedesktop.systemd1.Service", bus_service_interface, + "org.freedesktop.systemd1.Socket", bus_socket_interface, + "org.freedesktop.systemd1.Target", bus_target_interface, + "org.freedesktop.systemd1.Device", bus_device_interface, + "org.freedesktop.systemd1.Mount", bus_mount_interface, + "org.freedesktop.systemd1.Automount", bus_automount_interface, + "org.freedesktop.systemd1.Snapshot", bus_snapshot_interface, + "org.freedesktop.systemd1.Swap", bus_swap_interface, + "org.freedesktop.systemd1.Timer", bus_timer_interface, + "org.freedesktop.systemd1.Path", bus_path_interface, + NULL +}; + +static void bus_done_api(Manager *m); +static void bus_done_system(Manager *m); +static void bus_done_private(Manager *m); +static void shutdown_connection(Manager *m, DBusConnection *c); + +static void bus_dispatch_status(DBusConnection *bus, DBusDispatchStatus status, void *data) { + Manager *m = data; + + assert(bus); + assert(m); + + /* We maintain two sets, one for those connections where we + * requested a dispatch, and another where we didn't. And then, + * we move the connections between the two sets. */ + + if (status == DBUS_DISPATCH_COMPLETE) + set_move_one(m->bus_connections, m->bus_connections_for_dispatch, bus); + else + set_move_one(m->bus_connections_for_dispatch, m->bus_connections, bus); +} + +void bus_watch_event(Manager *m, Watch *w, int events) { + assert(m); + assert(w); + + /* This is called by the event loop whenever there is + * something happening on D-Bus' file handles. */ + + if (!dbus_watch_get_enabled(w->data.bus_watch)) + return; + + dbus_watch_handle(w->data.bus_watch, bus_events_to_flags(events)); +} + +static dbus_bool_t bus_add_watch(DBusWatch *bus_watch, void *data) { + Manager *m = data; + Watch *w; + struct epoll_event ev; + + assert(bus_watch); + assert(m); + + if (!(w = new0(Watch, 1))) + return FALSE; + + w->fd = dbus_watch_get_unix_fd(bus_watch); + w->type = WATCH_DBUS_WATCH; + w->data.bus_watch = bus_watch; + + zero(ev); + ev.events = bus_flags_to_events(bus_watch); + ev.data.ptr = w; + + if (epoll_ctl(m->epoll_fd, EPOLL_CTL_ADD, w->fd, &ev) < 0) { + + if (errno != EEXIST) { + free(w); + return FALSE; + } + + /* Hmm, bloody D-Bus creates multiple watches on the + * same fd. epoll() does not like that. As a dirty + * hack we simply dup() the fd and hence get a second + * one we can safely add to the epoll(). */ + + if ((w->fd = dup(w->fd)) < 0) { + free(w); + return FALSE; + } + + if (epoll_ctl(m->epoll_fd, EPOLL_CTL_ADD, w->fd, &ev) < 0) { + close_nointr_nofail(w->fd); + free(w); + return FALSE; + } + + w->fd_is_dupped = true; + } + + dbus_watch_set_data(bus_watch, w, NULL); + + return TRUE; +} + +static void bus_remove_watch(DBusWatch *bus_watch, void *data) { + Manager *m = data; + Watch *w; + + assert(bus_watch); + assert(m); + + w = dbus_watch_get_data(bus_watch); + if (!w) + return; + + assert(w->type == WATCH_DBUS_WATCH); + assert_se(epoll_ctl(m->epoll_fd, EPOLL_CTL_DEL, w->fd, NULL) >= 0); + + if (w->fd_is_dupped) + close_nointr_nofail(w->fd); + + free(w); +} + +static void bus_toggle_watch(DBusWatch *bus_watch, void *data) { + Manager *m = data; + Watch *w; + struct epoll_event ev; + + assert(bus_watch); + assert(m); + + w = dbus_watch_get_data(bus_watch); + if (!w) + return; + + assert(w->type == WATCH_DBUS_WATCH); + + zero(ev); + ev.events = bus_flags_to_events(bus_watch); + ev.data.ptr = w; + + assert_se(epoll_ctl(m->epoll_fd, EPOLL_CTL_MOD, w->fd, &ev) == 0); +} + +static int bus_timeout_arm(Manager *m, Watch *w) { + struct itimerspec its; + + assert(m); + assert(w); + + zero(its); + + if (dbus_timeout_get_enabled(w->data.bus_timeout)) { + timespec_store(&its.it_value, dbus_timeout_get_interval(w->data.bus_timeout) * USEC_PER_MSEC); + its.it_interval = its.it_value; + } + + if (timerfd_settime(w->fd, 0, &its, NULL) < 0) + return -errno; + + return 0; +} + +void bus_timeout_event(Manager *m, Watch *w, int events) { + assert(m); + assert(w); + + /* This is called by the event loop whenever there is + * something happening on D-Bus' file handles. */ + + if (!(dbus_timeout_get_enabled(w->data.bus_timeout))) + return; + + dbus_timeout_handle(w->data.bus_timeout); +} + +static dbus_bool_t bus_add_timeout(DBusTimeout *timeout, void *data) { + Manager *m = data; + Watch *w; + struct epoll_event ev; + + assert(timeout); + assert(m); + + if (!(w = new0(Watch, 1))) + return FALSE; + + if ((w->fd = timerfd_create(CLOCK_MONOTONIC, TFD_NONBLOCK|TFD_CLOEXEC)) < 0) + goto fail; + + w->type = WATCH_DBUS_TIMEOUT; + w->data.bus_timeout = timeout; + + if (bus_timeout_arm(m, w) < 0) + goto fail; + + zero(ev); + ev.events = EPOLLIN; + ev.data.ptr = w; + + if (epoll_ctl(m->epoll_fd, EPOLL_CTL_ADD, w->fd, &ev) < 0) + goto fail; + + dbus_timeout_set_data(timeout, w, NULL); + + return TRUE; + +fail: + if (w->fd >= 0) + close_nointr_nofail(w->fd); + + free(w); + return FALSE; +} + +static void bus_remove_timeout(DBusTimeout *timeout, void *data) { + Manager *m = data; + Watch *w; + + assert(timeout); + assert(m); + + w = dbus_timeout_get_data(timeout); + if (!w) + return; + + assert(w->type == WATCH_DBUS_TIMEOUT); + + assert_se(epoll_ctl(m->epoll_fd, EPOLL_CTL_DEL, w->fd, NULL) >= 0); + close_nointr_nofail(w->fd); + free(w); +} + +static void bus_toggle_timeout(DBusTimeout *timeout, void *data) { + Manager *m = data; + Watch *w; + int r; + + assert(timeout); + assert(m); + + w = dbus_timeout_get_data(timeout); + if (!w) + return; + + assert(w->type == WATCH_DBUS_TIMEOUT); + + if ((r = bus_timeout_arm(m, w)) < 0) + log_error("Failed to rearm timer: %s", strerror(-r)); +} + +static DBusHandlerResult api_bus_message_filter(DBusConnection *connection, DBusMessage *message, void *data) { + Manager *m = data; + DBusError error; + DBusMessage *reply = NULL; + + assert(connection); + assert(message); + assert(m); + + dbus_error_init(&error); + + if (dbus_message_get_type(message) == DBUS_MESSAGE_TYPE_METHOD_CALL || + dbus_message_get_type(message) == DBUS_MESSAGE_TYPE_SIGNAL) + log_debug("Got D-Bus request: %s.%s() on %s", + dbus_message_get_interface(message), + dbus_message_get_member(message), + dbus_message_get_path(message)); + + if (dbus_message_is_signal(message, DBUS_INTERFACE_LOCAL, "Disconnected")) { + log_debug("API D-Bus connection terminated."); + bus_done_api(m); + + } else if (dbus_message_is_signal(message, DBUS_INTERFACE_DBUS, "NameOwnerChanged")) { + const char *name, *old_owner, *new_owner; + + if (!dbus_message_get_args(message, &error, + DBUS_TYPE_STRING, &name, + DBUS_TYPE_STRING, &old_owner, + DBUS_TYPE_STRING, &new_owner, + DBUS_TYPE_INVALID)) + log_error("Failed to parse NameOwnerChanged message: %s", bus_error_message(&error)); + else { + if (set_remove(BUS_CONNECTION_SUBSCRIBED(m, connection), (char*) name)) + log_debug("Subscription client vanished: %s (left: %u)", name, set_size(BUS_CONNECTION_SUBSCRIBED(m, connection))); + + if (old_owner[0] == 0) + old_owner = NULL; + + if (new_owner[0] == 0) + new_owner = NULL; + + manager_dispatch_bus_name_owner_changed(m, name, old_owner, new_owner); + } + } else if (dbus_message_is_signal(message, "org.freedesktop.systemd1.Activator", "ActivationRequest")) { + const char *name; + + if (!dbus_message_get_args(message, &error, + DBUS_TYPE_STRING, &name, + DBUS_TYPE_INVALID)) + log_error("Failed to parse ActivationRequest message: %s", bus_error_message(&error)); + else { + int r; + Unit *u; + + log_debug("Got D-Bus activation request for %s", name); + + if (manager_unit_pending_inactive(m, SPECIAL_DBUS_SERVICE) || + manager_unit_pending_inactive(m, SPECIAL_DBUS_SOCKET)) { + r = -EADDRNOTAVAIL; + dbus_set_error(&error, BUS_ERROR_SHUTTING_DOWN, "Refusing activation, D-Bus is shutting down."); + } else { + r = manager_load_unit(m, name, NULL, &error, &u); + + if (r >= 0 && u->refuse_manual_start) + r = -EPERM; + + if (r >= 0) + r = manager_add_job(m, JOB_START, u, JOB_REPLACE, true, &error, NULL); + } + + if (r < 0) { + const char *id, *text; + + log_debug("D-Bus activation failed for %s: %s", name, strerror(-r)); + + if (!(reply = dbus_message_new_signal("/org/freedesktop/systemd1", "org.freedesktop.systemd1.Activator", "ActivationFailure"))) + goto oom; + + id = error.name ? error.name : bus_errno_to_dbus(r); + text = bus_error(&error, r); + + if (!dbus_message_set_destination(reply, DBUS_SERVICE_DBUS) || + !dbus_message_append_args(reply, + DBUS_TYPE_STRING, &name, + DBUS_TYPE_STRING, &id, + DBUS_TYPE_STRING, &text, + DBUS_TYPE_INVALID)) + goto oom; + } + + /* On success we don't do anything, the service will be spawned now */ + } + } + + dbus_error_free(&error); + + if (reply) { + if (!dbus_connection_send(connection, reply, NULL)) + goto oom; + + dbus_message_unref(reply); + } + + return DBUS_HANDLER_RESULT_NOT_YET_HANDLED; + +oom: + if (reply) + dbus_message_unref(reply); + + dbus_error_free(&error); + + return DBUS_HANDLER_RESULT_NEED_MEMORY; +} + +static DBusHandlerResult system_bus_message_filter(DBusConnection *connection, DBusMessage *message, void *data) { + Manager *m = data; + DBusError error; + + assert(connection); + assert(message); + assert(m); + + dbus_error_init(&error); + + if (m->api_bus != m->system_bus && + (dbus_message_get_type(message) == DBUS_MESSAGE_TYPE_METHOD_CALL || + dbus_message_get_type(message) == DBUS_MESSAGE_TYPE_SIGNAL)) + log_debug("Got D-Bus request on system bus: %s.%s() on %s", + dbus_message_get_interface(message), + dbus_message_get_member(message), + dbus_message_get_path(message)); + + if (dbus_message_is_signal(message, DBUS_INTERFACE_LOCAL, "Disconnected")) { + log_debug("System D-Bus connection terminated."); + bus_done_system(m); + + } else if (m->running_as != MANAGER_SYSTEM && + dbus_message_is_signal(message, "org.freedesktop.systemd1.Agent", "Released")) { + + const char *cgroup; + + if (!dbus_message_get_args(message, &error, + DBUS_TYPE_STRING, &cgroup, + DBUS_TYPE_INVALID)) + log_error("Failed to parse Released message: %s", bus_error_message(&error)); + else + cgroup_notify_empty(m, cgroup); + } + + dbus_error_free(&error); + return DBUS_HANDLER_RESULT_NOT_YET_HANDLED; +} + +static DBusHandlerResult private_bus_message_filter(DBusConnection *connection, DBusMessage *message, void *data) { + Manager *m = data; + DBusError error; + + assert(connection); + assert(message); + assert(m); + + dbus_error_init(&error); + + if (dbus_message_get_type(message) == DBUS_MESSAGE_TYPE_METHOD_CALL || + dbus_message_get_type(message) == DBUS_MESSAGE_TYPE_SIGNAL) + log_debug("Got D-Bus request: %s.%s() on %s", + dbus_message_get_interface(message), + dbus_message_get_member(message), + dbus_message_get_path(message)); + + if (dbus_message_is_signal(message, DBUS_INTERFACE_LOCAL, "Disconnected")) + shutdown_connection(m, connection); + else if (m->running_as == MANAGER_SYSTEM && + dbus_message_is_signal(message, "org.freedesktop.systemd1.Agent", "Released")) { + + const char *cgroup; + + if (!dbus_message_get_args(message, &error, + DBUS_TYPE_STRING, &cgroup, + DBUS_TYPE_INVALID)) + log_error("Failed to parse Released message: %s", bus_error_message(&error)); + else + cgroup_notify_empty(m, cgroup); + + /* Forward the message to the system bus, so that user + * instances are notified as well */ + + if (m->system_bus) + dbus_connection_send(m->system_bus, message, NULL); + } + + dbus_error_free(&error); + + return DBUS_HANDLER_RESULT_NOT_YET_HANDLED; +} + +unsigned bus_dispatch(Manager *m) { + DBusConnection *c; + + assert(m); + + if (m->queued_message) { + /* 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. */ + + if (m->queued_message_connection) + if (!dbus_connection_send(m->queued_message_connection, m->queued_message, NULL)) + return 0; + + dbus_message_unref(m->queued_message); + m->queued_message = NULL; + m->queued_message_connection = NULL; + } + + if ((c = set_first(m->bus_connections_for_dispatch))) { + if (dbus_connection_dispatch(c) == DBUS_DISPATCH_COMPLETE) + set_move_one(m->bus_connections, m->bus_connections_for_dispatch, c); + + return 1; + } + + return 0; +} + +static void request_name_pending_cb(DBusPendingCall *pending, void *userdata) { + DBusMessage *reply; + DBusError error; + + dbus_error_init(&error); + + assert_se(reply = dbus_pending_call_steal_reply(pending)); + + switch (dbus_message_get_type(reply)) { + + case DBUS_MESSAGE_TYPE_ERROR: + + assert_se(dbus_set_error_from_message(&error, reply)); + log_warning("RequestName() failed: %s", bus_error_message(&error)); + break; + + case DBUS_MESSAGE_TYPE_METHOD_RETURN: { + uint32_t r; + + if (!dbus_message_get_args(reply, + &error, + DBUS_TYPE_UINT32, &r, + DBUS_TYPE_INVALID)) { + log_error("Failed to parse RequestName() reply: %s", bus_error_message(&error)); + break; + } + + if (r == 1) + log_debug("Successfully acquired name."); + else + log_error("Name already owned."); + + break; + } + + default: + assert_not_reached("Invalid reply message"); + } + + dbus_message_unref(reply); + dbus_error_free(&error); +} + +static int request_name(Manager *m) { + const char *name = "org.freedesktop.systemd1"; + /* 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 */ + uint32_t flags = DBUS_NAME_FLAG_ALLOW_REPLACEMENT|DBUS_NAME_FLAG_REPLACE_EXISTING; + DBusMessage *message = NULL; + DBusPendingCall *pending = NULL; + + if (!(message = dbus_message_new_method_call( + DBUS_SERVICE_DBUS, + DBUS_PATH_DBUS, + DBUS_INTERFACE_DBUS, + "RequestName"))) + goto oom; + + if (!dbus_message_append_args( + message, + DBUS_TYPE_STRING, &name, + DBUS_TYPE_UINT32, &flags, + DBUS_TYPE_INVALID)) + goto oom; + + if (!dbus_connection_send_with_reply(m->api_bus, message, &pending, -1)) + goto oom; + + if (!dbus_pending_call_set_notify(pending, request_name_pending_cb, m, NULL)) + goto oom; + + dbus_message_unref(message); + dbus_pending_call_unref(pending); + + /* We simple ask for the name and don't wait for it. Sooner or + * later we'll have it. */ + + return 0; + +oom: + if (pending) { + dbus_pending_call_cancel(pending); + dbus_pending_call_unref(pending); + } + + if (message) + dbus_message_unref(message); + + return -ENOMEM; +} + +static void query_name_list_pending_cb(DBusPendingCall *pending, void *userdata) { + DBusMessage *reply; + DBusError error; + Manager *m = userdata; + + assert(m); + + dbus_error_init(&error); + + assert_se(reply = dbus_pending_call_steal_reply(pending)); + + switch (dbus_message_get_type(reply)) { + + case DBUS_MESSAGE_TYPE_ERROR: + + assert_se(dbus_set_error_from_message(&error, reply)); + log_warning("ListNames() failed: %s", bus_error_message(&error)); + break; + + case DBUS_MESSAGE_TYPE_METHOD_RETURN: { + int r; + char **l; + + if ((r = bus_parse_strv(reply, &l)) < 0) + log_warning("Failed to parse ListNames() reply: %s", strerror(-r)); + else { + char **t; + + STRV_FOREACH(t, l) + /* This is a bit hacky, we say the + * owner of the name is the name + * itself, because we don't want the + * extra traffic to figure out the + * real owner. */ + manager_dispatch_bus_name_owner_changed(m, *t, NULL, *t); + + strv_free(l); + } + + break; + } + + default: + assert_not_reached("Invalid reply message"); + } + + dbus_message_unref(reply); + dbus_error_free(&error); +} + +static int query_name_list(Manager *m) { + DBusMessage *message = NULL; + DBusPendingCall *pending = NULL; + + /* Asks for the currently installed bus names */ + + if (!(message = dbus_message_new_method_call( + DBUS_SERVICE_DBUS, + DBUS_PATH_DBUS, + DBUS_INTERFACE_DBUS, + "ListNames"))) + goto oom; + + if (!dbus_connection_send_with_reply(m->api_bus, message, &pending, -1)) + goto oom; + + if (!dbus_pending_call_set_notify(pending, query_name_list_pending_cb, m, NULL)) + goto oom; + + dbus_message_unref(message); + dbus_pending_call_unref(pending); + + /* We simple ask for the list and don't wait for it. Sooner or + * later we'll get it. */ + + return 0; + +oom: + if (pending) { + dbus_pending_call_cancel(pending); + dbus_pending_call_unref(pending); + } + + if (message) + dbus_message_unref(message); + + return -ENOMEM; +} + +static int bus_setup_loop(Manager *m, DBusConnection *bus) { + assert(m); + assert(bus); + + dbus_connection_set_exit_on_disconnect(bus, FALSE); + + if (!dbus_connection_set_watch_functions(bus, bus_add_watch, bus_remove_watch, bus_toggle_watch, m, NULL) || + !dbus_connection_set_timeout_functions(bus, bus_add_timeout, bus_remove_timeout, bus_toggle_timeout, m, NULL)) { + log_error("Not enough memory"); + return -ENOMEM; + } + + if (set_put(m->bus_connections_for_dispatch, bus) < 0) { + log_error("Not enough memory"); + return -ENOMEM; + } + + dbus_connection_set_dispatch_status_function(bus, bus_dispatch_status, m, NULL); + return 0; +} + +static dbus_bool_t allow_only_same_user(DBusConnection *connection, unsigned long uid, void *data) { + return uid == 0 || uid == geteuid(); +} + +static void bus_new_connection( + DBusServer *server, + DBusConnection *new_connection, + void *data) { + + Manager *m = data; + + assert(m); + + if (set_size(m->bus_connections) >= CONNECTIONS_MAX) { + log_error("Too many concurrent connections."); + return; + } + + dbus_connection_set_unix_user_function(new_connection, allow_only_same_user, NULL, NULL); + + if (bus_setup_loop(m, new_connection) < 0) + return; + + if (!dbus_connection_register_object_path(new_connection, "/org/freedesktop/systemd1", &bus_manager_vtable, m) || + !dbus_connection_register_fallback(new_connection, "/org/freedesktop/systemd1/unit", &bus_unit_vtable, m) || + !dbus_connection_register_fallback(new_connection, "/org/freedesktop/systemd1/job", &bus_job_vtable, m) || + !dbus_connection_add_filter(new_connection, private_bus_message_filter, m, NULL)) { + log_error("Not enough memory."); + return; + } + + log_debug("Accepted connection on private bus."); + + dbus_connection_ref(new_connection); +} + +static int init_registered_system_bus(Manager *m) { + char *id; + + if (!dbus_connection_add_filter(m->system_bus, system_bus_message_filter, m, NULL)) { + log_error("Not enough memory"); + return -ENOMEM; + } + + if (m->running_as != MANAGER_SYSTEM) { + DBusError error; + + dbus_error_init(&error); + + dbus_bus_add_match(m->system_bus, + "type='signal'," + "interface='org.freedesktop.systemd1.Agent'," + "member='Released'," + "path='/org/freedesktop/systemd1/agent'", + &error); + + if (dbus_error_is_set(&error)) { + log_error("Failed to register match: %s", bus_error_message(&error)); + dbus_error_free(&error); + return -1; + } + } + + log_debug("Successfully connected to system D-Bus bus %s as %s", + strnull((id = dbus_connection_get_server_id(m->system_bus))), + strnull(dbus_bus_get_unique_name(m->system_bus))); + dbus_free(id); + + return 0; +} + +static int init_registered_api_bus(Manager *m) { + int r; + + if (!dbus_connection_register_object_path(m->api_bus, "/org/freedesktop/systemd1", &bus_manager_vtable, m) || + !dbus_connection_register_fallback(m->api_bus, "/org/freedesktop/systemd1/unit", &bus_unit_vtable, m) || + !dbus_connection_register_fallback(m->api_bus, "/org/freedesktop/systemd1/job", &bus_job_vtable, m) || + !dbus_connection_add_filter(m->api_bus, api_bus_message_filter, m, NULL)) { + log_error("Not enough memory"); + return -ENOMEM; + } + + /* Get NameOwnerChange messages */ + dbus_bus_add_match(m->api_bus, + "type='signal'," + "sender='"DBUS_SERVICE_DBUS"'," + "interface='"DBUS_INTERFACE_DBUS"'," + "member='NameOwnerChanged'," + "path='"DBUS_PATH_DBUS"'", + NULL); + + /* Get activation requests */ + dbus_bus_add_match(m->api_bus, + "type='signal'," + "sender='"DBUS_SERVICE_DBUS"'," + "interface='org.freedesktop.systemd1.Activator'," + "member='ActivationRequest'," + "path='"DBUS_PATH_DBUS"'", + NULL); + + r = request_name(m); + if (r < 0) + return r; + + r = query_name_list(m); + if (r < 0) + return r; + + if (m->running_as == MANAGER_USER) { + char *id; + log_debug("Successfully connected to API D-Bus bus %s as %s", + strnull((id = dbus_connection_get_server_id(m->api_bus))), + strnull(dbus_bus_get_unique_name(m->api_bus))); + dbus_free(id); + } else + log_debug("Successfully initialized API on the system bus"); + + return 0; +} + +static void bus_register_cb(DBusPendingCall *pending, void *userdata) { + Manager *m = userdata; + DBusConnection **conn; + DBusMessage *reply; + DBusError error; + const char *name; + int r = 0; + + dbus_error_init(&error); + + conn = dbus_pending_call_get_data(pending, m->conn_data_slot); + assert(conn == &m->system_bus || conn == &m->api_bus); + + reply = dbus_pending_call_steal_reply(pending); + + switch (dbus_message_get_type(reply)) { + case DBUS_MESSAGE_TYPE_ERROR: + assert_se(dbus_set_error_from_message(&error, reply)); + log_warning("Failed to register to bus: %s", bus_error_message(&error)); + r = -1; + break; + case DBUS_MESSAGE_TYPE_METHOD_RETURN: + if (!dbus_message_get_args(reply, &error, + DBUS_TYPE_STRING, &name, + DBUS_TYPE_INVALID)) { + log_error("Failed to parse Hello reply: %s", bus_error_message(&error)); + r = -1; + break; + } + + log_debug("Received name %s in reply to Hello", name); + if (!dbus_bus_set_unique_name(*conn, name)) { + log_error("Failed to set unique name"); + r = -1; + break; + } + + if (conn == &m->system_bus) { + r = init_registered_system_bus(m); + if (r == 0 && m->running_as == MANAGER_SYSTEM) + r = init_registered_api_bus(m); + } else + r = init_registered_api_bus(m); + + break; + default: + assert_not_reached("Invalid reply message"); + } + + dbus_message_unref(reply); + dbus_error_free(&error); + + if (r < 0) { + if (conn == &m->system_bus) { + log_debug("Failed setting up the system bus"); + bus_done_system(m); + } else { + log_debug("Failed setting up the API bus"); + bus_done_api(m); + } + } +} + +static int manager_bus_async_register(Manager *m, DBusConnection **conn) { + DBusMessage *message = NULL; + DBusPendingCall *pending = NULL; + + message = dbus_message_new_method_call(DBUS_SERVICE_DBUS, + DBUS_PATH_DBUS, + DBUS_INTERFACE_DBUS, + "Hello"); + if (!message) + goto oom; + + if (!dbus_connection_send_with_reply(*conn, message, &pending, -1)) + goto oom; + + if (!dbus_pending_call_set_data(pending, m->conn_data_slot, conn, NULL)) + goto oom; + + if (!dbus_pending_call_set_notify(pending, bus_register_cb, m, NULL)) + goto oom; + + dbus_message_unref(message); + dbus_pending_call_unref(pending); + + return 0; +oom: + if (pending) { + dbus_pending_call_cancel(pending); + dbus_pending_call_unref(pending); + } + + if (message) + dbus_message_unref(message); + + return -ENOMEM; +} + +static DBusConnection* manager_bus_connect_private(Manager *m, DBusBusType type) { + const char *address; + DBusConnection *connection; + DBusError error; + + switch (type) { + case DBUS_BUS_SYSTEM: + address = getenv("DBUS_SYSTEM_BUS_ADDRESS"); + if (!address || !address[0]) + address = DBUS_SYSTEM_BUS_DEFAULT_ADDRESS; + break; + case DBUS_BUS_SESSION: + address = getenv("DBUS_SESSION_BUS_ADDRESS"); + if (!address || !address[0]) + address = DBUS_SESSION_BUS_DEFAULT_ADDRESS; + break; + default: + assert_not_reached("Invalid bus type"); + } + + dbus_error_init(&error); + + connection = dbus_connection_open_private(address, &error); + if (!connection) { + log_warning("Failed to open private bus connection: %s", bus_error_message(&error)); + goto fail; + } + + return connection; +fail: + if (connection) + dbus_connection_close(connection); + dbus_error_free(&error); + return NULL; +} + +static int bus_init_system(Manager *m) { + int r; + + if (m->system_bus) + return 0; + + m->system_bus = manager_bus_connect_private(m, DBUS_BUS_SYSTEM); + if (!m->system_bus) { + log_debug("Failed to connect to system D-Bus, retrying later"); + r = 0; + goto fail; + } + + r = bus_setup_loop(m, m->system_bus); + if (r < 0) + goto fail; + + r = manager_bus_async_register(m, &m->system_bus); + if (r < 0) + goto fail; + + return 0; +fail: + bus_done_system(m); + + return r; +} + +static int bus_init_api(Manager *m) { + int r; + + if (m->api_bus) + return 0; + + if (m->running_as == MANAGER_SYSTEM) { + m->api_bus = m->system_bus; + /* In this mode there is no distinct connection to the API bus, + * the API is published on the system bus. + * bus_register_cb() is aware of that and will init the API + * when the system bus gets registered. + * No need to setup anything here. */ + return 0; + } + + m->api_bus = manager_bus_connect_private(m, DBUS_BUS_SESSION); + if (!m->api_bus) { + log_debug("Failed to connect to API D-Bus, retrying later"); + r = 0; + goto fail; + } + + r = bus_setup_loop(m, m->api_bus); + if (r < 0) + goto fail; + + r = manager_bus_async_register(m, &m->api_bus); + if (r < 0) + goto fail; + + return 0; +fail: + bus_done_api(m); + + return r; +} + +static int bus_init_private(Manager *m) { + DBusError error; + int r; + const char *const external_only[] = { + "EXTERNAL", + NULL + }; + + assert(m); + + dbus_error_init(&error); + + if (m->private_bus) + return 0; + + if (m->running_as == MANAGER_SYSTEM) { + + /* We want the private bus only when running as init */ + if (getpid() != 1) + return 0; + + unlink("/run/systemd/private"); + m->private_bus = dbus_server_listen("unix:path=/run/systemd/private", &error); + } else { + const char *e; + char *p; + + e = getenv("XDG_RUNTIME_DIR"); + if (!e) + return 0; + + if (asprintf(&p, "unix:path=%s/systemd/private", e) < 0) { + log_error("Not enough memory"); + r = -ENOMEM; + goto fail; + } + + mkdir_parents(p+10, 0755); + unlink(p+10); + m->private_bus = dbus_server_listen(p, &error); + free(p); + } + + if (!m->private_bus) { + log_error("Failed to create private D-Bus server: %s", bus_error_message(&error)); + r = -EIO; + goto fail; + } + + if (!dbus_server_set_auth_mechanisms(m->private_bus, (const char**) external_only) || + !dbus_server_set_watch_functions(m->private_bus, bus_add_watch, bus_remove_watch, bus_toggle_watch, m, NULL) || + !dbus_server_set_timeout_functions(m->private_bus, bus_add_timeout, bus_remove_timeout, bus_toggle_timeout, m, NULL)) { + log_error("Not enough memory"); + r = -ENOMEM; + goto fail; + } + + dbus_server_set_new_connection_function(m->private_bus, bus_new_connection, m, NULL); + + log_debug("Successfully created private D-Bus server."); + + return 0; + +fail: + bus_done_private(m); + dbus_error_free(&error); + + return r; +} + +int bus_init(Manager *m, bool try_bus_connect) { + int r; + + if (set_ensure_allocated(&m->bus_connections, trivial_hash_func, trivial_compare_func) < 0 || + set_ensure_allocated(&m->bus_connections_for_dispatch, trivial_hash_func, trivial_compare_func) < 0) + goto oom; + + if (m->name_data_slot < 0) + if (!dbus_pending_call_allocate_data_slot(&m->name_data_slot)) + goto oom; + + if (m->conn_data_slot < 0) + if (!dbus_pending_call_allocate_data_slot(&m->conn_data_slot)) + goto oom; + + if (m->subscribed_data_slot < 0) + if (!dbus_connection_allocate_data_slot(&m->subscribed_data_slot)) + goto oom; + + if (try_bus_connect) { + if ((r = bus_init_system(m)) < 0 || + (r = bus_init_api(m)) < 0) + return r; + } + + if ((r = bus_init_private(m)) < 0) + return r; + + return 0; +oom: + log_error("Not enough memory"); + return -ENOMEM; +} + +static void shutdown_connection(Manager *m, DBusConnection *c) { + Set *s; + Job *j; + Iterator i; + + HASHMAP_FOREACH(j, m->jobs, i) + if (j->bus == c) { + free(j->bus_client); + j->bus_client = NULL; + + j->bus = NULL; + } + + set_remove(m->bus_connections, c); + set_remove(m->bus_connections_for_dispatch, c); + + if ((s = BUS_CONNECTION_SUBSCRIBED(m, c))) { + char *t; + + while ((t = set_steal_first(s))) + free(t); + + set_free(s); + } + + if (m->queued_message_connection == c) { + m->queued_message_connection = NULL; + + if (m->queued_message) { + dbus_message_unref(m->queued_message); + m->queued_message = NULL; + } + } + + dbus_connection_set_dispatch_status_function(c, NULL, NULL, NULL); + /* system manager cannot afford to block on DBus */ + if (m->running_as != MANAGER_SYSTEM) + dbus_connection_flush(c); + dbus_connection_close(c); + dbus_connection_unref(c); +} + +static void bus_done_api(Manager *m) { + if (!m->api_bus) + return; + + if (m->running_as == MANAGER_USER) + shutdown_connection(m, m->api_bus); + + m->api_bus = NULL; + + if (m->queued_message) { + dbus_message_unref(m->queued_message); + m->queued_message = NULL; + } +} + +static void bus_done_system(Manager *m) { + if (!m->system_bus) + return; + + if (m->running_as == MANAGER_SYSTEM) + bus_done_api(m); + + shutdown_connection(m, m->system_bus); + m->system_bus = NULL; +} + +static void bus_done_private(Manager *m) { + if (!m->private_bus) + return; + + dbus_server_disconnect(m->private_bus); + dbus_server_unref(m->private_bus); + m->private_bus = NULL; +} + +void bus_done(Manager *m) { + DBusConnection *c; + + bus_done_api(m); + bus_done_system(m); + bus_done_private(m); + + while ((c = set_steal_first(m->bus_connections))) + shutdown_connection(m, c); + + while ((c = set_steal_first(m->bus_connections_for_dispatch))) + shutdown_connection(m, c); + + set_free(m->bus_connections); + set_free(m->bus_connections_for_dispatch); + + if (m->name_data_slot >= 0) + dbus_pending_call_free_data_slot(&m->name_data_slot); + + if (m->conn_data_slot >= 0) + dbus_pending_call_free_data_slot(&m->conn_data_slot); + + if (m->subscribed_data_slot >= 0) + dbus_connection_free_data_slot(&m->subscribed_data_slot); +} + +static void query_pid_pending_cb(DBusPendingCall *pending, void *userdata) { + Manager *m = userdata; + DBusMessage *reply; + DBusError error; + const char *name; + + dbus_error_init(&error); + + assert_se(name = BUS_PENDING_CALL_NAME(m, pending)); + assert_se(reply = dbus_pending_call_steal_reply(pending)); + + switch (dbus_message_get_type(reply)) { + + case DBUS_MESSAGE_TYPE_ERROR: + + assert_se(dbus_set_error_from_message(&error, reply)); + log_warning("GetConnectionUnixProcessID() failed: %s", bus_error_message(&error)); + break; + + case DBUS_MESSAGE_TYPE_METHOD_RETURN: { + uint32_t r; + + if (!dbus_message_get_args(reply, + &error, + DBUS_TYPE_UINT32, &r, + DBUS_TYPE_INVALID)) { + log_error("Failed to parse GetConnectionUnixProcessID() reply: %s", bus_error_message(&error)); + break; + } + + manager_dispatch_bus_query_pid_done(m, name, (pid_t) r); + break; + } + + default: + assert_not_reached("Invalid reply message"); + } + + dbus_message_unref(reply); + dbus_error_free(&error); +} + +int bus_query_pid(Manager *m, const char *name) { + DBusMessage *message = NULL; + DBusPendingCall *pending = NULL; + char *n = NULL; + + assert(m); + assert(name); + + if (!(message = dbus_message_new_method_call( + DBUS_SERVICE_DBUS, + DBUS_PATH_DBUS, + DBUS_INTERFACE_DBUS, + "GetConnectionUnixProcessID"))) + goto oom; + + if (!(dbus_message_append_args( + message, + DBUS_TYPE_STRING, &name, + DBUS_TYPE_INVALID))) + goto oom; + + if (!dbus_connection_send_with_reply(m->api_bus, message, &pending, -1)) + goto oom; + + if (!(n = strdup(name))) + goto oom; + + if (!dbus_pending_call_set_data(pending, m->name_data_slot, n, free)) + goto oom; + + n = NULL; + + if (!dbus_pending_call_set_notify(pending, query_pid_pending_cb, m, NULL)) + goto oom; + + dbus_message_unref(message); + dbus_pending_call_unref(pending); + + return 0; + +oom: + free(n); + + if (pending) { + dbus_pending_call_cancel(pending); + dbus_pending_call_unref(pending); + } + + if (message) + dbus_message_unref(message); + + return -ENOMEM; +} + +int bus_broadcast(Manager *m, DBusMessage *message) { + bool oom = false; + Iterator i; + DBusConnection *c; + + assert(m); + assert(message); + + SET_FOREACH(c, m->bus_connections_for_dispatch, i) + if (c != m->system_bus || m->running_as == MANAGER_SYSTEM) + oom = !dbus_connection_send(c, message, NULL); + + SET_FOREACH(c, m->bus_connections, i) + if (c != m->system_bus || m->running_as == MANAGER_SYSTEM) + oom = !dbus_connection_send(c, message, NULL); + + return oom ? -ENOMEM : 0; +} + +bool bus_has_subscriber(Manager *m) { + Iterator i; + DBusConnection *c; + + assert(m); + + SET_FOREACH(c, m->bus_connections_for_dispatch, i) + if (bus_connection_has_subscriber(m, c)) + return true; + + SET_FOREACH(c, m->bus_connections, i) + if (bus_connection_has_subscriber(m, c)) + return true; + + return false; +} + +bool bus_connection_has_subscriber(Manager *m, DBusConnection *c) { + assert(m); + assert(c); + + return !set_isempty(BUS_CONNECTION_SUBSCRIBED(m, c)); +} + +int bus_fdset_add_all(Manager *m, FDSet *fds) { + Iterator i; + DBusConnection *c; + + 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 that 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 */ + + SET_FOREACH(c, m->bus_connections_for_dispatch, i) { + int fd; + + if (dbus_connection_get_unix_fd(c, &fd)) { + fd = fdset_put_dup(fds, fd); + + if (fd < 0) + return fd; + } + } + + SET_FOREACH(c, m->bus_connections, i) { + int fd; + + if (dbus_connection_get_unix_fd(c, &fd)) { + fd = fdset_put_dup(fds, fd); + + if (fd < 0) + return fd; + } + } + + return 0; +} + +void bus_broadcast_finished( + Manager *m, + usec_t kernel_usec, + usec_t initrd_usec, + usec_t userspace_usec, + usec_t total_usec) { + + DBusMessage *message; + + assert(m); + + message = dbus_message_new_signal("/org/freedesktop/systemd1", "org.freedesktop.systemd1.Manager", "StartupFinished"); + if (!message) { + log_error("Out of memory."); + return; + } + + assert_cc(sizeof(usec_t) == sizeof(uint64_t)); + if (!dbus_message_append_args(message, + DBUS_TYPE_UINT64, &kernel_usec, + DBUS_TYPE_UINT64, &initrd_usec, + DBUS_TYPE_UINT64, &userspace_usec, + DBUS_TYPE_UINT64, &total_usec, + DBUS_TYPE_INVALID)) { + log_error("Out of memory."); + goto finish; + } + + + if (bus_broadcast(m, message) < 0) { + log_error("Out of memory."); + goto finish; + } + +finish: + if (message) + dbus_message_unref(message); +} diff --git a/src/core/dbus.h b/src/core/dbus.h new file mode 100644 index 0000000000..bd539d0e72 --- /dev/null +++ b/src/core/dbus.h @@ -0,0 +1,53 @@ +/*-*- Mode: C; c-basic-offset: 8; indent-tabs-mode: nil -*-*/ + +#ifndef foodbushfoo +#define foodbushfoo + +/*** + This file is part of systemd. + + Copyright 2010 Lennart Poettering + + systemd is free software; you can redistribute it and/or modify it + under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + systemd is distributed in the hope that 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 General Public License + along with systemd; If not, see . +***/ + +#include + +#include "manager.h" + +int bus_init(Manager *m, bool try_bus_connect); +void bus_done(Manager *m); + +unsigned bus_dispatch(Manager *m); + +void bus_watch_event(Manager *m, Watch *w, int events); +void bus_timeout_event(Manager *m, Watch *w, int events); + +int bus_query_pid(Manager *m, const char *name); + +int bus_broadcast(Manager *m, DBusMessage *message); + +bool bus_has_subscriber(Manager *m); +bool bus_connection_has_subscriber(Manager *m, DBusConnection *c); + +int bus_fdset_add_all(Manager *m, FDSet *fds); + +void bus_broadcast_finished(Manager *m, usec_t kernel_usec, usec_t initrd_usec, usec_t userspace_usec, usec_t total_usec); + +#define BUS_CONNECTION_SUBSCRIBED(m, c) dbus_connection_get_data((c), (m)->subscribed_data_slot) +#define BUS_PENDING_CALL_NAME(m, p) dbus_pending_call_get_data((p), (m)->name_data_slot) + +extern const char * const bus_interface_table[]; + +#endif diff --git a/src/core/device.c b/src/core/device.c new file mode 100644 index 0000000000..0575379d80 --- /dev/null +++ b/src/core/device.c @@ -0,0 +1,616 @@ +/*-*- Mode: C; c-basic-offset: 8; indent-tabs-mode: nil -*-*/ + +/*** + This file is part of systemd. + + Copyright 2010 Lennart Poettering + + systemd is free software; you can redistribute it and/or modify it + under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + systemd is distributed in the hope that 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 General Public License + along with systemd; If not, see . +***/ + +#include +#include +#include + +#include "unit.h" +#include "device.h" +#include "strv.h" +#include "log.h" +#include "unit-name.h" +#include "dbus-device.h" +#include "def.h" + +static const UnitActiveState state_translation_table[_DEVICE_STATE_MAX] = { + [DEVICE_DEAD] = UNIT_INACTIVE, + [DEVICE_PLUGGED] = UNIT_ACTIVE +}; + +static void device_unset_sysfs(Device *d) { + Device *first; + + assert(d); + + if (!d->sysfs) + return; + + /* Remove this unit from the chain of devices which share the + * same sysfs path. */ + first = hashmap_get(UNIT(d)->manager->devices_by_sysfs, d->sysfs); + LIST_REMOVE(Device, same_sysfs, first, d); + + if (first) + hashmap_remove_and_replace(UNIT(d)->manager->devices_by_sysfs, d->sysfs, first->sysfs, first); + else + hashmap_remove(UNIT(d)->manager->devices_by_sysfs, d->sysfs); + + free(d->sysfs); + d->sysfs = NULL; +} + +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. */ + UNIT(d)->job_timeout = DEFAULT_TIMEOUT_USEC; + + UNIT(d)->ignore_on_isolate = true; + UNIT(d)->ignore_on_snapshot = 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_debug("%s changed %s -> %s", + UNIT(d)->id, + 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->sysfs) + device_set_state(d, DEVICE_PLUGGED); + + 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)); +} + +static UnitActiveState device_active_state(Unit *u) { + assert(u); + + return state_translation_table[DEVICE(u)->state]; +} + +static const char *device_sub_state_to_string(Unit *u) { + assert(u); + + return device_state_to_string(DEVICE(u)->state); +} + +static int device_add_escaped_name(Unit *u, const char *dn) { + char *e; + int r; + + assert(u); + assert(dn); + assert(dn[0] == '/'); + + if (!(e = unit_name_from_path(dn, ".device"))) + return -ENOMEM; + + r = unit_add_name(u, e); + free(e); + + if (r < 0 && r != -EEXIST) + return r; + + return 0; +} + +static int device_find_escape_name(Manager *m, const char *dn, Unit **_u) { + char *e; + Unit *u; + + assert(m); + assert(dn); + assert(dn[0] == '/'); + assert(_u); + + if (!(e = unit_name_from_path(dn, ".device"))) + return -ENOMEM; + + u = manager_get_unit(m, e); + free(e); + + if (u) { + *_u = u; + return 1; + } + + return 0; +} + +static int device_update_unit(Manager *m, struct udev_device *dev, const char *path, bool main) { + const char *sysfs, *model; + Unit *u = NULL; + int r; + bool delete; + + assert(m); + + if (!(sysfs = udev_device_get_syspath(dev))) + return -ENOMEM; + + if ((r = device_find_escape_name(m, path, &u)) < 0) + return r; + + if (u && DEVICE(u)->sysfs && !path_equal(DEVICE(u)->sysfs, sysfs)) + return -EEXIST; + + if (!u) { + delete = true; + + u = unit_new(m, sizeof(Device)); + if (!u) + return -ENOMEM; + + r = device_add_escaped_name(u, path); + 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 (!DEVICE(u)->sysfs) { + Device *first; + + if (!(DEVICE(u)->sysfs = strdup(sysfs))) { + r = -ENOMEM; + goto fail; + } + + if (!m->devices_by_sysfs) + if (!(m->devices_by_sysfs = hashmap_new(string_hash_func, string_compare_func))) { + r = -ENOMEM; + goto fail; + } + + first = hashmap_get(m->devices_by_sysfs, sysfs); + LIST_PREPEND(Device, same_sysfs, first, DEVICE(u)); + + if ((r = hashmap_replace(m->devices_by_sysfs, DEVICE(u)->sysfs, first)) < 0) + goto fail; + } + + if ((model = udev_device_get_property_value(dev, "ID_MODEL_FROM_DATABASE")) || + (model = udev_device_get_property_value(dev, "ID_MODEL"))) { + if ((r = unit_set_description(u, model)) < 0) + goto fail; + } else + if ((r = unit_set_description(u, path)) < 0) + goto fail; + + if (main) { + /* The additional systemd udev properties we only + * interpret for the main object */ + const char *wants, *alias; + + if ((alias = udev_device_get_property_value(dev, "SYSTEMD_ALIAS"))) { + if (!is_path(alias)) + log_warning("SYSTEMD_ALIAS for %s is not a path, ignoring: %s", sysfs, alias); + else { + if ((r = device_add_escaped_name(u, alias)) < 0) + goto fail; + } + } + + if ((wants = udev_device_get_property_value(dev, "SYSTEMD_WANTS"))) { + char *state, *w; + size_t l; + + FOREACH_WORD_QUOTED(w, l, wants, state) { + char *e; + + if (!(e = strndup(w, l))) { + r = -ENOMEM; + goto fail; + } + + r = unit_add_dependency_by_name(u, UNIT_WANTS, e, NULL, true); + free(e); + + if (r < 0) + goto fail; + } + } + } + + unit_add_to_dbus_queue(u); + return 0; + +fail: + log_warning("Failed to load device unit: %s", strerror(-r)); + + if (delete && u) + unit_free(u); + + return r; +} + +static int device_process_new_device(Manager *m, struct udev_device *dev, bool update_state) { + const char *sysfs, *dn; + struct udev_list_entry *item = NULL, *first = NULL; + + assert(m); + + if (!(sysfs = udev_device_get_syspath(dev))) + return -ENOMEM; + + /* Add the main unit named after the sysfs path */ + device_update_unit(m, dev, sysfs, true); + + /* Add an additional unit for the device node */ + if ((dn = udev_device_get_devnode(dev))) + device_update_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; + + device_update_unit(m, dev, p, false); + } + + if (update_state) { + Device *d, *l; + + manager_dispatch_load_queue(m); + + l = hashmap_get(m->devices_by_sysfs, sysfs); + LIST_FOREACH(same_sysfs, d, l) + device_set_state(d, DEVICE_PLUGGED); + } + + return 0; +} + +static int device_process_path(Manager *m, const char *path, bool update_state) { + int r; + struct udev_device *dev; + + assert(m); + assert(path); + + if (!(dev = udev_device_new_from_syspath(m->udev, path))) { + log_warning("Failed to get udev device object from udev for path %s.", path); + return -ENOMEM; + } + + r = device_process_new_device(m, dev, update_state); + udev_device_unref(dev); + return r; +} + +static int device_process_removed_device(Manager *m, struct udev_device *dev) { + const char *sysfs; + Device *d; + + assert(m); + assert(dev); + + if (!(sysfs = udev_device_get_syspath(dev))) + return -ENOMEM; + + /* Remove all units of this sysfs path */ + while ((d = hashmap_get(m->devices_by_sysfs, sysfs))) { + device_unset_sysfs(d); + device_set_state(d, DEVICE_DEAD); + } + + return 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 **_s) { + Device *d = DEVICE(u); + Device *other; + Set *s; + int r; + + assert(d); + assert(_s); + + if (!d->same_sysfs_prev && !d->same_sysfs_next) { + *_s = NULL; + return 0; + } + + if (!(s = set_new(NULL, NULL))) + return -ENOMEM; + + for (other = d->same_sysfs_next; other; other = other->same_sysfs_next) + if ((r = set_put(s, other)) < 0) + goto fail; + + for (other = d->same_sysfs_prev; other; other = other->same_sysfs_prev) + if ((r = set_put(s, other)) < 0) + goto fail; + + *_s = s; + return 1; + +fail: + set_free(s); + return r; +} + +static void device_shutdown(Manager *m) { + assert(m); + + if (m->udev_monitor) { + udev_monitor_unref(m->udev_monitor); + m->udev_monitor = NULL; + } + + if (m->udev) { + udev_unref(m->udev); + m->udev = NULL; + } + + hashmap_free(m->devices_by_sysfs); + m->devices_by_sysfs = NULL; +} + +static int device_enumerate(Manager *m) { + struct epoll_event ev; + int r; + struct udev_enumerate *e = NULL; + struct udev_list_entry *item = NULL, *first = NULL; + + assert(m); + + if (!m->udev) { + if (!(m->udev = udev_new())) + return -ENOMEM; + + if (!(m->udev_monitor = udev_monitor_new_from_netlink(m->udev, "udev"))) { + r = -ENOMEM; + goto fail; + } + + /* This will fail if we are unprivileged, but that + * should not matter much, as user instances won't run + * during boot. */ + udev_monitor_set_receive_buffer_size(m->udev_monitor, 128*1024*1024); + + if (udev_monitor_filter_add_match_tag(m->udev_monitor, "systemd") < 0) { + r = -ENOMEM; + goto fail; + } + + if (udev_monitor_enable_receiving(m->udev_monitor) < 0) { + r = -EIO; + goto fail; + } + + m->udev_watch.type = WATCH_UDEV; + m->udev_watch.fd = udev_monitor_get_fd(m->udev_monitor); + + zero(ev); + ev.events = EPOLLIN; + ev.data.ptr = &m->udev_watch; + + if (epoll_ctl(m->epoll_fd, EPOLL_CTL_ADD, m->udev_watch.fd, &ev) < 0) + return -errno; + } + + if (!(e = udev_enumerate_new(m->udev))) { + r = -ENOMEM; + goto fail; + } + if (udev_enumerate_add_match_tag(e, "systemd") < 0) { + r = -EIO; + goto fail; + } + + if (udev_enumerate_scan_devices(e) < 0) { + r = -EIO; + goto fail; + } + + first = udev_enumerate_get_list_entry(e); + udev_list_entry_foreach(item, first) + device_process_path(m, udev_list_entry_get_name(item), false); + + udev_enumerate_unref(e); + return 0; + +fail: + if (e) + udev_enumerate_unref(e); + + device_shutdown(m); + return r; +} + +void device_fd_event(Manager *m, int events) { + struct udev_device *dev; + int r; + const char *action, *ready; + + assert(m); + + if (events != EPOLLIN) { + static RATELIMIT_DEFINE(limit, 10*USEC_PER_SEC, 5); + + if (!ratelimit_test(&limit)) + log_error("Failed to get udev event: %m"); + if (!(events & EPOLLIN)) + return; + } + + if (!(dev = udev_monitor_receive_device(m->udev_monitor))) { + /* + * libudev might filter-out devices which pass the bloom filter, + * so getting NULL here is not necessarily an error + */ + return; + } + + if (!(action = udev_device_get_action(dev))) { + log_error("Failed to get udev action string."); + goto fail; + } + + ready = udev_device_get_property_value(dev, "SYSTEMD_READY"); + + if (streq(action, "remove") || (ready && parse_boolean(ready) == 0)) { + if ((r = device_process_removed_device(m, dev)) < 0) { + log_error("Failed to process udev device event: %s", strerror(-r)); + goto fail; + } + } else { + if ((r = device_process_new_device(m, dev, true)) < 0) { + log_error("Failed to process udev device event: %s", strerror(-r)); + goto fail; + } + } + +fail: + udev_device_unref(dev); +} + +static const char* const device_state_table[_DEVICE_STATE_MAX] = { + [DEVICE_DEAD] = "dead", + [DEVICE_PLUGGED] = "plugged" +}; + +DEFINE_STRING_TABLE_LOOKUP(device_state, DeviceState); + +const UnitVTable device_vtable = { + .suffix = ".device", + .object_size = sizeof(Device), + .sections = + "Unit\0" + "Device\0" + "Install\0", + + .no_instances = true, + + .init = device_init, + + .load = unit_load_fragment_and_dropin_optional, + .done = device_done, + .coldplug = device_coldplug, + + .dump = device_dump, + + .active_state = device_active_state, + .sub_state_to_string = device_sub_state_to_string, + + .bus_interface = "org.freedesktop.systemd1.Device", + .bus_message_handler = bus_device_message_handler, + .bus_invalidating_properties = bus_device_invalidating_properties, + + .following = device_following, + .following_set = device_following_set, + + .enumerate = device_enumerate, + .shutdown = device_shutdown +}; diff --git a/src/core/device.h b/src/core/device.h new file mode 100644 index 0000000000..a05c3d37b0 --- /dev/null +++ b/src/core/device.h @@ -0,0 +1,59 @@ +/*-*- Mode: C; c-basic-offset: 8; indent-tabs-mode: nil -*-*/ + +#ifndef foodevicehfoo +#define foodevicehfoo + +/*** + This file is part of systemd. + + Copyright 2010 Lennart Poettering + + systemd is free software; you can redistribute it and/or modify it + under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + systemd is distributed in the hope that 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 General Public License + along with systemd; If not, see . +***/ + +typedef struct Device Device; + +#include "unit.h" + +/* We simply watch devices, we cannot plug/unplug them. That + * simplifies the state engine greatly */ +typedef enum DeviceState { + DEVICE_DEAD, + DEVICE_PLUGGED, + _DEVICE_STATE_MAX, + _DEVICE_STATE_INVALID = -1 +} DeviceState; + +struct Device { + Unit meta; + + char *sysfs; + + /* 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; +}; + +extern const UnitVTable device_vtable; + +void device_fd_event(Manager *m, int events); + +const char* device_state_to_string(DeviceState i); +DeviceState device_state_from_string(const char *s); + +#endif diff --git a/src/core/execute.c b/src/core/execute.c new file mode 100644 index 0000000000..179ca7e55c --- /dev/null +++ b/src/core/execute.c @@ -0,0 +1,2113 @@ +/*-*- Mode: C; c-basic-offset: 8; indent-tabs-mode: nil -*-*/ + +/*** + This file is part of systemd. + + Copyright 2010 Lennart Poettering + + systemd is free software; you can redistribute it and/or modify it + under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + systemd is distributed in the hope that 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 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 + +#ifdef HAVE_PAM +#include +#endif + +#include "execute.h" +#include "strv.h" +#include "macro.h" +#include "capability.h" +#include "util.h" +#include "log.h" +#include "ioprio.h" +#include "securebits.h" +#include "cgroup.h" +#include "namespace.h" +#include "tcpwrap.h" +#include "exit-status.h" +#include "missing.h" +#include "utmp-wtmp.h" +#include "def.h" +#include "loopback-setup.h" + +/* This assumes there is a 'tty' group */ +#define TTY_MODE 0620 + +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; + + if ((nfd = fcntl(fds[i], F_DUPFD, i+3)) < 0) + return -errno; + + close_nointr_nofail(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++) { + + if ((r = fd_nonblock(fds[i], nonblock)) < 0) + return r; + + /* We unconditionally drop FD_CLOEXEC from the fds, + * since after all we want to pass these fds to our + * children */ + + if ((r = fd_cloexec(fds[i], false)) < 0) + return r; + } + + return 0; +} + +static const char *tty_path(const ExecContext *context) { + assert(context); + + if (context->tty_path) + return context->tty_path; + + return "/dev/console"; +} + +void exec_context_tty_reset(const ExecContext *context) { + assert(context); + + if (context->tty_vhangup) + terminal_vhangup(tty_path(context)); + + if (context->tty_reset) + reset_terminal(tty_path(context)); + + if (context->tty_vt_disallocate && context->tty_path) + vt_disallocate(context->tty_path); +} + +static int open_null_as(int flags, int nfd) { + int fd, r; + + assert(nfd >= 0); + + if ((fd = open("/dev/null", flags|O_NOCTTY)) < 0) + return -errno; + + if (fd != nfd) { + r = dup2(fd, nfd) < 0 ? -errno : nfd; + close_nointr_nofail(fd); + } else + r = nfd; + + return r; +} + +static int connect_logger_as(const ExecContext *context, ExecOutput output, const char *ident, int nfd) { + int fd, r; + union sockaddr_union sa; + + assert(context); + assert(output < _EXEC_OUTPUT_MAX); + assert(ident); + assert(nfd >= 0); + + fd = socket(AF_UNIX, SOCK_STREAM, 0); + if (fd < 0) + return -errno; + + zero(sa); + sa.un.sun_family = AF_UNIX; + strncpy(sa.un.sun_path, "/run/systemd/journal/stdout", sizeof(sa.un.sun_path)); + + r = connect(fd, &sa.sa, offsetof(struct sockaddr_un, sun_path) + strlen(sa.un.sun_path)); + if (r < 0) { + close_nointr_nofail(fd); + return -errno; + } + + if (shutdown(fd, SHUT_RD) < 0) { + close_nointr_nofail(fd); + return -errno; + } + + dprintf(fd, + "%s\n" + "%i\n" + "%i\n" + "%i\n" + "%i\n" + "%i\n", + context->syslog_identifier ? context->syslog_identifier : ident, + 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, + output == EXEC_OUTPUT_SYSLOG_AND_CONSOLE || output == EXEC_OUTPUT_KMSG_AND_CONSOLE || output == EXEC_OUTPUT_JOURNAL_AND_CONSOLE); + + if (fd != nfd) { + r = dup2(fd, nfd) < 0 ? -errno : nfd; + close_nointr_nofail(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); + + if ((fd = open_terminal(path, mode | O_NOCTTY)) < 0) + return fd; + + if (fd != nfd) { + r = dup2(fd, nfd) < 0 ? -errno : nfd; + close_nointr_nofail(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, int socket_fd, bool apply_tty_stdin) { + ExecInput i; + + assert(context); + + i = fixup_input(context->std_input, socket_fd, 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; + + if ((fd = acquire_terminal( + tty_path(context), + i == EXEC_INPUT_TTY_FAIL, + i == EXEC_INPUT_TTY_FORCE, + false)) < 0) + return fd; + + if (fd != STDIN_FILENO) { + r = dup2(fd, STDIN_FILENO) < 0 ? -errno : STDIN_FILENO; + close_nointr_nofail(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(const ExecContext *context, int socket_fd, const char *ident, bool apply_tty_stdin) { + ExecOutput o; + ExecInput i; + + assert(context); + assert(ident); + + i = fixup_input(context->std_input, socket_fd, apply_tty_stdin); + o = fixup_output(context->std_output, socket_fd); + + /* This expects the input is already set up */ + + switch (o) { + + case 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(tty_path(context), O_WRONLY, STDOUT_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, STDOUT_FILENO) < 0 ? -errno : STDOUT_FILENO; + + /* If we are not started from PID 1 we just inherit STDOUT from our parent process. */ + if (getppid() != 1) + return STDOUT_FILENO; + + /* We need to open /dev/null here anew, to get the + * right access mode. So we fall through */ + + case EXEC_OUTPUT_NULL: + return open_null_as(O_WRONLY, STDOUT_FILENO); + + case EXEC_OUTPUT_TTY: + if (is_terminal_input(i)) + return dup2(STDIN_FILENO, STDOUT_FILENO) < 0 ? -errno : STDOUT_FILENO; + + /* We don't reset the terminal if this is just about output */ + return open_terminal_as(tty_path(context), O_WRONLY, STDOUT_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: + return connect_logger_as(context, o, ident, STDOUT_FILENO); + + case EXEC_OUTPUT_SOCKET: + assert(socket_fd >= 0); + return dup2(socket_fd, STDOUT_FILENO) < 0 ? -errno : STDOUT_FILENO; + + default: + assert_not_reached("Unknown output type"); + } +} + +static int setup_error(const ExecContext *context, int socket_fd, const char *ident, bool apply_tty_stdin) { + ExecOutput o, e; + ExecInput i; + + assert(context); + assert(ident); + + i = fixup_input(context->std_input, socket_fd, apply_tty_stdin); + o = fixup_output(context->std_output, socket_fd); + 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 STDERR_FILENO; + + /* Duplicate from stdout if possible */ + if (e == o || e == EXEC_OUTPUT_INHERIT) + return dup2(STDOUT_FILENO, STDERR_FILENO) < 0 ? -errno : STDERR_FILENO; + + switch (e) { + + case EXEC_OUTPUT_NULL: + return open_null_as(O_WRONLY, STDERR_FILENO); + + case EXEC_OUTPUT_TTY: + if (is_terminal_input(i)) + return dup2(STDIN_FILENO, STDERR_FILENO) < 0 ? -errno : STDERR_FILENO; + + /* We don't reset the terminal if this is just about output */ + return open_terminal_as(tty_path(context), O_WRONLY, STDERR_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: + return connect_logger_as(context, e, ident, STDERR_FILENO); + + case EXEC_OUTPUT_SOCKET: + assert(socket_fd >= 0); + return dup2(socket_fd, STDERR_FILENO) < 0 ? -errno : STDERR_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(const ExecContext *context, + int *_saved_stdin, + int *_saved_stdout) { + int fd = -1, saved_stdin, saved_stdout = -1, r; + + assert(context); + assert(_saved_stdin); + assert(_saved_stdout); + + /* This returns positive EXIT_xxx return values instead of + * negative errno style values! */ + + if ((saved_stdin = fcntl(STDIN_FILENO, F_DUPFD, 3)) < 0) + return EXIT_STDIN; + + if ((saved_stdout = fcntl(STDOUT_FILENO, F_DUPFD, 3)) < 0) { + r = EXIT_STDOUT; + goto fail; + } + + if ((fd = acquire_terminal( + tty_path(context), + context->std_input == EXEC_INPUT_TTY_FAIL, + context->std_input == EXEC_INPUT_TTY_FORCE, + false)) < 0) { + r = EXIT_STDIN; + goto fail; + } + + if (chown_terminal(fd, getuid()) < 0) { + r = EXIT_STDIN; + goto fail; + } + + if (dup2(fd, STDIN_FILENO) < 0) { + r = EXIT_STDIN; + goto fail; + } + + if (dup2(fd, STDOUT_FILENO) < 0) { + r = EXIT_STDOUT; + goto fail; + } + + if (fd >= 2) + close_nointr_nofail(fd); + + *_saved_stdin = saved_stdin; + *_saved_stdout = saved_stdout; + + return 0; + +fail: + if (saved_stdout >= 0) + close_nointr_nofail(saved_stdout); + + if (saved_stdin >= 0) + close_nointr_nofail(saved_stdin); + + if (fd >= 0) + close_nointr_nofail(fd); + + return r; +} + +static int restore_confirm_stdio(const ExecContext *context, + int *saved_stdin, + int *saved_stdout, + bool *keep_stdin, + bool *keep_stdout) { + + assert(context); + assert(saved_stdin); + assert(*saved_stdin >= 0); + assert(saved_stdout); + assert(*saved_stdout >= 0); + + /* This returns positive EXIT_xxx return values instead of + * negative errno style values! */ + + if (is_terminal_input(context->std_input)) { + + /* The service wants terminal input. */ + + *keep_stdin = true; + *keep_stdout = + context->std_output == EXEC_OUTPUT_INHERIT || + context->std_output == EXEC_OUTPUT_TTY; + + } else { + /* If the service doesn't want a controlling terminal, + * then we need to get rid entirely of what we have + * already. */ + + if (release_terminal() < 0) + return EXIT_STDIN; + + if (dup2(*saved_stdin, STDIN_FILENO) < 0) + return EXIT_STDIN; + + if (dup2(*saved_stdout, STDOUT_FILENO) < 0) + return EXIT_STDOUT; + + *keep_stdout = *keep_stdin = false; + } + + return 0; +} + +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) { + + if (context->group) { + const char *g = context->group; + + if ((r = get_group_creds(&g, &gid)) < 0) + return r; + } + + /* 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) { + if ((k = getgroups(ngroups_max, gids)) < 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) { + int r; + assert(context); + + /* Sets (but doesn't lookup) the uid and make sure we keep the + * capabilities while doing so. */ + + if (context->capabilities) { + cap_t d; + static const cap_value_t bits[] = { + CAP_SETUID, /* Necessary so that we can run setresuid() below */ + CAP_SETPCAP /* Necessary so that we can set PR_SET_SECUREBITS later on */ + }; + + /* First step: If we need to keep capabilities but + * drop privileges we need to make sure we keep our + * caps, whiel we drop privileges. */ + if (uid != 0) { + int sb = context->secure_bits|SECURE_KEEP_CAPS; + + if (prctl(PR_GET_SECUREBITS) != sb) + if (prctl(PR_SET_SECUREBITS, sb) < 0) + return -errno; + } + + /* Second step: set the capabilities. This will reduce + * the capabilities to the minimum we need. */ + + if (!(d = cap_dup(context->capabilities))) + return -errno; + + if (cap_set_flag(d, CAP_EFFECTIVE, ELEMENTSOF(bits), bits, CAP_SET) < 0 || + cap_set_flag(d, CAP_PERMITTED, ELEMENTSOF(bits), bits, CAP_SET) < 0) { + r = -errno; + cap_free(d); + return r; + } + + if (cap_set_proc(d) < 0) { + r = -errno; + cap_free(d); + return r; + } + + cap_free(d); + } + + /* Third step: actually set the uids */ + if (setresuid(uid, uid, uid) < 0) + return -errno; + + /* At this point we should have all necessary capabilities but + are otherwise a normal user. However, the caps might got + corrupted due to the setresuid() so we need clean them up + later. This is done outside of this call. */ + + return 0; +} + +#ifdef HAVE_PAM + +static int null_conv( + int num_msg, + const struct pam_message **msg, + struct pam_response **resp, + void *appdata_ptr) { + + /* We don't support conversations */ + + return PAM_CONV_ERR; +} + +static int setup_pam( + const char *name, + const char *user, + const char *tty, + char ***pam_env, + int fds[], unsigned n_fds) { + + static const struct pam_conv conv = { + .conv = null_conv, + .appdata_ptr = NULL + }; + + pam_handle_t *handle = NULL; + sigset_t ss, old_ss; + int pam_code = PAM_SUCCESS; + int err; + char **e = NULL; + bool close_session = false; + pid_t pam_pid = 0, parent_pid; + + assert(name); + assert(user); + assert(pam_env); + + /* We set up PAM in the parent process, then fork. The child + * will then stay around until killed via PR_GET_PDEATHSIG or + * systemd via the cgroup logic. It will then remove the PAM + * session again. The parent process will exec() the actual + * daemon. We do things this way to ensure that the main PID + * of the daemon is the one we initially fork()ed. */ + + if ((pam_code = pam_start(name, user, &conv, &handle)) != PAM_SUCCESS) { + handle = NULL; + goto fail; + } + + if (tty) + if ((pam_code = pam_set_item(handle, PAM_TTY, tty)) != PAM_SUCCESS) + goto fail; + + if ((pam_code = pam_acct_mgmt(handle, PAM_SILENT)) != PAM_SUCCESS) + goto fail; + + if ((pam_code = pam_open_session(handle, PAM_SILENT)) != PAM_SUCCESS) + goto fail; + + close_session = true; + + if ((!(e = pam_getenvlist(handle)))) { + pam_code = PAM_BUF_ERR; + goto fail; + } + + /* Block SIGTERM, so that we know that it won't get lost in + * the child */ + if (sigemptyset(&ss) < 0 || + sigaddset(&ss, SIGTERM) < 0 || + sigprocmask(SIG_BLOCK, &ss, &old_ss) < 0) + goto fail; + + parent_pid = getpid(); + + if ((pam_pid = fork()) < 0) + goto fail; + + if (pam_pid == 0) { + int sig; + int r = EXIT_PAM; + + /* The child's job is to reset the PAM session on + * termination */ + + /* 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); + + /* Wait until our parent died. This will most likely + * not work since the kernel does 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; + + /* Check if our parent process might already have + * died? */ + if (getppid() == parent_pid) { + 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) + if ((pam_code = pam_close_session(handle, PAM_DATA_SILENT)) != PAM_SUCCESS) + goto child_finish; + + r = 0; + + child_finish: + pam_end(handle, pam_code | PAM_DATA_SILENT); + _exit(r); + } + + /* 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 */ + if (sigprocmask(SIG_SETMASK, &old_ss, NULL) < 0) + goto fail; + + /* We close the log explicitly here, since the PAM modules + * might have opened it, but we don't want this fd around. */ + closelog(); + + *pam_env = e; + e = NULL; + + return 0; + +fail: + if (pam_code != PAM_SUCCESS) + err = -EPERM; /* PAM errors do not map to errno */ + else + err = -errno; + + if (handle) { + if (close_session) + pam_code = pam_close_session(handle, PAM_DATA_SILENT); + + pam_end(handle, pam_code | PAM_DATA_SILENT); + } + + strv_free(e); + + closelog(); + + if (pam_pid > 1) { + kill(pam_pid, SIGTERM); + kill(pam_pid, SIGCONT); + } + + return err; +} +#endif + +static int do_capability_bounding_set_drop(uint64_t drop) { + unsigned long i; + cap_t old_cap = NULL, new_cap = NULL; + cap_flag_value_t fv; + int r; + + /* If we are run as PID 1 we will lack CAP_SETPCAP by default + * in the effective set (yes, the kernel drops that when + * executing init!), so get it back temporarily so that we can + * call PR_CAPBSET_DROP. */ + + old_cap = cap_get_proc(); + if (!old_cap) + return -errno; + + if (cap_get_flag(old_cap, CAP_SETPCAP, CAP_EFFECTIVE, &fv) < 0) { + r = -errno; + goto finish; + } + + if (fv != CAP_SET) { + static const cap_value_t v = CAP_SETPCAP; + + new_cap = cap_dup(old_cap); + if (!new_cap) { + r = -errno; + goto finish; + } + + if (cap_set_flag(new_cap, CAP_EFFECTIVE, 1, &v, CAP_SET) < 0) { + r = -errno; + goto finish; + } + + if (cap_set_proc(new_cap) < 0) { + r = -errno; + goto finish; + } + } + + for (i = 0; i <= cap_last_cap(); i++) + if (drop & ((uint64_t) 1ULL << (uint64_t) i)) { + if (prctl(PR_CAPBSET_DROP, i) < 0) { + r = -errno; + goto finish; + } + } + + r = 0; + +finish: + if (new_cap) + cap_free(new_cap); + + if (old_cap) { + cap_set_proc(old_cap); + cap_free(old_cap); + } + + return r; +} + +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 = file_name_from_path(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); +} + +int exec_spawn(ExecCommand *command, + char **argv, + const ExecContext *context, + int fds[], unsigned n_fds, + char **environment, + bool apply_permissions, + bool apply_chroot, + bool apply_tty_stdin, + bool confirm_spawn, + CGroupBonding *cgroup_bondings, + CGroupAttribute *cgroup_attributes, + pid_t *ret) { + + pid_t pid; + int r; + char *line; + int socket_fd; + char **files_env = NULL; + + assert(command); + assert(context); + assert(ret); + assert(fds || n_fds <= 0); + + if (context->std_input == EXEC_INPUT_SOCKET || + context->std_output == EXEC_OUTPUT_SOCKET || + context->std_error == EXEC_OUTPUT_SOCKET) { + + if (n_fds != 1) + return -EINVAL; + + socket_fd = fds[0]; + + fds = NULL; + n_fds = 0; + } else + socket_fd = -1; + + if ((r = exec_context_load_environment(context, &files_env)) < 0) { + log_error("Failed to load environment files: %s", strerror(-r)); + return r; + } + + if (!argv) + argv = command->argv; + + if (!(line = exec_command_line(argv))) { + r = -ENOMEM; + goto fail_parent; + } + + log_debug("About to execute: %s", line); + free(line); + + r = cgroup_bonding_realize_list(cgroup_bondings); + if (r < 0) + goto fail_parent; + + cgroup_attribute_apply_list(cgroup_attributes, cgroup_bondings); + + if ((pid = fork()) < 0) { + r = -errno; + goto fail_parent; + } + + if (pid == 0) { + int i, err; + sigset_t ss; + const char *username = NULL, *home = NULL; + uid_t uid = (uid_t) -1; + gid_t gid = (gid_t) -1; + char **our_env = NULL, **pam_env = NULL, **final_env = NULL, **final_argv = NULL; + unsigned n_env = 0; + int saved_stdout = -1, saved_stdin = -1; + bool keep_stdout = false, keep_stdin = false, set_access = false; + + /* child */ + + 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. */ + default_signals(SIGNALS_CRASH_HANDLER, + SIGNALS_IGNORE, -1); + + if (context->ignore_sigpipe) + ignore_signals(SIGPIPE, -1); + + assert_se(sigemptyset(&ss) == 0); + if (sigprocmask(SIG_SETMASK, &ss, NULL) < 0) { + err = -errno; + r = EXIT_SIGNAL_MASK; + goto fail_child; + } + + /* Close sockets very early to make sure we don't + * block init reexecution because it cannot bind its + * sockets */ + log_forget_fds(); + err = close_all_fds(socket_fd >= 0 ? &socket_fd : fds, + socket_fd >= 0 ? 1 : n_fds); + if (err < 0) { + r = EXIT_FDS; + goto fail_child; + } + + if (!context->same_pgrp) + if (setsid() < 0) { + err = -errno; + r = EXIT_SETSID; + goto fail_child; + } + + if (context->tcpwrap_name) { + if (socket_fd >= 0) + if (!socket_tcpwrap(socket_fd, context->tcpwrap_name)) { + err = -EACCES; + r = EXIT_TCPWRAP; + goto fail_child; + } + + for (i = 0; i < (int) n_fds; i++) { + if (!socket_tcpwrap(fds[i], context->tcpwrap_name)) { + err = -EACCES; + r = EXIT_TCPWRAP; + goto fail_child; + } + } + } + + exec_context_tty_reset(context); + + /* We skip the confirmation step if we shall not apply the TTY */ + if (confirm_spawn && + (!is_terminal_input(context->std_input) || apply_tty_stdin)) { + char response; + + /* Set up terminal for the question */ + if ((r = setup_confirm_stdio(context, + &saved_stdin, &saved_stdout))) { + err = -errno; + goto fail_child; + } + + /* Now ask the question. */ + if (!(line = exec_command_line(argv))) { + err = -ENOMEM; + r = EXIT_MEMORY; + goto fail_child; + } + + r = ask(&response, "yns", "Execute %s? [Yes, No, Skip] ", line); + free(line); + + if (r < 0 || response == 'n') { + err = -ECANCELED; + r = EXIT_CONFIRM; + goto fail_child; + } else if (response == 's') { + err = r = 0; + goto fail_child; + } + + /* Release terminal for the question */ + if ((r = restore_confirm_stdio(context, + &saved_stdin, &saved_stdout, + &keep_stdin, &keep_stdout))) { + err = -errno; + goto fail_child; + } + } + + /* If a socket is connected to STDIN/STDOUT/STDERR, we + * must sure to drop O_NONBLOCK */ + if (socket_fd >= 0) + fd_nonblock(socket_fd, false); + + if (!keep_stdin) { + err = setup_input(context, socket_fd, apply_tty_stdin); + if (err < 0) { + r = EXIT_STDIN; + goto fail_child; + } + } + + if (!keep_stdout) { + err = setup_output(context, socket_fd, file_name_from_path(command->path), apply_tty_stdin); + if (err < 0) { + r = EXIT_STDOUT; + goto fail_child; + } + } + + err = setup_error(context, socket_fd, file_name_from_path(command->path), apply_tty_stdin); + if (err < 0) { + r = EXIT_STDERR; + goto fail_child; + } + + if (cgroup_bondings) { + err = cgroup_bonding_install_list(cgroup_bondings, 0); + if (err < 0) { + r = EXIT_CGROUP; + goto fail_child; + } + } + + if (context->oom_score_adjust_set) { + char t[16]; + + snprintf(t, sizeof(t), "%i", context->oom_score_adjust); + char_array_0(t); + + if (write_one_line_file("/proc/self/oom_score_adj", t) < 0) { + /* Compatibility with Linux <= 2.6.35 */ + + int adj; + + adj = (context->oom_score_adjust * -OOM_DISABLE) / OOM_SCORE_ADJ_MAX; + adj = CLAMP(adj, OOM_DISABLE, OOM_ADJUST_MAX); + + snprintf(t, sizeof(t), "%i", adj); + char_array_0(t); + + if (write_one_line_file("/proc/self/oom_adj", t) < 0 + && errno != EACCES) { + err = -errno; + r = EXIT_OOM_ADJUST; + goto fail_child; + } + } + } + + if (context->nice_set) + if (setpriority(PRIO_PROCESS, 0, context->nice) < 0) { + err = -errno; + r = EXIT_NICE; + goto fail_child; + } + + if (context->cpu_sched_set) { + struct sched_param param; + + zero(param); + param.sched_priority = context->cpu_sched_priority; + + if (sched_setscheduler(0, context->cpu_sched_policy | + (context->cpu_sched_reset_on_fork ? SCHED_RESET_ON_FORK : 0), ¶m) < 0) { + err = -errno; + r = EXIT_SETSCHEDULER; + goto fail_child; + } + } + + if (context->cpuset) + if (sched_setaffinity(0, CPU_ALLOC_SIZE(context->cpuset_ncpus), context->cpuset) < 0) { + err = -errno; + r = EXIT_CPUAFFINITY; + goto fail_child; + } + + if (context->ioprio_set) + if (ioprio_set(IOPRIO_WHO_PROCESS, 0, context->ioprio) < 0) { + err = -errno; + r = EXIT_IOPRIO; + goto fail_child; + } + + if (context->timer_slack_nsec_set) + if (prctl(PR_SET_TIMERSLACK, context->timer_slack_nsec) < 0) { + err = -errno; + r = EXIT_TIMERSLACK; + goto fail_child; + } + + if (context->utmp_id) + utmp_put_init_process(context->utmp_id, getpid(), getsid(0), context->tty_path); + + if (context->user) { + username = context->user; + err = get_user_creds(&username, &uid, &gid, &home); + if (err < 0) { + r = EXIT_USER; + goto fail_child; + } + + if (is_terminal_input(context->std_input)) { + err = chown_terminal(STDIN_FILENO, uid); + if (err < 0) { + r = EXIT_STDIN; + goto fail_child; + } + } + + if (cgroup_bondings && context->control_group_modify) { + err = cgroup_bonding_set_group_access_list(cgroup_bondings, 0755, uid, gid); + if (err >= 0) + err = cgroup_bonding_set_task_access_list(cgroup_bondings, 0644, uid, gid, context->control_group_persistent); + if (err < 0) { + r = EXIT_CGROUP; + goto fail_child; + } + + set_access = true; + } + } + + if (cgroup_bondings && !set_access && context->control_group_persistent >= 0) { + err = cgroup_bonding_set_task_access_list(cgroup_bondings, (mode_t) -1, (uid_t) -1, (uid_t) -1, context->control_group_persistent); + if (err < 0) { + r = EXIT_CGROUP; + goto fail_child; + } + } + + if (apply_permissions) { + err = enforce_groups(context, username, gid); + if (err < 0) { + r = EXIT_GROUP; + goto fail_child; + } + } + + umask(context->umask); + +#ifdef HAVE_PAM + if (context->pam_name && username) { + err = setup_pam(context->pam_name, username, context->tty_path, &pam_env, fds, n_fds); + if (err < 0) { + r = EXIT_PAM; + goto fail_child; + } + } +#endif + if (context->private_network) { + if (unshare(CLONE_NEWNET) < 0) { + err = -errno; + r = EXIT_NETWORK; + goto fail_child; + } + + loopback_setup(); + } + + if (strv_length(context->read_write_dirs) > 0 || + strv_length(context->read_only_dirs) > 0 || + strv_length(context->inaccessible_dirs) > 0 || + context->mount_flags != MS_SHARED || + context->private_tmp) { + err = setup_namespace(context->read_write_dirs, + context->read_only_dirs, + context->inaccessible_dirs, + context->private_tmp, + context->mount_flags); + if (err < 0) { + r = EXIT_NAMESPACE; + goto fail_child; + } + } + + if (apply_chroot) { + if (context->root_directory) + if (chroot(context->root_directory) < 0) { + err = -errno; + r = EXIT_CHROOT; + goto fail_child; + } + + if (chdir(context->working_directory ? context->working_directory : "/") < 0) { + err = -errno; + r = EXIT_CHDIR; + goto fail_child; + } + } else { + + char *d; + + if (asprintf(&d, "%s/%s", + context->root_directory ? context->root_directory : "", + context->working_directory ? context->working_directory : "") < 0) { + err = -ENOMEM; + r = EXIT_MEMORY; + goto fail_child; + } + + if (chdir(d) < 0) { + err = -errno; + free(d); + r = EXIT_CHDIR; + goto fail_child; + } + + free(d); + } + + /* We repeat the fd closing here, to make sure that + * nothing is leaked from the PAM modules */ + err = close_all_fds(fds, n_fds); + if (err >= 0) + err = shift_fds(fds, n_fds); + if (err >= 0) + err = flags_fds(fds, n_fds, context->non_blocking); + if (err < 0) { + r = EXIT_FDS; + goto fail_child; + } + + if (apply_permissions) { + + for (i = 0; i < RLIMIT_NLIMITS; i++) { + if (!context->rlimit[i]) + continue; + + if (setrlimit(i, context->rlimit[i]) < 0) { + err = -errno; + r = EXIT_LIMITS; + goto fail_child; + } + } + + if (context->capability_bounding_set_drop) { + err = do_capability_bounding_set_drop(context->capability_bounding_set_drop); + if (err < 0) { + r = EXIT_CAPABILITIES; + goto fail_child; + } + } + + if (context->user) { + err = enforce_user(context, uid); + if (err < 0) { + r = EXIT_USER; + goto fail_child; + } + } + + /* PR_GET_SECUREBITS is not privileged, while + * PR_SET_SECUREBITS is. So to suppress + * potential EPERMs we'll try not to call + * PR_SET_SECUREBITS unless necessary. */ + if (prctl(PR_GET_SECUREBITS) != context->secure_bits) + if (prctl(PR_SET_SECUREBITS, context->secure_bits) < 0) { + err = -errno; + r = EXIT_SECUREBITS; + goto fail_child; + } + + if (context->capabilities) + if (cap_set_proc(context->capabilities) < 0) { + err = -errno; + r = EXIT_CAPABILITIES; + goto fail_child; + } + } + + if (!(our_env = new0(char*, 7))) { + err = -ENOMEM; + r = EXIT_MEMORY; + goto fail_child; + } + + if (n_fds > 0) + if (asprintf(our_env + n_env++, "LISTEN_PID=%lu", (unsigned long) getpid()) < 0 || + asprintf(our_env + n_env++, "LISTEN_FDS=%u", n_fds) < 0) { + err = -ENOMEM; + r = EXIT_MEMORY; + goto fail_child; + } + + if (home) + if (asprintf(our_env + n_env++, "HOME=%s", home) < 0) { + err = -ENOMEM; + r = EXIT_MEMORY; + goto fail_child; + } + + if (username) + if (asprintf(our_env + n_env++, "LOGNAME=%s", username) < 0 || + asprintf(our_env + n_env++, "USER=%s", username) < 0) { + err = -ENOMEM; + r = EXIT_MEMORY; + goto fail_child; + } + + if (is_terminal_input(context->std_input) || + context->std_output == EXEC_OUTPUT_TTY || + context->std_error == EXEC_OUTPUT_TTY) + if (!(our_env[n_env++] = strdup(default_term_for_tty(tty_path(context))))) { + err = -ENOMEM; + r = EXIT_MEMORY; + goto fail_child; + } + + assert(n_env <= 7); + + if (!(final_env = strv_env_merge( + 5, + environment, + our_env, + context->environment, + files_env, + pam_env, + NULL))) { + err = -ENOMEM; + r = EXIT_MEMORY; + goto fail_child; + } + + if (!(final_argv = replace_env_argv(argv, final_env))) { + err = -ENOMEM; + r = EXIT_MEMORY; + goto fail_child; + } + + final_env = strv_env_clean(final_env); + + execve(command->path, final_argv, final_env); + err = -errno; + r = EXIT_EXEC; + + fail_child: + if (r != 0) { + log_open(); + log_warning("Failed at step %s spawning %s: %s", + exit_status_to_string(r, EXIT_STATUS_SYSTEMD), + command->path, strerror(-err)); + } + + strv_free(our_env); + strv_free(final_env); + strv_free(pam_env); + strv_free(files_env); + strv_free(final_argv); + + if (saved_stdin >= 0) + close_nointr_nofail(saved_stdin); + + if (saved_stdout >= 0) + close_nointr_nofail(saved_stdout); + + _exit(r); + } + + strv_free(files_env); + + /* 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 (cgroup_bondings) + cgroup_bonding_install_list(cgroup_bondings, pid); + + log_debug("Forked %s as %lu", command->path, (unsigned long) pid); + + exec_status_start(&command->exec_status, pid); + + *ret = pid; + return 0; + +fail_parent: + strv_free(files_env); + + return r; +} + +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->mount_flags = MS_SHARED; + c->kill_signal = SIGTERM; + c->send_sigkill = true; + c->control_group_persistent = -1; + c->ignore_sigpipe = true; +} + +void exec_context_done(ExecContext *c) { + unsigned l; + + assert(c); + + strv_free(c->environment); + c->environment = NULL; + + strv_free(c->environment_files); + c->environment_files = NULL; + + for (l = 0; l < ELEMENTSOF(c->rlimit); l++) { + free(c->rlimit[l]); + c->rlimit[l] = NULL; + } + + free(c->working_directory); + c->working_directory = NULL; + free(c->root_directory); + c->root_directory = NULL; + + free(c->tty_path); + c->tty_path = NULL; + + free(c->tcpwrap_name); + c->tcpwrap_name = NULL; + + free(c->syslog_identifier); + c->syslog_identifier = NULL; + + free(c->user); + c->user = NULL; + + free(c->group); + c->group = NULL; + + strv_free(c->supplementary_groups); + c->supplementary_groups = NULL; + + free(c->pam_name); + c->pam_name = NULL; + + if (c->capabilities) { + cap_free(c->capabilities); + c->capabilities = NULL; + } + + strv_free(c->read_only_dirs); + c->read_only_dirs = NULL; + + strv_free(c->read_write_dirs); + c->read_write_dirs = NULL; + + strv_free(c->inaccessible_dirs); + c->inaccessible_dirs = NULL; + + if (c->cpuset) + CPU_FREE(c->cpuset); + + free(c->utmp_id); + c->utmp_id = NULL; +} + +void exec_command_done(ExecCommand *c) { + assert(c); + + free(c->path); + c->path = NULL; + + strv_free(c->argv); + c->argv = NULL; +} + +void exec_command_done_array(ExecCommand *c, unsigned n) { + unsigned i; + + for (i = 0; i < n; i++) + exec_command_done(c+i); +} + +void exec_command_free_list(ExecCommand *c) { + ExecCommand *i; + + while ((i = c)) { + LIST_REMOVE(ExecCommand, command, c, i); + exec_command_done(i); + free(i); + } +} + +void exec_command_free_array(ExecCommand **c, unsigned n) { + unsigned i; + + for (i = 0; i < n; i++) { + exec_command_free_list(c[i]); + c[i] = NULL; + } +} + +int exec_context_load_environment(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; + + fn = *i; + + if (fn[0] == '-') { + ignore = true; + fn ++; + } + + if (!path_is_absolute(fn)) { + + if (ignore) + continue; + + strv_free(r); + return -EINVAL; + } + + if ((k = load_env_file(fn, &p)) < 0) { + + if (ignore) + continue; + + strv_free(r); + return k; + } + + 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 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; + unsigned i; + + assert(c); + assert(f); + + if (!prefix) + prefix = ""; + + fprintf(f, + "%sUMask: %04o\n" + "%sWorkingDirectory: %s\n" + "%sRootDirectory: %s\n" + "%sNonBlocking: %s\n" + "%sPrivateTmp: %s\n" + "%sControlGroupModify: %s\n" + "%sControlGroupPersistent: %s\n" + "%sPrivateNetwork: %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->control_group_modify), + prefix, yes_no(c->control_group_persistent), + prefix, yes_no(c->private_network)); + + 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); + + if (c->tcpwrap_name) + fprintf(f, + "%sTCPWrapName: %s\n", + prefix, c->tcpwrap_name); + + 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: %llu\n", prefix, rlimit_to_string(i), (unsigned long long) c->rlimit[i]->rlim_max); + + if (c->ioprio_set) + fprintf(f, + "%sIOSchedulingClass: %s\n" + "%sIOPriority: %i\n", + prefix, ioprio_class_to_string(IOPRIO_PRIO_CLASS(c->ioprio)), + prefix, (int) IOPRIO_PRIO_DATA(c->ioprio)); + + if (c->cpu_sched_set) + fprintf(f, + "%sCPUSchedulingPolicy: %s\n" + "%sCPUSchedulingPriority: %i\n" + "%sCPUSchedulingResetOnFork: %s\n", + prefix, sched_policy_to_string(c->cpu_sched_policy), + 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, " %i", i); + fputs("\n", f); + } + + if (c->timer_slack_nsec_set) + fprintf(f, "%sTimerSlackNSec: %lu\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) + fprintf(f, + "%sSyslogFacility: %s\n" + "%sSyslogLevel: %s\n", + prefix, log_facility_unshifted_to_string(c->syslog_priority >> 3), + prefix, log_level_to_string(LOG_PRI(c->syslog_priority))); + + if (c->capabilities) { + char *t; + if ((t = cap_to_text(c->capabilities, NULL))) { + fprintf(f, "%sCapabilities: %s\n", + prefix, t); + cap_free(t); + } + } + + if (c->secure_bits) + fprintf(f, "%sSecure Bits:%s%s%s%s%s%s\n", + prefix, + (c->secure_bits & SECURE_KEEP_CAPS) ? " keep-caps" : "", + (c->secure_bits & SECURE_KEEP_CAPS_LOCKED) ? " keep-caps-locked" : "", + (c->secure_bits & SECURE_NO_SETUID_FIXUP) ? " no-setuid-fixup" : "", + (c->secure_bits & SECURE_NO_SETUID_FIXUP_LOCKED) ? " no-setuid-fixup-locked" : "", + (c->secure_bits & SECURE_NOROOT) ? " noroot" : "", + (c->secure_bits & SECURE_NOROOT_LOCKED) ? "noroot-locked" : ""); + + if (c->capability_bounding_set_drop) { + unsigned long l; + fprintf(f, "%sCapabilityBoundingSet:", prefix); + + for (l = 0; l <= cap_last_cap(); l++) + if (!(c->capability_bounding_set_drop & ((uint64_t) 1ULL << (uint64_t) l))) { + char *t; + + if ((t = cap_to_name(l))) { + fprintf(f, " %s", t); + cap_free(t); + } + } + + 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); + } + + fprintf(f, + "%sKillMode: %s\n" + "%sKillSignal: SIG%s\n" + "%sSendSIGKILL: %s\n" + "%sIgnoreSIGPIPE: %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->ignore_sigpipe)); + + if (c->utmp_id) + fprintf(f, + "%sUtmpIdentifier: %s\n", + prefix, c->utmp_id); +} + +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); + } +} + +void exec_status_dump(ExecStatus *s, FILE *f, const char *prefix) { + char buf[FORMAT_TIMESTAMP_MAX]; + + assert(s); + assert(f); + + if (!prefix) + prefix = ""; + + if (s->pid <= 0) + return; + + fprintf(f, + "%sPID: %lu\n", + prefix, (unsigned long) 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) { + char *p2; + const char *prefix2; + + char *cmd; + + assert(c); + assert(f); + + if (!prefix) + prefix = ""; + p2 = strappend(prefix, "\t"); + prefix2 = p2 ? p2 : prefix; + + cmd = exec_command_line(c->argv); + + fprintf(f, + "%sCommand Line: %s\n", + prefix, cmd ? cmd : strerror(ENOMEM)); + + free(cmd); + + exec_status_dump(&c->exec_status, f, prefix2); + + free(p2); +} + +void exec_command_dump_list(ExecCommand *c, FILE *f, const char *prefix) { + assert(f); + + if (!prefix) + 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(ExecCommand, command, *l, end); + LIST_INSERT_AFTER(ExecCommand, 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; + + if (!(p = strdup(path))) { + strv_free(l); + return -ENOMEM; + } + + free(c->path); + c->path = p; + + strv_free(c->argv); + c->argv = l; + + return 0; +} + +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 kill_mode_table[_KILL_MODE_MAX] = { + [KILL_CONTROL_GROUP] = "control-group", + [KILL_PROCESS] = "process", + [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" +}; + +DEFINE_STRING_TABLE_LOOKUP(kill_who, KillWho); diff --git a/src/core/execute.h b/src/core/execute.h new file mode 100644 index 0000000000..0d7e7dd65d --- /dev/null +++ b/src/core/execute.h @@ -0,0 +1,233 @@ +/*-*- Mode: C; c-basic-offset: 8; indent-tabs-mode: nil -*-*/ + +#ifndef fooexecutehfoo +#define fooexecutehfoo + +/*** + This file is part of systemd. + + Copyright 2010 Lennart Poettering + + systemd is free software; you can redistribute it and/or modify it + under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + systemd is distributed in the hope that 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 General Public License + along with systemd; If not, see . +***/ + +typedef struct ExecStatus ExecStatus; +typedef struct ExecCommand ExecCommand; +typedef struct ExecContext ExecContext; + +#include +#include +#include +#include +#include +#include +#include + +struct CGroupBonding; +struct CGroupAttribute; + +#include "list.h" +#include "util.h" + +typedef enum KillMode { + KILL_CONTROL_GROUP = 0, + KILL_PROCESS, + KILL_NONE, + _KILL_MODE_MAX, + _KILL_MODE_INVALID = -1 +} KillMode; + +typedef enum KillWho { + KILL_MAIN, + KILL_CONTROL, + KILL_ALL, + _KILL_WHO_MAX, + _KILL_WHO_INVALID = -1 +} KillWho; + +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 ExecContext { + char **environment; + char **environment_files; + + struct rlimit *rlimit[RLIMIT_NLIMITS]; + char *working_directory, *root_directory; + + 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; + + unsigned long timer_slack_nsec; + + char *tcpwrap_name; + + 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; + + char **read_write_dirs, **read_only_dirs, **inaccessible_dirs; + unsigned long mount_flags; + + uint64_t capability_bounding_set_drop; + + /* Not relevant for spawning processes, just for killing */ + KillMode kill_mode; + int kill_signal; + bool send_sigkill; + + cap_t capabilities; + int secure_bits; + + int syslog_priority; + char *syslog_identifier; + bool syslog_level_prefix; + + bool cpu_sched_reset_on_fork; + bool non_blocking; + bool private_tmp; + bool private_network; + + bool control_group_modify; + int control_group_persistent; + + /* This is not exposed to the user but available + * internally. We need it to make sure that whenever we spawn + * /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; + + bool oom_score_adjust_set:1; + bool nice_set:1; + bool ioprio_set:1; + bool cpu_sched_set:1; + bool timer_slack_nsec_set:1; +}; + +int exec_spawn(ExecCommand *command, + char **argv, + const ExecContext *context, + int fds[], unsigned n_fds, + char **environment, + bool apply_permissions, + bool apply_chroot, + bool apply_tty_stdin, + bool confirm_spawn, + struct CGroupBonding *cgroup_bondings, + struct CGroupAttribute *cgroup_attributes, + pid_t *ret); + +void exec_command_done(ExecCommand *c); +void exec_command_done_array(ExecCommand *c, unsigned n); + +void 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, ...); + +void exec_context_init(ExecContext *c); +void exec_context_done(ExecContext *c); +void exec_context_dump(ExecContext *c, FILE* f, const char *prefix); +void exec_context_tty_reset(const ExecContext *context); + +int exec_context_load_environment(const ExecContext *c, char ***l); + +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); + +const char* exec_output_to_string(ExecOutput i); +ExecOutput exec_output_from_string(const char *s); + +const char* exec_input_to_string(ExecInput i); +ExecInput exec_input_from_string(const char *s); + +const char *kill_mode_to_string(KillMode k); +KillMode kill_mode_from_string(const char *s); + +const char *kill_who_to_string(KillWho k); +KillWho kill_who_from_string(const char *s); + +#endif diff --git a/src/core/fdset.c b/src/core/fdset.c new file mode 100644 index 0000000000..e67fe6fabf --- /dev/null +++ b/src/core/fdset.c @@ -0,0 +1,167 @@ +/*-*- Mode: C; c-basic-offset: 8; indent-tabs-mode: nil -*-*/ + +/*** + This file is part of systemd. + + Copyright 2010 Lennart Poettering + + systemd is free software; you can redistribute it and/or modify it + under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + systemd is distributed in the hope that 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 General Public License + along with systemd; If not, see . +***/ + +#include +#include +#include +#include + +#include "set.h" +#include "util.h" +#include "macro.h" +#include "fdset.h" + +#define MAKE_SET(s) ((Set*) s) +#define MAKE_FDSET(s) ((FDSet*) s) + +/* Make sure we can distuingish fd 0 and NULL */ +#define FD_TO_PTR(fd) INT_TO_PTR((fd)+1) +#define PTR_TO_FD(p) (PTR_TO_INT(p)-1) + +FDSet *fdset_new(void) { + return MAKE_FDSET(set_new(trivial_hash_func, trivial_compare_func)); +} + +void fdset_free(FDSet *s) { + void *p; + + while ((p = set_steal_first(MAKE_SET(s)))) { + /* Valgrind's fd might have ended up in this set here, + * due to fdset_new_fill(). We'll ignore all failures + * here, so that the EBADFD that valgrind will return + * us on close() doesn't influence us */ + + /* When reloading duplicates of the private bus + * connection fds and suchlike are closed here, which + * has no effect at all, since they are only + * duplicates. So don't be surprised about these log + * messages. */ + + log_debug("Closing left-over fd %i", PTR_TO_FD(p)); + close_nointr(PTR_TO_FD(p)); + } + + set_free(MAKE_SET(s)); +} + +int fdset_put(FDSet *s, int fd) { + assert(s); + assert(fd >= 0); + + return set_put(MAKE_SET(s), FD_TO_PTR(fd)); +} + +int fdset_put_dup(FDSet *s, int fd) { + int copy, r; + + assert(s); + assert(fd >= 0); + + if ((copy = fcntl(fd, F_DUPFD_CLOEXEC, 3)) < 0) + return -errno; + + if ((r = fdset_put(s, copy)) < 0) { + close_nointr_nofail(copy); + return r; + } + + return copy; +} + +bool fdset_contains(FDSet *s, int fd) { + assert(s); + assert(fd >= 0); + + return !!set_get(MAKE_SET(s), FD_TO_PTR(fd)); +} + +int fdset_remove(FDSet *s, int fd) { + assert(s); + assert(fd >= 0); + + return set_remove(MAKE_SET(s), FD_TO_PTR(fd)) ? fd : -ENOENT; +} + +int fdset_new_fill(FDSet **_s) { + DIR *d; + struct dirent *de; + int r = 0; + FDSet *s; + + assert(_s); + + /* Creates an fdsets and fills in all currently open file + * descriptors. */ + + if (!(d = opendir("/proc/self/fd"))) + return -errno; + + if (!(s = fdset_new())) { + r = -ENOMEM; + goto finish; + } + + while ((de = readdir(d))) { + int fd = -1; + + if (ignore_file(de->d_name)) + continue; + + if ((r = safe_atoi(de->d_name, &fd)) < 0) + goto finish; + + if (fd < 3) + continue; + + if (fd == dirfd(d)) + continue; + + if ((r = fdset_put(s, fd)) < 0) + goto finish; + } + + r = 0; + *_s = s; + s = NULL; + +finish: + closedir(d); + + /* We won't close the fds here! */ + if (s) + set_free(MAKE_SET(s)); + + return r; +} + +int fdset_cloexec(FDSet *fds, bool b) { + Iterator i; + void *p; + int r; + + assert(fds); + + SET_FOREACH(p, MAKE_SET(fds), i) + if ((r = fd_cloexec(PTR_TO_FD(p), b)) < 0) + return r; + + return 0; +} diff --git a/src/core/fdset.h b/src/core/fdset.h new file mode 100644 index 0000000000..044a9e6d1f --- /dev/null +++ b/src/core/fdset.h @@ -0,0 +1,40 @@ +/*-*- Mode: C; c-basic-offset: 8; indent-tabs-mode: nil -*-*/ + +#ifndef foofdsethfoo +#define foofdsethfoo + +/*** + This file is part of systemd. + + Copyright 2010 Lennart Poettering + + systemd is free software; you can redistribute it and/or modify it + under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + systemd is distributed in the hope that 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 General Public License + along with systemd; If not, see . +***/ + +typedef struct FDSet FDSet; + +FDSet* fdset_new(void); +void fdset_free(FDSet *s); + +int fdset_put(FDSet *s, int fd); +int fdset_put_dup(FDSet *s, int fd); + +bool fdset_contains(FDSet *s, int fd); +int fdset_remove(FDSet *s, int fd); + +int fdset_new_fill(FDSet **_s); + +int fdset_cloexec(FDSet *fds, bool b); + +#endif diff --git a/src/core/ima-setup.c b/src/core/ima-setup.c new file mode 100644 index 0000000000..03e43dcf16 --- /dev/null +++ b/src/core/ima-setup.c @@ -0,0 +1,115 @@ +/*-*- Mode: C; c-basic-offset: 8; indent-tabs-mode: nil -*-*/ + +/*** + 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 General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + systemd is distributed in the hope that 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 General Public License + along with systemd; If not, see . +***/ + +#include +#include +#include +#include +#include +#include +#include +#include + +#include "ima-setup.h" +#include "mount-setup.h" +#include "macro.h" +#include "util.h" +#include "log.h" +#include "label.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 + struct stat st; + ssize_t policy_size = 0, written = 0; + char *policy; + int policyfd = -1, imafd = -1; + int result = 0; + +#ifndef HAVE_SELINUX + /* Mount the securityfs filesystem */ + mount_setup_early(); +#endif + + if (stat(IMA_POLICY_PATH, &st) < 0) + return 0; + + policy_size = st.st_size; + if (stat(IMA_SECFS_DIR, &st) < 0) { + log_debug("IMA support is disabled in the kernel, ignoring."); + return 0; + } + + if (stat(IMA_SECFS_POLICY, &st) < 0) { + log_error("Another IMA custom policy has already been loaded, " + "ignoring."); + return 0; + } + + policyfd = open(IMA_POLICY_PATH, O_RDONLY|O_CLOEXEC); + if (policyfd < 0) { + log_error("Failed to open the IMA custom policy file %s (%m), " + "ignoring.", IMA_POLICY_PATH); + return 0; + } + + imafd = open(IMA_SECFS_POLICY, O_WRONLY|O_CLOEXEC); + if (imafd < 0) { + log_error("Failed to open the IMA kernel interface %s (%m), " + "ignoring.", IMA_SECFS_POLICY); + goto out; + } + + policy = mmap(NULL, policy_size, PROT_READ, MAP_PRIVATE, policyfd, 0); + if (policy == MAP_FAILED) { + log_error("mmap() failed (%m), freezing"); + result = -errno; + goto out; + } + + written = loop_write(imafd, policy, (size_t)policy_size, false); + if (written != policy_size) { + log_error("Failed to load the IMA custom policy file %s (%m), " + "ignoring.", IMA_POLICY_PATH); + goto out_mmap; + } + + log_info("Successfully loaded the IMA custom policy %s.", + IMA_POLICY_PATH); +out_mmap: + munmap(policy, policy_size); +out: + if (policyfd >= 0) + close_nointr_nofail(policyfd); + if (imafd >= 0) + close_nointr_nofail(imafd); + if (result) + return result; +#endif /* HAVE_IMA */ + + return 0; +} diff --git a/src/core/ima-setup.h b/src/core/ima-setup.h new file mode 100644 index 0000000000..7d677cf852 --- /dev/null +++ b/src/core/ima-setup.h @@ -0,0 +1,29 @@ +/*-*- Mode: C; c-basic-offset: 8; indent-tabs-mode: nil -*-*/ + +#ifndef fooimasetuphfoo +#define fooimasetuphfoo + +/*** + 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 General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + systemd is distributed in the hope that 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 General Public License + along with systemd; If not, see . +***/ + +int ima_setup(void); + +#endif diff --git a/src/core/initreq.h b/src/core/initreq.h new file mode 100644 index 0000000000..859042ce42 --- /dev/null +++ b/src/core/initreq.h @@ -0,0 +1,77 @@ +/* + * initreq.h Interface to talk to init through /dev/initctl. + * + * Copyright (C) 1995-2004 Miquel van Smoorenburg + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2 of the License, or (at your option) any later version. + * + * Version: @(#)initreq.h 1.28 31-Mar-2004 MvS + * + */ +#ifndef _INITREQ_H +#define _INITREQ_H + +#include + +#if defined(__FreeBSD_kernel__) +# define INIT_FIFO "/etc/.initctl" +#else +# define INIT_FIFO "/dev/initctl" +#endif + +#define INIT_MAGIC 0x03091969 +#define INIT_CMD_START 0 +#define INIT_CMD_RUNLVL 1 +#define INIT_CMD_POWERFAIL 2 +#define INIT_CMD_POWERFAILNOW 3 +#define INIT_CMD_POWEROK 4 +#define INIT_CMD_BSD 5 +#define INIT_CMD_SETENV 6 +#define INIT_CMD_UNSETENV 7 + +#define INIT_CMD_CHANGECONS 12345 + +#ifdef MAXHOSTNAMELEN +# define INITRQ_HLEN MAXHOSTNAMELEN +#else +# define INITRQ_HLEN 64 +#endif + +/* + * This is what BSD 4.4 uses when talking to init. + * Linux doesn't use this right now. + */ +struct init_request_bsd { + char gen_id[8]; /* Beats me.. telnetd uses "fe" */ + char tty_id[16]; /* Tty name minus /dev/tty */ + char host[INITRQ_HLEN]; /* Hostname */ + char term_type[16]; /* Terminal type */ + int signal; /* Signal to send */ + int pid; /* Process to send to */ + char exec_name[128]; /* Program to execute */ + char reserved[128]; /* For future expansion. */ +}; + + +/* + * Because of legacy interfaces, "runlevel" and "sleeptime" + * aren't in a separate struct in the union. + * + * The weird sizes are because init expects the whole + * struct to be 384 bytes. + */ +struct init_request { + int magic; /* Magic number */ + int cmd; /* What kind of request */ + int runlevel; /* Runlevel to change to */ + int sleeptime; /* Time between TERM and KILL */ + union { + struct init_request_bsd bsd; + char data[368]; + } i; +}; + +#endif diff --git a/src/core/job.c b/src/core/job.c new file mode 100644 index 0000000000..bae6ab9f32 --- /dev/null +++ b/src/core/job.c @@ -0,0 +1,701 @@ +/*-*- Mode: C; c-basic-offset: 8; indent-tabs-mode: nil -*-*/ + +/*** + This file is part of systemd. + + Copyright 2010 Lennart Poettering + + systemd is free software; you can redistribute it and/or modify it + under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + systemd is distributed in the hope that 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 General Public License + along with systemd; If not, see . +***/ + +#include +#include +#include +#include + +#include "set.h" +#include "unit.h" +#include "macro.h" +#include "strv.h" +#include "load-fragment.h" +#include "load-dropin.h" +#include "log.h" +#include "dbus-job.h" + +Job* job_new(Manager *m, JobType type, Unit *unit) { + Job *j; + + assert(m); + assert(type < _JOB_TYPE_MAX); + assert(unit); + + if (!(j = new0(Job, 1))) + return NULL; + + j->manager = m; + j->id = m->current_job_id++; + j->type = type; + j->unit = unit; + + j->timer_watch.type = WATCH_INVALID; + + /* We don't link it here, that's what job_dependency() is for */ + + return j; +} + +void job_free(Job *j) { + assert(j); + + /* Detach from next 'bigger' objects */ + if (j->installed) { + bus_job_send_removed_signal(j); + + if (j->unit->job == j) { + j->unit->job = NULL; + unit_add_to_gc_queue(j->unit); + } + + hashmap_remove(j->manager->jobs, UINT32_TO_PTR(j->id)); + j->installed = false; + } + + /* Detach from next 'smaller' objects */ + manager_transaction_unlink_job(j->manager, j, true); + + if (j->in_run_queue) + LIST_REMOVE(Job, run_queue, j->manager->run_queue, j); + + if (j->in_dbus_queue) + LIST_REMOVE(Job, dbus_queue, j->manager->dbus_job_queue, j); + + if (j->timer_watch.type != WATCH_INVALID) { + assert(j->timer_watch.type == WATCH_JOB_TIMER); + assert(j->timer_watch.data.job == j); + assert(j->timer_watch.fd >= 0); + + assert_se(epoll_ctl(j->manager->epoll_fd, EPOLL_CTL_DEL, j->timer_watch.fd, NULL) >= 0); + close_nointr_nofail(j->timer_watch.fd); + } + + free(j->bus_client); + free(j); +} + +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(JobDependency, subject, subject->subject_list, l); + else + LIST_PREPEND(JobDependency, subject, object->manager->transaction_anchor, l); + + LIST_PREPEND(JobDependency, object, object->object_list, l); + + return l; +} + +void job_dependency_free(JobDependency *l) { + assert(l); + + if (l->subject) + LIST_REMOVE(JobDependency, subject, l->subject->subject_list, l); + else + LIST_REMOVE(JobDependency, subject, l->object->manager->transaction_anchor, l); + + LIST_REMOVE(JobDependency, 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\tForced: %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->override)); +} + +bool job_is_anchor(Job *j) { + JobDependency *l; + + assert(j); + + LIST_FOREACH(object, l, j->object_list) + if (!l->subject) + return true; + + return false; +} + +/* + * 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. + * + * Merging is associative! A merged with B merged with C is the same as + * A merged with C merged with B. + * + * 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_RELOAD_OR_START JOB_RESTART JOB_TRY_RESTART */ +/************************************************************************************************************************************/ +/*JOB_START */ +/*JOB_VERIFY_ACTIVE */ JOB_START, +/*JOB_STOP */ -1, -1, +/*JOB_RELOAD */ JOB_RELOAD_OR_START, JOB_RELOAD, -1, +/*JOB_RELOAD_OR_START*/ JOB_RELOAD_OR_START, JOB_RELOAD_OR_START, -1, JOB_RELOAD_OR_START, +/*JOB_RESTART */ JOB_RESTART, JOB_RESTART, -1, JOB_RESTART, JOB_RESTART, +/*JOB_TRY_RESTART */ JOB_RESTART, JOB_TRY_RESTART, -1, JOB_TRY_RESTART, JOB_RESTART, JOB_RESTART, +}; + +JobType job_type_lookup_merge(JobType a, JobType b) { + assert_cc(ELEMENTSOF(job_merging_table) == _JOB_TYPE_MAX * (_JOB_TYPE_MAX - 1) / 2); + assert(a >= 0 && a < _JOB_TYPE_MAX); + assert(b >= 0 && b < _JOB_TYPE_MAX); + + 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_RELOAD_OR_START: + return + b == UNIT_ACTIVATING || + b == UNIT_RELOADING; + + case JOB_RESTART: + return + b == UNIT_ACTIVATING; + + case JOB_TRY_RESTART: + return + b == UNIT_ACTIVATING; + + default: + assert_not_reached("Invalid job type"); + } +} + +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. */ + + /* First check if there is an override */ + if (j->ignore_order) + return true; + + if (j->type == JOB_START || + j->type == JOB_VERIFY_ACTIVE || + j->type == JOB_RELOAD || + j->type == JOB_RELOAD_OR_START) { + + /* Immediate result is that the job is or might be + * started. In this case lets 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 lets wait. */ + + SET_FOREACH(other, j->unit->dependencies[UNIT_BEFORE], i) + if (other->job && + (other->job->type == JOB_STOP || + other->job->type == JOB_RESTART || + other->job->type == JOB_TRY_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) { + log_debug("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; +} + +int job_run_and_invalidate(Job *j) { + int r; + uint32_t id; + Manager *m; + + assert(j); + assert(j->installed); + + if (j->in_run_queue) { + LIST_REMOVE(Job, 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; + + j->state = JOB_RUNNING; + job_add_to_dbus_queue(j); + + /* While we execute this operation the job might go away (for + * example: because it 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. */ + id = j->id; + m = j->manager; + + switch (j->type) { + + case JOB_RELOAD_OR_START: + if (unit_active_state(j->unit) == UNIT_ACTIVE) { + job_change_type(j, JOB_RELOAD); + r = unit_reload(j->unit); + break; + } + job_change_type(j, JOB_START); + /* fall through */ + + case JOB_START: + r = unit_start(j->unit); + + /* If this unit cannot be started, then simply wait */ + if (r == -EBADR) + r = 0; + break; + + 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 = -ENOEXEC; + break; + } + + case JOB_TRY_RESTART: + if (UNIT_IS_INACTIVE_OR_DEACTIVATING(unit_active_state(j->unit))) { + r = -ENOEXEC; + break; + } + job_change_type(j, JOB_RESTART); + /* fall through */ + + case JOB_STOP: + case JOB_RESTART: + r = unit_stop(j->unit); + + /* If this unit cannot stopped, then simply wait. */ + if (r == -EBADR) + r = 0; + break; + + case JOB_RELOAD: + r = unit_reload(j->unit); + break; + + default: + assert_not_reached("Unknown job type"); + } + + if ((j = manager_get_job(m, id))) { + if (r == -EALREADY) + r = job_finish_and_invalidate(j, JOB_DONE); + else if (r == -ENOEXEC) + r = job_finish_and_invalidate(j, JOB_SKIPPED); + else if (r == -EAGAIN) + j->state = JOB_WAITING; + else if (r < 0) + r = job_finish_and_invalidate(j, JOB_FAILED); + } + + return r; +} + +static void job_print_status_message(Unit *u, JobType t, JobResult result) { + assert(u); + + if (t == JOB_START) { + + switch (result) { + + case JOB_DONE: + unit_status_printf(u, ANSI_HIGHLIGHT_GREEN_ON " OK " ANSI_HIGHLIGHT_OFF, "Started %s", unit_description(u)); + break; + + case JOB_FAILED: + unit_status_printf(u, ANSI_HIGHLIGHT_RED_ON "FAILED" ANSI_HIGHLIGHT_OFF, "Failed to start %s", unit_description(u)); + unit_status_printf(u, NULL, "See 'systemctl status %s' for details.", u->id); + break; + + case JOB_DEPENDENCY: + unit_status_printf(u, ANSI_HIGHLIGHT_RED_ON " ABORT" ANSI_HIGHLIGHT_OFF, "Dependency failed. Aborted start of %s", unit_description(u)); + break; + + case JOB_TIMEOUT: + unit_status_printf(u, ANSI_HIGHLIGHT_RED_ON " TIME " ANSI_HIGHLIGHT_OFF, "Timed out starting %s", unit_description(u)); + break; + + default: + ; + } + + } else if (t == JOB_STOP) { + + switch (result) { + + case JOB_TIMEOUT: + unit_status_printf(u, ANSI_HIGHLIGHT_RED_ON " TIME " ANSI_HIGHLIGHT_OFF, "Timed out stopping %s", unit_description(u)); + break; + + case JOB_DONE: + case JOB_FAILED: + unit_status_printf(u, ANSI_HIGHLIGHT_GREEN_ON " OK " ANSI_HIGHLIGHT_OFF, "Stopped %s", unit_description(u)); + break; + + default: + ; + } + } +} + +int job_finish_and_invalidate(Job *j, JobResult result) { + Unit *u; + Unit *other; + JobType t; + Iterator i; + bool recursed = false; + + assert(j); + assert(j->installed); + + job_add_to_dbus_queue(j); + + /* Patch restart jobs so that they become normal start jobs */ + if (result == JOB_DONE && j->type == JOB_RESTART) { + + job_change_type(j, JOB_START); + j->state = JOB_WAITING; + + job_add_to_run_queue(j); + + u = j->unit; + goto finish; + } + + j->result = result; + + log_debug("Job %s/%s finished, result=%s", j->unit->id, job_type_to_string(j->type), job_result_to_string(result)); + + if (result == JOB_FAILED) + j->manager->n_failed_jobs ++; + + u = j->unit; + t = j->type; + job_free(j); + + job_print_status_message(u, t, result); + + /* Fail depending jobs on failure */ + if (result != JOB_DONE) { + + if (t == JOB_START || + t == JOB_VERIFY_ACTIVE || + t == JOB_RELOAD_OR_START) { + + SET_FOREACH(other, u->dependencies[UNIT_REQUIRED_BY], i) + if (other->job && + (other->job->type == JOB_START || + other->job->type == JOB_VERIFY_ACTIVE || + other->job->type == JOB_RELOAD_OR_START)) { + job_finish_and_invalidate(other->job, JOB_DEPENDENCY); + recursed = true; + } + + SET_FOREACH(other, u->dependencies[UNIT_BOUND_BY], i) + if (other->job && + (other->job->type == JOB_START || + other->job->type == JOB_VERIFY_ACTIVE || + other->job->type == JOB_RELOAD_OR_START)) { + job_finish_and_invalidate(other->job, JOB_DEPENDENCY); + recursed = true; + } + + SET_FOREACH(other, u->dependencies[UNIT_REQUIRED_BY_OVERRIDABLE], i) + if (other->job && + !other->job->override && + (other->job->type == JOB_START || + other->job->type == JOB_VERIFY_ACTIVE || + other->job->type == JOB_RELOAD_OR_START)) { + job_finish_and_invalidate(other->job, JOB_DEPENDENCY); + recursed = true; + } + + } else if (t == JOB_STOP) { + + SET_FOREACH(other, u->dependencies[UNIT_CONFLICTED_BY], i) + if (other->job && + (other->job->type == JOB_START || + other->job->type == JOB_VERIFY_ACTIVE || + other->job->type == JOB_RELOAD_OR_START)) { + job_finish_and_invalidate(other->job, JOB_DEPENDENCY); + recursed = true; + } + } + } + + /* Trigger OnFailure dependencies that are not generated by + * the unit itself. We don't tread 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_notice("Job %s/%s failed with result '%s'.", + u->id, + job_type_to_string(t), + job_result_to_string(result)); + + unit_trigger_on_failure(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 recursed; +} + +int job_start_timer(Job *j) { + struct itimerspec its; + struct epoll_event ev; + int fd, r; + assert(j); + + if (j->unit->job_timeout <= 0 || + j->timer_watch.type == WATCH_JOB_TIMER) + return 0; + + assert(j->timer_watch.type == WATCH_INVALID); + + if ((fd = timerfd_create(CLOCK_MONOTONIC, TFD_NONBLOCK|TFD_CLOEXEC)) < 0) { + r = -errno; + goto fail; + } + + zero(its); + timespec_store(&its.it_value, j->unit->job_timeout); + + if (timerfd_settime(fd, 0, &its, NULL) < 0) { + r = -errno; + goto fail; + } + + zero(ev); + ev.data.ptr = &j->timer_watch; + ev.events = EPOLLIN; + + if (epoll_ctl(j->manager->epoll_fd, EPOLL_CTL_ADD, fd, &ev) < 0) { + r = -errno; + goto fail; + } + + j->timer_watch.type = WATCH_JOB_TIMER; + j->timer_watch.fd = fd; + j->timer_watch.data.job = j; + + return 0; + +fail: + if (fd >= 0) + close_nointr_nofail(fd); + + return r; +} + +void job_add_to_run_queue(Job *j) { + assert(j); + assert(j->installed); + + if (j->in_run_queue) + return; + + LIST_PREPEND(Job, 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(Job, 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/%lu", (unsigned long) j->id) < 0) + return NULL; + + return p; +} + +void job_timer_event(Job *j, uint64_t n_elapsed, Watch *w) { + assert(j); + assert(w == &j->timer_watch); + + log_warning("Job %s/%s timed out.", j->unit->id, job_type_to_string(j->type)); + job_finish_and_invalidate(j, JOB_TIMEOUT); +} + +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", +}; + +DEFINE_STRING_TABLE_LOOKUP(job_type, JobType); + +static const char* const job_mode_table[_JOB_MODE_MAX] = { + [JOB_FAIL] = "fail", + [JOB_REPLACE] = "replace", + [JOB_ISOLATE] = "isolate", + [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" +}; + +DEFINE_STRING_TABLE_LOOKUP(job_result, JobResult); diff --git a/src/core/job.h b/src/core/job.h new file mode 100644 index 0000000000..60a43e074d --- /dev/null +++ b/src/core/job.h @@ -0,0 +1,200 @@ +/*-*- Mode: C; c-basic-offset: 8; indent-tabs-mode: nil -*-*/ + +#ifndef foojobhfoo +#define foojobhfoo + +/*** + This file is part of systemd. + + Copyright 2010 Lennart Poettering + + systemd is free software; you can redistribute it and/or modify it + under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + systemd is distributed in the hope that 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 General Public License + along with systemd; If not, see . +***/ + +#include +#include +#include + +typedef struct Job Job; +typedef struct JobDependency JobDependency; +typedef enum JobType JobType; +typedef enum JobState JobState; +typedef enum JobMode JobMode; +typedef enum JobResult JobResult; + +#include "manager.h" +#include "unit.h" +#include "hashmap.h" +#include "list.h" + +/* 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 */ + JOB_RELOAD_OR_START, /* if running reload, if not running start */ + + /* 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_TRY_RESTART, /* if running stop and then 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_ISOLATE, /* Start a unit, and stop all others */ + 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_CANCELED, + JOB_TIMEOUT, + JOB_FAILED, + JOB_DEPENDENCY, + JOB_SKIPPED, + _JOB_RESULT_MAX, + _JOB_RESULT_INVALID = -1 +}; + +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; + + Watch timer_watch; + + /* Note that this bus object is not ref counted here. */ + DBusConnection *bus; + char *bus_client; + + JobResult result; + + bool installed:1; + bool in_run_queue:1; + bool matters_to_anchor:1; + bool override:1; + bool in_dbus_queue:1; + bool sent_dbus_new_signal:1; + bool ignore_order:1; +}; + +Job* job_new(Manager *m, JobType type, Unit *unit); +void job_free(Job *job); +void job_dump(Job *j, FILE*f, const char *prefix); + +JobDependency* job_dependency_new(Job *subject, Job *object, bool matters, bool conflicts); +void job_dependency_free(JobDependency *l); + +bool job_is_anchor(Job *j); + +int job_merge(Job *j, Job *other); + +JobType job_type_lookup_merge(JobType a, JobType b); + +static inline int job_type_merge(JobType *a, JobType b) { + JobType t = job_type_lookup_merge(*a, b); + if (t < 0) + return -EEXIST; + *a = t; + return 0; +} + +static inline bool job_type_is_mergeable(JobType a, JobType b) { + return job_type_lookup_merge(a, b) >= 0; +} + +static inline bool job_type_is_conflicting(JobType a, JobType b) { + return !job_type_is_mergeable(a, b); +} + +static inline bool job_type_is_superset(JobType a, JobType b) { + /* Checks whether operation a is a "superset" of b in its actions */ + return a == job_type_lookup_merge(a, b); +} + +bool job_type_is_redundant(JobType a, UnitActiveState b); + +bool job_is_runnable(Job *j); + +void job_add_to_run_queue(Job *j); +void job_add_to_dbus_queue(Job *j); + +int job_start_timer(Job *j); +void job_timer_event(Job *j, uint64_t n_elapsed, Watch *w); + +int job_run_and_invalidate(Job *j); +int job_finish_and_invalidate(Job *j, JobResult result); + +char *job_dbus_path(Job *j); + +const char* job_type_to_string(JobType t); +JobType job_type_from_string(const char *s); + +const char* job_state_to_string(JobState t); +JobState job_state_from_string(const char *s); + +const char* job_mode_to_string(JobMode t); +JobMode job_mode_from_string(const char *s); + +const char* job_result_to_string(JobResult t); +JobResult job_result_from_string(const char *s); + +#endif diff --git a/src/core/kmod-setup.c b/src/core/kmod-setup.c new file mode 100644 index 0000000000..debf87130d --- /dev/null +++ b/src/core/kmod-setup.c @@ -0,0 +1,96 @@ +/*-*- Mode: C; c-basic-offset: 8; indent-tabs-mode: nil -*-*/ + +/*** + This file is part of systemd. + + Copyright 2010 Lennart Poettering + + systemd is free software; you can redistribute it and/or modify it + under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + systemd is distributed in the hope that 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 General Public License + along with systemd; If not, see . +***/ + +#include +#include +#include +#include +#include + +#include "macro.h" +#include "execute.h" + +#include "kmod-setup.h" + +static const char * const kmod_table[] = { + "autofs4", "/sys/class/misc/autofs", + "ipv6", "/sys/module/ipv6", + "unix", "/proc/net/unix" +}; + +#pragma GCC diagnostic push +#pragma GCC diagnostic ignored "-Wformat-nonliteral" +static void systemd_kmod_log(void *data, int priority, const char *file, int line, + const char *fn, const char *format, va_list args) +{ + log_meta(priority, file, line, fn, format, args); +} +#pragma GCC diagnostic pop + +int kmod_setup(void) { + unsigned i; + struct kmod_ctx *ctx = NULL; + struct kmod_module *mod; + int err; + + for (i = 0; i < ELEMENTSOF(kmod_table); i += 2) { + + if (access(kmod_table[i+1], F_OK) >= 0) + continue; + + 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]); + + if (!ctx) { + ctx = kmod_new(NULL, NULL); + if (!ctx) { + log_error("Failed to allocate memory for kmod"); + return -ENOMEM; + } + + kmod_set_log_fn(ctx, systemd_kmod_log, NULL); + + kmod_load_resources(ctx); + } + + err = kmod_module_new_from_name(ctx, kmod_table[i], &mod); + if (err < 0) { + log_error("Failed to load module '%s'", kmod_table[i]); + continue; + } + + err = kmod_module_probe_insert_module(mod, KMOD_PROBE_APPLY_BLACKLIST, NULL, NULL, NULL, NULL); + if (err == 0) + log_info("Inserted module '%s'", kmod_module_get_name(mod)); + else if (err == KMOD_PROBE_APPLY_BLACKLIST) + log_info("Module '%s' is blacklisted", kmod_module_get_name(mod)); + else + log_error("Failed to insert '%s'", kmod_module_get_name(mod)); + + kmod_module_unref(mod); + } + + if (ctx) + kmod_unref(ctx); + + return 0; +} diff --git a/src/core/kmod-setup.h b/src/core/kmod-setup.h new file mode 100644 index 0000000000..496aef3e15 --- /dev/null +++ b/src/core/kmod-setup.h @@ -0,0 +1,27 @@ +/*-*- Mode: C; c-basic-offset: 8; indent-tabs-mode: nil -*-*/ + +#ifndef fookmodsetuphfoo +#define fookmodsetuphfoo + +/*** + This file is part of systemd. + + Copyright 2010 Lennart Poettering + + systemd is free software; you can redistribute it and/or modify it + under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + systemd is distributed in the hope that 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 General Public License + along with systemd; If not, see . +***/ + +int kmod_setup(void); + +#endif diff --git a/src/core/load-dropin.c b/src/core/load-dropin.c new file mode 100644 index 0000000000..d869ee0c76 --- /dev/null +++ b/src/core/load-dropin.c @@ -0,0 +1,150 @@ +/*-*- Mode: C; c-basic-offset: 8; indent-tabs-mode: nil -*-*/ + +/*** + This file is part of systemd. + + Copyright 2010 Lennart Poettering + + systemd is free software; you can redistribute it and/or modify it + under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + systemd is distributed in the hope that 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 General Public License + along with systemd; If not, see . +***/ + +#include +#include + +#include "unit.h" +#include "load-dropin.h" +#include "log.h" +#include "strv.h" +#include "unit-name.h" + +static int iterate_dir(Unit *u, const char *path, UnitDependency dependency) { + DIR *d; + struct dirent *de; + int r; + + assert(u); + assert(path); + + d = opendir(path); + if (!d) { + + if (errno == ENOENT) + return 0; + + return -errno; + } + + while ((de = readdir(d))) { + char *f; + + if (ignore_file(de->d_name)) + continue; + + f = join(path, "/", de->d_name, NULL); + if (!f) { + r = -ENOMEM; + goto finish; + } + + r = unit_add_dependency_by_name(u, dependency, de->d_name, f, true); + free(f); + + if (r < 0) + log_error("Cannot add dependency %s to %s, ignoring: %s", de->d_name, u->id, strerror(-r)); + } + + r = 0; + +finish: + closedir(d); + return r; +} + +static int process_dir(Unit *u, const char *unit_path, const char *name, const char *suffix, UnitDependency dependency) { + int r; + char *path; + + assert(u); + assert(unit_path); + assert(name); + assert(suffix); + + path = join(unit_path, "/", name, suffix, NULL); + if (!path) + return -ENOMEM; + + if (u->manager->unit_path_cache && + !set_get(u->manager->unit_path_cache, path)) + r = 0; + else + r = iterate_dir(u, path, dependency); + free(path); + + if (r < 0) + return r; + + if (u->instance) { + char *template; + /* Also try the template dir */ + + template = unit_name_template(name); + if (!template) + return -ENOMEM; + + path = join(unit_path, "/", template, suffix, NULL); + free(template); + + if (!path) + return -ENOMEM; + + if (u->manager->unit_path_cache && + !set_get(u->manager->unit_path_cache, path)) + r = 0; + else + r = iterate_dir(u, path, dependency); + free(path); + + if (r < 0) + return r; + } + + return 0; +} + +int unit_load_dropin(Unit *u) { + Iterator i; + char *t; + + assert(u); + + /* Load dependencies from supplementary drop-in directories */ + + SET_FOREACH(t, u->names, i) { + char **p; + + STRV_FOREACH(p, u->manager->lookup_paths.unit_path) { + int r; + + r = process_dir(u, *p, t, ".wants", UNIT_WANTS); + if (r < 0) + return r; + + r = process_dir(u, *p, t, ".requires", UNIT_REQUIRES); + if (r < 0) + return r; + } + } + + return 0; +} diff --git a/src/core/load-dropin.h b/src/core/load-dropin.h new file mode 100644 index 0000000000..cf3a799381 --- /dev/null +++ b/src/core/load-dropin.h @@ -0,0 +1,31 @@ +/*-*- Mode: C; c-basic-offset: 8; indent-tabs-mode: nil -*-*/ + +#ifndef fooloaddropinhfoo +#define fooloaddropinhfoo + +/*** + This file is part of systemd. + + Copyright 2010 Lennart Poettering + + systemd is free software; you can redistribute it and/or modify it + under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + systemd is distributed in the hope that 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 General Public License + along with systemd; If not, see . +***/ + +#include "unit.h" + +/* Read service data supplementary drop-in directories */ + +int unit_load_dropin(Unit *u); + +#endif diff --git a/src/core/load-fragment.c b/src/core/load-fragment.c new file mode 100644 index 0000000000..637c82b427 --- /dev/null +++ b/src/core/load-fragment.c @@ -0,0 +1,2445 @@ +/*-*- Mode: C; c-basic-offset: 8; indent-tabs-mode: nil -*-*/ + +/*** + This file is part of systemd. + + Copyright 2010 Lennart Poettering + + systemd is free software; you can redistribute it and/or modify it + under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + systemd is distributed in the hope that 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 General Public License + along with systemd; If not, see . +***/ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "unit.h" +#include "strv.h" +#include "conf-parser.h" +#include "load-fragment.h" +#include "log.h" +#include "ioprio.h" +#include "securebits.h" +#include "missing.h" +#include "unit-name.h" +#include "bus-errors.h" +#include "utf8.h" + +#ifndef HAVE_SYSV_COMPAT +int config_parse_warn_compat( + const char *filename, + unsigned line, + const char *section, + const char *lvalue, + int ltype, + const char *rvalue, + void *data, + void *userdata) { + + log_debug("[%s:%u] Support for option %s= has been disabled at compile time and is ignored", filename, line, lvalue); + return 0; +} +#endif + +int config_parse_unit_deps( + const char *filename, + unsigned line, + const char *section, + const char *lvalue, + int ltype, + const char *rvalue, + void *data, + void *userdata) { + + UnitDependency d = ltype; + Unit *u = userdata; + char *w; + size_t l; + char *state; + + assert(filename); + assert(lvalue); + assert(rvalue); + + FOREACH_WORD_QUOTED(w, l, rvalue, state) { + char *t, *k; + int r; + + t = strndup(w, l); + if (!t) + return -ENOMEM; + + k = unit_name_printf(u, t); + free(t); + if (!k) + return -ENOMEM; + + r = unit_add_dependency_by_name(u, d, k, NULL, true); + if (r < 0) + log_error("[%s:%u] Failed to add dependency on %s, ignoring: %s", filename, line, k, strerror(-r)); + + free(k); + } + + return 0; +} + +int config_parse_unit_names( + const char *filename, + unsigned line, + const char *section, + const char *lvalue, + int ltype, + const char *rvalue, + void *data, + void *userdata) { + + Unit *u = userdata; + char *w; + size_t l; + char *state; + + assert(filename); + assert(lvalue); + assert(rvalue); + assert(data); + + FOREACH_WORD_QUOTED(w, l, rvalue, state) { + char *t, *k; + int r; + + t = strndup(w, l); + if (!t) + return -ENOMEM; + + k = unit_name_printf(u, t); + free(t); + if (!k) + return -ENOMEM; + + r = unit_merge_by_name(u, k); + if (r < 0) + log_error("Failed to add name %s, ignoring: %s", k, strerror(-r)); + + free(k); + } + + return 0; +} + +int config_parse_unit_string_printf( + const char *filename, + unsigned line, + const char *section, + const char *lvalue, + int ltype, + const char *rvalue, + void *data, + void *userdata) { + + Unit *u = userdata; + char *k; + int r; + + assert(filename); + assert(lvalue); + assert(rvalue); + assert(u); + + k = unit_full_printf(u, rvalue); + if (!k) + return -ENOMEM; + + r = config_parse_string(filename, line, section, lvalue, ltype, k, data, userdata); + free (k); + + return r; +} + +int config_parse_unit_strv_printf( + const char *filename, + unsigned line, + const char *section, + const char *lvalue, + int ltype, + const char *rvalue, + void *data, + void *userdata) { + + Unit *u = userdata; + char *k; + int r; + + assert(filename); + assert(lvalue); + assert(rvalue); + assert(u); + + k = unit_full_printf(u, rvalue); + if (!k) + return -ENOMEM; + + r = config_parse_strv(filename, line, section, lvalue, ltype, k, data, userdata); + free(k); + + return r; +} + +int config_parse_unit_path_printf( + const char *filename, + unsigned line, + const char *section, + const char *lvalue, + int ltype, + const char *rvalue, + void *data, + void *userdata) { + + Unit *u = userdata; + char *k; + int r; + + assert(filename); + assert(lvalue); + assert(rvalue); + assert(u); + + k = unit_full_printf(u, rvalue); + if (!k) + return -ENOMEM; + + r = config_parse_path(filename, line, section, lvalue, ltype, k, data, userdata); + free(k); + + return r; +} + +int config_parse_socket_listen( + const char *filename, + unsigned line, + const char *section, + const char *lvalue, + int ltype, + const char *rvalue, + void *data, + void *userdata) { + + SocketPort *p, *tail; + Socket *s; + + assert(filename); + assert(lvalue); + assert(rvalue); + assert(data); + + s = SOCKET(data); + + p = new0(SocketPort, 1); + if (!p) + return -ENOMEM; + + if (streq(lvalue, "ListenFIFO")) { + p->type = SOCKET_FIFO; + + if (!(p->path = unit_full_printf(UNIT(s), rvalue))) { + free(p); + return -ENOMEM; + } + + path_kill_slashes(p->path); + + } else if (streq(lvalue, "ListenSpecial")) { + p->type = SOCKET_SPECIAL; + + if (!(p->path = unit_full_printf(UNIT(s), rvalue))) { + free(p); + return -ENOMEM; + } + + path_kill_slashes(p->path); + + } else if (streq(lvalue, "ListenMessageQueue")) { + + p->type = SOCKET_MQUEUE; + + if (!(p->path = unit_full_printf(UNIT(s), rvalue))) { + free(p); + return -ENOMEM; + } + + path_kill_slashes(p->path); + + } else if (streq(lvalue, "ListenNetlink")) { + char *k; + int r; + + p->type = SOCKET_SOCKET; + k = unit_full_printf(UNIT(s), rvalue); + r = socket_address_parse_netlink(&p->address, k); + free(k); + + if (r < 0) { + log_error("[%s:%u] Failed to parse address value, ignoring: %s", filename, line, rvalue); + free(p); + return 0; + } + + } else { + char *k; + int r; + + p->type = SOCKET_SOCKET; + k = unit_full_printf(UNIT(s), rvalue); + r = socket_address_parse(&p->address, k); + free(k); + + if (r < 0) { + log_error("[%s:%u] Failed to parse address value, ignoring: %s", filename, line, rvalue); + free(p); + 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_error("[%s:%u] Address family not supported, ignoring: %s", filename, line, rvalue); + free(p); + return 0; + } + } + + p->fd = -1; + + if (s->ports) { + LIST_FIND_TAIL(SocketPort, port, s->ports, tail); + LIST_INSERT_AFTER(SocketPort, port, s->ports, tail, p); + } else + LIST_PREPEND(SocketPort, port, s->ports, p); + + return 0; +} + +int config_parse_socket_bind( + const char *filename, + unsigned line, + const char *section, + 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); + + if ((b = socket_address_bind_ipv6_only_from_string(rvalue)) < 0) { + int r; + + if ((r = parse_boolean(rvalue)) < 0) { + log_error("[%s:%u] Failed to parse bind IPv6 only value, ignoring: %s", filename, line, 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 *filename, + unsigned line, + const char *section, + const char *lvalue, + int ltype, + const char *rvalue, + void *data, + void *userdata) { + + ExecContext *c = data; + int priority; + + assert(filename); + assert(lvalue); + assert(rvalue); + assert(data); + + if (safe_atoi(rvalue, &priority) < 0) { + log_error("[%s:%u] Failed to parse nice priority, ignoring: %s. ", filename, line, rvalue); + return 0; + } + + if (priority < PRIO_MIN || priority >= PRIO_MAX) { + log_error("[%s:%u] Nice priority out of range, ignoring: %s", filename, line, rvalue); + return 0; + } + + c->nice = priority; + c->nice_set = true; + + return 0; +} + +int config_parse_exec_oom_score_adjust( + const char *filename, + unsigned line, + const char *section, + const char *lvalue, + int ltype, + const char *rvalue, + void *data, + void *userdata) { + + ExecContext *c = data; + int oa; + + assert(filename); + assert(lvalue); + assert(rvalue); + assert(data); + + if (safe_atoi(rvalue, &oa) < 0) { + log_error("[%s:%u] Failed to parse the OOM score adjust value, ignoring: %s", filename, line, rvalue); + return 0; + } + + if (oa < OOM_SCORE_ADJ_MIN || oa > OOM_SCORE_ADJ_MAX) { + log_error("[%s:%u] OOM score adjust value out of range, ignoring: %s", filename, line, rvalue); + return 0; + } + + c->oom_score_adjust = oa; + c->oom_score_adjust_set = true; + + return 0; +} + +int config_parse_exec( + const char *filename, + unsigned line, + const char *section, + const char *lvalue, + int ltype, + const char *rvalue, + void *data, + void *userdata) { + + ExecCommand **e = data, *nce; + char *path, **n; + unsigned k; + int r; + + assert(filename); + assert(lvalue); + assert(rvalue); + assert(e); + + /* We accept an absolute path as first argument, or + * alternatively an absolute prefixed with @ to allow + * overriding of argv[0]. */ + + e += ltype; + + for (;;) { + char *w; + size_t l; + char *state; + bool honour_argv0 = false, ignore = false; + + path = NULL; + nce = NULL; + n = NULL; + + rvalue += strspn(rvalue, WHITESPACE); + + if (rvalue[0] == 0) + break; + + if (rvalue[0] == '-') { + ignore = true; + rvalue ++; + } + + if (rvalue[0] == '@') { + honour_argv0 = true; + rvalue ++; + } + + if (*rvalue != '/') { + log_error("[%s:%u] Invalid executable path in command line, ignoring: %s", filename, line, rvalue); + return 0; + } + + k = 0; + FOREACH_WORD_QUOTED(w, l, rvalue, state) { + if (strncmp(w, ";", MAX(l, 1U)) == 0) + break; + + k++; + } + + n = new(char*, k + !honour_argv0); + if (!n) + return -ENOMEM; + + k = 0; + FOREACH_WORD_QUOTED(w, l, rvalue, state) { + if (strncmp(w, ";", MAX(l, 1U)) == 0) + break; + + if (honour_argv0 && w == rvalue) { + assert(!path); + + path = strndup(w, l); + if (!path) { + r = -ENOMEM; + goto fail; + } + + if (!utf8_is_valid(path)) { + log_error("[%s:%u] Path is not UTF-8 clean, ignoring assignment: %s", filename, line, rvalue); + r = 0; + goto fail; + } + + } else { + char *c; + + c = n[k++] = cunescape_length(w, l); + if (!c) { + r = -ENOMEM; + goto fail; + } + + if (!utf8_is_valid(c)) { + log_error("[%s:%u] Path is not UTF-8 clean, ignoring assignment: %s", filename, line, rvalue); + r = 0; + goto fail; + } + } + } + + n[k] = NULL; + + if (!n[0]) { + log_error("[%s:%u] Invalid command line, ignoring: %s", filename, line, rvalue); + r = 0; + goto fail; + } + + if (!path) { + path = strdup(n[0]); + if (!path) { + r = -ENOMEM; + goto fail; + } + } + + assert(path_is_absolute(path)); + + nce = new0(ExecCommand, 1); + if (!nce) { + r = -ENOMEM; + goto fail; + } + + nce->argv = n; + nce->path = path; + nce->ignore = ignore; + + path_kill_slashes(nce->path); + + exec_command_append_list(e, nce); + + rvalue = state; + } + + return 0; + +fail: + n[k] = NULL; + strv_free(n); + free(path); + free(nce); + + return r; +} + +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 *filename, + unsigned line, + const char *section, + 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 (!(n = strdup(rvalue))) + return -ENOMEM; + } 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_facility( + const char *filename, + unsigned line, + const char *section, + const char *lvalue, + int ltype, + const char *rvalue, + void *data, + void *userdata) { + + + int *o = data, x; + + assert(filename); + assert(lvalue); + assert(rvalue); + assert(data); + + if ((x = log_facility_unshifted_from_string(rvalue)) < 0) { + log_error("[%s:%u] Failed to parse log facility, ignoring: %s", filename, line, rvalue); + return 0; + } + + *o = (x << 3) | LOG_PRI(*o); + + return 0; +} + +int config_parse_level( + const char *filename, + unsigned line, + const char *section, + const char *lvalue, + int ltype, + const char *rvalue, + void *data, + void *userdata) { + + + int *o = data, x; + + assert(filename); + assert(lvalue); + assert(rvalue); + assert(data); + + if ((x = log_level_from_string(rvalue)) < 0) { + log_error("[%s:%u] Failed to parse log level, ignoring: %s", filename, line, rvalue); + return 0; + } + + *o = (*o & LOG_FACMASK) | x; + return 0; +} + +int config_parse_exec_io_class( + const char *filename, + unsigned line, + const char *section, + 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); + + if ((x = ioprio_class_from_string(rvalue)) < 0) { + log_error("[%s:%u] Failed to parse IO scheduling class, ignoring: %s", filename, line, 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 *filename, + unsigned line, + const char *section, + const char *lvalue, + int ltype, + const char *rvalue, + void *data, + void *userdata) { + + ExecContext *c = data; + int i; + + assert(filename); + assert(lvalue); + assert(rvalue); + assert(data); + + if (safe_atoi(rvalue, &i) < 0 || i < 0 || i >= IOPRIO_BE_NR) { + log_error("[%s:%u] Failed to parse io priority, ignoring: %s", filename, line, 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 *filename, + unsigned line, + const char *section, + 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); + + if ((x = sched_policy_from_string(rvalue)) < 0) { + log_error("[%s:%u] Failed to parse CPU scheduling policy, ignoring: %s", filename, line, rvalue); + return 0; + } + + c->cpu_sched_policy = x; + c->cpu_sched_set = true; + + return 0; +} + +int config_parse_exec_cpu_sched_prio( + const char *filename, + unsigned line, + const char *section, + const char *lvalue, + int ltype, + const char *rvalue, + void *data, + void *userdata) { + + ExecContext *c = data; + int i; + + assert(filename); + assert(lvalue); + assert(rvalue); + assert(data); + + /* On Linux RR/FIFO have the same range */ + if (safe_atoi(rvalue, &i) < 0 || i < sched_get_priority_min(SCHED_RR) || i > sched_get_priority_max(SCHED_RR)) { + log_error("[%s:%u] Failed to parse CPU scheduling priority, ignoring: %s", filename, line, rvalue); + return 0; + } + + c->cpu_sched_priority = i; + c->cpu_sched_set = true; + + return 0; +} + +int config_parse_exec_cpu_affinity( + const char *filename, + unsigned line, + const char *section, + const char *lvalue, + int ltype, + const char *rvalue, + void *data, + void *userdata) { + + ExecContext *c = data; + char *w; + size_t l; + char *state; + + assert(filename); + assert(lvalue); + assert(rvalue); + assert(data); + + FOREACH_WORD_QUOTED(w, l, rvalue, state) { + char *t; + int r; + unsigned cpu; + + if (!(t = strndup(w, l))) + return -ENOMEM; + + r = safe_atou(t, &cpu); + free(t); + + if (!(c->cpuset)) + if (!(c->cpuset = cpu_set_malloc(&c->cpuset_ncpus))) + return -ENOMEM; + + if (r < 0 || cpu >= c->cpuset_ncpus) { + log_error("[%s:%u] Failed to parse CPU affinity, ignoring: %s", filename, line, rvalue); + return 0; + } + + CPU_SET_S(cpu, CPU_ALLOC_SIZE(c->cpuset_ncpus), c->cpuset); + } + + return 0; +} + +int config_parse_exec_capabilities( + const char *filename, + unsigned line, + const char *section, + const char *lvalue, + int ltype, + const char *rvalue, + void *data, + void *userdata) { + + ExecContext *c = data; + cap_t cap; + + assert(filename); + assert(lvalue); + assert(rvalue); + assert(data); + + if (!(cap = cap_from_text(rvalue))) { + if (errno == ENOMEM) + return -ENOMEM; + + log_error("[%s:%u] Failed to parse capabilities, ignoring: %s", filename, line, rvalue); + return 0; + } + + if (c->capabilities) + cap_free(c->capabilities); + c->capabilities = cap; + + return 0; +} + +int config_parse_exec_secure_bits( + const char *filename, + unsigned line, + const char *section, + const char *lvalue, + int ltype, + const char *rvalue, + void *data, + void *userdata) { + + ExecContext *c = data; + char *w; + size_t l; + char *state; + + assert(filename); + assert(lvalue); + assert(rvalue); + assert(data); + + FOREACH_WORD_QUOTED(w, l, rvalue, state) { + if (first_word(w, "keep-caps")) + c->secure_bits |= SECURE_KEEP_CAPS; + else if (first_word(w, "keep-caps-locked")) + c->secure_bits |= SECURE_KEEP_CAPS_LOCKED; + else if (first_word(w, "no-setuid-fixup")) + c->secure_bits |= SECURE_NO_SETUID_FIXUP; + else if (first_word(w, "no-setuid-fixup-locked")) + c->secure_bits |= SECURE_NO_SETUID_FIXUP_LOCKED; + else if (first_word(w, "noroot")) + c->secure_bits |= SECURE_NOROOT; + else if (first_word(w, "noroot-locked")) + c->secure_bits |= SECURE_NOROOT_LOCKED; + else { + log_error("[%s:%u] Failed to parse secure bits, ignoring: %s", filename, line, rvalue); + return 0; + } + } + + return 0; +} + +int config_parse_exec_bounding_set( + const char *filename, + unsigned line, + const char *section, + const char *lvalue, + int ltype, + const char *rvalue, + void *data, + void *userdata) { + + ExecContext *c = data; + char *w; + size_t l; + char *state; + bool invert = false; + uint64_t sum = 0; + + assert(filename); + assert(lvalue); + assert(rvalue); + assert(data); + + if (rvalue[0] == '~') { + invert = true; + rvalue++; + } + + /* Note that we store this inverted internally, since the + * kernel wants it like this. But we actually expose it + * non-inverted everywhere to have a fully normalized + * interface. */ + + FOREACH_WORD_QUOTED(w, l, rvalue, state) { + char *t; + int r; + cap_value_t cap; + + if (!(t = strndup(w, l))) + return -ENOMEM; + + r = cap_from_name(t, &cap); + free(t); + + if (r < 0) { + log_error("[%s:%u] Failed to parse capability bounding set, ignoring: %s", filename, line, rvalue); + return 0; + } + + sum |= ((uint64_t) 1ULL) << (uint64_t) cap; + } + + if (invert) + c->capability_bounding_set_drop |= sum; + else + c->capability_bounding_set_drop |= ~sum; + + return 0; +} + +int config_parse_exec_timer_slack_nsec( + const char *filename, + unsigned line, + const char *section, + const char *lvalue, + int ltype, + const char *rvalue, + void *data, + void *userdata) { + + ExecContext *c = data; + unsigned long u; + + assert(filename); + assert(lvalue); + assert(rvalue); + assert(data); + + if (safe_atolu(rvalue, &u) < 0) { + log_error("[%s:%u] Failed to parse time slack value, ignoring: %s", filename, line, rvalue); + return 0; + } + + c->timer_slack_nsec = u; + + return 0; +} + +int config_parse_limit( + const char *filename, + unsigned line, + const char *section, + const char *lvalue, + int ltype, + const char *rvalue, + void *data, + void *userdata) { + + struct rlimit **rl = data; + unsigned long long u; + + assert(filename); + assert(lvalue); + assert(rvalue); + assert(data); + + rl += ltype; + + if (streq(rvalue, "infinity")) + u = (unsigned long long) RLIM_INFINITY; + else if (safe_atollu(rvalue, &u) < 0) { + log_error("[%s:%u] Failed to parse resource value, ignoring: %s", filename, line, rvalue); + return 0; + } + + if (!*rl) + if (!(*rl = new(struct rlimit, 1))) + return -ENOMEM; + + (*rl)->rlim_cur = (*rl)->rlim_max = (rlim_t) u; + return 0; +} + +int config_parse_unit_cgroup( + const char *filename, + unsigned line, + const char *section, + const char *lvalue, + int ltype, + const char *rvalue, + void *data, + void *userdata) { + + Unit *u = userdata; + char *w; + size_t l; + char *state; + + FOREACH_WORD_QUOTED(w, l, rvalue, state) { + char *t, *k; + int r; + + t = strndup(w, l); + if (!t) + return -ENOMEM; + + k = unit_full_printf(u, t); + free(t); + + if (!k) + return -ENOMEM; + + t = cunescape(k); + free(k); + + if (!t) + return -ENOMEM; + + r = unit_add_cgroup_from_text(u, t); + free(t); + + if (r < 0) { + log_error("[%s:%u] Failed to parse cgroup value, ignoring: %s", filename, line, rvalue); + return 0; + } + } + + return 0; +} + +#ifdef HAVE_SYSV_COMPAT +int config_parse_sysv_priority( + const char *filename, + unsigned line, + const char *section, + const char *lvalue, + int ltype, + const char *rvalue, + void *data, + void *userdata) { + + int *priority = data; + int i; + + assert(filename); + assert(lvalue); + assert(rvalue); + assert(data); + + if (safe_atoi(rvalue, &i) < 0 || i < 0) { + log_error("[%s:%u] Failed to parse SysV start priority, ignoring: %s", filename, line, rvalue); + return 0; + } + + *priority = (int) i; + return 0; +} +#endif + +int config_parse_fsck_passno( + const char *filename, + unsigned line, + const char *section, + const char *lvalue, + int ltype, + const char *rvalue, + void *data, + void *userdata) { + + int *passno = data; + int i; + + assert(filename); + assert(lvalue); + assert(rvalue); + assert(data); + + if (safe_atoi(rvalue, &i) || i < 0) { + log_error("[%s:%u] Failed to parse fsck pass number, ignoring: %s", filename, line, rvalue); + return 0; + } + + *passno = (int) i; + return 0; +} + +DEFINE_CONFIG_PARSE_ENUM(config_parse_kill_mode, kill_mode, KillMode, "Failed to parse kill mode"); + +int config_parse_kill_signal( + const char *filename, + unsigned line, + const char *section, + const char *lvalue, + int ltype, + const char *rvalue, + void *data, + void *userdata) { + + int *sig = data; + int r; + + assert(filename); + assert(lvalue); + assert(rvalue); + assert(sig); + + if ((r = signal_from_string_try_harder(rvalue)) <= 0) { + log_error("[%s:%u] Failed to parse kill signal, ignoring: %s", filename, line, rvalue); + return 0; + } + + *sig = r; + return 0; +} + +int config_parse_exec_mount_flags( + const char *filename, + unsigned line, + const char *section, + const char *lvalue, + int ltype, + const char *rvalue, + void *data, + void *userdata) { + + ExecContext *c = data; + char *w; + size_t l; + char *state; + unsigned long flags = 0; + + assert(filename); + assert(lvalue); + assert(rvalue); + assert(data); + + FOREACH_WORD_QUOTED(w, l, rvalue, state) { + if (strncmp(w, "shared", MAX(l, 6U)) == 0) + flags |= MS_SHARED; + else if (strncmp(w, "slave", MAX(l, 5U)) == 0) + flags |= MS_SLAVE; + else if (strncmp(w, "private", MAX(l, 7U)) == 0) + flags |= MS_PRIVATE; + else { + log_error("[%s:%u] Failed to parse mount flags, ignoring: %s", filename, line, rvalue); + return 0; + } + } + + c->mount_flags = flags; + return 0; +} + +int config_parse_timer( + const char *filename, + unsigned line, + const char *section, + const char *lvalue, + int ltype, + const char *rvalue, + void *data, + void *userdata) { + + Timer *t = data; + usec_t u; + TimerValue *v; + TimerBase b; + + assert(filename); + assert(lvalue); + assert(rvalue); + assert(data); + + if ((b = timer_base_from_string(lvalue)) < 0) { + log_error("[%s:%u] Failed to parse timer base, ignoring: %s", filename, line, lvalue); + return 0; + } + + if (parse_usec(rvalue, &u) < 0) { + log_error("[%s:%u] Failed to parse timer value, ignoring: %s", filename, line, rvalue); + return 0; + } + + if (!(v = new0(TimerValue, 1))) + return -ENOMEM; + + v->base = b; + v->value = u; + + LIST_PREPEND(TimerValue, value, t->values, v); + + return 0; +} + +int config_parse_timer_unit( + const char *filename, + unsigned line, + const char *section, + const char *lvalue, + int ltype, + const char *rvalue, + void *data, + void *userdata) { + + Timer *t = data; + int r; + DBusError error; + Unit *u; + + assert(filename); + assert(lvalue); + assert(rvalue); + assert(data); + + dbus_error_init(&error); + + if (endswith(rvalue, ".timer")) { + log_error("[%s:%u] Unit cannot be of type timer, ignoring: %s", filename, line, rvalue); + return 0; + } + + r = manager_load_unit(UNIT(t)->manager, rvalue, NULL, NULL, &u); + if (r < 0) { + log_error("[%s:%u] Failed to load unit %s, ignoring: %s", filename, line, rvalue, bus_error(&error, r)); + dbus_error_free(&error); + return 0; + } + + unit_ref_set(&t->unit, u); + + return 0; +} + +int config_parse_path_spec( + const char *filename, + unsigned line, + const char *section, + const char *lvalue, + int ltype, + const char *rvalue, + void *data, + void *userdata) { + + Path *p = data; + PathSpec *s; + PathType b; + + assert(filename); + assert(lvalue); + assert(rvalue); + assert(data); + + if ((b = path_type_from_string(lvalue)) < 0) { + log_error("[%s:%u] Failed to parse path type, ignoring: %s", filename, line, lvalue); + return 0; + } + + if (!path_is_absolute(rvalue)) { + log_error("[%s:%u] Path is not absolute, ignoring: %s", filename, line, rvalue); + return 0; + } + + if (!(s = new0(PathSpec, 1))) + return -ENOMEM; + + if (!(s->path = strdup(rvalue))) { + free(s); + return -ENOMEM; + } + + path_kill_slashes(s->path); + + s->type = b; + s->inotify_fd = -1; + + LIST_PREPEND(PathSpec, spec, p->specs, s); + + return 0; +} + +int config_parse_path_unit( + const char *filename, + unsigned line, + const char *section, + const char *lvalue, + int ltype, + const char *rvalue, + void *data, + void *userdata) { + + Path *t = data; + int r; + DBusError error; + Unit *u; + + assert(filename); + assert(lvalue); + assert(rvalue); + assert(data); + + dbus_error_init(&error); + + if (endswith(rvalue, ".path")) { + log_error("[%s:%u] Unit cannot be of type path, ignoring: %s", filename, line, rvalue); + return 0; + } + + if ((r = manager_load_unit(UNIT(t)->manager, rvalue, NULL, &error, &u)) < 0) { + log_error("[%s:%u] Failed to load unit %s, ignoring: %s", filename, line, rvalue, bus_error(&error, r)); + dbus_error_free(&error); + return 0; + } + + unit_ref_set(&t->unit, u); + + return 0; +} + +int config_parse_socket_service( + const char *filename, + unsigned line, + const char *section, + const char *lvalue, + int ltype, + const char *rvalue, + void *data, + void *userdata) { + + Socket *s = data; + int r; + DBusError error; + Unit *x; + + assert(filename); + assert(lvalue); + assert(rvalue); + assert(data); + + dbus_error_init(&error); + + if (!endswith(rvalue, ".service")) { + log_error("[%s:%u] Unit must be of type service, ignoring: %s", filename, line, rvalue); + return 0; + } + + r = manager_load_unit(UNIT(s)->manager, rvalue, NULL, &error, &x); + if (r < 0) { + log_error("[%s:%u] Failed to load unit %s, ignoring: %s", filename, line, rvalue, bus_error(&error, r)); + dbus_error_free(&error); + return 0; + } + + unit_ref_set(&s->service, x); + + return 0; +} + +int config_parse_service_sockets( + const char *filename, + unsigned line, + const char *section, + const char *lvalue, + int ltype, + const char *rvalue, + void *data, + void *userdata) { + + Service *s = data; + int r; + char *state, *w; + size_t l; + + assert(filename); + assert(lvalue); + assert(rvalue); + assert(data); + + FOREACH_WORD_QUOTED(w, l, rvalue, state) { + char *t, *k; + + t = strndup(w, l); + if (!t) + return -ENOMEM; + + k = unit_name_printf(UNIT(s), t); + free(t); + + if (!k) + return -ENOMEM; + + if (!endswith(k, ".socket")) { + log_error("[%s:%u] Unit must be of type socket, ignoring: %s", filename, line, rvalue); + free(k); + continue; + } + + r = unit_add_two_dependencies_by_name(UNIT(s), UNIT_WANTS, UNIT_AFTER, k, NULL, true); + if (r < 0) + log_error("[%s:%u] Failed to add dependency on %s, ignoring: %s", filename, line, k, strerror(-r)); + + r = unit_add_dependency_by_name(UNIT(s), UNIT_TRIGGERED_BY, k, NULL, true); + if (r < 0) + return r; + + free(k); + } + + return 0; +} + +int config_parse_unit_env_file( + const char *filename, + unsigned line, + const char *section, + const char *lvalue, + int ltype, + const char *rvalue, + void *data, + void *userdata) { + + char ***env = data, **k; + Unit *u = userdata; + char *s; + + assert(filename); + assert(lvalue); + assert(rvalue); + assert(data); + + s = unit_full_printf(u, rvalue); + if (!s) + return -ENOMEM; + + if (!path_is_absolute(s[0] == '-' ? s + 1 : s)) { + log_error("[%s:%u] Path '%s' is not absolute, ignoring.", filename, line, s); + free(s); + return 0; + } + + k = strv_append(*env, s); + free(s); + if (!k) + return -ENOMEM; + + strv_free(*env); + *env = k; + + return 0; +} + +int config_parse_ip_tos( + const char *filename, + unsigned line, + const char *section, + 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); + + if ((x = ip_tos_from_string(rvalue)) < 0) + if (safe_atoi(rvalue, &x) < 0) { + log_error("[%s:%u] Failed to parse IP TOS value, ignoring: %s", filename, line, rvalue); + return 0; + } + + *ip_tos = x; + return 0; +} + +int config_parse_unit_condition_path( + const char *filename, + unsigned line, + const char *section, + const char *lvalue, + int ltype, + const char *rvalue, + void *data, + void *userdata) { + + ConditionType cond = ltype; + Unit *u = data; + bool trigger, negate; + Condition *c; + + assert(filename); + assert(lvalue); + assert(rvalue); + assert(data); + + trigger = rvalue[0] == '|'; + if (trigger) + rvalue++; + + negate = rvalue[0] == '!'; + if (negate) + rvalue++; + + if (!path_is_absolute(rvalue)) { + log_error("[%s:%u] Path in condition not absolute, ignoring: %s", filename, line, rvalue); + return 0; + } + + c = condition_new(cond, rvalue, trigger, negate); + if (!c) + return -ENOMEM; + + LIST_PREPEND(Condition, conditions, u->conditions, c); + return 0; +} + +int config_parse_unit_condition_string( + const char *filename, + unsigned line, + const char *section, + const char *lvalue, + int ltype, + const char *rvalue, + void *data, + void *userdata) { + + ConditionType cond = ltype; + Unit *u = data; + bool trigger, negate; + Condition *c; + + assert(filename); + assert(lvalue); + assert(rvalue); + assert(data); + + if ((trigger = rvalue[0] == '|')) + rvalue++; + + if ((negate = rvalue[0] == '!')) + rvalue++; + + if (!(c = condition_new(cond, rvalue, trigger, negate))) + return -ENOMEM; + + LIST_PREPEND(Condition, conditions, u->conditions, c); + return 0; +} + +int config_parse_unit_condition_null( + const char *filename, + unsigned line, + const char *section, + const char *lvalue, + int ltype, + const char *rvalue, + void *data, + void *userdata) { + + Unit *u = data; + Condition *c; + bool trigger, negate; + int b; + + assert(filename); + assert(lvalue); + assert(rvalue); + assert(data); + + if ((trigger = rvalue[0] == '|')) + rvalue++; + + if ((negate = rvalue[0] == '!')) + rvalue++; + + if ((b = parse_boolean(rvalue)) < 0) { + log_error("[%s:%u] Failed to parse boolean value in condition, ignoring: %s", filename, line, rvalue); + return 0; + } + + if (!b) + negate = !negate; + + if (!(c = condition_new(CONDITION_NULL, NULL, trigger, negate))) + return -ENOMEM; + + LIST_PREPEND(Condition, conditions, u->conditions, 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_start_limit_action, start_limit_action, StartLimitAction, "Failed to parse start limit action specifier"); + +int config_parse_unit_cgroup_attr( + const char *filename, + unsigned line, + const char *section, + const char *lvalue, + int ltype, + const char *rvalue, + void *data, + void *userdata) { + + Unit *u = data; + char **l; + int r; + + assert(filename); + assert(lvalue); + assert(rvalue); + assert(data); + + l = strv_split_quoted(rvalue); + if (!l) + return -ENOMEM; + + if (strv_length(l) != 2) { + log_error("[%s:%u] Failed to parse cgroup attribute value, ignoring: %s", filename, line, rvalue); + strv_free(l); + return 0; + } + + r = unit_add_cgroup_attribute(u, NULL, l[0], l[1], NULL); + strv_free(l); + + if (r < 0) { + log_error("[%s:%u] Failed to add cgroup attribute value, ignoring: %s", filename, line, rvalue); + return 0; + } + + return 0; +} + +int config_parse_unit_cpu_shares(const char *filename, unsigned line, const char *section, const char *lvalue, int ltype, const char *rvalue, void *data, void *userdata) { + Unit *u = data; + int r; + unsigned long ul; + char *t; + + assert(filename); + assert(lvalue); + assert(rvalue); + assert(data); + + if (safe_atolu(rvalue, &ul) < 0 || ul < 1) { + log_error("[%s:%u] Failed to parse CPU shares value, ignoring: %s", filename, line, rvalue); + return 0; + } + + if (asprintf(&t, "%lu", ul) < 0) + return -ENOMEM; + + r = unit_add_cgroup_attribute(u, "cpu", "cpu.shares", t, NULL); + free(t); + + if (r < 0) { + log_error("[%s:%u] Failed to add cgroup attribute value, ignoring: %s", filename, line, rvalue); + return 0; + } + + return 0; +} + +int config_parse_unit_memory_limit(const char *filename, unsigned line, const char *section, const char *lvalue, int ltype, const char *rvalue, void *data, void *userdata) { + Unit *u = data; + int r; + off_t sz; + char *t; + + assert(filename); + assert(lvalue); + assert(rvalue); + assert(data); + + if (parse_bytes(rvalue, &sz) < 0 || sz <= 0) { + log_error("[%s:%u] Failed to parse memory limit value, ignoring: %s", filename, line, rvalue); + return 0; + } + + if (asprintf(&t, "%llu", (unsigned long long) sz) < 0) + return -ENOMEM; + + r = unit_add_cgroup_attribute(u, + "memory", + streq(lvalue, "MemorySoftLimit") ? "memory.soft_limit_in_bytes" : "memory.limit_in_bytes", + t, NULL); + free(t); + + if (r < 0) { + log_error("[%s:%u] Failed to add cgroup attribute value, ignoring: %s", filename, line, rvalue); + return 0; + } + + return 0; +} + +static int device_map(const char *controller, const char *name, const char *value, char **ret) { + char **l; + + assert(controller); + assert(name); + assert(value); + assert(ret); + + l = strv_split_quoted(value); + if (!l) + return -ENOMEM; + + assert(strv_length(l) >= 1); + + if (streq(l[0], "*")) { + + if (asprintf(ret, "a *:*%s%s", + isempty(l[1]) ? "" : " ", strempty(l[1])) < 0) { + strv_free(l); + return -ENOMEM; + } + + } else { + struct stat st; + + if (stat(l[0], &st) < 0) { + log_warning("Couldn't stat device %s", l[0]); + strv_free(l); + return -errno; + } + + if (!S_ISCHR(st.st_mode) && !S_ISBLK(st.st_mode)) { + log_warning("%s is not a device.", l[0]); + strv_free(l); + return -ENODEV; + } + + if (asprintf(ret, "%c %u:%u%s%s", + S_ISCHR(st.st_mode) ? 'c' : 'b', + major(st.st_rdev), minor(st.st_rdev), + isempty(l[1]) ? "" : " ", strempty(l[1])) < 0) { + + strv_free(l); + return -ENOMEM; + } + } + + strv_free(l); + return 0; +} + +int config_parse_unit_device_allow(const char *filename, unsigned line, const char *section, const char *lvalue, int ltype, const char *rvalue, void *data, void *userdata) { + Unit *u = data; + char **l; + int r; + unsigned k; + + assert(filename); + assert(lvalue); + assert(rvalue); + assert(data); + + l = strv_split_quoted(rvalue); + if (!l) + return -ENOMEM; + + k = strv_length(l); + if (k < 1 || k > 2) { + log_error("[%s:%u] Failed to parse device value, ignoring: %s", filename, line, rvalue); + strv_free(l); + return 0; + } + + if (!streq(l[0], "*") && !path_startswith(l[0], "/dev")) { + log_error("[%s:%u] Device node path not absolute, ignoring: %s", filename, line, rvalue); + strv_free(l); + return 0; + } + + if (!isempty(l[1]) && !in_charset(l[1], "rwm")) { + log_error("[%s:%u] Device access string invalid, ignoring: %s", filename, line, rvalue); + strv_free(l); + return 0; + } + strv_free(l); + + r = unit_add_cgroup_attribute(u, "devices", + streq(lvalue, "DeviceAllow") ? "devices.allow" : "devices.deny", + rvalue, device_map); + + if (r < 0) { + log_error("[%s:%u] Failed to add cgroup attribute value, ignoring: %s", filename, line, rvalue); + return 0; + } + + return 0; +} + +static int blkio_map(const char *controller, const char *name, const char *value, char **ret) { + struct stat st; + char **l; + dev_t d; + + assert(controller); + assert(name); + assert(value); + assert(ret); + + l = strv_split_quoted(value); + if (!l) + return -ENOMEM; + + assert(strv_length(l) == 2); + + if (stat(l[0], &st) < 0) { + log_warning("Couldn't stat device %s", l[0]); + strv_free(l); + return -errno; + } + + if (S_ISBLK(st.st_mode)) + d = 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 */ + d = st.st_dev; + + /* If this is a partition, try to get the originating + * block device */ + block_get_whole_disk(d, &d); + } else { + log_warning("%s is not a block device and file system block device cannot be determined or is not local.", l[0]); + strv_free(l); + return -ENODEV; + } + + if (asprintf(ret, "%u:%u %s", major(d), minor(d), l[1]) < 0) { + strv_free(l); + return -ENOMEM; + } + + strv_free(l); + return 0; +} + +int config_parse_unit_blkio_weight(const char *filename, unsigned line, const char *section, const char *lvalue, int ltype, const char *rvalue, void *data, void *userdata) { + Unit *u = data; + int r; + unsigned long ul; + const char *device = NULL, *weight; + unsigned k; + char *t, **l; + + assert(filename); + assert(lvalue); + assert(rvalue); + assert(data); + + l = strv_split_quoted(rvalue); + if (!l) + return -ENOMEM; + + k = strv_length(l); + if (k < 1 || k > 2) { + log_error("[%s:%u] Failed to parse weight value, ignoring: %s", filename, line, rvalue); + strv_free(l); + return 0; + } + + if (k == 1) + weight = l[0]; + else { + device = l[0]; + weight = l[1]; + } + + if (device && !path_is_absolute(device)) { + log_error("[%s:%u] Failed to parse block device node value, ignoring: %s", filename, line, rvalue); + strv_free(l); + return 0; + } + + if (safe_atolu(weight, &ul) < 0 || ul < 10 || ul > 1000) { + log_error("[%s:%u] Failed to parse block IO weight value, ignoring: %s", filename, line, rvalue); + strv_free(l); + return 0; + } + + if (device) + r = asprintf(&t, "%s %lu", device, ul); + else + r = asprintf(&t, "%lu", ul); + strv_free(l); + + if (r < 0) + return -ENOMEM; + + if (device) + r = unit_add_cgroup_attribute(u, "blkio", "blkio.weight_device", t, blkio_map); + else + r = unit_add_cgroup_attribute(u, "blkio", "blkio.weight", t, NULL); + free(t); + + if (r < 0) { + log_error("[%s:%u] Failed to add cgroup attribute value, ignoring: %s", filename, line, rvalue); + return 0; + } + + return 0; +} + +int config_parse_unit_blkio_bandwidth(const char *filename, unsigned line, const char *section, const char *lvalue, int ltype, const char *rvalue, void *data, void *userdata) { + Unit *u = data; + int r; + off_t bytes; + unsigned k; + char *t, **l; + + assert(filename); + assert(lvalue); + assert(rvalue); + assert(data); + + l = strv_split_quoted(rvalue); + if (!l) + return -ENOMEM; + + k = strv_length(l); + if (k != 2) { + log_error("[%s:%u] Failed to parse bandwidth value, ignoring: %s", filename, line, rvalue); + strv_free(l); + return 0; + } + + if (!path_is_absolute(l[0])) { + log_error("[%s:%u] Failed to parse block device node value, ignoring: %s", filename, line, rvalue); + strv_free(l); + return 0; + } + + if (parse_bytes(l[1], &bytes) < 0 || bytes <= 0) { + log_error("[%s:%u] Failed to parse block IO bandwith value, ignoring: %s", filename, line, rvalue); + strv_free(l); + return 0; + } + + r = asprintf(&t, "%s %llu", l[0], (unsigned long long) bytes); + strv_free(l); + + if (r < 0) + return -ENOMEM; + + r = unit_add_cgroup_attribute(u, "blkio", + streq(lvalue, "BlockIOReadBandwidth") ? "blkio.read_bps_device" : "blkio.write_bps_device", + t, blkio_map); + free(t); + + if (r < 0) { + log_error("[%s:%u] Failed to add cgroup attribute value, ignoring: %s", filename, line, rvalue); + return 0; + } + + return 0; +} + + +#define FOLLOW_MAX 8 + +static int open_follow(char **filename, FILE **_f, Set *names, char **_final) { + unsigned c = 0; + int fd, r; + FILE *f; + char *id = NULL; + + assert(filename); + assert(*filename); + assert(_f); + assert(names); + + /* This will update the filename pointer if the loaded file is + * reached by a symlink. The old string will be freed. */ + + for (;;) { + char *target, *name; + + if (c++ >= FOLLOW_MAX) + return -ELOOP; + + path_kill_slashes(*filename); + + /* Add the file name we are currently looking at to + * the names of this unit, but only if it is a valid + * unit name. */ + name = file_name_from_path(*filename); + + if (unit_name_is_valid(name, true)) { + + id = set_get(names, name); + if (!id) { + id = strdup(name); + if (!id) + return -ENOMEM; + + r = set_put(names, id); + if (r < 0) { + free(id); + return r; + } + } + } + + /* Try to open the file name, but don't if its a symlink */ + if ((fd = open(*filename, O_RDONLY|O_CLOEXEC|O_NOCTTY|O_NOFOLLOW)) >= 0) + break; + + if (errno != ELOOP) + return -errno; + + /* Hmm, so this is a symlink. Let's read the name, and follow it manually */ + if ((r = readlink_and_make_absolute(*filename, &target)) < 0) + return r; + + free(*filename); + *filename = target; + } + + if (!(f = fdopen(fd, "re"))) { + r = -errno; + close_nointr_nofail(fd); + return r; + } + + *_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 */ + if ((r = unit_merge_by_name(*u, k)) < 0) { + Unit *other; + + /* Hmm, we couldn't merge the other unit into + * ours? Then let's try it the other way + * round */ + + other = manager_get_unit((*u)->manager, k); + free(k); + + if (other) + if ((r = unit_merge(other, *u)) >= 0) { + *u = other; + return merge_by_names(u, names, NULL); + } + + return r; + } + + if (id == k) + unit_choose_id(*u, id); + + free(k); + } + + return 0; +} + +static int load_from_path(Unit *u, const char *path) { + int r; + Set *symlink_names; + FILE *f = NULL; + char *filename = NULL, *id = NULL; + Unit *merged; + struct stat st; + + assert(u); + assert(path); + + symlink_names = set_new(string_hash_func, string_compare_func); + if (!symlink_names) + return -ENOMEM; + + if (path_is_absolute(path)) { + + if (!(filename = strdup(path))) { + r = -ENOMEM; + goto finish; + } + + if ((r = open_follow(&filename, &f, symlink_names, &id)) < 0) { + free(filename); + filename = NULL; + + if (r != -ENOENT) + goto finish; + } + + } else { + char **p; + + STRV_FOREACH(p, u->manager->lookup_paths.unit_path) { + + /* Instead of opening the path right away, we manually + * follow all symlinks and add their name to our unit + * name set while doing so */ + if (!(filename = path_make_absolute(path, *p))) { + r = -ENOMEM; + goto finish; + } + + 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) { + char *sn; + + free(filename); + filename = NULL; + + if (r != -ENOENT) + goto finish; + + /* Empty the symlink names for the next run */ + while ((sn = set_steal_first(symlink_names))) + free(sn); + + continue; + } + + break; + } + } + + if (!filename) { + /* Hmm, no suitable file found? */ + r = 0; + goto finish; + } + + merged = u; + if ((r = merge_by_names(&merged, symlink_names, id)) < 0) + goto finish; + + if (merged != u) { + u->load_state = UNIT_MERGED; + r = 0; + goto finish; + } + + zero(st); + if (fstat(fileno(f), &st) < 0) { + r = -errno; + goto finish; + } + + if (null_or_empty(&st)) + u->load_state = UNIT_MASKED; + else { + /* Now, parse the file contents */ + r = config_parse(filename, f, UNIT_VTABLE(u)->sections, config_item_perf_lookup, (void*) load_fragment_gperf_lookup, false, u); + if (r < 0) + goto finish; + + u->load_state = UNIT_LOADED; + } + + free(u->fragment_path); + u->fragment_path = filename; + filename = NULL; + + u->fragment_mtime = timespec_load(&st.st_mtim); + + r = 0; + +finish: + set_free_free(symlink_names); + free(filename); + + if (f) + fclose(f); + + return r; +} + +int unit_load_fragment(Unit *u) { + int r; + Iterator i; + const char *t; + + assert(u); + assert(u->load_state == UNIT_STUB); + assert(u->id); + + /* 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 */ + if ((r = load_from_path(u, u->id)) < 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; + + if ((r = load_from_path(u, t)) < 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) { + + if ((r = load_from_path(u, u->fragment_path)) < 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. */ + free(u->fragment_path); + u->fragment_path = NULL; + } + } + + /* Look for a template */ + if (u->load_state == UNIT_STUB && u->instance) { + char *k; + + if (!(k = unit_name_template(u->id))) + return -ENOMEM; + + r = load_from_path(u, k); + free(k); + + if (r < 0) + return r; + + if (u->load_state == UNIT_STUB) + SET_FOREACH(t, u->names, i) { + + if (t == u->id) + continue; + + if (!(k = unit_name_template(t))) + return -ENOMEM; + + r = load_from_path(u, k); + free(k); + + 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[] = { + { config_parse_int, "INTEGER" }, + { config_parse_unsigned, "UNSIGNED" }, + { config_parse_bytes_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_facility, "FACILITY" }, + { config_parse_level, "LEVEL" }, + { config_parse_exec_capabilities, "CAPABILITIES" }, + { config_parse_exec_secure_bits, "SECUREBITS" }, + { config_parse_exec_bounding_set, "BOUNDINGSET" }, + { config_parse_exec_timer_slack_nsec, "TIMERSLACK" }, + { config_parse_limit, "LIMIT" }, + { config_parse_unit_cgroup, "CGROUP [...]" }, + { config_parse_unit_deps, "UNIT [...]" }, + { config_parse_unit_names, "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" }, +#else + { config_parse_warn_compat, "NOTSUPPORTED" }, +#endif + { config_parse_kill_mode, "KILLMODE" }, + { config_parse_kill_signal, "SIGNAL" }, + { config_parse_socket_listen, "SOCKET [...]" }, + { config_parse_socket_bind, "SOCKETBIND" }, + { config_parse_socket_bindtodevice, "NETWORKINTERFACE" }, + { config_parse_usec, "SECONDS" }, + { config_parse_path_strv, "PATH [...]" }, + { config_parse_exec_mount_flags, "MOUNTFLAG [...]" }, + { config_parse_unit_string_printf, "STRING" }, + { config_parse_timer, "TIMER" }, + { config_parse_timer_unit, "NAME" }, + { config_parse_path_spec, "PATH" }, + { config_parse_path_unit, "UNIT" }, + { 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" }, + }; + + 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 || strncmp(prev, i, prefix_len+1) != 0) { + if (prev) + fputc('\n', f); + + fprintf(f, "[%.*s]\n", (int) prefix_len, i); + } + + for (j = 0; j < ELEMENTSOF(table); j++) + if (p->parse == table[j].callback) { + rvalue = table[j].rvalue; + break; + } + + fprintf(f, "%s=%s\n", lvalue, rvalue); + prev = i; + } +} diff --git a/src/core/load-fragment.h b/src/core/load-fragment.h new file mode 100644 index 0000000000..79fc76da92 --- /dev/null +++ b/src/core/load-fragment.h @@ -0,0 +1,91 @@ +/*-*- Mode: C; c-basic-offset: 8; indent-tabs-mode: nil -*-*/ + +#ifndef fooloadfragmenthfoo +#define fooloadfragmenthfoo + +/*** + This file is part of systemd. + + Copyright 2010 Lennart Poettering + + systemd is free software; you can redistribute it and/or modify it + under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + systemd is distributed in the hope that 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 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 *filename, unsigned line, const char *section, const char *lvalue, int ltype, const char *rvalue, void *data, void *userdata); +int config_parse_unit_deps(const char *filename, unsigned line, const char *section, const char *lvalue, int ltype, const char *rvalue, void *data, void *userdata); +int config_parse_unit_names(const char *filename, unsigned line, const char *section, const char *lvalue, int ltype, const char *rvalue, void *data, void *userdata); +int config_parse_unit_string_printf(const char *filename, unsigned line, const char *section, const char *lvalue, int ltype, const char *rvalue, void *data, void *userdata); +int config_parse_unit_strv_printf(const char *filename, unsigned line, const char *section, const char *lvalue, int ltype, const char *rvalue, void *data, void *userdata); +int config_parse_unit_path_printf(const char *filename, unsigned line, const char *section, const char *lvalue, int ltype, const char *rvalue, void *data, void *userdata); +int config_parse_socket_listen(const char *filename, unsigned line, const char *section, const char *lvalue, int ltype, const char *rvalue, void *data, void *userdata); +int config_parse_socket_bind(const char *filename, unsigned line, const char *section, const char *lvalue, int ltype, const char *rvalue, void *data, void *userdata); +int config_parse_exec_nice(const char *filename, unsigned line, const char *section, const char *lvalue, int ltype, const char *rvalue, void *data, void *userdata); +int config_parse_exec_oom_score_adjust(const char *filename, unsigned line, const char *section, const char *lvalue, int ltype, const char *rvalue, void *data, void *userdata); +int config_parse_exec(const char *filename, unsigned line, const char *section, const char *lvalue, int ltype, const char *rvalue, void *data, void *userdata); +int config_parse_service_type(const char *filename, unsigned line, const char *section, const char *lvalue, int ltype, const char *rvalue, void *data, void *userdata); +int config_parse_service_restart(const char *filename, unsigned line, const char *section, const char *lvalue, int ltype, const char *rvalue, void *data, void *userdata); +int config_parse_socket_bindtodevice(const char *filename, unsigned line, const char *section, const char *lvalue, int ltype, const char *rvalue, void *data, void *userdata); +int config_parse_output(const char *filename, unsigned line, const char *section, const char *lvalue, int ltype, const char *rvalue, void *data, void *userdata); +int config_parse_input(const char *filename, unsigned line, const char *section, const char *lvalue, int ltype, const char *rvalue, void *data, void *userdata); +int config_parse_facility(const char *filename, unsigned line, const char *section, const char *lvalue, int ltype, const char *rvalue, void *data, void *userdata); +int config_parse_level(const char *filename, unsigned line, const char *section, const char *lvalue, int ltype, const char *rvalue, void *data, void *userdata); +int config_parse_exec_io_class(const char *filename, unsigned line, const char *section, const char *lvalue, int ltype, const char *rvalue, void *data, void *userdata); +int config_parse_exec_io_priority(const char *filename, unsigned line, const char *section, const char *lvalue, int ltype, const char *rvalue, void *data, void *userdata); +int config_parse_exec_cpu_sched_policy(const char *filename, unsigned line, const char *section, const char *lvalue, int ltype, const char *rvalue, void *data, void *userdata); +int config_parse_exec_cpu_sched_prio(const char *filename, unsigned line, const char *section, const char *lvalue, int ltype, const char *rvalue, void *data, void *userdata); +int config_parse_exec_cpu_affinity(const char *filename, unsigned line, const char *section, const char *lvalue, int ltype, const char *rvalue, void *data, void *userdata); +int config_parse_exec_capabilities(const char *filename, unsigned line, const char *section, const char *lvalue, int ltype, const char *rvalue, void *data, void *userdata); +int config_parse_exec_secure_bits(const char *filename, unsigned line, const char *section, const char *lvalue, int ltype, const char *rvalue, void *data, void *userdata); +int config_parse_exec_bounding_set(const char *filename, unsigned line, const char *section, const char *lvalue, int ltype, const char *rvalue, void *data, void *userdata); +int config_parse_exec_timer_slack_nsec(const char *filename, unsigned line, const char *section, const char *lvalue, int ltype, const char *rvalue, void *data, void *userdata); +int config_parse_limit(const char *filename, unsigned line, const char *section, const char *lvalue, int ltype, const char *rvalue, void *data, void *userdata); +int config_parse_unit_cgroup(const char *filename, unsigned line, const char *section, const char *lvalue, int ltype, const char *rvalue, void *data, void *userdata); +int config_parse_sysv_priority(const char *filename, unsigned line, const char *section, const char *lvalue, int ltype, const char *rvalue, void *data, void *userdata); +int config_parse_fsck_passno(const char *filename, unsigned line, const char *section, const char *lvalue, int ltype, const char *rvalue, void *data, void *userdata); +int config_parse_kill_signal(const char *filename, unsigned line, const char *section, const char *lvalue, int ltype, const char *rvalue, void *data, void *userdata); +int config_parse_exec_mount_flags(const char *filename, unsigned line, const char *section, const char *lvalue, int ltype, const char *rvalue, void *data, void *userdata); +int config_parse_timer(const char *filename, unsigned line, const char *section, const char *lvalue, int ltype, const char *rvalue, void *data, void *userdata); +int config_parse_timer_unit(const char *filename, unsigned line, const char *section, const char *lvalue, int ltype, const char *rvalue, void *data, void *userdata); +int config_parse_path_spec(const char *filename, unsigned line, const char *section, const char *lvalue, int ltype, const char *rvalue, void *data, void *userdata); +int config_parse_path_unit(const char *filename, unsigned line, const char *section, const char *lvalue, int ltype, const char *rvalue, void *data, void *userdata); +int config_parse_socket_service(const char *filename, unsigned line, const char *section, const char *lvalue, int ltype, const char *rvalue, void *data, void *userdata); +int config_parse_service_sockets(const char *filename, unsigned line, const char *section, const char *lvalue, int ltype, const char *rvalue, void *data, void *userdata); +int config_parse_unit_env_file(const char *filename, unsigned line, const char *section, const char *lvalue, int ltype, const char *rvalue, void *data, void *userdata); +int config_parse_ip_tos(const char *filename, unsigned line, const char *section, const char *lvalue, int ltype, const char *rvalue, void *data, void *userdata); +int config_parse_unit_condition_path(const char *filename, unsigned line, const char *section, const char *lvalue, int ltype, const char *rvalue, void *data, void *userdata); +int config_parse_unit_condition_string(const char *filename, unsigned line, const char *section, const char *lvalue, int ltype, const char *rvalue, void *data, void *userdata); +int config_parse_unit_condition_null(const char *filename, unsigned line, const char *section, const char *lvalue, int ltype, const char *rvalue, void *data, void *userdata); +int config_parse_kill_mode(const char *filename, unsigned line, const char *section, const char *lvalue, int ltype, const char *rvalue, void *data, void *userdata); +int config_parse_notify_access(const char *filename, unsigned line, const char *section, const char *lvalue, int ltype, const char *rvalue, void *data, void *userdata); +int config_parse_start_limit_action(const char *filename, unsigned line, const char *section, const char *lvalue, int ltype, const char *rvalue, void *data, void *userdata); +int config_parse_unit_cgroup_attr(const char *filename, unsigned line, const char *section, const char *lvalue, int ltype, const char *rvalue, void *data, void *userdata); +int config_parse_unit_cpu_shares(const char *filename, unsigned line, const char *section, const char *lvalue, int ltype, const char *rvalue, void *data, void *userdata); +int config_parse_unit_memory_limit(const char *filename, unsigned line, const char *section, const char *lvalue, int ltype, const char *rvalue, void *data, void *userdata); +int config_parse_unit_device_allow(const char *filename, unsigned line, const char *section, const char *lvalue, int ltype, const char *rvalue, void *data, void *userdata); +int config_parse_unit_blkio_weight(const char *filename, unsigned line, const char *section, const char *lvalue, int ltype, const char *rvalue, void *data, void *userdata); +int config_parse_unit_blkio_bandwidth(const char *filename, unsigned line, const char *section, 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[]; + +#endif diff --git a/src/core/locale-setup.c b/src/core/locale-setup.c new file mode 100644 index 0000000000..7f692e9c5d --- /dev/null +++ b/src/core/locale-setup.c @@ -0,0 +1,251 @@ +/*-*- Mode: C; c-basic-offset: 8; indent-tabs-mode: nil -*-*/ + +/*** + This file is part of systemd. + + Copyright 2010 Lennart Poettering + + systemd is free software; you can redistribute it and/or modify it + under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + systemd is distributed in the hope that 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 General Public License + along with systemd; If not, see . +***/ + +#include +#include +#include + +#include "locale-setup.h" +#include "util.h" +#include "macro.h" +#include "virt.h" + +enum { + /* We don't list LC_ALL here on purpose. People should be + * using LANG instead. */ + + VARIABLE_LANG, + VARIABLE_LANGUAGE, + VARIABLE_LC_CTYPE, + VARIABLE_LC_NUMERIC, + VARIABLE_LC_TIME, + VARIABLE_LC_COLLATE, + VARIABLE_LC_MONETARY, + VARIABLE_LC_MESSAGES, + VARIABLE_LC_PAPER, + VARIABLE_LC_NAME, + VARIABLE_LC_ADDRESS, + VARIABLE_LC_TELEPHONE, + VARIABLE_LC_MEASUREMENT, + VARIABLE_LC_IDENTIFICATION, + _VARIABLE_MAX +}; + +static const char * const variable_names[_VARIABLE_MAX] = { + [VARIABLE_LANG] = "LANG", + [VARIABLE_LANGUAGE] = "LANGUAGE", + [VARIABLE_LC_CTYPE] = "LC_CTYPE", + [VARIABLE_LC_NUMERIC] = "LC_NUMERIC", + [VARIABLE_LC_TIME] = "LC_TIME", + [VARIABLE_LC_COLLATE] = "LC_COLLATE", + [VARIABLE_LC_MONETARY] = "LC_MONETARY", + [VARIABLE_LC_MESSAGES] = "LC_MESSAGES", + [VARIABLE_LC_PAPER] = "LC_PAPER", + [VARIABLE_LC_NAME] = "LC_NAME", + [VARIABLE_LC_ADDRESS] = "LC_ADDRESS", + [VARIABLE_LC_TELEPHONE] = "LC_TELEPHONE", + [VARIABLE_LC_MEASUREMENT] = "LC_MEASUREMENT", + [VARIABLE_LC_IDENTIFICATION] = "LC_IDENTIFICATION" +}; + +int locale_setup(void) { + char *variables[_VARIABLE_MAX]; + int r = 0, i; + + zero(variables); + + if (detect_container(NULL) <= 0) + if ((r = parse_env_file("/proc/cmdline", WHITESPACE, +#if defined(TARGET_FEDORA) || defined(TARGET_MEEGO) + "LANG", &variables[VARIABLE_LANG], +#endif + "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)) < 0) { + + if (r != -ENOENT) + log_warning("Failed to read /proc/cmdline: %s", strerror(-r)); + } + + /* 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)) < 0) { + + if (r != -ENOENT) + log_warning("Failed to read /etc/locale.conf: %s", strerror(-r)); + } + +#if defined(TARGET_FEDORA) || defined(TARGET_ALTLINUX) || defined(TARGET_MEEGO) + if (r <= 0 && + (r = parse_env_file("/etc/sysconfig/i18n", NEWLINE, + "LANG", &variables[VARIABLE_LANG], + NULL)) < 0) { + + if (r != -ENOENT) + log_warning("Failed to read /etc/sysconfig/i18n: %s", strerror(-r)); + } + +#elif defined(TARGET_SUSE) + if (r <= 0 && + (r = parse_env_file("/etc/sysconfig/language", NEWLINE, + "RC_LANG", &variables[VARIABLE_LANG], + NULL)) < 0) { + + if (r != -ENOENT) + log_warning("Failed to read /etc/sysconfig/language: %s", strerror(-r)); + } + +#elif defined(TARGET_DEBIAN) || defined(TARGET_UBUNTU) || defined(TARGET_ANGSTROM) + if (r <= 0 && + (r = parse_env_file("/etc/default/locale", NEWLINE, + "LANG", &variables[VARIABLE_LANG], + "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)) < 0) { + + if (r != -ENOENT) + log_warning("Failed to read /etc/default/locale: %s", strerror(-r)); + } + +#elif defined(TARGET_ARCH) + if (r <= 0 && + (r = parse_env_file("/etc/rc.conf", NEWLINE, + "LOCALE", &variables[VARIABLE_LANG], + NULL)) < 0) { + + if (r != -ENOENT) + log_warning("Failed to read /etc/rc.conf: %s", strerror(-r)); + } + +#elif defined(TARGET_GENTOO) + /* Gentoo's openrc expects locale variables in /etc/env.d/ + * These files are later compiled by env-update into shell + * export commands at /etc/profile.env, with variables being + * exported by openrc's runscript (so /etc/init.d/) + */ + if (r <= 0 && + (r = parse_env_file("/etc/profile.env", NEWLINE, + "export LANG", &variables[VARIABLE_LANG], + "export LC_CTYPE", &variables[VARIABLE_LC_CTYPE], + "export LC_NUMERIC", &variables[VARIABLE_LC_NUMERIC], + "export LC_TIME", &variables[VARIABLE_LC_TIME], + "export LC_COLLATE", &variables[VARIABLE_LC_COLLATE], + "export LC_MONETARY", &variables[VARIABLE_LC_MONETARY], + "export LC_MESSAGES", &variables[VARIABLE_LC_MESSAGES], + "export LC_PAPER", &variables[VARIABLE_LC_PAPER], + "export LC_NAME", &variables[VARIABLE_LC_NAME], + "export LC_ADDRESS", &variables[VARIABLE_LC_ADDRESS], + "export LC_TELEPHONE", &variables[VARIABLE_LC_TELEPHONE], + "export LC_MEASUREMENT", &variables[VARIABLE_LC_MEASUREMENT], + "export LC_IDENTIFICATION", &variables[VARIABLE_LC_IDENTIFICATION], + NULL)) < 0) { + + if (r != -ENOENT) + log_warning("Failed to read /etc/profile.env: %s", strerror(-r)); + } +#elif defined(TARGET_MANDRIVA) || defined(TARGET_MAGEIA ) + if (r <= 0 && + (r = parse_env_file("/etc/sysconfig/i18n", NEWLINE, + "LANG", &variables[VARIABLE_LANG], + "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)) < 0) { + + if (r != -ENOENT) + log_warning("Failed to read /etc/sysconfig/i18n: %s", strerror(-r)); + } + +#endif + + if (!variables[VARIABLE_LANG]) { + if (!(variables[VARIABLE_LANG] = strdup("C"))) { + r = -ENOMEM; + goto finish; + } + } + + for (i = 0; i < _VARIABLE_MAX; i++) { + + if (variables[i]) { + if (setenv(variable_names[i], variables[i], 1) < 0) { + r = -errno; + goto finish; + } + } else + unsetenv(variable_names[i]); + } + + r = 0; + +finish: + for (i = 0; i < _VARIABLE_MAX; i++) + free(variables[i]); + + return r; +} diff --git a/src/core/locale-setup.h b/src/core/locale-setup.h new file mode 100644 index 0000000000..09a6bc682d --- /dev/null +++ b/src/core/locale-setup.h @@ -0,0 +1,27 @@ +/*-*- Mode: C; c-basic-offset: 8; indent-tabs-mode: nil -*-*/ + +#ifndef foolocalesetuphfoo +#define foolocalesetuphfoo + +/*** + This file is part of systemd. + + Copyright 2010 Lennart Poettering + + systemd is free software; you can redistribute it and/or modify it + under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + systemd is distributed in the hope that 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 General Public License + along with systemd; If not, see . +***/ + +int locale_setup(void); + +#endif diff --git a/src/core/manager.c b/src/core/manager.c new file mode 100644 index 0000000000..312527aa9c --- /dev/null +++ b/src/core/manager.c @@ -0,0 +1,3235 @@ +/*-*- Mode: C; c-basic-offset: 8; indent-tabs-mode: nil -*-*/ + +/*** + This file is part of systemd. + + Copyright 2010 Lennart Poettering + + systemd is free software; you can redistribute it and/or modify it + under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + systemd is distributed in the hope that 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 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 + +#ifdef HAVE_AUDIT +#include +#endif + +#include + +#include "manager.h" +#include "hashmap.h" +#include "macro.h" +#include "strv.h" +#include "log.h" +#include "util.h" +#include "mkdir.h" +#include "ratelimit.h" +#include "cgroup.h" +#include "mount-setup.h" +#include "unit-name.h" +#include "dbus-unit.h" +#include "dbus-job.h" +#include "missing.h" +#include "path-lookup.h" +#include "special.h" +#include "bus-errors.h" +#include "exit-status.h" +#include "virt.h" +#include "watchdog.h" + +/* As soon as 16 units are in our GC queue, make sure to run a gc sweep */ +#define GC_QUEUE_ENTRIES_MAX 16 + +/* As soon as 5s passed since a unit was added to our GC queue, make sure to run a gc sweep */ +#define GC_QUEUE_USEC_MAX (10*USEC_PER_SEC) + +/* Where clients shall send notification messages to */ +#define NOTIFY_SOCKET_SYSTEM "/run/systemd/notify" +#define NOTIFY_SOCKET_USER "@/org/freedesktop/systemd1/notify" + +static int manager_setup_notify(Manager *m) { + union { + struct sockaddr sa; + struct sockaddr_un un; + } sa; + struct epoll_event ev; + int one = 1, r; + mode_t u; + + assert(m); + + m->notify_watch.type = WATCH_NOTIFY; + if ((m->notify_watch.fd = socket(AF_UNIX, SOCK_DGRAM|SOCK_CLOEXEC|SOCK_NONBLOCK, 0)) < 0) { + log_error("Failed to allocate notification socket: %m"); + return -errno; + } + + zero(sa); + sa.sa.sa_family = AF_UNIX; + + if (getpid() != 1) + snprintf(sa.un.sun_path, sizeof(sa.un.sun_path), NOTIFY_SOCKET_USER "/%llu", random_ull()); + else { + unlink(NOTIFY_SOCKET_SYSTEM); + strncpy(sa.un.sun_path, NOTIFY_SOCKET_SYSTEM, sizeof(sa.un.sun_path)); + } + + if (sa.un.sun_path[0] == '@') + sa.un.sun_path[0] = 0; + + u = umask(0111); + r = bind(m->notify_watch.fd, &sa.sa, offsetof(struct sockaddr_un, sun_path) + 1 + strlen(sa.un.sun_path+1)); + umask(u); + + if (r < 0) { + log_error("bind() failed: %m"); + return -errno; + } + + if (setsockopt(m->notify_watch.fd, SOL_SOCKET, SO_PASSCRED, &one, sizeof(one)) < 0) { + log_error("SO_PASSCRED failed: %m"); + return -errno; + } + + zero(ev); + ev.events = EPOLLIN; + ev.data.ptr = &m->notify_watch; + + if (epoll_ctl(m->epoll_fd, EPOLL_CTL_ADD, m->notify_watch.fd, &ev) < 0) + return -errno; + + if (sa.un.sun_path[0] == 0) + sa.un.sun_path[0] = '@'; + + if (!(m->notify_socket = strdup(sa.un.sun_path))) + return -ENOMEM; + + log_debug("Using notification socket %s", m->notify_socket); + + return 0; +} + +static int enable_special_signals(Manager *m) { + int fd; + + assert(m); + + /* Enable that we get SIGINT on control-alt-del */ + if (reboot(RB_DISABLE_CAD) < 0) + log_warning("Failed to enable ctrl-alt-del handling: %m"); + + if ((fd = open_terminal("/dev/tty0", O_RDWR|O_NOCTTY|O_CLOEXEC)) < 0) + log_warning("Failed to open /dev/tty0: %m"); + else { + /* Enable that we get SIGWINCH on kbrequest */ + if (ioctl(fd, KDSIGACCEPT, SIGWINCH) < 0) + log_warning("Failed to enable kbrequest handling: %s", strerror(errno)); + + close_nointr_nofail(fd); + } + + return 0; +} + +static int manager_setup_signals(Manager *m) { + sigset_t mask; + struct epoll_event ev; + struct sigaction sa; + + assert(m); + + /* We are not interested in SIGSTOP and friends. */ + zero(sa); + sa.sa_handler = SIG_DFL; + sa.sa_flags = SA_NOCLDSTOP|SA_RESTART; + assert_se(sigaction(SIGCHLD, &sa, NULL) == 0); + + 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 */ + SIGRTMIN+13, /* systemd: Immediate halt */ + SIGRTMIN+14, /* systemd: Immediate poweroff */ + SIGRTMIN+15, /* systemd: Immediate reboot */ + SIGRTMIN+16, /* systemd: Immediate kexec */ + 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+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 */ + -1); + assert_se(sigprocmask(SIG_SETMASK, &mask, NULL) == 0); + + m->signal_watch.type = WATCH_SIGNAL; + if ((m->signal_watch.fd = signalfd(-1, &mask, SFD_NONBLOCK|SFD_CLOEXEC)) < 0) + return -errno; + + zero(ev); + ev.events = EPOLLIN; + ev.data.ptr = &m->signal_watch; + + if (epoll_ctl(m->epoll_fd, EPOLL_CTL_ADD, m->signal_watch.fd, &ev) < 0) + return -errno; + + if (m->running_as == MANAGER_SYSTEM) + return enable_special_signals(m); + + return 0; +} + +static void manager_strip_environment(Manager *m) { + assert(m); + + /* Remove variables from the inherited set that are part of + * the container interface: + * http://www.freedesktop.org/wiki/Software/systemd/ContainerInterface */ + strv_remove_prefix(m->environment, "container="); + strv_remove_prefix(m->environment, "container_"); + + /* Remove variables from the inherited set that are part of + * the initrd interface: + * http://www.freedesktop.org/wiki/Software/systemd/InitrdInterface */ + strv_remove_prefix(m->environment, "RD_"); +} + +int manager_new(ManagerRunningAs running_as, Manager **_m) { + Manager *m; + int r = -ENOMEM; + + assert(_m); + assert(running_as >= 0); + assert(running_as < _MANAGER_RUNNING_AS_MAX); + + if (!(m = new0(Manager, 1))) + return -ENOMEM; + + dual_timestamp_get(&m->startup_timestamp); + + m->running_as = running_as; + m->name_data_slot = m->conn_data_slot = m->subscribed_data_slot = -1; + m->exit_code = _MANAGER_EXIT_CODE_INVALID; + m->pin_cgroupfs_fd = -1; + +#ifdef HAVE_AUDIT + m->audit_fd = -1; +#endif + + m->signal_watch.fd = m->mount_watch.fd = m->udev_watch.fd = m->epoll_fd = m->dev_autofs_fd = m->swap_watch.fd = -1; + m->current_job_id = 1; /* start as id #1, so that we can leave #0 around as "null-like" value */ + + m->environment = strv_copy(environ); + if (!m->environment) + goto fail; + + manager_strip_environment(m); + + if (running_as == MANAGER_SYSTEM) { + m->default_controllers = strv_new("cpu", NULL); + if (!m->default_controllers) + goto fail; + } + + if (!(m->units = hashmap_new(string_hash_func, string_compare_func))) + goto fail; + + if (!(m->jobs = hashmap_new(trivial_hash_func, trivial_compare_func))) + goto fail; + + if (!(m->transaction_jobs = hashmap_new(trivial_hash_func, trivial_compare_func))) + goto fail; + + if (!(m->watch_pids = hashmap_new(trivial_hash_func, trivial_compare_func))) + goto fail; + + if (!(m->cgroup_bondings = hashmap_new(string_hash_func, string_compare_func))) + goto fail; + + if (!(m->watch_bus = hashmap_new(string_hash_func, string_compare_func))) + goto fail; + + if ((m->epoll_fd = epoll_create1(EPOLL_CLOEXEC)) < 0) + goto fail; + + if ((r = lookup_paths_init(&m->lookup_paths, m->running_as, true)) < 0) + goto fail; + + if ((r = manager_setup_signals(m)) < 0) + goto fail; + + if ((r = manager_setup_cgroup(m)) < 0) + goto fail; + + if ((r = manager_setup_notify(m)) < 0) + goto fail; + + /* Try to connect to the busses, if possible. */ + if ((r = bus_init(m, running_as != MANAGER_SYSTEM)) < 0) + goto fail; + +#ifdef HAVE_AUDIT + if ((m->audit_fd = audit_open()) < 0 && + /* If the kernel lacks netlink or audit support, + * don't worry about it. */ + errno != EAFNOSUPPORT && errno != EPROTONOSUPPORT) + log_error("Failed to connect to audit log: %m"); +#endif + + m->taint_usr = dir_is_empty("/usr") > 0; + + *_m = m; + return 0; + +fail: + manager_free(m); + return r; +} + +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); + + if ((m->n_in_gc_queue < GC_QUEUE_ENTRIES_MAX) && + (m->gc_queue_timestamp <= 0 || + (m->gc_queue_timestamp + GC_QUEUE_USEC_MAX) > now(CLOCK_MONOTONIC))) + return 0; + + 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(Unit, 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) { + log_debug("Collecting %s", u->id); + u->gc_marker = gc_marker + GC_OFFSET_BAD; + unit_add_to_cleanup_queue(u); + } + } + + m->n_in_gc_queue = 0; + m->gc_queue_timestamp = 0; + + return n; +} + +static void manager_clear_jobs_and_units(Manager *m) { + Job *j; + Unit *u; + + assert(m); + + while ((j = hashmap_first(m->transaction_jobs))) + job_free(j); + + 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->transaction_jobs)); + assert(hashmap_isempty(m->jobs)); + assert(hashmap_isempty(m->units)); +} + +void manager_free(Manager *m) { + UnitType c; + + assert(m); + + manager_clear_jobs_and_units(m); + + for (c = 0; c < _UNIT_TYPE_MAX; c++) + if (unit_vtable[c]->shutdown) + unit_vtable[c]->shutdown(m); + + /* If we reexecute ourselves, we keep the root cgroup + * around */ + manager_shutdown_cgroup(m, m->exit_code != MANAGER_REEXECUTE); + + manager_undo_generators(m); + + bus_done(m); + + hashmap_free(m->units); + hashmap_free(m->jobs); + hashmap_free(m->transaction_jobs); + hashmap_free(m->watch_pids); + hashmap_free(m->watch_bus); + + if (m->epoll_fd >= 0) + close_nointr_nofail(m->epoll_fd); + if (m->signal_watch.fd >= 0) + close_nointr_nofail(m->signal_watch.fd); + if (m->notify_watch.fd >= 0) + close_nointr_nofail(m->notify_watch.fd); + +#ifdef HAVE_AUDIT + if (m->audit_fd >= 0) + audit_close(m->audit_fd); +#endif + + free(m->notify_socket); + + lookup_paths_free(&m->lookup_paths); + strv_free(m->environment); + + strv_free(m->default_controllers); + + hashmap_free(m->cgroup_bondings); + set_free_free(m->unit_path_cache); + + free(m); +} + +int manager_enumerate(Manager *m) { + int r = 0, q; + 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_vtable[c]->enumerate) + if ((q = unit_vtable[c]->enumerate(m)) < 0) + r = q; + + manager_dispatch_load_queue(m); + return r; +} + +int manager_coldplug(Manager *m) { + int r = 0, q; + Iterator i; + Unit *u; + char *k; + + 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; + + if ((q = unit_coldplug(u)) < 0) + r = q; + } + + return r; +} + +static void manager_build_unit_path_cache(Manager *m) { + char **i; + DIR *d = NULL; + int r; + + assert(m); + + set_free_free(m->unit_path_cache); + + if (!(m->unit_path_cache = set_new(string_hash_func, string_compare_func))) { + log_error("Failed to allocate unit path cache."); + return; + } + + /* This simply builds a list of files we know exist, so that + * we don't always have to go to disk */ + + STRV_FOREACH(i, m->lookup_paths.unit_path) { + struct dirent *de; + + if (!(d = opendir(*i))) { + log_error("Failed to open directory: %m"); + continue; + } + + while ((de = readdir(d))) { + char *p; + + if (ignore_file(de->d_name)) + continue; + + p = join(streq(*i, "/") ? "" : *i, "/", de->d_name, NULL); + if (!p) { + r = -ENOMEM; + goto fail; + } + + if ((r = set_put(m->unit_path_cache, p)) < 0) { + free(p); + goto fail; + } + } + + closedir(d); + d = NULL; + } + + return; + +fail: + log_error("Failed to build unit path cache: %s", strerror(-r)); + + set_free_free(m->unit_path_cache); + m->unit_path_cache = NULL; + + if (d) + closedir(d); +} + +int manager_startup(Manager *m, FILE *serialization, FDSet *fds) { + int r, q; + + assert(m); + + manager_run_generators(m); + + 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 */ + r = manager_enumerate(m); + + /* Second, deserialize if there is something to deserialize */ + if (serialization) + if ((q = manager_deserialize(m, serialization, fds)) < 0) + r = q; + + /* Third, fire things up! */ + if ((q = manager_coldplug(m)) < 0) + r = q; + + if (serialization) { + assert(m->n_reloading > 0); + m->n_reloading --; + } + + return r; +} + +static void transaction_delete_job(Manager *m, Job *j, bool delete_dependencies) { + assert(m); + assert(j); + + /* Deletes one job from the transaction */ + + manager_transaction_unlink_job(m, j, delete_dependencies); + + if (!j->installed) + job_free(j); +} + +static void transaction_delete_unit(Manager *m, Unit *u) { + Job *j; + + /* Deletes all jobs associated with a certain unit from the + * transaction */ + + while ((j = hashmap_get(m->transaction_jobs, u))) + transaction_delete_job(m, j, true); +} + +static void transaction_clean_dependencies(Manager *m) { + Iterator i; + Job *j; + + assert(m); + + /* Drops all dependencies of all installed jobs */ + + HASHMAP_FOREACH(j, m->jobs, i) { + while (j->subject_list) + job_dependency_free(j->subject_list); + while (j->object_list) + job_dependency_free(j->object_list); + } + + assert(!m->transaction_anchor); +} + +static void transaction_abort(Manager *m) { + Job *j; + + assert(m); + + while ((j = hashmap_first(m->transaction_jobs))) + if (j->installed) + transaction_delete_job(m, j, true); + else + job_free(j); + + assert(hashmap_isempty(m->transaction_jobs)); + + transaction_clean_dependencies(m); +} + +static void transaction_find_jobs_that_matter_to_anchor(Manager *m, Job *j, unsigned generation) { + JobDependency *l; + + assert(m); + + /* 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. */ + + if (j) + l = j->subject_list; + else + l = m->transaction_anchor; + + LIST_FOREACH(subject, l, l) { + + /* This link does not matter */ + if (!l->matters) + continue; + + /* This unit has already been marked */ + if (l->object->generation == generation) + continue; + + l->object->matters_to_anchor = true; + l->object->generation = generation; + + transaction_find_jobs_that_matter_to_anchor(m, l->object, generation); + } +} + +static void transaction_merge_and_delete_job(Manager *m, 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 j. */ + + j->type = t; + j->state = JOB_WAITING; + j->override = j->override || other->override; + + 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(m, other, true); +} +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(Manager *m, 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_debug("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_debug("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_debug("Fixing conflicting jobs by deleting job %s/%s", d->unit->id, job_type_to_string(d->type)); + transaction_delete_job(m, d, true); + return 0; + } + + return -EINVAL; +} + +static int transaction_merge_jobs(Manager *m, DBusError *e) { + Job *j; + Iterator i; + int r; + + assert(m); + + /* First step, check whether any of the jobs for one specific + * task conflict. If so, try to drop one of them. */ + HASHMAP_FOREACH(j, m->transaction_jobs, i) { + JobType t; + Job *k; + + t = j->type; + LIST_FOREACH(transaction, k, j->transaction_next) { + if (job_type_merge(&t, k->type) >= 0) + continue; + + /* OK, we could not merge all jobs for this + * action. Let's see if we can get rid of one + * of them */ + + if ((r = delete_one_unmergeable_job(m, j)) >= 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 */ + dbus_set_error(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); + return r; + } + } + + /* Second step, merge the jobs. */ + HASHMAP_FOREACH(j, m->transaction_jobs, i) { + JobType t = j->type; + Job *k; + + /* Merge all transactions */ + LIST_FOREACH(transaction, k, j->transaction_next) + assert_se(job_type_merge(&t, k->type) == 0); + + /* If an active job is mergeable, merge it too */ + if (j->unit->job) + job_type_merge(&t, j->unit->job->type); /* Might fail. Which is OK */ + + while ((k = j->transaction_next)) { + if (j->installed) { + transaction_merge_and_delete_job(m, k, j, t); + j = k; + } else + transaction_merge_and_delete_job(m, j, k, t); + } + + if (j->unit->job && !j->installed) + transaction_merge_and_delete_job(m, j, j->unit->job, t); + + assert(!j->transaction_next); + assert(!j->transaction_prev); + } + + return 0; +} + +static void transaction_drop_redundant(Manager *m) { + bool again; + + assert(m); + + /* Goes through the transaction and removes all jobs that are + * a noop */ + + do { + Job *j; + Iterator i; + + again = false; + + HASHMAP_FOREACH(j, m->transaction_jobs, i) { + bool changes_something = false; + Job *k; + + LIST_FOREACH(transaction, k, j) { + + if (!job_is_anchor(k) && + (k->installed || job_type_is_redundant(k->type, unit_active_state(k->unit))) && + (!k->unit->job || !job_type_is_conflicting(k->type, k->unit->job->type))) + continue; + + changes_something = true; + break; + } + + if (changes_something) + continue; + + /* log_debug("Found redundant job %s/%s, dropping.", j->unit->id, job_type_to_string(j->type)); */ + transaction_delete_job(m, j, false); + again = true; + break; + } + + } while (again); +} + +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(Manager *m, Job *j, Job *from, unsigned generation, DBusError *e) { + Iterator i; + Unit *u; + int r; + + assert(m); + assert(j); + assert(!j->transaction_prev); + + /* Does a recursive sweep through the ordering graph, looking + * for a cycle. If we find 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_warning("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)) { + + log_info("Walked on cycle path to %s/%s", k->unit->id, job_type_to_string(k->type)); + + if (!delete && + !k->installed && + !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) { + log_warning("Breaking ordering cycle by deleting job %s/%s", delete->unit->id, job_type_to_string(delete->type)); + transaction_delete_unit(m, delete->unit); + return -EAGAIN; + } + + log_error("Unable to break cycle"); + + dbus_set_error(e, BUS_ERROR_TRANSACTION_ORDER_IS_CYCLIC, "Transaction order is cyclic. See system logs for details."); + return -ENOEXEC; + } + + /* 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 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? */ + if (!(o = hashmap_get(m->transaction_jobs, u))) + + /* Ok, there is no job for this in the + * transaction, but maybe there is already one + * running? */ + if (!(o = u->job)) + continue; + + if ((r = transaction_verify_order_one(m, o, j, generation, e)) < 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(Manager *m, unsigned *generation, DBusError *e) { + Job *j; + int r; + Iterator i; + unsigned g; + + assert(m); + 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, m->transaction_jobs, i) + if ((r = transaction_verify_order_one(m, j, NULL, g, e)) < 0) + return r; + + return 0; +} + +static void transaction_collect_garbage(Manager *m) { + bool again; + + assert(m); + + /* Drop jobs that are not required by any other job */ + + do { + Iterator i; + Job *j; + + again = false; + + HASHMAP_FOREACH(j, m->transaction_jobs, i) { + if (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(m, j, true); + again = true; + break; + } + + } while (again); +} + +static int transaction_is_destructive(Manager *m, DBusError *e) { + Iterator i; + Job *j; + + assert(m); + + /* Checks whether applying this transaction means that + * existing jobs would be replaced */ + + HASHMAP_FOREACH(j, m->transaction_jobs, i) { + + /* Assume merged */ + assert(!j->transaction_prev); + assert(!j->transaction_next); + + if (j->unit->job && + j->unit->job != j && + !job_type_is_superset(j->type, j->unit->job->type)) { + + dbus_set_error(e, BUS_ERROR_TRANSACTION_IS_DESTRUCTIVE, "Transaction is destructive."); + return -EEXIST; + } + } + + return 0; +} + +static void transaction_minimize_impact(Manager *m) { + bool again; + assert(m); + + /* Drops all unnecessary jobs that reverse already active jobs + * or that stop a running service. */ + + do { + Job *j; + Iterator i; + + again = false; + + HASHMAP_FOREACH(j, m->transaction_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_debug("%s/%s would stop a running service.", j->unit->id, job_type_to_string(j->type)); + + if (changes_existing_job) + log_debug("%s/%s would change existing job.", j->unit->id, job_type_to_string(j->type)); + + /* Ok, let's get rid of this */ + log_debug("Deleting %s/%s to minimize impact.", j->unit->id, job_type_to_string(j->type)); + + transaction_delete_job(m, j, true); + again = true; + break; + } + + if (again) + break; + } + + } while (again); +} + +static int transaction_apply(Manager *m, JobMode mode) { + Iterator i; + Job *j; + int r; + + /* Moves the transaction jobs to the set of active jobs */ + + if (mode == JOB_ISOLATE) { + + /* When isolating first kill all installed jobs which + * aren't part of the new transaction */ + rescan: + HASHMAP_FOREACH(j, m->jobs, i) { + assert(j->installed); + + if (hashmap_get(m->transaction_jobs, j->unit)) + continue; + + /* 'j' itself is safe to remove, but if other jobs + are invalidated recursively, our iterator may become + invalid and we need to start over. */ + if (job_finish_and_invalidate(j, JOB_CANCELED) > 0) + goto rescan; + } + } + + HASHMAP_FOREACH(j, m->transaction_jobs, i) { + /* Assume merged */ + assert(!j->transaction_prev); + assert(!j->transaction_next); + + if (j->installed) + continue; + + if ((r = hashmap_put(m->jobs, UINT32_TO_PTR(j->id), j)) < 0) + goto rollback; + } + + while ((j = hashmap_steal_first(m->transaction_jobs))) { + if (j->installed) { + /* log_debug("Skipping already installed job %s/%s as %u", j->unit->id, job_type_to_string(j->type), (unsigned) j->id); */ + continue; + } + + if (j->unit->job) + job_free(j->unit->job); + + j->unit->job = j; + j->installed = true; + m->n_installed_jobs ++; + + /* We're fully installed. Now let's free data we don't + * need anymore. */ + + assert(!j->transaction_next); + assert(!j->transaction_prev); + + job_add_to_run_queue(j); + job_add_to_dbus_queue(j); + job_start_timer(j); + + log_debug("Installed new job %s/%s as %u", j->unit->id, job_type_to_string(j->type), (unsigned) j->id); + } + + /* As last step, kill all remaining job dependencies. */ + transaction_clean_dependencies(m); + + return 0; + +rollback: + + HASHMAP_FOREACH(j, m->transaction_jobs, i) { + if (j->installed) + continue; + + hashmap_remove(m->jobs, UINT32_TO_PTR(j->id)); + } + + return r; +} + +static int transaction_activate(Manager *m, JobMode mode, DBusError *e) { + int r; + unsigned generation = 1; + + assert(m); + + /* This applies the changes recorded in transaction_jobs to + * the actual list of jobs, if possible. */ + + /* First step: figure out which jobs matter */ + transaction_find_jobs_that_matter_to_anchor(m, NULL, 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(m); + + /* Third step: Drop redundant jobs */ + transaction_drop_redundant(m); + + for (;;) { + /* Fourth step: Let's remove unneeded jobs that might + * be lurking. */ + if (mode != JOB_ISOLATE) + transaction_collect_garbage(m); + + /* Fifth step: verify order makes sense and correct + * cycles if necessary and possible */ + if ((r = transaction_verify_order(m, &generation, e)) >= 0) + break; + + if (r != -EAGAIN) { + log_warning("Requested transaction contains an unfixable cyclic ordering dependency: %s", bus_error(e, r)); + goto rollback; + } + + /* 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 */ + if ((r = transaction_merge_jobs(m, e)) >= 0) + break; + + if (r != -EAGAIN) { + log_warning("Requested transaction contains unmergeable jobs: %s", bus_error(e, r)); + goto rollback; + } + + /* Seventh step: an entry got dropped, let's garbage + * collect its dependencies. */ + if (mode != JOB_ISOLATE) + transaction_collect_garbage(m); + + /* 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(m); + + /* Ninth step: check whether we can actually apply this */ + if (mode == JOB_FAIL) + if ((r = transaction_is_destructive(m, e)) < 0) { + log_notice("Requested transaction contradicts existing jobs: %s", bus_error(e, r)); + goto rollback; + } + + /* Tenth step: apply changes */ + if ((r = transaction_apply(m, mode)) < 0) { + log_warning("Failed to apply transaction: %s", strerror(-r)); + goto rollback; + } + + assert(hashmap_isempty(m->transaction_jobs)); + assert(!m->transaction_anchor); + + return 0; + +rollback: + transaction_abort(m); + return r; +} + +static Job* transaction_add_one_job(Manager *m, JobType type, Unit *unit, bool override, bool *is_new) { + Job *j, *f; + + assert(m); + 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(m->transaction_jobs, unit); + + LIST_FOREACH(transaction, j, f) { + assert(j->unit == unit); + + if (j->type == type) { + if (is_new) + *is_new = false; + return j; + } + } + + if (unit->job && unit->job->type == type) + j = unit->job; + else if (!(j = job_new(m, type, unit))) + return NULL; + + j->generation = 0; + j->marker = NULL; + j->matters_to_anchor = false; + j->override = override; + + LIST_PREPEND(Job, transaction, f, j); + + if (hashmap_replace(m->transaction_jobs, unit, f) < 0) { + 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; +} + +void manager_transaction_unlink_job(Manager *m, Job *j, bool delete_dependencies) { + assert(m); + assert(j); + + if (j->transaction_prev) + j->transaction_prev->transaction_next = j->transaction_next; + else if (j->transaction_next) + hashmap_replace(m->transaction_jobs, j->unit, j->transaction_next); + else + hashmap_remove_value(m->transaction_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_debug("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(m, other, delete_dependencies); + } + } +} + +static int transaction_add_job_and_dependencies( + Manager *m, + JobType type, + Unit *unit, + Job *by, + bool matters, + bool override, + bool conflicts, + bool ignore_requirements, + bool ignore_order, + DBusError *e, + Job **_ret) { + Job *ret; + Iterator i; + Unit *dep; + int r; + bool is_new; + + assert(m); + assert(type < _JOB_TYPE_MAX); + assert(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 (unit->load_state != UNIT_LOADED && + unit->load_state != UNIT_ERROR && + unit->load_state != UNIT_MASKED) { + dbus_set_error(e, BUS_ERROR_LOAD_FAILED, "Unit %s is not loaded properly.", unit->id); + return -EINVAL; + } + + if (type != JOB_STOP && unit->load_state == UNIT_ERROR) { + dbus_set_error(e, BUS_ERROR_LOAD_FAILED, + "Unit %s failed to load: %s. " + "See system logs and 'systemctl status %s' for details.", + unit->id, + strerror(-unit->load_error), + unit->id); + return -EINVAL; + } + + if (type != JOB_STOP && unit->load_state == UNIT_MASKED) { + dbus_set_error(e, BUS_ERROR_MASKED, "Unit %s is masked.", unit->id); + return -EINVAL; + } + + if (!unit_job_is_applicable(unit, type)) { + dbus_set_error(e, BUS_ERROR_JOB_TYPE_NOT_APPLICABLE, "Job type %s is not applicable for unit %s.", job_type_to_string(type), unit->id); + return -EBADR; + } + + /* First add the job. */ + if (!(ret = transaction_add_one_job(m, type, unit, override, &is_new))) + return -ENOMEM; + + ret->ignore_order = ret->ignore_order || ignore_order; + + /* Then, add a link to the job. */ + if (!job_dependency_new(by, ret, matters, conflicts)) + return -ENOMEM; + + if (is_new && !ignore_requirements) { + 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) + if ((r = transaction_add_job_and_dependencies(m, type, dep, ret, false, override, false, false, ignore_order, e, NULL)) < 0) { + log_warning("Cannot add dependency job for unit %s, ignoring: %s", dep->id, bus_error(e, r)); + + if (e) + dbus_error_free(e); + } + + set_free(following); + } + + /* Finally, recursively add in all dependencies. */ + if (type == JOB_START || type == JOB_RELOAD_OR_START) { + SET_FOREACH(dep, ret->unit->dependencies[UNIT_REQUIRES], i) + if ((r = transaction_add_job_and_dependencies(m, JOB_START, dep, ret, true, override, false, false, ignore_order, e, NULL)) < 0) { + if (r != -EBADR) + goto fail; + + if (e) + dbus_error_free(e); + } + + SET_FOREACH(dep, ret->unit->dependencies[UNIT_BIND_TO], i) + if ((r = transaction_add_job_and_dependencies(m, JOB_START, dep, ret, true, override, false, false, ignore_order, e, NULL)) < 0) { + + if (r != -EBADR) + goto fail; + + if (e) + dbus_error_free(e); + } + + SET_FOREACH(dep, ret->unit->dependencies[UNIT_REQUIRES_OVERRIDABLE], i) + if ((r = transaction_add_job_and_dependencies(m, JOB_START, dep, ret, !override, override, false, false, ignore_order, e, NULL)) < 0) { + log_warning("Cannot add dependency job for unit %s, ignoring: %s", dep->id, bus_error(e, r)); + + if (e) + dbus_error_free(e); + } + + SET_FOREACH(dep, ret->unit->dependencies[UNIT_WANTS], i) + if ((r = transaction_add_job_and_dependencies(m, JOB_START, dep, ret, false, false, false, false, ignore_order, e, NULL)) < 0) { + log_warning("Cannot add dependency job for unit %s, ignoring: %s", dep->id, bus_error(e, r)); + + if (e) + dbus_error_free(e); + } + + SET_FOREACH(dep, ret->unit->dependencies[UNIT_REQUISITE], i) + if ((r = transaction_add_job_and_dependencies(m, JOB_VERIFY_ACTIVE, dep, ret, true, override, false, false, ignore_order, e, NULL)) < 0) { + + if (r != -EBADR) + goto fail; + + if (e) + dbus_error_free(e); + } + + SET_FOREACH(dep, ret->unit->dependencies[UNIT_REQUISITE_OVERRIDABLE], i) + if ((r = transaction_add_job_and_dependencies(m, JOB_VERIFY_ACTIVE, dep, ret, !override, override, false, false, ignore_order, e, NULL)) < 0) { + log_warning("Cannot add dependency job for unit %s, ignoring: %s", dep->id, bus_error(e, r)); + + if (e) + dbus_error_free(e); + } + + SET_FOREACH(dep, ret->unit->dependencies[UNIT_CONFLICTS], i) + if ((r = transaction_add_job_and_dependencies(m, JOB_STOP, dep, ret, true, override, true, false, ignore_order, e, NULL)) < 0) { + + if (r != -EBADR) + goto fail; + + if (e) + dbus_error_free(e); + } + + SET_FOREACH(dep, ret->unit->dependencies[UNIT_CONFLICTED_BY], i) + if ((r = transaction_add_job_and_dependencies(m, JOB_STOP, dep, ret, false, override, false, false, ignore_order, e, NULL)) < 0) { + log_warning("Cannot add dependency job for unit %s, ignoring: %s", dep->id, bus_error(e, r)); + + if (e) + dbus_error_free(e); + } + + } + + if (type == JOB_STOP || type == JOB_RESTART || type == JOB_TRY_RESTART) { + + SET_FOREACH(dep, ret->unit->dependencies[UNIT_REQUIRED_BY], i) + if ((r = transaction_add_job_and_dependencies(m, type, dep, ret, true, override, false, false, ignore_order, e, NULL)) < 0) { + + if (r != -EBADR) + goto fail; + + if (e) + dbus_error_free(e); + } + + SET_FOREACH(dep, ret->unit->dependencies[UNIT_BOUND_BY], i) + if ((r = transaction_add_job_and_dependencies(m, type, dep, ret, true, override, false, false, ignore_order, e, NULL)) < 0) { + + if (r != -EBADR) + goto fail; + + if (e) + dbus_error_free(e); + } + } + + if (type == JOB_RELOAD || type == JOB_RELOAD_OR_START) { + + SET_FOREACH(dep, ret->unit->dependencies[UNIT_PROPAGATE_RELOAD_TO], i) { + r = transaction_add_job_and_dependencies(m, JOB_RELOAD, dep, ret, false, override, false, false, ignore_order, e, NULL); + + if (r < 0) { + log_warning("Cannot add dependency reload job for unit %s, ignoring: %s", dep->id, bus_error(e, r)); + + if (e) + dbus_error_free(e); + } + } + } + + /* JOB_VERIFY_STARTED, JOB_RELOAD require no dependency handling */ + } + + if (_ret) + *_ret = ret; + + return 0; + +fail: + return r; +} + +static int transaction_add_isolate_jobs(Manager *m) { + Iterator i; + Unit *u; + char *k; + int r; + + 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(m->transaction_jobs, u)) + continue; + + if ((r = transaction_add_job_and_dependencies(m, JOB_STOP, u, NULL, true, false, false, false, false, NULL, NULL)) < 0) + log_warning("Cannot add isolate job for unit %s, ignoring: %s", u->id, strerror(-r)); + } + + return 0; +} + +int manager_add_job(Manager *m, JobType type, Unit *unit, JobMode mode, bool override, DBusError *e, Job **_ret) { + int r; + Job *ret; + + assert(m); + assert(type < _JOB_TYPE_MAX); + assert(unit); + assert(mode < _JOB_MODE_MAX); + + if (mode == JOB_ISOLATE && type != JOB_START) { + dbus_set_error(e, BUS_ERROR_INVALID_JOB_MODE, "Isolate is only valid for start."); + return -EINVAL; + } + + if (mode == JOB_ISOLATE && !unit->allow_isolate) { + dbus_set_error(e, BUS_ERROR_NO_ISOLATION, "Operation refused, unit may not be isolated."); + return -EPERM; + } + + log_debug("Trying to enqueue job %s/%s/%s", unit->id, job_type_to_string(type), job_mode_to_string(mode)); + + if ((r = transaction_add_job_and_dependencies(m, type, unit, NULL, true, override, false, + mode == JOB_IGNORE_DEPENDENCIES || mode == JOB_IGNORE_REQUIREMENTS, + mode == JOB_IGNORE_DEPENDENCIES, e, &ret)) < 0) { + transaction_abort(m); + return r; + } + + if (mode == JOB_ISOLATE) + if ((r = transaction_add_isolate_jobs(m)) < 0) { + transaction_abort(m); + return r; + } + + if ((r = transaction_activate(m, mode, e)) < 0) + return r; + + log_debug("Enqueued job %s/%s as %u", unit->id, job_type_to_string(type), (unsigned) ret->id); + + if (_ret) + *_ret = ret; + + return 0; +} + +int manager_add_job_by_name(Manager *m, JobType type, const char *name, JobMode mode, bool override, DBusError *e, Job **_ret) { + Unit *unit; + int r; + + assert(m); + assert(type < _JOB_TYPE_MAX); + assert(name); + assert(mode < _JOB_MODE_MAX); + + if ((r = manager_load_unit(m, name, NULL, NULL, &unit)) < 0) + return r; + + return manager_add_job(m, type, unit, mode, override, e, _ret); +} + +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, DBusError *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)) { + dbus_set_error(e, BUS_ERROR_INVALID_PATH, "Path %s is not absolute.", path); + return -EINVAL; + } + + if (!name) + name = file_name_from_path(path); + + t = unit_name_to_type(name); + + if (t == _UNIT_TYPE_INVALID || !unit_name_is_valid_no_type(name, false)) { + dbus_set_error(e, BUS_ERROR_INVALID_NAME, "Unit name %s is not valid.", name); + return -EINVAL; + } + + 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; + } + } + + if ((r = unit_add_name(ret, name)) < 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, DBusError *e, Unit **_ret) { + int r; + + assert(m); + + /* This will load the service information files, but not actually + * start any services or anything. */ + + if ((r = manager_load_unit_prepare(m, name, path, e, _ret)) != 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); + + transaction_abort(m); + + while ((j = hashmap_first(m->jobs))) + job_finish_and_invalidate(j, JOB_CANCELED); +} + +unsigned manager_dispatch_run_queue(Manager *m) { + Job *j; + unsigned n = 0; + + if (m->dispatching_run_queue) + return 0; + + m->dispatching_run_queue = true; + + while ((j = m->run_queue)) { + assert(j->installed); + assert(j->in_run_queue); + + job_run_and_invalidate(j); + n++; + } + + m->dispatching_run_queue = false; + return n; +} + +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; + return n; +} + +static int manager_process_notify_fd(Manager *m) { + ssize_t n; + + assert(m); + + for (;;) { + char buf[4096]; + struct msghdr msghdr; + struct iovec iovec; + struct ucred *ucred; + union { + struct cmsghdr cmsghdr; + uint8_t buf[CMSG_SPACE(sizeof(struct ucred))]; + } control; + Unit *u; + char **tags; + + zero(iovec); + iovec.iov_base = buf; + iovec.iov_len = sizeof(buf)-1; + + zero(control); + zero(msghdr); + msghdr.msg_iov = &iovec; + msghdr.msg_iovlen = 1; + msghdr.msg_control = &control; + msghdr.msg_controllen = sizeof(control); + + if ((n = recvmsg(m->notify_watch.fd, &msghdr, MSG_DONTWAIT)) <= 0) { + if (n >= 0) + return -EIO; + + if (errno == EAGAIN || errno == EINTR) + break; + + return -errno; + } + + if (msghdr.msg_controllen < CMSG_LEN(sizeof(struct ucred)) || + control.cmsghdr.cmsg_level != SOL_SOCKET || + control.cmsghdr.cmsg_type != SCM_CREDENTIALS || + control.cmsghdr.cmsg_len != CMSG_LEN(sizeof(struct ucred))) { + log_warning("Received notify message without credentials. Ignoring."); + continue; + } + + ucred = (struct ucred*) CMSG_DATA(&control.cmsghdr); + + if (!(u = hashmap_get(m->watch_pids, LONG_TO_PTR(ucred->pid)))) + if (!(u = cgroup_unit_by_pid(m, ucred->pid))) { + log_warning("Cannot find unit for notify message of PID %lu.", (unsigned long) ucred->pid); + continue; + } + + assert((size_t) n < sizeof(buf)); + buf[n] = 0; + if (!(tags = strv_split(buf, "\n\r"))) + return -ENOMEM; + + log_debug("Got notification message for unit %s", u->id); + + if (UNIT_VTABLE(u)->notify_message) + UNIT_VTABLE(u)->notify_message(u, ucred->pid, tags); + + strv_free(tags); + } + + return 0; +} + +static int manager_dispatch_sigchld(Manager *m) { + assert(m); + + for (;;) { + siginfo_t si; + Unit *u; + int r; + + zero(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) { + char *name = NULL; + + get_process_comm(si.si_pid, &name); + log_debug("Got SIGCHLD for process %lu (%s)", (unsigned long) si.si_pid, strna(name)); + free(name); + } + + /* Let's flush any message the dying child might still + * have queued for us. This ensures that the process + * still exists in /proc so that we can figure out + * which cgroup and hence unit it belongs to. */ + if ((r = manager_process_notify_fd(m)) < 0) + return r; + + /* And now figure out the unit this belongs to */ + if (!(u = hashmap_get(m->watch_pids, LONG_TO_PTR(si.si_pid)))) + u = cgroup_unit_by_pid(m, si.si_pid); + + /* And now, we actually reap the zombie. */ + if (waitid(P_PID, si.si_pid, &si, WEXITED) < 0) { + if (errno == EINTR) + continue; + + return -errno; + } + + if (si.si_code != CLD_EXITED && si.si_code != CLD_KILLED && si.si_code != CLD_DUMPED) + continue; + + log_debug("Child %lu died (code=%s, status=%i/%s)", + (long unsigned) si.si_pid, + 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))); + + if (!u) + continue; + + log_debug("Child %lu belongs to %s", (long unsigned) si.si_pid, u->id); + + hashmap_remove(m->watch_pids, LONG_TO_PTR(si.si_pid)); + UNIT_VTABLE(u)->sigchld_event(u, si.si_pid, si.si_code, si.si_status); + } + + return 0; +} + +static int manager_start_target(Manager *m, const char *name, JobMode mode) { + int r; + DBusError error; + + dbus_error_init(&error); + + log_debug("Activating special unit %s", name); + + if ((r = manager_add_job_by_name(m, JOB_START, name, mode, true, &error, NULL)) < 0) + log_error("Failed to enqueue %s job: %s", name, bus_error(&error, r)); + + dbus_error_free(&error); + + return r; +} + +static int manager_process_signal_fd(Manager *m) { + ssize_t n; + struct signalfd_siginfo sfsi; + bool sigchld = false; + + assert(m); + + for (;;) { + if ((n = read(m->signal_watch.fd, &sfsi, sizeof(sfsi))) != sizeof(sfsi)) { + + if (n >= 0) + return -EIO; + + if (errno == EINTR || errno == EAGAIN) + break; + + return -errno; + } + + if (sfsi.ssi_pid > 0) { + char *p = NULL; + + get_process_comm(sfsi.ssi_pid, &p); + + log_debug("Received SIG%s from PID %lu (%s).", + signal_to_string(sfsi.ssi_signo), + (unsigned long) sfsi.ssi_pid, strna(p)); + free(p); + } else + log_debug("Received SIG%s.", signal_to_string(sfsi.ssi_signo)); + + switch (sfsi.ssi_signo) { + + case SIGCHLD: + sigchld = true; + break; + + case SIGTERM: + if (m->running_as == MANAGER_SYSTEM) { + /* This is for compatibility with the + * original sysvinit */ + m->exit_code = MANAGER_REEXECUTE; + break; + } + + /* Fall through */ + + case SIGINT: + if (m->running_as == MANAGER_SYSTEM) { + manager_start_target(m, SPECIAL_CTRL_ALT_DEL_TARGET, JOB_REPLACE); + break; + } + + /* Run the exit target if there is one, if not, just exit. */ + if (manager_start_target(m, SPECIAL_EXIT_TARGET, JOB_REPLACE) < 0) { + m->exit_code = MANAGER_EXIT; + return 0; + } + + break; + + case SIGWINCH: + if (m->running_as == MANAGER_SYSTEM) + manager_start_target(m, SPECIAL_KBREQUEST_TARGET, JOB_REPLACE); + + /* This is a nop on non-init */ + break; + + case SIGPWR: + if (m->running_as == MANAGER_SYSTEM) + manager_start_target(m, SPECIAL_SIGPWR_TARGET, JOB_REPLACE); + + /* This is a nop on non-init */ + break; + + case SIGUSR1: { + Unit *u; + + u = manager_get_unit(m, SPECIAL_DBUS_SERVICE); + + if (!u || UNIT_IS_ACTIVE_OR_RELOADING(unit_active_state(u))) { + log_info("Trying to reconnect to bus..."); + bus_init(m, true); + } + + if (!u || !UNIT_IS_ACTIVE_OR_ACTIVATING(unit_active_state(u))) { + log_info("Loading D-Bus service..."); + manager_start_target(m, SPECIAL_DBUS_SERVICE, JOB_REPLACE); + } + + break; + } + + case SIGUSR2: { + FILE *f; + char *dump = NULL; + size_t size; + + if (!(f = open_memstream(&dump, &size))) { + log_warning("Failed to allocate memory stream."); + break; + } + + manager_dump_units(m, f, "\t"); + manager_dump_jobs(m, f, "\t"); + + if (ferror(f)) { + fclose(f); + free(dump); + log_warning("Failed to write status stream"); + break; + } + + fclose(f); + log_dump(LOG_INFO, dump); + free(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: + log_debug("Enabling showing of status."); + manager_set_show_status(m, true); + break; + + case 21: + log_debug("Disabling showing of status."); + manager_set_show_status(m, false); + break; + + case 22: + log_set_max_level(LOG_DEBUG); + log_notice("Setting log level to debug."); + break; + + case 23: + log_set_max_level(LOG_INFO); + log_notice("Setting log level to info."); + break; + + case 26: + 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; + + case 29: + log_set_target(LOG_TARGET_SYSLOG_OR_KMSG); + log_notice("Setting log target to syslog-or-kmsg."); + break; + + default: + log_warning("Got unhandled signal <%s>.", signal_to_string(sfsi.ssi_signo)); + } + } + } + } + + if (sigchld) + return manager_dispatch_sigchld(m); + + return 0; +} + +static int process_event(Manager *m, struct epoll_event *ev) { + int r; + Watch *w; + + assert(m); + assert(ev); + + assert_se(w = ev->data.ptr); + + if (w->type == WATCH_INVALID) + return 0; + + switch (w->type) { + + case WATCH_SIGNAL: + + /* An incoming signal? */ + if (ev->events != EPOLLIN) + return -EINVAL; + + if ((r = manager_process_signal_fd(m)) < 0) + return r; + + break; + + case WATCH_NOTIFY: + + /* An incoming daemon notification event? */ + if (ev->events != EPOLLIN) + return -EINVAL; + + if ((r = manager_process_notify_fd(m)) < 0) + return r; + + break; + + case WATCH_FD: + + /* Some fd event, to be dispatched to the units */ + UNIT_VTABLE(w->data.unit)->fd_event(w->data.unit, w->fd, ev->events, w); + break; + + case WATCH_UNIT_TIMER: + case WATCH_JOB_TIMER: { + uint64_t v; + ssize_t k; + + /* Some timer event, to be dispatched to the units */ + if ((k = read(w->fd, &v, sizeof(v))) != sizeof(v)) { + + if (k < 0 && (errno == EINTR || errno == EAGAIN)) + break; + + return k < 0 ? -errno : -EIO; + } + + if (w->type == WATCH_UNIT_TIMER) + UNIT_VTABLE(w->data.unit)->timer_event(w->data.unit, v, w); + else + job_timer_event(w->data.job, v, w); + break; + } + + case WATCH_MOUNT: + /* Some mount table change, intended for the mount subsystem */ + mount_fd_event(m, ev->events); + break; + + case WATCH_SWAP: + /* Some swap table change, intended for the swap subsystem */ + swap_fd_event(m, ev->events); + break; + + case WATCH_UDEV: + /* Some notification from udev, intended for the device subsystem */ + device_fd_event(m, ev->events); + break; + + case WATCH_DBUS_WATCH: + bus_watch_event(m, w, ev->events); + break; + + case WATCH_DBUS_TIMEOUT: + bus_timeout_event(m, w, ev->events); + break; + + default: + log_error("event type=%i", w->type); + assert_not_reached("Unknown epoll event type."); + } + + return 0; +} + +int manager_loop(Manager *m) { + int r; + int wait_msec = -1; + + RATELIMIT_DEFINE(rl, 1*USEC_PER_SEC, 50000); + + assert(m); + m->exit_code = MANAGER_RUNNING; + + /* Release the path cache */ + set_free_free(m->unit_path_cache); + m->unit_path_cache = NULL; + + manager_check_finished(m); + + /* There might still be some zombies hanging around from + * before we were exec()'ed. Leat's reap them */ + r = manager_dispatch_sigchld(m); + if (r < 0) + return r; + + /* Sleep for half the watchdog time */ + if (m->runtime_watchdog > 0 && m->running_as == MANAGER_SYSTEM) { + wait_msec = (int) (m->runtime_watchdog / 2 / USEC_PER_MSEC); + if (wait_msec <= 0) + wait_msec = 1; + } + + while (m->exit_code == MANAGER_RUNNING) { + struct epoll_event event; + int n; + + if (wait_msec >= 0) + 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); + continue; + } + + if (manager_dispatch_load_queue(m) > 0) + continue; + + if (manager_dispatch_run_queue(m) > 0) + continue; + + if (bus_dispatch(m) > 0) + continue; + + if (manager_dispatch_cleanup_queue(m) > 0) + continue; + + if (manager_dispatch_gc_queue(m) > 0) + continue; + + if (manager_dispatch_dbus_queue(m) > 0) + continue; + + if (swap_dispatch_reload(m) > 0) + continue; + + n = epoll_wait(m->epoll_fd, &event, 1, wait_msec); + if (n < 0) { + + if (errno == EINTR) + continue; + + return -errno; + } else if (n == 0) + continue; + + assert(n == 1); + + r = process_event(m, &event); + if (r < 0) + return r; + } + + return m->exit_code; +} + +int manager_get_unit_from_dbus_path(Manager *m, const char *s, Unit **_u) { + char *n; + Unit *u; + + assert(m); + assert(s); + assert(_u); + + if (!startswith(s, "/org/freedesktop/systemd1/unit/")) + return -EINVAL; + + if (!(n = bus_path_unescape(s+31))) + return -ENOMEM; + + u = manager_get_unit(m, n); + free(n); + + if (!u) + return -ENOENT; + + *_u = u; + + return 0; +} + +int manager_get_job_from_dbus_path(Manager *m, const char *s, Job **_j) { + Job *j; + unsigned id; + int r; + + assert(m); + assert(s); + assert(_j); + + if (!startswith(s, "/org/freedesktop/systemd1/job/")) + return -EINVAL; + + if ((r = safe_atou(s + 30, &id)) < 0) + return r; + + if (!(j = manager_get_job(m, id))) + return -ENOENT; + + *_j = j; + + return 0; +} + +void manager_send_unit_audit(Manager *m, Unit *u, int type, bool success) { + +#ifdef HAVE_AUDIT + char *p; + + if (m->audit_fd < 0) + return; + + /* Don't generate audit events if the service was already + * started and we're just deserializing */ + if (m->n_reloading > 0) + return; + + if (m->running_as != MANAGER_SYSTEM) + return; + + if (u->type != UNIT_SERVICE) + return; + + if (!(p = unit_name_to_prefix_and_instance(u->id))) { + log_error("Failed to allocate unit name for audit message: %s", strerror(ENOMEM)); + return; + } + + if (audit_log_user_comm_message(m->audit_fd, type, "", p, NULL, NULL, NULL, success) < 0) { + log_warning("Failed to send audit message: %m"); + + if (errno == EPERM) { + /* We aren't allowed to send audit messages? + * Then let's not retry again, to avoid + * spamming the user with the same and same + * messages over and over. */ + + audit_close(m->audit_fd); + m->audit_fd = -1; + } + } + + free(p); +#endif + +} + +void manager_send_unit_plymouth(Manager *m, Unit *u) { + int fd = -1; + union sockaddr_union sa; + int n = 0; + char *message = NULL; + + /* Don't generate plymouth events if the service was already + * started and we're just deserializing */ + if (m->n_reloading > 0) + return; + + if (m->running_as != MANAGER_SYSTEM) + return; + + if (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 */ + if ((fd = socket(AF_UNIX, SOCK_STREAM|SOCK_CLOEXEC|SOCK_NONBLOCK, 0)) < 0) { + log_error("socket() failed: %m"); + return; + } + + zero(sa); + sa.sa.sa_family = AF_UNIX; + strncpy(sa.un.sun_path+1, "/org/freedesktop/plymouthd", sizeof(sa.un.sun_path)-1); + if (connect(fd, &sa.sa, offsetof(struct sockaddr_un, sun_path) + 1 + strlen(sa.un.sun_path+1)) < 0) { + + if (errno != EPIPE && + errno != EAGAIN && + errno != ENOENT && + errno != ECONNREFUSED && + errno != ECONNRESET && + errno != ECONNABORTED) + log_error("connect() failed: %m"); + + goto finish; + } + + if (asprintf(&message, "U\002%c%s%n", (int) (strlen(u->id) + 1), u->id, &n) < 0) { + log_error("Out of memory"); + goto finish; + } + + errno = 0; + if (write(fd, message, n + 1) != n + 1) { + + if (errno != EPIPE && + errno != EAGAIN && + errno != ENOENT && + errno != ECONNREFUSED && + errno != ECONNRESET && + errno != ECONNABORTED) + log_error("Failed to write Plymouth message: %m"); + + goto finish; + } + +finish: + if (fd >= 0) + close_nointr_nofail(fd); + + free(message); +} + +void manager_dispatch_bus_name_owner_changed( + Manager *m, + const char *name, + const char* old_owner, + const char *new_owner) { + + Unit *u; + + assert(m); + assert(name); + + if (!(u = hashmap_get(m->watch_bus, name))) + return; + + UNIT_VTABLE(u)->bus_name_owner_change(u, name, old_owner, new_owner); +} + +void manager_dispatch_bus_query_pid_done( + Manager *m, + const char *name, + pid_t pid) { + + Unit *u; + + assert(m); + assert(name); + assert(pid >= 1); + + if (!(u = hashmap_get(m->watch_bus, name))) + return; + + UNIT_VTABLE(u)->bus_query_pid_done(u, name, pid); +} + +int manager_open_serialization(Manager *m, FILE **_f) { + char *path = NULL; + mode_t saved_umask; + int fd; + FILE *f; + + assert(_f); + + if (m->running_as == MANAGER_SYSTEM) + asprintf(&path, "/run/systemd/dump-%lu-XXXXXX", (unsigned long) getpid()); + else + asprintf(&path, "/tmp/systemd-dump-%lu-XXXXXX", (unsigned long) getpid()); + + if (!path) + return -ENOMEM; + + saved_umask = umask(0077); + fd = mkostemp(path, O_RDWR|O_CLOEXEC); + umask(saved_umask); + + if (fd < 0) { + free(path); + return -errno; + } + + unlink(path); + + log_debug("Serializing state to %s", path); + free(path); + + if (!(f = fdopen(fd, "w+"))) + return -errno; + + *_f = f; + + return 0; +} + +int manager_serialize(Manager *m, FILE *f, FDSet *fds) { + Iterator i; + Unit *u; + const char *t; + int r; + + assert(m); + assert(f); + assert(fds); + + m->n_reloading ++; + + fprintf(f, "current-job-id=%i\n", m->current_job_id); + fprintf(f, "taint-usr=%s\n", yes_no(m->taint_usr)); + + dual_timestamp_serialize(f, "initrd-timestamp", &m->initrd_timestamp); + dual_timestamp_serialize(f, "startup-timestamp", &m->startup_timestamp); + dual_timestamp_serialize(f, "finish-timestamp", &m->finish_timestamp); + + fputc('\n', f); + + HASHMAP_FOREACH_KEY(u, t, m->units, i) { + if (u->id != t) + continue; + + if (!unit_can_serialize(u)) + continue; + + /* Start marker */ + fputs(u->id, f); + fputc('\n', f); + + if ((r = unit_serialize(u, f, fds)) < 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, "taint-usr=")) { + int b; + + if ((b = parse_boolean(l+10)) < 0) + log_debug("Failed to parse taint /usr flag %s", l+10); + else + m->taint_usr = m->taint_usr || b; + } else if (startswith(l, "initrd-timestamp=")) + dual_timestamp_deserialize(l+17, &m->initrd_timestamp); + else if (startswith(l, "startup-timestamp=")) + dual_timestamp_deserialize(l+18, &m->startup_timestamp); + else if (startswith(l, "finish-timestamp=")) + dual_timestamp_deserialize(l+17, &m->finish_timestamp); + else + 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); + + if ((r = manager_load_unit(m, strstrip(name), NULL, NULL, &u)) < 0) + goto finish; + + if ((r = unit_deserialize(u, f, fds)) < 0) + goto finish; + } + +finish: + if (ferror(f)) { + r = -EIO; + goto finish; + } + + assert(m->n_reloading > 0); + m->n_reloading --; + + return r; +} + +int manager_reload(Manager *m) { + int r, q; + FILE *f; + FDSet *fds; + + assert(m); + + if ((r = manager_open_serialization(m, &f)) < 0) + return r; + + m->n_reloading ++; + + if (!(fds = fdset_new())) { + m->n_reloading --; + r = -ENOMEM; + goto finish; + } + + if ((r = manager_serialize(m, f, fds)) < 0) { + m->n_reloading --; + goto finish; + } + + if (fseeko(f, 0, SEEK_SET) < 0) { + m->n_reloading --; + r = -errno; + goto finish; + } + + /* From here on there is no way back. */ + manager_clear_jobs_and_units(m); + manager_undo_generators(m); + + /* Find new unit paths */ + lookup_paths_free(&m->lookup_paths); + if ((q = lookup_paths_init(&m->lookup_paths, m->running_as, true)) < 0) + r = q; + + manager_run_generators(m); + + manager_build_unit_path_cache(m); + + /* First, enumerate what we can from all config files */ + if ((q = manager_enumerate(m)) < 0) + r = q; + + /* Second, deserialize our stored data */ + if ((q = manager_deserialize(m, f, fds)) < 0) + r = q; + + fclose(f); + f = NULL; + + /* Third, fire things up! */ + if ((q = manager_coldplug(m)) < 0) + r = q; + + assert(m->n_reloading > 0); + m->n_reloading--; + +finish: + if (f) + fclose(f); + + if (fds) + fdset_free(fds); + + return r; +} + +bool manager_is_booting_or_shutting_down(Manager *m) { + Unit *u; + + assert(m); + + /* Is the initial job still around? */ + if (manager_get_job(m, m->default_unit_job_id)) + return true; + + /* Is there a job for the shutdown target? */ + u = manager_get_unit(m, SPECIAL_SHUTDOWN_TARGET); + if (u) + return !!u->job; + + return false; +} + +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_pending_inactive(Manager *m, const char *name) { + Unit *u; + + assert(m); + assert(name); + + /* Returns true if the unit is inactive or going down */ + if (!(u = manager_get_unit(m, name))) + return true; + + return unit_pending_inactive(u); +} + +void manager_check_finished(Manager *m) { + char userspace[FORMAT_TIMESPAN_MAX], initrd[FORMAT_TIMESPAN_MAX], kernel[FORMAT_TIMESPAN_MAX], sum[FORMAT_TIMESPAN_MAX]; + usec_t kernel_usec = 0, initrd_usec = 0, userspace_usec = 0, total_usec = 0; + + assert(m); + + if (dual_timestamp_is_set(&m->finish_timestamp)) + return; + + if (hashmap_size(m->jobs) > 0) + return; + + dual_timestamp_get(&m->finish_timestamp); + + if (m->running_as == MANAGER_SYSTEM && detect_container(NULL) <= 0) { + + userspace_usec = m->finish_timestamp.monotonic - m->startup_timestamp.monotonic; + total_usec = m->finish_timestamp.monotonic; + + if (dual_timestamp_is_set(&m->initrd_timestamp)) { + + kernel_usec = m->initrd_timestamp.monotonic; + initrd_usec = m->startup_timestamp.monotonic - m->initrd_timestamp.monotonic; + + log_info("Startup finished in %s (kernel) + %s (initrd) + %s (userspace) = %s.", + format_timespan(kernel, sizeof(kernel), kernel_usec), + format_timespan(initrd, sizeof(initrd), initrd_usec), + format_timespan(userspace, sizeof(userspace), userspace_usec), + format_timespan(sum, sizeof(sum), total_usec)); + } else { + kernel_usec = m->startup_timestamp.monotonic; + initrd_usec = 0; + + log_info("Startup finished in %s (kernel) + %s (userspace) = %s.", + format_timespan(kernel, sizeof(kernel), kernel_usec), + format_timespan(userspace, sizeof(userspace), userspace_usec), + format_timespan(sum, sizeof(sum), total_usec)); + } + } else { + userspace_usec = initrd_usec = kernel_usec = 0; + total_usec = m->finish_timestamp.monotonic - m->startup_timestamp.monotonic; + + log_debug("Startup finished in %s.", + format_timespan(sum, sizeof(sum), total_usec)); + } + + bus_broadcast_finished(m, kernel_usec, initrd_usec, userspace_usec, total_usec); + + sd_notifyf(false, + "READY=1\nSTATUS=Startup finished in %s.", + format_timespan(sum, sizeof(sum), total_usec)); +} + +void manager_run_generators(Manager *m) { + DIR *d = NULL; + const char *generator_path; + const char *argv[3]; + mode_t u; + + assert(m); + + generator_path = m->running_as == MANAGER_SYSTEM ? SYSTEM_GENERATOR_PATH : USER_GENERATOR_PATH; + if (!(d = opendir(generator_path))) { + + if (errno == ENOENT) + return; + + log_error("Failed to enumerate generator directory: %m"); + return; + } + + if (!m->generator_unit_path) { + const char *p; + char user_path[] = "/tmp/systemd-generator-XXXXXX"; + + if (m->running_as == MANAGER_SYSTEM && getpid() == 1) { + p = "/run/systemd/generator"; + + if (mkdir_p(p, 0755) < 0) { + log_error("Failed to create generator directory: %m"); + goto finish; + } + + } else { + if (!(p = mkdtemp(user_path))) { + log_error("Failed to create generator directory: %m"); + goto finish; + } + } + + if (!(m->generator_unit_path = strdup(p))) { + log_error("Failed to allocate generator unit path."); + goto finish; + } + } + + argv[0] = NULL; /* Leave this empty, execute_directory() will fill something in */ + argv[1] = m->generator_unit_path; + argv[2] = NULL; + + u = umask(0022); + execute_directory(generator_path, d, (char**) argv); + umask(u); + + if (rmdir(m->generator_unit_path) >= 0) { + /* Uh? we were able to remove this dir? I guess that + * means the directory was empty, hence let's shortcut + * this */ + + free(m->generator_unit_path); + m->generator_unit_path = NULL; + goto finish; + } + + if (!strv_find(m->lookup_paths.unit_path, m->generator_unit_path)) { + char **l; + + if (!(l = strv_append(m->lookup_paths.unit_path, m->generator_unit_path))) { + log_error("Failed to add generator directory to unit search path: %m"); + goto finish; + } + + strv_free(m->lookup_paths.unit_path); + m->lookup_paths.unit_path = l; + + log_debug("Added generator unit path %s to search path.", m->generator_unit_path); + } + +finish: + if (d) + closedir(d); +} + +void manager_undo_generators(Manager *m) { + assert(m); + + if (!m->generator_unit_path) + return; + + strv_remove(m->lookup_paths.unit_path, m->generator_unit_path); + rm_rf(m->generator_unit_path, false, true, false); + + free(m->generator_unit_path); + m->generator_unit_path = NULL; +} + +int manager_set_default_controllers(Manager *m, char **controllers) { + char **l; + + assert(m); + + if (!(l = strv_copy(controllers))) + return -ENOMEM; + + strv_free(m->default_controllers); + m->default_controllers = l; + + return 0; +} + +void manager_recheck_journal(Manager *m) { + Unit *u; + + assert(m); + + if (m->running_as != MANAGER_SYSTEM) + return; + + u = manager_get_unit(m, SPECIAL_JOURNALD_SOCKET); + if (u && SOCKET(u)->state != SOCKET_RUNNING) { + log_close_journal(); + return; + } + + u = manager_get_unit(m, SPECIAL_JOURNALD_SERVICE); + if (u && SERVICE(u)->state != SERVICE_RUNNING) { + log_close_journal(); + return; + } + + /* Hmm, OK, so the socket is fully up and the service is up + * too, then let's make use of the thing. */ + log_open(); +} + +void manager_set_show_status(Manager *m, bool b) { + assert(m); + + if (m->running_as != MANAGER_SYSTEM) + return; + + m->show_status = b; + + if (b) + touch("/run/systemd/show-status"); + else + unlink("/run/systemd/show-status"); +} + +bool manager_get_show_status(Manager *m) { + assert(m); + + if (m->running_as != MANAGER_SYSTEM) + return false; + + if (m->show_status) + return true; + + /* If Plymouth is running make sure we show the status, so + * that there's something nice to see when people press Esc */ + + return plymouth_running(); +} + +static const char* const manager_running_as_table[_MANAGER_RUNNING_AS_MAX] = { + [MANAGER_SYSTEM] = "system", + [MANAGER_USER] = "user" +}; + +DEFINE_STRING_TABLE_LOOKUP(manager_running_as, ManagerRunningAs); diff --git a/src/core/manager.h b/src/core/manager.h new file mode 100644 index 0000000000..9ecc926cbf --- /dev/null +++ b/src/core/manager.h @@ -0,0 +1,304 @@ +/*-*- Mode: C; c-basic-offset: 8; indent-tabs-mode: nil -*-*/ + +#ifndef foomanagerhfoo +#define foomanagerhfoo + +/*** + This file is part of systemd. + + Copyright 2010 Lennart Poettering + + systemd is free software; you can redistribute it and/or modify it + under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + systemd is distributed in the hope that 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 General Public License + along with systemd; If not, see . +***/ + +#include +#include +#include +#include + +#include "fdset.h" + +/* Enforce upper limit how many names we allow */ +#define MANAGER_MAX_NAMES 131072 /* 128K */ + +typedef struct Manager Manager; +typedef enum WatchType WatchType; +typedef struct Watch Watch; + +typedef enum ManagerExitCode { + MANAGER_RUNNING, + MANAGER_EXIT, + MANAGER_RELOAD, + MANAGER_REEXECUTE, + MANAGER_REBOOT, + MANAGER_POWEROFF, + MANAGER_HALT, + MANAGER_KEXEC, + _MANAGER_EXIT_CODE_MAX, + _MANAGER_EXIT_CODE_INVALID = -1 +} ManagerExitCode; + +typedef enum ManagerRunningAs { + MANAGER_SYSTEM, + MANAGER_USER, + _MANAGER_RUNNING_AS_MAX, + _MANAGER_RUNNING_AS_INVALID = -1 +} ManagerRunningAs; + +enum WatchType { + WATCH_INVALID, + WATCH_SIGNAL, + WATCH_NOTIFY, + WATCH_FD, + WATCH_UNIT_TIMER, + WATCH_JOB_TIMER, + WATCH_MOUNT, + WATCH_SWAP, + WATCH_UDEV, + WATCH_DBUS_WATCH, + WATCH_DBUS_TIMEOUT +}; + +struct Watch { + int fd; + WatchType type; + union { + struct Unit *unit; + struct Job *job; + DBusWatch *bus_watch; + DBusTimeout *bus_timeout; + } data; + bool fd_is_dupped:1; + bool socket_accept:1; +}; + +#include "unit.h" +#include "job.h" +#include "hashmap.h" +#include "list.h" +#include "set.h" +#include "dbus.h" +#include "path-lookup.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); + + /* Jobs to be added */ + Hashmap *transaction_jobs; /* Unit object => Job object list 1:1 */ + JobDependency *transaction_anchor; + + Hashmap *watch_pids; /* pid => Unit object n:1 */ + + char *notify_socket; + + Watch notify_watch; + Watch signal_watch; + + int epoll_fd; + + unsigned n_snapshots; + + LookupPaths lookup_paths; + Set *unit_path_cache; + + char **environment; + char **default_controllers; + + usec_t runtime_watchdog; + usec_t shutdown_watchdog; + + dual_timestamp initrd_timestamp; + dual_timestamp startup_timestamp; + dual_timestamp finish_timestamp; + + char *generator_unit_path; + + /* Data specific to the device subsystem */ + struct udev* udev; + struct udev_monitor* udev_monitor; + Watch udev_watch; + Hashmap *devices_by_sysfs; + + /* Data specific to the mount subsystem */ + FILE *proc_self_mountinfo; + Watch mount_watch; + + /* Data specific to the swap filesystem */ + FILE *proc_swaps; + Hashmap *swaps_by_proc_swaps; + bool request_reload; + Watch swap_watch; + + /* Data specific to the D-Bus subsystem */ + DBusConnection *api_bus, *system_bus; + DBusServer *private_bus; + Set *bus_connections, *bus_connections_for_dispatch; + + DBusMessage *queued_message; /* This is used during reloading: + * before the reload we queue the + * reply message here, and + * afterwards we send it */ + DBusConnection *queued_message_connection; /* The connection to send the queued message on */ + + Hashmap *watch_bus; /* D-Bus names => Unit object n:1 */ + int32_t name_data_slot; + int32_t conn_data_slot; + int32_t subscribed_data_slot; + + 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_bondings; /* path string => CGroupBonding object 1:n */ + char *cgroup_hierarchy; + + usec_t gc_queue_timestamp; + int gc_marker; + unsigned n_in_gc_queue; + + /* Make sure the user cannot accidentally unmount our cgroup + * file system */ + int pin_cgroupfs_fd; + + /* Audit fd */ +#ifdef HAVE_AUDIT + int audit_fd; +#endif + + /* Flags */ + ManagerRunningAs running_as; + ManagerExitCode exit_code:5; + + bool dispatching_load_queue:1; + bool dispatching_run_queue:1; + bool dispatching_dbus_queue:1; + + bool taint_usr:1; + + bool show_status; + bool confirm_spawn; +#ifdef HAVE_SYSV_COMPAT + bool sysv_console; +#endif + bool mount_auto; + bool swap_auto; + + ExecOutput default_std_output, default_std_error; + + /* non-zero if we are reloading or reexecuting, */ + int n_reloading; + + unsigned n_installed_jobs; + unsigned n_failed_jobs; +}; + +int manager_new(ManagerRunningAs running_as, Manager **m); +void manager_free(Manager *m); + +int manager_enumerate(Manager *m); +int manager_coldplug(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_unit_from_dbus_path(Manager *m, const char *s, Unit **_u); +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, DBusError *e, Unit **_ret); +int manager_load_unit(Manager *m, const char *name, const char *path, DBusError *e, Unit **_ret); + +int manager_add_job(Manager *m, JobType type, Unit *unit, JobMode mode, bool force, DBusError *e, Job **_ret); +int manager_add_job_by_name(Manager *m, JobType type, const char *name, JobMode mode, bool force, DBusError *e, 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_transaction_unlink_job(Manager *m, Job *j, bool delete_dependencies); + +void manager_clear_jobs(Manager *m); + +unsigned manager_dispatch_load_queue(Manager *m); +unsigned manager_dispatch_run_queue(Manager *m); +unsigned manager_dispatch_dbus_queue(Manager *m); + +int manager_set_default_controllers(Manager *m, char **controllers); + +int manager_loop(Manager *m); + +void manager_dispatch_bus_name_owner_changed(Manager *m, const char *name, const char* old_owner, const char *new_owner); +void manager_dispatch_bus_query_pid_done(Manager *m, const char *name, pid_t pid); + +int manager_open_serialization(Manager *m, FILE **_f); + +int manager_serialize(Manager *m, FILE *f, FDSet *fds); +int manager_deserialize(Manager *m, FILE *f, FDSet *fds); + +int manager_reload(Manager *m); + +bool manager_is_booting_or_shutting_down(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_pending_inactive(Manager *m, const char *name); + +void manager_check_finished(Manager *m); + +void manager_run_generators(Manager *m); +void manager_undo_generators(Manager *m); + +void manager_recheck_journal(Manager *m); + +void manager_set_show_status(Manager *m, bool b); +bool manager_get_show_status(Manager *m); + +const char *manager_running_as_to_string(ManagerRunningAs i); +ManagerRunningAs manager_running_as_from_string(const char *s); + +#endif diff --git a/src/core/mount.c b/src/core/mount.c new file mode 100644 index 0000000000..7dbeaf9cf0 --- /dev/null +++ b/src/core/mount.c @@ -0,0 +1,1930 @@ +/*-*- Mode: C; c-basic-offset: 8; indent-tabs-mode: nil -*-*/ + +/*** + This file is part of systemd. + + Copyright 2010 Lennart Poettering + + systemd is free software; you can redistribute it and/or modify it + under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + systemd is distributed in the hope that 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 General Public License + along with systemd; If not, see . +***/ + +#include +#include +#include +#include +#include + +#include "unit.h" +#include "mount.h" +#include "load-fragment.h" +#include "load-dropin.h" +#include "log.h" +#include "strv.h" +#include "mkdir.h" +#include "mount-setup.h" +#include "unit-name.h" +#include "dbus-mount.h" +#include "special.h" +#include "bus-errors.h" +#include "exit-status.h" +#include "def.h" + +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 void mount_init(Unit *u) { + Mount *m = MOUNT(u); + + assert(u); + assert(u->load_state == UNIT_STUB); + + m->timeout_usec = DEFAULT_TIMEOUT_USEC; + m->directory_mode = 0755; + + exec_context_init(&m->exec_context); + + /* The stdio/kmsg bridge socket is on /, in order to avoid a + * dep loop, don't use kmsg logging for -.mount */ + if (!unit_has_name(u, "-.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 /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->timer_watch.type = WATCH_INVALID; + + m->control_command_id = _MOUNT_EXEC_COMMAND_INVALID; + + UNIT(m)->ignore_on_isolate = true; +} + +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); + + free(m->where); + m->where = NULL; + + mount_parameters_done(&m->parameters_etc_fstab); + mount_parameters_done(&m->parameters_proc_self_mountinfo); + mount_parameters_done(&m->parameters_fragment); + + exec_context_done(&m->exec_context); + exec_command_done_array(m->exec_command, _MOUNT_EXEC_COMMAND_MAX); + m->control_command = NULL; + + mount_unwatch_control_pid(m); + + unit_unwatch_timer(u, &m->timer_watch); +} + +static MountParameters* get_mount_parameters_configured(Mount *m) { + assert(m); + + if (m->from_fragment) + return &m->parameters_fragment; + else if (m->from_etc_fstab) + return &m->parameters_etc_fstab; + + return NULL; +} + +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_configured(m); +} + +static int mount_add_mount_links(Mount *m) { + Unit *other; + int r; + MountParameters *pm; + + assert(m); + + pm = get_mount_parameters_configured(m); + + /* Adds in links to other mount points that might lie below or + * above us in the hierarchy */ + + LIST_FOREACH(units_by_type, other, UNIT(m)->manager->units_by_type[UNIT_MOUNT]) { + Mount *n = MOUNT(other); + MountParameters *pn; + + if (n == m) + continue; + + if (UNIT(n)->load_state != UNIT_LOADED) + continue; + + pn = get_mount_parameters_configured(n); + + if (path_startswith(m->where, n->where)) { + + if ((r = unit_add_dependency(UNIT(m), UNIT_AFTER, UNIT(n), true)) < 0) + return r; + + if (pn) + if ((r = unit_add_dependency(UNIT(m), UNIT_REQUIRES, UNIT(n), true)) < 0) + return r; + + } else if (path_startswith(n->where, m->where)) { + + if ((r = unit_add_dependency(UNIT(n), UNIT_AFTER, UNIT(m), true)) < 0) + return r; + + if (pm) + if ((r = unit_add_dependency(UNIT(n), UNIT_REQUIRES, UNIT(m), true)) < 0) + return r; + + } else if (pm && pm->what && path_startswith(pm->what, n->where)) { + + if ((r = unit_add_dependency(UNIT(m), UNIT_AFTER, UNIT(n), true)) < 0) + return r; + + if ((r = unit_add_dependency(UNIT(m), UNIT_REQUIRES, UNIT(n), true)) < 0) + return r; + + } else if (pn && pn->what && path_startswith(pn->what, m->where)) { + + if ((r = unit_add_dependency(UNIT(n), UNIT_AFTER, UNIT(m), true)) < 0) + return r; + + if ((r = unit_add_dependency(UNIT(n), UNIT_REQUIRES, UNIT(m), true)) < 0) + return r; + } + } + + return 0; +} + +static int mount_add_swap_links(Mount *m) { + Unit *other; + int r; + + assert(m); + + LIST_FOREACH(units_by_type, other, UNIT(m)->manager->units_by_type[UNIT_SWAP]) + if ((r = swap_add_one_mount_link(SWAP(other), m)) < 0) + return r; + + return 0; +} + +static int mount_add_path_links(Mount *m) { + Unit *other; + int r; + + assert(m); + + LIST_FOREACH(units_by_type, other, UNIT(m)->manager->units_by_type[UNIT_PATH]) + if ((r = path_add_one_mount_link(PATH(other), m)) < 0) + return r; + + return 0; +} + +static int mount_add_automount_links(Mount *m) { + Unit *other; + int r; + + assert(m); + + LIST_FOREACH(units_by_type, other, UNIT(m)->manager->units_by_type[UNIT_AUTOMOUNT]) + if ((r = automount_add_one_mount_link(AUTOMOUNT(other), m)) < 0) + return r; + + return 0; +} + +static int mount_add_socket_links(Mount *m) { + Unit *other; + int r; + + assert(m); + + LIST_FOREACH(units_by_type, other, UNIT(m)->manager->units_by_type[UNIT_SOCKET]) + if ((r = socket_add_one_mount_link(SOCKET(other), m)) < 0) + return r; + + return 0; +} + +static char* mount_test_option(const char *haystack, const char *needle) { + struct mntent me; + + assert(needle); + + /* Like glibc's hasmntopt(), but works on a string, not a + * struct mntent */ + + if (!haystack) + return NULL; + + zero(me); + me.mnt_opts = (char*) haystack; + + return hasmntopt(&me, needle); +} + +static bool mount_is_network(MountParameters *p) { + assert(p); + + if (mount_test_option(p->options, "_netdev")) + return true; + + if (p->fstype && fstype_is_network(p->fstype)) + return true; + + return false; +} + +static bool mount_is_bind(MountParameters *p) { + assert(p); + + if (mount_test_option(p->options, "bind")) + return true; + + if (p->fstype && streq(p->fstype, "bind")) + return true; + + return false; +} + +static bool needs_quota(MountParameters *p) { + assert(p); + + if (mount_is_network(p)) + return false; + + if (mount_is_bind(p)) + return false; + + return mount_test_option(p->options, "usrquota") || + mount_test_option(p->options, "grpquota") || + mount_test_option(p->options, "quota") || + mount_test_option(p->options, "usrjquota") || + mount_test_option(p->options, "grpjquota"); +} + +static int mount_add_fstab_links(Mount *m) { + const char *target, *after, *tu_wants = NULL; + MountParameters *p; + Unit *tu; + int r; + bool noauto, nofail, handle, automount; + + assert(m); + + if (UNIT(m)->manager->running_as != MANAGER_SYSTEM) + return 0; + + if (!(p = get_mount_parameters_configured(m))) + return 0; + + if (p != &m->parameters_etc_fstab) + return 0; + + noauto = !!mount_test_option(p->options, "noauto"); + nofail = !!mount_test_option(p->options, "nofail"); + automount = + mount_test_option(p->options, "comment=systemd.automount") || + mount_test_option(p->options, "x-systemd-automount"); + handle = + automount || + mount_test_option(p->options, "comment=systemd.mount") || + mount_test_option(p->options, "x-systemd-mount") || + UNIT(m)->manager->mount_auto; + + if (mount_is_network(p)) { + target = SPECIAL_REMOTE_FS_TARGET; + after = tu_wants = SPECIAL_REMOTE_FS_PRE_TARGET; + } else { + target = SPECIAL_LOCAL_FS_TARGET; + after = SPECIAL_LOCAL_FS_PRE_TARGET; + } + + r = manager_load_unit(UNIT(m)->manager, target, NULL, NULL, &tu); + if (r < 0) + return r; + + if (tu_wants) { + r = unit_add_dependency_by_name(tu, UNIT_WANTS, tu_wants, NULL, true); + if (r < 0) + return r; + } + + if (after) { + r = unit_add_dependency_by_name(UNIT(m), UNIT_AFTER, after, NULL, true); + if (r < 0) + return r; + } + + if (automount) { + Unit *am; + + if ((r = unit_load_related_unit(UNIT(m), ".automount", &am)) < 0) + return r; + + /* If auto is configured as well also pull in the + * mount right-away, but don't rely on it. */ + if (!noauto) /* automount + auto */ + if ((r = unit_add_dependency(tu, UNIT_WANTS, UNIT(m), true)) < 0) + return r; + + /* Install automount unit */ + if (!nofail) /* automount + fail */ + return unit_add_two_dependencies(tu, UNIT_AFTER, UNIT_REQUIRES, am, true); + else /* automount + nofail */ + return unit_add_two_dependencies(tu, UNIT_AFTER, UNIT_WANTS, am, true); + + } else if (handle && !noauto) { + + /* Automatically add mount points that aren't natively + * configured to local-fs.target */ + + if (!nofail) /* auto + fail */ + return unit_add_two_dependencies(tu, UNIT_AFTER, UNIT_REQUIRES, UNIT(m), true); + else /* auto + nofail */ + return unit_add_dependency(tu, UNIT_WANTS, UNIT(m), true); + } + + return 0; +} + +static int mount_add_device_links(Mount *m) { + MountParameters *p; + int r; + + assert(m); + + if (!(p = get_mount_parameters_configured(m))) + return 0; + + if (!p->what) + return 0; + + if (!mount_is_bind(p) && + !path_equal(m->where, "/") && + p == &m->parameters_etc_fstab) { + bool nofail, noauto; + + noauto = !!mount_test_option(p->options, "noauto"); + nofail = !!mount_test_option(p->options, "nofail"); + + if ((r = unit_add_node_link(UNIT(m), p->what, + !noauto && nofail && + UNIT(m)->manager->running_as == MANAGER_SYSTEM)) < 0) + return r; + } + + if (p->passno > 0 && + !mount_is_bind(p) && + UNIT(m)->manager->running_as == MANAGER_SYSTEM && + !path_equal(m->where, "/")) { + char *name; + Unit *fsck; + /* Let's add in the fsck service */ + + /* aka SPECIAL_FSCK_SERVICE */ + if (!(name = unit_name_from_path_instance("fsck", p->what, ".service"))) + return -ENOMEM; + + if ((r = manager_load_unit_prepare(UNIT(m)->manager, name, NULL, NULL, &fsck)) < 0) { + log_warning("Failed to prepare unit %s: %s", name, strerror(-r)); + free(name); + return r; + } + + free(name); + + SERVICE(fsck)->fsck_passno = p->passno; + + if ((r = unit_add_two_dependencies(UNIT(m), UNIT_AFTER, UNIT_REQUIRES, fsck, true)) < 0) + return r; + } + + return 0; +} + +static int mount_add_default_dependencies(Mount *m) { + int r; + MountParameters *p; + + assert(m); + + if (UNIT(m)->manager->running_as != MANAGER_SYSTEM) + return 0; + + p = get_mount_parameters_configured(m); + if (p && needs_quota(p)) { + if ((r = unit_add_two_dependencies_by_name(UNIT(m), UNIT_BEFORE, UNIT_WANTS, SPECIAL_QUOTACHECK_SERVICE, NULL, true)) < 0 || + (r = unit_add_two_dependencies_by_name(UNIT(m), UNIT_BEFORE, UNIT_WANTS, SPECIAL_QUOTAON_SERVICE, NULL, true)) < 0) + return r; + } + + if (!path_equal(m->where, "/")) + if ((r = unit_add_two_dependencies_by_name(UNIT(m), UNIT_BEFORE, UNIT_CONFLICTS, SPECIAL_UMOUNT_TARGET, NULL, true)) < 0) + return r; + + return 0; +} + +static int mount_fix_timeouts(Mount *m) { + MountParameters *p; + const char *timeout = NULL; + Unit *other; + Iterator i; + usec_t u; + char *t; + int r; + + assert(m); + + if (!(p = get_mount_parameters_configured(m))) + return 0; + + /* Allow configuration how long we wait for a device that + * backs a mount point to show up. This is useful to support + * endless device timeouts for devices that show up only after + * user input, like crypto devices. */ + + if ((timeout = mount_test_option(p->options, "comment=systemd.device-timeout"))) + timeout += 31; + else if ((timeout = mount_test_option(p->options, "x-systemd-device-timeout"))) + timeout += 25; + else + return 0; + + t = strndup(timeout, strcspn(timeout, ",;" WHITESPACE)); + if (!t) + return -ENOMEM; + + r = parse_usec(t, &u); + free(t); + + if (r < 0) { + log_warning("Failed to parse timeout for %s, ignoring: %s", m->where, timeout); + return r; + } + + SET_FOREACH(other, UNIT(m)->dependencies[UNIT_AFTER], i) { + if (other->type != UNIT_DEVICE) + continue; + + other->job_timeout = u; + } + + return 0; +} + +static int mount_verify(Mount *m) { + bool b; + char *e; + assert(m); + + if (UNIT(m)->load_state != UNIT_LOADED) + return 0; + + if (!m->from_etc_fstab && !m->from_fragment && !m->from_proc_self_mountinfo) + return -ENOENT; + + if (!(e = unit_name_from_path(m->where, ".mount"))) + return -ENOMEM; + + b = unit_has_name(UNIT(m), e); + free(e); + + if (!b) { + log_error("%s's Where setting doesn't match unit name. Refusing.", UNIT(m)->id); + return -EINVAL; + } + + if (mount_point_is_api(m->where) || mount_point_ignore(m->where)) { + log_error("Cannot create mount unit for API file system %s. Refusing.", m->where); + return -EINVAL; + } + + if (UNIT(m)->fragment_path && !m->parameters_fragment.what) { + log_error("%s's What setting is missing. Refusing.", UNIT(m)->id); + return -EBADMSG; + } + + if (m->exec_context.pam_name && m->exec_context.kill_mode != KILL_CONTROL_GROUP) { + log_error("%s has PAM enabled. Kill mode must be set to 'control-group'. Refusing.", UNIT(m)->id); + return -EINVAL; + } + + return 0; +} + +static int mount_load(Unit *u) { + Mount *m = MOUNT(u); + int r; + + assert(u); + assert(u->load_state == UNIT_STUB); + + if ((r = unit_load_fragment_and_dropin_optional(u)) < 0) + return r; + + /* This is a new unit? Then let's add in some extras */ + if (u->load_state == UNIT_LOADED) { + if ((r = unit_add_exec_dependencies(u, &m->exec_context)) < 0) + return r; + + if (UNIT(m)->fragment_path) + m->from_fragment = true; + else if (m->from_etc_fstab) + /* We always add several default dependencies to fstab mounts, + * but we do not want the implicit complementing of Wants= with After= + * in the target unit that this mount unit will be hooked into. */ + UNIT(m)->default_dependencies = false; + + if (!m->where) + if (!(m->where = unit_name_to_path(u->id))) + return -ENOMEM; + + path_kill_slashes(m->where); + + if (!UNIT(m)->description) + if ((r = unit_set_description(u, m->where)) < 0) + return r; + + if ((r = mount_add_device_links(m)) < 0) + return r; + + if ((r = mount_add_mount_links(m)) < 0) + return r; + + if ((r = mount_add_socket_links(m)) < 0) + return r; + + if ((r = mount_add_swap_links(m)) < 0) + return r; + + if ((r = mount_add_path_links(m)) < 0) + return r; + + if ((r = mount_add_automount_links(m)) < 0) + return r; + + if ((r = mount_add_fstab_links(m)) < 0) + return r; + + if (UNIT(m)->default_dependencies || m->from_etc_fstab) + if ((r = mount_add_default_dependencies(m)) < 0) + return r; + + if ((r = unit_add_default_cgroups(u)) < 0) + return r; + + mount_fix_timeouts(m); + } + + return mount_verify(m); +} + +static int mount_notify_automount(Mount *m, int status) { + Unit *p; + int r; + Iterator i; + + assert(m); + + SET_FOREACH(p, UNIT(m)->dependencies[UNIT_TRIGGERED_BY], i) + if (p->type == UNIT_AUTOMOUNT) { + r = automount_send_ready(AUTOMOUNT(p), status); + if (r < 0) + return r; + } + + return 0; +} + +static void mount_set_state(Mount *m, MountState state) { + MountState old_state; + assert(m); + + old_state = m->state; + m->state = state; + + if (state != MOUNT_MOUNTING && + state != MOUNT_MOUNTING_DONE && + state != MOUNT_REMOUNTING && + state != MOUNT_UNMOUNTING && + state != MOUNT_MOUNTING_SIGTERM && + state != MOUNT_MOUNTING_SIGKILL && + state != MOUNT_UNMOUNTING_SIGTERM && + state != MOUNT_UNMOUNTING_SIGKILL && + state != MOUNT_REMOUNTING_SIGTERM && + state != MOUNT_REMOUNTING_SIGKILL) { + unit_unwatch_timer(UNIT(m), &m->timer_watch); + mount_unwatch_control_pid(m); + m->control_command = NULL; + m->control_command_id = _MOUNT_EXEC_COMMAND_INVALID; + } + + if (state == MOUNT_MOUNTED || + state == MOUNT_REMOUNTING) + mount_notify_automount(m, 0); + else if (state == MOUNT_DEAD || + state == MOUNT_UNMOUNTING || + state == MOUNT_MOUNTING_SIGTERM || + state == MOUNT_MOUNTING_SIGKILL || + state == MOUNT_REMOUNTING_SIGTERM || + state == MOUNT_REMOUNTING_SIGKILL || + state == MOUNT_UNMOUNTING_SIGTERM || + state == MOUNT_UNMOUNTING_SIGKILL || + state == MOUNT_FAILED) + mount_notify_automount(m, -ENODEV); + + if (state != old_state) + log_debug("%s changed %s -> %s", + UNIT(m)->id, + 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) { + + if (new_state == MOUNT_MOUNTING || + new_state == MOUNT_MOUNTING_DONE || + new_state == MOUNT_REMOUNTING || + new_state == MOUNT_UNMOUNTING || + new_state == MOUNT_MOUNTING_SIGTERM || + new_state == MOUNT_MOUNTING_SIGKILL || + new_state == MOUNT_UNMOUNTING_SIGTERM || + new_state == MOUNT_UNMOUNTING_SIGKILL || + new_state == MOUNT_REMOUNTING_SIGTERM || + new_state == MOUNT_REMOUNTING_SIGKILL) { + + if (m->control_pid <= 0) + return -EBADMSG; + + if ((r = unit_watch_pid(UNIT(m), m->control_pid)) < 0) + return r; + + if ((r = unit_watch_timer(UNIT(m), m->timeout_usec, &m->timer_watch)) < 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 /etc/fstab: %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, strna(p->what), + prefix, strna(p->fstype), + prefix, strna(p->options), + prefix, yes_no(m->from_etc_fstab), + 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: %lu\n", + prefix, (unsigned long) m->control_pid); + + exec_context_dump(&m->exec_context, f, prefix); +} + +static int mount_spawn(Mount *m, ExecCommand *c, pid_t *_pid) { + pid_t pid; + int r; + + assert(m); + assert(c); + assert(_pid); + + if ((r = unit_watch_timer(UNIT(m), m->timeout_usec, &m->timer_watch)) < 0) + goto fail; + + if ((r = exec_spawn(c, + NULL, + &m->exec_context, + NULL, 0, + UNIT(m)->manager->environment, + true, + true, + true, + UNIT(m)->manager->confirm_spawn, + UNIT(m)->cgroup_bondings, + UNIT(m)->cgroup_attributes, + &pid)) < 0) + goto fail; + + if ((r = unit_watch_pid(UNIT(m), pid)) < 0) + /* FIXME: we need to do something here */ + goto fail; + + *_pid = pid; + + return 0; + +fail: + unit_unwatch_timer(UNIT(m), &m->timer_watch); + + return r; +} + +static void mount_enter_dead(Mount *m, MountResult f) { + assert(m); + + if (f != MOUNT_SUCCESS) + m->result = f; + + 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; + Set *pid_set = NULL; + bool wait_for_exit = false; + + assert(m); + + if (f != MOUNT_SUCCESS) + m->result = f; + + if (m->exec_context.kill_mode != KILL_NONE) { + int sig = (state == MOUNT_MOUNTING_SIGTERM || + state == MOUNT_UNMOUNTING_SIGTERM || + state == MOUNT_REMOUNTING_SIGTERM) ? m->exec_context.kill_signal : SIGKILL; + + if (m->control_pid > 0) { + if (kill_and_sigcont(m->control_pid, sig) < 0 && errno != ESRCH) + + log_warning("Failed to kill control process %li: %m", (long) m->control_pid); + else + wait_for_exit = true; + } + + if (m->exec_context.kill_mode == KILL_CONTROL_GROUP) { + + if (!(pid_set = set_new(trivial_hash_func, trivial_compare_func))) { + r = -ENOMEM; + goto fail; + } + + /* Exclude the control pid from being killed via the cgroup */ + if (m->control_pid > 0) + if ((r = set_put(pid_set, LONG_TO_PTR(m->control_pid))) < 0) + goto fail; + + if ((r = cgroup_bonding_kill_list(UNIT(m)->cgroup_bondings, sig, true, pid_set)) < 0) { + if (r != -EAGAIN && r != -ESRCH && r != -ENOENT) + log_warning("Failed to kill control group: %s", strerror(-r)); + } else if (r > 0) + wait_for_exit = true; + + set_free(pid_set); + pid_set = NULL; + } + } + + if (wait_for_exit) { + if ((r = unit_watch_timer(UNIT(m), m->timeout_usec, &m->timer_watch)) < 0) + goto fail; + + mount_set_state(m, state); + } else if (state == MOUNT_REMOUNTING_SIGTERM || state == MOUNT_REMOUNTING_SIGKILL) + mount_enter_mounted(m, MOUNT_SUCCESS); + else + mount_enter_dead(m, MOUNT_SUCCESS); + + return; + +fail: + log_warning("%s failed to kill processes: %s", UNIT(m)->id, strerror(-r)); + + if (state == MOUNT_REMOUNTING_SIGTERM || state == MOUNT_REMOUNTING_SIGKILL) + mount_enter_mounted(m, MOUNT_FAILURE_RESOURCES); + else + mount_enter_dead(m, MOUNT_FAILURE_RESOURCES); + + if (pid_set) + set_free(pid_set); +} + +static void mount_enter_unmounting(Mount *m) { + int r; + + assert(m); + + m->control_command_id = MOUNT_EXEC_UNMOUNT; + m->control_command = m->exec_command + MOUNT_EXEC_UNMOUNT; + + if ((r = exec_command_set( + m->control_command, + "/bin/umount", + m->where, + NULL)) < 0) + goto fail; + + mount_unwatch_control_pid(m); + + if ((r = mount_spawn(m, m->control_command, &m->control_pid)) < 0) + goto fail; + + mount_set_state(m, MOUNT_UNMOUNTING); + + return; + +fail: + log_warning("%s failed to run 'umount' task: %s", UNIT(m)->id, strerror(-r)); + mount_enter_mounted(m, MOUNT_FAILURE_RESOURCES); +} + +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; + + mkdir_p(m->where, m->directory_mode); + + /* Create the source directory for bind-mounts if needed */ + p = get_mount_parameters_configured(m); + if (p && mount_is_bind(p)) + mkdir_p(p->what, m->directory_mode); + + if (m->from_fragment) + r = exec_command_set( + m->control_command, + "/bin/mount", + m->parameters_fragment.what, + m->where, + "-t", m->parameters_fragment.fstype ? m->parameters_fragment.fstype : "auto", + m->parameters_fragment.options ? "-o" : NULL, m->parameters_fragment.options, + NULL); + else if (m->from_etc_fstab) + r = exec_command_set( + m->control_command, + "/bin/mount", + m->where, + NULL); + else + r = -ENOENT; + + if (r < 0) + goto fail; + + mount_unwatch_control_pid(m); + + if ((r = mount_spawn(m, m->control_command, &m->control_pid)) < 0) + goto fail; + + mount_set_state(m, MOUNT_MOUNTING); + + return; + +fail: + log_warning("%s failed to run 'mount' task: %s", UNIT(m)->id, strerror(-r)); + mount_enter_dead(m, MOUNT_FAILURE_RESOURCES); +} + +static void mount_enter_mounting_done(Mount *m) { + assert(m); + + mount_set_state(m, MOUNT_MOUNTING_DONE); +} + +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) { + char *buf = NULL; + const char *o; + + if (m->parameters_fragment.options) { + if (!(buf = strappend("remount,", m->parameters_fragment.options))) { + r = -ENOMEM; + goto fail; + } + + o = buf; + } else + o = "remount"; + + r = exec_command_set( + m->control_command, + "/bin/mount", + m->parameters_fragment.what, + m->where, + "-t", m->parameters_fragment.fstype ? m->parameters_fragment.fstype : "auto", + "-o", o, + NULL); + + free(buf); + } else if (m->from_etc_fstab) + r = exec_command_set( + m->control_command, + "/bin/mount", + m->where, + "-o", "remount", + NULL); + else + r = -ENOENT; + + if (r < 0) + goto fail; + + mount_unwatch_control_pid(m); + + if ((r = mount_spawn(m, m->control_command, &m->control_pid)) < 0) + goto fail; + + mount_set_state(m, MOUNT_REMOUNTING); + + return; + +fail: + log_warning("%s failed to run 'remount' task: %s", UNIT(m)->id, strerror(-r)); + m->reload_result = MOUNT_FAILURE_RESOURCES; + mount_enter_mounted(m, MOUNT_SUCCESS); +} + +static int mount_start(Unit *u) { + Mount *m = MOUNT(u); + + assert(m); + + /* We cannot fulfill this request right now, try again later + * please! */ + if (m->state == MOUNT_UNMOUNTING || + m->state == MOUNT_UNMOUNTING_SIGTERM || + m->state == MOUNT_UNMOUNTING_SIGKILL || + m->state == MOUNT_MOUNTING_SIGTERM || + m->state == MOUNT_MOUNTING_SIGKILL) + return -EAGAIN; + + /* Already on it! */ + if (m->state == MOUNT_MOUNTING) + return 0; + + assert(m->state == MOUNT_DEAD || m->state == MOUNT_FAILED); + + m->result = MOUNT_SUCCESS; + m->reload_result = MOUNT_SUCCESS; + + mount_enter_mounting(m); + return 0; +} + +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 0; +} + +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 0; +} + +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", "%lu", (unsigned long) 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_debug("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_debug("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_debug("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_debug("Failed to parse control-pid value %s", value); + else + m->control_pid = pid; + } else if (streq(key, "control-command")) { + MountExecCommand id; + + if ((id = mount_exec_command_from_string(value)) < 0) + log_debug("Failed to parse exec-command value %s", value); + else { + m->control_command_id = id; + m->control_command = m->exec_command + id; + } + + } else + log_debug("Unknown serialization key '%s'", key); + + return 0; +} + +static UnitActiveState mount_active_state(Unit *u) { + assert(u); + + return state_translation_table[MOUNT(u)->state]; +} + +static const char *mount_sub_state_to_string(Unit *u) { + assert(u); + + return mount_state_to_string(MOUNT(u)->state); +} + +static bool mount_check_gc(Unit *u) { + Mount *m = MOUNT(u); + + assert(m); + + return m->from_etc_fstab || 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)) + 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_full(f == MOUNT_SUCCESS ? LOG_DEBUG : LOG_NOTICE, + "%s mount process exited, code=%s status=%i", u->id, 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) + 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 void mount_timer_event(Unit *u, uint64_t elapsed, Watch *w) { + Mount *m = MOUNT(u); + + assert(m); + assert(elapsed == 1); + assert(w == &m->timer_watch); + + switch (m->state) { + + case MOUNT_MOUNTING: + case MOUNT_MOUNTING_DONE: + log_warning("%s mounting timed out. Stopping.", u->id); + mount_enter_signal(m, MOUNT_MOUNTING_SIGTERM, MOUNT_FAILURE_TIMEOUT); + break; + + case MOUNT_REMOUNTING: + log_warning("%s remounting timed out. Stopping.", u->id); + m->reload_result = MOUNT_FAILURE_TIMEOUT; + mount_enter_mounted(m, MOUNT_SUCCESS); + break; + + case MOUNT_UNMOUNTING: + log_warning("%s unmounting timed out. Stopping.", u->id); + mount_enter_signal(m, MOUNT_UNMOUNTING_SIGTERM, MOUNT_FAILURE_TIMEOUT); + break; + + case MOUNT_MOUNTING_SIGTERM: + if (m->exec_context.send_sigkill) { + log_warning("%s mounting timed out. Killing.", u->id); + mount_enter_signal(m, MOUNT_MOUNTING_SIGKILL, MOUNT_FAILURE_TIMEOUT); + } else { + log_warning("%s mounting timed out. Skipping SIGKILL. Ignoring.", u->id); + + 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->exec_context.send_sigkill) { + log_warning("%s remounting timed out. Killing.", u->id); + mount_enter_signal(m, MOUNT_REMOUNTING_SIGKILL, MOUNT_FAILURE_TIMEOUT); + } else { + log_warning("%s remounting timed out. Skipping SIGKILL. Ignoring.", u->id); + + 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->exec_context.send_sigkill) { + log_warning("%s unmounting timed out. Killing.", u->id); + mount_enter_signal(m, MOUNT_UNMOUNTING_SIGKILL, MOUNT_FAILURE_TIMEOUT); + } else { + log_warning("%s unmounting timed out. Skipping SIGKILL. Ignoring.", u->id); + + 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_warning("%s mount process still around after SIGKILL. Ignoring.", u->id); + + 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."); + } +} + +static int mount_add_one( + Manager *m, + const char *what, + const char *where, + const char *options, + const char *fstype, + int passno, + bool from_proc_self_mountinfo, + bool set_flags) { + int r; + Unit *u; + bool delete; + char *e, *w = NULL, *o = NULL, *f = NULL; + MountParameters *p; + + assert(m); + assert(what); + assert(where); + assert(options); + assert(fstype); + + assert(!set_flags || from_proc_self_mountinfo); + + /* 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; + + e = unit_name_from_path(where, ".mount"); + if (!e) + return -ENOMEM; + + u = manager_get_unit(m, e); + if (!u) { + delete = true; + + u = unit_new(m, sizeof(Mount)); + if (!u) { + free(e); + return -ENOMEM; + } + + r = unit_add_name(u, e); + free(e); + + if (r < 0) + goto fail; + + MOUNT(u)->where = strdup(where); + if (!MOUNT(u)->where) { + r = -ENOMEM; + goto fail; + } + + unit_add_to_load_queue(u); + } else { + delete = false; + free(e); + } + + if (!(w = strdup(what)) || + !(o = strdup(options)) || + !(f = strdup(fstype))) { + r = -ENOMEM; + goto fail; + } + + if (from_proc_self_mountinfo) { + p = &MOUNT(u)->parameters_proc_self_mountinfo; + + if (set_flags) { + MOUNT(u)->is_mounted = true; + MOUNT(u)->just_mounted = !MOUNT(u)->from_proc_self_mountinfo; + MOUNT(u)->just_changed = !streq_ptr(p->options, o); + } + + MOUNT(u)->from_proc_self_mountinfo = true; + } else { + p = &MOUNT(u)->parameters_etc_fstab; + MOUNT(u)->from_etc_fstab = true; + } + + free(p->what); + p->what = w; + + free(p->options); + p->options = o; + + free(p->fstype); + p->fstype = f; + + p->passno = passno; + + unit_add_to_dbus_queue(u); + + return 0; + +fail: + free(w); + free(o); + free(f); + + if (delete && u) + unit_free(u); + + return r; +} + +static int mount_find_pri(char *options) { + char *end, *pri; + unsigned long r; + + if (!(pri = mount_test_option(options, "pri"))) + return 0; + + pri += 4; + + errno = 0; + r = strtoul(pri, &end, 10); + + if (errno != 0) + return -errno; + + if (end == pri || (*end != ',' && *end != 0)) + return -EINVAL; + + return (int) r; +} + +static int mount_load_etc_fstab(Manager *m) { + FILE *f; + int r = 0; + struct mntent* me; + + assert(m); + + errno = 0; + if (!(f = setmntent("/etc/fstab", "r"))) + return -errno; + + while ((me = getmntent(f))) { + char *where, *what; + int k; + + if (!(what = fstab_node_to_udev_node(me->mnt_fsname))) { + r = -ENOMEM; + goto finish; + } + + if (!(where = strdup(me->mnt_dir))) { + free(what); + r = -ENOMEM; + goto finish; + } + + if (what[0] == '/') + path_kill_slashes(what); + + if (where[0] == '/') + path_kill_slashes(where); + + if (streq(me->mnt_type, "swap")) { + int pri; + + if ((pri = mount_find_pri(me->mnt_opts)) < 0) + k = pri; + else + k = swap_add_one(m, + what, + NULL, + pri, + !!mount_test_option(me->mnt_opts, "noauto"), + !!mount_test_option(me->mnt_opts, "nofail"), + !!mount_test_option(me->mnt_opts, "comment=systemd.swapon"), + false); + } else + k = mount_add_one(m, what, where, me->mnt_opts, me->mnt_type, me->mnt_passno, false, false); + + free(what); + free(where); + + if (k < 0) + r = k; + } + +finish: + + endmntent(f); + return r; +} + +static int mount_load_proc_self_mountinfo(Manager *m, bool set_flags) { + int r = 0; + unsigned i; + char *device, *path, *options, *options2, *fstype, *d, *p, *o; + + assert(m); + + rewind(m->proc_self_mountinfo); + + for (i = 1;; i++) { + int k; + + device = path = options = options2 = fstype = d = p = o = NULL; + + if ((k = fscanf(m->proc_self_mountinfo, + "%*s " /* (1) mount id */ + "%*s " /* (2) parent id */ + "%*s " /* (3) major:minor */ + "%*s " /* (4) root */ + "%ms " /* (5) mount point */ + "%ms" /* (6) mount options */ + "%*[^-]" /* (7) optional fields */ + "- " /* (8) separator */ + "%ms " /* (9) file system type */ + "%ms" /* (10) mount source */ + "%ms" /* (11) mount options 2 */ + "%*[^\n]", /* some rubbish at the end */ + &path, + &options, + &fstype, + &device, + &options2)) != 5) { + + if (k == EOF) + break; + + log_warning("Failed to parse /proc/self/mountinfo:%u.", i); + goto clean_up; + } + + if (asprintf(&o, "%s,%s", options, options2) < 0) { + r = -ENOMEM; + goto finish; + } + + if (!(d = cunescape(device)) || + !(p = cunescape(path))) { + r = -ENOMEM; + goto finish; + } + + if ((k = mount_add_one(m, d, p, o, fstype, 0, true, set_flags)) < 0) + r = k; + +clean_up: + free(device); + free(path); + free(options); + free(options2); + free(fstype); + free(d); + free(p); + free(o); + } + +finish: + free(device); + free(path); + free(options); + free(options2); + free(fstype); + free(d); + free(p); + free(o); + + return r; +} + +static void mount_shutdown(Manager *m) { + assert(m); + + if (m->proc_self_mountinfo) { + fclose(m->proc_self_mountinfo); + m->proc_self_mountinfo = NULL; + } +} + +static int mount_enumerate(Manager *m) { + int r; + struct epoll_event ev; + assert(m); + + if (!m->proc_self_mountinfo) { + if (!(m->proc_self_mountinfo = fopen("/proc/self/mountinfo", "re"))) + return -errno; + + m->mount_watch.type = WATCH_MOUNT; + m->mount_watch.fd = fileno(m->proc_self_mountinfo); + + zero(ev); + ev.events = EPOLLPRI; + ev.data.ptr = &m->mount_watch; + + if (epoll_ctl(m->epoll_fd, EPOLL_CTL_ADD, m->mount_watch.fd, &ev) < 0) + return -errno; + } + + if ((r = mount_load_etc_fstab(m)) < 0) + goto fail; + + if ((r = mount_load_proc_self_mountinfo(m, false)) < 0) + goto fail; + + return 0; + +fail: + mount_shutdown(m); + return r; +} + +void mount_fd_event(Manager *m, int events) { + Unit *u; + int r; + + assert(m); + assert(events & EPOLLPRI); + + /* The manager calls this for every fd event happening on the + * /proc/self/mountinfo file, which informs us about mounting + * table changes */ + + if ((r = mount_load_proc_self_mountinfo(m, true)) < 0) { + log_error("Failed to reread /proc/self/mountinfo: %s", strerror(-r)); + + /* 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; + } + + 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) { + /* This has just been unmounted. */ + + mount->from_proc_self_mountinfo = false; + + switch (mount->state) { + + case MOUNT_MOUNTED: + mount_enter_dead(mount, MOUNT_SUCCESS); + break; + + default: + mount_set_state(mount, mount->state); + break; + + } + + } else if (mount->just_mounted || mount->just_changed) { + + /* New or changed mount entry */ + + switch (mount->state) { + + case MOUNT_DEAD: + case MOUNT_FAILED: + mount_enter_mounted(mount, MOUNT_SUCCESS); + break; + + case MOUNT_MOUNTING: + mount_enter_mounting_done(mount); + 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; + } + } + + /* Reset the flags for later calls */ + mount->is_mounted = mount->just_mounted = mount->just_changed = false; + } +} + +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, KillMode mode, int signo, DBusError *error) { + Mount *m = MOUNT(u); + int r = 0; + Set *pid_set = NULL; + + assert(m); + + if (who == KILL_MAIN) { + dbus_set_error(error, BUS_ERROR_NO_SUCH_PROCESS, "Mount units have no main processes"); + return -ESRCH; + } + + if (m->control_pid <= 0 && who == KILL_CONTROL) { + dbus_set_error(error, BUS_ERROR_NO_SUCH_PROCESS, "No control process to kill"); + return -ESRCH; + } + + if (who == KILL_CONTROL || who == KILL_ALL) + if (m->control_pid > 0) + if (kill(m->control_pid, signo) < 0) + r = -errno; + + if (who == KILL_ALL && mode == KILL_CONTROL_GROUP) { + int q; + + if (!(pid_set = set_new(trivial_hash_func, trivial_compare_func))) + return -ENOMEM; + + /* Exclude the control pid from being killed via the cgroup */ + if (m->control_pid > 0) + if ((q = set_put(pid_set, LONG_TO_PTR(m->control_pid))) < 0) { + r = q; + goto finish; + } + + if ((q = cgroup_bonding_kill_list(UNIT(m)->cgroup_bondings, signo, false, pid_set)) < 0) + if (q != -EAGAIN && q != -ESRCH && q != -ENOENT) + r = q; + } + +finish: + if (pid_set) + set_free(pid_set); + + return r; +} + +static const char* const mount_state_table[_MOUNT_STATE_MAX] = { + [MOUNT_DEAD] = "dead", + [MOUNT_MOUNTING] = "mounting", + [MOUNT_MOUNTING_DONE] = "mounting-done", + [MOUNT_MOUNTED] = "mounted", + [MOUNT_REMOUNTING] = "remounting", + [MOUNT_UNMOUNTING] = "unmounting", + [MOUNT_MOUNTING_SIGTERM] = "mounting-sigterm", + [MOUNT_MOUNTING_SIGKILL] = "mounting-sigkill", + [MOUNT_REMOUNTING_SIGTERM] = "remounting-sigterm", + [MOUNT_REMOUNTING_SIGKILL] = "remounting-sigkill", + [MOUNT_UNMOUNTING_SIGTERM] = "unmounting-sigterm", + [MOUNT_UNMOUNTING_SIGKILL] = "unmounting-sigkill", + [MOUNT_FAILED] = "failed" +}; + +DEFINE_STRING_TABLE_LOOKUP(mount_state, MountState); + +static const char* const mount_exec_command_table[_MOUNT_EXEC_COMMAND_MAX] = { + [MOUNT_EXEC_MOUNT] = "ExecMount", + [MOUNT_EXEC_UNMOUNT] = "ExecUnmount", + [MOUNT_EXEC_REMOUNT] = "ExecRemount", +}; + +DEFINE_STRING_TABLE_LOOKUP(mount_exec_command, MountExecCommand); + +static const char* const mount_result_table[_MOUNT_RESULT_MAX] = { + [MOUNT_SUCCESS] = "success", + [MOUNT_FAILURE_RESOURCES] = "resources", + [MOUNT_FAILURE_TIMEOUT] = "timeout", + [MOUNT_FAILURE_EXIT_CODE] = "exit-code", + [MOUNT_FAILURE_SIGNAL] = "signal", + [MOUNT_FAILURE_CORE_DUMP] = "core-dump" +}; + +DEFINE_STRING_TABLE_LOOKUP(mount_result, MountResult); + +const UnitVTable mount_vtable = { + .suffix = ".mount", + .object_size = sizeof(Mount), + .sections = + "Unit\0" + "Mount\0" + "Install\0", + + .no_alias = true, + .no_instances = true, + .show_status = true, + + .init = mount_init, + .load = mount_load, + .done = mount_done, + + .coldplug = mount_coldplug, + + .dump = mount_dump, + + .start = mount_start, + .stop = mount_stop, + .reload = mount_reload, + + .kill = mount_kill, + + .serialize = mount_serialize, + .deserialize_item = mount_deserialize_item, + + .active_state = mount_active_state, + .sub_state_to_string = mount_sub_state_to_string, + + .check_gc = mount_check_gc, + + .sigchld_event = mount_sigchld_event, + .timer_event = mount_timer_event, + + .reset_failed = mount_reset_failed, + + .bus_interface = "org.freedesktop.systemd1.Mount", + .bus_message_handler = bus_mount_message_handler, + .bus_invalidating_properties = bus_mount_invalidating_properties, + + .enumerate = mount_enumerate, + .shutdown = mount_shutdown +}; diff --git a/src/core/mount.h b/src/core/mount.h new file mode 100644 index 0000000000..9318444249 --- /dev/null +++ b/src/core/mount.h @@ -0,0 +1,124 @@ +/*-*- Mode: C; c-basic-offset: 8; indent-tabs-mode: nil -*-*/ + +#ifndef foomounthfoo +#define foomounthfoo + +/*** + This file is part of systemd. + + Copyright 2010 Lennart Poettering + + systemd is free software; you can redistribute it and/or modify it + under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + systemd is distributed in the hope that 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 General Public License + along with systemd; If not, see . +***/ + +typedef struct Mount Mount; + +#include "unit.h" + +typedef enum MountState { + MOUNT_DEAD, + MOUNT_MOUNTING, /* /bin/mount is running, but the mount is not done yet. */ + MOUNT_MOUNTING_DONE, /* /bin/mount is running, and the mount is done. */ + MOUNT_MOUNTED, + MOUNT_REMOUNTING, + MOUNT_UNMOUNTING, + MOUNT_MOUNTING_SIGTERM, + MOUNT_MOUNTING_SIGKILL, + MOUNT_REMOUNTING_SIGTERM, + MOUNT_REMOUNTING_SIGKILL, + MOUNT_UNMOUNTING_SIGTERM, + MOUNT_UNMOUNTING_SIGKILL, + MOUNT_FAILED, + _MOUNT_STATE_MAX, + _MOUNT_STATE_INVALID = -1 +} MountState; + +typedef enum MountExecCommand { + MOUNT_EXEC_MOUNT, + MOUNT_EXEC_UNMOUNT, + MOUNT_EXEC_REMOUNT, + _MOUNT_EXEC_COMMAND_MAX, + _MOUNT_EXEC_COMMAND_INVALID = -1 +} MountExecCommand; + +typedef struct MountParameters { + char *what; + char *options; + char *fstype; + int passno; +} MountParameters; + +typedef enum MountResult { + MOUNT_SUCCESS, + MOUNT_FAILURE_RESOURCES, + MOUNT_FAILURE_TIMEOUT, + MOUNT_FAILURE_EXIT_CODE, + MOUNT_FAILURE_SIGNAL, + MOUNT_FAILURE_CORE_DUMP, + _MOUNT_RESULT_MAX, + _MOUNT_RESULT_INVALID = -1 +} MountResult; + +struct Mount { + Unit meta; + + char *where; + + MountParameters parameters_etc_fstab; + MountParameters parameters_proc_self_mountinfo; + MountParameters parameters_fragment; + + bool from_etc_fstab:1; + 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; + + MountResult result; + MountResult reload_result; + + mode_t directory_mode; + + usec_t timeout_usec; + + ExecCommand exec_command[_MOUNT_EXEC_COMMAND_MAX]; + ExecContext exec_context; + + MountState state, deserialized_state; + + ExecCommand* control_command; + MountExecCommand control_command_id; + pid_t control_pid; + + Watch timer_watch; +}; + +extern const UnitVTable mount_vtable; + +void mount_fd_event(Manager *m, int events); + +const char* mount_state_to_string(MountState i); +MountState mount_state_from_string(const char *s); + +const char* mount_exec_command_to_string(MountExecCommand i); +MountExecCommand mount_exec_command_from_string(const char *s); + +const char* mount_result_to_string(MountResult i); +MountResult mount_result_from_string(const char *s); + +#endif diff --git a/src/core/namespace.c b/src/core/namespace.c new file mode 100644 index 0000000000..09bc82909f --- /dev/null +++ b/src/core/namespace.c @@ -0,0 +1,346 @@ +/*-*- Mode: C; c-basic-offset: 8; indent-tabs-mode: nil -*-*/ + +/*** + This file is part of systemd. + + Copyright 2010 Lennart Poettering + + systemd is free software; you can redistribute it and/or modify it + under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + systemd is distributed in the hope that 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 General Public License + along with systemd; If not, see . +***/ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "strv.h" +#include "util.h" +#include "namespace.h" +#include "missing.h" + +typedef enum PathMode { + /* This is ordered by priority! */ + INACCESSIBLE, + READONLY, + PRIVATE, + READWRITE +} PathMode; + +typedef struct Path { + const char *path; + PathMode mode; +} Path; + +static int append_paths(Path **p, char **strv, PathMode mode) { + char **i; + + STRV_FOREACH(i, strv) { + + if (!path_is_absolute(*i)) + return -EINVAL; + + (*p)->path = *i; + (*p)->mode = mode; + (*p)++; + } + + return 0; +} + +static int path_compare(const void *a, const void *b) { + const Path *p = a, *q = b; + + if (path_equal(p->path, q->path)) { + + /* 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 */ + if (path_startswith(p->path, q->path)) + return 1; + + if (path_startswith(q->path, p->path)) + return -1; + + return 0; +} + +static void drop_duplicates(Path *p, unsigned *n, bool *need_inaccessible, bool *need_private) { + Path *f, *t, *previous; + + assert(p); + assert(n); + assert(need_inaccessible); + assert(need_private); + + for (f = p, t = p, previous = NULL; f < p+*n; f++) { + + if (previous && path_equal(f->path, previous->path)) + continue; + + t->path = f->path; + t->mode = f->mode; + + if (t->mode == PRIVATE) + *need_private = true; + + if (t->mode == INACCESSIBLE) + *need_inaccessible = true; + + previous = t; + + t++; + } + + *n = t - p; +} + +static int apply_mount(Path *p, const char *root_dir, const char *inaccessible_dir, const char *private_dir, unsigned long flags) { + const char *what; + char *where; + int r; + + assert(p); + assert(root_dir); + assert(inaccessible_dir); + assert(private_dir); + + if (!(where = strappend(root_dir, p->path))) + return -ENOMEM; + + switch (p->mode) { + + case INACCESSIBLE: + what = inaccessible_dir; + flags |= MS_RDONLY; + break; + + case READONLY: + flags |= MS_RDONLY; + /* Fall through */ + + case READWRITE: + what = p->path; + break; + + case PRIVATE: + what = private_dir; + break; + + default: + assert_not_reached("Unknown mode"); + } + + if ((r = mount(what, where, NULL, MS_BIND|MS_REC, NULL)) >= 0) { + log_debug("Successfully mounted %s to %s", what, where); + + /* The bind mount will always inherit the original + * flags. If we want to set any flag we need + * to do so in a second independent step. */ + if (flags) + r = mount(NULL, where, NULL, MS_REMOUNT|MS_BIND|MS_REC|flags, NULL); + + /* Avoid exponential growth of trees */ + if (r >= 0 && path_equal(p->path, "/")) + r = mount(NULL, where, NULL, MS_REMOUNT|MS_BIND|MS_UNBINDABLE|flags, NULL); + + if (r < 0) { + r = -errno; + umount2(where, MNT_DETACH); + } + } + + free(where); + return r; +} + +int setup_namespace( + char **writable, + char **readable, + char **inaccessible, + bool private_tmp, + unsigned long flags) { + + char + tmp_dir[] = "/tmp/systemd-namespace-XXXXXX", + root_dir[] = "/tmp/systemd-namespace-XXXXXX/root", + old_root_dir[] = "/tmp/systemd-namespace-XXXXXX/root/tmp/old-root-XXXXXX", + inaccessible_dir[] = "/tmp/systemd-namespace-XXXXXX/inaccessible", + private_dir[] = "/tmp/systemd-namespace-XXXXXX/private"; + + Path *paths, *p; + unsigned n; + bool need_private = false, need_inaccessible = false; + bool remove_tmp = false, remove_root = false, remove_old_root = false, remove_inaccessible = false, remove_private = false; + int r; + const char *t; + + n = + strv_length(writable) + + strv_length(readable) + + strv_length(inaccessible) + + (private_tmp ? 2 : 1); + + if (!(paths = new(Path, n))) + return -ENOMEM; + + p = paths; + if ((r = append_paths(&p, writable, READWRITE)) < 0 || + (r = append_paths(&p, readable, READONLY)) < 0 || + (r = append_paths(&p, inaccessible, INACCESSIBLE)) < 0) + goto fail; + + if (private_tmp) { + p->path = "/tmp"; + p->mode = PRIVATE; + p++; + } + + p->path = "/"; + p->mode = READWRITE; + p++; + + assert(paths + n == p); + + qsort(paths, n, sizeof(Path), path_compare); + drop_duplicates(paths, &n, &need_inaccessible, &need_private); + + if (!mkdtemp(tmp_dir)) { + r = -errno; + goto fail; + } + remove_tmp = true; + + memcpy(root_dir, tmp_dir, sizeof(tmp_dir)-1); + if (mkdir(root_dir, 0777) < 0) { + r = -errno; + goto fail; + } + remove_root = true; + + if (need_inaccessible) { + memcpy(inaccessible_dir, tmp_dir, sizeof(tmp_dir)-1); + if (mkdir(inaccessible_dir, 0) < 0) { + r = -errno; + goto fail; + } + remove_inaccessible = true; + } + + if (need_private) { + mode_t u; + + memcpy(private_dir, tmp_dir, sizeof(tmp_dir)-1); + + u = umask(0000); + if (mkdir(private_dir, 0777 + S_ISVTX) < 0) { + umask(u); + + r = -errno; + goto fail; + } + + umask(u); + remove_private = true; + } + + if (unshare(CLONE_NEWNS) < 0) { + r = -errno; + goto fail; + } + + /* Remount / as SLAVE so that nothing mounted in the namespace + shows up in the parent */ + if (mount(NULL, "/", NULL, MS_SLAVE|MS_REC, NULL) < 0) { + r = -errno; + goto fail; + } + + for (p = paths; p < paths + n; p++) + if ((r = apply_mount(p, root_dir, inaccessible_dir, private_dir, flags)) < 0) + goto undo_mounts; + + memcpy(old_root_dir, tmp_dir, sizeof(tmp_dir)-1); + if (!mkdtemp(old_root_dir)) { + r = -errno; + goto undo_mounts; + } + remove_old_root = true; + + if (chdir(root_dir) < 0) { + r = -errno; + goto undo_mounts; + } + + if (pivot_root(root_dir, old_root_dir) < 0) { + r = -errno; + goto undo_mounts; + } + + t = old_root_dir + sizeof(root_dir) - 1; + if (umount2(t, MNT_DETACH) < 0) + /* At this point it's too late to turn anything back, + * since we are already in the new root. */ + return -errno; + + if (rmdir(t) < 0) + return -errno; + + return 0; + +undo_mounts: + + for (p--; p >= paths; p--) { + char full_path[PATH_MAX]; + + snprintf(full_path, sizeof(full_path), "%s%s", root_dir, p->path); + char_array_0(full_path); + + umount2(full_path, MNT_DETACH); + } + +fail: + if (remove_old_root) + rmdir(old_root_dir); + + if (remove_inaccessible) + rmdir(inaccessible_dir); + + if (remove_private) + rmdir(private_dir); + + if (remove_root) + rmdir(root_dir); + + if (remove_tmp) + rmdir(tmp_dir); + + free(paths); + + return r; +} diff --git a/src/core/namespace.h b/src/core/namespace.h new file mode 100644 index 0000000000..7cf1dedd32 --- /dev/null +++ b/src/core/namespace.h @@ -0,0 +1,34 @@ +/*-*- Mode: C; c-basic-offset: 8; indent-tabs-mode: nil -*-*/ + +#ifndef foonamespacehfoo +#define foonamespacehfoo + +/*** + This file is part of systemd. + + Copyright 2010 Lennart Poettering + + systemd is free software; you can redistribute it and/or modify it + under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + systemd is distributed in the hope that 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 General Public License + along with systemd; If not, see . +***/ + +#include + +int setup_namespace( + char **writable, + char **readable, + char **inaccessible, + bool private_tmp, + unsigned long flags); + +#endif diff --git a/src/core/path.c b/src/core/path.c new file mode 100644 index 0000000000..1d50885ed4 --- /dev/null +++ b/src/core/path.c @@ -0,0 +1,771 @@ +/*-*- Mode: C; c-basic-offset: 8; indent-tabs-mode: nil -*-*/ + +/*** + This file is part of systemd. + + Copyright 2010 Lennart Poettering + + systemd is free software; you can redistribute it and/or modify it + under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + systemd is distributed in the hope that 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 General Public License + along with systemd; If not, see . +***/ + +#include +#include +#include +#include +#include + +#include "unit.h" +#include "unit-name.h" +#include "path.h" +#include "mkdir.h" +#include "dbus-path.h" +#include "special.h" +#include "bus-errors.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 +}; + +int path_spec_watch(PathSpec *s, Unit *u) { + + 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 *k, *slash; + int r; + + assert(u); + assert(s); + + path_spec_unwatch(s, u); + + if (!(k = strdup(s->path))) + return -ENOMEM; + + if ((s->inotify_fd = inotify_init1(IN_NONBLOCK|IN_CLOEXEC)) < 0) { + r = -errno; + goto fail; + } + + if (unit_watch_fd(u, s->inotify_fd, EPOLLIN, &s->watch) < 0) { + r = -errno; + goto fail; + } + + if ((s->primary_wd = inotify_add_watch(s->inotify_fd, k, flags_table[s->type])) >= 0) + exists = true; + + do { + int flags; + + /* This assumes the path was passed through path_kill_slashes()! */ + if (!(slash = strrchr(k, '/'))) + break; + + /* Trim the path at the last slash. Keep the slash if it's the root dir. */ + slash[slash == k] = 0; + + flags = IN_MOVE_SELF; + if (!exists) + flags |= IN_DELETE_SELF | IN_ATTRIB | IN_CREATE | IN_MOVED_TO; + + if (inotify_add_watch(s->inotify_fd, k, flags) >= 0) + exists = true; + } while (slash != k); + + return 0; + +fail: + free(k); + + path_spec_unwatch(s, u); + return r; +} + +void path_spec_unwatch(PathSpec *s, Unit *u) { + + if (s->inotify_fd < 0) + return; + + unit_unwatch_fd(u, &s->watch); + + close_nointr_nofail(s->inotify_fd); + s->inotify_fd = -1; +} + +int path_spec_fd_event(PathSpec *s, uint32_t events) { + uint8_t *buf = NULL; + struct inotify_event *e; + ssize_t k; + int l; + int r = 0; + + if (events != EPOLLIN) { + log_error("Got Invalid poll event on inotify."); + r = -EINVAL; + goto out; + } + + if (ioctl(s->inotify_fd, FIONREAD, &l) < 0) { + log_error("FIONREAD failed: %m"); + r = -errno; + goto out; + } + + assert(l > 0); + + if (!(buf = malloc(l))) { + log_error("Failed to allocate buffer: %m"); + r = -errno; + goto out; + } + + if ((k = read(s->inotify_fd, buf, l)) < 0) { + log_error("Failed to read inotify event: %m"); + r = -errno; + goto out; + } + + e = (struct inotify_event*) buf; + + while (k > 0) { + size_t step; + + if ((s->type == PATH_CHANGED || s->type == PATH_MODIFIED) && + s->primary_wd == e->wd) + r = 1; + + step = sizeof(struct inotify_event) + e->len; + assert(step <= (size_t) k); + + e = (struct inotify_event*) ((uint8_t*) e + step); + k -= step; + } +out: + free(buf); + 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 bool path_spec_startswith(PathSpec *s, const char *what) { + return path_startswith(s->path, what); +} + +static void path_spec_mkdir(PathSpec *s, mode_t mode) { + int r; + + if (s->type == PATH_EXISTS || s->type == PATH_EXISTS_GLOB) + return; + + if ((r = mkdir_p(s->path, mode)) < 0) + log_warning("mkdir(%s) failed: %s", s->path, strerror(-r)); +} + +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; +} + +static void path_done(Unit *u) { + Path *p = PATH(u); + PathSpec *s; + + assert(p); + + unit_ref_unset(&p->unit); + + while ((s = p->specs)) { + path_spec_unwatch(s, u); + LIST_REMOVE(PathSpec, spec, p->specs, s); + path_spec_done(s); + free(s); + } +} + +int path_add_one_mount_link(Path *p, Mount *m) { + PathSpec *s; + int r; + + assert(p); + assert(m); + + if (UNIT(p)->load_state != UNIT_LOADED || + UNIT(m)->load_state != UNIT_LOADED) + return 0; + + LIST_FOREACH(spec, s, p->specs) { + + if (!path_spec_startswith(s, m->where)) + continue; + + if ((r = unit_add_two_dependencies(UNIT(p), UNIT_AFTER, UNIT_REQUIRES, UNIT(m), true)) < 0) + return r; + } + + return 0; +} + +static int path_add_mount_links(Path *p) { + Unit *other; + int r; + + assert(p); + + LIST_FOREACH(units_by_type, other, UNIT(p)->manager->units_by_type[UNIT_MOUNT]) + if ((r = path_add_one_mount_link(p, MOUNT(other))) < 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_error("%s lacks path setting. Refusing.", UNIT(p)->id); + return -EINVAL; + } + + return 0; +} + +static int path_add_default_dependencies(Path *p) { + int r; + + assert(p); + + if (UNIT(p)->manager->running_as == MANAGER_SYSTEM) { + if ((r = unit_add_dependency_by_name(UNIT(p), UNIT_BEFORE, SPECIAL_BASIC_TARGET, NULL, true)) < 0) + return r; + + if ((r = unit_add_two_dependencies_by_name(UNIT(p), UNIT_AFTER, UNIT_REQUIRES, SPECIAL_SYSINIT_TARGET, NULL, true)) < 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); + + if ((r = unit_load_fragment_and_dropin(u)) < 0) + return r; + + if (u->load_state == UNIT_LOADED) { + + if (!UNIT_DEREF(p->unit)) { + Unit *x; + + r = unit_load_related_unit(u, ".service", &x); + if (r < 0) + return r; + + unit_ref_set(&p->unit, x); + } + + r = unit_add_two_dependencies(u, UNIT_BEFORE, UNIT_TRIGGERS, UNIT_DEREF(p->unit), true); + if (r < 0) + return r; + + if ((r = path_add_mount_links(p)) < 0) + return r; + + if (UNIT(p)->default_dependencies) + if ((r = path_add_default_dependencies(p)) < 0) + return r; + } + + return path_verify(p); +} + +static void path_dump(Unit *u, FILE *f, const char *prefix) { + Path *p = PATH(u); + PathSpec *s; + + assert(p); + assert(f); + + 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, UNIT_DEREF(p->unit)->id, + 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, UNIT(p)); +} + +static int path_watch(Path *p) { + int r; + PathSpec *s; + + assert(p); + + LIST_FOREACH(spec, s, p->specs) + if ((r = path_spec_watch(s, UNIT(p))) < 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_debug("%s changed %s -> %s", + UNIT(p)->id, + 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) { + int r; + DBusError error; + + assert(p); + dbus_error_init(&error); + + /* Don't start job if we are supposed to go down */ + if (UNIT(p)->job && UNIT(p)->job->type == JOB_STOP) + return; + + if ((r = manager_add_job(UNIT(p)->manager, JOB_START, UNIT_DEREF(p->unit), JOB_REPLACE, true, &error, NULL)) < 0) + goto fail; + + p->inotify_triggered = false; + + if ((r = path_watch(p)) < 0) + goto fail; + + path_set_state(p, PATH_RUNNING); + return; + +fail: + log_warning("%s failed to queue unit startup job: %s", UNIT(p)->id, bus_error(&error, r)); + path_enter_dead(p, PATH_FAILURE_RESOURCES); + + dbus_error_free(&error); +} + +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_debug("%s got triggered.", UNIT(p)->id); + path_enter_running(p); + return; + } + + if ((r = path_watch(p)) < 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_debug("%s got triggered.", UNIT(p)->id); + path_enter_running(p); + return; + } + + path_set_state(p, PATH_WAITING); + return; + +fail: + log_warning("%s failed to enter waiting state: %s", UNIT(p)->id, strerror(-r)); + 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); + + assert(p); + assert(p->state == PATH_DEAD || p->state == PATH_FAILED); + + if (UNIT_DEREF(p->unit)->load_state != UNIT_LOADED) + return -ENOENT; + + path_mkdir(p); + + p->result = PATH_SUCCESS; + path_enter_waiting(p, true, true); + + return 0; +} + +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 0; +} + +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; + + if ((state = path_state_from_string(value)) < 0) + log_debug("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_debug("Failed to parse result value %s", value); + else if (f != PATH_SUCCESS) + p->result = f; + + } else + log_debug("Unknown serialization key '%s'", key); + + return 0; +} + +static UnitActiveState path_active_state(Unit *u) { + assert(u); + + return state_translation_table[PATH(u)->state]; +} + +static const char *path_sub_state_to_string(Unit *u) { + assert(u); + + return path_state_to_string(PATH(u)->state); +} + +static void path_fd_event(Unit *u, int fd, uint32_t events, Watch *w) { + Path *p = PATH(u); + PathSpec *s; + int changed; + + assert(p); + assert(fd >= 0); + + if (p->state != PATH_WAITING && + p->state != PATH_RUNNING) + return; + + /* 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, events); + 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; + +fail: + path_enter_dead(p, PATH_FAILURE_RESOURCES); +} + +void path_unit_notify(Unit *u, UnitActiveState new_state) { + Iterator i; + Unit *k; + + if (u->type == UNIT_PATH) + return; + + SET_FOREACH(k, u->dependencies[UNIT_TRIGGERED_BY], i) { + Path *p; + + if (k->type != UNIT_PATH) + continue; + + if (k->load_state != UNIT_LOADED) + continue; + + p = PATH(k); + + if (p->state == PATH_RUNNING && new_state == UNIT_INACTIVE) { + log_debug("%s got notified about unit deactivation.", UNIT(p)->id); + + /* 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_state_table[_PATH_STATE_MAX] = { + [PATH_DEAD] = "dead", + [PATH_WAITING] = "waiting", + [PATH_RUNNING] = "running", + [PATH_FAILED] = "failed" +}; + +DEFINE_STRING_TABLE_LOOKUP(path_state, PathState); + +static const char* const path_type_table[_PATH_TYPE_MAX] = { + [PATH_EXISTS] = "PathExists", + [PATH_EXISTS_GLOB] = "PathExistsGlob", + [PATH_CHANGED] = "PathChanged", + [PATH_MODIFIED] = "PathModified", + [PATH_DIRECTORY_NOT_EMPTY] = "DirectoryNotEmpty" +}; + +DEFINE_STRING_TABLE_LOOKUP(path_type, PathType); + +static const char* const path_result_table[_PATH_RESULT_MAX] = { + [PATH_SUCCESS] = "success", + [PATH_FAILURE_RESOURCES] = "resources" +}; + +DEFINE_STRING_TABLE_LOOKUP(path_result, PathResult); + +const UnitVTable path_vtable = { + .suffix = ".path", + .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, + + .fd_event = path_fd_event, + + .reset_failed = path_reset_failed, + + .bus_interface = "org.freedesktop.systemd1.Path", + .bus_message_handler = bus_path_message_handler, + .bus_invalidating_properties = bus_path_invalidating_properties +}; diff --git a/src/core/path.h b/src/core/path.h new file mode 100644 index 0000000000..efb6b5eb44 --- /dev/null +++ b/src/core/path.h @@ -0,0 +1,113 @@ +/*-*- Mode: C; c-basic-offset: 8; indent-tabs-mode: nil -*-*/ + +#ifndef foopathhfoo +#define foopathhfoo + +/*** + This file is part of systemd. + + Copyright 2010 Lennart Poettering + + systemd is free software; you can redistribute it and/or modify it + under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + systemd is distributed in the hope that 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 General Public License + along with systemd; If not, see . +***/ + +typedef struct Path Path; + +#include "unit.h" +#include "mount.h" + +typedef enum PathState { + PATH_DEAD, + PATH_WAITING, + PATH_RUNNING, + PATH_FAILED, + _PATH_STATE_MAX, + _PATH_STATE_INVALID = -1 +} PathState; + +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 { + char *path; + + Watch watch; + + LIST_FIELDS(struct PathSpec, spec); + + PathType type; + int inotify_fd; + int primary_wd; + + bool previous_exists; +} PathSpec; + +int path_spec_watch(PathSpec *s, Unit *u); +void path_spec_unwatch(PathSpec *s, Unit *u); +int path_spec_fd_event(PathSpec *s, uint32_t events); +void path_spec_done(PathSpec *s); + +static inline bool path_spec_owns_inotify_fd(PathSpec *s, int fd) { + return s->inotify_fd == fd; +} + +typedef enum PathResult { + PATH_SUCCESS, + PATH_FAILURE_RESOURCES, + _PATH_RESULT_MAX, + _PATH_RESULT_INVALID = -1 +} PathResult; + +struct Path { + Unit meta; + + LIST_HEAD(PathSpec, specs); + + UnitRef unit; + + PathState state, deserialized_state; + + bool inotify_triggered; + + bool make_directory; + mode_t directory_mode; + + PathResult result; +}; + +void path_unit_notify(Unit *u, UnitActiveState new_state); + +/* Called from the mount code figure out if a mount is a dependency of + * any of the paths of this path object */ +int path_add_one_mount_link(Path *p, Mount *m); + +extern const UnitVTable path_vtable; + +const char* path_state_to_string(PathState i); +PathState path_state_from_string(const char *s); + +const char* path_type_to_string(PathType i); +PathType path_type_from_string(const char *s); + +const char* path_result_to_string(PathResult i); +PathResult path_result_from_string(const char *s); + +#endif diff --git a/src/core/polkit.h b/src/core/polkit.h new file mode 100644 index 0000000000..0d08194b26 --- /dev/null +++ b/src/core/polkit.h @@ -0,0 +1,36 @@ +/*-*- Mode: C; c-basic-offset: 8; indent-tabs-mode: nil -*-*/ + +#ifndef foopolkithfoo +#define foopolkithfoo + +/*** + This file is part of systemd. + + Copyright 2010 Lennart Poettering + + systemd is free software; you can redistribute it and/or modify it + under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + systemd is distributed in the hope that 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 General Public License + along with systemd; If not, see . +***/ + +#include +#include + +int verify_polkit( + DBusConnection *c, + DBusMessage *request, + const char *action, + bool interactive, + bool *challenge, + DBusError *error); + +#endif diff --git a/src/core/securebits.h b/src/core/securebits.h new file mode 100644 index 0000000000..ba0bba5353 --- /dev/null +++ b/src/core/securebits.h @@ -0,0 +1,45 @@ +#ifndef _LINUX_SECUREBITS_H +#define _LINUX_SECUREBITS_H 1 + +/* This is minimal version of Linux' linux/securebits.h header file, + * which is licensed GPL2 */ + +#define SECUREBITS_DEFAULT 0x00000000 + +/* When set UID 0 has no special privileges. When unset, we support + inheritance of root-permissions and suid-root executable under + compatibility mode. We raise the effective and inheritable bitmasks + *of the executable file* if the effective uid of the new process is + 0. If the real uid is 0, we raise the effective (legacy) bit of the + executable file. */ +#define SECURE_NOROOT 0 +#define SECURE_NOROOT_LOCKED 1 /* make bit-0 immutable */ + +/* When set, setuid to/from uid 0 does not trigger capability-"fixup". + When unset, to provide compatibility with old programs relying on + set*uid to gain/lose privilege, transitions to/from uid 0 cause + capabilities to be gained/lost. */ +#define SECURE_NO_SETUID_FIXUP 2 +#define SECURE_NO_SETUID_FIXUP_LOCKED 3 /* make bit-2 immutable */ + +/* When set, a process can retain its capabilities even after + transitioning to a non-root user (the set-uid fixup suppressed by + bit 2). Bit-4 is cleared when a process calls exec(); setting both + bit 4 and 5 will create a barrier through exec that no exec()'d + child can use this feature again. */ +#define SECURE_KEEP_CAPS 4 +#define SECURE_KEEP_CAPS_LOCKED 5 /* make bit-4 immutable */ + +/* Each securesetting is implemented using two bits. One bit specifies + whether the setting is on or off. The other bit specify whether the + setting is locked or not. A setting which is locked cannot be + changed from user-level. */ +#define issecure_mask(X) (1 << (X)) +#define issecure(X) (issecure_mask(X) & current_cred_xxx(securebits)) + +#define SECURE_ALL_BITS (issecure_mask(SECURE_NOROOT) | \ + issecure_mask(SECURE_NO_SETUID_FIXUP) | \ + issecure_mask(SECURE_KEEP_CAPS)) +#define SECURE_ALL_LOCKS (SECURE_ALL_BITS << 1) + +#endif /* !_LINUX_SECUREBITS_H */ diff --git a/src/core/selinux-setup.c b/src/core/selinux-setup.c new file mode 100644 index 0000000000..a7e1fa4007 --- /dev/null +++ b/src/core/selinux-setup.c @@ -0,0 +1,112 @@ +/*-*- Mode: C; c-basic-offset: 8; indent-tabs-mode: nil -*-*/ + +/*** + This file is part of systemd. + + Copyright 2010 Lennart Poettering + + systemd is free software; you can redistribute it and/or modify it + under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + systemd is distributed in the hope that 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 General Public License + along with systemd; If not, see . +***/ + +#include +#include +#include +#include +#include + +#ifdef HAVE_SELINUX +#include +#endif + +#include "selinux-setup.h" +#include "mount-setup.h" +#include "macro.h" +#include "util.h" +#include "log.h" +#include "label.h" + +int selinux_setup(bool *loaded_policy) { + +#ifdef HAVE_SELINUX + int enforce = 0; + usec_t before_load, after_load; + security_context_t con; + int r; + + assert(loaded_policy); + + /* Make sure getcon() works, which needs /proc and /sys */ + mount_setup_early(); + + /* Already initialized by somebody else? */ + r = getcon_raw(&con); + if (r == 0) { + bool initialized; + + initialized = !streq(con, "kernel"); + freecon(con); + + if (initialized) + return 0; + } + + /* 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) { + char timespan[FORMAT_TIMESPAN_MAX]; + char *label; + + label_retest_selinux(); + + /* Transition to the new context */ + r = label_get_create_label_from_exe(SYSTEMD_BINARY_PATH, &label); + if (r < 0 || label == NULL) { + log_open(); + log_error("Failed to compute init label, ignoring."); + } else { + r = setcon(label); + + log_open(); + if (r < 0) + log_error("Failed to transition into init label '%s', ignoring.", label); + + label_free(label); + } + + after_load = now(CLOCK_MONOTONIC); + + log_info("Successfully loaded SELinux policy in %s.", + format_timespan(timespan, sizeof(timespan), after_load - before_load)); + + *loaded_policy = true; + + } else { + log_open(); + + if (enforce > 0) { + log_error("Failed to load SELinux policy. Freezing."); + return -EIO; + } else + log_debug("Unable to load SELinux policy. Ignoring."); + } +#endif + + return 0; +} diff --git a/src/core/selinux-setup.h b/src/core/selinux-setup.h new file mode 100644 index 0000000000..6b8fe00b7d --- /dev/null +++ b/src/core/selinux-setup.h @@ -0,0 +1,29 @@ +/*-*- Mode: C; c-basic-offset: 8; indent-tabs-mode: nil -*-*/ + +#ifndef fooselinuxsetuphfoo +#define fooselinuxsetuphfoo + +/*** + This file is part of systemd. + + Copyright 2010 Lennart Poettering + + systemd is free software; you can redistribute it and/or modify it + under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + systemd is distributed in the hope that 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 General Public License + along with systemd; If not, see . +***/ + +#include + +int selinux_setup(bool *loaded_policy); + +#endif diff --git a/src/core/service.c b/src/core/service.c new file mode 100644 index 0000000000..bf2e0a9d98 --- /dev/null +++ b/src/core/service.c @@ -0,0 +1,3797 @@ +/*-*- Mode: C; c-basic-offset: 8; indent-tabs-mode: nil -*-*/ + +/*** + This file is part of systemd. + + Copyright 2010 Lennart Poettering + + systemd is free software; you can redistribute it and/or modify it + under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + systemd is distributed in the hope that 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 General Public License + along with systemd; If not, see . +***/ + +#include +#include +#include +#include +#include + +#include "manager.h" +#include "unit.h" +#include "service.h" +#include "load-fragment.h" +#include "load-dropin.h" +#include "log.h" +#include "strv.h" +#include "unit-name.h" +#include "dbus-service.h" +#include "special.h" +#include "bus-errors.h" +#include "exit-status.h" +#include "def.h" +#include "util.h" +#include "utf8.h" + +#ifdef HAVE_SYSV_COMPAT + +#define DEFAULT_SYSV_TIMEOUT_USEC (5*USEC_PER_MINUTE) + +typedef enum RunlevelType { + RUNLEVEL_UP, + RUNLEVEL_DOWN, + RUNLEVEL_SYSINIT +} 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_RUNLEVEL2_TARGET, RUNLEVEL_UP }, + { "rc3.d", SPECIAL_RUNLEVEL3_TARGET, RUNLEVEL_UP }, + { "rc4.d", SPECIAL_RUNLEVEL4_TARGET, RUNLEVEL_UP }, + { "rc5.d", SPECIAL_RUNLEVEL5_TARGET, RUNLEVEL_UP }, + +#ifdef TARGET_SUSE + /* SUSE style boot.d */ + { "boot.d", SPECIAL_SYSINIT_TARGET, RUNLEVEL_SYSINIT }, +#endif + +#if defined(TARGET_DEBIAN) || defined(TARGET_UBUNTU) || defined(TARGET_ANGSTROM) + /* Debian style rcS.d */ + { "rcS.d", SPECIAL_SYSINIT_TARGET, RUNLEVEL_SYSINIT }, +#endif + + /* 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/SYSINIT must be read before DOWN */ +}; + +#define RUNLEVELS_UP "12345" +/* #define RUNLEVELS_DOWN "06" */ +#define RUNLEVELS_BOOT "bBsS" +#endif + +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_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 void service_init(Unit *u) { + Service *s = SERVICE(u); + + assert(u); + assert(u->load_state == UNIT_STUB); + + s->timeout_usec = DEFAULT_TIMEOUT_USEC; + s->restart_usec = DEFAULT_RESTART_USEC; + + s->watchdog_watch.type = WATCH_INVALID; + + s->timer_watch.type = WATCH_INVALID; +#ifdef HAVE_SYSV_COMPAT + s->sysv_start_priority = -1; + s->sysv_start_priority_from_rcnd = -1; +#endif + s->socket_fd = -1; + s->guess_main_pid = true; + + exec_context_init(&s->exec_context); + + RATELIMIT_INIT(s->start_limit, 10*USEC_PER_SEC, 5); + + 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_debug("Stopping watch for %s's PID file %s", UNIT(s)->id, s->pid_file_pathspec->path); + path_spec_unwatch(s->pid_file_pathspec, UNIT(s)); + path_spec_done(s->pid_file_pathspec); + free(s->pid_file_pathspec); + s->pid_file_pathspec = NULL; +} + +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; + + s->main_pid = pid; + s->main_pid_known = true; + + if (get_parent_of_pid(pid, &ppid) >= 0 && ppid != getpid()) { + log_warning("%s: Supervising process %lu which is not our child. We'll most likely not notice when it exits.", + UNIT(s)->id, (unsigned long) pid); + + s->main_pid_alien = true; + } else + s->main_pid_alien = false; + + exec_status_start(&s->main_exec_status, pid); + + return 0; +} + +static void service_close_socket_fd(Service *s) { + assert(s); + + if (s->socket_fd < 0) + return; + + close_nointr_nofail(s->socket_fd); + s->socket_fd = -1; +} + +static void service_connection_unref(Service *s) { + assert(s); + + if (!UNIT_DEREF(s->accept_socket)) + return; + + socket_connection_unref(SOCKET(UNIT_DEREF(s->accept_socket))); + unit_ref_unset(&s->accept_socket); +} + +static void service_stop_watchdog(Service *s) { + assert(s); + + unit_unwatch_timer(UNIT(s), &s->watchdog_watch); + s->watchdog_timestamp.realtime = 0; + s->watchdog_timestamp.monotonic = 0; +} + +static void service_enter_dead(Service *s, ServiceResult f, bool allow_restart); + +static void service_handle_watchdog(Service *s) { + usec_t offset; + int r; + + assert(s); + + if (s->watchdog_usec == 0) + return; + + offset = now(CLOCK_MONOTONIC) - s->watchdog_timestamp.monotonic; + if (offset >= s->watchdog_usec) { + log_error("%s watchdog timeout!", UNIT(s)->id); + service_enter_dead(s, SERVICE_FAILURE_WATCHDOG, true); + return; + } + + r = unit_watch_timer(UNIT(s), s->watchdog_usec - offset, &s->watchdog_watch); + if (r < 0) + log_warning("%s failed to install watchdog timer: %s", UNIT(s)->id, strerror(-r)); +} + +static void service_reset_watchdog(Service *s) { + assert(s); + + dual_timestamp_get(&s->watchdog_timestamp); + service_handle_watchdog(s); +} + +static void service_done(Unit *u) { + Service *s = SERVICE(u); + + assert(s); + + free(s->pid_file); + s->pid_file = NULL; + +#ifdef HAVE_SYSV_COMPAT + free(s->sysv_path); + s->sysv_path = NULL; + + free(s->sysv_runlevels); + s->sysv_runlevels = NULL; +#endif + + free(s->status_text); + s->status_text = NULL; + + exec_context_done(&s->exec_context); + exec_command_free_array(s->exec_command, _SERVICE_EXEC_COMMAND_MAX); + s->control_command = NULL; + s->main_command = NULL; + + /* 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); + free(s->bus_name); + s->bus_name = NULL; + } + + service_close_socket_fd(s); + service_connection_unref(s); + + unit_ref_unset(&s->accept_socket); + + service_stop_watchdog(s); + + unit_unwatch_timer(u, &s->timer_watch); +} + +#ifdef HAVE_SYSV_COMPAT +static char *sysv_translate_name(const char *name) { + char *r; + + if (!(r = new(char, strlen(name) + sizeof(".service")))) + return NULL; + +#if defined(TARGET_DEBIAN) || defined(TARGET_UBUNTU) || defined(TARGET_ANGSTROM) + if (endswith(name, ".sh")) + /* Drop Debian-style .sh suffix */ + strcpy(stpcpy(r, name) - 3, ".service"); +#endif +#ifdef TARGET_SUSE + if (startswith(name, "boot.")) + /* Drop SuSE-style boot. prefix */ + strcpy(stpcpy(r, name + 5), ".service"); +#endif +#ifdef TARGET_FRUGALWARE + if (startswith(name, "rc.")) + /* Drop Frugalware-style rc. prefix */ + strcpy(stpcpy(r, name + 3), ".service"); +#endif + else + /* Normal init scripts */ + strcpy(stpcpy(r, name), ".service"); + + return r; +} + +static int sysv_translate_facility(const char *name, const char *filename, char **_r) { + + /* 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", SPECIAL_LOCAL_FS_TARGET, +#if defined(TARGET_MANDRIVA) || defined(TARGET_MAGEIA) +#else + /* Due to unfortunate name selection in Mandriva, + * $network is provided by network-up which is ordered + * after network which actually starts interfaces. + * To break the loop, just ignore it */ + "network", SPECIAL_NETWORK_TARGET, +#endif + "named", SPECIAL_NSS_LOOKUP_TARGET, + "portmap", SPECIAL_RPCBIND_TARGET, + "remote_fs", SPECIAL_REMOTE_FS_TARGET, + "syslog", SPECIAL_SYSLOG_TARGET, + "time", SPECIAL_TIME_SYNC_TARGET, + + /* common extensions */ + "mail-transfer-agent", SPECIAL_MAIL_TRANSFER_AGENT_TARGET, + "x-display-manager", SPECIAL_DISPLAY_MANAGER_SERVICE, + "null", NULL, + +#if defined(TARGET_DEBIAN) || defined(TARGET_UBUNTU) || defined(TARGET_ANGSTROM) + "mail-transport-agent", SPECIAL_MAIL_TRANSFER_AGENT_TARGET, +#endif + +#ifdef TARGET_FEDORA + "MTA", SPECIAL_MAIL_TRANSFER_AGENT_TARGET, + "smtpdaemon", SPECIAL_MAIL_TRANSFER_AGENT_TARGET, + "httpd", SPECIAL_HTTP_DAEMON_TARGET, +#endif + +#ifdef TARGET_SUSE + "smtp", SPECIAL_MAIL_TRANSFER_AGENT_TARGET, +#endif + }; + + unsigned i; + char *r; + const char *n; + + assert(name); + assert(_r); + + 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; + + if (!(r = strdup(table[i+1]))) + return -ENOMEM; + + goto finish; + } + + /* If we don't know this name, fallback heuristics to figure + * out whether something is a target or a service alias. */ + + if (*name == '$') { + if (!unit_prefix_is_valid(n)) + return -EINVAL; + + /* Facilities starting with $ are most likely targets */ + r = unit_name_build(n, NULL, ".target"); + } else if (filename && streq(name, filename)) + /* Names equaling the file name of the services are redundant */ + return 0; + else + /* Everything else we assume to be normal service names */ + r = sysv_translate_name(n); + + if (!r) + return -ENOMEM; + +finish: + *_r = r; + + return 1; +} + +static int sysv_fix_order(Service *s) { + Unit *other; + int r; + + assert(s); + + if (s->sysv_start_priority < 0) + return 0; + + /* For each pair of services where at least one lacks a LSB + * header, we use the start priority value to order things. */ + + LIST_FOREACH(units_by_type, other, UNIT(s)->manager->units_by_type[UNIT_SERVICE]) { + Service *t; + UnitDependency d; + bool special_s, special_t; + + t = SERVICE(other); + + if (s == t) + continue; + + if (UNIT(t)->load_state != UNIT_LOADED) + continue; + + if (t->sysv_start_priority < 0) + continue; + + /* If both units have modern headers we don't care + * about the priorities */ + if ((UNIT(s)->fragment_path || s->sysv_has_lsb) && + (UNIT(t)->fragment_path || t->sysv_has_lsb)) + continue; + + special_s = s->sysv_runlevels && !chars_intersect(RUNLEVELS_UP, s->sysv_runlevels); + special_t = t->sysv_runlevels && !chars_intersect(RUNLEVELS_UP, t->sysv_runlevels); + + if (special_t && !special_s) + d = UNIT_AFTER; + else if (special_s && !special_t) + d = UNIT_BEFORE; + else if (t->sysv_start_priority < s->sysv_start_priority) + d = UNIT_AFTER; + else if (t->sysv_start_priority > s->sysv_start_priority) + d = UNIT_BEFORE; + else + continue; + + /* FIXME: Maybe we should compare the name here lexicographically? */ + + if ((r = unit_add_dependency(UNIT(s), d, UNIT(t), true)) < 0) + return r; + } + + return 0; +} + +static ExecCommand *exec_command_new(const char *path, const char *arg1) { + ExecCommand *c; + + if (!(c = new0(ExecCommand, 1))) + return NULL; + + if (!(c->path = strdup(path))) { + free(c); + return NULL; + } + + if (!(c->argv = strv_new(path, arg1, NULL))) { + free(c->path); + free(c); + return NULL; + } + + return c; +} + +static int sysv_exec_commands(Service *s) { + ExecCommand *c; + + assert(s); + assert(s->sysv_path); + + if (!(c = exec_command_new(s->sysv_path, "start"))) + return -ENOMEM; + exec_command_append_list(s->exec_command+SERVICE_EXEC_START, c); + + if (!(c = exec_command_new(s->sysv_path, "stop"))) + return -ENOMEM; + exec_command_append_list(s->exec_command+SERVICE_EXEC_STOP, c); + + if (!(c = exec_command_new(s->sysv_path, "reload"))) + return -ENOMEM; + exec_command_append_list(s->exec_command+SERVICE_EXEC_RELOAD, c); + + return 0; +} + +static int service_load_sysv_path(Service *s, const char *path) { + FILE *f; + Unit *u; + unsigned line = 0; + int r; + enum { + NORMAL, + DESCRIPTION, + LSB, + LSB_DESCRIPTION + } state = NORMAL; + char *short_description = NULL, *long_description = NULL, *chkconfig_description = NULL, *description; + struct stat st; + + assert(s); + assert(path); + + u = UNIT(s); + + if (!(f = fopen(path, "re"))) { + r = errno == ENOENT ? 0 : -errno; + goto finish; + } + + zero(st); + if (fstat(fileno(f), &st) < 0) { + r = -errno; + goto finish; + } + + free(s->sysv_path); + if (!(s->sysv_path = strdup(path))) { + r = -ENOMEM; + goto finish; + } + + s->sysv_mtime = timespec_load(&st.st_mtim); + + if (null_or_empty(&st)) { + u->load_state = UNIT_MASKED; + r = 0; + goto finish; + } + + while (!feof(f)) { + char l[LINE_MAX], *t; + + if (!fgets(l, sizeof(l), f)) { + if (feof(f)) + break; + + r = -errno; + log_error("Failed to read configuration file '%s': %s", path, strerror(-r)); + goto finish; + } + + line++; + + t = strstrip(l); + if (*t != '#') + continue; + + if (state == NORMAL && streq(t, "### BEGIN INIT INFO")) { + state = LSB; + s->sysv_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 chkconfig headers */ + + if (startswith_no_case(t, "chkconfig:")) { + int start_priority; + char runlevels[16], *k; + + state = NORMAL; + + if (sscanf(t+10, "%15s %i %*i", + runlevels, + &start_priority) != 2) { + + log_warning("[%s:%u] Failed to parse chkconfig line. Ignoring.", path, line); + continue; + } + + /* A start priority gathered from the + * symlink farms is preferred over the + * data from the LSB header. */ + if (start_priority < 0 || start_priority > 99) + log_warning("[%s:%u] Start priority out of range. Ignoring.", path, line); + else + s->sysv_start_priority = start_priority; + + char_array_0(runlevels); + k = delete_chars(runlevels, WHITESPACE "-"); + + if (k[0]) { + char *d; + + if (!(d = strdup(k))) { + r = -ENOMEM; + goto finish; + } + + free(s->sysv_runlevels); + s->sysv_runlevels = d; + } + + } else if (startswith_no_case(t, "description:")) { + + size_t k = strlen(t); + char *d; + const char *j; + + if (t[k-1] == '\\') { + state = DESCRIPTION; + t[k-1] = 0; + } + + if ((j = strstrip(t+12)) && *j) { + if (!(d = strdup(j))) { + r = -ENOMEM; + goto finish; + } + } else + d = NULL; + + free(chkconfig_description); + chkconfig_description = d; + + } else if (startswith_no_case(t, "pidfile:")) { + + char *fn; + + state = NORMAL; + + fn = strstrip(t+8); + if (!path_is_absolute(fn)) { + log_warning("[%s:%u] PID file not absolute. Ignoring.", path, line); + continue; + } + + if (!(fn = strdup(fn))) { + r = -ENOMEM; + goto finish; + } + + free(s->pid_file); + s->pid_file = fn; + } + + } else if (state == DESCRIPTION) { + + /* Try to parse Red Hat style description + * continuation */ + + size_t k = strlen(t); + char *j; + + if (t[k-1] == '\\') + t[k-1] = 0; + else + state = NORMAL; + + if ((j = strstrip(t)) && *j) { + char *d = NULL; + + if (chkconfig_description) + d = join(chkconfig_description, " ", j, NULL); + else + d = strdup(j); + + if (!d) { + r = -ENOMEM; + goto finish; + } + + free(chkconfig_description); + chkconfig_description = d; + } + + } else if (state == LSB || state == LSB_DESCRIPTION) { + + if (startswith_no_case(t, "Provides:")) { + char *i, *w; + size_t z; + + state = LSB; + + FOREACH_WORD_QUOTED(w, z, t+9, i) { + char *n, *m; + + if (!(n = strndup(w, z))) { + r = -ENOMEM; + goto finish; + } + + r = sysv_translate_facility(n, file_name_from_path(path), &m); + free(n); + + if (r < 0) + goto finish; + + if (r == 0) + continue; + + if (unit_name_to_type(m) == UNIT_SERVICE) + r = unit_add_name(u, m); + else + /* 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 the SysV + * services! */ + r = unit_add_two_dependencies_by_name(u, UNIT_BEFORE, UNIT_WANTS, m, NULL, true); + + if (r < 0) + log_error("[%s:%u] Failed to add LSB Provides name %s, ignoring: %s", path, line, m, strerror(-r)); + + free(m); + } + + } 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:")) { + char *i, *w; + size_t z; + + state = LSB; + + FOREACH_WORD_QUOTED(w, z, strchr(t, ':')+1, i) { + char *n, *m; + + if (!(n = strndup(w, z))) { + r = -ENOMEM; + goto finish; + } + + r = sysv_translate_facility(n, file_name_from_path(path), &m); + + if (r < 0) { + log_error("[%s:%u] Failed to translate LSB dependency %s, ignoring: %s", path, line, n, strerror(-r)); + free(n); + continue; + } + + free(n); + + if (r == 0) + continue; + + r = unit_add_dependency_by_name(u, startswith_no_case(t, "X-Start-Before:") ? UNIT_BEFORE : UNIT_AFTER, m, NULL, true); + + if (r < 0) + log_error("[%s:%u] Failed to add dependency on %s, ignoring: %s", path, line, m, strerror(-r)); + + free(m); + } + } else if (startswith_no_case(t, "Default-Start:")) { + char *k, *d; + + state = LSB; + + k = delete_chars(t+14, WHITESPACE "-"); + + if (k[0] != 0) { + if (!(d = strdup(k))) { + r = -ENOMEM; + goto finish; + } + + free(s->sysv_runlevels); + s->sysv_runlevels = d; + } + + } else if (startswith_no_case(t, "Description:")) { + char *d, *j; + + state = LSB_DESCRIPTION; + + if ((j = strstrip(t+12)) && *j) { + if (!(d = strdup(j))) { + r = -ENOMEM; + goto finish; + } + } else + d = NULL; + + free(long_description); + long_description = d; + + } else if (startswith_no_case(t, "Short-Description:")) { + char *d, *j; + + state = LSB; + + if ((j = strstrip(t+18)) && *j) { + if (!(d = strdup(j))) { + r = -ENOMEM; + goto finish; + } + } else + d = NULL; + + free(short_description); + short_description = d; + + } else if (state == LSB_DESCRIPTION) { + + if (startswith(l, "#\t") || startswith(l, "# ")) { + char *j; + + if ((j = strstrip(t)) && *j) { + char *d = NULL; + + if (long_description) + d = join(long_description, " ", t, NULL); + else + d = strdup(j); + + if (!d) { + r = -ENOMEM; + goto finish; + } + + free(long_description); + long_description = d; + } + + } else + state = LSB; + } + } + } + + if ((r = sysv_exec_commands(s)) < 0) + goto finish; + if (s->sysv_runlevels && + chars_intersect(RUNLEVELS_BOOT, s->sysv_runlevels) && + chars_intersect(RUNLEVELS_UP, s->sysv_runlevels)) { + /* Service has both boot and "up" runlevels + configured. Kill the "up" ones. */ + delete_chars(s->sysv_runlevels, RUNLEVELS_UP); + } + + if (s->sysv_runlevels && !chars_intersect(RUNLEVELS_UP, s->sysv_runlevels)) { + /* If there a runlevels configured for this service + * but none of the standard ones, then we assume this + * is some special kind of service (which might be + * needed for early boot) and don't create any links + * to it. */ + + UNIT(s)->default_dependencies = false; + + /* Don't timeout special services during boot (like fsck) */ + s->timeout_usec = 0; + } else + s->timeout_usec = DEFAULT_SYSV_TIMEOUT_USEC; + + /* Special setting for all SysV services */ + s->type = SERVICE_FORKING; + s->remain_after_exit = !s->pid_file; + s->guess_main_pid = false; + s->restart = SERVICE_RESTART_NO; + s->exec_context.ignore_sigpipe = false; + + if (UNIT(s)->manager->sysv_console) + s->exec_context.std_output = EXEC_OUTPUT_JOURNAL_AND_CONSOLE; + + s->exec_context.kill_mode = KILL_PROCESS; + + /* 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; + + if (!(d = strappend(s->sysv_has_lsb ? "LSB: " : "SYSV: ", description))) { + r = -ENOMEM; + goto finish; + } + + u->description = d; + } + + /* The priority that has been set in /etc/rcN.d/ hierarchies + * takes precedence over what is stored as default in the LSB + * header */ + if (s->sysv_start_priority_from_rcnd >= 0) + s->sysv_start_priority = s->sysv_start_priority_from_rcnd; + + u->load_state = UNIT_LOADED; + r = 0; + +finish: + + if (f) + fclose(f); + + free(short_description); + free(long_description); + free(chkconfig_description); + + return r; +} + +static int service_load_sysv_name(Service *s, const char *name) { + char **p; + + assert(s); + assert(name); + + /* For SysV services we strip the boot.*, rc.* and *.sh + * prefixes/suffixes. */ +#if defined(TARGET_DEBIAN) || defined(TARGET_UBUNTU) || defined(TARGET_ANGSTROM) + if (endswith(name, ".sh.service")) + return -ENOENT; +#endif + +#ifdef TARGET_SUSE + if (startswith(name, "boot.")) + return -ENOENT; +#endif + +#ifdef TARGET_FRUGALWARE + if (startswith(name, "rc.")) + return -ENOENT; +#endif + + STRV_FOREACH(p, UNIT(s)->manager->lookup_paths.sysvinit_path) { + char *path; + int r; + + path = join(*p, "/", name, NULL); + if (!path) + return -ENOMEM; + + assert(endswith(path, ".service")); + path[strlen(path)-8] = 0; + + r = service_load_sysv_path(s, path); + +#if defined(TARGET_DEBIAN) || defined(TARGET_UBUNTU) || defined(TARGET_ANGSTROM) + if (r >= 0 && UNIT(s)->load_state == UNIT_STUB) { + /* Try Debian style *.sh source'able init scripts */ + strcat(path, ".sh"); + r = service_load_sysv_path(s, path); + } +#endif + free(path); + +#ifdef TARGET_SUSE + if (r >= 0 && UNIT(s)->load_state == UNIT_STUB) { + /* Try SUSE style boot.* init scripts */ + + path = join(*p, "/boot.", name, NULL); + if (!path) + return -ENOMEM; + + /* Drop .service suffix */ + path[strlen(path)-8] = 0; + r = service_load_sysv_path(s, path); + free(path); + } +#endif + +#ifdef TARGET_FRUGALWARE + if (r >= 0 && UNIT(s)->load_state == UNIT_STUB) { + /* Try Frugalware style rc.* init scripts */ + + path = join(*p, "/rc.", name, NULL); + if (!path) + return -ENOMEM; + + /* Drop .service suffix */ + path[strlen(path)-8] = 0; + r = service_load_sysv_path(s, path); + free(path); + } +#endif + + if (r < 0) + return r; + + if ((UNIT(s)->load_state != UNIT_STUB)) + break; + } + + return 0; +} + +static int service_load_sysv(Service *s) { + const char *t; + Iterator i; + int r; + + assert(s); + + /* Load service data from SysV init scripts, preferably with + * LSB headers ... */ + + if (strv_isempty(UNIT(s)->manager->lookup_paths.sysvinit_path)) + return 0; + + if ((t = UNIT(s)->id)) + if ((r = service_load_sysv_name(s, t)) < 0) + return r; + + if (UNIT(s)->load_state == UNIT_STUB) + SET_FOREACH(t, UNIT(s)->names, i) { + if (t == UNIT(s)->id) + continue; + + if ((r = service_load_sysv_name(s, t)) < 0) + return r; + + if (UNIT(s)->load_state != UNIT_STUB) + break; + } + + return 0; +} +#endif + +static int fsck_fix_order(Service *s) { + Unit *other; + int r; + + assert(s); + + if (s->fsck_passno <= 0) + return 0; + + /* For each pair of services where both have an fsck priority + * we order things based on it. */ + + LIST_FOREACH(units_by_type, other, UNIT(s)->manager->units_by_type[UNIT_SERVICE]) { + Service *t; + UnitDependency d; + + t = SERVICE(other); + + if (s == t) + continue; + + if (UNIT(t)->load_state != UNIT_LOADED) + continue; + + if (t->fsck_passno <= 0) + continue; + + if (t->fsck_passno < s->fsck_passno) + d = UNIT_AFTER; + else if (t->fsck_passno > s->fsck_passno) + d = UNIT_BEFORE; + else + continue; + + if ((r = unit_add_dependency(UNIT(s), d, UNIT(t), true)) < 0) + return r; + } + + 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]) { + log_error("%s lacks ExecStart setting. Refusing.", UNIT(s)->id); + return -EINVAL; + } + + if (s->type != SERVICE_ONESHOT && + s->exec_command[SERVICE_EXEC_START]->command_next) { + log_error("%s has more than one ExecStart setting, which is only allowed for Type=oneshot services. Refusing.", UNIT(s)->id); + return -EINVAL; + } + + if (s->type == SERVICE_ONESHOT && + s->exec_command[SERVICE_EXEC_RELOAD]) { + log_error("%s has an ExecReload setting, which is not allowed for Type=oneshot services. Refusing.", UNIT(s)->id); + return -EINVAL; + } + + if (s->type == SERVICE_DBUS && !s->bus_name) { + log_error("%s is of type D-Bus but no D-Bus service name has been specified. Refusing.", UNIT(s)->id); + return -EINVAL; + } + + if (s->exec_context.pam_name && s->exec_context.kill_mode != KILL_CONTROL_GROUP) { + log_error("%s has PAM enabled. Kill mode must be set to 'control-group'. Refusing.", UNIT(s)->id); + return -EINVAL; + } + + return 0; +} + +static int service_add_default_dependencies(Service *s) { + int r; + + assert(s); + + /* Add a number of automatic dependencies useful for the + * majority of services. */ + + /* First, pull in base system */ + if (UNIT(s)->manager->running_as == MANAGER_SYSTEM) { + + if ((r = unit_add_two_dependencies_by_name(UNIT(s), UNIT_AFTER, UNIT_REQUIRES, SPECIAL_BASIC_TARGET, NULL, true)) < 0) + return r; + + } else if (UNIT(s)->manager->running_as == MANAGER_USER) { + + if ((r = unit_add_two_dependencies_by_name(UNIT(s), UNIT_AFTER, UNIT_REQUIRES, SPECIAL_SOCKETS_TARGET, NULL, true)) < 0) + return r; + } + + /* Second, activate 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_load(Unit *u) { + int r; + Service *s = SERVICE(u); + + assert(s); + + /* Load a .service file */ + if ((r = unit_load_fragment(u)) < 0) + return r; + +#ifdef HAVE_SYSV_COMPAT + /* Load a classic init script as a fallback, if we couldn't find anything */ + if (u->load_state == UNIT_STUB) + if ((r = service_load_sysv(s)) < 0) + return r; +#endif + + /* Still nothing found? Then let's give up */ + if (u->load_state == UNIT_STUB) + return -ENOENT; + + /* We were able to load something, then let's add in the + * dropin directories. */ + if ((r = unit_load_dropin(unit_follow_merge(u))) < 0) + return r; + + /* This is a new unit? Then let's add in some extras */ + if (u->load_state == UNIT_LOADED) { + service_fix_output(s); + + if ((r = unit_add_exec_dependencies(u, &s->exec_context)) < 0) + return r; + + if ((r = unit_add_default_cgroups(u)) < 0) + return r; + +#ifdef HAVE_SYSV_COMPAT + if ((r = sysv_fix_order(s)) < 0) + return r; +#endif + + if ((r = fsck_fix_order(s)) < 0) + return r; + + if (s->bus_name) + if ((r = unit_watch_bus_name(u, s->bus_name)) < 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; + + if (s->type == SERVICE_DBUS || s->bus_name) + if ((r = unit_add_two_dependencies_by_name(u, UNIT_AFTER, UNIT_REQUIRES, SPECIAL_DBUS_SOCKET, NULL, true)) < 0) + return r; + + if (UNIT(s)->default_dependencies) + if ((r = service_add_default_dependencies(s)) < 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; + char *p2; + + assert(s); + + p2 = strappend(prefix, "\t"); + prefix2 = p2 ? p2 : prefix; + + 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", + 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)); + + if (s->control_pid > 0) + fprintf(f, + "%sControl PID: %lu\n", + prefix, (unsigned long) s->control_pid); + + if (s->main_pid > 0) + fprintf(f, + "%sMain PID: %lu\n" + "%sMain PID Known: %s\n" + "%sMain PID Alien: %s\n", + prefix, (unsigned long) 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)); + + 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); + } + +#ifdef HAVE_SYSV_COMPAT + if (s->sysv_path) + fprintf(f, + "%sSysV Init Script Path: %s\n" + "%sSysV Init Script has LSB Header: %s\n" + "%sSysVEnabled: %s\n", + prefix, s->sysv_path, + prefix, yes_no(s->sysv_has_lsb), + prefix, yes_no(s->sysv_enabled)); + + if (s->sysv_start_priority >= 0) + fprintf(f, + "%sSysVStartPriority: %i\n", + prefix, s->sysv_start_priority); + + if (s->sysv_runlevels) + fprintf(f, "%sSysVRunLevels: %s\n", + prefix, s->sysv_runlevels); +#endif + + if (s->fsck_passno > 0) + fprintf(f, + "%sFsckPassNo: %i\n", + prefix, s->fsck_passno); + + if (s->status_text) + fprintf(f, "%sStatus Text: %s\n", + prefix, s->status_text); + + free(p2); +} + +static int service_load_pid_file(Service *s, bool may_warn) { + char *k; + int r; + pid_t pid; + + assert(s); + + if (!s->pid_file) + return -ENOENT; + + if ((r = read_one_line_file(s->pid_file, &k)) < 0) { + if (may_warn) + log_info("PID file %s not readable (yet?) after %s.", + s->pid_file, service_state_to_string(s->state)); + return r; + } + + r = parse_pid(k, &pid); + free(k); + + if (r < 0) + return r; + + if (kill(pid, 0) < 0 && errno != EPERM) { + if (may_warn) + log_info("PID %lu read from file %s does not exist.", + (unsigned long) pid, s->pid_file); + return -ESRCH; + } + + if (s->main_pid_known) { + if (pid == s->main_pid) + return 0; + + log_debug("Main PID changing: %lu -> %lu", + (unsigned long) s->main_pid, (unsigned long) pid); + service_unwatch_main_pid(s); + s->main_pid_known = false; + } else + log_debug("Main PID loaded: %lu", (unsigned long) pid); + + if ((r = service_set_main_pid(s, pid)) < 0) + return r; + + if ((r = unit_watch_pid(UNIT(s), pid)) < 0) + /* FIXME: we need to do something here */ + return r; + + return 0; +} + +static int service_search_main_pid(Service *s) { + pid_t pid; + int r; + + assert(s); + + /* If we know it anyway, don't ever fallback to unreliable + * heuristics */ + if (s->main_pid_known) + return 0; + + if (!s->guess_main_pid) + return 0; + + assert(s->main_pid <= 0); + + if ((pid = cgroup_bonding_search_main_pid_list(UNIT(s)->cgroup_bondings)) <= 0) + return -ENOENT; + + log_debug("Main PID guessed: %lu", (unsigned long) pid); + if ((r = service_set_main_pid(s, pid)) < 0) + return r; + + if ((r = unit_watch_pid(UNIT(s), pid)) < 0) + /* FIXME: we need to do something here */ + return r; + + return 0; +} + +static void service_notify_sockets_dead(Service *s, bool failed_permanent) { + Iterator i; + Unit *u; + + assert(s); + + /* Notifies all our sockets when we die */ + + if (s->socket_fd >= 0) + return; + + SET_FOREACH(u, UNIT(s)->dependencies[UNIT_TRIGGERED_BY], i) + if (u->type == UNIT_SOCKET) + socket_notify_service_dead(SOCKET(u), failed_permanent); + + return; +} + +static void service_set_state(Service *s, ServiceState state) { + ServiceState old_state; + assert(s); + + old_state = s->state; + s->state = state; + + service_unwatch_pid_file(s); + + if (state != SERVICE_START_PRE && + state != SERVICE_START && + state != SERVICE_START_POST && + state != SERVICE_RELOAD && + state != SERVICE_STOP && + state != SERVICE_STOP_SIGTERM && + state != SERVICE_STOP_SIGKILL && + state != SERVICE_STOP_POST && + state != SERVICE_FINAL_SIGTERM && + state != SERVICE_FINAL_SIGKILL && + state != SERVICE_AUTO_RESTART) + unit_unwatch_timer(UNIT(s), &s->timer_watch); + + if (state != SERVICE_START && + state != SERVICE_START_POST && + state != SERVICE_RUNNING && + state != SERVICE_RELOAD && + state != SERVICE_STOP && + state != SERVICE_STOP_SIGTERM && + state != SERVICE_STOP_SIGKILL) { + service_unwatch_main_pid(s); + s->main_command = NULL; + } + + if (state != SERVICE_START_PRE && + state != SERVICE_START && + state != SERVICE_START_POST && + state != SERVICE_RELOAD && + state != SERVICE_STOP && + state != SERVICE_STOP_SIGTERM && + state != SERVICE_STOP_SIGKILL && + state != SERVICE_STOP_POST && + state != SERVICE_FINAL_SIGTERM && + state != SERVICE_FINAL_SIGKILL) { + service_unwatch_control_pid(s); + s->control_command = NULL; + s->control_command_id = _SERVICE_EXEC_COMMAND_INVALID; + } + + if (state == SERVICE_DEAD || + state == SERVICE_STOP || + state == SERVICE_STOP_SIGTERM || + state == SERVICE_STOP_SIGKILL || + state == SERVICE_STOP_POST || + state == SERVICE_FINAL_SIGTERM || + state == SERVICE_FINAL_SIGKILL || + state == SERVICE_FAILED || + state == SERVICE_AUTO_RESTART) + service_notify_sockets_dead(s, false); + + if (state != SERVICE_START_PRE && + state != SERVICE_START && + state != SERVICE_START_POST && + state != SERVICE_RUNNING && + state != SERVICE_RELOAD && + state != SERVICE_STOP && + state != SERVICE_STOP_SIGTERM && + state != SERVICE_STOP_SIGKILL && + state != SERVICE_STOP_POST && + state != SERVICE_FINAL_SIGTERM && + state != SERVICE_FINAL_SIGKILL && + !(state == SERVICE_DEAD && UNIT(s)->job)) { + service_close_socket_fd(s); + service_connection_unref(s); + } + + if (state == SERVICE_STOP) + service_stop_watchdog(s); + + /* For the inactive states unit_notify() will trim the cgroup, + * but for exit we have to do that ourselves... */ + if (state == SERVICE_EXITED && UNIT(s)->manager->n_reloading <= 0) + cgroup_bonding_trim_list(UNIT(s)->cgroup_bondings, true); + + if (old_state != state) + log_debug("%s changed %s -> %s", UNIT(s)->id, service_state_to_string(old_state), service_state_to_string(state)); + + unit_notify(UNIT(s), state_translation_table[old_state], state_translation_table[state], s->reload_result == SERVICE_SUCCESS); + s->reload_result = SERVICE_SUCCESS; +} + +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) { + + if (s->deserialized_state == SERVICE_START_PRE || + s->deserialized_state == SERVICE_START || + s->deserialized_state == SERVICE_START_POST || + s->deserialized_state == SERVICE_RELOAD || + s->deserialized_state == SERVICE_STOP || + s->deserialized_state == SERVICE_STOP_SIGTERM || + s->deserialized_state == SERVICE_STOP_SIGKILL || + s->deserialized_state == SERVICE_STOP_POST || + s->deserialized_state == SERVICE_FINAL_SIGTERM || + s->deserialized_state == SERVICE_FINAL_SIGKILL || + s->deserialized_state == SERVICE_AUTO_RESTART) { + + if (s->deserialized_state == SERVICE_AUTO_RESTART || s->timeout_usec > 0) { + usec_t k; + + k = s->deserialized_state == SERVICE_AUTO_RESTART ? s->restart_usec : s->timeout_usec; + + if ((r = unit_watch_timer(UNIT(s), k, &s->timer_watch)) < 0) + return r; + } + } + + if ((s->deserialized_state == SERVICE_START && + (s->type == SERVICE_FORKING || + s->type == SERVICE_DBUS || + s->type == SERVICE_ONESHOT || + s->type == SERVICE_NOTIFY)) || + s->deserialized_state == SERVICE_START_POST || + s->deserialized_state == SERVICE_RUNNING || + s->deserialized_state == SERVICE_RELOAD || + s->deserialized_state == SERVICE_STOP || + s->deserialized_state == SERVICE_STOP_SIGTERM || + s->deserialized_state == SERVICE_STOP_SIGKILL) + if (s->main_pid > 0) + if ((r = unit_watch_pid(UNIT(s), s->main_pid)) < 0) + return r; + + if (s->deserialized_state == SERVICE_START_PRE || + s->deserialized_state == SERVICE_START || + s->deserialized_state == SERVICE_START_POST || + s->deserialized_state == SERVICE_RELOAD || + s->deserialized_state == SERVICE_STOP || + s->deserialized_state == SERVICE_STOP_SIGTERM || + s->deserialized_state == SERVICE_STOP_SIGKILL || + s->deserialized_state == SERVICE_STOP_POST || + s->deserialized_state == SERVICE_FINAL_SIGTERM || + s->deserialized_state == SERVICE_FINAL_SIGKILL) + if (s->control_pid > 0) + if ((r = unit_watch_pid(UNIT(s), s->control_pid)) < 0) + return r; + + if (s->deserialized_state == SERVICE_START_POST || + s->deserialized_state == SERVICE_RUNNING) + service_handle_watchdog(s); + + service_set_state(s, s->deserialized_state); + } + return 0; +} + +static int service_collect_fds(Service *s, int **fds, unsigned *n_fds) { + Iterator i; + int r; + int *rfds = NULL; + unsigned rn_fds = 0; + Unit *u; + + assert(s); + assert(fds); + assert(n_fds); + + if (s->socket_fd >= 0) + return 0; + + SET_FOREACH(u, UNIT(s)->dependencies[UNIT_TRIGGERED_BY], i) { + int *cfds; + unsigned cn_fds; + Socket *sock; + + if (u->type != UNIT_SOCKET) + continue; + + sock = SOCKET(u); + + if ((r = socket_collect_fds(sock, &cfds, &cn_fds)) < 0) + goto fail; + + if (!cfds) + continue; + + if (!rfds) { + rfds = cfds; + rn_fds = cn_fds; + } else { + int *t; + + if (!(t = new(int, rn_fds+cn_fds))) { + free(cfds); + r = -ENOMEM; + goto fail; + } + + memcpy(t, rfds, rn_fds * sizeof(int)); + memcpy(t+rn_fds, cfds, cn_fds * sizeof(int)); + free(rfds); + free(cfds); + + rfds = t; + rn_fds = rn_fds+cn_fds; + } + } + + *fds = rfds; + *n_fds = rn_fds; + + return 0; + +fail: + free(rfds); + + return r; +} + +static int service_spawn( + Service *s, + ExecCommand *c, + bool timeout, + bool pass_fds, + bool apply_permissions, + bool apply_chroot, + bool apply_tty_stdin, + bool set_notify_socket, + pid_t *_pid) { + + pid_t pid; + int r; + int *fds = NULL, *fdsbuf = NULL; + unsigned n_fds = 0, n_env = 0; + char **argv = NULL, **final_env = NULL, **our_env = NULL; + + assert(s); + assert(c); + assert(_pid); + + 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) { + + if (s->socket_fd >= 0) { + fds = &s->socket_fd; + n_fds = 1; + } else { + if ((r = service_collect_fds(s, &fdsbuf, &n_fds)) < 0) + goto fail; + + fds = fdsbuf; + } + } + + if (timeout && s->timeout_usec) { + if ((r = unit_watch_timer(UNIT(s), s->timeout_usec, &s->timer_watch)) < 0) + goto fail; + } else + unit_unwatch_timer(UNIT(s), &s->timer_watch); + + if (!(argv = unit_full_printf_strv(UNIT(s), c->argv))) { + r = -ENOMEM; + goto fail; + } + + if (!(our_env = new0(char*, 4))) { + r = -ENOMEM; + goto fail; + } + + if (set_notify_socket) + if (asprintf(our_env + n_env++, "NOTIFY_SOCKET=%s", UNIT(s)->manager->notify_socket) < 0) { + r = -ENOMEM; + goto fail; + } + + if (s->main_pid > 0) + if (asprintf(our_env + n_env++, "MAINPID=%lu", (unsigned long) s->main_pid) < 0) { + r = -ENOMEM; + goto fail; + } + + if (s->watchdog_usec > 0) + if (asprintf(our_env + n_env++, "WATCHDOG_USEC=%llu", (unsigned long long) s->watchdog_usec) < 0) { + r = -ENOMEM; + goto fail; + } + + if (!(final_env = strv_env_merge(2, + UNIT(s)->manager->environment, + our_env, + NULL))) { + r = -ENOMEM; + goto fail; + } + + r = exec_spawn(c, + argv, + &s->exec_context, + fds, n_fds, + final_env, + apply_permissions, + apply_chroot, + apply_tty_stdin, + UNIT(s)->manager->confirm_spawn, + UNIT(s)->cgroup_bondings, + UNIT(s)->cgroup_attributes, + &pid); + + if (r < 0) + goto fail; + + + if ((r = unit_watch_pid(UNIT(s), pid)) < 0) + /* FIXME: we need to do something here */ + goto fail; + + free(fdsbuf); + strv_free(argv); + strv_free(our_env); + strv_free(final_env); + + *_pid = pid; + + return 0; + +fail: + free(fdsbuf); + strv_free(argv); + strv_free(our_env); + strv_free(final_env); + + if (timeout) + unit_unwatch_timer(UNIT(s), &s->timer_watch); + + return r; +} + +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 lets 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) + return kill(s->main_pid, 0) >= 0 || errno != ESRCH; + + /* .. 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; +} + +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 ((r = cgroup_bonding_is_empty_list(UNIT(s)->cgroup_bondings)) < 0) + return r; + + return !r; +} + +static void service_enter_dead(Service *s, ServiceResult f, bool allow_restart) { + int r; + assert(s); + + if (f != SERVICE_SUCCESS) + s->result = f; + + if (allow_restart && + !s->forbid_restart && + (s->restart == SERVICE_RESTART_ALWAYS || + (s->restart == SERVICE_RESTART_ON_SUCCESS && s->result == SERVICE_SUCCESS) || + (s->restart == SERVICE_RESTART_ON_FAILURE && s->result != SERVICE_SUCCESS) || + (s->restart == SERVICE_RESTART_ON_ABORT && (s->result == SERVICE_FAILURE_SIGNAL || + s->result == SERVICE_FAILURE_CORE_DUMP)))) { + + r = unit_watch_timer(UNIT(s), s->restart_usec, &s->timer_watch); + if (r < 0) + goto fail; + + service_set_state(s, SERVICE_AUTO_RESTART); + } else + service_set_state(s, s->result != SERVICE_SUCCESS ? SERVICE_FAILED : SERVICE_DEAD); + + s->forbid_restart = false; + + return; + +fail: + log_warning("%s failed to run install restart timer: %s", UNIT(s)->id, strerror(-r)); + service_enter_dead(s, SERVICE_FAILURE_RESOURCES, false); +} + +static void service_enter_signal(Service *s, ServiceState state, ServiceResult f); + +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); + + if ((s->control_command = s->exec_command[SERVICE_EXEC_STOP_POST])) { + s->control_command_id = SERVICE_EXEC_STOP_POST; + + if ((r = service_spawn(s, + s->control_command, + true, + false, + !s->permissions_start_only, + !s->root_directory_start_only, + true, + false, + &s->control_pid)) < 0) + goto fail; + + + service_set_state(s, SERVICE_STOP_POST); + } else + service_enter_signal(s, SERVICE_FINAL_SIGTERM, SERVICE_SUCCESS); + + return; + +fail: + log_warning("%s failed to run 'stop-post' task: %s", UNIT(s)->id, strerror(-r)); + service_enter_signal(s, SERVICE_FINAL_SIGTERM, SERVICE_FAILURE_RESOURCES); +} + +static void service_enter_signal(Service *s, ServiceState state, ServiceResult f) { + int r; + Set *pid_set = NULL; + bool wait_for_exit = false; + + assert(s); + + if (f != SERVICE_SUCCESS) + s->result = f; + + if (s->exec_context.kill_mode != KILL_NONE) { + int sig = (state == SERVICE_STOP_SIGTERM || state == SERVICE_FINAL_SIGTERM) ? s->exec_context.kill_signal : SIGKILL; + + if (s->main_pid > 0) { + if (kill_and_sigcont(s->main_pid, sig) < 0 && errno != ESRCH) + log_warning("Failed to kill main process %li: %m", (long) s->main_pid); + else + wait_for_exit = !s->main_pid_alien; + } + + if (s->control_pid > 0) { + if (kill_and_sigcont(s->control_pid, sig) < 0 && errno != ESRCH) + log_warning("Failed to kill control process %li: %m", (long) s->control_pid); + else + wait_for_exit = true; + } + + if (s->exec_context.kill_mode == KILL_CONTROL_GROUP) { + + if (!(pid_set = set_new(trivial_hash_func, trivial_compare_func))) { + r = -ENOMEM; + goto fail; + } + + /* Exclude the main/control pids from being killed via the cgroup */ + if (s->main_pid > 0) + if ((r = set_put(pid_set, LONG_TO_PTR(s->main_pid))) < 0) + goto fail; + + if (s->control_pid > 0) + if ((r = set_put(pid_set, LONG_TO_PTR(s->control_pid))) < 0) + goto fail; + + if ((r = cgroup_bonding_kill_list(UNIT(s)->cgroup_bondings, sig, true, pid_set)) < 0) { + if (r != -EAGAIN && r != -ESRCH && r != -ENOENT) + log_warning("Failed to kill control group: %s", strerror(-r)); + } else if (r > 0) + wait_for_exit = true; + + set_free(pid_set); + pid_set = NULL; + } + } + + if (wait_for_exit) { + if (s->timeout_usec > 0) + if ((r = unit_watch_timer(UNIT(s), s->timeout_usec, &s->timer_watch)) < 0) + goto fail; + + service_set_state(s, state); + } else if (state == SERVICE_STOP_SIGTERM || state == SERVICE_STOP_SIGKILL) + service_enter_stop_post(s, SERVICE_SUCCESS); + else + service_enter_dead(s, SERVICE_SUCCESS, true); + + return; + +fail: + log_warning("%s failed to kill processes: %s", UNIT(s)->id, strerror(-r)); + + if (state == SERVICE_STOP_SIGTERM || state == SERVICE_STOP_SIGKILL) + service_enter_stop_post(s, SERVICE_FAILURE_RESOURCES); + else + service_enter_dead(s, SERVICE_FAILURE_RESOURCES, true); + + if (pid_set) + set_free(pid_set); +} + +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); + + if ((s->control_command = s->exec_command[SERVICE_EXEC_STOP])) { + s->control_command_id = SERVICE_EXEC_STOP; + + if ((r = service_spawn(s, + s->control_command, + true, + false, + !s->permissions_start_only, + !s->root_directory_start_only, + false, + false, + &s->control_pid)) < 0) + goto fail; + + service_set_state(s, SERVICE_STOP); + } else + service_enter_signal(s, SERVICE_STOP_SIGTERM, SERVICE_SUCCESS); + + return; + +fail: + log_warning("%s failed to run 'stop' task: %s", UNIT(s)->id, strerror(-r)); + service_enter_signal(s, SERVICE_STOP_SIGTERM, SERVICE_FAILURE_RESOURCES); +} + +static void service_enter_running(Service *s, ServiceResult f) { + int main_pid_ok, cgroup_ok; + assert(s); + + if (f != SERVICE_SUCCESS) + s->result = f; + + main_pid_ok = main_pid_good(s); + cgroup_ok = cgroup_good(s); + + if ((main_pid_ok > 0 || (main_pid_ok < 0 && cgroup_ok != 0)) && + (s->bus_name_good || s->type != SERVICE_DBUS)) + service_set_state(s, SERVICE_RUNNING); + 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); + + if (s->watchdog_usec > 0) + service_reset_watchdog(s); + + if ((s->control_command = s->exec_command[SERVICE_EXEC_START_POST])) { + s->control_command_id = SERVICE_EXEC_START_POST; + + if ((r = service_spawn(s, + s->control_command, + true, + false, + !s->permissions_start_only, + !s->root_directory_start_only, + false, + false, + &s->control_pid)) < 0) + goto fail; + + service_set_state(s, SERVICE_START_POST); + } else + service_enter_running(s, SERVICE_SUCCESS); + + return; + +fail: + log_warning("%s failed to run 'start-post' task: %s", UNIT(s)->id, strerror(-r)); + service_enter_stop(s, SERVICE_FAILURE_RESOURCES); +} + +static void service_enter_start(Service *s) { + pid_t pid; + int r; + ExecCommand *c; + + assert(s); + + assert(s->exec_command[SERVICE_EXEC_START]); + assert(!s->exec_command[SERVICE_EXEC_START]->command_next || s->type == SERVICE_ONESHOT); + + if (s->type == SERVICE_FORKING) + service_unwatch_control_pid(s); + else + 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. */ + cgroup_bonding_kill_list(UNIT(s)->cgroup_bondings, SIGKILL, true, NULL); + + 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 ((r = service_spawn(s, + c, + s->type == SERVICE_FORKING || s->type == SERVICE_DBUS || s->type == SERVICE_NOTIFY, + true, + true, + true, + true, + s->notify_access != NOTIFY_NONE, + &pid)) < 0) + goto fail; + + if (s->type == SERVICE_SIMPLE) { + /* 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 (s->type == SERVICE_ONESHOT || + s->type == SERVICE_DBUS || + s->type == 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_warning("%s failed to run 'start' task: %s", UNIT(s)->id, strerror(-r)); + 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); + + if ((s->control_command = s->exec_command[SERVICE_EXEC_START_PRE])) { + + /* Before we start anything, let's clear up what might + * be left from previous runs. */ + cgroup_bonding_kill_list(UNIT(s)->cgroup_bondings, SIGKILL, true, NULL); + + s->control_command_id = SERVICE_EXEC_START_PRE; + + if ((r = service_spawn(s, + s->control_command, + true, + false, + !s->permissions_start_only, + !s->root_directory_start_only, + true, + false, + &s->control_pid)) < 0) + goto fail; + + service_set_state(s, SERVICE_START_PRE); + } else + service_enter_start(s); + + return; + +fail: + log_warning("%s failed to run 'start-pre' task: %s", UNIT(s)->id, strerror(-r)); + service_enter_dead(s, SERVICE_FAILURE_RESOURCES, true); +} + +static void service_enter_restart(Service *s) { + int r; + DBusError error; + + assert(s); + dbus_error_init(&error); + + if (UNIT(s)->job) { + log_info("Job pending for unit, delaying automatic restart."); + + if ((r = unit_watch_timer(UNIT(s), s->restart_usec, &s->timer_watch)) < 0) + goto fail; + } + + /* 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, false, &error, NULL); + if (r < 0) + goto fail; + + log_debug("%s scheduled restart job.", UNIT(s)->id); + return; + +fail: + log_warning("%s failed to schedule restart job: %s", UNIT(s)->id, bus_error(&error, -r)); + service_enter_dead(s, SERVICE_FAILURE_RESOURCES, false); + + dbus_error_free(&error); +} + +static void service_enter_reload(Service *s) { + int r; + + assert(s); + + service_unwatch_control_pid(s); + + if ((s->control_command = s->exec_command[SERVICE_EXEC_RELOAD])) { + s->control_command_id = SERVICE_EXEC_RELOAD; + + if ((r = service_spawn(s, + s->control_command, + true, + false, + !s->permissions_start_only, + !s->root_directory_start_only, + false, + false, + &s->control_pid)) < 0) + goto fail; + + service_set_state(s, SERVICE_RELOAD); + } else + service_enter_running(s, SERVICE_SUCCESS); + + return; + +fail: + log_warning("%s failed to run 'reload' task: %s", UNIT(s)->id, strerror(-r)); + s->reload_result = SERVICE_FAILURE_RESOURCES; + service_enter_running(s, SERVICE_SUCCESS); +} + +static void service_run_next_control(Service *s) { + 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 ((r = service_spawn(s, + s->control_command, + true, + 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, + false, + &s->control_pid)) < 0) + goto fail; + + return; + +fail: + log_warning("%s failed to run next control task: %s", UNIT(s)->id, strerror(-r)); + + 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); + + if ((r = service_spawn(s, + s->main_command, + false, + true, + true, + true, + true, + s->notify_access != NOTIFY_NONE, + &pid)) < 0) + goto fail; + + service_set_main_pid(s, pid); + + return; + +fail: + log_warning("%s failed to run next main task: %s", UNIT(s)->id, strerror(-r)); + service_enter_stop(s, SERVICE_FAILURE_RESOURCES); +} + +static int service_start_limit_test(Service *s) { + assert(s); + + if (ratelimit_test(&s->start_limit)) + return 0; + + switch (s->start_limit_action) { + + case SERVICE_START_LIMIT_NONE: + log_warning("%s start request repeated too quickly, refusing to start.", UNIT(s)->id); + break; + + case SERVICE_START_LIMIT_REBOOT: { + DBusError error; + int r; + + dbus_error_init(&error); + + log_warning("%s start request repeated too quickly, rebooting.", UNIT(s)->id); + + r = manager_add_job_by_name(UNIT(s)->manager, JOB_START, SPECIAL_REBOOT_TARGET, JOB_REPLACE, true, &error, NULL); + if (r < 0) { + log_error("Failed to reboot: %s.", bus_error(&error, r)); + dbus_error_free(&error); + } + + break; + } + + case SERVICE_START_LIMIT_REBOOT_FORCE: + log_warning("%s start request repeated too quickly, forcibly rebooting.", UNIT(s)->id); + UNIT(s)->manager->exit_code = MANAGER_REBOOT; + break; + + case SERVICE_START_LIMIT_REBOOT_IMMEDIATE: + log_warning("%s start request repeated too quickly, rebooting immediately.", UNIT(s)->id); + reboot(RB_AUTOBOOT); + break; + + default: + log_error("start limit action=%i", s->start_limit_action); + assert_not_reached("Unknown StartLimitAction."); + } + + return -ECANCELED; +} + +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 (s->state == SERVICE_STOP || + s->state == SERVICE_STOP_SIGTERM || + s->state == SERVICE_STOP_SIGKILL || + s->state == SERVICE_STOP_POST || + s->state == SERVICE_FINAL_SIGTERM || + s->state == SERVICE_FINAL_SIGKILL) + return -EAGAIN; + + /* Already on it! */ + if (s->state == SERVICE_START_PRE || + s->state == SERVICE_START || + s->state == SERVICE_START_POST) + return 0; + + assert(s->state == SERVICE_DEAD || s->state == SERVICE_FAILED || s->state == SERVICE_AUTO_RESTART); + + /* Make sure we don't enter a busy loop of some kind. */ + r = service_start_limit_test(s); + if (r < 0) { + service_notify_sockets_dead(s, true); + 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; + + service_enter_start_pre(s); + return 0; +} + +static int service_stop(Unit *u) { + Service *s = SERVICE(u); + + assert(s); + + /* This is a user request, so don't do restarts on this + * shutdown. */ + s->forbid_restart = true; + + /* Already on it */ + if (s->state == SERVICE_STOP || + s->state == SERVICE_STOP_SIGTERM || + s->state == SERVICE_STOP_SIGKILL || + s->state == SERVICE_STOP_POST || + s->state == SERVICE_FINAL_SIGTERM || + s->state == SERVICE_FINAL_SIGKILL) + return 0; + + /* Don't allow a restart */ + 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 (s->state == SERVICE_START_PRE || + s->state == SERVICE_START || + s->state == SERVICE_START_POST || + s->state == SERVICE_RELOAD) { + service_enter_signal(s, SERVICE_STOP_SIGTERM, SERVICE_SUCCESS); + return 0; + } + + assert(s->state == SERVICE_RUNNING || + s->state == SERVICE_EXITED); + + service_enter_stop(s, SERVICE_SUCCESS); + return 0; +} + +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 0; +} + +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); + + 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", "%lu", (unsigned long) s->control_pid); + + if (s->main_pid_known && s->main_pid > 0) + unit_serialize_item_format(u, f, "main-pid", "%lu", (unsigned long) s->main_pid); + + unit_serialize_item(u, f, "main-pid-known", yes_no(s->main_pid_known)); + + if (s->status_text) + unit_serialize_item(u, f, "status-text", s->status_text); + + /* 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)); + + if (s->socket_fd >= 0) { + int copy; + + if ((copy = fdset_put_dup(fds, s->socket_fd)) < 0) + return copy; + + unit_serialize_item_format(u, f, "socket-fd", "%i", copy); + } + + if (s->main_exec_status.pid > 0) { + unit_serialize_item_format(u, f, "main-exec-status-pid", "%lu", (unsigned long) s->main_exec_status.pid); + dual_timestamp_serialize(f, "main-exec-status-start", &s->main_exec_status.start_timestamp); + dual_timestamp_serialize(f, "main-exec-status-exit", &s->main_exec_status.exit_timestamp); + + if (dual_timestamp_is_set(&s->main_exec_status.exit_timestamp)) { + unit_serialize_item_format(u, f, "main-exec-status-code", "%i", s->main_exec_status.code); + unit_serialize_item_format(u, f, "main-exec-status-status", "%i", s->main_exec_status.status); + } + } + if (dual_timestamp_is_set(&s->watchdog_timestamp)) + dual_timestamp_serialize(f, "watchdog-timestamp", &s->watchdog_timestamp); + + return 0; +} + +static int service_deserialize_item(Unit *u, const char *key, const char *value, FDSet *fds) { + Service *s = SERVICE(u); + + assert(u); + assert(key); + assert(value); + assert(fds); + + if (streq(key, "state")) { + ServiceState state; + + if ((state = service_state_from_string(value)) < 0) + log_debug("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_debug("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_debug("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_debug("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_debug("Failed to parse main-pid value %s", value); + else + service_set_main_pid(s, (pid_t) pid); + } else if (streq(key, "main-pid-known")) { + int b; + + if ((b = parse_boolean(value)) < 0) + log_debug("Failed to parse main-pid-known value %s", value); + else + s->main_pid_known = b; + } else if (streq(key, "status-text")) { + char *t; + + if ((t = strdup(value))) { + free(s->status_text); + s->status_text = t; + } + + } else if (streq(key, "control-command")) { + ServiceExecCommand id; + + if ((id = service_exec_command_from_string(value)) < 0) + log_debug("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_debug("Failed to parse socket-fd value %s", value); + else { + + if (s->socket_fd >= 0) + close_nointr_nofail(s->socket_fd); + s->socket_fd = fdset_remove(fds, fd); + } + } else if (streq(key, "main-exec-status-pid")) { + pid_t pid; + + if (parse_pid(value, &pid) < 0) + log_debug("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_debug("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_debug("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 + log_debug("Unknown serialization key '%s'", key); + + return 0; +} + +static UnitActiveState service_active_state(Unit *u) { + assert(u); + + return state_translation_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; + +#ifdef HAVE_SYSV_COMPAT + if (s->sysv_path) + return true; +#endif + + return false; +} + +static bool service_check_snapshot(Unit *u) { + Service *s = SERVICE(u); + + assert(s); + + return !s->got_socket_fd; +} + +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_debug("Setting watch for %s's PID file %s", UNIT(s)->id, s->pid_file_pathspec->path); + r = path_spec_watch(s->pid_file_pathspec, UNIT(s)); + if (r < 0) + goto fail; + + /* the pidfile might have appeared just before we set the watch */ + service_retry_pid_file(s); + + return 0; +fail: + log_error("Failed to set a watch for %s's PID file %s: %s", + UNIT(s)->id, s->pid_file_pathspec->path, strerror(-r)); + 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->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 void service_fd_event(Unit *u, int fd, uint32_t events, Watch *w) { + Service *s = SERVICE(u); + + 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_debug("inotify event for %s", u->id); + + if (path_spec_fd_event(s->pid_file_pathspec, events) < 0) + goto fail; + + if (service_retry_pid_file(s) == 0) + return; + + if (service_watch_pid_file(s) < 0) + goto fail; + + return; +fail: + service_unwatch_pid_file(s); + service_enter_signal(s, SERVICE_STOP_SIGTERM, SERVICE_FAILURE_RESOURCES); +} + +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) : is_clean_exit_lsb(code, 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 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 */ + if (s->main_command) { + s->main_command->exec_status = s->main_exec_status; + + if (s->main_command->ignore) + f = SERVICE_SUCCESS; + } + + log_full(f == SERVICE_SUCCESS ? LOG_DEBUG : LOG_NOTICE, + "%s: main process exited, code=%s, status=%i", u->id, sigchld_code_to_string(code), status); + + 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_debug("%s running next main command for state %s", u->id, 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; + } else { + assert(s->type == SERVICE_DBUS || s->type == SERVICE_NOTIFY); + + /* Fall through */ + } + + case SERVICE_RUNNING: + service_enter_running(s, f); + break; + + 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; + + 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_full(f == SERVICE_SUCCESS ? LOG_DEBUG : LOG_NOTICE, + "%s: control process exited, code=%s status=%i", u->id, sigchld_code_to_string(code), status); + + if (f != SERVICE_SUCCESS) + s->result = f; + + if (s->control_command && + s->control_command->command_next && + f == SERVICE_SUCCESS) { + + /* There is another command to * + * execute, so let's do that. */ + + log_debug("%s running next control command for state %s", u->id, 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_debug("%s got final SIGCHLD for state %s", u->id, 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: + assert(s->type == SERVICE_FORKING); + + 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_stop(s, 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) { + service_load_pid_file(s, true); + 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_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: + 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); +} + +static void service_timer_event(Unit *u, uint64_t elapsed, Watch* w) { + Service *s = SERVICE(u); + + assert(s); + assert(elapsed == 1); + + if (w == &s->watchdog_watch) { + service_handle_watchdog(s); + return; + } + + assert(w == &s->timer_watch); + + switch (s->state) { + + case SERVICE_START_PRE: + case SERVICE_START: + log_warning("%s operation timed out. Terminating.", u->id); + service_enter_signal(s, SERVICE_FINAL_SIGTERM, SERVICE_FAILURE_TIMEOUT); + break; + + case SERVICE_START_POST: + log_warning("%s operation timed out. Stopping.", u->id); + service_enter_stop(s, SERVICE_FAILURE_TIMEOUT); + break; + + case SERVICE_RELOAD: + log_warning("%s operation timed out. Stopping.", u->id); + s->reload_result = SERVICE_FAILURE_TIMEOUT; + service_enter_running(s, SERVICE_SUCCESS); + break; + + case SERVICE_STOP: + log_warning("%s stopping timed out. Terminating.", u->id); + service_enter_signal(s, SERVICE_STOP_SIGTERM, SERVICE_FAILURE_TIMEOUT); + break; + + case SERVICE_STOP_SIGTERM: + if (s->exec_context.send_sigkill) { + log_warning("%s stopping timed out. Killing.", u->id); + service_enter_signal(s, SERVICE_STOP_SIGKILL, SERVICE_FAILURE_TIMEOUT); + } else { + log_warning("%s stopping timed out. Skipping SIGKILL.", u->id); + 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_warning("%s still around after SIGKILL. Ignoring.", u->id); + service_enter_stop_post(s, SERVICE_FAILURE_TIMEOUT); + break; + + case SERVICE_STOP_POST: + log_warning("%s stopping timed out (2). Terminating.", u->id); + service_enter_signal(s, SERVICE_FINAL_SIGTERM, SERVICE_FAILURE_TIMEOUT); + break; + + case SERVICE_FINAL_SIGTERM: + if (s->exec_context.send_sigkill) { + log_warning("%s stopping timed out (2). Killing.", u->id); + service_enter_signal(s, SERVICE_FINAL_SIGKILL, SERVICE_FAILURE_TIMEOUT); + } else { + log_warning("%s stopping timed out (2). Skipping SIGKILL. Entering failed mode.", u->id); + service_enter_dead(s, SERVICE_FAILURE_TIMEOUT, false); + } + + break; + + case SERVICE_FINAL_SIGKILL: + log_warning("%s still around after SIGKILL (2). Entering failed mode.", u->id); + service_enter_dead(s, SERVICE_FAILURE_TIMEOUT, true); + break; + + case SERVICE_AUTO_RESTART: + log_info("%s holdoff time over, scheduling restart.", u->id); + service_enter_restart(s); + break; + + default: + assert_not_reached("Timeout at wrong time."); + } +} + +static void service_cgroup_notify_event(Unit *u) { + Service *s = SERVICE(u); + + assert(u); + + log_debug("%s: cgroup is empty", u->id); + + 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_warning("%s never wrote its PID file. Failing.", UNIT(s)->id); + 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_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_FINAL_SIGTERM: + case SERVICE_FINAL_SIGKILL: + if (main_pid_good(s) <= 0 && !control_pid_good(s)) + service_enter_dead(s, SERVICE_SUCCESS, SERVICE_SUCCESS); + + break; + + default: + ; + } +} + +static void service_notify_message(Unit *u, pid_t pid, char **tags) { + Service *s = SERVICE(u); + const char *e; + + assert(u); + + if (s->notify_access == NOTIFY_NONE) { + log_warning("%s: Got notification message from PID %lu, but reception is disabled.", + u->id, (unsigned long) pid); + return; + } + + if (s->notify_access == NOTIFY_MAIN && pid != s->main_pid) { + log_warning("%s: Got notification message from PID %lu, but reception only permitted for PID %lu", + u->id, (unsigned long) pid, (unsigned long) s->main_pid); + return; + } + + log_debug("%s: Got message", u->id); + + /* Interpret MAINPID= */ + if ((e = strv_find_prefix(tags, "MAINPID=")) && + (s->state == SERVICE_START || + s->state == SERVICE_START_POST || + s->state == SERVICE_RUNNING || + s->state == SERVICE_RELOAD)) { + + if (parse_pid(e + 8, &pid) < 0) + log_warning("Failed to parse notification message %s", e); + else { + log_debug("%s: got %s", u->id, e); + service_set_main_pid(s, pid); + } + } + + /* Interpret READY= */ + if (s->type == SERVICE_NOTIFY && + s->state == SERVICE_START && + strv_find(tags, "READY=1")) { + log_debug("%s: got READY=1", u->id); + + service_enter_start_post(s); + } + + /* Interpret STATUS= */ + e = strv_find_prefix(tags, "STATUS="); + if (e) { + char *t; + + if (e[7]) { + + if (!utf8_is_valid(e+7)) { + log_warning("Status message in notification is not UTF-8 clean."); + return; + } + + t = strdup(e+7); + if (!t) { + log_error("Failed to allocate string."); + return; + } + + log_debug("%s: got %s", u->id, e); + + free(s->status_text); + s->status_text = t; + } else { + free(s->status_text); + s->status_text = NULL; + } + + } + if (strv_find(tags, "WATCHDOG=1")) { + log_debug("%s: got WATCHDOG=1", u->id); + service_reset_watchdog(s); + } + + /* Notify clients about changed status or main pid */ + unit_add_to_dbus_queue(u); +} + +#ifdef HAVE_SYSV_COMPAT + +#ifdef TARGET_SUSE +static void sysv_facility_in_insserv_conf(Manager *mgr) { + FILE *f=NULL; + int r; + + if (!(f = fopen("/etc/insserv.conf", "re"))) { + r = errno == ENOENT ? 0 : -errno; + goto finish; + } + + while (!feof(f)) { + char l[LINE_MAX], *t; + char **parsed = NULL; + + if (!fgets(l, sizeof(l), f)) { + if (feof(f)) + break; + + r = -errno; + log_error("Failed to read configuration file '/etc/insserv.conf': %s", strerror(-r)); + goto finish; + } + + t = strstrip(l); + if (*t != '$' && *t != '<') + continue; + + parsed = strv_split(t,WHITESPACE); + /* we ignore , not used, equivalent to X-Interactive */ + if (parsed && !startswith_no_case (parsed[0], "")) { + char *facility; + Unit *u; + if (sysv_translate_facility(parsed[0], NULL, &facility) < 0) + continue; + if ((u = manager_get_unit(mgr, facility)) && (u->type == UNIT_TARGET)) { + UnitDependency e; + char *dep = NULL, *name, **j; + + STRV_FOREACH (j, parsed+1) { + if (*j[0]=='+') { + e = UNIT_WANTS; + name = *j+1; + } + else { + e = UNIT_REQUIRES; + name = *j; + } + if (sysv_translate_facility(name, NULL, &dep) < 0) + continue; + + r = unit_add_two_dependencies_by_name(u, UNIT_BEFORE, e, dep, NULL, true); + free(dep); + } + } + free(facility); + } + strv_free(parsed); + } +finish: + if (f) + fclose(f); + +} +#endif + +static int service_enumerate(Manager *m) { + char **p; + unsigned i; + DIR *d = NULL; + char *path = NULL, *fpath = NULL, *name = NULL; + Set *runlevel_services[ELEMENTSOF(rcnd_table)], *shutdown_services = NULL; + Unit *service; + Iterator j; + int r; + + assert(m); + + if (m->running_as != MANAGER_SYSTEM) + return 0; + + zero(runlevel_services); + + STRV_FOREACH(p, m->lookup_paths.sysvrcnd_path) + for (i = 0; i < ELEMENTSOF(rcnd_table); i ++) { + struct dirent *de; + + free(path); + path = join(*p, "/", rcnd_table[i].path, NULL); + if (!path) { + r = -ENOMEM; + goto finish; + } + + if (d) + closedir(d); + + if (!(d = opendir(path))) { + if (errno != ENOENT) + log_warning("opendir() failed on %s: %s", path, strerror(errno)); + + continue; + } + + while ((de = readdir(d))) { + int a, b; + + if (ignore_file(de->d_name)) + continue; + + 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; + + free(fpath); + fpath = join(path, "/", de->d_name, NULL); + if (!fpath) { + r = -ENOMEM; + goto finish; + } + + if (access(fpath, X_OK) < 0) { + + if (errno != ENOENT) + log_warning("access() failed on %s: %s", fpath, strerror(errno)); + + continue; + } + + free(name); + if (!(name = sysv_translate_name(de->d_name + 3))) { + r = -ENOMEM; + goto finish; + } + + if ((r = manager_load_unit_prepare(m, name, NULL, NULL, &service)) < 0) { + log_warning("Failed to prepare unit %s: %s", name, strerror(-r)); + continue; + } + + if (de->d_name[0] == 'S') { + + if (rcnd_table[i].type == RUNLEVEL_UP || rcnd_table[i].type == RUNLEVEL_SYSINIT) { + SERVICE(service)->sysv_start_priority_from_rcnd = + MAX(a*10 + b, SERVICE(service)->sysv_start_priority_from_rcnd); + + SERVICE(service)->sysv_enabled = true; + } + + if ((r = set_ensure_allocated(&runlevel_services[i], trivial_hash_func, trivial_compare_func)) < 0) + goto finish; + + if ((r = set_put(runlevel_services[i], service)) < 0) + goto finish; + + } else if (de->d_name[0] == 'K' && + (rcnd_table[i].type == RUNLEVEL_DOWN || + rcnd_table[i].type == RUNLEVEL_SYSINIT)) { + + if ((r = set_ensure_allocated(&shutdown_services, trivial_hash_func, trivial_compare_func)) < 0) + goto finish; + + if ((r = set_put(shutdown_services, service)) < 0) + goto finish; + } + } + } + + /* Now we loaded all stubs and are aware of the lowest + start-up priority for all services, not let's actually load + the services, this will also tell us which services are + actually native now */ + manager_dispatch_load_queue(m); + + /* If this is a native service, rely on native ways to pull in + * a service, don't pull it in via sysv rcN.d links. */ + for (i = 0; i < ELEMENTSOF(rcnd_table); i ++) + SET_FOREACH(service, runlevel_services[i], j) { + service = unit_follow_merge(service); + + if (service->fragment_path) + continue; + + if ((r = unit_add_two_dependencies_by_name_inverse(service, UNIT_AFTER, UNIT_WANTS, rcnd_table[i].target, NULL, true)) < 0) + goto finish; + } + + /* We honour K links only for halt/reboot. For the normal + * runlevels we assume the stop jobs will be implicitly added + * by the core logic. Also, we don't really distinguish here + * between the runlevels 0 and 6 and just add them to the + * special shutdown target. On SUSE the boot.d/ runlevel is + * also used for shutdown, so we add links for that too to the + * shutdown target.*/ + SET_FOREACH(service, shutdown_services, j) { + service = unit_follow_merge(service); + + if (service->fragment_path) + continue; + + if ((r = unit_add_two_dependencies_by_name(service, UNIT_BEFORE, UNIT_CONFLICTS, SPECIAL_SHUTDOWN_TARGET, NULL, true)) < 0) + goto finish; + } + + r = 0; + +#ifdef TARGET_SUSE + sysv_facility_in_insserv_conf (m); +#endif + +finish: + free(path); + free(fpath); + free(name); + + for (i = 0; i < ELEMENTSOF(rcnd_table); i++) + set_free(runlevel_services[i]); + set_free(shutdown_services); + + if (d) + closedir(d); + + return r; +} +#endif + +static void service_bus_name_owner_change( + Unit *u, + const char *name, + const char *old_owner, + const char *new_owner) { + + Service *s = SERVICE(u); + + assert(s); + assert(name); + + assert(streq(s->bus_name, name)); + assert(old_owner || new_owner); + + if (old_owner && new_owner) + log_debug("%s's D-Bus name %s changed owner from %s to %s", u->id, name, old_owner, new_owner); + else if (old_owner) + log_debug("%s's D-Bus name %s no longer registered by %s", u->id, name, old_owner); + else + log_debug("%s's D-Bus name %s now registered by %s", u->id, name, new_owner); + + s->bus_name_good = !!new_owner; + + 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)) { + + /* Try to acquire PID from bus service */ + log_debug("Trying to acquire PID from D-Bus name..."); + + bus_query_pid(u->manager, name); + } +} + +static void service_bus_query_pid_done( + Unit *u, + const char *name, + pid_t pid) { + + Service *s = SERVICE(u); + + assert(s); + assert(name); + + log_debug("%s's D-Bus name %s is now owned by process %u", u->id, name, (unsigned) pid); + + if (s->main_pid <= 0 && + (s->state == SERVICE_START || + s->state == SERVICE_START_POST || + s->state == SERVICE_RUNNING || + s->state == SERVICE_RELOAD)) + service_set_main_pid(s, pid); +} + +int service_set_socket_fd(Service *s, int fd, Socket *sock) { + + assert(s); + assert(fd >= 0); + + /* This is called by the socket code when instantiating a new + * service for a stream socket and the socket needs to be + * configured. */ + + if (UNIT(s)->load_state != UNIT_LOADED) + return -EINVAL; + + if (s->socket_fd >= 0) + return -EBUSY; + + if (s->state != SERVICE_DEAD) + return -EAGAIN; + + s->socket_fd = fd; + s->got_socket_fd = true; + + unit_ref_set(&s->accept_socket, UNIT(sock)); + + return unit_add_two_dependencies(UNIT(sock), UNIT_BEFORE, UNIT_TRIGGERS, UNIT(s), false); +} + +static void service_reset_failed(Unit *u) { + Service *s = SERVICE(u); + + assert(s); + + if (s->state == SERVICE_FAILED) + service_set_state(s, SERVICE_DEAD); + + s->result = SERVICE_SUCCESS; + s->reload_result = SERVICE_SUCCESS; +} + +static bool service_need_daemon_reload(Unit *u) { + Service *s = SERVICE(u); + + assert(s); + +#ifdef HAVE_SYSV_COMPAT + if (s->sysv_path) { + struct stat st; + + zero(st); + if (stat(s->sysv_path, &st) < 0) + /* What, cannot access this anymore? */ + return true; + + if (s->sysv_mtime > 0 && + timespec_load(&st.st_mtim) != s->sysv_mtime) + return true; + } +#endif + + return false; +} + +static int service_kill(Unit *u, KillWho who, KillMode mode, int signo, DBusError *error) { + Service *s = SERVICE(u); + int r = 0; + Set *pid_set = NULL; + + assert(s); + + if (s->main_pid <= 0 && who == KILL_MAIN) { + dbus_set_error(error, BUS_ERROR_NO_SUCH_PROCESS, "No main process to kill"); + return -ESRCH; + } + + if (s->control_pid <= 0 && who == KILL_CONTROL) { + dbus_set_error(error, BUS_ERROR_NO_SUCH_PROCESS, "No control process to kill"); + return -ESRCH; + } + + if (who == KILL_CONTROL || who == KILL_ALL) + if (s->control_pid > 0) + if (kill(s->control_pid, signo) < 0) + r = -errno; + + if (who == KILL_MAIN || who == KILL_ALL) + if (s->main_pid > 0) + if (kill(s->main_pid, signo) < 0) + r = -errno; + + if (who == KILL_ALL && mode == KILL_CONTROL_GROUP) { + int q; + + if (!(pid_set = set_new(trivial_hash_func, trivial_compare_func))) + return -ENOMEM; + + /* Exclude the control/main pid from being killed via the cgroup */ + if (s->control_pid > 0) + if ((q = set_put(pid_set, LONG_TO_PTR(s->control_pid))) < 0) { + r = q; + goto finish; + } + + if (s->main_pid > 0) + if ((q = set_put(pid_set, LONG_TO_PTR(s->main_pid))) < 0) { + r = q; + goto finish; + } + + if ((q = cgroup_bonding_kill_list(UNIT(s)->cgroup_bondings, signo, false, pid_set)) < 0) + if (q != -EAGAIN && q != -ESRCH && q != -ENOENT) + r = q; + } + +finish: + if (pid_set) + set_free(pid_set); + + return r; +} + +static const char* const service_state_table[_SERVICE_STATE_MAX] = { + [SERVICE_DEAD] = "dead", + [SERVICE_START_PRE] = "start-pre", + [SERVICE_START] = "start", + [SERVICE_START_POST] = "start-post", + [SERVICE_RUNNING] = "running", + [SERVICE_EXITED] = "exited", + [SERVICE_RELOAD] = "reload", + [SERVICE_STOP] = "stop", + [SERVICE_STOP_SIGTERM] = "stop-sigterm", + [SERVICE_STOP_SIGKILL] = "stop-sigkill", + [SERVICE_STOP_POST] = "stop-post", + [SERVICE_FINAL_SIGTERM] = "final-sigterm", + [SERVICE_FINAL_SIGKILL] = "final-sigkill", + [SERVICE_FAILED] = "failed", + [SERVICE_AUTO_RESTART] = "auto-restart", +}; + +DEFINE_STRING_TABLE_LOOKUP(service_state, ServiceState); + +static const char* const 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_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" +}; + +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 service_result_table[_SERVICE_RESULT_MAX] = { + [SERVICE_SUCCESS] = "success", + [SERVICE_FAILURE_RESOURCES] = "resources", + [SERVICE_FAILURE_TIMEOUT] = "timeout", + [SERVICE_FAILURE_EXIT_CODE] = "exit-code", + [SERVICE_FAILURE_SIGNAL] = "signal", + [SERVICE_FAILURE_CORE_DUMP] = "core-dump", + [SERVICE_FAILURE_WATCHDOG] = "watchdog" +}; + +DEFINE_STRING_TABLE_LOOKUP(service_result, ServiceResult); + +static const char* const start_limit_action_table[_SERVICE_START_LIMIT_MAX] = { + [SERVICE_START_LIMIT_NONE] = "none", + [SERVICE_START_LIMIT_REBOOT] = "reboot", + [SERVICE_START_LIMIT_REBOOT_FORCE] = "reboot-force", + [SERVICE_START_LIMIT_REBOOT_IMMEDIATE] = "reboot-immediate" +}; +DEFINE_STRING_TABLE_LOOKUP(start_limit_action, StartLimitAction); + +const UnitVTable service_vtable = { + .suffix = ".service", + .object_size = sizeof(Service), + .sections = + "Unit\0" + "Service\0" + "Install\0", + .show_status = true, + + .init = service_init, + .done = service_done, + .load = service_load, + + .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, + .check_snapshot = service_check_snapshot, + + .sigchld_event = service_sigchld_event, + .timer_event = service_timer_event, + .fd_event = service_fd_event, + + .reset_failed = service_reset_failed, + + .need_daemon_reload = service_need_daemon_reload, + + .cgroup_notify_empty = service_cgroup_notify_event, + .notify_message = service_notify_message, + + .bus_name_owner_change = service_bus_name_owner_change, + .bus_query_pid_done = service_bus_query_pid_done, + + .bus_interface = "org.freedesktop.systemd1.Service", + .bus_message_handler = bus_service_message_handler, + .bus_invalidating_properties = bus_service_invalidating_properties, + +#ifdef HAVE_SYSV_COMPAT + .enumerate = service_enumerate +#endif +}; diff --git a/src/core/service.h b/src/core/service.h new file mode 100644 index 0000000000..60b10516eb --- /dev/null +++ b/src/core/service.h @@ -0,0 +1,220 @@ +/*-*- Mode: C; c-basic-offset: 8; indent-tabs-mode: nil -*-*/ + +#ifndef fooservicehfoo +#define fooservicehfoo + +/*** + This file is part of systemd. + + Copyright 2010 Lennart Poettering + + systemd is free software; you can redistribute it and/or modify it + under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + systemd is distributed in the hope that 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 General Public License + along with systemd; If not, see . +***/ + +typedef struct Service Service; + +#include "unit.h" +#include "path.h" +#include "ratelimit.h" +#include "service.h" + +typedef enum ServiceState { + SERVICE_DEAD, + SERVICE_START_PRE, + SERVICE_START, + SERVICE_START_POST, + SERVICE_RUNNING, + SERVICE_EXITED, /* Nothing is running anymore, but RemainAfterExit is true hence this is OK */ + SERVICE_RELOAD, + SERVICE_STOP, /* No STOP_PRE state, instead just register multiple STOP executables */ + SERVICE_STOP_SIGTERM, + SERVICE_STOP_SIGKILL, + SERVICE_STOP_POST, + SERVICE_FINAL_SIGTERM, /* In case the STOP_POST executable hangs, we shoot that down, too */ + SERVICE_FINAL_SIGKILL, + SERVICE_FAILED, + SERVICE_AUTO_RESTART, + _SERVICE_STATE_MAX, + _SERVICE_STATE_INVALID = -1 +} ServiceState; + +typedef enum ServiceRestart { + SERVICE_RESTART_NO, + SERVICE_RESTART_ON_SUCCESS, + SERVICE_RESTART_ON_FAILURE, + 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_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 ServiceResult { + SERVICE_SUCCESS, + SERVICE_FAILURE_RESOURCES, + SERVICE_FAILURE_TIMEOUT, + SERVICE_FAILURE_EXIT_CODE, + SERVICE_FAILURE_SIGNAL, + SERVICE_FAILURE_CORE_DUMP, + SERVICE_FAILURE_WATCHDOG, + _SERVICE_RESULT_MAX, + _SERVICE_RESULT_INVALID = -1 +} ServiceResult; + +typedef enum StartLimitAction { + SERVICE_START_LIMIT_NONE, + SERVICE_START_LIMIT_REBOOT, + SERVICE_START_LIMIT_REBOOT_FORCE, + SERVICE_START_LIMIT_REBOOT_IMMEDIATE, + _SERVICE_START_LIMIT_MAX, + _SERVICE_START_LIMIT_INVALID = -1 +} StartLimitAction; + +struct Service { + Unit meta; + + ServiceType type; + ServiceRestart restart; + + /* If set we'll read the main daemon PID from this file */ + char *pid_file; + + usec_t restart_usec; + usec_t timeout_usec; + + dual_timestamp watchdog_timestamp; + usec_t watchdog_usec; + Watch watchdog_watch; + + ExecCommand* exec_command[_SERVICE_EXEC_COMMAND_MAX]; + ExecContext exec_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; + + pid_t main_pid, control_pid; + int socket_fd; + + int fsck_passno; + + 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 got_socket_fd:1; +#ifdef HAVE_SYSV_COMPAT + bool sysv_has_lsb:1; + bool sysv_enabled:1; + int sysv_start_priority_from_rcnd; + int sysv_start_priority; + + char *sysv_path; + char *sysv_runlevels; + usec_t sysv_mtime; +#endif + + char *bus_name; + + char *status_text; + + RateLimit start_limit; + StartLimitAction start_limit_action; + + + UnitRef accept_socket; + + Watch timer_watch; + PathSpec *pid_file_pathspec; + + NotifyAccess notify_access; +}; + +extern const UnitVTable service_vtable; + +struct Socket; + +int service_set_socket_fd(Service *s, int fd, struct Socket *socket); + +const char* service_state_to_string(ServiceState i); +ServiceState service_state_from_string(const char *s); + +const char* service_restart_to_string(ServiceRestart i); +ServiceRestart service_restart_from_string(const char *s); + +const char* service_type_to_string(ServiceType i); +ServiceType service_type_from_string(const char *s); + +const char* service_exec_command_to_string(ServiceExecCommand i); +ServiceExecCommand service_exec_command_from_string(const char *s); + +const char* notify_access_to_string(NotifyAccess i); +NotifyAccess notify_access_from_string(const char *s); + +const char* service_result_to_string(ServiceResult i); +ServiceResult service_result_from_string(const char *s); + +const char* start_limit_action_to_string(StartLimitAction i); +StartLimitAction start_limit_action_from_string(const char *s); + +#endif diff --git a/src/core/snapshot.c b/src/core/snapshot.c new file mode 100644 index 0000000000..82ec5104db --- /dev/null +++ b/src/core/snapshot.c @@ -0,0 +1,309 @@ +/*-*- Mode: C; c-basic-offset: 8; indent-tabs-mode: nil -*-*/ + +/*** + This file is part of systemd. + + Copyright 2010 Lennart Poettering + + systemd is free software; you can redistribute it and/or modify it + under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + systemd is distributed in the hope that 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 General Public License + along with systemd; If not, see . +***/ + +#include + +#include "unit.h" +#include "snapshot.h" +#include "unit-name.h" +#include "dbus-snapshot.h" +#include "bus-errors.h" + +static const UnitActiveState state_translation_table[_SNAPSHOT_STATE_MAX] = { + [SNAPSHOT_DEAD] = UNIT_INACTIVE, + [SNAPSHOT_ACTIVE] = UNIT_ACTIVE +}; + +static void snapshot_init(Unit *u) { + Snapshot *s = SNAPSHOT(u); + + assert(s); + assert(UNIT(s)->load_state == UNIT_STUB); + + UNIT(s)->ignore_on_isolate = true; + UNIT(s)->ignore_on_snapshot = true; +} + +static void snapshot_set_state(Snapshot *s, SnapshotState state) { + SnapshotState old_state; + assert(s); + + old_state = s->state; + s->state = state; + + if (state != old_state) + log_debug("%s changed %s -> %s", + UNIT(s)->id, + snapshot_state_to_string(old_state), + snapshot_state_to_string(state)); + + unit_notify(UNIT(s), state_translation_table[old_state], state_translation_table[state], true); +} + +static int snapshot_load(Unit *u) { + Snapshot *s = SNAPSHOT(u); + + assert(u); + assert(u->load_state == UNIT_STUB); + + /* Make sure that only snapshots created via snapshot_create() + * can be loaded */ + if (!s->by_snapshot_create && UNIT(s)->manager->n_reloading <= 0) + return -ENOENT; + + u->load_state = UNIT_LOADED; + return 0; +} + +static int snapshot_coldplug(Unit *u) { + Snapshot *s = SNAPSHOT(u); + + assert(s); + assert(s->state == SNAPSHOT_DEAD); + + if (s->deserialized_state != s->state) + snapshot_set_state(s, s->deserialized_state); + + return 0; +} + +static void snapshot_dump(Unit *u, FILE *f, const char *prefix) { + Snapshot *s = SNAPSHOT(u); + + assert(s); + assert(f); + + fprintf(f, + "%sSnapshot State: %s\n" + "%sClean Up: %s\n", + prefix, snapshot_state_to_string(s->state), + prefix, yes_no(s->cleanup)); +} + +static int snapshot_start(Unit *u) { + Snapshot *s = SNAPSHOT(u); + + assert(s); + assert(s->state == SNAPSHOT_DEAD); + + snapshot_set_state(s, SNAPSHOT_ACTIVE); + + if (s->cleanup) + unit_add_to_cleanup_queue(u); + + return 0; +} + +static int snapshot_stop(Unit *u) { + Snapshot *s = SNAPSHOT(u); + + assert(s); + assert(s->state == SNAPSHOT_ACTIVE); + + snapshot_set_state(s, SNAPSHOT_DEAD); + return 0; +} + +static int snapshot_serialize(Unit *u, FILE *f, FDSet *fds) { + Snapshot *s = SNAPSHOT(u); + Unit *other; + Iterator i; + + assert(s); + assert(f); + assert(fds); + + unit_serialize_item(u, f, "state", snapshot_state_to_string(s->state)); + unit_serialize_item(u, f, "cleanup", yes_no(s->cleanup)); + SET_FOREACH(other, u->dependencies[UNIT_WANTS], i) + unit_serialize_item(u, f, "wants", other->id); + + return 0; +} + +static int snapshot_deserialize_item(Unit *u, const char *key, const char *value, FDSet *fds) { + Snapshot *s = SNAPSHOT(u); + int r; + + assert(u); + assert(key); + assert(value); + assert(fds); + + if (streq(key, "state")) { + SnapshotState state; + + if ((state = snapshot_state_from_string(value)) < 0) + log_debug("Failed to parse state value %s", value); + else + s->deserialized_state = state; + + } else if (streq(key, "cleanup")) { + + if ((r = parse_boolean(value)) < 0) + log_debug("Failed to parse cleanup value %s", value); + else + s->cleanup = r; + + } else if (streq(key, "wants")) { + + if ((r = unit_add_two_dependencies_by_name(u, UNIT_AFTER, UNIT_WANTS, value, NULL, true)) < 0) + return r; + } else + log_debug("Unknown serialization key '%s'", key); + + return 0; +} + +static UnitActiveState snapshot_active_state(Unit *u) { + assert(u); + + return state_translation_table[SNAPSHOT(u)->state]; +} + +static const char *snapshot_sub_state_to_string(Unit *u) { + assert(u); + + return snapshot_state_to_string(SNAPSHOT(u)->state); +} + +int snapshot_create(Manager *m, const char *name, bool cleanup, DBusError *e, Snapshot **_s) { + Iterator i; + Unit *other, *u = NULL; + char *n = NULL; + int r; + const char *k; + + assert(m); + assert(_s); + + if (name) { + if (!unit_name_is_valid(name, false)) { + dbus_set_error(e, BUS_ERROR_INVALID_NAME, "Unit name %s is not valid.", name); + return -EINVAL; + } + + if (unit_name_to_type(name) != UNIT_SNAPSHOT) { + dbus_set_error(e, BUS_ERROR_UNIT_TYPE_MISMATCH, "Unit name %s lacks snapshot suffix.", name); + return -EINVAL; + } + + if (manager_get_unit(m, name)) { + dbus_set_error(e, BUS_ERROR_UNIT_EXISTS, "Snapshot %s exists already.", name); + return -EEXIST; + } + + } else { + + for (;;) { + if (asprintf(&n, "snapshot-%u.snapshot", ++ m->n_snapshots) < 0) + return -ENOMEM; + + if (!manager_get_unit(m, n)) + break; + + free(n); + } + + name = n; + } + + r = manager_load_unit_prepare(m, name, NULL, e, &u); + free(n); + + if (r < 0) + goto fail; + + SNAPSHOT(u)->by_snapshot_create = true; + manager_dispatch_load_queue(m); + assert(u->load_state == UNIT_LOADED); + + HASHMAP_FOREACH_KEY(other, k, m->units, i) { + + if (other->ignore_on_snapshot) + continue; + + if (k != other->id) + continue; + + if (UNIT_VTABLE(other)->check_snapshot) + if (!UNIT_VTABLE(other)->check_snapshot(other)) + continue; + + if (!UNIT_IS_ACTIVE_OR_ACTIVATING(unit_active_state(other))) + continue; + + if ((r = unit_add_two_dependencies(u, UNIT_AFTER, UNIT_WANTS, other, true)) < 0) + goto fail; + } + + SNAPSHOT(u)->cleanup = cleanup; + *_s = SNAPSHOT(u); + + return 0; + +fail: + if (u) + unit_add_to_cleanup_queue(u); + + return r; +} + +void snapshot_remove(Snapshot *s) { + assert(s); + + unit_add_to_cleanup_queue(UNIT(s)); +} + +static const char* const snapshot_state_table[_SNAPSHOT_STATE_MAX] = { + [SNAPSHOT_DEAD] = "dead", + [SNAPSHOT_ACTIVE] = "active" +}; + +DEFINE_STRING_TABLE_LOOKUP(snapshot_state, SnapshotState); + +const UnitVTable snapshot_vtable = { + .suffix = ".snapshot", + .object_size = sizeof(Snapshot), + + .no_alias = true, + .no_instances = true, + .no_gc = true, + + .init = snapshot_init, + + .load = snapshot_load, + .coldplug = snapshot_coldplug, + + .dump = snapshot_dump, + + .start = snapshot_start, + .stop = snapshot_stop, + + .serialize = snapshot_serialize, + .deserialize_item = snapshot_deserialize_item, + + .active_state = snapshot_active_state, + .sub_state_to_string = snapshot_sub_state_to_string, + + .bus_interface = "org.freedesktop.systemd1.Snapshot", + .bus_message_handler = bus_snapshot_message_handler +}; diff --git a/src/core/snapshot.h b/src/core/snapshot.h new file mode 100644 index 0000000000..bf92e99a8b --- /dev/null +++ b/src/core/snapshot.h @@ -0,0 +1,53 @@ +/*-*- Mode: C; c-basic-offset: 8; indent-tabs-mode: nil -*-*/ + +#ifndef foosnapshothfoo +#define foosnapshothfoo + +/*** + This file is part of systemd. + + Copyright 2010 Lennart Poettering + + systemd is free software; you can redistribute it and/or modify it + under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + systemd is distributed in the hope that 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 General Public License + along with systemd; If not, see . +***/ + +typedef struct Snapshot Snapshot; + +#include "unit.h" + +typedef enum SnapshotState { + SNAPSHOT_DEAD, + SNAPSHOT_ACTIVE, + _SNAPSHOT_STATE_MAX, + _SNAPSHOT_STATE_INVALID = -1 +} SnapshotState; + +struct Snapshot { + Unit meta; + + SnapshotState state, deserialized_state; + + bool cleanup; + bool by_snapshot_create:1; +}; + +extern const UnitVTable snapshot_vtable; + +int snapshot_create(Manager *m, const char *name, bool cleanup, DBusError *e, Snapshot **s); +void snapshot_remove(Snapshot *s); + +const char* snapshot_state_to_string(SnapshotState i); +SnapshotState snapshot_state_from_string(const char *s); + +#endif diff --git a/src/core/socket.c b/src/core/socket.c new file mode 100644 index 0000000000..5b24b3422b --- /dev/null +++ b/src/core/socket.c @@ -0,0 +1,2218 @@ +/*-*- Mode: C; c-basic-offset: 8; indent-tabs-mode: nil -*-*/ + +/*** + This file is part of systemd. + + Copyright 2010 Lennart Poettering + + systemd is free software; you can redistribute it and/or modify it + under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + systemd is distributed in the hope that 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 General Public License + along with systemd; If not, see . +***/ + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "unit.h" +#include "socket.h" +#include "netinet/tcp.h" +#include "log.h" +#include "load-dropin.h" +#include "load-fragment.h" +#include "strv.h" +#include "mkdir.h" +#include "unit-name.h" +#include "dbus-socket.h" +#include "missing.h" +#include "special.h" +#include "bus-errors.h" +#include "label.h" +#include "exit-status.h" +#include "def.h" + +static const UnitActiveState state_translation_table[_SOCKET_STATE_MAX] = { + [SOCKET_DEAD] = UNIT_INACTIVE, + [SOCKET_START_PRE] = 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 void socket_init(Unit *u) { + Socket *s = SOCKET(u); + + assert(u); + assert(u->load_state == UNIT_STUB); + + s->backlog = SOMAXCONN; + s->timeout_usec = DEFAULT_TIMEOUT_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; + + exec_context_init(&s->exec_context); + s->exec_context.std_output = u->manager->default_std_output; + s->exec_context.std_error = u->manager->default_std_error; + + s->control_command_id = _SOCKET_EXEC_COMMAND_INVALID; +} + +static void socket_unwatch_control_pid(Socket *s) { + assert(s); + + if (s->control_pid <= 0) + return; + + unit_unwatch_pid(UNIT(s), s->control_pid); + s->control_pid = 0; +} + +static void socket_done(Unit *u) { + Socket *s = SOCKET(u); + SocketPort *p; + + assert(s); + + while ((p = s->ports)) { + LIST_REMOVE(SocketPort, port, s->ports, p); + + if (p->fd >= 0) { + unit_unwatch_fd(UNIT(s), &p->fd_watch); + close_nointr_nofail(p->fd); + } + + free(p->path); + free(p); + } + + exec_context_done(&s->exec_context); + 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); + + free(s->tcp_congestion); + s->tcp_congestion = NULL; + + free(s->bind_to_device); + s->bind_to_device = NULL; + + unit_unwatch_timer(u, &s->timer_watch); +} + +static int socket_instantiate_service(Socket *s) { + char *prefix, *name; + 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; + + assert(s->accept); + + if (!(prefix = unit_name_to_prefix(UNIT(s)->id))) + return -ENOMEM; + + r = asprintf(&name, "%s@%u.service", prefix, s->n_accepted); + free(prefix); + + if (r < 0) + return -ENOMEM; + + r = manager_load_unit(UNIT(s)->manager, name, NULL, NULL, &u); + free(name); + + if (r < 0) + return r; + +#ifdef HAVE_SYSV_COMPAT + if (SERVICE(u)->sysv_path) { + log_error("Using SysV services for socket activation is not supported. Refusing."); + return -ENOENT; + } +#endif + + u->no_gc = true; + unit_ref_set(&s->service, u); + + return unit_add_two_dependencies(UNIT(s), UNIT_BEFORE, UNIT_TRIGGERS, u, false); +} + +static bool have_non_accept_socket(Socket *s) { + SocketPort *p; + + assert(s); + + if (!s->accept) + return true; + + LIST_FOREACH(port, p, s->ports) { + + if (p->type != SOCKET_SOCKET) + return true; + + if (!socket_address_can_accept(&p->address)) + return true; + } + + return false; +} + +static int socket_verify(Socket *s) { + assert(s); + + if (UNIT(s)->load_state != UNIT_LOADED) + return 0; + + if (!s->ports) { + log_error("%s lacks Listen setting. Refusing.", UNIT(s)->id); + return -EINVAL; + } + + if (s->accept && have_non_accept_socket(s)) { + log_error("%s configured for accepting sockets, but sockets are non-accepting. Refusing.", UNIT(s)->id); + return -EINVAL; + } + + if (s->accept && s->max_connections <= 0) { + log_error("%s's MaxConnection setting too small. Refusing.", UNIT(s)->id); + return -EINVAL; + } + + if (s->accept && UNIT_DEREF(s->service)) { + log_error("Explicit service configuration for accepting sockets not supported on %s. Refusing.", UNIT(s)->id); + return -EINVAL; + } + + if (s->exec_context.pam_name && s->exec_context.kill_mode != KILL_CONTROL_GROUP) { + log_error("%s has PAM enabled. Kill mode must be set to 'control-group'. Refusing.", UNIT(s)->id); + return -EINVAL; + } + + return 0; +} + +static bool socket_needs_mount(Socket *s, const char *prefix) { + SocketPort *p; + + assert(s); + + LIST_FOREACH(port, p, s->ports) { + + if (p->type == SOCKET_SOCKET) { + if (socket_address_needs_mount(&p->address, prefix)) + return true; + } else if (p->type == SOCKET_FIFO || p->type == SOCKET_SPECIAL) { + if (path_startswith(p->path, prefix)) + return true; + } + } + + return false; +} + +int socket_add_one_mount_link(Socket *s, Mount *m) { + int r; + + assert(s); + assert(m); + + if (UNIT(s)->load_state != UNIT_LOADED || + UNIT(m)->load_state != UNIT_LOADED) + return 0; + + if (!socket_needs_mount(s, m->where)) + return 0; + + if ((r = unit_add_two_dependencies(UNIT(s), UNIT_AFTER, UNIT_REQUIRES, UNIT(m), true)) < 0) + return r; + + return 0; +} + +static int socket_add_mount_links(Socket *s) { + Unit *other; + int r; + + assert(s); + + LIST_FOREACH(units_by_type, other, UNIT(s)->manager->units_by_type[UNIT_MOUNT]) + if ((r = socket_add_one_mount_link(s, MOUNT(other))) < 0) + return r; + + return 0; +} + +static int socket_add_device_link(Socket *s) { + char *t; + int r; + + assert(s); + + if (!s->bind_to_device) + return 0; + + if (asprintf(&t, "/sys/subsystem/net/devices/%s", s->bind_to_device) < 0) + return -ENOMEM; + + r = unit_add_node_link(UNIT(s), t, false); + free(t); + + return r; +} + +static int socket_add_default_dependencies(Socket *s) { + int r; + assert(s); + + if (UNIT(s)->manager->running_as == MANAGER_SYSTEM) { + if ((r = unit_add_dependency_by_name(UNIT(s), UNIT_BEFORE, SPECIAL_SOCKETS_TARGET, NULL, true)) < 0) + return r; + + if ((r = unit_add_two_dependencies_by_name(UNIT(s), UNIT_AFTER, UNIT_REQUIRES, SPECIAL_SYSINIT_TARGET, NULL, true)) < 0) + return r; + } + + return unit_add_two_dependencies_by_name(UNIT(s), UNIT_BEFORE, UNIT_CONFLICTS, SPECIAL_SHUTDOWN_TARGET, NULL, true); +} + +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_load(Unit *u) { + Socket *s = SOCKET(u); + int r; + + assert(u); + assert(u->load_state == UNIT_STUB); + + if ((r = unit_load_fragment_and_dropin(u)) < 0) + return r; + + /* This is a new unit? Then let's add in some extras */ + if (u->load_state == UNIT_LOADED) { + + 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; + } + + if ((r = socket_add_mount_links(s)) < 0) + return r; + + if ((r = socket_add_device_link(s)) < 0) + return r; + + if (socket_has_exec(s)) + if ((r = unit_add_exec_dependencies(u, &s->exec_context)) < 0) + return r; + + if ((r = unit_add_default_cgroups(u)) < 0) + return r; + + if (UNIT(s)->default_dependencies) + if ((r = socket_add_default_dependencies(s)) < 0) + return r; + } + + return socket_verify(s); +} + +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) { + + SocketExecCommand c; + Socket *s = SOCKET(u); + SocketPort *p; + const char *prefix2; + char *p2; + + assert(s); + assert(f); + + p2 = strappend(prefix, "\t"); + prefix2 = p2 ? p2 : prefix; + + fprintf(f, + "%sSocket State: %s\n" + "%sResult: %s\n" + "%sBindIPv6Only: %s\n" + "%sBacklog: %u\n" + "%sSocketMode: %04o\n" + "%sDirectoryMode: %04o\n" + "%sKeepAlive: %s\n" + "%sFreeBind: %s\n" + "%sTransparent: %s\n" + "%sBroadcast: %s\n" + "%sPassCredentials: %s\n" + "%sPassSecurity: %s\n" + "%sTCPCongestion: %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->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)); + + if (s->control_pid > 0) + fprintf(f, + "%sControl PID: %lu\n", + prefix, (unsigned long) 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); + + LIST_FOREACH(port, p, s->ports) { + + if (p->type == SOCKET_SOCKET) { + const char *t; + int r; + char *k = NULL; + + if ((r = socket_address_print(&p->address, &k)) < 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_MQUEUE) + fprintf(f, "%sListenMessageQueue: %s\n", prefix, p->path); + else + fprintf(f, "%sListenFIFO: %s\n", prefix, p->path); + } + + exec_context_dump(&s->exec_context, f, prefix); + + 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); + } + + free(p2); +} + +static int instance_from_socket(int fd, unsigned nr, char **instance) { + socklen_t l; + char *r; + union { + struct sockaddr sa; + struct sockaddr_un un; + struct sockaddr_in in; + struct sockaddr_in6 in6; + struct sockaddr_storage storage; + } 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 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; + + l = sizeof(ucred); + if (getsockopt(fd, SOL_SOCKET, SO_PEERCRED, &ucred, &l) < 0) + return -errno; + + if (asprintf(&r, + "%u-%lu-%lu", + nr, + (unsigned long) ucred.pid, + (unsigned long) ucred.uid) < 0) + return -ENOMEM; + + break; + } + + default: + assert_not_reached("Unhandled socket type."); + } + + *instance = r; + return 0; +} + +static void socket_close_fds(Socket *s) { + SocketPort *p; + + assert(s); + + LIST_FOREACH(port, p, s->ports) { + if (p->fd < 0) + continue; + + unit_unwatch_fd(UNIT(s), &p->fd_watch); + close_nointr_nofail(p->fd); + + /* One little note: we should never delete any sockets + * in the file system here! After all some other + * process we spawned might still have a reference of + * this fd and wants to continue to use it. Therefore + * we delete sockets in the file system before we + * create a new one, not after we stopped using + * one! */ + + p->fd = -1; + } +} + +static void socket_apply_socket_options(Socket *s, int fd) { + 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_warning("SO_KEEPALIVE failed: %m"); + } + + if (s->broadcast) { + int one = 1; + if (setsockopt(fd, SOL_SOCKET, SO_BROADCAST, &one, sizeof(one)) < 0) + log_warning("SO_BROADCAST failed: %m"); + } + + if (s->pass_cred) { + int one = 1; + if (setsockopt(fd, SOL_SOCKET, SO_PASSCRED, &one, sizeof(one)) < 0) + log_warning("SO_PASSCRED failed: %m"); + } + + if (s->pass_sec) { + int one = 1; + if (setsockopt(fd, SOL_SOCKET, SO_PASSSEC, &one, sizeof(one)) < 0) + log_warning("SO_PASSSEC failed: %m"); + } + + if (s->priority >= 0) + if (setsockopt(fd, SOL_SOCKET, SO_PRIORITY, &s->priority, sizeof(s->priority)) < 0) + log_warning("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_warning("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_warning("SO_SNDBUF failed: %m"); + } + + if (s->mark >= 0) + if (setsockopt(fd, SOL_SOCKET, SO_MARK, &s->mark, sizeof(s->mark)) < 0) + log_warning("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_warning("IP_TOS failed: %m"); + + if (s->ip_ttl >= 0) { + int r, 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_warning("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_warning("TCP_CONGESTION failed: %m"); +} + +static void socket_apply_fifo_options(Socket *s, int fd) { + assert(s); + assert(fd >= 0); + + if (s->pipe_size > 0) + if (fcntl(fd, F_SETPIPE_SZ, s->pipe_size) < 0) + log_warning("F_SETPIPE_SZ: %m"); +} + +static int fifo_address_create( + const char *path, + mode_t directory_mode, + mode_t socket_mode, + int *_fd) { + + int fd = -1, r = 0; + struct stat st; + mode_t old_mask; + + assert(path); + assert(_fd); + + mkdir_parents(path, directory_mode); + + if ((r = label_fifofile_set(path)) < 0) + goto fail; + + /* Enforce the right access mode for the fifo */ + old_mask = umask(~ socket_mode); + + /* Include the original umask in our mask */ + umask(~socket_mode | old_mask); + + r = mkfifo(path, socket_mode); + umask(old_mask); + + if (r < 0 && errno != EEXIST) { + r = -errno; + goto fail; + } + + if ((fd = open(path, O_RDWR|O_CLOEXEC|O_NOCTTY|O_NONBLOCK|O_NOFOLLOW)) < 0) { + r = -errno; + goto fail; + } + + label_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; + } + + *_fd = fd; + return 0; + +fail: + label_file_clear(); + + if (fd >= 0) + close_nointr_nofail(fd); + + return r; +} + +static int special_address_create( + const char *path, + int *_fd) { + + int fd = -1, r = 0; + struct stat st; + + assert(path); + assert(_fd); + + if ((fd = open(path, O_RDONLY|O_CLOEXEC|O_NOCTTY|O_NONBLOCK|O_NOFOLLOW)) < 0) { + r = -errno; + goto fail; + } + + if (fstat(fd, &st) < 0) { + r = -errno; + goto fail; + } + + /* Check whether this is a /proc, /sys or /dev file or char device */ + if (!S_ISREG(st.st_mode) && !S_ISCHR(st.st_mode)) { + r = -EEXIST; + goto fail; + } + + *_fd = fd; + return 0; + +fail: + if (fd >= 0) + close_nointr_nofail(fd); + + return r; +} + +static int mq_address_create( + const char *path, + mode_t mq_mode, + long maxmsg, + long msgsize, + int *_fd) { + + int fd = -1, r = 0; + struct stat st; + mode_t old_mask; + struct mq_attr _attr, *attr = NULL; + + assert(path); + assert(_fd); + + if (maxmsg > 0 && msgsize > 0) { + zero(_attr); + _attr.mq_flags = O_NONBLOCK; + _attr.mq_maxmsg = maxmsg; + _attr.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 */ + umask(~mq_mode | old_mask); + + fd = mq_open(path, O_RDONLY|O_CLOEXEC|O_NONBLOCK|O_CREAT, mq_mode, attr); + umask(old_mask); + + if (fd < 0) { + r = -errno; + goto fail; + } + + if (fstat(fd, &st) < 0) { + r = -errno; + goto fail; + } + + if ((st.st_mode & 0777) != (mq_mode & ~old_mask) || + st.st_uid != getuid() || + st.st_gid != getgid()) { + + r = -EEXIST; + goto fail; + } + + *_fd = fd; + return 0; + +fail: + if (fd >= 0) + close_nointr_nofail(fd); + + return r; +} + +static int socket_open_fds(Socket *s) { + SocketPort *p; + int r; + char *label = NULL; + bool know_label = false; + + assert(s); + + LIST_FOREACH(port, p, s->ports) { + + if (p->fd >= 0) + continue; + + if (p->type == SOCKET_SOCKET) { + + if (!know_label) { + + if ((r = socket_instantiate_service(s)) < 0) + return r; + + if (UNIT_DEREF(s->service) && + SERVICE(UNIT_DEREF(s->service))->exec_command[SERVICE_EXEC_START]) { + r = label_get_create_label_from_exe(SERVICE(UNIT_DEREF(s->service))->exec_command[SERVICE_EXEC_START]->path, &label); + + if (r < 0) { + if (r != -EPERM) + return r; + } + } + + know_label = true; + } + + if ((r = socket_address_listen( + &p->address, + s->backlog, + s->bind_ipv6_only, + s->bind_to_device, + s->free_bind, + s->transparent, + s->directory_mode, + s->socket_mode, + label, + &p->fd)) < 0) + goto rollback; + + socket_apply_socket_options(s, p->fd); + + } else if (p->type == SOCKET_SPECIAL) { + + if ((r = special_address_create( + p->path, + &p->fd)) < 0) + goto rollback; + + } else if (p->type == SOCKET_FIFO) { + + if ((r = fifo_address_create( + p->path, + s->directory_mode, + s->socket_mode, + &p->fd)) < 0) + goto rollback; + + socket_apply_fifo_options(s, p->fd); + } else if (p->type == SOCKET_MQUEUE) { + + if ((r = mq_address_create( + p->path, + s->socket_mode, + s->mq_maxmsg, + s->mq_msgsize, + &p->fd)) < 0) + goto rollback; + } else + assert_not_reached("Unknown port type"); + } + + label_free(label); + return 0; + +rollback: + socket_close_fds(s); + label_free(label); + return r; +} + +static void socket_unwatch_fds(Socket *s) { + SocketPort *p; + + assert(s); + + LIST_FOREACH(port, p, s->ports) { + if (p->fd < 0) + continue; + + unit_unwatch_fd(UNIT(s), &p->fd_watch); + } +} + +static int socket_watch_fds(Socket *s) { + SocketPort *p; + int r; + + assert(s); + + LIST_FOREACH(port, p, s->ports) { + if (p->fd < 0) + continue; + + p->fd_watch.socket_accept = + s->accept && + p->type == SOCKET_SOCKET && + socket_address_can_accept(&p->address); + + if ((r = unit_watch_fd(UNIT(s), p->fd, EPOLLIN, &p->fd_watch)) < 0) + goto fail; + } + + return 0; + +fail: + socket_unwatch_fds(s); + return r; +} + +static void socket_set_state(Socket *s, SocketState state) { + SocketState old_state; + assert(s); + + old_state = s->state; + s->state = state; + + if (state != SOCKET_START_PRE && + state != SOCKET_START_POST && + state != SOCKET_STOP_PRE && + state != SOCKET_STOP_PRE_SIGTERM && + state != SOCKET_STOP_PRE_SIGKILL && + state != SOCKET_STOP_POST && + state != SOCKET_FINAL_SIGTERM && + state != SOCKET_FINAL_SIGKILL) { + unit_unwatch_timer(UNIT(s), &s->timer_watch); + 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 (state != SOCKET_START_POST && + state != SOCKET_LISTENING && + state != SOCKET_RUNNING && + state != SOCKET_STOP_PRE && + state != SOCKET_STOP_PRE_SIGTERM && + state != SOCKET_STOP_PRE_SIGKILL) + socket_close_fds(s); + + if (state != old_state) + log_debug("%s changed %s -> %s", + UNIT(s)->id, + 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) { + + if (s->deserialized_state == SOCKET_START_PRE || + s->deserialized_state == SOCKET_START_POST || + s->deserialized_state == SOCKET_STOP_PRE || + s->deserialized_state == SOCKET_STOP_PRE_SIGTERM || + s->deserialized_state == SOCKET_STOP_PRE_SIGKILL || + s->deserialized_state == SOCKET_STOP_POST || + s->deserialized_state == SOCKET_FINAL_SIGTERM || + s->deserialized_state == SOCKET_FINAL_SIGKILL) { + + if (s->control_pid <= 0) + return -EBADMSG; + + if ((r = unit_watch_pid(UNIT(s), s->control_pid)) < 0) + return r; + + if ((r = unit_watch_timer(UNIT(s), s->timeout_usec, &s->timer_watch)) < 0) + return r; + } + + if (s->deserialized_state == SOCKET_START_POST || + s->deserialized_state == SOCKET_LISTENING || + s->deserialized_state == SOCKET_RUNNING || + s->deserialized_state == SOCKET_STOP_PRE || + s->deserialized_state == SOCKET_STOP_PRE_SIGTERM || + s->deserialized_state == SOCKET_STOP_PRE_SIGKILL) + if ((r = socket_open_fds(s)) < 0) + return r; + + if (s->deserialized_state == SOCKET_LISTENING) + if ((r = socket_watch_fds(s)) < 0) + return r; + + socket_set_state(s, s->deserialized_state); + } + + return 0; +} + +static int socket_spawn(Socket *s, ExecCommand *c, pid_t *_pid) { + pid_t pid; + int r; + char **argv; + + assert(s); + assert(c); + assert(_pid); + + if ((r = unit_watch_timer(UNIT(s), s->timeout_usec, &s->timer_watch)) < 0) + goto fail; + + if (!(argv = unit_full_printf_strv(UNIT(s), c->argv))) { + r = -ENOMEM; + goto fail; + } + + r = exec_spawn(c, + argv, + &s->exec_context, + NULL, 0, + UNIT(s)->manager->environment, + true, + true, + true, + UNIT(s)->manager->confirm_spawn, + UNIT(s)->cgroup_bondings, + UNIT(s)->cgroup_attributes, + &pid); + + strv_free(argv); + if (r < 0) + goto fail; + + if ((r = unit_watch_pid(UNIT(s), pid)) < 0) + /* FIXME: we need to do something here */ + goto fail; + + *_pid = pid; + + return 0; + +fail: + unit_unwatch_timer(UNIT(s), &s->timer_watch); + + return r; +} + +static void socket_enter_dead(Socket *s, SocketResult f) { + assert(s); + + if (f != SOCKET_SUCCESS) + s->result = f; + + 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; + + if ((s->control_command = s->exec_command[SOCKET_EXEC_STOP_POST])) { + if ((r = socket_spawn(s, s->control_command, &s->control_pid)) < 0) + goto fail; + + socket_set_state(s, SOCKET_STOP_POST); + } else + socket_enter_signal(s, SOCKET_FINAL_SIGTERM, SOCKET_SUCCESS); + + return; + +fail: + log_warning("%s failed to run 'stop-post' task: %s", UNIT(s)->id, strerror(-r)); + socket_enter_signal(s, SOCKET_FINAL_SIGTERM, SOCKET_FAILURE_RESOURCES); +} + +static void socket_enter_signal(Socket *s, SocketState state, SocketResult f) { + int r; + Set *pid_set = NULL; + bool wait_for_exit = false; + + assert(s); + + if (f != SOCKET_SUCCESS) + s->result = f; + + if (s->exec_context.kill_mode != KILL_NONE) { + int sig = (state == SOCKET_STOP_PRE_SIGTERM || state == SOCKET_FINAL_SIGTERM) ? s->exec_context.kill_signal : SIGKILL; + + if (s->control_pid > 0) { + if (kill_and_sigcont(s->control_pid, sig) < 0 && errno != ESRCH) + + log_warning("Failed to kill control process %li: %m", (long) s->control_pid); + else + wait_for_exit = true; + } + + if (s->exec_context.kill_mode == KILL_CONTROL_GROUP) { + + if (!(pid_set = set_new(trivial_hash_func, trivial_compare_func))) { + r = -ENOMEM; + goto fail; + } + + /* Exclude the control pid from being killed via the cgroup */ + if (s->control_pid > 0) + if ((r = set_put(pid_set, LONG_TO_PTR(s->control_pid))) < 0) + goto fail; + + if ((r = cgroup_bonding_kill_list(UNIT(s)->cgroup_bondings, sig, true, pid_set)) < 0) { + if (r != -EAGAIN && r != -ESRCH && r != -ENOENT) + log_warning("Failed to kill control group: %s", strerror(-r)); + } else if (r > 0) + wait_for_exit = true; + + set_free(pid_set); + pid_set = NULL; + } + } + + if (wait_for_exit) { + if ((r = unit_watch_timer(UNIT(s), s->timeout_usec, &s->timer_watch)) < 0) + goto fail; + + socket_set_state(s, state); + } else if (state == SOCKET_STOP_PRE_SIGTERM || state == SOCKET_STOP_PRE_SIGKILL) + socket_enter_stop_post(s, SOCKET_SUCCESS); + else + socket_enter_dead(s, SOCKET_SUCCESS); + + return; + +fail: + log_warning("%s failed to kill processes: %s", UNIT(s)->id, strerror(-r)); + + 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); + + if (pid_set) + set_free(pid_set); +} + +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; + + if ((s->control_command = s->exec_command[SOCKET_EXEC_STOP_PRE])) { + if ((r = socket_spawn(s, s->control_command, &s->control_pid)) < 0) + goto fail; + + socket_set_state(s, SOCKET_STOP_PRE); + } else + socket_enter_stop_post(s, SOCKET_SUCCESS); + + return; + +fail: + log_warning("%s failed to run 'stop-pre' task: %s", UNIT(s)->id, strerror(-r)); + 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_warning("%s failed to watch sockets: %s", UNIT(s)->id, strerror(-r)); + 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); + + r = socket_open_fds(s); + if (r < 0) { + log_warning("%s failed to listen on sockets: %s", UNIT(s)->id, strerror(-r)); + goto fail; + } + + socket_unwatch_control_pid(s); + + s->control_command_id = SOCKET_EXEC_START_POST; + + if ((s->control_command = s->exec_command[SOCKET_EXEC_START_POST])) { + r = socket_spawn(s, s->control_command, &s->control_pid); + if (r < 0) { + log_warning("%s failed to run 'start-post' task: %s", UNIT(s)->id, strerror(-r)); + 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_pre(Socket *s) { + int r; + assert(s); + + socket_unwatch_control_pid(s); + + s->control_command_id = SOCKET_EXEC_START_PRE; + + if ((s->control_command = s->exec_command[SOCKET_EXEC_START_PRE])) { + if ((r = socket_spawn(s, s->control_command, &s->control_pid)) < 0) + goto fail; + + socket_set_state(s, SOCKET_START_PRE); + } else + socket_enter_start_post(s); + + return; + +fail: + log_warning("%s failed to run 'start-pre' task: %s", UNIT(s)->id, strerror(-r)); + socket_enter_dead(s, SOCKET_FAILURE_RESOURCES); +} + +static void socket_enter_running(Socket *s, int cfd) { + int r; + DBusError error; + + assert(s); + dbus_error_init(&error); + + /* We don't take connections anymore if we are supposed to + * shut down anyway */ + if (unit_pending_inactive(UNIT(s))) { + log_debug("Suppressing connection request on %s since unit stop is scheduled.", UNIT(s)->id); + + if (cfd >= 0) + close_nointr_nofail(cfd); + else { + /* Flush all sockets by closing and reopening them */ + socket_close_fds(s); + + r = socket_watch_fds(s); + if (r < 0) { + log_warning("%s failed to watch sockets: %s", UNIT(s)->id, strerror(-r)); + socket_enter_stop_pre(s, SOCKET_FAILURE_RESOURCES); + } + } + + return; + } + + if (cfd < 0) { + Iterator i; + Unit *u; + bool pending = false; + + /* If there's already a start pending don't bother to + * do anything */ + SET_FOREACH(u, UNIT(s)->dependencies[UNIT_TRIGGERS], i) + if (unit_pending_active(u)) { + pending = true; + break; + } + + if (!pending) { + r = manager_add_job(UNIT(s)->manager, JOB_START, UNIT_DEREF(s->service), JOB_REPLACE, true, &error, NULL); + if (r < 0) + goto fail; + } + + socket_set_state(s, SOCKET_RUNNING); + } else { + char *prefix, *instance = NULL, *name; + Service *service; + + if (s->n_connections >= s->max_connections) { + log_warning("Too many incoming connections (%u)", s->n_connections); + close_nointr_nofail(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. */ + close_nointr_nofail(cfd); + return; + } + + prefix = unit_name_to_prefix(UNIT(s)->id); + if (!prefix) { + free(instance); + r = -ENOMEM; + goto fail; + } + + name = unit_name_build(prefix, instance, ".service"); + free(prefix); + free(instance); + + if (!name) { + r = -ENOMEM; + goto fail; + } + + r = unit_add_name(UNIT_DEREF(s->service), name); + if (r < 0) { + free(name); + goto fail; + } + + service = SERVICE(UNIT_DEREF(s->service)); + unit_ref_unset(&s->service); + s->n_accepted ++; + + UNIT(service)->no_gc = false; + + unit_choose_id(UNIT(service), name); + free(name); + + r = service_set_socket_fd(service, cfd, s); + if (r < 0) + goto fail; + + cfd = -1; + s->n_connections ++; + + r = manager_add_job(UNIT(s)->manager, JOB_START, UNIT(service), JOB_REPLACE, true, &error, NULL); + if (r < 0) + goto fail; + + /* Notify clients about changed counters */ + unit_add_to_dbus_queue(UNIT(s)); + } + + return; + +fail: + log_warning("%s failed to queue socket startup job: %s", UNIT(s)->id, bus_error(&error, r)); + socket_enter_stop_pre(s, SOCKET_FAILURE_RESOURCES); + + if (cfd >= 0) + close_nointr_nofail(cfd); + + dbus_error_free(&error); +} + +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; + + if ((r = socket_spawn(s, s->control_command, &s->control_pid)) < 0) + goto fail; + + return; + +fail: + log_warning("%s failed to run next task: %s", UNIT(s)->id, strerror(-r)); + + if (s->state == SOCKET_START_POST) + socket_enter_stop_pre(s, SOCKET_FAILURE_RESOURCES); + else if (s->state == SOCKET_STOP_POST) + socket_enter_dead(s, SOCKET_FAILURE_RESOURCES); + else + socket_enter_signal(s, SOCKET_FINAL_SIGTERM, SOCKET_FAILURE_RESOURCES); +} + +static int socket_start(Unit *u) { + Socket *s = SOCKET(u); + + assert(s); + + /* We cannot fulfill this request right now, try again later + * please! */ + if (s->state == SOCKET_STOP_PRE || + s->state == SOCKET_STOP_PRE_SIGKILL || + s->state == SOCKET_STOP_PRE_SIGTERM || + s->state == SOCKET_STOP_POST || + s->state == SOCKET_FINAL_SIGTERM || + s->state == SOCKET_FINAL_SIGKILL) + return -EAGAIN; + + if (s->state == SOCKET_START_PRE || + s->state == SOCKET_START_POST) + return 0; + + /* Cannot run this without the service being around */ + if (UNIT_DEREF(s->service)) { + Service *service; + + service = SERVICE(UNIT_DEREF(s->service)); + + if (UNIT(service)->load_state != UNIT_LOADED) { + log_error("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_error("Socket service %s already active, refusing.", UNIT(service)->id); + return -EBUSY; + } + +#ifdef HAVE_SYSV_COMPAT + if (service->sysv_path) { + log_error("Using SysV services for socket activation is not supported. Refusing."); + return -ENOENT; + } +#endif + } + + assert(s->state == SOCKET_DEAD || s->state == SOCKET_FAILED); + + s->result = SOCKET_SUCCESS; + socket_enter_start_pre(s); + return 0; +} + +static int socket_stop(Unit *u) { + Socket *s = SOCKET(u); + + assert(s); + + /* Already on it */ + if (s->state == SOCKET_STOP_PRE || + s->state == SOCKET_STOP_PRE_SIGTERM || + s->state == SOCKET_STOP_PRE_SIGKILL || + s->state == SOCKET_STOP_POST || + s->state == SOCKET_FINAL_SIGTERM || + s->state == SOCKET_FINAL_SIGKILL) + return 0; + + /* If there's already something running we go directly into + * kill mode. */ + if (s->state == SOCKET_START_PRE || + s->state == 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 0; +} + +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", "%lu", (unsigned long) 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; + + if ((copy = fdset_put_dup(fds, p->fd)) < 0) + return copy; + + if (p->type == SOCKET_SOCKET) { + char *t; + + if ((r = socket_address_print(&p->address, &t)) < 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); + free(t); + } else if (p->type == SOCKET_SPECIAL) + unit_serialize_item_format(u, f, "special", "%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); + assert(fds); + + if (streq(key, "state")) { + SocketState state; + + if ((state = socket_state_from_string(value)) < 0) + log_debug("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_debug("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_debug("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_debug("Failed to parse control-pid value %s", value); + else + s->control_pid = pid; + } else if (streq(key, "control-command")) { + SocketExecCommand id; + + if ((id = socket_exec_command_from_string(value)) < 0) + log_debug("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_debug("Failed to parse fifo value %s", value); + else { + + LIST_FOREACH(port, p, s->ports) + if (p->type == SOCKET_FIFO && + streq_ptr(p->path, value+skip)) + break; + + if (p) { + if (p->fd >= 0) + close_nointr_nofail(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_debug("Failed to parse special value %s", value); + else { + + LIST_FOREACH(port, p, s->ports) + if (p->type == SOCKET_SPECIAL && + streq_ptr(p->path, value+skip)) + break; + + if (p) { + if (p->fd >= 0) + close_nointr_nofail(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_debug("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) { + if (p->fd >= 0) + close_nointr_nofail(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_debug("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) { + if (p->fd >= 0) + close_nointr_nofail(p->fd); + p->fd = fdset_remove(fds, fd); + } + } + + } else + log_debug("Unknown serialization key '%s'", key); + + return 0; +} + +static UnitActiveState socket_active_state(Unit *u) { + assert(u); + + return state_translation_table[SOCKET(u)->state]; +} + +static const char *socket_sub_state_to_string(Unit *u) { + assert(u); + + return socket_state_to_string(SOCKET(u)->state); +} + +static bool socket_check_gc(Unit *u) { + Socket *s = SOCKET(u); + + assert(u); + + return s->n_connections > 0; +} + +static void socket_fd_event(Unit *u, int fd, uint32_t events, Watch *w) { + Socket *s = SOCKET(u); + int cfd = -1; + + assert(s); + assert(fd >= 0); + + if (s->state != SOCKET_LISTENING) + return; + + log_debug("Incoming traffic on %s", u->id); + + if (events != EPOLLIN) { + + if (events & EPOLLHUP) + log_error("%s: Got POLLHUP on a listening socket. The service probably invoked shutdown() on it, and should better not do that.", u->id); + else + log_error("%s: Got unexpected poll event (0x%x) on socket.", u->id, events); + + goto fail; + } + + if (w->socket_accept) { + for (;;) { + + if ((cfd = accept4(fd, NULL, NULL, SOCK_NONBLOCK)) < 0) { + + if (errno == EINTR) + continue; + + log_error("Failed to accept socket: %m"); + goto fail; + } + + break; + } + + socket_apply_socket_options(s, cfd); + } + + socket_enter_running(s, cfd); + return; + +fail: + socket_enter_stop_pre(s, SOCKET_FAILURE_RESOURCES); +} + +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)) + 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 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_full(f == SOCKET_SUCCESS ? LOG_DEBUG : LOG_NOTICE, + "%s control process exited, code=%s status=%i", u->id, 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_debug("%s running next command for state %s", u->id, 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_debug("%s got final SIGCHLD for state %s", u->id, socket_state_to_string(s->state)); + + switch (s->state) { + + case SOCKET_START_PRE: + if (f == SOCKET_SUCCESS) + socket_enter_start_post(s); + else + socket_enter_signal(s, SOCKET_FINAL_SIGTERM, 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 void socket_timer_event(Unit *u, uint64_t elapsed, Watch *w) { + Socket *s = SOCKET(u); + + assert(s); + assert(elapsed == 1); + assert(w == &s->timer_watch); + + switch (s->state) { + + case SOCKET_START_PRE: + log_warning("%s starting timed out. Terminating.", u->id); + socket_enter_signal(s, SOCKET_FINAL_SIGTERM, SOCKET_FAILURE_TIMEOUT); + break; + + case SOCKET_START_POST: + log_warning("%s starting timed out. Stopping.", u->id); + socket_enter_stop_pre(s, SOCKET_FAILURE_TIMEOUT); + break; + + case SOCKET_STOP_PRE: + log_warning("%s stopping timed out. Terminating.", u->id); + socket_enter_signal(s, SOCKET_STOP_PRE_SIGTERM, SOCKET_FAILURE_TIMEOUT); + break; + + case SOCKET_STOP_PRE_SIGTERM: + if (s->exec_context.send_sigkill) { + log_warning("%s stopping timed out. Killing.", u->id); + socket_enter_signal(s, SOCKET_STOP_PRE_SIGKILL, SOCKET_FAILURE_TIMEOUT); + } else { + log_warning("%s stopping timed out. Skipping SIGKILL. Ignoring.", u->id); + socket_enter_stop_post(s, SOCKET_FAILURE_TIMEOUT); + } + break; + + case SOCKET_STOP_PRE_SIGKILL: + log_warning("%s still around after SIGKILL. Ignoring.", u->id); + socket_enter_stop_post(s, SOCKET_FAILURE_TIMEOUT); + break; + + case SOCKET_STOP_POST: + log_warning("%s stopping timed out (2). Terminating.", u->id); + socket_enter_signal(s, SOCKET_FINAL_SIGTERM, SOCKET_FAILURE_TIMEOUT); + break; + + case SOCKET_FINAL_SIGTERM: + if (s->exec_context.send_sigkill) { + log_warning("%s stopping timed out (2). Killing.", u->id); + socket_enter_signal(s, SOCKET_FINAL_SIGKILL, SOCKET_FAILURE_TIMEOUT); + } else { + log_warning("%s stopping timed out (2). Skipping SIGKILL. Ignoring.", u->id); + socket_enter_dead(s, SOCKET_FAILURE_TIMEOUT); + } + break; + + case SOCKET_FINAL_SIGKILL: + log_warning("%s still around after SIGKILL (2). Entering failed mode.", u->id); + socket_enter_dead(s, SOCKET_FAILURE_TIMEOUT); + break; + + default: + assert_not_reached("Timeout at wrong time."); + } +} + +int socket_collect_fds(Socket *s, int **fds, unsigned *n_fds) { + int *rfds; + unsigned rn_fds, k; + SocketPort *p; + + assert(s); + assert(fds); + assert(n_fds); + + /* Called from the service code for requesting our fds */ + + rn_fds = 0; + LIST_FOREACH(port, p, s->ports) + if (p->fd >= 0) + rn_fds++; + + if (rn_fds <= 0) { + *fds = NULL; + *n_fds = 0; + return 0; + } + + if (!(rfds = new(int, rn_fds))) + return -ENOMEM; + + k = 0; + LIST_FOREACH(port, p, s->ports) + if (p->fd >= 0) + rfds[k++] = p->fd; + + assert(k == rn_fds); + + *fds = rfds; + *n_fds = rn_fds; + + return 0; +} + +void socket_notify_service_dead(Socket *s, bool failed_permanent) { + assert(s); + + /* The service is dead. Dang! + * + * This is strictly for one-instance-for-all-connections + * services. */ + + if (s->state == SOCKET_RUNNING) { + log_debug("%s got notified about service death (failed permanently: %s)", UNIT(s)->id, yes_no(failed_permanent)); + if (failed_permanent) + socket_enter_stop_pre(s, SOCKET_FAILURE_SERVICE_FAILED_PERMANENT); + else + socket_enter_listening(s); + } +} + +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_debug("%s: One connection closed, %u left.", UNIT(s)->id, s->n_connections); +} + +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; +} + +static int socket_kill(Unit *u, KillWho who, KillMode mode, int signo, DBusError *error) { + Socket *s = SOCKET(u); + int r = 0; + Set *pid_set = NULL; + + assert(s); + + if (who == KILL_MAIN) { + dbus_set_error(error, BUS_ERROR_NO_SUCH_PROCESS, "Socket units have no main processes"); + return -ESRCH; + } + + if (s->control_pid <= 0 && who == KILL_CONTROL) { + dbus_set_error(error, BUS_ERROR_NO_SUCH_PROCESS, "No control process to kill"); + return -ESRCH; + } + + if (who == KILL_CONTROL || who == KILL_ALL) + if (s->control_pid > 0) + if (kill(s->control_pid, signo) < 0) + r = -errno; + + if (who == KILL_ALL && mode == KILL_CONTROL_GROUP) { + int q; + + if (!(pid_set = set_new(trivial_hash_func, trivial_compare_func))) + return -ENOMEM; + + /* Exclude the control pid from being killed via the cgroup */ + if (s->control_pid > 0) + if ((q = set_put(pid_set, LONG_TO_PTR(s->control_pid))) < 0) { + r = q; + goto finish; + } + + if ((q = cgroup_bonding_kill_list(UNIT(s)->cgroup_bondings, signo, false, pid_set)) < 0) + if (q != -EAGAIN && q != -ESRCH && q != -ENOENT) + r = q; + } + +finish: + if (pid_set) + set_free(pid_set); + + return r; +} + +static const char* const socket_state_table[_SOCKET_STATE_MAX] = { + [SOCKET_DEAD] = "dead", + [SOCKET_START_PRE] = "start-pre", + [SOCKET_START_POST] = "start-post", + [SOCKET_LISTENING] = "listening", + [SOCKET_RUNNING] = "running", + [SOCKET_STOP_PRE] = "stop-pre", + [SOCKET_STOP_PRE_SIGTERM] = "stop-pre-sigterm", + [SOCKET_STOP_PRE_SIGKILL] = "stop-pre-sigkill", + [SOCKET_STOP_POST] = "stop-post", + [SOCKET_FINAL_SIGTERM] = "final-sigterm", + [SOCKET_FINAL_SIGKILL] = "final-sigkill", + [SOCKET_FAILED] = "failed" +}; + +DEFINE_STRING_TABLE_LOOKUP(socket_state, SocketState); + +static const char* const socket_exec_command_table[_SOCKET_EXEC_COMMAND_MAX] = { + [SOCKET_EXEC_START_PRE] = "StartPre", + [SOCKET_EXEC_START_POST] = "StartPost", + [SOCKET_EXEC_STOP_PRE] = "StopPre", + [SOCKET_EXEC_STOP_POST] = "StopPost" +}; + +DEFINE_STRING_TABLE_LOOKUP(socket_exec_command, SocketExecCommand); + +static const char* const socket_result_table[_SOCKET_RESULT_MAX] = { + [SOCKET_SUCCESS] = "success", + [SOCKET_FAILURE_RESOURCES] = "resources", + [SOCKET_FAILURE_TIMEOUT] = "timeout", + [SOCKET_FAILURE_EXIT_CODE] = "exit-code", + [SOCKET_FAILURE_SIGNAL] = "signal", + [SOCKET_FAILURE_CORE_DUMP] = "core-dump", + [SOCKET_FAILURE_SERVICE_FAILED_PERMANENT] = "service-failed-permanent" +}; + +DEFINE_STRING_TABLE_LOOKUP(socket_result, SocketResult); + +const UnitVTable socket_vtable = { + .suffix = ".socket", + .object_size = sizeof(Socket), + .sections = + "Unit\0" + "Socket\0" + "Install\0", + + .init = socket_init, + .done = socket_done, + .load = socket_load, + + .kill = socket_kill, + + .coldplug = socket_coldplug, + + .dump = socket_dump, + + .start = socket_start, + .stop = socket_stop, + + .serialize = socket_serialize, + .deserialize_item = socket_deserialize_item, + + .active_state = socket_active_state, + .sub_state_to_string = socket_sub_state_to_string, + + .check_gc = socket_check_gc, + + .fd_event = socket_fd_event, + .sigchld_event = socket_sigchld_event, + .timer_event = socket_timer_event, + + .reset_failed = socket_reset_failed, + + .bus_interface = "org.freedesktop.systemd1.Socket", + .bus_message_handler = bus_socket_message_handler, + .bus_invalidating_properties = bus_socket_invalidating_properties +}; diff --git a/src/core/socket.h b/src/core/socket.h new file mode 100644 index 0000000000..6470d8b63e --- /dev/null +++ b/src/core/socket.h @@ -0,0 +1,173 @@ +/*-*- Mode: C; c-basic-offset: 8; indent-tabs-mode: nil -*-*/ + +#ifndef foosockethfoo +#define foosockethfoo + +/*** + This file is part of systemd. + + Copyright 2010 Lennart Poettering + + systemd is free software; you can redistribute it and/or modify it + under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + systemd is distributed in the hope that 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 General Public License + along with systemd; If not, see . +***/ + +typedef struct Socket Socket; + +#include "manager.h" +#include "unit.h" +#include "socket-util.h" +#include "mount.h" +#include "service.h" + +typedef enum SocketState { + SOCKET_DEAD, + SOCKET_START_PRE, + SOCKET_START_POST, + SOCKET_LISTENING, + SOCKET_RUNNING, + SOCKET_STOP_PRE, + SOCKET_STOP_PRE_SIGTERM, + SOCKET_STOP_PRE_SIGKILL, + SOCKET_STOP_POST, + SOCKET_FINAL_SIGTERM, + SOCKET_FINAL_SIGKILL, + SOCKET_FAILED, + _SOCKET_STATE_MAX, + _SOCKET_STATE_INVALID = -1 +} SocketState; + +typedef enum SocketExecCommand { + SOCKET_EXEC_START_PRE, + 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_FIFO_MAX, + _SOCKET_FIFO_INVALID = -1 +} SocketType; + +typedef enum SocketResult { + SOCKET_SUCCESS, + SOCKET_FAILURE_RESOURCES, + SOCKET_FAILURE_TIMEOUT, + SOCKET_FAILURE_EXIT_CODE, + SOCKET_FAILURE_SIGNAL, + SOCKET_FAILURE_CORE_DUMP, + SOCKET_FAILURE_SERVICE_FAILED_PERMANENT, + _SOCKET_RESULT_MAX, + _SOCKET_RESULT_INVALID = -1 +} SocketResult; + +typedef struct SocketPort { + SocketType type; + int fd; + + SocketAddress address; + char *path; + Watch fd_watch; + + 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; + usec_t timeout_usec; + + ExecCommand* exec_command[_SOCKET_EXEC_COMMAND_MAX]; + ExecContext exec_context; + + /* 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; + + Watch timer_watch; + + ExecCommand* control_command; + SocketExecCommand control_command_id; + pid_t control_pid; + + mode_t directory_mode; + mode_t socket_mode; + + SocketResult result; + + bool accept; + + /* Socket options */ + bool keep_alive; + bool free_bind; + bool transparent; + bool broadcast; + bool pass_cred; + bool pass_sec; + 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; + long mq_maxmsg; + long mq_msgsize; + + /* Only for INET6 sockets: issue IPV6_V6ONLY sockopt */ + SocketAddressBindIPv6Only bind_ipv6_only; +}; + +/* Called from the service code when collecting fds */ +int socket_collect_fds(Socket *s, int **fds, unsigned *n_fds); + +/* Called from the service when it shut down */ +void socket_notify_service_dead(Socket *s, bool failed_permanent); + +/* Called from the mount code figure out if a mount is a dependency of + * any of the sockets of this socket */ +int socket_add_one_mount_link(Socket *s, Mount *m); + +/* Called from the service code when a per-connection service ended */ +void socket_connection_unref(Socket *s); + +extern const UnitVTable socket_vtable; + +const char* socket_state_to_string(SocketState i); +SocketState socket_state_from_string(const char *s); + +const char* socket_exec_command_to_string(SocketExecCommand i); +SocketExecCommand socket_exec_command_from_string(const char *s); + +const char* socket_result_to_string(SocketResult i); +SocketResult socket_result_from_string(const char *s); + +#endif diff --git a/src/core/spawn-agent.h b/src/core/spawn-agent.h new file mode 100644 index 0000000000..fd0a9109b8 --- /dev/null +++ b/src/core/spawn-agent.h @@ -0,0 +1,28 @@ +/*-*- Mode: C; c-basic-offset: 8; indent-tabs-mode: nil -*-*/ + +#ifndef foospawnagenthfoo +#define foospawnagenthfoo + +/*** + This file is part of systemd. + + Copyright 2011 Lennart Poettering + + systemd is free software; you can redistribute it and/or modify it + under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + systemd is distributed in the hope that 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 General Public License + along with systemd; If not, see . +***/ + +void agent_open(void); +void agent_close(void); + +#endif diff --git a/src/core/special.h b/src/core/special.h new file mode 100644 index 0000000000..43e2e6f6d7 --- /dev/null +++ b/src/core/special.h @@ -0,0 +1,88 @@ +/*-*- Mode: C; c-basic-offset: 8; indent-tabs-mode: nil -*-*/ + +#ifndef foospecialhfoo +#define foospecialhfoo + +/*** + This file is part of systemd. + + Copyright 2010 Lennart Poettering + + systemd is free software; you can redistribute it and/or modify it + under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + systemd is distributed in the hope that 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 General Public License + along with systemd; If not, see . +***/ + +#define SPECIAL_DEFAULT_TARGET "default.target" + +/* Shutdown targets */ +#define SPECIAL_UMOUNT_TARGET "umount.target" +/* This is not really intended to be started by directly. This is + * mostly so that other targets (reboot/halt/poweroff) can depend on + * it to bring all services down that want to be brought down on + * system shutdown. */ +#define SPECIAL_SHUTDOWN_TARGET "shutdown.target" +#define SPECIAL_HALT_TARGET "halt.target" +#define SPECIAL_POWEROFF_TARGET "poweroff.target" +#define SPECIAL_REBOOT_TARGET "reboot.target" +#define SPECIAL_KEXEC_TARGET "kexec.target" +#define SPECIAL_EXIT_TARGET "exit.target" + +/* Special boot targets */ +#define SPECIAL_RESCUE_TARGET "rescue.target" +#define SPECIAL_EMERGENCY_TARGET "emergency.target" + +/* Early boot targets */ +#define SPECIAL_SYSINIT_TARGET "sysinit.target" +#define SPECIAL_SOCKETS_TARGET "sockets.target" +#define SPECIAL_LOCAL_FS_TARGET "local-fs.target" /* LSB's $local_fs */ +#define SPECIAL_LOCAL_FS_PRE_TARGET "local-fs-pre.target" +#define SPECIAL_REMOTE_FS_TARGET "remote-fs.target" /* LSB's $remote_fs */ +#define SPECIAL_REMOTE_FS_PRE_TARGET "remote-fs-pre.target" +#define SPECIAL_SWAP_TARGET "swap.target" +#define SPECIAL_BASIC_TARGET "basic.target" + +/* LSB compatibility */ +#define SPECIAL_NETWORK_TARGET "network.target" /* LSB's $network */ +#define SPECIAL_NSS_LOOKUP_TARGET "nss-lookup.target" /* LSB's $named */ +#define SPECIAL_RPCBIND_TARGET "rpcbind.target" /* LSB's $portmap */ +#define SPECIAL_SYSLOG_TARGET "syslog.target" /* LSB's $syslog */ +#define SPECIAL_TIME_SYNC_TARGET "time-sync.target" /* LSB's $time */ +#define SPECIAL_DISPLAY_MANAGER_SERVICE "display-manager.service" /* Debian's $x-display-manager */ +#define SPECIAL_MAIL_TRANSFER_AGENT_TARGET "mail-transfer-agent.target" /* Debian's $mail-{transport|transfer-agent */ +#define SPECIAL_HTTP_DAEMON_TARGET "http-daemon.target" + +/* Magic early boot services */ +#define SPECIAL_FSCK_SERVICE "fsck@.service" +#define SPECIAL_QUOTACHECK_SERVICE "quotacheck.service" +#define SPECIAL_QUOTAON_SERVICE "quotaon.service" +#define SPECIAL_REMOUNT_ROOTFS_SERVICE "remount-rootfs.service" + +/* Services systemd relies on */ +#define SPECIAL_DBUS_SERVICE "dbus.service" +#define SPECIAL_DBUS_SOCKET "dbus.socket" +#define SPECIAL_JOURNALD_SOCKET "systemd-journald.socket" +#define SPECIAL_JOURNALD_SERVICE "systemd-journald.service" + +/* Magic init signals */ +#define SPECIAL_KBREQUEST_TARGET "kbrequest.target" +#define SPECIAL_SIGPWR_TARGET "sigpwr.target" +#define SPECIAL_CTRL_ALT_DEL_TARGET "ctrl-alt-del.target" + +/* For SysV compatibility. Usually an alias for a saner target. On + * SysV-free systems this doesn't exist. */ +#define SPECIAL_RUNLEVEL2_TARGET "runlevel2.target" +#define SPECIAL_RUNLEVEL3_TARGET "runlevel3.target" +#define SPECIAL_RUNLEVEL4_TARGET "runlevel4.target" +#define SPECIAL_RUNLEVEL5_TARGET "runlevel5.target" + +#endif diff --git a/src/core/swap.c b/src/core/swap.c new file mode 100644 index 0000000000..9c72732b94 --- /dev/null +++ b/src/core/swap.c @@ -0,0 +1,1415 @@ +/*-*- Mode: C; c-basic-offset: 8; indent-tabs-mode: nil -*-*/ + +/*** + This file is part of systemd. + + Copyright 2010 Lennart Poettering + + systemd is free software; you can redistribute it and/or modify it + under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + systemd is distributed in the hope that 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 General Public License + along with systemd; If not, see . +***/ + +#include +#include +#include +#include +#include +#include +#include +#include + +#include "unit.h" +#include "swap.h" +#include "load-fragment.h" +#include "load-dropin.h" +#include "unit-name.h" +#include "dbus-swap.h" +#include "special.h" +#include "bus-errors.h" +#include "exit-status.h" +#include "def.h" + +static const UnitActiveState state_translation_table[_SWAP_STATE_MAX] = { + [SWAP_DEAD] = UNIT_INACTIVE, + [SWAP_ACTIVATING] = UNIT_ACTIVATING, + [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 void swap_unset_proc_swaps(Swap *s) { + Swap *first; + + assert(s); + + if (!s->parameters_proc_swaps.what) + return; + + /* Remove this unit from the chain of swaps which share the + * same kernel swap device. */ + + first = hashmap_get(UNIT(s)->manager->swaps_by_proc_swaps, s->parameters_proc_swaps.what); + LIST_REMOVE(Swap, same_proc_swaps, first, s); + + if (first) + hashmap_remove_and_replace(UNIT(s)->manager->swaps_by_proc_swaps, s->parameters_proc_swaps.what, first->parameters_proc_swaps.what, first); + else + hashmap_remove(UNIT(s)->manager->swaps_by_proc_swaps, s->parameters_proc_swaps.what); + + free(s->parameters_proc_swaps.what); + s->parameters_proc_swaps.what = NULL; +} + +static void swap_init(Unit *u) { + Swap *s = SWAP(u); + + assert(s); + assert(UNIT(s)->load_state == UNIT_STUB); + + s->timeout_usec = DEFAULT_TIMEOUT_USEC; + + exec_context_init(&s->exec_context); + s->exec_context.std_output = u->manager->default_std_output; + s->exec_context.std_error = u->manager->default_std_error; + + s->parameters_etc_fstab.priority = s->parameters_proc_swaps.priority = s->parameters_fragment.priority = -1; + + s->timer_watch.type = WATCH_INVALID; + + s->control_command_id = _SWAP_EXEC_COMMAND_INVALID; + + UNIT(s)->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); + + free(s->what); + s->what = NULL; + + free(s->parameters_etc_fstab.what); + free(s->parameters_fragment.what); + s->parameters_etc_fstab.what = s->parameters_fragment.what = NULL; + + exec_context_done(&s->exec_context); + exec_command_done_array(s->exec_command, _SWAP_EXEC_COMMAND_MAX); + s->control_command = NULL; + + swap_unwatch_control_pid(s); + + unit_unwatch_timer(u, &s->timer_watch); +} + +int swap_add_one_mount_link(Swap *s, Mount *m) { + int r; + + assert(s); + assert(m); + + if (UNIT(s)->load_state != UNIT_LOADED || + UNIT(m)->load_state != UNIT_LOADED) + return 0; + + if (is_device_path(s->what)) + return 0; + + if (!path_startswith(s->what, m->where)) + return 0; + + if ((r = unit_add_two_dependencies(UNIT(s), UNIT_AFTER, UNIT_REQUIRES, UNIT(m), true)) < 0) + return r; + + return 0; +} + +static int swap_add_mount_links(Swap *s) { + Unit *other; + int r; + + assert(s); + + LIST_FOREACH(units_by_type, other, UNIT(s)->manager->units_by_type[UNIT_MOUNT]) + if ((r = swap_add_one_mount_link(s, MOUNT(other))) < 0) + return r; + + return 0; +} + +static int swap_add_target_links(Swap *s) { + Unit *tu; + SwapParameters *p; + int r; + + assert(s); + + if (s->from_fragment) + p = &s->parameters_fragment; + else if (s->from_etc_fstab) + p = &s->parameters_etc_fstab; + else + return 0; + + if ((r = manager_load_unit(UNIT(s)->manager, SPECIAL_SWAP_TARGET, NULL, NULL, &tu)) < 0) + return r; + + if (!p->noauto && + !p->nofail && + (p->handle || UNIT(s)->manager->swap_auto) && + s->from_etc_fstab && + UNIT(s)->manager->running_as == MANAGER_SYSTEM) + if ((r = unit_add_dependency(tu, UNIT_WANTS, UNIT(s), true)) < 0) + return r; + + return unit_add_dependency(UNIT(s), UNIT_BEFORE, tu, true); +} + +static int swap_add_device_links(Swap *s) { + SwapParameters *p; + + assert(s); + + if (!s->what) + return 0; + + if (s->from_fragment) + p = &s->parameters_fragment; + else if (s->from_etc_fstab) + p = &s->parameters_etc_fstab; + else + return 0; + + if (is_device_path(s->what)) + return unit_add_node_link(UNIT(s), s->what, + !p->noauto && p->nofail && + UNIT(s)->manager->running_as == MANAGER_SYSTEM); + else + /* File based swap devices need to be ordered after + * remount-rootfs.service, since they might need a + * writable file system. */ + return unit_add_dependency_by_name(UNIT(s), UNIT_AFTER, SPECIAL_REMOUNT_ROOTFS_SERVICE, NULL, true); +} + +static int swap_add_default_dependencies(Swap *s) { + int r; + + assert(s); + + if (UNIT(s)->manager->running_as == MANAGER_SYSTEM) { + + if ((r = unit_add_two_dependencies_by_name(UNIT(s), UNIT_BEFORE, UNIT_CONFLICTS, SPECIAL_UMOUNT_TARGET, NULL, true)) < 0) + return r; + } + + return 0; +} + +static int swap_verify(Swap *s) { + bool b; + char *e; + + if (UNIT(s)->load_state != UNIT_LOADED) + return 0; + + if (!(e = unit_name_from_path(s->what, ".swap"))) + return -ENOMEM; + + b = unit_has_name(UNIT(s), e); + free(e); + + if (!b) { + log_error("%s: Value of \"What\" and unit name do not match, not loading.\n", UNIT(s)->id); + return -EINVAL; + } + + if (s->exec_context.pam_name && s->exec_context.kill_mode != KILL_CONTROL_GROUP) { + log_error("%s has PAM enabled. Kill mode must be set to 'control-group'. Refusing.", UNIT(s)->id); + return -EINVAL; + } + + return 0; +} + +static int swap_load(Unit *u) { + int r; + Swap *s = SWAP(u); + + assert(s); + assert(u->load_state == UNIT_STUB); + + /* Load a .swap file */ + if ((r = unit_load_fragment_and_dropin_optional(u)) < 0) + return r; + + if (u->load_state == UNIT_LOADED) { + if ((r = unit_add_exec_dependencies(u, &s->exec_context)) < 0) + return r; + + 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_etc_fstab.what) + s->what = strdup(s->parameters_etc_fstab.what); + else if (s->parameters_proc_swaps.what) + s->what = strdup(s->parameters_proc_swaps.what); + else + s->what = unit_name_to_path(u->id); + + if (!s->what) + return -ENOMEM; + } + + path_kill_slashes(s->what); + + if (!UNIT(s)->description) + if ((r = unit_set_description(u, s->what)) < 0) + return r; + + if ((r = swap_add_device_links(s)) < 0) + return r; + + if ((r = swap_add_mount_links(s)) < 0) + return r; + + if ((r = swap_add_target_links(s)) < 0) + return r; + + if ((r = unit_add_default_cgroups(u)) < 0) + return r; + + if (UNIT(s)->default_dependencies) + if ((r = swap_add_default_dependencies(s)) < 0) + return r; + } + + return swap_verify(s); +} + +int swap_add_one( + Manager *m, + const char *what, + const char *what_proc_swaps, + int priority, + bool noauto, + bool nofail, + bool handle, + bool set_flags) { + + Unit *u = NULL; + char *e = NULL, *wp = NULL; + bool delete = false; + int r; + SwapParameters *p; + + assert(m); + assert(what); + + e = unit_name_from_path(what, ".swap"); + if (!e) + return -ENOMEM; + + u = manager_get_unit(m, e); + + if (what_proc_swaps && + u && + SWAP(u)->from_proc_swaps && + !path_equal(SWAP(u)->parameters_proc_swaps.what, what_proc_swaps)) + return -EEXIST; + + if (!u) { + delete = true; + + u = unit_new(m, sizeof(Swap)); + if (!u) { + free(e); + return -ENOMEM; + } + + 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; + + if (what_proc_swaps) { + Swap *first; + + p = &SWAP(u)->parameters_proc_swaps; + + if (!p->what) { + if (!(wp = strdup(what_proc_swaps))) { + r = -ENOMEM; + goto fail; + } + + if (!m->swaps_by_proc_swaps) + if (!(m->swaps_by_proc_swaps = hashmap_new(string_hash_func, string_compare_func))) { + r = -ENOMEM; + goto fail; + } + + free(p->what); + p->what = wp; + + first = hashmap_get(m->swaps_by_proc_swaps, wp); + LIST_PREPEND(Swap, same_proc_swaps, first, SWAP(u)); + + if ((r = hashmap_replace(m->swaps_by_proc_swaps, wp, first)) < 0) + 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; + + } else { + p = &SWAP(u)->parameters_etc_fstab; + + if (!(wp = strdup(what))) { + r = -ENOMEM; + goto fail; + } + + free(p->what); + p->what = wp; + + SWAP(u)->from_etc_fstab = true; + } + + p->priority = priority; + p->noauto = noauto; + p->nofail = nofail; + p->handle = handle; + + unit_add_to_dbus_queue(u); + + free(e); + + return 0; + +fail: + log_warning("Failed to load swap unit: %s", strerror(-r)); + + free(wp); + free(e); + + if (delete && u) + unit_free(u); + + return r; +} + +static int swap_process_new_swap(Manager *m, const char *device, int prio, bool set_flags) { + struct stat st; + int r = 0, k; + + assert(m); + + if (stat(device, &st) >= 0 && S_ISBLK(st.st_mode)) { + struct udev_device *d; + const char *dn; + struct udev_list_entry *item = NULL, *first = NULL; + + /* So this is a proper swap device. Create swap units + * for all names this swap device is known under */ + + if (!(d = udev_device_new_from_devnum(m->udev, 'b', st.st_rdev))) + return -ENOMEM; + + if ((dn = udev_device_get_devnode(d))) + r = swap_add_one(m, dn, device, prio, false, false, false, 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 (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; + + if ((k = swap_add_one(m, p, device, prio, false, false, false, set_flags)) < 0) + r = k; + } + + udev_device_unref(d); + } + + if ((k = swap_add_one(m, device, device, prio, false, false, false, set_flags)) < 0) + r = k; + + return r; +} + +static void swap_set_state(Swap *s, SwapState state) { + SwapState old_state; + + assert(s); + + old_state = s->state; + s->state = state; + + if (state != SWAP_ACTIVATING && + state != SWAP_ACTIVATING_SIGTERM && + state != SWAP_ACTIVATING_SIGKILL && + state != SWAP_DEACTIVATING && + state != SWAP_DEACTIVATING_SIGTERM && + state != SWAP_DEACTIVATING_SIGKILL) { + unit_unwatch_timer(UNIT(s), &s->timer_watch); + swap_unwatch_control_pid(s); + s->control_command = NULL; + s->control_command_id = _SWAP_EXEC_COMMAND_INVALID; + } + + if (state != old_state) + log_debug("%s changed %s -> %s", + UNIT(s)->id, + 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); +} + +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) { + + if (new_state == SWAP_ACTIVATING || + new_state == SWAP_ACTIVATING_SIGTERM || + new_state == SWAP_ACTIVATING_SIGKILL || + new_state == SWAP_DEACTIVATING || + new_state == SWAP_DEACTIVATING_SIGTERM || + new_state == SWAP_DEACTIVATING_SIGKILL) { + + if (s->control_pid <= 0) + return -EBADMSG; + + if ((r = unit_watch_pid(UNIT(s), s->control_pid)) < 0) + return r; + + if ((r = unit_watch_timer(UNIT(s), s->timeout_usec, &s->timer_watch)) < 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 = &s->parameters_etc_fstab; + + fprintf(f, + "%sSwap State: %s\n" + "%sResult: %s\n" + "%sWhat: %s\n" + "%sPriority: %i\n" + "%sNoAuto: %s\n" + "%sNoFail: %s\n" + "%sHandle: %s\n" + "%sFrom /etc/fstab: %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, p->priority, + prefix, yes_no(p->noauto), + prefix, yes_no(p->nofail), + prefix, yes_no(p->handle), + prefix, yes_no(s->from_etc_fstab), + prefix, yes_no(s->from_proc_swaps), + prefix, yes_no(s->from_fragment)); + + if (s->control_pid > 0) + fprintf(f, + "%sControl PID: %lu\n", + prefix, (unsigned long) s->control_pid); + + exec_context_dump(&s->exec_context, f, prefix); +} + +static int swap_spawn(Swap *s, ExecCommand *c, pid_t *_pid) { + pid_t pid; + int r; + + assert(s); + assert(c); + assert(_pid); + + if ((r = unit_watch_timer(UNIT(s), s->timeout_usec, &s->timer_watch)) < 0) + goto fail; + + if ((r = exec_spawn(c, + NULL, + &s->exec_context, + NULL, 0, + UNIT(s)->manager->environment, + true, + true, + true, + UNIT(s)->manager->confirm_spawn, + UNIT(s)->cgroup_bondings, + UNIT(s)->cgroup_attributes, + &pid)) < 0) + goto fail; + + if ((r = unit_watch_pid(UNIT(s), pid)) < 0) + /* FIXME: we need to do something here */ + goto fail; + + *_pid = pid; + + return 0; + +fail: + unit_unwatch_timer(UNIT(s), &s->timer_watch); + + return r; +} + +static void swap_enter_dead(Swap *s, SwapResult f) { + assert(s); + + if (f != SWAP_SUCCESS) + s->result = f; + + 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; + Set *pid_set = NULL; + bool wait_for_exit = false; + + assert(s); + + if (f != SWAP_SUCCESS) + s->result = f; + + if (s->exec_context.kill_mode != KILL_NONE) { + int sig = (state == SWAP_ACTIVATING_SIGTERM || + state == SWAP_DEACTIVATING_SIGTERM) ? s->exec_context.kill_signal : SIGKILL; + + if (s->control_pid > 0) { + if (kill_and_sigcont(s->control_pid, sig) < 0 && errno != ESRCH) + + log_warning("Failed to kill control process %li: %m", (long) s->control_pid); + else + wait_for_exit = true; + } + + if (s->exec_context.kill_mode == KILL_CONTROL_GROUP) { + + if (!(pid_set = set_new(trivial_hash_func, trivial_compare_func))) { + r = -ENOMEM; + goto fail; + } + + /* Exclude the control pid from being killed via the cgroup */ + if (s->control_pid > 0) + if ((r = set_put(pid_set, LONG_TO_PTR(s->control_pid))) < 0) + goto fail; + + if ((r = cgroup_bonding_kill_list(UNIT(s)->cgroup_bondings, sig, true, pid_set)) < 0) { + if (r != -EAGAIN && r != -ESRCH && r != -ENOENT) + log_warning("Failed to kill control group: %s", strerror(-r)); + } else if (r > 0) + wait_for_exit = true; + + set_free(pid_set); + pid_set = NULL; + } + } + + if (wait_for_exit) { + if ((r = unit_watch_timer(UNIT(s), s->timeout_usec, &s->timer_watch)) < 0) + goto fail; + + swap_set_state(s, state); + } else + swap_enter_dead(s, SWAP_SUCCESS); + + return; + +fail: + log_warning("%s failed to kill processes: %s", UNIT(s)->id, strerror(-r)); + + swap_enter_dead(s, SWAP_FAILURE_RESOURCES); + + if (pid_set) + set_free(pid_set); +} + +static void swap_enter_activating(Swap *s) { + int r, priority; + + assert(s); + + s->control_command_id = SWAP_EXEC_ACTIVATE; + s->control_command = s->exec_command + SWAP_EXEC_ACTIVATE; + + if (s->from_fragment) + priority = s->parameters_fragment.priority; + else if (s->from_etc_fstab) + priority = s->parameters_etc_fstab.priority; + else + priority = -1; + + if (priority >= 0) { + char p[LINE_MAX]; + + snprintf(p, sizeof(p), "%i", priority); + char_array_0(p); + + r = exec_command_set( + s->control_command, + "/sbin/swapon", + "-p", + p, + s->what, + NULL); + } else + r = exec_command_set( + s->control_command, + "/sbin/swapon", + s->what, + NULL); + + if (r < 0) + goto fail; + + swap_unwatch_control_pid(s); + + if ((r = swap_spawn(s, s->control_command, &s->control_pid)) < 0) + goto fail; + + swap_set_state(s, SWAP_ACTIVATING); + + return; + +fail: + log_warning("%s failed to run 'swapon' task: %s", UNIT(s)->id, strerror(-r)); + 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; + + if ((r = exec_command_set( + s->control_command, + "/sbin/swapoff", + s->what, + NULL)) < 0) + goto fail; + + swap_unwatch_control_pid(s); + + if ((r = swap_spawn(s, s->control_command, &s->control_pid)) < 0) + goto fail; + + swap_set_state(s, SWAP_DEACTIVATING); + + return; + +fail: + log_warning("%s failed to run 'swapoff' task: %s", UNIT(s)->id, strerror(-r)); + swap_enter_active(s, SWAP_FAILURE_RESOURCES); +} + +static int swap_start(Unit *u) { + Swap *s = SWAP(u); + + 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); + + s->result = SWAP_SUCCESS; + swap_enter_activating(s); + return 0; +} + +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_ACTIVE); + + swap_enter_deactivating(s); + return 0; +} + +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", "%lu", (unsigned long) 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; + + if ((state = swap_state_from_string(value)) < 0) + log_debug("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_debug("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_debug("Failed to parse control-pid value %s", value); + else + s->control_pid = pid; + + } else if (streq(key, "control-command")) { + SwapExecCommand id; + + if ((id = swap_exec_command_from_string(value)) < 0) + log_debug("Failed to parse exec-command value %s", value); + else { + s->control_command_id = id; + s->control_command = s->exec_command + id; + } + + } else + log_debug("Unknown serialization key '%s'", key); + + return 0; +} + +static UnitActiveState swap_active_state(Unit *u) { + assert(u); + + return state_translation_table[SWAP(u)->state]; +} + +static const char *swap_sub_state_to_string(Unit *u) { + assert(u); + + return swap_state_to_string(SWAP(u)->state); +} + +static bool swap_check_gc(Unit *u) { + Swap *s = SWAP(u); + + assert(s); + + return s->from_etc_fstab || 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)) + 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_full(f == SWAP_SUCCESS ? LOG_DEBUG : LOG_NOTICE, + "%s swap process exited, code=%s status=%i", u->id, sigchld_code_to_string(code), status); + + switch (s->state) { + + case SWAP_ACTIVATING: + 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: + + if (f == SWAP_SUCCESS) + swap_enter_dead(s, f); + else + 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); + + /* Request a reload of /proc/swaps, so that following units + * can follow our state change */ + u->manager->request_reload = true; +} + +static void swap_timer_event(Unit *u, uint64_t elapsed, Watch *w) { + Swap *s = SWAP(u); + + assert(s); + assert(elapsed == 1); + assert(w == &s->timer_watch); + + switch (s->state) { + + case SWAP_ACTIVATING: + log_warning("%s activation timed out. Stopping.", u->id); + swap_enter_signal(s, SWAP_ACTIVATING_SIGTERM, SWAP_FAILURE_TIMEOUT); + break; + + case SWAP_DEACTIVATING: + log_warning("%s deactivation timed out. Stopping.", u->id); + swap_enter_signal(s, SWAP_DEACTIVATING_SIGTERM, SWAP_FAILURE_TIMEOUT); + break; + + case SWAP_ACTIVATING_SIGTERM: + if (s->exec_context.send_sigkill) { + log_warning("%s activation timed out. Killing.", u->id); + swap_enter_signal(s, SWAP_ACTIVATING_SIGKILL, SWAP_FAILURE_TIMEOUT); + } else { + log_warning("%s activation timed out. Skipping SIGKILL. Ignoring.", u->id); + swap_enter_dead(s, SWAP_FAILURE_TIMEOUT); + } + break; + + case SWAP_DEACTIVATING_SIGTERM: + if (s->exec_context.send_sigkill) { + log_warning("%s deactivation timed out. Killing.", u->id); + swap_enter_signal(s, SWAP_DEACTIVATING_SIGKILL, SWAP_FAILURE_TIMEOUT); + } else { + log_warning("%s deactivation timed out. Skipping SIGKILL. Ignoring.", u->id); + swap_enter_dead(s, SWAP_FAILURE_TIMEOUT); + } + break; + + case SWAP_ACTIVATING_SIGKILL: + case SWAP_DEACTIVATING_SIGKILL: + log_warning("%s swap process still around after SIGKILL. Ignoring.", u->id); + swap_enter_dead(s, SWAP_FAILURE_TIMEOUT); + break; + + default: + assert_not_reached("Timeout at wrong time."); + } +} + +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++) { + char *dev = NULL, *d; + int prio = 0, k; + + if ((k = fscanf(m->proc_swaps, + "%ms " /* device/file */ + "%*s " /* type of swap */ + "%*s " /* swap size */ + "%*s " /* used */ + "%i\n", /* priority */ + &dev, &prio)) != 2) { + + if (k == EOF) + break; + + log_warning("Failed to parse /proc/swaps:%u.", i); + free(dev); + continue; + } + + d = cunescape(dev); + free(dev); + + if (!d) + return -ENOMEM; + + k = swap_process_new_swap(m, d, prio, set_flags); + free(d); + + if (k < 0) + r = k; + } + + return r; +} + +int swap_dispatch_reload(Manager *m) { + /* This function should go as soon as the kernel properly notifies us */ + + if (_likely_(!m->request_reload)) + return 0; + + m->request_reload = false; + + return swap_fd_event(m, EPOLLPRI); +} + +int swap_fd_event(Manager *m, int events) { + Unit *u; + int r; + + assert(m); + assert(events & EPOLLPRI); + + if ((r = swap_load_proc_swaps(m, true)) < 0) { + log_error("Failed to reread /proc/swaps: %s", strerror(-r)); + + /* 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->from_proc_swaps = false; + swap_unset_proc_swaps(swap); + + switch (swap->state) { + + case SWAP_ACTIVE: + swap_enter_dead(swap, SWAP_SUCCESS); + break; + + default: + swap_set_state(swap, swap->state); + break; + } + + } else if (swap->just_activated) { + + /* New swap entry */ + + switch (swap->state) { + + case SWAP_DEAD: + case SWAP_FAILED: + swap_enter_active(swap, SWAP_SUCCESS); + 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 (streq_ptr(s->what, s->parameters_proc_swaps.what)) + return NULL; + + /* Make everybody follow the unit that's named after the swap + * device in the kernel */ + + LIST_FOREACH_AFTER(same_proc_swaps, other, s) + if (streq_ptr(other->what, other->parameters_proc_swaps.what)) + return UNIT(other); + + LIST_FOREACH_BEFORE(same_proc_swaps, other, s) { + if (streq_ptr(other->what, other->parameters_proc_swaps.what)) + return UNIT(other); + + first = other; + } + + return UNIT(first); +} + +static int swap_following_set(Unit *u, Set **_set) { + Swap *s = SWAP(u); + Swap *other; + Set *set; + int r; + + assert(s); + assert(_set); + + if (LIST_JUST_US(same_proc_swaps, s)) { + *_set = NULL; + return 0; + } + + if (!(set = set_new(NULL, NULL))) + return -ENOMEM; + + LIST_FOREACH_AFTER(same_proc_swaps, other, s) + if ((r = set_put(set, other)) < 0) + goto fail; + + LIST_FOREACH_BEFORE(same_proc_swaps, other, s) + if ((r = set_put(set, other)) < 0) + goto fail; + + *_set = set; + return 1; + +fail: + set_free(set); + return r; +} + +static void swap_shutdown(Manager *m) { + assert(m); + + if (m->proc_swaps) { + fclose(m->proc_swaps); + m->proc_swaps = NULL; + } + + hashmap_free(m->swaps_by_proc_swaps); + m->swaps_by_proc_swaps = NULL; +} + +static int swap_enumerate(Manager *m) { + int r; + struct epoll_event ev; + assert(m); + + if (!m->proc_swaps) { + if (!(m->proc_swaps = fopen("/proc/swaps", "re"))) + return (errno == ENOENT) ? 0 : -errno; + + m->swap_watch.type = WATCH_SWAP; + m->swap_watch.fd = fileno(m->proc_swaps); + + zero(ev); + ev.events = EPOLLPRI; + ev.data.ptr = &m->swap_watch; + + if (epoll_ctl(m->epoll_fd, EPOLL_CTL_ADD, m->swap_watch.fd, &ev) < 0) + return -errno; + } + + /* We rely on mount.c to load /etc/fstab for us */ + + if ((r = swap_load_proc_swaps(m, false)) < 0) + swap_shutdown(m); + + 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, KillMode mode, int signo, DBusError *error) { + Swap *s = SWAP(u); + int r = 0; + Set *pid_set = NULL; + + assert(s); + + if (who == KILL_MAIN) { + dbus_set_error(error, BUS_ERROR_NO_SUCH_PROCESS, "Swap units have no main processes"); + return -ESRCH; + } + + if (s->control_pid <= 0 && who == KILL_CONTROL) { + dbus_set_error(error, BUS_ERROR_NO_SUCH_PROCESS, "No control process to kill"); + return -ESRCH; + } + + if (who == KILL_CONTROL || who == KILL_ALL) + if (s->control_pid > 0) + if (kill(s->control_pid, signo) < 0) + r = -errno; + + if (who == KILL_ALL && mode == KILL_CONTROL_GROUP) { + int q; + + if (!(pid_set = set_new(trivial_hash_func, trivial_compare_func))) + return -ENOMEM; + + /* Exclude the control pid from being killed via the cgroup */ + if (s->control_pid > 0) + if ((q = set_put(pid_set, LONG_TO_PTR(s->control_pid))) < 0) { + r = q; + goto finish; + } + + if ((q = cgroup_bonding_kill_list(UNIT(s)->cgroup_bondings, signo, false, pid_set)) < 0) + if (q != -EAGAIN && q != -ESRCH && q != -ENOENT) + r = q; + } + +finish: + if (pid_set) + set_free(pid_set); + + return r; +} + +static const char* const swap_state_table[_SWAP_STATE_MAX] = { + [SWAP_DEAD] = "dead", + [SWAP_ACTIVATING] = "activating", + [SWAP_ACTIVE] = "active", + [SWAP_DEACTIVATING] = "deactivating", + [SWAP_ACTIVATING_SIGTERM] = "activating-sigterm", + [SWAP_ACTIVATING_SIGKILL] = "activating-sigkill", + [SWAP_DEACTIVATING_SIGTERM] = "deactivating-sigterm", + [SWAP_DEACTIVATING_SIGKILL] = "deactivating-sigkill", + [SWAP_FAILED] = "failed" +}; + +DEFINE_STRING_TABLE_LOOKUP(swap_state, SwapState); + +static const char* const swap_exec_command_table[_SWAP_EXEC_COMMAND_MAX] = { + [SWAP_EXEC_ACTIVATE] = "ExecActivate", + [SWAP_EXEC_DEACTIVATE] = "ExecDeactivate", +}; + +DEFINE_STRING_TABLE_LOOKUP(swap_exec_command, SwapExecCommand); + +static const char* const swap_result_table[_SWAP_RESULT_MAX] = { + [SWAP_SUCCESS] = "success", + [SWAP_FAILURE_RESOURCES] = "resources", + [SWAP_FAILURE_TIMEOUT] = "timeout", + [SWAP_FAILURE_EXIT_CODE] = "exit-code", + [SWAP_FAILURE_SIGNAL] = "signal", + [SWAP_FAILURE_CORE_DUMP] = "core-dump" +}; + +DEFINE_STRING_TABLE_LOOKUP(swap_result, SwapResult); + +const UnitVTable swap_vtable = { + .suffix = ".swap", + .object_size = sizeof(Swap), + .sections = + "Unit\0" + "Swap\0" + "Install\0", + + .no_alias = true, + .no_instances = true, + .show_status = true, + + .init = swap_init, + .load = swap_load, + .done = swap_done, + + .coldplug = swap_coldplug, + + .dump = swap_dump, + + .start = swap_start, + .stop = swap_stop, + + .kill = swap_kill, + + .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, + .timer_event = swap_timer_event, + + .reset_failed = swap_reset_failed, + + .bus_interface = "org.freedesktop.systemd1.Swap", + .bus_message_handler = bus_swap_message_handler, + .bus_invalidating_properties = bus_swap_invalidating_properties, + + .following = swap_following, + .following_set = swap_following_set, + + .enumerate = swap_enumerate, + .shutdown = swap_shutdown +}; diff --git a/src/core/swap.h b/src/core/swap.h new file mode 100644 index 0000000000..62d08da30b --- /dev/null +++ b/src/core/swap.h @@ -0,0 +1,128 @@ +/*-*- Mode: C; c-basic-offset: 8; indent-tabs-mode: nil -*-*/ + +#ifndef fooswaphfoo +#define fooswaphfoo + +/*** + 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 General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + systemd is distributed in the hope that 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 General Public License + along with systemd; If not, see . +***/ + +typedef struct Swap Swap; + +#include "unit.h" + +typedef enum SwapState { + SWAP_DEAD, + SWAP_ACTIVATING, + SWAP_ACTIVE, + SWAP_DEACTIVATING, + SWAP_ACTIVATING_SIGTERM, + SWAP_ACTIVATING_SIGKILL, + SWAP_DEACTIVATING_SIGTERM, + SWAP_DEACTIVATING_SIGKILL, + SWAP_FAILED, + _SWAP_STATE_MAX, + _SWAP_STATE_INVALID = -1 +} SwapState; + +typedef enum SwapExecCommand { + SWAP_EXEC_ACTIVATE, + SWAP_EXEC_DEACTIVATE, + _SWAP_EXEC_COMMAND_MAX, + _SWAP_EXEC_COMMAND_INVALID = -1 +} SwapExecCommand; + +typedef struct SwapParameters { + char *what; + int priority; + bool noauto:1; + bool nofail:1; + bool handle:1; +} SwapParameters; + +typedef enum SwapResult { + SWAP_SUCCESS, + SWAP_FAILURE_RESOURCES, + SWAP_FAILURE_TIMEOUT, + SWAP_FAILURE_EXIT_CODE, + SWAP_FAILURE_SIGNAL, + SWAP_FAILURE_CORE_DUMP, + _SWAP_RESULT_MAX, + _SWAP_RESULT_INVALID = -1 +} SwapResult; + +struct Swap { + Unit meta; + + char *what; + + SwapParameters parameters_etc_fstab; + SwapParameters parameters_proc_swaps; + SwapParameters parameters_fragment; + + bool from_etc_fstab:1; + 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; + + SwapResult result; + + usec_t timeout_usec; + + ExecCommand exec_command[_SWAP_EXEC_COMMAND_MAX]; + ExecContext exec_context; + + SwapState state, deserialized_state; + + ExecCommand* control_command; + SwapExecCommand control_command_id; + pid_t control_pid; + + Watch timer_watch; + + /* 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_proc_swaps); +}; + +extern const UnitVTable swap_vtable; + +int swap_add_one(Manager *m, const char *what, const char *what_proc_swaps, int prio, bool no_auto, bool no_fail, bool handle, bool set_flags); + +int swap_add_one_mount_link(Swap *s, Mount *m); + +int swap_dispatch_reload(Manager *m); +int swap_fd_event(Manager *m, int events); + +const char* swap_state_to_string(SwapState i); +SwapState swap_state_from_string(const char *s); + +const char* swap_exec_command_to_string(SwapExecCommand i); +SwapExecCommand swap_exec_command_from_string(const char *s); + +const char* swap_result_to_string(SwapResult i); +SwapResult swap_result_from_string(const char *s); + +#endif diff --git a/src/core/sysfs-show.h b/src/core/sysfs-show.h new file mode 100644 index 0000000000..9939e8b069 --- /dev/null +++ b/src/core/sysfs-show.h @@ -0,0 +1,27 @@ +/*-*- Mode: C; c-basic-offset: 8; indent-tabs-mode: nil -*-*/ + +#ifndef foosysfsshowhfoo +#define foosysfsshowhfoo + +/*** + This file is part of systemd. + + Copyright 2011 Lennart Poettering + + systemd is free software; you can redistribute it and/or modify it + under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + systemd is distributed in the hope that 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 General Public License + along with systemd; If not, see . +***/ + +int show_sysfs(const char *seat, const char *prefix, unsigned columns); + +#endif diff --git a/src/core/target.c b/src/core/target.c new file mode 100644 index 0000000000..6c1e0c368a --- /dev/null +++ b/src/core/target.c @@ -0,0 +1,224 @@ +/*-*- Mode: C; c-basic-offset: 8; indent-tabs-mode: nil -*-*/ + +/*** + This file is part of systemd. + + Copyright 2010 Lennart Poettering + + systemd is free software; you can redistribute it and/or modify it + under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + systemd is distributed in the hope that 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 General Public License + along with systemd; If not, see . +***/ + +#include +#include +#include + +#include "unit.h" +#include "target.h" +#include "load-fragment.h" +#include "log.h" +#include "dbus-target.h" +#include "special.h" +#include "unit-name.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_REQUIRES_OVERRIDABLE, + UNIT_REQUISITE, + UNIT_REQUISITE_OVERRIDABLE, + UNIT_WANTS, + UNIT_BIND_TO + }; + + 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) + if ((r = unit_add_default_target_dependency(other, UNIT(t))) < 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); + + if ((r = unit_load_fragment_and_dropin(u)) < 0) + return r; + + /* This is a new unit? Then let's add in some extras */ + if (u->load_state == UNIT_LOADED) { + if (u->default_dependencies) + if ((r = target_add_default_dependencies(t)) < 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 0; +} + +static int target_stop(Unit *u) { + Target *t = TARGET(u); + + assert(t); + assert(t->state == TARGET_ACTIVE); + + target_set_state(t, TARGET_DEAD); + return 0; +} + +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; + + if ((state = target_state_from_string(value)) < 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; +} + +static UnitActiveState target_active_state(Unit *u) { + assert(u); + + return state_translation_table[TARGET(u)->state]; +} + +static const char *target_sub_state_to_string(Unit *u) { + assert(u); + + return target_state_to_string(TARGET(u)->state); +} + +static const char* const target_state_table[_TARGET_STATE_MAX] = { + [TARGET_DEAD] = "dead", + [TARGET_ACTIVE] = "active" +}; + +DEFINE_STRING_TABLE_LOOKUP(target_state, TargetState); + +const UnitVTable target_vtable = { + .suffix = ".target", + .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_interface = "org.freedesktop.systemd1.Target", + .bus_message_handler = bus_target_message_handler +}; diff --git a/src/core/target.h b/src/core/target.h new file mode 100644 index 0000000000..5b97c86fbf --- /dev/null +++ b/src/core/target.h @@ -0,0 +1,47 @@ +/*-*- Mode: C; c-basic-offset: 8; indent-tabs-mode: nil -*-*/ + +#ifndef footargethfoo +#define footargethfoo + +/*** + This file is part of systemd. + + Copyright 2010 Lennart Poettering + + systemd is free software; you can redistribute it and/or modify it + under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + systemd is distributed in the hope that 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 General Public License + along with systemd; If not, see . +***/ + +typedef struct Target Target; + +#include "unit.h" + +typedef enum TargetState { + TARGET_DEAD, + TARGET_ACTIVE, + _TARGET_STATE_MAX, + _TARGET_STATE_INVALID = -1 +} TargetState; + +struct Target { + Unit meta; + + TargetState state, deserialized_state; +}; + +extern const UnitVTable target_vtable; + +const char* target_state_to_string(TargetState i); +TargetState target_state_from_string(const char *s); + +#endif diff --git a/src/core/tcpwrap.c b/src/core/tcpwrap.c new file mode 100644 index 0000000000..0aab1427dd --- /dev/null +++ b/src/core/tcpwrap.c @@ -0,0 +1,68 @@ +/*-*- Mode: C; c-basic-offset: 8; indent-tabs-mode: nil -*-*/ + +/*** + This file is part of systemd. + + Copyright 2010 Lennart Poettering + + systemd is free software; you can redistribute it and/or modify it + under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + systemd is distributed in the hope that 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 General Public License + along with systemd; If not, see . +***/ + +#include +#include +#include +#include + +#ifdef HAVE_LIBWRAP +#include +#endif + +#include "tcpwrap.h" +#include "log.h" + +bool socket_tcpwrap(int fd, const char *name) { +#ifdef HAVE_LIBWRAP + struct request_info req; + union { + struct sockaddr sa; + struct sockaddr_in in; + struct sockaddr_in6 in6; + struct sockaddr_un un; + struct sockaddr_storage storage; + } sa_union; + socklen_t l = sizeof(sa_union); + + if (getsockname(fd, &sa_union.sa, &l) < 0) + return true; + + if (sa_union.sa.sa_family != AF_INET && + sa_union.sa.sa_family != AF_INET6) + return true; + + request_init(&req, + RQ_DAEMON, name, + RQ_FILE, fd, + NULL); + + fromhost(&req); + + if (!hosts_access(&req)) { + log_warning("Connection refused by tcpwrap."); + return false; + } + + log_debug("Connection accepted by tcpwrap."); +#endif + return true; +} diff --git a/src/core/tcpwrap.h b/src/core/tcpwrap.h new file mode 100644 index 0000000000..4d4553e8ae --- /dev/null +++ b/src/core/tcpwrap.h @@ -0,0 +1,29 @@ +/*-*- Mode: C; c-basic-offset: 8; indent-tabs-mode: nil -*-*/ + +#ifndef foolibwraphfoo +#define foolibwraphfoo + +/*** + This file is part of systemd. + + Copyright 2010 Lennart Poettering + + systemd is free software; you can redistribute it and/or modify it + under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + systemd is distributed in the hope that 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 General Public License + along with systemd; If not, see . +***/ + +#include + +bool socket_tcpwrap(int fd, const char *name); + +#endif diff --git a/src/core/timer.c b/src/core/timer.c new file mode 100644 index 0000000000..e318fee201 --- /dev/null +++ b/src/core/timer.c @@ -0,0 +1,520 @@ +/*-*- Mode: C; c-basic-offset: 8; indent-tabs-mode: nil -*-*/ + +/*** + This file is part of systemd. + + Copyright 2010 Lennart Poettering + + systemd is free software; you can redistribute it and/or modify it + under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + systemd is distributed in the hope that 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 General Public License + along with systemd; If not, see . +***/ + +#include + +#include "unit.h" +#include "unit-name.h" +#include "timer.h" +#include "dbus-timer.h" +#include "special.h" +#include "bus-errors.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 void timer_init(Unit *u) { + Timer *t = TIMER(u); + + assert(u); + assert(u->load_state == UNIT_STUB); + + t->next_elapse = (usec_t) -1; +} + +static void timer_done(Unit *u) { + Timer *t = TIMER(u); + TimerValue *v; + + assert(t); + + while ((v = t->values)) { + LIST_REMOVE(TimerValue, value, t->values, v); + free(v); + } + + unit_unwatch_timer(u, &t->timer_watch); + + unit_ref_unset(&t->unit); +} + +static int timer_verify(Timer *t) { + assert(t); + + if (UNIT(t)->load_state != UNIT_LOADED) + return 0; + + if (!t->values) { + log_error("%s lacks value setting. Refusing.", UNIT(t)->id); + return -EINVAL; + } + + return 0; +} + +static int timer_add_default_dependencies(Timer *t) { + int r; + + assert(t); + + if (UNIT(t)->manager->running_as == MANAGER_SYSTEM) { + if ((r = unit_add_dependency_by_name(UNIT(t), UNIT_BEFORE, SPECIAL_BASIC_TARGET, NULL, true)) < 0) + return r; + + if ((r = unit_add_two_dependencies_by_name(UNIT(t), UNIT_AFTER, UNIT_REQUIRES, SPECIAL_SYSINIT_TARGET, NULL, true)) < 0) + return r; + } + + return unit_add_two_dependencies_by_name(UNIT(t), UNIT_BEFORE, UNIT_CONFLICTS, SPECIAL_SHUTDOWN_TARGET, NULL, true); +} + +static int timer_load(Unit *u) { + Timer *t = TIMER(u); + int r; + + assert(u); + assert(u->load_state == UNIT_STUB); + + if ((r = unit_load_fragment_and_dropin(u)) < 0) + return r; + + if (u->load_state == UNIT_LOADED) { + + if (!UNIT_DEREF(t->unit)) { + Unit *x; + + r = unit_load_related_unit(u, ".service", &x); + if (r < 0) + return r; + + unit_ref_set(&t->unit, x); + } + + r = unit_add_two_dependencies(u, UNIT_BEFORE, UNIT_TRIGGERS, UNIT_DEREF(t->unit), true); + if (r < 0) + return r; + + if (UNIT(t)->default_dependencies) + if ((r = timer_add_default_dependencies(t)) < 0) + return r; + } + + return timer_verify(t); +} + +static void timer_dump(Unit *u, FILE *f, const char *prefix) { + Timer *t = TIMER(u); + TimerValue *v; + char + timespan1[FORMAT_TIMESPAN_MAX]; + + fprintf(f, + "%sTimer State: %s\n" + "%sResult: %s\n" + "%sUnit: %s\n", + prefix, timer_state_to_string(t->state), + prefix, timer_result_to_string(t->result), + prefix, UNIT_DEREF(t->unit)->id); + + LIST_FOREACH(value, v, t->values) + fprintf(f, + "%s%s: %s\n", + prefix, + timer_base_to_string(v->base), + strna(format_timespan(timespan1, sizeof(timespan1), v->value))); +} + +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) + unit_unwatch_timer(UNIT(t), &t->timer_watch); + + if (state != old_state) + log_debug("%s changed %s -> %s", + UNIT(t)->id, + 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) { + + 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_waiting(Timer *t, bool initial) { + TimerValue *v; + usec_t base = 0, delay, n; + bool found = false; + int r; + + n = now(CLOCK_MONOTONIC); + + LIST_FOREACH(value, v, t->values) { + + if (v->disabled) + continue; + + switch (v->base) { + + case TIMER_ACTIVE: + if (state_translation_table[t->state] == UNIT_ACTIVE) + base = UNIT(t)->inactive_exit_timestamp.monotonic; + else + base = n; + break; + + case TIMER_BOOT: + /* CLOCK_MONOTONIC equals the uptime on Linux */ + base = 0; + break; + + case TIMER_STARTUP: + base = UNIT(t)->manager->startup_timestamp.monotonic; + break; + + case TIMER_UNIT_ACTIVE: + + if (UNIT_DEREF(t->unit)->inactive_exit_timestamp.monotonic <= 0) + continue; + + base = UNIT_DEREF(t->unit)->inactive_exit_timestamp.monotonic; + break; + + case TIMER_UNIT_INACTIVE: + + if (UNIT_DEREF(t->unit)->inactive_enter_timestamp.monotonic <= 0) + continue; + + base = UNIT_DEREF(t->unit)->inactive_enter_timestamp.monotonic; + break; + + default: + assert_not_reached("Unknown timer base"); + } + + v->next_elapse = base + v->value; + + if (!initial && v->next_elapse < n) { + v->disabled = true; + continue; + } + + if (!found) + t->next_elapse = v->next_elapse; + else + t->next_elapse = MIN(t->next_elapse, v->next_elapse); + + found = true; + } + + if (!found) { + timer_set_state(t, TIMER_ELAPSED); + return; + } + + delay = n < t->next_elapse ? t->next_elapse - n : 0; + + if ((r = unit_watch_timer(UNIT(t), delay, &t->timer_watch)) < 0) + goto fail; + + timer_set_state(t, TIMER_WAITING); + return; + +fail: + log_warning("%s failed to enter waiting state: %s", UNIT(t)->id, strerror(-r)); + timer_enter_dead(t, TIMER_FAILURE_RESOURCES); +} + +static void timer_enter_running(Timer *t) { + DBusError error; + int r; + + assert(t); + dbus_error_init(&error); + + /* Don't start job if we are supposed to go down */ + if (UNIT(t)->job && UNIT(t)->job->type == JOB_STOP) + return; + + if ((r = manager_add_job(UNIT(t)->manager, JOB_START, UNIT_DEREF(t->unit), JOB_REPLACE, true, &error, NULL)) < 0) + goto fail; + + timer_set_state(t, TIMER_RUNNING); + return; + +fail: + log_warning("%s failed to queue unit startup job: %s", UNIT(t)->id, bus_error(&error, r)); + timer_enter_dead(t, TIMER_FAILURE_RESOURCES); + + dbus_error_free(&error); +} + +static int timer_start(Unit *u) { + Timer *t = TIMER(u); + + assert(t); + assert(t->state == TIMER_DEAD || t->state == TIMER_FAILED); + + if (UNIT_DEREF(t->unit)->load_state != UNIT_LOADED) + return -ENOENT; + + t->result = TIMER_SUCCESS; + timer_enter_waiting(t, true); + return 0; +} + +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 0; +} + +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)); + + return 0; +} + +static int timer_deserialize_item(Unit *u, const char *key, const char *value, FDSet *fds) { + Timer *t = TIMER(u); + + assert(u); + assert(key); + assert(value); + assert(fds); + + if (streq(key, "state")) { + TimerState state; + + if ((state = timer_state_from_string(value)) < 0) + log_debug("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_debug("Failed to parse result value %s", value); + else if (f != TIMER_SUCCESS) + t->result = f; + + } else + log_debug("Unknown serialization key '%s'", key); + + return 0; +} + +static UnitActiveState timer_active_state(Unit *u) { + assert(u); + + return state_translation_table[TIMER(u)->state]; +} + +static const char *timer_sub_state_to_string(Unit *u) { + assert(u); + + return timer_state_to_string(TIMER(u)->state); +} + +static void timer_timer_event(Unit *u, uint64_t elapsed, Watch *w) { + Timer *t = TIMER(u); + + assert(t); + assert(elapsed == 1); + + if (t->state != TIMER_WAITING) + return; + + log_debug("Timer elapsed on %s", u->id); + timer_enter_running(t); +} + +void timer_unit_notify(Unit *u, UnitActiveState new_state) { + Iterator i; + Unit *k; + + if (u->type == UNIT_TIMER) + return; + + SET_FOREACH(k, u->dependencies[UNIT_TRIGGERED_BY], i) { + Timer *t; + TimerValue *v; + + if (k->type != UNIT_TIMER) + continue; + + if (k->load_state != UNIT_LOADED) + continue; + + t = TIMER(k); + + /* 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(new_state)) { + log_debug("%s got notified about unit deactivation.", UNIT(t)->id); + 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 const char* const timer_state_table[_TIMER_STATE_MAX] = { + [TIMER_DEAD] = "dead", + [TIMER_WAITING] = "waiting", + [TIMER_RUNNING] = "running", + [TIMER_ELAPSED] = "elapsed", + [TIMER_FAILED] = "failed" +}; + +DEFINE_STRING_TABLE_LOOKUP(timer_state, TimerState); + +static const char* const timer_base_table[_TIMER_BASE_MAX] = { + [TIMER_ACTIVE] = "OnActiveSec", + [TIMER_BOOT] = "OnBootSec", + [TIMER_STARTUP] = "OnStartupSec", + [TIMER_UNIT_ACTIVE] = "OnUnitActiveSec", + [TIMER_UNIT_INACTIVE] = "OnUnitInactiveSec" +}; + +DEFINE_STRING_TABLE_LOOKUP(timer_base, TimerBase); + +static const char* const timer_result_table[_TIMER_RESULT_MAX] = { + [TIMER_SUCCESS] = "success", + [TIMER_FAILURE_RESOURCES] = "resources" +}; + +DEFINE_STRING_TABLE_LOOKUP(timer_result, TimerResult); + +const UnitVTable timer_vtable = { + .suffix = ".timer", + .object_size = sizeof(Timer), + .sections = + "Unit\0" + "Timer\0" + "Install\0", + + .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, + + .timer_event = timer_timer_event, + + .reset_failed = timer_reset_failed, + + .bus_interface = "org.freedesktop.systemd1.Timer", + .bus_message_handler = bus_timer_message_handler, + .bus_invalidating_properties = bus_timer_invalidating_properties +}; diff --git a/src/core/timer.h b/src/core/timer.h new file mode 100644 index 0000000000..f5c5c64f25 --- /dev/null +++ b/src/core/timer.h @@ -0,0 +1,93 @@ +/*-*- Mode: C; c-basic-offset: 8; indent-tabs-mode: nil -*-*/ + +#ifndef footimerhfoo +#define footimerhfoo + +/*** + This file is part of systemd. + + Copyright 2010 Lennart Poettering + + systemd is free software; you can redistribute it and/or modify it + under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + systemd is distributed in the hope that 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 General Public License + along with systemd; If not, see . +***/ + +typedef struct Timer Timer; + +#include "unit.h" + +typedef enum TimerState { + TIMER_DEAD, + TIMER_WAITING, + TIMER_RUNNING, + TIMER_ELAPSED, + TIMER_FAILED, + _TIMER_STATE_MAX, + _TIMER_STATE_INVALID = -1 +} TimerState; + +typedef enum TimerBase { + TIMER_ACTIVE, + TIMER_BOOT, + TIMER_STARTUP, + TIMER_UNIT_ACTIVE, + TIMER_UNIT_INACTIVE, + _TIMER_BASE_MAX, + _TIMER_BASE_INVALID = -1 +} TimerBase; + +typedef struct TimerValue { + usec_t value; + usec_t next_elapse; + + LIST_FIELDS(struct TimerValue, value); + + TimerBase base; + bool disabled; +} TimerValue; + +typedef enum TimerResult { + TIMER_SUCCESS, + TIMER_FAILURE_RESOURCES, + _TIMER_RESULT_MAX, + _TIMER_RESULT_INVALID = -1 +} TimerResult; + +struct Timer { + Unit meta; + + LIST_HEAD(TimerValue, values); + usec_t next_elapse; + + TimerState state, deserialized_state; + UnitRef unit; + + Watch timer_watch; + + TimerResult result; +}; + +void timer_unit_notify(Unit *u, UnitActiveState new_state); + +extern const UnitVTable timer_vtable; + +const char *timer_state_to_string(TimerState i); +TimerState timer_state_from_string(const char *s); + +const char *timer_base_to_string(TimerBase i); +TimerBase timer_base_from_string(const char *s); + +const char* timer_result_to_string(TimerResult i); +TimerResult timer_result_from_string(const char *s); + +#endif diff --git a/src/core/unit.c b/src/core/unit.c new file mode 100644 index 0000000000..9e33701c8f --- /dev/null +++ b/src/core/unit.c @@ -0,0 +1,2676 @@ +/*-*- Mode: C; c-basic-offset: 8; indent-tabs-mode: nil -*-*/ + +/*** + This file is part of systemd. + + Copyright 2010 Lennart Poettering + + systemd is free software; you can redistribute it and/or modify it + under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + systemd is distributed in the hope that 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 General Public License + along with systemd; If not, see . +***/ + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "set.h" +#include "unit.h" +#include "macro.h" +#include "strv.h" +#include "load-fragment.h" +#include "load-dropin.h" +#include "log.h" +#include "unit-name.h" +#include "specifier.h" +#include "dbus-unit.h" +#include "special.h" +#include "cgroup-util.h" +#include "missing.h" +#include "cgroup-attr.h" + +const UnitVTable * const unit_vtable[_UNIT_TYPE_MAX] = { + [UNIT_SERVICE] = &service_vtable, + [UNIT_TIMER] = &timer_vtable, + [UNIT_SOCKET] = &socket_vtable, + [UNIT_TARGET] = &target_vtable, + [UNIT_DEVICE] = &device_vtable, + [UNIT_MOUNT] = &mount_vtable, + [UNIT_AUTOMOUNT] = &automount_vtable, + [UNIT_SNAPSHOT] = &snapshot_vtable, + [UNIT_SWAP] = &swap_vtable, + [UNIT_PATH] = &path_vtable +}; + +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_func, string_compare_func); + if (!u->names) { + free(u); + return NULL; + } + + u->manager = m; + u->type = _UNIT_TYPE_INVALID; + u->deserialized_job = _JOB_TYPE_INVALID; + u->default_dependencies = true; + u->unit_file_state = _UNIT_FILE_STATE_INVALID; + + return u; +} + +bool unit_has_name(Unit *u, const char *name) { + assert(u); + assert(name); + + return !!set_get(u->names, (char*) name); +} + +int unit_add_name(Unit *u, const char *text) { + UnitType t; + char *s, *i = NULL; + int r; + + assert(u); + assert(text); + + if (unit_name_is_template(text)) { + if (!u->instance) + return -EINVAL; + + s = unit_name_replace_instance(text, u->instance); + } else + s = strdup(text); + + if (!s) + return -ENOMEM; + + if (!unit_name_is_valid(s, false)) { + r = -EINVAL; + goto fail; + } + + assert_se((t = unit_name_to_type(s)) >= 0); + + if (u->type != _UNIT_TYPE_INVALID && t != u->type) { + r = -EINVAL; + goto fail; + } + + if ((r = unit_name_to_instance(s, &i)) < 0) + goto fail; + + if (i && unit_vtable[t]->no_instances) { + r = -EINVAL; + goto fail; + } + + /* Ensure that this unit is either instanced or not instanced, + * but not both. */ + if (u->type != _UNIT_TYPE_INVALID && !u->instance != !i) { + r = -EINVAL; + goto fail; + } + + if (unit_vtable[t]->no_alias && + !set_isempty(u->names) && + !set_get(u->names, s)) { + r = -EEXIST; + goto fail; + } + + if (hashmap_size(u->manager->units) >= MANAGER_MAX_NAMES) { + r = -E2BIG; + goto fail; + } + + if ((r = set_put(u->names, s)) < 0) { + if (r == -EEXIST) + r = 0; + goto fail; + } + + if ((r = hashmap_put(u->manager->units, s, u)) < 0) { + set_remove(u->names, s); + goto fail; + } + + if (u->type == _UNIT_TYPE_INVALID) { + + u->type = t; + u->id = s; + u->instance = i; + + LIST_PREPEND(Unit, units_by_type, u->manager->units_by_type[t], u); + + if (UNIT_VTABLE(u)->init) + UNIT_VTABLE(u)->init(u); + } else + free(i); + + unit_add_to_dbus_queue(u); + return 0; + +fail: + free(s); + free(i); + + return r; +} + +int unit_choose_id(Unit *u, const char *name) { + char *s, *t = NULL, *i; + int r; + + assert(u); + assert(name); + + if (unit_name_is_template(name)) { + + if (!u->instance) + return -EINVAL; + + if (!(t = unit_name_replace_instance(name, u->instance))) + return -ENOMEM; + + name = t; + } + + /* Selects one of the names of this unit as the id */ + s = set_get(u->names, (char*) name); + free(t); + + if (!s) + return -ENOENT; + + if ((r = unit_name_to_instance(s, &i)) < 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 (!(s = strdup(description))) + return -ENOMEM; + + free(u->description); + u->description = s; + + unit_add_to_dbus_queue(u); + return 0; +} + +bool unit_check_gc(Unit *u) { + assert(u); + + if (u->load_state == UNIT_STUB) + return true; + + if (UNIT_VTABLE(u)->no_gc) + return true; + + if (u->no_gc) + return true; + + if (u->job) + return true; + + if (unit_active_state(u) != UNIT_INACTIVE) + 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(Unit, 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(Unit, 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(Unit, gc_queue, u->manager->gc_queue, u); + u->in_gc_queue = true; + + u->manager->n_in_gc_queue ++; + + if (u->manager->gc_queue_timestamp <= 0) + u->manager->gc_queue_timestamp = now(CLOCK_MONOTONIC); +} + +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 (!bus_has_subscriber(u->manager)) { + u->sent_dbus_new_signal = true; + return; + } + + LIST_PREPEND(Unit, 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); +} + +void unit_free(Unit *u) { + UnitDependency d; + Iterator i; + char *t; + + assert(u); + + bus_unit_send_removed_signal(u); + + if (u->load_state != UNIT_STUB) + if (UNIT_VTABLE(u)->done) + UNIT_VTABLE(u)->done(u); + + SET_FOREACH(t, u->names, i) + hashmap_remove_value(u->manager->units, t, u); + + if (u->job) + job_free(u->job); + + for (d = 0; d < _UNIT_DEPENDENCY_MAX; d++) + bidi_set_free(u, u->dependencies[d]); + + if (u->type != _UNIT_TYPE_INVALID) + LIST_REMOVE(Unit, units_by_type, u->manager->units_by_type[u->type], u); + + if (u->in_load_queue) + LIST_REMOVE(Unit, load_queue, u->manager->load_queue, u); + + if (u->in_dbus_queue) + LIST_REMOVE(Unit, dbus_queue, u->manager->dbus_unit_queue, u); + + if (u->in_cleanup_queue) + LIST_REMOVE(Unit, cleanup_queue, u->manager->cleanup_queue, u); + + if (u->in_gc_queue) { + LIST_REMOVE(Unit, gc_queue, u->manager->gc_queue, u); + u->manager->n_in_gc_queue--; + } + + cgroup_bonding_free_list(u->cgroup_bondings, u->manager->n_reloading <= 0); + cgroup_attribute_free_list(u->cgroup_attributes); + + free(u->description); + free(u->fragment_path); + free(u->instance); + + set_free_free(u->names); + + condition_free_list(u->conditions); + + 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 void complete_move(Set **s, Set **other) { + assert(s); + assert(other); + + if (!*other) + return; + + if (*s) + set_move(*s, *other); + else { + *s = *other; + *other = NULL; + } +} + +static void merge_names(Unit *u, Unit *other) { + char *t; + Iterator i; + + assert(u); + assert(other); + + complete_move(&u->names, &other->names); + + 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); +} + +static void merge_dependencies(Unit *u, Unit *other, 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++) + if ((r = set_remove_and_put(back->dependencies[k], other, u)) < 0) { + + if (r == -EEXIST) + set_remove(back->dependencies[k], other); + else + assert(r == -ENOENT); + } + } + + complete_move(&u->dependencies[d], &other->dependencies[d]); + + set_free(other->dependencies[d]); + other->dependencies[d] = NULL; +} + +int unit_merge(Unit *u, Unit *other) { + UnitDependency d; + + assert(u); + assert(other); + assert(u->manager == other->manager); + assert(u->type != _UNIT_TYPE_INVALID); + + other = unit_follow_merge(other); + + if (other == u) + return 0; + + if (u->type != other->type) + return -EINVAL; + + if (!u->instance != !other->instance) + return -EINVAL; + + if (other->load_state != UNIT_STUB && + other->load_state != UNIT_ERROR) + return -EEXIST; + + if (other->job) + return -EEXIST; + + if (!UNIT_IS_INACTIVE_OR_FAILED(unit_active_state(other))) + return -EEXIST; + + /* Merge names */ + merge_names(u, other); + + /* 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, d); + + other->load_state = UNIT_MERGED; + other->merged_into = u; + + /* If there is still some data attached to the other node, we + * don't need it anymore, and can free it. */ + if (other->load_state != UNIT_STUB) + if (UNIT_VTABLE(other)->done) + UNIT_VTABLE(other)->done(other); + + unit_add_to_dbus_queue(u); + unit_add_to_cleanup_queue(other); + + return 0; +} + +int unit_merge_by_name(Unit *u, const char *name) { + Unit *other; + int r; + char *s = NULL; + + assert(u); + assert(name); + + if (unit_name_is_template(name)) { + if (!u->instance) + return -EINVAL; + + if (!(s = unit_name_replace_instance(name, u->instance))) + return -ENOMEM; + + name = s; + } + + if (!(other = manager_get_unit(u->manager, name))) + r = unit_add_name(u, name); + else + r = unit_merge(u, other); + + free(s); + return r; +} + +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->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. */ + + if (u->manager->running_as == MANAGER_SYSTEM) + if ((r = unit_add_two_dependencies_by_name(u, UNIT_REQUIRES, UNIT_AFTER, SPECIAL_JOURNALD_SOCKET, NULL, true)) < 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; + UnitDependency d; + Iterator i; + char *p2; + const char *prefix2; + char + timestamp1[FORMAT_TIMESTAMP_MAX], + timestamp2[FORMAT_TIMESTAMP_MAX], + timestamp3[FORMAT_TIMESTAMP_MAX], + timestamp4[FORMAT_TIMESTAMP_MAX], + timespan[FORMAT_TIMESPAN_MAX]; + Unit *following; + + assert(u); + assert(u->type >= 0); + + if (!prefix) + prefix = ""; + p2 = strappend(prefix, "\t"); + prefix2 = p2 ? p2 : prefix; + + 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\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", + 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(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))); + + SET_FOREACH(t, u->names, i) + fprintf(f, "%s\tName: %s\n", prefix, t); + + if ((following = unit_following(u))) + fprintf(f, "%s\tFollowing: %s\n", prefix, following->id); + + if (u->fragment_path) + fprintf(f, "%s\tFragment Path: %s\n", prefix, u->fragment_path); + + if (u->job_timeout > 0) + fprintf(f, "%s\tJob Timeout: %s\n", prefix, format_timespan(timespan, sizeof(timespan), u->job_timeout)); + + condition_dump_list(u->conditions, f, prefix); + + 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)); + + 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 (u->load_state == UNIT_LOADED) { + CGroupBonding *b; + CGroupAttribute *a; + + fprintf(f, + "%s\tStopWhenUnneeded: %s\n" + "%s\tRefuseManualStart: %s\n" + "%s\tRefuseManualStop: %s\n" + "%s\tDefaultDependencies: %s\n" + "%s\tOnFailureIsolate: %s\n" + "%s\tIgnoreOnIsolate: %s\n" + "%s\tIgnoreOnSnapshot: %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, yes_no(u->on_failure_isolate), + prefix, yes_no(u->ignore_on_isolate), + prefix, yes_no(u->ignore_on_snapshot)); + + LIST_FOREACH(by_unit, b, u->cgroup_bondings) + fprintf(f, "%s\tControlGroup: %s:%s\n", + prefix, b->controller, b->path); + + LIST_FOREACH(by_unit, a, u->cgroup_attributes) { + char *v = NULL; + + if (a->map_callback) + a->map_callback(a->controller, a->name, a->value, &v); + + fprintf(f, "%s\tControlGroupAttribute: %s %s \"%s\"\n", + prefix, a->controller, a->name, v ? v : a->value); + + free(v); + } + + 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); + + free(p2); +} + +/* Common implementation for multiple backends */ +int unit_load_fragment_and_dropin(Unit *u) { + int r; + + assert(u); + + /* Load a .service file */ + if ((r = unit_load_fragment(u)) < 0) + return r; + + if (u->load_state == UNIT_STUB) + return -ENOENT; + + /* Load drop-in directory data */ + if ((r = unit_load_dropin(unit_follow_merge(u))) < 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 */ + if ((r = unit_load_fragment(u)) < 0) + return r; + + if (u->load_state == UNIT_STUB) + u->load_state = UNIT_LOADED; + + /* Load drop-in directory data */ + if ((r = unit_load_dropin(unit_follow_merge(u))) < 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_default_dependencies(Unit *u) { + static const UnitDependency deps[] = { + UNIT_REQUIRED_BY, + UNIT_REQUIRED_BY_OVERRIDABLE, + UNIT_WANTED_BY, + UNIT_BOUND_BY + }; + + Unit *target; + Iterator i; + int r; + unsigned k; + + assert(u); + + for (k = 0; k < ELEMENTSOF(deps); k++) + SET_FOREACH(target, u->dependencies[deps[k]], i) + if ((r = unit_add_default_target_dependency(u, target)) < 0) + return r; + + return 0; +} + +int unit_load(Unit *u) { + int r; + + assert(u); + + if (u->in_load_queue) { + LIST_REMOVE(Unit, load_queue, u->manager->load_queue, u); + u->in_load_queue = false; + } + + if (u->type == _UNIT_TYPE_INVALID) + return -EINVAL; + + if (u->load_state != UNIT_STUB) + return 0; + + if (UNIT_VTABLE(u)->load) + if ((r = UNIT_VTABLE(u)->load(u)) < 0) + goto fail; + + if (u->load_state == UNIT_STUB) { + r = -ENOENT; + goto fail; + } + + if (u->load_state == UNIT_LOADED && + u->default_dependencies) + if ((r = unit_add_default_dependencies(u)) < 0) + goto fail; + + if (u->on_failure_isolate && + set_size(u->dependencies[UNIT_ON_FAILURE]) > 1) { + + log_error("More than one OnFailure= dependencies specified for %s but OnFailureIsolate= enabled. Refusing.", + u->id); + + r = -EINVAL; + goto fail; + } + + 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 = UNIT_ERROR; + u->load_error = r; + unit_add_to_dbus_queue(u); + unit_add_to_gc_queue(u); + + log_debug("Failed to load configuration for %s: %s", u->id, strerror(-r)); + + return r; +} + +bool unit_condition_test(Unit *u) { + assert(u); + + dual_timestamp_get(&u->condition_timestamp); + u->condition_result = condition_test_list(u->conditions); + + return u->condition_result; +} + +/* 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. + */ +int unit_start(Unit *u) { + UnitActiveState state; + Unit *following; + + assert(u); + + if (u->load_state != UNIT_LOADED) + return -EINVAL; + + /* 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; + + /* 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_debug("Starting of %s requested but condition failed. Ignoring.", u->id); + return -EALREADY; + } + + /* Forward to the main object, if we aren't it. */ + if ((following = unit_following(u))) { + log_debug("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); + + unit_status_printf(u, NULL, "Starting %s...", unit_description(u)); + return UNIT_VTABLE(u)->start(u); +} + +bool unit_can_start(Unit *u) { + assert(u); + + 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; + + if ((following = unit_following(u))) { + log_debug("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); + + unit_status_printf(u, NULL, "Stopping %s...", unit_description(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) + return -ENOEXEC; + + if ((following = unit_following(u))) { + log_debug("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) { + Iterator i; + Unit *other; + + 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; + + SET_FOREACH(other, u->dependencies[UNIT_REQUIRED_BY], i) + if (unit_pending_active(other)) + return; + + SET_FOREACH(other, u->dependencies[UNIT_REQUIRED_BY_OVERRIDABLE], i) + if (unit_pending_active(other)) + return; + + SET_FOREACH(other, u->dependencies[UNIT_WANTED_BY], i) + if (unit_pending_active(other)) + return; + + SET_FOREACH(other, u->dependencies[UNIT_BOUND_BY], i) + if (unit_pending_active(other)) + return; + + log_info("Service %s is not needed anymore. Stopping.", u->id); + + /* Ok, nobody needs us anymore. Sniff. Then let's commit suicide */ + manager_add_job(u->manager, JOB_STOP, u, JOB_FAIL, true, NULL, NULL); +} + +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, true, NULL, NULL); + + SET_FOREACH(other, u->dependencies[UNIT_BIND_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, true, NULL, NULL); + + SET_FOREACH(other, u->dependencies[UNIT_REQUIRES_OVERRIDABLE], 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, false, NULL, NULL); + + SET_FOREACH(other, u->dependencies[UNIT_REQUISITE], 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, true, 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, false, 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, true, 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, true, 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, true, 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_REQUIRES_OVERRIDABLE], 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_REQUISITE_OVERRIDABLE], i) + if (!UNIT_IS_INACTIVE_OR_DEACTIVATING(unit_active_state(other))) + unit_check_unneeded(other); + SET_FOREACH(other, u->dependencies[UNIT_BIND_TO], i) + if (!UNIT_IS_INACTIVE_OR_DEACTIVATING(unit_active_state(other))) + unit_check_unneeded(other); +} + +void unit_trigger_on_failure(Unit *u) { + Unit *other; + Iterator i; + + assert(u); + + if (set_size(u->dependencies[UNIT_ON_FAILURE]) <= 0) + return; + + log_info("Triggering OnFailure= dependencies of %s.", u->id); + + SET_FOREACH(other, u->dependencies[UNIT_ON_FAILURE], i) { + int r; + + if ((r = manager_add_job(u->manager, JOB_START, other, u->on_failure_isolate ? JOB_ISOLATE : JOB_REPLACE, true, NULL, NULL)) < 0) + log_error("Failed to enqueue OnFailure= job: %s", strerror(-r)); + } +} + +void unit_notify(Unit *u, UnitActiveState os, UnitActiveState ns, bool reload_success) { + 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 OK an expected + * behaviour here. For example: if a mount point is remounted + * this function will be called too! */ + + if (u->manager->n_reloading <= 0) { + dual_timestamp ts; + + dual_timestamp_get(&ts); + + if (UNIT_IS_INACTIVE_OR_FAILED(os) && !UNIT_IS_INACTIVE_OR_FAILED(ns)) + u->inactive_exit_timestamp = ts; + else if (!UNIT_IS_INACTIVE_OR_FAILED(os) && UNIT_IS_INACTIVE_OR_FAILED(ns)) + u->inactive_enter_timestamp = ts; + + if (!UNIT_IS_ACTIVE_OR_RELOADING(os) && UNIT_IS_ACTIVE_OR_RELOADING(ns)) + u->active_enter_timestamp = ts; + else if (UNIT_IS_ACTIVE_OR_RELOADING(os) && !UNIT_IS_ACTIVE_OR_RELOADING(ns)) + u->active_exit_timestamp = ts; + + timer_unit_notify(u, ns); + path_unit_notify(u, ns); + } + + if (UNIT_IS_INACTIVE_OR_FAILED(ns)) + cgroup_bonding_trim_list(u->cgroup_bondings, true); + + 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); + 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); + } + + break; + + case JOB_RELOAD: + case JOB_RELOAD_OR_START: + + if (u->job->state == JOB_RUNNING) { + if (ns == UNIT_ACTIVE) + job_finish_and_invalidate(u->job, reload_success ? JOB_DONE : JOB_FAILED); + 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); + } + } + + 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); + else if (u->job->state == JOB_RUNNING && ns != UNIT_DEACTIVATING) { + unexpected = true; + job_finish_and_invalidate(u->job, JOB_FAILED); + } + + break; + + default: + assert_not_reached("Job type unknown"); + } + + } else + unexpected = true; + + if (u->manager->n_reloading <= 0) { + + /* If this state change happened without being + * requested by a job, then let's retroactively start + * or stop dependencies. We skip that step when + * deserializing, since we don't want to create any + * additional jobs just because something is already + * activated. */ + + if (unexpected) { + if (UNIT_IS_INACTIVE_OR_FAILED(os) && UNIT_IS_ACTIVE_OR_ACTIVATING(ns)) + retroactively_start_dependencies(u); + else if (UNIT_IS_ACTIVE_OR_ACTIVATING(os) && UNIT_IS_INACTIVE_OR_DEACTIVATING(ns)) + retroactively_stop_dependencies(u); + } + + /* stop unneeded units regardless if going down was expected or not */ + if (UNIT_IS_ACTIVE_OR_ACTIVATING(os) && UNIT_IS_INACTIVE_OR_DEACTIVATING(ns)) + check_unneeded_dependencies(u); + + if (ns != os && ns == UNIT_FAILED) { + log_notice("Unit %s entered failed state.", u->id); + unit_trigger_on_failure(u); + } + } + + /* Some names are special */ + if (UNIT_IS_ACTIVE_OR_RELOADING(ns)) { + + if (unit_has_name(u, SPECIAL_DBUS_SERVICE)) + /* The bus just might have become available, + * hence try to connect to it, if we aren't + * yet connected. */ + bus_init(u->manager, true); + + if (u->type == UNIT_SERVICE && + !UNIT_IS_ACTIVE_OR_RELOADING(os) && + u->manager->n_reloading <= 0) { + /* Write audit record if we have just finished starting up */ + manager_send_unit_audit(u->manager, u, AUDIT_SERVICE_START, true); + u->in_audit = true; + } + + if (!UNIT_IS_ACTIVE_OR_RELOADING(os)) + manager_send_unit_plymouth(u->manager, 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) && + u->manager->n_reloading <= 0) { + + /* Hmm, if there was no start record written + * write it now, so that we always have a nice + * pair */ + if (!u->in_audit) { + manager_send_unit_audit(u->manager, u, AUDIT_SERVICE_START, ns == UNIT_INACTIVE); + + if (ns == UNIT_INACTIVE) + manager_send_unit_audit(u->manager, u, AUDIT_SERVICE_STOP, true); + } else + /* Write audit record if we have just finished shutting down */ + manager_send_unit_audit(u->manager, u, AUDIT_SERVICE_STOP, ns == UNIT_INACTIVE); + + u->in_audit = false; + } + } + + manager_recheck_journal(u->manager); + + /* Maybe we finished startup and are now ready for being + * stopped because unneeded? */ + unit_check_unneeded(u); + + unit_add_to_dbus_queue(u); + unit_add_to_gc_queue(u); +} + +int unit_watch_fd(Unit *u, int fd, uint32_t events, Watch *w) { + struct epoll_event ev; + + assert(u); + assert(fd >= 0); + assert(w); + assert(w->type == WATCH_INVALID || (w->type == WATCH_FD && w->fd == fd && w->data.unit == u)); + + zero(ev); + ev.data.ptr = w; + ev.events = events; + + if (epoll_ctl(u->manager->epoll_fd, + w->type == WATCH_INVALID ? EPOLL_CTL_ADD : EPOLL_CTL_MOD, + fd, + &ev) < 0) + return -errno; + + w->fd = fd; + w->type = WATCH_FD; + w->data.unit = u; + + return 0; +} + +void unit_unwatch_fd(Unit *u, Watch *w) { + assert(u); + assert(w); + + if (w->type == WATCH_INVALID) + return; + + assert(w->type == WATCH_FD); + assert(w->data.unit == u); + assert_se(epoll_ctl(u->manager->epoll_fd, EPOLL_CTL_DEL, w->fd, NULL) >= 0); + + w->fd = -1; + w->type = WATCH_INVALID; + w->data.unit = NULL; +} + +int unit_watch_pid(Unit *u, pid_t pid) { + assert(u); + assert(pid >= 1); + + /* Watch a specific PID. We only support one unit watching + * each PID for now. */ + + return hashmap_put(u->manager->watch_pids, LONG_TO_PTR(pid), u); +} + +void unit_unwatch_pid(Unit *u, pid_t pid) { + assert(u); + assert(pid >= 1); + + hashmap_remove_value(u->manager->watch_pids, LONG_TO_PTR(pid), u); +} + +int unit_watch_timer(Unit *u, usec_t delay, Watch *w) { + struct itimerspec its; + int flags, fd; + bool ours; + + assert(u); + assert(w); + assert(w->type == WATCH_INVALID || (w->type == WATCH_UNIT_TIMER && w->data.unit == u)); + + /* This will try to reuse the old timer if there is one */ + + if (w->type == WATCH_UNIT_TIMER) { + assert(w->data.unit == u); + assert(w->fd >= 0); + + ours = false; + fd = w->fd; + } else if (w->type == WATCH_INVALID) { + + ours = true; + if ((fd = timerfd_create(CLOCK_MONOTONIC, TFD_NONBLOCK|TFD_CLOEXEC)) < 0) + return -errno; + } else + assert_not_reached("Invalid watch type"); + + zero(its); + + if (delay <= 0) { + /* Set absolute time in the past, but not 0, since we + * don't want to disarm the timer */ + its.it_value.tv_sec = 0; + its.it_value.tv_nsec = 1; + + flags = TFD_TIMER_ABSTIME; + } else { + timespec_store(&its.it_value, delay); + flags = 0; + } + + /* This will also flush the elapse counter */ + if (timerfd_settime(fd, flags, &its, NULL) < 0) + goto fail; + + if (w->type == WATCH_INVALID) { + struct epoll_event ev; + + zero(ev); + ev.data.ptr = w; + ev.events = EPOLLIN; + + if (epoll_ctl(u->manager->epoll_fd, EPOLL_CTL_ADD, fd, &ev) < 0) + goto fail; + } + + w->type = WATCH_UNIT_TIMER; + w->fd = fd; + w->data.unit = u; + + return 0; + +fail: + if (ours) + close_nointr_nofail(fd); + + return -errno; +} + +void unit_unwatch_timer(Unit *u, Watch *w) { + assert(u); + assert(w); + + if (w->type == WATCH_INVALID) + return; + + assert(w->type == WATCH_UNIT_TIMER); + assert(w->data.unit == u); + assert(w->fd >= 0); + + assert_se(epoll_ctl(u->manager->epoll_fd, EPOLL_CTL_DEL, w->fd, NULL) >= 0); + close_nointr_nofail(w->fd); + + w->fd = -1; + w->type = WATCH_INVALID; + w->data.unit = NULL; +} + +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: + return true; + + case JOB_RESTART: + case JOB_TRY_RESTART: + return unit_can_start(u); + + case JOB_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"); + } +} + +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_REQUIRES_OVERRIDABLE] = UNIT_REQUIRED_BY_OVERRIDABLE, + [UNIT_WANTS] = UNIT_WANTED_BY, + [UNIT_REQUISITE] = UNIT_REQUIRED_BY, + [UNIT_REQUISITE_OVERRIDABLE] = UNIT_REQUIRED_BY_OVERRIDABLE, + [UNIT_BIND_TO] = UNIT_BOUND_BY, + [UNIT_REQUIRED_BY] = _UNIT_DEPENDENCY_INVALID, + [UNIT_REQUIRED_BY_OVERRIDABLE] = _UNIT_DEPENDENCY_INVALID, + [UNIT_WANTED_BY] = _UNIT_DEPENDENCY_INVALID, + [UNIT_BOUND_BY] = UNIT_BIND_TO, + [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_PROPAGATE_RELOAD_TO] = UNIT_PROPAGATE_RELOAD_FROM, + [UNIT_PROPAGATE_RELOAD_FROM] = UNIT_PROPAGATE_RELOAD_TO + }; + int r, q = 0, v = 0, w = 0; + + 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) + return 0; + + if ((r = set_ensure_allocated(&u->dependencies[d], trivial_hash_func, trivial_compare_func)) < 0) + return r; + + if (inverse_table[d] != _UNIT_DEPENDENCY_INVALID) + if ((r = set_ensure_allocated(&other->dependencies[inverse_table[d]], trivial_hash_func, trivial_compare_func)) < 0) + return r; + + if (add_reference) + if ((r = set_ensure_allocated(&u->dependencies[UNIT_REFERENCES], trivial_hash_func, trivial_compare_func)) < 0 || + (r = set_ensure_allocated(&other->dependencies[UNIT_REFERENCED_BY], trivial_hash_func, trivial_compare_func)) < 0) + return r; + + if ((q = set_put(u->dependencies[d], other)) < 0) + return q; + + if (inverse_table[d] != _UNIT_DEPENDENCY_INVALID) + if ((v = set_put(other->dependencies[inverse_table[d]], u)) < 0) { + r = v; + goto fail; + } + + if (add_reference) { + if ((w = set_put(u->dependencies[UNIT_REFERENCES], other)) < 0) { + r = w; + goto fail; + } + + if ((r = set_put(other->dependencies[UNIT_REFERENCED_BY], u)) < 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); + + if ((r = unit_add_dependency(u, d, other, add_reference)) < 0) + return r; + + if ((r = unit_add_dependency(u, e, other, add_reference)) < 0) + return r; + + return 0; +} + +static const char *resolve_template(Unit *u, const char *name, const char*path, char **p) { + char *s; + + assert(u); + assert(name || path); + + if (!name) + name = file_name_from_path(path); + + if (!unit_name_is_template(name)) { + *p = NULL; + return name; + } + + if (u->instance) + s = unit_name_replace_instance(name, u->instance); + else { + char *i; + + if (!(i = unit_name_to_prefix(u->id))) + return NULL; + + s = unit_name_replace_instance(name, i); + free(i); + } + + if (!s) + return NULL; + + *p = s; + return s; +} + +int unit_add_dependency_by_name(Unit *u, UnitDependency d, const char *name, const char *path, bool add_reference) { + Unit *other; + int r; + char *s; + + assert(u); + assert(name || path); + + if (!(name = resolve_template(u, name, path, &s))) + return -ENOMEM; + + if ((r = manager_load_unit(u->manager, name, path, NULL, &other)) < 0) + goto finish; + + r = unit_add_dependency(u, d, other, add_reference); + +finish: + free(s); + return r; +} + +int unit_add_two_dependencies_by_name(Unit *u, UnitDependency d, UnitDependency e, const char *name, const char *path, bool add_reference) { + Unit *other; + int r; + char *s; + + assert(u); + assert(name || path); + + if (!(name = resolve_template(u, name, path, &s))) + return -ENOMEM; + + if ((r = manager_load_unit(u->manager, name, path, NULL, &other)) < 0) + goto finish; + + r = unit_add_two_dependencies(u, d, e, other, add_reference); + +finish: + free(s); + return r; +} + +int unit_add_dependency_by_name_inverse(Unit *u, UnitDependency d, const char *name, const char *path, bool add_reference) { + Unit *other; + int r; + char *s; + + assert(u); + assert(name || path); + + if (!(name = resolve_template(u, name, path, &s))) + return -ENOMEM; + + if ((r = manager_load_unit(u->manager, name, path, NULL, &other)) < 0) + goto finish; + + r = unit_add_dependency(other, d, u, add_reference); + +finish: + free(s); + return r; +} + +int unit_add_two_dependencies_by_name_inverse(Unit *u, UnitDependency d, UnitDependency e, const char *name, const char *path, bool add_reference) { + Unit *other; + int r; + char *s; + + assert(u); + assert(name || path); + + if (!(name = resolve_template(u, name, path, &s))) + return -ENOMEM; + + if ((r = manager_load_unit(u->manager, name, path, NULL, &other)) < 0) + goto finish; + + if ((r = unit_add_two_dependencies(other, d, e, u, add_reference)) < 0) + goto finish; + +finish: + free(s); + return r; +} + +int set_unit_path(const char *p) { + char *cwd, *c; + int r; + + /* This is mostly for debug purposes */ + + if (path_is_absolute(p)) { + if (!(c = strdup(p))) + return -ENOMEM; + } else { + if (!(cwd = get_current_dir_name())) + return -errno; + + r = asprintf(&c, "%s/%s", cwd, p); + free(cwd); + + if (r < 0) + return -ENOMEM; + } + + if (setenv("SYSTEMD_UNIT_PATH", c, 0) < 0) { + r = -errno; + free(c); + return r; + } + + return 0; +} + +char *unit_dbus_path(Unit *u) { + char *p, *e; + + assert(u); + + if (!u->id) + return NULL; + + if (!(e = bus_path_escape(u->id))) + return NULL; + + p = strappend("/org/freedesktop/systemd1/unit/", e); + free(e); + + return p; +} + +int unit_add_cgroup(Unit *u, CGroupBonding *b) { + int r; + + assert(u); + assert(b); + + assert(b->path); + + if (!b->controller) { + if (!(b->controller = strdup(SYSTEMD_CGROUP_CONTROLLER))) + return -ENOMEM; + + b->ours = true; + } + + /* Ensure this hasn't been added yet */ + assert(!b->unit); + + if (streq(b->controller, SYSTEMD_CGROUP_CONTROLLER)) { + CGroupBonding *l; + + l = hashmap_get(u->manager->cgroup_bondings, b->path); + LIST_PREPEND(CGroupBonding, by_path, l, b); + + if ((r = hashmap_replace(u->manager->cgroup_bondings, b->path, l)) < 0) { + LIST_REMOVE(CGroupBonding, by_path, l, b); + return r; + } + } + + LIST_PREPEND(CGroupBonding, by_unit, u->cgroup_bondings, b); + b->unit = u; + + return 0; +} + +static char *default_cgroup_path(Unit *u) { + char *p; + + assert(u); + + if (u->instance) { + char *t; + + t = unit_name_template(u->id); + if (!t) + return NULL; + + p = join(u->manager->cgroup_hierarchy, "/", t, "/", u->instance, NULL); + free(t); + } else + p = join(u->manager->cgroup_hierarchy, "/", u->id, NULL); + + return p; +} + +int unit_add_cgroup_from_text(Unit *u, const char *name) { + char *controller = NULL, *path = NULL; + CGroupBonding *b = NULL; + bool ours = false; + int r; + + assert(u); + assert(name); + + if ((r = cg_split_spec(name, &controller, &path)) < 0) + return r; + + if (!path) { + path = default_cgroup_path(u); + ours = true; + } + + if (!controller) { + controller = strdup(SYSTEMD_CGROUP_CONTROLLER); + ours = true; + } + + if (!path || !controller) { + free(path); + free(controller); + + return -ENOMEM; + } + + if (cgroup_bonding_find_list(u->cgroup_bondings, controller)) { + r = -EEXIST; + goto fail; + } + + if (!(b = new0(CGroupBonding, 1))) { + r = -ENOMEM; + goto fail; + } + + b->controller = controller; + b->path = path; + b->ours = ours; + b->essential = streq(controller, SYSTEMD_CGROUP_CONTROLLER); + + if ((r = unit_add_cgroup(u, b)) < 0) + goto fail; + + return 0; + +fail: + free(path); + free(controller); + free(b); + + return r; +} + +static int unit_add_one_default_cgroup(Unit *u, const char *controller) { + CGroupBonding *b = NULL; + int r = -ENOMEM; + + assert(u); + + if (!controller) + controller = SYSTEMD_CGROUP_CONTROLLER; + + if (cgroup_bonding_find_list(u->cgroup_bondings, controller)) + return 0; + + if (!(b = new0(CGroupBonding, 1))) + return -ENOMEM; + + if (!(b->controller = strdup(controller))) + goto fail; + + if (!(b->path = default_cgroup_path(u))) + goto fail; + + b->ours = true; + b->essential = streq(controller, SYSTEMD_CGROUP_CONTROLLER); + + if ((r = unit_add_cgroup(u, b)) < 0) + goto fail; + + return 0; + +fail: + free(b->path); + free(b->controller); + free(b); + + return r; +} + +int unit_add_default_cgroups(Unit *u) { + CGroupAttribute *a; + char **c; + int r; + + assert(u); + + /* Adds in the default cgroups, if they weren't specified + * otherwise. */ + + if (!u->manager->cgroup_hierarchy) + return 0; + + if ((r = unit_add_one_default_cgroup(u, NULL)) < 0) + return r; + + STRV_FOREACH(c, u->manager->default_controllers) + unit_add_one_default_cgroup(u, *c); + + LIST_FOREACH(by_unit, a, u->cgroup_attributes) + unit_add_one_default_cgroup(u, a->controller); + + return 0; +} + +CGroupBonding* unit_get_default_cgroup(Unit *u) { + assert(u); + + return cgroup_bonding_find_list(u->cgroup_bondings, SYSTEMD_CGROUP_CONTROLLER); +} + +int unit_add_cgroup_attribute(Unit *u, const char *controller, const char *name, const char *value, CGroupAttributeMapCallback map_callback) { + int r; + char *c = NULL; + CGroupAttribute *a; + + assert(u); + assert(name); + assert(value); + + if (!controller) { + const char *dot; + + dot = strchr(name, '.'); + if (!dot) + return -EINVAL; + + c = strndup(name, dot - name); + if (!c) + return -ENOMEM; + + controller = c; + } + + if (streq(controller, SYSTEMD_CGROUP_CONTROLLER)) { + r = -EINVAL; + goto finish; + } + + a = new0(CGroupAttribute, 1); + if (!a) { + r = -ENOMEM; + goto finish; + } + + if (c) { + a->controller = c; + c = NULL; + } else + a->controller = strdup(controller); + + a->name = strdup(name); + a->value = strdup(value); + + if (!a->controller || !a->name || !a->value) { + free(a->controller); + free(a->name); + free(a->value); + free(a); + + return -ENOMEM; + } + + a->map_callback = map_callback; + + LIST_PREPEND(CGroupAttribute, by_unit, u->cgroup_attributes, a); + + r = 0; + +finish: + free(c); + return r; +} + +int unit_load_related_unit(Unit *u, const char *type, Unit **_found) { + char *t; + int r; + + assert(u); + assert(type); + assert(_found); + + if (!(t = unit_name_change_suffix(u->id, type))) + return -ENOMEM; + + assert(!unit_has_name(u, t)); + + r = manager_load_unit(u->manager, t, NULL, NULL, _found); + free(t); + + assert(r < 0 || *_found != u); + + return r; +} + +int unit_get_related_unit(Unit *u, const char *type, Unit **_found) { + Unit *found; + char *t; + + assert(u); + assert(type); + assert(_found); + + if (!(t = unit_name_change_suffix(u->id, type))) + return -ENOMEM; + + assert(!unit_has_name(u, t)); + + found = manager_get_unit(u->manager, t); + free(t); + + if (!found) + return -ENOENT; + + *_found = found; + return 0; +} + +static char *specifier_prefix_and_instance(char specifier, void *data, void *userdata) { + Unit *u = userdata; + assert(u); + + return unit_name_to_prefix_and_instance(u->id); +} + +static char *specifier_prefix(char specifier, void *data, void *userdata) { + Unit *u = userdata; + assert(u); + + return unit_name_to_prefix(u->id); +} + +static char *specifier_prefix_unescaped(char specifier, void *data, void *userdata) { + Unit *u = userdata; + char *p, *r; + + assert(u); + + if (!(p = unit_name_to_prefix(u->id))) + return NULL; + + r = unit_name_unescape(p); + free(p); + + return r; +} + +static char *specifier_instance_unescaped(char specifier, void *data, void *userdata) { + Unit *u = userdata; + assert(u); + + if (u->instance) + return unit_name_unescape(u->instance); + + return strdup(""); +} + +static char *specifier_filename(char specifier, void *data, void *userdata) { + Unit *u = userdata; + assert(u); + + if (u->instance) + return unit_name_path_unescape(u->instance); + + return unit_name_to_path(u->instance); +} + +static char *specifier_cgroup(char specifier, void *data, void *userdata) { + Unit *u = userdata; + assert(u); + + return default_cgroup_path(u); +} + +static char *specifier_cgroup_root(char specifier, void *data, void *userdata) { + Unit *u = userdata; + char *p; + assert(u); + + if (specifier == 'r') + return strdup(u->manager->cgroup_hierarchy); + + if (parent_of_path(u->manager->cgroup_hierarchy, &p) < 0) + return strdup(""); + + if (streq(p, "/")) { + free(p); + return strdup(""); + } + + return p; +} + +static char *specifier_runtime(char specifier, void *data, void *userdata) { + Unit *u = userdata; + assert(u); + + if (u->manager->running_as == MANAGER_USER) { + const char *e; + + e = getenv("XDG_RUNTIME_DIR"); + if (e) + return strdup(e); + } + + return strdup("/run"); +} + +char *unit_name_printf(Unit *u, const char* format) { + + /* + * 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); + + return specifier_printf(format, table, u); +} + +char *unit_full_printf(Unit *u, const char *format) { + + /* This is similar to unit_name_printf() but also supports + * unescaping. Also, adds a couple of additional codes: + * + * %c cgroup path of unit + * %r root cgroup path of this systemd instance (e.g. "/user/lennart/shared/systemd-4711") + * %R parent of root cgroup path (e.g. "/usr/lennart/shared") + * %t the runtime directory to place sockets in (e.g. "/run" or $XDG_RUNTIME_DIR) + */ + + 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_root, NULL }, + { 'R', specifier_cgroup_root, NULL }, + { 't', specifier_runtime, NULL }, + { 0, NULL, NULL } + }; + + assert(u); + assert(format); + + return specifier_printf(format, table, u); +} + +char **unit_full_printf_strv(Unit *u, char **l) { + size_t n; + char **r, **i, **j; + + /* Applies unit_full_printf to every entry in l */ + + assert(u); + + n = strv_length(l); + if (!(r = new(char*, n+1))) + return NULL; + + for (i = l, j = r; *i; i++, j++) + if (!(*j = unit_full_printf(u, *i))) + goto fail; + + *j = NULL; + return r; + +fail: + for (j--; j >= r; j--) + free(*j); + + free(r); + + return NULL; +} + +int unit_watch_bus_name(Unit *u, const char *name) { + assert(u); + assert(name); + + /* Watch a specific name on the bus. We only support one unit + * watching each name for now. */ + + return hashmap_put(u->manager->watch_bus, name, u); +} + +void unit_unwatch_bus_name(Unit *u, const char *name) { + assert(u); + assert(name); + + hashmap_remove_value(u->manager->watch_bus, name, u); +} + +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) { + int r; + + assert(u); + assert(f); + assert(fds); + + if (!unit_can_serialize(u)) + return 0; + + if ((r = UNIT_VTABLE(u)->serialize(u, f, fds)) < 0) + return r; + + if (u->job) + unit_serialize_item(u, f, "job", job_type_to_string(u->job->type)); + + 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); + + if (dual_timestamp_is_set(&u->condition_timestamp)) + unit_serialize_item(u, f, "condition-result", yes_no(u->condition_result)); + + /* End marker */ + fputc('\n', f); + return 0; +} + +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); +} + +void unit_serialize_item(Unit *u, FILE *f, const char *key, const char *value) { + assert(u); + assert(f); + assert(key); + assert(value); + + fprintf(f, "%s=%s\n", key, value); +} + +int unit_deserialize(Unit *u, FILE *f, FDSet *fds) { + int r; + + assert(u); + assert(f); + assert(fds); + + if (!unit_can_serialize(u)) + return 0; + + 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")) { + JobType type; + + if ((type = job_type_from_string(v)) < 0) + log_debug("Failed to parse job type value %s", v); + else + u->deserialized_job = type; + + 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, "condition-result")) { + int b; + + if ((b = parse_boolean(v)) < 0) + log_debug("Failed to parse condition result value %s", v); + else + u->condition_result = b; + + continue; + } + + if ((r = UNIT_VTABLE(u)->deserialize_item(u, l, v, fds)) < 0) + return r; + } +} + +int unit_add_node_link(Unit *u, const char *what, bool wants) { + Unit *device; + char *e; + int r; + + assert(u); + + if (!what) + return 0; + + /* Adds in links to the device node that this unit is based on */ + + if (!is_device_path(what)) + return 0; + + if (!(e = unit_name_build_escape(what+1, NULL, ".device"))) + return -ENOMEM; + + r = manager_load_unit(u->manager, e, NULL, NULL, &device); + free(e); + + if (r < 0) + return r; + + if ((r = unit_add_two_dependencies(u, UNIT_AFTER, UNIT_BIND_TO, device, true)) < 0) + return r; + + if (wants) + if ((r = unit_add_dependency(device, UNIT_WANTS, u, false)) < 0) + return r; + + return 0; +} + +int unit_coldplug(Unit *u) { + int r; + + assert(u); + + if (UNIT_VTABLE(u)->coldplug) + if ((r = UNIT_VTABLE(u)->coldplug(u)) < 0) + return r; + + if (u->deserialized_job >= 0) { + if ((r = manager_add_job(u->manager, u->deserialized_job, u, JOB_IGNORE_REQUIREMENTS, false, NULL, NULL)) < 0) + return r; + + u->deserialized_job = _JOB_TYPE_INVALID; + } + + return 0; +} + +void unit_status_printf(Unit *u, const char *status, const char *format, ...) { + va_list ap; + + assert(u); + assert(format); + + if (!UNIT_VTABLE(u)->show_status) + return; + + if (!manager_get_show_status(u->manager)) + return; + + if (!manager_is_booting_or_shutting_down(u->manager)) + return; + + va_start(ap, format); + status_vprintf(status, true, format, ap); + va_end(ap); +} + +bool unit_need_daemon_reload(Unit *u) { + assert(u); + + if (u->fragment_path) { + struct stat st; + + zero(st); + if (stat(u->fragment_path, &st) < 0) + /* What, cannot access this anymore? */ + return true; + + if (u->fragment_mtime > 0 && + timespec_load(&st.st_mtim) != u->fragment_mtime) + return true; + } + + if (UNIT_VTABLE(u)->need_daemon_reload) + return UNIT_VTABLE(u)->need_daemon_reload(u); + + return false; +} + +void unit_reset_failed(Unit *u) { + assert(u); + + if (UNIT_VTABLE(u)->reset_failed) + UNIT_VTABLE(u)->reset_failed(u); +} + +Unit *unit_following(Unit *u) { + assert(u); + + if (UNIT_VTABLE(u)->following) + return UNIT_VTABLE(u)->following(u); + + return NULL; +} + +bool unit_pending_inactive(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 (u->job && u->job->type == JOB_STOP) + return true; + + return false; +} + +bool unit_pending_active(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; +} + +UnitType unit_name_to_type(const char *n) { + UnitType t; + + assert(n); + + for (t = 0; t < _UNIT_TYPE_MAX; t++) + if (endswith(n, unit_vtable[t]->suffix)) + return t; + + return _UNIT_TYPE_INVALID; +} + +bool unit_name_is_valid(const char *n, bool template_ok) { + UnitType t; + + t = unit_name_to_type(n); + if (t < 0 || t >= _UNIT_TYPE_MAX) + return false; + + return unit_name_is_valid_no_type(n, template_ok); +} + +int unit_kill(Unit *u, KillWho w, KillMode m, int signo, DBusError *error) { + assert(u); + assert(w >= 0 && w < _KILL_WHO_MAX); + assert(m >= 0 && m < _KILL_MODE_MAX); + assert(signo > 0); + assert(signo < _NSIG); + + if (m == KILL_NONE) + return 0; + + if (!UNIT_VTABLE(u)->kill) + return -ENOTSUP; + + return UNIT_VTABLE(u)->kill(u, w, m, signo, error); +} + +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) { + assert(u); + + if (u->unit_file_state < 0 && u->fragment_path) + u->unit_file_state = unit_file_get_state( + u->manager->running_as == MANAGER_SYSTEM ? UNIT_FILE_SYSTEM : UNIT_FILE_USER, + NULL, file_name_from_path(u->fragment_path)); + + return u->unit_file_state; +} + +Unit* unit_ref_set(UnitRef *ref, Unit *u) { + assert(ref); + assert(u); + + if (ref->unit) + unit_ref_unset(ref); + + ref->unit = u; + LIST_PREPEND(UnitRef, refs, u->refs, ref); + return u; +} + +void unit_ref_unset(UnitRef *ref) { + assert(ref); + + if (!ref->unit) + return; + + LIST_REMOVE(UnitRef, refs, ref->unit->refs, ref); + ref->unit = NULL; +} + +static const char* const unit_load_state_table[_UNIT_LOAD_STATE_MAX] = { + [UNIT_STUB] = "stub", + [UNIT_LOADED] = "loaded", + [UNIT_ERROR] = "error", + [UNIT_MERGED] = "merged", + [UNIT_MASKED] = "masked" +}; + +DEFINE_STRING_TABLE_LOOKUP(unit_load_state, UnitLoadState); + +static const char* const unit_active_state_table[_UNIT_ACTIVE_STATE_MAX] = { + [UNIT_ACTIVE] = "active", + [UNIT_RELOADING] = "reloading", + [UNIT_INACTIVE] = "inactive", + [UNIT_FAILED] = "failed", + [UNIT_ACTIVATING] = "activating", + [UNIT_DEACTIVATING] = "deactivating" +}; + +DEFINE_STRING_TABLE_LOOKUP(unit_active_state, UnitActiveState); + +static const char* const unit_dependency_table[_UNIT_DEPENDENCY_MAX] = { + [UNIT_REQUIRES] = "Requires", + [UNIT_REQUIRES_OVERRIDABLE] = "RequiresOverridable", + [UNIT_WANTS] = "Wants", + [UNIT_REQUISITE] = "Requisite", + [UNIT_REQUISITE_OVERRIDABLE] = "RequisiteOverridable", + [UNIT_REQUIRED_BY] = "RequiredBy", + [UNIT_REQUIRED_BY_OVERRIDABLE] = "RequiredByOverridable", + [UNIT_BIND_TO] = "BindTo", + [UNIT_WANTED_BY] = "WantedBy", + [UNIT_CONFLICTS] = "Conflicts", + [UNIT_CONFLICTED_BY] = "ConflictedBy", + [UNIT_BOUND_BY] = "BoundBy", + [UNIT_BEFORE] = "Before", + [UNIT_AFTER] = "After", + [UNIT_REFERENCES] = "References", + [UNIT_REFERENCED_BY] = "ReferencedBy", + [UNIT_ON_FAILURE] = "OnFailure", + [UNIT_TRIGGERS] = "Triggers", + [UNIT_TRIGGERED_BY] = "TriggeredBy", + [UNIT_PROPAGATE_RELOAD_TO] = "PropagateReloadTo", + [UNIT_PROPAGATE_RELOAD_FROM] = "PropagateReloadFrom" +}; + +DEFINE_STRING_TABLE_LOOKUP(unit_dependency, UnitDependency); diff --git a/src/core/unit.h b/src/core/unit.h new file mode 100644 index 0000000000..756f465da3 --- /dev/null +++ b/src/core/unit.h @@ -0,0 +1,562 @@ +/*-*- Mode: C; c-basic-offset: 8; indent-tabs-mode: nil -*-*/ + +#ifndef foounithfoo +#define foounithfoo + +/*** + This file is part of systemd. + + Copyright 2010 Lennart Poettering + + systemd is free software; you can redistribute it and/or modify it + under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + systemd is distributed in the hope that 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 General Public License + along with systemd; If not, see . +***/ + +#include +#include + +typedef struct Unit Unit; +typedef struct UnitVTable UnitVTable; +typedef enum UnitType UnitType; +typedef enum UnitLoadState UnitLoadState; +typedef enum UnitActiveState UnitActiveState; +typedef enum UnitDependency UnitDependency; +typedef struct UnitRef UnitRef; + +#include "set.h" +#include "util.h" +#include "list.h" +#include "socket-util.h" +#include "execute.h" +#include "condition.h" +#include "install.h" + +enum UnitType { + UNIT_SERVICE = 0, + UNIT_SOCKET, + UNIT_TARGET, + UNIT_DEVICE, + UNIT_MOUNT, + UNIT_AUTOMOUNT, + UNIT_SNAPSHOT, + UNIT_TIMER, + UNIT_SWAP, + UNIT_PATH, + _UNIT_TYPE_MAX, + _UNIT_TYPE_INVALID = -1 +}; + +enum UnitLoadState { + UNIT_STUB, + UNIT_LOADED, + UNIT_ERROR, + UNIT_MERGED, + UNIT_MASKED, + _UNIT_LOAD_STATE_MAX, + _UNIT_LOAD_STATE_INVALID = -1 +}; + +enum UnitActiveState { + UNIT_ACTIVE, + UNIT_RELOADING, + UNIT_INACTIVE, + UNIT_FAILED, + UNIT_ACTIVATING, + UNIT_DEACTIVATING, + _UNIT_ACTIVE_STATE_MAX, + _UNIT_ACTIVE_STATE_INVALID = -1 +}; + +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; +} + +enum UnitDependency { + /* Positive dependencies */ + UNIT_REQUIRES, + UNIT_REQUIRES_OVERRIDABLE, + UNIT_REQUISITE, + UNIT_REQUISITE_OVERRIDABLE, + UNIT_WANTS, + UNIT_BIND_TO, + + /* Inverse of the above */ + UNIT_REQUIRED_BY, /* inverse of 'requires' and 'requisite' is 'required_by' */ + UNIT_REQUIRED_BY_OVERRIDABLE, /* inverse of 'requires_overridable' and 'requisite_overridable' is 'soft_required_by' */ + UNIT_WANTED_BY, /* inverse of 'wants' */ + UNIT_BOUND_BY, /* inverse of 'bind_to' */ + + /* Negative dependencies */ + UNIT_CONFLICTS, /* inverse of 'conflicts' is 'conflicted_by' */ + UNIT_CONFLICTED_BY, + + /* Order */ + UNIT_BEFORE, /* inverse of 'before' is 'after' and vice versa */ + UNIT_AFTER, + + /* On Failure */ + UNIT_ON_FAILURE, + + /* Triggers (i.e. a socket triggers a service) */ + UNIT_TRIGGERS, + UNIT_TRIGGERED_BY, + + /* Propagate reloads */ + UNIT_PROPAGATE_RELOAD_TO, + UNIT_PROPAGATE_RELOAD_FROM, + + /* Reference information for GC logic */ + UNIT_REFERENCES, /* Inverse of 'references' is 'referenced_by' */ + UNIT_REFERENCED_BY, + + _UNIT_DEPENDENCY_MAX, + _UNIT_DEPENDENCY_INVALID = -1 +}; + +#include "manager.h" +#include "job.h" +#include "cgroup.h" +#include "cgroup-attr.h" + +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 *description; + + char *fragment_path; /* if loaded from a config file this is the primary path to it */ + usec_t fragment_mtime; + + /* If there is something to do with this unit, then this is + * the job for it */ + Job *job; + + usec_t job_timeout; + + /* References to this */ + LIST_HEAD(UnitRef, refs); + + /* Conditions to check */ + LIST_HEAD(Condition, conditions); + + dual_timestamp condition_timestamp; + + dual_timestamp inactive_exit_timestamp; + dual_timestamp active_enter_timestamp; + dual_timestamp active_exit_timestamp; + dual_timestamp inactive_enter_timestamp; + + /* Counterparts in the cgroup filesystem */ + CGroupBonding *cgroup_bondings; + CGroupAttribute *cgroup_attributes; + + /* Per type list */ + LIST_FIELDS(Unit, units_by_type); + + /* 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); + + /* Used during GC sweeps */ + unsigned gc_marker; + + /* When deserializing, temporarily store the job type for this + * unit here, if there was a job scheduled */ + int deserialized_job; /* This is actually of type JobType */ + + /* Error code when we didn't manage to load the unit (negative) */ + int load_error; + + /* Cached unit file state */ + UnitFileState unit_file_state; + + /* 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; + + /* Isolate OnFailure unit */ + bool on_failure_isolate; + + /* Ignore this unit when isolating */ + bool ignore_on_isolate; + + /* Ignore this unit when snapshotting */ + bool ignore_on_snapshot; + + /* Did the last condition check suceed? */ + bool condition_result; + + bool in_load_queue:1; + bool in_dbus_queue:1; + bool in_cleanup_queue:1; + bool in_gc_queue:1; + + bool sent_dbus_new_signal:1; + + bool no_gc:1; + + bool in_audit:1; +}; + +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); +}; + +#include "service.h" +#include "timer.h" +#include "socket.h" +#include "target.h" +#include "device.h" +#include "mount.h" +#include "automount.h" +#include "snapshot.h" +#include "swap.h" +#include "path.h" + +struct UnitVTable { + const char *suffix; + + /* How much memory does an object of this unit type need */ + size_t object_size; + + /* 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 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, KillMode m, int signo, DBusError *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); + + /* 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); + + /* Return true when this unit is suitable for snapshotting */ + bool (*check_snapshot)(Unit *u); + + void (*fd_event)(Unit *u, int fd, uint32_t events, Watch *w); + void (*sigchld_event)(Unit *u, pid_t pid, int code, int status); + void (*timer_event)(Unit *u, uint64_t n_elapsed, Watch *w); + + /* Check whether unit needs a daemon reload */ + bool (*need_daemon_reload)(Unit *u); + + /* 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 (*cgroup_notify_empty)(Unit *u); + + /* Called whenever a process of this unit sends us a message */ + void (*notify_message)(Unit *u, pid_t pid, char **tags); + + /* Called whenever a name thus 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 whenever a bus PID lookup finishes */ + void (*bus_query_pid_done)(Unit *u, const char *name, pid_t pid); + + /* Called for each message received on the bus */ + DBusHandlerResult (*bus_message_handler)(Unit *u, DBusConnection *c, DBusMessage *message); + + /* 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); + + /* 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. */ + int (*enumerate)(Manager *m); + + /* Type specific cleanups. */ + void (*shutdown)(Manager *m); + + /* When sending out PropertiesChanged signal, which properties + * shall be invalidated? This is a NUL separated list of + * strings, to minimize relocations a little. */ + const char *bus_invalidating_properties; + + /* The interface name */ + const char *bus_interface; + + /* Can units of this type have multiple names? */ + bool no_alias:1; + + /* Instances make no sense for this type */ + bool no_instances:1; + + /* Exclude from automatic gc */ + bool no_gc:1; + + /* Show status updates on the console */ + bool show_status: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_CAST(SOCKET, Socket); +DEFINE_CAST(TIMER, Timer); +DEFINE_CAST(SERVICE, Service); +DEFINE_CAST(TARGET, Target); +DEFINE_CAST(DEVICE, Device); +DEFINE_CAST(MOUNT, Mount); +DEFINE_CAST(AUTOMOUNT, Automount); +DEFINE_CAST(SNAPSHOT, Snapshot); +DEFINE_CAST(SWAP, Swap); +DEFINE_CAST(PATH, Path); + +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_dependency_by_name_inverse(Unit *u, UnitDependency d, const char *name, const char *filename, bool add_reference); +int unit_add_two_dependencies_by_name_inverse(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_add_cgroup(Unit *u, CGroupBonding *b); +int unit_add_cgroup_from_text(Unit *u, const char *name); +int unit_add_default_cgroups(Unit *u); +CGroupBonding* unit_get_default_cgroup(Unit *u); +int unit_add_cgroup_attribute(Unit *u, const char *controller, const char *name, const char *value, CGroupAttributeMapCallback map_callback); + +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); + +int unit_load_fragment_and_dropin(Unit *u); +int unit_load_fragment_and_dropin_optional(Unit *u); +int unit_load(Unit *unit); + +const char *unit_description(Unit *u); + +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); +bool unit_can_start(Unit *u); +bool unit_can_isolate(Unit *u); + +int unit_start(Unit *u); +int unit_stop(Unit *u); +int unit_reload(Unit *u); + +int unit_kill(Unit *u, KillWho w, KillMode m, int signo, DBusError *error); + +void unit_notify(Unit *u, UnitActiveState os, UnitActiveState ns, bool reload_success); + +int unit_watch_fd(Unit *u, int fd, uint32_t events, Watch *w); +void unit_unwatch_fd(Unit *u, Watch *w); + +int unit_watch_pid(Unit *u, pid_t pid); +void unit_unwatch_pid(Unit *u, pid_t pid); + +int unit_watch_timer(Unit *u, usec_t delay, Watch *w); +void unit_unwatch_timer(Unit *u, Watch *w); + +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); +int unit_get_related_unit(Unit *u, const char *type, Unit **_found); + +char *unit_name_printf(Unit *u, const char* text); +char *unit_full_printf(Unit *u, const char *text); +char **unit_full_printf_strv(Unit *u, char **l); + +bool unit_can_serialize(Unit *u); +int unit_serialize(Unit *u, FILE *f, FDSet *fds); +void unit_serialize_item_format(Unit *u, FILE *f, const char *key, const char *value, ...) _printf_attr_(4,5); +void unit_serialize_item(Unit *u, FILE *f, const char *key, const char *value); +int unit_deserialize(Unit *u, FILE *f, FDSet *fds); + +int unit_add_node_link(Unit *u, const char *what, bool wants); + +int unit_coldplug(Unit *u); + +void unit_status_printf(Unit *u, const char *status, const char *format, ...); + +bool unit_need_daemon_reload(Unit *u); + +void unit_reset_failed(Unit *u); + +Unit *unit_following(Unit *u); + +bool unit_pending_inactive(Unit *u); +bool unit_pending_active(Unit *u); + +int unit_add_default_target_dependency(Unit *u, Unit *target); + +int unit_following_set(Unit *u, Set **s); + +UnitType unit_name_to_type(const char *n); +bool unit_name_is_valid(const char *n, bool template_ok); + +void unit_trigger_on_failure(Unit *u); + +bool unit_condition_test(Unit *u); + +UnitFileState unit_get_unit_file_state(Unit *u); + +Unit* unit_ref_set(UnitRef *ref, Unit *u); +void unit_ref_unset(UnitRef *ref); + +#define UNIT_DEREF(ref) ((ref).unit) + +const char *unit_load_state_to_string(UnitLoadState i); +UnitLoadState unit_load_state_from_string(const char *s); + +const char *unit_active_state_to_string(UnitActiveState i); +UnitActiveState unit_active_state_from_string(const char *s); + +const char *unit_dependency_to_string(UnitDependency i); +UnitDependency unit_dependency_from_string(const char *s); + +#endif diff --git a/src/dbus-automount.c b/src/dbus-automount.c deleted file mode 100644 index 8e45f81fcf..0000000000 --- a/src/dbus-automount.c +++ /dev/null @@ -1,72 +0,0 @@ -/*-*- Mode: C; c-basic-offset: 8; indent-tabs-mode: nil -*-*/ - -/*** - This file is part of systemd. - - Copyright 2010 Lennart Poettering - - systemd is free software; you can redistribute it and/or modify it - under the terms of the GNU General Public License as published by - the Free Software Foundation; either version 2 of the License, or - (at your option) any later version. - - systemd is distributed in the hope that 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 General Public License - along with systemd; If not, see . -***/ - -#include - -#include "dbus-unit.h" -#include "dbus-automount.h" -#include "dbus-common.h" - -#define BUS_AUTOMOUNT_INTERFACE \ - " \n" \ - " \n" \ - " \n" \ - " \n" \ - " \n" - -#define INTROSPECTION \ - DBUS_INTROSPECT_1_0_XML_DOCTYPE_DECL_NODE \ - "\n" \ - BUS_UNIT_INTERFACE \ - BUS_AUTOMOUNT_INTERFACE \ - BUS_PROPERTIES_INTERFACE \ - BUS_PEER_INTERFACE \ - BUS_INTROSPECTABLE_INTERFACE \ - "\n" - -#define INTERFACES_LIST \ - BUS_UNIT_INTERFACES_LIST \ - "org.freedesktop.systemd1.Automount\0" - -const char bus_automount_interface[] _introspect_("Automount") = BUS_AUTOMOUNT_INTERFACE; - -const char bus_automount_invalidating_properties[] = - "Result\0"; - -static DEFINE_BUS_PROPERTY_APPEND_ENUM(bus_automount_append_automount_result, automount_result, AutomountResult); - -static const BusProperty bus_automount_properties[] = { - { "Where", bus_property_append_string, "s", offsetof(Automount, where), true }, - { "DirectoryMode", bus_property_append_mode, "u", offsetof(Automount, directory_mode) }, - { "Result", bus_automount_append_automount_result, "s", offsetof(Automount, result) }, - { NULL, } -}; - -DBusHandlerResult bus_automount_message_handler(Unit *u, DBusConnection *c, DBusMessage *message) { - Automount *am = AUTOMOUNT(u); - const BusBoundProperties bps[] = { - { "org.freedesktop.systemd1.Unit", bus_unit_properties, u }, - { "org.freedesktop.systemd1.Automount", bus_automount_properties, am }, - { NULL, } - }; - - return bus_default_message_handler(c, message, INTROSPECTION, INTERFACES_LIST, bps); -} diff --git a/src/dbus-automount.h b/src/dbus-automount.h deleted file mode 100644 index 2fc8345048..0000000000 --- a/src/dbus-automount.h +++ /dev/null @@ -1,34 +0,0 @@ -/*-*- Mode: C; c-basic-offset: 8; indent-tabs-mode: nil -*-*/ - -#ifndef foodbusautomounthfoo -#define foodbusautomounthfoo - -/*** - This file is part of systemd. - - Copyright 2010 Lennart Poettering - - systemd is free software; you can redistribute it and/or modify it - under the terms of the GNU General Public License as published by - the Free Software Foundation; either version 2 of the License, or - (at your option) any later version. - - systemd is distributed in the hope that 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 General Public License - along with systemd; If not, see . -***/ - -#include - -#include "unit.h" - -DBusHandlerResult bus_automount_message_handler(Unit *u, DBusConnection *c, DBusMessage *message); - -extern const char bus_automount_interface[]; -extern const char bus_automount_invalidating_properties[]; - -#endif diff --git a/src/dbus-device.c b/src/dbus-device.c deleted file mode 100644 index b39fb9d381..0000000000 --- a/src/dbus-device.c +++ /dev/null @@ -1,65 +0,0 @@ -/*-*- Mode: C; c-basic-offset: 8; indent-tabs-mode: nil -*-*/ - -/*** - This file is part of systemd. - - Copyright 2010 Lennart Poettering - - systemd is free software; you can redistribute it and/or modify it - under the terms of the GNU General Public License as published by - the Free Software Foundation; either version 2 of the License, or - (at your option) any later version. - - systemd is distributed in the hope that 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 General Public License - along with systemd; If not, see . -***/ - -#include "dbus-unit.h" -#include "dbus-device.h" -#include "dbus-common.h" - -#define BUS_DEVICE_INTERFACE \ - " \n" \ - " \n" \ - " \n" - -#define INTROSPECTION \ - DBUS_INTROSPECT_1_0_XML_DOCTYPE_DECL_NODE \ - "\n" \ - BUS_UNIT_INTERFACE \ - BUS_DEVICE_INTERFACE \ - BUS_PROPERTIES_INTERFACE \ - BUS_PEER_INTERFACE \ - BUS_INTROSPECTABLE_INTERFACE \ - "\n" - -#define INTERFACES_LIST \ - BUS_UNIT_INTERFACES_LIST \ - "org.freedesktop.systemd1.Device\0" - -const char bus_device_interface[] _introspect_("Device") = BUS_DEVICE_INTERFACE; - -const char bus_device_invalidating_properties[] = - "SysFSPath\0"; - -static const BusProperty bus_device_properties[] = { - { "SysFSPath", bus_property_append_string, "s", offsetof(Device, sysfs), true }, - { NULL, } -}; - - -DBusHandlerResult bus_device_message_handler(Unit *u, DBusConnection *c, DBusMessage *message) { - Device *d = DEVICE(u); - const BusBoundProperties bps[] = { - { "org.freedesktop.systemd1.Unit", bus_unit_properties, u }, - { "org.freedesktop.systemd1.Device", bus_device_properties, d }, - { NULL, } - }; - - return bus_default_message_handler(c, message, INTROSPECTION, INTERFACES_LIST, bps); -} diff --git a/src/dbus-device.h b/src/dbus-device.h deleted file mode 100644 index fba270b4e6..0000000000 --- a/src/dbus-device.h +++ /dev/null @@ -1,34 +0,0 @@ -/*-*- Mode: C; c-basic-offset: 8; indent-tabs-mode: nil -*-*/ - -#ifndef foodbusdevicehfoo -#define foodbusdevicehfoo - -/*** - This file is part of systemd. - - Copyright 2010 Lennart Poettering - - systemd is free software; you can redistribute it and/or modify it - under the terms of the GNU General Public License as published by - the Free Software Foundation; either version 2 of the License, or - (at your option) any later version. - - systemd is distributed in the hope that 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 General Public License - along with systemd; If not, see . -***/ - -#include - -#include "unit.h" - -DBusHandlerResult bus_device_message_handler(Unit *u, DBusConnection *c, DBusMessage *message); - -extern const char bus_device_interface[]; -extern const char bus_device_invalidating_properties[]; - -#endif diff --git a/src/dbus-execute.c b/src/dbus-execute.c deleted file mode 100644 index 1fd2b21336..0000000000 --- a/src/dbus-execute.c +++ /dev/null @@ -1,422 +0,0 @@ -/*-*- Mode: C; c-basic-offset: 8; indent-tabs-mode: nil -*-*/ - -/*** - This file is part of systemd. - - Copyright 2010 Lennart Poettering - - systemd is free software; you can redistribute it and/or modify it - under the terms of the GNU General Public License as published by - the Free Software Foundation; either version 2 of the License, or - (at your option) any later version. - - systemd is distributed in the hope that 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 General Public License - along with systemd; If not, see . -***/ - -#include -#include -#include - -#include "dbus-execute.h" -#include "missing.h" -#include "ioprio.h" -#include "strv.h" -#include "dbus-common.h" - -DEFINE_BUS_PROPERTY_APPEND_ENUM(bus_execute_append_kill_mode, kill_mode, KillMode); - -DEFINE_BUS_PROPERTY_APPEND_ENUM(bus_execute_append_input, exec_input, ExecInput); -DEFINE_BUS_PROPERTY_APPEND_ENUM(bus_execute_append_output, exec_output, ExecOutput); - -int bus_execute_append_env_files(DBusMessageIter *i, const char *property, void *data) { - char **env_files = data, **j; - DBusMessageIter sub, sub2; - - assert(i); - assert(property); - - if (!dbus_message_iter_open_container(i, DBUS_TYPE_ARRAY, "(sb)", &sub)) - return -ENOMEM; - - STRV_FOREACH(j, env_files) { - dbus_bool_t b = false; - char *fn = *j; - - if (fn[0] == '-') { - b = true; - fn++; - } - - if (!dbus_message_iter_open_container(&sub, DBUS_TYPE_STRUCT, NULL, &sub2) || - !dbus_message_iter_append_basic(&sub2, DBUS_TYPE_STRING, &fn) || - !dbus_message_iter_append_basic(&sub2, DBUS_TYPE_BOOLEAN, &b) || - !dbus_message_iter_close_container(&sub, &sub2)) - return -ENOMEM; - } - - if (!dbus_message_iter_close_container(i, &sub)) - return -ENOMEM; - - return 0; -} - -int bus_execute_append_oom_score_adjust(DBusMessageIter *i, const char *property, void *data) { - ExecContext *c = data; - int32_t n; - - assert(i); - assert(property); - assert(c); - - if (c->oom_score_adjust_set) - n = c->oom_score_adjust; - else { - char *t; - - n = 0; - if (read_one_line_file("/proc/self/oom_score_adj", &t) >= 0) { - safe_atoi(t, &n); - free(t); - } else if (read_one_line_file("/proc/self/oom_adj", &t) >= 0) { - safe_atoi(t, &n); - free(t); - - if (n == OOM_ADJUST_MAX) - n = OOM_SCORE_ADJ_MAX; - else - n = (n * OOM_SCORE_ADJ_MAX) / -OOM_DISABLE; - } - } - - if (!dbus_message_iter_append_basic(i, DBUS_TYPE_INT32, &n)) - return -ENOMEM; - - return 0; -} - -int bus_execute_append_nice(DBusMessageIter *i, const char *property, void *data) { - ExecContext *c = data; - int32_t n; - - assert(i); - assert(property); - assert(c); - - if (c->nice_set) - n = c->nice; - else - n = getpriority(PRIO_PROCESS, 0); - - if (!dbus_message_iter_append_basic(i, DBUS_TYPE_INT32, &n)) - return -ENOMEM; - - return 0; -} - -int bus_execute_append_ioprio(DBusMessageIter *i, const char *property, void *data) { - ExecContext *c = data; - int32_t n; - - assert(i); - assert(property); - assert(c); - - if (c->ioprio_set) - n = c->ioprio; - else - n = ioprio_get(IOPRIO_WHO_PROCESS, 0); - - if (!dbus_message_iter_append_basic(i, DBUS_TYPE_INT32, &n)) - return -ENOMEM; - - return 0; -} - -int bus_execute_append_cpu_sched_policy(DBusMessageIter *i, const char *property, void *data) { - ExecContext *c = data; - int32_t n; - - assert(i); - assert(property); - assert(c); - - if (c->cpu_sched_set) - n = c->cpu_sched_policy; - else - n = sched_getscheduler(0); - - if (!dbus_message_iter_append_basic(i, DBUS_TYPE_INT32, &n)) - return -ENOMEM; - - return 0; -} - -int bus_execute_append_cpu_sched_priority(DBusMessageIter *i, const char *property, void *data) { - ExecContext *c = data; - int32_t n; - - assert(i); - assert(property); - assert(c); - - if (c->cpu_sched_set) - n = c->cpu_sched_priority; - else { - struct sched_param p; - n = 0; - - zero(p); - if (sched_getparam(0, &p) >= 0) - n = p.sched_priority; - } - - if (!dbus_message_iter_append_basic(i, DBUS_TYPE_INT32, &n)) - return -ENOMEM; - - return 0; -} - -int bus_execute_append_affinity(DBusMessageIter *i, const char *property, void *data) { - ExecContext *c = data; - dbus_bool_t b; - DBusMessageIter sub; - - assert(i); - assert(property); - assert(c); - - if (!dbus_message_iter_open_container(i, DBUS_TYPE_ARRAY, "y", &sub)) - return -ENOMEM; - - if (c->cpuset) - b = dbus_message_iter_append_fixed_array(&sub, DBUS_TYPE_BYTE, &c->cpuset, CPU_ALLOC_SIZE(c->cpuset_ncpus)); - else - b = dbus_message_iter_append_fixed_array(&sub, DBUS_TYPE_BYTE, &c->cpuset, 0); - - if (!b) - return -ENOMEM; - - if (!dbus_message_iter_close_container(i, &sub)) - return -ENOMEM; - - return 0; -} - -int bus_execute_append_timer_slack_nsec(DBusMessageIter *i, const char *property, void *data) { - ExecContext *c = data; - uint64_t u; - - assert(i); - assert(property); - assert(c); - - if (c->timer_slack_nsec_set) - u = (uint64_t) c->timer_slack_nsec; - else - u = (uint64_t) prctl(PR_GET_TIMERSLACK); - - if (!dbus_message_iter_append_basic(i, DBUS_TYPE_UINT64, &u)) - return -ENOMEM; - - return 0; -} - -int bus_execute_append_capability_bs(DBusMessageIter *i, const char *property, void *data) { - ExecContext *c = data; - uint64_t normal, inverted; - - assert(i); - assert(property); - assert(c); - - /* We store this negated internally, to match the kernel, but - * we expose it normalized. */ - - normal = *(uint64_t*) data; - inverted = ~normal; - - return bus_property_append_uint64(i, property, &inverted); -} - -int bus_execute_append_capabilities(DBusMessageIter *i, const char *property, void *data) { - ExecContext *c = data; - char *t = NULL; - const char *s; - dbus_bool_t b; - - assert(i); - assert(property); - assert(c); - - if (c->capabilities) - s = t = cap_to_text(c->capabilities, NULL); - else - s = ""; - - if (!s) - return -ENOMEM; - - b = dbus_message_iter_append_basic(i, DBUS_TYPE_STRING, &s); - - if (t) - cap_free(t); - - if (!b) - return -ENOMEM; - - return 0; -} - -int bus_execute_append_rlimits(DBusMessageIter *i, const char *property, void *data) { - ExecContext *c = data; - int r; - uint64_t u; - - assert(i); - assert(property); - assert(c); - - assert_se((r = rlimit_from_string(property)) >= 0); - - if (c->rlimit[r]) - u = (uint64_t) c->rlimit[r]->rlim_max; - else { - struct rlimit rl; - - zero(rl); - getrlimit(r, &rl); - - u = (uint64_t) rl.rlim_max; - } - - if (!dbus_message_iter_append_basic(i, DBUS_TYPE_UINT64, &u)) - return -ENOMEM; - - return 0; -} - -int bus_execute_append_command(DBusMessageIter *i, const char *property, void *data) { - ExecCommand *c = data; - DBusMessageIter sub, sub2, sub3; - - assert(i); - assert(property); - - if (!dbus_message_iter_open_container(i, DBUS_TYPE_ARRAY, "(sasbttttuii)", &sub)) - return -ENOMEM; - - LIST_FOREACH(command, c, c) { - char **l; - uint32_t pid; - int32_t code, status; - dbus_bool_t b; - - if (!c->path) - continue; - - if (!dbus_message_iter_open_container(&sub, DBUS_TYPE_STRUCT, NULL, &sub2) || - !dbus_message_iter_append_basic(&sub2, DBUS_TYPE_STRING, &c->path) || - !dbus_message_iter_open_container(&sub2, DBUS_TYPE_ARRAY, "s", &sub3)) - return -ENOMEM; - - STRV_FOREACH(l, c->argv) - if (!dbus_message_iter_append_basic(&sub3, DBUS_TYPE_STRING, l)) - return -ENOMEM; - - pid = (uint32_t) c->exec_status.pid; - code = (int32_t) c->exec_status.code; - status = (int32_t) c->exec_status.status; - - b = !!c->ignore; - - if (!dbus_message_iter_close_container(&sub2, &sub3) || - !dbus_message_iter_append_basic(&sub2, DBUS_TYPE_BOOLEAN, &b) || - !dbus_message_iter_append_basic(&sub2, DBUS_TYPE_UINT64, &c->exec_status.start_timestamp.realtime) || - !dbus_message_iter_append_basic(&sub2, DBUS_TYPE_UINT64, &c->exec_status.start_timestamp.monotonic) || - !dbus_message_iter_append_basic(&sub2, DBUS_TYPE_UINT64, &c->exec_status.exit_timestamp.realtime) || - !dbus_message_iter_append_basic(&sub2, DBUS_TYPE_UINT64, &c->exec_status.exit_timestamp.monotonic) || - !dbus_message_iter_append_basic(&sub2, DBUS_TYPE_UINT32, &pid) || - !dbus_message_iter_append_basic(&sub2, DBUS_TYPE_INT32, &code) || - !dbus_message_iter_append_basic(&sub2, DBUS_TYPE_INT32, &status)) - return -ENOMEM; - - if (!dbus_message_iter_close_container(&sub, &sub2)) - return -ENOMEM; - } - - if (!dbus_message_iter_close_container(i, &sub)) - return -ENOMEM; - - return 0; -} - -const BusProperty bus_exec_context_properties[] = { - { "Environment", bus_property_append_strv, "as", offsetof(ExecContext, environment), true }, - { "EnvironmentFiles", bus_execute_append_env_files, "a(sb)", offsetof(ExecContext, environment_files), true }, - { "UMask", bus_property_append_mode, "u", offsetof(ExecContext, umask) }, - { "LimitCPU", bus_execute_append_rlimits, "t", 0 }, - { "LimitFSIZE", bus_execute_append_rlimits, "t", 0 }, - { "LimitDATA", bus_execute_append_rlimits, "t", 0 }, - { "LimitSTACK", bus_execute_append_rlimits, "t", 0 }, - { "LimitCORE", bus_execute_append_rlimits, "t", 0 }, - { "LimitRSS", bus_execute_append_rlimits, "t", 0 }, - { "LimitNOFILE", bus_execute_append_rlimits, "t", 0 }, - { "LimitAS", bus_execute_append_rlimits, "t", 0 }, - { "LimitNPROC", bus_execute_append_rlimits, "t", 0 }, - { "LimitMEMLOCK", bus_execute_append_rlimits, "t", 0 }, - { "LimitLOCKS", bus_execute_append_rlimits, "t", 0 }, - { "LimitSIGPENDING", bus_execute_append_rlimits, "t", 0 }, - { "LimitMSGQUEUE", bus_execute_append_rlimits, "t", 0 }, - { "LimitNICE", bus_execute_append_rlimits, "t", 0 }, - { "LimitRTPRIO", bus_execute_append_rlimits, "t", 0 }, - { "LimitRTTIME", bus_execute_append_rlimits, "t", 0 }, - { "WorkingDirectory", bus_property_append_string, "s", offsetof(ExecContext, working_directory), true }, - { "RootDirectory", bus_property_append_string, "s", offsetof(ExecContext, root_directory), true }, - { "OOMScoreAdjust", bus_execute_append_oom_score_adjust, "i", 0 }, - { "Nice", bus_execute_append_nice, "i", 0 }, - { "IOScheduling", bus_execute_append_ioprio, "i", 0 }, - { "CPUSchedulingPolicy", bus_execute_append_cpu_sched_policy, "i", 0 }, - { "CPUSchedulingPriority", bus_execute_append_cpu_sched_priority, "i", 0 }, - { "CPUAffinity", bus_execute_append_affinity, "ay", 0 }, - { "TimerSlackNSec", bus_execute_append_timer_slack_nsec, "t", 0 }, - { "CPUSchedulingResetOnFork", bus_property_append_bool, "b", offsetof(ExecContext, cpu_sched_reset_on_fork) }, - { "NonBlocking", bus_property_append_bool, "b", offsetof(ExecContext, non_blocking) }, - { "StandardInput", bus_execute_append_input, "s", offsetof(ExecContext, std_input) }, - { "StandardOutput", bus_execute_append_output, "s", offsetof(ExecContext, std_output) }, - { "StandardError", bus_execute_append_output, "s", offsetof(ExecContext, std_error) }, - { "TTYPath", bus_property_append_string, "s", offsetof(ExecContext, tty_path), true }, - { "TTYReset", bus_property_append_bool, "b", offsetof(ExecContext, tty_reset) }, - { "TTYVHangup", bus_property_append_bool, "b", offsetof(ExecContext, tty_vhangup) }, - { "TTYVTDisallocate", bus_property_append_bool, "b", offsetof(ExecContext, tty_vt_disallocate) }, - { "SyslogPriority", bus_property_append_int, "i", offsetof(ExecContext, syslog_priority) }, - { "SyslogIdentifier", bus_property_append_string, "s", offsetof(ExecContext, syslog_identifier), true }, - { "SyslogLevelPrefix", bus_property_append_bool, "b", offsetof(ExecContext, syslog_level_prefix) }, - { "Capabilities", bus_execute_append_capabilities, "s", 0 }, - { "SecureBits", bus_property_append_int, "i", offsetof(ExecContext, secure_bits) }, - { "CapabilityBoundingSet", bus_execute_append_capability_bs, "t", offsetof(ExecContext, capability_bounding_set_drop) }, - { "User", bus_property_append_string, "s", offsetof(ExecContext, user), true }, - { "Group", bus_property_append_string, "s", offsetof(ExecContext, group), true }, - { "SupplementaryGroups", bus_property_append_strv, "as", offsetof(ExecContext, supplementary_groups), true }, - { "TCPWrapName", bus_property_append_string, "s", offsetof(ExecContext, tcpwrap_name), true }, - { "PAMName", bus_property_append_string, "s", offsetof(ExecContext, pam_name), true }, - { "ReadWriteDirectories", bus_property_append_strv, "as", offsetof(ExecContext, read_write_dirs), true }, - { "ReadOnlyDirectories", bus_property_append_strv, "as", offsetof(ExecContext, read_only_dirs), true }, - { "InaccessibleDirectories", bus_property_append_strv, "as", offsetof(ExecContext, inaccessible_dirs), true }, - { "MountFlags", bus_property_append_ul, "t", offsetof(ExecContext, mount_flags) }, - { "PrivateTmp", bus_property_append_bool, "b", offsetof(ExecContext, private_tmp) }, - { "PrivateNetwork", bus_property_append_bool, "b", offsetof(ExecContext, private_network) }, - { "SameProcessGroup", bus_property_append_bool, "b", offsetof(ExecContext, same_pgrp) }, - { "KillMode", bus_execute_append_kill_mode, "s", offsetof(ExecContext, kill_mode) }, - { "KillSignal", bus_property_append_int, "i", offsetof(ExecContext, kill_signal) }, - { "UtmpIdentifier", bus_property_append_string, "s", offsetof(ExecContext, utmp_id), true }, - { "ControlGroupModify", bus_property_append_bool, "b", offsetof(ExecContext, control_group_modify) }, - { "ControlGroupPersistent", bus_property_append_tristate_false, "b", offsetof(ExecContext, control_group_persistent) }, - { "IgnoreSIGPIPE", bus_property_append_bool, "b", offsetof(ExecContext, ignore_sigpipe ) }, - { NULL, } -}; diff --git a/src/dbus-execute.h b/src/dbus-execute.h deleted file mode 100644 index 03cd69d126..0000000000 --- a/src/dbus-execute.h +++ /dev/null @@ -1,125 +0,0 @@ -/*-*- Mode: C; c-basic-offset: 8; indent-tabs-mode: nil -*-*/ - -#ifndef foodbusexecutehfoo -#define foodbusexecutehfoo - -/*** - This file is part of systemd. - - Copyright 2010 Lennart Poettering - - systemd is free software; you can redistribute it and/or modify it - under the terms of the GNU General Public License as published by - the Free Software Foundation; either version 2 of the License, or - (at your option) any later version. - - systemd is distributed in the hope that 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 General Public License - along with systemd; If not, see . -***/ - -#include - -#include "manager.h" -#include "dbus-common.h" - -#define BUS_EXEC_STATUS_INTERFACE(prefix) \ - " \n" \ - " \n" \ - " \n" \ - " \n" \ - " \n" \ - " \n" \ - " \n" - -#define BUS_EXEC_CONTEXT_INTERFACE \ - " \n" \ - " \n" \ - " \n" \ - " \n" \ - " \n" \ - " \n" \ - " \n" \ - " \n" \ - " \n" \ - " \n" \ - " \n" \ - " \n" \ - " \n" \ - " \n" \ - " \n" \ - " \n" \ - " \n" \ - " \n" \ - " \n" \ - " \n" \ - " \n" \ - " \n" \ - " \n" \ - " \n" \ - " \n" \ - " \n" \ - " \n" \ - " \n" \ - " \n" \ - " \n" \ - " \n" \ - " \n" \ - " \n" \ - " \n" \ - " \n" \ - " \n" \ - " \n" \ - " \n" \ - " \n" \ - " \n" \ - " \n" \ - " \n" \ - " \n" \ - " \n" \ - " \n" \ - " \n" \ - " \n" \ - " \n" \ - " \n" \ - " \n" \ - " \n" \ - " \n" \ - " \n" \ - " \n" \ - " \n" \ - " \n" \ - " \n" \ - " \n" \ - " \n" \ - " \n" - -#define BUS_EXEC_COMMAND_INTERFACE(name) \ - " \n" - -extern const BusProperty bus_exec_context_properties[]; - -#define BUS_EXEC_COMMAND_PROPERTY(name, command, indirect) \ - { name, bus_execute_append_command, "a(sasbttttuii)", (command), (indirect), NULL } - -int bus_execute_append_output(DBusMessageIter *i, const char *property, void *data); -int bus_execute_append_input(DBusMessageIter *i, const char *property, void *data); -int bus_execute_append_oom_score_adjust(DBusMessageIter *i, const char *property, void *data); -int bus_execute_append_nice(DBusMessageIter *i, const char *property, void *data); -int bus_execute_append_ioprio(DBusMessageIter *i, const char *property, void *data); -int bus_execute_append_cpu_sched_policy(DBusMessageIter *i, const char *property, void *data); -int bus_execute_append_cpu_sched_priority(DBusMessageIter *i, const char *property, void *data); -int bus_execute_append_affinity(DBusMessageIter *i, const char *property, void *data); -int bus_execute_append_timer_slack_nsec(DBusMessageIter *i, const char *property, void *data); -int bus_execute_append_capabilities(DBusMessageIter *i, const char *property, void *data); -int bus_execute_append_capability_bs(DBusMessageIter *i, const char *property, void *data); -int bus_execute_append_rlimits(DBusMessageIter *i, const char *property, void *data); -int bus_execute_append_command(DBusMessageIter *u, const char *property, void *data); -int bus_execute_append_kill_mode(DBusMessageIter *i, const char *property, void *data); -int bus_execute_append_env_files(DBusMessageIter *i, const char *property, void *data); - -#endif diff --git a/src/dbus-job.c b/src/dbus-job.c deleted file mode 100644 index ab6d610243..0000000000 --- a/src/dbus-job.c +++ /dev/null @@ -1,354 +0,0 @@ -/*-*- Mode: C; c-basic-offset: 8; indent-tabs-mode: nil -*-*/ - -/*** - This file is part of systemd. - - Copyright 2010 Lennart Poettering - - systemd is free software; you can redistribute it and/or modify it - under the terms of the GNU General Public License as published by - the Free Software Foundation; either version 2 of the License, or - (at your option) any later version. - - systemd is distributed in the hope that 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 General Public License - along with systemd; If not, see . -***/ - -#include - -#include "dbus.h" -#include "log.h" -#include "dbus-job.h" -#include "dbus-common.h" - -#define BUS_JOB_INTERFACE \ - " \n" \ - " \n" \ - " \n" \ - " \n" \ - " \n" \ - " \n" \ - " \n" - -#define INTROSPECTION \ - DBUS_INTROSPECT_1_0_XML_DOCTYPE_DECL_NODE \ - "\n" \ - BUS_JOB_INTERFACE \ - BUS_PROPERTIES_INTERFACE \ - BUS_PEER_INTERFACE \ - BUS_INTROSPECTABLE_INTERFACE \ - "\n" - -const char bus_job_interface[] _introspect_("Job") = BUS_JOB_INTERFACE; - -#define INTERFACES_LIST \ - BUS_GENERIC_INTERFACES_LIST \ - "org.freedesktop.systemd1.Job\0" - -#define INVALIDATING_PROPERTIES \ - "State\0" - -static DEFINE_BUS_PROPERTY_APPEND_ENUM(bus_job_append_state, job_state, JobState); -static DEFINE_BUS_PROPERTY_APPEND_ENUM(bus_job_append_type, job_type, JobType); - -static int bus_job_append_unit(DBusMessageIter *i, const char *property, void *data) { - Job *j = data; - DBusMessageIter sub; - char *p; - - assert(i); - assert(property); - assert(j); - - if (!dbus_message_iter_open_container(i, DBUS_TYPE_STRUCT, NULL, &sub)) - return -ENOMEM; - - if (!(p = unit_dbus_path(j->unit))) - return -ENOMEM; - - if (!dbus_message_iter_append_basic(&sub, DBUS_TYPE_STRING, &j->unit->id) || - !dbus_message_iter_append_basic(&sub, DBUS_TYPE_OBJECT_PATH, &p)) { - free(p); - return -ENOMEM; - } - - free(p); - - if (!dbus_message_iter_close_container(i, &sub)) - return -ENOMEM; - - return 0; -} - -static const BusProperty bus_job_properties[] = { - { "Id", bus_property_append_uint32, "u", offsetof(Job, id) }, - { "State", bus_job_append_state, "s", offsetof(Job, state) }, - { "JobType", bus_job_append_type, "s", offsetof(Job, type) }, - { "Unit", bus_job_append_unit, "(so)", 0 }, - { NULL, } -}; - -static DBusHandlerResult bus_job_message_dispatch(Job *j, DBusConnection *connection, DBusMessage *message) { - DBusMessage *reply = NULL; - - if (dbus_message_is_method_call(message, "org.freedesktop.systemd1.Job", "Cancel")) { - if (!(reply = dbus_message_new_method_return(message))) - goto oom; - - job_finish_and_invalidate(j, JOB_CANCELED); - - } else { - const BusBoundProperties bps[] = { - { "org.freedesktop.systemd1.Job", bus_job_properties, j }, - { NULL, } - }; - return bus_default_message_handler(connection, message, INTROSPECTION, INTERFACES_LIST, bps); - } - - if (reply) { - if (!dbus_connection_send(connection, reply, NULL)) - goto oom; - - dbus_message_unref(reply); - } - - return DBUS_HANDLER_RESULT_HANDLED; - -oom: - if (reply) - dbus_message_unref(reply); - - return DBUS_HANDLER_RESULT_NEED_MEMORY; -} - -static DBusHandlerResult bus_job_message_handler(DBusConnection *connection, DBusMessage *message, void *data) { - Manager *m = data; - Job *j; - int r; - DBusMessage *reply; - - assert(connection); - assert(message); - assert(m); - - if (streq(dbus_message_get_path(message), "/org/freedesktop/systemd1/job")) { - /* Be nice to gdbus and return introspection data for our mid-level paths */ - - if (dbus_message_is_method_call(message, "org.freedesktop.DBus.Introspectable", "Introspect")) { - char *introspection = NULL; - FILE *f; - Iterator i; - size_t size; - - if (!(reply = dbus_message_new_method_return(message))) - goto oom; - - /* We roll our own introspection code here, instead of - * relying on bus_default_message_handler() because we - * need to generate our introspection string - * dynamically. */ - - if (!(f = open_memstream(&introspection, &size))) - goto oom; - - fputs(DBUS_INTROSPECT_1_0_XML_DOCTYPE_DECL_NODE - "\n", f); - - fputs(BUS_INTROSPECTABLE_INTERFACE, f); - fputs(BUS_PEER_INTERFACE, f); - - HASHMAP_FOREACH(j, m->jobs, i) - fprintf(f, "", (unsigned long) j->id); - - fputs("\n", f); - - if (ferror(f)) { - fclose(f); - free(introspection); - goto oom; - } - - fclose(f); - - if (!introspection) - goto oom; - - if (!dbus_message_append_args(reply, DBUS_TYPE_STRING, &introspection, DBUS_TYPE_INVALID)) { - free(introspection); - goto oom; - } - - free(introspection); - - if (!dbus_connection_send(connection, reply, NULL)) - goto oom; - - dbus_message_unref(reply); - - return DBUS_HANDLER_RESULT_HANDLED; - } - - return DBUS_HANDLER_RESULT_NOT_YET_HANDLED; - } - - if ((r = manager_get_job_from_dbus_path(m, dbus_message_get_path(message), &j)) < 0) { - - if (r == -ENOMEM) - return DBUS_HANDLER_RESULT_NEED_MEMORY; - - if (r == -ENOENT) { - DBusError e; - - dbus_error_init(&e); - dbus_set_error_const(&e, DBUS_ERROR_UNKNOWN_OBJECT, "Unknown job"); - return bus_send_error_reply(connection, message, &e, r); - } - - return bus_send_error_reply(connection, message, NULL, r); - } - - return bus_job_message_dispatch(j, connection, message); - -oom: - if (reply) - dbus_message_unref(reply); - - return DBUS_HANDLER_RESULT_NEED_MEMORY; -} - -const DBusObjectPathVTable bus_job_vtable = { - .message_function = bus_job_message_handler -}; - -static int job_send_message(Job *j, DBusMessage *m) { - int r; - - assert(j); - assert(m); - - if (bus_has_subscriber(j->manager)) { - if ((r = bus_broadcast(j->manager, m)) < 0) - return r; - - } else if (j->bus_client) { - /* If nobody is subscribed, we just send the message - * to the client which created the job */ - - assert(j->bus); - - if (!dbus_message_set_destination(m, j->bus_client)) - return -ENOMEM; - - if (!dbus_connection_send(j->bus, m, NULL)) - return -ENOMEM; - } - - return 0; -} - -void bus_job_send_change_signal(Job *j) { - char *p = NULL; - DBusMessage *m = NULL; - - assert(j); - - if (j->in_dbus_queue) { - LIST_REMOVE(Job, dbus_queue, j->manager->dbus_job_queue, j); - j->in_dbus_queue = false; - } - - if (!bus_has_subscriber(j->manager) && !j->bus_client) { - j->sent_dbus_new_signal = true; - return; - } - - if (!(p = job_dbus_path(j))) - goto oom; - - if (j->sent_dbus_new_signal) { - /* Send a properties changed signal */ - - if (!(m = bus_properties_changed_new(p, "org.freedesktop.systemd1.Job", INVALIDATING_PROPERTIES))) - goto oom; - - } else { - /* Send a new signal */ - - if (!(m = dbus_message_new_signal("/org/freedesktop/systemd1", "org.freedesktop.systemd1.Manager", "JobNew"))) - goto oom; - - if (!dbus_message_append_args(m, - DBUS_TYPE_UINT32, &j->id, - DBUS_TYPE_OBJECT_PATH, &p, - DBUS_TYPE_INVALID)) - goto oom; - } - - if (job_send_message(j, m) < 0) - goto oom; - - free(p); - dbus_message_unref(m); - - j->sent_dbus_new_signal = true; - - return; - -oom: - free(p); - - if (m) - dbus_message_unref(m); - - log_error("Failed to allocate job change signal."); -} - -void bus_job_send_removed_signal(Job *j) { - char *p = NULL; - DBusMessage *m = NULL; - const char *r; - - assert(j); - - if (!bus_has_subscriber(j->manager) && !j->bus_client) - return; - - if (!j->sent_dbus_new_signal) - bus_job_send_change_signal(j); - - if (!(p = job_dbus_path(j))) - goto oom; - - if (!(m = dbus_message_new_signal("/org/freedesktop/systemd1", "org.freedesktop.systemd1.Manager", "JobRemoved"))) - goto oom; - - r = job_result_to_string(j->result); - - if (!dbus_message_append_args(m, - DBUS_TYPE_UINT32, &j->id, - DBUS_TYPE_OBJECT_PATH, &p, - DBUS_TYPE_STRING, &r, - DBUS_TYPE_INVALID)) - goto oom; - - if (job_send_message(j, m) < 0) - goto oom; - - free(p); - dbus_message_unref(m); - - return; - -oom: - free(p); - - if (m) - dbus_message_unref(m); - - log_error("Failed to allocate job remove signal."); -} diff --git a/src/dbus-job.h b/src/dbus-job.h deleted file mode 100644 index 103c2ff3ec..0000000000 --- a/src/dbus-job.h +++ /dev/null @@ -1,36 +0,0 @@ -/*-*- Mode: C; c-basic-offset: 8; indent-tabs-mode: nil -*-*/ - -#ifndef foodbusjobhfoo -#define foodbusjobhfoo - -/*** - This file is part of systemd. - - Copyright 2010 Lennart Poettering - - systemd is free software; you can redistribute it and/or modify it - under the terms of the GNU General Public License as published by - the Free Software Foundation; either version 2 of the License, or - (at your option) any later version. - - systemd is distributed in the hope that 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 General Public License - along with systemd; If not, see . -***/ - -#include - -#include "job.h" - -void bus_job_send_change_signal(Job *j); -void bus_job_send_removed_signal(Job *j); - -extern const DBusObjectPathVTable bus_job_vtable; - -extern const char bus_job_interface[]; - -#endif diff --git a/src/dbus-loop.h b/src/dbus-loop.h deleted file mode 100644 index 0bbdfe5925..0000000000 --- a/src/dbus-loop.h +++ /dev/null @@ -1,30 +0,0 @@ -/*-*- Mode: C; c-basic-offset: 8; indent-tabs-mode: nil -*-*/ - -#ifndef foodbusloophfoo -#define foodbusloophfoo - -/*** - This file is part of systemd. - - Copyright 2011 Lennart Poettering - - systemd is free software; you can redistribute it and/or modify it - under the terms of the GNU General Public License as published by - the Free Software Foundation; either version 2 of the License, or - (at your option) any later version. - - systemd is distributed in the hope that 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 General Public License - along with systemd; If not, see . -***/ - -#include - -int bus_loop_open(DBusConnection *c); -int bus_loop_dispatch(int fd); - -#endif diff --git a/src/dbus-manager.c b/src/dbus-manager.c deleted file mode 100644 index 3bf0c07b88..0000000000 --- a/src/dbus-manager.c +++ /dev/null @@ -1,1544 +0,0 @@ -/*-*- Mode: C; c-basic-offset: 8; indent-tabs-mode: nil -*-*/ - -/*** - This file is part of systemd. - - Copyright 2010 Lennart Poettering - - systemd is free software; you can redistribute it and/or modify it - under the terms of the GNU General Public License as published by - the Free Software Foundation; either version 2 of the License, or - (at your option) any later version. - - systemd is distributed in the hope that 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 General Public License - along with systemd; If not, see . -***/ - -#include -#include - -#include "dbus.h" -#include "log.h" -#include "dbus-manager.h" -#include "strv.h" -#include "bus-errors.h" -#include "build.h" -#include "dbus-common.h" -#include "install.h" - -#define BUS_MANAGER_INTERFACE_BEGIN \ - " \n" - -#define BUS_MANAGER_INTERFACE_METHODS \ - " \n" \ - " \n" \ - " \n" \ - " \n" \ - " \n" \ - " \n" \ - " \n" \ - " \n" \ - " \n" \ - " \n" \ - " \n" \ - " \n" \ - " \n" \ - " \n" \ - " \n" \ - " \n" \ - " \n" \ - " \n" \ - " \n" \ - " \n" \ - " \n" \ - " \n" \ - " \n" \ - " \n" \ - " \n" \ - " \n" \ - " \n" \ - " \n" \ - " \n" \ - " \n" \ - " \n" \ - " \n" \ - " \n" \ - " \n" \ - " \n" \ - " \n" \ - " \n" \ - " \n" \ - " \n" \ - " \n" \ - " \n" \ - " \n" \ - " \n" \ - " \n" \ - " \n" \ - " \n" \ - " \n" \ - " \n" \ - " \n" \ - " \n" \ - " \n" \ - " \n" \ - " \n" \ - " \n" \ - " \n" \ - " \n" \ - " \n" \ - " \n" \ - " \n" \ - " \n" \ - " \n" \ - " \n" \ - " \n" \ - " \n" \ - " \n" \ - " \n" \ - " \n" \ - " \n" \ - " \n" \ - " \n" \ - " \n" \ - " \n" \ - " \n" \ - " \n" \ - " \n" \ - " \n" \ - " \n" \ - " \n" \ - " \n" \ - " \n" \ - " \n" \ - " \n" \ - " \n" \ - " \n" \ - " \n" \ - " \n" \ - " \n" \ - " \n" \ - " \n" \ - " \n" \ - " \n" \ - " \n" \ - " \n" \ - " \n" \ - " \n" \ - " \n" \ - " \n" \ - " \n" \ - " \n" \ - " \n" \ - " \n" \ - " \n" \ - " \n" \ - " \n" \ - " \n" \ - " \n" \ - " \n" \ - " \n" \ - " \n" \ - " \n" \ - " \n" \ - " \n" \ - " \n" \ - " \n" \ - " \n" \ - " \n" \ - " \n" \ - " \n" \ - " \n" \ - " \n" \ - " \n" \ - " \n" \ - " \n" \ - " \n" \ - " \n" \ - " \n" \ - " \n" \ - " \n" \ - " \n" \ - " \n" \ - " \n" \ - " \n" \ - " \n" \ - " \n" \ - " \n" \ - " \n" \ - " \n" \ - " \n" \ - " \n" \ - " \n" \ - " \n" \ - " \n" \ - " \n" \ - " \n" \ - " \n" \ - " \n" \ - " \n" \ - " \n" \ - " \n" - -#define BUS_MANAGER_INTERFACE_SIGNALS \ - " \n" \ - " \n" \ - " \n" \ - " \n" \ - " \n" \ - " \n" \ - " \n" \ - " \n" \ - " \n" \ - " \n" \ - " \n" \ - " \n" \ - " \n" \ - " \n" \ - " \n" \ - " \n" \ - " " \ - " \n" \ - " \n" \ - " \n" \ - " \n" \ - " \n" \ - " " \ - " \n" - -#define BUS_MANAGER_INTERFACE_PROPERTIES_GENERAL \ - " \n" \ - " \n" \ - " \n" \ - " \n" \ - " \n" \ - " \n" \ - " \n" \ - " \n" \ - " \n" \ - " \n" \ - " \n" \ - " \n" \ - " \n" \ - " \n" \ - " \n" \ - " \n" \ - " \n" \ - " \n" \ - " \n" \ - " \n" \ - " \n" \ - " \n" \ - " \n" \ - " \n" \ - " \n" \ - " \n" \ - " \n" \ - " \n" \ - " \n" \ - " \n" \ - " \n" - -#ifdef HAVE_SYSV_COMPAT -#define BUS_MANAGER_INTERFACE_PROPERTIES_SYSV \ - " \n" \ - " \n" \ - " \n" -#else -#define BUS_MANAGER_INTERFACE_PROPERTIES_SYSV -#endif - -#define BUS_MANAGER_INTERFACE_END \ - " \n" - -#define BUS_MANAGER_INTERFACE \ - BUS_MANAGER_INTERFACE_BEGIN \ - BUS_MANAGER_INTERFACE_METHODS \ - BUS_MANAGER_INTERFACE_SIGNALS \ - BUS_MANAGER_INTERFACE_PROPERTIES_GENERAL \ - BUS_MANAGER_INTERFACE_PROPERTIES_SYSV \ - BUS_MANAGER_INTERFACE_END - -#define INTROSPECTION_BEGIN \ - DBUS_INTROSPECT_1_0_XML_DOCTYPE_DECL_NODE \ - "\n" \ - BUS_MANAGER_INTERFACE \ - BUS_PROPERTIES_INTERFACE \ - BUS_PEER_INTERFACE \ - BUS_INTROSPECTABLE_INTERFACE - -#define INTROSPECTION_END \ - "\n" - -#define INTERFACES_LIST \ - BUS_GENERIC_INTERFACES_LIST \ - "org.freedesktop.systemd1.Manager\0" - -const char bus_manager_interface[] _introspect_("Manager") = BUS_MANAGER_INTERFACE; - -static DEFINE_BUS_PROPERTY_APPEND_ENUM(bus_manager_append_running_as, manager_running_as, ManagerRunningAs); -static DEFINE_BUS_PROPERTY_APPEND_ENUM(bus_manager_append_exec_output, exec_output, ExecOutput); - -static int bus_manager_append_tainted(DBusMessageIter *i, const char *property, void *data) { - const char *t; - Manager *m = data; - char buf[LINE_MAX] = "", *e = buf, *p = NULL; - - assert(i); - assert(property); - assert(m); - - if (m->taint_usr) - e = stpcpy(e, "usr-separate-fs "); - - if (readlink_malloc("/etc/mtab", &p) < 0) - e = stpcpy(e, "etc-mtab-not-symlink "); - else - free(p); - - if (access("/proc/cgroups", F_OK) < 0) - stpcpy(e, "cgroups-missing "); - - t = strstrip(buf); - - if (!dbus_message_iter_append_basic(i, DBUS_TYPE_STRING, &t)) - return -ENOMEM; - - return 0; -} - -static int bus_manager_append_log_target(DBusMessageIter *i, const char *property, void *data) { - const char *t; - - assert(i); - assert(property); - - t = log_target_to_string(log_get_target()); - - if (!dbus_message_iter_append_basic(i, DBUS_TYPE_STRING, &t)) - return -ENOMEM; - - return 0; -} - -static int bus_manager_set_log_target(DBusMessageIter *i, const char *property, void *data) { - const char *t; - - assert(i); - assert(property); - - dbus_message_iter_get_basic(i, &t); - - return log_set_target_from_string(t); -} - -static int bus_manager_append_log_level(DBusMessageIter *i, const char *property, void *data) { - const char *t; - - assert(i); - assert(property); - - t = log_level_to_string(log_get_max_level()); - - if (!dbus_message_iter_append_basic(i, DBUS_TYPE_STRING, &t)) - return -ENOMEM; - - return 0; -} - -static int bus_manager_set_log_level(DBusMessageIter *i, const char *property, void *data) { - const char *t; - - assert(i); - assert(property); - - dbus_message_iter_get_basic(i, &t); - - return log_set_max_level_from_string(t); -} - -static int bus_manager_append_n_names(DBusMessageIter *i, const char *property, void *data) { - Manager *m = data; - uint32_t u; - - assert(i); - assert(property); - assert(m); - - u = hashmap_size(m->units); - - if (!dbus_message_iter_append_basic(i, DBUS_TYPE_UINT32, &u)) - return -ENOMEM; - - return 0; -} - -static int bus_manager_append_n_jobs(DBusMessageIter *i, const char *property, void *data) { - Manager *m = data; - uint32_t u; - - assert(i); - assert(property); - assert(m); - - u = hashmap_size(m->jobs); - - if (!dbus_message_iter_append_basic(i, DBUS_TYPE_UINT32, &u)) - return -ENOMEM; - - return 0; -} - -static int bus_manager_append_progress(DBusMessageIter *i, const char *property, void *data) { - double d; - Manager *m = data; - - assert(i); - assert(property); - 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); - - if (!dbus_message_iter_append_basic(i, DBUS_TYPE_DOUBLE, &d)) - return -ENOMEM; - - return 0; -} - -static const char *message_get_sender_with_fallback(DBusMessage *m) { - const char *s; - - assert(m); - - if ((s = dbus_message_get_sender(m))) - return s; - - /* When the message came in from a direct connection the - * message will have no sender. We fix that here. */ - - return ":no-sender"; -} - -static DBusMessage *message_from_file_changes( - DBusMessage *m, - UnitFileChange *changes, - unsigned n_changes, - int carries_install_info) { - - DBusMessageIter iter, sub, sub2; - DBusMessage *reply; - unsigned i; - - reply = dbus_message_new_method_return(m); - if (!reply) - return NULL; - - dbus_message_iter_init_append(reply, &iter); - - if (carries_install_info >= 0) { - dbus_bool_t b; - - b = !!carries_install_info; - if (!dbus_message_iter_append_basic(&iter, DBUS_TYPE_BOOLEAN, &b)) - goto oom; - } - - if (!dbus_message_iter_open_container(&iter, DBUS_TYPE_ARRAY, "(sss)", &sub)) - goto oom; - - for (i = 0; i < n_changes; i++) { - const char *type, *path, *source; - - type = unit_file_change_type_to_string(changes[i].type); - path = strempty(changes[i].path); - source = strempty(changes[i].source); - - if (!dbus_message_iter_open_container(&sub, DBUS_TYPE_STRUCT, NULL, &sub2) || - !dbus_message_iter_append_basic(&sub2, DBUS_TYPE_STRING, &type) || - !dbus_message_iter_append_basic(&sub2, DBUS_TYPE_STRING, &path) || - !dbus_message_iter_append_basic(&sub2, DBUS_TYPE_STRING, &source) || - !dbus_message_iter_close_container(&sub, &sub2)) - goto oom; - } - - if (!dbus_message_iter_close_container(&iter, &sub)) - goto oom; - - return reply; - -oom: - dbus_message_unref(reply); - return NULL; -} - -static int bus_manager_send_unit_files_changed(Manager *m) { - DBusMessage *s; - int r; - - s = dbus_message_new_signal("/org/freedesktop/systemd1", "org.freedesktop.systemd1.Manager", "UnitFilesChanged"); - if (!s) - return -ENOMEM; - - r = bus_broadcast(m, s); - dbus_message_unref(s); - - return r; -} - -static const char systemd_property_string[] = - PACKAGE_STRING "\0" - DISTRIBUTION "\0" - SYSTEMD_FEATURES; - -static const BusProperty bus_systemd_properties[] = { - { "Version", bus_property_append_string, "s", 0 }, - { "Distribution", bus_property_append_string, "s", sizeof(PACKAGE_STRING) }, - { "Features", bus_property_append_string, "s", sizeof(PACKAGE_STRING) + sizeof(DISTRIBUTION) }, - { NULL, } -}; - -static const BusProperty bus_manager_properties[] = { - { "RunningAs", bus_manager_append_running_as, "s", offsetof(Manager, running_as) }, - { "Tainted", bus_manager_append_tainted, "s", 0 }, - { "InitRDTimestamp", bus_property_append_uint64, "t", offsetof(Manager, initrd_timestamp.realtime) }, - { "InitRDTimestampMonotonic", bus_property_append_uint64, "t", offsetof(Manager, initrd_timestamp.monotonic) }, - { "StartupTimestamp", bus_property_append_uint64, "t", offsetof(Manager, startup_timestamp.realtime) }, - { "StartupTimestampMonotonic", bus_property_append_uint64, "t", offsetof(Manager, startup_timestamp.monotonic) }, - { "FinishTimestamp", bus_property_append_uint64, "t", offsetof(Manager, finish_timestamp.realtime) }, - { "FinishTimestampMonotonic", bus_property_append_uint64, "t", offsetof(Manager, finish_timestamp.monotonic) }, - { "LogLevel", bus_manager_append_log_level, "s", 0, 0, bus_manager_set_log_level }, - { "LogTarget", bus_manager_append_log_target, "s", 0, 0, bus_manager_set_log_target }, - { "NNames", bus_manager_append_n_names, "u", 0 }, - { "NJobs", bus_manager_append_n_jobs, "u", 0 }, - { "NInstalledJobs",bus_property_append_uint32, "u", offsetof(Manager, n_installed_jobs) }, - { "NFailedJobs", bus_property_append_uint32, "u", offsetof(Manager, n_failed_jobs) }, - { "Progress", bus_manager_append_progress, "d", 0 }, - { "Environment", bus_property_append_strv, "as", offsetof(Manager, environment), true }, - { "ConfirmSpawn", bus_property_append_bool, "b", offsetof(Manager, confirm_spawn) }, - { "ShowStatus", bus_property_append_bool, "b", offsetof(Manager, show_status) }, - { "UnitPath", bus_property_append_strv, "as", offsetof(Manager, lookup_paths.unit_path), true }, - { "NotifySocket", bus_property_append_string, "s", offsetof(Manager, notify_socket), true }, - { "ControlGroupHierarchy", bus_property_append_string, "s", offsetof(Manager, cgroup_hierarchy), true }, - { "MountAuto", bus_property_append_bool, "b", offsetof(Manager, mount_auto) }, - { "SwapAuto", bus_property_append_bool, "b", offsetof(Manager, swap_auto) }, - { "DefaultControllers", bus_property_append_strv, "as", offsetof(Manager, default_controllers), true }, - { "DefaultStandardOutput", bus_manager_append_exec_output, "s", offsetof(Manager, default_std_output) }, - { "DefaultStandardError", bus_manager_append_exec_output, "s", offsetof(Manager, default_std_error) }, - { "RuntimeWatchdogUSec", bus_property_append_usec, "t", offsetof(Manager, runtime_watchdog), }, - { "ShutdownWatchdogUSec", bus_property_append_usec, "t", offsetof(Manager, shutdown_watchdog), }, -#ifdef HAVE_SYSV_COMPAT - { "SysVConsole", bus_property_append_bool, "b", offsetof(Manager, sysv_console) }, - { "SysVInitPath", bus_property_append_strv, "as", offsetof(Manager, lookup_paths.sysvinit_path), true }, - { "SysVRcndPath", bus_property_append_strv, "as", offsetof(Manager, lookup_paths.sysvrcnd_path), true }, -#endif - { NULL, } -}; - -static DBusHandlerResult bus_manager_message_handler(DBusConnection *connection, DBusMessage *message, void *data) { - Manager *m = data; - - int r; - DBusError error; - DBusMessage *reply = NULL; - char * path = NULL; - JobType job_type = _JOB_TYPE_INVALID; - bool reload_if_possible = false; - const char *member; - - assert(connection); - assert(message); - assert(m); - - dbus_error_init(&error); - - member = dbus_message_get_member(message); - - if (dbus_message_is_method_call(message, "org.freedesktop.systemd1.Manager", "GetUnit")) { - const char *name; - Unit *u; - - if (!dbus_message_get_args( - message, - &error, - DBUS_TYPE_STRING, &name, - DBUS_TYPE_INVALID)) - return bus_send_error_reply(connection, message, &error, -EINVAL); - - if (!(u = manager_get_unit(m, name))) { - dbus_set_error(&error, BUS_ERROR_NO_SUCH_UNIT, "Unit %s is not loaded.", name); - return bus_send_error_reply(connection, message, &error, -ENOENT); - } - - if (!(reply = dbus_message_new_method_return(message))) - goto oom; - - if (!(path = unit_dbus_path(u))) - goto oom; - - if (!dbus_message_append_args( - reply, - DBUS_TYPE_OBJECT_PATH, &path, - DBUS_TYPE_INVALID)) - goto oom; - } else if (dbus_message_is_method_call(message, "org.freedesktop.systemd1.Manager", "GetUnitByPID")) { - Unit *u; - uint32_t pid; - - if (!dbus_message_get_args( - message, - &error, - DBUS_TYPE_UINT32, &pid, - DBUS_TYPE_INVALID)) - return bus_send_error_reply(connection, message, &error, -EINVAL); - - if (!(u = cgroup_unit_by_pid(m, (pid_t) pid))) { - dbus_set_error(&error, BUS_ERROR_NO_SUCH_UNIT, "No unit for PID %lu is loaded.", (unsigned long) pid); - return bus_send_error_reply(connection, message, &error, -ENOENT); - } - - if (!(reply = dbus_message_new_method_return(message))) - goto oom; - - if (!(path = unit_dbus_path(u))) - goto oom; - - if (!dbus_message_append_args( - reply, - DBUS_TYPE_OBJECT_PATH, &path, - DBUS_TYPE_INVALID)) - goto oom; - } else if (dbus_message_is_method_call(message, "org.freedesktop.systemd1.Manager", "LoadUnit")) { - const char *name; - Unit *u; - - if (!dbus_message_get_args( - message, - &error, - DBUS_TYPE_STRING, &name, - DBUS_TYPE_INVALID)) - return bus_send_error_reply(connection, message, &error, -EINVAL); - - if ((r = manager_load_unit(m, name, NULL, &error, &u)) < 0) - return bus_send_error_reply(connection, message, &error, r); - - if (!(reply = dbus_message_new_method_return(message))) - goto oom; - - if (!(path = unit_dbus_path(u))) - goto oom; - - if (!dbus_message_append_args( - reply, - DBUS_TYPE_OBJECT_PATH, &path, - DBUS_TYPE_INVALID)) - goto oom; - - } else if (dbus_message_is_method_call(message, "org.freedesktop.systemd1.Manager", "StartUnit")) - job_type = JOB_START; - else if (dbus_message_is_method_call(message, "org.freedesktop.systemd1.Manager", "StartUnitReplace")) - job_type = JOB_START; - else if (dbus_message_is_method_call(message, "org.freedesktop.systemd1.Manager", "StopUnit")) - job_type = JOB_STOP; - else if (dbus_message_is_method_call(message, "org.freedesktop.systemd1.Manager", "ReloadUnit")) - job_type = JOB_RELOAD; - else if (dbus_message_is_method_call(message, "org.freedesktop.systemd1.Manager", "RestartUnit")) - job_type = JOB_RESTART; - else if (dbus_message_is_method_call(message, "org.freedesktop.systemd1.Manager", "TryRestartUnit")) - job_type = JOB_TRY_RESTART; - else if (dbus_message_is_method_call(message, "org.freedesktop.systemd1.Manager", "ReloadOrRestartUnit")) { - reload_if_possible = true; - job_type = JOB_RESTART; - } else if (dbus_message_is_method_call(message, "org.freedesktop.systemd1.Manager", "ReloadOrTryRestartUnit")) { - reload_if_possible = true; - job_type = JOB_TRY_RESTART; - } else if (dbus_message_is_method_call(message, "org.freedesktop.systemd1.Manager", "KillUnit")) { - const char *name, *swho, *smode; - int32_t signo; - Unit *u; - KillMode mode; - KillWho who; - - if (!dbus_message_get_args( - message, - &error, - DBUS_TYPE_STRING, &name, - DBUS_TYPE_STRING, &swho, - DBUS_TYPE_STRING, &smode, - DBUS_TYPE_INT32, &signo, - DBUS_TYPE_INVALID)) - return bus_send_error_reply(connection, message, &error, -EINVAL); - - if (isempty(swho)) - who = KILL_ALL; - else { - who = kill_who_from_string(swho); - if (who < 0) - return bus_send_error_reply(connection, message, &error, -EINVAL); - } - - if (isempty(smode)) - mode = KILL_CONTROL_GROUP; - else { - mode = kill_mode_from_string(smode); - if (mode < 0) - return bus_send_error_reply(connection, message, &error, -EINVAL); - } - - if (signo <= 0 || signo >= _NSIG) - return bus_send_error_reply(connection, message, &error, -EINVAL); - - if (!(u = manager_get_unit(m, name))) { - dbus_set_error(&error, BUS_ERROR_NO_SUCH_UNIT, "Unit %s is not loaded.", name); - return bus_send_error_reply(connection, message, &error, -ENOENT); - } - - if ((r = unit_kill(u, who, mode, signo, &error)) < 0) - return bus_send_error_reply(connection, message, &error, r); - - if (!(reply = dbus_message_new_method_return(message))) - goto oom; - - } else if (dbus_message_is_method_call(message, "org.freedesktop.systemd1.Manager", "GetJob")) { - uint32_t id; - Job *j; - - if (!dbus_message_get_args( - message, - &error, - DBUS_TYPE_UINT32, &id, - DBUS_TYPE_INVALID)) - return bus_send_error_reply(connection, message, &error, -EINVAL); - - if (!(j = manager_get_job(m, id))) { - dbus_set_error(&error, BUS_ERROR_NO_SUCH_JOB, "Job %u does not exist.", (unsigned) id); - return bus_send_error_reply(connection, message, &error, -ENOENT); - } - - if (!(reply = dbus_message_new_method_return(message))) - goto oom; - - if (!(path = job_dbus_path(j))) - goto oom; - - if (!dbus_message_append_args( - reply, - DBUS_TYPE_OBJECT_PATH, &path, - DBUS_TYPE_INVALID)) - goto oom; - - } else if (dbus_message_is_method_call(message, "org.freedesktop.systemd1.Manager", "ClearJobs")) { - - manager_clear_jobs(m); - - if (!(reply = dbus_message_new_method_return(message))) - goto oom; - - } else if (dbus_message_is_method_call(message, "org.freedesktop.systemd1.Manager", "ResetFailed")) { - - manager_reset_failed(m); - - if (!(reply = dbus_message_new_method_return(message))) - goto oom; - - } else if (dbus_message_is_method_call(message, "org.freedesktop.systemd1.Manager", "ResetFailedUnit")) { - const char *name; - Unit *u; - - if (!dbus_message_get_args( - message, - &error, - DBUS_TYPE_STRING, &name, - DBUS_TYPE_INVALID)) - return bus_send_error_reply(connection, message, &error, -EINVAL); - - if (!(u = manager_get_unit(m, name))) { - dbus_set_error(&error, BUS_ERROR_NO_SUCH_UNIT, "Unit %s is not loaded.", name); - return bus_send_error_reply(connection, message, &error, -ENOENT); - } - - unit_reset_failed(u); - - if (!(reply = dbus_message_new_method_return(message))) - goto oom; - - } else if (dbus_message_is_method_call(message, "org.freedesktop.systemd1.Manager", "ListUnits")) { - DBusMessageIter iter, sub; - Iterator i; - Unit *u; - const char *k; - - if (!(reply = dbus_message_new_method_return(message))) - goto oom; - - dbus_message_iter_init_append(reply, &iter); - - if (!dbus_message_iter_open_container(&iter, DBUS_TYPE_ARRAY, "(ssssssouso)", &sub)) - goto oom; - - HASHMAP_FOREACH_KEY(u, k, m->units, i) { - char *u_path, *j_path; - const char *description, *load_state, *active_state, *sub_state, *sjob_type, *following; - DBusMessageIter sub2; - uint32_t job_id; - Unit *f; - - if (k != u->id) - continue; - - if (!dbus_message_iter_open_container(&sub, DBUS_TYPE_STRUCT, NULL, &sub2)) - goto oom; - - description = unit_description(u); - load_state = unit_load_state_to_string(u->load_state); - active_state = unit_active_state_to_string(unit_active_state(u)); - sub_state = unit_sub_state_to_string(u); - - f = unit_following(u); - following = f ? f->id : ""; - - if (!(u_path = unit_dbus_path(u))) - goto oom; - - if (u->job) { - job_id = (uint32_t) u->job->id; - - if (!(j_path = job_dbus_path(u->job))) { - free(u_path); - goto oom; - } - - sjob_type = job_type_to_string(u->job->type); - } else { - job_id = 0; - j_path = u_path; - sjob_type = ""; - } - - if (!dbus_message_iter_append_basic(&sub2, DBUS_TYPE_STRING, &u->id) || - !dbus_message_iter_append_basic(&sub2, DBUS_TYPE_STRING, &description) || - !dbus_message_iter_append_basic(&sub2, DBUS_TYPE_STRING, &load_state) || - !dbus_message_iter_append_basic(&sub2, DBUS_TYPE_STRING, &active_state) || - !dbus_message_iter_append_basic(&sub2, DBUS_TYPE_STRING, &sub_state) || - !dbus_message_iter_append_basic(&sub2, DBUS_TYPE_STRING, &following) || - !dbus_message_iter_append_basic(&sub2, DBUS_TYPE_OBJECT_PATH, &u_path) || - !dbus_message_iter_append_basic(&sub2, DBUS_TYPE_UINT32, &job_id) || - !dbus_message_iter_append_basic(&sub2, DBUS_TYPE_STRING, &sjob_type) || - !dbus_message_iter_append_basic(&sub2, DBUS_TYPE_OBJECT_PATH, &j_path)) { - free(u_path); - if (u->job) - free(j_path); - goto oom; - } - - free(u_path); - if (u->job) - free(j_path); - - if (!dbus_message_iter_close_container(&sub, &sub2)) - goto oom; - } - - if (!dbus_message_iter_close_container(&iter, &sub)) - goto oom; - - } else if (dbus_message_is_method_call(message, "org.freedesktop.systemd1.Manager", "ListJobs")) { - DBusMessageIter iter, sub; - Iterator i; - Job *j; - - if (!(reply = dbus_message_new_method_return(message))) - goto oom; - - dbus_message_iter_init_append(reply, &iter); - - if (!dbus_message_iter_open_container(&iter, DBUS_TYPE_ARRAY, "(usssoo)", &sub)) - goto oom; - - HASHMAP_FOREACH(j, m->jobs, i) { - char *u_path, *j_path; - const char *state, *type; - uint32_t id; - DBusMessageIter sub2; - - if (!dbus_message_iter_open_container(&sub, DBUS_TYPE_STRUCT, NULL, &sub2)) - goto oom; - - id = (uint32_t) j->id; - state = job_state_to_string(j->state); - type = job_type_to_string(j->type); - - if (!(j_path = job_dbus_path(j))) - goto oom; - - if (!(u_path = unit_dbus_path(j->unit))) { - free(j_path); - goto oom; - } - - if (!dbus_message_iter_append_basic(&sub2, DBUS_TYPE_UINT32, &id) || - !dbus_message_iter_append_basic(&sub2, DBUS_TYPE_STRING, &j->unit->id) || - !dbus_message_iter_append_basic(&sub2, DBUS_TYPE_STRING, &type) || - !dbus_message_iter_append_basic(&sub2, DBUS_TYPE_STRING, &state) || - !dbus_message_iter_append_basic(&sub2, DBUS_TYPE_OBJECT_PATH, &j_path) || - !dbus_message_iter_append_basic(&sub2, DBUS_TYPE_OBJECT_PATH, &u_path)) { - free(j_path); - free(u_path); - goto oom; - } - - free(j_path); - free(u_path); - - if (!dbus_message_iter_close_container(&sub, &sub2)) - goto oom; - } - - if (!dbus_message_iter_close_container(&iter, &sub)) - goto oom; - - } else if (dbus_message_is_method_call(message, "org.freedesktop.systemd1.Manager", "Subscribe")) { - char *client; - Set *s; - - if (!(s = BUS_CONNECTION_SUBSCRIBED(m, connection))) { - if (!(s = set_new(string_hash_func, string_compare_func))) - goto oom; - - if (!(dbus_connection_set_data(connection, m->subscribed_data_slot, s, NULL))) { - set_free(s); - goto oom; - } - } - - if (!(client = strdup(message_get_sender_with_fallback(message)))) - goto oom; - - if ((r = set_put(s, client)) < 0) { - free(client); - return bus_send_error_reply(connection, message, NULL, r); - } - - if (!(reply = dbus_message_new_method_return(message))) - goto oom; - - } else if (dbus_message_is_method_call(message, "org.freedesktop.systemd1.Manager", "Unsubscribe")) { - char *client; - - if (!(client = set_remove(BUS_CONNECTION_SUBSCRIBED(m, connection), (char*) message_get_sender_with_fallback(message)))) { - dbus_set_error(&error, BUS_ERROR_NOT_SUBSCRIBED, "Client is not subscribed."); - return bus_send_error_reply(connection, message, &error, -ENOENT); - } - - free(client); - - if (!(reply = dbus_message_new_method_return(message))) - goto oom; - - } else if (dbus_message_is_method_call(message, "org.freedesktop.systemd1.Manager", "Dump")) { - FILE *f; - char *dump = NULL; - size_t size; - - if (!(reply = dbus_message_new_method_return(message))) - goto oom; - - if (!(f = open_memstream(&dump, &size))) - goto oom; - - manager_dump_units(m, f, NULL); - manager_dump_jobs(m, f, NULL); - - if (ferror(f)) { - fclose(f); - free(dump); - goto oom; - } - - fclose(f); - - if (!dbus_message_append_args(reply, DBUS_TYPE_STRING, &dump, DBUS_TYPE_INVALID)) { - free(dump); - goto oom; - } - - free(dump); - } else if (dbus_message_is_method_call(message, "org.freedesktop.systemd1.Manager", "CreateSnapshot")) { - const char *name; - dbus_bool_t cleanup; - Snapshot *s; - - if (!dbus_message_get_args( - message, - &error, - DBUS_TYPE_STRING, &name, - DBUS_TYPE_BOOLEAN, &cleanup, - DBUS_TYPE_INVALID)) - return bus_send_error_reply(connection, message, &error, -EINVAL); - - if (name && name[0] == 0) - name = NULL; - - if ((r = snapshot_create(m, name, cleanup, &error, &s)) < 0) - return bus_send_error_reply(connection, message, &error, r); - - if (!(reply = dbus_message_new_method_return(message))) - goto oom; - - if (!(path = unit_dbus_path(UNIT(s)))) - goto oom; - - if (!dbus_message_append_args( - reply, - DBUS_TYPE_OBJECT_PATH, &path, - DBUS_TYPE_INVALID)) - goto oom; - - } else if (dbus_message_is_method_call(message, "org.freedesktop.DBus.Introspectable", "Introspect")) { - char *introspection = NULL; - FILE *f; - Iterator i; - Unit *u; - Job *j; - const char *k; - size_t size; - - if (!(reply = dbus_message_new_method_return(message))) - goto oom; - - /* We roll our own introspection code here, instead of - * relying on bus_default_message_handler() because we - * need to generate our introspection string - * dynamically. */ - - if (!(f = open_memstream(&introspection, &size))) - goto oom; - - fputs(INTROSPECTION_BEGIN, f); - - HASHMAP_FOREACH_KEY(u, k, m->units, i) { - char *p; - - if (k != u->id) - continue; - - if (!(p = bus_path_escape(k))) { - fclose(f); - free(introspection); - goto oom; - } - - fprintf(f, "", p); - free(p); - } - - HASHMAP_FOREACH(j, m->jobs, i) - fprintf(f, "", (unsigned long) j->id); - - fputs(INTROSPECTION_END, f); - - if (ferror(f)) { - fclose(f); - free(introspection); - goto oom; - } - - fclose(f); - - if (!introspection) - goto oom; - - if (!dbus_message_append_args(reply, DBUS_TYPE_STRING, &introspection, DBUS_TYPE_INVALID)) { - free(introspection); - goto oom; - } - - free(introspection); - - } else if (dbus_message_is_method_call(message, "org.freedesktop.systemd1.Manager", "Reload")) { - - assert(!m->queued_message); - - /* 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. */ - - if (!(m->queued_message = dbus_message_new_method_return(message))) - goto oom; - - m->queued_message_connection = connection; - m->exit_code = MANAGER_RELOAD; - - } else if (dbus_message_is_method_call(message, "org.freedesktop.systemd1.Manager", "Reexecute")) { - - /* We don't send a reply back here, the client should - * just wait for us disconnecting. */ - - m->exit_code = MANAGER_REEXECUTE; - - } else if (dbus_message_is_method_call(message, "org.freedesktop.systemd1.Manager", "Exit")) { - - if (m->running_as == MANAGER_SYSTEM) { - dbus_set_error(&error, BUS_ERROR_NOT_SUPPORTED, "Exit is only supported for user service managers."); - return bus_send_error_reply(connection, message, &error, -ENOTSUP); - } - - if (!(reply = dbus_message_new_method_return(message))) - goto oom; - - m->exit_code = MANAGER_EXIT; - - } else if (dbus_message_is_method_call(message, "org.freedesktop.systemd1.Manager", "Reboot")) { - - if (m->running_as != MANAGER_SYSTEM) { - dbus_set_error(&error, BUS_ERROR_NOT_SUPPORTED, "Reboot is only supported for system managers."); - return bus_send_error_reply(connection, message, &error, -ENOTSUP); - } - - if (!(reply = dbus_message_new_method_return(message))) - goto oom; - - m->exit_code = MANAGER_REBOOT; - - } else if (dbus_message_is_method_call(message, "org.freedesktop.systemd1.Manager", "PowerOff")) { - - if (m->running_as != MANAGER_SYSTEM) { - dbus_set_error(&error, BUS_ERROR_NOT_SUPPORTED, "Powering off is only supported for system managers."); - return bus_send_error_reply(connection, message, &error, -ENOTSUP); - } - - if (!(reply = dbus_message_new_method_return(message))) - goto oom; - - m->exit_code = MANAGER_POWEROFF; - - } else if (dbus_message_is_method_call(message, "org.freedesktop.systemd1.Manager", "Halt")) { - - if (m->running_as != MANAGER_SYSTEM) { - dbus_set_error(&error, BUS_ERROR_NOT_SUPPORTED, "Halting is only supported for system managers."); - return bus_send_error_reply(connection, message, &error, -ENOTSUP); - } - - if (!(reply = dbus_message_new_method_return(message))) - goto oom; - - m->exit_code = MANAGER_HALT; - - } else if (dbus_message_is_method_call(message, "org.freedesktop.systemd1.Manager", "KExec")) { - - if (m->running_as != MANAGER_SYSTEM) { - dbus_set_error(&error, BUS_ERROR_NOT_SUPPORTED, "kexec is only supported for system managers."); - return bus_send_error_reply(connection, message, &error, -ENOTSUP); - } - - if (!(reply = dbus_message_new_method_return(message))) - goto oom; - - m->exit_code = MANAGER_KEXEC; - - } else if (dbus_message_is_method_call(message, "org.freedesktop.systemd1.Manager", "SetEnvironment")) { - char **l = NULL, **e = NULL; - - if ((r = bus_parse_strv(message, &l)) < 0) { - if (r == -ENOMEM) - goto oom; - - return bus_send_error_reply(connection, message, NULL, r); - } - - e = strv_env_merge(2, m->environment, l); - strv_free(l); - - if (!e) - goto oom; - - if (!(reply = dbus_message_new_method_return(message))) { - strv_free(e); - goto oom; - } - - strv_free(m->environment); - m->environment = e; - - } else if (dbus_message_is_method_call(message, "org.freedesktop.systemd1.Manager", "UnsetEnvironment")) { - char **l = NULL, **e = NULL; - - if ((r = bus_parse_strv(message, &l)) < 0) { - if (r == -ENOMEM) - goto oom; - - return bus_send_error_reply(connection, message, NULL, r); - } - - e = strv_env_delete(m->environment, 1, l); - strv_free(l); - - if (!e) - goto oom; - - if (!(reply = dbus_message_new_method_return(message))) { - strv_free(e); - goto oom; - } - - strv_free(m->environment); - m->environment = e; - - } else if (dbus_message_is_method_call(message, "org.freedesktop.systemd1.Manager", "UnsetAndSetEnvironment")) { - char **l_set = NULL, **l_unset = NULL, **e = NULL, **f = NULL; - DBusMessageIter iter; - - if (!dbus_message_iter_init(message, &iter)) - goto oom; - - r = bus_parse_strv_iter(&iter, &l_unset); - if (r < 0) { - if (r == -ENOMEM) - goto oom; - - return bus_send_error_reply(connection, message, NULL, r); - } - - if (!dbus_message_iter_next(&iter)) { - strv_free(l_unset); - return bus_send_error_reply(connection, message, NULL, -EINVAL); - } - - r = bus_parse_strv_iter(&iter, &l_set); - if (r < 0) { - strv_free(l_unset); - if (r == -ENOMEM) - goto oom; - - return bus_send_error_reply(connection, message, NULL, r); - } - - e = strv_env_delete(m->environment, 1, l_unset); - strv_free(l_unset); - - if (!e) { - strv_free(l_set); - goto oom; - } - - f = strv_env_merge(2, e, l_set); - strv_free(l_set); - strv_free(e); - - if (!f) - goto oom; - - if (!(reply = dbus_message_new_method_return(message))) { - strv_free(f); - goto oom; - } - - strv_free(m->environment); - m->environment = f; - } else if (dbus_message_is_method_call(message, "org.freedesktop.systemd1.Manager", "ListUnitFiles")) { - DBusMessageIter iter, sub, sub2; - Hashmap *h; - Iterator i; - UnitFileList *item; - - reply = dbus_message_new_method_return(message); - if (!reply) - goto oom; - - h = hashmap_new(string_hash_func, string_compare_func); - if (!h) - goto oom; - - r = unit_file_get_list(m->running_as == MANAGER_SYSTEM ? UNIT_FILE_SYSTEM : UNIT_FILE_USER, NULL, h); - if (r < 0) { - unit_file_list_free(h); - dbus_message_unref(reply); - return bus_send_error_reply(connection, message, NULL, r); - } - - dbus_message_iter_init_append(reply, &iter); - - if (!dbus_message_iter_open_container(&iter, DBUS_TYPE_ARRAY, "(ss)", &sub)) { - unit_file_list_free(h); - goto oom; - } - - HASHMAP_FOREACH(item, h, i) { - const char *state; - - state = unit_file_state_to_string(item->state); - assert(state); - - if (!dbus_message_iter_open_container(&sub, DBUS_TYPE_STRUCT, NULL, &sub2) || - !dbus_message_iter_append_basic(&sub2, DBUS_TYPE_STRING, &item->path) || - !dbus_message_iter_append_basic(&sub2, DBUS_TYPE_STRING, &state) || - !dbus_message_iter_close_container(&sub, &sub2)) { - unit_file_list_free(h); - goto oom; - } - } - - unit_file_list_free(h); - - if (!dbus_message_iter_close_container(&iter, &sub)) - goto oom; - - } else if (dbus_message_is_method_call(message, "org.freedesktop.systemd1.Manager", "GetUnitFileState")) { - const char *name; - UnitFileState state; - const char *s; - - if (!dbus_message_get_args( - message, - &error, - DBUS_TYPE_STRING, &name, - DBUS_TYPE_INVALID)) - return bus_send_error_reply(connection, message, &error, -EINVAL); - - state = unit_file_get_state(m->running_as == MANAGER_SYSTEM ? UNIT_FILE_SYSTEM : UNIT_FILE_USER, NULL, name); - if (state < 0) - return bus_send_error_reply(connection, message, NULL, state); - - s = unit_file_state_to_string(state); - assert(s); - - reply = dbus_message_new_method_return(message); - if (!reply) - goto oom; - - if (!dbus_message_append_args( - reply, - DBUS_TYPE_STRING, &s, - DBUS_TYPE_INVALID)) - goto oom; - } else if (dbus_message_is_method_call(message, "org.freedesktop.systemd1.Manager", "EnableUnitFiles") || - dbus_message_is_method_call(message, "org.freedesktop.systemd1.Manager", "ReenableUnitFiles") || - dbus_message_is_method_call(message, "org.freedesktop.systemd1.Manager", "LinkUnitFiles") || - dbus_message_is_method_call(message, "org.freedesktop.systemd1.Manager", "PresetUnitFiles") || - dbus_message_is_method_call(message, "org.freedesktop.systemd1.Manager", "MaskUnitFiles")) { - - char **l = NULL; - DBusMessageIter iter; - UnitFileScope scope = m->running_as == MANAGER_SYSTEM ? UNIT_FILE_SYSTEM : UNIT_FILE_USER; - UnitFileChange *changes = NULL; - unsigned n_changes = 0; - dbus_bool_t runtime, force; - int carries_install_info = -1; - - if (!dbus_message_iter_init(message, &iter)) - goto oom; - - r = bus_parse_strv_iter(&iter, &l); - if (r < 0) { - if (r == -ENOMEM) - goto oom; - - return bus_send_error_reply(connection, message, NULL, r); - } - - if (!dbus_message_iter_next(&iter) || - bus_iter_get_basic_and_next(&iter, DBUS_TYPE_BOOLEAN, &runtime, true) < 0 || - bus_iter_get_basic_and_next(&iter, DBUS_TYPE_BOOLEAN, &force, false) < 0) { - strv_free(l); - return bus_send_error_reply(connection, message, NULL, -EIO); - } - - if (streq(member, "EnableUnitFiles")) { - r = unit_file_enable(scope, runtime, NULL, l, force, &changes, &n_changes); - carries_install_info = r; - } else if (streq(member, "ReenableUnitFiles")) { - r = unit_file_reenable(scope, runtime, NULL, l, force, &changes, &n_changes); - carries_install_info = r; - } else if (streq(member, "LinkUnitFiles")) - r = unit_file_link(scope, runtime, NULL, l, force, &changes, &n_changes); - else if (streq(member, "PresetUnitFiles")) { - r = unit_file_preset(scope, runtime, NULL, l, force, &changes, &n_changes); - carries_install_info = r; - } else if (streq(member, "MaskUnitFiles")) - r = unit_file_mask(scope, runtime, NULL, l, force, &changes, &n_changes); - else - assert_not_reached("Uh? Wrong method"); - - strv_free(l); - bus_manager_send_unit_files_changed(m); - - if (r < 0) { - unit_file_changes_free(changes, n_changes); - return bus_send_error_reply(connection, message, NULL, r); - } - - reply = message_from_file_changes(message, changes, n_changes, carries_install_info); - unit_file_changes_free(changes, n_changes); - - if (!reply) - goto oom; - - } else if (dbus_message_is_method_call(message, "org.freedesktop.systemd1.Manager", "DisableUnitFiles") || - dbus_message_is_method_call(message, "org.freedesktop.systemd1.Manager", "UnmaskUnitFiles")) { - - char **l = NULL; - DBusMessageIter iter; - UnitFileScope scope = m->running_as == MANAGER_SYSTEM ? UNIT_FILE_SYSTEM : UNIT_FILE_USER; - UnitFileChange *changes = NULL; - unsigned n_changes = 0; - dbus_bool_t runtime; - - if (!dbus_message_iter_init(message, &iter)) - goto oom; - - r = bus_parse_strv_iter(&iter, &l); - if (r < 0) { - if (r == -ENOMEM) - goto oom; - - return bus_send_error_reply(connection, message, NULL, r); - } - - if (!dbus_message_iter_next(&iter) || - bus_iter_get_basic_and_next(&iter, DBUS_TYPE_BOOLEAN, &runtime, false) < 0) { - strv_free(l); - return bus_send_error_reply(connection, message, NULL, -EIO); - } - - if (streq(member, "DisableUnitFiles")) - r = unit_file_disable(scope, runtime, NULL, l, &changes, &n_changes); - else if (streq(member, "UnmaskUnitFiles")) - r = unit_file_unmask(scope, runtime, NULL, l, &changes, &n_changes); - else - assert_not_reached("Uh? Wrong method"); - - strv_free(l); - bus_manager_send_unit_files_changed(m); - - if (r < 0) { - unit_file_changes_free(changes, n_changes); - return bus_send_error_reply(connection, message, NULL, r); - } - - reply = message_from_file_changes(message, changes, n_changes, -1); - unit_file_changes_free(changes, n_changes); - - if (!reply) - goto oom; - - } else { - const BusBoundProperties bps[] = { - { "org.freedesktop.systemd1.Manager", bus_systemd_properties, systemd_property_string }, - { "org.freedesktop.systemd1.Manager", bus_manager_properties, m }, - { NULL, } - }; - return bus_default_message_handler(connection, message, NULL, INTERFACES_LIST, bps); - } - - if (job_type != _JOB_TYPE_INVALID) { - const char *name, *smode, *old_name = NULL; - JobMode mode; - Job *j; - Unit *u; - bool b; - - if (dbus_message_is_method_call(message, "org.freedesktop.systemd1.Manager", "StartUnitReplace")) - b = dbus_message_get_args( - message, - &error, - DBUS_TYPE_STRING, &old_name, - DBUS_TYPE_STRING, &name, - DBUS_TYPE_STRING, &smode, - DBUS_TYPE_INVALID); - else - b = dbus_message_get_args( - message, - &error, - DBUS_TYPE_STRING, &name, - DBUS_TYPE_STRING, &smode, - DBUS_TYPE_INVALID); - - if (!b) - return bus_send_error_reply(connection, message, &error, -EINVAL); - - if (old_name) - if (!(u = manager_get_unit(m, old_name)) || - !u->job || - u->job->type != JOB_START) { - dbus_set_error(&error, BUS_ERROR_NO_SUCH_JOB, "No job queued for unit %s", old_name); - return bus_send_error_reply(connection, message, &error, -ENOENT); - } - - - if ((mode = job_mode_from_string(smode)) == _JOB_MODE_INVALID) { - dbus_set_error(&error, BUS_ERROR_INVALID_JOB_MODE, "Job mode %s is invalid.", smode); - return bus_send_error_reply(connection, message, &error, -EINVAL); - } - - if ((r = manager_load_unit(m, name, NULL, &error, &u)) < 0) - return bus_send_error_reply(connection, message, &error, r); - - if (reload_if_possible && unit_can_reload(u)) { - if (job_type == JOB_RESTART) - job_type = JOB_RELOAD_OR_START; - else if (job_type == JOB_TRY_RESTART) - job_type = JOB_RELOAD; - } - - if ((job_type == JOB_START && u->refuse_manual_start) || - (job_type == JOB_STOP && u->refuse_manual_stop) || - ((job_type == JOB_RESTART || job_type == JOB_TRY_RESTART) && - (u->refuse_manual_start || u->refuse_manual_stop))) { - dbus_set_error(&error, BUS_ERROR_ONLY_BY_DEPENDENCY, "Operation refused, may be requested by dependency only."); - return bus_send_error_reply(connection, message, &error, -EPERM); - } - - if ((r = manager_add_job(m, job_type, u, mode, true, &error, &j)) < 0) - return bus_send_error_reply(connection, message, &error, r); - - if (!(j->bus_client = strdup(message_get_sender_with_fallback(message)))) - goto oom; - - j->bus = connection; - - if (!(reply = dbus_message_new_method_return(message))) - goto oom; - - if (!(path = job_dbus_path(j))) - goto oom; - - if (!dbus_message_append_args( - reply, - DBUS_TYPE_OBJECT_PATH, &path, - DBUS_TYPE_INVALID)) - goto oom; - } - - if (reply) { - if (!dbus_connection_send(connection, reply, NULL)) - goto oom; - - dbus_message_unref(reply); - } - - free(path); - - return DBUS_HANDLER_RESULT_HANDLED; - -oom: - free(path); - - if (reply) - dbus_message_unref(reply); - - dbus_error_free(&error); - - return DBUS_HANDLER_RESULT_NEED_MEMORY; -} - -const DBusObjectPathVTable bus_manager_vtable = { - .message_function = bus_manager_message_handler -}; diff --git a/src/dbus-manager.h b/src/dbus-manager.h deleted file mode 100644 index 2eb2fbbed6..0000000000 --- a/src/dbus-manager.h +++ /dev/null @@ -1,31 +0,0 @@ -/*-*- Mode: C; c-basic-offset: 8; indent-tabs-mode: nil -*-*/ - -#ifndef foodbusmanagerhfoo -#define foodbusmanagerhfoo - -/*** - This file is part of systemd. - - Copyright 2010 Lennart Poettering - - systemd is free software; you can redistribute it and/or modify it - under the terms of the GNU General Public License as published by - the Free Software Foundation; either version 2 of the License, or - (at your option) any later version. - - systemd is distributed in the hope that 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 General Public License - along with systemd; If not, see . -***/ - -#include - -extern const DBusObjectPathVTable bus_manager_vtable; - -extern const char bus_manager_interface[]; - -#endif diff --git a/src/dbus-mount.c b/src/dbus-mount.c deleted file mode 100644 index 35d6ea7a1d..0000000000 --- a/src/dbus-mount.c +++ /dev/null @@ -1,168 +0,0 @@ -/*-*- Mode: C; c-basic-offset: 8; indent-tabs-mode: nil -*-*/ - -/*** - This file is part of systemd. - - Copyright 2010 Lennart Poettering - - systemd is free software; you can redistribute it and/or modify it - under the terms of the GNU General Public License as published by - the Free Software Foundation; either version 2 of the License, or - (at your option) any later version. - - systemd is distributed in the hope that 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 General Public License - along with systemd; If not, see . -***/ - -#include - -#include "dbus-unit.h" -#include "dbus-mount.h" -#include "dbus-execute.h" -#include "dbus-common.h" - -#define BUS_MOUNT_INTERFACE \ - " \n" \ - " \n" \ - " \n" \ - " \n" \ - " \n" \ - " \n" \ - BUS_EXEC_COMMAND_INTERFACE("ExecMount") \ - BUS_EXEC_COMMAND_INTERFACE("ExecUnmount") \ - BUS_EXEC_COMMAND_INTERFACE("ExecRemount") \ - BUS_EXEC_CONTEXT_INTERFACE \ - " \n" \ - " \n" \ - " \n" \ - " \n" - -#define INTROSPECTION \ - DBUS_INTROSPECT_1_0_XML_DOCTYPE_DECL_NODE \ - "\n" \ - BUS_UNIT_INTERFACE \ - BUS_MOUNT_INTERFACE \ - BUS_PROPERTIES_INTERFACE \ - BUS_PEER_INTERFACE \ - BUS_INTROSPECTABLE_INTERFACE \ - "\n" - -#define INTERFACES_LIST \ - BUS_UNIT_INTERFACES_LIST \ - "org.freedesktop.systemd1.Mount\0" - -const char bus_mount_interface[] _introspect_("Mount") = BUS_MOUNT_INTERFACE; - -const char bus_mount_invalidating_properties[] = - "What\0" - "Options\0" - "Type\0" - "ExecMount\0" - "ExecUnmount\0" - "ExecRemount\0" - "ControlPID\0" - "Result\0"; - -static int bus_mount_append_what(DBusMessageIter *i, const char *property, void *data) { - Mount *m = data; - const char *d; - - assert(i); - assert(property); - 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 if (m->from_etc_fstab && m->parameters_etc_fstab.what) - d = m->parameters_etc_fstab.what; - else - d = ""; - - if (!dbus_message_iter_append_basic(i, DBUS_TYPE_STRING, &d)) - return -ENOMEM; - - return 0; -} - -static int bus_mount_append_options(DBusMessageIter *i, const char *property, void *data) { - Mount *m = data; - const char *d; - - assert(i); - assert(property); - 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 if (m->from_etc_fstab && m->parameters_etc_fstab.options) - d = m->parameters_etc_fstab.options; - else - d = ""; - - if (!dbus_message_iter_append_basic(i, DBUS_TYPE_STRING, &d)) - return -ENOMEM; - - return 0; -} - -static int bus_mount_append_type(DBusMessageIter *i, const char *property, void *data) { - Mount *m = data; - const char *d; - - assert(i); - assert(property); - 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 if (m->from_etc_fstab && m->parameters_etc_fstab.fstype) - d = m->parameters_etc_fstab.fstype; - else - d = ""; - - if (!dbus_message_iter_append_basic(i, DBUS_TYPE_STRING, &d)) - return -ENOMEM; - - return 0; -} - -static DEFINE_BUS_PROPERTY_APPEND_ENUM(bus_mount_append_mount_result, mount_result, MountResult); - -static const BusProperty bus_mount_properties[] = { - { "Where", bus_property_append_string, "s", offsetof(Mount, where), true }, - { "What", bus_mount_append_what, "s", 0 }, - { "Options", bus_mount_append_options, "s", 0 }, - { "Type", bus_mount_append_type, "s", 0 }, - { "TimeoutUSec", bus_property_append_usec, "t", offsetof(Mount, timeout_usec) }, - BUS_EXEC_COMMAND_PROPERTY("ExecMount", offsetof(Mount, exec_command[MOUNT_EXEC_MOUNT]), false), - BUS_EXEC_COMMAND_PROPERTY("ExecUnmount", offsetof(Mount, exec_command[MOUNT_EXEC_UNMOUNT]), false), - BUS_EXEC_COMMAND_PROPERTY("ExecRemount", offsetof(Mount, exec_command[MOUNT_EXEC_REMOUNT]), false), - { "ControlPID", bus_property_append_pid, "u", offsetof(Mount, control_pid) }, - { "DirectoryMode", bus_property_append_mode, "u", offsetof(Mount, directory_mode) }, - { "Result", bus_mount_append_mount_result, "s", offsetof(Mount, result) }, - { NULL, } -}; - -DBusHandlerResult bus_mount_message_handler(Unit *u, DBusConnection *c, DBusMessage *message) { - Mount *m = MOUNT(u); - - const BusBoundProperties bps[] = { - { "org.freedesktop.systemd1.Unit", bus_unit_properties, u }, - { "org.freedesktop.systemd1.Mount", bus_mount_properties, m }, - { "org.freedesktop.systemd1.Mount", bus_exec_context_properties, &m->exec_context }, - { NULL, } - }; - - return bus_default_message_handler(c, message, INTROSPECTION, INTERFACES_LIST, bps ); -} diff --git a/src/dbus-mount.h b/src/dbus-mount.h deleted file mode 100644 index b5613fa7b6..0000000000 --- a/src/dbus-mount.h +++ /dev/null @@ -1,34 +0,0 @@ -/*-*- Mode: C; c-basic-offset: 8; indent-tabs-mode: nil -*-*/ - -#ifndef foodbusmounthfoo -#define foodbusmounthfoo - -/*** - This file is part of systemd. - - Copyright 2010 Lennart Poettering - - systemd is free software; you can redistribute it and/or modify it - under the terms of the GNU General Public License as published by - the Free Software Foundation; either version 2 of the License, or - (at your option) any later version. - - systemd is distributed in the hope that 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 General Public License - along with systemd; If not, see . -***/ - -#include - -#include "unit.h" - -DBusHandlerResult bus_mount_message_handler(Unit *u, DBusConnection *c, DBusMessage *message); - -extern const char bus_mount_interface[]; -extern const char bus_mount_invalidating_properties[]; - -#endif diff --git a/src/dbus-path.c b/src/dbus-path.c deleted file mode 100644 index 5506784c38..0000000000 --- a/src/dbus-path.c +++ /dev/null @@ -1,119 +0,0 @@ -/*-*- Mode: C; c-basic-offset: 8; indent-tabs-mode: nil -*-*/ - -/*** - This file is part of systemd. - - Copyright 2010 Lennart Poettering - - systemd is free software; you can redistribute it and/or modify it - under the terms of the GNU General Public License as published by - the Free Software Foundation; either version 2 of the License, or - (at your option) any later version. - - systemd is distributed in the hope that 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 General Public License - along with systemd; If not, see . -***/ - -#include - -#include "dbus-unit.h" -#include "dbus-path.h" -#include "dbus-execute.h" -#include "dbus-common.h" - -#define BUS_PATH_INTERFACE \ - " \n" \ - " \n" \ - " \n" \ - " \n" \ - " \n" \ - " \n" \ - " \n" - -#define INTROSPECTION \ - DBUS_INTROSPECT_1_0_XML_DOCTYPE_DECL_NODE \ - "\n" \ - BUS_UNIT_INTERFACE \ - BUS_PATH_INTERFACE \ - BUS_PROPERTIES_INTERFACE \ - BUS_PEER_INTERFACE \ - BUS_INTROSPECTABLE_INTERFACE \ - "\n" - -#define INTERFACES_LIST \ - BUS_UNIT_INTERFACES_LIST \ - "org.freedesktop.systemd1.Path\0" - -const char bus_path_interface[] _introspect_("Path") = BUS_PATH_INTERFACE; - -const char bus_path_invalidating_properties[] = - "Result\0"; - -static int bus_path_append_paths(DBusMessageIter *i, const char *property, void *data) { - Path *p = data; - DBusMessageIter sub, sub2; - PathSpec *k; - - assert(i); - assert(property); - assert(p); - - if (!dbus_message_iter_open_container(i, DBUS_TYPE_ARRAY, "(ss)", &sub)) - return -ENOMEM; - - LIST_FOREACH(spec, k, p->specs) { - const char *t = path_type_to_string(k->type); - - if (!dbus_message_iter_open_container(&sub, DBUS_TYPE_STRUCT, NULL, &sub2) || - !dbus_message_iter_append_basic(&sub2, DBUS_TYPE_STRING, &t) || - !dbus_message_iter_append_basic(&sub2, DBUS_TYPE_STRING, &k->path) || - !dbus_message_iter_close_container(&sub, &sub2)) - return -ENOMEM; - } - - if (!dbus_message_iter_close_container(i, &sub)) - return -ENOMEM; - - return 0; -} - -static int bus_path_append_unit(DBusMessageIter *i, const char *property, void *data) { - Unit *u = data; - Path *p = PATH(u); - const char *t; - - assert(i); - assert(property); - assert(u); - - t = UNIT_DEREF(p->unit) ? UNIT_DEREF(p->unit)->id : ""; - - return dbus_message_iter_append_basic(i, DBUS_TYPE_STRING, &t) ? 0 : -ENOMEM; -} - -static DEFINE_BUS_PROPERTY_APPEND_ENUM(bus_path_append_path_result, path_result, PathResult); - -static const BusProperty bus_path_properties[] = { - { "Unit", bus_path_append_unit, "s", 0 }, - { "Paths", bus_path_append_paths, "a(ss)", 0 }, - { "MakeDirectory", bus_property_append_bool, "b", offsetof(Path, make_directory) }, - { "DirectoryMode", bus_property_append_mode, "u", offsetof(Path, directory_mode) }, - { "Result", bus_path_append_path_result, "s", offsetof(Path, result) }, - { NULL, } -}; - -DBusHandlerResult bus_path_message_handler(Unit *u, DBusConnection *c, DBusMessage *message) { - Path *p = PATH(u); - const BusBoundProperties bps[] = { - { "org.freedesktop.systemd1.Unit", bus_unit_properties, u }, - { "org.freedesktop.systemd1.Path", bus_path_properties, p }, - { NULL, } - }; - - return bus_default_message_handler(c, message, INTROSPECTION, INTERFACES_LIST, bps); -} diff --git a/src/dbus-path.h b/src/dbus-path.h deleted file mode 100644 index 2888400a13..0000000000 --- a/src/dbus-path.h +++ /dev/null @@ -1,35 +0,0 @@ -/*-*- Mode: C; c-basic-offset: 8; indent-tabs-mode: nil -*-*/ - -#ifndef foodbuspathhfoo -#define foodbuspathhfoo - -/*** - This file is part of systemd. - - Copyright 2010 Lennart Poettering - - systemd is free software; you can redistribute it and/or modify it - under the terms of the GNU General Public License as published by - the Free Software Foundation; either version 2 of the License, or - (at your option) any later version. - - systemd is distributed in the hope that 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 General Public License - along with systemd; If not, see . -***/ - -#include - -#include "unit.h" - -DBusHandlerResult bus_path_message_handler(Unit *u, DBusConnection *c, DBusMessage *message); - -extern const char bus_path_interface[]; - -extern const char bus_path_invalidating_properties[]; - -#endif diff --git a/src/dbus-service.c b/src/dbus-service.c deleted file mode 100644 index d840415417..0000000000 --- a/src/dbus-service.c +++ /dev/null @@ -1,169 +0,0 @@ -/*-*- Mode: C; c-basic-offset: 8; indent-tabs-mode: nil -*-*/ - -/*** - This file is part of systemd. - - Copyright 2010 Lennart Poettering - - systemd is free software; you can redistribute it and/or modify it - under the terms of the GNU General Public License as published by - the Free Software Foundation; either version 2 of the License, or - (at your option) any later version. - - systemd is distributed in the hope that 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 General Public License - along with systemd; If not, see . -***/ - -#include - -#include "dbus-unit.h" -#include "dbus-execute.h" -#include "dbus-service.h" -#include "dbus-common.h" - -#ifdef HAVE_SYSV_COMPAT -#define BUS_SERVICE_SYSV_INTERFACE_FRAGMENT \ - " \n" \ - " \n" \ - " \n" -#else -#define BUS_SERVICE_SYSV_INTERFACE_FRAGMENT "" -#endif - -#define BUS_SERVICE_INTERFACE \ - " \n" \ - " \n" \ - " \n" \ - " \n" \ - " \n" \ - " \n" \ - " \n" \ - " \n" \ - " \n" \ - " \n" \ - " \n" \ - " \n" \ - " \n" \ - BUS_EXEC_COMMAND_INTERFACE("ExecStartPre") \ - BUS_EXEC_COMMAND_INTERFACE("ExecStart") \ - BUS_EXEC_COMMAND_INTERFACE("ExecStartPost") \ - BUS_EXEC_COMMAND_INTERFACE("ExecReload") \ - BUS_EXEC_COMMAND_INTERFACE("ExecStop") \ - BUS_EXEC_COMMAND_INTERFACE("ExecStopPost") \ - BUS_EXEC_CONTEXT_INTERFACE \ - " \n" \ - " \n" \ - " \n" \ - BUS_EXEC_STATUS_INTERFACE("ExecMain") \ - " \n" \ - " \n" \ - " \n" \ - " \n" \ - " \n" \ - " \n" \ - BUS_SERVICE_SYSV_INTERFACE_FRAGMENT \ - " \n" - -#define INTROSPECTION \ - DBUS_INTROSPECT_1_0_XML_DOCTYPE_DECL_NODE \ - "\n" \ - BUS_UNIT_INTERFACE \ - BUS_SERVICE_INTERFACE \ - BUS_PROPERTIES_INTERFACE \ - BUS_PEER_INTERFACE \ - BUS_INTROSPECTABLE_INTERFACE \ - "\n" - -#define INTERFACES_LIST \ - BUS_UNIT_INTERFACES_LIST \ - "org.freedesktop.systemd1.Service\0" - -const char bus_service_interface[] _introspect_("Service") = BUS_SERVICE_INTERFACE; - -const char bus_service_invalidating_properties[] = - "ExecStartPre\0" - "ExecStart\0" - "ExecStartPost\0" - "ExecReload\0" - "ExecStop\0" - "ExecStopPost\0" - "ExecMain\0" - "WatchdogTimestamp\0" - "WatchdogTimestampMonotonic\0" - "MainPID\0" - "ControlPID\0" - "StatusText\0" - "Result\0"; - -static DEFINE_BUS_PROPERTY_APPEND_ENUM(bus_service_append_type, service_type, ServiceType); -static DEFINE_BUS_PROPERTY_APPEND_ENUM(bus_service_append_restart, service_restart, ServiceRestart); -static DEFINE_BUS_PROPERTY_APPEND_ENUM(bus_service_append_notify_access, notify_access, NotifyAccess); -static DEFINE_BUS_PROPERTY_APPEND_ENUM(bus_service_append_service_result, service_result, ServiceResult); -static DEFINE_BUS_PROPERTY_APPEND_ENUM(bus_service_append_start_limit_action, start_limit_action, StartLimitAction); -static DEFINE_BUS_PROPERTY_SET_ENUM(bus_service_set_start_limit_action, start_limit_action, StartLimitAction); - -static const BusProperty bus_exec_main_status_properties[] = { - { "ExecMainStartTimestamp", bus_property_append_usec, "t", offsetof(ExecStatus, start_timestamp.realtime) }, - { "ExecMainStartTimestampMonotonic",bus_property_append_usec, "t", offsetof(ExecStatus, start_timestamp.monotonic) }, - { "ExecMainExitTimestamp", bus_property_append_usec, "t", offsetof(ExecStatus, start_timestamp.realtime) }, - { "ExecMainExitTimestampMonotonic", bus_property_append_usec, "t", offsetof(ExecStatus, start_timestamp.monotonic) }, - { "ExecMainPID", bus_property_append_pid, "u", offsetof(ExecStatus, pid) }, - { "ExecMainCode", bus_property_append_int, "i", offsetof(ExecStatus, code) }, - { "ExecMainStatus", bus_property_append_int, "i", offsetof(ExecStatus, status) }, - { NULL, } -}; - -static const BusProperty bus_service_properties[] = { - { "Type", bus_service_append_type, "s", offsetof(Service, type) }, - { "Restart", bus_service_append_restart, "s", offsetof(Service, restart) }, - { "PIDFile", bus_property_append_string, "s", offsetof(Service, pid_file), true }, - { "NotifyAccess", bus_service_append_notify_access, "s", offsetof(Service, notify_access) }, - { "RestartUSec", bus_property_append_usec, "t", offsetof(Service, restart_usec) }, - { "TimeoutUSec", bus_property_append_usec, "t", offsetof(Service, timeout_usec) }, - { "WatchdogUSec", bus_property_append_usec, "t", offsetof(Service, watchdog_usec) }, - { "WatchdogTimestamp", bus_property_append_usec, "t", offsetof(Service, watchdog_timestamp.realtime) }, - { "WatchdogTimestampMonotonic",bus_property_append_usec, "t", offsetof(Service, watchdog_timestamp.monotonic) }, - { "StartLimitInterval", bus_property_append_usec, "t", offsetof(Service, start_limit.interval) }, - { "StartLimitBurst", bus_property_append_uint32, "u", offsetof(Service, start_limit.burst) }, - { "StartLimitAction", bus_service_append_start_limit_action,"s", offsetof(Service, start_limit_action), false, bus_service_set_start_limit_action}, - BUS_EXEC_COMMAND_PROPERTY("ExecStartPre", offsetof(Service, exec_command[SERVICE_EXEC_START_PRE]), true ), - BUS_EXEC_COMMAND_PROPERTY("ExecStart", offsetof(Service, exec_command[SERVICE_EXEC_START]), true ), - BUS_EXEC_COMMAND_PROPERTY("ExecStartPost", offsetof(Service, exec_command[SERVICE_EXEC_START_POST]), true ), - BUS_EXEC_COMMAND_PROPERTY("ExecReload", offsetof(Service, exec_command[SERVICE_EXEC_RELOAD]), true ), - BUS_EXEC_COMMAND_PROPERTY("ExecStop", offsetof(Service, exec_command[SERVICE_EXEC_STOP]), true ), - BUS_EXEC_COMMAND_PROPERTY("ExecStopPost", offsetof(Service, exec_command[SERVICE_EXEC_STOP_POST]), true ), - { "PermissionsStartOnly", bus_property_append_bool, "b", offsetof(Service, permissions_start_only) }, - { "RootDirectoryStartOnly", bus_property_append_bool, "b", offsetof(Service, root_directory_start_only) }, - { "RemainAfterExit", bus_property_append_bool, "b", offsetof(Service, remain_after_exit) }, - { "GuessMainPID", bus_property_append_bool, "b", offsetof(Service, guess_main_pid) }, - { "MainPID", bus_property_append_pid, "u", offsetof(Service, main_pid) }, - { "ControlPID", bus_property_append_pid, "u", offsetof(Service, control_pid) }, - { "BusName", bus_property_append_string, "s", offsetof(Service, bus_name), true }, - { "StatusText", bus_property_append_string, "s", offsetof(Service, status_text), true }, -#ifdef HAVE_SYSV_COMPAT - { "SysVRunLevels", bus_property_append_string, "s", offsetof(Service, sysv_runlevels), true }, - { "SysVStartPriority", bus_property_append_int, "i", offsetof(Service, sysv_start_priority) }, - { "SysVPath", bus_property_append_string, "s", offsetof(Service, sysv_path), true }, -#endif - { "FsckPassNo", bus_property_append_int, "i", offsetof(Service, fsck_passno) }, - { "Result", bus_service_append_service_result,"s", offsetof(Service, result) }, - { NULL, } -}; - -DBusHandlerResult bus_service_message_handler(Unit *u, DBusConnection *connection, DBusMessage *message) { - Service *s = SERVICE(u); - const BusBoundProperties bps[] = { - { "org.freedesktop.systemd1.Unit", bus_unit_properties, u }, - { "org.freedesktop.systemd1.Service", bus_service_properties, s }, - { "org.freedesktop.systemd1.Service", bus_exec_context_properties, &s->exec_context }, - { "org.freedesktop.systemd1.Service", bus_exec_main_status_properties, &s->main_exec_status }, - { NULL, } - }; - - return bus_default_message_handler(connection, message, INTROSPECTION, INTERFACES_LIST, bps); -} diff --git a/src/dbus-service.h b/src/dbus-service.h deleted file mode 100644 index d6eab65c52..0000000000 --- a/src/dbus-service.h +++ /dev/null @@ -1,34 +0,0 @@ -/*-*- Mode: C; c-basic-offset: 8; indent-tabs-mode: nil -*-*/ - -#ifndef foodbusservicehfoo -#define foodbusservicehfoo - -/*** - This file is part of systemd. - - Copyright 2010 Lennart Poettering - - systemd is free software; you can redistribute it and/or modify it - under the terms of the GNU General Public License as published by - the Free Software Foundation; either version 2 of the License, or - (at your option) any later version. - - systemd is distributed in the hope that 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 General Public License - along with systemd; If not, see . -***/ - -#include - -#include "unit.h" - -DBusHandlerResult bus_service_message_handler(Unit *u, DBusConnection *c, DBusMessage *message); - -extern const char bus_service_interface[]; -extern const char bus_service_invalidating_properties[]; - -#endif diff --git a/src/dbus-snapshot.c b/src/dbus-snapshot.c deleted file mode 100644 index e69388a524..0000000000 --- a/src/dbus-snapshot.c +++ /dev/null @@ -1,93 +0,0 @@ -/*-*- Mode: C; c-basic-offset: 8; indent-tabs-mode: nil -*-*/ - -/*** - This file is part of systemd. - - Copyright 2010 Lennart Poettering - - systemd is free software; you can redistribute it and/or modify it - under the terms of the GNU General Public License as published by - the Free Software Foundation; either version 2 of the License, or - (at your option) any later version. - - systemd is distributed in the hope that 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 General Public License - along with systemd; If not, see . -***/ - -#include "dbus-unit.h" -#include "dbus-snapshot.h" -#include "dbus-common.h" - -#define BUS_SNAPSHOT_INTERFACE \ - " \n" \ - " \n" \ - " \n" \ - " \n" - -#define INTROSPECTION \ - DBUS_INTROSPECT_1_0_XML_DOCTYPE_DECL_NODE \ - "\n" \ - BUS_UNIT_INTERFACE \ - BUS_SNAPSHOT_INTERFACE \ - BUS_PROPERTIES_INTERFACE \ - BUS_PEER_INTERFACE \ - BUS_INTROSPECTABLE_INTERFACE \ - "\n" - -#define INTERFACES_LIST \ - BUS_UNIT_INTERFACES_LIST \ - "org.freedesktop.systemd1.Snapshot\0" - -const char bus_snapshot_interface[] _introspect_("Snapshot") = BUS_SNAPSHOT_INTERFACE; - -static const BusProperty bus_snapshot_properties[] = { - { "Cleanup", bus_property_append_bool, "b", offsetof(Snapshot, cleanup) }, - { NULL, } -}; - -DBusHandlerResult bus_snapshot_message_handler(Unit *u, DBusConnection *c, DBusMessage *message) { - Snapshot *s = SNAPSHOT(u); - - DBusMessage *reply = NULL; - DBusError error; - - dbus_error_init(&error); - - if (dbus_message_is_method_call(message, "org.freedesktop.systemd1.Snapshot", "Remove")) { - - snapshot_remove(SNAPSHOT(u)); - - if (!(reply = dbus_message_new_method_return(message))) - goto oom; - - } else { - const BusBoundProperties bps[] = { - { "org.freedesktop.systemd1.Unit", bus_unit_properties, u }, - { "org.freedesktop.systemd1.Snapshot", bus_snapshot_properties, s }, - { NULL, } - }; - return bus_default_message_handler(c, message, INTROSPECTION, INTERFACES_LIST, bps); - } - - if (reply) { - if (!dbus_connection_send(c, reply, NULL)) - goto oom; - - dbus_message_unref(reply); - } - - return DBUS_HANDLER_RESULT_HANDLED; - -oom: - if (reply) - dbus_message_unref(reply); - - dbus_error_free(&error); - - return DBUS_HANDLER_RESULT_NEED_MEMORY; -} diff --git a/src/dbus-snapshot.h b/src/dbus-snapshot.h deleted file mode 100644 index 0b82279f1c..0000000000 --- a/src/dbus-snapshot.h +++ /dev/null @@ -1,33 +0,0 @@ -/*-*- Mode: C; c-basic-offset: 8; indent-tabs-mode: nil -*-*/ - -#ifndef foodbussnapshothfoo -#define foodbussnapshothfoo - -/*** - This file is part of systemd. - - Copyright 2010 Lennart Poettering - - systemd is free software; you can redistribute it and/or modify it - under the terms of the GNU General Public License as published by - the Free Software Foundation; either version 2 of the License, or - (at your option) any later version. - - systemd is distributed in the hope that 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 General Public License - along with systemd; If not, see . -***/ - -#include - -#include "unit.h" - -DBusHandlerResult bus_snapshot_message_handler(Unit *u, DBusConnection *c, DBusMessage *message); - -extern const char bus_snapshot_interface[]; - -#endif diff --git a/src/dbus-socket.c b/src/dbus-socket.c deleted file mode 100644 index 2e3342cb55..0000000000 --- a/src/dbus-socket.c +++ /dev/null @@ -1,139 +0,0 @@ -/*-*- Mode: C; c-basic-offset: 8; indent-tabs-mode: nil -*-*/ - -/*** - This file is part of systemd. - - Copyright 2010 Lennart Poettering - - systemd is free software; you can redistribute it and/or modify it - under the terms of the GNU General Public License as published by - the Free Software Foundation; either version 2 of the License, or - (at your option) any later version. - - systemd is distributed in the hope that 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 General Public License - along with systemd; If not, see . -***/ - -#include - -#include "dbus-unit.h" -#include "dbus-socket.h" -#include "dbus-execute.h" -#include "dbus-common.h" - -#define BUS_SOCKET_INTERFACE \ - " \n" \ - " \n" \ - " \n" \ - " \n" \ - BUS_EXEC_COMMAND_INTERFACE("ExecStartPre") \ - BUS_EXEC_COMMAND_INTERFACE("ExecStartPost") \ - BUS_EXEC_COMMAND_INTERFACE("ExecStopPre") \ - BUS_EXEC_COMMAND_INTERFACE("ExecStopPost") \ - BUS_EXEC_CONTEXT_INTERFACE \ - " \n" \ - " \n" \ - " \n" \ - " \n" \ - " \n" \ - " \n" \ - " \n" \ - " \n" \ - " \n" \ - " \n" \ - " \n" \ - " \n" \ - " \n" \ - " \n" \ - " \n" \ - " \n" \ - " \n" \ - " \n" \ - " \n" \ - " \n" \ - " \n" \ - " \n" \ - " \n" \ - " \n" \ - " \n" \ - -#define INTROSPECTION \ - DBUS_INTROSPECT_1_0_XML_DOCTYPE_DECL_NODE \ - "\n" \ - BUS_UNIT_INTERFACE \ - BUS_SOCKET_INTERFACE \ - BUS_PROPERTIES_INTERFACE \ - BUS_PEER_INTERFACE \ - BUS_INTROSPECTABLE_INTERFACE \ - "\n" - -#define INTERFACES_LIST \ - BUS_UNIT_INTERFACES_LIST \ - "org.freedesktop.systemd1.Socket\0" - -const char bus_socket_interface[] _introspect_("Socket") = BUS_SOCKET_INTERFACE; - -const char bus_socket_invalidating_properties[] = - "ExecStartPre\0" - "ExecStartPost\0" - "ExecStopPre\0" - "ExecStopPost\0" - "ControlPID\0" - "NAccepted\0" - "NConnections\0" - "Result\0"; - -static DEFINE_BUS_PROPERTY_APPEND_ENUM(bus_socket_append_bind_ipv6_only, socket_address_bind_ipv6_only, SocketAddressBindIPv6Only); -static DEFINE_BUS_PROPERTY_APPEND_ENUM(bus_socket_append_socket_result, socket_result, SocketResult); - -static const BusProperty bus_socket_properties[] = { - { "BindIPv6Only", bus_socket_append_bind_ipv6_only, "s", offsetof(Socket, bind_ipv6_only) }, - { "Backlog", bus_property_append_unsigned, "u", offsetof(Socket, backlog) }, - { "TimeoutUSec", bus_property_append_usec, "t", offsetof(Socket, timeout_usec) }, - BUS_EXEC_COMMAND_PROPERTY("ExecStartPre", offsetof(Socket, exec_command[SOCKET_EXEC_START_PRE]), true ), - BUS_EXEC_COMMAND_PROPERTY("ExecStartPost", offsetof(Socket, exec_command[SOCKET_EXEC_START_POST]), true ), - BUS_EXEC_COMMAND_PROPERTY("ExecStopPre", offsetof(Socket, exec_command[SOCKET_EXEC_STOP_PRE]), true ), - BUS_EXEC_COMMAND_PROPERTY("ExecStopPost", offsetof(Socket, exec_command[SOCKET_EXEC_STOP_POST]), true ), - { "ControlPID", bus_property_append_pid, "u", offsetof(Socket, control_pid) }, - { "BindToDevice", bus_property_append_string, "s", offsetof(Socket, bind_to_device), true }, - { "DirectoryMode", bus_property_append_mode, "u", offsetof(Socket, directory_mode) }, - { "SocketMode", bus_property_append_mode, "u", offsetof(Socket, socket_mode) }, - { "Accept", bus_property_append_bool, "b", offsetof(Socket, accept) }, - { "KeepAlive", bus_property_append_bool, "b", offsetof(Socket, keep_alive) }, - { "Priority", bus_property_append_int, "i", offsetof(Socket, priority) }, - { "ReceiveBuffer", bus_property_append_size, "t", offsetof(Socket, receive_buffer) }, - { "SendBuffer", bus_property_append_size, "t", offsetof(Socket, send_buffer) }, - { "IPTOS", bus_property_append_int, "i", offsetof(Socket, ip_tos) }, - { "IPTTL", bus_property_append_int, "i", offsetof(Socket, ip_ttl) }, - { "PipeSize", bus_property_append_size, "t", offsetof(Socket, pipe_size) }, - { "FreeBind", bus_property_append_bool, "b", offsetof(Socket, free_bind) }, - { "Transparent", bus_property_append_bool, "b", offsetof(Socket, transparent) }, - { "Broadcast", bus_property_append_bool, "b", offsetof(Socket, broadcast) }, - { "PassCredentials",bus_property_append_bool, "b", offsetof(Socket, pass_cred) }, - { "PassSecurity", bus_property_append_bool, "b", offsetof(Socket, pass_sec) }, - { "Mark", bus_property_append_int, "i", offsetof(Socket, mark) }, - { "MaxConnections", bus_property_append_unsigned, "u", offsetof(Socket, max_connections) }, - { "NConnections", bus_property_append_unsigned, "u", offsetof(Socket, n_connections) }, - { "NAccepted", bus_property_append_unsigned, "u", offsetof(Socket, n_accepted) }, - { "MessageQueueMaxMessages", bus_property_append_long, "x", offsetof(Socket, mq_maxmsg) }, - { "MessageQueueMessageSize", bus_property_append_long, "x", offsetof(Socket, mq_msgsize) }, - { "Result", bus_socket_append_socket_result, "s", offsetof(Socket, result) }, - { NULL, } -}; - -DBusHandlerResult bus_socket_message_handler(Unit *u, DBusConnection *c, DBusMessage *message) { - Socket *s = SOCKET(u); - const BusBoundProperties bps[] = { - { "org.freedesktop.systemd1.Unit", bus_unit_properties, u }, - { "org.freedesktop.systemd1.Socket", bus_socket_properties, s }, - { "org.freedesktop.systemd1.Socket", bus_exec_context_properties, &s->exec_context }, - { NULL, } - }; - - return bus_default_message_handler(c, message, INTROSPECTION, INTERFACES_LIST, bps); -} diff --git a/src/dbus-socket.h b/src/dbus-socket.h deleted file mode 100644 index 069a2f5df0..0000000000 --- a/src/dbus-socket.h +++ /dev/null @@ -1,34 +0,0 @@ -/*-*- Mode: C; c-basic-offset: 8; indent-tabs-mode: nil -*-*/ - -#ifndef foodbussockethfoo -#define foodbussockethfoo - -/*** - This file is part of systemd. - - Copyright 2010 Lennart Poettering - - systemd is free software; you can redistribute it and/or modify it - under the terms of the GNU General Public License as published by - the Free Software Foundation; either version 2 of the License, or - (at your option) any later version. - - systemd is distributed in the hope that 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 General Public License - along with systemd; If not, see . -***/ - -#include - -#include "unit.h" - -DBusHandlerResult bus_socket_message_handler(Unit *u, DBusConnection *c, DBusMessage *message); - -extern const char bus_socket_interface[]; -extern const char bus_socket_invalidating_properties[]; - -#endif diff --git a/src/dbus-swap.c b/src/dbus-swap.c deleted file mode 100644 index 09cd1e8b9c..0000000000 --- a/src/dbus-swap.c +++ /dev/null @@ -1,111 +0,0 @@ -/*-*- Mode: C; c-basic-offset: 8; indent-tabs-mode: nil -*-*/ - -/*** - 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 General Public License as published by - the Free Software Foundation; either version 2 of the License, or - (at your option) any later version. - - systemd is distributed in the hope that 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 General Public License - along with systemd; If not, see . -***/ - -#include - -#include "dbus-unit.h" -#include "dbus-swap.h" -#include "dbus-execute.h" -#include "dbus-common.h" - -#define BUS_SWAP_INTERFACE \ - " \n" \ - " \n" \ - " \n" \ - " \n" \ - BUS_EXEC_COMMAND_INTERFACE("ExecActivate") \ - BUS_EXEC_COMMAND_INTERFACE("ExecDeactivate") \ - BUS_EXEC_CONTEXT_INTERFACE \ - " \n" \ - " \n" \ - " \n" - -#define INTROSPECTION \ - DBUS_INTROSPECT_1_0_XML_DOCTYPE_DECL_NODE \ - "\n" \ - BUS_UNIT_INTERFACE \ - BUS_SWAP_INTERFACE \ - BUS_PROPERTIES_INTERFACE \ - BUS_PEER_INTERFACE \ - BUS_INTROSPECTABLE_INTERFACE \ - "\n" - -#define INTERFACES_LIST \ - BUS_UNIT_INTERFACES_LIST \ - "org.freedesktop.systemd1.Swap\0" - -const char bus_swap_interface[] _introspect_("Swap") = BUS_SWAP_INTERFACE; - -const char bus_swap_invalidating_properties[] = - "What\0" - "Priority\0" - "ExecActivate\0" - "ExecDeactivate\0" - "ControlPID\0" - "Result\0"; - -static int bus_swap_append_priority(DBusMessageIter *i, const char *property, void *data) { - Swap *s = data; - dbus_int32_t j; - - assert(i); - assert(property); - assert(s); - - if (s->from_proc_swaps) - j = s->parameters_proc_swaps.priority; - else if (s->from_fragment) - j = s->parameters_fragment.priority; - else if (s->from_etc_fstab) - j = s->parameters_etc_fstab.priority; - else - j = -1; - - if (!dbus_message_iter_append_basic(i, DBUS_TYPE_INT32, &j)) - return -ENOMEM; - - return 0; -} - -static DEFINE_BUS_PROPERTY_APPEND_ENUM(bus_swap_append_swap_result, swap_result, SwapResult); - -static const BusProperty bus_swap_properties[] = { - { "What", bus_property_append_string, "s", offsetof(Swap, what), true }, - { "Priority", bus_swap_append_priority, "i", 0 }, - BUS_EXEC_COMMAND_PROPERTY("ExecActivate", offsetof(Swap, exec_command[SWAP_EXEC_ACTIVATE]), false), - BUS_EXEC_COMMAND_PROPERTY("ExecDeactivate", offsetof(Swap, exec_command[SWAP_EXEC_DEACTIVATE]), false), - { "ControlPID", bus_property_append_pid, "u", offsetof(Swap, control_pid) }, - { "Result", bus_swap_append_swap_result,"s", offsetof(Swap, result) }, - { NULL, } -}; - -DBusHandlerResult bus_swap_message_handler(Unit *u, DBusConnection *c, DBusMessage *message) { - Swap *s = SWAP(u); - const BusBoundProperties bps[] = { - { "org.freedesktop.systemd1.Unit", bus_unit_properties, u }, - { "org.freedesktop.systemd1.Swap", bus_swap_properties, s }, - { "org.freedesktop.systemd1.Swap", bus_exec_context_properties, &s->exec_context }, - { NULL, } - }; - - return bus_default_message_handler(c, message, INTROSPECTION, INTERFACES_LIST, bps); -} diff --git a/src/dbus-swap.h b/src/dbus-swap.h deleted file mode 100644 index 15b914726b..0000000000 --- a/src/dbus-swap.h +++ /dev/null @@ -1,35 +0,0 @@ -/*-*- Mode: C; c-basic-offset: 8; indent-tabs-mode: nil -*-*/ - -#ifndef foodbusswaphfoo -#define foodbusswaphfoo - -/*** - 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 General Public License as published by - the Free Software Foundation; either version 2 of the License, or - (at your option) any later version. - - systemd is distributed in the hope that 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 General Public License - along with systemd; If not, see . -***/ - -#include - -#include "unit.h" - -DBusHandlerResult bus_swap_message_handler(Unit *u, DBusConnection *c, DBusMessage *message); - -extern const char bus_swap_interface[]; -extern const char bus_swap_invalidating_properties[]; - -#endif diff --git a/src/dbus-target.c b/src/dbus-target.c deleted file mode 100644 index 55cf862de0..0000000000 --- a/src/dbus-target.c +++ /dev/null @@ -1,55 +0,0 @@ -/*-*- Mode: C; c-basic-offset: 8; indent-tabs-mode: nil -*-*/ - -/*** - This file is part of systemd. - - Copyright 2010 Lennart Poettering - - systemd is free software; you can redistribute it and/or modify it - under the terms of the GNU General Public License as published by - the Free Software Foundation; either version 2 of the License, or - (at your option) any later version. - - systemd is distributed in the hope that 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 General Public License - along with systemd; If not, see . -***/ - -#include - -#include "dbus-unit.h" -#include "dbus-target.h" -#include "dbus-common.h" - -#define BUS_TARGET_INTERFACE \ - " \n" \ - " \n" - -#define INTROSPECTION \ - DBUS_INTROSPECT_1_0_XML_DOCTYPE_DECL_NODE \ - "\n" \ - BUS_UNIT_INTERFACE \ - BUS_TARGET_INTERFACE \ - BUS_PROPERTIES_INTERFACE \ - BUS_PEER_INTERFACE \ - BUS_INTROSPECTABLE_INTERFACE \ - "\n" - -#define INTERFACES_LIST \ - BUS_UNIT_INTERFACES_LIST \ - "org.freedesktop.systemd1.Target\0" - -const char bus_target_interface[] _introspect_("Target") = BUS_TARGET_INTERFACE; - -DBusHandlerResult bus_target_message_handler(Unit *u, DBusConnection *c, DBusMessage *message) { - const BusBoundProperties bps[] = { - { "org.freedesktop.systemd1.Unit", bus_unit_properties, u }, - { NULL, } - }; - - return bus_default_message_handler(c, message, INTROSPECTION, INTERFACES_LIST, bps); -} diff --git a/src/dbus-target.h b/src/dbus-target.h deleted file mode 100644 index 13d38764a2..0000000000 --- a/src/dbus-target.h +++ /dev/null @@ -1,33 +0,0 @@ -/*-*- Mode: C; c-basic-offset: 8; indent-tabs-mode: nil -*-*/ - -#ifndef foodbustargethfoo -#define foodbustargethfoo - -/*** - This file is part of systemd. - - Copyright 2010 Lennart Poettering - - systemd is free software; you can redistribute it and/or modify it - under the terms of the GNU General Public License as published by - the Free Software Foundation; either version 2 of the License, or - (at your option) any later version. - - systemd is distributed in the hope that 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 General Public License - along with systemd; If not, see . -***/ - -#include - -#include "unit.h" - -DBusHandlerResult bus_target_message_handler(Unit *u, DBusConnection *c, DBusMessage *message); - -extern const char bus_target_interface[]; - -#endif diff --git a/src/dbus-timer.c b/src/dbus-timer.c deleted file mode 100644 index b396aed047..0000000000 --- a/src/dbus-timer.c +++ /dev/null @@ -1,137 +0,0 @@ -/*-*- Mode: C; c-basic-offset: 8; indent-tabs-mode: nil -*-*/ - -/*** - This file is part of systemd. - - Copyright 2010 Lennart Poettering - - systemd is free software; you can redistribute it and/or modify it - under the terms of the GNU General Public License as published by - the Free Software Foundation; either version 2 of the License, or - (at your option) any later version. - - systemd is distributed in the hope that 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 General Public License - along with systemd; If not, see . -***/ - -#include - -#include "dbus-unit.h" -#include "dbus-timer.h" -#include "dbus-execute.h" -#include "dbus-common.h" - -#define BUS_TIMER_INTERFACE \ - " \n" \ - " \n" \ - " \n" \ - " \n" \ - " \n" \ - " \n" - -#define INTROSPECTION \ - DBUS_INTROSPECT_1_0_XML_DOCTYPE_DECL_NODE \ - "\n" \ - BUS_UNIT_INTERFACE \ - BUS_TIMER_INTERFACE \ - BUS_PROPERTIES_INTERFACE \ - BUS_PEER_INTERFACE \ - BUS_INTROSPECTABLE_INTERFACE \ - "\n" - -#define INTERFACES_LIST \ - BUS_UNIT_INTERFACES_LIST \ - "org.freedesktop.systemd1.Timer\0" - -const char bus_timer_interface[] _introspect_("Timer") = BUS_TIMER_INTERFACE; - -const char bus_timer_invalidating_properties[] = - "Timers\0" - "NextElapseUSec\0" - "Result\0"; - -static int bus_timer_append_timers(DBusMessageIter *i, const char *property, void *data) { - Timer *p = data; - DBusMessageIter sub, sub2; - TimerValue *k; - - assert(i); - assert(property); - assert(p); - - if (!dbus_message_iter_open_container(i, DBUS_TYPE_ARRAY, "(stt)", &sub)) - return -ENOMEM; - - LIST_FOREACH(value, k, p->values) { - char *buf; - const char *t; - size_t l; - bool b; - - t = timer_base_to_string(k->base); - assert(endswith(t, "Sec")); - - /* s/Sec/USec/ */ - l = strlen(t); - if (!(buf = new(char, l+2))) - return -ENOMEM; - - memcpy(buf, t, l-3); - memcpy(buf+l-3, "USec", 5); - - b = dbus_message_iter_open_container(&sub, DBUS_TYPE_STRUCT, NULL, &sub2) && - dbus_message_iter_append_basic(&sub2, DBUS_TYPE_STRING, &buf) && - dbus_message_iter_append_basic(&sub2, DBUS_TYPE_UINT64, &k->value) && - dbus_message_iter_append_basic(&sub2, DBUS_TYPE_UINT64, &k->next_elapse) && - dbus_message_iter_close_container(&sub, &sub2); - - free(buf); - if (!b) - return -ENOMEM; - } - - if (!dbus_message_iter_close_container(i, &sub)) - return -ENOMEM; - - return 0; -} - -static int bus_timer_append_unit(DBusMessageIter *i, const char *property, void *data) { - Unit *u = data; - Timer *timer = TIMER(u); - const char *t; - - assert(i); - assert(property); - assert(u); - - t = UNIT_DEREF(timer->unit) ? UNIT_DEREF(timer->unit)->id : ""; - - return dbus_message_iter_append_basic(i, DBUS_TYPE_STRING, &t) ? 0 : -ENOMEM; -} - -static DEFINE_BUS_PROPERTY_APPEND_ENUM(bus_timer_append_timer_result, timer_result, TimerResult); - -static const BusProperty bus_timer_properties[] = { - { "Unit", bus_timer_append_unit, "s", 0 }, - { "Timers", bus_timer_append_timers, "a(stt)", 0 }, - { "NextElapseUSec", bus_property_append_usec, "t", offsetof(Timer, next_elapse) }, - { "Result", bus_timer_append_timer_result,"s", offsetof(Timer, result) }, - { NULL, } -}; - -DBusHandlerResult bus_timer_message_handler(Unit *u, DBusConnection *c, DBusMessage *message) { - Timer *t = TIMER(u); - const BusBoundProperties bps[] = { - { "org.freedesktop.systemd1.Unit", bus_unit_properties, u }, - { "org.freedesktop.systemd1.Timer", bus_timer_properties, t }, - { NULL, } - }; - - return bus_default_message_handler(c, message, INTROSPECTION, INTERFACES_LIST, bps); -} diff --git a/src/dbus-timer.h b/src/dbus-timer.h deleted file mode 100644 index e692e12fcb..0000000000 --- a/src/dbus-timer.h +++ /dev/null @@ -1,34 +0,0 @@ -/*-*- Mode: C; c-basic-offset: 8; indent-tabs-mode: nil -*-*/ - -#ifndef foodbustimerhfoo -#define foodbustimerhfoo - -/*** - This file is part of systemd. - - Copyright 2010 Lennart Poettering - - systemd is free software; you can redistribute it and/or modify it - under the terms of the GNU General Public License as published by - the Free Software Foundation; either version 2 of the License, or - (at your option) any later version. - - systemd is distributed in the hope that 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 General Public License - along with systemd; If not, see . -***/ - -#include - -#include "unit.h" - -DBusHandlerResult bus_timer_message_handler(Unit *u, DBusConnection *c, DBusMessage *message); - -extern const char bus_timer_interface[]; -extern const char bus_timer_invalidating_properties[]; - -#endif diff --git a/src/dbus-unit.c b/src/dbus-unit.c deleted file mode 100644 index c7532c7255..0000000000 --- a/src/dbus-unit.c +++ /dev/null @@ -1,850 +0,0 @@ -/*-*- Mode: C; c-basic-offset: 8; indent-tabs-mode: nil -*-*/ - -/*** - This file is part of systemd. - - Copyright 2010 Lennart Poettering - - systemd is free software; you can redistribute it and/or modify it - under the terms of the GNU General Public License as published by - the Free Software Foundation; either version 2 of the License, or - (at your option) any later version. - - systemd is distributed in the hope that 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 General Public License - along with systemd; If not, see . -***/ - -#include - -#include "dbus.h" -#include "log.h" -#include "dbus-unit.h" -#include "bus-errors.h" -#include "dbus-common.h" - -const char bus_unit_interface[] _introspect_("Unit") = BUS_UNIT_INTERFACE; - -#define INVALIDATING_PROPERTIES \ - "LoadState\0" \ - "ActiveState\0" \ - "SubState\0" \ - "InactiveExitTimestamp\0" \ - "ActiveEnterTimestamp\0" \ - "ActiveExitTimestamp\0" \ - "InactiveEnterTimestamp\0" \ - "Job\0" \ - "NeedDaemonReload\0" - -static int bus_unit_append_names(DBusMessageIter *i, const char *property, void *data) { - char *t; - Iterator j; - DBusMessageIter sub; - Unit *u = data; - - if (!dbus_message_iter_open_container(i, DBUS_TYPE_ARRAY, "s", &sub)) - return -ENOMEM; - - SET_FOREACH(t, u->names, j) - if (!dbus_message_iter_append_basic(&sub, DBUS_TYPE_STRING, &t)) - return -ENOMEM; - - if (!dbus_message_iter_close_container(i, &sub)) - return -ENOMEM; - - return 0; -} - -static int bus_unit_append_following(DBusMessageIter *i, const char *property, void *data) { - Unit *u = data, *f; - const char *d; - - assert(i); - assert(property); - assert(u); - - f = unit_following(u); - d = f ? f->id : ""; - - if (!dbus_message_iter_append_basic(i, DBUS_TYPE_STRING, &d)) - return -ENOMEM; - - return 0; -} - -static int bus_unit_append_dependencies(DBusMessageIter *i, const char *property, void *data) { - Unit *u; - Iterator j; - DBusMessageIter sub; - Set *s = data; - - if (!dbus_message_iter_open_container(i, DBUS_TYPE_ARRAY, "s", &sub)) - return -ENOMEM; - - SET_FOREACH(u, s, j) - if (!dbus_message_iter_append_basic(&sub, DBUS_TYPE_STRING, &u->id)) - return -ENOMEM; - - if (!dbus_message_iter_close_container(i, &sub)) - return -ENOMEM; - - return 0; -} - -static int bus_unit_append_description(DBusMessageIter *i, const char *property, void *data) { - Unit *u = data; - const char *d; - - assert(i); - assert(property); - assert(u); - - d = unit_description(u); - - if (!dbus_message_iter_append_basic(i, DBUS_TYPE_STRING, &d)) - return -ENOMEM; - - return 0; -} - -static DEFINE_BUS_PROPERTY_APPEND_ENUM(bus_unit_append_load_state, unit_load_state, UnitLoadState); - -static int bus_unit_append_active_state(DBusMessageIter *i, const char *property, void *data) { - Unit *u = data; - const char *state; - - assert(i); - assert(property); - assert(u); - - state = unit_active_state_to_string(unit_active_state(u)); - - if (!dbus_message_iter_append_basic(i, DBUS_TYPE_STRING, &state)) - return -ENOMEM; - - return 0; -} - -static int bus_unit_append_sub_state(DBusMessageIter *i, const char *property, void *data) { - Unit *u = data; - const char *state; - - assert(i); - assert(property); - assert(u); - - state = unit_sub_state_to_string(u); - - if (!dbus_message_iter_append_basic(i, DBUS_TYPE_STRING, &state)) - return -ENOMEM; - - return 0; -} - -static int bus_unit_append_file_state(DBusMessageIter *i, const char *property, void *data) { - Unit *u = data; - const char *state; - - assert(i); - assert(property); - assert(u); - - state = strempty(unit_file_state_to_string(unit_get_unit_file_state(u))); - - if (!dbus_message_iter_append_basic(i, DBUS_TYPE_STRING, &state)) - return -ENOMEM; - - return 0; -} - -static int bus_unit_append_can_start(DBusMessageIter *i, const char *property, void *data) { - Unit *u = data; - dbus_bool_t b; - - assert(i); - assert(property); - assert(u); - - b = unit_can_start(u) && - !u->refuse_manual_start; - - if (!dbus_message_iter_append_basic(i, DBUS_TYPE_BOOLEAN, &b)) - return -ENOMEM; - - return 0; -} - -static int bus_unit_append_can_stop(DBusMessageIter *i, const char *property, void *data) { - Unit *u = data; - dbus_bool_t b; - - assert(i); - assert(property); - assert(u); - - /* On the lower levels we assume that every unit we can start - * we can also stop */ - - b = unit_can_start(u) && - !u->refuse_manual_stop; - - if (!dbus_message_iter_append_basic(i, DBUS_TYPE_BOOLEAN, &b)) - return -ENOMEM; - - return 0; -} - -static int bus_unit_append_can_reload(DBusMessageIter *i, const char *property, void *data) { - Unit *u = data; - dbus_bool_t b; - - assert(i); - assert(property); - assert(u); - - b = unit_can_reload(u); - - if (!dbus_message_iter_append_basic(i, DBUS_TYPE_BOOLEAN, &b)) - return -ENOMEM; - - return 0; -} - -static int bus_unit_append_can_isolate(DBusMessageIter *i, const char *property, void *data) { - Unit *u = data; - dbus_bool_t b; - - assert(i); - assert(property); - assert(u); - - b = unit_can_isolate(u) && - !u->refuse_manual_start; - - if (!dbus_message_iter_append_basic(i, DBUS_TYPE_BOOLEAN, &b)) - return -ENOMEM; - - return 0; -} - -static int bus_unit_append_job(DBusMessageIter *i, const char *property, void *data) { - Unit *u = data; - DBusMessageIter sub; - char *p; - - assert(i); - assert(property); - assert(u); - - if (!dbus_message_iter_open_container(i, DBUS_TYPE_STRUCT, NULL, &sub)) - return -ENOMEM; - - if (u->job) { - - if (!(p = job_dbus_path(u->job))) - return -ENOMEM; - - if (!dbus_message_iter_append_basic(&sub, DBUS_TYPE_UINT32, &u->job->id) || - !dbus_message_iter_append_basic(&sub, DBUS_TYPE_OBJECT_PATH, &p)) { - free(p); - return -ENOMEM; - } - } else { - uint32_t id = 0; - - /* No job, so let's fill in some placeholder - * data. Since we need to fill in a valid path we - * simple point to ourselves. */ - - if (!(p = unit_dbus_path(u))) - return -ENOMEM; - - if (!dbus_message_iter_append_basic(&sub, DBUS_TYPE_UINT32, &id) || - !dbus_message_iter_append_basic(&sub, DBUS_TYPE_OBJECT_PATH, &p)) { - free(p); - return -ENOMEM; - } - } - - free(p); - - if (!dbus_message_iter_close_container(i, &sub)) - return -ENOMEM; - - return 0; -} - -static int bus_unit_append_default_cgroup(DBusMessageIter *i, const char *property, void *data) { - Unit *u = data; - char *t; - CGroupBonding *cgb; - bool success; - - assert(i); - assert(property); - assert(u); - - if ((cgb = unit_get_default_cgroup(u))) { - if (!(t = cgroup_bonding_to_string(cgb))) - return -ENOMEM; - } else - t = (char*) ""; - - success = dbus_message_iter_append_basic(i, DBUS_TYPE_STRING, &t); - - if (cgb) - free(t); - - return success ? 0 : -ENOMEM; -} - -static int bus_unit_append_cgroups(DBusMessageIter *i, const char *property, void *data) { - Unit *u = data; - CGroupBonding *cgb; - DBusMessageIter sub; - - if (!dbus_message_iter_open_container(i, DBUS_TYPE_ARRAY, "s", &sub)) - return -ENOMEM; - - LIST_FOREACH(by_unit, cgb, u->cgroup_bondings) { - char *t; - bool success; - - if (!(t = cgroup_bonding_to_string(cgb))) - return -ENOMEM; - - success = dbus_message_iter_append_basic(&sub, DBUS_TYPE_STRING, &t); - free(t); - - if (!success) - return -ENOMEM; - } - - if (!dbus_message_iter_close_container(i, &sub)) - return -ENOMEM; - - return 0; -} - -static int bus_unit_append_cgroup_attrs(DBusMessageIter *i, const char *property, void *data) { - Unit *u = data; - CGroupAttribute *a; - DBusMessageIter sub, sub2; - - if (!dbus_message_iter_open_container(i, DBUS_TYPE_ARRAY, "(sss)", &sub)) - return -ENOMEM; - - LIST_FOREACH(by_unit, a, u->cgroup_attributes) { - char *v = NULL; - bool success; - - if (a->map_callback) - a->map_callback(a->controller, a->name, a->value, &v); - - success = - dbus_message_iter_open_container(&sub, DBUS_TYPE_STRUCT, NULL, &sub2) && - dbus_message_iter_append_basic(&sub2, DBUS_TYPE_STRING, &a->controller) && - dbus_message_iter_append_basic(&sub2, DBUS_TYPE_STRING, &a->name) && - dbus_message_iter_append_basic(&sub2, DBUS_TYPE_STRING, v ? &v : &a->value) && - dbus_message_iter_close_container(&sub, &sub2); - - free(v); - - if (!success) - return -ENOMEM; - } - - if (!dbus_message_iter_close_container(i, &sub)) - return -ENOMEM; - - return 0; -} - -static int bus_unit_append_need_daemon_reload(DBusMessageIter *i, const char *property, void *data) { - Unit *u = data; - dbus_bool_t b; - - assert(i); - assert(property); - assert(u); - - b = unit_need_daemon_reload(u); - - if (!dbus_message_iter_append_basic(i, DBUS_TYPE_BOOLEAN, &b)) - return -ENOMEM; - - return 0; -} - -static int bus_unit_append_load_error(DBusMessageIter *i, const char *property, void *data) { - Unit *u = data; - const char *name, *message; - DBusMessageIter sub; - - assert(i); - assert(property); - assert(u); - - if (u->load_error != 0) { - name = bus_errno_to_dbus(u->load_error); - message = strempty(strerror(-u->load_error)); - } else - name = message = ""; - - if (!dbus_message_iter_open_container(i, DBUS_TYPE_STRUCT, NULL, &sub) || - !dbus_message_iter_append_basic(&sub, DBUS_TYPE_STRING, &name) || - !dbus_message_iter_append_basic(&sub, DBUS_TYPE_STRING, &message) || - !dbus_message_iter_close_container(i, &sub)) - return -ENOMEM; - - return 0; -} - -static DBusHandlerResult bus_unit_message_dispatch(Unit *u, DBusConnection *connection, DBusMessage *message) { - DBusMessage *reply = NULL; - Manager *m = u->manager; - DBusError error; - JobType job_type = _JOB_TYPE_INVALID; - char *path = NULL; - bool reload_if_possible = false; - - dbus_error_init(&error); - - if (dbus_message_is_method_call(message, "org.freedesktop.systemd1.Unit", "Start")) - job_type = JOB_START; - else if (dbus_message_is_method_call(message, "org.freedesktop.systemd1.Unit", "Stop")) - job_type = JOB_STOP; - else if (dbus_message_is_method_call(message, "org.freedesktop.systemd1.Unit", "Reload")) - job_type = JOB_RELOAD; - else if (dbus_message_is_method_call(message, "org.freedesktop.systemd1.Unit", "Restart")) - job_type = JOB_RESTART; - else if (dbus_message_is_method_call(message, "org.freedesktop.systemd1.Unit", "TryRestart")) - job_type = JOB_TRY_RESTART; - else if (dbus_message_is_method_call(message, "org.freedesktop.systemd1.Unit", "ReloadOrRestart")) { - reload_if_possible = true; - job_type = JOB_RESTART; - } else if (dbus_message_is_method_call(message, "org.freedesktop.systemd1.Unit", "ReloadOrTryRestart")) { - reload_if_possible = true; - job_type = JOB_TRY_RESTART; - } else if (dbus_message_is_method_call(message, "org.freedesktop.systemd1.Unit", "Kill")) { - const char *swho, *smode; - int32_t signo; - KillMode mode; - KillWho who; - int r; - - if (!dbus_message_get_args( - message, - &error, - DBUS_TYPE_STRING, &swho, - DBUS_TYPE_STRING, &smode, - DBUS_TYPE_INT32, &signo, - DBUS_TYPE_INVALID)) - return bus_send_error_reply(connection, message, &error, -EINVAL); - - if (isempty(swho)) - who = KILL_ALL; - else { - who = kill_who_from_string(swho); - if (who < 0) - return bus_send_error_reply(connection, message, &error, -EINVAL); - } - - if (isempty(smode)) - mode = KILL_CONTROL_GROUP; - else { - mode = kill_mode_from_string(smode); - if (mode < 0) - return bus_send_error_reply(connection, message, &error, -EINVAL); - } - - if (signo <= 0 || signo >= _NSIG) - return bus_send_error_reply(connection, message, &error, -EINVAL); - - if ((r = unit_kill(u, who, mode, signo, &error)) < 0) - return bus_send_error_reply(connection, message, &error, r); - - if (!(reply = dbus_message_new_method_return(message))) - goto oom; - - } else if (dbus_message_is_method_call(message, "org.freedesktop.systemd1.Unit", "ResetFailed")) { - - unit_reset_failed(u); - - if (!(reply = dbus_message_new_method_return(message))) - goto oom; - - } else if (UNIT_VTABLE(u)->bus_message_handler) - return UNIT_VTABLE(u)->bus_message_handler(u, connection, message); - else - return DBUS_HANDLER_RESULT_NOT_YET_HANDLED; - - if (job_type != _JOB_TYPE_INVALID) { - const char *smode; - JobMode mode; - Job *j; - int r; - - if ((job_type == JOB_START && u->refuse_manual_start) || - (job_type == JOB_STOP && u->refuse_manual_stop) || - ((job_type == JOB_RESTART || job_type == JOB_TRY_RESTART) && - (u->refuse_manual_start || u->refuse_manual_stop))) { - dbus_set_error(&error, BUS_ERROR_ONLY_BY_DEPENDENCY, "Operation refused, may be requested by dependency only."); - return bus_send_error_reply(connection, message, &error, -EPERM); - } - - if (!dbus_message_get_args( - message, - &error, - DBUS_TYPE_STRING, &smode, - DBUS_TYPE_INVALID)) - return bus_send_error_reply(connection, message, &error, -EINVAL); - - if (reload_if_possible && unit_can_reload(u)) { - if (job_type == JOB_RESTART) - job_type = JOB_RELOAD_OR_START; - else if (job_type == JOB_TRY_RESTART) - job_type = JOB_RELOAD; - } - - if ((mode = job_mode_from_string(smode)) == _JOB_MODE_INVALID) { - dbus_set_error(&error, BUS_ERROR_INVALID_JOB_MODE, "Job mode %s is invalid.", smode); - return bus_send_error_reply(connection, message, &error, -EINVAL); - } - - if ((r = manager_add_job(m, job_type, u, mode, true, &error, &j)) < 0) - return bus_send_error_reply(connection, message, &error, r); - - if (!(reply = dbus_message_new_method_return(message))) - goto oom; - - if (!(path = job_dbus_path(j))) - goto oom; - - if (!dbus_message_append_args( - reply, - DBUS_TYPE_OBJECT_PATH, &path, - DBUS_TYPE_INVALID)) - goto oom; - } - - if (reply) { - if (!dbus_connection_send(connection, reply, NULL)) - goto oom; - - dbus_message_unref(reply); - } - - free(path); - - return DBUS_HANDLER_RESULT_HANDLED; - -oom: - free(path); - - if (reply) - dbus_message_unref(reply); - - dbus_error_free(&error); - - return DBUS_HANDLER_RESULT_NEED_MEMORY; -} - -static DBusHandlerResult bus_unit_message_handler(DBusConnection *connection, DBusMessage *message, void *data) { - Manager *m = data; - Unit *u; - int r; - DBusMessage *reply; - - assert(connection); - assert(message); - assert(m); - - if (streq(dbus_message_get_path(message), "/org/freedesktop/systemd1/unit")) { - /* Be nice to gdbus and return introspection data for our mid-level paths */ - - if (dbus_message_is_method_call(message, "org.freedesktop.DBus.Introspectable", "Introspect")) { - char *introspection = NULL; - FILE *f; - Iterator i; - const char *k; - size_t size; - - if (!(reply = dbus_message_new_method_return(message))) - goto oom; - - /* We roll our own introspection code here, instead of - * relying on bus_default_message_handler() because we - * need to generate our introspection string - * dynamically. */ - - if (!(f = open_memstream(&introspection, &size))) - goto oom; - - fputs(DBUS_INTROSPECT_1_0_XML_DOCTYPE_DECL_NODE - "\n", f); - - fputs(BUS_INTROSPECTABLE_INTERFACE, f); - fputs(BUS_PEER_INTERFACE, f); - - HASHMAP_FOREACH_KEY(u, k, m->units, i) { - char *p; - - if (k != u->id) - continue; - - if (!(p = bus_path_escape(k))) { - fclose(f); - free(introspection); - goto oom; - } - - fprintf(f, "", p); - free(p); - } - - fputs("\n", f); - - if (ferror(f)) { - fclose(f); - free(introspection); - goto oom; - } - - fclose(f); - - if (!introspection) - goto oom; - - if (!dbus_message_append_args(reply, DBUS_TYPE_STRING, &introspection, DBUS_TYPE_INVALID)) { - free(introspection); - goto oom; - } - - free(introspection); - - if (!dbus_connection_send(connection, reply, NULL)) - goto oom; - - dbus_message_unref(reply); - - return DBUS_HANDLER_RESULT_HANDLED; - } - - return DBUS_HANDLER_RESULT_NOT_YET_HANDLED; - } - - if ((r = manager_get_unit_from_dbus_path(m, dbus_message_get_path(message), &u)) < 0) { - - if (r == -ENOMEM) - return DBUS_HANDLER_RESULT_NEED_MEMORY; - - if (r == -ENOENT) { - DBusError e; - - dbus_error_init(&e); - dbus_set_error_const(&e, DBUS_ERROR_UNKNOWN_OBJECT, "Unknown unit"); - return bus_send_error_reply(connection, message, &e, r); - } - - return bus_send_error_reply(connection, message, NULL, r); - } - - return bus_unit_message_dispatch(u, connection, message); - -oom: - if (reply) - dbus_message_unref(reply); - - return DBUS_HANDLER_RESULT_NEED_MEMORY; -} - -const DBusObjectPathVTable bus_unit_vtable = { - .message_function = bus_unit_message_handler -}; - -void bus_unit_send_change_signal(Unit *u) { - char *p = NULL; - DBusMessage *m = NULL; - - assert(u); - - if (u->in_dbus_queue) { - LIST_REMOVE(Unit, dbus_queue, u->manager->dbus_unit_queue, u); - u->in_dbus_queue = false; - } - - if (!u->id) - return; - - if (!bus_has_subscriber(u->manager)) { - u->sent_dbus_new_signal = true; - return; - } - - if (!(p = unit_dbus_path(u))) - goto oom; - - if (u->sent_dbus_new_signal) { - /* 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 - * behaviour if needed. */ - - if (UNIT_VTABLE(u)->bus_invalidating_properties) { - - if (!(m = bus_properties_changed_new(p, - UNIT_VTABLE(u)->bus_interface, - UNIT_VTABLE(u)->bus_invalidating_properties))) - goto oom; - - if (bus_broadcast(u->manager, m) < 0) - goto oom; - - dbus_message_unref(m); - } - - if (!(m = bus_properties_changed_new(p, "org.freedesktop.systemd1.Unit", INVALIDATING_PROPERTIES))) - goto oom; - - } else { - /* Send a new signal */ - - if (!(m = dbus_message_new_signal("/org/freedesktop/systemd1", "org.freedesktop.systemd1.Manager", "UnitNew"))) - goto oom; - - if (!dbus_message_append_args(m, - DBUS_TYPE_STRING, &u->id, - DBUS_TYPE_OBJECT_PATH, &p, - DBUS_TYPE_INVALID)) - goto oom; - } - - if (bus_broadcast(u->manager, m) < 0) - goto oom; - - free(p); - dbus_message_unref(m); - - u->sent_dbus_new_signal = true; - - return; - -oom: - free(p); - - if (m) - dbus_message_unref(m); - - log_error("Failed to allocate unit change/new signal."); -} - -void bus_unit_send_removed_signal(Unit *u) { - char *p = NULL; - DBusMessage *m = NULL; - - assert(u); - - if (!bus_has_subscriber(u->manager)) - return; - - if (!u->sent_dbus_new_signal) - bus_unit_send_change_signal(u); - - if (!u->id) - return; - - if (!(p = unit_dbus_path(u))) - goto oom; - - if (!(m = dbus_message_new_signal("/org/freedesktop/systemd1", "org.freedesktop.systemd1.Manager", "UnitRemoved"))) - goto oom; - - if (!dbus_message_append_args(m, - DBUS_TYPE_STRING, &u->id, - DBUS_TYPE_OBJECT_PATH, &p, - DBUS_TYPE_INVALID)) - goto oom; - - if (bus_broadcast(u->manager, m) < 0) - goto oom; - - free(p); - dbus_message_unref(m); - - return; - -oom: - free(p); - - if (m) - dbus_message_unref(m); - - log_error("Failed to allocate unit remove signal."); -} - -const BusProperty bus_unit_properties[] = { - { "Id", bus_property_append_string, "s", offsetof(Unit, id), true }, - { "Names", bus_unit_append_names, "as", 0 }, - { "Following", bus_unit_append_following, "s", 0 }, - { "Requires", bus_unit_append_dependencies, "as", offsetof(Unit, dependencies[UNIT_REQUIRES]), true }, - { "RequiresOverridable", bus_unit_append_dependencies, "as", offsetof(Unit, dependencies[UNIT_REQUIRES_OVERRIDABLE]), true }, - { "Requisite", bus_unit_append_dependencies, "as", offsetof(Unit, dependencies[UNIT_REQUISITE]), true }, - { "RequisiteOverridable", bus_unit_append_dependencies, "as", offsetof(Unit, dependencies[UNIT_REQUISITE_OVERRIDABLE]), true }, - { "Wants", bus_unit_append_dependencies, "as", offsetof(Unit, dependencies[UNIT_WANTS]), true }, - { "BindTo", bus_unit_append_dependencies, "as", offsetof(Unit, dependencies[UNIT_BIND_TO]), true }, - { "RequiredBy", bus_unit_append_dependencies, "as", offsetof(Unit, dependencies[UNIT_REQUIRED_BY]), true }, - { "RequiredByOverridable",bus_unit_append_dependencies, "as", offsetof(Unit, dependencies[UNIT_REQUIRED_BY_OVERRIDABLE]), true }, - { "WantedBy", bus_unit_append_dependencies, "as", offsetof(Unit, dependencies[UNIT_WANTED_BY]), true }, - { "BoundBy", bus_unit_append_dependencies, "as", offsetof(Unit, dependencies[UNIT_BOUND_BY]), true }, - { "Conflicts", bus_unit_append_dependencies, "as", offsetof(Unit, dependencies[UNIT_CONFLICTS]), true }, - { "ConflictedBy", bus_unit_append_dependencies, "as", offsetof(Unit, dependencies[UNIT_CONFLICTED_BY]), true }, - { "Before", bus_unit_append_dependencies, "as", offsetof(Unit, dependencies[UNIT_BEFORE]), true }, - { "After", bus_unit_append_dependencies, "as", offsetof(Unit, dependencies[UNIT_AFTER]), true }, - { "OnFailure", bus_unit_append_dependencies, "as", offsetof(Unit, dependencies[UNIT_ON_FAILURE]), true }, - { "Triggers", bus_unit_append_dependencies, "as", offsetof(Unit, dependencies[UNIT_TRIGGERS]), true }, - { "TriggeredBy", bus_unit_append_dependencies, "as", offsetof(Unit, dependencies[UNIT_TRIGGERED_BY]), true }, - { "PropagateReloadTo", bus_unit_append_dependencies, "as", offsetof(Unit, dependencies[UNIT_PROPAGATE_RELOAD_TO]), true }, - { "PropagateReloadFrom", bus_unit_append_dependencies, "as", offsetof(Unit, dependencies[UNIT_PROPAGATE_RELOAD_FROM]), true }, - { "Description", bus_unit_append_description, "s", 0 }, - { "LoadState", bus_unit_append_load_state, "s", offsetof(Unit, load_state) }, - { "ActiveState", bus_unit_append_active_state, "s", 0 }, - { "SubState", bus_unit_append_sub_state, "s", 0 }, - { "FragmentPath", bus_property_append_string, "s", offsetof(Unit, fragment_path), true }, - { "UnitFileState", bus_unit_append_file_state, "s", 0 }, - { "InactiveExitTimestamp",bus_property_append_usec, "t", offsetof(Unit, inactive_exit_timestamp.realtime) }, - { "InactiveExitTimestampMonotonic", bus_property_append_usec, "t", offsetof(Unit, inactive_exit_timestamp.monotonic) }, - { "ActiveEnterTimestamp", bus_property_append_usec, "t", offsetof(Unit, active_enter_timestamp.realtime) }, - { "ActiveEnterTimestampMonotonic", bus_property_append_usec, "t", offsetof(Unit, active_enter_timestamp.monotonic) }, - { "ActiveExitTimestamp", bus_property_append_usec, "t", offsetof(Unit, active_exit_timestamp.realtime) }, - { "ActiveExitTimestampMonotonic", bus_property_append_usec, "t", offsetof(Unit, active_exit_timestamp.monotonic) }, - { "InactiveEnterTimestamp", bus_property_append_usec, "t", offsetof(Unit, inactive_enter_timestamp.realtime) }, - { "InactiveEnterTimestampMonotonic",bus_property_append_usec, "t", offsetof(Unit, inactive_enter_timestamp.monotonic) }, - { "CanStart", bus_unit_append_can_start, "b", 0 }, - { "CanStop", bus_unit_append_can_stop, "b", 0 }, - { "CanReload", bus_unit_append_can_reload, "b", 0 }, - { "CanIsolate", bus_unit_append_can_isolate, "b", 0 }, - { "Job", bus_unit_append_job, "(uo)", 0 }, - { "StopWhenUnneeded", bus_property_append_bool, "b", offsetof(Unit, stop_when_unneeded) }, - { "RefuseManualStart", bus_property_append_bool, "b", offsetof(Unit, refuse_manual_start) }, - { "RefuseManualStop", bus_property_append_bool, "b", offsetof(Unit, refuse_manual_stop) }, - { "AllowIsolate", bus_property_append_bool, "b", offsetof(Unit, allow_isolate) }, - { "DefaultDependencies", bus_property_append_bool, "b", offsetof(Unit, default_dependencies) }, - { "OnFailureIsolate", bus_property_append_bool, "b", offsetof(Unit, on_failure_isolate) }, - { "IgnoreOnIsolate", bus_property_append_bool, "b", offsetof(Unit, ignore_on_isolate) }, - { "IgnoreOnSnapshot", bus_property_append_bool, "b", offsetof(Unit, ignore_on_snapshot) }, - { "DefaultControlGroup", bus_unit_append_default_cgroup, "s", 0 }, - { "ControlGroup", bus_unit_append_cgroups, "as", 0 }, - { "ControlGroupAttributes", bus_unit_append_cgroup_attrs,"a(sss)", 0 }, - { "NeedDaemonReload", bus_unit_append_need_daemon_reload, "b", 0 }, - { "JobTimeoutUSec", bus_property_append_usec, "t", offsetof(Unit, job_timeout) }, - { "ConditionTimestamp", bus_property_append_usec, "t", offsetof(Unit, condition_timestamp.realtime) }, - { "ConditionTimestampMonotonic", bus_property_append_usec, "t", offsetof(Unit, condition_timestamp.monotonic) }, - { "ConditionResult", bus_property_append_bool, "b", offsetof(Unit, condition_result) }, - { "LoadError", bus_unit_append_load_error, "(ss)", 0 }, - { NULL, } -}; diff --git a/src/dbus-unit.h b/src/dbus-unit.h deleted file mode 100644 index 4f19a808bf..0000000000 --- a/src/dbus-unit.h +++ /dev/null @@ -1,139 +0,0 @@ -/*-*- Mode: C; c-basic-offset: 8; indent-tabs-mode: nil -*-*/ - -#ifndef foodbusunithfoo -#define foodbusunithfoo - -/*** - This file is part of systemd. - - Copyright 2010 Lennart Poettering - - systemd is free software; you can redistribute it and/or modify it - under the terms of the GNU General Public License as published by - the Free Software Foundation; either version 2 of the License, or - (at your option) any later version. - - systemd is distributed in the hope that 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 General Public License - along with systemd; If not, see . -***/ - -#include - -#include "manager.h" -#include "dbus-common.h" - -#define BUS_UNIT_INTERFACE \ - " \n" \ - " \n" \ - " \n" \ - " \n" \ - " \n" \ - " \n" \ - " \n" \ - " \n" \ - " \n" \ - " \n" \ - " \n" \ - " \n" \ - " \n" \ - " \n" \ - " \n" \ - " \n" \ - " \n" \ - " \n" \ - " \n" \ - " \n" \ - " \n" \ - " \n" \ - " \n" \ - " \n" \ - " \n" \ - " \n" \ - " \n" \ - " \n" \ - " \n" \ - " \n" \ - " \n" \ - " \n" \ - " \n" \ - " \n" \ - " \n" \ - " \n" \ - " \n" \ - " \n" \ - " \n" \ - " \n" \ - " \n" \ - " \n" \ - " \n" \ - " \n" \ - " \n" \ - " \n" \ - " \n" \ - " \n" \ - " \n" \ - " \n" \ - " \n" \ - " \n" \ - " \n" \ - " \n" \ - " \n" \ - " \n" \ - " \n" \ - " \n" \ - " \n" \ - " \n" \ - " \n" \ - " \n" \ - " \n" \ - " \n" \ - " \n" \ - " \n" \ - " \n" \ - " \n" \ - " \n" \ - " \n" \ - " \n" \ - " \n" \ - " \n" \ - " \n" \ - " \n" \ - " \n" \ - " \n" \ - " \n" \ - " \n" \ - " \n" \ - " \n" \ - " \n" \ - " \n" \ - " \n" \ - " \n" \ - " \n" \ - " \n" \ - " \n" \ - " \n" \ - " \n" \ - " \n" \ - " \n" \ - " \n" \ - " \n" - -#define BUS_UNIT_INTERFACES_LIST \ - BUS_GENERIC_INTERFACES_LIST \ - "org.freedesktop.systemd1.Unit\0" - -extern const BusProperty bus_unit_properties[]; - -void bus_unit_send_change_signal(Unit *u); -void bus_unit_send_removed_signal(Unit *u); - -extern const DBusObjectPathVTable bus_unit_vtable; - -extern const char bus_unit_interface[]; - -#endif diff --git a/src/dbus.c b/src/dbus.c deleted file mode 100644 index ddf91f225a..0000000000 --- a/src/dbus.c +++ /dev/null @@ -1,1483 +0,0 @@ -/*-*- Mode: C; c-basic-offset: 8; indent-tabs-mode: nil -*-*/ - -/*** - This file is part of systemd. - - Copyright 2010 Lennart Poettering - - systemd is free software; you can redistribute it and/or modify it - under the terms of the GNU General Public License as published by - the Free Software Foundation; either version 2 of the License, or - (at your option) any later version. - - systemd is distributed in the hope that 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 General Public License - along with systemd; If not, see . -***/ - -#include -#include -#include -#include -#include - -#include "dbus.h" -#include "log.h" -#include "strv.h" -#include "cgroup.h" -#include "mkdir.h" -#include "dbus-unit.h" -#include "dbus-job.h" -#include "dbus-manager.h" -#include "dbus-service.h" -#include "dbus-socket.h" -#include "dbus-target.h" -#include "dbus-device.h" -#include "dbus-mount.h" -#include "dbus-automount.h" -#include "dbus-snapshot.h" -#include "dbus-swap.h" -#include "dbus-timer.h" -#include "dbus-path.h" -#include "bus-errors.h" -#include "special.h" -#include "dbus-common.h" - -#define CONNECTIONS_MAX 52 - -/* Well-known address (http://dbus.freedesktop.org/doc/dbus-specification.html#message-bus-types) */ -#define DBUS_SYSTEM_BUS_DEFAULT_ADDRESS "unix:path=/var/run/dbus/system_bus_socket" -/* Only used as a fallback */ -#define DBUS_SESSION_BUS_DEFAULT_ADDRESS "autolaunch:" - -static const char bus_properties_interface[] = BUS_PROPERTIES_INTERFACE; -static const char bus_introspectable_interface[] = BUS_INTROSPECTABLE_INTERFACE; - -const char *const bus_interface_table[] = { - "org.freedesktop.DBus.Properties", bus_properties_interface, - "org.freedesktop.DBus.Introspectable", bus_introspectable_interface, - "org.freedesktop.systemd1.Manager", bus_manager_interface, - "org.freedesktop.systemd1.Job", bus_job_interface, - "org.freedesktop.systemd1.Unit", bus_unit_interface, - "org.freedesktop.systemd1.Service", bus_service_interface, - "org.freedesktop.systemd1.Socket", bus_socket_interface, - "org.freedesktop.systemd1.Target", bus_target_interface, - "org.freedesktop.systemd1.Device", bus_device_interface, - "org.freedesktop.systemd1.Mount", bus_mount_interface, - "org.freedesktop.systemd1.Automount", bus_automount_interface, - "org.freedesktop.systemd1.Snapshot", bus_snapshot_interface, - "org.freedesktop.systemd1.Swap", bus_swap_interface, - "org.freedesktop.systemd1.Timer", bus_timer_interface, - "org.freedesktop.systemd1.Path", bus_path_interface, - NULL -}; - -static void bus_done_api(Manager *m); -static void bus_done_system(Manager *m); -static void bus_done_private(Manager *m); -static void shutdown_connection(Manager *m, DBusConnection *c); - -static void bus_dispatch_status(DBusConnection *bus, DBusDispatchStatus status, void *data) { - Manager *m = data; - - assert(bus); - assert(m); - - /* We maintain two sets, one for those connections where we - * requested a dispatch, and another where we didn't. And then, - * we move the connections between the two sets. */ - - if (status == DBUS_DISPATCH_COMPLETE) - set_move_one(m->bus_connections, m->bus_connections_for_dispatch, bus); - else - set_move_one(m->bus_connections_for_dispatch, m->bus_connections, bus); -} - -void bus_watch_event(Manager *m, Watch *w, int events) { - assert(m); - assert(w); - - /* This is called by the event loop whenever there is - * something happening on D-Bus' file handles. */ - - if (!dbus_watch_get_enabled(w->data.bus_watch)) - return; - - dbus_watch_handle(w->data.bus_watch, bus_events_to_flags(events)); -} - -static dbus_bool_t bus_add_watch(DBusWatch *bus_watch, void *data) { - Manager *m = data; - Watch *w; - struct epoll_event ev; - - assert(bus_watch); - assert(m); - - if (!(w = new0(Watch, 1))) - return FALSE; - - w->fd = dbus_watch_get_unix_fd(bus_watch); - w->type = WATCH_DBUS_WATCH; - w->data.bus_watch = bus_watch; - - zero(ev); - ev.events = bus_flags_to_events(bus_watch); - ev.data.ptr = w; - - if (epoll_ctl(m->epoll_fd, EPOLL_CTL_ADD, w->fd, &ev) < 0) { - - if (errno != EEXIST) { - free(w); - return FALSE; - } - - /* Hmm, bloody D-Bus creates multiple watches on the - * same fd. epoll() does not like that. As a dirty - * hack we simply dup() the fd and hence get a second - * one we can safely add to the epoll(). */ - - if ((w->fd = dup(w->fd)) < 0) { - free(w); - return FALSE; - } - - if (epoll_ctl(m->epoll_fd, EPOLL_CTL_ADD, w->fd, &ev) < 0) { - close_nointr_nofail(w->fd); - free(w); - return FALSE; - } - - w->fd_is_dupped = true; - } - - dbus_watch_set_data(bus_watch, w, NULL); - - return TRUE; -} - -static void bus_remove_watch(DBusWatch *bus_watch, void *data) { - Manager *m = data; - Watch *w; - - assert(bus_watch); - assert(m); - - w = dbus_watch_get_data(bus_watch); - if (!w) - return; - - assert(w->type == WATCH_DBUS_WATCH); - assert_se(epoll_ctl(m->epoll_fd, EPOLL_CTL_DEL, w->fd, NULL) >= 0); - - if (w->fd_is_dupped) - close_nointr_nofail(w->fd); - - free(w); -} - -static void bus_toggle_watch(DBusWatch *bus_watch, void *data) { - Manager *m = data; - Watch *w; - struct epoll_event ev; - - assert(bus_watch); - assert(m); - - w = dbus_watch_get_data(bus_watch); - if (!w) - return; - - assert(w->type == WATCH_DBUS_WATCH); - - zero(ev); - ev.events = bus_flags_to_events(bus_watch); - ev.data.ptr = w; - - assert_se(epoll_ctl(m->epoll_fd, EPOLL_CTL_MOD, w->fd, &ev) == 0); -} - -static int bus_timeout_arm(Manager *m, Watch *w) { - struct itimerspec its; - - assert(m); - assert(w); - - zero(its); - - if (dbus_timeout_get_enabled(w->data.bus_timeout)) { - timespec_store(&its.it_value, dbus_timeout_get_interval(w->data.bus_timeout) * USEC_PER_MSEC); - its.it_interval = its.it_value; - } - - if (timerfd_settime(w->fd, 0, &its, NULL) < 0) - return -errno; - - return 0; -} - -void bus_timeout_event(Manager *m, Watch *w, int events) { - assert(m); - assert(w); - - /* This is called by the event loop whenever there is - * something happening on D-Bus' file handles. */ - - if (!(dbus_timeout_get_enabled(w->data.bus_timeout))) - return; - - dbus_timeout_handle(w->data.bus_timeout); -} - -static dbus_bool_t bus_add_timeout(DBusTimeout *timeout, void *data) { - Manager *m = data; - Watch *w; - struct epoll_event ev; - - assert(timeout); - assert(m); - - if (!(w = new0(Watch, 1))) - return FALSE; - - if ((w->fd = timerfd_create(CLOCK_MONOTONIC, TFD_NONBLOCK|TFD_CLOEXEC)) < 0) - goto fail; - - w->type = WATCH_DBUS_TIMEOUT; - w->data.bus_timeout = timeout; - - if (bus_timeout_arm(m, w) < 0) - goto fail; - - zero(ev); - ev.events = EPOLLIN; - ev.data.ptr = w; - - if (epoll_ctl(m->epoll_fd, EPOLL_CTL_ADD, w->fd, &ev) < 0) - goto fail; - - dbus_timeout_set_data(timeout, w, NULL); - - return TRUE; - -fail: - if (w->fd >= 0) - close_nointr_nofail(w->fd); - - free(w); - return FALSE; -} - -static void bus_remove_timeout(DBusTimeout *timeout, void *data) { - Manager *m = data; - Watch *w; - - assert(timeout); - assert(m); - - w = dbus_timeout_get_data(timeout); - if (!w) - return; - - assert(w->type == WATCH_DBUS_TIMEOUT); - - assert_se(epoll_ctl(m->epoll_fd, EPOLL_CTL_DEL, w->fd, NULL) >= 0); - close_nointr_nofail(w->fd); - free(w); -} - -static void bus_toggle_timeout(DBusTimeout *timeout, void *data) { - Manager *m = data; - Watch *w; - int r; - - assert(timeout); - assert(m); - - w = dbus_timeout_get_data(timeout); - if (!w) - return; - - assert(w->type == WATCH_DBUS_TIMEOUT); - - if ((r = bus_timeout_arm(m, w)) < 0) - log_error("Failed to rearm timer: %s", strerror(-r)); -} - -static DBusHandlerResult api_bus_message_filter(DBusConnection *connection, DBusMessage *message, void *data) { - Manager *m = data; - DBusError error; - DBusMessage *reply = NULL; - - assert(connection); - assert(message); - assert(m); - - dbus_error_init(&error); - - if (dbus_message_get_type(message) == DBUS_MESSAGE_TYPE_METHOD_CALL || - dbus_message_get_type(message) == DBUS_MESSAGE_TYPE_SIGNAL) - log_debug("Got D-Bus request: %s.%s() on %s", - dbus_message_get_interface(message), - dbus_message_get_member(message), - dbus_message_get_path(message)); - - if (dbus_message_is_signal(message, DBUS_INTERFACE_LOCAL, "Disconnected")) { - log_debug("API D-Bus connection terminated."); - bus_done_api(m); - - } else if (dbus_message_is_signal(message, DBUS_INTERFACE_DBUS, "NameOwnerChanged")) { - const char *name, *old_owner, *new_owner; - - if (!dbus_message_get_args(message, &error, - DBUS_TYPE_STRING, &name, - DBUS_TYPE_STRING, &old_owner, - DBUS_TYPE_STRING, &new_owner, - DBUS_TYPE_INVALID)) - log_error("Failed to parse NameOwnerChanged message: %s", bus_error_message(&error)); - else { - if (set_remove(BUS_CONNECTION_SUBSCRIBED(m, connection), (char*) name)) - log_debug("Subscription client vanished: %s (left: %u)", name, set_size(BUS_CONNECTION_SUBSCRIBED(m, connection))); - - if (old_owner[0] == 0) - old_owner = NULL; - - if (new_owner[0] == 0) - new_owner = NULL; - - manager_dispatch_bus_name_owner_changed(m, name, old_owner, new_owner); - } - } else if (dbus_message_is_signal(message, "org.freedesktop.systemd1.Activator", "ActivationRequest")) { - const char *name; - - if (!dbus_message_get_args(message, &error, - DBUS_TYPE_STRING, &name, - DBUS_TYPE_INVALID)) - log_error("Failed to parse ActivationRequest message: %s", bus_error_message(&error)); - else { - int r; - Unit *u; - - log_debug("Got D-Bus activation request for %s", name); - - if (manager_unit_pending_inactive(m, SPECIAL_DBUS_SERVICE) || - manager_unit_pending_inactive(m, SPECIAL_DBUS_SOCKET)) { - r = -EADDRNOTAVAIL; - dbus_set_error(&error, BUS_ERROR_SHUTTING_DOWN, "Refusing activation, D-Bus is shutting down."); - } else { - r = manager_load_unit(m, name, NULL, &error, &u); - - if (r >= 0 && u->refuse_manual_start) - r = -EPERM; - - if (r >= 0) - r = manager_add_job(m, JOB_START, u, JOB_REPLACE, true, &error, NULL); - } - - if (r < 0) { - const char *id, *text; - - log_debug("D-Bus activation failed for %s: %s", name, strerror(-r)); - - if (!(reply = dbus_message_new_signal("/org/freedesktop/systemd1", "org.freedesktop.systemd1.Activator", "ActivationFailure"))) - goto oom; - - id = error.name ? error.name : bus_errno_to_dbus(r); - text = bus_error(&error, r); - - if (!dbus_message_set_destination(reply, DBUS_SERVICE_DBUS) || - !dbus_message_append_args(reply, - DBUS_TYPE_STRING, &name, - DBUS_TYPE_STRING, &id, - DBUS_TYPE_STRING, &text, - DBUS_TYPE_INVALID)) - goto oom; - } - - /* On success we don't do anything, the service will be spawned now */ - } - } - - dbus_error_free(&error); - - if (reply) { - if (!dbus_connection_send(connection, reply, NULL)) - goto oom; - - dbus_message_unref(reply); - } - - return DBUS_HANDLER_RESULT_NOT_YET_HANDLED; - -oom: - if (reply) - dbus_message_unref(reply); - - dbus_error_free(&error); - - return DBUS_HANDLER_RESULT_NEED_MEMORY; -} - -static DBusHandlerResult system_bus_message_filter(DBusConnection *connection, DBusMessage *message, void *data) { - Manager *m = data; - DBusError error; - - assert(connection); - assert(message); - assert(m); - - dbus_error_init(&error); - - if (m->api_bus != m->system_bus && - (dbus_message_get_type(message) == DBUS_MESSAGE_TYPE_METHOD_CALL || - dbus_message_get_type(message) == DBUS_MESSAGE_TYPE_SIGNAL)) - log_debug("Got D-Bus request on system bus: %s.%s() on %s", - dbus_message_get_interface(message), - dbus_message_get_member(message), - dbus_message_get_path(message)); - - if (dbus_message_is_signal(message, DBUS_INTERFACE_LOCAL, "Disconnected")) { - log_debug("System D-Bus connection terminated."); - bus_done_system(m); - - } else if (m->running_as != MANAGER_SYSTEM && - dbus_message_is_signal(message, "org.freedesktop.systemd1.Agent", "Released")) { - - const char *cgroup; - - if (!dbus_message_get_args(message, &error, - DBUS_TYPE_STRING, &cgroup, - DBUS_TYPE_INVALID)) - log_error("Failed to parse Released message: %s", bus_error_message(&error)); - else - cgroup_notify_empty(m, cgroup); - } - - dbus_error_free(&error); - return DBUS_HANDLER_RESULT_NOT_YET_HANDLED; -} - -static DBusHandlerResult private_bus_message_filter(DBusConnection *connection, DBusMessage *message, void *data) { - Manager *m = data; - DBusError error; - - assert(connection); - assert(message); - assert(m); - - dbus_error_init(&error); - - if (dbus_message_get_type(message) == DBUS_MESSAGE_TYPE_METHOD_CALL || - dbus_message_get_type(message) == DBUS_MESSAGE_TYPE_SIGNAL) - log_debug("Got D-Bus request: %s.%s() on %s", - dbus_message_get_interface(message), - dbus_message_get_member(message), - dbus_message_get_path(message)); - - if (dbus_message_is_signal(message, DBUS_INTERFACE_LOCAL, "Disconnected")) - shutdown_connection(m, connection); - else if (m->running_as == MANAGER_SYSTEM && - dbus_message_is_signal(message, "org.freedesktop.systemd1.Agent", "Released")) { - - const char *cgroup; - - if (!dbus_message_get_args(message, &error, - DBUS_TYPE_STRING, &cgroup, - DBUS_TYPE_INVALID)) - log_error("Failed to parse Released message: %s", bus_error_message(&error)); - else - cgroup_notify_empty(m, cgroup); - - /* Forward the message to the system bus, so that user - * instances are notified as well */ - - if (m->system_bus) - dbus_connection_send(m->system_bus, message, NULL); - } - - dbus_error_free(&error); - - return DBUS_HANDLER_RESULT_NOT_YET_HANDLED; -} - -unsigned bus_dispatch(Manager *m) { - DBusConnection *c; - - assert(m); - - if (m->queued_message) { - /* 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. */ - - if (m->queued_message_connection) - if (!dbus_connection_send(m->queued_message_connection, m->queued_message, NULL)) - return 0; - - dbus_message_unref(m->queued_message); - m->queued_message = NULL; - m->queued_message_connection = NULL; - } - - if ((c = set_first(m->bus_connections_for_dispatch))) { - if (dbus_connection_dispatch(c) == DBUS_DISPATCH_COMPLETE) - set_move_one(m->bus_connections, m->bus_connections_for_dispatch, c); - - return 1; - } - - return 0; -} - -static void request_name_pending_cb(DBusPendingCall *pending, void *userdata) { - DBusMessage *reply; - DBusError error; - - dbus_error_init(&error); - - assert_se(reply = dbus_pending_call_steal_reply(pending)); - - switch (dbus_message_get_type(reply)) { - - case DBUS_MESSAGE_TYPE_ERROR: - - assert_se(dbus_set_error_from_message(&error, reply)); - log_warning("RequestName() failed: %s", bus_error_message(&error)); - break; - - case DBUS_MESSAGE_TYPE_METHOD_RETURN: { - uint32_t r; - - if (!dbus_message_get_args(reply, - &error, - DBUS_TYPE_UINT32, &r, - DBUS_TYPE_INVALID)) { - log_error("Failed to parse RequestName() reply: %s", bus_error_message(&error)); - break; - } - - if (r == 1) - log_debug("Successfully acquired name."); - else - log_error("Name already owned."); - - break; - } - - default: - assert_not_reached("Invalid reply message"); - } - - dbus_message_unref(reply); - dbus_error_free(&error); -} - -static int request_name(Manager *m) { - const char *name = "org.freedesktop.systemd1"; - /* 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 */ - uint32_t flags = DBUS_NAME_FLAG_ALLOW_REPLACEMENT|DBUS_NAME_FLAG_REPLACE_EXISTING; - DBusMessage *message = NULL; - DBusPendingCall *pending = NULL; - - if (!(message = dbus_message_new_method_call( - DBUS_SERVICE_DBUS, - DBUS_PATH_DBUS, - DBUS_INTERFACE_DBUS, - "RequestName"))) - goto oom; - - if (!dbus_message_append_args( - message, - DBUS_TYPE_STRING, &name, - DBUS_TYPE_UINT32, &flags, - DBUS_TYPE_INVALID)) - goto oom; - - if (!dbus_connection_send_with_reply(m->api_bus, message, &pending, -1)) - goto oom; - - if (!dbus_pending_call_set_notify(pending, request_name_pending_cb, m, NULL)) - goto oom; - - dbus_message_unref(message); - dbus_pending_call_unref(pending); - - /* We simple ask for the name and don't wait for it. Sooner or - * later we'll have it. */ - - return 0; - -oom: - if (pending) { - dbus_pending_call_cancel(pending); - dbus_pending_call_unref(pending); - } - - if (message) - dbus_message_unref(message); - - return -ENOMEM; -} - -static void query_name_list_pending_cb(DBusPendingCall *pending, void *userdata) { - DBusMessage *reply; - DBusError error; - Manager *m = userdata; - - assert(m); - - dbus_error_init(&error); - - assert_se(reply = dbus_pending_call_steal_reply(pending)); - - switch (dbus_message_get_type(reply)) { - - case DBUS_MESSAGE_TYPE_ERROR: - - assert_se(dbus_set_error_from_message(&error, reply)); - log_warning("ListNames() failed: %s", bus_error_message(&error)); - break; - - case DBUS_MESSAGE_TYPE_METHOD_RETURN: { - int r; - char **l; - - if ((r = bus_parse_strv(reply, &l)) < 0) - log_warning("Failed to parse ListNames() reply: %s", strerror(-r)); - else { - char **t; - - STRV_FOREACH(t, l) - /* This is a bit hacky, we say the - * owner of the name is the name - * itself, because we don't want the - * extra traffic to figure out the - * real owner. */ - manager_dispatch_bus_name_owner_changed(m, *t, NULL, *t); - - strv_free(l); - } - - break; - } - - default: - assert_not_reached("Invalid reply message"); - } - - dbus_message_unref(reply); - dbus_error_free(&error); -} - -static int query_name_list(Manager *m) { - DBusMessage *message = NULL; - DBusPendingCall *pending = NULL; - - /* Asks for the currently installed bus names */ - - if (!(message = dbus_message_new_method_call( - DBUS_SERVICE_DBUS, - DBUS_PATH_DBUS, - DBUS_INTERFACE_DBUS, - "ListNames"))) - goto oom; - - if (!dbus_connection_send_with_reply(m->api_bus, message, &pending, -1)) - goto oom; - - if (!dbus_pending_call_set_notify(pending, query_name_list_pending_cb, m, NULL)) - goto oom; - - dbus_message_unref(message); - dbus_pending_call_unref(pending); - - /* We simple ask for the list and don't wait for it. Sooner or - * later we'll get it. */ - - return 0; - -oom: - if (pending) { - dbus_pending_call_cancel(pending); - dbus_pending_call_unref(pending); - } - - if (message) - dbus_message_unref(message); - - return -ENOMEM; -} - -static int bus_setup_loop(Manager *m, DBusConnection *bus) { - assert(m); - assert(bus); - - dbus_connection_set_exit_on_disconnect(bus, FALSE); - - if (!dbus_connection_set_watch_functions(bus, bus_add_watch, bus_remove_watch, bus_toggle_watch, m, NULL) || - !dbus_connection_set_timeout_functions(bus, bus_add_timeout, bus_remove_timeout, bus_toggle_timeout, m, NULL)) { - log_error("Not enough memory"); - return -ENOMEM; - } - - if (set_put(m->bus_connections_for_dispatch, bus) < 0) { - log_error("Not enough memory"); - return -ENOMEM; - } - - dbus_connection_set_dispatch_status_function(bus, bus_dispatch_status, m, NULL); - return 0; -} - -static dbus_bool_t allow_only_same_user(DBusConnection *connection, unsigned long uid, void *data) { - return uid == 0 || uid == geteuid(); -} - -static void bus_new_connection( - DBusServer *server, - DBusConnection *new_connection, - void *data) { - - Manager *m = data; - - assert(m); - - if (set_size(m->bus_connections) >= CONNECTIONS_MAX) { - log_error("Too many concurrent connections."); - return; - } - - dbus_connection_set_unix_user_function(new_connection, allow_only_same_user, NULL, NULL); - - if (bus_setup_loop(m, new_connection) < 0) - return; - - if (!dbus_connection_register_object_path(new_connection, "/org/freedesktop/systemd1", &bus_manager_vtable, m) || - !dbus_connection_register_fallback(new_connection, "/org/freedesktop/systemd1/unit", &bus_unit_vtable, m) || - !dbus_connection_register_fallback(new_connection, "/org/freedesktop/systemd1/job", &bus_job_vtable, m) || - !dbus_connection_add_filter(new_connection, private_bus_message_filter, m, NULL)) { - log_error("Not enough memory."); - return; - } - - log_debug("Accepted connection on private bus."); - - dbus_connection_ref(new_connection); -} - -static int init_registered_system_bus(Manager *m) { - char *id; - - if (!dbus_connection_add_filter(m->system_bus, system_bus_message_filter, m, NULL)) { - log_error("Not enough memory"); - return -ENOMEM; - } - - if (m->running_as != MANAGER_SYSTEM) { - DBusError error; - - dbus_error_init(&error); - - dbus_bus_add_match(m->system_bus, - "type='signal'," - "interface='org.freedesktop.systemd1.Agent'," - "member='Released'," - "path='/org/freedesktop/systemd1/agent'", - &error); - - if (dbus_error_is_set(&error)) { - log_error("Failed to register match: %s", bus_error_message(&error)); - dbus_error_free(&error); - return -1; - } - } - - log_debug("Successfully connected to system D-Bus bus %s as %s", - strnull((id = dbus_connection_get_server_id(m->system_bus))), - strnull(dbus_bus_get_unique_name(m->system_bus))); - dbus_free(id); - - return 0; -} - -static int init_registered_api_bus(Manager *m) { - int r; - - if (!dbus_connection_register_object_path(m->api_bus, "/org/freedesktop/systemd1", &bus_manager_vtable, m) || - !dbus_connection_register_fallback(m->api_bus, "/org/freedesktop/systemd1/unit", &bus_unit_vtable, m) || - !dbus_connection_register_fallback(m->api_bus, "/org/freedesktop/systemd1/job", &bus_job_vtable, m) || - !dbus_connection_add_filter(m->api_bus, api_bus_message_filter, m, NULL)) { - log_error("Not enough memory"); - return -ENOMEM; - } - - /* Get NameOwnerChange messages */ - dbus_bus_add_match(m->api_bus, - "type='signal'," - "sender='"DBUS_SERVICE_DBUS"'," - "interface='"DBUS_INTERFACE_DBUS"'," - "member='NameOwnerChanged'," - "path='"DBUS_PATH_DBUS"'", - NULL); - - /* Get activation requests */ - dbus_bus_add_match(m->api_bus, - "type='signal'," - "sender='"DBUS_SERVICE_DBUS"'," - "interface='org.freedesktop.systemd1.Activator'," - "member='ActivationRequest'," - "path='"DBUS_PATH_DBUS"'", - NULL); - - r = request_name(m); - if (r < 0) - return r; - - r = query_name_list(m); - if (r < 0) - return r; - - if (m->running_as == MANAGER_USER) { - char *id; - log_debug("Successfully connected to API D-Bus bus %s as %s", - strnull((id = dbus_connection_get_server_id(m->api_bus))), - strnull(dbus_bus_get_unique_name(m->api_bus))); - dbus_free(id); - } else - log_debug("Successfully initialized API on the system bus"); - - return 0; -} - -static void bus_register_cb(DBusPendingCall *pending, void *userdata) { - Manager *m = userdata; - DBusConnection **conn; - DBusMessage *reply; - DBusError error; - const char *name; - int r = 0; - - dbus_error_init(&error); - - conn = dbus_pending_call_get_data(pending, m->conn_data_slot); - assert(conn == &m->system_bus || conn == &m->api_bus); - - reply = dbus_pending_call_steal_reply(pending); - - switch (dbus_message_get_type(reply)) { - case DBUS_MESSAGE_TYPE_ERROR: - assert_se(dbus_set_error_from_message(&error, reply)); - log_warning("Failed to register to bus: %s", bus_error_message(&error)); - r = -1; - break; - case DBUS_MESSAGE_TYPE_METHOD_RETURN: - if (!dbus_message_get_args(reply, &error, - DBUS_TYPE_STRING, &name, - DBUS_TYPE_INVALID)) { - log_error("Failed to parse Hello reply: %s", bus_error_message(&error)); - r = -1; - break; - } - - log_debug("Received name %s in reply to Hello", name); - if (!dbus_bus_set_unique_name(*conn, name)) { - log_error("Failed to set unique name"); - r = -1; - break; - } - - if (conn == &m->system_bus) { - r = init_registered_system_bus(m); - if (r == 0 && m->running_as == MANAGER_SYSTEM) - r = init_registered_api_bus(m); - } else - r = init_registered_api_bus(m); - - break; - default: - assert_not_reached("Invalid reply message"); - } - - dbus_message_unref(reply); - dbus_error_free(&error); - - if (r < 0) { - if (conn == &m->system_bus) { - log_debug("Failed setting up the system bus"); - bus_done_system(m); - } else { - log_debug("Failed setting up the API bus"); - bus_done_api(m); - } - } -} - -static int manager_bus_async_register(Manager *m, DBusConnection **conn) { - DBusMessage *message = NULL; - DBusPendingCall *pending = NULL; - - message = dbus_message_new_method_call(DBUS_SERVICE_DBUS, - DBUS_PATH_DBUS, - DBUS_INTERFACE_DBUS, - "Hello"); - if (!message) - goto oom; - - if (!dbus_connection_send_with_reply(*conn, message, &pending, -1)) - goto oom; - - if (!dbus_pending_call_set_data(pending, m->conn_data_slot, conn, NULL)) - goto oom; - - if (!dbus_pending_call_set_notify(pending, bus_register_cb, m, NULL)) - goto oom; - - dbus_message_unref(message); - dbus_pending_call_unref(pending); - - return 0; -oom: - if (pending) { - dbus_pending_call_cancel(pending); - dbus_pending_call_unref(pending); - } - - if (message) - dbus_message_unref(message); - - return -ENOMEM; -} - -static DBusConnection* manager_bus_connect_private(Manager *m, DBusBusType type) { - const char *address; - DBusConnection *connection; - DBusError error; - - switch (type) { - case DBUS_BUS_SYSTEM: - address = getenv("DBUS_SYSTEM_BUS_ADDRESS"); - if (!address || !address[0]) - address = DBUS_SYSTEM_BUS_DEFAULT_ADDRESS; - break; - case DBUS_BUS_SESSION: - address = getenv("DBUS_SESSION_BUS_ADDRESS"); - if (!address || !address[0]) - address = DBUS_SESSION_BUS_DEFAULT_ADDRESS; - break; - default: - assert_not_reached("Invalid bus type"); - } - - dbus_error_init(&error); - - connection = dbus_connection_open_private(address, &error); - if (!connection) { - log_warning("Failed to open private bus connection: %s", bus_error_message(&error)); - goto fail; - } - - return connection; -fail: - if (connection) - dbus_connection_close(connection); - dbus_error_free(&error); - return NULL; -} - -static int bus_init_system(Manager *m) { - int r; - - if (m->system_bus) - return 0; - - m->system_bus = manager_bus_connect_private(m, DBUS_BUS_SYSTEM); - if (!m->system_bus) { - log_debug("Failed to connect to system D-Bus, retrying later"); - r = 0; - goto fail; - } - - r = bus_setup_loop(m, m->system_bus); - if (r < 0) - goto fail; - - r = manager_bus_async_register(m, &m->system_bus); - if (r < 0) - goto fail; - - return 0; -fail: - bus_done_system(m); - - return r; -} - -static int bus_init_api(Manager *m) { - int r; - - if (m->api_bus) - return 0; - - if (m->running_as == MANAGER_SYSTEM) { - m->api_bus = m->system_bus; - /* In this mode there is no distinct connection to the API bus, - * the API is published on the system bus. - * bus_register_cb() is aware of that and will init the API - * when the system bus gets registered. - * No need to setup anything here. */ - return 0; - } - - m->api_bus = manager_bus_connect_private(m, DBUS_BUS_SESSION); - if (!m->api_bus) { - log_debug("Failed to connect to API D-Bus, retrying later"); - r = 0; - goto fail; - } - - r = bus_setup_loop(m, m->api_bus); - if (r < 0) - goto fail; - - r = manager_bus_async_register(m, &m->api_bus); - if (r < 0) - goto fail; - - return 0; -fail: - bus_done_api(m); - - return r; -} - -static int bus_init_private(Manager *m) { - DBusError error; - int r; - const char *const external_only[] = { - "EXTERNAL", - NULL - }; - - assert(m); - - dbus_error_init(&error); - - if (m->private_bus) - return 0; - - if (m->running_as == MANAGER_SYSTEM) { - - /* We want the private bus only when running as init */ - if (getpid() != 1) - return 0; - - unlink("/run/systemd/private"); - m->private_bus = dbus_server_listen("unix:path=/run/systemd/private", &error); - } else { - const char *e; - char *p; - - e = getenv("XDG_RUNTIME_DIR"); - if (!e) - return 0; - - if (asprintf(&p, "unix:path=%s/systemd/private", e) < 0) { - log_error("Not enough memory"); - r = -ENOMEM; - goto fail; - } - - mkdir_parents(p+10, 0755); - unlink(p+10); - m->private_bus = dbus_server_listen(p, &error); - free(p); - } - - if (!m->private_bus) { - log_error("Failed to create private D-Bus server: %s", bus_error_message(&error)); - r = -EIO; - goto fail; - } - - if (!dbus_server_set_auth_mechanisms(m->private_bus, (const char**) external_only) || - !dbus_server_set_watch_functions(m->private_bus, bus_add_watch, bus_remove_watch, bus_toggle_watch, m, NULL) || - !dbus_server_set_timeout_functions(m->private_bus, bus_add_timeout, bus_remove_timeout, bus_toggle_timeout, m, NULL)) { - log_error("Not enough memory"); - r = -ENOMEM; - goto fail; - } - - dbus_server_set_new_connection_function(m->private_bus, bus_new_connection, m, NULL); - - log_debug("Successfully created private D-Bus server."); - - return 0; - -fail: - bus_done_private(m); - dbus_error_free(&error); - - return r; -} - -int bus_init(Manager *m, bool try_bus_connect) { - int r; - - if (set_ensure_allocated(&m->bus_connections, trivial_hash_func, trivial_compare_func) < 0 || - set_ensure_allocated(&m->bus_connections_for_dispatch, trivial_hash_func, trivial_compare_func) < 0) - goto oom; - - if (m->name_data_slot < 0) - if (!dbus_pending_call_allocate_data_slot(&m->name_data_slot)) - goto oom; - - if (m->conn_data_slot < 0) - if (!dbus_pending_call_allocate_data_slot(&m->conn_data_slot)) - goto oom; - - if (m->subscribed_data_slot < 0) - if (!dbus_connection_allocate_data_slot(&m->subscribed_data_slot)) - goto oom; - - if (try_bus_connect) { - if ((r = bus_init_system(m)) < 0 || - (r = bus_init_api(m)) < 0) - return r; - } - - if ((r = bus_init_private(m)) < 0) - return r; - - return 0; -oom: - log_error("Not enough memory"); - return -ENOMEM; -} - -static void shutdown_connection(Manager *m, DBusConnection *c) { - Set *s; - Job *j; - Iterator i; - - HASHMAP_FOREACH(j, m->jobs, i) - if (j->bus == c) { - free(j->bus_client); - j->bus_client = NULL; - - j->bus = NULL; - } - - set_remove(m->bus_connections, c); - set_remove(m->bus_connections_for_dispatch, c); - - if ((s = BUS_CONNECTION_SUBSCRIBED(m, c))) { - char *t; - - while ((t = set_steal_first(s))) - free(t); - - set_free(s); - } - - if (m->queued_message_connection == c) { - m->queued_message_connection = NULL; - - if (m->queued_message) { - dbus_message_unref(m->queued_message); - m->queued_message = NULL; - } - } - - dbus_connection_set_dispatch_status_function(c, NULL, NULL, NULL); - /* system manager cannot afford to block on DBus */ - if (m->running_as != MANAGER_SYSTEM) - dbus_connection_flush(c); - dbus_connection_close(c); - dbus_connection_unref(c); -} - -static void bus_done_api(Manager *m) { - if (!m->api_bus) - return; - - if (m->running_as == MANAGER_USER) - shutdown_connection(m, m->api_bus); - - m->api_bus = NULL; - - if (m->queued_message) { - dbus_message_unref(m->queued_message); - m->queued_message = NULL; - } -} - -static void bus_done_system(Manager *m) { - if (!m->system_bus) - return; - - if (m->running_as == MANAGER_SYSTEM) - bus_done_api(m); - - shutdown_connection(m, m->system_bus); - m->system_bus = NULL; -} - -static void bus_done_private(Manager *m) { - if (!m->private_bus) - return; - - dbus_server_disconnect(m->private_bus); - dbus_server_unref(m->private_bus); - m->private_bus = NULL; -} - -void bus_done(Manager *m) { - DBusConnection *c; - - bus_done_api(m); - bus_done_system(m); - bus_done_private(m); - - while ((c = set_steal_first(m->bus_connections))) - shutdown_connection(m, c); - - while ((c = set_steal_first(m->bus_connections_for_dispatch))) - shutdown_connection(m, c); - - set_free(m->bus_connections); - set_free(m->bus_connections_for_dispatch); - - if (m->name_data_slot >= 0) - dbus_pending_call_free_data_slot(&m->name_data_slot); - - if (m->conn_data_slot >= 0) - dbus_pending_call_free_data_slot(&m->conn_data_slot); - - if (m->subscribed_data_slot >= 0) - dbus_connection_free_data_slot(&m->subscribed_data_slot); -} - -static void query_pid_pending_cb(DBusPendingCall *pending, void *userdata) { - Manager *m = userdata; - DBusMessage *reply; - DBusError error; - const char *name; - - dbus_error_init(&error); - - assert_se(name = BUS_PENDING_CALL_NAME(m, pending)); - assert_se(reply = dbus_pending_call_steal_reply(pending)); - - switch (dbus_message_get_type(reply)) { - - case DBUS_MESSAGE_TYPE_ERROR: - - assert_se(dbus_set_error_from_message(&error, reply)); - log_warning("GetConnectionUnixProcessID() failed: %s", bus_error_message(&error)); - break; - - case DBUS_MESSAGE_TYPE_METHOD_RETURN: { - uint32_t r; - - if (!dbus_message_get_args(reply, - &error, - DBUS_TYPE_UINT32, &r, - DBUS_TYPE_INVALID)) { - log_error("Failed to parse GetConnectionUnixProcessID() reply: %s", bus_error_message(&error)); - break; - } - - manager_dispatch_bus_query_pid_done(m, name, (pid_t) r); - break; - } - - default: - assert_not_reached("Invalid reply message"); - } - - dbus_message_unref(reply); - dbus_error_free(&error); -} - -int bus_query_pid(Manager *m, const char *name) { - DBusMessage *message = NULL; - DBusPendingCall *pending = NULL; - char *n = NULL; - - assert(m); - assert(name); - - if (!(message = dbus_message_new_method_call( - DBUS_SERVICE_DBUS, - DBUS_PATH_DBUS, - DBUS_INTERFACE_DBUS, - "GetConnectionUnixProcessID"))) - goto oom; - - if (!(dbus_message_append_args( - message, - DBUS_TYPE_STRING, &name, - DBUS_TYPE_INVALID))) - goto oom; - - if (!dbus_connection_send_with_reply(m->api_bus, message, &pending, -1)) - goto oom; - - if (!(n = strdup(name))) - goto oom; - - if (!dbus_pending_call_set_data(pending, m->name_data_slot, n, free)) - goto oom; - - n = NULL; - - if (!dbus_pending_call_set_notify(pending, query_pid_pending_cb, m, NULL)) - goto oom; - - dbus_message_unref(message); - dbus_pending_call_unref(pending); - - return 0; - -oom: - free(n); - - if (pending) { - dbus_pending_call_cancel(pending); - dbus_pending_call_unref(pending); - } - - if (message) - dbus_message_unref(message); - - return -ENOMEM; -} - -int bus_broadcast(Manager *m, DBusMessage *message) { - bool oom = false; - Iterator i; - DBusConnection *c; - - assert(m); - assert(message); - - SET_FOREACH(c, m->bus_connections_for_dispatch, i) - if (c != m->system_bus || m->running_as == MANAGER_SYSTEM) - oom = !dbus_connection_send(c, message, NULL); - - SET_FOREACH(c, m->bus_connections, i) - if (c != m->system_bus || m->running_as == MANAGER_SYSTEM) - oom = !dbus_connection_send(c, message, NULL); - - return oom ? -ENOMEM : 0; -} - -bool bus_has_subscriber(Manager *m) { - Iterator i; - DBusConnection *c; - - assert(m); - - SET_FOREACH(c, m->bus_connections_for_dispatch, i) - if (bus_connection_has_subscriber(m, c)) - return true; - - SET_FOREACH(c, m->bus_connections, i) - if (bus_connection_has_subscriber(m, c)) - return true; - - return false; -} - -bool bus_connection_has_subscriber(Manager *m, DBusConnection *c) { - assert(m); - assert(c); - - return !set_isempty(BUS_CONNECTION_SUBSCRIBED(m, c)); -} - -int bus_fdset_add_all(Manager *m, FDSet *fds) { - Iterator i; - DBusConnection *c; - - 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 that 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 */ - - SET_FOREACH(c, m->bus_connections_for_dispatch, i) { - int fd; - - if (dbus_connection_get_unix_fd(c, &fd)) { - fd = fdset_put_dup(fds, fd); - - if (fd < 0) - return fd; - } - } - - SET_FOREACH(c, m->bus_connections, i) { - int fd; - - if (dbus_connection_get_unix_fd(c, &fd)) { - fd = fdset_put_dup(fds, fd); - - if (fd < 0) - return fd; - } - } - - return 0; -} - -void bus_broadcast_finished( - Manager *m, - usec_t kernel_usec, - usec_t initrd_usec, - usec_t userspace_usec, - usec_t total_usec) { - - DBusMessage *message; - - assert(m); - - message = dbus_message_new_signal("/org/freedesktop/systemd1", "org.freedesktop.systemd1.Manager", "StartupFinished"); - if (!message) { - log_error("Out of memory."); - return; - } - - assert_cc(sizeof(usec_t) == sizeof(uint64_t)); - if (!dbus_message_append_args(message, - DBUS_TYPE_UINT64, &kernel_usec, - DBUS_TYPE_UINT64, &initrd_usec, - DBUS_TYPE_UINT64, &userspace_usec, - DBUS_TYPE_UINT64, &total_usec, - DBUS_TYPE_INVALID)) { - log_error("Out of memory."); - goto finish; - } - - - if (bus_broadcast(m, message) < 0) { - log_error("Out of memory."); - goto finish; - } - -finish: - if (message) - dbus_message_unref(message); -} diff --git a/src/dbus.h b/src/dbus.h deleted file mode 100644 index bd539d0e72..0000000000 --- a/src/dbus.h +++ /dev/null @@ -1,53 +0,0 @@ -/*-*- Mode: C; c-basic-offset: 8; indent-tabs-mode: nil -*-*/ - -#ifndef foodbushfoo -#define foodbushfoo - -/*** - This file is part of systemd. - - Copyright 2010 Lennart Poettering - - systemd is free software; you can redistribute it and/or modify it - under the terms of the GNU General Public License as published by - the Free Software Foundation; either version 2 of the License, or - (at your option) any later version. - - systemd is distributed in the hope that 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 General Public License - along with systemd; If not, see . -***/ - -#include - -#include "manager.h" - -int bus_init(Manager *m, bool try_bus_connect); -void bus_done(Manager *m); - -unsigned bus_dispatch(Manager *m); - -void bus_watch_event(Manager *m, Watch *w, int events); -void bus_timeout_event(Manager *m, Watch *w, int events); - -int bus_query_pid(Manager *m, const char *name); - -int bus_broadcast(Manager *m, DBusMessage *message); - -bool bus_has_subscriber(Manager *m); -bool bus_connection_has_subscriber(Manager *m, DBusConnection *c); - -int bus_fdset_add_all(Manager *m, FDSet *fds); - -void bus_broadcast_finished(Manager *m, usec_t kernel_usec, usec_t initrd_usec, usec_t userspace_usec, usec_t total_usec); - -#define BUS_CONNECTION_SUBSCRIBED(m, c) dbus_connection_get_data((c), (m)->subscribed_data_slot) -#define BUS_PENDING_CALL_NAME(m, p) dbus_pending_call_get_data((p), (m)->name_data_slot) - -extern const char * const bus_interface_table[]; - -#endif diff --git a/src/device.c b/src/device.c deleted file mode 100644 index 0575379d80..0000000000 --- a/src/device.c +++ /dev/null @@ -1,616 +0,0 @@ -/*-*- Mode: C; c-basic-offset: 8; indent-tabs-mode: nil -*-*/ - -/*** - This file is part of systemd. - - Copyright 2010 Lennart Poettering - - systemd is free software; you can redistribute it and/or modify it - under the terms of the GNU General Public License as published by - the Free Software Foundation; either version 2 of the License, or - (at your option) any later version. - - systemd is distributed in the hope that 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 General Public License - along with systemd; If not, see . -***/ - -#include -#include -#include - -#include "unit.h" -#include "device.h" -#include "strv.h" -#include "log.h" -#include "unit-name.h" -#include "dbus-device.h" -#include "def.h" - -static const UnitActiveState state_translation_table[_DEVICE_STATE_MAX] = { - [DEVICE_DEAD] = UNIT_INACTIVE, - [DEVICE_PLUGGED] = UNIT_ACTIVE -}; - -static void device_unset_sysfs(Device *d) { - Device *first; - - assert(d); - - if (!d->sysfs) - return; - - /* Remove this unit from the chain of devices which share the - * same sysfs path. */ - first = hashmap_get(UNIT(d)->manager->devices_by_sysfs, d->sysfs); - LIST_REMOVE(Device, same_sysfs, first, d); - - if (first) - hashmap_remove_and_replace(UNIT(d)->manager->devices_by_sysfs, d->sysfs, first->sysfs, first); - else - hashmap_remove(UNIT(d)->manager->devices_by_sysfs, d->sysfs); - - free(d->sysfs); - d->sysfs = NULL; -} - -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. */ - UNIT(d)->job_timeout = DEFAULT_TIMEOUT_USEC; - - UNIT(d)->ignore_on_isolate = true; - UNIT(d)->ignore_on_snapshot = 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_debug("%s changed %s -> %s", - UNIT(d)->id, - 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->sysfs) - device_set_state(d, DEVICE_PLUGGED); - - 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)); -} - -static UnitActiveState device_active_state(Unit *u) { - assert(u); - - return state_translation_table[DEVICE(u)->state]; -} - -static const char *device_sub_state_to_string(Unit *u) { - assert(u); - - return device_state_to_string(DEVICE(u)->state); -} - -static int device_add_escaped_name(Unit *u, const char *dn) { - char *e; - int r; - - assert(u); - assert(dn); - assert(dn[0] == '/'); - - if (!(e = unit_name_from_path(dn, ".device"))) - return -ENOMEM; - - r = unit_add_name(u, e); - free(e); - - if (r < 0 && r != -EEXIST) - return r; - - return 0; -} - -static int device_find_escape_name(Manager *m, const char *dn, Unit **_u) { - char *e; - Unit *u; - - assert(m); - assert(dn); - assert(dn[0] == '/'); - assert(_u); - - if (!(e = unit_name_from_path(dn, ".device"))) - return -ENOMEM; - - u = manager_get_unit(m, e); - free(e); - - if (u) { - *_u = u; - return 1; - } - - return 0; -} - -static int device_update_unit(Manager *m, struct udev_device *dev, const char *path, bool main) { - const char *sysfs, *model; - Unit *u = NULL; - int r; - bool delete; - - assert(m); - - if (!(sysfs = udev_device_get_syspath(dev))) - return -ENOMEM; - - if ((r = device_find_escape_name(m, path, &u)) < 0) - return r; - - if (u && DEVICE(u)->sysfs && !path_equal(DEVICE(u)->sysfs, sysfs)) - return -EEXIST; - - if (!u) { - delete = true; - - u = unit_new(m, sizeof(Device)); - if (!u) - return -ENOMEM; - - r = device_add_escaped_name(u, path); - 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 (!DEVICE(u)->sysfs) { - Device *first; - - if (!(DEVICE(u)->sysfs = strdup(sysfs))) { - r = -ENOMEM; - goto fail; - } - - if (!m->devices_by_sysfs) - if (!(m->devices_by_sysfs = hashmap_new(string_hash_func, string_compare_func))) { - r = -ENOMEM; - goto fail; - } - - first = hashmap_get(m->devices_by_sysfs, sysfs); - LIST_PREPEND(Device, same_sysfs, first, DEVICE(u)); - - if ((r = hashmap_replace(m->devices_by_sysfs, DEVICE(u)->sysfs, first)) < 0) - goto fail; - } - - if ((model = udev_device_get_property_value(dev, "ID_MODEL_FROM_DATABASE")) || - (model = udev_device_get_property_value(dev, "ID_MODEL"))) { - if ((r = unit_set_description(u, model)) < 0) - goto fail; - } else - if ((r = unit_set_description(u, path)) < 0) - goto fail; - - if (main) { - /* The additional systemd udev properties we only - * interpret for the main object */ - const char *wants, *alias; - - if ((alias = udev_device_get_property_value(dev, "SYSTEMD_ALIAS"))) { - if (!is_path(alias)) - log_warning("SYSTEMD_ALIAS for %s is not a path, ignoring: %s", sysfs, alias); - else { - if ((r = device_add_escaped_name(u, alias)) < 0) - goto fail; - } - } - - if ((wants = udev_device_get_property_value(dev, "SYSTEMD_WANTS"))) { - char *state, *w; - size_t l; - - FOREACH_WORD_QUOTED(w, l, wants, state) { - char *e; - - if (!(e = strndup(w, l))) { - r = -ENOMEM; - goto fail; - } - - r = unit_add_dependency_by_name(u, UNIT_WANTS, e, NULL, true); - free(e); - - if (r < 0) - goto fail; - } - } - } - - unit_add_to_dbus_queue(u); - return 0; - -fail: - log_warning("Failed to load device unit: %s", strerror(-r)); - - if (delete && u) - unit_free(u); - - return r; -} - -static int device_process_new_device(Manager *m, struct udev_device *dev, bool update_state) { - const char *sysfs, *dn; - struct udev_list_entry *item = NULL, *first = NULL; - - assert(m); - - if (!(sysfs = udev_device_get_syspath(dev))) - return -ENOMEM; - - /* Add the main unit named after the sysfs path */ - device_update_unit(m, dev, sysfs, true); - - /* Add an additional unit for the device node */ - if ((dn = udev_device_get_devnode(dev))) - device_update_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; - - device_update_unit(m, dev, p, false); - } - - if (update_state) { - Device *d, *l; - - manager_dispatch_load_queue(m); - - l = hashmap_get(m->devices_by_sysfs, sysfs); - LIST_FOREACH(same_sysfs, d, l) - device_set_state(d, DEVICE_PLUGGED); - } - - return 0; -} - -static int device_process_path(Manager *m, const char *path, bool update_state) { - int r; - struct udev_device *dev; - - assert(m); - assert(path); - - if (!(dev = udev_device_new_from_syspath(m->udev, path))) { - log_warning("Failed to get udev device object from udev for path %s.", path); - return -ENOMEM; - } - - r = device_process_new_device(m, dev, update_state); - udev_device_unref(dev); - return r; -} - -static int device_process_removed_device(Manager *m, struct udev_device *dev) { - const char *sysfs; - Device *d; - - assert(m); - assert(dev); - - if (!(sysfs = udev_device_get_syspath(dev))) - return -ENOMEM; - - /* Remove all units of this sysfs path */ - while ((d = hashmap_get(m->devices_by_sysfs, sysfs))) { - device_unset_sysfs(d); - device_set_state(d, DEVICE_DEAD); - } - - return 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 **_s) { - Device *d = DEVICE(u); - Device *other; - Set *s; - int r; - - assert(d); - assert(_s); - - if (!d->same_sysfs_prev && !d->same_sysfs_next) { - *_s = NULL; - return 0; - } - - if (!(s = set_new(NULL, NULL))) - return -ENOMEM; - - for (other = d->same_sysfs_next; other; other = other->same_sysfs_next) - if ((r = set_put(s, other)) < 0) - goto fail; - - for (other = d->same_sysfs_prev; other; other = other->same_sysfs_prev) - if ((r = set_put(s, other)) < 0) - goto fail; - - *_s = s; - return 1; - -fail: - set_free(s); - return r; -} - -static void device_shutdown(Manager *m) { - assert(m); - - if (m->udev_monitor) { - udev_monitor_unref(m->udev_monitor); - m->udev_monitor = NULL; - } - - if (m->udev) { - udev_unref(m->udev); - m->udev = NULL; - } - - hashmap_free(m->devices_by_sysfs); - m->devices_by_sysfs = NULL; -} - -static int device_enumerate(Manager *m) { - struct epoll_event ev; - int r; - struct udev_enumerate *e = NULL; - struct udev_list_entry *item = NULL, *first = NULL; - - assert(m); - - if (!m->udev) { - if (!(m->udev = udev_new())) - return -ENOMEM; - - if (!(m->udev_monitor = udev_monitor_new_from_netlink(m->udev, "udev"))) { - r = -ENOMEM; - goto fail; - } - - /* This will fail if we are unprivileged, but that - * should not matter much, as user instances won't run - * during boot. */ - udev_monitor_set_receive_buffer_size(m->udev_monitor, 128*1024*1024); - - if (udev_monitor_filter_add_match_tag(m->udev_monitor, "systemd") < 0) { - r = -ENOMEM; - goto fail; - } - - if (udev_monitor_enable_receiving(m->udev_monitor) < 0) { - r = -EIO; - goto fail; - } - - m->udev_watch.type = WATCH_UDEV; - m->udev_watch.fd = udev_monitor_get_fd(m->udev_monitor); - - zero(ev); - ev.events = EPOLLIN; - ev.data.ptr = &m->udev_watch; - - if (epoll_ctl(m->epoll_fd, EPOLL_CTL_ADD, m->udev_watch.fd, &ev) < 0) - return -errno; - } - - if (!(e = udev_enumerate_new(m->udev))) { - r = -ENOMEM; - goto fail; - } - if (udev_enumerate_add_match_tag(e, "systemd") < 0) { - r = -EIO; - goto fail; - } - - if (udev_enumerate_scan_devices(e) < 0) { - r = -EIO; - goto fail; - } - - first = udev_enumerate_get_list_entry(e); - udev_list_entry_foreach(item, first) - device_process_path(m, udev_list_entry_get_name(item), false); - - udev_enumerate_unref(e); - return 0; - -fail: - if (e) - udev_enumerate_unref(e); - - device_shutdown(m); - return r; -} - -void device_fd_event(Manager *m, int events) { - struct udev_device *dev; - int r; - const char *action, *ready; - - assert(m); - - if (events != EPOLLIN) { - static RATELIMIT_DEFINE(limit, 10*USEC_PER_SEC, 5); - - if (!ratelimit_test(&limit)) - log_error("Failed to get udev event: %m"); - if (!(events & EPOLLIN)) - return; - } - - if (!(dev = udev_monitor_receive_device(m->udev_monitor))) { - /* - * libudev might filter-out devices which pass the bloom filter, - * so getting NULL here is not necessarily an error - */ - return; - } - - if (!(action = udev_device_get_action(dev))) { - log_error("Failed to get udev action string."); - goto fail; - } - - ready = udev_device_get_property_value(dev, "SYSTEMD_READY"); - - if (streq(action, "remove") || (ready && parse_boolean(ready) == 0)) { - if ((r = device_process_removed_device(m, dev)) < 0) { - log_error("Failed to process udev device event: %s", strerror(-r)); - goto fail; - } - } else { - if ((r = device_process_new_device(m, dev, true)) < 0) { - log_error("Failed to process udev device event: %s", strerror(-r)); - goto fail; - } - } - -fail: - udev_device_unref(dev); -} - -static const char* const device_state_table[_DEVICE_STATE_MAX] = { - [DEVICE_DEAD] = "dead", - [DEVICE_PLUGGED] = "plugged" -}; - -DEFINE_STRING_TABLE_LOOKUP(device_state, DeviceState); - -const UnitVTable device_vtable = { - .suffix = ".device", - .object_size = sizeof(Device), - .sections = - "Unit\0" - "Device\0" - "Install\0", - - .no_instances = true, - - .init = device_init, - - .load = unit_load_fragment_and_dropin_optional, - .done = device_done, - .coldplug = device_coldplug, - - .dump = device_dump, - - .active_state = device_active_state, - .sub_state_to_string = device_sub_state_to_string, - - .bus_interface = "org.freedesktop.systemd1.Device", - .bus_message_handler = bus_device_message_handler, - .bus_invalidating_properties = bus_device_invalidating_properties, - - .following = device_following, - .following_set = device_following_set, - - .enumerate = device_enumerate, - .shutdown = device_shutdown -}; diff --git a/src/device.h b/src/device.h deleted file mode 100644 index a05c3d37b0..0000000000 --- a/src/device.h +++ /dev/null @@ -1,59 +0,0 @@ -/*-*- Mode: C; c-basic-offset: 8; indent-tabs-mode: nil -*-*/ - -#ifndef foodevicehfoo -#define foodevicehfoo - -/*** - This file is part of systemd. - - Copyright 2010 Lennart Poettering - - systemd is free software; you can redistribute it and/or modify it - under the terms of the GNU General Public License as published by - the Free Software Foundation; either version 2 of the License, or - (at your option) any later version. - - systemd is distributed in the hope that 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 General Public License - along with systemd; If not, see . -***/ - -typedef struct Device Device; - -#include "unit.h" - -/* We simply watch devices, we cannot plug/unplug them. That - * simplifies the state engine greatly */ -typedef enum DeviceState { - DEVICE_DEAD, - DEVICE_PLUGGED, - _DEVICE_STATE_MAX, - _DEVICE_STATE_INVALID = -1 -} DeviceState; - -struct Device { - Unit meta; - - char *sysfs; - - /* 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; -}; - -extern const UnitVTable device_vtable; - -void device_fd_event(Manager *m, int events); - -const char* device_state_to_string(DeviceState i); -DeviceState device_state_from_string(const char *s); - -#endif diff --git a/src/execute.c b/src/execute.c deleted file mode 100644 index 179ca7e55c..0000000000 --- a/src/execute.c +++ /dev/null @@ -1,2113 +0,0 @@ -/*-*- Mode: C; c-basic-offset: 8; indent-tabs-mode: nil -*-*/ - -/*** - This file is part of systemd. - - Copyright 2010 Lennart Poettering - - systemd is free software; you can redistribute it and/or modify it - under the terms of the GNU General Public License as published by - the Free Software Foundation; either version 2 of the License, or - (at your option) any later version. - - systemd is distributed in the hope that 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 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 - -#ifdef HAVE_PAM -#include -#endif - -#include "execute.h" -#include "strv.h" -#include "macro.h" -#include "capability.h" -#include "util.h" -#include "log.h" -#include "ioprio.h" -#include "securebits.h" -#include "cgroup.h" -#include "namespace.h" -#include "tcpwrap.h" -#include "exit-status.h" -#include "missing.h" -#include "utmp-wtmp.h" -#include "def.h" -#include "loopback-setup.h" - -/* This assumes there is a 'tty' group */ -#define TTY_MODE 0620 - -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; - - if ((nfd = fcntl(fds[i], F_DUPFD, i+3)) < 0) - return -errno; - - close_nointr_nofail(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++) { - - if ((r = fd_nonblock(fds[i], nonblock)) < 0) - return r; - - /* We unconditionally drop FD_CLOEXEC from the fds, - * since after all we want to pass these fds to our - * children */ - - if ((r = fd_cloexec(fds[i], false)) < 0) - return r; - } - - return 0; -} - -static const char *tty_path(const ExecContext *context) { - assert(context); - - if (context->tty_path) - return context->tty_path; - - return "/dev/console"; -} - -void exec_context_tty_reset(const ExecContext *context) { - assert(context); - - if (context->tty_vhangup) - terminal_vhangup(tty_path(context)); - - if (context->tty_reset) - reset_terminal(tty_path(context)); - - if (context->tty_vt_disallocate && context->tty_path) - vt_disallocate(context->tty_path); -} - -static int open_null_as(int flags, int nfd) { - int fd, r; - - assert(nfd >= 0); - - if ((fd = open("/dev/null", flags|O_NOCTTY)) < 0) - return -errno; - - if (fd != nfd) { - r = dup2(fd, nfd) < 0 ? -errno : nfd; - close_nointr_nofail(fd); - } else - r = nfd; - - return r; -} - -static int connect_logger_as(const ExecContext *context, ExecOutput output, const char *ident, int nfd) { - int fd, r; - union sockaddr_union sa; - - assert(context); - assert(output < _EXEC_OUTPUT_MAX); - assert(ident); - assert(nfd >= 0); - - fd = socket(AF_UNIX, SOCK_STREAM, 0); - if (fd < 0) - return -errno; - - zero(sa); - sa.un.sun_family = AF_UNIX; - strncpy(sa.un.sun_path, "/run/systemd/journal/stdout", sizeof(sa.un.sun_path)); - - r = connect(fd, &sa.sa, offsetof(struct sockaddr_un, sun_path) + strlen(sa.un.sun_path)); - if (r < 0) { - close_nointr_nofail(fd); - return -errno; - } - - if (shutdown(fd, SHUT_RD) < 0) { - close_nointr_nofail(fd); - return -errno; - } - - dprintf(fd, - "%s\n" - "%i\n" - "%i\n" - "%i\n" - "%i\n" - "%i\n", - context->syslog_identifier ? context->syslog_identifier : ident, - 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, - output == EXEC_OUTPUT_SYSLOG_AND_CONSOLE || output == EXEC_OUTPUT_KMSG_AND_CONSOLE || output == EXEC_OUTPUT_JOURNAL_AND_CONSOLE); - - if (fd != nfd) { - r = dup2(fd, nfd) < 0 ? -errno : nfd; - close_nointr_nofail(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); - - if ((fd = open_terminal(path, mode | O_NOCTTY)) < 0) - return fd; - - if (fd != nfd) { - r = dup2(fd, nfd) < 0 ? -errno : nfd; - close_nointr_nofail(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, int socket_fd, bool apply_tty_stdin) { - ExecInput i; - - assert(context); - - i = fixup_input(context->std_input, socket_fd, 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; - - if ((fd = acquire_terminal( - tty_path(context), - i == EXEC_INPUT_TTY_FAIL, - i == EXEC_INPUT_TTY_FORCE, - false)) < 0) - return fd; - - if (fd != STDIN_FILENO) { - r = dup2(fd, STDIN_FILENO) < 0 ? -errno : STDIN_FILENO; - close_nointr_nofail(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(const ExecContext *context, int socket_fd, const char *ident, bool apply_tty_stdin) { - ExecOutput o; - ExecInput i; - - assert(context); - assert(ident); - - i = fixup_input(context->std_input, socket_fd, apply_tty_stdin); - o = fixup_output(context->std_output, socket_fd); - - /* This expects the input is already set up */ - - switch (o) { - - case 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(tty_path(context), O_WRONLY, STDOUT_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, STDOUT_FILENO) < 0 ? -errno : STDOUT_FILENO; - - /* If we are not started from PID 1 we just inherit STDOUT from our parent process. */ - if (getppid() != 1) - return STDOUT_FILENO; - - /* We need to open /dev/null here anew, to get the - * right access mode. So we fall through */ - - case EXEC_OUTPUT_NULL: - return open_null_as(O_WRONLY, STDOUT_FILENO); - - case EXEC_OUTPUT_TTY: - if (is_terminal_input(i)) - return dup2(STDIN_FILENO, STDOUT_FILENO) < 0 ? -errno : STDOUT_FILENO; - - /* We don't reset the terminal if this is just about output */ - return open_terminal_as(tty_path(context), O_WRONLY, STDOUT_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: - return connect_logger_as(context, o, ident, STDOUT_FILENO); - - case EXEC_OUTPUT_SOCKET: - assert(socket_fd >= 0); - return dup2(socket_fd, STDOUT_FILENO) < 0 ? -errno : STDOUT_FILENO; - - default: - assert_not_reached("Unknown output type"); - } -} - -static int setup_error(const ExecContext *context, int socket_fd, const char *ident, bool apply_tty_stdin) { - ExecOutput o, e; - ExecInput i; - - assert(context); - assert(ident); - - i = fixup_input(context->std_input, socket_fd, apply_tty_stdin); - o = fixup_output(context->std_output, socket_fd); - 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 STDERR_FILENO; - - /* Duplicate from stdout if possible */ - if (e == o || e == EXEC_OUTPUT_INHERIT) - return dup2(STDOUT_FILENO, STDERR_FILENO) < 0 ? -errno : STDERR_FILENO; - - switch (e) { - - case EXEC_OUTPUT_NULL: - return open_null_as(O_WRONLY, STDERR_FILENO); - - case EXEC_OUTPUT_TTY: - if (is_terminal_input(i)) - return dup2(STDIN_FILENO, STDERR_FILENO) < 0 ? -errno : STDERR_FILENO; - - /* We don't reset the terminal if this is just about output */ - return open_terminal_as(tty_path(context), O_WRONLY, STDERR_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: - return connect_logger_as(context, e, ident, STDERR_FILENO); - - case EXEC_OUTPUT_SOCKET: - assert(socket_fd >= 0); - return dup2(socket_fd, STDERR_FILENO) < 0 ? -errno : STDERR_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(const ExecContext *context, - int *_saved_stdin, - int *_saved_stdout) { - int fd = -1, saved_stdin, saved_stdout = -1, r; - - assert(context); - assert(_saved_stdin); - assert(_saved_stdout); - - /* This returns positive EXIT_xxx return values instead of - * negative errno style values! */ - - if ((saved_stdin = fcntl(STDIN_FILENO, F_DUPFD, 3)) < 0) - return EXIT_STDIN; - - if ((saved_stdout = fcntl(STDOUT_FILENO, F_DUPFD, 3)) < 0) { - r = EXIT_STDOUT; - goto fail; - } - - if ((fd = acquire_terminal( - tty_path(context), - context->std_input == EXEC_INPUT_TTY_FAIL, - context->std_input == EXEC_INPUT_TTY_FORCE, - false)) < 0) { - r = EXIT_STDIN; - goto fail; - } - - if (chown_terminal(fd, getuid()) < 0) { - r = EXIT_STDIN; - goto fail; - } - - if (dup2(fd, STDIN_FILENO) < 0) { - r = EXIT_STDIN; - goto fail; - } - - if (dup2(fd, STDOUT_FILENO) < 0) { - r = EXIT_STDOUT; - goto fail; - } - - if (fd >= 2) - close_nointr_nofail(fd); - - *_saved_stdin = saved_stdin; - *_saved_stdout = saved_stdout; - - return 0; - -fail: - if (saved_stdout >= 0) - close_nointr_nofail(saved_stdout); - - if (saved_stdin >= 0) - close_nointr_nofail(saved_stdin); - - if (fd >= 0) - close_nointr_nofail(fd); - - return r; -} - -static int restore_confirm_stdio(const ExecContext *context, - int *saved_stdin, - int *saved_stdout, - bool *keep_stdin, - bool *keep_stdout) { - - assert(context); - assert(saved_stdin); - assert(*saved_stdin >= 0); - assert(saved_stdout); - assert(*saved_stdout >= 0); - - /* This returns positive EXIT_xxx return values instead of - * negative errno style values! */ - - if (is_terminal_input(context->std_input)) { - - /* The service wants terminal input. */ - - *keep_stdin = true; - *keep_stdout = - context->std_output == EXEC_OUTPUT_INHERIT || - context->std_output == EXEC_OUTPUT_TTY; - - } else { - /* If the service doesn't want a controlling terminal, - * then we need to get rid entirely of what we have - * already. */ - - if (release_terminal() < 0) - return EXIT_STDIN; - - if (dup2(*saved_stdin, STDIN_FILENO) < 0) - return EXIT_STDIN; - - if (dup2(*saved_stdout, STDOUT_FILENO) < 0) - return EXIT_STDOUT; - - *keep_stdout = *keep_stdin = false; - } - - return 0; -} - -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) { - - if (context->group) { - const char *g = context->group; - - if ((r = get_group_creds(&g, &gid)) < 0) - return r; - } - - /* 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) { - if ((k = getgroups(ngroups_max, gids)) < 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) { - int r; - assert(context); - - /* Sets (but doesn't lookup) the uid and make sure we keep the - * capabilities while doing so. */ - - if (context->capabilities) { - cap_t d; - static const cap_value_t bits[] = { - CAP_SETUID, /* Necessary so that we can run setresuid() below */ - CAP_SETPCAP /* Necessary so that we can set PR_SET_SECUREBITS later on */ - }; - - /* First step: If we need to keep capabilities but - * drop privileges we need to make sure we keep our - * caps, whiel we drop privileges. */ - if (uid != 0) { - int sb = context->secure_bits|SECURE_KEEP_CAPS; - - if (prctl(PR_GET_SECUREBITS) != sb) - if (prctl(PR_SET_SECUREBITS, sb) < 0) - return -errno; - } - - /* Second step: set the capabilities. This will reduce - * the capabilities to the minimum we need. */ - - if (!(d = cap_dup(context->capabilities))) - return -errno; - - if (cap_set_flag(d, CAP_EFFECTIVE, ELEMENTSOF(bits), bits, CAP_SET) < 0 || - cap_set_flag(d, CAP_PERMITTED, ELEMENTSOF(bits), bits, CAP_SET) < 0) { - r = -errno; - cap_free(d); - return r; - } - - if (cap_set_proc(d) < 0) { - r = -errno; - cap_free(d); - return r; - } - - cap_free(d); - } - - /* Third step: actually set the uids */ - if (setresuid(uid, uid, uid) < 0) - return -errno; - - /* At this point we should have all necessary capabilities but - are otherwise a normal user. However, the caps might got - corrupted due to the setresuid() so we need clean them up - later. This is done outside of this call. */ - - return 0; -} - -#ifdef HAVE_PAM - -static int null_conv( - int num_msg, - const struct pam_message **msg, - struct pam_response **resp, - void *appdata_ptr) { - - /* We don't support conversations */ - - return PAM_CONV_ERR; -} - -static int setup_pam( - const char *name, - const char *user, - const char *tty, - char ***pam_env, - int fds[], unsigned n_fds) { - - static const struct pam_conv conv = { - .conv = null_conv, - .appdata_ptr = NULL - }; - - pam_handle_t *handle = NULL; - sigset_t ss, old_ss; - int pam_code = PAM_SUCCESS; - int err; - char **e = NULL; - bool close_session = false; - pid_t pam_pid = 0, parent_pid; - - assert(name); - assert(user); - assert(pam_env); - - /* We set up PAM in the parent process, then fork. The child - * will then stay around until killed via PR_GET_PDEATHSIG or - * systemd via the cgroup logic. It will then remove the PAM - * session again. The parent process will exec() the actual - * daemon. We do things this way to ensure that the main PID - * of the daemon is the one we initially fork()ed. */ - - if ((pam_code = pam_start(name, user, &conv, &handle)) != PAM_SUCCESS) { - handle = NULL; - goto fail; - } - - if (tty) - if ((pam_code = pam_set_item(handle, PAM_TTY, tty)) != PAM_SUCCESS) - goto fail; - - if ((pam_code = pam_acct_mgmt(handle, PAM_SILENT)) != PAM_SUCCESS) - goto fail; - - if ((pam_code = pam_open_session(handle, PAM_SILENT)) != PAM_SUCCESS) - goto fail; - - close_session = true; - - if ((!(e = pam_getenvlist(handle)))) { - pam_code = PAM_BUF_ERR; - goto fail; - } - - /* Block SIGTERM, so that we know that it won't get lost in - * the child */ - if (sigemptyset(&ss) < 0 || - sigaddset(&ss, SIGTERM) < 0 || - sigprocmask(SIG_BLOCK, &ss, &old_ss) < 0) - goto fail; - - parent_pid = getpid(); - - if ((pam_pid = fork()) < 0) - goto fail; - - if (pam_pid == 0) { - int sig; - int r = EXIT_PAM; - - /* The child's job is to reset the PAM session on - * termination */ - - /* 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); - - /* Wait until our parent died. This will most likely - * not work since the kernel does 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; - - /* Check if our parent process might already have - * died? */ - if (getppid() == parent_pid) { - 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) - if ((pam_code = pam_close_session(handle, PAM_DATA_SILENT)) != PAM_SUCCESS) - goto child_finish; - - r = 0; - - child_finish: - pam_end(handle, pam_code | PAM_DATA_SILENT); - _exit(r); - } - - /* 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 */ - if (sigprocmask(SIG_SETMASK, &old_ss, NULL) < 0) - goto fail; - - /* We close the log explicitly here, since the PAM modules - * might have opened it, but we don't want this fd around. */ - closelog(); - - *pam_env = e; - e = NULL; - - return 0; - -fail: - if (pam_code != PAM_SUCCESS) - err = -EPERM; /* PAM errors do not map to errno */ - else - err = -errno; - - if (handle) { - if (close_session) - pam_code = pam_close_session(handle, PAM_DATA_SILENT); - - pam_end(handle, pam_code | PAM_DATA_SILENT); - } - - strv_free(e); - - closelog(); - - if (pam_pid > 1) { - kill(pam_pid, SIGTERM); - kill(pam_pid, SIGCONT); - } - - return err; -} -#endif - -static int do_capability_bounding_set_drop(uint64_t drop) { - unsigned long i; - cap_t old_cap = NULL, new_cap = NULL; - cap_flag_value_t fv; - int r; - - /* If we are run as PID 1 we will lack CAP_SETPCAP by default - * in the effective set (yes, the kernel drops that when - * executing init!), so get it back temporarily so that we can - * call PR_CAPBSET_DROP. */ - - old_cap = cap_get_proc(); - if (!old_cap) - return -errno; - - if (cap_get_flag(old_cap, CAP_SETPCAP, CAP_EFFECTIVE, &fv) < 0) { - r = -errno; - goto finish; - } - - if (fv != CAP_SET) { - static const cap_value_t v = CAP_SETPCAP; - - new_cap = cap_dup(old_cap); - if (!new_cap) { - r = -errno; - goto finish; - } - - if (cap_set_flag(new_cap, CAP_EFFECTIVE, 1, &v, CAP_SET) < 0) { - r = -errno; - goto finish; - } - - if (cap_set_proc(new_cap) < 0) { - r = -errno; - goto finish; - } - } - - for (i = 0; i <= cap_last_cap(); i++) - if (drop & ((uint64_t) 1ULL << (uint64_t) i)) { - if (prctl(PR_CAPBSET_DROP, i) < 0) { - r = -errno; - goto finish; - } - } - - r = 0; - -finish: - if (new_cap) - cap_free(new_cap); - - if (old_cap) { - cap_set_proc(old_cap); - cap_free(old_cap); - } - - return r; -} - -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 = file_name_from_path(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); -} - -int exec_spawn(ExecCommand *command, - char **argv, - const ExecContext *context, - int fds[], unsigned n_fds, - char **environment, - bool apply_permissions, - bool apply_chroot, - bool apply_tty_stdin, - bool confirm_spawn, - CGroupBonding *cgroup_bondings, - CGroupAttribute *cgroup_attributes, - pid_t *ret) { - - pid_t pid; - int r; - char *line; - int socket_fd; - char **files_env = NULL; - - assert(command); - assert(context); - assert(ret); - assert(fds || n_fds <= 0); - - if (context->std_input == EXEC_INPUT_SOCKET || - context->std_output == EXEC_OUTPUT_SOCKET || - context->std_error == EXEC_OUTPUT_SOCKET) { - - if (n_fds != 1) - return -EINVAL; - - socket_fd = fds[0]; - - fds = NULL; - n_fds = 0; - } else - socket_fd = -1; - - if ((r = exec_context_load_environment(context, &files_env)) < 0) { - log_error("Failed to load environment files: %s", strerror(-r)); - return r; - } - - if (!argv) - argv = command->argv; - - if (!(line = exec_command_line(argv))) { - r = -ENOMEM; - goto fail_parent; - } - - log_debug("About to execute: %s", line); - free(line); - - r = cgroup_bonding_realize_list(cgroup_bondings); - if (r < 0) - goto fail_parent; - - cgroup_attribute_apply_list(cgroup_attributes, cgroup_bondings); - - if ((pid = fork()) < 0) { - r = -errno; - goto fail_parent; - } - - if (pid == 0) { - int i, err; - sigset_t ss; - const char *username = NULL, *home = NULL; - uid_t uid = (uid_t) -1; - gid_t gid = (gid_t) -1; - char **our_env = NULL, **pam_env = NULL, **final_env = NULL, **final_argv = NULL; - unsigned n_env = 0; - int saved_stdout = -1, saved_stdin = -1; - bool keep_stdout = false, keep_stdin = false, set_access = false; - - /* child */ - - 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. */ - default_signals(SIGNALS_CRASH_HANDLER, - SIGNALS_IGNORE, -1); - - if (context->ignore_sigpipe) - ignore_signals(SIGPIPE, -1); - - assert_se(sigemptyset(&ss) == 0); - if (sigprocmask(SIG_SETMASK, &ss, NULL) < 0) { - err = -errno; - r = EXIT_SIGNAL_MASK; - goto fail_child; - } - - /* Close sockets very early to make sure we don't - * block init reexecution because it cannot bind its - * sockets */ - log_forget_fds(); - err = close_all_fds(socket_fd >= 0 ? &socket_fd : fds, - socket_fd >= 0 ? 1 : n_fds); - if (err < 0) { - r = EXIT_FDS; - goto fail_child; - } - - if (!context->same_pgrp) - if (setsid() < 0) { - err = -errno; - r = EXIT_SETSID; - goto fail_child; - } - - if (context->tcpwrap_name) { - if (socket_fd >= 0) - if (!socket_tcpwrap(socket_fd, context->tcpwrap_name)) { - err = -EACCES; - r = EXIT_TCPWRAP; - goto fail_child; - } - - for (i = 0; i < (int) n_fds; i++) { - if (!socket_tcpwrap(fds[i], context->tcpwrap_name)) { - err = -EACCES; - r = EXIT_TCPWRAP; - goto fail_child; - } - } - } - - exec_context_tty_reset(context); - - /* We skip the confirmation step if we shall not apply the TTY */ - if (confirm_spawn && - (!is_terminal_input(context->std_input) || apply_tty_stdin)) { - char response; - - /* Set up terminal for the question */ - if ((r = setup_confirm_stdio(context, - &saved_stdin, &saved_stdout))) { - err = -errno; - goto fail_child; - } - - /* Now ask the question. */ - if (!(line = exec_command_line(argv))) { - err = -ENOMEM; - r = EXIT_MEMORY; - goto fail_child; - } - - r = ask(&response, "yns", "Execute %s? [Yes, No, Skip] ", line); - free(line); - - if (r < 0 || response == 'n') { - err = -ECANCELED; - r = EXIT_CONFIRM; - goto fail_child; - } else if (response == 's') { - err = r = 0; - goto fail_child; - } - - /* Release terminal for the question */ - if ((r = restore_confirm_stdio(context, - &saved_stdin, &saved_stdout, - &keep_stdin, &keep_stdout))) { - err = -errno; - goto fail_child; - } - } - - /* If a socket is connected to STDIN/STDOUT/STDERR, we - * must sure to drop O_NONBLOCK */ - if (socket_fd >= 0) - fd_nonblock(socket_fd, false); - - if (!keep_stdin) { - err = setup_input(context, socket_fd, apply_tty_stdin); - if (err < 0) { - r = EXIT_STDIN; - goto fail_child; - } - } - - if (!keep_stdout) { - err = setup_output(context, socket_fd, file_name_from_path(command->path), apply_tty_stdin); - if (err < 0) { - r = EXIT_STDOUT; - goto fail_child; - } - } - - err = setup_error(context, socket_fd, file_name_from_path(command->path), apply_tty_stdin); - if (err < 0) { - r = EXIT_STDERR; - goto fail_child; - } - - if (cgroup_bondings) { - err = cgroup_bonding_install_list(cgroup_bondings, 0); - if (err < 0) { - r = EXIT_CGROUP; - goto fail_child; - } - } - - if (context->oom_score_adjust_set) { - char t[16]; - - snprintf(t, sizeof(t), "%i", context->oom_score_adjust); - char_array_0(t); - - if (write_one_line_file("/proc/self/oom_score_adj", t) < 0) { - /* Compatibility with Linux <= 2.6.35 */ - - int adj; - - adj = (context->oom_score_adjust * -OOM_DISABLE) / OOM_SCORE_ADJ_MAX; - adj = CLAMP(adj, OOM_DISABLE, OOM_ADJUST_MAX); - - snprintf(t, sizeof(t), "%i", adj); - char_array_0(t); - - if (write_one_line_file("/proc/self/oom_adj", t) < 0 - && errno != EACCES) { - err = -errno; - r = EXIT_OOM_ADJUST; - goto fail_child; - } - } - } - - if (context->nice_set) - if (setpriority(PRIO_PROCESS, 0, context->nice) < 0) { - err = -errno; - r = EXIT_NICE; - goto fail_child; - } - - if (context->cpu_sched_set) { - struct sched_param param; - - zero(param); - param.sched_priority = context->cpu_sched_priority; - - if (sched_setscheduler(0, context->cpu_sched_policy | - (context->cpu_sched_reset_on_fork ? SCHED_RESET_ON_FORK : 0), ¶m) < 0) { - err = -errno; - r = EXIT_SETSCHEDULER; - goto fail_child; - } - } - - if (context->cpuset) - if (sched_setaffinity(0, CPU_ALLOC_SIZE(context->cpuset_ncpus), context->cpuset) < 0) { - err = -errno; - r = EXIT_CPUAFFINITY; - goto fail_child; - } - - if (context->ioprio_set) - if (ioprio_set(IOPRIO_WHO_PROCESS, 0, context->ioprio) < 0) { - err = -errno; - r = EXIT_IOPRIO; - goto fail_child; - } - - if (context->timer_slack_nsec_set) - if (prctl(PR_SET_TIMERSLACK, context->timer_slack_nsec) < 0) { - err = -errno; - r = EXIT_TIMERSLACK; - goto fail_child; - } - - if (context->utmp_id) - utmp_put_init_process(context->utmp_id, getpid(), getsid(0), context->tty_path); - - if (context->user) { - username = context->user; - err = get_user_creds(&username, &uid, &gid, &home); - if (err < 0) { - r = EXIT_USER; - goto fail_child; - } - - if (is_terminal_input(context->std_input)) { - err = chown_terminal(STDIN_FILENO, uid); - if (err < 0) { - r = EXIT_STDIN; - goto fail_child; - } - } - - if (cgroup_bondings && context->control_group_modify) { - err = cgroup_bonding_set_group_access_list(cgroup_bondings, 0755, uid, gid); - if (err >= 0) - err = cgroup_bonding_set_task_access_list(cgroup_bondings, 0644, uid, gid, context->control_group_persistent); - if (err < 0) { - r = EXIT_CGROUP; - goto fail_child; - } - - set_access = true; - } - } - - if (cgroup_bondings && !set_access && context->control_group_persistent >= 0) { - err = cgroup_bonding_set_task_access_list(cgroup_bondings, (mode_t) -1, (uid_t) -1, (uid_t) -1, context->control_group_persistent); - if (err < 0) { - r = EXIT_CGROUP; - goto fail_child; - } - } - - if (apply_permissions) { - err = enforce_groups(context, username, gid); - if (err < 0) { - r = EXIT_GROUP; - goto fail_child; - } - } - - umask(context->umask); - -#ifdef HAVE_PAM - if (context->pam_name && username) { - err = setup_pam(context->pam_name, username, context->tty_path, &pam_env, fds, n_fds); - if (err < 0) { - r = EXIT_PAM; - goto fail_child; - } - } -#endif - if (context->private_network) { - if (unshare(CLONE_NEWNET) < 0) { - err = -errno; - r = EXIT_NETWORK; - goto fail_child; - } - - loopback_setup(); - } - - if (strv_length(context->read_write_dirs) > 0 || - strv_length(context->read_only_dirs) > 0 || - strv_length(context->inaccessible_dirs) > 0 || - context->mount_flags != MS_SHARED || - context->private_tmp) { - err = setup_namespace(context->read_write_dirs, - context->read_only_dirs, - context->inaccessible_dirs, - context->private_tmp, - context->mount_flags); - if (err < 0) { - r = EXIT_NAMESPACE; - goto fail_child; - } - } - - if (apply_chroot) { - if (context->root_directory) - if (chroot(context->root_directory) < 0) { - err = -errno; - r = EXIT_CHROOT; - goto fail_child; - } - - if (chdir(context->working_directory ? context->working_directory : "/") < 0) { - err = -errno; - r = EXIT_CHDIR; - goto fail_child; - } - } else { - - char *d; - - if (asprintf(&d, "%s/%s", - context->root_directory ? context->root_directory : "", - context->working_directory ? context->working_directory : "") < 0) { - err = -ENOMEM; - r = EXIT_MEMORY; - goto fail_child; - } - - if (chdir(d) < 0) { - err = -errno; - free(d); - r = EXIT_CHDIR; - goto fail_child; - } - - free(d); - } - - /* We repeat the fd closing here, to make sure that - * nothing is leaked from the PAM modules */ - err = close_all_fds(fds, n_fds); - if (err >= 0) - err = shift_fds(fds, n_fds); - if (err >= 0) - err = flags_fds(fds, n_fds, context->non_blocking); - if (err < 0) { - r = EXIT_FDS; - goto fail_child; - } - - if (apply_permissions) { - - for (i = 0; i < RLIMIT_NLIMITS; i++) { - if (!context->rlimit[i]) - continue; - - if (setrlimit(i, context->rlimit[i]) < 0) { - err = -errno; - r = EXIT_LIMITS; - goto fail_child; - } - } - - if (context->capability_bounding_set_drop) { - err = do_capability_bounding_set_drop(context->capability_bounding_set_drop); - if (err < 0) { - r = EXIT_CAPABILITIES; - goto fail_child; - } - } - - if (context->user) { - err = enforce_user(context, uid); - if (err < 0) { - r = EXIT_USER; - goto fail_child; - } - } - - /* PR_GET_SECUREBITS is not privileged, while - * PR_SET_SECUREBITS is. So to suppress - * potential EPERMs we'll try not to call - * PR_SET_SECUREBITS unless necessary. */ - if (prctl(PR_GET_SECUREBITS) != context->secure_bits) - if (prctl(PR_SET_SECUREBITS, context->secure_bits) < 0) { - err = -errno; - r = EXIT_SECUREBITS; - goto fail_child; - } - - if (context->capabilities) - if (cap_set_proc(context->capabilities) < 0) { - err = -errno; - r = EXIT_CAPABILITIES; - goto fail_child; - } - } - - if (!(our_env = new0(char*, 7))) { - err = -ENOMEM; - r = EXIT_MEMORY; - goto fail_child; - } - - if (n_fds > 0) - if (asprintf(our_env + n_env++, "LISTEN_PID=%lu", (unsigned long) getpid()) < 0 || - asprintf(our_env + n_env++, "LISTEN_FDS=%u", n_fds) < 0) { - err = -ENOMEM; - r = EXIT_MEMORY; - goto fail_child; - } - - if (home) - if (asprintf(our_env + n_env++, "HOME=%s", home) < 0) { - err = -ENOMEM; - r = EXIT_MEMORY; - goto fail_child; - } - - if (username) - if (asprintf(our_env + n_env++, "LOGNAME=%s", username) < 0 || - asprintf(our_env + n_env++, "USER=%s", username) < 0) { - err = -ENOMEM; - r = EXIT_MEMORY; - goto fail_child; - } - - if (is_terminal_input(context->std_input) || - context->std_output == EXEC_OUTPUT_TTY || - context->std_error == EXEC_OUTPUT_TTY) - if (!(our_env[n_env++] = strdup(default_term_for_tty(tty_path(context))))) { - err = -ENOMEM; - r = EXIT_MEMORY; - goto fail_child; - } - - assert(n_env <= 7); - - if (!(final_env = strv_env_merge( - 5, - environment, - our_env, - context->environment, - files_env, - pam_env, - NULL))) { - err = -ENOMEM; - r = EXIT_MEMORY; - goto fail_child; - } - - if (!(final_argv = replace_env_argv(argv, final_env))) { - err = -ENOMEM; - r = EXIT_MEMORY; - goto fail_child; - } - - final_env = strv_env_clean(final_env); - - execve(command->path, final_argv, final_env); - err = -errno; - r = EXIT_EXEC; - - fail_child: - if (r != 0) { - log_open(); - log_warning("Failed at step %s spawning %s: %s", - exit_status_to_string(r, EXIT_STATUS_SYSTEMD), - command->path, strerror(-err)); - } - - strv_free(our_env); - strv_free(final_env); - strv_free(pam_env); - strv_free(files_env); - strv_free(final_argv); - - if (saved_stdin >= 0) - close_nointr_nofail(saved_stdin); - - if (saved_stdout >= 0) - close_nointr_nofail(saved_stdout); - - _exit(r); - } - - strv_free(files_env); - - /* 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 (cgroup_bondings) - cgroup_bonding_install_list(cgroup_bondings, pid); - - log_debug("Forked %s as %lu", command->path, (unsigned long) pid); - - exec_status_start(&command->exec_status, pid); - - *ret = pid; - return 0; - -fail_parent: - strv_free(files_env); - - return r; -} - -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->mount_flags = MS_SHARED; - c->kill_signal = SIGTERM; - c->send_sigkill = true; - c->control_group_persistent = -1; - c->ignore_sigpipe = true; -} - -void exec_context_done(ExecContext *c) { - unsigned l; - - assert(c); - - strv_free(c->environment); - c->environment = NULL; - - strv_free(c->environment_files); - c->environment_files = NULL; - - for (l = 0; l < ELEMENTSOF(c->rlimit); l++) { - free(c->rlimit[l]); - c->rlimit[l] = NULL; - } - - free(c->working_directory); - c->working_directory = NULL; - free(c->root_directory); - c->root_directory = NULL; - - free(c->tty_path); - c->tty_path = NULL; - - free(c->tcpwrap_name); - c->tcpwrap_name = NULL; - - free(c->syslog_identifier); - c->syslog_identifier = NULL; - - free(c->user); - c->user = NULL; - - free(c->group); - c->group = NULL; - - strv_free(c->supplementary_groups); - c->supplementary_groups = NULL; - - free(c->pam_name); - c->pam_name = NULL; - - if (c->capabilities) { - cap_free(c->capabilities); - c->capabilities = NULL; - } - - strv_free(c->read_only_dirs); - c->read_only_dirs = NULL; - - strv_free(c->read_write_dirs); - c->read_write_dirs = NULL; - - strv_free(c->inaccessible_dirs); - c->inaccessible_dirs = NULL; - - if (c->cpuset) - CPU_FREE(c->cpuset); - - free(c->utmp_id); - c->utmp_id = NULL; -} - -void exec_command_done(ExecCommand *c) { - assert(c); - - free(c->path); - c->path = NULL; - - strv_free(c->argv); - c->argv = NULL; -} - -void exec_command_done_array(ExecCommand *c, unsigned n) { - unsigned i; - - for (i = 0; i < n; i++) - exec_command_done(c+i); -} - -void exec_command_free_list(ExecCommand *c) { - ExecCommand *i; - - while ((i = c)) { - LIST_REMOVE(ExecCommand, command, c, i); - exec_command_done(i); - free(i); - } -} - -void exec_command_free_array(ExecCommand **c, unsigned n) { - unsigned i; - - for (i = 0; i < n; i++) { - exec_command_free_list(c[i]); - c[i] = NULL; - } -} - -int exec_context_load_environment(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; - - fn = *i; - - if (fn[0] == '-') { - ignore = true; - fn ++; - } - - if (!path_is_absolute(fn)) { - - if (ignore) - continue; - - strv_free(r); - return -EINVAL; - } - - if ((k = load_env_file(fn, &p)) < 0) { - - if (ignore) - continue; - - strv_free(r); - return k; - } - - 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 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; - unsigned i; - - assert(c); - assert(f); - - if (!prefix) - prefix = ""; - - fprintf(f, - "%sUMask: %04o\n" - "%sWorkingDirectory: %s\n" - "%sRootDirectory: %s\n" - "%sNonBlocking: %s\n" - "%sPrivateTmp: %s\n" - "%sControlGroupModify: %s\n" - "%sControlGroupPersistent: %s\n" - "%sPrivateNetwork: %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->control_group_modify), - prefix, yes_no(c->control_group_persistent), - prefix, yes_no(c->private_network)); - - 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); - - if (c->tcpwrap_name) - fprintf(f, - "%sTCPWrapName: %s\n", - prefix, c->tcpwrap_name); - - 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: %llu\n", prefix, rlimit_to_string(i), (unsigned long long) c->rlimit[i]->rlim_max); - - if (c->ioprio_set) - fprintf(f, - "%sIOSchedulingClass: %s\n" - "%sIOPriority: %i\n", - prefix, ioprio_class_to_string(IOPRIO_PRIO_CLASS(c->ioprio)), - prefix, (int) IOPRIO_PRIO_DATA(c->ioprio)); - - if (c->cpu_sched_set) - fprintf(f, - "%sCPUSchedulingPolicy: %s\n" - "%sCPUSchedulingPriority: %i\n" - "%sCPUSchedulingResetOnFork: %s\n", - prefix, sched_policy_to_string(c->cpu_sched_policy), - 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, " %i", i); - fputs("\n", f); - } - - if (c->timer_slack_nsec_set) - fprintf(f, "%sTimerSlackNSec: %lu\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) - fprintf(f, - "%sSyslogFacility: %s\n" - "%sSyslogLevel: %s\n", - prefix, log_facility_unshifted_to_string(c->syslog_priority >> 3), - prefix, log_level_to_string(LOG_PRI(c->syslog_priority))); - - if (c->capabilities) { - char *t; - if ((t = cap_to_text(c->capabilities, NULL))) { - fprintf(f, "%sCapabilities: %s\n", - prefix, t); - cap_free(t); - } - } - - if (c->secure_bits) - fprintf(f, "%sSecure Bits:%s%s%s%s%s%s\n", - prefix, - (c->secure_bits & SECURE_KEEP_CAPS) ? " keep-caps" : "", - (c->secure_bits & SECURE_KEEP_CAPS_LOCKED) ? " keep-caps-locked" : "", - (c->secure_bits & SECURE_NO_SETUID_FIXUP) ? " no-setuid-fixup" : "", - (c->secure_bits & SECURE_NO_SETUID_FIXUP_LOCKED) ? " no-setuid-fixup-locked" : "", - (c->secure_bits & SECURE_NOROOT) ? " noroot" : "", - (c->secure_bits & SECURE_NOROOT_LOCKED) ? "noroot-locked" : ""); - - if (c->capability_bounding_set_drop) { - unsigned long l; - fprintf(f, "%sCapabilityBoundingSet:", prefix); - - for (l = 0; l <= cap_last_cap(); l++) - if (!(c->capability_bounding_set_drop & ((uint64_t) 1ULL << (uint64_t) l))) { - char *t; - - if ((t = cap_to_name(l))) { - fprintf(f, " %s", t); - cap_free(t); - } - } - - 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); - } - - fprintf(f, - "%sKillMode: %s\n" - "%sKillSignal: SIG%s\n" - "%sSendSIGKILL: %s\n" - "%sIgnoreSIGPIPE: %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->ignore_sigpipe)); - - if (c->utmp_id) - fprintf(f, - "%sUtmpIdentifier: %s\n", - prefix, c->utmp_id); -} - -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); - } -} - -void exec_status_dump(ExecStatus *s, FILE *f, const char *prefix) { - char buf[FORMAT_TIMESTAMP_MAX]; - - assert(s); - assert(f); - - if (!prefix) - prefix = ""; - - if (s->pid <= 0) - return; - - fprintf(f, - "%sPID: %lu\n", - prefix, (unsigned long) 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) { - char *p2; - const char *prefix2; - - char *cmd; - - assert(c); - assert(f); - - if (!prefix) - prefix = ""; - p2 = strappend(prefix, "\t"); - prefix2 = p2 ? p2 : prefix; - - cmd = exec_command_line(c->argv); - - fprintf(f, - "%sCommand Line: %s\n", - prefix, cmd ? cmd : strerror(ENOMEM)); - - free(cmd); - - exec_status_dump(&c->exec_status, f, prefix2); - - free(p2); -} - -void exec_command_dump_list(ExecCommand *c, FILE *f, const char *prefix) { - assert(f); - - if (!prefix) - 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(ExecCommand, command, *l, end); - LIST_INSERT_AFTER(ExecCommand, 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; - - if (!(p = strdup(path))) { - strv_free(l); - return -ENOMEM; - } - - free(c->path); - c->path = p; - - strv_free(c->argv); - c->argv = l; - - return 0; -} - -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 kill_mode_table[_KILL_MODE_MAX] = { - [KILL_CONTROL_GROUP] = "control-group", - [KILL_PROCESS] = "process", - [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" -}; - -DEFINE_STRING_TABLE_LOOKUP(kill_who, KillWho); diff --git a/src/execute.h b/src/execute.h deleted file mode 100644 index 0d7e7dd65d..0000000000 --- a/src/execute.h +++ /dev/null @@ -1,233 +0,0 @@ -/*-*- Mode: C; c-basic-offset: 8; indent-tabs-mode: nil -*-*/ - -#ifndef fooexecutehfoo -#define fooexecutehfoo - -/*** - This file is part of systemd. - - Copyright 2010 Lennart Poettering - - systemd is free software; you can redistribute it and/or modify it - under the terms of the GNU General Public License as published by - the Free Software Foundation; either version 2 of the License, or - (at your option) any later version. - - systemd is distributed in the hope that 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 General Public License - along with systemd; If not, see . -***/ - -typedef struct ExecStatus ExecStatus; -typedef struct ExecCommand ExecCommand; -typedef struct ExecContext ExecContext; - -#include -#include -#include -#include -#include -#include -#include - -struct CGroupBonding; -struct CGroupAttribute; - -#include "list.h" -#include "util.h" - -typedef enum KillMode { - KILL_CONTROL_GROUP = 0, - KILL_PROCESS, - KILL_NONE, - _KILL_MODE_MAX, - _KILL_MODE_INVALID = -1 -} KillMode; - -typedef enum KillWho { - KILL_MAIN, - KILL_CONTROL, - KILL_ALL, - _KILL_WHO_MAX, - _KILL_WHO_INVALID = -1 -} KillWho; - -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 ExecContext { - char **environment; - char **environment_files; - - struct rlimit *rlimit[RLIMIT_NLIMITS]; - char *working_directory, *root_directory; - - 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; - - unsigned long timer_slack_nsec; - - char *tcpwrap_name; - - 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; - - char **read_write_dirs, **read_only_dirs, **inaccessible_dirs; - unsigned long mount_flags; - - uint64_t capability_bounding_set_drop; - - /* Not relevant for spawning processes, just for killing */ - KillMode kill_mode; - int kill_signal; - bool send_sigkill; - - cap_t capabilities; - int secure_bits; - - int syslog_priority; - char *syslog_identifier; - bool syslog_level_prefix; - - bool cpu_sched_reset_on_fork; - bool non_blocking; - bool private_tmp; - bool private_network; - - bool control_group_modify; - int control_group_persistent; - - /* This is not exposed to the user but available - * internally. We need it to make sure that whenever we spawn - * /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; - - bool oom_score_adjust_set:1; - bool nice_set:1; - bool ioprio_set:1; - bool cpu_sched_set:1; - bool timer_slack_nsec_set:1; -}; - -int exec_spawn(ExecCommand *command, - char **argv, - const ExecContext *context, - int fds[], unsigned n_fds, - char **environment, - bool apply_permissions, - bool apply_chroot, - bool apply_tty_stdin, - bool confirm_spawn, - struct CGroupBonding *cgroup_bondings, - struct CGroupAttribute *cgroup_attributes, - pid_t *ret); - -void exec_command_done(ExecCommand *c); -void exec_command_done_array(ExecCommand *c, unsigned n); - -void 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, ...); - -void exec_context_init(ExecContext *c); -void exec_context_done(ExecContext *c); -void exec_context_dump(ExecContext *c, FILE* f, const char *prefix); -void exec_context_tty_reset(const ExecContext *context); - -int exec_context_load_environment(const ExecContext *c, char ***l); - -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); - -const char* exec_output_to_string(ExecOutput i); -ExecOutput exec_output_from_string(const char *s); - -const char* exec_input_to_string(ExecInput i); -ExecInput exec_input_from_string(const char *s); - -const char *kill_mode_to_string(KillMode k); -KillMode kill_mode_from_string(const char *s); - -const char *kill_who_to_string(KillWho k); -KillWho kill_who_from_string(const char *s); - -#endif diff --git a/src/fdset.c b/src/fdset.c deleted file mode 100644 index e67fe6fabf..0000000000 --- a/src/fdset.c +++ /dev/null @@ -1,167 +0,0 @@ -/*-*- Mode: C; c-basic-offset: 8; indent-tabs-mode: nil -*-*/ - -/*** - This file is part of systemd. - - Copyright 2010 Lennart Poettering - - systemd is free software; you can redistribute it and/or modify it - under the terms of the GNU General Public License as published by - the Free Software Foundation; either version 2 of the License, or - (at your option) any later version. - - systemd is distributed in the hope that 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 General Public License - along with systemd; If not, see . -***/ - -#include -#include -#include -#include - -#include "set.h" -#include "util.h" -#include "macro.h" -#include "fdset.h" - -#define MAKE_SET(s) ((Set*) s) -#define MAKE_FDSET(s) ((FDSet*) s) - -/* Make sure we can distuingish fd 0 and NULL */ -#define FD_TO_PTR(fd) INT_TO_PTR((fd)+1) -#define PTR_TO_FD(p) (PTR_TO_INT(p)-1) - -FDSet *fdset_new(void) { - return MAKE_FDSET(set_new(trivial_hash_func, trivial_compare_func)); -} - -void fdset_free(FDSet *s) { - void *p; - - while ((p = set_steal_first(MAKE_SET(s)))) { - /* Valgrind's fd might have ended up in this set here, - * due to fdset_new_fill(). We'll ignore all failures - * here, so that the EBADFD that valgrind will return - * us on close() doesn't influence us */ - - /* When reloading duplicates of the private bus - * connection fds and suchlike are closed here, which - * has no effect at all, since they are only - * duplicates. So don't be surprised about these log - * messages. */ - - log_debug("Closing left-over fd %i", PTR_TO_FD(p)); - close_nointr(PTR_TO_FD(p)); - } - - set_free(MAKE_SET(s)); -} - -int fdset_put(FDSet *s, int fd) { - assert(s); - assert(fd >= 0); - - return set_put(MAKE_SET(s), FD_TO_PTR(fd)); -} - -int fdset_put_dup(FDSet *s, int fd) { - int copy, r; - - assert(s); - assert(fd >= 0); - - if ((copy = fcntl(fd, F_DUPFD_CLOEXEC, 3)) < 0) - return -errno; - - if ((r = fdset_put(s, copy)) < 0) { - close_nointr_nofail(copy); - return r; - } - - return copy; -} - -bool fdset_contains(FDSet *s, int fd) { - assert(s); - assert(fd >= 0); - - return !!set_get(MAKE_SET(s), FD_TO_PTR(fd)); -} - -int fdset_remove(FDSet *s, int fd) { - assert(s); - assert(fd >= 0); - - return set_remove(MAKE_SET(s), FD_TO_PTR(fd)) ? fd : -ENOENT; -} - -int fdset_new_fill(FDSet **_s) { - DIR *d; - struct dirent *de; - int r = 0; - FDSet *s; - - assert(_s); - - /* Creates an fdsets and fills in all currently open file - * descriptors. */ - - if (!(d = opendir("/proc/self/fd"))) - return -errno; - - if (!(s = fdset_new())) { - r = -ENOMEM; - goto finish; - } - - while ((de = readdir(d))) { - int fd = -1; - - if (ignore_file(de->d_name)) - continue; - - if ((r = safe_atoi(de->d_name, &fd)) < 0) - goto finish; - - if (fd < 3) - continue; - - if (fd == dirfd(d)) - continue; - - if ((r = fdset_put(s, fd)) < 0) - goto finish; - } - - r = 0; - *_s = s; - s = NULL; - -finish: - closedir(d); - - /* We won't close the fds here! */ - if (s) - set_free(MAKE_SET(s)); - - return r; -} - -int fdset_cloexec(FDSet *fds, bool b) { - Iterator i; - void *p; - int r; - - assert(fds); - - SET_FOREACH(p, MAKE_SET(fds), i) - if ((r = fd_cloexec(PTR_TO_FD(p), b)) < 0) - return r; - - return 0; -} diff --git a/src/fdset.h b/src/fdset.h deleted file mode 100644 index 044a9e6d1f..0000000000 --- a/src/fdset.h +++ /dev/null @@ -1,40 +0,0 @@ -/*-*- Mode: C; c-basic-offset: 8; indent-tabs-mode: nil -*-*/ - -#ifndef foofdsethfoo -#define foofdsethfoo - -/*** - This file is part of systemd. - - Copyright 2010 Lennart Poettering - - systemd is free software; you can redistribute it and/or modify it - under the terms of the GNU General Public License as published by - the Free Software Foundation; either version 2 of the License, or - (at your option) any later version. - - systemd is distributed in the hope that 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 General Public License - along with systemd; If not, see . -***/ - -typedef struct FDSet FDSet; - -FDSet* fdset_new(void); -void fdset_free(FDSet *s); - -int fdset_put(FDSet *s, int fd); -int fdset_put_dup(FDSet *s, int fd); - -bool fdset_contains(FDSet *s, int fd); -int fdset_remove(FDSet *s, int fd); - -int fdset_new_fill(FDSet **_s); - -int fdset_cloexec(FDSet *fds, bool b); - -#endif diff --git a/src/ima-setup.c b/src/ima-setup.c deleted file mode 100644 index 03e43dcf16..0000000000 --- a/src/ima-setup.c +++ /dev/null @@ -1,115 +0,0 @@ -/*-*- Mode: C; c-basic-offset: 8; indent-tabs-mode: nil -*-*/ - -/*** - 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 General Public License as published by - the Free Software Foundation; either version 2 of the License, or - (at your option) any later version. - - systemd is distributed in the hope that 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 General Public License - along with systemd; If not, see . -***/ - -#include -#include -#include -#include -#include -#include -#include -#include - -#include "ima-setup.h" -#include "mount-setup.h" -#include "macro.h" -#include "util.h" -#include "log.h" -#include "label.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 - struct stat st; - ssize_t policy_size = 0, written = 0; - char *policy; - int policyfd = -1, imafd = -1; - int result = 0; - -#ifndef HAVE_SELINUX - /* Mount the securityfs filesystem */ - mount_setup_early(); -#endif - - if (stat(IMA_POLICY_PATH, &st) < 0) - return 0; - - policy_size = st.st_size; - if (stat(IMA_SECFS_DIR, &st) < 0) { - log_debug("IMA support is disabled in the kernel, ignoring."); - return 0; - } - - if (stat(IMA_SECFS_POLICY, &st) < 0) { - log_error("Another IMA custom policy has already been loaded, " - "ignoring."); - return 0; - } - - policyfd = open(IMA_POLICY_PATH, O_RDONLY|O_CLOEXEC); - if (policyfd < 0) { - log_error("Failed to open the IMA custom policy file %s (%m), " - "ignoring.", IMA_POLICY_PATH); - return 0; - } - - imafd = open(IMA_SECFS_POLICY, O_WRONLY|O_CLOEXEC); - if (imafd < 0) { - log_error("Failed to open the IMA kernel interface %s (%m), " - "ignoring.", IMA_SECFS_POLICY); - goto out; - } - - policy = mmap(NULL, policy_size, PROT_READ, MAP_PRIVATE, policyfd, 0); - if (policy == MAP_FAILED) { - log_error("mmap() failed (%m), freezing"); - result = -errno; - goto out; - } - - written = loop_write(imafd, policy, (size_t)policy_size, false); - if (written != policy_size) { - log_error("Failed to load the IMA custom policy file %s (%m), " - "ignoring.", IMA_POLICY_PATH); - goto out_mmap; - } - - log_info("Successfully loaded the IMA custom policy %s.", - IMA_POLICY_PATH); -out_mmap: - munmap(policy, policy_size); -out: - if (policyfd >= 0) - close_nointr_nofail(policyfd); - if (imafd >= 0) - close_nointr_nofail(imafd); - if (result) - return result; -#endif /* HAVE_IMA */ - - return 0; -} diff --git a/src/ima-setup.h b/src/ima-setup.h deleted file mode 100644 index 7d677cf852..0000000000 --- a/src/ima-setup.h +++ /dev/null @@ -1,29 +0,0 @@ -/*-*- Mode: C; c-basic-offset: 8; indent-tabs-mode: nil -*-*/ - -#ifndef fooimasetuphfoo -#define fooimasetuphfoo - -/*** - 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 General Public License as published by - the Free Software Foundation; either version 2 of the License, or - (at your option) any later version. - - systemd is distributed in the hope that 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 General Public License - along with systemd; If not, see . -***/ - -int ima_setup(void); - -#endif diff --git a/src/initreq.h b/src/initreq.h deleted file mode 100644 index 859042ce42..0000000000 --- a/src/initreq.h +++ /dev/null @@ -1,77 +0,0 @@ -/* - * initreq.h Interface to talk to init through /dev/initctl. - * - * Copyright (C) 1995-2004 Miquel van Smoorenburg - * - * This library is free software; you can redistribute it and/or - * modify it under the terms of the GNU Lesser General Public - * License as published by the Free Software Foundation; either - * version 2 of the License, or (at your option) any later version. - * - * Version: @(#)initreq.h 1.28 31-Mar-2004 MvS - * - */ -#ifndef _INITREQ_H -#define _INITREQ_H - -#include - -#if defined(__FreeBSD_kernel__) -# define INIT_FIFO "/etc/.initctl" -#else -# define INIT_FIFO "/dev/initctl" -#endif - -#define INIT_MAGIC 0x03091969 -#define INIT_CMD_START 0 -#define INIT_CMD_RUNLVL 1 -#define INIT_CMD_POWERFAIL 2 -#define INIT_CMD_POWERFAILNOW 3 -#define INIT_CMD_POWEROK 4 -#define INIT_CMD_BSD 5 -#define INIT_CMD_SETENV 6 -#define INIT_CMD_UNSETENV 7 - -#define INIT_CMD_CHANGECONS 12345 - -#ifdef MAXHOSTNAMELEN -# define INITRQ_HLEN MAXHOSTNAMELEN -#else -# define INITRQ_HLEN 64 -#endif - -/* - * This is what BSD 4.4 uses when talking to init. - * Linux doesn't use this right now. - */ -struct init_request_bsd { - char gen_id[8]; /* Beats me.. telnetd uses "fe" */ - char tty_id[16]; /* Tty name minus /dev/tty */ - char host[INITRQ_HLEN]; /* Hostname */ - char term_type[16]; /* Terminal type */ - int signal; /* Signal to send */ - int pid; /* Process to send to */ - char exec_name[128]; /* Program to execute */ - char reserved[128]; /* For future expansion. */ -}; - - -/* - * Because of legacy interfaces, "runlevel" and "sleeptime" - * aren't in a separate struct in the union. - * - * The weird sizes are because init expects the whole - * struct to be 384 bytes. - */ -struct init_request { - int magic; /* Magic number */ - int cmd; /* What kind of request */ - int runlevel; /* Runlevel to change to */ - int sleeptime; /* Time between TERM and KILL */ - union { - struct init_request_bsd bsd; - char data[368]; - } i; -}; - -#endif diff --git a/src/job.c b/src/job.c deleted file mode 100644 index bae6ab9f32..0000000000 --- a/src/job.c +++ /dev/null @@ -1,701 +0,0 @@ -/*-*- Mode: C; c-basic-offset: 8; indent-tabs-mode: nil -*-*/ - -/*** - This file is part of systemd. - - Copyright 2010 Lennart Poettering - - systemd is free software; you can redistribute it and/or modify it - under the terms of the GNU General Public License as published by - the Free Software Foundation; either version 2 of the License, or - (at your option) any later version. - - systemd is distributed in the hope that 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 General Public License - along with systemd; If not, see . -***/ - -#include -#include -#include -#include - -#include "set.h" -#include "unit.h" -#include "macro.h" -#include "strv.h" -#include "load-fragment.h" -#include "load-dropin.h" -#include "log.h" -#include "dbus-job.h" - -Job* job_new(Manager *m, JobType type, Unit *unit) { - Job *j; - - assert(m); - assert(type < _JOB_TYPE_MAX); - assert(unit); - - if (!(j = new0(Job, 1))) - return NULL; - - j->manager = m; - j->id = m->current_job_id++; - j->type = type; - j->unit = unit; - - j->timer_watch.type = WATCH_INVALID; - - /* We don't link it here, that's what job_dependency() is for */ - - return j; -} - -void job_free(Job *j) { - assert(j); - - /* Detach from next 'bigger' objects */ - if (j->installed) { - bus_job_send_removed_signal(j); - - if (j->unit->job == j) { - j->unit->job = NULL; - unit_add_to_gc_queue(j->unit); - } - - hashmap_remove(j->manager->jobs, UINT32_TO_PTR(j->id)); - j->installed = false; - } - - /* Detach from next 'smaller' objects */ - manager_transaction_unlink_job(j->manager, j, true); - - if (j->in_run_queue) - LIST_REMOVE(Job, run_queue, j->manager->run_queue, j); - - if (j->in_dbus_queue) - LIST_REMOVE(Job, dbus_queue, j->manager->dbus_job_queue, j); - - if (j->timer_watch.type != WATCH_INVALID) { - assert(j->timer_watch.type == WATCH_JOB_TIMER); - assert(j->timer_watch.data.job == j); - assert(j->timer_watch.fd >= 0); - - assert_se(epoll_ctl(j->manager->epoll_fd, EPOLL_CTL_DEL, j->timer_watch.fd, NULL) >= 0); - close_nointr_nofail(j->timer_watch.fd); - } - - free(j->bus_client); - free(j); -} - -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(JobDependency, subject, subject->subject_list, l); - else - LIST_PREPEND(JobDependency, subject, object->manager->transaction_anchor, l); - - LIST_PREPEND(JobDependency, object, object->object_list, l); - - return l; -} - -void job_dependency_free(JobDependency *l) { - assert(l); - - if (l->subject) - LIST_REMOVE(JobDependency, subject, l->subject->subject_list, l); - else - LIST_REMOVE(JobDependency, subject, l->object->manager->transaction_anchor, l); - - LIST_REMOVE(JobDependency, 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\tForced: %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->override)); -} - -bool job_is_anchor(Job *j) { - JobDependency *l; - - assert(j); - - LIST_FOREACH(object, l, j->object_list) - if (!l->subject) - return true; - - return false; -} - -/* - * 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. - * - * Merging is associative! A merged with B merged with C is the same as - * A merged with C merged with B. - * - * 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_RELOAD_OR_START JOB_RESTART JOB_TRY_RESTART */ -/************************************************************************************************************************************/ -/*JOB_START */ -/*JOB_VERIFY_ACTIVE */ JOB_START, -/*JOB_STOP */ -1, -1, -/*JOB_RELOAD */ JOB_RELOAD_OR_START, JOB_RELOAD, -1, -/*JOB_RELOAD_OR_START*/ JOB_RELOAD_OR_START, JOB_RELOAD_OR_START, -1, JOB_RELOAD_OR_START, -/*JOB_RESTART */ JOB_RESTART, JOB_RESTART, -1, JOB_RESTART, JOB_RESTART, -/*JOB_TRY_RESTART */ JOB_RESTART, JOB_TRY_RESTART, -1, JOB_TRY_RESTART, JOB_RESTART, JOB_RESTART, -}; - -JobType job_type_lookup_merge(JobType a, JobType b) { - assert_cc(ELEMENTSOF(job_merging_table) == _JOB_TYPE_MAX * (_JOB_TYPE_MAX - 1) / 2); - assert(a >= 0 && a < _JOB_TYPE_MAX); - assert(b >= 0 && b < _JOB_TYPE_MAX); - - 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_RELOAD_OR_START: - return - b == UNIT_ACTIVATING || - b == UNIT_RELOADING; - - case JOB_RESTART: - return - b == UNIT_ACTIVATING; - - case JOB_TRY_RESTART: - return - b == UNIT_ACTIVATING; - - default: - assert_not_reached("Invalid job type"); - } -} - -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. */ - - /* First check if there is an override */ - if (j->ignore_order) - return true; - - if (j->type == JOB_START || - j->type == JOB_VERIFY_ACTIVE || - j->type == JOB_RELOAD || - j->type == JOB_RELOAD_OR_START) { - - /* Immediate result is that the job is or might be - * started. In this case lets 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 lets wait. */ - - SET_FOREACH(other, j->unit->dependencies[UNIT_BEFORE], i) - if (other->job && - (other->job->type == JOB_STOP || - other->job->type == JOB_RESTART || - other->job->type == JOB_TRY_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) { - log_debug("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; -} - -int job_run_and_invalidate(Job *j) { - int r; - uint32_t id; - Manager *m; - - assert(j); - assert(j->installed); - - if (j->in_run_queue) { - LIST_REMOVE(Job, 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; - - j->state = JOB_RUNNING; - job_add_to_dbus_queue(j); - - /* While we execute this operation the job might go away (for - * example: because it 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. */ - id = j->id; - m = j->manager; - - switch (j->type) { - - case JOB_RELOAD_OR_START: - if (unit_active_state(j->unit) == UNIT_ACTIVE) { - job_change_type(j, JOB_RELOAD); - r = unit_reload(j->unit); - break; - } - job_change_type(j, JOB_START); - /* fall through */ - - case JOB_START: - r = unit_start(j->unit); - - /* If this unit cannot be started, then simply wait */ - if (r == -EBADR) - r = 0; - break; - - 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 = -ENOEXEC; - break; - } - - case JOB_TRY_RESTART: - if (UNIT_IS_INACTIVE_OR_DEACTIVATING(unit_active_state(j->unit))) { - r = -ENOEXEC; - break; - } - job_change_type(j, JOB_RESTART); - /* fall through */ - - case JOB_STOP: - case JOB_RESTART: - r = unit_stop(j->unit); - - /* If this unit cannot stopped, then simply wait. */ - if (r == -EBADR) - r = 0; - break; - - case JOB_RELOAD: - r = unit_reload(j->unit); - break; - - default: - assert_not_reached("Unknown job type"); - } - - if ((j = manager_get_job(m, id))) { - if (r == -EALREADY) - r = job_finish_and_invalidate(j, JOB_DONE); - else if (r == -ENOEXEC) - r = job_finish_and_invalidate(j, JOB_SKIPPED); - else if (r == -EAGAIN) - j->state = JOB_WAITING; - else if (r < 0) - r = job_finish_and_invalidate(j, JOB_FAILED); - } - - return r; -} - -static void job_print_status_message(Unit *u, JobType t, JobResult result) { - assert(u); - - if (t == JOB_START) { - - switch (result) { - - case JOB_DONE: - unit_status_printf(u, ANSI_HIGHLIGHT_GREEN_ON " OK " ANSI_HIGHLIGHT_OFF, "Started %s", unit_description(u)); - break; - - case JOB_FAILED: - unit_status_printf(u, ANSI_HIGHLIGHT_RED_ON "FAILED" ANSI_HIGHLIGHT_OFF, "Failed to start %s", unit_description(u)); - unit_status_printf(u, NULL, "See 'systemctl status %s' for details.", u->id); - break; - - case JOB_DEPENDENCY: - unit_status_printf(u, ANSI_HIGHLIGHT_RED_ON " ABORT" ANSI_HIGHLIGHT_OFF, "Dependency failed. Aborted start of %s", unit_description(u)); - break; - - case JOB_TIMEOUT: - unit_status_printf(u, ANSI_HIGHLIGHT_RED_ON " TIME " ANSI_HIGHLIGHT_OFF, "Timed out starting %s", unit_description(u)); - break; - - default: - ; - } - - } else if (t == JOB_STOP) { - - switch (result) { - - case JOB_TIMEOUT: - unit_status_printf(u, ANSI_HIGHLIGHT_RED_ON " TIME " ANSI_HIGHLIGHT_OFF, "Timed out stopping %s", unit_description(u)); - break; - - case JOB_DONE: - case JOB_FAILED: - unit_status_printf(u, ANSI_HIGHLIGHT_GREEN_ON " OK " ANSI_HIGHLIGHT_OFF, "Stopped %s", unit_description(u)); - break; - - default: - ; - } - } -} - -int job_finish_and_invalidate(Job *j, JobResult result) { - Unit *u; - Unit *other; - JobType t; - Iterator i; - bool recursed = false; - - assert(j); - assert(j->installed); - - job_add_to_dbus_queue(j); - - /* Patch restart jobs so that they become normal start jobs */ - if (result == JOB_DONE && j->type == JOB_RESTART) { - - job_change_type(j, JOB_START); - j->state = JOB_WAITING; - - job_add_to_run_queue(j); - - u = j->unit; - goto finish; - } - - j->result = result; - - log_debug("Job %s/%s finished, result=%s", j->unit->id, job_type_to_string(j->type), job_result_to_string(result)); - - if (result == JOB_FAILED) - j->manager->n_failed_jobs ++; - - u = j->unit; - t = j->type; - job_free(j); - - job_print_status_message(u, t, result); - - /* Fail depending jobs on failure */ - if (result != JOB_DONE) { - - if (t == JOB_START || - t == JOB_VERIFY_ACTIVE || - t == JOB_RELOAD_OR_START) { - - SET_FOREACH(other, u->dependencies[UNIT_REQUIRED_BY], i) - if (other->job && - (other->job->type == JOB_START || - other->job->type == JOB_VERIFY_ACTIVE || - other->job->type == JOB_RELOAD_OR_START)) { - job_finish_and_invalidate(other->job, JOB_DEPENDENCY); - recursed = true; - } - - SET_FOREACH(other, u->dependencies[UNIT_BOUND_BY], i) - if (other->job && - (other->job->type == JOB_START || - other->job->type == JOB_VERIFY_ACTIVE || - other->job->type == JOB_RELOAD_OR_START)) { - job_finish_and_invalidate(other->job, JOB_DEPENDENCY); - recursed = true; - } - - SET_FOREACH(other, u->dependencies[UNIT_REQUIRED_BY_OVERRIDABLE], i) - if (other->job && - !other->job->override && - (other->job->type == JOB_START || - other->job->type == JOB_VERIFY_ACTIVE || - other->job->type == JOB_RELOAD_OR_START)) { - job_finish_and_invalidate(other->job, JOB_DEPENDENCY); - recursed = true; - } - - } else if (t == JOB_STOP) { - - SET_FOREACH(other, u->dependencies[UNIT_CONFLICTED_BY], i) - if (other->job && - (other->job->type == JOB_START || - other->job->type == JOB_VERIFY_ACTIVE || - other->job->type == JOB_RELOAD_OR_START)) { - job_finish_and_invalidate(other->job, JOB_DEPENDENCY); - recursed = true; - } - } - } - - /* Trigger OnFailure dependencies that are not generated by - * the unit itself. We don't tread 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_notice("Job %s/%s failed with result '%s'.", - u->id, - job_type_to_string(t), - job_result_to_string(result)); - - unit_trigger_on_failure(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 recursed; -} - -int job_start_timer(Job *j) { - struct itimerspec its; - struct epoll_event ev; - int fd, r; - assert(j); - - if (j->unit->job_timeout <= 0 || - j->timer_watch.type == WATCH_JOB_TIMER) - return 0; - - assert(j->timer_watch.type == WATCH_INVALID); - - if ((fd = timerfd_create(CLOCK_MONOTONIC, TFD_NONBLOCK|TFD_CLOEXEC)) < 0) { - r = -errno; - goto fail; - } - - zero(its); - timespec_store(&its.it_value, j->unit->job_timeout); - - if (timerfd_settime(fd, 0, &its, NULL) < 0) { - r = -errno; - goto fail; - } - - zero(ev); - ev.data.ptr = &j->timer_watch; - ev.events = EPOLLIN; - - if (epoll_ctl(j->manager->epoll_fd, EPOLL_CTL_ADD, fd, &ev) < 0) { - r = -errno; - goto fail; - } - - j->timer_watch.type = WATCH_JOB_TIMER; - j->timer_watch.fd = fd; - j->timer_watch.data.job = j; - - return 0; - -fail: - if (fd >= 0) - close_nointr_nofail(fd); - - return r; -} - -void job_add_to_run_queue(Job *j) { - assert(j); - assert(j->installed); - - if (j->in_run_queue) - return; - - LIST_PREPEND(Job, 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(Job, 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/%lu", (unsigned long) j->id) < 0) - return NULL; - - return p; -} - -void job_timer_event(Job *j, uint64_t n_elapsed, Watch *w) { - assert(j); - assert(w == &j->timer_watch); - - log_warning("Job %s/%s timed out.", j->unit->id, job_type_to_string(j->type)); - job_finish_and_invalidate(j, JOB_TIMEOUT); -} - -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", -}; - -DEFINE_STRING_TABLE_LOOKUP(job_type, JobType); - -static const char* const job_mode_table[_JOB_MODE_MAX] = { - [JOB_FAIL] = "fail", - [JOB_REPLACE] = "replace", - [JOB_ISOLATE] = "isolate", - [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" -}; - -DEFINE_STRING_TABLE_LOOKUP(job_result, JobResult); diff --git a/src/job.h b/src/job.h deleted file mode 100644 index 60a43e074d..0000000000 --- a/src/job.h +++ /dev/null @@ -1,200 +0,0 @@ -/*-*- Mode: C; c-basic-offset: 8; indent-tabs-mode: nil -*-*/ - -#ifndef foojobhfoo -#define foojobhfoo - -/*** - This file is part of systemd. - - Copyright 2010 Lennart Poettering - - systemd is free software; you can redistribute it and/or modify it - under the terms of the GNU General Public License as published by - the Free Software Foundation; either version 2 of the License, or - (at your option) any later version. - - systemd is distributed in the hope that 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 General Public License - along with systemd; If not, see . -***/ - -#include -#include -#include - -typedef struct Job Job; -typedef struct JobDependency JobDependency; -typedef enum JobType JobType; -typedef enum JobState JobState; -typedef enum JobMode JobMode; -typedef enum JobResult JobResult; - -#include "manager.h" -#include "unit.h" -#include "hashmap.h" -#include "list.h" - -/* 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 */ - JOB_RELOAD_OR_START, /* if running reload, if not running start */ - - /* 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_TRY_RESTART, /* if running stop and then 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_ISOLATE, /* Start a unit, and stop all others */ - 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_CANCELED, - JOB_TIMEOUT, - JOB_FAILED, - JOB_DEPENDENCY, - JOB_SKIPPED, - _JOB_RESULT_MAX, - _JOB_RESULT_INVALID = -1 -}; - -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; - - Watch timer_watch; - - /* Note that this bus object is not ref counted here. */ - DBusConnection *bus; - char *bus_client; - - JobResult result; - - bool installed:1; - bool in_run_queue:1; - bool matters_to_anchor:1; - bool override:1; - bool in_dbus_queue:1; - bool sent_dbus_new_signal:1; - bool ignore_order:1; -}; - -Job* job_new(Manager *m, JobType type, Unit *unit); -void job_free(Job *job); -void job_dump(Job *j, FILE*f, const char *prefix); - -JobDependency* job_dependency_new(Job *subject, Job *object, bool matters, bool conflicts); -void job_dependency_free(JobDependency *l); - -bool job_is_anchor(Job *j); - -int job_merge(Job *j, Job *other); - -JobType job_type_lookup_merge(JobType a, JobType b); - -static inline int job_type_merge(JobType *a, JobType b) { - JobType t = job_type_lookup_merge(*a, b); - if (t < 0) - return -EEXIST; - *a = t; - return 0; -} - -static inline bool job_type_is_mergeable(JobType a, JobType b) { - return job_type_lookup_merge(a, b) >= 0; -} - -static inline bool job_type_is_conflicting(JobType a, JobType b) { - return !job_type_is_mergeable(a, b); -} - -static inline bool job_type_is_superset(JobType a, JobType b) { - /* Checks whether operation a is a "superset" of b in its actions */ - return a == job_type_lookup_merge(a, b); -} - -bool job_type_is_redundant(JobType a, UnitActiveState b); - -bool job_is_runnable(Job *j); - -void job_add_to_run_queue(Job *j); -void job_add_to_dbus_queue(Job *j); - -int job_start_timer(Job *j); -void job_timer_event(Job *j, uint64_t n_elapsed, Watch *w); - -int job_run_and_invalidate(Job *j); -int job_finish_and_invalidate(Job *j, JobResult result); - -char *job_dbus_path(Job *j); - -const char* job_type_to_string(JobType t); -JobType job_type_from_string(const char *s); - -const char* job_state_to_string(JobState t); -JobState job_state_from_string(const char *s); - -const char* job_mode_to_string(JobMode t); -JobMode job_mode_from_string(const char *s); - -const char* job_result_to_string(JobResult t); -JobResult job_result_from_string(const char *s); - -#endif diff --git a/src/kmod-setup.c b/src/kmod-setup.c deleted file mode 100644 index debf87130d..0000000000 --- a/src/kmod-setup.c +++ /dev/null @@ -1,96 +0,0 @@ -/*-*- Mode: C; c-basic-offset: 8; indent-tabs-mode: nil -*-*/ - -/*** - This file is part of systemd. - - Copyright 2010 Lennart Poettering - - systemd is free software; you can redistribute it and/or modify it - under the terms of the GNU General Public License as published by - the Free Software Foundation; either version 2 of the License, or - (at your option) any later version. - - systemd is distributed in the hope that 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 General Public License - along with systemd; If not, see . -***/ - -#include -#include -#include -#include -#include - -#include "macro.h" -#include "execute.h" - -#include "kmod-setup.h" - -static const char * const kmod_table[] = { - "autofs4", "/sys/class/misc/autofs", - "ipv6", "/sys/module/ipv6", - "unix", "/proc/net/unix" -}; - -#pragma GCC diagnostic push -#pragma GCC diagnostic ignored "-Wformat-nonliteral" -static void systemd_kmod_log(void *data, int priority, const char *file, int line, - const char *fn, const char *format, va_list args) -{ - log_meta(priority, file, line, fn, format, args); -} -#pragma GCC diagnostic pop - -int kmod_setup(void) { - unsigned i; - struct kmod_ctx *ctx = NULL; - struct kmod_module *mod; - int err; - - for (i = 0; i < ELEMENTSOF(kmod_table); i += 2) { - - if (access(kmod_table[i+1], F_OK) >= 0) - continue; - - 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]); - - if (!ctx) { - ctx = kmod_new(NULL, NULL); - if (!ctx) { - log_error("Failed to allocate memory for kmod"); - return -ENOMEM; - } - - kmod_set_log_fn(ctx, systemd_kmod_log, NULL); - - kmod_load_resources(ctx); - } - - err = kmod_module_new_from_name(ctx, kmod_table[i], &mod); - if (err < 0) { - log_error("Failed to load module '%s'", kmod_table[i]); - continue; - } - - err = kmod_module_probe_insert_module(mod, KMOD_PROBE_APPLY_BLACKLIST, NULL, NULL, NULL, NULL); - if (err == 0) - log_info("Inserted module '%s'", kmod_module_get_name(mod)); - else if (err == KMOD_PROBE_APPLY_BLACKLIST) - log_info("Module '%s' is blacklisted", kmod_module_get_name(mod)); - else - log_error("Failed to insert '%s'", kmod_module_get_name(mod)); - - kmod_module_unref(mod); - } - - if (ctx) - kmod_unref(ctx); - - return 0; -} diff --git a/src/kmod-setup.h b/src/kmod-setup.h deleted file mode 100644 index 496aef3e15..0000000000 --- a/src/kmod-setup.h +++ /dev/null @@ -1,27 +0,0 @@ -/*-*- Mode: C; c-basic-offset: 8; indent-tabs-mode: nil -*-*/ - -#ifndef fookmodsetuphfoo -#define fookmodsetuphfoo - -/*** - This file is part of systemd. - - Copyright 2010 Lennart Poettering - - systemd is free software; you can redistribute it and/or modify it - under the terms of the GNU General Public License as published by - the Free Software Foundation; either version 2 of the License, or - (at your option) any later version. - - systemd is distributed in the hope that 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 General Public License - along with systemd; If not, see . -***/ - -int kmod_setup(void); - -#endif diff --git a/src/load-dropin.c b/src/load-dropin.c deleted file mode 100644 index d869ee0c76..0000000000 --- a/src/load-dropin.c +++ /dev/null @@ -1,150 +0,0 @@ -/*-*- Mode: C; c-basic-offset: 8; indent-tabs-mode: nil -*-*/ - -/*** - This file is part of systemd. - - Copyright 2010 Lennart Poettering - - systemd is free software; you can redistribute it and/or modify it - under the terms of the GNU General Public License as published by - the Free Software Foundation; either version 2 of the License, or - (at your option) any later version. - - systemd is distributed in the hope that 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 General Public License - along with systemd; If not, see . -***/ - -#include -#include - -#include "unit.h" -#include "load-dropin.h" -#include "log.h" -#include "strv.h" -#include "unit-name.h" - -static int iterate_dir(Unit *u, const char *path, UnitDependency dependency) { - DIR *d; - struct dirent *de; - int r; - - assert(u); - assert(path); - - d = opendir(path); - if (!d) { - - if (errno == ENOENT) - return 0; - - return -errno; - } - - while ((de = readdir(d))) { - char *f; - - if (ignore_file(de->d_name)) - continue; - - f = join(path, "/", de->d_name, NULL); - if (!f) { - r = -ENOMEM; - goto finish; - } - - r = unit_add_dependency_by_name(u, dependency, de->d_name, f, true); - free(f); - - if (r < 0) - log_error("Cannot add dependency %s to %s, ignoring: %s", de->d_name, u->id, strerror(-r)); - } - - r = 0; - -finish: - closedir(d); - return r; -} - -static int process_dir(Unit *u, const char *unit_path, const char *name, const char *suffix, UnitDependency dependency) { - int r; - char *path; - - assert(u); - assert(unit_path); - assert(name); - assert(suffix); - - path = join(unit_path, "/", name, suffix, NULL); - if (!path) - return -ENOMEM; - - if (u->manager->unit_path_cache && - !set_get(u->manager->unit_path_cache, path)) - r = 0; - else - r = iterate_dir(u, path, dependency); - free(path); - - if (r < 0) - return r; - - if (u->instance) { - char *template; - /* Also try the template dir */ - - template = unit_name_template(name); - if (!template) - return -ENOMEM; - - path = join(unit_path, "/", template, suffix, NULL); - free(template); - - if (!path) - return -ENOMEM; - - if (u->manager->unit_path_cache && - !set_get(u->manager->unit_path_cache, path)) - r = 0; - else - r = iterate_dir(u, path, dependency); - free(path); - - if (r < 0) - return r; - } - - return 0; -} - -int unit_load_dropin(Unit *u) { - Iterator i; - char *t; - - assert(u); - - /* Load dependencies from supplementary drop-in directories */ - - SET_FOREACH(t, u->names, i) { - char **p; - - STRV_FOREACH(p, u->manager->lookup_paths.unit_path) { - int r; - - r = process_dir(u, *p, t, ".wants", UNIT_WANTS); - if (r < 0) - return r; - - r = process_dir(u, *p, t, ".requires", UNIT_REQUIRES); - if (r < 0) - return r; - } - } - - return 0; -} diff --git a/src/load-dropin.h b/src/load-dropin.h deleted file mode 100644 index cf3a799381..0000000000 --- a/src/load-dropin.h +++ /dev/null @@ -1,31 +0,0 @@ -/*-*- Mode: C; c-basic-offset: 8; indent-tabs-mode: nil -*-*/ - -#ifndef fooloaddropinhfoo -#define fooloaddropinhfoo - -/*** - This file is part of systemd. - - Copyright 2010 Lennart Poettering - - systemd is free software; you can redistribute it and/or modify it - under the terms of the GNU General Public License as published by - the Free Software Foundation; either version 2 of the License, or - (at your option) any later version. - - systemd is distributed in the hope that 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 General Public License - along with systemd; If not, see . -***/ - -#include "unit.h" - -/* Read service data supplementary drop-in directories */ - -int unit_load_dropin(Unit *u); - -#endif diff --git a/src/load-fragment.c b/src/load-fragment.c deleted file mode 100644 index 637c82b427..0000000000 --- a/src/load-fragment.c +++ /dev/null @@ -1,2445 +0,0 @@ -/*-*- Mode: C; c-basic-offset: 8; indent-tabs-mode: nil -*-*/ - -/*** - This file is part of systemd. - - Copyright 2010 Lennart Poettering - - systemd is free software; you can redistribute it and/or modify it - under the terms of the GNU General Public License as published by - the Free Software Foundation; either version 2 of the License, or - (at your option) any later version. - - systemd is distributed in the hope that 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 General Public License - along with systemd; If not, see . -***/ - -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include - -#include "unit.h" -#include "strv.h" -#include "conf-parser.h" -#include "load-fragment.h" -#include "log.h" -#include "ioprio.h" -#include "securebits.h" -#include "missing.h" -#include "unit-name.h" -#include "bus-errors.h" -#include "utf8.h" - -#ifndef HAVE_SYSV_COMPAT -int config_parse_warn_compat( - const char *filename, - unsigned line, - const char *section, - const char *lvalue, - int ltype, - const char *rvalue, - void *data, - void *userdata) { - - log_debug("[%s:%u] Support for option %s= has been disabled at compile time and is ignored", filename, line, lvalue); - return 0; -} -#endif - -int config_parse_unit_deps( - const char *filename, - unsigned line, - const char *section, - const char *lvalue, - int ltype, - const char *rvalue, - void *data, - void *userdata) { - - UnitDependency d = ltype; - Unit *u = userdata; - char *w; - size_t l; - char *state; - - assert(filename); - assert(lvalue); - assert(rvalue); - - FOREACH_WORD_QUOTED(w, l, rvalue, state) { - char *t, *k; - int r; - - t = strndup(w, l); - if (!t) - return -ENOMEM; - - k = unit_name_printf(u, t); - free(t); - if (!k) - return -ENOMEM; - - r = unit_add_dependency_by_name(u, d, k, NULL, true); - if (r < 0) - log_error("[%s:%u] Failed to add dependency on %s, ignoring: %s", filename, line, k, strerror(-r)); - - free(k); - } - - return 0; -} - -int config_parse_unit_names( - const char *filename, - unsigned line, - const char *section, - const char *lvalue, - int ltype, - const char *rvalue, - void *data, - void *userdata) { - - Unit *u = userdata; - char *w; - size_t l; - char *state; - - assert(filename); - assert(lvalue); - assert(rvalue); - assert(data); - - FOREACH_WORD_QUOTED(w, l, rvalue, state) { - char *t, *k; - int r; - - t = strndup(w, l); - if (!t) - return -ENOMEM; - - k = unit_name_printf(u, t); - free(t); - if (!k) - return -ENOMEM; - - r = unit_merge_by_name(u, k); - if (r < 0) - log_error("Failed to add name %s, ignoring: %s", k, strerror(-r)); - - free(k); - } - - return 0; -} - -int config_parse_unit_string_printf( - const char *filename, - unsigned line, - const char *section, - const char *lvalue, - int ltype, - const char *rvalue, - void *data, - void *userdata) { - - Unit *u = userdata; - char *k; - int r; - - assert(filename); - assert(lvalue); - assert(rvalue); - assert(u); - - k = unit_full_printf(u, rvalue); - if (!k) - return -ENOMEM; - - r = config_parse_string(filename, line, section, lvalue, ltype, k, data, userdata); - free (k); - - return r; -} - -int config_parse_unit_strv_printf( - const char *filename, - unsigned line, - const char *section, - const char *lvalue, - int ltype, - const char *rvalue, - void *data, - void *userdata) { - - Unit *u = userdata; - char *k; - int r; - - assert(filename); - assert(lvalue); - assert(rvalue); - assert(u); - - k = unit_full_printf(u, rvalue); - if (!k) - return -ENOMEM; - - r = config_parse_strv(filename, line, section, lvalue, ltype, k, data, userdata); - free(k); - - return r; -} - -int config_parse_unit_path_printf( - const char *filename, - unsigned line, - const char *section, - const char *lvalue, - int ltype, - const char *rvalue, - void *data, - void *userdata) { - - Unit *u = userdata; - char *k; - int r; - - assert(filename); - assert(lvalue); - assert(rvalue); - assert(u); - - k = unit_full_printf(u, rvalue); - if (!k) - return -ENOMEM; - - r = config_parse_path(filename, line, section, lvalue, ltype, k, data, userdata); - free(k); - - return r; -} - -int config_parse_socket_listen( - const char *filename, - unsigned line, - const char *section, - const char *lvalue, - int ltype, - const char *rvalue, - void *data, - void *userdata) { - - SocketPort *p, *tail; - Socket *s; - - assert(filename); - assert(lvalue); - assert(rvalue); - assert(data); - - s = SOCKET(data); - - p = new0(SocketPort, 1); - if (!p) - return -ENOMEM; - - if (streq(lvalue, "ListenFIFO")) { - p->type = SOCKET_FIFO; - - if (!(p->path = unit_full_printf(UNIT(s), rvalue))) { - free(p); - return -ENOMEM; - } - - path_kill_slashes(p->path); - - } else if (streq(lvalue, "ListenSpecial")) { - p->type = SOCKET_SPECIAL; - - if (!(p->path = unit_full_printf(UNIT(s), rvalue))) { - free(p); - return -ENOMEM; - } - - path_kill_slashes(p->path); - - } else if (streq(lvalue, "ListenMessageQueue")) { - - p->type = SOCKET_MQUEUE; - - if (!(p->path = unit_full_printf(UNIT(s), rvalue))) { - free(p); - return -ENOMEM; - } - - path_kill_slashes(p->path); - - } else if (streq(lvalue, "ListenNetlink")) { - char *k; - int r; - - p->type = SOCKET_SOCKET; - k = unit_full_printf(UNIT(s), rvalue); - r = socket_address_parse_netlink(&p->address, k); - free(k); - - if (r < 0) { - log_error("[%s:%u] Failed to parse address value, ignoring: %s", filename, line, rvalue); - free(p); - return 0; - } - - } else { - char *k; - int r; - - p->type = SOCKET_SOCKET; - k = unit_full_printf(UNIT(s), rvalue); - r = socket_address_parse(&p->address, k); - free(k); - - if (r < 0) { - log_error("[%s:%u] Failed to parse address value, ignoring: %s", filename, line, rvalue); - free(p); - 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_error("[%s:%u] Address family not supported, ignoring: %s", filename, line, rvalue); - free(p); - return 0; - } - } - - p->fd = -1; - - if (s->ports) { - LIST_FIND_TAIL(SocketPort, port, s->ports, tail); - LIST_INSERT_AFTER(SocketPort, port, s->ports, tail, p); - } else - LIST_PREPEND(SocketPort, port, s->ports, p); - - return 0; -} - -int config_parse_socket_bind( - const char *filename, - unsigned line, - const char *section, - 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); - - if ((b = socket_address_bind_ipv6_only_from_string(rvalue)) < 0) { - int r; - - if ((r = parse_boolean(rvalue)) < 0) { - log_error("[%s:%u] Failed to parse bind IPv6 only value, ignoring: %s", filename, line, 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 *filename, - unsigned line, - const char *section, - const char *lvalue, - int ltype, - const char *rvalue, - void *data, - void *userdata) { - - ExecContext *c = data; - int priority; - - assert(filename); - assert(lvalue); - assert(rvalue); - assert(data); - - if (safe_atoi(rvalue, &priority) < 0) { - log_error("[%s:%u] Failed to parse nice priority, ignoring: %s. ", filename, line, rvalue); - return 0; - } - - if (priority < PRIO_MIN || priority >= PRIO_MAX) { - log_error("[%s:%u] Nice priority out of range, ignoring: %s", filename, line, rvalue); - return 0; - } - - c->nice = priority; - c->nice_set = true; - - return 0; -} - -int config_parse_exec_oom_score_adjust( - const char *filename, - unsigned line, - const char *section, - const char *lvalue, - int ltype, - const char *rvalue, - void *data, - void *userdata) { - - ExecContext *c = data; - int oa; - - assert(filename); - assert(lvalue); - assert(rvalue); - assert(data); - - if (safe_atoi(rvalue, &oa) < 0) { - log_error("[%s:%u] Failed to parse the OOM score adjust value, ignoring: %s", filename, line, rvalue); - return 0; - } - - if (oa < OOM_SCORE_ADJ_MIN || oa > OOM_SCORE_ADJ_MAX) { - log_error("[%s:%u] OOM score adjust value out of range, ignoring: %s", filename, line, rvalue); - return 0; - } - - c->oom_score_adjust = oa; - c->oom_score_adjust_set = true; - - return 0; -} - -int config_parse_exec( - const char *filename, - unsigned line, - const char *section, - const char *lvalue, - int ltype, - const char *rvalue, - void *data, - void *userdata) { - - ExecCommand **e = data, *nce; - char *path, **n; - unsigned k; - int r; - - assert(filename); - assert(lvalue); - assert(rvalue); - assert(e); - - /* We accept an absolute path as first argument, or - * alternatively an absolute prefixed with @ to allow - * overriding of argv[0]. */ - - e += ltype; - - for (;;) { - char *w; - size_t l; - char *state; - bool honour_argv0 = false, ignore = false; - - path = NULL; - nce = NULL; - n = NULL; - - rvalue += strspn(rvalue, WHITESPACE); - - if (rvalue[0] == 0) - break; - - if (rvalue[0] == '-') { - ignore = true; - rvalue ++; - } - - if (rvalue[0] == '@') { - honour_argv0 = true; - rvalue ++; - } - - if (*rvalue != '/') { - log_error("[%s:%u] Invalid executable path in command line, ignoring: %s", filename, line, rvalue); - return 0; - } - - k = 0; - FOREACH_WORD_QUOTED(w, l, rvalue, state) { - if (strncmp(w, ";", MAX(l, 1U)) == 0) - break; - - k++; - } - - n = new(char*, k + !honour_argv0); - if (!n) - return -ENOMEM; - - k = 0; - FOREACH_WORD_QUOTED(w, l, rvalue, state) { - if (strncmp(w, ";", MAX(l, 1U)) == 0) - break; - - if (honour_argv0 && w == rvalue) { - assert(!path); - - path = strndup(w, l); - if (!path) { - r = -ENOMEM; - goto fail; - } - - if (!utf8_is_valid(path)) { - log_error("[%s:%u] Path is not UTF-8 clean, ignoring assignment: %s", filename, line, rvalue); - r = 0; - goto fail; - } - - } else { - char *c; - - c = n[k++] = cunescape_length(w, l); - if (!c) { - r = -ENOMEM; - goto fail; - } - - if (!utf8_is_valid(c)) { - log_error("[%s:%u] Path is not UTF-8 clean, ignoring assignment: %s", filename, line, rvalue); - r = 0; - goto fail; - } - } - } - - n[k] = NULL; - - if (!n[0]) { - log_error("[%s:%u] Invalid command line, ignoring: %s", filename, line, rvalue); - r = 0; - goto fail; - } - - if (!path) { - path = strdup(n[0]); - if (!path) { - r = -ENOMEM; - goto fail; - } - } - - assert(path_is_absolute(path)); - - nce = new0(ExecCommand, 1); - if (!nce) { - r = -ENOMEM; - goto fail; - } - - nce->argv = n; - nce->path = path; - nce->ignore = ignore; - - path_kill_slashes(nce->path); - - exec_command_append_list(e, nce); - - rvalue = state; - } - - return 0; - -fail: - n[k] = NULL; - strv_free(n); - free(path); - free(nce); - - return r; -} - -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 *filename, - unsigned line, - const char *section, - 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 (!(n = strdup(rvalue))) - return -ENOMEM; - } 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_facility( - const char *filename, - unsigned line, - const char *section, - const char *lvalue, - int ltype, - const char *rvalue, - void *data, - void *userdata) { - - - int *o = data, x; - - assert(filename); - assert(lvalue); - assert(rvalue); - assert(data); - - if ((x = log_facility_unshifted_from_string(rvalue)) < 0) { - log_error("[%s:%u] Failed to parse log facility, ignoring: %s", filename, line, rvalue); - return 0; - } - - *o = (x << 3) | LOG_PRI(*o); - - return 0; -} - -int config_parse_level( - const char *filename, - unsigned line, - const char *section, - const char *lvalue, - int ltype, - const char *rvalue, - void *data, - void *userdata) { - - - int *o = data, x; - - assert(filename); - assert(lvalue); - assert(rvalue); - assert(data); - - if ((x = log_level_from_string(rvalue)) < 0) { - log_error("[%s:%u] Failed to parse log level, ignoring: %s", filename, line, rvalue); - return 0; - } - - *o = (*o & LOG_FACMASK) | x; - return 0; -} - -int config_parse_exec_io_class( - const char *filename, - unsigned line, - const char *section, - 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); - - if ((x = ioprio_class_from_string(rvalue)) < 0) { - log_error("[%s:%u] Failed to parse IO scheduling class, ignoring: %s", filename, line, 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 *filename, - unsigned line, - const char *section, - const char *lvalue, - int ltype, - const char *rvalue, - void *data, - void *userdata) { - - ExecContext *c = data; - int i; - - assert(filename); - assert(lvalue); - assert(rvalue); - assert(data); - - if (safe_atoi(rvalue, &i) < 0 || i < 0 || i >= IOPRIO_BE_NR) { - log_error("[%s:%u] Failed to parse io priority, ignoring: %s", filename, line, 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 *filename, - unsigned line, - const char *section, - 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); - - if ((x = sched_policy_from_string(rvalue)) < 0) { - log_error("[%s:%u] Failed to parse CPU scheduling policy, ignoring: %s", filename, line, rvalue); - return 0; - } - - c->cpu_sched_policy = x; - c->cpu_sched_set = true; - - return 0; -} - -int config_parse_exec_cpu_sched_prio( - const char *filename, - unsigned line, - const char *section, - const char *lvalue, - int ltype, - const char *rvalue, - void *data, - void *userdata) { - - ExecContext *c = data; - int i; - - assert(filename); - assert(lvalue); - assert(rvalue); - assert(data); - - /* On Linux RR/FIFO have the same range */ - if (safe_atoi(rvalue, &i) < 0 || i < sched_get_priority_min(SCHED_RR) || i > sched_get_priority_max(SCHED_RR)) { - log_error("[%s:%u] Failed to parse CPU scheduling priority, ignoring: %s", filename, line, rvalue); - return 0; - } - - c->cpu_sched_priority = i; - c->cpu_sched_set = true; - - return 0; -} - -int config_parse_exec_cpu_affinity( - const char *filename, - unsigned line, - const char *section, - const char *lvalue, - int ltype, - const char *rvalue, - void *data, - void *userdata) { - - ExecContext *c = data; - char *w; - size_t l; - char *state; - - assert(filename); - assert(lvalue); - assert(rvalue); - assert(data); - - FOREACH_WORD_QUOTED(w, l, rvalue, state) { - char *t; - int r; - unsigned cpu; - - if (!(t = strndup(w, l))) - return -ENOMEM; - - r = safe_atou(t, &cpu); - free(t); - - if (!(c->cpuset)) - if (!(c->cpuset = cpu_set_malloc(&c->cpuset_ncpus))) - return -ENOMEM; - - if (r < 0 || cpu >= c->cpuset_ncpus) { - log_error("[%s:%u] Failed to parse CPU affinity, ignoring: %s", filename, line, rvalue); - return 0; - } - - CPU_SET_S(cpu, CPU_ALLOC_SIZE(c->cpuset_ncpus), c->cpuset); - } - - return 0; -} - -int config_parse_exec_capabilities( - const char *filename, - unsigned line, - const char *section, - const char *lvalue, - int ltype, - const char *rvalue, - void *data, - void *userdata) { - - ExecContext *c = data; - cap_t cap; - - assert(filename); - assert(lvalue); - assert(rvalue); - assert(data); - - if (!(cap = cap_from_text(rvalue))) { - if (errno == ENOMEM) - return -ENOMEM; - - log_error("[%s:%u] Failed to parse capabilities, ignoring: %s", filename, line, rvalue); - return 0; - } - - if (c->capabilities) - cap_free(c->capabilities); - c->capabilities = cap; - - return 0; -} - -int config_parse_exec_secure_bits( - const char *filename, - unsigned line, - const char *section, - const char *lvalue, - int ltype, - const char *rvalue, - void *data, - void *userdata) { - - ExecContext *c = data; - char *w; - size_t l; - char *state; - - assert(filename); - assert(lvalue); - assert(rvalue); - assert(data); - - FOREACH_WORD_QUOTED(w, l, rvalue, state) { - if (first_word(w, "keep-caps")) - c->secure_bits |= SECURE_KEEP_CAPS; - else if (first_word(w, "keep-caps-locked")) - c->secure_bits |= SECURE_KEEP_CAPS_LOCKED; - else if (first_word(w, "no-setuid-fixup")) - c->secure_bits |= SECURE_NO_SETUID_FIXUP; - else if (first_word(w, "no-setuid-fixup-locked")) - c->secure_bits |= SECURE_NO_SETUID_FIXUP_LOCKED; - else if (first_word(w, "noroot")) - c->secure_bits |= SECURE_NOROOT; - else if (first_word(w, "noroot-locked")) - c->secure_bits |= SECURE_NOROOT_LOCKED; - else { - log_error("[%s:%u] Failed to parse secure bits, ignoring: %s", filename, line, rvalue); - return 0; - } - } - - return 0; -} - -int config_parse_exec_bounding_set( - const char *filename, - unsigned line, - const char *section, - const char *lvalue, - int ltype, - const char *rvalue, - void *data, - void *userdata) { - - ExecContext *c = data; - char *w; - size_t l; - char *state; - bool invert = false; - uint64_t sum = 0; - - assert(filename); - assert(lvalue); - assert(rvalue); - assert(data); - - if (rvalue[0] == '~') { - invert = true; - rvalue++; - } - - /* Note that we store this inverted internally, since the - * kernel wants it like this. But we actually expose it - * non-inverted everywhere to have a fully normalized - * interface. */ - - FOREACH_WORD_QUOTED(w, l, rvalue, state) { - char *t; - int r; - cap_value_t cap; - - if (!(t = strndup(w, l))) - return -ENOMEM; - - r = cap_from_name(t, &cap); - free(t); - - if (r < 0) { - log_error("[%s:%u] Failed to parse capability bounding set, ignoring: %s", filename, line, rvalue); - return 0; - } - - sum |= ((uint64_t) 1ULL) << (uint64_t) cap; - } - - if (invert) - c->capability_bounding_set_drop |= sum; - else - c->capability_bounding_set_drop |= ~sum; - - return 0; -} - -int config_parse_exec_timer_slack_nsec( - const char *filename, - unsigned line, - const char *section, - const char *lvalue, - int ltype, - const char *rvalue, - void *data, - void *userdata) { - - ExecContext *c = data; - unsigned long u; - - assert(filename); - assert(lvalue); - assert(rvalue); - assert(data); - - if (safe_atolu(rvalue, &u) < 0) { - log_error("[%s:%u] Failed to parse time slack value, ignoring: %s", filename, line, rvalue); - return 0; - } - - c->timer_slack_nsec = u; - - return 0; -} - -int config_parse_limit( - const char *filename, - unsigned line, - const char *section, - const char *lvalue, - int ltype, - const char *rvalue, - void *data, - void *userdata) { - - struct rlimit **rl = data; - unsigned long long u; - - assert(filename); - assert(lvalue); - assert(rvalue); - assert(data); - - rl += ltype; - - if (streq(rvalue, "infinity")) - u = (unsigned long long) RLIM_INFINITY; - else if (safe_atollu(rvalue, &u) < 0) { - log_error("[%s:%u] Failed to parse resource value, ignoring: %s", filename, line, rvalue); - return 0; - } - - if (!*rl) - if (!(*rl = new(struct rlimit, 1))) - return -ENOMEM; - - (*rl)->rlim_cur = (*rl)->rlim_max = (rlim_t) u; - return 0; -} - -int config_parse_unit_cgroup( - const char *filename, - unsigned line, - const char *section, - const char *lvalue, - int ltype, - const char *rvalue, - void *data, - void *userdata) { - - Unit *u = userdata; - char *w; - size_t l; - char *state; - - FOREACH_WORD_QUOTED(w, l, rvalue, state) { - char *t, *k; - int r; - - t = strndup(w, l); - if (!t) - return -ENOMEM; - - k = unit_full_printf(u, t); - free(t); - - if (!k) - return -ENOMEM; - - t = cunescape(k); - free(k); - - if (!t) - return -ENOMEM; - - r = unit_add_cgroup_from_text(u, t); - free(t); - - if (r < 0) { - log_error("[%s:%u] Failed to parse cgroup value, ignoring: %s", filename, line, rvalue); - return 0; - } - } - - return 0; -} - -#ifdef HAVE_SYSV_COMPAT -int config_parse_sysv_priority( - const char *filename, - unsigned line, - const char *section, - const char *lvalue, - int ltype, - const char *rvalue, - void *data, - void *userdata) { - - int *priority = data; - int i; - - assert(filename); - assert(lvalue); - assert(rvalue); - assert(data); - - if (safe_atoi(rvalue, &i) < 0 || i < 0) { - log_error("[%s:%u] Failed to parse SysV start priority, ignoring: %s", filename, line, rvalue); - return 0; - } - - *priority = (int) i; - return 0; -} -#endif - -int config_parse_fsck_passno( - const char *filename, - unsigned line, - const char *section, - const char *lvalue, - int ltype, - const char *rvalue, - void *data, - void *userdata) { - - int *passno = data; - int i; - - assert(filename); - assert(lvalue); - assert(rvalue); - assert(data); - - if (safe_atoi(rvalue, &i) || i < 0) { - log_error("[%s:%u] Failed to parse fsck pass number, ignoring: %s", filename, line, rvalue); - return 0; - } - - *passno = (int) i; - return 0; -} - -DEFINE_CONFIG_PARSE_ENUM(config_parse_kill_mode, kill_mode, KillMode, "Failed to parse kill mode"); - -int config_parse_kill_signal( - const char *filename, - unsigned line, - const char *section, - const char *lvalue, - int ltype, - const char *rvalue, - void *data, - void *userdata) { - - int *sig = data; - int r; - - assert(filename); - assert(lvalue); - assert(rvalue); - assert(sig); - - if ((r = signal_from_string_try_harder(rvalue)) <= 0) { - log_error("[%s:%u] Failed to parse kill signal, ignoring: %s", filename, line, rvalue); - return 0; - } - - *sig = r; - return 0; -} - -int config_parse_exec_mount_flags( - const char *filename, - unsigned line, - const char *section, - const char *lvalue, - int ltype, - const char *rvalue, - void *data, - void *userdata) { - - ExecContext *c = data; - char *w; - size_t l; - char *state; - unsigned long flags = 0; - - assert(filename); - assert(lvalue); - assert(rvalue); - assert(data); - - FOREACH_WORD_QUOTED(w, l, rvalue, state) { - if (strncmp(w, "shared", MAX(l, 6U)) == 0) - flags |= MS_SHARED; - else if (strncmp(w, "slave", MAX(l, 5U)) == 0) - flags |= MS_SLAVE; - else if (strncmp(w, "private", MAX(l, 7U)) == 0) - flags |= MS_PRIVATE; - else { - log_error("[%s:%u] Failed to parse mount flags, ignoring: %s", filename, line, rvalue); - return 0; - } - } - - c->mount_flags = flags; - return 0; -} - -int config_parse_timer( - const char *filename, - unsigned line, - const char *section, - const char *lvalue, - int ltype, - const char *rvalue, - void *data, - void *userdata) { - - Timer *t = data; - usec_t u; - TimerValue *v; - TimerBase b; - - assert(filename); - assert(lvalue); - assert(rvalue); - assert(data); - - if ((b = timer_base_from_string(lvalue)) < 0) { - log_error("[%s:%u] Failed to parse timer base, ignoring: %s", filename, line, lvalue); - return 0; - } - - if (parse_usec(rvalue, &u) < 0) { - log_error("[%s:%u] Failed to parse timer value, ignoring: %s", filename, line, rvalue); - return 0; - } - - if (!(v = new0(TimerValue, 1))) - return -ENOMEM; - - v->base = b; - v->value = u; - - LIST_PREPEND(TimerValue, value, t->values, v); - - return 0; -} - -int config_parse_timer_unit( - const char *filename, - unsigned line, - const char *section, - const char *lvalue, - int ltype, - const char *rvalue, - void *data, - void *userdata) { - - Timer *t = data; - int r; - DBusError error; - Unit *u; - - assert(filename); - assert(lvalue); - assert(rvalue); - assert(data); - - dbus_error_init(&error); - - if (endswith(rvalue, ".timer")) { - log_error("[%s:%u] Unit cannot be of type timer, ignoring: %s", filename, line, rvalue); - return 0; - } - - r = manager_load_unit(UNIT(t)->manager, rvalue, NULL, NULL, &u); - if (r < 0) { - log_error("[%s:%u] Failed to load unit %s, ignoring: %s", filename, line, rvalue, bus_error(&error, r)); - dbus_error_free(&error); - return 0; - } - - unit_ref_set(&t->unit, u); - - return 0; -} - -int config_parse_path_spec( - const char *filename, - unsigned line, - const char *section, - const char *lvalue, - int ltype, - const char *rvalue, - void *data, - void *userdata) { - - Path *p = data; - PathSpec *s; - PathType b; - - assert(filename); - assert(lvalue); - assert(rvalue); - assert(data); - - if ((b = path_type_from_string(lvalue)) < 0) { - log_error("[%s:%u] Failed to parse path type, ignoring: %s", filename, line, lvalue); - return 0; - } - - if (!path_is_absolute(rvalue)) { - log_error("[%s:%u] Path is not absolute, ignoring: %s", filename, line, rvalue); - return 0; - } - - if (!(s = new0(PathSpec, 1))) - return -ENOMEM; - - if (!(s->path = strdup(rvalue))) { - free(s); - return -ENOMEM; - } - - path_kill_slashes(s->path); - - s->type = b; - s->inotify_fd = -1; - - LIST_PREPEND(PathSpec, spec, p->specs, s); - - return 0; -} - -int config_parse_path_unit( - const char *filename, - unsigned line, - const char *section, - const char *lvalue, - int ltype, - const char *rvalue, - void *data, - void *userdata) { - - Path *t = data; - int r; - DBusError error; - Unit *u; - - assert(filename); - assert(lvalue); - assert(rvalue); - assert(data); - - dbus_error_init(&error); - - if (endswith(rvalue, ".path")) { - log_error("[%s:%u] Unit cannot be of type path, ignoring: %s", filename, line, rvalue); - return 0; - } - - if ((r = manager_load_unit(UNIT(t)->manager, rvalue, NULL, &error, &u)) < 0) { - log_error("[%s:%u] Failed to load unit %s, ignoring: %s", filename, line, rvalue, bus_error(&error, r)); - dbus_error_free(&error); - return 0; - } - - unit_ref_set(&t->unit, u); - - return 0; -} - -int config_parse_socket_service( - const char *filename, - unsigned line, - const char *section, - const char *lvalue, - int ltype, - const char *rvalue, - void *data, - void *userdata) { - - Socket *s = data; - int r; - DBusError error; - Unit *x; - - assert(filename); - assert(lvalue); - assert(rvalue); - assert(data); - - dbus_error_init(&error); - - if (!endswith(rvalue, ".service")) { - log_error("[%s:%u] Unit must be of type service, ignoring: %s", filename, line, rvalue); - return 0; - } - - r = manager_load_unit(UNIT(s)->manager, rvalue, NULL, &error, &x); - if (r < 0) { - log_error("[%s:%u] Failed to load unit %s, ignoring: %s", filename, line, rvalue, bus_error(&error, r)); - dbus_error_free(&error); - return 0; - } - - unit_ref_set(&s->service, x); - - return 0; -} - -int config_parse_service_sockets( - const char *filename, - unsigned line, - const char *section, - const char *lvalue, - int ltype, - const char *rvalue, - void *data, - void *userdata) { - - Service *s = data; - int r; - char *state, *w; - size_t l; - - assert(filename); - assert(lvalue); - assert(rvalue); - assert(data); - - FOREACH_WORD_QUOTED(w, l, rvalue, state) { - char *t, *k; - - t = strndup(w, l); - if (!t) - return -ENOMEM; - - k = unit_name_printf(UNIT(s), t); - free(t); - - if (!k) - return -ENOMEM; - - if (!endswith(k, ".socket")) { - log_error("[%s:%u] Unit must be of type socket, ignoring: %s", filename, line, rvalue); - free(k); - continue; - } - - r = unit_add_two_dependencies_by_name(UNIT(s), UNIT_WANTS, UNIT_AFTER, k, NULL, true); - if (r < 0) - log_error("[%s:%u] Failed to add dependency on %s, ignoring: %s", filename, line, k, strerror(-r)); - - r = unit_add_dependency_by_name(UNIT(s), UNIT_TRIGGERED_BY, k, NULL, true); - if (r < 0) - return r; - - free(k); - } - - return 0; -} - -int config_parse_unit_env_file( - const char *filename, - unsigned line, - const char *section, - const char *lvalue, - int ltype, - const char *rvalue, - void *data, - void *userdata) { - - char ***env = data, **k; - Unit *u = userdata; - char *s; - - assert(filename); - assert(lvalue); - assert(rvalue); - assert(data); - - s = unit_full_printf(u, rvalue); - if (!s) - return -ENOMEM; - - if (!path_is_absolute(s[0] == '-' ? s + 1 : s)) { - log_error("[%s:%u] Path '%s' is not absolute, ignoring.", filename, line, s); - free(s); - return 0; - } - - k = strv_append(*env, s); - free(s); - if (!k) - return -ENOMEM; - - strv_free(*env); - *env = k; - - return 0; -} - -int config_parse_ip_tos( - const char *filename, - unsigned line, - const char *section, - 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); - - if ((x = ip_tos_from_string(rvalue)) < 0) - if (safe_atoi(rvalue, &x) < 0) { - log_error("[%s:%u] Failed to parse IP TOS value, ignoring: %s", filename, line, rvalue); - return 0; - } - - *ip_tos = x; - return 0; -} - -int config_parse_unit_condition_path( - const char *filename, - unsigned line, - const char *section, - const char *lvalue, - int ltype, - const char *rvalue, - void *data, - void *userdata) { - - ConditionType cond = ltype; - Unit *u = data; - bool trigger, negate; - Condition *c; - - assert(filename); - assert(lvalue); - assert(rvalue); - assert(data); - - trigger = rvalue[0] == '|'; - if (trigger) - rvalue++; - - negate = rvalue[0] == '!'; - if (negate) - rvalue++; - - if (!path_is_absolute(rvalue)) { - log_error("[%s:%u] Path in condition not absolute, ignoring: %s", filename, line, rvalue); - return 0; - } - - c = condition_new(cond, rvalue, trigger, negate); - if (!c) - return -ENOMEM; - - LIST_PREPEND(Condition, conditions, u->conditions, c); - return 0; -} - -int config_parse_unit_condition_string( - const char *filename, - unsigned line, - const char *section, - const char *lvalue, - int ltype, - const char *rvalue, - void *data, - void *userdata) { - - ConditionType cond = ltype; - Unit *u = data; - bool trigger, negate; - Condition *c; - - assert(filename); - assert(lvalue); - assert(rvalue); - assert(data); - - if ((trigger = rvalue[0] == '|')) - rvalue++; - - if ((negate = rvalue[0] == '!')) - rvalue++; - - if (!(c = condition_new(cond, rvalue, trigger, negate))) - return -ENOMEM; - - LIST_PREPEND(Condition, conditions, u->conditions, c); - return 0; -} - -int config_parse_unit_condition_null( - const char *filename, - unsigned line, - const char *section, - const char *lvalue, - int ltype, - const char *rvalue, - void *data, - void *userdata) { - - Unit *u = data; - Condition *c; - bool trigger, negate; - int b; - - assert(filename); - assert(lvalue); - assert(rvalue); - assert(data); - - if ((trigger = rvalue[0] == '|')) - rvalue++; - - if ((negate = rvalue[0] == '!')) - rvalue++; - - if ((b = parse_boolean(rvalue)) < 0) { - log_error("[%s:%u] Failed to parse boolean value in condition, ignoring: %s", filename, line, rvalue); - return 0; - } - - if (!b) - negate = !negate; - - if (!(c = condition_new(CONDITION_NULL, NULL, trigger, negate))) - return -ENOMEM; - - LIST_PREPEND(Condition, conditions, u->conditions, 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_start_limit_action, start_limit_action, StartLimitAction, "Failed to parse start limit action specifier"); - -int config_parse_unit_cgroup_attr( - const char *filename, - unsigned line, - const char *section, - const char *lvalue, - int ltype, - const char *rvalue, - void *data, - void *userdata) { - - Unit *u = data; - char **l; - int r; - - assert(filename); - assert(lvalue); - assert(rvalue); - assert(data); - - l = strv_split_quoted(rvalue); - if (!l) - return -ENOMEM; - - if (strv_length(l) != 2) { - log_error("[%s:%u] Failed to parse cgroup attribute value, ignoring: %s", filename, line, rvalue); - strv_free(l); - return 0; - } - - r = unit_add_cgroup_attribute(u, NULL, l[0], l[1], NULL); - strv_free(l); - - if (r < 0) { - log_error("[%s:%u] Failed to add cgroup attribute value, ignoring: %s", filename, line, rvalue); - return 0; - } - - return 0; -} - -int config_parse_unit_cpu_shares(const char *filename, unsigned line, const char *section, const char *lvalue, int ltype, const char *rvalue, void *data, void *userdata) { - Unit *u = data; - int r; - unsigned long ul; - char *t; - - assert(filename); - assert(lvalue); - assert(rvalue); - assert(data); - - if (safe_atolu(rvalue, &ul) < 0 || ul < 1) { - log_error("[%s:%u] Failed to parse CPU shares value, ignoring: %s", filename, line, rvalue); - return 0; - } - - if (asprintf(&t, "%lu", ul) < 0) - return -ENOMEM; - - r = unit_add_cgroup_attribute(u, "cpu", "cpu.shares", t, NULL); - free(t); - - if (r < 0) { - log_error("[%s:%u] Failed to add cgroup attribute value, ignoring: %s", filename, line, rvalue); - return 0; - } - - return 0; -} - -int config_parse_unit_memory_limit(const char *filename, unsigned line, const char *section, const char *lvalue, int ltype, const char *rvalue, void *data, void *userdata) { - Unit *u = data; - int r; - off_t sz; - char *t; - - assert(filename); - assert(lvalue); - assert(rvalue); - assert(data); - - if (parse_bytes(rvalue, &sz) < 0 || sz <= 0) { - log_error("[%s:%u] Failed to parse memory limit value, ignoring: %s", filename, line, rvalue); - return 0; - } - - if (asprintf(&t, "%llu", (unsigned long long) sz) < 0) - return -ENOMEM; - - r = unit_add_cgroup_attribute(u, - "memory", - streq(lvalue, "MemorySoftLimit") ? "memory.soft_limit_in_bytes" : "memory.limit_in_bytes", - t, NULL); - free(t); - - if (r < 0) { - log_error("[%s:%u] Failed to add cgroup attribute value, ignoring: %s", filename, line, rvalue); - return 0; - } - - return 0; -} - -static int device_map(const char *controller, const char *name, const char *value, char **ret) { - char **l; - - assert(controller); - assert(name); - assert(value); - assert(ret); - - l = strv_split_quoted(value); - if (!l) - return -ENOMEM; - - assert(strv_length(l) >= 1); - - if (streq(l[0], "*")) { - - if (asprintf(ret, "a *:*%s%s", - isempty(l[1]) ? "" : " ", strempty(l[1])) < 0) { - strv_free(l); - return -ENOMEM; - } - - } else { - struct stat st; - - if (stat(l[0], &st) < 0) { - log_warning("Couldn't stat device %s", l[0]); - strv_free(l); - return -errno; - } - - if (!S_ISCHR(st.st_mode) && !S_ISBLK(st.st_mode)) { - log_warning("%s is not a device.", l[0]); - strv_free(l); - return -ENODEV; - } - - if (asprintf(ret, "%c %u:%u%s%s", - S_ISCHR(st.st_mode) ? 'c' : 'b', - major(st.st_rdev), minor(st.st_rdev), - isempty(l[1]) ? "" : " ", strempty(l[1])) < 0) { - - strv_free(l); - return -ENOMEM; - } - } - - strv_free(l); - return 0; -} - -int config_parse_unit_device_allow(const char *filename, unsigned line, const char *section, const char *lvalue, int ltype, const char *rvalue, void *data, void *userdata) { - Unit *u = data; - char **l; - int r; - unsigned k; - - assert(filename); - assert(lvalue); - assert(rvalue); - assert(data); - - l = strv_split_quoted(rvalue); - if (!l) - return -ENOMEM; - - k = strv_length(l); - if (k < 1 || k > 2) { - log_error("[%s:%u] Failed to parse device value, ignoring: %s", filename, line, rvalue); - strv_free(l); - return 0; - } - - if (!streq(l[0], "*") && !path_startswith(l[0], "/dev")) { - log_error("[%s:%u] Device node path not absolute, ignoring: %s", filename, line, rvalue); - strv_free(l); - return 0; - } - - if (!isempty(l[1]) && !in_charset(l[1], "rwm")) { - log_error("[%s:%u] Device access string invalid, ignoring: %s", filename, line, rvalue); - strv_free(l); - return 0; - } - strv_free(l); - - r = unit_add_cgroup_attribute(u, "devices", - streq(lvalue, "DeviceAllow") ? "devices.allow" : "devices.deny", - rvalue, device_map); - - if (r < 0) { - log_error("[%s:%u] Failed to add cgroup attribute value, ignoring: %s", filename, line, rvalue); - return 0; - } - - return 0; -} - -static int blkio_map(const char *controller, const char *name, const char *value, char **ret) { - struct stat st; - char **l; - dev_t d; - - assert(controller); - assert(name); - assert(value); - assert(ret); - - l = strv_split_quoted(value); - if (!l) - return -ENOMEM; - - assert(strv_length(l) == 2); - - if (stat(l[0], &st) < 0) { - log_warning("Couldn't stat device %s", l[0]); - strv_free(l); - return -errno; - } - - if (S_ISBLK(st.st_mode)) - d = 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 */ - d = st.st_dev; - - /* If this is a partition, try to get the originating - * block device */ - block_get_whole_disk(d, &d); - } else { - log_warning("%s is not a block device and file system block device cannot be determined or is not local.", l[0]); - strv_free(l); - return -ENODEV; - } - - if (asprintf(ret, "%u:%u %s", major(d), minor(d), l[1]) < 0) { - strv_free(l); - return -ENOMEM; - } - - strv_free(l); - return 0; -} - -int config_parse_unit_blkio_weight(const char *filename, unsigned line, const char *section, const char *lvalue, int ltype, const char *rvalue, void *data, void *userdata) { - Unit *u = data; - int r; - unsigned long ul; - const char *device = NULL, *weight; - unsigned k; - char *t, **l; - - assert(filename); - assert(lvalue); - assert(rvalue); - assert(data); - - l = strv_split_quoted(rvalue); - if (!l) - return -ENOMEM; - - k = strv_length(l); - if (k < 1 || k > 2) { - log_error("[%s:%u] Failed to parse weight value, ignoring: %s", filename, line, rvalue); - strv_free(l); - return 0; - } - - if (k == 1) - weight = l[0]; - else { - device = l[0]; - weight = l[1]; - } - - if (device && !path_is_absolute(device)) { - log_error("[%s:%u] Failed to parse block device node value, ignoring: %s", filename, line, rvalue); - strv_free(l); - return 0; - } - - if (safe_atolu(weight, &ul) < 0 || ul < 10 || ul > 1000) { - log_error("[%s:%u] Failed to parse block IO weight value, ignoring: %s", filename, line, rvalue); - strv_free(l); - return 0; - } - - if (device) - r = asprintf(&t, "%s %lu", device, ul); - else - r = asprintf(&t, "%lu", ul); - strv_free(l); - - if (r < 0) - return -ENOMEM; - - if (device) - r = unit_add_cgroup_attribute(u, "blkio", "blkio.weight_device", t, blkio_map); - else - r = unit_add_cgroup_attribute(u, "blkio", "blkio.weight", t, NULL); - free(t); - - if (r < 0) { - log_error("[%s:%u] Failed to add cgroup attribute value, ignoring: %s", filename, line, rvalue); - return 0; - } - - return 0; -} - -int config_parse_unit_blkio_bandwidth(const char *filename, unsigned line, const char *section, const char *lvalue, int ltype, const char *rvalue, void *data, void *userdata) { - Unit *u = data; - int r; - off_t bytes; - unsigned k; - char *t, **l; - - assert(filename); - assert(lvalue); - assert(rvalue); - assert(data); - - l = strv_split_quoted(rvalue); - if (!l) - return -ENOMEM; - - k = strv_length(l); - if (k != 2) { - log_error("[%s:%u] Failed to parse bandwidth value, ignoring: %s", filename, line, rvalue); - strv_free(l); - return 0; - } - - if (!path_is_absolute(l[0])) { - log_error("[%s:%u] Failed to parse block device node value, ignoring: %s", filename, line, rvalue); - strv_free(l); - return 0; - } - - if (parse_bytes(l[1], &bytes) < 0 || bytes <= 0) { - log_error("[%s:%u] Failed to parse block IO bandwith value, ignoring: %s", filename, line, rvalue); - strv_free(l); - return 0; - } - - r = asprintf(&t, "%s %llu", l[0], (unsigned long long) bytes); - strv_free(l); - - if (r < 0) - return -ENOMEM; - - r = unit_add_cgroup_attribute(u, "blkio", - streq(lvalue, "BlockIOReadBandwidth") ? "blkio.read_bps_device" : "blkio.write_bps_device", - t, blkio_map); - free(t); - - if (r < 0) { - log_error("[%s:%u] Failed to add cgroup attribute value, ignoring: %s", filename, line, rvalue); - return 0; - } - - return 0; -} - - -#define FOLLOW_MAX 8 - -static int open_follow(char **filename, FILE **_f, Set *names, char **_final) { - unsigned c = 0; - int fd, r; - FILE *f; - char *id = NULL; - - assert(filename); - assert(*filename); - assert(_f); - assert(names); - - /* This will update the filename pointer if the loaded file is - * reached by a symlink. The old string will be freed. */ - - for (;;) { - char *target, *name; - - if (c++ >= FOLLOW_MAX) - return -ELOOP; - - path_kill_slashes(*filename); - - /* Add the file name we are currently looking at to - * the names of this unit, but only if it is a valid - * unit name. */ - name = file_name_from_path(*filename); - - if (unit_name_is_valid(name, true)) { - - id = set_get(names, name); - if (!id) { - id = strdup(name); - if (!id) - return -ENOMEM; - - r = set_put(names, id); - if (r < 0) { - free(id); - return r; - } - } - } - - /* Try to open the file name, but don't if its a symlink */ - if ((fd = open(*filename, O_RDONLY|O_CLOEXEC|O_NOCTTY|O_NOFOLLOW)) >= 0) - break; - - if (errno != ELOOP) - return -errno; - - /* Hmm, so this is a symlink. Let's read the name, and follow it manually */ - if ((r = readlink_and_make_absolute(*filename, &target)) < 0) - return r; - - free(*filename); - *filename = target; - } - - if (!(f = fdopen(fd, "re"))) { - r = -errno; - close_nointr_nofail(fd); - return r; - } - - *_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 */ - if ((r = unit_merge_by_name(*u, k)) < 0) { - Unit *other; - - /* Hmm, we couldn't merge the other unit into - * ours? Then let's try it the other way - * round */ - - other = manager_get_unit((*u)->manager, k); - free(k); - - if (other) - if ((r = unit_merge(other, *u)) >= 0) { - *u = other; - return merge_by_names(u, names, NULL); - } - - return r; - } - - if (id == k) - unit_choose_id(*u, id); - - free(k); - } - - return 0; -} - -static int load_from_path(Unit *u, const char *path) { - int r; - Set *symlink_names; - FILE *f = NULL; - char *filename = NULL, *id = NULL; - Unit *merged; - struct stat st; - - assert(u); - assert(path); - - symlink_names = set_new(string_hash_func, string_compare_func); - if (!symlink_names) - return -ENOMEM; - - if (path_is_absolute(path)) { - - if (!(filename = strdup(path))) { - r = -ENOMEM; - goto finish; - } - - if ((r = open_follow(&filename, &f, symlink_names, &id)) < 0) { - free(filename); - filename = NULL; - - if (r != -ENOENT) - goto finish; - } - - } else { - char **p; - - STRV_FOREACH(p, u->manager->lookup_paths.unit_path) { - - /* Instead of opening the path right away, we manually - * follow all symlinks and add their name to our unit - * name set while doing so */ - if (!(filename = path_make_absolute(path, *p))) { - r = -ENOMEM; - goto finish; - } - - 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) { - char *sn; - - free(filename); - filename = NULL; - - if (r != -ENOENT) - goto finish; - - /* Empty the symlink names for the next run */ - while ((sn = set_steal_first(symlink_names))) - free(sn); - - continue; - } - - break; - } - } - - if (!filename) { - /* Hmm, no suitable file found? */ - r = 0; - goto finish; - } - - merged = u; - if ((r = merge_by_names(&merged, symlink_names, id)) < 0) - goto finish; - - if (merged != u) { - u->load_state = UNIT_MERGED; - r = 0; - goto finish; - } - - zero(st); - if (fstat(fileno(f), &st) < 0) { - r = -errno; - goto finish; - } - - if (null_or_empty(&st)) - u->load_state = UNIT_MASKED; - else { - /* Now, parse the file contents */ - r = config_parse(filename, f, UNIT_VTABLE(u)->sections, config_item_perf_lookup, (void*) load_fragment_gperf_lookup, false, u); - if (r < 0) - goto finish; - - u->load_state = UNIT_LOADED; - } - - free(u->fragment_path); - u->fragment_path = filename; - filename = NULL; - - u->fragment_mtime = timespec_load(&st.st_mtim); - - r = 0; - -finish: - set_free_free(symlink_names); - free(filename); - - if (f) - fclose(f); - - return r; -} - -int unit_load_fragment(Unit *u) { - int r; - Iterator i; - const char *t; - - assert(u); - assert(u->load_state == UNIT_STUB); - assert(u->id); - - /* 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 */ - if ((r = load_from_path(u, u->id)) < 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; - - if ((r = load_from_path(u, t)) < 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) { - - if ((r = load_from_path(u, u->fragment_path)) < 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. */ - free(u->fragment_path); - u->fragment_path = NULL; - } - } - - /* Look for a template */ - if (u->load_state == UNIT_STUB && u->instance) { - char *k; - - if (!(k = unit_name_template(u->id))) - return -ENOMEM; - - r = load_from_path(u, k); - free(k); - - if (r < 0) - return r; - - if (u->load_state == UNIT_STUB) - SET_FOREACH(t, u->names, i) { - - if (t == u->id) - continue; - - if (!(k = unit_name_template(t))) - return -ENOMEM; - - r = load_from_path(u, k); - free(k); - - 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[] = { - { config_parse_int, "INTEGER" }, - { config_parse_unsigned, "UNSIGNED" }, - { config_parse_bytes_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_facility, "FACILITY" }, - { config_parse_level, "LEVEL" }, - { config_parse_exec_capabilities, "CAPABILITIES" }, - { config_parse_exec_secure_bits, "SECUREBITS" }, - { config_parse_exec_bounding_set, "BOUNDINGSET" }, - { config_parse_exec_timer_slack_nsec, "TIMERSLACK" }, - { config_parse_limit, "LIMIT" }, - { config_parse_unit_cgroup, "CGROUP [...]" }, - { config_parse_unit_deps, "UNIT [...]" }, - { config_parse_unit_names, "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" }, -#else - { config_parse_warn_compat, "NOTSUPPORTED" }, -#endif - { config_parse_kill_mode, "KILLMODE" }, - { config_parse_kill_signal, "SIGNAL" }, - { config_parse_socket_listen, "SOCKET [...]" }, - { config_parse_socket_bind, "SOCKETBIND" }, - { config_parse_socket_bindtodevice, "NETWORKINTERFACE" }, - { config_parse_usec, "SECONDS" }, - { config_parse_path_strv, "PATH [...]" }, - { config_parse_exec_mount_flags, "MOUNTFLAG [...]" }, - { config_parse_unit_string_printf, "STRING" }, - { config_parse_timer, "TIMER" }, - { config_parse_timer_unit, "NAME" }, - { config_parse_path_spec, "PATH" }, - { config_parse_path_unit, "UNIT" }, - { 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" }, - }; - - 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 || strncmp(prev, i, prefix_len+1) != 0) { - 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/load-fragment.h b/src/load-fragment.h deleted file mode 100644 index 79fc76da92..0000000000 --- a/src/load-fragment.h +++ /dev/null @@ -1,91 +0,0 @@ -/*-*- Mode: C; c-basic-offset: 8; indent-tabs-mode: nil -*-*/ - -#ifndef fooloadfragmenthfoo -#define fooloadfragmenthfoo - -/*** - This file is part of systemd. - - Copyright 2010 Lennart Poettering - - systemd is free software; you can redistribute it and/or modify it - under the terms of the GNU General Public License as published by - the Free Software Foundation; either version 2 of the License, or - (at your option) any later version. - - systemd is distributed in the hope that 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 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 *filename, unsigned line, const char *section, const char *lvalue, int ltype, const char *rvalue, void *data, void *userdata); -int config_parse_unit_deps(const char *filename, unsigned line, const char *section, const char *lvalue, int ltype, const char *rvalue, void *data, void *userdata); -int config_parse_unit_names(const char *filename, unsigned line, const char *section, const char *lvalue, int ltype, const char *rvalue, void *data, void *userdata); -int config_parse_unit_string_printf(const char *filename, unsigned line, const char *section, const char *lvalue, int ltype, const char *rvalue, void *data, void *userdata); -int config_parse_unit_strv_printf(const char *filename, unsigned line, const char *section, const char *lvalue, int ltype, const char *rvalue, void *data, void *userdata); -int config_parse_unit_path_printf(const char *filename, unsigned line, const char *section, const char *lvalue, int ltype, const char *rvalue, void *data, void *userdata); -int config_parse_socket_listen(const char *filename, unsigned line, const char *section, const char *lvalue, int ltype, const char *rvalue, void *data, void *userdata); -int config_parse_socket_bind(const char *filename, unsigned line, const char *section, const char *lvalue, int ltype, const char *rvalue, void *data, void *userdata); -int config_parse_exec_nice(const char *filename, unsigned line, const char *section, const char *lvalue, int ltype, const char *rvalue, void *data, void *userdata); -int config_parse_exec_oom_score_adjust(const char *filename, unsigned line, const char *section, const char *lvalue, int ltype, const char *rvalue, void *data, void *userdata); -int config_parse_exec(const char *filename, unsigned line, const char *section, const char *lvalue, int ltype, const char *rvalue, void *data, void *userdata); -int config_parse_service_type(const char *filename, unsigned line, const char *section, const char *lvalue, int ltype, const char *rvalue, void *data, void *userdata); -int config_parse_service_restart(const char *filename, unsigned line, const char *section, const char *lvalue, int ltype, const char *rvalue, void *data, void *userdata); -int config_parse_socket_bindtodevice(const char *filename, unsigned line, const char *section, const char *lvalue, int ltype, const char *rvalue, void *data, void *userdata); -int config_parse_output(const char *filename, unsigned line, const char *section, const char *lvalue, int ltype, const char *rvalue, void *data, void *userdata); -int config_parse_input(const char *filename, unsigned line, const char *section, const char *lvalue, int ltype, const char *rvalue, void *data, void *userdata); -int config_parse_facility(const char *filename, unsigned line, const char *section, const char *lvalue, int ltype, const char *rvalue, void *data, void *userdata); -int config_parse_level(const char *filename, unsigned line, const char *section, const char *lvalue, int ltype, const char *rvalue, void *data, void *userdata); -int config_parse_exec_io_class(const char *filename, unsigned line, const char *section, const char *lvalue, int ltype, const char *rvalue, void *data, void *userdata); -int config_parse_exec_io_priority(const char *filename, unsigned line, const char *section, const char *lvalue, int ltype, const char *rvalue, void *data, void *userdata); -int config_parse_exec_cpu_sched_policy(const char *filename, unsigned line, const char *section, const char *lvalue, int ltype, const char *rvalue, void *data, void *userdata); -int config_parse_exec_cpu_sched_prio(const char *filename, unsigned line, const char *section, const char *lvalue, int ltype, const char *rvalue, void *data, void *userdata); -int config_parse_exec_cpu_affinity(const char *filename, unsigned line, const char *section, const char *lvalue, int ltype, const char *rvalue, void *data, void *userdata); -int config_parse_exec_capabilities(const char *filename, unsigned line, const char *section, const char *lvalue, int ltype, const char *rvalue, void *data, void *userdata); -int config_parse_exec_secure_bits(const char *filename, unsigned line, const char *section, const char *lvalue, int ltype, const char *rvalue, void *data, void *userdata); -int config_parse_exec_bounding_set(const char *filename, unsigned line, const char *section, const char *lvalue, int ltype, const char *rvalue, void *data, void *userdata); -int config_parse_exec_timer_slack_nsec(const char *filename, unsigned line, const char *section, const char *lvalue, int ltype, const char *rvalue, void *data, void *userdata); -int config_parse_limit(const char *filename, unsigned line, const char *section, const char *lvalue, int ltype, const char *rvalue, void *data, void *userdata); -int config_parse_unit_cgroup(const char *filename, unsigned line, const char *section, const char *lvalue, int ltype, const char *rvalue, void *data, void *userdata); -int config_parse_sysv_priority(const char *filename, unsigned line, const char *section, const char *lvalue, int ltype, const char *rvalue, void *data, void *userdata); -int config_parse_fsck_passno(const char *filename, unsigned line, const char *section, const char *lvalue, int ltype, const char *rvalue, void *data, void *userdata); -int config_parse_kill_signal(const char *filename, unsigned line, const char *section, const char *lvalue, int ltype, const char *rvalue, void *data, void *userdata); -int config_parse_exec_mount_flags(const char *filename, unsigned line, const char *section, const char *lvalue, int ltype, const char *rvalue, void *data, void *userdata); -int config_parse_timer(const char *filename, unsigned line, const char *section, const char *lvalue, int ltype, const char *rvalue, void *data, void *userdata); -int config_parse_timer_unit(const char *filename, unsigned line, const char *section, const char *lvalue, int ltype, const char *rvalue, void *data, void *userdata); -int config_parse_path_spec(const char *filename, unsigned line, const char *section, const char *lvalue, int ltype, const char *rvalue, void *data, void *userdata); -int config_parse_path_unit(const char *filename, unsigned line, const char *section, const char *lvalue, int ltype, const char *rvalue, void *data, void *userdata); -int config_parse_socket_service(const char *filename, unsigned line, const char *section, const char *lvalue, int ltype, const char *rvalue, void *data, void *userdata); -int config_parse_service_sockets(const char *filename, unsigned line, const char *section, const char *lvalue, int ltype, const char *rvalue, void *data, void *userdata); -int config_parse_unit_env_file(const char *filename, unsigned line, const char *section, const char *lvalue, int ltype, const char *rvalue, void *data, void *userdata); -int config_parse_ip_tos(const char *filename, unsigned line, const char *section, const char *lvalue, int ltype, const char *rvalue, void *data, void *userdata); -int config_parse_unit_condition_path(const char *filename, unsigned line, const char *section, const char *lvalue, int ltype, const char *rvalue, void *data, void *userdata); -int config_parse_unit_condition_string(const char *filename, unsigned line, const char *section, const char *lvalue, int ltype, const char *rvalue, void *data, void *userdata); -int config_parse_unit_condition_null(const char *filename, unsigned line, const char *section, const char *lvalue, int ltype, const char *rvalue, void *data, void *userdata); -int config_parse_kill_mode(const char *filename, unsigned line, const char *section, const char *lvalue, int ltype, const char *rvalue, void *data, void *userdata); -int config_parse_notify_access(const char *filename, unsigned line, const char *section, const char *lvalue, int ltype, const char *rvalue, void *data, void *userdata); -int config_parse_start_limit_action(const char *filename, unsigned line, const char *section, const char *lvalue, int ltype, const char *rvalue, void *data, void *userdata); -int config_parse_unit_cgroup_attr(const char *filename, unsigned line, const char *section, const char *lvalue, int ltype, const char *rvalue, void *data, void *userdata); -int config_parse_unit_cpu_shares(const char *filename, unsigned line, const char *section, const char *lvalue, int ltype, const char *rvalue, void *data, void *userdata); -int config_parse_unit_memory_limit(const char *filename, unsigned line, const char *section, const char *lvalue, int ltype, const char *rvalue, void *data, void *userdata); -int config_parse_unit_device_allow(const char *filename, unsigned line, const char *section, const char *lvalue, int ltype, const char *rvalue, void *data, void *userdata); -int config_parse_unit_blkio_weight(const char *filename, unsigned line, const char *section, const char *lvalue, int ltype, const char *rvalue, void *data, void *userdata); -int config_parse_unit_blkio_bandwidth(const char *filename, unsigned line, const char *section, 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[]; - -#endif diff --git a/src/locale-setup.c b/src/locale-setup.c deleted file mode 100644 index 7f692e9c5d..0000000000 --- a/src/locale-setup.c +++ /dev/null @@ -1,251 +0,0 @@ -/*-*- Mode: C; c-basic-offset: 8; indent-tabs-mode: nil -*-*/ - -/*** - This file is part of systemd. - - Copyright 2010 Lennart Poettering - - systemd is free software; you can redistribute it and/or modify it - under the terms of the GNU General Public License as published by - the Free Software Foundation; either version 2 of the License, or - (at your option) any later version. - - systemd is distributed in the hope that 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 General Public License - along with systemd; If not, see . -***/ - -#include -#include -#include - -#include "locale-setup.h" -#include "util.h" -#include "macro.h" -#include "virt.h" - -enum { - /* We don't list LC_ALL here on purpose. People should be - * using LANG instead. */ - - VARIABLE_LANG, - VARIABLE_LANGUAGE, - VARIABLE_LC_CTYPE, - VARIABLE_LC_NUMERIC, - VARIABLE_LC_TIME, - VARIABLE_LC_COLLATE, - VARIABLE_LC_MONETARY, - VARIABLE_LC_MESSAGES, - VARIABLE_LC_PAPER, - VARIABLE_LC_NAME, - VARIABLE_LC_ADDRESS, - VARIABLE_LC_TELEPHONE, - VARIABLE_LC_MEASUREMENT, - VARIABLE_LC_IDENTIFICATION, - _VARIABLE_MAX -}; - -static const char * const variable_names[_VARIABLE_MAX] = { - [VARIABLE_LANG] = "LANG", - [VARIABLE_LANGUAGE] = "LANGUAGE", - [VARIABLE_LC_CTYPE] = "LC_CTYPE", - [VARIABLE_LC_NUMERIC] = "LC_NUMERIC", - [VARIABLE_LC_TIME] = "LC_TIME", - [VARIABLE_LC_COLLATE] = "LC_COLLATE", - [VARIABLE_LC_MONETARY] = "LC_MONETARY", - [VARIABLE_LC_MESSAGES] = "LC_MESSAGES", - [VARIABLE_LC_PAPER] = "LC_PAPER", - [VARIABLE_LC_NAME] = "LC_NAME", - [VARIABLE_LC_ADDRESS] = "LC_ADDRESS", - [VARIABLE_LC_TELEPHONE] = "LC_TELEPHONE", - [VARIABLE_LC_MEASUREMENT] = "LC_MEASUREMENT", - [VARIABLE_LC_IDENTIFICATION] = "LC_IDENTIFICATION" -}; - -int locale_setup(void) { - char *variables[_VARIABLE_MAX]; - int r = 0, i; - - zero(variables); - - if (detect_container(NULL) <= 0) - if ((r = parse_env_file("/proc/cmdline", WHITESPACE, -#if defined(TARGET_FEDORA) || defined(TARGET_MEEGO) - "LANG", &variables[VARIABLE_LANG], -#endif - "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)) < 0) { - - if (r != -ENOENT) - log_warning("Failed to read /proc/cmdline: %s", strerror(-r)); - } - - /* 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)) < 0) { - - if (r != -ENOENT) - log_warning("Failed to read /etc/locale.conf: %s", strerror(-r)); - } - -#if defined(TARGET_FEDORA) || defined(TARGET_ALTLINUX) || defined(TARGET_MEEGO) - if (r <= 0 && - (r = parse_env_file("/etc/sysconfig/i18n", NEWLINE, - "LANG", &variables[VARIABLE_LANG], - NULL)) < 0) { - - if (r != -ENOENT) - log_warning("Failed to read /etc/sysconfig/i18n: %s", strerror(-r)); - } - -#elif defined(TARGET_SUSE) - if (r <= 0 && - (r = parse_env_file("/etc/sysconfig/language", NEWLINE, - "RC_LANG", &variables[VARIABLE_LANG], - NULL)) < 0) { - - if (r != -ENOENT) - log_warning("Failed to read /etc/sysconfig/language: %s", strerror(-r)); - } - -#elif defined(TARGET_DEBIAN) || defined(TARGET_UBUNTU) || defined(TARGET_ANGSTROM) - if (r <= 0 && - (r = parse_env_file("/etc/default/locale", NEWLINE, - "LANG", &variables[VARIABLE_LANG], - "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)) < 0) { - - if (r != -ENOENT) - log_warning("Failed to read /etc/default/locale: %s", strerror(-r)); - } - -#elif defined(TARGET_ARCH) - if (r <= 0 && - (r = parse_env_file("/etc/rc.conf", NEWLINE, - "LOCALE", &variables[VARIABLE_LANG], - NULL)) < 0) { - - if (r != -ENOENT) - log_warning("Failed to read /etc/rc.conf: %s", strerror(-r)); - } - -#elif defined(TARGET_GENTOO) - /* Gentoo's openrc expects locale variables in /etc/env.d/ - * These files are later compiled by env-update into shell - * export commands at /etc/profile.env, with variables being - * exported by openrc's runscript (so /etc/init.d/) - */ - if (r <= 0 && - (r = parse_env_file("/etc/profile.env", NEWLINE, - "export LANG", &variables[VARIABLE_LANG], - "export LC_CTYPE", &variables[VARIABLE_LC_CTYPE], - "export LC_NUMERIC", &variables[VARIABLE_LC_NUMERIC], - "export LC_TIME", &variables[VARIABLE_LC_TIME], - "export LC_COLLATE", &variables[VARIABLE_LC_COLLATE], - "export LC_MONETARY", &variables[VARIABLE_LC_MONETARY], - "export LC_MESSAGES", &variables[VARIABLE_LC_MESSAGES], - "export LC_PAPER", &variables[VARIABLE_LC_PAPER], - "export LC_NAME", &variables[VARIABLE_LC_NAME], - "export LC_ADDRESS", &variables[VARIABLE_LC_ADDRESS], - "export LC_TELEPHONE", &variables[VARIABLE_LC_TELEPHONE], - "export LC_MEASUREMENT", &variables[VARIABLE_LC_MEASUREMENT], - "export LC_IDENTIFICATION", &variables[VARIABLE_LC_IDENTIFICATION], - NULL)) < 0) { - - if (r != -ENOENT) - log_warning("Failed to read /etc/profile.env: %s", strerror(-r)); - } -#elif defined(TARGET_MANDRIVA) || defined(TARGET_MAGEIA ) - if (r <= 0 && - (r = parse_env_file("/etc/sysconfig/i18n", NEWLINE, - "LANG", &variables[VARIABLE_LANG], - "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)) < 0) { - - if (r != -ENOENT) - log_warning("Failed to read /etc/sysconfig/i18n: %s", strerror(-r)); - } - -#endif - - if (!variables[VARIABLE_LANG]) { - if (!(variables[VARIABLE_LANG] = strdup("C"))) { - r = -ENOMEM; - goto finish; - } - } - - for (i = 0; i < _VARIABLE_MAX; i++) { - - if (variables[i]) { - if (setenv(variable_names[i], variables[i], 1) < 0) { - r = -errno; - goto finish; - } - } else - unsetenv(variable_names[i]); - } - - r = 0; - -finish: - for (i = 0; i < _VARIABLE_MAX; i++) - free(variables[i]); - - return r; -} diff --git a/src/locale-setup.h b/src/locale-setup.h deleted file mode 100644 index 09a6bc682d..0000000000 --- a/src/locale-setup.h +++ /dev/null @@ -1,27 +0,0 @@ -/*-*- Mode: C; c-basic-offset: 8; indent-tabs-mode: nil -*-*/ - -#ifndef foolocalesetuphfoo -#define foolocalesetuphfoo - -/*** - This file is part of systemd. - - Copyright 2010 Lennart Poettering - - systemd is free software; you can redistribute it and/or modify it - under the terms of the GNU General Public License as published by - the Free Software Foundation; either version 2 of the License, or - (at your option) any later version. - - systemd is distributed in the hope that 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 General Public License - along with systemd; If not, see . -***/ - -int locale_setup(void); - -#endif diff --git a/src/manager.c b/src/manager.c deleted file mode 100644 index 312527aa9c..0000000000 --- a/src/manager.c +++ /dev/null @@ -1,3235 +0,0 @@ -/*-*- Mode: C; c-basic-offset: 8; indent-tabs-mode: nil -*-*/ - -/*** - This file is part of systemd. - - Copyright 2010 Lennart Poettering - - systemd is free software; you can redistribute it and/or modify it - under the terms of the GNU General Public License as published by - the Free Software Foundation; either version 2 of the License, or - (at your option) any later version. - - systemd is distributed in the hope that 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 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 - -#ifdef HAVE_AUDIT -#include -#endif - -#include - -#include "manager.h" -#include "hashmap.h" -#include "macro.h" -#include "strv.h" -#include "log.h" -#include "util.h" -#include "mkdir.h" -#include "ratelimit.h" -#include "cgroup.h" -#include "mount-setup.h" -#include "unit-name.h" -#include "dbus-unit.h" -#include "dbus-job.h" -#include "missing.h" -#include "path-lookup.h" -#include "special.h" -#include "bus-errors.h" -#include "exit-status.h" -#include "virt.h" -#include "watchdog.h" - -/* As soon as 16 units are in our GC queue, make sure to run a gc sweep */ -#define GC_QUEUE_ENTRIES_MAX 16 - -/* As soon as 5s passed since a unit was added to our GC queue, make sure to run a gc sweep */ -#define GC_QUEUE_USEC_MAX (10*USEC_PER_SEC) - -/* Where clients shall send notification messages to */ -#define NOTIFY_SOCKET_SYSTEM "/run/systemd/notify" -#define NOTIFY_SOCKET_USER "@/org/freedesktop/systemd1/notify" - -static int manager_setup_notify(Manager *m) { - union { - struct sockaddr sa; - struct sockaddr_un un; - } sa; - struct epoll_event ev; - int one = 1, r; - mode_t u; - - assert(m); - - m->notify_watch.type = WATCH_NOTIFY; - if ((m->notify_watch.fd = socket(AF_UNIX, SOCK_DGRAM|SOCK_CLOEXEC|SOCK_NONBLOCK, 0)) < 0) { - log_error("Failed to allocate notification socket: %m"); - return -errno; - } - - zero(sa); - sa.sa.sa_family = AF_UNIX; - - if (getpid() != 1) - snprintf(sa.un.sun_path, sizeof(sa.un.sun_path), NOTIFY_SOCKET_USER "/%llu", random_ull()); - else { - unlink(NOTIFY_SOCKET_SYSTEM); - strncpy(sa.un.sun_path, NOTIFY_SOCKET_SYSTEM, sizeof(sa.un.sun_path)); - } - - if (sa.un.sun_path[0] == '@') - sa.un.sun_path[0] = 0; - - u = umask(0111); - r = bind(m->notify_watch.fd, &sa.sa, offsetof(struct sockaddr_un, sun_path) + 1 + strlen(sa.un.sun_path+1)); - umask(u); - - if (r < 0) { - log_error("bind() failed: %m"); - return -errno; - } - - if (setsockopt(m->notify_watch.fd, SOL_SOCKET, SO_PASSCRED, &one, sizeof(one)) < 0) { - log_error("SO_PASSCRED failed: %m"); - return -errno; - } - - zero(ev); - ev.events = EPOLLIN; - ev.data.ptr = &m->notify_watch; - - if (epoll_ctl(m->epoll_fd, EPOLL_CTL_ADD, m->notify_watch.fd, &ev) < 0) - return -errno; - - if (sa.un.sun_path[0] == 0) - sa.un.sun_path[0] = '@'; - - if (!(m->notify_socket = strdup(sa.un.sun_path))) - return -ENOMEM; - - log_debug("Using notification socket %s", m->notify_socket); - - return 0; -} - -static int enable_special_signals(Manager *m) { - int fd; - - assert(m); - - /* Enable that we get SIGINT on control-alt-del */ - if (reboot(RB_DISABLE_CAD) < 0) - log_warning("Failed to enable ctrl-alt-del handling: %m"); - - if ((fd = open_terminal("/dev/tty0", O_RDWR|O_NOCTTY|O_CLOEXEC)) < 0) - log_warning("Failed to open /dev/tty0: %m"); - else { - /* Enable that we get SIGWINCH on kbrequest */ - if (ioctl(fd, KDSIGACCEPT, SIGWINCH) < 0) - log_warning("Failed to enable kbrequest handling: %s", strerror(errno)); - - close_nointr_nofail(fd); - } - - return 0; -} - -static int manager_setup_signals(Manager *m) { - sigset_t mask; - struct epoll_event ev; - struct sigaction sa; - - assert(m); - - /* We are not interested in SIGSTOP and friends. */ - zero(sa); - sa.sa_handler = SIG_DFL; - sa.sa_flags = SA_NOCLDSTOP|SA_RESTART; - assert_se(sigaction(SIGCHLD, &sa, NULL) == 0); - - 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 */ - SIGRTMIN+13, /* systemd: Immediate halt */ - SIGRTMIN+14, /* systemd: Immediate poweroff */ - SIGRTMIN+15, /* systemd: Immediate reboot */ - SIGRTMIN+16, /* systemd: Immediate kexec */ - 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+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 */ - -1); - assert_se(sigprocmask(SIG_SETMASK, &mask, NULL) == 0); - - m->signal_watch.type = WATCH_SIGNAL; - if ((m->signal_watch.fd = signalfd(-1, &mask, SFD_NONBLOCK|SFD_CLOEXEC)) < 0) - return -errno; - - zero(ev); - ev.events = EPOLLIN; - ev.data.ptr = &m->signal_watch; - - if (epoll_ctl(m->epoll_fd, EPOLL_CTL_ADD, m->signal_watch.fd, &ev) < 0) - return -errno; - - if (m->running_as == MANAGER_SYSTEM) - return enable_special_signals(m); - - return 0; -} - -static void manager_strip_environment(Manager *m) { - assert(m); - - /* Remove variables from the inherited set that are part of - * the container interface: - * http://www.freedesktop.org/wiki/Software/systemd/ContainerInterface */ - strv_remove_prefix(m->environment, "container="); - strv_remove_prefix(m->environment, "container_"); - - /* Remove variables from the inherited set that are part of - * the initrd interface: - * http://www.freedesktop.org/wiki/Software/systemd/InitrdInterface */ - strv_remove_prefix(m->environment, "RD_"); -} - -int manager_new(ManagerRunningAs running_as, Manager **_m) { - Manager *m; - int r = -ENOMEM; - - assert(_m); - assert(running_as >= 0); - assert(running_as < _MANAGER_RUNNING_AS_MAX); - - if (!(m = new0(Manager, 1))) - return -ENOMEM; - - dual_timestamp_get(&m->startup_timestamp); - - m->running_as = running_as; - m->name_data_slot = m->conn_data_slot = m->subscribed_data_slot = -1; - m->exit_code = _MANAGER_EXIT_CODE_INVALID; - m->pin_cgroupfs_fd = -1; - -#ifdef HAVE_AUDIT - m->audit_fd = -1; -#endif - - m->signal_watch.fd = m->mount_watch.fd = m->udev_watch.fd = m->epoll_fd = m->dev_autofs_fd = m->swap_watch.fd = -1; - m->current_job_id = 1; /* start as id #1, so that we can leave #0 around as "null-like" value */ - - m->environment = strv_copy(environ); - if (!m->environment) - goto fail; - - manager_strip_environment(m); - - if (running_as == MANAGER_SYSTEM) { - m->default_controllers = strv_new("cpu", NULL); - if (!m->default_controllers) - goto fail; - } - - if (!(m->units = hashmap_new(string_hash_func, string_compare_func))) - goto fail; - - if (!(m->jobs = hashmap_new(trivial_hash_func, trivial_compare_func))) - goto fail; - - if (!(m->transaction_jobs = hashmap_new(trivial_hash_func, trivial_compare_func))) - goto fail; - - if (!(m->watch_pids = hashmap_new(trivial_hash_func, trivial_compare_func))) - goto fail; - - if (!(m->cgroup_bondings = hashmap_new(string_hash_func, string_compare_func))) - goto fail; - - if (!(m->watch_bus = hashmap_new(string_hash_func, string_compare_func))) - goto fail; - - if ((m->epoll_fd = epoll_create1(EPOLL_CLOEXEC)) < 0) - goto fail; - - if ((r = lookup_paths_init(&m->lookup_paths, m->running_as, true)) < 0) - goto fail; - - if ((r = manager_setup_signals(m)) < 0) - goto fail; - - if ((r = manager_setup_cgroup(m)) < 0) - goto fail; - - if ((r = manager_setup_notify(m)) < 0) - goto fail; - - /* Try to connect to the busses, if possible. */ - if ((r = bus_init(m, running_as != MANAGER_SYSTEM)) < 0) - goto fail; - -#ifdef HAVE_AUDIT - if ((m->audit_fd = audit_open()) < 0 && - /* If the kernel lacks netlink or audit support, - * don't worry about it. */ - errno != EAFNOSUPPORT && errno != EPROTONOSUPPORT) - log_error("Failed to connect to audit log: %m"); -#endif - - m->taint_usr = dir_is_empty("/usr") > 0; - - *_m = m; - return 0; - -fail: - manager_free(m); - return r; -} - -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); - - if ((m->n_in_gc_queue < GC_QUEUE_ENTRIES_MAX) && - (m->gc_queue_timestamp <= 0 || - (m->gc_queue_timestamp + GC_QUEUE_USEC_MAX) > now(CLOCK_MONOTONIC))) - return 0; - - 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(Unit, 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) { - log_debug("Collecting %s", u->id); - u->gc_marker = gc_marker + GC_OFFSET_BAD; - unit_add_to_cleanup_queue(u); - } - } - - m->n_in_gc_queue = 0; - m->gc_queue_timestamp = 0; - - return n; -} - -static void manager_clear_jobs_and_units(Manager *m) { - Job *j; - Unit *u; - - assert(m); - - while ((j = hashmap_first(m->transaction_jobs))) - job_free(j); - - 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->transaction_jobs)); - assert(hashmap_isempty(m->jobs)); - assert(hashmap_isempty(m->units)); -} - -void manager_free(Manager *m) { - UnitType c; - - assert(m); - - manager_clear_jobs_and_units(m); - - for (c = 0; c < _UNIT_TYPE_MAX; c++) - if (unit_vtable[c]->shutdown) - unit_vtable[c]->shutdown(m); - - /* If we reexecute ourselves, we keep the root cgroup - * around */ - manager_shutdown_cgroup(m, m->exit_code != MANAGER_REEXECUTE); - - manager_undo_generators(m); - - bus_done(m); - - hashmap_free(m->units); - hashmap_free(m->jobs); - hashmap_free(m->transaction_jobs); - hashmap_free(m->watch_pids); - hashmap_free(m->watch_bus); - - if (m->epoll_fd >= 0) - close_nointr_nofail(m->epoll_fd); - if (m->signal_watch.fd >= 0) - close_nointr_nofail(m->signal_watch.fd); - if (m->notify_watch.fd >= 0) - close_nointr_nofail(m->notify_watch.fd); - -#ifdef HAVE_AUDIT - if (m->audit_fd >= 0) - audit_close(m->audit_fd); -#endif - - free(m->notify_socket); - - lookup_paths_free(&m->lookup_paths); - strv_free(m->environment); - - strv_free(m->default_controllers); - - hashmap_free(m->cgroup_bondings); - set_free_free(m->unit_path_cache); - - free(m); -} - -int manager_enumerate(Manager *m) { - int r = 0, q; - 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_vtable[c]->enumerate) - if ((q = unit_vtable[c]->enumerate(m)) < 0) - r = q; - - manager_dispatch_load_queue(m); - return r; -} - -int manager_coldplug(Manager *m) { - int r = 0, q; - Iterator i; - Unit *u; - char *k; - - 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; - - if ((q = unit_coldplug(u)) < 0) - r = q; - } - - return r; -} - -static void manager_build_unit_path_cache(Manager *m) { - char **i; - DIR *d = NULL; - int r; - - assert(m); - - set_free_free(m->unit_path_cache); - - if (!(m->unit_path_cache = set_new(string_hash_func, string_compare_func))) { - log_error("Failed to allocate unit path cache."); - return; - } - - /* This simply builds a list of files we know exist, so that - * we don't always have to go to disk */ - - STRV_FOREACH(i, m->lookup_paths.unit_path) { - struct dirent *de; - - if (!(d = opendir(*i))) { - log_error("Failed to open directory: %m"); - continue; - } - - while ((de = readdir(d))) { - char *p; - - if (ignore_file(de->d_name)) - continue; - - p = join(streq(*i, "/") ? "" : *i, "/", de->d_name, NULL); - if (!p) { - r = -ENOMEM; - goto fail; - } - - if ((r = set_put(m->unit_path_cache, p)) < 0) { - free(p); - goto fail; - } - } - - closedir(d); - d = NULL; - } - - return; - -fail: - log_error("Failed to build unit path cache: %s", strerror(-r)); - - set_free_free(m->unit_path_cache); - m->unit_path_cache = NULL; - - if (d) - closedir(d); -} - -int manager_startup(Manager *m, FILE *serialization, FDSet *fds) { - int r, q; - - assert(m); - - manager_run_generators(m); - - 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 */ - r = manager_enumerate(m); - - /* Second, deserialize if there is something to deserialize */ - if (serialization) - if ((q = manager_deserialize(m, serialization, fds)) < 0) - r = q; - - /* Third, fire things up! */ - if ((q = manager_coldplug(m)) < 0) - r = q; - - if (serialization) { - assert(m->n_reloading > 0); - m->n_reloading --; - } - - return r; -} - -static void transaction_delete_job(Manager *m, Job *j, bool delete_dependencies) { - assert(m); - assert(j); - - /* Deletes one job from the transaction */ - - manager_transaction_unlink_job(m, j, delete_dependencies); - - if (!j->installed) - job_free(j); -} - -static void transaction_delete_unit(Manager *m, Unit *u) { - Job *j; - - /* Deletes all jobs associated with a certain unit from the - * transaction */ - - while ((j = hashmap_get(m->transaction_jobs, u))) - transaction_delete_job(m, j, true); -} - -static void transaction_clean_dependencies(Manager *m) { - Iterator i; - Job *j; - - assert(m); - - /* Drops all dependencies of all installed jobs */ - - HASHMAP_FOREACH(j, m->jobs, i) { - while (j->subject_list) - job_dependency_free(j->subject_list); - while (j->object_list) - job_dependency_free(j->object_list); - } - - assert(!m->transaction_anchor); -} - -static void transaction_abort(Manager *m) { - Job *j; - - assert(m); - - while ((j = hashmap_first(m->transaction_jobs))) - if (j->installed) - transaction_delete_job(m, j, true); - else - job_free(j); - - assert(hashmap_isempty(m->transaction_jobs)); - - transaction_clean_dependencies(m); -} - -static void transaction_find_jobs_that_matter_to_anchor(Manager *m, Job *j, unsigned generation) { - JobDependency *l; - - assert(m); - - /* 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. */ - - if (j) - l = j->subject_list; - else - l = m->transaction_anchor; - - LIST_FOREACH(subject, l, l) { - - /* This link does not matter */ - if (!l->matters) - continue; - - /* This unit has already been marked */ - if (l->object->generation == generation) - continue; - - l->object->matters_to_anchor = true; - l->object->generation = generation; - - transaction_find_jobs_that_matter_to_anchor(m, l->object, generation); - } -} - -static void transaction_merge_and_delete_job(Manager *m, 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 j. */ - - j->type = t; - j->state = JOB_WAITING; - j->override = j->override || other->override; - - 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(m, other, true); -} -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(Manager *m, 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_debug("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_debug("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_debug("Fixing conflicting jobs by deleting job %s/%s", d->unit->id, job_type_to_string(d->type)); - transaction_delete_job(m, d, true); - return 0; - } - - return -EINVAL; -} - -static int transaction_merge_jobs(Manager *m, DBusError *e) { - Job *j; - Iterator i; - int r; - - assert(m); - - /* First step, check whether any of the jobs for one specific - * task conflict. If so, try to drop one of them. */ - HASHMAP_FOREACH(j, m->transaction_jobs, i) { - JobType t; - Job *k; - - t = j->type; - LIST_FOREACH(transaction, k, j->transaction_next) { - if (job_type_merge(&t, k->type) >= 0) - continue; - - /* OK, we could not merge all jobs for this - * action. Let's see if we can get rid of one - * of them */ - - if ((r = delete_one_unmergeable_job(m, j)) >= 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 */ - dbus_set_error(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); - return r; - } - } - - /* Second step, merge the jobs. */ - HASHMAP_FOREACH(j, m->transaction_jobs, i) { - JobType t = j->type; - Job *k; - - /* Merge all transactions */ - LIST_FOREACH(transaction, k, j->transaction_next) - assert_se(job_type_merge(&t, k->type) == 0); - - /* If an active job is mergeable, merge it too */ - if (j->unit->job) - job_type_merge(&t, j->unit->job->type); /* Might fail. Which is OK */ - - while ((k = j->transaction_next)) { - if (j->installed) { - transaction_merge_and_delete_job(m, k, j, t); - j = k; - } else - transaction_merge_and_delete_job(m, j, k, t); - } - - if (j->unit->job && !j->installed) - transaction_merge_and_delete_job(m, j, j->unit->job, t); - - assert(!j->transaction_next); - assert(!j->transaction_prev); - } - - return 0; -} - -static void transaction_drop_redundant(Manager *m) { - bool again; - - assert(m); - - /* Goes through the transaction and removes all jobs that are - * a noop */ - - do { - Job *j; - Iterator i; - - again = false; - - HASHMAP_FOREACH(j, m->transaction_jobs, i) { - bool changes_something = false; - Job *k; - - LIST_FOREACH(transaction, k, j) { - - if (!job_is_anchor(k) && - (k->installed || job_type_is_redundant(k->type, unit_active_state(k->unit))) && - (!k->unit->job || !job_type_is_conflicting(k->type, k->unit->job->type))) - continue; - - changes_something = true; - break; - } - - if (changes_something) - continue; - - /* log_debug("Found redundant job %s/%s, dropping.", j->unit->id, job_type_to_string(j->type)); */ - transaction_delete_job(m, j, false); - again = true; - break; - } - - } while (again); -} - -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(Manager *m, Job *j, Job *from, unsigned generation, DBusError *e) { - Iterator i; - Unit *u; - int r; - - assert(m); - assert(j); - assert(!j->transaction_prev); - - /* Does a recursive sweep through the ordering graph, looking - * for a cycle. If we find 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_warning("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)) { - - log_info("Walked on cycle path to %s/%s", k->unit->id, job_type_to_string(k->type)); - - if (!delete && - !k->installed && - !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) { - log_warning("Breaking ordering cycle by deleting job %s/%s", delete->unit->id, job_type_to_string(delete->type)); - transaction_delete_unit(m, delete->unit); - return -EAGAIN; - } - - log_error("Unable to break cycle"); - - dbus_set_error(e, BUS_ERROR_TRANSACTION_ORDER_IS_CYCLIC, "Transaction order is cyclic. See system logs for details."); - return -ENOEXEC; - } - - /* 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 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? */ - if (!(o = hashmap_get(m->transaction_jobs, u))) - - /* Ok, there is no job for this in the - * transaction, but maybe there is already one - * running? */ - if (!(o = u->job)) - continue; - - if ((r = transaction_verify_order_one(m, o, j, generation, e)) < 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(Manager *m, unsigned *generation, DBusError *e) { - Job *j; - int r; - Iterator i; - unsigned g; - - assert(m); - 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, m->transaction_jobs, i) - if ((r = transaction_verify_order_one(m, j, NULL, g, e)) < 0) - return r; - - return 0; -} - -static void transaction_collect_garbage(Manager *m) { - bool again; - - assert(m); - - /* Drop jobs that are not required by any other job */ - - do { - Iterator i; - Job *j; - - again = false; - - HASHMAP_FOREACH(j, m->transaction_jobs, i) { - if (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(m, j, true); - again = true; - break; - } - - } while (again); -} - -static int transaction_is_destructive(Manager *m, DBusError *e) { - Iterator i; - Job *j; - - assert(m); - - /* Checks whether applying this transaction means that - * existing jobs would be replaced */ - - HASHMAP_FOREACH(j, m->transaction_jobs, i) { - - /* Assume merged */ - assert(!j->transaction_prev); - assert(!j->transaction_next); - - if (j->unit->job && - j->unit->job != j && - !job_type_is_superset(j->type, j->unit->job->type)) { - - dbus_set_error(e, BUS_ERROR_TRANSACTION_IS_DESTRUCTIVE, "Transaction is destructive."); - return -EEXIST; - } - } - - return 0; -} - -static void transaction_minimize_impact(Manager *m) { - bool again; - assert(m); - - /* Drops all unnecessary jobs that reverse already active jobs - * or that stop a running service. */ - - do { - Job *j; - Iterator i; - - again = false; - - HASHMAP_FOREACH(j, m->transaction_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_debug("%s/%s would stop a running service.", j->unit->id, job_type_to_string(j->type)); - - if (changes_existing_job) - log_debug("%s/%s would change existing job.", j->unit->id, job_type_to_string(j->type)); - - /* Ok, let's get rid of this */ - log_debug("Deleting %s/%s to minimize impact.", j->unit->id, job_type_to_string(j->type)); - - transaction_delete_job(m, j, true); - again = true; - break; - } - - if (again) - break; - } - - } while (again); -} - -static int transaction_apply(Manager *m, JobMode mode) { - Iterator i; - Job *j; - int r; - - /* Moves the transaction jobs to the set of active jobs */ - - if (mode == JOB_ISOLATE) { - - /* When isolating first kill all installed jobs which - * aren't part of the new transaction */ - rescan: - HASHMAP_FOREACH(j, m->jobs, i) { - assert(j->installed); - - if (hashmap_get(m->transaction_jobs, j->unit)) - continue; - - /* 'j' itself is safe to remove, but if other jobs - are invalidated recursively, our iterator may become - invalid and we need to start over. */ - if (job_finish_and_invalidate(j, JOB_CANCELED) > 0) - goto rescan; - } - } - - HASHMAP_FOREACH(j, m->transaction_jobs, i) { - /* Assume merged */ - assert(!j->transaction_prev); - assert(!j->transaction_next); - - if (j->installed) - continue; - - if ((r = hashmap_put(m->jobs, UINT32_TO_PTR(j->id), j)) < 0) - goto rollback; - } - - while ((j = hashmap_steal_first(m->transaction_jobs))) { - if (j->installed) { - /* log_debug("Skipping already installed job %s/%s as %u", j->unit->id, job_type_to_string(j->type), (unsigned) j->id); */ - continue; - } - - if (j->unit->job) - job_free(j->unit->job); - - j->unit->job = j; - j->installed = true; - m->n_installed_jobs ++; - - /* We're fully installed. Now let's free data we don't - * need anymore. */ - - assert(!j->transaction_next); - assert(!j->transaction_prev); - - job_add_to_run_queue(j); - job_add_to_dbus_queue(j); - job_start_timer(j); - - log_debug("Installed new job %s/%s as %u", j->unit->id, job_type_to_string(j->type), (unsigned) j->id); - } - - /* As last step, kill all remaining job dependencies. */ - transaction_clean_dependencies(m); - - return 0; - -rollback: - - HASHMAP_FOREACH(j, m->transaction_jobs, i) { - if (j->installed) - continue; - - hashmap_remove(m->jobs, UINT32_TO_PTR(j->id)); - } - - return r; -} - -static int transaction_activate(Manager *m, JobMode mode, DBusError *e) { - int r; - unsigned generation = 1; - - assert(m); - - /* This applies the changes recorded in transaction_jobs to - * the actual list of jobs, if possible. */ - - /* First step: figure out which jobs matter */ - transaction_find_jobs_that_matter_to_anchor(m, NULL, 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(m); - - /* Third step: Drop redundant jobs */ - transaction_drop_redundant(m); - - for (;;) { - /* Fourth step: Let's remove unneeded jobs that might - * be lurking. */ - if (mode != JOB_ISOLATE) - transaction_collect_garbage(m); - - /* Fifth step: verify order makes sense and correct - * cycles if necessary and possible */ - if ((r = transaction_verify_order(m, &generation, e)) >= 0) - break; - - if (r != -EAGAIN) { - log_warning("Requested transaction contains an unfixable cyclic ordering dependency: %s", bus_error(e, r)); - goto rollback; - } - - /* 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 */ - if ((r = transaction_merge_jobs(m, e)) >= 0) - break; - - if (r != -EAGAIN) { - log_warning("Requested transaction contains unmergeable jobs: %s", bus_error(e, r)); - goto rollback; - } - - /* Seventh step: an entry got dropped, let's garbage - * collect its dependencies. */ - if (mode != JOB_ISOLATE) - transaction_collect_garbage(m); - - /* 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(m); - - /* Ninth step: check whether we can actually apply this */ - if (mode == JOB_FAIL) - if ((r = transaction_is_destructive(m, e)) < 0) { - log_notice("Requested transaction contradicts existing jobs: %s", bus_error(e, r)); - goto rollback; - } - - /* Tenth step: apply changes */ - if ((r = transaction_apply(m, mode)) < 0) { - log_warning("Failed to apply transaction: %s", strerror(-r)); - goto rollback; - } - - assert(hashmap_isempty(m->transaction_jobs)); - assert(!m->transaction_anchor); - - return 0; - -rollback: - transaction_abort(m); - return r; -} - -static Job* transaction_add_one_job(Manager *m, JobType type, Unit *unit, bool override, bool *is_new) { - Job *j, *f; - - assert(m); - 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(m->transaction_jobs, unit); - - LIST_FOREACH(transaction, j, f) { - assert(j->unit == unit); - - if (j->type == type) { - if (is_new) - *is_new = false; - return j; - } - } - - if (unit->job && unit->job->type == type) - j = unit->job; - else if (!(j = job_new(m, type, unit))) - return NULL; - - j->generation = 0; - j->marker = NULL; - j->matters_to_anchor = false; - j->override = override; - - LIST_PREPEND(Job, transaction, f, j); - - if (hashmap_replace(m->transaction_jobs, unit, f) < 0) { - 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; -} - -void manager_transaction_unlink_job(Manager *m, Job *j, bool delete_dependencies) { - assert(m); - assert(j); - - if (j->transaction_prev) - j->transaction_prev->transaction_next = j->transaction_next; - else if (j->transaction_next) - hashmap_replace(m->transaction_jobs, j->unit, j->transaction_next); - else - hashmap_remove_value(m->transaction_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_debug("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(m, other, delete_dependencies); - } - } -} - -static int transaction_add_job_and_dependencies( - Manager *m, - JobType type, - Unit *unit, - Job *by, - bool matters, - bool override, - bool conflicts, - bool ignore_requirements, - bool ignore_order, - DBusError *e, - Job **_ret) { - Job *ret; - Iterator i; - Unit *dep; - int r; - bool is_new; - - assert(m); - assert(type < _JOB_TYPE_MAX); - assert(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 (unit->load_state != UNIT_LOADED && - unit->load_state != UNIT_ERROR && - unit->load_state != UNIT_MASKED) { - dbus_set_error(e, BUS_ERROR_LOAD_FAILED, "Unit %s is not loaded properly.", unit->id); - return -EINVAL; - } - - if (type != JOB_STOP && unit->load_state == UNIT_ERROR) { - dbus_set_error(e, BUS_ERROR_LOAD_FAILED, - "Unit %s failed to load: %s. " - "See system logs and 'systemctl status %s' for details.", - unit->id, - strerror(-unit->load_error), - unit->id); - return -EINVAL; - } - - if (type != JOB_STOP && unit->load_state == UNIT_MASKED) { - dbus_set_error(e, BUS_ERROR_MASKED, "Unit %s is masked.", unit->id); - return -EINVAL; - } - - if (!unit_job_is_applicable(unit, type)) { - dbus_set_error(e, BUS_ERROR_JOB_TYPE_NOT_APPLICABLE, "Job type %s is not applicable for unit %s.", job_type_to_string(type), unit->id); - return -EBADR; - } - - /* First add the job. */ - if (!(ret = transaction_add_one_job(m, type, unit, override, &is_new))) - return -ENOMEM; - - ret->ignore_order = ret->ignore_order || ignore_order; - - /* Then, add a link to the job. */ - if (!job_dependency_new(by, ret, matters, conflicts)) - return -ENOMEM; - - if (is_new && !ignore_requirements) { - 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) - if ((r = transaction_add_job_and_dependencies(m, type, dep, ret, false, override, false, false, ignore_order, e, NULL)) < 0) { - log_warning("Cannot add dependency job for unit %s, ignoring: %s", dep->id, bus_error(e, r)); - - if (e) - dbus_error_free(e); - } - - set_free(following); - } - - /* Finally, recursively add in all dependencies. */ - if (type == JOB_START || type == JOB_RELOAD_OR_START) { - SET_FOREACH(dep, ret->unit->dependencies[UNIT_REQUIRES], i) - if ((r = transaction_add_job_and_dependencies(m, JOB_START, dep, ret, true, override, false, false, ignore_order, e, NULL)) < 0) { - if (r != -EBADR) - goto fail; - - if (e) - dbus_error_free(e); - } - - SET_FOREACH(dep, ret->unit->dependencies[UNIT_BIND_TO], i) - if ((r = transaction_add_job_and_dependencies(m, JOB_START, dep, ret, true, override, false, false, ignore_order, e, NULL)) < 0) { - - if (r != -EBADR) - goto fail; - - if (e) - dbus_error_free(e); - } - - SET_FOREACH(dep, ret->unit->dependencies[UNIT_REQUIRES_OVERRIDABLE], i) - if ((r = transaction_add_job_and_dependencies(m, JOB_START, dep, ret, !override, override, false, false, ignore_order, e, NULL)) < 0) { - log_warning("Cannot add dependency job for unit %s, ignoring: %s", dep->id, bus_error(e, r)); - - if (e) - dbus_error_free(e); - } - - SET_FOREACH(dep, ret->unit->dependencies[UNIT_WANTS], i) - if ((r = transaction_add_job_and_dependencies(m, JOB_START, dep, ret, false, false, false, false, ignore_order, e, NULL)) < 0) { - log_warning("Cannot add dependency job for unit %s, ignoring: %s", dep->id, bus_error(e, r)); - - if (e) - dbus_error_free(e); - } - - SET_FOREACH(dep, ret->unit->dependencies[UNIT_REQUISITE], i) - if ((r = transaction_add_job_and_dependencies(m, JOB_VERIFY_ACTIVE, dep, ret, true, override, false, false, ignore_order, e, NULL)) < 0) { - - if (r != -EBADR) - goto fail; - - if (e) - dbus_error_free(e); - } - - SET_FOREACH(dep, ret->unit->dependencies[UNIT_REQUISITE_OVERRIDABLE], i) - if ((r = transaction_add_job_and_dependencies(m, JOB_VERIFY_ACTIVE, dep, ret, !override, override, false, false, ignore_order, e, NULL)) < 0) { - log_warning("Cannot add dependency job for unit %s, ignoring: %s", dep->id, bus_error(e, r)); - - if (e) - dbus_error_free(e); - } - - SET_FOREACH(dep, ret->unit->dependencies[UNIT_CONFLICTS], i) - if ((r = transaction_add_job_and_dependencies(m, JOB_STOP, dep, ret, true, override, true, false, ignore_order, e, NULL)) < 0) { - - if (r != -EBADR) - goto fail; - - if (e) - dbus_error_free(e); - } - - SET_FOREACH(dep, ret->unit->dependencies[UNIT_CONFLICTED_BY], i) - if ((r = transaction_add_job_and_dependencies(m, JOB_STOP, dep, ret, false, override, false, false, ignore_order, e, NULL)) < 0) { - log_warning("Cannot add dependency job for unit %s, ignoring: %s", dep->id, bus_error(e, r)); - - if (e) - dbus_error_free(e); - } - - } - - if (type == JOB_STOP || type == JOB_RESTART || type == JOB_TRY_RESTART) { - - SET_FOREACH(dep, ret->unit->dependencies[UNIT_REQUIRED_BY], i) - if ((r = transaction_add_job_and_dependencies(m, type, dep, ret, true, override, false, false, ignore_order, e, NULL)) < 0) { - - if (r != -EBADR) - goto fail; - - if (e) - dbus_error_free(e); - } - - SET_FOREACH(dep, ret->unit->dependencies[UNIT_BOUND_BY], i) - if ((r = transaction_add_job_and_dependencies(m, type, dep, ret, true, override, false, false, ignore_order, e, NULL)) < 0) { - - if (r != -EBADR) - goto fail; - - if (e) - dbus_error_free(e); - } - } - - if (type == JOB_RELOAD || type == JOB_RELOAD_OR_START) { - - SET_FOREACH(dep, ret->unit->dependencies[UNIT_PROPAGATE_RELOAD_TO], i) { - r = transaction_add_job_and_dependencies(m, JOB_RELOAD, dep, ret, false, override, false, false, ignore_order, e, NULL); - - if (r < 0) { - log_warning("Cannot add dependency reload job for unit %s, ignoring: %s", dep->id, bus_error(e, r)); - - if (e) - dbus_error_free(e); - } - } - } - - /* JOB_VERIFY_STARTED, JOB_RELOAD require no dependency handling */ - } - - if (_ret) - *_ret = ret; - - return 0; - -fail: - return r; -} - -static int transaction_add_isolate_jobs(Manager *m) { - Iterator i; - Unit *u; - char *k; - int r; - - 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(m->transaction_jobs, u)) - continue; - - if ((r = transaction_add_job_and_dependencies(m, JOB_STOP, u, NULL, true, false, false, false, false, NULL, NULL)) < 0) - log_warning("Cannot add isolate job for unit %s, ignoring: %s", u->id, strerror(-r)); - } - - return 0; -} - -int manager_add_job(Manager *m, JobType type, Unit *unit, JobMode mode, bool override, DBusError *e, Job **_ret) { - int r; - Job *ret; - - assert(m); - assert(type < _JOB_TYPE_MAX); - assert(unit); - assert(mode < _JOB_MODE_MAX); - - if (mode == JOB_ISOLATE && type != JOB_START) { - dbus_set_error(e, BUS_ERROR_INVALID_JOB_MODE, "Isolate is only valid for start."); - return -EINVAL; - } - - if (mode == JOB_ISOLATE && !unit->allow_isolate) { - dbus_set_error(e, BUS_ERROR_NO_ISOLATION, "Operation refused, unit may not be isolated."); - return -EPERM; - } - - log_debug("Trying to enqueue job %s/%s/%s", unit->id, job_type_to_string(type), job_mode_to_string(mode)); - - if ((r = transaction_add_job_and_dependencies(m, type, unit, NULL, true, override, false, - mode == JOB_IGNORE_DEPENDENCIES || mode == JOB_IGNORE_REQUIREMENTS, - mode == JOB_IGNORE_DEPENDENCIES, e, &ret)) < 0) { - transaction_abort(m); - return r; - } - - if (mode == JOB_ISOLATE) - if ((r = transaction_add_isolate_jobs(m)) < 0) { - transaction_abort(m); - return r; - } - - if ((r = transaction_activate(m, mode, e)) < 0) - return r; - - log_debug("Enqueued job %s/%s as %u", unit->id, job_type_to_string(type), (unsigned) ret->id); - - if (_ret) - *_ret = ret; - - return 0; -} - -int manager_add_job_by_name(Manager *m, JobType type, const char *name, JobMode mode, bool override, DBusError *e, Job **_ret) { - Unit *unit; - int r; - - assert(m); - assert(type < _JOB_TYPE_MAX); - assert(name); - assert(mode < _JOB_MODE_MAX); - - if ((r = manager_load_unit(m, name, NULL, NULL, &unit)) < 0) - return r; - - return manager_add_job(m, type, unit, mode, override, e, _ret); -} - -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, DBusError *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)) { - dbus_set_error(e, BUS_ERROR_INVALID_PATH, "Path %s is not absolute.", path); - return -EINVAL; - } - - if (!name) - name = file_name_from_path(path); - - t = unit_name_to_type(name); - - if (t == _UNIT_TYPE_INVALID || !unit_name_is_valid_no_type(name, false)) { - dbus_set_error(e, BUS_ERROR_INVALID_NAME, "Unit name %s is not valid.", name); - return -EINVAL; - } - - 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; - } - } - - if ((r = unit_add_name(ret, name)) < 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, DBusError *e, Unit **_ret) { - int r; - - assert(m); - - /* This will load the service information files, but not actually - * start any services or anything. */ - - if ((r = manager_load_unit_prepare(m, name, path, e, _ret)) != 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); - - transaction_abort(m); - - while ((j = hashmap_first(m->jobs))) - job_finish_and_invalidate(j, JOB_CANCELED); -} - -unsigned manager_dispatch_run_queue(Manager *m) { - Job *j; - unsigned n = 0; - - if (m->dispatching_run_queue) - return 0; - - m->dispatching_run_queue = true; - - while ((j = m->run_queue)) { - assert(j->installed); - assert(j->in_run_queue); - - job_run_and_invalidate(j); - n++; - } - - m->dispatching_run_queue = false; - return n; -} - -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; - return n; -} - -static int manager_process_notify_fd(Manager *m) { - ssize_t n; - - assert(m); - - for (;;) { - char buf[4096]; - struct msghdr msghdr; - struct iovec iovec; - struct ucred *ucred; - union { - struct cmsghdr cmsghdr; - uint8_t buf[CMSG_SPACE(sizeof(struct ucred))]; - } control; - Unit *u; - char **tags; - - zero(iovec); - iovec.iov_base = buf; - iovec.iov_len = sizeof(buf)-1; - - zero(control); - zero(msghdr); - msghdr.msg_iov = &iovec; - msghdr.msg_iovlen = 1; - msghdr.msg_control = &control; - msghdr.msg_controllen = sizeof(control); - - if ((n = recvmsg(m->notify_watch.fd, &msghdr, MSG_DONTWAIT)) <= 0) { - if (n >= 0) - return -EIO; - - if (errno == EAGAIN || errno == EINTR) - break; - - return -errno; - } - - if (msghdr.msg_controllen < CMSG_LEN(sizeof(struct ucred)) || - control.cmsghdr.cmsg_level != SOL_SOCKET || - control.cmsghdr.cmsg_type != SCM_CREDENTIALS || - control.cmsghdr.cmsg_len != CMSG_LEN(sizeof(struct ucred))) { - log_warning("Received notify message without credentials. Ignoring."); - continue; - } - - ucred = (struct ucred*) CMSG_DATA(&control.cmsghdr); - - if (!(u = hashmap_get(m->watch_pids, LONG_TO_PTR(ucred->pid)))) - if (!(u = cgroup_unit_by_pid(m, ucred->pid))) { - log_warning("Cannot find unit for notify message of PID %lu.", (unsigned long) ucred->pid); - continue; - } - - assert((size_t) n < sizeof(buf)); - buf[n] = 0; - if (!(tags = strv_split(buf, "\n\r"))) - return -ENOMEM; - - log_debug("Got notification message for unit %s", u->id); - - if (UNIT_VTABLE(u)->notify_message) - UNIT_VTABLE(u)->notify_message(u, ucred->pid, tags); - - strv_free(tags); - } - - return 0; -} - -static int manager_dispatch_sigchld(Manager *m) { - assert(m); - - for (;;) { - siginfo_t si; - Unit *u; - int r; - - zero(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) { - char *name = NULL; - - get_process_comm(si.si_pid, &name); - log_debug("Got SIGCHLD for process %lu (%s)", (unsigned long) si.si_pid, strna(name)); - free(name); - } - - /* Let's flush any message the dying child might still - * have queued for us. This ensures that the process - * still exists in /proc so that we can figure out - * which cgroup and hence unit it belongs to. */ - if ((r = manager_process_notify_fd(m)) < 0) - return r; - - /* And now figure out the unit this belongs to */ - if (!(u = hashmap_get(m->watch_pids, LONG_TO_PTR(si.si_pid)))) - u = cgroup_unit_by_pid(m, si.si_pid); - - /* And now, we actually reap the zombie. */ - if (waitid(P_PID, si.si_pid, &si, WEXITED) < 0) { - if (errno == EINTR) - continue; - - return -errno; - } - - if (si.si_code != CLD_EXITED && si.si_code != CLD_KILLED && si.si_code != CLD_DUMPED) - continue; - - log_debug("Child %lu died (code=%s, status=%i/%s)", - (long unsigned) si.si_pid, - 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))); - - if (!u) - continue; - - log_debug("Child %lu belongs to %s", (long unsigned) si.si_pid, u->id); - - hashmap_remove(m->watch_pids, LONG_TO_PTR(si.si_pid)); - UNIT_VTABLE(u)->sigchld_event(u, si.si_pid, si.si_code, si.si_status); - } - - return 0; -} - -static int manager_start_target(Manager *m, const char *name, JobMode mode) { - int r; - DBusError error; - - dbus_error_init(&error); - - log_debug("Activating special unit %s", name); - - if ((r = manager_add_job_by_name(m, JOB_START, name, mode, true, &error, NULL)) < 0) - log_error("Failed to enqueue %s job: %s", name, bus_error(&error, r)); - - dbus_error_free(&error); - - return r; -} - -static int manager_process_signal_fd(Manager *m) { - ssize_t n; - struct signalfd_siginfo sfsi; - bool sigchld = false; - - assert(m); - - for (;;) { - if ((n = read(m->signal_watch.fd, &sfsi, sizeof(sfsi))) != sizeof(sfsi)) { - - if (n >= 0) - return -EIO; - - if (errno == EINTR || errno == EAGAIN) - break; - - return -errno; - } - - if (sfsi.ssi_pid > 0) { - char *p = NULL; - - get_process_comm(sfsi.ssi_pid, &p); - - log_debug("Received SIG%s from PID %lu (%s).", - signal_to_string(sfsi.ssi_signo), - (unsigned long) sfsi.ssi_pid, strna(p)); - free(p); - } else - log_debug("Received SIG%s.", signal_to_string(sfsi.ssi_signo)); - - switch (sfsi.ssi_signo) { - - case SIGCHLD: - sigchld = true; - break; - - case SIGTERM: - if (m->running_as == MANAGER_SYSTEM) { - /* This is for compatibility with the - * original sysvinit */ - m->exit_code = MANAGER_REEXECUTE; - break; - } - - /* Fall through */ - - case SIGINT: - if (m->running_as == MANAGER_SYSTEM) { - manager_start_target(m, SPECIAL_CTRL_ALT_DEL_TARGET, JOB_REPLACE); - break; - } - - /* Run the exit target if there is one, if not, just exit. */ - if (manager_start_target(m, SPECIAL_EXIT_TARGET, JOB_REPLACE) < 0) { - m->exit_code = MANAGER_EXIT; - return 0; - } - - break; - - case SIGWINCH: - if (m->running_as == MANAGER_SYSTEM) - manager_start_target(m, SPECIAL_KBREQUEST_TARGET, JOB_REPLACE); - - /* This is a nop on non-init */ - break; - - case SIGPWR: - if (m->running_as == MANAGER_SYSTEM) - manager_start_target(m, SPECIAL_SIGPWR_TARGET, JOB_REPLACE); - - /* This is a nop on non-init */ - break; - - case SIGUSR1: { - Unit *u; - - u = manager_get_unit(m, SPECIAL_DBUS_SERVICE); - - if (!u || UNIT_IS_ACTIVE_OR_RELOADING(unit_active_state(u))) { - log_info("Trying to reconnect to bus..."); - bus_init(m, true); - } - - if (!u || !UNIT_IS_ACTIVE_OR_ACTIVATING(unit_active_state(u))) { - log_info("Loading D-Bus service..."); - manager_start_target(m, SPECIAL_DBUS_SERVICE, JOB_REPLACE); - } - - break; - } - - case SIGUSR2: { - FILE *f; - char *dump = NULL; - size_t size; - - if (!(f = open_memstream(&dump, &size))) { - log_warning("Failed to allocate memory stream."); - break; - } - - manager_dump_units(m, f, "\t"); - manager_dump_jobs(m, f, "\t"); - - if (ferror(f)) { - fclose(f); - free(dump); - log_warning("Failed to write status stream"); - break; - } - - fclose(f); - log_dump(LOG_INFO, dump); - free(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: - log_debug("Enabling showing of status."); - manager_set_show_status(m, true); - break; - - case 21: - log_debug("Disabling showing of status."); - manager_set_show_status(m, false); - break; - - case 22: - log_set_max_level(LOG_DEBUG); - log_notice("Setting log level to debug."); - break; - - case 23: - log_set_max_level(LOG_INFO); - log_notice("Setting log level to info."); - break; - - case 26: - 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; - - case 29: - log_set_target(LOG_TARGET_SYSLOG_OR_KMSG); - log_notice("Setting log target to syslog-or-kmsg."); - break; - - default: - log_warning("Got unhandled signal <%s>.", signal_to_string(sfsi.ssi_signo)); - } - } - } - } - - if (sigchld) - return manager_dispatch_sigchld(m); - - return 0; -} - -static int process_event(Manager *m, struct epoll_event *ev) { - int r; - Watch *w; - - assert(m); - assert(ev); - - assert_se(w = ev->data.ptr); - - if (w->type == WATCH_INVALID) - return 0; - - switch (w->type) { - - case WATCH_SIGNAL: - - /* An incoming signal? */ - if (ev->events != EPOLLIN) - return -EINVAL; - - if ((r = manager_process_signal_fd(m)) < 0) - return r; - - break; - - case WATCH_NOTIFY: - - /* An incoming daemon notification event? */ - if (ev->events != EPOLLIN) - return -EINVAL; - - if ((r = manager_process_notify_fd(m)) < 0) - return r; - - break; - - case WATCH_FD: - - /* Some fd event, to be dispatched to the units */ - UNIT_VTABLE(w->data.unit)->fd_event(w->data.unit, w->fd, ev->events, w); - break; - - case WATCH_UNIT_TIMER: - case WATCH_JOB_TIMER: { - uint64_t v; - ssize_t k; - - /* Some timer event, to be dispatched to the units */ - if ((k = read(w->fd, &v, sizeof(v))) != sizeof(v)) { - - if (k < 0 && (errno == EINTR || errno == EAGAIN)) - break; - - return k < 0 ? -errno : -EIO; - } - - if (w->type == WATCH_UNIT_TIMER) - UNIT_VTABLE(w->data.unit)->timer_event(w->data.unit, v, w); - else - job_timer_event(w->data.job, v, w); - break; - } - - case WATCH_MOUNT: - /* Some mount table change, intended for the mount subsystem */ - mount_fd_event(m, ev->events); - break; - - case WATCH_SWAP: - /* Some swap table change, intended for the swap subsystem */ - swap_fd_event(m, ev->events); - break; - - case WATCH_UDEV: - /* Some notification from udev, intended for the device subsystem */ - device_fd_event(m, ev->events); - break; - - case WATCH_DBUS_WATCH: - bus_watch_event(m, w, ev->events); - break; - - case WATCH_DBUS_TIMEOUT: - bus_timeout_event(m, w, ev->events); - break; - - default: - log_error("event type=%i", w->type); - assert_not_reached("Unknown epoll event type."); - } - - return 0; -} - -int manager_loop(Manager *m) { - int r; - int wait_msec = -1; - - RATELIMIT_DEFINE(rl, 1*USEC_PER_SEC, 50000); - - assert(m); - m->exit_code = MANAGER_RUNNING; - - /* Release the path cache */ - set_free_free(m->unit_path_cache); - m->unit_path_cache = NULL; - - manager_check_finished(m); - - /* There might still be some zombies hanging around from - * before we were exec()'ed. Leat's reap them */ - r = manager_dispatch_sigchld(m); - if (r < 0) - return r; - - /* Sleep for half the watchdog time */ - if (m->runtime_watchdog > 0 && m->running_as == MANAGER_SYSTEM) { - wait_msec = (int) (m->runtime_watchdog / 2 / USEC_PER_MSEC); - if (wait_msec <= 0) - wait_msec = 1; - } - - while (m->exit_code == MANAGER_RUNNING) { - struct epoll_event event; - int n; - - if (wait_msec >= 0) - 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); - continue; - } - - if (manager_dispatch_load_queue(m) > 0) - continue; - - if (manager_dispatch_run_queue(m) > 0) - continue; - - if (bus_dispatch(m) > 0) - continue; - - if (manager_dispatch_cleanup_queue(m) > 0) - continue; - - if (manager_dispatch_gc_queue(m) > 0) - continue; - - if (manager_dispatch_dbus_queue(m) > 0) - continue; - - if (swap_dispatch_reload(m) > 0) - continue; - - n = epoll_wait(m->epoll_fd, &event, 1, wait_msec); - if (n < 0) { - - if (errno == EINTR) - continue; - - return -errno; - } else if (n == 0) - continue; - - assert(n == 1); - - r = process_event(m, &event); - if (r < 0) - return r; - } - - return m->exit_code; -} - -int manager_get_unit_from_dbus_path(Manager *m, const char *s, Unit **_u) { - char *n; - Unit *u; - - assert(m); - assert(s); - assert(_u); - - if (!startswith(s, "/org/freedesktop/systemd1/unit/")) - return -EINVAL; - - if (!(n = bus_path_unescape(s+31))) - return -ENOMEM; - - u = manager_get_unit(m, n); - free(n); - - if (!u) - return -ENOENT; - - *_u = u; - - return 0; -} - -int manager_get_job_from_dbus_path(Manager *m, const char *s, Job **_j) { - Job *j; - unsigned id; - int r; - - assert(m); - assert(s); - assert(_j); - - if (!startswith(s, "/org/freedesktop/systemd1/job/")) - return -EINVAL; - - if ((r = safe_atou(s + 30, &id)) < 0) - return r; - - if (!(j = manager_get_job(m, id))) - return -ENOENT; - - *_j = j; - - return 0; -} - -void manager_send_unit_audit(Manager *m, Unit *u, int type, bool success) { - -#ifdef HAVE_AUDIT - char *p; - - if (m->audit_fd < 0) - return; - - /* Don't generate audit events if the service was already - * started and we're just deserializing */ - if (m->n_reloading > 0) - return; - - if (m->running_as != MANAGER_SYSTEM) - return; - - if (u->type != UNIT_SERVICE) - return; - - if (!(p = unit_name_to_prefix_and_instance(u->id))) { - log_error("Failed to allocate unit name for audit message: %s", strerror(ENOMEM)); - return; - } - - if (audit_log_user_comm_message(m->audit_fd, type, "", p, NULL, NULL, NULL, success) < 0) { - log_warning("Failed to send audit message: %m"); - - if (errno == EPERM) { - /* We aren't allowed to send audit messages? - * Then let's not retry again, to avoid - * spamming the user with the same and same - * messages over and over. */ - - audit_close(m->audit_fd); - m->audit_fd = -1; - } - } - - free(p); -#endif - -} - -void manager_send_unit_plymouth(Manager *m, Unit *u) { - int fd = -1; - union sockaddr_union sa; - int n = 0; - char *message = NULL; - - /* Don't generate plymouth events if the service was already - * started and we're just deserializing */ - if (m->n_reloading > 0) - return; - - if (m->running_as != MANAGER_SYSTEM) - return; - - if (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 */ - if ((fd = socket(AF_UNIX, SOCK_STREAM|SOCK_CLOEXEC|SOCK_NONBLOCK, 0)) < 0) { - log_error("socket() failed: %m"); - return; - } - - zero(sa); - sa.sa.sa_family = AF_UNIX; - strncpy(sa.un.sun_path+1, "/org/freedesktop/plymouthd", sizeof(sa.un.sun_path)-1); - if (connect(fd, &sa.sa, offsetof(struct sockaddr_un, sun_path) + 1 + strlen(sa.un.sun_path+1)) < 0) { - - if (errno != EPIPE && - errno != EAGAIN && - errno != ENOENT && - errno != ECONNREFUSED && - errno != ECONNRESET && - errno != ECONNABORTED) - log_error("connect() failed: %m"); - - goto finish; - } - - if (asprintf(&message, "U\002%c%s%n", (int) (strlen(u->id) + 1), u->id, &n) < 0) { - log_error("Out of memory"); - goto finish; - } - - errno = 0; - if (write(fd, message, n + 1) != n + 1) { - - if (errno != EPIPE && - errno != EAGAIN && - errno != ENOENT && - errno != ECONNREFUSED && - errno != ECONNRESET && - errno != ECONNABORTED) - log_error("Failed to write Plymouth message: %m"); - - goto finish; - } - -finish: - if (fd >= 0) - close_nointr_nofail(fd); - - free(message); -} - -void manager_dispatch_bus_name_owner_changed( - Manager *m, - const char *name, - const char* old_owner, - const char *new_owner) { - - Unit *u; - - assert(m); - assert(name); - - if (!(u = hashmap_get(m->watch_bus, name))) - return; - - UNIT_VTABLE(u)->bus_name_owner_change(u, name, old_owner, new_owner); -} - -void manager_dispatch_bus_query_pid_done( - Manager *m, - const char *name, - pid_t pid) { - - Unit *u; - - assert(m); - assert(name); - assert(pid >= 1); - - if (!(u = hashmap_get(m->watch_bus, name))) - return; - - UNIT_VTABLE(u)->bus_query_pid_done(u, name, pid); -} - -int manager_open_serialization(Manager *m, FILE **_f) { - char *path = NULL; - mode_t saved_umask; - int fd; - FILE *f; - - assert(_f); - - if (m->running_as == MANAGER_SYSTEM) - asprintf(&path, "/run/systemd/dump-%lu-XXXXXX", (unsigned long) getpid()); - else - asprintf(&path, "/tmp/systemd-dump-%lu-XXXXXX", (unsigned long) getpid()); - - if (!path) - return -ENOMEM; - - saved_umask = umask(0077); - fd = mkostemp(path, O_RDWR|O_CLOEXEC); - umask(saved_umask); - - if (fd < 0) { - free(path); - return -errno; - } - - unlink(path); - - log_debug("Serializing state to %s", path); - free(path); - - if (!(f = fdopen(fd, "w+"))) - return -errno; - - *_f = f; - - return 0; -} - -int manager_serialize(Manager *m, FILE *f, FDSet *fds) { - Iterator i; - Unit *u; - const char *t; - int r; - - assert(m); - assert(f); - assert(fds); - - m->n_reloading ++; - - fprintf(f, "current-job-id=%i\n", m->current_job_id); - fprintf(f, "taint-usr=%s\n", yes_no(m->taint_usr)); - - dual_timestamp_serialize(f, "initrd-timestamp", &m->initrd_timestamp); - dual_timestamp_serialize(f, "startup-timestamp", &m->startup_timestamp); - dual_timestamp_serialize(f, "finish-timestamp", &m->finish_timestamp); - - fputc('\n', f); - - HASHMAP_FOREACH_KEY(u, t, m->units, i) { - if (u->id != t) - continue; - - if (!unit_can_serialize(u)) - continue; - - /* Start marker */ - fputs(u->id, f); - fputc('\n', f); - - if ((r = unit_serialize(u, f, fds)) < 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, "taint-usr=")) { - int b; - - if ((b = parse_boolean(l+10)) < 0) - log_debug("Failed to parse taint /usr flag %s", l+10); - else - m->taint_usr = m->taint_usr || b; - } else if (startswith(l, "initrd-timestamp=")) - dual_timestamp_deserialize(l+17, &m->initrd_timestamp); - else if (startswith(l, "startup-timestamp=")) - dual_timestamp_deserialize(l+18, &m->startup_timestamp); - else if (startswith(l, "finish-timestamp=")) - dual_timestamp_deserialize(l+17, &m->finish_timestamp); - else - 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); - - if ((r = manager_load_unit(m, strstrip(name), NULL, NULL, &u)) < 0) - goto finish; - - if ((r = unit_deserialize(u, f, fds)) < 0) - goto finish; - } - -finish: - if (ferror(f)) { - r = -EIO; - goto finish; - } - - assert(m->n_reloading > 0); - m->n_reloading --; - - return r; -} - -int manager_reload(Manager *m) { - int r, q; - FILE *f; - FDSet *fds; - - assert(m); - - if ((r = manager_open_serialization(m, &f)) < 0) - return r; - - m->n_reloading ++; - - if (!(fds = fdset_new())) { - m->n_reloading --; - r = -ENOMEM; - goto finish; - } - - if ((r = manager_serialize(m, f, fds)) < 0) { - m->n_reloading --; - goto finish; - } - - if (fseeko(f, 0, SEEK_SET) < 0) { - m->n_reloading --; - r = -errno; - goto finish; - } - - /* From here on there is no way back. */ - manager_clear_jobs_and_units(m); - manager_undo_generators(m); - - /* Find new unit paths */ - lookup_paths_free(&m->lookup_paths); - if ((q = lookup_paths_init(&m->lookup_paths, m->running_as, true)) < 0) - r = q; - - manager_run_generators(m); - - manager_build_unit_path_cache(m); - - /* First, enumerate what we can from all config files */ - if ((q = manager_enumerate(m)) < 0) - r = q; - - /* Second, deserialize our stored data */ - if ((q = manager_deserialize(m, f, fds)) < 0) - r = q; - - fclose(f); - f = NULL; - - /* Third, fire things up! */ - if ((q = manager_coldplug(m)) < 0) - r = q; - - assert(m->n_reloading > 0); - m->n_reloading--; - -finish: - if (f) - fclose(f); - - if (fds) - fdset_free(fds); - - return r; -} - -bool manager_is_booting_or_shutting_down(Manager *m) { - Unit *u; - - assert(m); - - /* Is the initial job still around? */ - if (manager_get_job(m, m->default_unit_job_id)) - return true; - - /* Is there a job for the shutdown target? */ - u = manager_get_unit(m, SPECIAL_SHUTDOWN_TARGET); - if (u) - return !!u->job; - - return false; -} - -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_pending_inactive(Manager *m, const char *name) { - Unit *u; - - assert(m); - assert(name); - - /* Returns true if the unit is inactive or going down */ - if (!(u = manager_get_unit(m, name))) - return true; - - return unit_pending_inactive(u); -} - -void manager_check_finished(Manager *m) { - char userspace[FORMAT_TIMESPAN_MAX], initrd[FORMAT_TIMESPAN_MAX], kernel[FORMAT_TIMESPAN_MAX], sum[FORMAT_TIMESPAN_MAX]; - usec_t kernel_usec = 0, initrd_usec = 0, userspace_usec = 0, total_usec = 0; - - assert(m); - - if (dual_timestamp_is_set(&m->finish_timestamp)) - return; - - if (hashmap_size(m->jobs) > 0) - return; - - dual_timestamp_get(&m->finish_timestamp); - - if (m->running_as == MANAGER_SYSTEM && detect_container(NULL) <= 0) { - - userspace_usec = m->finish_timestamp.monotonic - m->startup_timestamp.monotonic; - total_usec = m->finish_timestamp.monotonic; - - if (dual_timestamp_is_set(&m->initrd_timestamp)) { - - kernel_usec = m->initrd_timestamp.monotonic; - initrd_usec = m->startup_timestamp.monotonic - m->initrd_timestamp.monotonic; - - log_info("Startup finished in %s (kernel) + %s (initrd) + %s (userspace) = %s.", - format_timespan(kernel, sizeof(kernel), kernel_usec), - format_timespan(initrd, sizeof(initrd), initrd_usec), - format_timespan(userspace, sizeof(userspace), userspace_usec), - format_timespan(sum, sizeof(sum), total_usec)); - } else { - kernel_usec = m->startup_timestamp.monotonic; - initrd_usec = 0; - - log_info("Startup finished in %s (kernel) + %s (userspace) = %s.", - format_timespan(kernel, sizeof(kernel), kernel_usec), - format_timespan(userspace, sizeof(userspace), userspace_usec), - format_timespan(sum, sizeof(sum), total_usec)); - } - } else { - userspace_usec = initrd_usec = kernel_usec = 0; - total_usec = m->finish_timestamp.monotonic - m->startup_timestamp.monotonic; - - log_debug("Startup finished in %s.", - format_timespan(sum, sizeof(sum), total_usec)); - } - - bus_broadcast_finished(m, kernel_usec, initrd_usec, userspace_usec, total_usec); - - sd_notifyf(false, - "READY=1\nSTATUS=Startup finished in %s.", - format_timespan(sum, sizeof(sum), total_usec)); -} - -void manager_run_generators(Manager *m) { - DIR *d = NULL; - const char *generator_path; - const char *argv[3]; - mode_t u; - - assert(m); - - generator_path = m->running_as == MANAGER_SYSTEM ? SYSTEM_GENERATOR_PATH : USER_GENERATOR_PATH; - if (!(d = opendir(generator_path))) { - - if (errno == ENOENT) - return; - - log_error("Failed to enumerate generator directory: %m"); - return; - } - - if (!m->generator_unit_path) { - const char *p; - char user_path[] = "/tmp/systemd-generator-XXXXXX"; - - if (m->running_as == MANAGER_SYSTEM && getpid() == 1) { - p = "/run/systemd/generator"; - - if (mkdir_p(p, 0755) < 0) { - log_error("Failed to create generator directory: %m"); - goto finish; - } - - } else { - if (!(p = mkdtemp(user_path))) { - log_error("Failed to create generator directory: %m"); - goto finish; - } - } - - if (!(m->generator_unit_path = strdup(p))) { - log_error("Failed to allocate generator unit path."); - goto finish; - } - } - - argv[0] = NULL; /* Leave this empty, execute_directory() will fill something in */ - argv[1] = m->generator_unit_path; - argv[2] = NULL; - - u = umask(0022); - execute_directory(generator_path, d, (char**) argv); - umask(u); - - if (rmdir(m->generator_unit_path) >= 0) { - /* Uh? we were able to remove this dir? I guess that - * means the directory was empty, hence let's shortcut - * this */ - - free(m->generator_unit_path); - m->generator_unit_path = NULL; - goto finish; - } - - if (!strv_find(m->lookup_paths.unit_path, m->generator_unit_path)) { - char **l; - - if (!(l = strv_append(m->lookup_paths.unit_path, m->generator_unit_path))) { - log_error("Failed to add generator directory to unit search path: %m"); - goto finish; - } - - strv_free(m->lookup_paths.unit_path); - m->lookup_paths.unit_path = l; - - log_debug("Added generator unit path %s to search path.", m->generator_unit_path); - } - -finish: - if (d) - closedir(d); -} - -void manager_undo_generators(Manager *m) { - assert(m); - - if (!m->generator_unit_path) - return; - - strv_remove(m->lookup_paths.unit_path, m->generator_unit_path); - rm_rf(m->generator_unit_path, false, true, false); - - free(m->generator_unit_path); - m->generator_unit_path = NULL; -} - -int manager_set_default_controllers(Manager *m, char **controllers) { - char **l; - - assert(m); - - if (!(l = strv_copy(controllers))) - return -ENOMEM; - - strv_free(m->default_controllers); - m->default_controllers = l; - - return 0; -} - -void manager_recheck_journal(Manager *m) { - Unit *u; - - assert(m); - - if (m->running_as != MANAGER_SYSTEM) - return; - - u = manager_get_unit(m, SPECIAL_JOURNALD_SOCKET); - if (u && SOCKET(u)->state != SOCKET_RUNNING) { - log_close_journal(); - return; - } - - u = manager_get_unit(m, SPECIAL_JOURNALD_SERVICE); - if (u && SERVICE(u)->state != SERVICE_RUNNING) { - log_close_journal(); - return; - } - - /* Hmm, OK, so the socket is fully up and the service is up - * too, then let's make use of the thing. */ - log_open(); -} - -void manager_set_show_status(Manager *m, bool b) { - assert(m); - - if (m->running_as != MANAGER_SYSTEM) - return; - - m->show_status = b; - - if (b) - touch("/run/systemd/show-status"); - else - unlink("/run/systemd/show-status"); -} - -bool manager_get_show_status(Manager *m) { - assert(m); - - if (m->running_as != MANAGER_SYSTEM) - return false; - - if (m->show_status) - return true; - - /* If Plymouth is running make sure we show the status, so - * that there's something nice to see when people press Esc */ - - return plymouth_running(); -} - -static const char* const manager_running_as_table[_MANAGER_RUNNING_AS_MAX] = { - [MANAGER_SYSTEM] = "system", - [MANAGER_USER] = "user" -}; - -DEFINE_STRING_TABLE_LOOKUP(manager_running_as, ManagerRunningAs); diff --git a/src/manager.h b/src/manager.h deleted file mode 100644 index 9ecc926cbf..0000000000 --- a/src/manager.h +++ /dev/null @@ -1,304 +0,0 @@ -/*-*- Mode: C; c-basic-offset: 8; indent-tabs-mode: nil -*-*/ - -#ifndef foomanagerhfoo -#define foomanagerhfoo - -/*** - This file is part of systemd. - - Copyright 2010 Lennart Poettering - - systemd is free software; you can redistribute it and/or modify it - under the terms of the GNU General Public License as published by - the Free Software Foundation; either version 2 of the License, or - (at your option) any later version. - - systemd is distributed in the hope that 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 General Public License - along with systemd; If not, see . -***/ - -#include -#include -#include -#include - -#include "fdset.h" - -/* Enforce upper limit how many names we allow */ -#define MANAGER_MAX_NAMES 131072 /* 128K */ - -typedef struct Manager Manager; -typedef enum WatchType WatchType; -typedef struct Watch Watch; - -typedef enum ManagerExitCode { - MANAGER_RUNNING, - MANAGER_EXIT, - MANAGER_RELOAD, - MANAGER_REEXECUTE, - MANAGER_REBOOT, - MANAGER_POWEROFF, - MANAGER_HALT, - MANAGER_KEXEC, - _MANAGER_EXIT_CODE_MAX, - _MANAGER_EXIT_CODE_INVALID = -1 -} ManagerExitCode; - -typedef enum ManagerRunningAs { - MANAGER_SYSTEM, - MANAGER_USER, - _MANAGER_RUNNING_AS_MAX, - _MANAGER_RUNNING_AS_INVALID = -1 -} ManagerRunningAs; - -enum WatchType { - WATCH_INVALID, - WATCH_SIGNAL, - WATCH_NOTIFY, - WATCH_FD, - WATCH_UNIT_TIMER, - WATCH_JOB_TIMER, - WATCH_MOUNT, - WATCH_SWAP, - WATCH_UDEV, - WATCH_DBUS_WATCH, - WATCH_DBUS_TIMEOUT -}; - -struct Watch { - int fd; - WatchType type; - union { - struct Unit *unit; - struct Job *job; - DBusWatch *bus_watch; - DBusTimeout *bus_timeout; - } data; - bool fd_is_dupped:1; - bool socket_accept:1; -}; - -#include "unit.h" -#include "job.h" -#include "hashmap.h" -#include "list.h" -#include "set.h" -#include "dbus.h" -#include "path-lookup.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); - - /* Jobs to be added */ - Hashmap *transaction_jobs; /* Unit object => Job object list 1:1 */ - JobDependency *transaction_anchor; - - Hashmap *watch_pids; /* pid => Unit object n:1 */ - - char *notify_socket; - - Watch notify_watch; - Watch signal_watch; - - int epoll_fd; - - unsigned n_snapshots; - - LookupPaths lookup_paths; - Set *unit_path_cache; - - char **environment; - char **default_controllers; - - usec_t runtime_watchdog; - usec_t shutdown_watchdog; - - dual_timestamp initrd_timestamp; - dual_timestamp startup_timestamp; - dual_timestamp finish_timestamp; - - char *generator_unit_path; - - /* Data specific to the device subsystem */ - struct udev* udev; - struct udev_monitor* udev_monitor; - Watch udev_watch; - Hashmap *devices_by_sysfs; - - /* Data specific to the mount subsystem */ - FILE *proc_self_mountinfo; - Watch mount_watch; - - /* Data specific to the swap filesystem */ - FILE *proc_swaps; - Hashmap *swaps_by_proc_swaps; - bool request_reload; - Watch swap_watch; - - /* Data specific to the D-Bus subsystem */ - DBusConnection *api_bus, *system_bus; - DBusServer *private_bus; - Set *bus_connections, *bus_connections_for_dispatch; - - DBusMessage *queued_message; /* This is used during reloading: - * before the reload we queue the - * reply message here, and - * afterwards we send it */ - DBusConnection *queued_message_connection; /* The connection to send the queued message on */ - - Hashmap *watch_bus; /* D-Bus names => Unit object n:1 */ - int32_t name_data_slot; - int32_t conn_data_slot; - int32_t subscribed_data_slot; - - 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_bondings; /* path string => CGroupBonding object 1:n */ - char *cgroup_hierarchy; - - usec_t gc_queue_timestamp; - int gc_marker; - unsigned n_in_gc_queue; - - /* Make sure the user cannot accidentally unmount our cgroup - * file system */ - int pin_cgroupfs_fd; - - /* Audit fd */ -#ifdef HAVE_AUDIT - int audit_fd; -#endif - - /* Flags */ - ManagerRunningAs running_as; - ManagerExitCode exit_code:5; - - bool dispatching_load_queue:1; - bool dispatching_run_queue:1; - bool dispatching_dbus_queue:1; - - bool taint_usr:1; - - bool show_status; - bool confirm_spawn; -#ifdef HAVE_SYSV_COMPAT - bool sysv_console; -#endif - bool mount_auto; - bool swap_auto; - - ExecOutput default_std_output, default_std_error; - - /* non-zero if we are reloading or reexecuting, */ - int n_reloading; - - unsigned n_installed_jobs; - unsigned n_failed_jobs; -}; - -int manager_new(ManagerRunningAs running_as, Manager **m); -void manager_free(Manager *m); - -int manager_enumerate(Manager *m); -int manager_coldplug(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_unit_from_dbus_path(Manager *m, const char *s, Unit **_u); -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, DBusError *e, Unit **_ret); -int manager_load_unit(Manager *m, const char *name, const char *path, DBusError *e, Unit **_ret); - -int manager_add_job(Manager *m, JobType type, Unit *unit, JobMode mode, bool force, DBusError *e, Job **_ret); -int manager_add_job_by_name(Manager *m, JobType type, const char *name, JobMode mode, bool force, DBusError *e, 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_transaction_unlink_job(Manager *m, Job *j, bool delete_dependencies); - -void manager_clear_jobs(Manager *m); - -unsigned manager_dispatch_load_queue(Manager *m); -unsigned manager_dispatch_run_queue(Manager *m); -unsigned manager_dispatch_dbus_queue(Manager *m); - -int manager_set_default_controllers(Manager *m, char **controllers); - -int manager_loop(Manager *m); - -void manager_dispatch_bus_name_owner_changed(Manager *m, const char *name, const char* old_owner, const char *new_owner); -void manager_dispatch_bus_query_pid_done(Manager *m, const char *name, pid_t pid); - -int manager_open_serialization(Manager *m, FILE **_f); - -int manager_serialize(Manager *m, FILE *f, FDSet *fds); -int manager_deserialize(Manager *m, FILE *f, FDSet *fds); - -int manager_reload(Manager *m); - -bool manager_is_booting_or_shutting_down(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_pending_inactive(Manager *m, const char *name); - -void manager_check_finished(Manager *m); - -void manager_run_generators(Manager *m); -void manager_undo_generators(Manager *m); - -void manager_recheck_journal(Manager *m); - -void manager_set_show_status(Manager *m, bool b); -bool manager_get_show_status(Manager *m); - -const char *manager_running_as_to_string(ManagerRunningAs i); -ManagerRunningAs manager_running_as_from_string(const char *s); - -#endif diff --git a/src/mount.c b/src/mount.c deleted file mode 100644 index 7dbeaf9cf0..0000000000 --- a/src/mount.c +++ /dev/null @@ -1,1930 +0,0 @@ -/*-*- Mode: C; c-basic-offset: 8; indent-tabs-mode: nil -*-*/ - -/*** - This file is part of systemd. - - Copyright 2010 Lennart Poettering - - systemd is free software; you can redistribute it and/or modify it - under the terms of the GNU General Public License as published by - the Free Software Foundation; either version 2 of the License, or - (at your option) any later version. - - systemd is distributed in the hope that 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 General Public License - along with systemd; If not, see . -***/ - -#include -#include -#include -#include -#include - -#include "unit.h" -#include "mount.h" -#include "load-fragment.h" -#include "load-dropin.h" -#include "log.h" -#include "strv.h" -#include "mkdir.h" -#include "mount-setup.h" -#include "unit-name.h" -#include "dbus-mount.h" -#include "special.h" -#include "bus-errors.h" -#include "exit-status.h" -#include "def.h" - -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 void mount_init(Unit *u) { - Mount *m = MOUNT(u); - - assert(u); - assert(u->load_state == UNIT_STUB); - - m->timeout_usec = DEFAULT_TIMEOUT_USEC; - m->directory_mode = 0755; - - exec_context_init(&m->exec_context); - - /* The stdio/kmsg bridge socket is on /, in order to avoid a - * dep loop, don't use kmsg logging for -.mount */ - if (!unit_has_name(u, "-.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 /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->timer_watch.type = WATCH_INVALID; - - m->control_command_id = _MOUNT_EXEC_COMMAND_INVALID; - - UNIT(m)->ignore_on_isolate = true; -} - -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); - - free(m->where); - m->where = NULL; - - mount_parameters_done(&m->parameters_etc_fstab); - mount_parameters_done(&m->parameters_proc_self_mountinfo); - mount_parameters_done(&m->parameters_fragment); - - exec_context_done(&m->exec_context); - exec_command_done_array(m->exec_command, _MOUNT_EXEC_COMMAND_MAX); - m->control_command = NULL; - - mount_unwatch_control_pid(m); - - unit_unwatch_timer(u, &m->timer_watch); -} - -static MountParameters* get_mount_parameters_configured(Mount *m) { - assert(m); - - if (m->from_fragment) - return &m->parameters_fragment; - else if (m->from_etc_fstab) - return &m->parameters_etc_fstab; - - return NULL; -} - -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_configured(m); -} - -static int mount_add_mount_links(Mount *m) { - Unit *other; - int r; - MountParameters *pm; - - assert(m); - - pm = get_mount_parameters_configured(m); - - /* Adds in links to other mount points that might lie below or - * above us in the hierarchy */ - - LIST_FOREACH(units_by_type, other, UNIT(m)->manager->units_by_type[UNIT_MOUNT]) { - Mount *n = MOUNT(other); - MountParameters *pn; - - if (n == m) - continue; - - if (UNIT(n)->load_state != UNIT_LOADED) - continue; - - pn = get_mount_parameters_configured(n); - - if (path_startswith(m->where, n->where)) { - - if ((r = unit_add_dependency(UNIT(m), UNIT_AFTER, UNIT(n), true)) < 0) - return r; - - if (pn) - if ((r = unit_add_dependency(UNIT(m), UNIT_REQUIRES, UNIT(n), true)) < 0) - return r; - - } else if (path_startswith(n->where, m->where)) { - - if ((r = unit_add_dependency(UNIT(n), UNIT_AFTER, UNIT(m), true)) < 0) - return r; - - if (pm) - if ((r = unit_add_dependency(UNIT(n), UNIT_REQUIRES, UNIT(m), true)) < 0) - return r; - - } else if (pm && pm->what && path_startswith(pm->what, n->where)) { - - if ((r = unit_add_dependency(UNIT(m), UNIT_AFTER, UNIT(n), true)) < 0) - return r; - - if ((r = unit_add_dependency(UNIT(m), UNIT_REQUIRES, UNIT(n), true)) < 0) - return r; - - } else if (pn && pn->what && path_startswith(pn->what, m->where)) { - - if ((r = unit_add_dependency(UNIT(n), UNIT_AFTER, UNIT(m), true)) < 0) - return r; - - if ((r = unit_add_dependency(UNIT(n), UNIT_REQUIRES, UNIT(m), true)) < 0) - return r; - } - } - - return 0; -} - -static int mount_add_swap_links(Mount *m) { - Unit *other; - int r; - - assert(m); - - LIST_FOREACH(units_by_type, other, UNIT(m)->manager->units_by_type[UNIT_SWAP]) - if ((r = swap_add_one_mount_link(SWAP(other), m)) < 0) - return r; - - return 0; -} - -static int mount_add_path_links(Mount *m) { - Unit *other; - int r; - - assert(m); - - LIST_FOREACH(units_by_type, other, UNIT(m)->manager->units_by_type[UNIT_PATH]) - if ((r = path_add_one_mount_link(PATH(other), m)) < 0) - return r; - - return 0; -} - -static int mount_add_automount_links(Mount *m) { - Unit *other; - int r; - - assert(m); - - LIST_FOREACH(units_by_type, other, UNIT(m)->manager->units_by_type[UNIT_AUTOMOUNT]) - if ((r = automount_add_one_mount_link(AUTOMOUNT(other), m)) < 0) - return r; - - return 0; -} - -static int mount_add_socket_links(Mount *m) { - Unit *other; - int r; - - assert(m); - - LIST_FOREACH(units_by_type, other, UNIT(m)->manager->units_by_type[UNIT_SOCKET]) - if ((r = socket_add_one_mount_link(SOCKET(other), m)) < 0) - return r; - - return 0; -} - -static char* mount_test_option(const char *haystack, const char *needle) { - struct mntent me; - - assert(needle); - - /* Like glibc's hasmntopt(), but works on a string, not a - * struct mntent */ - - if (!haystack) - return NULL; - - zero(me); - me.mnt_opts = (char*) haystack; - - return hasmntopt(&me, needle); -} - -static bool mount_is_network(MountParameters *p) { - assert(p); - - if (mount_test_option(p->options, "_netdev")) - return true; - - if (p->fstype && fstype_is_network(p->fstype)) - return true; - - return false; -} - -static bool mount_is_bind(MountParameters *p) { - assert(p); - - if (mount_test_option(p->options, "bind")) - return true; - - if (p->fstype && streq(p->fstype, "bind")) - return true; - - return false; -} - -static bool needs_quota(MountParameters *p) { - assert(p); - - if (mount_is_network(p)) - return false; - - if (mount_is_bind(p)) - return false; - - return mount_test_option(p->options, "usrquota") || - mount_test_option(p->options, "grpquota") || - mount_test_option(p->options, "quota") || - mount_test_option(p->options, "usrjquota") || - mount_test_option(p->options, "grpjquota"); -} - -static int mount_add_fstab_links(Mount *m) { - const char *target, *after, *tu_wants = NULL; - MountParameters *p; - Unit *tu; - int r; - bool noauto, nofail, handle, automount; - - assert(m); - - if (UNIT(m)->manager->running_as != MANAGER_SYSTEM) - return 0; - - if (!(p = get_mount_parameters_configured(m))) - return 0; - - if (p != &m->parameters_etc_fstab) - return 0; - - noauto = !!mount_test_option(p->options, "noauto"); - nofail = !!mount_test_option(p->options, "nofail"); - automount = - mount_test_option(p->options, "comment=systemd.automount") || - mount_test_option(p->options, "x-systemd-automount"); - handle = - automount || - mount_test_option(p->options, "comment=systemd.mount") || - mount_test_option(p->options, "x-systemd-mount") || - UNIT(m)->manager->mount_auto; - - if (mount_is_network(p)) { - target = SPECIAL_REMOTE_FS_TARGET; - after = tu_wants = SPECIAL_REMOTE_FS_PRE_TARGET; - } else { - target = SPECIAL_LOCAL_FS_TARGET; - after = SPECIAL_LOCAL_FS_PRE_TARGET; - } - - r = manager_load_unit(UNIT(m)->manager, target, NULL, NULL, &tu); - if (r < 0) - return r; - - if (tu_wants) { - r = unit_add_dependency_by_name(tu, UNIT_WANTS, tu_wants, NULL, true); - if (r < 0) - return r; - } - - if (after) { - r = unit_add_dependency_by_name(UNIT(m), UNIT_AFTER, after, NULL, true); - if (r < 0) - return r; - } - - if (automount) { - Unit *am; - - if ((r = unit_load_related_unit(UNIT(m), ".automount", &am)) < 0) - return r; - - /* If auto is configured as well also pull in the - * mount right-away, but don't rely on it. */ - if (!noauto) /* automount + auto */ - if ((r = unit_add_dependency(tu, UNIT_WANTS, UNIT(m), true)) < 0) - return r; - - /* Install automount unit */ - if (!nofail) /* automount + fail */ - return unit_add_two_dependencies(tu, UNIT_AFTER, UNIT_REQUIRES, am, true); - else /* automount + nofail */ - return unit_add_two_dependencies(tu, UNIT_AFTER, UNIT_WANTS, am, true); - - } else if (handle && !noauto) { - - /* Automatically add mount points that aren't natively - * configured to local-fs.target */ - - if (!nofail) /* auto + fail */ - return unit_add_two_dependencies(tu, UNIT_AFTER, UNIT_REQUIRES, UNIT(m), true); - else /* auto + nofail */ - return unit_add_dependency(tu, UNIT_WANTS, UNIT(m), true); - } - - return 0; -} - -static int mount_add_device_links(Mount *m) { - MountParameters *p; - int r; - - assert(m); - - if (!(p = get_mount_parameters_configured(m))) - return 0; - - if (!p->what) - return 0; - - if (!mount_is_bind(p) && - !path_equal(m->where, "/") && - p == &m->parameters_etc_fstab) { - bool nofail, noauto; - - noauto = !!mount_test_option(p->options, "noauto"); - nofail = !!mount_test_option(p->options, "nofail"); - - if ((r = unit_add_node_link(UNIT(m), p->what, - !noauto && nofail && - UNIT(m)->manager->running_as == MANAGER_SYSTEM)) < 0) - return r; - } - - if (p->passno > 0 && - !mount_is_bind(p) && - UNIT(m)->manager->running_as == MANAGER_SYSTEM && - !path_equal(m->where, "/")) { - char *name; - Unit *fsck; - /* Let's add in the fsck service */ - - /* aka SPECIAL_FSCK_SERVICE */ - if (!(name = unit_name_from_path_instance("fsck", p->what, ".service"))) - return -ENOMEM; - - if ((r = manager_load_unit_prepare(UNIT(m)->manager, name, NULL, NULL, &fsck)) < 0) { - log_warning("Failed to prepare unit %s: %s", name, strerror(-r)); - free(name); - return r; - } - - free(name); - - SERVICE(fsck)->fsck_passno = p->passno; - - if ((r = unit_add_two_dependencies(UNIT(m), UNIT_AFTER, UNIT_REQUIRES, fsck, true)) < 0) - return r; - } - - return 0; -} - -static int mount_add_default_dependencies(Mount *m) { - int r; - MountParameters *p; - - assert(m); - - if (UNIT(m)->manager->running_as != MANAGER_SYSTEM) - return 0; - - p = get_mount_parameters_configured(m); - if (p && needs_quota(p)) { - if ((r = unit_add_two_dependencies_by_name(UNIT(m), UNIT_BEFORE, UNIT_WANTS, SPECIAL_QUOTACHECK_SERVICE, NULL, true)) < 0 || - (r = unit_add_two_dependencies_by_name(UNIT(m), UNIT_BEFORE, UNIT_WANTS, SPECIAL_QUOTAON_SERVICE, NULL, true)) < 0) - return r; - } - - if (!path_equal(m->where, "/")) - if ((r = unit_add_two_dependencies_by_name(UNIT(m), UNIT_BEFORE, UNIT_CONFLICTS, SPECIAL_UMOUNT_TARGET, NULL, true)) < 0) - return r; - - return 0; -} - -static int mount_fix_timeouts(Mount *m) { - MountParameters *p; - const char *timeout = NULL; - Unit *other; - Iterator i; - usec_t u; - char *t; - int r; - - assert(m); - - if (!(p = get_mount_parameters_configured(m))) - return 0; - - /* Allow configuration how long we wait for a device that - * backs a mount point to show up. This is useful to support - * endless device timeouts for devices that show up only after - * user input, like crypto devices. */ - - if ((timeout = mount_test_option(p->options, "comment=systemd.device-timeout"))) - timeout += 31; - else if ((timeout = mount_test_option(p->options, "x-systemd-device-timeout"))) - timeout += 25; - else - return 0; - - t = strndup(timeout, strcspn(timeout, ",;" WHITESPACE)); - if (!t) - return -ENOMEM; - - r = parse_usec(t, &u); - free(t); - - if (r < 0) { - log_warning("Failed to parse timeout for %s, ignoring: %s", m->where, timeout); - return r; - } - - SET_FOREACH(other, UNIT(m)->dependencies[UNIT_AFTER], i) { - if (other->type != UNIT_DEVICE) - continue; - - other->job_timeout = u; - } - - return 0; -} - -static int mount_verify(Mount *m) { - bool b; - char *e; - assert(m); - - if (UNIT(m)->load_state != UNIT_LOADED) - return 0; - - if (!m->from_etc_fstab && !m->from_fragment && !m->from_proc_self_mountinfo) - return -ENOENT; - - if (!(e = unit_name_from_path(m->where, ".mount"))) - return -ENOMEM; - - b = unit_has_name(UNIT(m), e); - free(e); - - if (!b) { - log_error("%s's Where setting doesn't match unit name. Refusing.", UNIT(m)->id); - return -EINVAL; - } - - if (mount_point_is_api(m->where) || mount_point_ignore(m->where)) { - log_error("Cannot create mount unit for API file system %s. Refusing.", m->where); - return -EINVAL; - } - - if (UNIT(m)->fragment_path && !m->parameters_fragment.what) { - log_error("%s's What setting is missing. Refusing.", UNIT(m)->id); - return -EBADMSG; - } - - if (m->exec_context.pam_name && m->exec_context.kill_mode != KILL_CONTROL_GROUP) { - log_error("%s has PAM enabled. Kill mode must be set to 'control-group'. Refusing.", UNIT(m)->id); - return -EINVAL; - } - - return 0; -} - -static int mount_load(Unit *u) { - Mount *m = MOUNT(u); - int r; - - assert(u); - assert(u->load_state == UNIT_STUB); - - if ((r = unit_load_fragment_and_dropin_optional(u)) < 0) - return r; - - /* This is a new unit? Then let's add in some extras */ - if (u->load_state == UNIT_LOADED) { - if ((r = unit_add_exec_dependencies(u, &m->exec_context)) < 0) - return r; - - if (UNIT(m)->fragment_path) - m->from_fragment = true; - else if (m->from_etc_fstab) - /* We always add several default dependencies to fstab mounts, - * but we do not want the implicit complementing of Wants= with After= - * in the target unit that this mount unit will be hooked into. */ - UNIT(m)->default_dependencies = false; - - if (!m->where) - if (!(m->where = unit_name_to_path(u->id))) - return -ENOMEM; - - path_kill_slashes(m->where); - - if (!UNIT(m)->description) - if ((r = unit_set_description(u, m->where)) < 0) - return r; - - if ((r = mount_add_device_links(m)) < 0) - return r; - - if ((r = mount_add_mount_links(m)) < 0) - return r; - - if ((r = mount_add_socket_links(m)) < 0) - return r; - - if ((r = mount_add_swap_links(m)) < 0) - return r; - - if ((r = mount_add_path_links(m)) < 0) - return r; - - if ((r = mount_add_automount_links(m)) < 0) - return r; - - if ((r = mount_add_fstab_links(m)) < 0) - return r; - - if (UNIT(m)->default_dependencies || m->from_etc_fstab) - if ((r = mount_add_default_dependencies(m)) < 0) - return r; - - if ((r = unit_add_default_cgroups(u)) < 0) - return r; - - mount_fix_timeouts(m); - } - - return mount_verify(m); -} - -static int mount_notify_automount(Mount *m, int status) { - Unit *p; - int r; - Iterator i; - - assert(m); - - SET_FOREACH(p, UNIT(m)->dependencies[UNIT_TRIGGERED_BY], i) - if (p->type == UNIT_AUTOMOUNT) { - r = automount_send_ready(AUTOMOUNT(p), status); - if (r < 0) - return r; - } - - return 0; -} - -static void mount_set_state(Mount *m, MountState state) { - MountState old_state; - assert(m); - - old_state = m->state; - m->state = state; - - if (state != MOUNT_MOUNTING && - state != MOUNT_MOUNTING_DONE && - state != MOUNT_REMOUNTING && - state != MOUNT_UNMOUNTING && - state != MOUNT_MOUNTING_SIGTERM && - state != MOUNT_MOUNTING_SIGKILL && - state != MOUNT_UNMOUNTING_SIGTERM && - state != MOUNT_UNMOUNTING_SIGKILL && - state != MOUNT_REMOUNTING_SIGTERM && - state != MOUNT_REMOUNTING_SIGKILL) { - unit_unwatch_timer(UNIT(m), &m->timer_watch); - mount_unwatch_control_pid(m); - m->control_command = NULL; - m->control_command_id = _MOUNT_EXEC_COMMAND_INVALID; - } - - if (state == MOUNT_MOUNTED || - state == MOUNT_REMOUNTING) - mount_notify_automount(m, 0); - else if (state == MOUNT_DEAD || - state == MOUNT_UNMOUNTING || - state == MOUNT_MOUNTING_SIGTERM || - state == MOUNT_MOUNTING_SIGKILL || - state == MOUNT_REMOUNTING_SIGTERM || - state == MOUNT_REMOUNTING_SIGKILL || - state == MOUNT_UNMOUNTING_SIGTERM || - state == MOUNT_UNMOUNTING_SIGKILL || - state == MOUNT_FAILED) - mount_notify_automount(m, -ENODEV); - - if (state != old_state) - log_debug("%s changed %s -> %s", - UNIT(m)->id, - 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) { - - if (new_state == MOUNT_MOUNTING || - new_state == MOUNT_MOUNTING_DONE || - new_state == MOUNT_REMOUNTING || - new_state == MOUNT_UNMOUNTING || - new_state == MOUNT_MOUNTING_SIGTERM || - new_state == MOUNT_MOUNTING_SIGKILL || - new_state == MOUNT_UNMOUNTING_SIGTERM || - new_state == MOUNT_UNMOUNTING_SIGKILL || - new_state == MOUNT_REMOUNTING_SIGTERM || - new_state == MOUNT_REMOUNTING_SIGKILL) { - - if (m->control_pid <= 0) - return -EBADMSG; - - if ((r = unit_watch_pid(UNIT(m), m->control_pid)) < 0) - return r; - - if ((r = unit_watch_timer(UNIT(m), m->timeout_usec, &m->timer_watch)) < 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 /etc/fstab: %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, strna(p->what), - prefix, strna(p->fstype), - prefix, strna(p->options), - prefix, yes_no(m->from_etc_fstab), - 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: %lu\n", - prefix, (unsigned long) m->control_pid); - - exec_context_dump(&m->exec_context, f, prefix); -} - -static int mount_spawn(Mount *m, ExecCommand *c, pid_t *_pid) { - pid_t pid; - int r; - - assert(m); - assert(c); - assert(_pid); - - if ((r = unit_watch_timer(UNIT(m), m->timeout_usec, &m->timer_watch)) < 0) - goto fail; - - if ((r = exec_spawn(c, - NULL, - &m->exec_context, - NULL, 0, - UNIT(m)->manager->environment, - true, - true, - true, - UNIT(m)->manager->confirm_spawn, - UNIT(m)->cgroup_bondings, - UNIT(m)->cgroup_attributes, - &pid)) < 0) - goto fail; - - if ((r = unit_watch_pid(UNIT(m), pid)) < 0) - /* FIXME: we need to do something here */ - goto fail; - - *_pid = pid; - - return 0; - -fail: - unit_unwatch_timer(UNIT(m), &m->timer_watch); - - return r; -} - -static void mount_enter_dead(Mount *m, MountResult f) { - assert(m); - - if (f != MOUNT_SUCCESS) - m->result = f; - - 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; - Set *pid_set = NULL; - bool wait_for_exit = false; - - assert(m); - - if (f != MOUNT_SUCCESS) - m->result = f; - - if (m->exec_context.kill_mode != KILL_NONE) { - int sig = (state == MOUNT_MOUNTING_SIGTERM || - state == MOUNT_UNMOUNTING_SIGTERM || - state == MOUNT_REMOUNTING_SIGTERM) ? m->exec_context.kill_signal : SIGKILL; - - if (m->control_pid > 0) { - if (kill_and_sigcont(m->control_pid, sig) < 0 && errno != ESRCH) - - log_warning("Failed to kill control process %li: %m", (long) m->control_pid); - else - wait_for_exit = true; - } - - if (m->exec_context.kill_mode == KILL_CONTROL_GROUP) { - - if (!(pid_set = set_new(trivial_hash_func, trivial_compare_func))) { - r = -ENOMEM; - goto fail; - } - - /* Exclude the control pid from being killed via the cgroup */ - if (m->control_pid > 0) - if ((r = set_put(pid_set, LONG_TO_PTR(m->control_pid))) < 0) - goto fail; - - if ((r = cgroup_bonding_kill_list(UNIT(m)->cgroup_bondings, sig, true, pid_set)) < 0) { - if (r != -EAGAIN && r != -ESRCH && r != -ENOENT) - log_warning("Failed to kill control group: %s", strerror(-r)); - } else if (r > 0) - wait_for_exit = true; - - set_free(pid_set); - pid_set = NULL; - } - } - - if (wait_for_exit) { - if ((r = unit_watch_timer(UNIT(m), m->timeout_usec, &m->timer_watch)) < 0) - goto fail; - - mount_set_state(m, state); - } else if (state == MOUNT_REMOUNTING_SIGTERM || state == MOUNT_REMOUNTING_SIGKILL) - mount_enter_mounted(m, MOUNT_SUCCESS); - else - mount_enter_dead(m, MOUNT_SUCCESS); - - return; - -fail: - log_warning("%s failed to kill processes: %s", UNIT(m)->id, strerror(-r)); - - if (state == MOUNT_REMOUNTING_SIGTERM || state == MOUNT_REMOUNTING_SIGKILL) - mount_enter_mounted(m, MOUNT_FAILURE_RESOURCES); - else - mount_enter_dead(m, MOUNT_FAILURE_RESOURCES); - - if (pid_set) - set_free(pid_set); -} - -static void mount_enter_unmounting(Mount *m) { - int r; - - assert(m); - - m->control_command_id = MOUNT_EXEC_UNMOUNT; - m->control_command = m->exec_command + MOUNT_EXEC_UNMOUNT; - - if ((r = exec_command_set( - m->control_command, - "/bin/umount", - m->where, - NULL)) < 0) - goto fail; - - mount_unwatch_control_pid(m); - - if ((r = mount_spawn(m, m->control_command, &m->control_pid)) < 0) - goto fail; - - mount_set_state(m, MOUNT_UNMOUNTING); - - return; - -fail: - log_warning("%s failed to run 'umount' task: %s", UNIT(m)->id, strerror(-r)); - mount_enter_mounted(m, MOUNT_FAILURE_RESOURCES); -} - -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; - - mkdir_p(m->where, m->directory_mode); - - /* Create the source directory for bind-mounts if needed */ - p = get_mount_parameters_configured(m); - if (p && mount_is_bind(p)) - mkdir_p(p->what, m->directory_mode); - - if (m->from_fragment) - r = exec_command_set( - m->control_command, - "/bin/mount", - m->parameters_fragment.what, - m->where, - "-t", m->parameters_fragment.fstype ? m->parameters_fragment.fstype : "auto", - m->parameters_fragment.options ? "-o" : NULL, m->parameters_fragment.options, - NULL); - else if (m->from_etc_fstab) - r = exec_command_set( - m->control_command, - "/bin/mount", - m->where, - NULL); - else - r = -ENOENT; - - if (r < 0) - goto fail; - - mount_unwatch_control_pid(m); - - if ((r = mount_spawn(m, m->control_command, &m->control_pid)) < 0) - goto fail; - - mount_set_state(m, MOUNT_MOUNTING); - - return; - -fail: - log_warning("%s failed to run 'mount' task: %s", UNIT(m)->id, strerror(-r)); - mount_enter_dead(m, MOUNT_FAILURE_RESOURCES); -} - -static void mount_enter_mounting_done(Mount *m) { - assert(m); - - mount_set_state(m, MOUNT_MOUNTING_DONE); -} - -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) { - char *buf = NULL; - const char *o; - - if (m->parameters_fragment.options) { - if (!(buf = strappend("remount,", m->parameters_fragment.options))) { - r = -ENOMEM; - goto fail; - } - - o = buf; - } else - o = "remount"; - - r = exec_command_set( - m->control_command, - "/bin/mount", - m->parameters_fragment.what, - m->where, - "-t", m->parameters_fragment.fstype ? m->parameters_fragment.fstype : "auto", - "-o", o, - NULL); - - free(buf); - } else if (m->from_etc_fstab) - r = exec_command_set( - m->control_command, - "/bin/mount", - m->where, - "-o", "remount", - NULL); - else - r = -ENOENT; - - if (r < 0) - goto fail; - - mount_unwatch_control_pid(m); - - if ((r = mount_spawn(m, m->control_command, &m->control_pid)) < 0) - goto fail; - - mount_set_state(m, MOUNT_REMOUNTING); - - return; - -fail: - log_warning("%s failed to run 'remount' task: %s", UNIT(m)->id, strerror(-r)); - m->reload_result = MOUNT_FAILURE_RESOURCES; - mount_enter_mounted(m, MOUNT_SUCCESS); -} - -static int mount_start(Unit *u) { - Mount *m = MOUNT(u); - - assert(m); - - /* We cannot fulfill this request right now, try again later - * please! */ - if (m->state == MOUNT_UNMOUNTING || - m->state == MOUNT_UNMOUNTING_SIGTERM || - m->state == MOUNT_UNMOUNTING_SIGKILL || - m->state == MOUNT_MOUNTING_SIGTERM || - m->state == MOUNT_MOUNTING_SIGKILL) - return -EAGAIN; - - /* Already on it! */ - if (m->state == MOUNT_MOUNTING) - return 0; - - assert(m->state == MOUNT_DEAD || m->state == MOUNT_FAILED); - - m->result = MOUNT_SUCCESS; - m->reload_result = MOUNT_SUCCESS; - - mount_enter_mounting(m); - return 0; -} - -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 0; -} - -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 0; -} - -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", "%lu", (unsigned long) 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_debug("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_debug("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_debug("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_debug("Failed to parse control-pid value %s", value); - else - m->control_pid = pid; - } else if (streq(key, "control-command")) { - MountExecCommand id; - - if ((id = mount_exec_command_from_string(value)) < 0) - log_debug("Failed to parse exec-command value %s", value); - else { - m->control_command_id = id; - m->control_command = m->exec_command + id; - } - - } else - log_debug("Unknown serialization key '%s'", key); - - return 0; -} - -static UnitActiveState mount_active_state(Unit *u) { - assert(u); - - return state_translation_table[MOUNT(u)->state]; -} - -static const char *mount_sub_state_to_string(Unit *u) { - assert(u); - - return mount_state_to_string(MOUNT(u)->state); -} - -static bool mount_check_gc(Unit *u) { - Mount *m = MOUNT(u); - - assert(m); - - return m->from_etc_fstab || 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)) - 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_full(f == MOUNT_SUCCESS ? LOG_DEBUG : LOG_NOTICE, - "%s mount process exited, code=%s status=%i", u->id, 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) - 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 void mount_timer_event(Unit *u, uint64_t elapsed, Watch *w) { - Mount *m = MOUNT(u); - - assert(m); - assert(elapsed == 1); - assert(w == &m->timer_watch); - - switch (m->state) { - - case MOUNT_MOUNTING: - case MOUNT_MOUNTING_DONE: - log_warning("%s mounting timed out. Stopping.", u->id); - mount_enter_signal(m, MOUNT_MOUNTING_SIGTERM, MOUNT_FAILURE_TIMEOUT); - break; - - case MOUNT_REMOUNTING: - log_warning("%s remounting timed out. Stopping.", u->id); - m->reload_result = MOUNT_FAILURE_TIMEOUT; - mount_enter_mounted(m, MOUNT_SUCCESS); - break; - - case MOUNT_UNMOUNTING: - log_warning("%s unmounting timed out. Stopping.", u->id); - mount_enter_signal(m, MOUNT_UNMOUNTING_SIGTERM, MOUNT_FAILURE_TIMEOUT); - break; - - case MOUNT_MOUNTING_SIGTERM: - if (m->exec_context.send_sigkill) { - log_warning("%s mounting timed out. Killing.", u->id); - mount_enter_signal(m, MOUNT_MOUNTING_SIGKILL, MOUNT_FAILURE_TIMEOUT); - } else { - log_warning("%s mounting timed out. Skipping SIGKILL. Ignoring.", u->id); - - 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->exec_context.send_sigkill) { - log_warning("%s remounting timed out. Killing.", u->id); - mount_enter_signal(m, MOUNT_REMOUNTING_SIGKILL, MOUNT_FAILURE_TIMEOUT); - } else { - log_warning("%s remounting timed out. Skipping SIGKILL. Ignoring.", u->id); - - 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->exec_context.send_sigkill) { - log_warning("%s unmounting timed out. Killing.", u->id); - mount_enter_signal(m, MOUNT_UNMOUNTING_SIGKILL, MOUNT_FAILURE_TIMEOUT); - } else { - log_warning("%s unmounting timed out. Skipping SIGKILL. Ignoring.", u->id); - - 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_warning("%s mount process still around after SIGKILL. Ignoring.", u->id); - - 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."); - } -} - -static int mount_add_one( - Manager *m, - const char *what, - const char *where, - const char *options, - const char *fstype, - int passno, - bool from_proc_self_mountinfo, - bool set_flags) { - int r; - Unit *u; - bool delete; - char *e, *w = NULL, *o = NULL, *f = NULL; - MountParameters *p; - - assert(m); - assert(what); - assert(where); - assert(options); - assert(fstype); - - assert(!set_flags || from_proc_self_mountinfo); - - /* 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; - - e = unit_name_from_path(where, ".mount"); - if (!e) - return -ENOMEM; - - u = manager_get_unit(m, e); - if (!u) { - delete = true; - - u = unit_new(m, sizeof(Mount)); - if (!u) { - free(e); - return -ENOMEM; - } - - r = unit_add_name(u, e); - free(e); - - if (r < 0) - goto fail; - - MOUNT(u)->where = strdup(where); - if (!MOUNT(u)->where) { - r = -ENOMEM; - goto fail; - } - - unit_add_to_load_queue(u); - } else { - delete = false; - free(e); - } - - if (!(w = strdup(what)) || - !(o = strdup(options)) || - !(f = strdup(fstype))) { - r = -ENOMEM; - goto fail; - } - - if (from_proc_self_mountinfo) { - p = &MOUNT(u)->parameters_proc_self_mountinfo; - - if (set_flags) { - MOUNT(u)->is_mounted = true; - MOUNT(u)->just_mounted = !MOUNT(u)->from_proc_self_mountinfo; - MOUNT(u)->just_changed = !streq_ptr(p->options, o); - } - - MOUNT(u)->from_proc_self_mountinfo = true; - } else { - p = &MOUNT(u)->parameters_etc_fstab; - MOUNT(u)->from_etc_fstab = true; - } - - free(p->what); - p->what = w; - - free(p->options); - p->options = o; - - free(p->fstype); - p->fstype = f; - - p->passno = passno; - - unit_add_to_dbus_queue(u); - - return 0; - -fail: - free(w); - free(o); - free(f); - - if (delete && u) - unit_free(u); - - return r; -} - -static int mount_find_pri(char *options) { - char *end, *pri; - unsigned long r; - - if (!(pri = mount_test_option(options, "pri"))) - return 0; - - pri += 4; - - errno = 0; - r = strtoul(pri, &end, 10); - - if (errno != 0) - return -errno; - - if (end == pri || (*end != ',' && *end != 0)) - return -EINVAL; - - return (int) r; -} - -static int mount_load_etc_fstab(Manager *m) { - FILE *f; - int r = 0; - struct mntent* me; - - assert(m); - - errno = 0; - if (!(f = setmntent("/etc/fstab", "r"))) - return -errno; - - while ((me = getmntent(f))) { - char *where, *what; - int k; - - if (!(what = fstab_node_to_udev_node(me->mnt_fsname))) { - r = -ENOMEM; - goto finish; - } - - if (!(where = strdup(me->mnt_dir))) { - free(what); - r = -ENOMEM; - goto finish; - } - - if (what[0] == '/') - path_kill_slashes(what); - - if (where[0] == '/') - path_kill_slashes(where); - - if (streq(me->mnt_type, "swap")) { - int pri; - - if ((pri = mount_find_pri(me->mnt_opts)) < 0) - k = pri; - else - k = swap_add_one(m, - what, - NULL, - pri, - !!mount_test_option(me->mnt_opts, "noauto"), - !!mount_test_option(me->mnt_opts, "nofail"), - !!mount_test_option(me->mnt_opts, "comment=systemd.swapon"), - false); - } else - k = mount_add_one(m, what, where, me->mnt_opts, me->mnt_type, me->mnt_passno, false, false); - - free(what); - free(where); - - if (k < 0) - r = k; - } - -finish: - - endmntent(f); - return r; -} - -static int mount_load_proc_self_mountinfo(Manager *m, bool set_flags) { - int r = 0; - unsigned i; - char *device, *path, *options, *options2, *fstype, *d, *p, *o; - - assert(m); - - rewind(m->proc_self_mountinfo); - - for (i = 1;; i++) { - int k; - - device = path = options = options2 = fstype = d = p = o = NULL; - - if ((k = fscanf(m->proc_self_mountinfo, - "%*s " /* (1) mount id */ - "%*s " /* (2) parent id */ - "%*s " /* (3) major:minor */ - "%*s " /* (4) root */ - "%ms " /* (5) mount point */ - "%ms" /* (6) mount options */ - "%*[^-]" /* (7) optional fields */ - "- " /* (8) separator */ - "%ms " /* (9) file system type */ - "%ms" /* (10) mount source */ - "%ms" /* (11) mount options 2 */ - "%*[^\n]", /* some rubbish at the end */ - &path, - &options, - &fstype, - &device, - &options2)) != 5) { - - if (k == EOF) - break; - - log_warning("Failed to parse /proc/self/mountinfo:%u.", i); - goto clean_up; - } - - if (asprintf(&o, "%s,%s", options, options2) < 0) { - r = -ENOMEM; - goto finish; - } - - if (!(d = cunescape(device)) || - !(p = cunescape(path))) { - r = -ENOMEM; - goto finish; - } - - if ((k = mount_add_one(m, d, p, o, fstype, 0, true, set_flags)) < 0) - r = k; - -clean_up: - free(device); - free(path); - free(options); - free(options2); - free(fstype); - free(d); - free(p); - free(o); - } - -finish: - free(device); - free(path); - free(options); - free(options2); - free(fstype); - free(d); - free(p); - free(o); - - return r; -} - -static void mount_shutdown(Manager *m) { - assert(m); - - if (m->proc_self_mountinfo) { - fclose(m->proc_self_mountinfo); - m->proc_self_mountinfo = NULL; - } -} - -static int mount_enumerate(Manager *m) { - int r; - struct epoll_event ev; - assert(m); - - if (!m->proc_self_mountinfo) { - if (!(m->proc_self_mountinfo = fopen("/proc/self/mountinfo", "re"))) - return -errno; - - m->mount_watch.type = WATCH_MOUNT; - m->mount_watch.fd = fileno(m->proc_self_mountinfo); - - zero(ev); - ev.events = EPOLLPRI; - ev.data.ptr = &m->mount_watch; - - if (epoll_ctl(m->epoll_fd, EPOLL_CTL_ADD, m->mount_watch.fd, &ev) < 0) - return -errno; - } - - if ((r = mount_load_etc_fstab(m)) < 0) - goto fail; - - if ((r = mount_load_proc_self_mountinfo(m, false)) < 0) - goto fail; - - return 0; - -fail: - mount_shutdown(m); - return r; -} - -void mount_fd_event(Manager *m, int events) { - Unit *u; - int r; - - assert(m); - assert(events & EPOLLPRI); - - /* The manager calls this for every fd event happening on the - * /proc/self/mountinfo file, which informs us about mounting - * table changes */ - - if ((r = mount_load_proc_self_mountinfo(m, true)) < 0) { - log_error("Failed to reread /proc/self/mountinfo: %s", strerror(-r)); - - /* 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; - } - - 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) { - /* This has just been unmounted. */ - - mount->from_proc_self_mountinfo = false; - - switch (mount->state) { - - case MOUNT_MOUNTED: - mount_enter_dead(mount, MOUNT_SUCCESS); - break; - - default: - mount_set_state(mount, mount->state); - break; - - } - - } else if (mount->just_mounted || mount->just_changed) { - - /* New or changed mount entry */ - - switch (mount->state) { - - case MOUNT_DEAD: - case MOUNT_FAILED: - mount_enter_mounted(mount, MOUNT_SUCCESS); - break; - - case MOUNT_MOUNTING: - mount_enter_mounting_done(mount); - 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; - } - } - - /* Reset the flags for later calls */ - mount->is_mounted = mount->just_mounted = mount->just_changed = false; - } -} - -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, KillMode mode, int signo, DBusError *error) { - Mount *m = MOUNT(u); - int r = 0; - Set *pid_set = NULL; - - assert(m); - - if (who == KILL_MAIN) { - dbus_set_error(error, BUS_ERROR_NO_SUCH_PROCESS, "Mount units have no main processes"); - return -ESRCH; - } - - if (m->control_pid <= 0 && who == KILL_CONTROL) { - dbus_set_error(error, BUS_ERROR_NO_SUCH_PROCESS, "No control process to kill"); - return -ESRCH; - } - - if (who == KILL_CONTROL || who == KILL_ALL) - if (m->control_pid > 0) - if (kill(m->control_pid, signo) < 0) - r = -errno; - - if (who == KILL_ALL && mode == KILL_CONTROL_GROUP) { - int q; - - if (!(pid_set = set_new(trivial_hash_func, trivial_compare_func))) - return -ENOMEM; - - /* Exclude the control pid from being killed via the cgroup */ - if (m->control_pid > 0) - if ((q = set_put(pid_set, LONG_TO_PTR(m->control_pid))) < 0) { - r = q; - goto finish; - } - - if ((q = cgroup_bonding_kill_list(UNIT(m)->cgroup_bondings, signo, false, pid_set)) < 0) - if (q != -EAGAIN && q != -ESRCH && q != -ENOENT) - r = q; - } - -finish: - if (pid_set) - set_free(pid_set); - - return r; -} - -static const char* const mount_state_table[_MOUNT_STATE_MAX] = { - [MOUNT_DEAD] = "dead", - [MOUNT_MOUNTING] = "mounting", - [MOUNT_MOUNTING_DONE] = "mounting-done", - [MOUNT_MOUNTED] = "mounted", - [MOUNT_REMOUNTING] = "remounting", - [MOUNT_UNMOUNTING] = "unmounting", - [MOUNT_MOUNTING_SIGTERM] = "mounting-sigterm", - [MOUNT_MOUNTING_SIGKILL] = "mounting-sigkill", - [MOUNT_REMOUNTING_SIGTERM] = "remounting-sigterm", - [MOUNT_REMOUNTING_SIGKILL] = "remounting-sigkill", - [MOUNT_UNMOUNTING_SIGTERM] = "unmounting-sigterm", - [MOUNT_UNMOUNTING_SIGKILL] = "unmounting-sigkill", - [MOUNT_FAILED] = "failed" -}; - -DEFINE_STRING_TABLE_LOOKUP(mount_state, MountState); - -static const char* const mount_exec_command_table[_MOUNT_EXEC_COMMAND_MAX] = { - [MOUNT_EXEC_MOUNT] = "ExecMount", - [MOUNT_EXEC_UNMOUNT] = "ExecUnmount", - [MOUNT_EXEC_REMOUNT] = "ExecRemount", -}; - -DEFINE_STRING_TABLE_LOOKUP(mount_exec_command, MountExecCommand); - -static const char* const mount_result_table[_MOUNT_RESULT_MAX] = { - [MOUNT_SUCCESS] = "success", - [MOUNT_FAILURE_RESOURCES] = "resources", - [MOUNT_FAILURE_TIMEOUT] = "timeout", - [MOUNT_FAILURE_EXIT_CODE] = "exit-code", - [MOUNT_FAILURE_SIGNAL] = "signal", - [MOUNT_FAILURE_CORE_DUMP] = "core-dump" -}; - -DEFINE_STRING_TABLE_LOOKUP(mount_result, MountResult); - -const UnitVTable mount_vtable = { - .suffix = ".mount", - .object_size = sizeof(Mount), - .sections = - "Unit\0" - "Mount\0" - "Install\0", - - .no_alias = true, - .no_instances = true, - .show_status = true, - - .init = mount_init, - .load = mount_load, - .done = mount_done, - - .coldplug = mount_coldplug, - - .dump = mount_dump, - - .start = mount_start, - .stop = mount_stop, - .reload = mount_reload, - - .kill = mount_kill, - - .serialize = mount_serialize, - .deserialize_item = mount_deserialize_item, - - .active_state = mount_active_state, - .sub_state_to_string = mount_sub_state_to_string, - - .check_gc = mount_check_gc, - - .sigchld_event = mount_sigchld_event, - .timer_event = mount_timer_event, - - .reset_failed = mount_reset_failed, - - .bus_interface = "org.freedesktop.systemd1.Mount", - .bus_message_handler = bus_mount_message_handler, - .bus_invalidating_properties = bus_mount_invalidating_properties, - - .enumerate = mount_enumerate, - .shutdown = mount_shutdown -}; diff --git a/src/mount.h b/src/mount.h deleted file mode 100644 index 9318444249..0000000000 --- a/src/mount.h +++ /dev/null @@ -1,124 +0,0 @@ -/*-*- Mode: C; c-basic-offset: 8; indent-tabs-mode: nil -*-*/ - -#ifndef foomounthfoo -#define foomounthfoo - -/*** - This file is part of systemd. - - Copyright 2010 Lennart Poettering - - systemd is free software; you can redistribute it and/or modify it - under the terms of the GNU General Public License as published by - the Free Software Foundation; either version 2 of the License, or - (at your option) any later version. - - systemd is distributed in the hope that 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 General Public License - along with systemd; If not, see . -***/ - -typedef struct Mount Mount; - -#include "unit.h" - -typedef enum MountState { - MOUNT_DEAD, - MOUNT_MOUNTING, /* /bin/mount is running, but the mount is not done yet. */ - MOUNT_MOUNTING_DONE, /* /bin/mount is running, and the mount is done. */ - MOUNT_MOUNTED, - MOUNT_REMOUNTING, - MOUNT_UNMOUNTING, - MOUNT_MOUNTING_SIGTERM, - MOUNT_MOUNTING_SIGKILL, - MOUNT_REMOUNTING_SIGTERM, - MOUNT_REMOUNTING_SIGKILL, - MOUNT_UNMOUNTING_SIGTERM, - MOUNT_UNMOUNTING_SIGKILL, - MOUNT_FAILED, - _MOUNT_STATE_MAX, - _MOUNT_STATE_INVALID = -1 -} MountState; - -typedef enum MountExecCommand { - MOUNT_EXEC_MOUNT, - MOUNT_EXEC_UNMOUNT, - MOUNT_EXEC_REMOUNT, - _MOUNT_EXEC_COMMAND_MAX, - _MOUNT_EXEC_COMMAND_INVALID = -1 -} MountExecCommand; - -typedef struct MountParameters { - char *what; - char *options; - char *fstype; - int passno; -} MountParameters; - -typedef enum MountResult { - MOUNT_SUCCESS, - MOUNT_FAILURE_RESOURCES, - MOUNT_FAILURE_TIMEOUT, - MOUNT_FAILURE_EXIT_CODE, - MOUNT_FAILURE_SIGNAL, - MOUNT_FAILURE_CORE_DUMP, - _MOUNT_RESULT_MAX, - _MOUNT_RESULT_INVALID = -1 -} MountResult; - -struct Mount { - Unit meta; - - char *where; - - MountParameters parameters_etc_fstab; - MountParameters parameters_proc_self_mountinfo; - MountParameters parameters_fragment; - - bool from_etc_fstab:1; - 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; - - MountResult result; - MountResult reload_result; - - mode_t directory_mode; - - usec_t timeout_usec; - - ExecCommand exec_command[_MOUNT_EXEC_COMMAND_MAX]; - ExecContext exec_context; - - MountState state, deserialized_state; - - ExecCommand* control_command; - MountExecCommand control_command_id; - pid_t control_pid; - - Watch timer_watch; -}; - -extern const UnitVTable mount_vtable; - -void mount_fd_event(Manager *m, int events); - -const char* mount_state_to_string(MountState i); -MountState mount_state_from_string(const char *s); - -const char* mount_exec_command_to_string(MountExecCommand i); -MountExecCommand mount_exec_command_from_string(const char *s); - -const char* mount_result_to_string(MountResult i); -MountResult mount_result_from_string(const char *s); - -#endif diff --git a/src/namespace.c b/src/namespace.c deleted file mode 100644 index 09bc82909f..0000000000 --- a/src/namespace.c +++ /dev/null @@ -1,346 +0,0 @@ -/*-*- Mode: C; c-basic-offset: 8; indent-tabs-mode: nil -*-*/ - -/*** - This file is part of systemd. - - Copyright 2010 Lennart Poettering - - systemd is free software; you can redistribute it and/or modify it - under the terms of the GNU General Public License as published by - the Free Software Foundation; either version 2 of the License, or - (at your option) any later version. - - systemd is distributed in the hope that 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 General Public License - along with systemd; If not, see . -***/ - -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include - -#include "strv.h" -#include "util.h" -#include "namespace.h" -#include "missing.h" - -typedef enum PathMode { - /* This is ordered by priority! */ - INACCESSIBLE, - READONLY, - PRIVATE, - READWRITE -} PathMode; - -typedef struct Path { - const char *path; - PathMode mode; -} Path; - -static int append_paths(Path **p, char **strv, PathMode mode) { - char **i; - - STRV_FOREACH(i, strv) { - - if (!path_is_absolute(*i)) - return -EINVAL; - - (*p)->path = *i; - (*p)->mode = mode; - (*p)++; - } - - return 0; -} - -static int path_compare(const void *a, const void *b) { - const Path *p = a, *q = b; - - if (path_equal(p->path, q->path)) { - - /* 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 */ - if (path_startswith(p->path, q->path)) - return 1; - - if (path_startswith(q->path, p->path)) - return -1; - - return 0; -} - -static void drop_duplicates(Path *p, unsigned *n, bool *need_inaccessible, bool *need_private) { - Path *f, *t, *previous; - - assert(p); - assert(n); - assert(need_inaccessible); - assert(need_private); - - for (f = p, t = p, previous = NULL; f < p+*n; f++) { - - if (previous && path_equal(f->path, previous->path)) - continue; - - t->path = f->path; - t->mode = f->mode; - - if (t->mode == PRIVATE) - *need_private = true; - - if (t->mode == INACCESSIBLE) - *need_inaccessible = true; - - previous = t; - - t++; - } - - *n = t - p; -} - -static int apply_mount(Path *p, const char *root_dir, const char *inaccessible_dir, const char *private_dir, unsigned long flags) { - const char *what; - char *where; - int r; - - assert(p); - assert(root_dir); - assert(inaccessible_dir); - assert(private_dir); - - if (!(where = strappend(root_dir, p->path))) - return -ENOMEM; - - switch (p->mode) { - - case INACCESSIBLE: - what = inaccessible_dir; - flags |= MS_RDONLY; - break; - - case READONLY: - flags |= MS_RDONLY; - /* Fall through */ - - case READWRITE: - what = p->path; - break; - - case PRIVATE: - what = private_dir; - break; - - default: - assert_not_reached("Unknown mode"); - } - - if ((r = mount(what, where, NULL, MS_BIND|MS_REC, NULL)) >= 0) { - log_debug("Successfully mounted %s to %s", what, where); - - /* The bind mount will always inherit the original - * flags. If we want to set any flag we need - * to do so in a second independent step. */ - if (flags) - r = mount(NULL, where, NULL, MS_REMOUNT|MS_BIND|MS_REC|flags, NULL); - - /* Avoid exponential growth of trees */ - if (r >= 0 && path_equal(p->path, "/")) - r = mount(NULL, where, NULL, MS_REMOUNT|MS_BIND|MS_UNBINDABLE|flags, NULL); - - if (r < 0) { - r = -errno; - umount2(where, MNT_DETACH); - } - } - - free(where); - return r; -} - -int setup_namespace( - char **writable, - char **readable, - char **inaccessible, - bool private_tmp, - unsigned long flags) { - - char - tmp_dir[] = "/tmp/systemd-namespace-XXXXXX", - root_dir[] = "/tmp/systemd-namespace-XXXXXX/root", - old_root_dir[] = "/tmp/systemd-namespace-XXXXXX/root/tmp/old-root-XXXXXX", - inaccessible_dir[] = "/tmp/systemd-namespace-XXXXXX/inaccessible", - private_dir[] = "/tmp/systemd-namespace-XXXXXX/private"; - - Path *paths, *p; - unsigned n; - bool need_private = false, need_inaccessible = false; - bool remove_tmp = false, remove_root = false, remove_old_root = false, remove_inaccessible = false, remove_private = false; - int r; - const char *t; - - n = - strv_length(writable) + - strv_length(readable) + - strv_length(inaccessible) + - (private_tmp ? 2 : 1); - - if (!(paths = new(Path, n))) - return -ENOMEM; - - p = paths; - if ((r = append_paths(&p, writable, READWRITE)) < 0 || - (r = append_paths(&p, readable, READONLY)) < 0 || - (r = append_paths(&p, inaccessible, INACCESSIBLE)) < 0) - goto fail; - - if (private_tmp) { - p->path = "/tmp"; - p->mode = PRIVATE; - p++; - } - - p->path = "/"; - p->mode = READWRITE; - p++; - - assert(paths + n == p); - - qsort(paths, n, sizeof(Path), path_compare); - drop_duplicates(paths, &n, &need_inaccessible, &need_private); - - if (!mkdtemp(tmp_dir)) { - r = -errno; - goto fail; - } - remove_tmp = true; - - memcpy(root_dir, tmp_dir, sizeof(tmp_dir)-1); - if (mkdir(root_dir, 0777) < 0) { - r = -errno; - goto fail; - } - remove_root = true; - - if (need_inaccessible) { - memcpy(inaccessible_dir, tmp_dir, sizeof(tmp_dir)-1); - if (mkdir(inaccessible_dir, 0) < 0) { - r = -errno; - goto fail; - } - remove_inaccessible = true; - } - - if (need_private) { - mode_t u; - - memcpy(private_dir, tmp_dir, sizeof(tmp_dir)-1); - - u = umask(0000); - if (mkdir(private_dir, 0777 + S_ISVTX) < 0) { - umask(u); - - r = -errno; - goto fail; - } - - umask(u); - remove_private = true; - } - - if (unshare(CLONE_NEWNS) < 0) { - r = -errno; - goto fail; - } - - /* Remount / as SLAVE so that nothing mounted in the namespace - shows up in the parent */ - if (mount(NULL, "/", NULL, MS_SLAVE|MS_REC, NULL) < 0) { - r = -errno; - goto fail; - } - - for (p = paths; p < paths + n; p++) - if ((r = apply_mount(p, root_dir, inaccessible_dir, private_dir, flags)) < 0) - goto undo_mounts; - - memcpy(old_root_dir, tmp_dir, sizeof(tmp_dir)-1); - if (!mkdtemp(old_root_dir)) { - r = -errno; - goto undo_mounts; - } - remove_old_root = true; - - if (chdir(root_dir) < 0) { - r = -errno; - goto undo_mounts; - } - - if (pivot_root(root_dir, old_root_dir) < 0) { - r = -errno; - goto undo_mounts; - } - - t = old_root_dir + sizeof(root_dir) - 1; - if (umount2(t, MNT_DETACH) < 0) - /* At this point it's too late to turn anything back, - * since we are already in the new root. */ - return -errno; - - if (rmdir(t) < 0) - return -errno; - - return 0; - -undo_mounts: - - for (p--; p >= paths; p--) { - char full_path[PATH_MAX]; - - snprintf(full_path, sizeof(full_path), "%s%s", root_dir, p->path); - char_array_0(full_path); - - umount2(full_path, MNT_DETACH); - } - -fail: - if (remove_old_root) - rmdir(old_root_dir); - - if (remove_inaccessible) - rmdir(inaccessible_dir); - - if (remove_private) - rmdir(private_dir); - - if (remove_root) - rmdir(root_dir); - - if (remove_tmp) - rmdir(tmp_dir); - - free(paths); - - return r; -} diff --git a/src/namespace.h b/src/namespace.h deleted file mode 100644 index 7cf1dedd32..0000000000 --- a/src/namespace.h +++ /dev/null @@ -1,34 +0,0 @@ -/*-*- Mode: C; c-basic-offset: 8; indent-tabs-mode: nil -*-*/ - -#ifndef foonamespacehfoo -#define foonamespacehfoo - -/*** - This file is part of systemd. - - Copyright 2010 Lennart Poettering - - systemd is free software; you can redistribute it and/or modify it - under the terms of the GNU General Public License as published by - the Free Software Foundation; either version 2 of the License, or - (at your option) any later version. - - systemd is distributed in the hope that 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 General Public License - along with systemd; If not, see . -***/ - -#include - -int setup_namespace( - char **writable, - char **readable, - char **inaccessible, - bool private_tmp, - unsigned long flags); - -#endif diff --git a/src/path.c b/src/path.c deleted file mode 100644 index 1d50885ed4..0000000000 --- a/src/path.c +++ /dev/null @@ -1,771 +0,0 @@ -/*-*- Mode: C; c-basic-offset: 8; indent-tabs-mode: nil -*-*/ - -/*** - This file is part of systemd. - - Copyright 2010 Lennart Poettering - - systemd is free software; you can redistribute it and/or modify it - under the terms of the GNU General Public License as published by - the Free Software Foundation; either version 2 of the License, or - (at your option) any later version. - - systemd is distributed in the hope that 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 General Public License - along with systemd; If not, see . -***/ - -#include -#include -#include -#include -#include - -#include "unit.h" -#include "unit-name.h" -#include "path.h" -#include "mkdir.h" -#include "dbus-path.h" -#include "special.h" -#include "bus-errors.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 -}; - -int path_spec_watch(PathSpec *s, Unit *u) { - - 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 *k, *slash; - int r; - - assert(u); - assert(s); - - path_spec_unwatch(s, u); - - if (!(k = strdup(s->path))) - return -ENOMEM; - - if ((s->inotify_fd = inotify_init1(IN_NONBLOCK|IN_CLOEXEC)) < 0) { - r = -errno; - goto fail; - } - - if (unit_watch_fd(u, s->inotify_fd, EPOLLIN, &s->watch) < 0) { - r = -errno; - goto fail; - } - - if ((s->primary_wd = inotify_add_watch(s->inotify_fd, k, flags_table[s->type])) >= 0) - exists = true; - - do { - int flags; - - /* This assumes the path was passed through path_kill_slashes()! */ - if (!(slash = strrchr(k, '/'))) - break; - - /* Trim the path at the last slash. Keep the slash if it's the root dir. */ - slash[slash == k] = 0; - - flags = IN_MOVE_SELF; - if (!exists) - flags |= IN_DELETE_SELF | IN_ATTRIB | IN_CREATE | IN_MOVED_TO; - - if (inotify_add_watch(s->inotify_fd, k, flags) >= 0) - exists = true; - } while (slash != k); - - return 0; - -fail: - free(k); - - path_spec_unwatch(s, u); - return r; -} - -void path_spec_unwatch(PathSpec *s, Unit *u) { - - if (s->inotify_fd < 0) - return; - - unit_unwatch_fd(u, &s->watch); - - close_nointr_nofail(s->inotify_fd); - s->inotify_fd = -1; -} - -int path_spec_fd_event(PathSpec *s, uint32_t events) { - uint8_t *buf = NULL; - struct inotify_event *e; - ssize_t k; - int l; - int r = 0; - - if (events != EPOLLIN) { - log_error("Got Invalid poll event on inotify."); - r = -EINVAL; - goto out; - } - - if (ioctl(s->inotify_fd, FIONREAD, &l) < 0) { - log_error("FIONREAD failed: %m"); - r = -errno; - goto out; - } - - assert(l > 0); - - if (!(buf = malloc(l))) { - log_error("Failed to allocate buffer: %m"); - r = -errno; - goto out; - } - - if ((k = read(s->inotify_fd, buf, l)) < 0) { - log_error("Failed to read inotify event: %m"); - r = -errno; - goto out; - } - - e = (struct inotify_event*) buf; - - while (k > 0) { - size_t step; - - if ((s->type == PATH_CHANGED || s->type == PATH_MODIFIED) && - s->primary_wd == e->wd) - r = 1; - - step = sizeof(struct inotify_event) + e->len; - assert(step <= (size_t) k); - - e = (struct inotify_event*) ((uint8_t*) e + step); - k -= step; - } -out: - free(buf); - 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 bool path_spec_startswith(PathSpec *s, const char *what) { - return path_startswith(s->path, what); -} - -static void path_spec_mkdir(PathSpec *s, mode_t mode) { - int r; - - if (s->type == PATH_EXISTS || s->type == PATH_EXISTS_GLOB) - return; - - if ((r = mkdir_p(s->path, mode)) < 0) - log_warning("mkdir(%s) failed: %s", s->path, strerror(-r)); -} - -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; -} - -static void path_done(Unit *u) { - Path *p = PATH(u); - PathSpec *s; - - assert(p); - - unit_ref_unset(&p->unit); - - while ((s = p->specs)) { - path_spec_unwatch(s, u); - LIST_REMOVE(PathSpec, spec, p->specs, s); - path_spec_done(s); - free(s); - } -} - -int path_add_one_mount_link(Path *p, Mount *m) { - PathSpec *s; - int r; - - assert(p); - assert(m); - - if (UNIT(p)->load_state != UNIT_LOADED || - UNIT(m)->load_state != UNIT_LOADED) - return 0; - - LIST_FOREACH(spec, s, p->specs) { - - if (!path_spec_startswith(s, m->where)) - continue; - - if ((r = unit_add_two_dependencies(UNIT(p), UNIT_AFTER, UNIT_REQUIRES, UNIT(m), true)) < 0) - return r; - } - - return 0; -} - -static int path_add_mount_links(Path *p) { - Unit *other; - int r; - - assert(p); - - LIST_FOREACH(units_by_type, other, UNIT(p)->manager->units_by_type[UNIT_MOUNT]) - if ((r = path_add_one_mount_link(p, MOUNT(other))) < 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_error("%s lacks path setting. Refusing.", UNIT(p)->id); - return -EINVAL; - } - - return 0; -} - -static int path_add_default_dependencies(Path *p) { - int r; - - assert(p); - - if (UNIT(p)->manager->running_as == MANAGER_SYSTEM) { - if ((r = unit_add_dependency_by_name(UNIT(p), UNIT_BEFORE, SPECIAL_BASIC_TARGET, NULL, true)) < 0) - return r; - - if ((r = unit_add_two_dependencies_by_name(UNIT(p), UNIT_AFTER, UNIT_REQUIRES, SPECIAL_SYSINIT_TARGET, NULL, true)) < 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); - - if ((r = unit_load_fragment_and_dropin(u)) < 0) - return r; - - if (u->load_state == UNIT_LOADED) { - - if (!UNIT_DEREF(p->unit)) { - Unit *x; - - r = unit_load_related_unit(u, ".service", &x); - if (r < 0) - return r; - - unit_ref_set(&p->unit, x); - } - - r = unit_add_two_dependencies(u, UNIT_BEFORE, UNIT_TRIGGERS, UNIT_DEREF(p->unit), true); - if (r < 0) - return r; - - if ((r = path_add_mount_links(p)) < 0) - return r; - - if (UNIT(p)->default_dependencies) - if ((r = path_add_default_dependencies(p)) < 0) - return r; - } - - return path_verify(p); -} - -static void path_dump(Unit *u, FILE *f, const char *prefix) { - Path *p = PATH(u); - PathSpec *s; - - assert(p); - assert(f); - - 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, UNIT_DEREF(p->unit)->id, - 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, UNIT(p)); -} - -static int path_watch(Path *p) { - int r; - PathSpec *s; - - assert(p); - - LIST_FOREACH(spec, s, p->specs) - if ((r = path_spec_watch(s, UNIT(p))) < 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_debug("%s changed %s -> %s", - UNIT(p)->id, - 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) { - int r; - DBusError error; - - assert(p); - dbus_error_init(&error); - - /* Don't start job if we are supposed to go down */ - if (UNIT(p)->job && UNIT(p)->job->type == JOB_STOP) - return; - - if ((r = manager_add_job(UNIT(p)->manager, JOB_START, UNIT_DEREF(p->unit), JOB_REPLACE, true, &error, NULL)) < 0) - goto fail; - - p->inotify_triggered = false; - - if ((r = path_watch(p)) < 0) - goto fail; - - path_set_state(p, PATH_RUNNING); - return; - -fail: - log_warning("%s failed to queue unit startup job: %s", UNIT(p)->id, bus_error(&error, r)); - path_enter_dead(p, PATH_FAILURE_RESOURCES); - - dbus_error_free(&error); -} - -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_debug("%s got triggered.", UNIT(p)->id); - path_enter_running(p); - return; - } - - if ((r = path_watch(p)) < 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_debug("%s got triggered.", UNIT(p)->id); - path_enter_running(p); - return; - } - - path_set_state(p, PATH_WAITING); - return; - -fail: - log_warning("%s failed to enter waiting state: %s", UNIT(p)->id, strerror(-r)); - 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); - - assert(p); - assert(p->state == PATH_DEAD || p->state == PATH_FAILED); - - if (UNIT_DEREF(p->unit)->load_state != UNIT_LOADED) - return -ENOENT; - - path_mkdir(p); - - p->result = PATH_SUCCESS; - path_enter_waiting(p, true, true); - - return 0; -} - -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 0; -} - -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; - - if ((state = path_state_from_string(value)) < 0) - log_debug("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_debug("Failed to parse result value %s", value); - else if (f != PATH_SUCCESS) - p->result = f; - - } else - log_debug("Unknown serialization key '%s'", key); - - return 0; -} - -static UnitActiveState path_active_state(Unit *u) { - assert(u); - - return state_translation_table[PATH(u)->state]; -} - -static const char *path_sub_state_to_string(Unit *u) { - assert(u); - - return path_state_to_string(PATH(u)->state); -} - -static void path_fd_event(Unit *u, int fd, uint32_t events, Watch *w) { - Path *p = PATH(u); - PathSpec *s; - int changed; - - assert(p); - assert(fd >= 0); - - if (p->state != PATH_WAITING && - p->state != PATH_RUNNING) - return; - - /* 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, events); - 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; - -fail: - path_enter_dead(p, PATH_FAILURE_RESOURCES); -} - -void path_unit_notify(Unit *u, UnitActiveState new_state) { - Iterator i; - Unit *k; - - if (u->type == UNIT_PATH) - return; - - SET_FOREACH(k, u->dependencies[UNIT_TRIGGERED_BY], i) { - Path *p; - - if (k->type != UNIT_PATH) - continue; - - if (k->load_state != UNIT_LOADED) - continue; - - p = PATH(k); - - if (p->state == PATH_RUNNING && new_state == UNIT_INACTIVE) { - log_debug("%s got notified about unit deactivation.", UNIT(p)->id); - - /* 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_state_table[_PATH_STATE_MAX] = { - [PATH_DEAD] = "dead", - [PATH_WAITING] = "waiting", - [PATH_RUNNING] = "running", - [PATH_FAILED] = "failed" -}; - -DEFINE_STRING_TABLE_LOOKUP(path_state, PathState); - -static const char* const path_type_table[_PATH_TYPE_MAX] = { - [PATH_EXISTS] = "PathExists", - [PATH_EXISTS_GLOB] = "PathExistsGlob", - [PATH_CHANGED] = "PathChanged", - [PATH_MODIFIED] = "PathModified", - [PATH_DIRECTORY_NOT_EMPTY] = "DirectoryNotEmpty" -}; - -DEFINE_STRING_TABLE_LOOKUP(path_type, PathType); - -static const char* const path_result_table[_PATH_RESULT_MAX] = { - [PATH_SUCCESS] = "success", - [PATH_FAILURE_RESOURCES] = "resources" -}; - -DEFINE_STRING_TABLE_LOOKUP(path_result, PathResult); - -const UnitVTable path_vtable = { - .suffix = ".path", - .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, - - .fd_event = path_fd_event, - - .reset_failed = path_reset_failed, - - .bus_interface = "org.freedesktop.systemd1.Path", - .bus_message_handler = bus_path_message_handler, - .bus_invalidating_properties = bus_path_invalidating_properties -}; diff --git a/src/path.h b/src/path.h deleted file mode 100644 index efb6b5eb44..0000000000 --- a/src/path.h +++ /dev/null @@ -1,113 +0,0 @@ -/*-*- Mode: C; c-basic-offset: 8; indent-tabs-mode: nil -*-*/ - -#ifndef foopathhfoo -#define foopathhfoo - -/*** - This file is part of systemd. - - Copyright 2010 Lennart Poettering - - systemd is free software; you can redistribute it and/or modify it - under the terms of the GNU General Public License as published by - the Free Software Foundation; either version 2 of the License, or - (at your option) any later version. - - systemd is distributed in the hope that 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 General Public License - along with systemd; If not, see . -***/ - -typedef struct Path Path; - -#include "unit.h" -#include "mount.h" - -typedef enum PathState { - PATH_DEAD, - PATH_WAITING, - PATH_RUNNING, - PATH_FAILED, - _PATH_STATE_MAX, - _PATH_STATE_INVALID = -1 -} PathState; - -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 { - char *path; - - Watch watch; - - LIST_FIELDS(struct PathSpec, spec); - - PathType type; - int inotify_fd; - int primary_wd; - - bool previous_exists; -} PathSpec; - -int path_spec_watch(PathSpec *s, Unit *u); -void path_spec_unwatch(PathSpec *s, Unit *u); -int path_spec_fd_event(PathSpec *s, uint32_t events); -void path_spec_done(PathSpec *s); - -static inline bool path_spec_owns_inotify_fd(PathSpec *s, int fd) { - return s->inotify_fd == fd; -} - -typedef enum PathResult { - PATH_SUCCESS, - PATH_FAILURE_RESOURCES, - _PATH_RESULT_MAX, - _PATH_RESULT_INVALID = -1 -} PathResult; - -struct Path { - Unit meta; - - LIST_HEAD(PathSpec, specs); - - UnitRef unit; - - PathState state, deserialized_state; - - bool inotify_triggered; - - bool make_directory; - mode_t directory_mode; - - PathResult result; -}; - -void path_unit_notify(Unit *u, UnitActiveState new_state); - -/* Called from the mount code figure out if a mount is a dependency of - * any of the paths of this path object */ -int path_add_one_mount_link(Path *p, Mount *m); - -extern const UnitVTable path_vtable; - -const char* path_state_to_string(PathState i); -PathState path_state_from_string(const char *s); - -const char* path_type_to_string(PathType i); -PathType path_type_from_string(const char *s); - -const char* path_result_to_string(PathResult i); -PathResult path_result_from_string(const char *s); - -#endif diff --git a/src/polkit.h b/src/polkit.h deleted file mode 100644 index 0d08194b26..0000000000 --- a/src/polkit.h +++ /dev/null @@ -1,36 +0,0 @@ -/*-*- Mode: C; c-basic-offset: 8; indent-tabs-mode: nil -*-*/ - -#ifndef foopolkithfoo -#define foopolkithfoo - -/*** - This file is part of systemd. - - Copyright 2010 Lennart Poettering - - systemd is free software; you can redistribute it and/or modify it - under the terms of the GNU General Public License as published by - the Free Software Foundation; either version 2 of the License, or - (at your option) any later version. - - systemd is distributed in the hope that 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 General Public License - along with systemd; If not, see . -***/ - -#include -#include - -int verify_polkit( - DBusConnection *c, - DBusMessage *request, - const char *action, - bool interactive, - bool *challenge, - DBusError *error); - -#endif diff --git a/src/securebits.h b/src/securebits.h deleted file mode 100644 index ba0bba5353..0000000000 --- a/src/securebits.h +++ /dev/null @@ -1,45 +0,0 @@ -#ifndef _LINUX_SECUREBITS_H -#define _LINUX_SECUREBITS_H 1 - -/* This is minimal version of Linux' linux/securebits.h header file, - * which is licensed GPL2 */ - -#define SECUREBITS_DEFAULT 0x00000000 - -/* When set UID 0 has no special privileges. When unset, we support - inheritance of root-permissions and suid-root executable under - compatibility mode. We raise the effective and inheritable bitmasks - *of the executable file* if the effective uid of the new process is - 0. If the real uid is 0, we raise the effective (legacy) bit of the - executable file. */ -#define SECURE_NOROOT 0 -#define SECURE_NOROOT_LOCKED 1 /* make bit-0 immutable */ - -/* When set, setuid to/from uid 0 does not trigger capability-"fixup". - When unset, to provide compatibility with old programs relying on - set*uid to gain/lose privilege, transitions to/from uid 0 cause - capabilities to be gained/lost. */ -#define SECURE_NO_SETUID_FIXUP 2 -#define SECURE_NO_SETUID_FIXUP_LOCKED 3 /* make bit-2 immutable */ - -/* When set, a process can retain its capabilities even after - transitioning to a non-root user (the set-uid fixup suppressed by - bit 2). Bit-4 is cleared when a process calls exec(); setting both - bit 4 and 5 will create a barrier through exec that no exec()'d - child can use this feature again. */ -#define SECURE_KEEP_CAPS 4 -#define SECURE_KEEP_CAPS_LOCKED 5 /* make bit-4 immutable */ - -/* Each securesetting is implemented using two bits. One bit specifies - whether the setting is on or off. The other bit specify whether the - setting is locked or not. A setting which is locked cannot be - changed from user-level. */ -#define issecure_mask(X) (1 << (X)) -#define issecure(X) (issecure_mask(X) & current_cred_xxx(securebits)) - -#define SECURE_ALL_BITS (issecure_mask(SECURE_NOROOT) | \ - issecure_mask(SECURE_NO_SETUID_FIXUP) | \ - issecure_mask(SECURE_KEEP_CAPS)) -#define SECURE_ALL_LOCKS (SECURE_ALL_BITS << 1) - -#endif /* !_LINUX_SECUREBITS_H */ diff --git a/src/selinux-setup.c b/src/selinux-setup.c deleted file mode 100644 index a7e1fa4007..0000000000 --- a/src/selinux-setup.c +++ /dev/null @@ -1,112 +0,0 @@ -/*-*- Mode: C; c-basic-offset: 8; indent-tabs-mode: nil -*-*/ - -/*** - This file is part of systemd. - - Copyright 2010 Lennart Poettering - - systemd is free software; you can redistribute it and/or modify it - under the terms of the GNU General Public License as published by - the Free Software Foundation; either version 2 of the License, or - (at your option) any later version. - - systemd is distributed in the hope that 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 General Public License - along with systemd; If not, see . -***/ - -#include -#include -#include -#include -#include - -#ifdef HAVE_SELINUX -#include -#endif - -#include "selinux-setup.h" -#include "mount-setup.h" -#include "macro.h" -#include "util.h" -#include "log.h" -#include "label.h" - -int selinux_setup(bool *loaded_policy) { - -#ifdef HAVE_SELINUX - int enforce = 0; - usec_t before_load, after_load; - security_context_t con; - int r; - - assert(loaded_policy); - - /* Make sure getcon() works, which needs /proc and /sys */ - mount_setup_early(); - - /* Already initialized by somebody else? */ - r = getcon_raw(&con); - if (r == 0) { - bool initialized; - - initialized = !streq(con, "kernel"); - freecon(con); - - if (initialized) - return 0; - } - - /* 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) { - char timespan[FORMAT_TIMESPAN_MAX]; - char *label; - - label_retest_selinux(); - - /* Transition to the new context */ - r = label_get_create_label_from_exe(SYSTEMD_BINARY_PATH, &label); - if (r < 0 || label == NULL) { - log_open(); - log_error("Failed to compute init label, ignoring."); - } else { - r = setcon(label); - - log_open(); - if (r < 0) - log_error("Failed to transition into init label '%s', ignoring.", label); - - label_free(label); - } - - after_load = now(CLOCK_MONOTONIC); - - log_info("Successfully loaded SELinux policy in %s.", - format_timespan(timespan, sizeof(timespan), after_load - before_load)); - - *loaded_policy = true; - - } else { - log_open(); - - if (enforce > 0) { - log_error("Failed to load SELinux policy. Freezing."); - return -EIO; - } else - log_debug("Unable to load SELinux policy. Ignoring."); - } -#endif - - return 0; -} diff --git a/src/selinux-setup.h b/src/selinux-setup.h deleted file mode 100644 index 6b8fe00b7d..0000000000 --- a/src/selinux-setup.h +++ /dev/null @@ -1,29 +0,0 @@ -/*-*- Mode: C; c-basic-offset: 8; indent-tabs-mode: nil -*-*/ - -#ifndef fooselinuxsetuphfoo -#define fooselinuxsetuphfoo - -/*** - This file is part of systemd. - - Copyright 2010 Lennart Poettering - - systemd is free software; you can redistribute it and/or modify it - under the terms of the GNU General Public License as published by - the Free Software Foundation; either version 2 of the License, or - (at your option) any later version. - - systemd is distributed in the hope that 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 General Public License - along with systemd; If not, see . -***/ - -#include - -int selinux_setup(bool *loaded_policy); - -#endif diff --git a/src/service.c b/src/service.c deleted file mode 100644 index bf2e0a9d98..0000000000 --- a/src/service.c +++ /dev/null @@ -1,3797 +0,0 @@ -/*-*- Mode: C; c-basic-offset: 8; indent-tabs-mode: nil -*-*/ - -/*** - This file is part of systemd. - - Copyright 2010 Lennart Poettering - - systemd is free software; you can redistribute it and/or modify it - under the terms of the GNU General Public License as published by - the Free Software Foundation; either version 2 of the License, or - (at your option) any later version. - - systemd is distributed in the hope that 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 General Public License - along with systemd; If not, see . -***/ - -#include -#include -#include -#include -#include - -#include "manager.h" -#include "unit.h" -#include "service.h" -#include "load-fragment.h" -#include "load-dropin.h" -#include "log.h" -#include "strv.h" -#include "unit-name.h" -#include "dbus-service.h" -#include "special.h" -#include "bus-errors.h" -#include "exit-status.h" -#include "def.h" -#include "util.h" -#include "utf8.h" - -#ifdef HAVE_SYSV_COMPAT - -#define DEFAULT_SYSV_TIMEOUT_USEC (5*USEC_PER_MINUTE) - -typedef enum RunlevelType { - RUNLEVEL_UP, - RUNLEVEL_DOWN, - RUNLEVEL_SYSINIT -} 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_RUNLEVEL2_TARGET, RUNLEVEL_UP }, - { "rc3.d", SPECIAL_RUNLEVEL3_TARGET, RUNLEVEL_UP }, - { "rc4.d", SPECIAL_RUNLEVEL4_TARGET, RUNLEVEL_UP }, - { "rc5.d", SPECIAL_RUNLEVEL5_TARGET, RUNLEVEL_UP }, - -#ifdef TARGET_SUSE - /* SUSE style boot.d */ - { "boot.d", SPECIAL_SYSINIT_TARGET, RUNLEVEL_SYSINIT }, -#endif - -#if defined(TARGET_DEBIAN) || defined(TARGET_UBUNTU) || defined(TARGET_ANGSTROM) - /* Debian style rcS.d */ - { "rcS.d", SPECIAL_SYSINIT_TARGET, RUNLEVEL_SYSINIT }, -#endif - - /* 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/SYSINIT must be read before DOWN */ -}; - -#define RUNLEVELS_UP "12345" -/* #define RUNLEVELS_DOWN "06" */ -#define RUNLEVELS_BOOT "bBsS" -#endif - -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_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 void service_init(Unit *u) { - Service *s = SERVICE(u); - - assert(u); - assert(u->load_state == UNIT_STUB); - - s->timeout_usec = DEFAULT_TIMEOUT_USEC; - s->restart_usec = DEFAULT_RESTART_USEC; - - s->watchdog_watch.type = WATCH_INVALID; - - s->timer_watch.type = WATCH_INVALID; -#ifdef HAVE_SYSV_COMPAT - s->sysv_start_priority = -1; - s->sysv_start_priority_from_rcnd = -1; -#endif - s->socket_fd = -1; - s->guess_main_pid = true; - - exec_context_init(&s->exec_context); - - RATELIMIT_INIT(s->start_limit, 10*USEC_PER_SEC, 5); - - 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_debug("Stopping watch for %s's PID file %s", UNIT(s)->id, s->pid_file_pathspec->path); - path_spec_unwatch(s->pid_file_pathspec, UNIT(s)); - path_spec_done(s->pid_file_pathspec); - free(s->pid_file_pathspec); - s->pid_file_pathspec = NULL; -} - -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; - - s->main_pid = pid; - s->main_pid_known = true; - - if (get_parent_of_pid(pid, &ppid) >= 0 && ppid != getpid()) { - log_warning("%s: Supervising process %lu which is not our child. We'll most likely not notice when it exits.", - UNIT(s)->id, (unsigned long) pid); - - s->main_pid_alien = true; - } else - s->main_pid_alien = false; - - exec_status_start(&s->main_exec_status, pid); - - return 0; -} - -static void service_close_socket_fd(Service *s) { - assert(s); - - if (s->socket_fd < 0) - return; - - close_nointr_nofail(s->socket_fd); - s->socket_fd = -1; -} - -static void service_connection_unref(Service *s) { - assert(s); - - if (!UNIT_DEREF(s->accept_socket)) - return; - - socket_connection_unref(SOCKET(UNIT_DEREF(s->accept_socket))); - unit_ref_unset(&s->accept_socket); -} - -static void service_stop_watchdog(Service *s) { - assert(s); - - unit_unwatch_timer(UNIT(s), &s->watchdog_watch); - s->watchdog_timestamp.realtime = 0; - s->watchdog_timestamp.monotonic = 0; -} - -static void service_enter_dead(Service *s, ServiceResult f, bool allow_restart); - -static void service_handle_watchdog(Service *s) { - usec_t offset; - int r; - - assert(s); - - if (s->watchdog_usec == 0) - return; - - offset = now(CLOCK_MONOTONIC) - s->watchdog_timestamp.monotonic; - if (offset >= s->watchdog_usec) { - log_error("%s watchdog timeout!", UNIT(s)->id); - service_enter_dead(s, SERVICE_FAILURE_WATCHDOG, true); - return; - } - - r = unit_watch_timer(UNIT(s), s->watchdog_usec - offset, &s->watchdog_watch); - if (r < 0) - log_warning("%s failed to install watchdog timer: %s", UNIT(s)->id, strerror(-r)); -} - -static void service_reset_watchdog(Service *s) { - assert(s); - - dual_timestamp_get(&s->watchdog_timestamp); - service_handle_watchdog(s); -} - -static void service_done(Unit *u) { - Service *s = SERVICE(u); - - assert(s); - - free(s->pid_file); - s->pid_file = NULL; - -#ifdef HAVE_SYSV_COMPAT - free(s->sysv_path); - s->sysv_path = NULL; - - free(s->sysv_runlevels); - s->sysv_runlevels = NULL; -#endif - - free(s->status_text); - s->status_text = NULL; - - exec_context_done(&s->exec_context); - exec_command_free_array(s->exec_command, _SERVICE_EXEC_COMMAND_MAX); - s->control_command = NULL; - s->main_command = NULL; - - /* 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); - free(s->bus_name); - s->bus_name = NULL; - } - - service_close_socket_fd(s); - service_connection_unref(s); - - unit_ref_unset(&s->accept_socket); - - service_stop_watchdog(s); - - unit_unwatch_timer(u, &s->timer_watch); -} - -#ifdef HAVE_SYSV_COMPAT -static char *sysv_translate_name(const char *name) { - char *r; - - if (!(r = new(char, strlen(name) + sizeof(".service")))) - return NULL; - -#if defined(TARGET_DEBIAN) || defined(TARGET_UBUNTU) || defined(TARGET_ANGSTROM) - if (endswith(name, ".sh")) - /* Drop Debian-style .sh suffix */ - strcpy(stpcpy(r, name) - 3, ".service"); -#endif -#ifdef TARGET_SUSE - if (startswith(name, "boot.")) - /* Drop SuSE-style boot. prefix */ - strcpy(stpcpy(r, name + 5), ".service"); -#endif -#ifdef TARGET_FRUGALWARE - if (startswith(name, "rc.")) - /* Drop Frugalware-style rc. prefix */ - strcpy(stpcpy(r, name + 3), ".service"); -#endif - else - /* Normal init scripts */ - strcpy(stpcpy(r, name), ".service"); - - return r; -} - -static int sysv_translate_facility(const char *name, const char *filename, char **_r) { - - /* 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", SPECIAL_LOCAL_FS_TARGET, -#if defined(TARGET_MANDRIVA) || defined(TARGET_MAGEIA) -#else - /* Due to unfortunate name selection in Mandriva, - * $network is provided by network-up which is ordered - * after network which actually starts interfaces. - * To break the loop, just ignore it */ - "network", SPECIAL_NETWORK_TARGET, -#endif - "named", SPECIAL_NSS_LOOKUP_TARGET, - "portmap", SPECIAL_RPCBIND_TARGET, - "remote_fs", SPECIAL_REMOTE_FS_TARGET, - "syslog", SPECIAL_SYSLOG_TARGET, - "time", SPECIAL_TIME_SYNC_TARGET, - - /* common extensions */ - "mail-transfer-agent", SPECIAL_MAIL_TRANSFER_AGENT_TARGET, - "x-display-manager", SPECIAL_DISPLAY_MANAGER_SERVICE, - "null", NULL, - -#if defined(TARGET_DEBIAN) || defined(TARGET_UBUNTU) || defined(TARGET_ANGSTROM) - "mail-transport-agent", SPECIAL_MAIL_TRANSFER_AGENT_TARGET, -#endif - -#ifdef TARGET_FEDORA - "MTA", SPECIAL_MAIL_TRANSFER_AGENT_TARGET, - "smtpdaemon", SPECIAL_MAIL_TRANSFER_AGENT_TARGET, - "httpd", SPECIAL_HTTP_DAEMON_TARGET, -#endif - -#ifdef TARGET_SUSE - "smtp", SPECIAL_MAIL_TRANSFER_AGENT_TARGET, -#endif - }; - - unsigned i; - char *r; - const char *n; - - assert(name); - assert(_r); - - 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; - - if (!(r = strdup(table[i+1]))) - return -ENOMEM; - - goto finish; - } - - /* If we don't know this name, fallback heuristics to figure - * out whether something is a target or a service alias. */ - - if (*name == '$') { - if (!unit_prefix_is_valid(n)) - return -EINVAL; - - /* Facilities starting with $ are most likely targets */ - r = unit_name_build(n, NULL, ".target"); - } else if (filename && streq(name, filename)) - /* Names equaling the file name of the services are redundant */ - return 0; - else - /* Everything else we assume to be normal service names */ - r = sysv_translate_name(n); - - if (!r) - return -ENOMEM; - -finish: - *_r = r; - - return 1; -} - -static int sysv_fix_order(Service *s) { - Unit *other; - int r; - - assert(s); - - if (s->sysv_start_priority < 0) - return 0; - - /* For each pair of services where at least one lacks a LSB - * header, we use the start priority value to order things. */ - - LIST_FOREACH(units_by_type, other, UNIT(s)->manager->units_by_type[UNIT_SERVICE]) { - Service *t; - UnitDependency d; - bool special_s, special_t; - - t = SERVICE(other); - - if (s == t) - continue; - - if (UNIT(t)->load_state != UNIT_LOADED) - continue; - - if (t->sysv_start_priority < 0) - continue; - - /* If both units have modern headers we don't care - * about the priorities */ - if ((UNIT(s)->fragment_path || s->sysv_has_lsb) && - (UNIT(t)->fragment_path || t->sysv_has_lsb)) - continue; - - special_s = s->sysv_runlevels && !chars_intersect(RUNLEVELS_UP, s->sysv_runlevels); - special_t = t->sysv_runlevels && !chars_intersect(RUNLEVELS_UP, t->sysv_runlevels); - - if (special_t && !special_s) - d = UNIT_AFTER; - else if (special_s && !special_t) - d = UNIT_BEFORE; - else if (t->sysv_start_priority < s->sysv_start_priority) - d = UNIT_AFTER; - else if (t->sysv_start_priority > s->sysv_start_priority) - d = UNIT_BEFORE; - else - continue; - - /* FIXME: Maybe we should compare the name here lexicographically? */ - - if ((r = unit_add_dependency(UNIT(s), d, UNIT(t), true)) < 0) - return r; - } - - return 0; -} - -static ExecCommand *exec_command_new(const char *path, const char *arg1) { - ExecCommand *c; - - if (!(c = new0(ExecCommand, 1))) - return NULL; - - if (!(c->path = strdup(path))) { - free(c); - return NULL; - } - - if (!(c->argv = strv_new(path, arg1, NULL))) { - free(c->path); - free(c); - return NULL; - } - - return c; -} - -static int sysv_exec_commands(Service *s) { - ExecCommand *c; - - assert(s); - assert(s->sysv_path); - - if (!(c = exec_command_new(s->sysv_path, "start"))) - return -ENOMEM; - exec_command_append_list(s->exec_command+SERVICE_EXEC_START, c); - - if (!(c = exec_command_new(s->sysv_path, "stop"))) - return -ENOMEM; - exec_command_append_list(s->exec_command+SERVICE_EXEC_STOP, c); - - if (!(c = exec_command_new(s->sysv_path, "reload"))) - return -ENOMEM; - exec_command_append_list(s->exec_command+SERVICE_EXEC_RELOAD, c); - - return 0; -} - -static int service_load_sysv_path(Service *s, const char *path) { - FILE *f; - Unit *u; - unsigned line = 0; - int r; - enum { - NORMAL, - DESCRIPTION, - LSB, - LSB_DESCRIPTION - } state = NORMAL; - char *short_description = NULL, *long_description = NULL, *chkconfig_description = NULL, *description; - struct stat st; - - assert(s); - assert(path); - - u = UNIT(s); - - if (!(f = fopen(path, "re"))) { - r = errno == ENOENT ? 0 : -errno; - goto finish; - } - - zero(st); - if (fstat(fileno(f), &st) < 0) { - r = -errno; - goto finish; - } - - free(s->sysv_path); - if (!(s->sysv_path = strdup(path))) { - r = -ENOMEM; - goto finish; - } - - s->sysv_mtime = timespec_load(&st.st_mtim); - - if (null_or_empty(&st)) { - u->load_state = UNIT_MASKED; - r = 0; - goto finish; - } - - while (!feof(f)) { - char l[LINE_MAX], *t; - - if (!fgets(l, sizeof(l), f)) { - if (feof(f)) - break; - - r = -errno; - log_error("Failed to read configuration file '%s': %s", path, strerror(-r)); - goto finish; - } - - line++; - - t = strstrip(l); - if (*t != '#') - continue; - - if (state == NORMAL && streq(t, "### BEGIN INIT INFO")) { - state = LSB; - s->sysv_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 chkconfig headers */ - - if (startswith_no_case(t, "chkconfig:")) { - int start_priority; - char runlevels[16], *k; - - state = NORMAL; - - if (sscanf(t+10, "%15s %i %*i", - runlevels, - &start_priority) != 2) { - - log_warning("[%s:%u] Failed to parse chkconfig line. Ignoring.", path, line); - continue; - } - - /* A start priority gathered from the - * symlink farms is preferred over the - * data from the LSB header. */ - if (start_priority < 0 || start_priority > 99) - log_warning("[%s:%u] Start priority out of range. Ignoring.", path, line); - else - s->sysv_start_priority = start_priority; - - char_array_0(runlevels); - k = delete_chars(runlevels, WHITESPACE "-"); - - if (k[0]) { - char *d; - - if (!(d = strdup(k))) { - r = -ENOMEM; - goto finish; - } - - free(s->sysv_runlevels); - s->sysv_runlevels = d; - } - - } else if (startswith_no_case(t, "description:")) { - - size_t k = strlen(t); - char *d; - const char *j; - - if (t[k-1] == '\\') { - state = DESCRIPTION; - t[k-1] = 0; - } - - if ((j = strstrip(t+12)) && *j) { - if (!(d = strdup(j))) { - r = -ENOMEM; - goto finish; - } - } else - d = NULL; - - free(chkconfig_description); - chkconfig_description = d; - - } else if (startswith_no_case(t, "pidfile:")) { - - char *fn; - - state = NORMAL; - - fn = strstrip(t+8); - if (!path_is_absolute(fn)) { - log_warning("[%s:%u] PID file not absolute. Ignoring.", path, line); - continue; - } - - if (!(fn = strdup(fn))) { - r = -ENOMEM; - goto finish; - } - - free(s->pid_file); - s->pid_file = fn; - } - - } else if (state == DESCRIPTION) { - - /* Try to parse Red Hat style description - * continuation */ - - size_t k = strlen(t); - char *j; - - if (t[k-1] == '\\') - t[k-1] = 0; - else - state = NORMAL; - - if ((j = strstrip(t)) && *j) { - char *d = NULL; - - if (chkconfig_description) - d = join(chkconfig_description, " ", j, NULL); - else - d = strdup(j); - - if (!d) { - r = -ENOMEM; - goto finish; - } - - free(chkconfig_description); - chkconfig_description = d; - } - - } else if (state == LSB || state == LSB_DESCRIPTION) { - - if (startswith_no_case(t, "Provides:")) { - char *i, *w; - size_t z; - - state = LSB; - - FOREACH_WORD_QUOTED(w, z, t+9, i) { - char *n, *m; - - if (!(n = strndup(w, z))) { - r = -ENOMEM; - goto finish; - } - - r = sysv_translate_facility(n, file_name_from_path(path), &m); - free(n); - - if (r < 0) - goto finish; - - if (r == 0) - continue; - - if (unit_name_to_type(m) == UNIT_SERVICE) - r = unit_add_name(u, m); - else - /* 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 the SysV - * services! */ - r = unit_add_two_dependencies_by_name(u, UNIT_BEFORE, UNIT_WANTS, m, NULL, true); - - if (r < 0) - log_error("[%s:%u] Failed to add LSB Provides name %s, ignoring: %s", path, line, m, strerror(-r)); - - free(m); - } - - } 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:")) { - char *i, *w; - size_t z; - - state = LSB; - - FOREACH_WORD_QUOTED(w, z, strchr(t, ':')+1, i) { - char *n, *m; - - if (!(n = strndup(w, z))) { - r = -ENOMEM; - goto finish; - } - - r = sysv_translate_facility(n, file_name_from_path(path), &m); - - if (r < 0) { - log_error("[%s:%u] Failed to translate LSB dependency %s, ignoring: %s", path, line, n, strerror(-r)); - free(n); - continue; - } - - free(n); - - if (r == 0) - continue; - - r = unit_add_dependency_by_name(u, startswith_no_case(t, "X-Start-Before:") ? UNIT_BEFORE : UNIT_AFTER, m, NULL, true); - - if (r < 0) - log_error("[%s:%u] Failed to add dependency on %s, ignoring: %s", path, line, m, strerror(-r)); - - free(m); - } - } else if (startswith_no_case(t, "Default-Start:")) { - char *k, *d; - - state = LSB; - - k = delete_chars(t+14, WHITESPACE "-"); - - if (k[0] != 0) { - if (!(d = strdup(k))) { - r = -ENOMEM; - goto finish; - } - - free(s->sysv_runlevels); - s->sysv_runlevels = d; - } - - } else if (startswith_no_case(t, "Description:")) { - char *d, *j; - - state = LSB_DESCRIPTION; - - if ((j = strstrip(t+12)) && *j) { - if (!(d = strdup(j))) { - r = -ENOMEM; - goto finish; - } - } else - d = NULL; - - free(long_description); - long_description = d; - - } else if (startswith_no_case(t, "Short-Description:")) { - char *d, *j; - - state = LSB; - - if ((j = strstrip(t+18)) && *j) { - if (!(d = strdup(j))) { - r = -ENOMEM; - goto finish; - } - } else - d = NULL; - - free(short_description); - short_description = d; - - } else if (state == LSB_DESCRIPTION) { - - if (startswith(l, "#\t") || startswith(l, "# ")) { - char *j; - - if ((j = strstrip(t)) && *j) { - char *d = NULL; - - if (long_description) - d = join(long_description, " ", t, NULL); - else - d = strdup(j); - - if (!d) { - r = -ENOMEM; - goto finish; - } - - free(long_description); - long_description = d; - } - - } else - state = LSB; - } - } - } - - if ((r = sysv_exec_commands(s)) < 0) - goto finish; - if (s->sysv_runlevels && - chars_intersect(RUNLEVELS_BOOT, s->sysv_runlevels) && - chars_intersect(RUNLEVELS_UP, s->sysv_runlevels)) { - /* Service has both boot and "up" runlevels - configured. Kill the "up" ones. */ - delete_chars(s->sysv_runlevels, RUNLEVELS_UP); - } - - if (s->sysv_runlevels && !chars_intersect(RUNLEVELS_UP, s->sysv_runlevels)) { - /* If there a runlevels configured for this service - * but none of the standard ones, then we assume this - * is some special kind of service (which might be - * needed for early boot) and don't create any links - * to it. */ - - UNIT(s)->default_dependencies = false; - - /* Don't timeout special services during boot (like fsck) */ - s->timeout_usec = 0; - } else - s->timeout_usec = DEFAULT_SYSV_TIMEOUT_USEC; - - /* Special setting for all SysV services */ - s->type = SERVICE_FORKING; - s->remain_after_exit = !s->pid_file; - s->guess_main_pid = false; - s->restart = SERVICE_RESTART_NO; - s->exec_context.ignore_sigpipe = false; - - if (UNIT(s)->manager->sysv_console) - s->exec_context.std_output = EXEC_OUTPUT_JOURNAL_AND_CONSOLE; - - s->exec_context.kill_mode = KILL_PROCESS; - - /* 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; - - if (!(d = strappend(s->sysv_has_lsb ? "LSB: " : "SYSV: ", description))) { - r = -ENOMEM; - goto finish; - } - - u->description = d; - } - - /* The priority that has been set in /etc/rcN.d/ hierarchies - * takes precedence over what is stored as default in the LSB - * header */ - if (s->sysv_start_priority_from_rcnd >= 0) - s->sysv_start_priority = s->sysv_start_priority_from_rcnd; - - u->load_state = UNIT_LOADED; - r = 0; - -finish: - - if (f) - fclose(f); - - free(short_description); - free(long_description); - free(chkconfig_description); - - return r; -} - -static int service_load_sysv_name(Service *s, const char *name) { - char **p; - - assert(s); - assert(name); - - /* For SysV services we strip the boot.*, rc.* and *.sh - * prefixes/suffixes. */ -#if defined(TARGET_DEBIAN) || defined(TARGET_UBUNTU) || defined(TARGET_ANGSTROM) - if (endswith(name, ".sh.service")) - return -ENOENT; -#endif - -#ifdef TARGET_SUSE - if (startswith(name, "boot.")) - return -ENOENT; -#endif - -#ifdef TARGET_FRUGALWARE - if (startswith(name, "rc.")) - return -ENOENT; -#endif - - STRV_FOREACH(p, UNIT(s)->manager->lookup_paths.sysvinit_path) { - char *path; - int r; - - path = join(*p, "/", name, NULL); - if (!path) - return -ENOMEM; - - assert(endswith(path, ".service")); - path[strlen(path)-8] = 0; - - r = service_load_sysv_path(s, path); - -#if defined(TARGET_DEBIAN) || defined(TARGET_UBUNTU) || defined(TARGET_ANGSTROM) - if (r >= 0 && UNIT(s)->load_state == UNIT_STUB) { - /* Try Debian style *.sh source'able init scripts */ - strcat(path, ".sh"); - r = service_load_sysv_path(s, path); - } -#endif - free(path); - -#ifdef TARGET_SUSE - if (r >= 0 && UNIT(s)->load_state == UNIT_STUB) { - /* Try SUSE style boot.* init scripts */ - - path = join(*p, "/boot.", name, NULL); - if (!path) - return -ENOMEM; - - /* Drop .service suffix */ - path[strlen(path)-8] = 0; - r = service_load_sysv_path(s, path); - free(path); - } -#endif - -#ifdef TARGET_FRUGALWARE - if (r >= 0 && UNIT(s)->load_state == UNIT_STUB) { - /* Try Frugalware style rc.* init scripts */ - - path = join(*p, "/rc.", name, NULL); - if (!path) - return -ENOMEM; - - /* Drop .service suffix */ - path[strlen(path)-8] = 0; - r = service_load_sysv_path(s, path); - free(path); - } -#endif - - if (r < 0) - return r; - - if ((UNIT(s)->load_state != UNIT_STUB)) - break; - } - - return 0; -} - -static int service_load_sysv(Service *s) { - const char *t; - Iterator i; - int r; - - assert(s); - - /* Load service data from SysV init scripts, preferably with - * LSB headers ... */ - - if (strv_isempty(UNIT(s)->manager->lookup_paths.sysvinit_path)) - return 0; - - if ((t = UNIT(s)->id)) - if ((r = service_load_sysv_name(s, t)) < 0) - return r; - - if (UNIT(s)->load_state == UNIT_STUB) - SET_FOREACH(t, UNIT(s)->names, i) { - if (t == UNIT(s)->id) - continue; - - if ((r = service_load_sysv_name(s, t)) < 0) - return r; - - if (UNIT(s)->load_state != UNIT_STUB) - break; - } - - return 0; -} -#endif - -static int fsck_fix_order(Service *s) { - Unit *other; - int r; - - assert(s); - - if (s->fsck_passno <= 0) - return 0; - - /* For each pair of services where both have an fsck priority - * we order things based on it. */ - - LIST_FOREACH(units_by_type, other, UNIT(s)->manager->units_by_type[UNIT_SERVICE]) { - Service *t; - UnitDependency d; - - t = SERVICE(other); - - if (s == t) - continue; - - if (UNIT(t)->load_state != UNIT_LOADED) - continue; - - if (t->fsck_passno <= 0) - continue; - - if (t->fsck_passno < s->fsck_passno) - d = UNIT_AFTER; - else if (t->fsck_passno > s->fsck_passno) - d = UNIT_BEFORE; - else - continue; - - if ((r = unit_add_dependency(UNIT(s), d, UNIT(t), true)) < 0) - return r; - } - - 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]) { - log_error("%s lacks ExecStart setting. Refusing.", UNIT(s)->id); - return -EINVAL; - } - - if (s->type != SERVICE_ONESHOT && - s->exec_command[SERVICE_EXEC_START]->command_next) { - log_error("%s has more than one ExecStart setting, which is only allowed for Type=oneshot services. Refusing.", UNIT(s)->id); - return -EINVAL; - } - - if (s->type == SERVICE_ONESHOT && - s->exec_command[SERVICE_EXEC_RELOAD]) { - log_error("%s has an ExecReload setting, which is not allowed for Type=oneshot services. Refusing.", UNIT(s)->id); - return -EINVAL; - } - - if (s->type == SERVICE_DBUS && !s->bus_name) { - log_error("%s is of type D-Bus but no D-Bus service name has been specified. Refusing.", UNIT(s)->id); - return -EINVAL; - } - - if (s->exec_context.pam_name && s->exec_context.kill_mode != KILL_CONTROL_GROUP) { - log_error("%s has PAM enabled. Kill mode must be set to 'control-group'. Refusing.", UNIT(s)->id); - return -EINVAL; - } - - return 0; -} - -static int service_add_default_dependencies(Service *s) { - int r; - - assert(s); - - /* Add a number of automatic dependencies useful for the - * majority of services. */ - - /* First, pull in base system */ - if (UNIT(s)->manager->running_as == MANAGER_SYSTEM) { - - if ((r = unit_add_two_dependencies_by_name(UNIT(s), UNIT_AFTER, UNIT_REQUIRES, SPECIAL_BASIC_TARGET, NULL, true)) < 0) - return r; - - } else if (UNIT(s)->manager->running_as == MANAGER_USER) { - - if ((r = unit_add_two_dependencies_by_name(UNIT(s), UNIT_AFTER, UNIT_REQUIRES, SPECIAL_SOCKETS_TARGET, NULL, true)) < 0) - return r; - } - - /* Second, activate 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_load(Unit *u) { - int r; - Service *s = SERVICE(u); - - assert(s); - - /* Load a .service file */ - if ((r = unit_load_fragment(u)) < 0) - return r; - -#ifdef HAVE_SYSV_COMPAT - /* Load a classic init script as a fallback, if we couldn't find anything */ - if (u->load_state == UNIT_STUB) - if ((r = service_load_sysv(s)) < 0) - return r; -#endif - - /* Still nothing found? Then let's give up */ - if (u->load_state == UNIT_STUB) - return -ENOENT; - - /* We were able to load something, then let's add in the - * dropin directories. */ - if ((r = unit_load_dropin(unit_follow_merge(u))) < 0) - return r; - - /* This is a new unit? Then let's add in some extras */ - if (u->load_state == UNIT_LOADED) { - service_fix_output(s); - - if ((r = unit_add_exec_dependencies(u, &s->exec_context)) < 0) - return r; - - if ((r = unit_add_default_cgroups(u)) < 0) - return r; - -#ifdef HAVE_SYSV_COMPAT - if ((r = sysv_fix_order(s)) < 0) - return r; -#endif - - if ((r = fsck_fix_order(s)) < 0) - return r; - - if (s->bus_name) - if ((r = unit_watch_bus_name(u, s->bus_name)) < 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; - - if (s->type == SERVICE_DBUS || s->bus_name) - if ((r = unit_add_two_dependencies_by_name(u, UNIT_AFTER, UNIT_REQUIRES, SPECIAL_DBUS_SOCKET, NULL, true)) < 0) - return r; - - if (UNIT(s)->default_dependencies) - if ((r = service_add_default_dependencies(s)) < 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; - char *p2; - - assert(s); - - p2 = strappend(prefix, "\t"); - prefix2 = p2 ? p2 : prefix; - - 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", - 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)); - - if (s->control_pid > 0) - fprintf(f, - "%sControl PID: %lu\n", - prefix, (unsigned long) s->control_pid); - - if (s->main_pid > 0) - fprintf(f, - "%sMain PID: %lu\n" - "%sMain PID Known: %s\n" - "%sMain PID Alien: %s\n", - prefix, (unsigned long) 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)); - - 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); - } - -#ifdef HAVE_SYSV_COMPAT - if (s->sysv_path) - fprintf(f, - "%sSysV Init Script Path: %s\n" - "%sSysV Init Script has LSB Header: %s\n" - "%sSysVEnabled: %s\n", - prefix, s->sysv_path, - prefix, yes_no(s->sysv_has_lsb), - prefix, yes_no(s->sysv_enabled)); - - if (s->sysv_start_priority >= 0) - fprintf(f, - "%sSysVStartPriority: %i\n", - prefix, s->sysv_start_priority); - - if (s->sysv_runlevels) - fprintf(f, "%sSysVRunLevels: %s\n", - prefix, s->sysv_runlevels); -#endif - - if (s->fsck_passno > 0) - fprintf(f, - "%sFsckPassNo: %i\n", - prefix, s->fsck_passno); - - if (s->status_text) - fprintf(f, "%sStatus Text: %s\n", - prefix, s->status_text); - - free(p2); -} - -static int service_load_pid_file(Service *s, bool may_warn) { - char *k; - int r; - pid_t pid; - - assert(s); - - if (!s->pid_file) - return -ENOENT; - - if ((r = read_one_line_file(s->pid_file, &k)) < 0) { - if (may_warn) - log_info("PID file %s not readable (yet?) after %s.", - s->pid_file, service_state_to_string(s->state)); - return r; - } - - r = parse_pid(k, &pid); - free(k); - - if (r < 0) - return r; - - if (kill(pid, 0) < 0 && errno != EPERM) { - if (may_warn) - log_info("PID %lu read from file %s does not exist.", - (unsigned long) pid, s->pid_file); - return -ESRCH; - } - - if (s->main_pid_known) { - if (pid == s->main_pid) - return 0; - - log_debug("Main PID changing: %lu -> %lu", - (unsigned long) s->main_pid, (unsigned long) pid); - service_unwatch_main_pid(s); - s->main_pid_known = false; - } else - log_debug("Main PID loaded: %lu", (unsigned long) pid); - - if ((r = service_set_main_pid(s, pid)) < 0) - return r; - - if ((r = unit_watch_pid(UNIT(s), pid)) < 0) - /* FIXME: we need to do something here */ - return r; - - return 0; -} - -static int service_search_main_pid(Service *s) { - pid_t pid; - int r; - - assert(s); - - /* If we know it anyway, don't ever fallback to unreliable - * heuristics */ - if (s->main_pid_known) - return 0; - - if (!s->guess_main_pid) - return 0; - - assert(s->main_pid <= 0); - - if ((pid = cgroup_bonding_search_main_pid_list(UNIT(s)->cgroup_bondings)) <= 0) - return -ENOENT; - - log_debug("Main PID guessed: %lu", (unsigned long) pid); - if ((r = service_set_main_pid(s, pid)) < 0) - return r; - - if ((r = unit_watch_pid(UNIT(s), pid)) < 0) - /* FIXME: we need to do something here */ - return r; - - return 0; -} - -static void service_notify_sockets_dead(Service *s, bool failed_permanent) { - Iterator i; - Unit *u; - - assert(s); - - /* Notifies all our sockets when we die */ - - if (s->socket_fd >= 0) - return; - - SET_FOREACH(u, UNIT(s)->dependencies[UNIT_TRIGGERED_BY], i) - if (u->type == UNIT_SOCKET) - socket_notify_service_dead(SOCKET(u), failed_permanent); - - return; -} - -static void service_set_state(Service *s, ServiceState state) { - ServiceState old_state; - assert(s); - - old_state = s->state; - s->state = state; - - service_unwatch_pid_file(s); - - if (state != SERVICE_START_PRE && - state != SERVICE_START && - state != SERVICE_START_POST && - state != SERVICE_RELOAD && - state != SERVICE_STOP && - state != SERVICE_STOP_SIGTERM && - state != SERVICE_STOP_SIGKILL && - state != SERVICE_STOP_POST && - state != SERVICE_FINAL_SIGTERM && - state != SERVICE_FINAL_SIGKILL && - state != SERVICE_AUTO_RESTART) - unit_unwatch_timer(UNIT(s), &s->timer_watch); - - if (state != SERVICE_START && - state != SERVICE_START_POST && - state != SERVICE_RUNNING && - state != SERVICE_RELOAD && - state != SERVICE_STOP && - state != SERVICE_STOP_SIGTERM && - state != SERVICE_STOP_SIGKILL) { - service_unwatch_main_pid(s); - s->main_command = NULL; - } - - if (state != SERVICE_START_PRE && - state != SERVICE_START && - state != SERVICE_START_POST && - state != SERVICE_RELOAD && - state != SERVICE_STOP && - state != SERVICE_STOP_SIGTERM && - state != SERVICE_STOP_SIGKILL && - state != SERVICE_STOP_POST && - state != SERVICE_FINAL_SIGTERM && - state != SERVICE_FINAL_SIGKILL) { - service_unwatch_control_pid(s); - s->control_command = NULL; - s->control_command_id = _SERVICE_EXEC_COMMAND_INVALID; - } - - if (state == SERVICE_DEAD || - state == SERVICE_STOP || - state == SERVICE_STOP_SIGTERM || - state == SERVICE_STOP_SIGKILL || - state == SERVICE_STOP_POST || - state == SERVICE_FINAL_SIGTERM || - state == SERVICE_FINAL_SIGKILL || - state == SERVICE_FAILED || - state == SERVICE_AUTO_RESTART) - service_notify_sockets_dead(s, false); - - if (state != SERVICE_START_PRE && - state != SERVICE_START && - state != SERVICE_START_POST && - state != SERVICE_RUNNING && - state != SERVICE_RELOAD && - state != SERVICE_STOP && - state != SERVICE_STOP_SIGTERM && - state != SERVICE_STOP_SIGKILL && - state != SERVICE_STOP_POST && - state != SERVICE_FINAL_SIGTERM && - state != SERVICE_FINAL_SIGKILL && - !(state == SERVICE_DEAD && UNIT(s)->job)) { - service_close_socket_fd(s); - service_connection_unref(s); - } - - if (state == SERVICE_STOP) - service_stop_watchdog(s); - - /* For the inactive states unit_notify() will trim the cgroup, - * but for exit we have to do that ourselves... */ - if (state == SERVICE_EXITED && UNIT(s)->manager->n_reloading <= 0) - cgroup_bonding_trim_list(UNIT(s)->cgroup_bondings, true); - - if (old_state != state) - log_debug("%s changed %s -> %s", UNIT(s)->id, service_state_to_string(old_state), service_state_to_string(state)); - - unit_notify(UNIT(s), state_translation_table[old_state], state_translation_table[state], s->reload_result == SERVICE_SUCCESS); - s->reload_result = SERVICE_SUCCESS; -} - -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) { - - if (s->deserialized_state == SERVICE_START_PRE || - s->deserialized_state == SERVICE_START || - s->deserialized_state == SERVICE_START_POST || - s->deserialized_state == SERVICE_RELOAD || - s->deserialized_state == SERVICE_STOP || - s->deserialized_state == SERVICE_STOP_SIGTERM || - s->deserialized_state == SERVICE_STOP_SIGKILL || - s->deserialized_state == SERVICE_STOP_POST || - s->deserialized_state == SERVICE_FINAL_SIGTERM || - s->deserialized_state == SERVICE_FINAL_SIGKILL || - s->deserialized_state == SERVICE_AUTO_RESTART) { - - if (s->deserialized_state == SERVICE_AUTO_RESTART || s->timeout_usec > 0) { - usec_t k; - - k = s->deserialized_state == SERVICE_AUTO_RESTART ? s->restart_usec : s->timeout_usec; - - if ((r = unit_watch_timer(UNIT(s), k, &s->timer_watch)) < 0) - return r; - } - } - - if ((s->deserialized_state == SERVICE_START && - (s->type == SERVICE_FORKING || - s->type == SERVICE_DBUS || - s->type == SERVICE_ONESHOT || - s->type == SERVICE_NOTIFY)) || - s->deserialized_state == SERVICE_START_POST || - s->deserialized_state == SERVICE_RUNNING || - s->deserialized_state == SERVICE_RELOAD || - s->deserialized_state == SERVICE_STOP || - s->deserialized_state == SERVICE_STOP_SIGTERM || - s->deserialized_state == SERVICE_STOP_SIGKILL) - if (s->main_pid > 0) - if ((r = unit_watch_pid(UNIT(s), s->main_pid)) < 0) - return r; - - if (s->deserialized_state == SERVICE_START_PRE || - s->deserialized_state == SERVICE_START || - s->deserialized_state == SERVICE_START_POST || - s->deserialized_state == SERVICE_RELOAD || - s->deserialized_state == SERVICE_STOP || - s->deserialized_state == SERVICE_STOP_SIGTERM || - s->deserialized_state == SERVICE_STOP_SIGKILL || - s->deserialized_state == SERVICE_STOP_POST || - s->deserialized_state == SERVICE_FINAL_SIGTERM || - s->deserialized_state == SERVICE_FINAL_SIGKILL) - if (s->control_pid > 0) - if ((r = unit_watch_pid(UNIT(s), s->control_pid)) < 0) - return r; - - if (s->deserialized_state == SERVICE_START_POST || - s->deserialized_state == SERVICE_RUNNING) - service_handle_watchdog(s); - - service_set_state(s, s->deserialized_state); - } - return 0; -} - -static int service_collect_fds(Service *s, int **fds, unsigned *n_fds) { - Iterator i; - int r; - int *rfds = NULL; - unsigned rn_fds = 0; - Unit *u; - - assert(s); - assert(fds); - assert(n_fds); - - if (s->socket_fd >= 0) - return 0; - - SET_FOREACH(u, UNIT(s)->dependencies[UNIT_TRIGGERED_BY], i) { - int *cfds; - unsigned cn_fds; - Socket *sock; - - if (u->type != UNIT_SOCKET) - continue; - - sock = SOCKET(u); - - if ((r = socket_collect_fds(sock, &cfds, &cn_fds)) < 0) - goto fail; - - if (!cfds) - continue; - - if (!rfds) { - rfds = cfds; - rn_fds = cn_fds; - } else { - int *t; - - if (!(t = new(int, rn_fds+cn_fds))) { - free(cfds); - r = -ENOMEM; - goto fail; - } - - memcpy(t, rfds, rn_fds * sizeof(int)); - memcpy(t+rn_fds, cfds, cn_fds * sizeof(int)); - free(rfds); - free(cfds); - - rfds = t; - rn_fds = rn_fds+cn_fds; - } - } - - *fds = rfds; - *n_fds = rn_fds; - - return 0; - -fail: - free(rfds); - - return r; -} - -static int service_spawn( - Service *s, - ExecCommand *c, - bool timeout, - bool pass_fds, - bool apply_permissions, - bool apply_chroot, - bool apply_tty_stdin, - bool set_notify_socket, - pid_t *_pid) { - - pid_t pid; - int r; - int *fds = NULL, *fdsbuf = NULL; - unsigned n_fds = 0, n_env = 0; - char **argv = NULL, **final_env = NULL, **our_env = NULL; - - assert(s); - assert(c); - assert(_pid); - - 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) { - - if (s->socket_fd >= 0) { - fds = &s->socket_fd; - n_fds = 1; - } else { - if ((r = service_collect_fds(s, &fdsbuf, &n_fds)) < 0) - goto fail; - - fds = fdsbuf; - } - } - - if (timeout && s->timeout_usec) { - if ((r = unit_watch_timer(UNIT(s), s->timeout_usec, &s->timer_watch)) < 0) - goto fail; - } else - unit_unwatch_timer(UNIT(s), &s->timer_watch); - - if (!(argv = unit_full_printf_strv(UNIT(s), c->argv))) { - r = -ENOMEM; - goto fail; - } - - if (!(our_env = new0(char*, 4))) { - r = -ENOMEM; - goto fail; - } - - if (set_notify_socket) - if (asprintf(our_env + n_env++, "NOTIFY_SOCKET=%s", UNIT(s)->manager->notify_socket) < 0) { - r = -ENOMEM; - goto fail; - } - - if (s->main_pid > 0) - if (asprintf(our_env + n_env++, "MAINPID=%lu", (unsigned long) s->main_pid) < 0) { - r = -ENOMEM; - goto fail; - } - - if (s->watchdog_usec > 0) - if (asprintf(our_env + n_env++, "WATCHDOG_USEC=%llu", (unsigned long long) s->watchdog_usec) < 0) { - r = -ENOMEM; - goto fail; - } - - if (!(final_env = strv_env_merge(2, - UNIT(s)->manager->environment, - our_env, - NULL))) { - r = -ENOMEM; - goto fail; - } - - r = exec_spawn(c, - argv, - &s->exec_context, - fds, n_fds, - final_env, - apply_permissions, - apply_chroot, - apply_tty_stdin, - UNIT(s)->manager->confirm_spawn, - UNIT(s)->cgroup_bondings, - UNIT(s)->cgroup_attributes, - &pid); - - if (r < 0) - goto fail; - - - if ((r = unit_watch_pid(UNIT(s), pid)) < 0) - /* FIXME: we need to do something here */ - goto fail; - - free(fdsbuf); - strv_free(argv); - strv_free(our_env); - strv_free(final_env); - - *_pid = pid; - - return 0; - -fail: - free(fdsbuf); - strv_free(argv); - strv_free(our_env); - strv_free(final_env); - - if (timeout) - unit_unwatch_timer(UNIT(s), &s->timer_watch); - - return r; -} - -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 lets 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) - return kill(s->main_pid, 0) >= 0 || errno != ESRCH; - - /* .. 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; -} - -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 ((r = cgroup_bonding_is_empty_list(UNIT(s)->cgroup_bondings)) < 0) - return r; - - return !r; -} - -static void service_enter_dead(Service *s, ServiceResult f, bool allow_restart) { - int r; - assert(s); - - if (f != SERVICE_SUCCESS) - s->result = f; - - if (allow_restart && - !s->forbid_restart && - (s->restart == SERVICE_RESTART_ALWAYS || - (s->restart == SERVICE_RESTART_ON_SUCCESS && s->result == SERVICE_SUCCESS) || - (s->restart == SERVICE_RESTART_ON_FAILURE && s->result != SERVICE_SUCCESS) || - (s->restart == SERVICE_RESTART_ON_ABORT && (s->result == SERVICE_FAILURE_SIGNAL || - s->result == SERVICE_FAILURE_CORE_DUMP)))) { - - r = unit_watch_timer(UNIT(s), s->restart_usec, &s->timer_watch); - if (r < 0) - goto fail; - - service_set_state(s, SERVICE_AUTO_RESTART); - } else - service_set_state(s, s->result != SERVICE_SUCCESS ? SERVICE_FAILED : SERVICE_DEAD); - - s->forbid_restart = false; - - return; - -fail: - log_warning("%s failed to run install restart timer: %s", UNIT(s)->id, strerror(-r)); - service_enter_dead(s, SERVICE_FAILURE_RESOURCES, false); -} - -static void service_enter_signal(Service *s, ServiceState state, ServiceResult f); - -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); - - if ((s->control_command = s->exec_command[SERVICE_EXEC_STOP_POST])) { - s->control_command_id = SERVICE_EXEC_STOP_POST; - - if ((r = service_spawn(s, - s->control_command, - true, - false, - !s->permissions_start_only, - !s->root_directory_start_only, - true, - false, - &s->control_pid)) < 0) - goto fail; - - - service_set_state(s, SERVICE_STOP_POST); - } else - service_enter_signal(s, SERVICE_FINAL_SIGTERM, SERVICE_SUCCESS); - - return; - -fail: - log_warning("%s failed to run 'stop-post' task: %s", UNIT(s)->id, strerror(-r)); - service_enter_signal(s, SERVICE_FINAL_SIGTERM, SERVICE_FAILURE_RESOURCES); -} - -static void service_enter_signal(Service *s, ServiceState state, ServiceResult f) { - int r; - Set *pid_set = NULL; - bool wait_for_exit = false; - - assert(s); - - if (f != SERVICE_SUCCESS) - s->result = f; - - if (s->exec_context.kill_mode != KILL_NONE) { - int sig = (state == SERVICE_STOP_SIGTERM || state == SERVICE_FINAL_SIGTERM) ? s->exec_context.kill_signal : SIGKILL; - - if (s->main_pid > 0) { - if (kill_and_sigcont(s->main_pid, sig) < 0 && errno != ESRCH) - log_warning("Failed to kill main process %li: %m", (long) s->main_pid); - else - wait_for_exit = !s->main_pid_alien; - } - - if (s->control_pid > 0) { - if (kill_and_sigcont(s->control_pid, sig) < 0 && errno != ESRCH) - log_warning("Failed to kill control process %li: %m", (long) s->control_pid); - else - wait_for_exit = true; - } - - if (s->exec_context.kill_mode == KILL_CONTROL_GROUP) { - - if (!(pid_set = set_new(trivial_hash_func, trivial_compare_func))) { - r = -ENOMEM; - goto fail; - } - - /* Exclude the main/control pids from being killed via the cgroup */ - if (s->main_pid > 0) - if ((r = set_put(pid_set, LONG_TO_PTR(s->main_pid))) < 0) - goto fail; - - if (s->control_pid > 0) - if ((r = set_put(pid_set, LONG_TO_PTR(s->control_pid))) < 0) - goto fail; - - if ((r = cgroup_bonding_kill_list(UNIT(s)->cgroup_bondings, sig, true, pid_set)) < 0) { - if (r != -EAGAIN && r != -ESRCH && r != -ENOENT) - log_warning("Failed to kill control group: %s", strerror(-r)); - } else if (r > 0) - wait_for_exit = true; - - set_free(pid_set); - pid_set = NULL; - } - } - - if (wait_for_exit) { - if (s->timeout_usec > 0) - if ((r = unit_watch_timer(UNIT(s), s->timeout_usec, &s->timer_watch)) < 0) - goto fail; - - service_set_state(s, state); - } else if (state == SERVICE_STOP_SIGTERM || state == SERVICE_STOP_SIGKILL) - service_enter_stop_post(s, SERVICE_SUCCESS); - else - service_enter_dead(s, SERVICE_SUCCESS, true); - - return; - -fail: - log_warning("%s failed to kill processes: %s", UNIT(s)->id, strerror(-r)); - - if (state == SERVICE_STOP_SIGTERM || state == SERVICE_STOP_SIGKILL) - service_enter_stop_post(s, SERVICE_FAILURE_RESOURCES); - else - service_enter_dead(s, SERVICE_FAILURE_RESOURCES, true); - - if (pid_set) - set_free(pid_set); -} - -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); - - if ((s->control_command = s->exec_command[SERVICE_EXEC_STOP])) { - s->control_command_id = SERVICE_EXEC_STOP; - - if ((r = service_spawn(s, - s->control_command, - true, - false, - !s->permissions_start_only, - !s->root_directory_start_only, - false, - false, - &s->control_pid)) < 0) - goto fail; - - service_set_state(s, SERVICE_STOP); - } else - service_enter_signal(s, SERVICE_STOP_SIGTERM, SERVICE_SUCCESS); - - return; - -fail: - log_warning("%s failed to run 'stop' task: %s", UNIT(s)->id, strerror(-r)); - service_enter_signal(s, SERVICE_STOP_SIGTERM, SERVICE_FAILURE_RESOURCES); -} - -static void service_enter_running(Service *s, ServiceResult f) { - int main_pid_ok, cgroup_ok; - assert(s); - - if (f != SERVICE_SUCCESS) - s->result = f; - - main_pid_ok = main_pid_good(s); - cgroup_ok = cgroup_good(s); - - if ((main_pid_ok > 0 || (main_pid_ok < 0 && cgroup_ok != 0)) && - (s->bus_name_good || s->type != SERVICE_DBUS)) - service_set_state(s, SERVICE_RUNNING); - 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); - - if (s->watchdog_usec > 0) - service_reset_watchdog(s); - - if ((s->control_command = s->exec_command[SERVICE_EXEC_START_POST])) { - s->control_command_id = SERVICE_EXEC_START_POST; - - if ((r = service_spawn(s, - s->control_command, - true, - false, - !s->permissions_start_only, - !s->root_directory_start_only, - false, - false, - &s->control_pid)) < 0) - goto fail; - - service_set_state(s, SERVICE_START_POST); - } else - service_enter_running(s, SERVICE_SUCCESS); - - return; - -fail: - log_warning("%s failed to run 'start-post' task: %s", UNIT(s)->id, strerror(-r)); - service_enter_stop(s, SERVICE_FAILURE_RESOURCES); -} - -static void service_enter_start(Service *s) { - pid_t pid; - int r; - ExecCommand *c; - - assert(s); - - assert(s->exec_command[SERVICE_EXEC_START]); - assert(!s->exec_command[SERVICE_EXEC_START]->command_next || s->type == SERVICE_ONESHOT); - - if (s->type == SERVICE_FORKING) - service_unwatch_control_pid(s); - else - 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. */ - cgroup_bonding_kill_list(UNIT(s)->cgroup_bondings, SIGKILL, true, NULL); - - 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 ((r = service_spawn(s, - c, - s->type == SERVICE_FORKING || s->type == SERVICE_DBUS || s->type == SERVICE_NOTIFY, - true, - true, - true, - true, - s->notify_access != NOTIFY_NONE, - &pid)) < 0) - goto fail; - - if (s->type == SERVICE_SIMPLE) { - /* 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 (s->type == SERVICE_ONESHOT || - s->type == SERVICE_DBUS || - s->type == 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_warning("%s failed to run 'start' task: %s", UNIT(s)->id, strerror(-r)); - 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); - - if ((s->control_command = s->exec_command[SERVICE_EXEC_START_PRE])) { - - /* Before we start anything, let's clear up what might - * be left from previous runs. */ - cgroup_bonding_kill_list(UNIT(s)->cgroup_bondings, SIGKILL, true, NULL); - - s->control_command_id = SERVICE_EXEC_START_PRE; - - if ((r = service_spawn(s, - s->control_command, - true, - false, - !s->permissions_start_only, - !s->root_directory_start_only, - true, - false, - &s->control_pid)) < 0) - goto fail; - - service_set_state(s, SERVICE_START_PRE); - } else - service_enter_start(s); - - return; - -fail: - log_warning("%s failed to run 'start-pre' task: %s", UNIT(s)->id, strerror(-r)); - service_enter_dead(s, SERVICE_FAILURE_RESOURCES, true); -} - -static void service_enter_restart(Service *s) { - int r; - DBusError error; - - assert(s); - dbus_error_init(&error); - - if (UNIT(s)->job) { - log_info("Job pending for unit, delaying automatic restart."); - - if ((r = unit_watch_timer(UNIT(s), s->restart_usec, &s->timer_watch)) < 0) - goto fail; - } - - /* 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, false, &error, NULL); - if (r < 0) - goto fail; - - log_debug("%s scheduled restart job.", UNIT(s)->id); - return; - -fail: - log_warning("%s failed to schedule restart job: %s", UNIT(s)->id, bus_error(&error, -r)); - service_enter_dead(s, SERVICE_FAILURE_RESOURCES, false); - - dbus_error_free(&error); -} - -static void service_enter_reload(Service *s) { - int r; - - assert(s); - - service_unwatch_control_pid(s); - - if ((s->control_command = s->exec_command[SERVICE_EXEC_RELOAD])) { - s->control_command_id = SERVICE_EXEC_RELOAD; - - if ((r = service_spawn(s, - s->control_command, - true, - false, - !s->permissions_start_only, - !s->root_directory_start_only, - false, - false, - &s->control_pid)) < 0) - goto fail; - - service_set_state(s, SERVICE_RELOAD); - } else - service_enter_running(s, SERVICE_SUCCESS); - - return; - -fail: - log_warning("%s failed to run 'reload' task: %s", UNIT(s)->id, strerror(-r)); - s->reload_result = SERVICE_FAILURE_RESOURCES; - service_enter_running(s, SERVICE_SUCCESS); -} - -static void service_run_next_control(Service *s) { - 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 ((r = service_spawn(s, - s->control_command, - true, - 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, - false, - &s->control_pid)) < 0) - goto fail; - - return; - -fail: - log_warning("%s failed to run next control task: %s", UNIT(s)->id, strerror(-r)); - - 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); - - if ((r = service_spawn(s, - s->main_command, - false, - true, - true, - true, - true, - s->notify_access != NOTIFY_NONE, - &pid)) < 0) - goto fail; - - service_set_main_pid(s, pid); - - return; - -fail: - log_warning("%s failed to run next main task: %s", UNIT(s)->id, strerror(-r)); - service_enter_stop(s, SERVICE_FAILURE_RESOURCES); -} - -static int service_start_limit_test(Service *s) { - assert(s); - - if (ratelimit_test(&s->start_limit)) - return 0; - - switch (s->start_limit_action) { - - case SERVICE_START_LIMIT_NONE: - log_warning("%s start request repeated too quickly, refusing to start.", UNIT(s)->id); - break; - - case SERVICE_START_LIMIT_REBOOT: { - DBusError error; - int r; - - dbus_error_init(&error); - - log_warning("%s start request repeated too quickly, rebooting.", UNIT(s)->id); - - r = manager_add_job_by_name(UNIT(s)->manager, JOB_START, SPECIAL_REBOOT_TARGET, JOB_REPLACE, true, &error, NULL); - if (r < 0) { - log_error("Failed to reboot: %s.", bus_error(&error, r)); - dbus_error_free(&error); - } - - break; - } - - case SERVICE_START_LIMIT_REBOOT_FORCE: - log_warning("%s start request repeated too quickly, forcibly rebooting.", UNIT(s)->id); - UNIT(s)->manager->exit_code = MANAGER_REBOOT; - break; - - case SERVICE_START_LIMIT_REBOOT_IMMEDIATE: - log_warning("%s start request repeated too quickly, rebooting immediately.", UNIT(s)->id); - reboot(RB_AUTOBOOT); - break; - - default: - log_error("start limit action=%i", s->start_limit_action); - assert_not_reached("Unknown StartLimitAction."); - } - - return -ECANCELED; -} - -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 (s->state == SERVICE_STOP || - s->state == SERVICE_STOP_SIGTERM || - s->state == SERVICE_STOP_SIGKILL || - s->state == SERVICE_STOP_POST || - s->state == SERVICE_FINAL_SIGTERM || - s->state == SERVICE_FINAL_SIGKILL) - return -EAGAIN; - - /* Already on it! */ - if (s->state == SERVICE_START_PRE || - s->state == SERVICE_START || - s->state == SERVICE_START_POST) - return 0; - - assert(s->state == SERVICE_DEAD || s->state == SERVICE_FAILED || s->state == SERVICE_AUTO_RESTART); - - /* Make sure we don't enter a busy loop of some kind. */ - r = service_start_limit_test(s); - if (r < 0) { - service_notify_sockets_dead(s, true); - 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; - - service_enter_start_pre(s); - return 0; -} - -static int service_stop(Unit *u) { - Service *s = SERVICE(u); - - assert(s); - - /* This is a user request, so don't do restarts on this - * shutdown. */ - s->forbid_restart = true; - - /* Already on it */ - if (s->state == SERVICE_STOP || - s->state == SERVICE_STOP_SIGTERM || - s->state == SERVICE_STOP_SIGKILL || - s->state == SERVICE_STOP_POST || - s->state == SERVICE_FINAL_SIGTERM || - s->state == SERVICE_FINAL_SIGKILL) - return 0; - - /* Don't allow a restart */ - 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 (s->state == SERVICE_START_PRE || - s->state == SERVICE_START || - s->state == SERVICE_START_POST || - s->state == SERVICE_RELOAD) { - service_enter_signal(s, SERVICE_STOP_SIGTERM, SERVICE_SUCCESS); - return 0; - } - - assert(s->state == SERVICE_RUNNING || - s->state == SERVICE_EXITED); - - service_enter_stop(s, SERVICE_SUCCESS); - return 0; -} - -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 0; -} - -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); - - 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", "%lu", (unsigned long) s->control_pid); - - if (s->main_pid_known && s->main_pid > 0) - unit_serialize_item_format(u, f, "main-pid", "%lu", (unsigned long) s->main_pid); - - unit_serialize_item(u, f, "main-pid-known", yes_no(s->main_pid_known)); - - if (s->status_text) - unit_serialize_item(u, f, "status-text", s->status_text); - - /* 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)); - - if (s->socket_fd >= 0) { - int copy; - - if ((copy = fdset_put_dup(fds, s->socket_fd)) < 0) - return copy; - - unit_serialize_item_format(u, f, "socket-fd", "%i", copy); - } - - if (s->main_exec_status.pid > 0) { - unit_serialize_item_format(u, f, "main-exec-status-pid", "%lu", (unsigned long) s->main_exec_status.pid); - dual_timestamp_serialize(f, "main-exec-status-start", &s->main_exec_status.start_timestamp); - dual_timestamp_serialize(f, "main-exec-status-exit", &s->main_exec_status.exit_timestamp); - - if (dual_timestamp_is_set(&s->main_exec_status.exit_timestamp)) { - unit_serialize_item_format(u, f, "main-exec-status-code", "%i", s->main_exec_status.code); - unit_serialize_item_format(u, f, "main-exec-status-status", "%i", s->main_exec_status.status); - } - } - if (dual_timestamp_is_set(&s->watchdog_timestamp)) - dual_timestamp_serialize(f, "watchdog-timestamp", &s->watchdog_timestamp); - - return 0; -} - -static int service_deserialize_item(Unit *u, const char *key, const char *value, FDSet *fds) { - Service *s = SERVICE(u); - - assert(u); - assert(key); - assert(value); - assert(fds); - - if (streq(key, "state")) { - ServiceState state; - - if ((state = service_state_from_string(value)) < 0) - log_debug("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_debug("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_debug("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_debug("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_debug("Failed to parse main-pid value %s", value); - else - service_set_main_pid(s, (pid_t) pid); - } else if (streq(key, "main-pid-known")) { - int b; - - if ((b = parse_boolean(value)) < 0) - log_debug("Failed to parse main-pid-known value %s", value); - else - s->main_pid_known = b; - } else if (streq(key, "status-text")) { - char *t; - - if ((t = strdup(value))) { - free(s->status_text); - s->status_text = t; - } - - } else if (streq(key, "control-command")) { - ServiceExecCommand id; - - if ((id = service_exec_command_from_string(value)) < 0) - log_debug("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_debug("Failed to parse socket-fd value %s", value); - else { - - if (s->socket_fd >= 0) - close_nointr_nofail(s->socket_fd); - s->socket_fd = fdset_remove(fds, fd); - } - } else if (streq(key, "main-exec-status-pid")) { - pid_t pid; - - if (parse_pid(value, &pid) < 0) - log_debug("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_debug("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_debug("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 - log_debug("Unknown serialization key '%s'", key); - - return 0; -} - -static UnitActiveState service_active_state(Unit *u) { - assert(u); - - return state_translation_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; - -#ifdef HAVE_SYSV_COMPAT - if (s->sysv_path) - return true; -#endif - - return false; -} - -static bool service_check_snapshot(Unit *u) { - Service *s = SERVICE(u); - - assert(s); - - return !s->got_socket_fd; -} - -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_debug("Setting watch for %s's PID file %s", UNIT(s)->id, s->pid_file_pathspec->path); - r = path_spec_watch(s->pid_file_pathspec, UNIT(s)); - if (r < 0) - goto fail; - - /* the pidfile might have appeared just before we set the watch */ - service_retry_pid_file(s); - - return 0; -fail: - log_error("Failed to set a watch for %s's PID file %s: %s", - UNIT(s)->id, s->pid_file_pathspec->path, strerror(-r)); - 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->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 void service_fd_event(Unit *u, int fd, uint32_t events, Watch *w) { - Service *s = SERVICE(u); - - 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_debug("inotify event for %s", u->id); - - if (path_spec_fd_event(s->pid_file_pathspec, events) < 0) - goto fail; - - if (service_retry_pid_file(s) == 0) - return; - - if (service_watch_pid_file(s) < 0) - goto fail; - - return; -fail: - service_unwatch_pid_file(s); - service_enter_signal(s, SERVICE_STOP_SIGTERM, SERVICE_FAILURE_RESOURCES); -} - -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) : is_clean_exit_lsb(code, 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 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 */ - if (s->main_command) { - s->main_command->exec_status = s->main_exec_status; - - if (s->main_command->ignore) - f = SERVICE_SUCCESS; - } - - log_full(f == SERVICE_SUCCESS ? LOG_DEBUG : LOG_NOTICE, - "%s: main process exited, code=%s, status=%i", u->id, sigchld_code_to_string(code), status); - - 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_debug("%s running next main command for state %s", u->id, 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; - } else { - assert(s->type == SERVICE_DBUS || s->type == SERVICE_NOTIFY); - - /* Fall through */ - } - - case SERVICE_RUNNING: - service_enter_running(s, f); - break; - - 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; - - 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_full(f == SERVICE_SUCCESS ? LOG_DEBUG : LOG_NOTICE, - "%s: control process exited, code=%s status=%i", u->id, sigchld_code_to_string(code), status); - - if (f != SERVICE_SUCCESS) - s->result = f; - - if (s->control_command && - s->control_command->command_next && - f == SERVICE_SUCCESS) { - - /* There is another command to * - * execute, so let's do that. */ - - log_debug("%s running next control command for state %s", u->id, 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_debug("%s got final SIGCHLD for state %s", u->id, 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: - assert(s->type == SERVICE_FORKING); - - 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_stop(s, 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) { - service_load_pid_file(s, true); - 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_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: - 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); -} - -static void service_timer_event(Unit *u, uint64_t elapsed, Watch* w) { - Service *s = SERVICE(u); - - assert(s); - assert(elapsed == 1); - - if (w == &s->watchdog_watch) { - service_handle_watchdog(s); - return; - } - - assert(w == &s->timer_watch); - - switch (s->state) { - - case SERVICE_START_PRE: - case SERVICE_START: - log_warning("%s operation timed out. Terminating.", u->id); - service_enter_signal(s, SERVICE_FINAL_SIGTERM, SERVICE_FAILURE_TIMEOUT); - break; - - case SERVICE_START_POST: - log_warning("%s operation timed out. Stopping.", u->id); - service_enter_stop(s, SERVICE_FAILURE_TIMEOUT); - break; - - case SERVICE_RELOAD: - log_warning("%s operation timed out. Stopping.", u->id); - s->reload_result = SERVICE_FAILURE_TIMEOUT; - service_enter_running(s, SERVICE_SUCCESS); - break; - - case SERVICE_STOP: - log_warning("%s stopping timed out. Terminating.", u->id); - service_enter_signal(s, SERVICE_STOP_SIGTERM, SERVICE_FAILURE_TIMEOUT); - break; - - case SERVICE_STOP_SIGTERM: - if (s->exec_context.send_sigkill) { - log_warning("%s stopping timed out. Killing.", u->id); - service_enter_signal(s, SERVICE_STOP_SIGKILL, SERVICE_FAILURE_TIMEOUT); - } else { - log_warning("%s stopping timed out. Skipping SIGKILL.", u->id); - 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_warning("%s still around after SIGKILL. Ignoring.", u->id); - service_enter_stop_post(s, SERVICE_FAILURE_TIMEOUT); - break; - - case SERVICE_STOP_POST: - log_warning("%s stopping timed out (2). Terminating.", u->id); - service_enter_signal(s, SERVICE_FINAL_SIGTERM, SERVICE_FAILURE_TIMEOUT); - break; - - case SERVICE_FINAL_SIGTERM: - if (s->exec_context.send_sigkill) { - log_warning("%s stopping timed out (2). Killing.", u->id); - service_enter_signal(s, SERVICE_FINAL_SIGKILL, SERVICE_FAILURE_TIMEOUT); - } else { - log_warning("%s stopping timed out (2). Skipping SIGKILL. Entering failed mode.", u->id); - service_enter_dead(s, SERVICE_FAILURE_TIMEOUT, false); - } - - break; - - case SERVICE_FINAL_SIGKILL: - log_warning("%s still around after SIGKILL (2). Entering failed mode.", u->id); - service_enter_dead(s, SERVICE_FAILURE_TIMEOUT, true); - break; - - case SERVICE_AUTO_RESTART: - log_info("%s holdoff time over, scheduling restart.", u->id); - service_enter_restart(s); - break; - - default: - assert_not_reached("Timeout at wrong time."); - } -} - -static void service_cgroup_notify_event(Unit *u) { - Service *s = SERVICE(u); - - assert(u); - - log_debug("%s: cgroup is empty", u->id); - - 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_warning("%s never wrote its PID file. Failing.", UNIT(s)->id); - 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_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_FINAL_SIGTERM: - case SERVICE_FINAL_SIGKILL: - if (main_pid_good(s) <= 0 && !control_pid_good(s)) - service_enter_dead(s, SERVICE_SUCCESS, SERVICE_SUCCESS); - - break; - - default: - ; - } -} - -static void service_notify_message(Unit *u, pid_t pid, char **tags) { - Service *s = SERVICE(u); - const char *e; - - assert(u); - - if (s->notify_access == NOTIFY_NONE) { - log_warning("%s: Got notification message from PID %lu, but reception is disabled.", - u->id, (unsigned long) pid); - return; - } - - if (s->notify_access == NOTIFY_MAIN && pid != s->main_pid) { - log_warning("%s: Got notification message from PID %lu, but reception only permitted for PID %lu", - u->id, (unsigned long) pid, (unsigned long) s->main_pid); - return; - } - - log_debug("%s: Got message", u->id); - - /* Interpret MAINPID= */ - if ((e = strv_find_prefix(tags, "MAINPID=")) && - (s->state == SERVICE_START || - s->state == SERVICE_START_POST || - s->state == SERVICE_RUNNING || - s->state == SERVICE_RELOAD)) { - - if (parse_pid(e + 8, &pid) < 0) - log_warning("Failed to parse notification message %s", e); - else { - log_debug("%s: got %s", u->id, e); - service_set_main_pid(s, pid); - } - } - - /* Interpret READY= */ - if (s->type == SERVICE_NOTIFY && - s->state == SERVICE_START && - strv_find(tags, "READY=1")) { - log_debug("%s: got READY=1", u->id); - - service_enter_start_post(s); - } - - /* Interpret STATUS= */ - e = strv_find_prefix(tags, "STATUS="); - if (e) { - char *t; - - if (e[7]) { - - if (!utf8_is_valid(e+7)) { - log_warning("Status message in notification is not UTF-8 clean."); - return; - } - - t = strdup(e+7); - if (!t) { - log_error("Failed to allocate string."); - return; - } - - log_debug("%s: got %s", u->id, e); - - free(s->status_text); - s->status_text = t; - } else { - free(s->status_text); - s->status_text = NULL; - } - - } - if (strv_find(tags, "WATCHDOG=1")) { - log_debug("%s: got WATCHDOG=1", u->id); - service_reset_watchdog(s); - } - - /* Notify clients about changed status or main pid */ - unit_add_to_dbus_queue(u); -} - -#ifdef HAVE_SYSV_COMPAT - -#ifdef TARGET_SUSE -static void sysv_facility_in_insserv_conf(Manager *mgr) { - FILE *f=NULL; - int r; - - if (!(f = fopen("/etc/insserv.conf", "re"))) { - r = errno == ENOENT ? 0 : -errno; - goto finish; - } - - while (!feof(f)) { - char l[LINE_MAX], *t; - char **parsed = NULL; - - if (!fgets(l, sizeof(l), f)) { - if (feof(f)) - break; - - r = -errno; - log_error("Failed to read configuration file '/etc/insserv.conf': %s", strerror(-r)); - goto finish; - } - - t = strstrip(l); - if (*t != '$' && *t != '<') - continue; - - parsed = strv_split(t,WHITESPACE); - /* we ignore , not used, equivalent to X-Interactive */ - if (parsed && !startswith_no_case (parsed[0], "")) { - char *facility; - Unit *u; - if (sysv_translate_facility(parsed[0], NULL, &facility) < 0) - continue; - if ((u = manager_get_unit(mgr, facility)) && (u->type == UNIT_TARGET)) { - UnitDependency e; - char *dep = NULL, *name, **j; - - STRV_FOREACH (j, parsed+1) { - if (*j[0]=='+') { - e = UNIT_WANTS; - name = *j+1; - } - else { - e = UNIT_REQUIRES; - name = *j; - } - if (sysv_translate_facility(name, NULL, &dep) < 0) - continue; - - r = unit_add_two_dependencies_by_name(u, UNIT_BEFORE, e, dep, NULL, true); - free(dep); - } - } - free(facility); - } - strv_free(parsed); - } -finish: - if (f) - fclose(f); - -} -#endif - -static int service_enumerate(Manager *m) { - char **p; - unsigned i; - DIR *d = NULL; - char *path = NULL, *fpath = NULL, *name = NULL; - Set *runlevel_services[ELEMENTSOF(rcnd_table)], *shutdown_services = NULL; - Unit *service; - Iterator j; - int r; - - assert(m); - - if (m->running_as != MANAGER_SYSTEM) - return 0; - - zero(runlevel_services); - - STRV_FOREACH(p, m->lookup_paths.sysvrcnd_path) - for (i = 0; i < ELEMENTSOF(rcnd_table); i ++) { - struct dirent *de; - - free(path); - path = join(*p, "/", rcnd_table[i].path, NULL); - if (!path) { - r = -ENOMEM; - goto finish; - } - - if (d) - closedir(d); - - if (!(d = opendir(path))) { - if (errno != ENOENT) - log_warning("opendir() failed on %s: %s", path, strerror(errno)); - - continue; - } - - while ((de = readdir(d))) { - int a, b; - - if (ignore_file(de->d_name)) - continue; - - 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; - - free(fpath); - fpath = join(path, "/", de->d_name, NULL); - if (!fpath) { - r = -ENOMEM; - goto finish; - } - - if (access(fpath, X_OK) < 0) { - - if (errno != ENOENT) - log_warning("access() failed on %s: %s", fpath, strerror(errno)); - - continue; - } - - free(name); - if (!(name = sysv_translate_name(de->d_name + 3))) { - r = -ENOMEM; - goto finish; - } - - if ((r = manager_load_unit_prepare(m, name, NULL, NULL, &service)) < 0) { - log_warning("Failed to prepare unit %s: %s", name, strerror(-r)); - continue; - } - - if (de->d_name[0] == 'S') { - - if (rcnd_table[i].type == RUNLEVEL_UP || rcnd_table[i].type == RUNLEVEL_SYSINIT) { - SERVICE(service)->sysv_start_priority_from_rcnd = - MAX(a*10 + b, SERVICE(service)->sysv_start_priority_from_rcnd); - - SERVICE(service)->sysv_enabled = true; - } - - if ((r = set_ensure_allocated(&runlevel_services[i], trivial_hash_func, trivial_compare_func)) < 0) - goto finish; - - if ((r = set_put(runlevel_services[i], service)) < 0) - goto finish; - - } else if (de->d_name[0] == 'K' && - (rcnd_table[i].type == RUNLEVEL_DOWN || - rcnd_table[i].type == RUNLEVEL_SYSINIT)) { - - if ((r = set_ensure_allocated(&shutdown_services, trivial_hash_func, trivial_compare_func)) < 0) - goto finish; - - if ((r = set_put(shutdown_services, service)) < 0) - goto finish; - } - } - } - - /* Now we loaded all stubs and are aware of the lowest - start-up priority for all services, not let's actually load - the services, this will also tell us which services are - actually native now */ - manager_dispatch_load_queue(m); - - /* If this is a native service, rely on native ways to pull in - * a service, don't pull it in via sysv rcN.d links. */ - for (i = 0; i < ELEMENTSOF(rcnd_table); i ++) - SET_FOREACH(service, runlevel_services[i], j) { - service = unit_follow_merge(service); - - if (service->fragment_path) - continue; - - if ((r = unit_add_two_dependencies_by_name_inverse(service, UNIT_AFTER, UNIT_WANTS, rcnd_table[i].target, NULL, true)) < 0) - goto finish; - } - - /* We honour K links only for halt/reboot. For the normal - * runlevels we assume the stop jobs will be implicitly added - * by the core logic. Also, we don't really distinguish here - * between the runlevels 0 and 6 and just add them to the - * special shutdown target. On SUSE the boot.d/ runlevel is - * also used for shutdown, so we add links for that too to the - * shutdown target.*/ - SET_FOREACH(service, shutdown_services, j) { - service = unit_follow_merge(service); - - if (service->fragment_path) - continue; - - if ((r = unit_add_two_dependencies_by_name(service, UNIT_BEFORE, UNIT_CONFLICTS, SPECIAL_SHUTDOWN_TARGET, NULL, true)) < 0) - goto finish; - } - - r = 0; - -#ifdef TARGET_SUSE - sysv_facility_in_insserv_conf (m); -#endif - -finish: - free(path); - free(fpath); - free(name); - - for (i = 0; i < ELEMENTSOF(rcnd_table); i++) - set_free(runlevel_services[i]); - set_free(shutdown_services); - - if (d) - closedir(d); - - return r; -} -#endif - -static void service_bus_name_owner_change( - Unit *u, - const char *name, - const char *old_owner, - const char *new_owner) { - - Service *s = SERVICE(u); - - assert(s); - assert(name); - - assert(streq(s->bus_name, name)); - assert(old_owner || new_owner); - - if (old_owner && new_owner) - log_debug("%s's D-Bus name %s changed owner from %s to %s", u->id, name, old_owner, new_owner); - else if (old_owner) - log_debug("%s's D-Bus name %s no longer registered by %s", u->id, name, old_owner); - else - log_debug("%s's D-Bus name %s now registered by %s", u->id, name, new_owner); - - s->bus_name_good = !!new_owner; - - 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)) { - - /* Try to acquire PID from bus service */ - log_debug("Trying to acquire PID from D-Bus name..."); - - bus_query_pid(u->manager, name); - } -} - -static void service_bus_query_pid_done( - Unit *u, - const char *name, - pid_t pid) { - - Service *s = SERVICE(u); - - assert(s); - assert(name); - - log_debug("%s's D-Bus name %s is now owned by process %u", u->id, name, (unsigned) pid); - - if (s->main_pid <= 0 && - (s->state == SERVICE_START || - s->state == SERVICE_START_POST || - s->state == SERVICE_RUNNING || - s->state == SERVICE_RELOAD)) - service_set_main_pid(s, pid); -} - -int service_set_socket_fd(Service *s, int fd, Socket *sock) { - - assert(s); - assert(fd >= 0); - - /* This is called by the socket code when instantiating a new - * service for a stream socket and the socket needs to be - * configured. */ - - if (UNIT(s)->load_state != UNIT_LOADED) - return -EINVAL; - - if (s->socket_fd >= 0) - return -EBUSY; - - if (s->state != SERVICE_DEAD) - return -EAGAIN; - - s->socket_fd = fd; - s->got_socket_fd = true; - - unit_ref_set(&s->accept_socket, UNIT(sock)); - - return unit_add_two_dependencies(UNIT(sock), UNIT_BEFORE, UNIT_TRIGGERS, UNIT(s), false); -} - -static void service_reset_failed(Unit *u) { - Service *s = SERVICE(u); - - assert(s); - - if (s->state == SERVICE_FAILED) - service_set_state(s, SERVICE_DEAD); - - s->result = SERVICE_SUCCESS; - s->reload_result = SERVICE_SUCCESS; -} - -static bool service_need_daemon_reload(Unit *u) { - Service *s = SERVICE(u); - - assert(s); - -#ifdef HAVE_SYSV_COMPAT - if (s->sysv_path) { - struct stat st; - - zero(st); - if (stat(s->sysv_path, &st) < 0) - /* What, cannot access this anymore? */ - return true; - - if (s->sysv_mtime > 0 && - timespec_load(&st.st_mtim) != s->sysv_mtime) - return true; - } -#endif - - return false; -} - -static int service_kill(Unit *u, KillWho who, KillMode mode, int signo, DBusError *error) { - Service *s = SERVICE(u); - int r = 0; - Set *pid_set = NULL; - - assert(s); - - if (s->main_pid <= 0 && who == KILL_MAIN) { - dbus_set_error(error, BUS_ERROR_NO_SUCH_PROCESS, "No main process to kill"); - return -ESRCH; - } - - if (s->control_pid <= 0 && who == KILL_CONTROL) { - dbus_set_error(error, BUS_ERROR_NO_SUCH_PROCESS, "No control process to kill"); - return -ESRCH; - } - - if (who == KILL_CONTROL || who == KILL_ALL) - if (s->control_pid > 0) - if (kill(s->control_pid, signo) < 0) - r = -errno; - - if (who == KILL_MAIN || who == KILL_ALL) - if (s->main_pid > 0) - if (kill(s->main_pid, signo) < 0) - r = -errno; - - if (who == KILL_ALL && mode == KILL_CONTROL_GROUP) { - int q; - - if (!(pid_set = set_new(trivial_hash_func, trivial_compare_func))) - return -ENOMEM; - - /* Exclude the control/main pid from being killed via the cgroup */ - if (s->control_pid > 0) - if ((q = set_put(pid_set, LONG_TO_PTR(s->control_pid))) < 0) { - r = q; - goto finish; - } - - if (s->main_pid > 0) - if ((q = set_put(pid_set, LONG_TO_PTR(s->main_pid))) < 0) { - r = q; - goto finish; - } - - if ((q = cgroup_bonding_kill_list(UNIT(s)->cgroup_bondings, signo, false, pid_set)) < 0) - if (q != -EAGAIN && q != -ESRCH && q != -ENOENT) - r = q; - } - -finish: - if (pid_set) - set_free(pid_set); - - return r; -} - -static const char* const service_state_table[_SERVICE_STATE_MAX] = { - [SERVICE_DEAD] = "dead", - [SERVICE_START_PRE] = "start-pre", - [SERVICE_START] = "start", - [SERVICE_START_POST] = "start-post", - [SERVICE_RUNNING] = "running", - [SERVICE_EXITED] = "exited", - [SERVICE_RELOAD] = "reload", - [SERVICE_STOP] = "stop", - [SERVICE_STOP_SIGTERM] = "stop-sigterm", - [SERVICE_STOP_SIGKILL] = "stop-sigkill", - [SERVICE_STOP_POST] = "stop-post", - [SERVICE_FINAL_SIGTERM] = "final-sigterm", - [SERVICE_FINAL_SIGKILL] = "final-sigkill", - [SERVICE_FAILED] = "failed", - [SERVICE_AUTO_RESTART] = "auto-restart", -}; - -DEFINE_STRING_TABLE_LOOKUP(service_state, ServiceState); - -static const char* const 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_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" -}; - -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 service_result_table[_SERVICE_RESULT_MAX] = { - [SERVICE_SUCCESS] = "success", - [SERVICE_FAILURE_RESOURCES] = "resources", - [SERVICE_FAILURE_TIMEOUT] = "timeout", - [SERVICE_FAILURE_EXIT_CODE] = "exit-code", - [SERVICE_FAILURE_SIGNAL] = "signal", - [SERVICE_FAILURE_CORE_DUMP] = "core-dump", - [SERVICE_FAILURE_WATCHDOG] = "watchdog" -}; - -DEFINE_STRING_TABLE_LOOKUP(service_result, ServiceResult); - -static const char* const start_limit_action_table[_SERVICE_START_LIMIT_MAX] = { - [SERVICE_START_LIMIT_NONE] = "none", - [SERVICE_START_LIMIT_REBOOT] = "reboot", - [SERVICE_START_LIMIT_REBOOT_FORCE] = "reboot-force", - [SERVICE_START_LIMIT_REBOOT_IMMEDIATE] = "reboot-immediate" -}; -DEFINE_STRING_TABLE_LOOKUP(start_limit_action, StartLimitAction); - -const UnitVTable service_vtable = { - .suffix = ".service", - .object_size = sizeof(Service), - .sections = - "Unit\0" - "Service\0" - "Install\0", - .show_status = true, - - .init = service_init, - .done = service_done, - .load = service_load, - - .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, - .check_snapshot = service_check_snapshot, - - .sigchld_event = service_sigchld_event, - .timer_event = service_timer_event, - .fd_event = service_fd_event, - - .reset_failed = service_reset_failed, - - .need_daemon_reload = service_need_daemon_reload, - - .cgroup_notify_empty = service_cgroup_notify_event, - .notify_message = service_notify_message, - - .bus_name_owner_change = service_bus_name_owner_change, - .bus_query_pid_done = service_bus_query_pid_done, - - .bus_interface = "org.freedesktop.systemd1.Service", - .bus_message_handler = bus_service_message_handler, - .bus_invalidating_properties = bus_service_invalidating_properties, - -#ifdef HAVE_SYSV_COMPAT - .enumerate = service_enumerate -#endif -}; diff --git a/src/service.h b/src/service.h deleted file mode 100644 index 60b10516eb..0000000000 --- a/src/service.h +++ /dev/null @@ -1,220 +0,0 @@ -/*-*- Mode: C; c-basic-offset: 8; indent-tabs-mode: nil -*-*/ - -#ifndef fooservicehfoo -#define fooservicehfoo - -/*** - This file is part of systemd. - - Copyright 2010 Lennart Poettering - - systemd is free software; you can redistribute it and/or modify it - under the terms of the GNU General Public License as published by - the Free Software Foundation; either version 2 of the License, or - (at your option) any later version. - - systemd is distributed in the hope that 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 General Public License - along with systemd; If not, see . -***/ - -typedef struct Service Service; - -#include "unit.h" -#include "path.h" -#include "ratelimit.h" -#include "service.h" - -typedef enum ServiceState { - SERVICE_DEAD, - SERVICE_START_PRE, - SERVICE_START, - SERVICE_START_POST, - SERVICE_RUNNING, - SERVICE_EXITED, /* Nothing is running anymore, but RemainAfterExit is true hence this is OK */ - SERVICE_RELOAD, - SERVICE_STOP, /* No STOP_PRE state, instead just register multiple STOP executables */ - SERVICE_STOP_SIGTERM, - SERVICE_STOP_SIGKILL, - SERVICE_STOP_POST, - SERVICE_FINAL_SIGTERM, /* In case the STOP_POST executable hangs, we shoot that down, too */ - SERVICE_FINAL_SIGKILL, - SERVICE_FAILED, - SERVICE_AUTO_RESTART, - _SERVICE_STATE_MAX, - _SERVICE_STATE_INVALID = -1 -} ServiceState; - -typedef enum ServiceRestart { - SERVICE_RESTART_NO, - SERVICE_RESTART_ON_SUCCESS, - SERVICE_RESTART_ON_FAILURE, - 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_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 ServiceResult { - SERVICE_SUCCESS, - SERVICE_FAILURE_RESOURCES, - SERVICE_FAILURE_TIMEOUT, - SERVICE_FAILURE_EXIT_CODE, - SERVICE_FAILURE_SIGNAL, - SERVICE_FAILURE_CORE_DUMP, - SERVICE_FAILURE_WATCHDOG, - _SERVICE_RESULT_MAX, - _SERVICE_RESULT_INVALID = -1 -} ServiceResult; - -typedef enum StartLimitAction { - SERVICE_START_LIMIT_NONE, - SERVICE_START_LIMIT_REBOOT, - SERVICE_START_LIMIT_REBOOT_FORCE, - SERVICE_START_LIMIT_REBOOT_IMMEDIATE, - _SERVICE_START_LIMIT_MAX, - _SERVICE_START_LIMIT_INVALID = -1 -} StartLimitAction; - -struct Service { - Unit meta; - - ServiceType type; - ServiceRestart restart; - - /* If set we'll read the main daemon PID from this file */ - char *pid_file; - - usec_t restart_usec; - usec_t timeout_usec; - - dual_timestamp watchdog_timestamp; - usec_t watchdog_usec; - Watch watchdog_watch; - - ExecCommand* exec_command[_SERVICE_EXEC_COMMAND_MAX]; - ExecContext exec_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; - - pid_t main_pid, control_pid; - int socket_fd; - - int fsck_passno; - - 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 got_socket_fd:1; -#ifdef HAVE_SYSV_COMPAT - bool sysv_has_lsb:1; - bool sysv_enabled:1; - int sysv_start_priority_from_rcnd; - int sysv_start_priority; - - char *sysv_path; - char *sysv_runlevels; - usec_t sysv_mtime; -#endif - - char *bus_name; - - char *status_text; - - RateLimit start_limit; - StartLimitAction start_limit_action; - - - UnitRef accept_socket; - - Watch timer_watch; - PathSpec *pid_file_pathspec; - - NotifyAccess notify_access; -}; - -extern const UnitVTable service_vtable; - -struct Socket; - -int service_set_socket_fd(Service *s, int fd, struct Socket *socket); - -const char* service_state_to_string(ServiceState i); -ServiceState service_state_from_string(const char *s); - -const char* service_restart_to_string(ServiceRestart i); -ServiceRestart service_restart_from_string(const char *s); - -const char* service_type_to_string(ServiceType i); -ServiceType service_type_from_string(const char *s); - -const char* service_exec_command_to_string(ServiceExecCommand i); -ServiceExecCommand service_exec_command_from_string(const char *s); - -const char* notify_access_to_string(NotifyAccess i); -NotifyAccess notify_access_from_string(const char *s); - -const char* service_result_to_string(ServiceResult i); -ServiceResult service_result_from_string(const char *s); - -const char* start_limit_action_to_string(StartLimitAction i); -StartLimitAction start_limit_action_from_string(const char *s); - -#endif diff --git a/src/snapshot.c b/src/snapshot.c deleted file mode 100644 index 82ec5104db..0000000000 --- a/src/snapshot.c +++ /dev/null @@ -1,309 +0,0 @@ -/*-*- Mode: C; c-basic-offset: 8; indent-tabs-mode: nil -*-*/ - -/*** - This file is part of systemd. - - Copyright 2010 Lennart Poettering - - systemd is free software; you can redistribute it and/or modify it - under the terms of the GNU General Public License as published by - the Free Software Foundation; either version 2 of the License, or - (at your option) any later version. - - systemd is distributed in the hope that 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 General Public License - along with systemd; If not, see . -***/ - -#include - -#include "unit.h" -#include "snapshot.h" -#include "unit-name.h" -#include "dbus-snapshot.h" -#include "bus-errors.h" - -static const UnitActiveState state_translation_table[_SNAPSHOT_STATE_MAX] = { - [SNAPSHOT_DEAD] = UNIT_INACTIVE, - [SNAPSHOT_ACTIVE] = UNIT_ACTIVE -}; - -static void snapshot_init(Unit *u) { - Snapshot *s = SNAPSHOT(u); - - assert(s); - assert(UNIT(s)->load_state == UNIT_STUB); - - UNIT(s)->ignore_on_isolate = true; - UNIT(s)->ignore_on_snapshot = true; -} - -static void snapshot_set_state(Snapshot *s, SnapshotState state) { - SnapshotState old_state; - assert(s); - - old_state = s->state; - s->state = state; - - if (state != old_state) - log_debug("%s changed %s -> %s", - UNIT(s)->id, - snapshot_state_to_string(old_state), - snapshot_state_to_string(state)); - - unit_notify(UNIT(s), state_translation_table[old_state], state_translation_table[state], true); -} - -static int snapshot_load(Unit *u) { - Snapshot *s = SNAPSHOT(u); - - assert(u); - assert(u->load_state == UNIT_STUB); - - /* Make sure that only snapshots created via snapshot_create() - * can be loaded */ - if (!s->by_snapshot_create && UNIT(s)->manager->n_reloading <= 0) - return -ENOENT; - - u->load_state = UNIT_LOADED; - return 0; -} - -static int snapshot_coldplug(Unit *u) { - Snapshot *s = SNAPSHOT(u); - - assert(s); - assert(s->state == SNAPSHOT_DEAD); - - if (s->deserialized_state != s->state) - snapshot_set_state(s, s->deserialized_state); - - return 0; -} - -static void snapshot_dump(Unit *u, FILE *f, const char *prefix) { - Snapshot *s = SNAPSHOT(u); - - assert(s); - assert(f); - - fprintf(f, - "%sSnapshot State: %s\n" - "%sClean Up: %s\n", - prefix, snapshot_state_to_string(s->state), - prefix, yes_no(s->cleanup)); -} - -static int snapshot_start(Unit *u) { - Snapshot *s = SNAPSHOT(u); - - assert(s); - assert(s->state == SNAPSHOT_DEAD); - - snapshot_set_state(s, SNAPSHOT_ACTIVE); - - if (s->cleanup) - unit_add_to_cleanup_queue(u); - - return 0; -} - -static int snapshot_stop(Unit *u) { - Snapshot *s = SNAPSHOT(u); - - assert(s); - assert(s->state == SNAPSHOT_ACTIVE); - - snapshot_set_state(s, SNAPSHOT_DEAD); - return 0; -} - -static int snapshot_serialize(Unit *u, FILE *f, FDSet *fds) { - Snapshot *s = SNAPSHOT(u); - Unit *other; - Iterator i; - - assert(s); - assert(f); - assert(fds); - - unit_serialize_item(u, f, "state", snapshot_state_to_string(s->state)); - unit_serialize_item(u, f, "cleanup", yes_no(s->cleanup)); - SET_FOREACH(other, u->dependencies[UNIT_WANTS], i) - unit_serialize_item(u, f, "wants", other->id); - - return 0; -} - -static int snapshot_deserialize_item(Unit *u, const char *key, const char *value, FDSet *fds) { - Snapshot *s = SNAPSHOT(u); - int r; - - assert(u); - assert(key); - assert(value); - assert(fds); - - if (streq(key, "state")) { - SnapshotState state; - - if ((state = snapshot_state_from_string(value)) < 0) - log_debug("Failed to parse state value %s", value); - else - s->deserialized_state = state; - - } else if (streq(key, "cleanup")) { - - if ((r = parse_boolean(value)) < 0) - log_debug("Failed to parse cleanup value %s", value); - else - s->cleanup = r; - - } else if (streq(key, "wants")) { - - if ((r = unit_add_two_dependencies_by_name(u, UNIT_AFTER, UNIT_WANTS, value, NULL, true)) < 0) - return r; - } else - log_debug("Unknown serialization key '%s'", key); - - return 0; -} - -static UnitActiveState snapshot_active_state(Unit *u) { - assert(u); - - return state_translation_table[SNAPSHOT(u)->state]; -} - -static const char *snapshot_sub_state_to_string(Unit *u) { - assert(u); - - return snapshot_state_to_string(SNAPSHOT(u)->state); -} - -int snapshot_create(Manager *m, const char *name, bool cleanup, DBusError *e, Snapshot **_s) { - Iterator i; - Unit *other, *u = NULL; - char *n = NULL; - int r; - const char *k; - - assert(m); - assert(_s); - - if (name) { - if (!unit_name_is_valid(name, false)) { - dbus_set_error(e, BUS_ERROR_INVALID_NAME, "Unit name %s is not valid.", name); - return -EINVAL; - } - - if (unit_name_to_type(name) != UNIT_SNAPSHOT) { - dbus_set_error(e, BUS_ERROR_UNIT_TYPE_MISMATCH, "Unit name %s lacks snapshot suffix.", name); - return -EINVAL; - } - - if (manager_get_unit(m, name)) { - dbus_set_error(e, BUS_ERROR_UNIT_EXISTS, "Snapshot %s exists already.", name); - return -EEXIST; - } - - } else { - - for (;;) { - if (asprintf(&n, "snapshot-%u.snapshot", ++ m->n_snapshots) < 0) - return -ENOMEM; - - if (!manager_get_unit(m, n)) - break; - - free(n); - } - - name = n; - } - - r = manager_load_unit_prepare(m, name, NULL, e, &u); - free(n); - - if (r < 0) - goto fail; - - SNAPSHOT(u)->by_snapshot_create = true; - manager_dispatch_load_queue(m); - assert(u->load_state == UNIT_LOADED); - - HASHMAP_FOREACH_KEY(other, k, m->units, i) { - - if (other->ignore_on_snapshot) - continue; - - if (k != other->id) - continue; - - if (UNIT_VTABLE(other)->check_snapshot) - if (!UNIT_VTABLE(other)->check_snapshot(other)) - continue; - - if (!UNIT_IS_ACTIVE_OR_ACTIVATING(unit_active_state(other))) - continue; - - if ((r = unit_add_two_dependencies(u, UNIT_AFTER, UNIT_WANTS, other, true)) < 0) - goto fail; - } - - SNAPSHOT(u)->cleanup = cleanup; - *_s = SNAPSHOT(u); - - return 0; - -fail: - if (u) - unit_add_to_cleanup_queue(u); - - return r; -} - -void snapshot_remove(Snapshot *s) { - assert(s); - - unit_add_to_cleanup_queue(UNIT(s)); -} - -static const char* const snapshot_state_table[_SNAPSHOT_STATE_MAX] = { - [SNAPSHOT_DEAD] = "dead", - [SNAPSHOT_ACTIVE] = "active" -}; - -DEFINE_STRING_TABLE_LOOKUP(snapshot_state, SnapshotState); - -const UnitVTable snapshot_vtable = { - .suffix = ".snapshot", - .object_size = sizeof(Snapshot), - - .no_alias = true, - .no_instances = true, - .no_gc = true, - - .init = snapshot_init, - - .load = snapshot_load, - .coldplug = snapshot_coldplug, - - .dump = snapshot_dump, - - .start = snapshot_start, - .stop = snapshot_stop, - - .serialize = snapshot_serialize, - .deserialize_item = snapshot_deserialize_item, - - .active_state = snapshot_active_state, - .sub_state_to_string = snapshot_sub_state_to_string, - - .bus_interface = "org.freedesktop.systemd1.Snapshot", - .bus_message_handler = bus_snapshot_message_handler -}; diff --git a/src/snapshot.h b/src/snapshot.h deleted file mode 100644 index bf92e99a8b..0000000000 --- a/src/snapshot.h +++ /dev/null @@ -1,53 +0,0 @@ -/*-*- Mode: C; c-basic-offset: 8; indent-tabs-mode: nil -*-*/ - -#ifndef foosnapshothfoo -#define foosnapshothfoo - -/*** - This file is part of systemd. - - Copyright 2010 Lennart Poettering - - systemd is free software; you can redistribute it and/or modify it - under the terms of the GNU General Public License as published by - the Free Software Foundation; either version 2 of the License, or - (at your option) any later version. - - systemd is distributed in the hope that 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 General Public License - along with systemd; If not, see . -***/ - -typedef struct Snapshot Snapshot; - -#include "unit.h" - -typedef enum SnapshotState { - SNAPSHOT_DEAD, - SNAPSHOT_ACTIVE, - _SNAPSHOT_STATE_MAX, - _SNAPSHOT_STATE_INVALID = -1 -} SnapshotState; - -struct Snapshot { - Unit meta; - - SnapshotState state, deserialized_state; - - bool cleanup; - bool by_snapshot_create:1; -}; - -extern const UnitVTable snapshot_vtable; - -int snapshot_create(Manager *m, const char *name, bool cleanup, DBusError *e, Snapshot **s); -void snapshot_remove(Snapshot *s); - -const char* snapshot_state_to_string(SnapshotState i); -SnapshotState snapshot_state_from_string(const char *s); - -#endif diff --git a/src/socket.c b/src/socket.c deleted file mode 100644 index 5b24b3422b..0000000000 --- a/src/socket.c +++ /dev/null @@ -1,2218 +0,0 @@ -/*-*- Mode: C; c-basic-offset: 8; indent-tabs-mode: nil -*-*/ - -/*** - This file is part of systemd. - - Copyright 2010 Lennart Poettering - - systemd is free software; you can redistribute it and/or modify it - under the terms of the GNU General Public License as published by - the Free Software Foundation; either version 2 of the License, or - (at your option) any later version. - - systemd is distributed in the hope that 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 General Public License - along with systemd; If not, see . -***/ - -#include -#include -#include -#include -#include -#include -#include -#include -#include - -#include "unit.h" -#include "socket.h" -#include "netinet/tcp.h" -#include "log.h" -#include "load-dropin.h" -#include "load-fragment.h" -#include "strv.h" -#include "mkdir.h" -#include "unit-name.h" -#include "dbus-socket.h" -#include "missing.h" -#include "special.h" -#include "bus-errors.h" -#include "label.h" -#include "exit-status.h" -#include "def.h" - -static const UnitActiveState state_translation_table[_SOCKET_STATE_MAX] = { - [SOCKET_DEAD] = UNIT_INACTIVE, - [SOCKET_START_PRE] = 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 void socket_init(Unit *u) { - Socket *s = SOCKET(u); - - assert(u); - assert(u->load_state == UNIT_STUB); - - s->backlog = SOMAXCONN; - s->timeout_usec = DEFAULT_TIMEOUT_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; - - exec_context_init(&s->exec_context); - s->exec_context.std_output = u->manager->default_std_output; - s->exec_context.std_error = u->manager->default_std_error; - - s->control_command_id = _SOCKET_EXEC_COMMAND_INVALID; -} - -static void socket_unwatch_control_pid(Socket *s) { - assert(s); - - if (s->control_pid <= 0) - return; - - unit_unwatch_pid(UNIT(s), s->control_pid); - s->control_pid = 0; -} - -static void socket_done(Unit *u) { - Socket *s = SOCKET(u); - SocketPort *p; - - assert(s); - - while ((p = s->ports)) { - LIST_REMOVE(SocketPort, port, s->ports, p); - - if (p->fd >= 0) { - unit_unwatch_fd(UNIT(s), &p->fd_watch); - close_nointr_nofail(p->fd); - } - - free(p->path); - free(p); - } - - exec_context_done(&s->exec_context); - 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); - - free(s->tcp_congestion); - s->tcp_congestion = NULL; - - free(s->bind_to_device); - s->bind_to_device = NULL; - - unit_unwatch_timer(u, &s->timer_watch); -} - -static int socket_instantiate_service(Socket *s) { - char *prefix, *name; - 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; - - assert(s->accept); - - if (!(prefix = unit_name_to_prefix(UNIT(s)->id))) - return -ENOMEM; - - r = asprintf(&name, "%s@%u.service", prefix, s->n_accepted); - free(prefix); - - if (r < 0) - return -ENOMEM; - - r = manager_load_unit(UNIT(s)->manager, name, NULL, NULL, &u); - free(name); - - if (r < 0) - return r; - -#ifdef HAVE_SYSV_COMPAT - if (SERVICE(u)->sysv_path) { - log_error("Using SysV services for socket activation is not supported. Refusing."); - return -ENOENT; - } -#endif - - u->no_gc = true; - unit_ref_set(&s->service, u); - - return unit_add_two_dependencies(UNIT(s), UNIT_BEFORE, UNIT_TRIGGERS, u, false); -} - -static bool have_non_accept_socket(Socket *s) { - SocketPort *p; - - assert(s); - - if (!s->accept) - return true; - - LIST_FOREACH(port, p, s->ports) { - - if (p->type != SOCKET_SOCKET) - return true; - - if (!socket_address_can_accept(&p->address)) - return true; - } - - return false; -} - -static int socket_verify(Socket *s) { - assert(s); - - if (UNIT(s)->load_state != UNIT_LOADED) - return 0; - - if (!s->ports) { - log_error("%s lacks Listen setting. Refusing.", UNIT(s)->id); - return -EINVAL; - } - - if (s->accept && have_non_accept_socket(s)) { - log_error("%s configured for accepting sockets, but sockets are non-accepting. Refusing.", UNIT(s)->id); - return -EINVAL; - } - - if (s->accept && s->max_connections <= 0) { - log_error("%s's MaxConnection setting too small. Refusing.", UNIT(s)->id); - return -EINVAL; - } - - if (s->accept && UNIT_DEREF(s->service)) { - log_error("Explicit service configuration for accepting sockets not supported on %s. Refusing.", UNIT(s)->id); - return -EINVAL; - } - - if (s->exec_context.pam_name && s->exec_context.kill_mode != KILL_CONTROL_GROUP) { - log_error("%s has PAM enabled. Kill mode must be set to 'control-group'. Refusing.", UNIT(s)->id); - return -EINVAL; - } - - return 0; -} - -static bool socket_needs_mount(Socket *s, const char *prefix) { - SocketPort *p; - - assert(s); - - LIST_FOREACH(port, p, s->ports) { - - if (p->type == SOCKET_SOCKET) { - if (socket_address_needs_mount(&p->address, prefix)) - return true; - } else if (p->type == SOCKET_FIFO || p->type == SOCKET_SPECIAL) { - if (path_startswith(p->path, prefix)) - return true; - } - } - - return false; -} - -int socket_add_one_mount_link(Socket *s, Mount *m) { - int r; - - assert(s); - assert(m); - - if (UNIT(s)->load_state != UNIT_LOADED || - UNIT(m)->load_state != UNIT_LOADED) - return 0; - - if (!socket_needs_mount(s, m->where)) - return 0; - - if ((r = unit_add_two_dependencies(UNIT(s), UNIT_AFTER, UNIT_REQUIRES, UNIT(m), true)) < 0) - return r; - - return 0; -} - -static int socket_add_mount_links(Socket *s) { - Unit *other; - int r; - - assert(s); - - LIST_FOREACH(units_by_type, other, UNIT(s)->manager->units_by_type[UNIT_MOUNT]) - if ((r = socket_add_one_mount_link(s, MOUNT(other))) < 0) - return r; - - return 0; -} - -static int socket_add_device_link(Socket *s) { - char *t; - int r; - - assert(s); - - if (!s->bind_to_device) - return 0; - - if (asprintf(&t, "/sys/subsystem/net/devices/%s", s->bind_to_device) < 0) - return -ENOMEM; - - r = unit_add_node_link(UNIT(s), t, false); - free(t); - - return r; -} - -static int socket_add_default_dependencies(Socket *s) { - int r; - assert(s); - - if (UNIT(s)->manager->running_as == MANAGER_SYSTEM) { - if ((r = unit_add_dependency_by_name(UNIT(s), UNIT_BEFORE, SPECIAL_SOCKETS_TARGET, NULL, true)) < 0) - return r; - - if ((r = unit_add_two_dependencies_by_name(UNIT(s), UNIT_AFTER, UNIT_REQUIRES, SPECIAL_SYSINIT_TARGET, NULL, true)) < 0) - return r; - } - - return unit_add_two_dependencies_by_name(UNIT(s), UNIT_BEFORE, UNIT_CONFLICTS, SPECIAL_SHUTDOWN_TARGET, NULL, true); -} - -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_load(Unit *u) { - Socket *s = SOCKET(u); - int r; - - assert(u); - assert(u->load_state == UNIT_STUB); - - if ((r = unit_load_fragment_and_dropin(u)) < 0) - return r; - - /* This is a new unit? Then let's add in some extras */ - if (u->load_state == UNIT_LOADED) { - - 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; - } - - if ((r = socket_add_mount_links(s)) < 0) - return r; - - if ((r = socket_add_device_link(s)) < 0) - return r; - - if (socket_has_exec(s)) - if ((r = unit_add_exec_dependencies(u, &s->exec_context)) < 0) - return r; - - if ((r = unit_add_default_cgroups(u)) < 0) - return r; - - if (UNIT(s)->default_dependencies) - if ((r = socket_add_default_dependencies(s)) < 0) - return r; - } - - return socket_verify(s); -} - -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) { - - SocketExecCommand c; - Socket *s = SOCKET(u); - SocketPort *p; - const char *prefix2; - char *p2; - - assert(s); - assert(f); - - p2 = strappend(prefix, "\t"); - prefix2 = p2 ? p2 : prefix; - - fprintf(f, - "%sSocket State: %s\n" - "%sResult: %s\n" - "%sBindIPv6Only: %s\n" - "%sBacklog: %u\n" - "%sSocketMode: %04o\n" - "%sDirectoryMode: %04o\n" - "%sKeepAlive: %s\n" - "%sFreeBind: %s\n" - "%sTransparent: %s\n" - "%sBroadcast: %s\n" - "%sPassCredentials: %s\n" - "%sPassSecurity: %s\n" - "%sTCPCongestion: %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->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)); - - if (s->control_pid > 0) - fprintf(f, - "%sControl PID: %lu\n", - prefix, (unsigned long) 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); - - LIST_FOREACH(port, p, s->ports) { - - if (p->type == SOCKET_SOCKET) { - const char *t; - int r; - char *k = NULL; - - if ((r = socket_address_print(&p->address, &k)) < 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_MQUEUE) - fprintf(f, "%sListenMessageQueue: %s\n", prefix, p->path); - else - fprintf(f, "%sListenFIFO: %s\n", prefix, p->path); - } - - exec_context_dump(&s->exec_context, f, prefix); - - 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); - } - - free(p2); -} - -static int instance_from_socket(int fd, unsigned nr, char **instance) { - socklen_t l; - char *r; - union { - struct sockaddr sa; - struct sockaddr_un un; - struct sockaddr_in in; - struct sockaddr_in6 in6; - struct sockaddr_storage storage; - } 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 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; - - l = sizeof(ucred); - if (getsockopt(fd, SOL_SOCKET, SO_PEERCRED, &ucred, &l) < 0) - return -errno; - - if (asprintf(&r, - "%u-%lu-%lu", - nr, - (unsigned long) ucred.pid, - (unsigned long) ucred.uid) < 0) - return -ENOMEM; - - break; - } - - default: - assert_not_reached("Unhandled socket type."); - } - - *instance = r; - return 0; -} - -static void socket_close_fds(Socket *s) { - SocketPort *p; - - assert(s); - - LIST_FOREACH(port, p, s->ports) { - if (p->fd < 0) - continue; - - unit_unwatch_fd(UNIT(s), &p->fd_watch); - close_nointr_nofail(p->fd); - - /* One little note: we should never delete any sockets - * in the file system here! After all some other - * process we spawned might still have a reference of - * this fd and wants to continue to use it. Therefore - * we delete sockets in the file system before we - * create a new one, not after we stopped using - * one! */ - - p->fd = -1; - } -} - -static void socket_apply_socket_options(Socket *s, int fd) { - 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_warning("SO_KEEPALIVE failed: %m"); - } - - if (s->broadcast) { - int one = 1; - if (setsockopt(fd, SOL_SOCKET, SO_BROADCAST, &one, sizeof(one)) < 0) - log_warning("SO_BROADCAST failed: %m"); - } - - if (s->pass_cred) { - int one = 1; - if (setsockopt(fd, SOL_SOCKET, SO_PASSCRED, &one, sizeof(one)) < 0) - log_warning("SO_PASSCRED failed: %m"); - } - - if (s->pass_sec) { - int one = 1; - if (setsockopt(fd, SOL_SOCKET, SO_PASSSEC, &one, sizeof(one)) < 0) - log_warning("SO_PASSSEC failed: %m"); - } - - if (s->priority >= 0) - if (setsockopt(fd, SOL_SOCKET, SO_PRIORITY, &s->priority, sizeof(s->priority)) < 0) - log_warning("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_warning("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_warning("SO_SNDBUF failed: %m"); - } - - if (s->mark >= 0) - if (setsockopt(fd, SOL_SOCKET, SO_MARK, &s->mark, sizeof(s->mark)) < 0) - log_warning("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_warning("IP_TOS failed: %m"); - - if (s->ip_ttl >= 0) { - int r, 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_warning("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_warning("TCP_CONGESTION failed: %m"); -} - -static void socket_apply_fifo_options(Socket *s, int fd) { - assert(s); - assert(fd >= 0); - - if (s->pipe_size > 0) - if (fcntl(fd, F_SETPIPE_SZ, s->pipe_size) < 0) - log_warning("F_SETPIPE_SZ: %m"); -} - -static int fifo_address_create( - const char *path, - mode_t directory_mode, - mode_t socket_mode, - int *_fd) { - - int fd = -1, r = 0; - struct stat st; - mode_t old_mask; - - assert(path); - assert(_fd); - - mkdir_parents(path, directory_mode); - - if ((r = label_fifofile_set(path)) < 0) - goto fail; - - /* Enforce the right access mode for the fifo */ - old_mask = umask(~ socket_mode); - - /* Include the original umask in our mask */ - umask(~socket_mode | old_mask); - - r = mkfifo(path, socket_mode); - umask(old_mask); - - if (r < 0 && errno != EEXIST) { - r = -errno; - goto fail; - } - - if ((fd = open(path, O_RDWR|O_CLOEXEC|O_NOCTTY|O_NONBLOCK|O_NOFOLLOW)) < 0) { - r = -errno; - goto fail; - } - - label_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; - } - - *_fd = fd; - return 0; - -fail: - label_file_clear(); - - if (fd >= 0) - close_nointr_nofail(fd); - - return r; -} - -static int special_address_create( - const char *path, - int *_fd) { - - int fd = -1, r = 0; - struct stat st; - - assert(path); - assert(_fd); - - if ((fd = open(path, O_RDONLY|O_CLOEXEC|O_NOCTTY|O_NONBLOCK|O_NOFOLLOW)) < 0) { - r = -errno; - goto fail; - } - - if (fstat(fd, &st) < 0) { - r = -errno; - goto fail; - } - - /* Check whether this is a /proc, /sys or /dev file or char device */ - if (!S_ISREG(st.st_mode) && !S_ISCHR(st.st_mode)) { - r = -EEXIST; - goto fail; - } - - *_fd = fd; - return 0; - -fail: - if (fd >= 0) - close_nointr_nofail(fd); - - return r; -} - -static int mq_address_create( - const char *path, - mode_t mq_mode, - long maxmsg, - long msgsize, - int *_fd) { - - int fd = -1, r = 0; - struct stat st; - mode_t old_mask; - struct mq_attr _attr, *attr = NULL; - - assert(path); - assert(_fd); - - if (maxmsg > 0 && msgsize > 0) { - zero(_attr); - _attr.mq_flags = O_NONBLOCK; - _attr.mq_maxmsg = maxmsg; - _attr.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 */ - umask(~mq_mode | old_mask); - - fd = mq_open(path, O_RDONLY|O_CLOEXEC|O_NONBLOCK|O_CREAT, mq_mode, attr); - umask(old_mask); - - if (fd < 0) { - r = -errno; - goto fail; - } - - if (fstat(fd, &st) < 0) { - r = -errno; - goto fail; - } - - if ((st.st_mode & 0777) != (mq_mode & ~old_mask) || - st.st_uid != getuid() || - st.st_gid != getgid()) { - - r = -EEXIST; - goto fail; - } - - *_fd = fd; - return 0; - -fail: - if (fd >= 0) - close_nointr_nofail(fd); - - return r; -} - -static int socket_open_fds(Socket *s) { - SocketPort *p; - int r; - char *label = NULL; - bool know_label = false; - - assert(s); - - LIST_FOREACH(port, p, s->ports) { - - if (p->fd >= 0) - continue; - - if (p->type == SOCKET_SOCKET) { - - if (!know_label) { - - if ((r = socket_instantiate_service(s)) < 0) - return r; - - if (UNIT_DEREF(s->service) && - SERVICE(UNIT_DEREF(s->service))->exec_command[SERVICE_EXEC_START]) { - r = label_get_create_label_from_exe(SERVICE(UNIT_DEREF(s->service))->exec_command[SERVICE_EXEC_START]->path, &label); - - if (r < 0) { - if (r != -EPERM) - return r; - } - } - - know_label = true; - } - - if ((r = socket_address_listen( - &p->address, - s->backlog, - s->bind_ipv6_only, - s->bind_to_device, - s->free_bind, - s->transparent, - s->directory_mode, - s->socket_mode, - label, - &p->fd)) < 0) - goto rollback; - - socket_apply_socket_options(s, p->fd); - - } else if (p->type == SOCKET_SPECIAL) { - - if ((r = special_address_create( - p->path, - &p->fd)) < 0) - goto rollback; - - } else if (p->type == SOCKET_FIFO) { - - if ((r = fifo_address_create( - p->path, - s->directory_mode, - s->socket_mode, - &p->fd)) < 0) - goto rollback; - - socket_apply_fifo_options(s, p->fd); - } else if (p->type == SOCKET_MQUEUE) { - - if ((r = mq_address_create( - p->path, - s->socket_mode, - s->mq_maxmsg, - s->mq_msgsize, - &p->fd)) < 0) - goto rollback; - } else - assert_not_reached("Unknown port type"); - } - - label_free(label); - return 0; - -rollback: - socket_close_fds(s); - label_free(label); - return r; -} - -static void socket_unwatch_fds(Socket *s) { - SocketPort *p; - - assert(s); - - LIST_FOREACH(port, p, s->ports) { - if (p->fd < 0) - continue; - - unit_unwatch_fd(UNIT(s), &p->fd_watch); - } -} - -static int socket_watch_fds(Socket *s) { - SocketPort *p; - int r; - - assert(s); - - LIST_FOREACH(port, p, s->ports) { - if (p->fd < 0) - continue; - - p->fd_watch.socket_accept = - s->accept && - p->type == SOCKET_SOCKET && - socket_address_can_accept(&p->address); - - if ((r = unit_watch_fd(UNIT(s), p->fd, EPOLLIN, &p->fd_watch)) < 0) - goto fail; - } - - return 0; - -fail: - socket_unwatch_fds(s); - return r; -} - -static void socket_set_state(Socket *s, SocketState state) { - SocketState old_state; - assert(s); - - old_state = s->state; - s->state = state; - - if (state != SOCKET_START_PRE && - state != SOCKET_START_POST && - state != SOCKET_STOP_PRE && - state != SOCKET_STOP_PRE_SIGTERM && - state != SOCKET_STOP_PRE_SIGKILL && - state != SOCKET_STOP_POST && - state != SOCKET_FINAL_SIGTERM && - state != SOCKET_FINAL_SIGKILL) { - unit_unwatch_timer(UNIT(s), &s->timer_watch); - 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 (state != SOCKET_START_POST && - state != SOCKET_LISTENING && - state != SOCKET_RUNNING && - state != SOCKET_STOP_PRE && - state != SOCKET_STOP_PRE_SIGTERM && - state != SOCKET_STOP_PRE_SIGKILL) - socket_close_fds(s); - - if (state != old_state) - log_debug("%s changed %s -> %s", - UNIT(s)->id, - 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) { - - if (s->deserialized_state == SOCKET_START_PRE || - s->deserialized_state == SOCKET_START_POST || - s->deserialized_state == SOCKET_STOP_PRE || - s->deserialized_state == SOCKET_STOP_PRE_SIGTERM || - s->deserialized_state == SOCKET_STOP_PRE_SIGKILL || - s->deserialized_state == SOCKET_STOP_POST || - s->deserialized_state == SOCKET_FINAL_SIGTERM || - s->deserialized_state == SOCKET_FINAL_SIGKILL) { - - if (s->control_pid <= 0) - return -EBADMSG; - - if ((r = unit_watch_pid(UNIT(s), s->control_pid)) < 0) - return r; - - if ((r = unit_watch_timer(UNIT(s), s->timeout_usec, &s->timer_watch)) < 0) - return r; - } - - if (s->deserialized_state == SOCKET_START_POST || - s->deserialized_state == SOCKET_LISTENING || - s->deserialized_state == SOCKET_RUNNING || - s->deserialized_state == SOCKET_STOP_PRE || - s->deserialized_state == SOCKET_STOP_PRE_SIGTERM || - s->deserialized_state == SOCKET_STOP_PRE_SIGKILL) - if ((r = socket_open_fds(s)) < 0) - return r; - - if (s->deserialized_state == SOCKET_LISTENING) - if ((r = socket_watch_fds(s)) < 0) - return r; - - socket_set_state(s, s->deserialized_state); - } - - return 0; -} - -static int socket_spawn(Socket *s, ExecCommand *c, pid_t *_pid) { - pid_t pid; - int r; - char **argv; - - assert(s); - assert(c); - assert(_pid); - - if ((r = unit_watch_timer(UNIT(s), s->timeout_usec, &s->timer_watch)) < 0) - goto fail; - - if (!(argv = unit_full_printf_strv(UNIT(s), c->argv))) { - r = -ENOMEM; - goto fail; - } - - r = exec_spawn(c, - argv, - &s->exec_context, - NULL, 0, - UNIT(s)->manager->environment, - true, - true, - true, - UNIT(s)->manager->confirm_spawn, - UNIT(s)->cgroup_bondings, - UNIT(s)->cgroup_attributes, - &pid); - - strv_free(argv); - if (r < 0) - goto fail; - - if ((r = unit_watch_pid(UNIT(s), pid)) < 0) - /* FIXME: we need to do something here */ - goto fail; - - *_pid = pid; - - return 0; - -fail: - unit_unwatch_timer(UNIT(s), &s->timer_watch); - - return r; -} - -static void socket_enter_dead(Socket *s, SocketResult f) { - assert(s); - - if (f != SOCKET_SUCCESS) - s->result = f; - - 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; - - if ((s->control_command = s->exec_command[SOCKET_EXEC_STOP_POST])) { - if ((r = socket_spawn(s, s->control_command, &s->control_pid)) < 0) - goto fail; - - socket_set_state(s, SOCKET_STOP_POST); - } else - socket_enter_signal(s, SOCKET_FINAL_SIGTERM, SOCKET_SUCCESS); - - return; - -fail: - log_warning("%s failed to run 'stop-post' task: %s", UNIT(s)->id, strerror(-r)); - socket_enter_signal(s, SOCKET_FINAL_SIGTERM, SOCKET_FAILURE_RESOURCES); -} - -static void socket_enter_signal(Socket *s, SocketState state, SocketResult f) { - int r; - Set *pid_set = NULL; - bool wait_for_exit = false; - - assert(s); - - if (f != SOCKET_SUCCESS) - s->result = f; - - if (s->exec_context.kill_mode != KILL_NONE) { - int sig = (state == SOCKET_STOP_PRE_SIGTERM || state == SOCKET_FINAL_SIGTERM) ? s->exec_context.kill_signal : SIGKILL; - - if (s->control_pid > 0) { - if (kill_and_sigcont(s->control_pid, sig) < 0 && errno != ESRCH) - - log_warning("Failed to kill control process %li: %m", (long) s->control_pid); - else - wait_for_exit = true; - } - - if (s->exec_context.kill_mode == KILL_CONTROL_GROUP) { - - if (!(pid_set = set_new(trivial_hash_func, trivial_compare_func))) { - r = -ENOMEM; - goto fail; - } - - /* Exclude the control pid from being killed via the cgroup */ - if (s->control_pid > 0) - if ((r = set_put(pid_set, LONG_TO_PTR(s->control_pid))) < 0) - goto fail; - - if ((r = cgroup_bonding_kill_list(UNIT(s)->cgroup_bondings, sig, true, pid_set)) < 0) { - if (r != -EAGAIN && r != -ESRCH && r != -ENOENT) - log_warning("Failed to kill control group: %s", strerror(-r)); - } else if (r > 0) - wait_for_exit = true; - - set_free(pid_set); - pid_set = NULL; - } - } - - if (wait_for_exit) { - if ((r = unit_watch_timer(UNIT(s), s->timeout_usec, &s->timer_watch)) < 0) - goto fail; - - socket_set_state(s, state); - } else if (state == SOCKET_STOP_PRE_SIGTERM || state == SOCKET_STOP_PRE_SIGKILL) - socket_enter_stop_post(s, SOCKET_SUCCESS); - else - socket_enter_dead(s, SOCKET_SUCCESS); - - return; - -fail: - log_warning("%s failed to kill processes: %s", UNIT(s)->id, strerror(-r)); - - 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); - - if (pid_set) - set_free(pid_set); -} - -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; - - if ((s->control_command = s->exec_command[SOCKET_EXEC_STOP_PRE])) { - if ((r = socket_spawn(s, s->control_command, &s->control_pid)) < 0) - goto fail; - - socket_set_state(s, SOCKET_STOP_PRE); - } else - socket_enter_stop_post(s, SOCKET_SUCCESS); - - return; - -fail: - log_warning("%s failed to run 'stop-pre' task: %s", UNIT(s)->id, strerror(-r)); - 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_warning("%s failed to watch sockets: %s", UNIT(s)->id, strerror(-r)); - 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); - - r = socket_open_fds(s); - if (r < 0) { - log_warning("%s failed to listen on sockets: %s", UNIT(s)->id, strerror(-r)); - goto fail; - } - - socket_unwatch_control_pid(s); - - s->control_command_id = SOCKET_EXEC_START_POST; - - if ((s->control_command = s->exec_command[SOCKET_EXEC_START_POST])) { - r = socket_spawn(s, s->control_command, &s->control_pid); - if (r < 0) { - log_warning("%s failed to run 'start-post' task: %s", UNIT(s)->id, strerror(-r)); - 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_pre(Socket *s) { - int r; - assert(s); - - socket_unwatch_control_pid(s); - - s->control_command_id = SOCKET_EXEC_START_PRE; - - if ((s->control_command = s->exec_command[SOCKET_EXEC_START_PRE])) { - if ((r = socket_spawn(s, s->control_command, &s->control_pid)) < 0) - goto fail; - - socket_set_state(s, SOCKET_START_PRE); - } else - socket_enter_start_post(s); - - return; - -fail: - log_warning("%s failed to run 'start-pre' task: %s", UNIT(s)->id, strerror(-r)); - socket_enter_dead(s, SOCKET_FAILURE_RESOURCES); -} - -static void socket_enter_running(Socket *s, int cfd) { - int r; - DBusError error; - - assert(s); - dbus_error_init(&error); - - /* We don't take connections anymore if we are supposed to - * shut down anyway */ - if (unit_pending_inactive(UNIT(s))) { - log_debug("Suppressing connection request on %s since unit stop is scheduled.", UNIT(s)->id); - - if (cfd >= 0) - close_nointr_nofail(cfd); - else { - /* Flush all sockets by closing and reopening them */ - socket_close_fds(s); - - r = socket_watch_fds(s); - if (r < 0) { - log_warning("%s failed to watch sockets: %s", UNIT(s)->id, strerror(-r)); - socket_enter_stop_pre(s, SOCKET_FAILURE_RESOURCES); - } - } - - return; - } - - if (cfd < 0) { - Iterator i; - Unit *u; - bool pending = false; - - /* If there's already a start pending don't bother to - * do anything */ - SET_FOREACH(u, UNIT(s)->dependencies[UNIT_TRIGGERS], i) - if (unit_pending_active(u)) { - pending = true; - break; - } - - if (!pending) { - r = manager_add_job(UNIT(s)->manager, JOB_START, UNIT_DEREF(s->service), JOB_REPLACE, true, &error, NULL); - if (r < 0) - goto fail; - } - - socket_set_state(s, SOCKET_RUNNING); - } else { - char *prefix, *instance = NULL, *name; - Service *service; - - if (s->n_connections >= s->max_connections) { - log_warning("Too many incoming connections (%u)", s->n_connections); - close_nointr_nofail(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. */ - close_nointr_nofail(cfd); - return; - } - - prefix = unit_name_to_prefix(UNIT(s)->id); - if (!prefix) { - free(instance); - r = -ENOMEM; - goto fail; - } - - name = unit_name_build(prefix, instance, ".service"); - free(prefix); - free(instance); - - if (!name) { - r = -ENOMEM; - goto fail; - } - - r = unit_add_name(UNIT_DEREF(s->service), name); - if (r < 0) { - free(name); - goto fail; - } - - service = SERVICE(UNIT_DEREF(s->service)); - unit_ref_unset(&s->service); - s->n_accepted ++; - - UNIT(service)->no_gc = false; - - unit_choose_id(UNIT(service), name); - free(name); - - r = service_set_socket_fd(service, cfd, s); - if (r < 0) - goto fail; - - cfd = -1; - s->n_connections ++; - - r = manager_add_job(UNIT(s)->manager, JOB_START, UNIT(service), JOB_REPLACE, true, &error, NULL); - if (r < 0) - goto fail; - - /* Notify clients about changed counters */ - unit_add_to_dbus_queue(UNIT(s)); - } - - return; - -fail: - log_warning("%s failed to queue socket startup job: %s", UNIT(s)->id, bus_error(&error, r)); - socket_enter_stop_pre(s, SOCKET_FAILURE_RESOURCES); - - if (cfd >= 0) - close_nointr_nofail(cfd); - - dbus_error_free(&error); -} - -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; - - if ((r = socket_spawn(s, s->control_command, &s->control_pid)) < 0) - goto fail; - - return; - -fail: - log_warning("%s failed to run next task: %s", UNIT(s)->id, strerror(-r)); - - if (s->state == SOCKET_START_POST) - socket_enter_stop_pre(s, SOCKET_FAILURE_RESOURCES); - else if (s->state == SOCKET_STOP_POST) - socket_enter_dead(s, SOCKET_FAILURE_RESOURCES); - else - socket_enter_signal(s, SOCKET_FINAL_SIGTERM, SOCKET_FAILURE_RESOURCES); -} - -static int socket_start(Unit *u) { - Socket *s = SOCKET(u); - - assert(s); - - /* We cannot fulfill this request right now, try again later - * please! */ - if (s->state == SOCKET_STOP_PRE || - s->state == SOCKET_STOP_PRE_SIGKILL || - s->state == SOCKET_STOP_PRE_SIGTERM || - s->state == SOCKET_STOP_POST || - s->state == SOCKET_FINAL_SIGTERM || - s->state == SOCKET_FINAL_SIGKILL) - return -EAGAIN; - - if (s->state == SOCKET_START_PRE || - s->state == SOCKET_START_POST) - return 0; - - /* Cannot run this without the service being around */ - if (UNIT_DEREF(s->service)) { - Service *service; - - service = SERVICE(UNIT_DEREF(s->service)); - - if (UNIT(service)->load_state != UNIT_LOADED) { - log_error("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_error("Socket service %s already active, refusing.", UNIT(service)->id); - return -EBUSY; - } - -#ifdef HAVE_SYSV_COMPAT - if (service->sysv_path) { - log_error("Using SysV services for socket activation is not supported. Refusing."); - return -ENOENT; - } -#endif - } - - assert(s->state == SOCKET_DEAD || s->state == SOCKET_FAILED); - - s->result = SOCKET_SUCCESS; - socket_enter_start_pre(s); - return 0; -} - -static int socket_stop(Unit *u) { - Socket *s = SOCKET(u); - - assert(s); - - /* Already on it */ - if (s->state == SOCKET_STOP_PRE || - s->state == SOCKET_STOP_PRE_SIGTERM || - s->state == SOCKET_STOP_PRE_SIGKILL || - s->state == SOCKET_STOP_POST || - s->state == SOCKET_FINAL_SIGTERM || - s->state == SOCKET_FINAL_SIGKILL) - return 0; - - /* If there's already something running we go directly into - * kill mode. */ - if (s->state == SOCKET_START_PRE || - s->state == 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 0; -} - -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", "%lu", (unsigned long) 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; - - if ((copy = fdset_put_dup(fds, p->fd)) < 0) - return copy; - - if (p->type == SOCKET_SOCKET) { - char *t; - - if ((r = socket_address_print(&p->address, &t)) < 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); - free(t); - } else if (p->type == SOCKET_SPECIAL) - unit_serialize_item_format(u, f, "special", "%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); - assert(fds); - - if (streq(key, "state")) { - SocketState state; - - if ((state = socket_state_from_string(value)) < 0) - log_debug("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_debug("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_debug("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_debug("Failed to parse control-pid value %s", value); - else - s->control_pid = pid; - } else if (streq(key, "control-command")) { - SocketExecCommand id; - - if ((id = socket_exec_command_from_string(value)) < 0) - log_debug("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_debug("Failed to parse fifo value %s", value); - else { - - LIST_FOREACH(port, p, s->ports) - if (p->type == SOCKET_FIFO && - streq_ptr(p->path, value+skip)) - break; - - if (p) { - if (p->fd >= 0) - close_nointr_nofail(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_debug("Failed to parse special value %s", value); - else { - - LIST_FOREACH(port, p, s->ports) - if (p->type == SOCKET_SPECIAL && - streq_ptr(p->path, value+skip)) - break; - - if (p) { - if (p->fd >= 0) - close_nointr_nofail(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_debug("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) { - if (p->fd >= 0) - close_nointr_nofail(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_debug("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) { - if (p->fd >= 0) - close_nointr_nofail(p->fd); - p->fd = fdset_remove(fds, fd); - } - } - - } else - log_debug("Unknown serialization key '%s'", key); - - return 0; -} - -static UnitActiveState socket_active_state(Unit *u) { - assert(u); - - return state_translation_table[SOCKET(u)->state]; -} - -static const char *socket_sub_state_to_string(Unit *u) { - assert(u); - - return socket_state_to_string(SOCKET(u)->state); -} - -static bool socket_check_gc(Unit *u) { - Socket *s = SOCKET(u); - - assert(u); - - return s->n_connections > 0; -} - -static void socket_fd_event(Unit *u, int fd, uint32_t events, Watch *w) { - Socket *s = SOCKET(u); - int cfd = -1; - - assert(s); - assert(fd >= 0); - - if (s->state != SOCKET_LISTENING) - return; - - log_debug("Incoming traffic on %s", u->id); - - if (events != EPOLLIN) { - - if (events & EPOLLHUP) - log_error("%s: Got POLLHUP on a listening socket. The service probably invoked shutdown() on it, and should better not do that.", u->id); - else - log_error("%s: Got unexpected poll event (0x%x) on socket.", u->id, events); - - goto fail; - } - - if (w->socket_accept) { - for (;;) { - - if ((cfd = accept4(fd, NULL, NULL, SOCK_NONBLOCK)) < 0) { - - if (errno == EINTR) - continue; - - log_error("Failed to accept socket: %m"); - goto fail; - } - - break; - } - - socket_apply_socket_options(s, cfd); - } - - socket_enter_running(s, cfd); - return; - -fail: - socket_enter_stop_pre(s, SOCKET_FAILURE_RESOURCES); -} - -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)) - 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 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_full(f == SOCKET_SUCCESS ? LOG_DEBUG : LOG_NOTICE, - "%s control process exited, code=%s status=%i", u->id, 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_debug("%s running next command for state %s", u->id, 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_debug("%s got final SIGCHLD for state %s", u->id, socket_state_to_string(s->state)); - - switch (s->state) { - - case SOCKET_START_PRE: - if (f == SOCKET_SUCCESS) - socket_enter_start_post(s); - else - socket_enter_signal(s, SOCKET_FINAL_SIGTERM, 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 void socket_timer_event(Unit *u, uint64_t elapsed, Watch *w) { - Socket *s = SOCKET(u); - - assert(s); - assert(elapsed == 1); - assert(w == &s->timer_watch); - - switch (s->state) { - - case SOCKET_START_PRE: - log_warning("%s starting timed out. Terminating.", u->id); - socket_enter_signal(s, SOCKET_FINAL_SIGTERM, SOCKET_FAILURE_TIMEOUT); - break; - - case SOCKET_START_POST: - log_warning("%s starting timed out. Stopping.", u->id); - socket_enter_stop_pre(s, SOCKET_FAILURE_TIMEOUT); - break; - - case SOCKET_STOP_PRE: - log_warning("%s stopping timed out. Terminating.", u->id); - socket_enter_signal(s, SOCKET_STOP_PRE_SIGTERM, SOCKET_FAILURE_TIMEOUT); - break; - - case SOCKET_STOP_PRE_SIGTERM: - if (s->exec_context.send_sigkill) { - log_warning("%s stopping timed out. Killing.", u->id); - socket_enter_signal(s, SOCKET_STOP_PRE_SIGKILL, SOCKET_FAILURE_TIMEOUT); - } else { - log_warning("%s stopping timed out. Skipping SIGKILL. Ignoring.", u->id); - socket_enter_stop_post(s, SOCKET_FAILURE_TIMEOUT); - } - break; - - case SOCKET_STOP_PRE_SIGKILL: - log_warning("%s still around after SIGKILL. Ignoring.", u->id); - socket_enter_stop_post(s, SOCKET_FAILURE_TIMEOUT); - break; - - case SOCKET_STOP_POST: - log_warning("%s stopping timed out (2). Terminating.", u->id); - socket_enter_signal(s, SOCKET_FINAL_SIGTERM, SOCKET_FAILURE_TIMEOUT); - break; - - case SOCKET_FINAL_SIGTERM: - if (s->exec_context.send_sigkill) { - log_warning("%s stopping timed out (2). Killing.", u->id); - socket_enter_signal(s, SOCKET_FINAL_SIGKILL, SOCKET_FAILURE_TIMEOUT); - } else { - log_warning("%s stopping timed out (2). Skipping SIGKILL. Ignoring.", u->id); - socket_enter_dead(s, SOCKET_FAILURE_TIMEOUT); - } - break; - - case SOCKET_FINAL_SIGKILL: - log_warning("%s still around after SIGKILL (2). Entering failed mode.", u->id); - socket_enter_dead(s, SOCKET_FAILURE_TIMEOUT); - break; - - default: - assert_not_reached("Timeout at wrong time."); - } -} - -int socket_collect_fds(Socket *s, int **fds, unsigned *n_fds) { - int *rfds; - unsigned rn_fds, k; - SocketPort *p; - - assert(s); - assert(fds); - assert(n_fds); - - /* Called from the service code for requesting our fds */ - - rn_fds = 0; - LIST_FOREACH(port, p, s->ports) - if (p->fd >= 0) - rn_fds++; - - if (rn_fds <= 0) { - *fds = NULL; - *n_fds = 0; - return 0; - } - - if (!(rfds = new(int, rn_fds))) - return -ENOMEM; - - k = 0; - LIST_FOREACH(port, p, s->ports) - if (p->fd >= 0) - rfds[k++] = p->fd; - - assert(k == rn_fds); - - *fds = rfds; - *n_fds = rn_fds; - - return 0; -} - -void socket_notify_service_dead(Socket *s, bool failed_permanent) { - assert(s); - - /* The service is dead. Dang! - * - * This is strictly for one-instance-for-all-connections - * services. */ - - if (s->state == SOCKET_RUNNING) { - log_debug("%s got notified about service death (failed permanently: %s)", UNIT(s)->id, yes_no(failed_permanent)); - if (failed_permanent) - socket_enter_stop_pre(s, SOCKET_FAILURE_SERVICE_FAILED_PERMANENT); - else - socket_enter_listening(s); - } -} - -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_debug("%s: One connection closed, %u left.", UNIT(s)->id, s->n_connections); -} - -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; -} - -static int socket_kill(Unit *u, KillWho who, KillMode mode, int signo, DBusError *error) { - Socket *s = SOCKET(u); - int r = 0; - Set *pid_set = NULL; - - assert(s); - - if (who == KILL_MAIN) { - dbus_set_error(error, BUS_ERROR_NO_SUCH_PROCESS, "Socket units have no main processes"); - return -ESRCH; - } - - if (s->control_pid <= 0 && who == KILL_CONTROL) { - dbus_set_error(error, BUS_ERROR_NO_SUCH_PROCESS, "No control process to kill"); - return -ESRCH; - } - - if (who == KILL_CONTROL || who == KILL_ALL) - if (s->control_pid > 0) - if (kill(s->control_pid, signo) < 0) - r = -errno; - - if (who == KILL_ALL && mode == KILL_CONTROL_GROUP) { - int q; - - if (!(pid_set = set_new(trivial_hash_func, trivial_compare_func))) - return -ENOMEM; - - /* Exclude the control pid from being killed via the cgroup */ - if (s->control_pid > 0) - if ((q = set_put(pid_set, LONG_TO_PTR(s->control_pid))) < 0) { - r = q; - goto finish; - } - - if ((q = cgroup_bonding_kill_list(UNIT(s)->cgroup_bondings, signo, false, pid_set)) < 0) - if (q != -EAGAIN && q != -ESRCH && q != -ENOENT) - r = q; - } - -finish: - if (pid_set) - set_free(pid_set); - - return r; -} - -static const char* const socket_state_table[_SOCKET_STATE_MAX] = { - [SOCKET_DEAD] = "dead", - [SOCKET_START_PRE] = "start-pre", - [SOCKET_START_POST] = "start-post", - [SOCKET_LISTENING] = "listening", - [SOCKET_RUNNING] = "running", - [SOCKET_STOP_PRE] = "stop-pre", - [SOCKET_STOP_PRE_SIGTERM] = "stop-pre-sigterm", - [SOCKET_STOP_PRE_SIGKILL] = "stop-pre-sigkill", - [SOCKET_STOP_POST] = "stop-post", - [SOCKET_FINAL_SIGTERM] = "final-sigterm", - [SOCKET_FINAL_SIGKILL] = "final-sigkill", - [SOCKET_FAILED] = "failed" -}; - -DEFINE_STRING_TABLE_LOOKUP(socket_state, SocketState); - -static const char* const socket_exec_command_table[_SOCKET_EXEC_COMMAND_MAX] = { - [SOCKET_EXEC_START_PRE] = "StartPre", - [SOCKET_EXEC_START_POST] = "StartPost", - [SOCKET_EXEC_STOP_PRE] = "StopPre", - [SOCKET_EXEC_STOP_POST] = "StopPost" -}; - -DEFINE_STRING_TABLE_LOOKUP(socket_exec_command, SocketExecCommand); - -static const char* const socket_result_table[_SOCKET_RESULT_MAX] = { - [SOCKET_SUCCESS] = "success", - [SOCKET_FAILURE_RESOURCES] = "resources", - [SOCKET_FAILURE_TIMEOUT] = "timeout", - [SOCKET_FAILURE_EXIT_CODE] = "exit-code", - [SOCKET_FAILURE_SIGNAL] = "signal", - [SOCKET_FAILURE_CORE_DUMP] = "core-dump", - [SOCKET_FAILURE_SERVICE_FAILED_PERMANENT] = "service-failed-permanent" -}; - -DEFINE_STRING_TABLE_LOOKUP(socket_result, SocketResult); - -const UnitVTable socket_vtable = { - .suffix = ".socket", - .object_size = sizeof(Socket), - .sections = - "Unit\0" - "Socket\0" - "Install\0", - - .init = socket_init, - .done = socket_done, - .load = socket_load, - - .kill = socket_kill, - - .coldplug = socket_coldplug, - - .dump = socket_dump, - - .start = socket_start, - .stop = socket_stop, - - .serialize = socket_serialize, - .deserialize_item = socket_deserialize_item, - - .active_state = socket_active_state, - .sub_state_to_string = socket_sub_state_to_string, - - .check_gc = socket_check_gc, - - .fd_event = socket_fd_event, - .sigchld_event = socket_sigchld_event, - .timer_event = socket_timer_event, - - .reset_failed = socket_reset_failed, - - .bus_interface = "org.freedesktop.systemd1.Socket", - .bus_message_handler = bus_socket_message_handler, - .bus_invalidating_properties = bus_socket_invalidating_properties -}; diff --git a/src/socket.h b/src/socket.h deleted file mode 100644 index 6470d8b63e..0000000000 --- a/src/socket.h +++ /dev/null @@ -1,173 +0,0 @@ -/*-*- Mode: C; c-basic-offset: 8; indent-tabs-mode: nil -*-*/ - -#ifndef foosockethfoo -#define foosockethfoo - -/*** - This file is part of systemd. - - Copyright 2010 Lennart Poettering - - systemd is free software; you can redistribute it and/or modify it - under the terms of the GNU General Public License as published by - the Free Software Foundation; either version 2 of the License, or - (at your option) any later version. - - systemd is distributed in the hope that 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 General Public License - along with systemd; If not, see . -***/ - -typedef struct Socket Socket; - -#include "manager.h" -#include "unit.h" -#include "socket-util.h" -#include "mount.h" -#include "service.h" - -typedef enum SocketState { - SOCKET_DEAD, - SOCKET_START_PRE, - SOCKET_START_POST, - SOCKET_LISTENING, - SOCKET_RUNNING, - SOCKET_STOP_PRE, - SOCKET_STOP_PRE_SIGTERM, - SOCKET_STOP_PRE_SIGKILL, - SOCKET_STOP_POST, - SOCKET_FINAL_SIGTERM, - SOCKET_FINAL_SIGKILL, - SOCKET_FAILED, - _SOCKET_STATE_MAX, - _SOCKET_STATE_INVALID = -1 -} SocketState; - -typedef enum SocketExecCommand { - SOCKET_EXEC_START_PRE, - 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_FIFO_MAX, - _SOCKET_FIFO_INVALID = -1 -} SocketType; - -typedef enum SocketResult { - SOCKET_SUCCESS, - SOCKET_FAILURE_RESOURCES, - SOCKET_FAILURE_TIMEOUT, - SOCKET_FAILURE_EXIT_CODE, - SOCKET_FAILURE_SIGNAL, - SOCKET_FAILURE_CORE_DUMP, - SOCKET_FAILURE_SERVICE_FAILED_PERMANENT, - _SOCKET_RESULT_MAX, - _SOCKET_RESULT_INVALID = -1 -} SocketResult; - -typedef struct SocketPort { - SocketType type; - int fd; - - SocketAddress address; - char *path; - Watch fd_watch; - - 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; - usec_t timeout_usec; - - ExecCommand* exec_command[_SOCKET_EXEC_COMMAND_MAX]; - ExecContext exec_context; - - /* 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; - - Watch timer_watch; - - ExecCommand* control_command; - SocketExecCommand control_command_id; - pid_t control_pid; - - mode_t directory_mode; - mode_t socket_mode; - - SocketResult result; - - bool accept; - - /* Socket options */ - bool keep_alive; - bool free_bind; - bool transparent; - bool broadcast; - bool pass_cred; - bool pass_sec; - 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; - long mq_maxmsg; - long mq_msgsize; - - /* Only for INET6 sockets: issue IPV6_V6ONLY sockopt */ - SocketAddressBindIPv6Only bind_ipv6_only; -}; - -/* Called from the service code when collecting fds */ -int socket_collect_fds(Socket *s, int **fds, unsigned *n_fds); - -/* Called from the service when it shut down */ -void socket_notify_service_dead(Socket *s, bool failed_permanent); - -/* Called from the mount code figure out if a mount is a dependency of - * any of the sockets of this socket */ -int socket_add_one_mount_link(Socket *s, Mount *m); - -/* Called from the service code when a per-connection service ended */ -void socket_connection_unref(Socket *s); - -extern const UnitVTable socket_vtable; - -const char* socket_state_to_string(SocketState i); -SocketState socket_state_from_string(const char *s); - -const char* socket_exec_command_to_string(SocketExecCommand i); -SocketExecCommand socket_exec_command_from_string(const char *s); - -const char* socket_result_to_string(SocketResult i); -SocketResult socket_result_from_string(const char *s); - -#endif diff --git a/src/spawn-agent.h b/src/spawn-agent.h deleted file mode 100644 index fd0a9109b8..0000000000 --- a/src/spawn-agent.h +++ /dev/null @@ -1,28 +0,0 @@ -/*-*- Mode: C; c-basic-offset: 8; indent-tabs-mode: nil -*-*/ - -#ifndef foospawnagenthfoo -#define foospawnagenthfoo - -/*** - This file is part of systemd. - - Copyright 2011 Lennart Poettering - - systemd is free software; you can redistribute it and/or modify it - under the terms of the GNU General Public License as published by - the Free Software Foundation; either version 2 of the License, or - (at your option) any later version. - - systemd is distributed in the hope that 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 General Public License - along with systemd; If not, see . -***/ - -void agent_open(void); -void agent_close(void); - -#endif diff --git a/src/special.h b/src/special.h deleted file mode 100644 index 43e2e6f6d7..0000000000 --- a/src/special.h +++ /dev/null @@ -1,88 +0,0 @@ -/*-*- Mode: C; c-basic-offset: 8; indent-tabs-mode: nil -*-*/ - -#ifndef foospecialhfoo -#define foospecialhfoo - -/*** - This file is part of systemd. - - Copyright 2010 Lennart Poettering - - systemd is free software; you can redistribute it and/or modify it - under the terms of the GNU General Public License as published by - the Free Software Foundation; either version 2 of the License, or - (at your option) any later version. - - systemd is distributed in the hope that 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 General Public License - along with systemd; If not, see . -***/ - -#define SPECIAL_DEFAULT_TARGET "default.target" - -/* Shutdown targets */ -#define SPECIAL_UMOUNT_TARGET "umount.target" -/* This is not really intended to be started by directly. This is - * mostly so that other targets (reboot/halt/poweroff) can depend on - * it to bring all services down that want to be brought down on - * system shutdown. */ -#define SPECIAL_SHUTDOWN_TARGET "shutdown.target" -#define SPECIAL_HALT_TARGET "halt.target" -#define SPECIAL_POWEROFF_TARGET "poweroff.target" -#define SPECIAL_REBOOT_TARGET "reboot.target" -#define SPECIAL_KEXEC_TARGET "kexec.target" -#define SPECIAL_EXIT_TARGET "exit.target" - -/* Special boot targets */ -#define SPECIAL_RESCUE_TARGET "rescue.target" -#define SPECIAL_EMERGENCY_TARGET "emergency.target" - -/* Early boot targets */ -#define SPECIAL_SYSINIT_TARGET "sysinit.target" -#define SPECIAL_SOCKETS_TARGET "sockets.target" -#define SPECIAL_LOCAL_FS_TARGET "local-fs.target" /* LSB's $local_fs */ -#define SPECIAL_LOCAL_FS_PRE_TARGET "local-fs-pre.target" -#define SPECIAL_REMOTE_FS_TARGET "remote-fs.target" /* LSB's $remote_fs */ -#define SPECIAL_REMOTE_FS_PRE_TARGET "remote-fs-pre.target" -#define SPECIAL_SWAP_TARGET "swap.target" -#define SPECIAL_BASIC_TARGET "basic.target" - -/* LSB compatibility */ -#define SPECIAL_NETWORK_TARGET "network.target" /* LSB's $network */ -#define SPECIAL_NSS_LOOKUP_TARGET "nss-lookup.target" /* LSB's $named */ -#define SPECIAL_RPCBIND_TARGET "rpcbind.target" /* LSB's $portmap */ -#define SPECIAL_SYSLOG_TARGET "syslog.target" /* LSB's $syslog */ -#define SPECIAL_TIME_SYNC_TARGET "time-sync.target" /* LSB's $time */ -#define SPECIAL_DISPLAY_MANAGER_SERVICE "display-manager.service" /* Debian's $x-display-manager */ -#define SPECIAL_MAIL_TRANSFER_AGENT_TARGET "mail-transfer-agent.target" /* Debian's $mail-{transport|transfer-agent */ -#define SPECIAL_HTTP_DAEMON_TARGET "http-daemon.target" - -/* Magic early boot services */ -#define SPECIAL_FSCK_SERVICE "fsck@.service" -#define SPECIAL_QUOTACHECK_SERVICE "quotacheck.service" -#define SPECIAL_QUOTAON_SERVICE "quotaon.service" -#define SPECIAL_REMOUNT_ROOTFS_SERVICE "remount-rootfs.service" - -/* Services systemd relies on */ -#define SPECIAL_DBUS_SERVICE "dbus.service" -#define SPECIAL_DBUS_SOCKET "dbus.socket" -#define SPECIAL_JOURNALD_SOCKET "systemd-journald.socket" -#define SPECIAL_JOURNALD_SERVICE "systemd-journald.service" - -/* Magic init signals */ -#define SPECIAL_KBREQUEST_TARGET "kbrequest.target" -#define SPECIAL_SIGPWR_TARGET "sigpwr.target" -#define SPECIAL_CTRL_ALT_DEL_TARGET "ctrl-alt-del.target" - -/* For SysV compatibility. Usually an alias for a saner target. On - * SysV-free systems this doesn't exist. */ -#define SPECIAL_RUNLEVEL2_TARGET "runlevel2.target" -#define SPECIAL_RUNLEVEL3_TARGET "runlevel3.target" -#define SPECIAL_RUNLEVEL4_TARGET "runlevel4.target" -#define SPECIAL_RUNLEVEL5_TARGET "runlevel5.target" - -#endif diff --git a/src/swap.c b/src/swap.c deleted file mode 100644 index 9c72732b94..0000000000 --- a/src/swap.c +++ /dev/null @@ -1,1415 +0,0 @@ -/*-*- Mode: C; c-basic-offset: 8; indent-tabs-mode: nil -*-*/ - -/*** - This file is part of systemd. - - Copyright 2010 Lennart Poettering - - systemd is free software; you can redistribute it and/or modify it - under the terms of the GNU General Public License as published by - the Free Software Foundation; either version 2 of the License, or - (at your option) any later version. - - systemd is distributed in the hope that 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 General Public License - along with systemd; If not, see . -***/ - -#include -#include -#include -#include -#include -#include -#include -#include - -#include "unit.h" -#include "swap.h" -#include "load-fragment.h" -#include "load-dropin.h" -#include "unit-name.h" -#include "dbus-swap.h" -#include "special.h" -#include "bus-errors.h" -#include "exit-status.h" -#include "def.h" - -static const UnitActiveState state_translation_table[_SWAP_STATE_MAX] = { - [SWAP_DEAD] = UNIT_INACTIVE, - [SWAP_ACTIVATING] = UNIT_ACTIVATING, - [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 void swap_unset_proc_swaps(Swap *s) { - Swap *first; - - assert(s); - - if (!s->parameters_proc_swaps.what) - return; - - /* Remove this unit from the chain of swaps which share the - * same kernel swap device. */ - - first = hashmap_get(UNIT(s)->manager->swaps_by_proc_swaps, s->parameters_proc_swaps.what); - LIST_REMOVE(Swap, same_proc_swaps, first, s); - - if (first) - hashmap_remove_and_replace(UNIT(s)->manager->swaps_by_proc_swaps, s->parameters_proc_swaps.what, first->parameters_proc_swaps.what, first); - else - hashmap_remove(UNIT(s)->manager->swaps_by_proc_swaps, s->parameters_proc_swaps.what); - - free(s->parameters_proc_swaps.what); - s->parameters_proc_swaps.what = NULL; -} - -static void swap_init(Unit *u) { - Swap *s = SWAP(u); - - assert(s); - assert(UNIT(s)->load_state == UNIT_STUB); - - s->timeout_usec = DEFAULT_TIMEOUT_USEC; - - exec_context_init(&s->exec_context); - s->exec_context.std_output = u->manager->default_std_output; - s->exec_context.std_error = u->manager->default_std_error; - - s->parameters_etc_fstab.priority = s->parameters_proc_swaps.priority = s->parameters_fragment.priority = -1; - - s->timer_watch.type = WATCH_INVALID; - - s->control_command_id = _SWAP_EXEC_COMMAND_INVALID; - - UNIT(s)->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); - - free(s->what); - s->what = NULL; - - free(s->parameters_etc_fstab.what); - free(s->parameters_fragment.what); - s->parameters_etc_fstab.what = s->parameters_fragment.what = NULL; - - exec_context_done(&s->exec_context); - exec_command_done_array(s->exec_command, _SWAP_EXEC_COMMAND_MAX); - s->control_command = NULL; - - swap_unwatch_control_pid(s); - - unit_unwatch_timer(u, &s->timer_watch); -} - -int swap_add_one_mount_link(Swap *s, Mount *m) { - int r; - - assert(s); - assert(m); - - if (UNIT(s)->load_state != UNIT_LOADED || - UNIT(m)->load_state != UNIT_LOADED) - return 0; - - if (is_device_path(s->what)) - return 0; - - if (!path_startswith(s->what, m->where)) - return 0; - - if ((r = unit_add_two_dependencies(UNIT(s), UNIT_AFTER, UNIT_REQUIRES, UNIT(m), true)) < 0) - return r; - - return 0; -} - -static int swap_add_mount_links(Swap *s) { - Unit *other; - int r; - - assert(s); - - LIST_FOREACH(units_by_type, other, UNIT(s)->manager->units_by_type[UNIT_MOUNT]) - if ((r = swap_add_one_mount_link(s, MOUNT(other))) < 0) - return r; - - return 0; -} - -static int swap_add_target_links(Swap *s) { - Unit *tu; - SwapParameters *p; - int r; - - assert(s); - - if (s->from_fragment) - p = &s->parameters_fragment; - else if (s->from_etc_fstab) - p = &s->parameters_etc_fstab; - else - return 0; - - if ((r = manager_load_unit(UNIT(s)->manager, SPECIAL_SWAP_TARGET, NULL, NULL, &tu)) < 0) - return r; - - if (!p->noauto && - !p->nofail && - (p->handle || UNIT(s)->manager->swap_auto) && - s->from_etc_fstab && - UNIT(s)->manager->running_as == MANAGER_SYSTEM) - if ((r = unit_add_dependency(tu, UNIT_WANTS, UNIT(s), true)) < 0) - return r; - - return unit_add_dependency(UNIT(s), UNIT_BEFORE, tu, true); -} - -static int swap_add_device_links(Swap *s) { - SwapParameters *p; - - assert(s); - - if (!s->what) - return 0; - - if (s->from_fragment) - p = &s->parameters_fragment; - else if (s->from_etc_fstab) - p = &s->parameters_etc_fstab; - else - return 0; - - if (is_device_path(s->what)) - return unit_add_node_link(UNIT(s), s->what, - !p->noauto && p->nofail && - UNIT(s)->manager->running_as == MANAGER_SYSTEM); - else - /* File based swap devices need to be ordered after - * remount-rootfs.service, since they might need a - * writable file system. */ - return unit_add_dependency_by_name(UNIT(s), UNIT_AFTER, SPECIAL_REMOUNT_ROOTFS_SERVICE, NULL, true); -} - -static int swap_add_default_dependencies(Swap *s) { - int r; - - assert(s); - - if (UNIT(s)->manager->running_as == MANAGER_SYSTEM) { - - if ((r = unit_add_two_dependencies_by_name(UNIT(s), UNIT_BEFORE, UNIT_CONFLICTS, SPECIAL_UMOUNT_TARGET, NULL, true)) < 0) - return r; - } - - return 0; -} - -static int swap_verify(Swap *s) { - bool b; - char *e; - - if (UNIT(s)->load_state != UNIT_LOADED) - return 0; - - if (!(e = unit_name_from_path(s->what, ".swap"))) - return -ENOMEM; - - b = unit_has_name(UNIT(s), e); - free(e); - - if (!b) { - log_error("%s: Value of \"What\" and unit name do not match, not loading.\n", UNIT(s)->id); - return -EINVAL; - } - - if (s->exec_context.pam_name && s->exec_context.kill_mode != KILL_CONTROL_GROUP) { - log_error("%s has PAM enabled. Kill mode must be set to 'control-group'. Refusing.", UNIT(s)->id); - return -EINVAL; - } - - return 0; -} - -static int swap_load(Unit *u) { - int r; - Swap *s = SWAP(u); - - assert(s); - assert(u->load_state == UNIT_STUB); - - /* Load a .swap file */ - if ((r = unit_load_fragment_and_dropin_optional(u)) < 0) - return r; - - if (u->load_state == UNIT_LOADED) { - if ((r = unit_add_exec_dependencies(u, &s->exec_context)) < 0) - return r; - - 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_etc_fstab.what) - s->what = strdup(s->parameters_etc_fstab.what); - else if (s->parameters_proc_swaps.what) - s->what = strdup(s->parameters_proc_swaps.what); - else - s->what = unit_name_to_path(u->id); - - if (!s->what) - return -ENOMEM; - } - - path_kill_slashes(s->what); - - if (!UNIT(s)->description) - if ((r = unit_set_description(u, s->what)) < 0) - return r; - - if ((r = swap_add_device_links(s)) < 0) - return r; - - if ((r = swap_add_mount_links(s)) < 0) - return r; - - if ((r = swap_add_target_links(s)) < 0) - return r; - - if ((r = unit_add_default_cgroups(u)) < 0) - return r; - - if (UNIT(s)->default_dependencies) - if ((r = swap_add_default_dependencies(s)) < 0) - return r; - } - - return swap_verify(s); -} - -int swap_add_one( - Manager *m, - const char *what, - const char *what_proc_swaps, - int priority, - bool noauto, - bool nofail, - bool handle, - bool set_flags) { - - Unit *u = NULL; - char *e = NULL, *wp = NULL; - bool delete = false; - int r; - SwapParameters *p; - - assert(m); - assert(what); - - e = unit_name_from_path(what, ".swap"); - if (!e) - return -ENOMEM; - - u = manager_get_unit(m, e); - - if (what_proc_swaps && - u && - SWAP(u)->from_proc_swaps && - !path_equal(SWAP(u)->parameters_proc_swaps.what, what_proc_swaps)) - return -EEXIST; - - if (!u) { - delete = true; - - u = unit_new(m, sizeof(Swap)); - if (!u) { - free(e); - return -ENOMEM; - } - - 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; - - if (what_proc_swaps) { - Swap *first; - - p = &SWAP(u)->parameters_proc_swaps; - - if (!p->what) { - if (!(wp = strdup(what_proc_swaps))) { - r = -ENOMEM; - goto fail; - } - - if (!m->swaps_by_proc_swaps) - if (!(m->swaps_by_proc_swaps = hashmap_new(string_hash_func, string_compare_func))) { - r = -ENOMEM; - goto fail; - } - - free(p->what); - p->what = wp; - - first = hashmap_get(m->swaps_by_proc_swaps, wp); - LIST_PREPEND(Swap, same_proc_swaps, first, SWAP(u)); - - if ((r = hashmap_replace(m->swaps_by_proc_swaps, wp, first)) < 0) - 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; - - } else { - p = &SWAP(u)->parameters_etc_fstab; - - if (!(wp = strdup(what))) { - r = -ENOMEM; - goto fail; - } - - free(p->what); - p->what = wp; - - SWAP(u)->from_etc_fstab = true; - } - - p->priority = priority; - p->noauto = noauto; - p->nofail = nofail; - p->handle = handle; - - unit_add_to_dbus_queue(u); - - free(e); - - return 0; - -fail: - log_warning("Failed to load swap unit: %s", strerror(-r)); - - free(wp); - free(e); - - if (delete && u) - unit_free(u); - - return r; -} - -static int swap_process_new_swap(Manager *m, const char *device, int prio, bool set_flags) { - struct stat st; - int r = 0, k; - - assert(m); - - if (stat(device, &st) >= 0 && S_ISBLK(st.st_mode)) { - struct udev_device *d; - const char *dn; - struct udev_list_entry *item = NULL, *first = NULL; - - /* So this is a proper swap device. Create swap units - * for all names this swap device is known under */ - - if (!(d = udev_device_new_from_devnum(m->udev, 'b', st.st_rdev))) - return -ENOMEM; - - if ((dn = udev_device_get_devnode(d))) - r = swap_add_one(m, dn, device, prio, false, false, false, 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 (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; - - if ((k = swap_add_one(m, p, device, prio, false, false, false, set_flags)) < 0) - r = k; - } - - udev_device_unref(d); - } - - if ((k = swap_add_one(m, device, device, prio, false, false, false, set_flags)) < 0) - r = k; - - return r; -} - -static void swap_set_state(Swap *s, SwapState state) { - SwapState old_state; - - assert(s); - - old_state = s->state; - s->state = state; - - if (state != SWAP_ACTIVATING && - state != SWAP_ACTIVATING_SIGTERM && - state != SWAP_ACTIVATING_SIGKILL && - state != SWAP_DEACTIVATING && - state != SWAP_DEACTIVATING_SIGTERM && - state != SWAP_DEACTIVATING_SIGKILL) { - unit_unwatch_timer(UNIT(s), &s->timer_watch); - swap_unwatch_control_pid(s); - s->control_command = NULL; - s->control_command_id = _SWAP_EXEC_COMMAND_INVALID; - } - - if (state != old_state) - log_debug("%s changed %s -> %s", - UNIT(s)->id, - 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); -} - -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) { - - if (new_state == SWAP_ACTIVATING || - new_state == SWAP_ACTIVATING_SIGTERM || - new_state == SWAP_ACTIVATING_SIGKILL || - new_state == SWAP_DEACTIVATING || - new_state == SWAP_DEACTIVATING_SIGTERM || - new_state == SWAP_DEACTIVATING_SIGKILL) { - - if (s->control_pid <= 0) - return -EBADMSG; - - if ((r = unit_watch_pid(UNIT(s), s->control_pid)) < 0) - return r; - - if ((r = unit_watch_timer(UNIT(s), s->timeout_usec, &s->timer_watch)) < 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 = &s->parameters_etc_fstab; - - fprintf(f, - "%sSwap State: %s\n" - "%sResult: %s\n" - "%sWhat: %s\n" - "%sPriority: %i\n" - "%sNoAuto: %s\n" - "%sNoFail: %s\n" - "%sHandle: %s\n" - "%sFrom /etc/fstab: %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, p->priority, - prefix, yes_no(p->noauto), - prefix, yes_no(p->nofail), - prefix, yes_no(p->handle), - prefix, yes_no(s->from_etc_fstab), - prefix, yes_no(s->from_proc_swaps), - prefix, yes_no(s->from_fragment)); - - if (s->control_pid > 0) - fprintf(f, - "%sControl PID: %lu\n", - prefix, (unsigned long) s->control_pid); - - exec_context_dump(&s->exec_context, f, prefix); -} - -static int swap_spawn(Swap *s, ExecCommand *c, pid_t *_pid) { - pid_t pid; - int r; - - assert(s); - assert(c); - assert(_pid); - - if ((r = unit_watch_timer(UNIT(s), s->timeout_usec, &s->timer_watch)) < 0) - goto fail; - - if ((r = exec_spawn(c, - NULL, - &s->exec_context, - NULL, 0, - UNIT(s)->manager->environment, - true, - true, - true, - UNIT(s)->manager->confirm_spawn, - UNIT(s)->cgroup_bondings, - UNIT(s)->cgroup_attributes, - &pid)) < 0) - goto fail; - - if ((r = unit_watch_pid(UNIT(s), pid)) < 0) - /* FIXME: we need to do something here */ - goto fail; - - *_pid = pid; - - return 0; - -fail: - unit_unwatch_timer(UNIT(s), &s->timer_watch); - - return r; -} - -static void swap_enter_dead(Swap *s, SwapResult f) { - assert(s); - - if (f != SWAP_SUCCESS) - s->result = f; - - 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; - Set *pid_set = NULL; - bool wait_for_exit = false; - - assert(s); - - if (f != SWAP_SUCCESS) - s->result = f; - - if (s->exec_context.kill_mode != KILL_NONE) { - int sig = (state == SWAP_ACTIVATING_SIGTERM || - state == SWAP_DEACTIVATING_SIGTERM) ? s->exec_context.kill_signal : SIGKILL; - - if (s->control_pid > 0) { - if (kill_and_sigcont(s->control_pid, sig) < 0 && errno != ESRCH) - - log_warning("Failed to kill control process %li: %m", (long) s->control_pid); - else - wait_for_exit = true; - } - - if (s->exec_context.kill_mode == KILL_CONTROL_GROUP) { - - if (!(pid_set = set_new(trivial_hash_func, trivial_compare_func))) { - r = -ENOMEM; - goto fail; - } - - /* Exclude the control pid from being killed via the cgroup */ - if (s->control_pid > 0) - if ((r = set_put(pid_set, LONG_TO_PTR(s->control_pid))) < 0) - goto fail; - - if ((r = cgroup_bonding_kill_list(UNIT(s)->cgroup_bondings, sig, true, pid_set)) < 0) { - if (r != -EAGAIN && r != -ESRCH && r != -ENOENT) - log_warning("Failed to kill control group: %s", strerror(-r)); - } else if (r > 0) - wait_for_exit = true; - - set_free(pid_set); - pid_set = NULL; - } - } - - if (wait_for_exit) { - if ((r = unit_watch_timer(UNIT(s), s->timeout_usec, &s->timer_watch)) < 0) - goto fail; - - swap_set_state(s, state); - } else - swap_enter_dead(s, SWAP_SUCCESS); - - return; - -fail: - log_warning("%s failed to kill processes: %s", UNIT(s)->id, strerror(-r)); - - swap_enter_dead(s, SWAP_FAILURE_RESOURCES); - - if (pid_set) - set_free(pid_set); -} - -static void swap_enter_activating(Swap *s) { - int r, priority; - - assert(s); - - s->control_command_id = SWAP_EXEC_ACTIVATE; - s->control_command = s->exec_command + SWAP_EXEC_ACTIVATE; - - if (s->from_fragment) - priority = s->parameters_fragment.priority; - else if (s->from_etc_fstab) - priority = s->parameters_etc_fstab.priority; - else - priority = -1; - - if (priority >= 0) { - char p[LINE_MAX]; - - snprintf(p, sizeof(p), "%i", priority); - char_array_0(p); - - r = exec_command_set( - s->control_command, - "/sbin/swapon", - "-p", - p, - s->what, - NULL); - } else - r = exec_command_set( - s->control_command, - "/sbin/swapon", - s->what, - NULL); - - if (r < 0) - goto fail; - - swap_unwatch_control_pid(s); - - if ((r = swap_spawn(s, s->control_command, &s->control_pid)) < 0) - goto fail; - - swap_set_state(s, SWAP_ACTIVATING); - - return; - -fail: - log_warning("%s failed to run 'swapon' task: %s", UNIT(s)->id, strerror(-r)); - 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; - - if ((r = exec_command_set( - s->control_command, - "/sbin/swapoff", - s->what, - NULL)) < 0) - goto fail; - - swap_unwatch_control_pid(s); - - if ((r = swap_spawn(s, s->control_command, &s->control_pid)) < 0) - goto fail; - - swap_set_state(s, SWAP_DEACTIVATING); - - return; - -fail: - log_warning("%s failed to run 'swapoff' task: %s", UNIT(s)->id, strerror(-r)); - swap_enter_active(s, SWAP_FAILURE_RESOURCES); -} - -static int swap_start(Unit *u) { - Swap *s = SWAP(u); - - 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); - - s->result = SWAP_SUCCESS; - swap_enter_activating(s); - return 0; -} - -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_ACTIVE); - - swap_enter_deactivating(s); - return 0; -} - -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", "%lu", (unsigned long) 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; - - if ((state = swap_state_from_string(value)) < 0) - log_debug("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_debug("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_debug("Failed to parse control-pid value %s", value); - else - s->control_pid = pid; - - } else if (streq(key, "control-command")) { - SwapExecCommand id; - - if ((id = swap_exec_command_from_string(value)) < 0) - log_debug("Failed to parse exec-command value %s", value); - else { - s->control_command_id = id; - s->control_command = s->exec_command + id; - } - - } else - log_debug("Unknown serialization key '%s'", key); - - return 0; -} - -static UnitActiveState swap_active_state(Unit *u) { - assert(u); - - return state_translation_table[SWAP(u)->state]; -} - -static const char *swap_sub_state_to_string(Unit *u) { - assert(u); - - return swap_state_to_string(SWAP(u)->state); -} - -static bool swap_check_gc(Unit *u) { - Swap *s = SWAP(u); - - assert(s); - - return s->from_etc_fstab || 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)) - 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_full(f == SWAP_SUCCESS ? LOG_DEBUG : LOG_NOTICE, - "%s swap process exited, code=%s status=%i", u->id, sigchld_code_to_string(code), status); - - switch (s->state) { - - case SWAP_ACTIVATING: - 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: - - if (f == SWAP_SUCCESS) - swap_enter_dead(s, f); - else - 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); - - /* Request a reload of /proc/swaps, so that following units - * can follow our state change */ - u->manager->request_reload = true; -} - -static void swap_timer_event(Unit *u, uint64_t elapsed, Watch *w) { - Swap *s = SWAP(u); - - assert(s); - assert(elapsed == 1); - assert(w == &s->timer_watch); - - switch (s->state) { - - case SWAP_ACTIVATING: - log_warning("%s activation timed out. Stopping.", u->id); - swap_enter_signal(s, SWAP_ACTIVATING_SIGTERM, SWAP_FAILURE_TIMEOUT); - break; - - case SWAP_DEACTIVATING: - log_warning("%s deactivation timed out. Stopping.", u->id); - swap_enter_signal(s, SWAP_DEACTIVATING_SIGTERM, SWAP_FAILURE_TIMEOUT); - break; - - case SWAP_ACTIVATING_SIGTERM: - if (s->exec_context.send_sigkill) { - log_warning("%s activation timed out. Killing.", u->id); - swap_enter_signal(s, SWAP_ACTIVATING_SIGKILL, SWAP_FAILURE_TIMEOUT); - } else { - log_warning("%s activation timed out. Skipping SIGKILL. Ignoring.", u->id); - swap_enter_dead(s, SWAP_FAILURE_TIMEOUT); - } - break; - - case SWAP_DEACTIVATING_SIGTERM: - if (s->exec_context.send_sigkill) { - log_warning("%s deactivation timed out. Killing.", u->id); - swap_enter_signal(s, SWAP_DEACTIVATING_SIGKILL, SWAP_FAILURE_TIMEOUT); - } else { - log_warning("%s deactivation timed out. Skipping SIGKILL. Ignoring.", u->id); - swap_enter_dead(s, SWAP_FAILURE_TIMEOUT); - } - break; - - case SWAP_ACTIVATING_SIGKILL: - case SWAP_DEACTIVATING_SIGKILL: - log_warning("%s swap process still around after SIGKILL. Ignoring.", u->id); - swap_enter_dead(s, SWAP_FAILURE_TIMEOUT); - break; - - default: - assert_not_reached("Timeout at wrong time."); - } -} - -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++) { - char *dev = NULL, *d; - int prio = 0, k; - - if ((k = fscanf(m->proc_swaps, - "%ms " /* device/file */ - "%*s " /* type of swap */ - "%*s " /* swap size */ - "%*s " /* used */ - "%i\n", /* priority */ - &dev, &prio)) != 2) { - - if (k == EOF) - break; - - log_warning("Failed to parse /proc/swaps:%u.", i); - free(dev); - continue; - } - - d = cunescape(dev); - free(dev); - - if (!d) - return -ENOMEM; - - k = swap_process_new_swap(m, d, prio, set_flags); - free(d); - - if (k < 0) - r = k; - } - - return r; -} - -int swap_dispatch_reload(Manager *m) { - /* This function should go as soon as the kernel properly notifies us */ - - if (_likely_(!m->request_reload)) - return 0; - - m->request_reload = false; - - return swap_fd_event(m, EPOLLPRI); -} - -int swap_fd_event(Manager *m, int events) { - Unit *u; - int r; - - assert(m); - assert(events & EPOLLPRI); - - if ((r = swap_load_proc_swaps(m, true)) < 0) { - log_error("Failed to reread /proc/swaps: %s", strerror(-r)); - - /* 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->from_proc_swaps = false; - swap_unset_proc_swaps(swap); - - switch (swap->state) { - - case SWAP_ACTIVE: - swap_enter_dead(swap, SWAP_SUCCESS); - break; - - default: - swap_set_state(swap, swap->state); - break; - } - - } else if (swap->just_activated) { - - /* New swap entry */ - - switch (swap->state) { - - case SWAP_DEAD: - case SWAP_FAILED: - swap_enter_active(swap, SWAP_SUCCESS); - 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 (streq_ptr(s->what, s->parameters_proc_swaps.what)) - return NULL; - - /* Make everybody follow the unit that's named after the swap - * device in the kernel */ - - LIST_FOREACH_AFTER(same_proc_swaps, other, s) - if (streq_ptr(other->what, other->parameters_proc_swaps.what)) - return UNIT(other); - - LIST_FOREACH_BEFORE(same_proc_swaps, other, s) { - if (streq_ptr(other->what, other->parameters_proc_swaps.what)) - return UNIT(other); - - first = other; - } - - return UNIT(first); -} - -static int swap_following_set(Unit *u, Set **_set) { - Swap *s = SWAP(u); - Swap *other; - Set *set; - int r; - - assert(s); - assert(_set); - - if (LIST_JUST_US(same_proc_swaps, s)) { - *_set = NULL; - return 0; - } - - if (!(set = set_new(NULL, NULL))) - return -ENOMEM; - - LIST_FOREACH_AFTER(same_proc_swaps, other, s) - if ((r = set_put(set, other)) < 0) - goto fail; - - LIST_FOREACH_BEFORE(same_proc_swaps, other, s) - if ((r = set_put(set, other)) < 0) - goto fail; - - *_set = set; - return 1; - -fail: - set_free(set); - return r; -} - -static void swap_shutdown(Manager *m) { - assert(m); - - if (m->proc_swaps) { - fclose(m->proc_swaps); - m->proc_swaps = NULL; - } - - hashmap_free(m->swaps_by_proc_swaps); - m->swaps_by_proc_swaps = NULL; -} - -static int swap_enumerate(Manager *m) { - int r; - struct epoll_event ev; - assert(m); - - if (!m->proc_swaps) { - if (!(m->proc_swaps = fopen("/proc/swaps", "re"))) - return (errno == ENOENT) ? 0 : -errno; - - m->swap_watch.type = WATCH_SWAP; - m->swap_watch.fd = fileno(m->proc_swaps); - - zero(ev); - ev.events = EPOLLPRI; - ev.data.ptr = &m->swap_watch; - - if (epoll_ctl(m->epoll_fd, EPOLL_CTL_ADD, m->swap_watch.fd, &ev) < 0) - return -errno; - } - - /* We rely on mount.c to load /etc/fstab for us */ - - if ((r = swap_load_proc_swaps(m, false)) < 0) - swap_shutdown(m); - - 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, KillMode mode, int signo, DBusError *error) { - Swap *s = SWAP(u); - int r = 0; - Set *pid_set = NULL; - - assert(s); - - if (who == KILL_MAIN) { - dbus_set_error(error, BUS_ERROR_NO_SUCH_PROCESS, "Swap units have no main processes"); - return -ESRCH; - } - - if (s->control_pid <= 0 && who == KILL_CONTROL) { - dbus_set_error(error, BUS_ERROR_NO_SUCH_PROCESS, "No control process to kill"); - return -ESRCH; - } - - if (who == KILL_CONTROL || who == KILL_ALL) - if (s->control_pid > 0) - if (kill(s->control_pid, signo) < 0) - r = -errno; - - if (who == KILL_ALL && mode == KILL_CONTROL_GROUP) { - int q; - - if (!(pid_set = set_new(trivial_hash_func, trivial_compare_func))) - return -ENOMEM; - - /* Exclude the control pid from being killed via the cgroup */ - if (s->control_pid > 0) - if ((q = set_put(pid_set, LONG_TO_PTR(s->control_pid))) < 0) { - r = q; - goto finish; - } - - if ((q = cgroup_bonding_kill_list(UNIT(s)->cgroup_bondings, signo, false, pid_set)) < 0) - if (q != -EAGAIN && q != -ESRCH && q != -ENOENT) - r = q; - } - -finish: - if (pid_set) - set_free(pid_set); - - return r; -} - -static const char* const swap_state_table[_SWAP_STATE_MAX] = { - [SWAP_DEAD] = "dead", - [SWAP_ACTIVATING] = "activating", - [SWAP_ACTIVE] = "active", - [SWAP_DEACTIVATING] = "deactivating", - [SWAP_ACTIVATING_SIGTERM] = "activating-sigterm", - [SWAP_ACTIVATING_SIGKILL] = "activating-sigkill", - [SWAP_DEACTIVATING_SIGTERM] = "deactivating-sigterm", - [SWAP_DEACTIVATING_SIGKILL] = "deactivating-sigkill", - [SWAP_FAILED] = "failed" -}; - -DEFINE_STRING_TABLE_LOOKUP(swap_state, SwapState); - -static const char* const swap_exec_command_table[_SWAP_EXEC_COMMAND_MAX] = { - [SWAP_EXEC_ACTIVATE] = "ExecActivate", - [SWAP_EXEC_DEACTIVATE] = "ExecDeactivate", -}; - -DEFINE_STRING_TABLE_LOOKUP(swap_exec_command, SwapExecCommand); - -static const char* const swap_result_table[_SWAP_RESULT_MAX] = { - [SWAP_SUCCESS] = "success", - [SWAP_FAILURE_RESOURCES] = "resources", - [SWAP_FAILURE_TIMEOUT] = "timeout", - [SWAP_FAILURE_EXIT_CODE] = "exit-code", - [SWAP_FAILURE_SIGNAL] = "signal", - [SWAP_FAILURE_CORE_DUMP] = "core-dump" -}; - -DEFINE_STRING_TABLE_LOOKUP(swap_result, SwapResult); - -const UnitVTable swap_vtable = { - .suffix = ".swap", - .object_size = sizeof(Swap), - .sections = - "Unit\0" - "Swap\0" - "Install\0", - - .no_alias = true, - .no_instances = true, - .show_status = true, - - .init = swap_init, - .load = swap_load, - .done = swap_done, - - .coldplug = swap_coldplug, - - .dump = swap_dump, - - .start = swap_start, - .stop = swap_stop, - - .kill = swap_kill, - - .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, - .timer_event = swap_timer_event, - - .reset_failed = swap_reset_failed, - - .bus_interface = "org.freedesktop.systemd1.Swap", - .bus_message_handler = bus_swap_message_handler, - .bus_invalidating_properties = bus_swap_invalidating_properties, - - .following = swap_following, - .following_set = swap_following_set, - - .enumerate = swap_enumerate, - .shutdown = swap_shutdown -}; diff --git a/src/swap.h b/src/swap.h deleted file mode 100644 index 62d08da30b..0000000000 --- a/src/swap.h +++ /dev/null @@ -1,128 +0,0 @@ -/*-*- Mode: C; c-basic-offset: 8; indent-tabs-mode: nil -*-*/ - -#ifndef fooswaphfoo -#define fooswaphfoo - -/*** - 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 General Public License as published by - the Free Software Foundation; either version 2 of the License, or - (at your option) any later version. - - systemd is distributed in the hope that 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 General Public License - along with systemd; If not, see . -***/ - -typedef struct Swap Swap; - -#include "unit.h" - -typedef enum SwapState { - SWAP_DEAD, - SWAP_ACTIVATING, - SWAP_ACTIVE, - SWAP_DEACTIVATING, - SWAP_ACTIVATING_SIGTERM, - SWAP_ACTIVATING_SIGKILL, - SWAP_DEACTIVATING_SIGTERM, - SWAP_DEACTIVATING_SIGKILL, - SWAP_FAILED, - _SWAP_STATE_MAX, - _SWAP_STATE_INVALID = -1 -} SwapState; - -typedef enum SwapExecCommand { - SWAP_EXEC_ACTIVATE, - SWAP_EXEC_DEACTIVATE, - _SWAP_EXEC_COMMAND_MAX, - _SWAP_EXEC_COMMAND_INVALID = -1 -} SwapExecCommand; - -typedef struct SwapParameters { - char *what; - int priority; - bool noauto:1; - bool nofail:1; - bool handle:1; -} SwapParameters; - -typedef enum SwapResult { - SWAP_SUCCESS, - SWAP_FAILURE_RESOURCES, - SWAP_FAILURE_TIMEOUT, - SWAP_FAILURE_EXIT_CODE, - SWAP_FAILURE_SIGNAL, - SWAP_FAILURE_CORE_DUMP, - _SWAP_RESULT_MAX, - _SWAP_RESULT_INVALID = -1 -} SwapResult; - -struct Swap { - Unit meta; - - char *what; - - SwapParameters parameters_etc_fstab; - SwapParameters parameters_proc_swaps; - SwapParameters parameters_fragment; - - bool from_etc_fstab:1; - 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; - - SwapResult result; - - usec_t timeout_usec; - - ExecCommand exec_command[_SWAP_EXEC_COMMAND_MAX]; - ExecContext exec_context; - - SwapState state, deserialized_state; - - ExecCommand* control_command; - SwapExecCommand control_command_id; - pid_t control_pid; - - Watch timer_watch; - - /* 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_proc_swaps); -}; - -extern const UnitVTable swap_vtable; - -int swap_add_one(Manager *m, const char *what, const char *what_proc_swaps, int prio, bool no_auto, bool no_fail, bool handle, bool set_flags); - -int swap_add_one_mount_link(Swap *s, Mount *m); - -int swap_dispatch_reload(Manager *m); -int swap_fd_event(Manager *m, int events); - -const char* swap_state_to_string(SwapState i); -SwapState swap_state_from_string(const char *s); - -const char* swap_exec_command_to_string(SwapExecCommand i); -SwapExecCommand swap_exec_command_from_string(const char *s); - -const char* swap_result_to_string(SwapResult i); -SwapResult swap_result_from_string(const char *s); - -#endif diff --git a/src/sysfs-show.h b/src/sysfs-show.h deleted file mode 100644 index 9939e8b069..0000000000 --- a/src/sysfs-show.h +++ /dev/null @@ -1,27 +0,0 @@ -/*-*- Mode: C; c-basic-offset: 8; indent-tabs-mode: nil -*-*/ - -#ifndef foosysfsshowhfoo -#define foosysfsshowhfoo - -/*** - This file is part of systemd. - - Copyright 2011 Lennart Poettering - - systemd is free software; you can redistribute it and/or modify it - under the terms of the GNU General Public License as published by - the Free Software Foundation; either version 2 of the License, or - (at your option) any later version. - - systemd is distributed in the hope that 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 General Public License - along with systemd; If not, see . -***/ - -int show_sysfs(const char *seat, const char *prefix, unsigned columns); - -#endif diff --git a/src/target.c b/src/target.c deleted file mode 100644 index 6c1e0c368a..0000000000 --- a/src/target.c +++ /dev/null @@ -1,224 +0,0 @@ -/*-*- Mode: C; c-basic-offset: 8; indent-tabs-mode: nil -*-*/ - -/*** - This file is part of systemd. - - Copyright 2010 Lennart Poettering - - systemd is free software; you can redistribute it and/or modify it - under the terms of the GNU General Public License as published by - the Free Software Foundation; either version 2 of the License, or - (at your option) any later version. - - systemd is distributed in the hope that 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 General Public License - along with systemd; If not, see . -***/ - -#include -#include -#include - -#include "unit.h" -#include "target.h" -#include "load-fragment.h" -#include "log.h" -#include "dbus-target.h" -#include "special.h" -#include "unit-name.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_REQUIRES_OVERRIDABLE, - UNIT_REQUISITE, - UNIT_REQUISITE_OVERRIDABLE, - UNIT_WANTS, - UNIT_BIND_TO - }; - - 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) - if ((r = unit_add_default_target_dependency(other, UNIT(t))) < 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); - - if ((r = unit_load_fragment_and_dropin(u)) < 0) - return r; - - /* This is a new unit? Then let's add in some extras */ - if (u->load_state == UNIT_LOADED) { - if (u->default_dependencies) - if ((r = target_add_default_dependencies(t)) < 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 0; -} - -static int target_stop(Unit *u) { - Target *t = TARGET(u); - - assert(t); - assert(t->state == TARGET_ACTIVE); - - target_set_state(t, TARGET_DEAD); - return 0; -} - -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; - - if ((state = target_state_from_string(value)) < 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; -} - -static UnitActiveState target_active_state(Unit *u) { - assert(u); - - return state_translation_table[TARGET(u)->state]; -} - -static const char *target_sub_state_to_string(Unit *u) { - assert(u); - - return target_state_to_string(TARGET(u)->state); -} - -static const char* const target_state_table[_TARGET_STATE_MAX] = { - [TARGET_DEAD] = "dead", - [TARGET_ACTIVE] = "active" -}; - -DEFINE_STRING_TABLE_LOOKUP(target_state, TargetState); - -const UnitVTable target_vtable = { - .suffix = ".target", - .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_interface = "org.freedesktop.systemd1.Target", - .bus_message_handler = bus_target_message_handler -}; diff --git a/src/target.h b/src/target.h deleted file mode 100644 index 5b97c86fbf..0000000000 --- a/src/target.h +++ /dev/null @@ -1,47 +0,0 @@ -/*-*- Mode: C; c-basic-offset: 8; indent-tabs-mode: nil -*-*/ - -#ifndef footargethfoo -#define footargethfoo - -/*** - This file is part of systemd. - - Copyright 2010 Lennart Poettering - - systemd is free software; you can redistribute it and/or modify it - under the terms of the GNU General Public License as published by - the Free Software Foundation; either version 2 of the License, or - (at your option) any later version. - - systemd is distributed in the hope that 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 General Public License - along with systemd; If not, see . -***/ - -typedef struct Target Target; - -#include "unit.h" - -typedef enum TargetState { - TARGET_DEAD, - TARGET_ACTIVE, - _TARGET_STATE_MAX, - _TARGET_STATE_INVALID = -1 -} TargetState; - -struct Target { - Unit meta; - - TargetState state, deserialized_state; -}; - -extern const UnitVTable target_vtable; - -const char* target_state_to_string(TargetState i); -TargetState target_state_from_string(const char *s); - -#endif diff --git a/src/tcpwrap.c b/src/tcpwrap.c deleted file mode 100644 index 0aab1427dd..0000000000 --- a/src/tcpwrap.c +++ /dev/null @@ -1,68 +0,0 @@ -/*-*- Mode: C; c-basic-offset: 8; indent-tabs-mode: nil -*-*/ - -/*** - This file is part of systemd. - - Copyright 2010 Lennart Poettering - - systemd is free software; you can redistribute it and/or modify it - under the terms of the GNU General Public License as published by - the Free Software Foundation; either version 2 of the License, or - (at your option) any later version. - - systemd is distributed in the hope that 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 General Public License - along with systemd; If not, see . -***/ - -#include -#include -#include -#include - -#ifdef HAVE_LIBWRAP -#include -#endif - -#include "tcpwrap.h" -#include "log.h" - -bool socket_tcpwrap(int fd, const char *name) { -#ifdef HAVE_LIBWRAP - struct request_info req; - union { - struct sockaddr sa; - struct sockaddr_in in; - struct sockaddr_in6 in6; - struct sockaddr_un un; - struct sockaddr_storage storage; - } sa_union; - socklen_t l = sizeof(sa_union); - - if (getsockname(fd, &sa_union.sa, &l) < 0) - return true; - - if (sa_union.sa.sa_family != AF_INET && - sa_union.sa.sa_family != AF_INET6) - return true; - - request_init(&req, - RQ_DAEMON, name, - RQ_FILE, fd, - NULL); - - fromhost(&req); - - if (!hosts_access(&req)) { - log_warning("Connection refused by tcpwrap."); - return false; - } - - log_debug("Connection accepted by tcpwrap."); -#endif - return true; -} diff --git a/src/tcpwrap.h b/src/tcpwrap.h deleted file mode 100644 index 4d4553e8ae..0000000000 --- a/src/tcpwrap.h +++ /dev/null @@ -1,29 +0,0 @@ -/*-*- Mode: C; c-basic-offset: 8; indent-tabs-mode: nil -*-*/ - -#ifndef foolibwraphfoo -#define foolibwraphfoo - -/*** - This file is part of systemd. - - Copyright 2010 Lennart Poettering - - systemd is free software; you can redistribute it and/or modify it - under the terms of the GNU General Public License as published by - the Free Software Foundation; either version 2 of the License, or - (at your option) any later version. - - systemd is distributed in the hope that 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 General Public License - along with systemd; If not, see . -***/ - -#include - -bool socket_tcpwrap(int fd, const char *name); - -#endif diff --git a/src/timer.c b/src/timer.c deleted file mode 100644 index e318fee201..0000000000 --- a/src/timer.c +++ /dev/null @@ -1,520 +0,0 @@ -/*-*- Mode: C; c-basic-offset: 8; indent-tabs-mode: nil -*-*/ - -/*** - This file is part of systemd. - - Copyright 2010 Lennart Poettering - - systemd is free software; you can redistribute it and/or modify it - under the terms of the GNU General Public License as published by - the Free Software Foundation; either version 2 of the License, or - (at your option) any later version. - - systemd is distributed in the hope that 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 General Public License - along with systemd; If not, see . -***/ - -#include - -#include "unit.h" -#include "unit-name.h" -#include "timer.h" -#include "dbus-timer.h" -#include "special.h" -#include "bus-errors.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 void timer_init(Unit *u) { - Timer *t = TIMER(u); - - assert(u); - assert(u->load_state == UNIT_STUB); - - t->next_elapse = (usec_t) -1; -} - -static void timer_done(Unit *u) { - Timer *t = TIMER(u); - TimerValue *v; - - assert(t); - - while ((v = t->values)) { - LIST_REMOVE(TimerValue, value, t->values, v); - free(v); - } - - unit_unwatch_timer(u, &t->timer_watch); - - unit_ref_unset(&t->unit); -} - -static int timer_verify(Timer *t) { - assert(t); - - if (UNIT(t)->load_state != UNIT_LOADED) - return 0; - - if (!t->values) { - log_error("%s lacks value setting. Refusing.", UNIT(t)->id); - return -EINVAL; - } - - return 0; -} - -static int timer_add_default_dependencies(Timer *t) { - int r; - - assert(t); - - if (UNIT(t)->manager->running_as == MANAGER_SYSTEM) { - if ((r = unit_add_dependency_by_name(UNIT(t), UNIT_BEFORE, SPECIAL_BASIC_TARGET, NULL, true)) < 0) - return r; - - if ((r = unit_add_two_dependencies_by_name(UNIT(t), UNIT_AFTER, UNIT_REQUIRES, SPECIAL_SYSINIT_TARGET, NULL, true)) < 0) - return r; - } - - return unit_add_two_dependencies_by_name(UNIT(t), UNIT_BEFORE, UNIT_CONFLICTS, SPECIAL_SHUTDOWN_TARGET, NULL, true); -} - -static int timer_load(Unit *u) { - Timer *t = TIMER(u); - int r; - - assert(u); - assert(u->load_state == UNIT_STUB); - - if ((r = unit_load_fragment_and_dropin(u)) < 0) - return r; - - if (u->load_state == UNIT_LOADED) { - - if (!UNIT_DEREF(t->unit)) { - Unit *x; - - r = unit_load_related_unit(u, ".service", &x); - if (r < 0) - return r; - - unit_ref_set(&t->unit, x); - } - - r = unit_add_two_dependencies(u, UNIT_BEFORE, UNIT_TRIGGERS, UNIT_DEREF(t->unit), true); - if (r < 0) - return r; - - if (UNIT(t)->default_dependencies) - if ((r = timer_add_default_dependencies(t)) < 0) - return r; - } - - return timer_verify(t); -} - -static void timer_dump(Unit *u, FILE *f, const char *prefix) { - Timer *t = TIMER(u); - TimerValue *v; - char - timespan1[FORMAT_TIMESPAN_MAX]; - - fprintf(f, - "%sTimer State: %s\n" - "%sResult: %s\n" - "%sUnit: %s\n", - prefix, timer_state_to_string(t->state), - prefix, timer_result_to_string(t->result), - prefix, UNIT_DEREF(t->unit)->id); - - LIST_FOREACH(value, v, t->values) - fprintf(f, - "%s%s: %s\n", - prefix, - timer_base_to_string(v->base), - strna(format_timespan(timespan1, sizeof(timespan1), v->value))); -} - -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) - unit_unwatch_timer(UNIT(t), &t->timer_watch); - - if (state != old_state) - log_debug("%s changed %s -> %s", - UNIT(t)->id, - 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) { - - 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_waiting(Timer *t, bool initial) { - TimerValue *v; - usec_t base = 0, delay, n; - bool found = false; - int r; - - n = now(CLOCK_MONOTONIC); - - LIST_FOREACH(value, v, t->values) { - - if (v->disabled) - continue; - - switch (v->base) { - - case TIMER_ACTIVE: - if (state_translation_table[t->state] == UNIT_ACTIVE) - base = UNIT(t)->inactive_exit_timestamp.monotonic; - else - base = n; - break; - - case TIMER_BOOT: - /* CLOCK_MONOTONIC equals the uptime on Linux */ - base = 0; - break; - - case TIMER_STARTUP: - base = UNIT(t)->manager->startup_timestamp.monotonic; - break; - - case TIMER_UNIT_ACTIVE: - - if (UNIT_DEREF(t->unit)->inactive_exit_timestamp.monotonic <= 0) - continue; - - base = UNIT_DEREF(t->unit)->inactive_exit_timestamp.monotonic; - break; - - case TIMER_UNIT_INACTIVE: - - if (UNIT_DEREF(t->unit)->inactive_enter_timestamp.monotonic <= 0) - continue; - - base = UNIT_DEREF(t->unit)->inactive_enter_timestamp.monotonic; - break; - - default: - assert_not_reached("Unknown timer base"); - } - - v->next_elapse = base + v->value; - - if (!initial && v->next_elapse < n) { - v->disabled = true; - continue; - } - - if (!found) - t->next_elapse = v->next_elapse; - else - t->next_elapse = MIN(t->next_elapse, v->next_elapse); - - found = true; - } - - if (!found) { - timer_set_state(t, TIMER_ELAPSED); - return; - } - - delay = n < t->next_elapse ? t->next_elapse - n : 0; - - if ((r = unit_watch_timer(UNIT(t), delay, &t->timer_watch)) < 0) - goto fail; - - timer_set_state(t, TIMER_WAITING); - return; - -fail: - log_warning("%s failed to enter waiting state: %s", UNIT(t)->id, strerror(-r)); - timer_enter_dead(t, TIMER_FAILURE_RESOURCES); -} - -static void timer_enter_running(Timer *t) { - DBusError error; - int r; - - assert(t); - dbus_error_init(&error); - - /* Don't start job if we are supposed to go down */ - if (UNIT(t)->job && UNIT(t)->job->type == JOB_STOP) - return; - - if ((r = manager_add_job(UNIT(t)->manager, JOB_START, UNIT_DEREF(t->unit), JOB_REPLACE, true, &error, NULL)) < 0) - goto fail; - - timer_set_state(t, TIMER_RUNNING); - return; - -fail: - log_warning("%s failed to queue unit startup job: %s", UNIT(t)->id, bus_error(&error, r)); - timer_enter_dead(t, TIMER_FAILURE_RESOURCES); - - dbus_error_free(&error); -} - -static int timer_start(Unit *u) { - Timer *t = TIMER(u); - - assert(t); - assert(t->state == TIMER_DEAD || t->state == TIMER_FAILED); - - if (UNIT_DEREF(t->unit)->load_state != UNIT_LOADED) - return -ENOENT; - - t->result = TIMER_SUCCESS; - timer_enter_waiting(t, true); - return 0; -} - -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 0; -} - -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)); - - return 0; -} - -static int timer_deserialize_item(Unit *u, const char *key, const char *value, FDSet *fds) { - Timer *t = TIMER(u); - - assert(u); - assert(key); - assert(value); - assert(fds); - - if (streq(key, "state")) { - TimerState state; - - if ((state = timer_state_from_string(value)) < 0) - log_debug("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_debug("Failed to parse result value %s", value); - else if (f != TIMER_SUCCESS) - t->result = f; - - } else - log_debug("Unknown serialization key '%s'", key); - - return 0; -} - -static UnitActiveState timer_active_state(Unit *u) { - assert(u); - - return state_translation_table[TIMER(u)->state]; -} - -static const char *timer_sub_state_to_string(Unit *u) { - assert(u); - - return timer_state_to_string(TIMER(u)->state); -} - -static void timer_timer_event(Unit *u, uint64_t elapsed, Watch *w) { - Timer *t = TIMER(u); - - assert(t); - assert(elapsed == 1); - - if (t->state != TIMER_WAITING) - return; - - log_debug("Timer elapsed on %s", u->id); - timer_enter_running(t); -} - -void timer_unit_notify(Unit *u, UnitActiveState new_state) { - Iterator i; - Unit *k; - - if (u->type == UNIT_TIMER) - return; - - SET_FOREACH(k, u->dependencies[UNIT_TRIGGERED_BY], i) { - Timer *t; - TimerValue *v; - - if (k->type != UNIT_TIMER) - continue; - - if (k->load_state != UNIT_LOADED) - continue; - - t = TIMER(k); - - /* 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(new_state)) { - log_debug("%s got notified about unit deactivation.", UNIT(t)->id); - 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 const char* const timer_state_table[_TIMER_STATE_MAX] = { - [TIMER_DEAD] = "dead", - [TIMER_WAITING] = "waiting", - [TIMER_RUNNING] = "running", - [TIMER_ELAPSED] = "elapsed", - [TIMER_FAILED] = "failed" -}; - -DEFINE_STRING_TABLE_LOOKUP(timer_state, TimerState); - -static const char* const timer_base_table[_TIMER_BASE_MAX] = { - [TIMER_ACTIVE] = "OnActiveSec", - [TIMER_BOOT] = "OnBootSec", - [TIMER_STARTUP] = "OnStartupSec", - [TIMER_UNIT_ACTIVE] = "OnUnitActiveSec", - [TIMER_UNIT_INACTIVE] = "OnUnitInactiveSec" -}; - -DEFINE_STRING_TABLE_LOOKUP(timer_base, TimerBase); - -static const char* const timer_result_table[_TIMER_RESULT_MAX] = { - [TIMER_SUCCESS] = "success", - [TIMER_FAILURE_RESOURCES] = "resources" -}; - -DEFINE_STRING_TABLE_LOOKUP(timer_result, TimerResult); - -const UnitVTable timer_vtable = { - .suffix = ".timer", - .object_size = sizeof(Timer), - .sections = - "Unit\0" - "Timer\0" - "Install\0", - - .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, - - .timer_event = timer_timer_event, - - .reset_failed = timer_reset_failed, - - .bus_interface = "org.freedesktop.systemd1.Timer", - .bus_message_handler = bus_timer_message_handler, - .bus_invalidating_properties = bus_timer_invalidating_properties -}; diff --git a/src/timer.h b/src/timer.h deleted file mode 100644 index f5c5c64f25..0000000000 --- a/src/timer.h +++ /dev/null @@ -1,93 +0,0 @@ -/*-*- Mode: C; c-basic-offset: 8; indent-tabs-mode: nil -*-*/ - -#ifndef footimerhfoo -#define footimerhfoo - -/*** - This file is part of systemd. - - Copyright 2010 Lennart Poettering - - systemd is free software; you can redistribute it and/or modify it - under the terms of the GNU General Public License as published by - the Free Software Foundation; either version 2 of the License, or - (at your option) any later version. - - systemd is distributed in the hope that 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 General Public License - along with systemd; If not, see . -***/ - -typedef struct Timer Timer; - -#include "unit.h" - -typedef enum TimerState { - TIMER_DEAD, - TIMER_WAITING, - TIMER_RUNNING, - TIMER_ELAPSED, - TIMER_FAILED, - _TIMER_STATE_MAX, - _TIMER_STATE_INVALID = -1 -} TimerState; - -typedef enum TimerBase { - TIMER_ACTIVE, - TIMER_BOOT, - TIMER_STARTUP, - TIMER_UNIT_ACTIVE, - TIMER_UNIT_INACTIVE, - _TIMER_BASE_MAX, - _TIMER_BASE_INVALID = -1 -} TimerBase; - -typedef struct TimerValue { - usec_t value; - usec_t next_elapse; - - LIST_FIELDS(struct TimerValue, value); - - TimerBase base; - bool disabled; -} TimerValue; - -typedef enum TimerResult { - TIMER_SUCCESS, - TIMER_FAILURE_RESOURCES, - _TIMER_RESULT_MAX, - _TIMER_RESULT_INVALID = -1 -} TimerResult; - -struct Timer { - Unit meta; - - LIST_HEAD(TimerValue, values); - usec_t next_elapse; - - TimerState state, deserialized_state; - UnitRef unit; - - Watch timer_watch; - - TimerResult result; -}; - -void timer_unit_notify(Unit *u, UnitActiveState new_state); - -extern const UnitVTable timer_vtable; - -const char *timer_state_to_string(TimerState i); -TimerState timer_state_from_string(const char *s); - -const char *timer_base_to_string(TimerBase i); -TimerBase timer_base_from_string(const char *s); - -const char* timer_result_to_string(TimerResult i); -TimerResult timer_result_from_string(const char *s); - -#endif diff --git a/src/unit.c b/src/unit.c deleted file mode 100644 index 9e33701c8f..0000000000 --- a/src/unit.c +++ /dev/null @@ -1,2676 +0,0 @@ -/*-*- Mode: C; c-basic-offset: 8; indent-tabs-mode: nil -*-*/ - -/*** - This file is part of systemd. - - Copyright 2010 Lennart Poettering - - systemd is free software; you can redistribute it and/or modify it - under the terms of the GNU General Public License as published by - the Free Software Foundation; either version 2 of the License, or - (at your option) any later version. - - systemd is distributed in the hope that 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 General Public License - along with systemd; If not, see . -***/ - -#include -#include -#include -#include -#include -#include -#include -#include -#include - -#include "set.h" -#include "unit.h" -#include "macro.h" -#include "strv.h" -#include "load-fragment.h" -#include "load-dropin.h" -#include "log.h" -#include "unit-name.h" -#include "specifier.h" -#include "dbus-unit.h" -#include "special.h" -#include "cgroup-util.h" -#include "missing.h" -#include "cgroup-attr.h" - -const UnitVTable * const unit_vtable[_UNIT_TYPE_MAX] = { - [UNIT_SERVICE] = &service_vtable, - [UNIT_TIMER] = &timer_vtable, - [UNIT_SOCKET] = &socket_vtable, - [UNIT_TARGET] = &target_vtable, - [UNIT_DEVICE] = &device_vtable, - [UNIT_MOUNT] = &mount_vtable, - [UNIT_AUTOMOUNT] = &automount_vtable, - [UNIT_SNAPSHOT] = &snapshot_vtable, - [UNIT_SWAP] = &swap_vtable, - [UNIT_PATH] = &path_vtable -}; - -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_func, string_compare_func); - if (!u->names) { - free(u); - return NULL; - } - - u->manager = m; - u->type = _UNIT_TYPE_INVALID; - u->deserialized_job = _JOB_TYPE_INVALID; - u->default_dependencies = true; - u->unit_file_state = _UNIT_FILE_STATE_INVALID; - - return u; -} - -bool unit_has_name(Unit *u, const char *name) { - assert(u); - assert(name); - - return !!set_get(u->names, (char*) name); -} - -int unit_add_name(Unit *u, const char *text) { - UnitType t; - char *s, *i = NULL; - int r; - - assert(u); - assert(text); - - if (unit_name_is_template(text)) { - if (!u->instance) - return -EINVAL; - - s = unit_name_replace_instance(text, u->instance); - } else - s = strdup(text); - - if (!s) - return -ENOMEM; - - if (!unit_name_is_valid(s, false)) { - r = -EINVAL; - goto fail; - } - - assert_se((t = unit_name_to_type(s)) >= 0); - - if (u->type != _UNIT_TYPE_INVALID && t != u->type) { - r = -EINVAL; - goto fail; - } - - if ((r = unit_name_to_instance(s, &i)) < 0) - goto fail; - - if (i && unit_vtable[t]->no_instances) { - r = -EINVAL; - goto fail; - } - - /* Ensure that this unit is either instanced or not instanced, - * but not both. */ - if (u->type != _UNIT_TYPE_INVALID && !u->instance != !i) { - r = -EINVAL; - goto fail; - } - - if (unit_vtable[t]->no_alias && - !set_isempty(u->names) && - !set_get(u->names, s)) { - r = -EEXIST; - goto fail; - } - - if (hashmap_size(u->manager->units) >= MANAGER_MAX_NAMES) { - r = -E2BIG; - goto fail; - } - - if ((r = set_put(u->names, s)) < 0) { - if (r == -EEXIST) - r = 0; - goto fail; - } - - if ((r = hashmap_put(u->manager->units, s, u)) < 0) { - set_remove(u->names, s); - goto fail; - } - - if (u->type == _UNIT_TYPE_INVALID) { - - u->type = t; - u->id = s; - u->instance = i; - - LIST_PREPEND(Unit, units_by_type, u->manager->units_by_type[t], u); - - if (UNIT_VTABLE(u)->init) - UNIT_VTABLE(u)->init(u); - } else - free(i); - - unit_add_to_dbus_queue(u); - return 0; - -fail: - free(s); - free(i); - - return r; -} - -int unit_choose_id(Unit *u, const char *name) { - char *s, *t = NULL, *i; - int r; - - assert(u); - assert(name); - - if (unit_name_is_template(name)) { - - if (!u->instance) - return -EINVAL; - - if (!(t = unit_name_replace_instance(name, u->instance))) - return -ENOMEM; - - name = t; - } - - /* Selects one of the names of this unit as the id */ - s = set_get(u->names, (char*) name); - free(t); - - if (!s) - return -ENOENT; - - if ((r = unit_name_to_instance(s, &i)) < 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 (!(s = strdup(description))) - return -ENOMEM; - - free(u->description); - u->description = s; - - unit_add_to_dbus_queue(u); - return 0; -} - -bool unit_check_gc(Unit *u) { - assert(u); - - if (u->load_state == UNIT_STUB) - return true; - - if (UNIT_VTABLE(u)->no_gc) - return true; - - if (u->no_gc) - return true; - - if (u->job) - return true; - - if (unit_active_state(u) != UNIT_INACTIVE) - 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(Unit, 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(Unit, 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(Unit, gc_queue, u->manager->gc_queue, u); - u->in_gc_queue = true; - - u->manager->n_in_gc_queue ++; - - if (u->manager->gc_queue_timestamp <= 0) - u->manager->gc_queue_timestamp = now(CLOCK_MONOTONIC); -} - -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 (!bus_has_subscriber(u->manager)) { - u->sent_dbus_new_signal = true; - return; - } - - LIST_PREPEND(Unit, 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); -} - -void unit_free(Unit *u) { - UnitDependency d; - Iterator i; - char *t; - - assert(u); - - bus_unit_send_removed_signal(u); - - if (u->load_state != UNIT_STUB) - if (UNIT_VTABLE(u)->done) - UNIT_VTABLE(u)->done(u); - - SET_FOREACH(t, u->names, i) - hashmap_remove_value(u->manager->units, t, u); - - if (u->job) - job_free(u->job); - - for (d = 0; d < _UNIT_DEPENDENCY_MAX; d++) - bidi_set_free(u, u->dependencies[d]); - - if (u->type != _UNIT_TYPE_INVALID) - LIST_REMOVE(Unit, units_by_type, u->manager->units_by_type[u->type], u); - - if (u->in_load_queue) - LIST_REMOVE(Unit, load_queue, u->manager->load_queue, u); - - if (u->in_dbus_queue) - LIST_REMOVE(Unit, dbus_queue, u->manager->dbus_unit_queue, u); - - if (u->in_cleanup_queue) - LIST_REMOVE(Unit, cleanup_queue, u->manager->cleanup_queue, u); - - if (u->in_gc_queue) { - LIST_REMOVE(Unit, gc_queue, u->manager->gc_queue, u); - u->manager->n_in_gc_queue--; - } - - cgroup_bonding_free_list(u->cgroup_bondings, u->manager->n_reloading <= 0); - cgroup_attribute_free_list(u->cgroup_attributes); - - free(u->description); - free(u->fragment_path); - free(u->instance); - - set_free_free(u->names); - - condition_free_list(u->conditions); - - 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 void complete_move(Set **s, Set **other) { - assert(s); - assert(other); - - if (!*other) - return; - - if (*s) - set_move(*s, *other); - else { - *s = *other; - *other = NULL; - } -} - -static void merge_names(Unit *u, Unit *other) { - char *t; - Iterator i; - - assert(u); - assert(other); - - complete_move(&u->names, &other->names); - - 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); -} - -static void merge_dependencies(Unit *u, Unit *other, 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++) - if ((r = set_remove_and_put(back->dependencies[k], other, u)) < 0) { - - if (r == -EEXIST) - set_remove(back->dependencies[k], other); - else - assert(r == -ENOENT); - } - } - - complete_move(&u->dependencies[d], &other->dependencies[d]); - - set_free(other->dependencies[d]); - other->dependencies[d] = NULL; -} - -int unit_merge(Unit *u, Unit *other) { - UnitDependency d; - - assert(u); - assert(other); - assert(u->manager == other->manager); - assert(u->type != _UNIT_TYPE_INVALID); - - other = unit_follow_merge(other); - - if (other == u) - return 0; - - if (u->type != other->type) - return -EINVAL; - - if (!u->instance != !other->instance) - return -EINVAL; - - if (other->load_state != UNIT_STUB && - other->load_state != UNIT_ERROR) - return -EEXIST; - - if (other->job) - return -EEXIST; - - if (!UNIT_IS_INACTIVE_OR_FAILED(unit_active_state(other))) - return -EEXIST; - - /* Merge names */ - merge_names(u, other); - - /* 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, d); - - other->load_state = UNIT_MERGED; - other->merged_into = u; - - /* If there is still some data attached to the other node, we - * don't need it anymore, and can free it. */ - if (other->load_state != UNIT_STUB) - if (UNIT_VTABLE(other)->done) - UNIT_VTABLE(other)->done(other); - - unit_add_to_dbus_queue(u); - unit_add_to_cleanup_queue(other); - - return 0; -} - -int unit_merge_by_name(Unit *u, const char *name) { - Unit *other; - int r; - char *s = NULL; - - assert(u); - assert(name); - - if (unit_name_is_template(name)) { - if (!u->instance) - return -EINVAL; - - if (!(s = unit_name_replace_instance(name, u->instance))) - return -ENOMEM; - - name = s; - } - - if (!(other = manager_get_unit(u->manager, name))) - r = unit_add_name(u, name); - else - r = unit_merge(u, other); - - free(s); - return r; -} - -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->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. */ - - if (u->manager->running_as == MANAGER_SYSTEM) - if ((r = unit_add_two_dependencies_by_name(u, UNIT_REQUIRES, UNIT_AFTER, SPECIAL_JOURNALD_SOCKET, NULL, true)) < 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; - UnitDependency d; - Iterator i; - char *p2; - const char *prefix2; - char - timestamp1[FORMAT_TIMESTAMP_MAX], - timestamp2[FORMAT_TIMESTAMP_MAX], - timestamp3[FORMAT_TIMESTAMP_MAX], - timestamp4[FORMAT_TIMESTAMP_MAX], - timespan[FORMAT_TIMESPAN_MAX]; - Unit *following; - - assert(u); - assert(u->type >= 0); - - if (!prefix) - prefix = ""; - p2 = strappend(prefix, "\t"); - prefix2 = p2 ? p2 : prefix; - - 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\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", - 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(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))); - - SET_FOREACH(t, u->names, i) - fprintf(f, "%s\tName: %s\n", prefix, t); - - if ((following = unit_following(u))) - fprintf(f, "%s\tFollowing: %s\n", prefix, following->id); - - if (u->fragment_path) - fprintf(f, "%s\tFragment Path: %s\n", prefix, u->fragment_path); - - if (u->job_timeout > 0) - fprintf(f, "%s\tJob Timeout: %s\n", prefix, format_timespan(timespan, sizeof(timespan), u->job_timeout)); - - condition_dump_list(u->conditions, f, prefix); - - 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)); - - 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 (u->load_state == UNIT_LOADED) { - CGroupBonding *b; - CGroupAttribute *a; - - fprintf(f, - "%s\tStopWhenUnneeded: %s\n" - "%s\tRefuseManualStart: %s\n" - "%s\tRefuseManualStop: %s\n" - "%s\tDefaultDependencies: %s\n" - "%s\tOnFailureIsolate: %s\n" - "%s\tIgnoreOnIsolate: %s\n" - "%s\tIgnoreOnSnapshot: %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, yes_no(u->on_failure_isolate), - prefix, yes_no(u->ignore_on_isolate), - prefix, yes_no(u->ignore_on_snapshot)); - - LIST_FOREACH(by_unit, b, u->cgroup_bondings) - fprintf(f, "%s\tControlGroup: %s:%s\n", - prefix, b->controller, b->path); - - LIST_FOREACH(by_unit, a, u->cgroup_attributes) { - char *v = NULL; - - if (a->map_callback) - a->map_callback(a->controller, a->name, a->value, &v); - - fprintf(f, "%s\tControlGroupAttribute: %s %s \"%s\"\n", - prefix, a->controller, a->name, v ? v : a->value); - - free(v); - } - - 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); - - free(p2); -} - -/* Common implementation for multiple backends */ -int unit_load_fragment_and_dropin(Unit *u) { - int r; - - assert(u); - - /* Load a .service file */ - if ((r = unit_load_fragment(u)) < 0) - return r; - - if (u->load_state == UNIT_STUB) - return -ENOENT; - - /* Load drop-in directory data */ - if ((r = unit_load_dropin(unit_follow_merge(u))) < 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 */ - if ((r = unit_load_fragment(u)) < 0) - return r; - - if (u->load_state == UNIT_STUB) - u->load_state = UNIT_LOADED; - - /* Load drop-in directory data */ - if ((r = unit_load_dropin(unit_follow_merge(u))) < 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_default_dependencies(Unit *u) { - static const UnitDependency deps[] = { - UNIT_REQUIRED_BY, - UNIT_REQUIRED_BY_OVERRIDABLE, - UNIT_WANTED_BY, - UNIT_BOUND_BY - }; - - Unit *target; - Iterator i; - int r; - unsigned k; - - assert(u); - - for (k = 0; k < ELEMENTSOF(deps); k++) - SET_FOREACH(target, u->dependencies[deps[k]], i) - if ((r = unit_add_default_target_dependency(u, target)) < 0) - return r; - - return 0; -} - -int unit_load(Unit *u) { - int r; - - assert(u); - - if (u->in_load_queue) { - LIST_REMOVE(Unit, load_queue, u->manager->load_queue, u); - u->in_load_queue = false; - } - - if (u->type == _UNIT_TYPE_INVALID) - return -EINVAL; - - if (u->load_state != UNIT_STUB) - return 0; - - if (UNIT_VTABLE(u)->load) - if ((r = UNIT_VTABLE(u)->load(u)) < 0) - goto fail; - - if (u->load_state == UNIT_STUB) { - r = -ENOENT; - goto fail; - } - - if (u->load_state == UNIT_LOADED && - u->default_dependencies) - if ((r = unit_add_default_dependencies(u)) < 0) - goto fail; - - if (u->on_failure_isolate && - set_size(u->dependencies[UNIT_ON_FAILURE]) > 1) { - - log_error("More than one OnFailure= dependencies specified for %s but OnFailureIsolate= enabled. Refusing.", - u->id); - - r = -EINVAL; - goto fail; - } - - 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 = UNIT_ERROR; - u->load_error = r; - unit_add_to_dbus_queue(u); - unit_add_to_gc_queue(u); - - log_debug("Failed to load configuration for %s: %s", u->id, strerror(-r)); - - return r; -} - -bool unit_condition_test(Unit *u) { - assert(u); - - dual_timestamp_get(&u->condition_timestamp); - u->condition_result = condition_test_list(u->conditions); - - return u->condition_result; -} - -/* 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. - */ -int unit_start(Unit *u) { - UnitActiveState state; - Unit *following; - - assert(u); - - if (u->load_state != UNIT_LOADED) - return -EINVAL; - - /* 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; - - /* 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_debug("Starting of %s requested but condition failed. Ignoring.", u->id); - return -EALREADY; - } - - /* Forward to the main object, if we aren't it. */ - if ((following = unit_following(u))) { - log_debug("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); - - unit_status_printf(u, NULL, "Starting %s...", unit_description(u)); - return UNIT_VTABLE(u)->start(u); -} - -bool unit_can_start(Unit *u) { - assert(u); - - 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; - - if ((following = unit_following(u))) { - log_debug("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); - - unit_status_printf(u, NULL, "Stopping %s...", unit_description(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) - return -ENOEXEC; - - if ((following = unit_following(u))) { - log_debug("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) { - Iterator i; - Unit *other; - - 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; - - SET_FOREACH(other, u->dependencies[UNIT_REQUIRED_BY], i) - if (unit_pending_active(other)) - return; - - SET_FOREACH(other, u->dependencies[UNIT_REQUIRED_BY_OVERRIDABLE], i) - if (unit_pending_active(other)) - return; - - SET_FOREACH(other, u->dependencies[UNIT_WANTED_BY], i) - if (unit_pending_active(other)) - return; - - SET_FOREACH(other, u->dependencies[UNIT_BOUND_BY], i) - if (unit_pending_active(other)) - return; - - log_info("Service %s is not needed anymore. Stopping.", u->id); - - /* Ok, nobody needs us anymore. Sniff. Then let's commit suicide */ - manager_add_job(u->manager, JOB_STOP, u, JOB_FAIL, true, NULL, NULL); -} - -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, true, NULL, NULL); - - SET_FOREACH(other, u->dependencies[UNIT_BIND_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, true, NULL, NULL); - - SET_FOREACH(other, u->dependencies[UNIT_REQUIRES_OVERRIDABLE], 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, false, NULL, NULL); - - SET_FOREACH(other, u->dependencies[UNIT_REQUISITE], 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, true, 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, false, 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, true, 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, true, 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, true, 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_REQUIRES_OVERRIDABLE], 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_REQUISITE_OVERRIDABLE], i) - if (!UNIT_IS_INACTIVE_OR_DEACTIVATING(unit_active_state(other))) - unit_check_unneeded(other); - SET_FOREACH(other, u->dependencies[UNIT_BIND_TO], i) - if (!UNIT_IS_INACTIVE_OR_DEACTIVATING(unit_active_state(other))) - unit_check_unneeded(other); -} - -void unit_trigger_on_failure(Unit *u) { - Unit *other; - Iterator i; - - assert(u); - - if (set_size(u->dependencies[UNIT_ON_FAILURE]) <= 0) - return; - - log_info("Triggering OnFailure= dependencies of %s.", u->id); - - SET_FOREACH(other, u->dependencies[UNIT_ON_FAILURE], i) { - int r; - - if ((r = manager_add_job(u->manager, JOB_START, other, u->on_failure_isolate ? JOB_ISOLATE : JOB_REPLACE, true, NULL, NULL)) < 0) - log_error("Failed to enqueue OnFailure= job: %s", strerror(-r)); - } -} - -void unit_notify(Unit *u, UnitActiveState os, UnitActiveState ns, bool reload_success) { - 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 OK an expected - * behaviour here. For example: if a mount point is remounted - * this function will be called too! */ - - if (u->manager->n_reloading <= 0) { - dual_timestamp ts; - - dual_timestamp_get(&ts); - - if (UNIT_IS_INACTIVE_OR_FAILED(os) && !UNIT_IS_INACTIVE_OR_FAILED(ns)) - u->inactive_exit_timestamp = ts; - else if (!UNIT_IS_INACTIVE_OR_FAILED(os) && UNIT_IS_INACTIVE_OR_FAILED(ns)) - u->inactive_enter_timestamp = ts; - - if (!UNIT_IS_ACTIVE_OR_RELOADING(os) && UNIT_IS_ACTIVE_OR_RELOADING(ns)) - u->active_enter_timestamp = ts; - else if (UNIT_IS_ACTIVE_OR_RELOADING(os) && !UNIT_IS_ACTIVE_OR_RELOADING(ns)) - u->active_exit_timestamp = ts; - - timer_unit_notify(u, ns); - path_unit_notify(u, ns); - } - - if (UNIT_IS_INACTIVE_OR_FAILED(ns)) - cgroup_bonding_trim_list(u->cgroup_bondings, true); - - 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); - 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); - } - - break; - - case JOB_RELOAD: - case JOB_RELOAD_OR_START: - - if (u->job->state == JOB_RUNNING) { - if (ns == UNIT_ACTIVE) - job_finish_and_invalidate(u->job, reload_success ? JOB_DONE : JOB_FAILED); - 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); - } - } - - 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); - else if (u->job->state == JOB_RUNNING && ns != UNIT_DEACTIVATING) { - unexpected = true; - job_finish_and_invalidate(u->job, JOB_FAILED); - } - - break; - - default: - assert_not_reached("Job type unknown"); - } - - } else - unexpected = true; - - if (u->manager->n_reloading <= 0) { - - /* If this state change happened without being - * requested by a job, then let's retroactively start - * or stop dependencies. We skip that step when - * deserializing, since we don't want to create any - * additional jobs just because something is already - * activated. */ - - if (unexpected) { - if (UNIT_IS_INACTIVE_OR_FAILED(os) && UNIT_IS_ACTIVE_OR_ACTIVATING(ns)) - retroactively_start_dependencies(u); - else if (UNIT_IS_ACTIVE_OR_ACTIVATING(os) && UNIT_IS_INACTIVE_OR_DEACTIVATING(ns)) - retroactively_stop_dependencies(u); - } - - /* stop unneeded units regardless if going down was expected or not */ - if (UNIT_IS_ACTIVE_OR_ACTIVATING(os) && UNIT_IS_INACTIVE_OR_DEACTIVATING(ns)) - check_unneeded_dependencies(u); - - if (ns != os && ns == UNIT_FAILED) { - log_notice("Unit %s entered failed state.", u->id); - unit_trigger_on_failure(u); - } - } - - /* Some names are special */ - if (UNIT_IS_ACTIVE_OR_RELOADING(ns)) { - - if (unit_has_name(u, SPECIAL_DBUS_SERVICE)) - /* The bus just might have become available, - * hence try to connect to it, if we aren't - * yet connected. */ - bus_init(u->manager, true); - - if (u->type == UNIT_SERVICE && - !UNIT_IS_ACTIVE_OR_RELOADING(os) && - u->manager->n_reloading <= 0) { - /* Write audit record if we have just finished starting up */ - manager_send_unit_audit(u->manager, u, AUDIT_SERVICE_START, true); - u->in_audit = true; - } - - if (!UNIT_IS_ACTIVE_OR_RELOADING(os)) - manager_send_unit_plymouth(u->manager, 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) && - u->manager->n_reloading <= 0) { - - /* Hmm, if there was no start record written - * write it now, so that we always have a nice - * pair */ - if (!u->in_audit) { - manager_send_unit_audit(u->manager, u, AUDIT_SERVICE_START, ns == UNIT_INACTIVE); - - if (ns == UNIT_INACTIVE) - manager_send_unit_audit(u->manager, u, AUDIT_SERVICE_STOP, true); - } else - /* Write audit record if we have just finished shutting down */ - manager_send_unit_audit(u->manager, u, AUDIT_SERVICE_STOP, ns == UNIT_INACTIVE); - - u->in_audit = false; - } - } - - manager_recheck_journal(u->manager); - - /* Maybe we finished startup and are now ready for being - * stopped because unneeded? */ - unit_check_unneeded(u); - - unit_add_to_dbus_queue(u); - unit_add_to_gc_queue(u); -} - -int unit_watch_fd(Unit *u, int fd, uint32_t events, Watch *w) { - struct epoll_event ev; - - assert(u); - assert(fd >= 0); - assert(w); - assert(w->type == WATCH_INVALID || (w->type == WATCH_FD && w->fd == fd && w->data.unit == u)); - - zero(ev); - ev.data.ptr = w; - ev.events = events; - - if (epoll_ctl(u->manager->epoll_fd, - w->type == WATCH_INVALID ? EPOLL_CTL_ADD : EPOLL_CTL_MOD, - fd, - &ev) < 0) - return -errno; - - w->fd = fd; - w->type = WATCH_FD; - w->data.unit = u; - - return 0; -} - -void unit_unwatch_fd(Unit *u, Watch *w) { - assert(u); - assert(w); - - if (w->type == WATCH_INVALID) - return; - - assert(w->type == WATCH_FD); - assert(w->data.unit == u); - assert_se(epoll_ctl(u->manager->epoll_fd, EPOLL_CTL_DEL, w->fd, NULL) >= 0); - - w->fd = -1; - w->type = WATCH_INVALID; - w->data.unit = NULL; -} - -int unit_watch_pid(Unit *u, pid_t pid) { - assert(u); - assert(pid >= 1); - - /* Watch a specific PID. We only support one unit watching - * each PID for now. */ - - return hashmap_put(u->manager->watch_pids, LONG_TO_PTR(pid), u); -} - -void unit_unwatch_pid(Unit *u, pid_t pid) { - assert(u); - assert(pid >= 1); - - hashmap_remove_value(u->manager->watch_pids, LONG_TO_PTR(pid), u); -} - -int unit_watch_timer(Unit *u, usec_t delay, Watch *w) { - struct itimerspec its; - int flags, fd; - bool ours; - - assert(u); - assert(w); - assert(w->type == WATCH_INVALID || (w->type == WATCH_UNIT_TIMER && w->data.unit == u)); - - /* This will try to reuse the old timer if there is one */ - - if (w->type == WATCH_UNIT_TIMER) { - assert(w->data.unit == u); - assert(w->fd >= 0); - - ours = false; - fd = w->fd; - } else if (w->type == WATCH_INVALID) { - - ours = true; - if ((fd = timerfd_create(CLOCK_MONOTONIC, TFD_NONBLOCK|TFD_CLOEXEC)) < 0) - return -errno; - } else - assert_not_reached("Invalid watch type"); - - zero(its); - - if (delay <= 0) { - /* Set absolute time in the past, but not 0, since we - * don't want to disarm the timer */ - its.it_value.tv_sec = 0; - its.it_value.tv_nsec = 1; - - flags = TFD_TIMER_ABSTIME; - } else { - timespec_store(&its.it_value, delay); - flags = 0; - } - - /* This will also flush the elapse counter */ - if (timerfd_settime(fd, flags, &its, NULL) < 0) - goto fail; - - if (w->type == WATCH_INVALID) { - struct epoll_event ev; - - zero(ev); - ev.data.ptr = w; - ev.events = EPOLLIN; - - if (epoll_ctl(u->manager->epoll_fd, EPOLL_CTL_ADD, fd, &ev) < 0) - goto fail; - } - - w->type = WATCH_UNIT_TIMER; - w->fd = fd; - w->data.unit = u; - - return 0; - -fail: - if (ours) - close_nointr_nofail(fd); - - return -errno; -} - -void unit_unwatch_timer(Unit *u, Watch *w) { - assert(u); - assert(w); - - if (w->type == WATCH_INVALID) - return; - - assert(w->type == WATCH_UNIT_TIMER); - assert(w->data.unit == u); - assert(w->fd >= 0); - - assert_se(epoll_ctl(u->manager->epoll_fd, EPOLL_CTL_DEL, w->fd, NULL) >= 0); - close_nointr_nofail(w->fd); - - w->fd = -1; - w->type = WATCH_INVALID; - w->data.unit = NULL; -} - -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: - return true; - - case JOB_RESTART: - case JOB_TRY_RESTART: - return unit_can_start(u); - - case JOB_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"); - } -} - -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_REQUIRES_OVERRIDABLE] = UNIT_REQUIRED_BY_OVERRIDABLE, - [UNIT_WANTS] = UNIT_WANTED_BY, - [UNIT_REQUISITE] = UNIT_REQUIRED_BY, - [UNIT_REQUISITE_OVERRIDABLE] = UNIT_REQUIRED_BY_OVERRIDABLE, - [UNIT_BIND_TO] = UNIT_BOUND_BY, - [UNIT_REQUIRED_BY] = _UNIT_DEPENDENCY_INVALID, - [UNIT_REQUIRED_BY_OVERRIDABLE] = _UNIT_DEPENDENCY_INVALID, - [UNIT_WANTED_BY] = _UNIT_DEPENDENCY_INVALID, - [UNIT_BOUND_BY] = UNIT_BIND_TO, - [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_PROPAGATE_RELOAD_TO] = UNIT_PROPAGATE_RELOAD_FROM, - [UNIT_PROPAGATE_RELOAD_FROM] = UNIT_PROPAGATE_RELOAD_TO - }; - int r, q = 0, v = 0, w = 0; - - 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) - return 0; - - if ((r = set_ensure_allocated(&u->dependencies[d], trivial_hash_func, trivial_compare_func)) < 0) - return r; - - if (inverse_table[d] != _UNIT_DEPENDENCY_INVALID) - if ((r = set_ensure_allocated(&other->dependencies[inverse_table[d]], trivial_hash_func, trivial_compare_func)) < 0) - return r; - - if (add_reference) - if ((r = set_ensure_allocated(&u->dependencies[UNIT_REFERENCES], trivial_hash_func, trivial_compare_func)) < 0 || - (r = set_ensure_allocated(&other->dependencies[UNIT_REFERENCED_BY], trivial_hash_func, trivial_compare_func)) < 0) - return r; - - if ((q = set_put(u->dependencies[d], other)) < 0) - return q; - - if (inverse_table[d] != _UNIT_DEPENDENCY_INVALID) - if ((v = set_put(other->dependencies[inverse_table[d]], u)) < 0) { - r = v; - goto fail; - } - - if (add_reference) { - if ((w = set_put(u->dependencies[UNIT_REFERENCES], other)) < 0) { - r = w; - goto fail; - } - - if ((r = set_put(other->dependencies[UNIT_REFERENCED_BY], u)) < 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); - - if ((r = unit_add_dependency(u, d, other, add_reference)) < 0) - return r; - - if ((r = unit_add_dependency(u, e, other, add_reference)) < 0) - return r; - - return 0; -} - -static const char *resolve_template(Unit *u, const char *name, const char*path, char **p) { - char *s; - - assert(u); - assert(name || path); - - if (!name) - name = file_name_from_path(path); - - if (!unit_name_is_template(name)) { - *p = NULL; - return name; - } - - if (u->instance) - s = unit_name_replace_instance(name, u->instance); - else { - char *i; - - if (!(i = unit_name_to_prefix(u->id))) - return NULL; - - s = unit_name_replace_instance(name, i); - free(i); - } - - if (!s) - return NULL; - - *p = s; - return s; -} - -int unit_add_dependency_by_name(Unit *u, UnitDependency d, const char *name, const char *path, bool add_reference) { - Unit *other; - int r; - char *s; - - assert(u); - assert(name || path); - - if (!(name = resolve_template(u, name, path, &s))) - return -ENOMEM; - - if ((r = manager_load_unit(u->manager, name, path, NULL, &other)) < 0) - goto finish; - - r = unit_add_dependency(u, d, other, add_reference); - -finish: - free(s); - return r; -} - -int unit_add_two_dependencies_by_name(Unit *u, UnitDependency d, UnitDependency e, const char *name, const char *path, bool add_reference) { - Unit *other; - int r; - char *s; - - assert(u); - assert(name || path); - - if (!(name = resolve_template(u, name, path, &s))) - return -ENOMEM; - - if ((r = manager_load_unit(u->manager, name, path, NULL, &other)) < 0) - goto finish; - - r = unit_add_two_dependencies(u, d, e, other, add_reference); - -finish: - free(s); - return r; -} - -int unit_add_dependency_by_name_inverse(Unit *u, UnitDependency d, const char *name, const char *path, bool add_reference) { - Unit *other; - int r; - char *s; - - assert(u); - assert(name || path); - - if (!(name = resolve_template(u, name, path, &s))) - return -ENOMEM; - - if ((r = manager_load_unit(u->manager, name, path, NULL, &other)) < 0) - goto finish; - - r = unit_add_dependency(other, d, u, add_reference); - -finish: - free(s); - return r; -} - -int unit_add_two_dependencies_by_name_inverse(Unit *u, UnitDependency d, UnitDependency e, const char *name, const char *path, bool add_reference) { - Unit *other; - int r; - char *s; - - assert(u); - assert(name || path); - - if (!(name = resolve_template(u, name, path, &s))) - return -ENOMEM; - - if ((r = manager_load_unit(u->manager, name, path, NULL, &other)) < 0) - goto finish; - - if ((r = unit_add_two_dependencies(other, d, e, u, add_reference)) < 0) - goto finish; - -finish: - free(s); - return r; -} - -int set_unit_path(const char *p) { - char *cwd, *c; - int r; - - /* This is mostly for debug purposes */ - - if (path_is_absolute(p)) { - if (!(c = strdup(p))) - return -ENOMEM; - } else { - if (!(cwd = get_current_dir_name())) - return -errno; - - r = asprintf(&c, "%s/%s", cwd, p); - free(cwd); - - if (r < 0) - return -ENOMEM; - } - - if (setenv("SYSTEMD_UNIT_PATH", c, 0) < 0) { - r = -errno; - free(c); - return r; - } - - return 0; -} - -char *unit_dbus_path(Unit *u) { - char *p, *e; - - assert(u); - - if (!u->id) - return NULL; - - if (!(e = bus_path_escape(u->id))) - return NULL; - - p = strappend("/org/freedesktop/systemd1/unit/", e); - free(e); - - return p; -} - -int unit_add_cgroup(Unit *u, CGroupBonding *b) { - int r; - - assert(u); - assert(b); - - assert(b->path); - - if (!b->controller) { - if (!(b->controller = strdup(SYSTEMD_CGROUP_CONTROLLER))) - return -ENOMEM; - - b->ours = true; - } - - /* Ensure this hasn't been added yet */ - assert(!b->unit); - - if (streq(b->controller, SYSTEMD_CGROUP_CONTROLLER)) { - CGroupBonding *l; - - l = hashmap_get(u->manager->cgroup_bondings, b->path); - LIST_PREPEND(CGroupBonding, by_path, l, b); - - if ((r = hashmap_replace(u->manager->cgroup_bondings, b->path, l)) < 0) { - LIST_REMOVE(CGroupBonding, by_path, l, b); - return r; - } - } - - LIST_PREPEND(CGroupBonding, by_unit, u->cgroup_bondings, b); - b->unit = u; - - return 0; -} - -static char *default_cgroup_path(Unit *u) { - char *p; - - assert(u); - - if (u->instance) { - char *t; - - t = unit_name_template(u->id); - if (!t) - return NULL; - - p = join(u->manager->cgroup_hierarchy, "/", t, "/", u->instance, NULL); - free(t); - } else - p = join(u->manager->cgroup_hierarchy, "/", u->id, NULL); - - return p; -} - -int unit_add_cgroup_from_text(Unit *u, const char *name) { - char *controller = NULL, *path = NULL; - CGroupBonding *b = NULL; - bool ours = false; - int r; - - assert(u); - assert(name); - - if ((r = cg_split_spec(name, &controller, &path)) < 0) - return r; - - if (!path) { - path = default_cgroup_path(u); - ours = true; - } - - if (!controller) { - controller = strdup(SYSTEMD_CGROUP_CONTROLLER); - ours = true; - } - - if (!path || !controller) { - free(path); - free(controller); - - return -ENOMEM; - } - - if (cgroup_bonding_find_list(u->cgroup_bondings, controller)) { - r = -EEXIST; - goto fail; - } - - if (!(b = new0(CGroupBonding, 1))) { - r = -ENOMEM; - goto fail; - } - - b->controller = controller; - b->path = path; - b->ours = ours; - b->essential = streq(controller, SYSTEMD_CGROUP_CONTROLLER); - - if ((r = unit_add_cgroup(u, b)) < 0) - goto fail; - - return 0; - -fail: - free(path); - free(controller); - free(b); - - return r; -} - -static int unit_add_one_default_cgroup(Unit *u, const char *controller) { - CGroupBonding *b = NULL; - int r = -ENOMEM; - - assert(u); - - if (!controller) - controller = SYSTEMD_CGROUP_CONTROLLER; - - if (cgroup_bonding_find_list(u->cgroup_bondings, controller)) - return 0; - - if (!(b = new0(CGroupBonding, 1))) - return -ENOMEM; - - if (!(b->controller = strdup(controller))) - goto fail; - - if (!(b->path = default_cgroup_path(u))) - goto fail; - - b->ours = true; - b->essential = streq(controller, SYSTEMD_CGROUP_CONTROLLER); - - if ((r = unit_add_cgroup(u, b)) < 0) - goto fail; - - return 0; - -fail: - free(b->path); - free(b->controller); - free(b); - - return r; -} - -int unit_add_default_cgroups(Unit *u) { - CGroupAttribute *a; - char **c; - int r; - - assert(u); - - /* Adds in the default cgroups, if they weren't specified - * otherwise. */ - - if (!u->manager->cgroup_hierarchy) - return 0; - - if ((r = unit_add_one_default_cgroup(u, NULL)) < 0) - return r; - - STRV_FOREACH(c, u->manager->default_controllers) - unit_add_one_default_cgroup(u, *c); - - LIST_FOREACH(by_unit, a, u->cgroup_attributes) - unit_add_one_default_cgroup(u, a->controller); - - return 0; -} - -CGroupBonding* unit_get_default_cgroup(Unit *u) { - assert(u); - - return cgroup_bonding_find_list(u->cgroup_bondings, SYSTEMD_CGROUP_CONTROLLER); -} - -int unit_add_cgroup_attribute(Unit *u, const char *controller, const char *name, const char *value, CGroupAttributeMapCallback map_callback) { - int r; - char *c = NULL; - CGroupAttribute *a; - - assert(u); - assert(name); - assert(value); - - if (!controller) { - const char *dot; - - dot = strchr(name, '.'); - if (!dot) - return -EINVAL; - - c = strndup(name, dot - name); - if (!c) - return -ENOMEM; - - controller = c; - } - - if (streq(controller, SYSTEMD_CGROUP_CONTROLLER)) { - r = -EINVAL; - goto finish; - } - - a = new0(CGroupAttribute, 1); - if (!a) { - r = -ENOMEM; - goto finish; - } - - if (c) { - a->controller = c; - c = NULL; - } else - a->controller = strdup(controller); - - a->name = strdup(name); - a->value = strdup(value); - - if (!a->controller || !a->name || !a->value) { - free(a->controller); - free(a->name); - free(a->value); - free(a); - - return -ENOMEM; - } - - a->map_callback = map_callback; - - LIST_PREPEND(CGroupAttribute, by_unit, u->cgroup_attributes, a); - - r = 0; - -finish: - free(c); - return r; -} - -int unit_load_related_unit(Unit *u, const char *type, Unit **_found) { - char *t; - int r; - - assert(u); - assert(type); - assert(_found); - - if (!(t = unit_name_change_suffix(u->id, type))) - return -ENOMEM; - - assert(!unit_has_name(u, t)); - - r = manager_load_unit(u->manager, t, NULL, NULL, _found); - free(t); - - assert(r < 0 || *_found != u); - - return r; -} - -int unit_get_related_unit(Unit *u, const char *type, Unit **_found) { - Unit *found; - char *t; - - assert(u); - assert(type); - assert(_found); - - if (!(t = unit_name_change_suffix(u->id, type))) - return -ENOMEM; - - assert(!unit_has_name(u, t)); - - found = manager_get_unit(u->manager, t); - free(t); - - if (!found) - return -ENOENT; - - *_found = found; - return 0; -} - -static char *specifier_prefix_and_instance(char specifier, void *data, void *userdata) { - Unit *u = userdata; - assert(u); - - return unit_name_to_prefix_and_instance(u->id); -} - -static char *specifier_prefix(char specifier, void *data, void *userdata) { - Unit *u = userdata; - assert(u); - - return unit_name_to_prefix(u->id); -} - -static char *specifier_prefix_unescaped(char specifier, void *data, void *userdata) { - Unit *u = userdata; - char *p, *r; - - assert(u); - - if (!(p = unit_name_to_prefix(u->id))) - return NULL; - - r = unit_name_unescape(p); - free(p); - - return r; -} - -static char *specifier_instance_unescaped(char specifier, void *data, void *userdata) { - Unit *u = userdata; - assert(u); - - if (u->instance) - return unit_name_unescape(u->instance); - - return strdup(""); -} - -static char *specifier_filename(char specifier, void *data, void *userdata) { - Unit *u = userdata; - assert(u); - - if (u->instance) - return unit_name_path_unescape(u->instance); - - return unit_name_to_path(u->instance); -} - -static char *specifier_cgroup(char specifier, void *data, void *userdata) { - Unit *u = userdata; - assert(u); - - return default_cgroup_path(u); -} - -static char *specifier_cgroup_root(char specifier, void *data, void *userdata) { - Unit *u = userdata; - char *p; - assert(u); - - if (specifier == 'r') - return strdup(u->manager->cgroup_hierarchy); - - if (parent_of_path(u->manager->cgroup_hierarchy, &p) < 0) - return strdup(""); - - if (streq(p, "/")) { - free(p); - return strdup(""); - } - - return p; -} - -static char *specifier_runtime(char specifier, void *data, void *userdata) { - Unit *u = userdata; - assert(u); - - if (u->manager->running_as == MANAGER_USER) { - const char *e; - - e = getenv("XDG_RUNTIME_DIR"); - if (e) - return strdup(e); - } - - return strdup("/run"); -} - -char *unit_name_printf(Unit *u, const char* format) { - - /* - * 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); - - return specifier_printf(format, table, u); -} - -char *unit_full_printf(Unit *u, const char *format) { - - /* This is similar to unit_name_printf() but also supports - * unescaping. Also, adds a couple of additional codes: - * - * %c cgroup path of unit - * %r root cgroup path of this systemd instance (e.g. "/user/lennart/shared/systemd-4711") - * %R parent of root cgroup path (e.g. "/usr/lennart/shared") - * %t the runtime directory to place sockets in (e.g. "/run" or $XDG_RUNTIME_DIR) - */ - - 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_root, NULL }, - { 'R', specifier_cgroup_root, NULL }, - { 't', specifier_runtime, NULL }, - { 0, NULL, NULL } - }; - - assert(u); - assert(format); - - return specifier_printf(format, table, u); -} - -char **unit_full_printf_strv(Unit *u, char **l) { - size_t n; - char **r, **i, **j; - - /* Applies unit_full_printf to every entry in l */ - - assert(u); - - n = strv_length(l); - if (!(r = new(char*, n+1))) - return NULL; - - for (i = l, j = r; *i; i++, j++) - if (!(*j = unit_full_printf(u, *i))) - goto fail; - - *j = NULL; - return r; - -fail: - for (j--; j >= r; j--) - free(*j); - - free(r); - - return NULL; -} - -int unit_watch_bus_name(Unit *u, const char *name) { - assert(u); - assert(name); - - /* Watch a specific name on the bus. We only support one unit - * watching each name for now. */ - - return hashmap_put(u->manager->watch_bus, name, u); -} - -void unit_unwatch_bus_name(Unit *u, const char *name) { - assert(u); - assert(name); - - hashmap_remove_value(u->manager->watch_bus, name, u); -} - -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) { - int r; - - assert(u); - assert(f); - assert(fds); - - if (!unit_can_serialize(u)) - return 0; - - if ((r = UNIT_VTABLE(u)->serialize(u, f, fds)) < 0) - return r; - - if (u->job) - unit_serialize_item(u, f, "job", job_type_to_string(u->job->type)); - - 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); - - if (dual_timestamp_is_set(&u->condition_timestamp)) - unit_serialize_item(u, f, "condition-result", yes_no(u->condition_result)); - - /* End marker */ - fputc('\n', f); - return 0; -} - -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); -} - -void unit_serialize_item(Unit *u, FILE *f, const char *key, const char *value) { - assert(u); - assert(f); - assert(key); - assert(value); - - fprintf(f, "%s=%s\n", key, value); -} - -int unit_deserialize(Unit *u, FILE *f, FDSet *fds) { - int r; - - assert(u); - assert(f); - assert(fds); - - if (!unit_can_serialize(u)) - return 0; - - 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")) { - JobType type; - - if ((type = job_type_from_string(v)) < 0) - log_debug("Failed to parse job type value %s", v); - else - u->deserialized_job = type; - - 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, "condition-result")) { - int b; - - if ((b = parse_boolean(v)) < 0) - log_debug("Failed to parse condition result value %s", v); - else - u->condition_result = b; - - continue; - } - - if ((r = UNIT_VTABLE(u)->deserialize_item(u, l, v, fds)) < 0) - return r; - } -} - -int unit_add_node_link(Unit *u, const char *what, bool wants) { - Unit *device; - char *e; - int r; - - assert(u); - - if (!what) - return 0; - - /* Adds in links to the device node that this unit is based on */ - - if (!is_device_path(what)) - return 0; - - if (!(e = unit_name_build_escape(what+1, NULL, ".device"))) - return -ENOMEM; - - r = manager_load_unit(u->manager, e, NULL, NULL, &device); - free(e); - - if (r < 0) - return r; - - if ((r = unit_add_two_dependencies(u, UNIT_AFTER, UNIT_BIND_TO, device, true)) < 0) - return r; - - if (wants) - if ((r = unit_add_dependency(device, UNIT_WANTS, u, false)) < 0) - return r; - - return 0; -} - -int unit_coldplug(Unit *u) { - int r; - - assert(u); - - if (UNIT_VTABLE(u)->coldplug) - if ((r = UNIT_VTABLE(u)->coldplug(u)) < 0) - return r; - - if (u->deserialized_job >= 0) { - if ((r = manager_add_job(u->manager, u->deserialized_job, u, JOB_IGNORE_REQUIREMENTS, false, NULL, NULL)) < 0) - return r; - - u->deserialized_job = _JOB_TYPE_INVALID; - } - - return 0; -} - -void unit_status_printf(Unit *u, const char *status, const char *format, ...) { - va_list ap; - - assert(u); - assert(format); - - if (!UNIT_VTABLE(u)->show_status) - return; - - if (!manager_get_show_status(u->manager)) - return; - - if (!manager_is_booting_or_shutting_down(u->manager)) - return; - - va_start(ap, format); - status_vprintf(status, true, format, ap); - va_end(ap); -} - -bool unit_need_daemon_reload(Unit *u) { - assert(u); - - if (u->fragment_path) { - struct stat st; - - zero(st); - if (stat(u->fragment_path, &st) < 0) - /* What, cannot access this anymore? */ - return true; - - if (u->fragment_mtime > 0 && - timespec_load(&st.st_mtim) != u->fragment_mtime) - return true; - } - - if (UNIT_VTABLE(u)->need_daemon_reload) - return UNIT_VTABLE(u)->need_daemon_reload(u); - - return false; -} - -void unit_reset_failed(Unit *u) { - assert(u); - - if (UNIT_VTABLE(u)->reset_failed) - UNIT_VTABLE(u)->reset_failed(u); -} - -Unit *unit_following(Unit *u) { - assert(u); - - if (UNIT_VTABLE(u)->following) - return UNIT_VTABLE(u)->following(u); - - return NULL; -} - -bool unit_pending_inactive(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 (u->job && u->job->type == JOB_STOP) - return true; - - return false; -} - -bool unit_pending_active(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; -} - -UnitType unit_name_to_type(const char *n) { - UnitType t; - - assert(n); - - for (t = 0; t < _UNIT_TYPE_MAX; t++) - if (endswith(n, unit_vtable[t]->suffix)) - return t; - - return _UNIT_TYPE_INVALID; -} - -bool unit_name_is_valid(const char *n, bool template_ok) { - UnitType t; - - t = unit_name_to_type(n); - if (t < 0 || t >= _UNIT_TYPE_MAX) - return false; - - return unit_name_is_valid_no_type(n, template_ok); -} - -int unit_kill(Unit *u, KillWho w, KillMode m, int signo, DBusError *error) { - assert(u); - assert(w >= 0 && w < _KILL_WHO_MAX); - assert(m >= 0 && m < _KILL_MODE_MAX); - assert(signo > 0); - assert(signo < _NSIG); - - if (m == KILL_NONE) - return 0; - - if (!UNIT_VTABLE(u)->kill) - return -ENOTSUP; - - return UNIT_VTABLE(u)->kill(u, w, m, signo, error); -} - -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) { - assert(u); - - if (u->unit_file_state < 0 && u->fragment_path) - u->unit_file_state = unit_file_get_state( - u->manager->running_as == MANAGER_SYSTEM ? UNIT_FILE_SYSTEM : UNIT_FILE_USER, - NULL, file_name_from_path(u->fragment_path)); - - return u->unit_file_state; -} - -Unit* unit_ref_set(UnitRef *ref, Unit *u) { - assert(ref); - assert(u); - - if (ref->unit) - unit_ref_unset(ref); - - ref->unit = u; - LIST_PREPEND(UnitRef, refs, u->refs, ref); - return u; -} - -void unit_ref_unset(UnitRef *ref) { - assert(ref); - - if (!ref->unit) - return; - - LIST_REMOVE(UnitRef, refs, ref->unit->refs, ref); - ref->unit = NULL; -} - -static const char* const unit_load_state_table[_UNIT_LOAD_STATE_MAX] = { - [UNIT_STUB] = "stub", - [UNIT_LOADED] = "loaded", - [UNIT_ERROR] = "error", - [UNIT_MERGED] = "merged", - [UNIT_MASKED] = "masked" -}; - -DEFINE_STRING_TABLE_LOOKUP(unit_load_state, UnitLoadState); - -static const char* const unit_active_state_table[_UNIT_ACTIVE_STATE_MAX] = { - [UNIT_ACTIVE] = "active", - [UNIT_RELOADING] = "reloading", - [UNIT_INACTIVE] = "inactive", - [UNIT_FAILED] = "failed", - [UNIT_ACTIVATING] = "activating", - [UNIT_DEACTIVATING] = "deactivating" -}; - -DEFINE_STRING_TABLE_LOOKUP(unit_active_state, UnitActiveState); - -static const char* const unit_dependency_table[_UNIT_DEPENDENCY_MAX] = { - [UNIT_REQUIRES] = "Requires", - [UNIT_REQUIRES_OVERRIDABLE] = "RequiresOverridable", - [UNIT_WANTS] = "Wants", - [UNIT_REQUISITE] = "Requisite", - [UNIT_REQUISITE_OVERRIDABLE] = "RequisiteOverridable", - [UNIT_REQUIRED_BY] = "RequiredBy", - [UNIT_REQUIRED_BY_OVERRIDABLE] = "RequiredByOverridable", - [UNIT_BIND_TO] = "BindTo", - [UNIT_WANTED_BY] = "WantedBy", - [UNIT_CONFLICTS] = "Conflicts", - [UNIT_CONFLICTED_BY] = "ConflictedBy", - [UNIT_BOUND_BY] = "BoundBy", - [UNIT_BEFORE] = "Before", - [UNIT_AFTER] = "After", - [UNIT_REFERENCES] = "References", - [UNIT_REFERENCED_BY] = "ReferencedBy", - [UNIT_ON_FAILURE] = "OnFailure", - [UNIT_TRIGGERS] = "Triggers", - [UNIT_TRIGGERED_BY] = "TriggeredBy", - [UNIT_PROPAGATE_RELOAD_TO] = "PropagateReloadTo", - [UNIT_PROPAGATE_RELOAD_FROM] = "PropagateReloadFrom" -}; - -DEFINE_STRING_TABLE_LOOKUP(unit_dependency, UnitDependency); diff --git a/src/unit.h b/src/unit.h deleted file mode 100644 index 756f465da3..0000000000 --- a/src/unit.h +++ /dev/null @@ -1,562 +0,0 @@ -/*-*- Mode: C; c-basic-offset: 8; indent-tabs-mode: nil -*-*/ - -#ifndef foounithfoo -#define foounithfoo - -/*** - This file is part of systemd. - - Copyright 2010 Lennart Poettering - - systemd is free software; you can redistribute it and/or modify it - under the terms of the GNU General Public License as published by - the Free Software Foundation; either version 2 of the License, or - (at your option) any later version. - - systemd is distributed in the hope that 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 General Public License - along with systemd; If not, see . -***/ - -#include -#include - -typedef struct Unit Unit; -typedef struct UnitVTable UnitVTable; -typedef enum UnitType UnitType; -typedef enum UnitLoadState UnitLoadState; -typedef enum UnitActiveState UnitActiveState; -typedef enum UnitDependency UnitDependency; -typedef struct UnitRef UnitRef; - -#include "set.h" -#include "util.h" -#include "list.h" -#include "socket-util.h" -#include "execute.h" -#include "condition.h" -#include "install.h" - -enum UnitType { - UNIT_SERVICE = 0, - UNIT_SOCKET, - UNIT_TARGET, - UNIT_DEVICE, - UNIT_MOUNT, - UNIT_AUTOMOUNT, - UNIT_SNAPSHOT, - UNIT_TIMER, - UNIT_SWAP, - UNIT_PATH, - _UNIT_TYPE_MAX, - _UNIT_TYPE_INVALID = -1 -}; - -enum UnitLoadState { - UNIT_STUB, - UNIT_LOADED, - UNIT_ERROR, - UNIT_MERGED, - UNIT_MASKED, - _UNIT_LOAD_STATE_MAX, - _UNIT_LOAD_STATE_INVALID = -1 -}; - -enum UnitActiveState { - UNIT_ACTIVE, - UNIT_RELOADING, - UNIT_INACTIVE, - UNIT_FAILED, - UNIT_ACTIVATING, - UNIT_DEACTIVATING, - _UNIT_ACTIVE_STATE_MAX, - _UNIT_ACTIVE_STATE_INVALID = -1 -}; - -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; -} - -enum UnitDependency { - /* Positive dependencies */ - UNIT_REQUIRES, - UNIT_REQUIRES_OVERRIDABLE, - UNIT_REQUISITE, - UNIT_REQUISITE_OVERRIDABLE, - UNIT_WANTS, - UNIT_BIND_TO, - - /* Inverse of the above */ - UNIT_REQUIRED_BY, /* inverse of 'requires' and 'requisite' is 'required_by' */ - UNIT_REQUIRED_BY_OVERRIDABLE, /* inverse of 'requires_overridable' and 'requisite_overridable' is 'soft_required_by' */ - UNIT_WANTED_BY, /* inverse of 'wants' */ - UNIT_BOUND_BY, /* inverse of 'bind_to' */ - - /* Negative dependencies */ - UNIT_CONFLICTS, /* inverse of 'conflicts' is 'conflicted_by' */ - UNIT_CONFLICTED_BY, - - /* Order */ - UNIT_BEFORE, /* inverse of 'before' is 'after' and vice versa */ - UNIT_AFTER, - - /* On Failure */ - UNIT_ON_FAILURE, - - /* Triggers (i.e. a socket triggers a service) */ - UNIT_TRIGGERS, - UNIT_TRIGGERED_BY, - - /* Propagate reloads */ - UNIT_PROPAGATE_RELOAD_TO, - UNIT_PROPAGATE_RELOAD_FROM, - - /* Reference information for GC logic */ - UNIT_REFERENCES, /* Inverse of 'references' is 'referenced_by' */ - UNIT_REFERENCED_BY, - - _UNIT_DEPENDENCY_MAX, - _UNIT_DEPENDENCY_INVALID = -1 -}; - -#include "manager.h" -#include "job.h" -#include "cgroup.h" -#include "cgroup-attr.h" - -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 *description; - - char *fragment_path; /* if loaded from a config file this is the primary path to it */ - usec_t fragment_mtime; - - /* If there is something to do with this unit, then this is - * the job for it */ - Job *job; - - usec_t job_timeout; - - /* References to this */ - LIST_HEAD(UnitRef, refs); - - /* Conditions to check */ - LIST_HEAD(Condition, conditions); - - dual_timestamp condition_timestamp; - - dual_timestamp inactive_exit_timestamp; - dual_timestamp active_enter_timestamp; - dual_timestamp active_exit_timestamp; - dual_timestamp inactive_enter_timestamp; - - /* Counterparts in the cgroup filesystem */ - CGroupBonding *cgroup_bondings; - CGroupAttribute *cgroup_attributes; - - /* Per type list */ - LIST_FIELDS(Unit, units_by_type); - - /* 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); - - /* Used during GC sweeps */ - unsigned gc_marker; - - /* When deserializing, temporarily store the job type for this - * unit here, if there was a job scheduled */ - int deserialized_job; /* This is actually of type JobType */ - - /* Error code when we didn't manage to load the unit (negative) */ - int load_error; - - /* Cached unit file state */ - UnitFileState unit_file_state; - - /* 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; - - /* Isolate OnFailure unit */ - bool on_failure_isolate; - - /* Ignore this unit when isolating */ - bool ignore_on_isolate; - - /* Ignore this unit when snapshotting */ - bool ignore_on_snapshot; - - /* Did the last condition check suceed? */ - bool condition_result; - - bool in_load_queue:1; - bool in_dbus_queue:1; - bool in_cleanup_queue:1; - bool in_gc_queue:1; - - bool sent_dbus_new_signal:1; - - bool no_gc:1; - - bool in_audit:1; -}; - -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); -}; - -#include "service.h" -#include "timer.h" -#include "socket.h" -#include "target.h" -#include "device.h" -#include "mount.h" -#include "automount.h" -#include "snapshot.h" -#include "swap.h" -#include "path.h" - -struct UnitVTable { - const char *suffix; - - /* How much memory does an object of this unit type need */ - size_t object_size; - - /* 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 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, KillMode m, int signo, DBusError *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); - - /* 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); - - /* Return true when this unit is suitable for snapshotting */ - bool (*check_snapshot)(Unit *u); - - void (*fd_event)(Unit *u, int fd, uint32_t events, Watch *w); - void (*sigchld_event)(Unit *u, pid_t pid, int code, int status); - void (*timer_event)(Unit *u, uint64_t n_elapsed, Watch *w); - - /* Check whether unit needs a daemon reload */ - bool (*need_daemon_reload)(Unit *u); - - /* 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 (*cgroup_notify_empty)(Unit *u); - - /* Called whenever a process of this unit sends us a message */ - void (*notify_message)(Unit *u, pid_t pid, char **tags); - - /* Called whenever a name thus 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 whenever a bus PID lookup finishes */ - void (*bus_query_pid_done)(Unit *u, const char *name, pid_t pid); - - /* Called for each message received on the bus */ - DBusHandlerResult (*bus_message_handler)(Unit *u, DBusConnection *c, DBusMessage *message); - - /* 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); - - /* 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. */ - int (*enumerate)(Manager *m); - - /* Type specific cleanups. */ - void (*shutdown)(Manager *m); - - /* When sending out PropertiesChanged signal, which properties - * shall be invalidated? This is a NUL separated list of - * strings, to minimize relocations a little. */ - const char *bus_invalidating_properties; - - /* The interface name */ - const char *bus_interface; - - /* Can units of this type have multiple names? */ - bool no_alias:1; - - /* Instances make no sense for this type */ - bool no_instances:1; - - /* Exclude from automatic gc */ - bool no_gc:1; - - /* Show status updates on the console */ - bool show_status: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_CAST(SOCKET, Socket); -DEFINE_CAST(TIMER, Timer); -DEFINE_CAST(SERVICE, Service); -DEFINE_CAST(TARGET, Target); -DEFINE_CAST(DEVICE, Device); -DEFINE_CAST(MOUNT, Mount); -DEFINE_CAST(AUTOMOUNT, Automount); -DEFINE_CAST(SNAPSHOT, Snapshot); -DEFINE_CAST(SWAP, Swap); -DEFINE_CAST(PATH, Path); - -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_dependency_by_name_inverse(Unit *u, UnitDependency d, const char *name, const char *filename, bool add_reference); -int unit_add_two_dependencies_by_name_inverse(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_add_cgroup(Unit *u, CGroupBonding *b); -int unit_add_cgroup_from_text(Unit *u, const char *name); -int unit_add_default_cgroups(Unit *u); -CGroupBonding* unit_get_default_cgroup(Unit *u); -int unit_add_cgroup_attribute(Unit *u, const char *controller, const char *name, const char *value, CGroupAttributeMapCallback map_callback); - -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); - -int unit_load_fragment_and_dropin(Unit *u); -int unit_load_fragment_and_dropin_optional(Unit *u); -int unit_load(Unit *unit); - -const char *unit_description(Unit *u); - -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); -bool unit_can_start(Unit *u); -bool unit_can_isolate(Unit *u); - -int unit_start(Unit *u); -int unit_stop(Unit *u); -int unit_reload(Unit *u); - -int unit_kill(Unit *u, KillWho w, KillMode m, int signo, DBusError *error); - -void unit_notify(Unit *u, UnitActiveState os, UnitActiveState ns, bool reload_success); - -int unit_watch_fd(Unit *u, int fd, uint32_t events, Watch *w); -void unit_unwatch_fd(Unit *u, Watch *w); - -int unit_watch_pid(Unit *u, pid_t pid); -void unit_unwatch_pid(Unit *u, pid_t pid); - -int unit_watch_timer(Unit *u, usec_t delay, Watch *w); -void unit_unwatch_timer(Unit *u, Watch *w); - -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); -int unit_get_related_unit(Unit *u, const char *type, Unit **_found); - -char *unit_name_printf(Unit *u, const char* text); -char *unit_full_printf(Unit *u, const char *text); -char **unit_full_printf_strv(Unit *u, char **l); - -bool unit_can_serialize(Unit *u); -int unit_serialize(Unit *u, FILE *f, FDSet *fds); -void unit_serialize_item_format(Unit *u, FILE *f, const char *key, const char *value, ...) _printf_attr_(4,5); -void unit_serialize_item(Unit *u, FILE *f, const char *key, const char *value); -int unit_deserialize(Unit *u, FILE *f, FDSet *fds); - -int unit_add_node_link(Unit *u, const char *what, bool wants); - -int unit_coldplug(Unit *u); - -void unit_status_printf(Unit *u, const char *status, const char *format, ...); - -bool unit_need_daemon_reload(Unit *u); - -void unit_reset_failed(Unit *u); - -Unit *unit_following(Unit *u); - -bool unit_pending_inactive(Unit *u); -bool unit_pending_active(Unit *u); - -int unit_add_default_target_dependency(Unit *u, Unit *target); - -int unit_following_set(Unit *u, Set **s); - -UnitType unit_name_to_type(const char *n); -bool unit_name_is_valid(const char *n, bool template_ok); - -void unit_trigger_on_failure(Unit *u); - -bool unit_condition_test(Unit *u); - -UnitFileState unit_get_unit_file_state(Unit *u); - -Unit* unit_ref_set(UnitRef *ref, Unit *u); -void unit_ref_unset(UnitRef *ref); - -#define UNIT_DEREF(ref) ((ref).unit) - -const char *unit_load_state_to_string(UnitLoadState i); -UnitLoadState unit_load_state_from_string(const char *s); - -const char *unit_active_state_to_string(UnitActiveState i); -UnitActiveState unit_active_state_from_string(const char *s); - -const char *unit_dependency_to_string(UnitDependency i); -UnitDependency unit_dependency_from_string(const char *s); - -#endif -- cgit v1.2.3-54-g00ecf