chiark / gitweb /
import udev repository
authorKay Sievers <kay.sievers@vrfy.org>
Tue, 3 Apr 2012 19:08:04 +0000 (21:08 +0200)
committerKay Sievers <kay.sievers@vrfy.org>
Tue, 3 Apr 2012 19:08:04 +0000 (21:08 +0200)
741 files changed:
.dir-locals.el [new file with mode: 0644]
.gitignore
.mailmap [new file with mode: 0644]
CODING_STYLE [new file with mode: 0644]
DISTRO_PORTING [new file with mode: 0644]
LICENSE [new file with mode: 0644]
Makefile.am
NEWS
README
TODO
autogen.sh
configure.ac
introspect.awk [new file with mode: 0644]
m4/.gitignore
m4/acx_libwrap.m4 [new file with mode: 0644]
m4/attributes.m4 [new file with mode: 0644]
man/Makefile [new symlink]
man/binfmt.d.xml [new file with mode: 0644]
man/custom-html.xsl [new file with mode: 0644]
man/daemon.xml [new file with mode: 0644]
man/halt.xml [new file with mode: 0644]
man/hostname.xml [new file with mode: 0644]
man/journalctl.xml [new file with mode: 0644]
man/journald.conf.xml [new file with mode: 0644]
man/locale.conf.xml [new file with mode: 0644]
man/loginctl.xml [new file with mode: 0644]
man/logind.conf.xml [new file with mode: 0644]
man/machine-id.xml [new file with mode: 0644]
man/machine-info.xml [new file with mode: 0644]
man/modules-load.d.xml [new file with mode: 0644]
man/os-release.xml [new file with mode: 0644]
man/pam_systemd.xml [new file with mode: 0644]
man/runlevel.xml [new file with mode: 0644]
man/sd-daemon.xml [new file with mode: 0644]
man/sd-login.xml [new file with mode: 0644]
man/sd-readahead.xml [new file with mode: 0644]
man/sd_booted.xml [new file with mode: 0644]
man/sd_get_seats.xml [new file with mode: 0644]
man/sd_is_fifo.xml [new file with mode: 0644]
man/sd_listen_fds.xml [new file with mode: 0644]
man/sd_login_monitor_new.xml [new file with mode: 0644]
man/sd_notify.xml [new file with mode: 0644]
man/sd_pid_get_session.xml [new file with mode: 0644]
man/sd_readahead.xml [new file with mode: 0644]
man/sd_seat_get_active.xml [new file with mode: 0644]
man/sd_session_is_active.xml [new file with mode: 0644]
man/sd_uid_get_state.xml [new file with mode: 0644]
man/shutdown.xml [new file with mode: 0644]
man/sysctl.d.xml [new file with mode: 0644]
man/systemctl.xml [new file with mode: 0644]
man/systemd-ask-password.xml [new file with mode: 0644]
man/systemd-cat.xml [new file with mode: 0644]
man/systemd-cgls.xml [new file with mode: 0644]
man/systemd-cgtop.xml [new file with mode: 0644]
man/systemd-machine-id-setup.xml [new file with mode: 0644]
man/systemd-notify.xml [new file with mode: 0644]
man/systemd-nspawn.xml [new file with mode: 0644]
man/systemd-tmpfiles.xml [new file with mode: 0644]
man/systemd.automount.xml [new file with mode: 0644]
man/systemd.conf.xml [new file with mode: 0644]
man/systemd.device.xml [new file with mode: 0644]
man/systemd.exec.xml [new file with mode: 0644]
man/systemd.mount.xml [new file with mode: 0644]
man/systemd.path.xml [new file with mode: 0644]
man/systemd.service.xml [new file with mode: 0644]
man/systemd.snapshot.xml [new file with mode: 0644]
man/systemd.socket.xml [new file with mode: 0644]
man/systemd.special.xml [new file with mode: 0644]
man/systemd.swap.xml [new file with mode: 0644]
man/systemd.target.xml [new file with mode: 0644]
man/systemd.timer.xml [new file with mode: 0644]
man/systemd.unit.xml [new file with mode: 0644]
man/systemd.xml [new file with mode: 0644]
man/telinit.xml [new file with mode: 0644]
man/timezone.xml [new file with mode: 0644]
man/tmpfiles.d.xml [new file with mode: 0644]
man/vconsole.conf.xml [new file with mode: 0644]
po/.gitignore [new file with mode: 0644]
po/POTFILES.in [new file with mode: 0644]
po/POTFILES.skip [new file with mode: 0644]
po/pl.po [new file with mode: 0644]
src/.gitignore
src/99-systemd.rules.in [new file with mode: 0644]
src/Makefile [new file with mode: 0644]
src/ac-power.c [new file with mode: 0644]
src/acl-util.c [new file with mode: 0644]
src/acl-util.h [new file with mode: 0644]
src/ask-password-api.c [new file with mode: 0644]
src/ask-password-api.h [new file with mode: 0644]
src/ask-password.c [new file with mode: 0644]
src/automount.c [new file with mode: 0644]
src/automount.h [new file with mode: 0644]
src/binfmt/Makefile [new symlink]
src/binfmt/binfmt.c [new file with mode: 0644]
src/bridge.c [new file with mode: 0644]
src/build.h [new file with mode: 0644]
src/bus-errors.h [new file with mode: 0644]
src/cgls.c [new file with mode: 0644]
src/cgroup-attr.c [new file with mode: 0644]
src/cgroup-attr.h [new file with mode: 0644]
src/cgroup-show.c [new file with mode: 0644]
src/cgroup-show.h [new file with mode: 0644]
src/cgroup-util.c [new file with mode: 0644]
src/cgroup-util.h [new file with mode: 0644]
src/cgroup.c [new file with mode: 0644]
src/cgroup.h [new file with mode: 0644]
src/cgroups-agent.c [new file with mode: 0644]
src/cgtop.c [new file with mode: 0644]
src/condition.c [new file with mode: 0644]
src/condition.h [new file with mode: 0644]
src/conf-parser.c [new file with mode: 0644]
src/conf-parser.h [new file with mode: 0644]
src/cryptsetup/Makefile [new symlink]
src/cryptsetup/cryptsetup-generator.c [new file with mode: 0644]
src/cryptsetup/cryptsetup.c [new file with mode: 0644]
src/dbus-automount.c [new file with mode: 0644]
src/dbus-automount.h [new file with mode: 0644]
src/dbus-common.c [new file with mode: 0644]
src/dbus-common.h [new file with mode: 0644]
src/dbus-device.c [new file with mode: 0644]
src/dbus-device.h [new file with mode: 0644]
src/dbus-execute.c [new file with mode: 0644]
src/dbus-execute.h [new file with mode: 0644]
src/dbus-job.c [new file with mode: 0644]
src/dbus-job.h [new file with mode: 0644]
src/dbus-loop.c [new file with mode: 0644]
src/dbus-loop.h [new file with mode: 0644]
src/dbus-manager.c [new file with mode: 0644]
src/dbus-manager.h [new file with mode: 0644]
src/dbus-mount.c [new file with mode: 0644]
src/dbus-mount.h [new file with mode: 0644]
src/dbus-path.c [new file with mode: 0644]
src/dbus-path.h [new file with mode: 0644]
src/dbus-service.c [new file with mode: 0644]
src/dbus-service.h [new file with mode: 0644]
src/dbus-snapshot.c [new file with mode: 0644]
src/dbus-snapshot.h [new file with mode: 0644]
src/dbus-socket.c [new file with mode: 0644]
src/dbus-socket.h [new file with mode: 0644]
src/dbus-swap.c [new file with mode: 0644]
src/dbus-swap.h [new file with mode: 0644]
src/dbus-target.c [new file with mode: 0644]
src/dbus-target.h [new file with mode: 0644]
src/dbus-timer.c [new file with mode: 0644]
src/dbus-timer.h [new file with mode: 0644]
src/dbus-unit.c [new file with mode: 0644]
src/dbus-unit.h [new file with mode: 0644]
src/dbus.c [new file with mode: 0644]
src/dbus.h [new file with mode: 0644]
src/def.h [new file with mode: 0644]
src/detect-virt.c [new file with mode: 0644]
src/device.c [new file with mode: 0644]
src/device.h [new file with mode: 0644]
src/execute.c [new file with mode: 0644]
src/execute.h [new file with mode: 0644]
src/exit-status.c [new file with mode: 0644]
src/exit-status.h [new file with mode: 0644]
src/fdset.c [new file with mode: 0644]
src/fdset.h [new file with mode: 0644]
src/fsck.c [new file with mode: 0644]
src/getty-generator.c [new file with mode: 0644]
src/hashmap.c [new file with mode: 0644]
src/hashmap.h [new file with mode: 0644]
src/hostname-setup.c [new file with mode: 0644]
src/hostname-setup.h [new file with mode: 0644]
src/hostname/.gitignore [new file with mode: 0644]
src/hostname/Makefile [new symlink]
src/hostname/hostnamed.c [new file with mode: 0644]
src/hostname/org.freedesktop.hostname1.conf [new file with mode: 0644]
src/hostname/org.freedesktop.hostname1.policy.in [new file with mode: 0644]
src/hostname/org.freedesktop.hostname1.service [new file with mode: 0644]
src/ima-setup.c [new file with mode: 0644]
src/ima-setup.h [new file with mode: 0644]
src/initctl.c [new file with mode: 0644]
src/initreq.h [new file with mode: 0644]
src/install.c [new file with mode: 0644]
src/install.h [new file with mode: 0644]
src/ioprio.h [new file with mode: 0644]
src/job.c [new file with mode: 0644]
src/job.h [new file with mode: 0644]
src/journal/.gitignore [new file with mode: 0644]
src/journal/Makefile [new symlink]
src/journal/cat.c [new file with mode: 0644]
src/journal/compress.c [new file with mode: 0644]
src/journal/compress.h [new file with mode: 0644]
src/journal/coredump.c [new file with mode: 0644]
src/journal/journal-def.h [new file with mode: 0644]
src/journal/journal-file.c [new file with mode: 0644]
src/journal/journal-file.h [new file with mode: 0644]
src/journal/journal-internal.h [new file with mode: 0644]
src/journal/journal-rate-limit.c [new file with mode: 0644]
src/journal/journal-rate-limit.h [new file with mode: 0644]
src/journal/journal-send.c [new file with mode: 0644]
src/journal/journalctl.c [new file with mode: 0644]
src/journal/journald-gperf.gperf [new file with mode: 0644]
src/journal/journald.c [new file with mode: 0644]
src/journal/journald.conf [new file with mode: 0644]
src/journal/journald.h [new file with mode: 0644]
src/journal/libsystemd-journal.pc.in [new file with mode: 0644]
src/journal/libsystemd-journal.sym [new file with mode: 0644]
src/journal/lookup3.c [new file with mode: 0644]
src/journal/lookup3.h [new file with mode: 0644]
src/journal/sd-journal.c [new file with mode: 0644]
src/journal/sparse-endian.h [new file with mode: 0644]
src/journal/test-journal-send.c [new file with mode: 0644]
src/journal/test-journal.c [new file with mode: 0644]
src/kmod-setup.c [new file with mode: 0644]
src/kmod-setup.h [new file with mode: 0644]
src/label.c [new file with mode: 0644]
src/label.h [new file with mode: 0644]
src/libsystemd-daemon.pc.in [new file with mode: 0644]
src/libsystemd-daemon.sym [new file with mode: 0644]
src/libsystemd-id128.pc.in [new file with mode: 0644]
src/libsystemd-id128.sym [new file with mode: 0644]
src/linux/Makefile [new symlink]
src/linux/auto_dev-ioctl.h [new file with mode: 0644]
src/linux/fanotify.h [new file with mode: 0644]
src/list.h [new file with mode: 0644]
src/load-dropin.c [new file with mode: 0644]
src/load-dropin.h [new file with mode: 0644]
src/load-fragment-gperf.gperf.m4 [new file with mode: 0644]
src/load-fragment.c [new file with mode: 0644]
src/load-fragment.h [new file with mode: 0644]
src/locale-setup.c [new file with mode: 0644]
src/locale-setup.h [new file with mode: 0644]
src/locale/.gitignore [new file with mode: 0644]
src/locale/Makefile [new symlink]
src/locale/generate-kbd-model-map [new file with mode: 0755]
src/locale/kbd-model-map [new file with mode: 0644]
src/locale/localed.c [new file with mode: 0644]
src/locale/org.freedesktop.locale1.conf [new file with mode: 0644]
src/locale/org.freedesktop.locale1.policy.in [new file with mode: 0644]
src/locale/org.freedesktop.locale1.service [new file with mode: 0644]
src/log.c [new file with mode: 0644]
src/log.h [new file with mode: 0644]
src/login/.gitignore [new file with mode: 0644]
src/login/70-uaccess.rules [new file with mode: 0644]
src/login/71-seat.rules [new file with mode: 0644]
src/login/73-seat-late.rules.in [new file with mode: 0644]
src/login/Makefile [new symlink]
src/login/libsystemd-login.pc.in [new file with mode: 0644]
src/login/libsystemd-login.sym [new file with mode: 0644]
src/login/loginctl.c [new file with mode: 0644]
src/login/logind-acl.c [new file with mode: 0644]
src/login/logind-acl.h [new file with mode: 0644]
src/login/logind-dbus.c [new file with mode: 0644]
src/login/logind-device.c [new file with mode: 0644]
src/login/logind-device.h [new file with mode: 0644]
src/login/logind-gperf.gperf [new file with mode: 0644]
src/login/logind-seat-dbus.c [new file with mode: 0644]
src/login/logind-seat.c [new file with mode: 0644]
src/login/logind-seat.h [new file with mode: 0644]
src/login/logind-session-dbus.c [new file with mode: 0644]
src/login/logind-session.c [new file with mode: 0644]
src/login/logind-session.h [new file with mode: 0644]
src/login/logind-user-dbus.c [new file with mode: 0644]
src/login/logind-user.c [new file with mode: 0644]
src/login/logind-user.h [new file with mode: 0644]
src/login/logind.c [new file with mode: 0644]
src/login/logind.conf [new file with mode: 0644]
src/login/logind.h [new file with mode: 0644]
src/login/multi-seat-x.c [new file with mode: 0644]
src/login/org.freedesktop.login1.conf [new file with mode: 0644]
src/login/org.freedesktop.login1.policy.in [new file with mode: 0644]
src/login/org.freedesktop.login1.service [new file with mode: 0644]
src/login/pam-module.c [new file with mode: 0644]
src/login/sd-login.c [new file with mode: 0644]
src/login/sysfs-show.c [new file with mode: 0644]
src/login/test-login.c [new file with mode: 0644]
src/login/uaccess.c [new file with mode: 0644]
src/login/user-sessions.c [new file with mode: 0644]
src/logs-show.c [new file with mode: 0644]
src/logs-show.h [new file with mode: 0644]
src/loopback-setup.c [new file with mode: 0644]
src/loopback-setup.h [new file with mode: 0644]
src/machine-id-main.c [new file with mode: 0644]
src/machine-id-setup.c [new file with mode: 0644]
src/machine-id-setup.h [new file with mode: 0644]
src/macro.h [new file with mode: 0644]
src/main.c [new file with mode: 0644]
src/manager.c [new file with mode: 0644]
src/manager.h [new file with mode: 0644]
src/missing.h [new file with mode: 0644]
src/modules-load.c [new file with mode: 0644]
src/mount-setup.c [new file with mode: 0644]
src/mount-setup.h [new file with mode: 0644]
src/mount.c [new file with mode: 0644]
src/mount.h [new file with mode: 0644]
src/namespace.c [new file with mode: 0644]
src/namespace.h [new file with mode: 0644]
src/notify.c [new file with mode: 0644]
src/nspawn.c [new file with mode: 0644]
src/org.freedesktop.systemd1.conf [new file with mode: 0644]
src/org.freedesktop.systemd1.policy.in.in [new file with mode: 0644]
src/org.freedesktop.systemd1.service [new file with mode: 0644]
src/pager.c [new file with mode: 0644]
src/pager.h [new file with mode: 0644]
src/path-lookup.c [new file with mode: 0644]
src/path-lookup.h [new file with mode: 0644]
src/path.c [new file with mode: 0644]
src/path.h [new file with mode: 0644]
src/polkit.c [new file with mode: 0644]
src/polkit.h [new file with mode: 0644]
src/quotacheck.c [new file with mode: 0644]
src/random-seed.c [new file with mode: 0644]
src/ratelimit.c [new file with mode: 0644]
src/ratelimit.h [new file with mode: 0644]
src/rc-local-generator.c [new file with mode: 0644]
src/readahead/Makefile [new symlink]
src/readahead/readahead-collect.c [new file with mode: 0644]
src/readahead/readahead-common.c [new file with mode: 0644]
src/readahead/readahead-common.h [new file with mode: 0644]
src/readahead/readahead-replay.c [new file with mode: 0644]
src/readahead/sd-readahead.c [new file with mode: 0644]
src/remount-api-vfs.c [new file with mode: 0644]
src/reply-password.c [new file with mode: 0644]
src/sd-id128.c [new file with mode: 0644]
src/securebits.h [new file with mode: 0644]
src/selinux-setup.c [new file with mode: 0644]
src/selinux-setup.h [new file with mode: 0644]
src/service.c [new file with mode: 0644]
src/service.h [new file with mode: 0644]
src/set.c [new file with mode: 0644]
src/set.h [new file with mode: 0644]
src/shutdown.c [new file with mode: 0644]
src/shutdownd.c [new file with mode: 0644]
src/shutdownd.h [new file with mode: 0644]
src/snapshot.c [new file with mode: 0644]
src/snapshot.h [new file with mode: 0644]
src/socket-util.c [new file with mode: 0644]
src/socket-util.h [new file with mode: 0644]
src/socket.c [new file with mode: 0644]
src/socket.h [new file with mode: 0644]
src/spawn-agent.c [new file with mode: 0644]
src/spawn-agent.h [new file with mode: 0644]
src/special.h [new file with mode: 0644]
src/specifier.c [new file with mode: 0644]
src/specifier.h [new file with mode: 0644]
src/strv.c [new file with mode: 0644]
src/strv.h [new file with mode: 0644]
src/swap.c [new file with mode: 0644]
src/swap.h [new file with mode: 0644]
src/sysctl.c [new file with mode: 0644]
src/sysfs-show.h [new file with mode: 0644]
src/system.conf [new file with mode: 0644]
src/systemctl.c [new file with mode: 0644]
src/systemd-analyze [new file with mode: 0755]
src/systemd-bash-completion.sh [new file with mode: 0644]
src/systemd.pc.in [new file with mode: 0644]
src/systemd/Makefile [new symlink]
src/systemd/sd-daemon.h [moved from src/sd-daemon.h with 100% similarity]
src/systemd/sd-id128.h [new file with mode: 0644]
src/systemd/sd-journal.h [new file with mode: 0644]
src/systemd/sd-login.h [new file with mode: 0644]
src/systemd/sd-messages.h [new file with mode: 0644]
src/systemd/sd-readahead.h [new file with mode: 0644]
src/target.c [new file with mode: 0644]
src/target.h [new file with mode: 0644]
src/tcpwrap.c [new file with mode: 0644]
src/tcpwrap.h [new file with mode: 0644]
src/test-cgroup.c [new file with mode: 0644]
src/test-daemon.c [new file with mode: 0644]
src/test-engine.c [new file with mode: 0644]
src/test-env-replace.c [new file with mode: 0644]
src/test-hostname.c [new file with mode: 0644]
src/test-id128.c [new file with mode: 0644]
src/test-install.c [new file with mode: 0644]
src/test-job-type.c [new file with mode: 0644]
src/test-loopback.c [new file with mode: 0644]
src/test-ns.c [new file with mode: 0644]
src/test-strv.c [new file with mode: 0644]
src/timedate/.gitignore [new file with mode: 0644]
src/timedate/Makefile [new symlink]
src/timedate/org.freedesktop.timedate1.conf [new file with mode: 0644]
src/timedate/org.freedesktop.timedate1.policy.in [new file with mode: 0644]
src/timedate/org.freedesktop.timedate1.service [new file with mode: 0644]
src/timedate/timedated.c [new file with mode: 0644]
src/timer.c [new file with mode: 0644]
src/timer.h [new file with mode: 0644]
src/timestamp.c [new file with mode: 0644]
src/tmpfiles.c [new file with mode: 0644]
src/tty-ask-password-agent.c [new file with mode: 0644]
src/udev/.gitignore [new file with mode: 0644]
src/udev/.vimrc [new file with mode: 0644]
src/udev/COPYING [moved from COPYING with 100% similarity]
src/udev/ChangeLog [moved from ChangeLog with 100% similarity]
src/udev/INSTALL [moved from INSTALL with 100% similarity]
src/udev/Makefile.am [new file with mode: 0644]
src/udev/NEWS [new file with mode: 0644]
src/udev/README [new file with mode: 0644]
src/udev/TODO [new file with mode: 0644]
src/udev/autogen.sh [new file with mode: 0755]
src/udev/configure.ac [new file with mode: 0644]
src/udev/m4/.gitignore [new file with mode: 0644]
src/udev/rules/42-usb-hid-pm.rules [moved from rules/42-usb-hid-pm.rules with 100% similarity]
src/udev/rules/50-udev-default.rules [moved from rules/50-udev-default.rules with 100% similarity]
src/udev/rules/60-persistent-alsa.rules [moved from rules/60-persistent-alsa.rules with 100% similarity]
src/udev/rules/60-persistent-input.rules [moved from rules/60-persistent-input.rules with 100% similarity]
src/udev/rules/60-persistent-serial.rules [moved from rules/60-persistent-serial.rules with 100% similarity]
src/udev/rules/60-persistent-storage-tape.rules [moved from rules/60-persistent-storage-tape.rules with 100% similarity]
src/udev/rules/60-persistent-storage.rules [moved from rules/60-persistent-storage.rules with 100% similarity]
src/udev/rules/75-net-description.rules [moved from rules/75-net-description.rules with 100% similarity]
src/udev/rules/75-tty-description.rules [moved from rules/75-tty-description.rules with 100% similarity]
src/udev/rules/78-sound-card.rules [moved from rules/78-sound-card.rules with 100% similarity]
src/udev/rules/80-drivers.rules [moved from rules/80-drivers.rules with 100% similarity]
src/udev/rules/95-udev-late.rules [moved from rules/95-udev-late.rules with 100% similarity]
src/udev/src/.gitignore [new file with mode: 0644]
src/udev/src/COPYING [moved from src/COPYING with 100% similarity]
src/udev/src/accelerometer/61-accelerometer.rules [moved from src/accelerometer/61-accelerometer.rules with 100% similarity]
src/udev/src/accelerometer/accelerometer.c [moved from src/accelerometer/accelerometer.c with 100% similarity]
src/udev/src/ata_id/ata_id.c [moved from src/ata_id/ata_id.c with 100% similarity]
src/udev/src/cdrom_id/60-cdrom_id.rules [moved from src/cdrom_id/60-cdrom_id.rules with 100% similarity]
src/udev/src/cdrom_id/cdrom_id.c [moved from src/cdrom_id/cdrom_id.c with 100% similarity]
src/udev/src/collect/collect.c [moved from src/collect/collect.c with 100% similarity]
src/udev/src/docs/.gitignore [moved from src/docs/.gitignore with 100% similarity]
src/udev/src/docs/Makefile.am [moved from src/docs/Makefile.am with 100% similarity]
src/udev/src/docs/libudev-docs.xml [moved from src/docs/libudev-docs.xml with 100% similarity]
src/udev/src/docs/libudev-sections.txt [moved from src/docs/libudev-sections.txt with 100% similarity]
src/udev/src/docs/libudev.types [moved from src/docs/libudev.types with 100% similarity]
src/udev/src/docs/version.xml.in [moved from src/docs/version.xml.in with 100% similarity]
src/udev/src/floppy/60-floppy.rules [moved from src/floppy/60-floppy.rules with 100% similarity]
src/udev/src/floppy/create_floppy_devices.c [moved from src/floppy/create_floppy_devices.c with 100% similarity]
src/udev/src/gudev/.gitignore [moved from src/gudev/.gitignore with 100% similarity]
src/udev/src/gudev/COPYING [moved from src/gudev/COPYING with 100% similarity]
src/udev/src/gudev/docs/.gitignore [moved from src/gudev/docs/.gitignore with 100% similarity]
src/udev/src/gudev/docs/Makefile.am [moved from src/gudev/docs/Makefile.am with 100% similarity]
src/udev/src/gudev/docs/gudev-docs.xml [moved from src/gudev/docs/gudev-docs.xml with 100% similarity]
src/udev/src/gudev/docs/gudev-sections.txt [moved from src/gudev/docs/gudev-sections.txt with 100% similarity]
src/udev/src/gudev/docs/gudev.types [moved from src/gudev/docs/gudev.types with 100% similarity]
src/udev/src/gudev/docs/version.xml.in [moved from src/gudev/docs/version.xml.in with 100% similarity]
src/udev/src/gudev/gjs-example.js [moved from src/gudev/gjs-example.js with 100% similarity]
src/udev/src/gudev/gudev-1.0.pc.in [moved from src/gudev/gudev-1.0.pc.in with 100% similarity]
src/udev/src/gudev/gudev.h [moved from src/gudev/gudev.h with 100% similarity]
src/udev/src/gudev/gudevclient.c [moved from src/gudev/gudevclient.c with 100% similarity]
src/udev/src/gudev/gudevclient.h [moved from src/gudev/gudevclient.h with 100% similarity]
src/udev/src/gudev/gudevdevice.c [moved from src/gudev/gudevdevice.c with 100% similarity]
src/udev/src/gudev/gudevdevice.h [moved from src/gudev/gudevdevice.h with 100% similarity]
src/udev/src/gudev/gudevenumerator.c [moved from src/gudev/gudevenumerator.c with 100% similarity]
src/udev/src/gudev/gudevenumerator.h [moved from src/gudev/gudevenumerator.h with 100% similarity]
src/udev/src/gudev/gudevenums.h [moved from src/gudev/gudevenums.h with 100% similarity]
src/udev/src/gudev/gudevenumtypes.c.template [moved from src/gudev/gudevenumtypes.c.template with 100% similarity]
src/udev/src/gudev/gudevenumtypes.h.template [moved from src/gudev/gudevenumtypes.h.template with 100% similarity]
src/udev/src/gudev/gudevmarshal.list [moved from src/gudev/gudevmarshal.list with 100% similarity]
src/udev/src/gudev/gudevprivate.h [moved from src/gudev/gudevprivate.h with 100% similarity]
src/udev/src/gudev/gudevtypes.h [moved from src/gudev/gudevtypes.h with 100% similarity]
src/udev/src/gudev/seed-example-enum.js [moved from src/gudev/seed-example-enum.js with 100% similarity]
src/udev/src/gudev/seed-example.js [moved from src/gudev/seed-example.js with 100% similarity]
src/udev/src/keymap/.gitignore [moved from src/keymap/.gitignore with 100% similarity]
src/udev/src/keymap/95-keyboard-force-release.rules [moved from src/keymap/95-keyboard-force-release.rules with 100% similarity]
src/udev/src/keymap/95-keymap.rules [moved from src/keymap/95-keymap.rules with 100% similarity]
src/udev/src/keymap/README.keymap.txt [moved from src/keymap/README.keymap.txt with 100% similarity]
src/udev/src/keymap/check-keymaps.sh [moved from src/keymap/check-keymaps.sh with 100% similarity]
src/udev/src/keymap/findkeyboards [moved from src/keymap/findkeyboards with 100% similarity]
src/udev/src/keymap/force-release-maps/common-volume-keys [moved from src/keymap/force-release-maps/common-volume-keys with 100% similarity]
src/udev/src/keymap/force-release-maps/dell-touchpad [moved from src/keymap/force-release-maps/dell-touchpad with 100% similarity]
src/udev/src/keymap/force-release-maps/hp-other [moved from src/keymap/force-release-maps/hp-other with 100% similarity]
src/udev/src/keymap/force-release-maps/samsung-90x3a [moved from src/keymap/force-release-maps/samsung-90x3a with 100% similarity]
src/udev/src/keymap/force-release-maps/samsung-other [moved from src/keymap/force-release-maps/samsung-other with 100% similarity]
src/udev/src/keymap/keyboard-force-release.sh.in [moved from src/keymap/keyboard-force-release.sh.in with 100% similarity]
src/udev/src/keymap/keymap.c [moved from src/keymap/keymap.c with 100% similarity]
src/udev/src/keymap/keymaps/acer [moved from src/keymap/keymaps/acer with 100% similarity]
src/udev/src/keymap/keymaps/acer-aspire_5720 [moved from src/keymap/keymaps/acer-aspire_5720 with 100% similarity]
src/udev/src/keymap/keymaps/acer-aspire_5920g [moved from src/keymap/keymaps/acer-aspire_5920g with 100% similarity]
src/udev/src/keymap/keymaps/acer-aspire_6920 [moved from src/keymap/keymaps/acer-aspire_6920 with 100% similarity]
src/udev/src/keymap/keymaps/acer-aspire_8930 [moved from src/keymap/keymaps/acer-aspire_8930 with 100% similarity]
src/udev/src/keymap/keymaps/acer-travelmate_c300 [moved from src/keymap/keymaps/acer-travelmate_c300 with 100% similarity]
src/udev/src/keymap/keymaps/asus [moved from src/keymap/keymaps/asus with 100% similarity]
src/udev/src/keymap/keymaps/compaq-e_evo [moved from src/keymap/keymaps/compaq-e_evo with 100% similarity]
src/udev/src/keymap/keymaps/dell [moved from src/keymap/keymaps/dell with 100% similarity]
src/udev/src/keymap/keymaps/dell-latitude-xt2 [moved from src/keymap/keymaps/dell-latitude-xt2 with 100% similarity]
src/udev/src/keymap/keymaps/everex-xt5000 [moved from src/keymap/keymaps/everex-xt5000 with 100% similarity]
src/udev/src/keymap/keymaps/fujitsu-amilo_li_2732 [moved from src/keymap/keymaps/fujitsu-amilo_li_2732 with 100% similarity]
src/udev/src/keymap/keymaps/fujitsu-amilo_pa_2548 [moved from src/keymap/keymaps/fujitsu-amilo_pa_2548 with 100% similarity]
src/udev/src/keymap/keymaps/fujitsu-amilo_pro_edition_v3505 [moved from src/keymap/keymaps/fujitsu-amilo_pro_edition_v3505 with 100% similarity]
src/udev/src/keymap/keymaps/fujitsu-amilo_pro_v3205 [moved from src/keymap/keymaps/fujitsu-amilo_pro_v3205 with 100% similarity]
src/udev/src/keymap/keymaps/fujitsu-amilo_si_1520 [moved from src/keymap/keymaps/fujitsu-amilo_si_1520 with 100% similarity]
src/udev/src/keymap/keymaps/fujitsu-esprimo_mobile_v5 [moved from src/keymap/keymaps/fujitsu-esprimo_mobile_v5 with 100% similarity]
src/udev/src/keymap/keymaps/fujitsu-esprimo_mobile_v6 [moved from src/keymap/keymaps/fujitsu-esprimo_mobile_v6 with 100% similarity]
src/udev/src/keymap/keymaps/genius-slimstar-320 [moved from src/keymap/keymaps/genius-slimstar-320 with 100% similarity]
src/udev/src/keymap/keymaps/hewlett-packard [moved from src/keymap/keymaps/hewlett-packard with 100% similarity]
src/udev/src/keymap/keymaps/hewlett-packard-2510p_2530p [moved from src/keymap/keymaps/hewlett-packard-2510p_2530p with 100% similarity]
src/udev/src/keymap/keymaps/hewlett-packard-compaq_elitebook [moved from src/keymap/keymaps/hewlett-packard-compaq_elitebook with 100% similarity]
src/udev/src/keymap/keymaps/hewlett-packard-pavilion [moved from src/keymap/keymaps/hewlett-packard-pavilion with 100% similarity]
src/udev/src/keymap/keymaps/hewlett-packard-presario-2100 [moved from src/keymap/keymaps/hewlett-packard-presario-2100 with 100% similarity]
src/udev/src/keymap/keymaps/hewlett-packard-tablet [moved from src/keymap/keymaps/hewlett-packard-tablet with 100% similarity]
src/udev/src/keymap/keymaps/hewlett-packard-tx2 [moved from src/keymap/keymaps/hewlett-packard-tx2 with 100% similarity]
src/udev/src/keymap/keymaps/ibm-thinkpad-usb-keyboard-trackpoint [moved from src/keymap/keymaps/ibm-thinkpad-usb-keyboard-trackpoint with 100% similarity]
src/udev/src/keymap/keymaps/inventec-symphony_6.0_7.0 [moved from src/keymap/keymaps/inventec-symphony_6.0_7.0 with 100% similarity]
src/udev/src/keymap/keymaps/lenovo-3000 [moved from src/keymap/keymaps/lenovo-3000 with 100% similarity]
src/udev/src/keymap/keymaps/lenovo-ideapad [moved from src/keymap/keymaps/lenovo-ideapad with 100% similarity]
src/udev/src/keymap/keymaps/lenovo-thinkpad-usb-keyboard-trackpoint [moved from src/keymap/keymaps/lenovo-thinkpad-usb-keyboard-trackpoint with 100% similarity]
src/udev/src/keymap/keymaps/lenovo-thinkpad_x200_tablet [moved from src/keymap/keymaps/lenovo-thinkpad_x200_tablet with 100% similarity]
src/udev/src/keymap/keymaps/lenovo-thinkpad_x6_tablet [moved from src/keymap/keymaps/lenovo-thinkpad_x6_tablet with 100% similarity]
src/udev/src/keymap/keymaps/lg-x110 [moved from src/keymap/keymaps/lg-x110 with 100% similarity]
src/udev/src/keymap/keymaps/logitech-wave [moved from src/keymap/keymaps/logitech-wave with 100% similarity]
src/udev/src/keymap/keymaps/logitech-wave-cordless [moved from src/keymap/keymaps/logitech-wave-cordless with 100% similarity]
src/udev/src/keymap/keymaps/logitech-wave-pro-cordless [moved from src/keymap/keymaps/logitech-wave-pro-cordless with 100% similarity]
src/udev/src/keymap/keymaps/maxdata-pro_7000 [moved from src/keymap/keymaps/maxdata-pro_7000 with 100% similarity]
src/udev/src/keymap/keymaps/medion-fid2060 [moved from src/keymap/keymaps/medion-fid2060 with 100% similarity]
src/udev/src/keymap/keymaps/medionnb-a555 [moved from src/keymap/keymaps/medionnb-a555 with 100% similarity]
src/udev/src/keymap/keymaps/micro-star [moved from src/keymap/keymaps/micro-star with 100% similarity]
src/udev/src/keymap/keymaps/module-asus-w3j [moved from src/keymap/keymaps/module-asus-w3j with 100% similarity]
src/udev/src/keymap/keymaps/module-ibm [moved from src/keymap/keymaps/module-ibm with 100% similarity]
src/udev/src/keymap/keymaps/module-lenovo [moved from src/keymap/keymaps/module-lenovo with 100% similarity]
src/udev/src/keymap/keymaps/module-sony [moved from src/keymap/keymaps/module-sony with 100% similarity]
src/udev/src/keymap/keymaps/module-sony-old [moved from src/keymap/keymaps/module-sony-old with 100% similarity]
src/udev/src/keymap/keymaps/module-sony-vgn [moved from src/keymap/keymaps/module-sony-vgn with 100% similarity]
src/udev/src/keymap/keymaps/olpc-xo [moved from src/keymap/keymaps/olpc-xo with 100% similarity]
src/udev/src/keymap/keymaps/onkyo [moved from src/keymap/keymaps/onkyo with 100% similarity]
src/udev/src/keymap/keymaps/oqo-model2 [moved from src/keymap/keymaps/oqo-model2 with 100% similarity]
src/udev/src/keymap/keymaps/samsung-90x3a [moved from src/keymap/keymaps/samsung-90x3a with 100% similarity]
src/udev/src/keymap/keymaps/samsung-other [moved from src/keymap/keymaps/samsung-other with 100% similarity]
src/udev/src/keymap/keymaps/samsung-sq1us [moved from src/keymap/keymaps/samsung-sq1us with 100% similarity]
src/udev/src/keymap/keymaps/samsung-sx20s [moved from src/keymap/keymaps/samsung-sx20s with 100% similarity]
src/udev/src/keymap/keymaps/toshiba-satellite_a100 [moved from src/keymap/keymaps/toshiba-satellite_a100 with 100% similarity]
src/udev/src/keymap/keymaps/toshiba-satellite_a110 [moved from src/keymap/keymaps/toshiba-satellite_a110 with 100% similarity]
src/udev/src/keymap/keymaps/toshiba-satellite_m30x [moved from src/keymap/keymaps/toshiba-satellite_m30x with 100% similarity]
src/udev/src/keymap/keymaps/zepto-znote [moved from src/keymap/keymaps/zepto-znote with 100% similarity]
src/udev/src/libudev-device-private.c [moved from src/libudev-device-private.c with 100% similarity]
src/udev/src/libudev-device.c [moved from src/libudev-device.c with 100% similarity]
src/udev/src/libudev-enumerate.c [moved from src/libudev-enumerate.c with 100% similarity]
src/udev/src/libudev-list.c [moved from src/libudev-list.c with 100% similarity]
src/udev/src/libudev-monitor.c [moved from src/libudev-monitor.c with 100% similarity]
src/udev/src/libudev-private.h [moved from src/libudev-private.h with 100% similarity]
src/udev/src/libudev-queue-private.c [moved from src/libudev-queue-private.c with 100% similarity]
src/udev/src/libudev-queue.c [moved from src/libudev-queue.c with 100% similarity]
src/udev/src/libudev-selinux-private.c [moved from src/libudev-selinux-private.c with 100% similarity]
src/udev/src/libudev-util-private.c [moved from src/libudev-util-private.c with 100% similarity]
src/udev/src/libudev-util.c [moved from src/libudev-util.c with 100% similarity]
src/udev/src/libudev.c [moved from src/libudev.c with 100% similarity]
src/udev/src/libudev.h [moved from src/libudev.h with 100% similarity]
src/udev/src/libudev.pc.in [moved from src/libudev.pc.in with 100% similarity]
src/udev/src/mtd_probe/75-probe_mtd.rules [moved from src/mtd_probe/75-probe_mtd.rules with 100% similarity]
src/udev/src/mtd_probe/mtd_probe.c [moved from src/mtd_probe/mtd_probe.c with 100% similarity]
src/udev/src/mtd_probe/mtd_probe.h [moved from src/mtd_probe/mtd_probe.h with 100% similarity]
src/udev/src/mtd_probe/probe_smartmedia.c [moved from src/mtd_probe/probe_smartmedia.c with 100% similarity]
src/udev/src/rule_generator/75-cd-aliases-generator.rules [moved from src/rule_generator/75-cd-aliases-generator.rules with 100% similarity]
src/udev/src/rule_generator/75-persistent-net-generator.rules [moved from src/rule_generator/75-persistent-net-generator.rules with 100% similarity]
src/udev/src/rule_generator/rule_generator.functions [moved from src/rule_generator/rule_generator.functions with 100% similarity]
src/udev/src/rule_generator/write_cd_rules [moved from src/rule_generator/write_cd_rules with 100% similarity]
src/udev/src/rule_generator/write_net_rules [moved from src/rule_generator/write_net_rules with 100% similarity]
src/udev/src/scsi_id/.gitignore [moved from src/scsi_id/.gitignore with 100% similarity]
src/udev/src/scsi_id/README [moved from src/scsi_id/README with 100% similarity]
src/udev/src/scsi_id/scsi.h [moved from src/scsi_id/scsi.h with 100% similarity]
src/udev/src/scsi_id/scsi_id.8 [moved from src/scsi_id/scsi_id.8 with 100% similarity]
src/udev/src/scsi_id/scsi_id.c [moved from src/scsi_id/scsi_id.c with 100% similarity]
src/udev/src/scsi_id/scsi_id.h [moved from src/scsi_id/scsi_id.h with 100% similarity]
src/udev/src/scsi_id/scsi_serial.c [moved from src/scsi_id/scsi_serial.c with 100% similarity]
src/udev/src/sd-daemon.c [new file with mode: 0644]
src/udev/src/sd-daemon.h [new file with mode: 0644]
src/udev/src/test-libudev.c [moved from src/test-libudev.c with 100% similarity]
src/udev/src/test-udev.c [moved from src/test-udev.c with 100% similarity]
src/udev/src/udev-builtin-blkid.c [moved from src/udev-builtin-blkid.c with 100% similarity]
src/udev/src/udev-builtin-firmware.c [moved from src/udev-builtin-firmware.c with 100% similarity]
src/udev/src/udev-builtin-hwdb.c [moved from src/udev-builtin-hwdb.c with 100% similarity]
src/udev/src/udev-builtin-input_id.c [moved from src/udev-builtin-input_id.c with 100% similarity]
src/udev/src/udev-builtin-kmod.c [moved from src/udev-builtin-kmod.c with 100% similarity]
src/udev/src/udev-builtin-path_id.c [moved from src/udev-builtin-path_id.c with 100% similarity]
src/udev/src/udev-builtin-usb_id.c [moved from src/udev-builtin-usb_id.c with 100% similarity]
src/udev/src/udev-builtin.c [moved from src/udev-builtin.c with 100% similarity]
src/udev/src/udev-control.socket [moved from src/udev-control.socket with 100% similarity]
src/udev/src/udev-ctrl.c [moved from src/udev-ctrl.c with 100% similarity]
src/udev/src/udev-event.c [moved from src/udev-event.c with 100% similarity]
src/udev/src/udev-kernel.socket [moved from src/udev-kernel.socket with 100% similarity]
src/udev/src/udev-node.c [moved from src/udev-node.c with 100% similarity]
src/udev/src/udev-rules.c [moved from src/udev-rules.c with 100% similarity]
src/udev/src/udev-settle.service.in [moved from src/udev-settle.service.in with 100% similarity]
src/udev/src/udev-trigger.service.in [moved from src/udev-trigger.service.in with 100% similarity]
src/udev/src/udev-watch.c [moved from src/udev-watch.c with 100% similarity]
src/udev/src/udev.conf [moved from src/udev.conf with 100% similarity]
src/udev/src/udev.h [moved from src/udev.h with 100% similarity]
src/udev/src/udev.pc.in [moved from src/udev.pc.in with 100% similarity]
src/udev/src/udev.service.in [moved from src/udev.service.in with 100% similarity]
src/udev/src/udev.xml [moved from src/udev.xml with 100% similarity]
src/udev/src/udevadm-control.c [moved from src/udevadm-control.c with 100% similarity]
src/udev/src/udevadm-info.c [moved from src/udevadm-info.c with 100% similarity]
src/udev/src/udevadm-monitor.c [moved from src/udevadm-monitor.c with 100% similarity]
src/udev/src/udevadm-settle.c [moved from src/udevadm-settle.c with 100% similarity]
src/udev/src/udevadm-test-builtin.c [moved from src/udevadm-test-builtin.c with 100% similarity]
src/udev/src/udevadm-test.c [moved from src/udevadm-test.c with 100% similarity]
src/udev/src/udevadm-trigger.c [moved from src/udevadm-trigger.c with 100% similarity]
src/udev/src/udevadm.c [moved from src/udevadm.c with 100% similarity]
src/udev/src/udevadm.xml [moved from src/udevadm.xml with 100% similarity]
src/udev/src/udevd.c [moved from src/udevd.c with 100% similarity]
src/udev/src/udevd.xml [moved from src/udevd.xml with 100% similarity]
src/udev/src/v4l_id/60-persistent-v4l.rules [moved from src/v4l_id/60-persistent-v4l.rules with 100% similarity]
src/udev/src/v4l_id/v4l_id.c [moved from src/v4l_id/v4l_id.c with 100% similarity]
src/udev/test/.gitignore [moved from test/.gitignore with 100% similarity]
src/udev/test/rule-syntax-check.py [moved from test/rule-syntax-check.py with 100% similarity]
src/udev/test/rules-test.sh [moved from test/rules-test.sh with 100% similarity]
src/udev/test/sys.tar.xz [moved from test/sys.tar.xz with 100% similarity]
src/udev/test/udev-test.pl [moved from test/udev-test.pl with 100% similarity]
src/umount.c [new file with mode: 0644]
src/umount.h [new file with mode: 0644]
src/unit-name.c [new file with mode: 0644]
src/unit-name.h [new file with mode: 0644]
src/unit.c [new file with mode: 0644]
src/unit.h [new file with mode: 0644]
src/update-utmp.c [new file with mode: 0644]
src/user.conf [new file with mode: 0644]
src/utf8.c [new file with mode: 0644]
src/utf8.h [new file with mode: 0644]
src/util.c [new file with mode: 0644]
src/util.h [new file with mode: 0644]
src/utmp-wtmp.c [new file with mode: 0644]
src/utmp-wtmp.h [new file with mode: 0644]
src/vconsole/Makefile [new symlink]
src/vconsole/vconsole-setup.c [new file with mode: 0644]
src/virt.c [new file with mode: 0644]
src/virt.h [new file with mode: 0644]
sysctl.d/.gitignore [new file with mode: 0644]
sysctl.d/Makefile [new symlink]
sysctl.d/coredump.conf.in [new file with mode: 0644]
test/Makefile [new file with mode: 0644]
test/a.service [new file with mode: 0644]
test/b.service [new file with mode: 0644]
test/c.service [new file with mode: 0644]
test/d.service [new file with mode: 0644]
test/e.service [new file with mode: 0644]
test/f.service [new file with mode: 0644]
test/g.service [new file with mode: 0644]
test/h.service [new file with mode: 0644]
tmpfiles.d/Makefile [new symlink]
tmpfiles.d/legacy.conf [new file with mode: 0644]
tmpfiles.d/systemd.conf [new file with mode: 0644]
tmpfiles.d/tmp.conf [new file with mode: 0644]
tmpfiles.d/x11.conf [new file with mode: 0644]
units/.gitignore [new file with mode: 0644]
units/Makefile [new symlink]
units/basic.target [new file with mode: 0644]
units/bluetooth.target [new file with mode: 0644]
units/console-shell.service.m4 [new file with mode: 0644]
units/cryptsetup.target [new file with mode: 0644]
units/dev-hugepages.mount [new file with mode: 0644]
units/dev-mqueue.mount [new file with mode: 0644]
units/emergency.service [new file with mode: 0644]
units/emergency.target [new file with mode: 0644]
units/fedora/Makefile [new symlink]
units/fedora/halt-local.service [new file with mode: 0644]
units/fedora/prefdm.service [new file with mode: 0644]
units/fedora/rc-local.service [new file with mode: 0644]
units/final.target [new file with mode: 0644]
units/frugalware/display-manager.service [new file with mode: 0644]
units/fsck-root.service.in [new file with mode: 0644]
units/fsck@.service.in [new file with mode: 0644]
units/getty.target [new file with mode: 0644]
units/getty@.service.m4 [new file with mode: 0644]
units/graphical.target [new file with mode: 0644]
units/halt.service.in [new file with mode: 0644]
units/halt.target [new file with mode: 0644]
units/http-daemon.target [new file with mode: 0644]
units/kexec.service.in [new file with mode: 0644]
units/kexec.target [new file with mode: 0644]
units/local-fs-pre.target [new file with mode: 0644]
units/local-fs.target [new file with mode: 0644]
units/mageia/prefdm.service [new file with mode: 0644]
units/mail-transfer-agent.target [new file with mode: 0644]
units/mandriva/prefdm.service [new file with mode: 0644]
units/multi-user.target [new file with mode: 0644]
units/network.target [new file with mode: 0644]
units/nss-lookup.target [new file with mode: 0644]
units/plymouth-halt.service [new file with mode: 0644]
units/plymouth-kexec.service [new file with mode: 0644]
units/plymouth-poweroff.service [new file with mode: 0644]
units/plymouth-quit-wait.service [new file with mode: 0644]
units/plymouth-quit.service [new file with mode: 0644]
units/plymouth-read-write.service [new file with mode: 0644]
units/plymouth-reboot.service [new file with mode: 0644]
units/plymouth-start.service [new file with mode: 0644]
units/poweroff.service.in [new file with mode: 0644]
units/poweroff.target [new file with mode: 0644]
units/printer.target [new file with mode: 0644]
units/proc-sys-fs-binfmt_misc.automount [new file with mode: 0644]
units/proc-sys-fs-binfmt_misc.mount [new file with mode: 0644]
units/quotacheck.service.in [new file with mode: 0644]
units/quotaon.service [new file with mode: 0644]
units/reboot.service.in [new file with mode: 0644]
units/reboot.target [new file with mode: 0644]
units/remote-fs-pre.target [new file with mode: 0644]
units/remote-fs.target [new file with mode: 0644]
units/remount-rootfs.service [new file with mode: 0644]
units/rescue.service.m4 [new file with mode: 0644]
units/rescue.target [new file with mode: 0644]
units/rpcbind.target [new file with mode: 0644]
units/serial-getty@.service.m4 [new file with mode: 0644]
units/shutdown.target [new file with mode: 0644]
units/sigpwr.target [new file with mode: 0644]
units/smartcard.target [new file with mode: 0644]
units/sockets.target [new file with mode: 0644]
units/sound.target [new file with mode: 0644]
units/suse/halt-local.service [new file with mode: 0644]
units/suse/rc-local.service [new file with mode: 0644]
units/swap.target [new file with mode: 0644]
units/sys-fs-fuse-connections.mount [new file with mode: 0644]
units/sys-kernel-config.mount [new file with mode: 0644]
units/sys-kernel-debug.mount [new file with mode: 0644]
units/sysinit.target [new file with mode: 0644]
units/syslog.socket [new file with mode: 0644]
units/syslog.target [new file with mode: 0644]
units/systemd-ask-password-console.path [new file with mode: 0644]
units/systemd-ask-password-console.service.in [new file with mode: 0644]
units/systemd-ask-password-plymouth.path [new file with mode: 0644]
units/systemd-ask-password-plymouth.service.in [new file with mode: 0644]
units/systemd-ask-password-wall.path [new file with mode: 0644]
units/systemd-ask-password-wall.service.in [new file with mode: 0644]
units/systemd-binfmt.service.in [new file with mode: 0644]
units/systemd-hostnamed.service.in [new file with mode: 0644]
units/systemd-initctl.service.in [new file with mode: 0644]
units/systemd-initctl.socket [new file with mode: 0644]
units/systemd-journald.service.in [new file with mode: 0644]
units/systemd-journald.socket [new file with mode: 0644]
units/systemd-localed.service.in [new file with mode: 0644]
units/systemd-logind.service.in [new file with mode: 0644]
units/systemd-modules-load.service.in [new file with mode: 0644]
units/systemd-random-seed-load.service.in [new file with mode: 0644]
units/systemd-random-seed-save.service.in [new file with mode: 0644]
units/systemd-readahead-collect.service.in [new file with mode: 0644]
units/systemd-readahead-done.service.in [new file with mode: 0644]
units/systemd-readahead-done.timer [new file with mode: 0644]
units/systemd-readahead-replay.service.in [new file with mode: 0644]
units/systemd-remount-api-vfs.service.in [new file with mode: 0644]
units/systemd-shutdownd.service.in [new file with mode: 0644]
units/systemd-shutdownd.socket [new file with mode: 0644]
units/systemd-sysctl.service.in [new file with mode: 0644]
units/systemd-timedated.service.in [new file with mode: 0644]
units/systemd-tmpfiles-clean.service.in [new file with mode: 0644]
units/systemd-tmpfiles-clean.timer [new file with mode: 0644]
units/systemd-tmpfiles-setup.service.in [new file with mode: 0644]
units/systemd-update-utmp-runlevel.service.in [new file with mode: 0644]
units/systemd-update-utmp-shutdown.service.in [new file with mode: 0644]
units/systemd-user-sessions.service.in [new file with mode: 0644]
units/systemd-vconsole-setup.service.in [new file with mode: 0644]
units/time-sync.target [new file with mode: 0644]
units/tmp.mount [new file with mode: 0644]
units/umount.target [new file with mode: 0644]
units/user/.gitignore [new file with mode: 0644]
units/user/Makefile [new symlink]
units/user/default.target [new file with mode: 0644]
units/user/exit.service.in [new file with mode: 0644]
units/user/exit.target [new file with mode: 0644]
units/user@.service.in [new file with mode: 0644]

diff --git a/.dir-locals.el b/.dir-locals.el
new file mode 100644 (file)
index 0000000..9d9f8cd
--- /dev/null
@@ -0,0 +1,7 @@
+; Sets emacs variables based on mode.
+; A list of (major-mode . ((var1 . value1) (var2 . value2)))
+; Mode can be nil, which gives default values.
+
+((nil . ((indent-tabs-mode . nil)
+         (tab-width . 8)))
+)
index fa3500ba963fe1b7f09f50f51aaf9719749f7ada..f36dd8a81afd10669df05cfbf9df5d5c90830f7a 100644 (file)
+/test-journal-send
+/systemd-multi-seat-x
+/systemd-cgtop
+/systemd-coredump
+/systemd-cat
+/systemd-rc-local-generator
+/libsystemd-id128.pc
+journalctl
+systemd-journald
+test-id128
+test-journal
+test-install
+org.freedesktop.hostname1.xml
+org.freedesktop.locale1.xml
+libsystemd-daemon.pc
+libsystemd-login.pc
+test-login
+loginctl
+systemd-localed
+systemd-timedated
+org.freedesktop.timedate1.xml
+systemd-logind
+systemd-uaccess
+systemd-hostnamed
+systemd-binfmt
+systemd-getty-generator
+systemd-nspawn
+systemd-stdio-bridge
+systemd-machine-id-setup
+systemd-detect-virt
+systemd-sysctl
+test-strv
+systemd-ac-power
+systemd-timestamp
+systemd-cryptsetup
+systemd-cryptsetup-generator
+systemd-tty-ask-password-agent
+systemd-fsck
+systemd-quotacheck
+systemd-user-sessions
+systemd-shutdown
+systemd-tmpfiles
+systemd-readahead-collect
+systemd-readahead-replay
+systemd-reply-password
+systemd-gnome-ask-password-agent
+systemd-ask-password
+systemd-kmsg-syslogd
+systemd-remount-api-vfs
+test-hostname
+systemd-modules-load
+systemd-vconsole-setup
+systemd-shutdownd
+systemd-random-seed
+systemd-update-utmp
+test-env-replace
+systemd-cgls
+systemd.pc
+test-cgroup
+.libs/
+systemd-notify
+test-daemon
+systemd-install
+org.freedesktop.systemd1.*.xml
+test-ns
+test-loopback
+systemd-cgroups-agent
+systemd-initctl
+/systemd
+test-engine
+test-job-type
+systemd-stdout-syslog-bridge
+systemctl
+systemadm
+.dirstamp
+*.1
+*.3
+*.5
+*.7
+*.8
+*.html
 *~
 *.o
-*.a
 *.lo
+*.a
 *.la
-.libs
-.deps
-.dirstamp
-Makefile
+.deps/
 Makefile.in
-/aclocal.m4
-/autom4te.cache
-/config.h
-/config.h.in
-/config.log
-/config.status
-/config.guess
-/config.sub
-/libtool
-/ltmain.sh
-/install-sh
-/missing
-/configure
-/stamp-h1
-/depcomp
-/gtk-doc.make
-/build-aux
-/udev-test-install
-/udevd
-/udevadm
-/test-udev
-/test-libudev
-/accelerometer
-/ata_id
-/cdrom_id
-/collect
-/mtd_probe
-/v4l_id
-/keymap
-/scsi_id
+aclocal.m4
+*.cache
+compile
+config.guess
+config.h
+config.h.in
+config.log
+config.status
+config.sub
+configure
+depcomp
+install-sh
+missing
+stamp-*
+*.stamp
+/Makefile
+ltmain.sh
+*.tar.xz
+*.tar.gz
+*.tar.bz2
+libtool
diff --git a/.mailmap b/.mailmap
new file mode 100644 (file)
index 0000000..04b1c6c
--- /dev/null
+++ b/.mailmap
@@ -0,0 +1,4 @@
+Kay Sievers <kay.sievers@vrfy.org> <kay.sievers@suse.de>
+Robert Gerus <ar@bash.org.pl> Robert "arachnist" Gerus <ar@bash.org.pl>
+Zbigniew JÄ™drzejewski-Szmek <zbyszek@in.waw.pl> Zbyszek Szmek <zbyszek@in.waw.pl>
+Fabiano Fidêncio <fabianofidencio@gmail.com> Fabiano Fidencio <fidencio@profusion.mobi>
diff --git a/CODING_STYLE b/CODING_STYLE
new file mode 100644 (file)
index 0000000..9341b48
--- /dev/null
@@ -0,0 +1,27 @@
+
+- 8ch indent, no tabs
+
+- structs in MixedCase, variables, functions in lower_case
+
+- the destructors always unregister the object from the next bigger
+  object, not the other way around
+
+- to minimize strict aliasing violations we prefer unions over casting
+
+- for robustness reasons destructors should be able to destruct
+  half-initialized objects, too
+
+- error codes are returned as negative Exxx. i.e. return -EINVAL. There
+  are some exceptions: for constructors its is OK to return NULL on
+  OOM. For lookup functions NULL is fine too for "not found".
+
+- Do not issue NSS requests (that includes user name and host name
+  lookups) from the main daemon as this might trigger deadlocks when
+  we those lookups involve synchronously talking to services that we
+  would need to start up.
+
+- Do not access any directories outside of /etc/, /dev, /lib from the
+  init daemon to avoid deadlocks with the automounter.
+
+- Don't synchronously talk to any other service, due to risk of
+  deadlocks.
diff --git a/DISTRO_PORTING b/DISTRO_PORTING
new file mode 100644 (file)
index 0000000..2b08bf8
--- /dev/null
@@ -0,0 +1,58 @@
+Porting systemd To New Distributions
+
+HOWTO:
+        You need to make the follow changes to adapt systemd to your
+        distribution:
+
+        0) Make your distribution recognized via the autoconf checks
+        in configure.ac. Grep for the word "fedora" (case
+        insensitively) and you should be able to find the places where
+        you need to add/change things.
+
+        1) Patch src/hostname-setup.c so that systemd knows where to
+        read your host name from. You might also want to update
+        status_welcome() in util.c.
+
+        2) Check the unit files in units/ if they match your
+        distribution. Most likely you will have to make additions to
+        units/*.m4 and create a copy of units/fedora/ with changes for
+        your distribution.
+
+        3) Adjust Makefile.am to register the unit files you added in
+        step 2. Also you might need to update the m4 invocation in
+        Makefile.am. Grep for the word "fedora" (case insensitively)
+        and you should be able to find the places where you need to
+        add/change things.
+
+        4) Try it out. Play around with 'systemd --test --system' for
+        a test run of systemd without booting. This will read the unit
+        files and print the initial transaction it would execute
+        during boot-up. This will also inform you about ordering loops
+        and suchlike.
+
+CONTRIBUTING UPSTREAM:
+        We are interested in merging your changes upstream, if they
+        are for a big, and well-known distribution. Unfortunately we
+        don't have the time and resources to maintain
+        distribution-specific patches for all distributions on the
+        planet, hence please do not send us patches that add systemd
+        support for non-mainstream or niche distributions.
+
+        Thank you for understanding.
+
+BE CONSIDERATE:
+        We'd like to keep differences between the distributions
+        minimal. This both simplifies our maintenance work, as well
+        as it helps administrators to move from one distribution to
+        another.
+
+        Hence we'd like to ask you to keep your changes minimal, and
+        not rename any units without a very good reason (if you need a
+        particular name for compatibility reasons, consider using
+        alias names via symlinks). Before you make changes that change
+        semantics from upstream, please talk to us!
+
+        In SysV almost every distribution uses a different
+        nomenclature and different locations for the boot-up
+        scripts. We'd like to avoid chaos like that with systemd right
+        from the beginning. So please, be considerate!
diff --git a/LICENSE b/LICENSE
new file mode 100644 (file)
index 0000000..d511905
--- /dev/null
+++ b/LICENSE
@@ -0,0 +1,339 @@
+                   GNU GENERAL PUBLIC LICENSE
+                      Version 2, June 1991
+
+ Copyright (C) 1989, 1991 Free Software Foundation, Inc.,
+ 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
+ Everyone is permitted to copy and distribute verbatim copies
+ of this license document, but changing it is not allowed.
+
+                           Preamble
+
+  The licenses for most software are designed to take away your
+freedom to share and change it.  By contrast, the GNU General Public
+License is intended to guarantee your freedom to share and change free
+software--to make sure the software is free for all its users.  This
+General Public License applies to most of the Free Software
+Foundation's software and to any other program whose authors commit to
+using it.  (Some other Free Software Foundation software is covered by
+the GNU Lesser General Public License instead.)  You can apply it to
+your programs, too.
+
+  When we speak of free software, we are referring to freedom, not
+price.  Our General Public Licenses are designed to make sure that you
+have the freedom to distribute copies of free software (and charge for
+this service if you wish), that you receive source code or can get it
+if you want it, that you can change the software or use pieces of it
+in new free programs; and that you know you can do these things.
+
+  To protect your rights, we need to make restrictions that forbid
+anyone to deny you these rights or to ask you to surrender the rights.
+These restrictions translate to certain responsibilities for you if you
+distribute copies of the software, or if you modify it.
+
+  For example, if you distribute copies of such a program, whether
+gratis or for a fee, you must give the recipients all the rights that
+you have.  You must make sure that they, too, receive or can get the
+source code.  And you must show them these terms so they know their
+rights.
+
+  We protect your rights with two steps: (1) copyright the software, and
+(2) offer you this license which gives you legal permission to copy,
+distribute and/or modify the software.
+
+  Also, for each author's protection and ours, we want to make certain
+that everyone understands that there is no warranty for this free
+software.  If the software is modified by someone else and passed on, we
+want its recipients to know that what they have is not the original, so
+that any problems introduced by others will not reflect on the original
+authors' reputations.
+
+  Finally, any free program is threatened constantly by software
+patents.  We wish to avoid the danger that redistributors of a free
+program will individually obtain patent licenses, in effect making the
+program proprietary.  To prevent this, we have made it clear that any
+patent must be licensed for everyone's free use or not licensed at all.
+
+  The precise terms and conditions for copying, distribution and
+modification follow.
+
+                   GNU GENERAL PUBLIC LICENSE
+   TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION
+
+  0. This License applies to any program or other work which contains
+a notice placed by the copyright holder saying it may be distributed
+under the terms of this General Public License.  The "Program", below,
+refers to any such program or work, and a "work based on the Program"
+means either the Program or any derivative work under copyright law:
+that is to say, a work containing the Program or a portion of it,
+either verbatim or with modifications and/or translated into another
+language.  (Hereinafter, translation is included without limitation in
+the term "modification".)  Each licensee is addressed as "you".
+
+Activities other than copying, distribution and modification are not
+covered by this License; they are outside its scope.  The act of
+running the Program is not restricted, and the output from the Program
+is covered only if its contents constitute a work based on the
+Program (independent of having been made by running the Program).
+Whether that is true depends on what the Program does.
+
+  1. You may copy and distribute verbatim copies of the Program's
+source code as you receive it, in any medium, provided that you
+conspicuously and appropriately publish on each copy an appropriate
+copyright notice and disclaimer of warranty; keep intact all the
+notices that refer to this License and to the absence of any warranty;
+and give any other recipients of the Program a copy of this License
+along with the Program.
+
+You may charge a fee for the physical act of transferring a copy, and
+you may at your option offer warranty protection in exchange for a fee.
+
+  2. You may modify your copy or copies of the Program or any portion
+of it, thus forming a work based on the Program, and copy and
+distribute such modifications or work under the terms of Section 1
+above, provided that you also meet all of these conditions:
+
+    a) You must cause the modified files to carry prominent notices
+    stating that you changed the files and the date of any change.
+
+    b) You must cause any work that you distribute or publish, that in
+    whole or in part contains or is derived from the Program or any
+    part thereof, to be licensed as a whole at no charge to all third
+    parties under the terms of this License.
+
+    c) If the modified program normally reads commands interactively
+    when run, you must cause it, when started running for such
+    interactive use in the most ordinary way, to print or display an
+    announcement including an appropriate copyright notice and a
+    notice that there is no warranty (or else, saying that you provide
+    a warranty) and that users may redistribute the program under
+    these conditions, and telling the user how to view a copy of this
+    License.  (Exception: if the Program itself is interactive but
+    does not normally print such an announcement, your work based on
+    the Program is not required to print an announcement.)
+
+These requirements apply to the modified work as a whole.  If
+identifiable sections of that work are not derived from the Program,
+and can be reasonably considered independent and separate works in
+themselves, then this License, and its terms, do not apply to those
+sections when you distribute them as separate works.  But when you
+distribute the same sections as part of a whole which is a work based
+on the Program, the distribution of the whole must be on the terms of
+this License, whose permissions for other licensees extend to the
+entire whole, and thus to each and every part regardless of who wrote it.
+
+Thus, it is not the intent of this section to claim rights or contest
+your rights to work written entirely by you; rather, the intent is to
+exercise the right to control the distribution of derivative or
+collective works based on the Program.
+
+In addition, mere aggregation of another work not based on the Program
+with the Program (or with a work based on the Program) on a volume of
+a storage or distribution medium does not bring the other work under
+the scope of this License.
+
+  3. You may copy and distribute the Program (or a work based on it,
+under Section 2) in object code or executable form under the terms of
+Sections 1 and 2 above provided that you also do one of the following:
+
+    a) Accompany it with the complete corresponding machine-readable
+    source code, which must be distributed under the terms of Sections
+    1 and 2 above on a medium customarily used for software interchange; or,
+
+    b) Accompany it with a written offer, valid for at least three
+    years, to give any third party, for a charge no more than your
+    cost of physically performing source distribution, a complete
+    machine-readable copy of the corresponding source code, to be
+    distributed under the terms of Sections 1 and 2 above on a medium
+    customarily used for software interchange; or,
+
+    c) Accompany it with the information you received as to the offer
+    to distribute corresponding source code.  (This alternative is
+    allowed only for noncommercial distribution and only if you
+    received the program in object code or executable form with such
+    an offer, in accord with Subsection b above.)
+
+The source code for a work means the preferred form of the work for
+making modifications to it.  For an executable work, complete source
+code means all the source code for all modules it contains, plus any
+associated interface definition files, plus the scripts used to
+control compilation and installation of the executable.  However, as a
+special exception, the source code distributed need not include
+anything that is normally distributed (in either source or binary
+form) with the major components (compiler, kernel, and so on) of the
+operating system on which the executable runs, unless that component
+itself accompanies the executable.
+
+If distribution of executable or object code is made by offering
+access to copy from a designated place, then offering equivalent
+access to copy the source code from the same place counts as
+distribution of the source code, even though third parties are not
+compelled to copy the source along with the object code.
+
+  4. You may not copy, modify, sublicense, or distribute the Program
+except as expressly provided under this License.  Any attempt
+otherwise to copy, modify, sublicense or distribute the Program is
+void, and will automatically terminate your rights under this License.
+However, parties who have received copies, or rights, from you under
+this License will not have their licenses terminated so long as such
+parties remain in full compliance.
+
+  5. You are not required to accept this License, since you have not
+signed it.  However, nothing else grants you permission to modify or
+distribute the Program or its derivative works.  These actions are
+prohibited by law if you do not accept this License.  Therefore, by
+modifying or distributing the Program (or any work based on the
+Program), you indicate your acceptance of this License to do so, and
+all its terms and conditions for copying, distributing or modifying
+the Program or works based on it.
+
+  6. Each time you redistribute the Program (or any work based on the
+Program), the recipient automatically receives a license from the
+original licensor to copy, distribute or modify the Program subject to
+these terms and conditions.  You may not impose any further
+restrictions on the recipients' exercise of the rights granted herein.
+You are not responsible for enforcing compliance by third parties to
+this License.
+
+  7. If, as a consequence of a court judgment or allegation of patent
+infringement or for any other reason (not limited to patent issues),
+conditions are imposed on you (whether by court order, agreement or
+otherwise) that contradict the conditions of this License, they do not
+excuse you from the conditions of this License.  If you cannot
+distribute so as to satisfy simultaneously your obligations under this
+License and any other pertinent obligations, then as a consequence you
+may not distribute the Program at all.  For example, if a patent
+license would not permit royalty-free redistribution of the Program by
+all those who receive copies directly or indirectly through you, then
+the only way you could satisfy both it and this License would be to
+refrain entirely from distribution of the Program.
+
+If any portion of this section is held invalid or unenforceable under
+any particular circumstance, the balance of the section is intended to
+apply and the section as a whole is intended to apply in other
+circumstances.
+
+It is not the purpose of this section to induce you to infringe any
+patents or other property right claims or to contest validity of any
+such claims; this section has the sole purpose of protecting the
+integrity of the free software distribution system, which is
+implemented by public license practices.  Many people have made
+generous contributions to the wide range of software distributed
+through that system in reliance on consistent application of that
+system; it is up to the author/donor to decide if he or she is willing
+to distribute software through any other system and a licensee cannot
+impose that choice.
+
+This section is intended to make thoroughly clear what is believed to
+be a consequence of the rest of this License.
+
+  8. If the distribution and/or use of the Program is restricted in
+certain countries either by patents or by copyrighted interfaces, the
+original copyright holder who places the Program under this License
+may add an explicit geographical distribution limitation excluding
+those countries, so that distribution is permitted only in or among
+countries not thus excluded.  In such case, this License incorporates
+the limitation as if written in the body of this License.
+
+  9. The Free Software Foundation may publish revised and/or new versions
+of the General Public License from time to time.  Such new versions will
+be similar in spirit to the present version, but may differ in detail to
+address new problems or concerns.
+
+Each version is given a distinguishing version number.  If the Program
+specifies a version number of this License which applies to it and "any
+later version", you have the option of following the terms and conditions
+either of that version or of any later version published by the Free
+Software Foundation.  If the Program does not specify a version number of
+this License, you may choose any version ever published by the Free Software
+Foundation.
+
+  10. If you wish to incorporate parts of the Program into other free
+programs whose distribution conditions are different, write to the author
+to ask for permission.  For software which is copyrighted by the Free
+Software Foundation, write to the Free Software Foundation; we sometimes
+make exceptions for this.  Our decision will be guided by the two goals
+of preserving the free status of all derivatives of our free software and
+of promoting the sharing and reuse of software generally.
+
+                           NO WARRANTY
+
+  11. BECAUSE THE PROGRAM IS LICENSED FREE OF CHARGE, THERE IS NO WARRANTY
+FOR THE PROGRAM, TO THE EXTENT PERMITTED BY APPLICABLE LAW.  EXCEPT WHEN
+OTHERWISE STATED IN WRITING THE COPYRIGHT HOLDERS AND/OR OTHER PARTIES
+PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY OF ANY KIND, EITHER EXPRESSED
+OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
+MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE.  THE ENTIRE RISK AS
+TO THE QUALITY AND PERFORMANCE OF THE PROGRAM IS WITH YOU.  SHOULD THE
+PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF ALL NECESSARY SERVICING,
+REPAIR OR CORRECTION.
+
+  12. IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING
+WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MAY MODIFY AND/OR
+REDISTRIBUTE THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES,
+INCLUDING ANY GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING
+OUT OF THE USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED
+TO LOSS OF DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY
+YOU OR THIRD PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER
+PROGRAMS), EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE
+POSSIBILITY OF SUCH DAMAGES.
+
+                    END OF TERMS AND CONDITIONS
+
+           How to Apply These Terms to Your New Programs
+
+  If you develop a new program, and you want it to be of the greatest
+possible use to the public, the best way to achieve this is to make it
+free software which everyone can redistribute and change under these terms.
+
+  To do so, attach the following notices to the program.  It is safest
+to attach them to the start of each source file to most effectively
+convey the exclusion of warranty; and each file should have at least
+the "copyright" line and a pointer to where the full notice is found.
+
+    <one line to give the program's name and a brief idea of what it does.>
+    Copyright (C) <year>  <name of author>
+
+    This program 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.
+
+    This program is distributed in the hope that 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 this program; if not, write to the Free Software Foundation, Inc.,
+    51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+
+Also add information on how to contact you by electronic and paper mail.
+
+If the program is interactive, make it output a short notice like this
+when it starts in an interactive mode:
+
+    Gnomovision version 69, Copyright (C) year name of author
+    Gnomovision comes with ABSOLUTELY NO WARRANTY; for details type `show w'.
+    This is free software, and you are welcome to redistribute it
+    under certain conditions; type `show c' for details.
+
+The hypothetical commands `show w' and `show c' should show the appropriate
+parts of the General Public License.  Of course, the commands you use may
+be called something other than `show w' and `show c'; they could even be
+mouse-clicks or menu items--whatever suits your program.
+
+You should also get your employer (if you work as a programmer) or your
+school, if any, to sign a "copyright disclaimer" for the program, if
+necessary.  Here is a sample; alter the names:
+
+  Yoyodyne, Inc., hereby disclaims all copyright interest in the program
+  `Gnomovision' (which makes passes at compilers) written by James Hacker.
+
+  <signature of Ty Coon>, 1 April 1989
+  Ty Coon, President of Vice
+
+This General Public License does not permit incorporating your program into
+proprietary programs.  If your program is a subroutine library, you may
+consider it more useful to permit linking proprietary applications with the
+library.  If this is what you want to do, use the GNU Lesser General
+Public License instead of this License.
index 1c7f86b08157aa461078883f9ebe586c5650246e..219d8ded8e6115c14f63f2dd8c24e440bb49f420 100644 (file)
-# Copyright (C) 2008-2012 Kay Sievers <kay.sievers@vrfy.org>
-# Copyright (C) 2009 Diego Elio 'Flameeyes' Pettenò <flameeyes@gmail.com>
+#  This file is part of systemd.
+#
+#  Copyright 2011 Lennart Poettering
+#  Copyright 2011 Kay Sievers
+#
+#  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 <http://www.gnu.org/licenses/>.
+
+ACLOCAL_AMFLAGS = -I m4
+
+SUBDIRS = po
+
+LIBSYSTEMD_LOGIN_CURRENT=2
+LIBSYSTEMD_LOGIN_REVISION=1
+LIBSYSTEMD_LOGIN_AGE=2
+
+LIBSYSTEMD_DAEMON_CURRENT=0
+LIBSYSTEMD_DAEMON_REVISION=1
+LIBSYSTEMD_DAEMON_AGE=0
+
+LIBSYSTEMD_ID128_CURRENT=0
+LIBSYSTEMD_ID128_REVISION=3
+LIBSYSTEMD_ID128_AGE=0
+
+LIBSYSTEMD_JOURNAL_CURRENT=0
+LIBSYSTEMD_JOURNAL_REVISION=3
+LIBSYSTEMD_JOURNAL_AGE=0
+
+# Dirs of external packages
+dbuspolicydir=@dbuspolicydir@
+dbussessionservicedir=@dbussessionservicedir@
+dbussystemservicedir=@dbussystemservicedir@
+dbusinterfacedir=@dbusinterfacedir@
+udevrulesdir=@udevrulesdir@
+pamlibdir=@pamlibdir@
+pkgconfigdatadir=$(datadir)/pkgconfig
+pkgconfiglibdir=$(libdir)/pkgconfig
+polkitpolicydir=$(datadir)/polkit-1/actions
+bashcompletiondir=$(sysconfdir)/bash_completion.d
+
+# Our own, non-special dirs
+pkgsysconfdir=$(sysconfdir)/systemd
+userunitdir=$(prefix)/lib/systemd/user
+tmpfilesdir=$(prefix)/lib/tmpfiles.d
+sysctldir=$(prefix)/lib/sysctl.d
+usergeneratordir=$(pkglibexecdir)/user-generators
+pkgincludedir=$(includedir)/systemd
+
+# And these are the special ones for /
+rootprefix=@rootprefix@
+rootbindir=$(rootprefix)/bin
+rootlibexecdir=$(rootprefix)/lib/systemd
+systemgeneratordir=$(rootlibexecdir)/system-generators
+systemshutdowndir=$(rootlibexecdir)/system-shutdown
+systemunitdir=$(rootprefix)/lib/systemd/system
 
-SUBDIRS = .
+CLEANFILES =
+EXTRA_DIST =
+INSTALL_EXEC_HOOKS =
+UNINSTALL_EXEC_HOOKS =
+INSTALL_DATA_HOOKS =
+pkginclude_HEADERS =
+lib_LTLIBRARIES =
+pkgconfiglib_DATA =
+polkitpolicy_in_files =
+dist_udevrules_DATA =
 
-ACLOCAL_AMFLAGS = -I m4 ${ACLOCAL_FLAGS}
+AM_CPPFLAGS = \
+       -include $(top_builddir)/config.h \
+       -DSYSTEM_CONFIG_FILE=\"$(pkgsysconfdir)/system.conf\" \
+       -DSYSTEM_CONFIG_UNIT_PATH=\"$(pkgsysconfdir)/system\" \
+       -DSYSTEM_DATA_UNIT_PATH=\"$(systemunitdir)\" \
+       -DSYSTEM_SYSVINIT_PATH=\"$(SYSTEM_SYSVINIT_PATH)\" \
+       -DSYSTEM_SYSVRCND_PATH=\"$(SYSTEM_SYSVRCND_PATH)\" \
+       -DUSER_CONFIG_FILE=\"$(pkgsysconfdir)/user.conf\" \
+       -DUSER_CONFIG_UNIT_PATH=\"$(pkgsysconfdir)/user\" \
+       -DUSER_DATA_UNIT_PATH=\"$(userunitdir)\" \
+       -DSYSTEMD_CGROUP_AGENT_PATH=\"$(rootlibexecdir)/systemd-cgroups-agent\" \
+       -DSYSTEMD_BINARY_PATH=\"$(rootlibexecdir)/systemd\" \
+       -DSYSTEMD_SHUTDOWN_BINARY_PATH=\"$(rootlibexecdir)/systemd-shutdown\" \
+       -DSYSTEMCTL_BINARY_PATH=\"$(rootbindir)/systemctl\" \
+       -DSYSTEMD_TTY_ASK_PASSWORD_AGENT_BINARY_PATH=\"$(rootbindir)/systemd-tty-ask-password-agent\" \
+       -DSYSTEMD_STDIO_BRIDGE_BINARY_PATH=\"$(bindir)/systemd-stdio-bridge\" \
+       -DROOTPREFIX=\"$(rootprefix)\" \
+       -DRUNTIME_DIR=\"/run\" \
+       -DRANDOM_SEED=\"$(localstatedir)/lib/random-seed\" \
+       -DSYSTEMD_CRYPTSETUP_PATH=\"$(rootlibexecdir)/systemd-cryptsetup\" \
+       -DSYSTEM_GENERATOR_PATH=\"$(systemgeneratordir)\" \
+       -DUSER_GENERATOR_PATH=\"$(usergeneratordir)\" \
+       -DSYSTEM_SHUTDOWN_PATH=\"$(systemshutdowndir)\" \
+       -DSYSTEMD_KBD_MODEL_MAP=\"$(pkgdatadir)/kbd-model-map\" \
+        -DX_SERVER=\"$(bindir)/X\" \
+       -I $(top_srcdir)/src \
+       -I $(top_srcdir)/src/readahead \
+       -I $(top_srcdir)/src/login \
+       -I $(top_srcdir)/src/journal \
+       -I $(top_srcdir)/src/systemd
+
+AM_CFLAGS = $(WARNINGFLAGS)
+AM_LDFLAGS = $(GCLDFLAGS)
+
+if TARGET_GENTOO
+AM_CPPFLAGS += \
+       -DKBD_LOADKEYS=\"/usr/bin/loadkeys\" \
+       -DKBD_SETFONT=\"/usr/bin/setfont\" \
+       -DDEFAULT_FONT=\"LatArCyrHeb-16\"
+else
+if TARGET_ARCH
+AM_CPPFLAGS += \
+       -DKBD_LOADKEYS=\"/usr/bin/loadkeys\" \
+       -DKBD_SETFONT=\"/usr/bin/setfont\" \
+       -DDEFAULT_FONT=\"LatArCyrHeb-16\"
+else
+if TARGET_FRUGALWARE
+AM_CPPFLAGS += \
+       -DKBD_LOADKEYS=\"/usr/bin/loadkeys\" \
+       -DKBD_SETFONT=\"/usr/bin/setfont\" \
+       -DDEFAULT_FONT=\"LatArCyrHeb-16\"
+else
+if TARGET_MANDRIVA
+AM_CPPFLAGS += \
+       -DKBD_LOADKEYS=\"/bin/loadkeys\" \
+       -DKBD_SETFONT=\"/bin/setfont\" \
+       -DDEFAULT_FONT=\"LatArCyrHeb-16\"
+else
+if TARGET_MEEGO
+AM_CPPFLAGS += \
+       -DKBD_LOADKEYS=\"/bin/loadkeys\" \
+       -DKBD_SETFONT=\"/bin/setfont\" \
+       -DDEFAULT_FONT=\"LatArCyrHeb-16\"
+else
+if TARGET_ANGSTROM
+AM_CPPFLAGS += \
+       -DKBD_LOADKEYS=\"/usr/bin/loadkeys\" \
+       -DKBD_SETFONT=\"/usr/bin/setfont\" \
+       -DDEFAULT_FONT=\"LatArCyrHeb-16\"
+else
+if TARGET_MAGEIA
+AM_CPPFLAGS += \
+       -DKBD_LOADKEYS=\"/bin/loadkeys\" \
+       -DKBD_SETFONT=\"/bin/setfont\" \
+       -DDEFAULT_FONT=\"LatArCyrHeb-16\"
+else
+AM_CPPFLAGS += \
+       -DKBD_LOADKEYS=\"/bin/loadkeys\" \
+       -DKBD_SETFONT=\"/bin/setfont\" \
+       -DDEFAULT_FONT=\"latarcyrheb-sun16\"
+endif
+endif
+endif
+endif
+endif
+endif
+endif
 
-AM_MAKEFLAGS = --no-print-directory
+rootbin_PROGRAMS = \
+       systemctl \
+       systemd-notify \
+       systemd-ask-password \
+       systemd-tty-ask-password-agent \
+       systemd-tmpfiles \
+       systemd-machine-id-setup
 
-LIBUDEV_CURRENT=13
-LIBUDEV_REVISION=2
-LIBUDEV_AGE=13
+bin_PROGRAMS = \
+       systemd-cgls \
+       systemd-cgtop \
+       systemd-stdio-bridge \
+       systemd-nspawn
+
+dist_bin_SCRIPTS = \
+       src/systemd-analyze
+
+rootlibexec_PROGRAMS = \
+       systemd \
+       systemd-cgroups-agent \
+       systemd-initctl \
+       systemd-update-utmp \
+       systemd-shutdownd \
+       systemd-shutdown \
+       systemd-modules-load \
+       systemd-remount-api-vfs \
+       systemd-reply-password \
+       systemd-fsck \
+       systemd-timestamp \
+       systemd-ac-power \
+       systemd-detect-virt \
+       systemd-sysctl
+
+systemgenerator_PROGRAMS = \
+       systemd-getty-generator
+
+noinst_PROGRAMS = \
+       test-engine \
+       test-job-type \
+       test-ns \
+       test-loopback \
+       test-hostname \
+       test-daemon \
+       test-cgroup \
+       test-env-replace \
+       test-strv \
+       test-install
+
+dist_pkgsysconf_DATA = \
+       src/system.conf \
+       src/user.conf
+
+dist_dbuspolicy_DATA = \
+       src/org.freedesktop.systemd1.conf
+
+dist_dbussystemservice_DATA = \
+       src/org.freedesktop.systemd1.service
+
+nodist_udevrules_DATA = \
+       src/99-systemd.rules
+
+dbusinterface_DATA = \
+       org.freedesktop.systemd1.Manager.xml \
+       org.freedesktop.systemd1.Job.xml \
+       org.freedesktop.systemd1.Unit.xml \
+       org.freedesktop.systemd1.Service.xml \
+       org.freedesktop.systemd1.Socket.xml \
+       org.freedesktop.systemd1.Timer.xml \
+       org.freedesktop.systemd1.Target.xml \
+       org.freedesktop.systemd1.Device.xml \
+       org.freedesktop.systemd1.Mount.xml \
+       org.freedesktop.systemd1.Automount.xml \
+       org.freedesktop.systemd1.Snapshot.xml \
+       org.freedesktop.systemd1.Swap.xml \
+       org.freedesktop.systemd1.Path.xml
+
+dist_bashcompletion_DATA = \
+       src/systemd-bash-completion.sh
+
+dist_tmpfiles_DATA = \
+       tmpfiles.d/systemd.conf \
+       tmpfiles.d/tmp.conf \
+       tmpfiles.d/x11.conf
+
+if HAVE_SYSV_COMPAT
+dist_tmpfiles_DATA += \
+       tmpfiles.d/legacy.conf
+endif
 
-LIBGUDEV_CURRENT=1
-LIBGUDEV_REVISION=1
-LIBGUDEV_AGE=1
+dist_systemunit_DATA = \
+       units/graphical.target \
+       units/multi-user.target \
+       units/emergency.service \
+       units/emergency.target \
+       units/sysinit.target \
+       units/basic.target \
+       units/getty.target \
+       units/halt.target \
+       units/kexec.target \
+       units/local-fs.target \
+       units/local-fs-pre.target \
+       units/remote-fs.target \
+       units/remote-fs-pre.target \
+       units/network.target \
+       units/nss-lookup.target \
+       units/mail-transfer-agent.target \
+       units/http-daemon.target \
+       units/poweroff.target \
+       units/reboot.target \
+       units/rescue.target \
+       units/rpcbind.target \
+       units/time-sync.target \
+       units/shutdown.target \
+       units/final.target \
+       units/umount.target \
+       units/sigpwr.target \
+       units/sockets.target \
+       units/swap.target \
+       units/systemd-initctl.socket \
+       units/systemd-shutdownd.socket \
+       units/syslog.socket \
+       units/dev-hugepages.mount \
+       units/dev-mqueue.mount \
+       units/sys-kernel-config.mount \
+       units/sys-kernel-debug.mount \
+       units/sys-fs-fuse-connections.mount \
+       units/tmp.mount \
+       units/remount-rootfs.service \
+       units/printer.target \
+       units/sound.target \
+       units/bluetooth.target \
+       units/smartcard.target \
+       units/systemd-tmpfiles-clean.timer \
+       units/quotaon.service \
+       units/systemd-ask-password-wall.path \
+       units/systemd-ask-password-console.path \
+       units/syslog.target
+
+nodist_systemunit_DATA = \
+       units/getty@.service \
+       units/serial-getty@.service \
+       units/console-shell.service \
+       units/systemd-initctl.service \
+       units/systemd-shutdownd.service \
+       units/systemd-modules-load.service \
+       units/systemd-remount-api-vfs.service \
+       units/systemd-update-utmp-runlevel.service \
+       units/systemd-update-utmp-shutdown.service \
+       units/systemd-tmpfiles-setup.service \
+       units/systemd-tmpfiles-clean.service \
+       units/systemd-ask-password-wall.service \
+       units/systemd-ask-password-console.service \
+       units/systemd-sysctl.service \
+       units/halt.service \
+       units/poweroff.service \
+       units/reboot.service \
+       units/kexec.service \
+       units/fsck@.service \
+       units/fsck-root.service \
+       units/rescue.service \
+       units/user@.service
+
+dist_userunit_DATA = \
+       units/user/default.target \
+       units/user/exit.target
+
+nodist_userunit_DATA = \
+       units/user/exit.service
 
-AM_CPPFLAGS = \
-       -include $(top_builddir)/config.h \
-       -I$(top_srcdir)/src \
-       -DSYSCONFDIR=\""$(sysconfdir)"\" \
-       -DPKGLIBEXECDIR=\""$(libexecdir)/udev"\"
+EXTRA_DIST += \
+       units/getty@.service.m4 \
+       units/serial-getty@.service.m4 \
+       units/console-shell.service.m4 \
+       units/rescue.service.m4 \
+       units/systemd-initctl.service.in \
+       units/systemd-shutdownd.service.in \
+       units/systemd-modules-load.service.in \
+       units/systemd-remount-api-vfs.service.in \
+       units/systemd-update-utmp-runlevel.service.in \
+       units/systemd-update-utmp-shutdown.service.in \
+       units/systemd-tmpfiles-setup.service.in \
+       units/systemd-tmpfiles-clean.service.in \
+       units/systemd-ask-password-wall.service.in \
+       units/systemd-ask-password-console.service.in \
+       units/systemd-sysctl.service.in \
+       units/halt.service.in \
+       units/poweroff.service.in \
+       units/reboot.service.in \
+       units/kexec.service.in \
+       units/user/exit.service.in \
+       units/fsck@.service.in \
+       units/fsck-root.service.in \
+       units/user@.service.in \
+       src/systemd.pc.in \
+       introspect.awk \
+       src/99-systemd.rules.in \
+       man/custom-html.xsl
+
+if TARGET_FEDORA
+dist_systemunit_DATA += \
+       units/fedora/prefdm.service \
+       units/fedora/rc-local.service \
+       units/fedora/halt-local.service
+systemgenerator_PROGRAMS += \
+       systemd-rc-local-generator
+endif
 
-AM_CFLAGS = \
-       ${my_CFLAGS} \
-       -fvisibility=hidden \
-       -ffunction-sections \
-       -fdata-sections
+if TARGET_MANDRIVA
+dist_systemunit_DATA += \
+       units/mandriva/prefdm.service \
+       units/fedora/rc-local.service \
+       units/fedora/halt-local.service
+systemgenerator_PROGRAMS += \
+       systemd-rc-local-generator
+endif
 
-AM_LDFLAGS = \
-       -Wl,--gc-sections \
-       -Wl,--as-needed
+if TARGET_FRUGALWARE
+dist_systemunit_DATA += \
+       units/frugalware/display-manager.service
+endif
 
-DISTCHECK_CONFIGURE_FLAGS = \
-       --enable-debug \
-       --enable-rule_generator \
-       --enable-floppy \
-       --with-selinux \
-       --enable-gtk-doc \
-       --with-systemdsystemunitdir=$$dc_install_base/$(systemdsystemunitdir)
-
-BUILT_SOURCES =
-EXTRA_DIST =
-CLEANFILES =
-INSTALL_EXEC_HOOKS =
-INSTALL_DATA_HOOKS =
-UNINSTALL_EXEC_HOOKS =
-DISTCHECK_HOOKS =
-DISTCLEAN_LOCAL_HOOKS =
+if TARGET_SUSE
+dist_systemunit_DATA += \
+       units/suse/rc-local.service \
+       units/suse/halt-local.service
+systemgenerator_PROGRAMS += \
+       systemd-rc-local-generator
+endif
 
-udevhomedir = $(libexecdir)/udev
-udevhome_SCRIPTS =
-dist_udevhome_SCRIPTS =
-dist_udevhome_DATA =
-dist_man_MANS =
+if TARGET_MAGEIA
+dist_systemunit_DATA += \
+       units/mageia/prefdm.service \
+       units/fedora/rc-local.service \
+       units/fedora/halt-local.service
+systemgenerator_PROGRAMS += \
+       systemd-rc-local-generator
+endif
 
-SED_PROCESS = \
-       $(AM_V_GEN)$(MKDIR_P) $(dir $@) && $(SED) \
-       -e 's,@VERSION\@,$(VERSION),g' \
-       -e 's,@prefix\@,$(prefix),g' \
-       -e 's,@rootprefix\@,$(rootprefix),g' \
-       -e 's,@exec_prefix\@,$(exec_prefix),g' \
-       -e 's,@libdir\@,$(libdir),g' \
-       -e 's,@includedir\@,$(includedir),g' \
-       -e 's,@bindir\@,$(bindir),g' \
-       -e 's,@pkglibexecdir\@,$(libexecdir)/udev,g' \
-       < $< > $@ || rm $@
+if HAVE_PLYMOUTH
+dist_systemunit_DATA += \
+       units/plymouth-start.service \
+       units/plymouth-read-write.service \
+       units/plymouth-quit.service \
+       units/plymouth-quit-wait.service \
+       units/plymouth-reboot.service \
+       units/plymouth-kexec.service \
+       units/plymouth-poweroff.service \
+       units/plymouth-halt.service \
+       units/systemd-ask-password-plymouth.path
+
+nodist_systemunit_DATA += \
+       units/systemd-ask-password-plymouth.service
 
-%.pc: %.pc.in Makefile
-       $(SED_PROCESS)
+EXTRA_DIST += \
+       units/systemd-ask-password-plymouth.service.in
+endif
 
-%.rules: %.rules.in Makefile
-       $(SED_PROCESS)
+dist_doc_DATA = \
+       README \
+        NEWS \
+       LICENSE \
+       DISTRO_PORTING
 
-%.service: %.service.in Makefile
-       $(SED_PROCESS)
+pkgconfigdata_DATA = \
+       src/systemd.pc
 
-%.sh: %.sh.in Makefile
-       $(SED_PROCESS)
-       $(AM_V_GEN)chmod +x $@
+# First passed through sed, followed by intltool
+polkitpolicy_in_in_files = \
+       src/org.freedesktop.systemd1.policy.in.in
 
-%.pl: %.pl.in Makefile
-       $(SED_PROCESS)
-       $(AM_V_GEN)chmod +x $@
+nodist_polkitpolicy_DATA = \
+       $(polkitpolicy_in_files:.policy.in=.policy) \
+       $(polkitpolicy_in_in_files:.policy.in.in=.policy)
+
+EXTRA_DIST += \
+       $(polkitpolicy_in_files) \
+       $(polkitpolicy_in_in_files)
+
+@INTLTOOL_POLICY_RULE@
+
+noinst_LTLIBRARIES = \
+       libsystemd-basic.la \
+       libsystemd-core.la
+
+libsystemd_basic_la_SOURCES = \
+       src/util.c \
+       src/virt.c \
+       src/label.c \
+       src/hashmap.c \
+       src/set.c \
+       src/strv.c \
+       src/conf-parser.c \
+       src/socket-util.c \
+       src/log.c \
+       src/ratelimit.c \
+       src/exit-status.c \
+        src/utf8.c
+
+libsystemd_basic_la_CFLAGS = \
+       $(AM_CFLAGS) \
+       $(SELINUX_CFLAGS)
+
+libsystemd_basic_la_LIBADD = \
+       $(SELINUX_LIBS) \
+       $(CAP_LIBS)
+
+libsystemd_core_la_SOURCES = \
+       src/unit.c \
+       src/job.c \
+       src/manager.c \
+       src/path-lookup.c \
+       src/load-fragment.c \
+       src/service.c \
+       src/automount.c \
+       src/mount.c \
+       src/swap.c \
+       src/device.c \
+       src/target.c \
+       src/snapshot.c \
+       src/socket.c \
+       src/timer.c \
+       src/path.c \
+       src/load-dropin.c \
+       src/execute.c \
+       src/utmp-wtmp.c \
+       src/dbus.c \
+       src/dbus-manager.c \
+       src/dbus-unit.c \
+       src/dbus-job.c \
+       src/dbus-service.c \
+       src/dbus-socket.c \
+       src/dbus-timer.c \
+       src/dbus-target.c \
+       src/dbus-mount.c \
+       src/dbus-automount.c \
+       src/dbus-swap.c \
+       src/dbus-snapshot.c \
+       src/dbus-device.c \
+       src/dbus-execute.c \
+       src/dbus-path.c \
+       src/cgroup.c \
+       src/mount-setup.c \
+       src/hostname-setup.c \
+       src/selinux-setup.c \
+       src/ima-setup.c \
+       src/loopback-setup.c \
+       src/kmod-setup.c \
+       src/locale-setup.c \
+       src/machine-id-setup.c \
+       src/specifier.c \
+       src/unit-name.c \
+       src/fdset.c \
+       src/namespace.c \
+       src/tcpwrap.c \
+       src/cgroup-util.c \
+       src/condition.c \
+       src/dbus-common.c \
+       src/sd-daemon.c \
+       src/install.c \
+       src/cgroup-attr.c \
+       src/sd-id128.c
+
+nodist_libsystemd_core_la_SOURCES = \
+       src/load-fragment-gperf.c \
+       src/load-fragment-gperf-nulstr.c
+
+EXTRA_DIST += \
+       src/load-fragment-gperf.gperf.m4
+
+libsystemd_core_la_CFLAGS = \
+       $(AM_CFLAGS) \
+       $(DBUS_CFLAGS) \
+       $(UDEV_CFLAGS) \
+       $(LIBWRAP_CFLAGS) \
+       $(PAM_CFLAGS) \
+       $(AUDIT_CFLAGS) \
+       $(KMOD_CFLAGS)
+
+libsystemd_core_la_LIBADD = \
+       libsystemd-basic.la \
+       $(DBUS_LIBS) \
+       $(UDEV_LIBS) \
+       $(LIBWRAP_LIBS) \
+       $(PAM_LIBS) \
+       $(AUDIT_LIBS) \
+       $(CAP_LIBS) \
+       $(KMOD_LIBS)
+
+# This is needed because automake is buggy in how it generates the
+# rules for C programs, but not Vala programs. We therefore can't
+# list the .h files as dependencies if we want make dist to work.
+
+EXTRA_DIST += \
+       src/util.h \
+       src/virt.h \
+       src/label.h \
+       src/hashmap.h \
+       src/set.h \
+       src/strv.h \
+       src/conf-parser.h \
+       src/socket-util.h \
+       src/log.h \
+       src/ratelimit.h \
+       src/exit-status.h \
+       src/unit.h \
+       src/job.h \
+       src/manager.h \
+       src/path-lookup.h \
+       src/load-fragment.h \
+       src/service.h \
+       src/automount.h \
+       src/mount.h \
+       src/swap.h \
+       src/device.h \
+       src/target.h \
+       src/snapshot.h \
+       src/socket.h \
+       src/timer.h \
+       src/path.h \
+       src/load-dropin.h \
+       src/execute.h \
+       src/utmp-wtmp.h \
+       src/dbus.h \
+       src/dbus-manager.h \
+       src/dbus-unit.h \
+       src/dbus-job.h \
+       src/dbus-service.h \
+       src/dbus-socket.h \
+       src/dbus-timer.h \
+       src/dbus-target.h \
+       src/dbus-mount.h \
+       src/dbus-automount.h \
+       src/dbus-swap.h \
+       src/dbus-snapshot.h \
+       src/dbus-device.h \
+       src/dbus-execute.h \
+       src/dbus-path.h \
+       src/cgroup.h \
+       src/mount-setup.h \
+       src/hostname-setup.h \
+       src/selinux-setup.h \
+       src/loopback-setup.h \
+       src/kmod-setup.h \
+       src/locale-setup.h \
+       src/machine-id-setup.h \
+       src/specifier.h \
+       src/unit-name.h \
+       src/fdset.h \
+       src/namespace.h \
+       src/tcpwrap.h \
+       src/cgroup-util.h \
+       src/condition.h \
+       src/dbus-common.h \
+       src/install.h \
+       src/cgroup-attr.h \
+       src/macro.h \
+       src/def.h \
+       src/ioprio.h \
+       src/missing.h \
+       src/list.h \
+       src/securebits.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/shutdownd.h \
+       src/umount.h \
+       src/ask-password-api.h \
+       src/pager.h \
+       src/sysfs-show.h \
+       src/polkit.h \
+       src/dbus-loop.h \
+       src/spawn-agent.h \
+       src/acl-util.h \
+       src/logs-show.h \
+        src/utf8.h \
+        src/journal/sparse-endian.h \
+        src/ima-setup.h
+
+MANPAGES = \
+       man/systemd.1 \
+       man/systemctl.1 \
+       man/systemd-cgls.1 \
+       man/systemd-cgtop.1 \
+       man/systemd-nspawn.1 \
+       man/systemd-tmpfiles.8 \
+       man/systemd-notify.1 \
+       man/systemd.unit.5 \
+       man/systemd.service.5 \
+       man/systemd.socket.5 \
+       man/systemd.mount.5 \
+       man/systemd.automount.5 \
+       man/systemd.swap.5 \
+       man/systemd.timer.5 \
+       man/systemd.path.5 \
+       man/systemd.target.5 \
+       man/systemd.device.5 \
+       man/systemd.snapshot.5 \
+       man/systemd.exec.5 \
+       man/systemd.special.7 \
+       man/daemon.7 \
+       man/runlevel.8 \
+       man/telinit.8 \
+       man/halt.8 \
+       man/shutdown.8 \
+       man/pam_systemd.8 \
+       man/systemd.conf.5 \
+       man/tmpfiles.d.5 \
+       man/hostname.5 \
+       man/timezone.5 \
+       man/machine-id.5 \
+       man/locale.conf.5 \
+       man/os-release.5 \
+       man/machine-info.5 \
+       man/modules-load.d.5 \
+       man/sysctl.d.5 \
+       man/systemd-ask-password.1 \
+       man/systemd-cat.1 \
+       man/systemd-machine-id-setup.1 \
+       man/journald.conf.5 \
+       man/journalctl.1
+
+MANPAGES_ALIAS = \
+       man/reboot.8 \
+       man/poweroff.8 \
+       man/init.1
+
+man/reboot.8: man/halt.8
+man/poweroff.8: man/halt.8
+man/init.1: man/systemd.1
+
+XML_FILES = \
+       ${patsubst %.1,%.xml,${patsubst %.3,%.xml,${patsubst %.5,%.xml,${patsubst %.7,%.xml,${patsubst %.8,%.xml,$(MANPAGES)}}}}}
+
+if ENABLE_MANPAGES
+man_MANS = \
+       $(MANPAGES) \
+       $(MANPAGES_ALIAS)
+
+noinst_DATA = \
+       ${XML_FILES:.xml=.html}
+endif
+
+EXTRA_DIST += \
+       $(XML_FILES) \
+       ${XML_FILES:.xml=.html} \
+       $(MANPAGES) \
+       $(MANPAGES_ALIAS)
+
+systemd_SOURCES = \
+       src/main.c
+
+systemd_CFLAGS = \
+       $(AM_CFLAGS) \
+       $(DBUS_CFLAGS) \
+       $(UDEV_CFLAGS)
+
+systemd_LDADD = \
+       libsystemd-core.la
+
+test_engine_SOURCES = \
+       src/test-engine.c
+
+test_engine_CFLAGS = $(systemd_CFLAGS)
+test_engine_LDADD = $(systemd_LDADD)
+
+test_job_type_SOURCES = \
+       src/test-job-type.c
+
+test_job_type_CFLAGS = $(systemd_CFLAGS)
+test_job_type_LDADD = $(systemd_LDADD)
+
+test_ns_SOURCES = \
+       src/test-ns.c
+
+test_ns_CFLAGS = $(systemd_CFLAGS)
+test_ns_LDADD = $(systemd_LDADD)
+
+test_loopback_SOURCES = \
+       src/test-loopback.c \
+       src/loopback-setup.c
+
+test_loopback_LDADD = \
+       libsystemd-basic.la
+
+test_hostname_SOURCES = \
+       src/test-hostname.c \
+       src/hostname-setup.c
+
+test_hostname_LDADD = \
+       libsystemd-basic.la
+
+test_daemon_SOURCES = \
+       src/test-daemon.c
+
+test_daemon_LDADD = \
+       libsystemd-basic.la \
+       libsystemd-daemon.la
+
+test_cgroup_SOURCES = \
+       src/test-cgroup.c \
+       src/cgroup-util.c
+
+test_cgroup_LDADD = \
+       libsystemd-basic.la
+
+test_env_replace_SOURCES = \
+       src/test-env-replace.c
+
+test_env_replace_LDADD = \
+       libsystemd-basic.la
+
+test_strv_SOURCES = \
+       src/test-strv.c \
+       src/specifier.c
+
+test_strv_LDADD = \
+       libsystemd-basic.la
+
+test_install_SOURCES = \
+       src/test-install.c \
+       src/install.c \
+       src/path-lookup.c \
+       src/unit-name.c
+
+test_install_CFLAGS = \
+       $(AM_CFLAGS) \
+       $(DBUS_CFLAGS)
+
+test_install_LDADD = \
+       libsystemd-basic.la
+
+systemd_initctl_SOURCES = \
+       src/initctl.c \
+       src/dbus-common.c
+
+systemd_initctl_CFLAGS = \
+       $(AM_CFLAGS) \
+       $(DBUS_CFLAGS)
+
+systemd_initctl_LDADD = \
+       libsystemd-basic.la \
+       libsystemd-daemon.la \
+       $(DBUS_LIBS)
+
+systemd_update_utmp_SOURCES = \
+       src/update-utmp.c \
+       src/dbus-common.c \
+       src/utmp-wtmp.c
+
+systemd_update_utmp_CFLAGS = \
+       $(AM_CFLAGS) \
+       $(DBUS_CFLAGS) \
+       $(AUDIT_CFLAGS)
+
+systemd_update_utmp_LDADD = \
+       libsystemd-basic.la \
+       $(DBUS_LIBS) \
+       $(AUDIT_LIBS)
+
+systemd_shutdownd_SOURCES = \
+       src/utmp-wtmp.c \
+       src/shutdownd.c
+
+systemd_shutdownd_CFLAGS = \
+       $(AM_CFLAGS)
+
+systemd_shutdownd_LDADD = \
+       libsystemd-basic.la \
+       libsystemd-daemon.la
+
+systemd_shutdown_SOURCES = \
+       src/mount-setup.c \
+       src/umount.c \
+       src/shutdown.c
+
+systemd_shutdown_CFLAGS = \
+       $(AM_CFLAGS) \
+       $(UDEV_CFLAGS)
+
+systemd_shutdown_LDADD = \
+       libsystemd-basic.la \
+       $(UDEV_LIBS)
+
+systemd_modules_load_SOURCES = \
+       src/modules-load.c
+
+systemd_modules_load_CFLAGS = \
+       $(KMOD_CFLAGS)
+
+systemd_modules_load_LDADD = \
+       libsystemd-basic.la \
+       $(KMOD_LIBS)
+
+systemd_tmpfiles_SOURCES = \
+       src/tmpfiles.c
+
+systemd_tmpfiles_LDADD = \
+       libsystemd-basic.la
+
+systemd_machine_id_setup_SOURCES = \
+       src/machine-id-setup.c \
+       src/machine-id-main.c \
+       src/sd-id128.c
+
+systemd_machine_id_setup_LDADD = \
+       libsystemd-basic.la
+
+systemd_sysctl_SOURCES = \
+       src/sysctl.c
+
+systemd_sysctl_LDADD = \
+       libsystemd-basic.la
+
+systemd_fsck_SOURCES = \
+       src/fsck.c \
+       src/dbus-common.c
+
+systemd_fsck_CFLAGS = \
+       $(AM_CFLAGS) \
+       $(UDEV_CFLAGS) \
+       $(DBUS_CFLAGS)
+
+systemd_fsck_LDADD = \
+       libsystemd-basic.la \
+       $(UDEV_LIBS) \
+       $(DBUS_LIBS)
+
+systemd_timestamp_SOURCES = \
+       src/timestamp.c
+
+systemd_timestamp_LDADD = \
+       libsystemd-basic.la
+
+systemd_ac_power_SOURCES = \
+       src/ac-power.c
+
+systemd_ac_power_CFLAGS = \
+       $(AM_CFLAGS) \
+       $(UDEV_CFLAGS)
+
+systemd_ac_power_LDADD = \
+       libsystemd-basic.la \
+       $(UDEV_LIBS)
+
+systemd_detect_virt_SOURCES = \
+       src/detect-virt.c
+
+systemd_detect_virt_LDADD = \
+       libsystemd-basic.la
+
+systemd_getty_generator_SOURCES = \
+       src/getty-generator.c \
+       src/unit-name.c
+
+systemd_getty_generator_LDADD = \
+       libsystemd-basic.la
+
+systemd_rc_local_generator_SOURCES = \
+       src/rc-local-generator.c
+
+systemd_rc_local_generator_LDADD = \
+       libsystemd-basic.la
+
+systemd_remount_api_vfs_SOURCES = \
+       src/remount-api-vfs.c \
+       src/mount-setup.c \
+       src/exit-status.c
+
+systemd_remount_api_vfs_LDADD = \
+       libsystemd-basic.la
+
+systemd_cgroups_agent_SOURCES = \
+       src/cgroups-agent.c \
+       src/dbus-common.c
+
+systemd_cgroups_agent_CFLAGS = \
+       $(AM_CFLAGS) \
+       $(DBUS_CFLAGS)
+
+systemd_cgroups_agent_LDADD = \
+       libsystemd-basic.la \
+       $(DBUS_LIBS)
+
+systemctl_SOURCES = \
+       src/systemctl.c \
+       src/utmp-wtmp.c \
+       src/dbus-common.c \
+       src/path-lookup.c \
+       src/cgroup-show.c \
+       src/cgroup-util.c \
+       src/exit-status.c \
+       src/unit-name.c \
+       src/pager.c \
+       src/install.c \
+       src/spawn-agent.c \
+       src/logs-show.c
+
+systemctl_CFLAGS = \
+       $(AM_CFLAGS) \
+       $(DBUS_CFLAGS)
+
+systemctl_LDADD = \
+       libsystemd-basic.la \
+       libsystemd-daemon.la \
+       libsystemd-journal.la \
+       libsystemd-id128.la \
+       $(DBUS_LIBS)
+
+systemd_notify_SOURCES = \
+       src/notify.c \
+       src/readahead/sd-readahead.c
+
+systemd_notify_LDADD = \
+       libsystemd-basic.la \
+       libsystemd-daemon.la
+
+systemd_ask_password_SOURCES = \
+       src/ask-password.c \
+       src/ask-password-api.c
+
+systemd_ask_password_LDADD = \
+       libsystemd-basic.la
+
+systemd_reply_password_SOURCES = \
+       src/reply-password.c
+
+systemd_reply_password_LDADD = \
+       libsystemd-basic.la
+
+systemd_cgls_SOURCES = \
+       src/cgls.c \
+       src/cgroup-show.c \
+       src/cgroup-util.c \
+       src/pager.c
+
+systemd_cgls_LDADD = \
+       libsystemd-basic.la
+
+systemd_cgtop_SOURCES = \
+       src/cgtop.c \
+        src/cgroup-util.c
+
+systemd_cgtop_LDADD = \
+       libsystemd-basic.la
+
+systemd_nspawn_SOURCES = \
+       src/nspawn.c \
+       src/cgroup-util.c \
+       src/loopback-setup.c
+
+systemd_nspawn_LDADD = \
+       libsystemd-basic.la \
+       libsystemd-daemon.la
+
+systemd_stdio_bridge_SOURCES = \
+       src/bridge.c
+
+systemd_stdio_bridge_LDADD = \
+       libsystemd-basic.la
+
+systemd_tty_ask_password_agent_SOURCES = \
+       src/tty-ask-password-agent.c \
+       src/ask-password-api.c \
+       src/utmp-wtmp.c
+
+systemd_tty_ask_password_agent_LDADD = \
+       libsystemd-basic.la
 
 # ------------------------------------------------------------------------------
-SUBDIRS += src/docs
-
-include_HEADERS = src/libudev.h
-lib_LTLIBRARIES = libudev.la
-noinst_LTLIBRARIES = libudev-private.la
-
-libudev_la_SOURCES =\
-       src/libudev-private.h \
-       src/libudev.c \
-       src/libudev-list.c \
-       src/libudev-util.c \
-       src/libudev-device.c \
-       src/libudev-enumerate.c \
-       src/libudev-monitor.c \
-       src/libudev-queue.c
-
-libudev_la_LDFLAGS = \
-       $(AM_LDFLAGS) \
-       -version-info $(LIBUDEV_CURRENT):$(LIBUDEV_REVISION):$(LIBUDEV_AGE)
-
-libudev_private_la_SOURCES =\
-       $(libudev_la_SOURCES) \
-       src/libudev-util-private.c \
-       src/libudev-device-private.c \
-       src/libudev-queue-private.c
-
-if WITH_SELINUX
-libudev_private_la_SOURCES += src/libudev-selinux-private.c
-libudev_private_la_LIBADD = $(SELINUX_LIBS)
-endif
-
-pkgconfigdir = $(libdir)/pkgconfig
-pkgconfig_DATA = src/libudev.pc
-EXTRA_DIST += src/libudev.pc.in
-CLEANFILES += src/libudev.pc
-
-EXTRA_DIST += src/COPYING
-# move lib from $(libdir) to $(rootlib_execdir) and update devel link, if needed
-libudev-install-move-hook:
-       if test "$(libdir)" != "$(rootlib_execdir)"; then \
-               mkdir -p $(DESTDIR)$(rootlib_execdir) && \
-               so_img_name=$$(readlink $(DESTDIR)$(libdir)/libudev.so) && \
+libsystemd_daemon_la_SOURCES = \
+       src/sd-daemon.c
+
+libsystemd_daemon_la_CFLAGS = \
+       $(AM_CFLAGS) \
+       -fvisibility=hidden \
+       -DSD_EXPORT_SYMBOLS
+
+libsystemd_daemon_la_LDFLAGS = \
+       -shared \
+       -version-info $(LIBSYSTEMD_DAEMON_CURRENT):$(LIBSYSTEMD_DAEMON_REVISION):$(LIBSYSTEMD_DAEMON_AGE) \
+       -Wl,--version-script=$(top_srcdir)/src/libsystemd-daemon.sym
+
+pkginclude_HEADERS += \
+       src/systemd/sd-daemon.h
+
+# move lib from $(libdir) to $(rootlibdir) and update devel link, if needed
+libsystemd-daemon-install-hook:
+       if test "$(libdir)" != "$(rootlibdir)"; then \
+               mkdir -p $(DESTDIR)$(rootlibdir) && \
+               so_img_name=$$(readlink $(DESTDIR)$(libdir)/libsystemd-daemon.so) && \
                so_img_rel_target_prefix=$$(echo $(libdir) | sed 's,\(^/\|\)[^/][^/]*,..,g') && \
-               ln -sf $$so_img_rel_target_prefix$(rootlib_execdir)/$$so_img_name $(DESTDIR)$(libdir)/libudev.so && \
-               mv $(DESTDIR)$(libdir)/libudev.so.* $(DESTDIR)$(rootlib_execdir); \
+               ln -sf $$so_img_rel_target_prefix$(rootlibdir)/$$so_img_name $(DESTDIR)$(libdir)/libsystemd-daemon.so && \
+               mv $(DESTDIR)$(libdir)/libsystemd-daemon.so.* $(DESTDIR)$(rootlibdir); \
        fi
 
-libudev-uninstall-move-hook:
-       rm -f $(DESTDIR)$(rootlib_execdir)/libudev.so*
+INSTALL_EXEC_HOOKS += \
+       libsystemd-daemon-install-hook
+
+libsystemd-daemon-uninstall-hook:
+       rm -f $(DESTDIR)$(rootlibdir)/libsystemd-daemon.so*
+
+UNINSTALL_EXEC_HOOKS += \
+       libsystemd-daemon-uninstall-hook
+
+lib_LTLIBRARIES += \
+       libsystemd-daemon.la
 
-INSTALL_EXEC_HOOKS += libudev-install-move-hook
-UNINSTALL_EXEC_HOOKS += libudev-uninstall-move-hook
+pkgconfiglib_DATA += \
+       src/libsystemd-daemon.pc
+
+MANPAGES += \
+       man/sd-daemon.7 \
+       man/sd_notify.3 \
+       man/sd_listen_fds.3 \
+       man/sd_is_fifo.3 \
+       man/sd_booted.3
+
+MANPAGES_ALIAS += \
+       man/sd_is_socket.3 \
+       man/sd_is_socket_unix.3 \
+       man/sd_is_socket_inet.3 \
+       man/sd_is_mq.3 \
+       man/sd_notifyf.3
+
+man/sd_is_socket.3: man/sd_is_fifo.3
+man/sd_is_socket_unix.3: man/sd_is_fifo.3
+man/sd_is_socket_inet.3: man/sd_is_fifo.3
+man/sd_is_mq.3: man/sd_is_fifo.3
+man/sd_notifyf.3: man/sd_notify.3
+
+EXTRA_DIST += \
+       src/libsystemd-daemon.pc.in \
+       src/libsystemd-daemon.sym
 
 # ------------------------------------------------------------------------------
-udev-confdirs:
-       -mkdir -p $(DESTDIR)$(sysconfdir)/udev/rules.d
-       -mkdir -p $(DESTDIR)$(libexecdir)/udev/devices
-
-INSTALL_DATA_HOOKS += udev-confdirs
-
-udevrulesdir = $(libexecdir)/udev/rules.d
-dist_udevrules_DATA = \
-       rules/42-usb-hid-pm.rules \
-       rules/50-udev-default.rules \
-       rules/60-persistent-storage-tape.rules \
-       rules/60-persistent-serial.rules \
-       rules/60-persistent-input.rules \
-       rules/60-persistent-alsa.rules \
-       rules/60-persistent-storage.rules \
-       rules/75-net-description.rules \
-       rules/75-tty-description.rules \
-       rules/78-sound-card.rules \
-       rules/80-drivers.rules \
-       rules/95-udev-late.rules
-
-udevconfdir = $(sysconfdir)/udev
-dist_udevconf_DATA = src/udev.conf
-
-sharepkgconfigdir = $(datadir)/pkgconfig
-sharepkgconfig_DATA = src/udev.pc
-EXTRA_DIST += src/udev.pc.in
-CLEANFILES += src/udev.pc
-
-if WITH_SYSTEMD
-dist_systemdsystemunit_DATA = \
-       src/udev-control.socket \
-       src/udev-kernel.socket
-
-systemdsystemunit_DATA = \
-       src/udev.service \
-       src/udev-trigger.service \
-       src/udev-settle.service
+libsystemd_id128_la_SOURCES = \
+       src/sd-id128.c
+
+libsystemd_id128_la_CFLAGS = \
+       $(AM_CFLAGS) \
+       -fvisibility=hidden
+
+libsystemd_id128_la_LDFLAGS = \
+       -shared \
+       -version-info $(LIBSYSTEMD_ID128_CURRENT):$(LIBSYSTEMD_ID128_REVISION):$(LIBSYSTEMD_ID128_AGE) \
+       -Wl,--version-script=$(top_srcdir)/src/libsystemd-id128.sym
+
+libsystemd_id128_la_LIBADD = \
+       libsystemd-basic.la
+
+test_id128_SOURCES = \
+       src/test-id128.c \
+       src/sd-id128.c
+
+test_id128_LDADD = \
+       libsystemd-basic.la
+
+noinst_PROGRAMS += \
+       test-id128
+
+pkginclude_HEADERS += \
+       src/systemd/sd-id128.h
+
+lib_LTLIBRARIES += \
+       libsystemd-id128.la
+
+pkgconfiglib_DATA += \
+       src/libsystemd-id128.pc
+
+# move lib from $(libdir) to $(rootlibdir) and update devel link, if needed
+libsystemd-id128-install-hook:
+       if test "$(libdir)" != "$(rootlibdir)"; then \
+               mkdir -p $(DESTDIR)$(rootlibdir) && \
+               so_img_name=$$(readlink $(DESTDIR)$(libdir)/libsystemd-id128.so) && \
+               so_img_rel_target_prefix=$$(echo $(libdir) | sed 's,\(^/\|\)[^/][^/]*,..,g') && \
+               ln -sf $$so_img_rel_target_prefix$(rootlibdir)/$$so_img_name $(DESTDIR)$(libdir)/libsystemd-id128.so && \
+               mv $(DESTDIR)$(libdir)/libsystemd-id128.so.* $(DESTDIR)$(rootlibdir); \
+       fi
+
+INSTALL_EXEC_HOOKS += \
+       libsystemd-id128-install-hook
+
+libsystemd-id128-uninstall-hook:
+       rm -f $(DESTDIR)$(rootlibdir)/libsystemd-id128.so*
+
+UNINSTALL_EXEC_HOOKS += \
+       libsystemd-id128-uninstall-hook
 
 EXTRA_DIST += \
-       src/udev.service.in \
-       src/udev-trigger.service.in \
-       src/udev-settle.service.in
+       src/libsystemd-id128.pc.in \
+       src/libsystemd-id128.sym
 
-CLEANFILES += \
-       src/udev.service \
-       src/udev-trigger.service \
-       src/udev-settle.service
+# ------------------------------------------------------------------------------
+systemd_journald_SOURCES = \
+       src/journal/journald.c \
+       src/journal/sd-journal.c \
+       src/journal/journal-file.c \
+       src/journal/lookup3.c \
+       src/journal/journal-rate-limit.c \
+       src/sd-id128.c \
+       src/cgroup-util.c
+
+if HAVE_ACL
+systemd_journald_SOURCES += \
+       src/acl-util.c
+endif
 
-systemd-install-hook:
-       mkdir -p $(DESTDIR)$(systemdsystemunitdir)/sockets.target.wants
-       ln -sf ../udev-control.socket $(DESTDIR)$(systemdsystemunitdir)/sockets.target.wants/udev-control.socket
-       ln -sf ../udev-kernel.socket $(DESTDIR)$(systemdsystemunitdir)/sockets.target.wants/udev-kernel.socket
-       mkdir -p $(DESTDIR)$(systemdsystemunitdir)/basic.target.wants
-       ln -sf ../udev.service $(DESTDIR)$(systemdsystemunitdir)/basic.target.wants/udev.service
-       ln -sf ../udev-trigger.service $(DESTDIR)$(systemdsystemunitdir)/basic.target.wants/udev-trigger.service
+nodist_systemd_journald_SOURCES = \
+       src/journal/journald-gperf.c
+
+systemd_journald_CFLAGS = \
+       $(AM_CFLAGS) \
+       $(ACL_CFLAGS)
+
+systemd_journald_LDADD = \
+       libsystemd-basic.la \
+       libsystemd-daemon.la \
+       libsystemd-login.la \
+       $(ACL_LIBS)
+
+if HAVE_XZ
+systemd_journald_SOURCES += \
+       src/journal/compress.c
+systemd_journald_CFLAGS += \
+       $(XZ_CFLAGS)
+systemd_journald_LDADD += \
+       $(XZ_LIBS)
+endif
 
-INSTALL_DATA_HOOKS += systemd-install-hook
+systemd_cat_SOURCES = \
+       src/journal/cat.c
+
+systemd_cat_LDADD = \
+       libsystemd-basic.la \
+       libsystemd-journal.la
+
+journalctl_SOURCES = \
+       src/journal/journalctl.c \
+       src/pager.c \
+       src/logs-show.c
+
+journalctl_LDADD = \
+       libsystemd-basic.la \
+       libsystemd-journal.la \
+       libsystemd-id128.la
+
+if HAVE_XZ
+journalctl_SOURCES += \
+       src/journal/compress.c
+journalctl_CFLAGS = \
+       $(AM_CFLAGS) \
+       $(XZ_CFLAGS)
+journalctl_LDADD += \
+       $(XZ_LIBS)
 endif
 
-bin_PROGRAMS = \
-       udevadm
-
-pkglibexec_PROGRAMS = \
-       udevd
-
-udev_common_sources = \
-       src/udev.h \
-       src/udev-event.c \
-       src/udev-watch.c \
-       src/udev-node.c \
-       src/udev-rules.c \
-       src/udev-ctrl.c \
-       src/udev-builtin.c \
-       src/udev-builtin-blkid.c \
-       src/udev-builtin-firmware.c \
-       src/udev-builtin-hwdb.c \
-       src/udev-builtin-input_id.c \
-       src/udev-builtin-kmod.c \
-       src/udev-builtin-path_id.c \
-       src/udev-builtin-usb_id.c
-
-udev_common_CFLAGS = \
-       $(BLKID_CFLAGS) \
-       $(KMOD_CFLAGS)
+test_journal_SOURCES = \
+       src/journal/test-journal.c \
+       src/journal/sd-journal.c \
+       src/journal/journal-file.c \
+       src/journal/lookup3.c \
+       src/journal/journal-send.c \
+       src/sd-id128.c
 
-udev_common_LDADD = \
-       libudev-private.la \
-       $(BLKID_LIBS) \
-       $(KMOD_LIBS)
+test_journal_LDADD = \
+       libsystemd-basic.la
 
-udev_common_CPPFLAGS = \
-       $(AM_CPPFLAGS) \
-       -DFIRMWARE_PATH="$(FIRMWARE_PATH)" \
-       -DUSB_DATABASE=\"$(USB_DATABASE)\" -DPCI_DATABASE=\"$(PCI_DATABASE)\"
+if HAVE_XZ
+test_journal_SOURCES += \
+       src/journal/compress.c
 
-udevd_SOURCES = \
-       $(udev_common_sources) \
-       src/udevd.c \
-       src/sd-daemon.h \
-       src/sd-daemon.c
-udevd_CFLAGS = $(udev_common_CFLAGS)
-udevd_LDADD = $(udev_common_LDADD)
-udevd_CPPFLAGS = $(udev_common_CPPFLAGS)
-
-udevadm_SOURCES = \
-       $(udev_common_sources) \
-       src/udevadm.c \
-       src/udevadm-info.c \
-       src/udevadm-control.c \
-       src/udevadm-monitor.c \
-       src/udevadm-settle.c \
-       src/udevadm-trigger.c \
-       src/udevadm-test.c \
-       src/udevadm-test-builtin.c
-udevadm_CFLAGS = $(udev_common_CFLAGS)
-udevadm_LDADD = $(udev_common_LDADD)
-udevadm_CPPFLAGS = $(udev_common_CPPFLAGS)
+test_journal_CFLAGS = \
+       $(AM_CFLAGS) \
+       $(XZ_CFLAGS)
 
-# ------------------------------------------------------------------------------
-if ENABLE_MANPAGES
-dist_man_MANS += \
-       src/udev.7 \
-       src/udevadm.8 \
-       src/udevd.8
+test_journal_LDADD += \
+       $(XZ_LIBS)
+endif
+
+test_journal_send_SOURCES = \
+       src/journal/test-journal-send.c
+
+test_journal_send_LDADD = \
+       libsystemd-basic.la \
+       libsystemd-journal.la
+
+libsystemd_journal_la_SOURCES = \
+       src/journal/sd-journal.c \
+       src/journal/journal-file.c \
+       src/journal/lookup3.c \
+       src/journal/journal-send.c
+
+libsystemd_journal_la_CFLAGS = \
+       $(AM_CFLAGS) \
+       -fvisibility=hidden
+
+libsystemd_journal_la_LDFLAGS = \
+       -shared \
+       -version-info $(LIBSYSTEMD_JOURNAL_CURRENT):$(LIBSYSTEMD_JOURNAL_REVISION):$(LIBSYSTEMD_JOURNAL_AGE) \
+       -Wl,--version-script=$(top_srcdir)/src/journal/libsystemd-journal.sym
+
+libsystemd_journal_la_LIBADD = \
+       libsystemd-basic.la \
+       libsystemd-id128.la
+
+if HAVE_XZ
+libsystemd_journal_la_SOURCES += \
+       src/journal/compress.c
+
+libsystemd_journal_la_CFLAGS += \
+       $(XZ_CFLAGS)
+
+libsystemd_journal_la_LIBADD += \
+       $(XZ_LIBS)
 endif
 
+# move lib from $(libdir) to $(rootlibdir) and update devel link, if needed
+libsystemd-journal-install-hook:
+       if test "$(libdir)" != "$(rootlibdir)"; then \
+               mkdir -p $(DESTDIR)$(rootlibdir) && \
+               so_img_name=$$(readlink $(DESTDIR)$(libdir)/libsystemd-journal.so) && \
+               so_img_rel_target_prefix=$$(echo $(libdir) | sed 's,\(^/\|\)[^/][^/]*,..,g') && \
+               ln -sf $$so_img_rel_target_prefix$(rootlibdir)/$$so_img_name $(DESTDIR)$(libdir)/libsystemd-journal.so && \
+               mv $(DESTDIR)$(libdir)/libsystemd-journal.so.* $(DESTDIR)$(rootlibdir); \
+       fi
+
+INSTALL_EXEC_HOOKS += \
+       libsystemd-journal-install-hook
+
+libsystemd-journal-uninstall-hook:
+       rm -f $(DESTDIR)$(rootlibdir)/libsystemd-journal.so*
+
+UNINSTALL_EXEC_HOOKS += \
+       libsystemd-journal-uninstall-hook
+
+noinst_PROGRAMS += \
+       test-journal \
+        test-journal-send
+
+pkginclude_HEADERS += \
+       src/systemd/sd-journal.h \
+       src/systemd/sd-messages.h
+
+lib_LTLIBRARIES += \
+       libsystemd-journal.la
+
+rootlibexec_PROGRAMS += \
+       systemd-journald
+
+rootbin_PROGRAMS += \
+       journalctl
+
+bin_PROGRAMS += \
+       systemd-cat
+
+dist_systemunit_DATA += \
+       units/systemd-journald.socket
+
+nodist_systemunit_DATA += \
+       units/systemd-journald.service
+
+dist_pkgsysconf_DATA += \
+       src/journal/journald.conf
+
+pkgconfiglib_DATA += \
+       src/journal/libsystemd-journal.pc
+
+journal-install-data-hook:
+       $(MKDIR_P) -m 0755 \
+               $(DESTDIR)$(systemunitdir)/sockets.target.wants \
+               $(DESTDIR)$(systemunitdir)/sysinit.target.wants
+       ( cd $(DESTDIR)$(systemunitdir)/sockets.target.wants && \
+               rm -f systemd-journald.socket && \
+               $(LN_S) ../systemd-journald.socket )
+       ( cd $(DESTDIR)$(systemunitdir)/sysinit.target.wants && \
+               rm -f systemd-journald.service && \
+               $(LN_S) ../systemd-journald.service )
+
+INSTALL_DATA_HOOKS += \
+       journal-install-data-hook
+
 EXTRA_DIST += \
-       src/udev.xml \
-       src/udevadm.xml \
-       src/udevd.xml
+       src/journal/journald.h \
+       src/journal/journal-def.h \
+       src/journal/journal-internal.h \
+       src/journal/journal-file.h \
+       src/journal/lookup3.h \
+       src/journal/compress.h \
+       src/journal/journal-rate-limit.h \
+       src/journal/libsystemd-journal.pc.in \
+       src/journal/libsystemd-journal.sym \
+       units/systemd-journald.service.in \
+       src/journal/journald-gperf.gperf
 
-if HAVE_XSLTPROC
-dist_noinst_DATA = \
-       src/udev.html \
-       src/udevadm.html \
-       src/udevd.html
+CLEANFILES += \
+       src/journal/journald-gperf.c
+
+# ------------------------------------------------------------------------------
+if ENABLE_COREDUMP
+systemd_coredump_SOURCES = \
+       src/journal/coredump.c
+
+systemd_coredump_LDADD = \
+       libsystemd-basic.la \
+       libsystemd-journal.la \
+       libsystemd-login.la
+
+rootlibexec_PROGRAMS += \
+       systemd-coredump
 
-src/%.7 src/%.8 : src/%.xml
-       $(AM_V_GEN)$(XSLTPROC) -o $@ -nonet http://docbook.sourceforge.net/release/xsl/current/manpages/docbook.xsl $<
+sysctl_DATA = \
+       sysctl.d/coredump.conf
 
-src/%.html : src/%.xml
-       $(AM_V_GEN)$(XSLTPROC) -o $@ -nonet http://docbook.sourceforge.net/release/xsl/current/xhtml-1_1/docbook.xsl $<
+EXTRA_DIST += \
+       sysctl.d/coredump.conf.in
+
+CLEANFILES += \
+       sysctl.d/coredump.conf
 endif
 
 # ------------------------------------------------------------------------------
-TESTS = \
-       test/udev-test.pl \
-       test/rules-test.sh
+if ENABLE_BINFMT
+systemd_binfmt_SOURCES = \
+       src/binfmt/binfmt.c
 
-check_PROGRAMS = \
-       test-libudev \
-       test-udev
+systemd_binfmt_LDADD = \
+       libsystemd-basic.la
 
-test_libudev_SOURCES = src/test-libudev.c
-test_libudev_LDADD = libudev.la
+rootlibexec_PROGRAMS += \
+       systemd-binfmt
 
-test_udev_SOURCES = \
-       $(udev_common_sources) \
-       src/test-udev.c
-test_udev_CFLAGS = $(udev_common_CFLAGS)
-test_udev_LDADD = $(udev_common_LDADD)
-test_udev_CPPFLAGS = $(udev_common_CPPFLAGS)
-test_udev_DEPENDENCIES = test/sys
+dist_systemunit_DATA += \
+       units/proc-sys-fs-binfmt_misc.automount \
+       units/proc-sys-fs-binfmt_misc.mount
 
-# packed sysfs test tree
-test/sys:
-       $(AM_V_GEN)mkdir -p test && tar -C test/ -xJf $(top_srcdir)/test/sys.tar.xz
+nodist_systemunit_DATA += \
+       units/systemd-binfmt.service
 
-test-sys-distclean:
-       -rm -rf test/sys
-DISTCLEAN_LOCAL_HOOKS += test-sys-distclean
+binfmt-install-data-hook:
+       $(MKDIR_P) -m 0755 \
+               $(DESTDIR)$(prefix)/lib/binfmt.d \
+               $(DESTDIR)$(sysconfdir)/binfmt.d \
+               $(DESTDIR)$(systemunitdir)/sysinit.target.wants
+       ( cd $(DESTDIR)$(systemunitdir)/sysinit.target.wants && \
+               rm -f systemd-binfmt.service \
+                       proc-sys-fs-binfmt_misc.automount && \
+               $(LN_S) ../systemd-binfmt.service systemd-binfmt.service && \
+               $(LN_S) ../proc-sys-fs-binfmt_misc.automount proc-sys-fs-binfmt_misc.automount )
 
-EXTRA_DIST += test/sys.tar.xz
+INSTALL_DATA_HOOKS += \
+       binfmt-install-data-hook
+
+MANPAGES += \
+       man/binfmt.d.5
+
+EXTRA_DIST += \
+       units/systemd-binfmt.service.in
+endif
 
 # ------------------------------------------------------------------------------
-ata_id_SOURCES = src/ata_id/ata_id.c
-ata_id_LDADD = libudev-private.la
-pkglibexec_PROGRAMS += ata_id
+if ENABLE_VCONSOLE
+systemd_vconsole_setup_SOURCES = \
+       src/vconsole/vconsole-setup.c
+
+systemd_vconsole_setup_LDADD = \
+       libsystemd-basic.la
+
+rootlibexec_PROGRAMS += \
+       systemd-vconsole-setup
+
+nodist_systemunit_DATA += \
+       units/systemd-vconsole-setup.service
+
+vconsole-install-data-hook:
+       $(MKDIR_P) -m 0755 \
+               $(DESTDIR)$(systemunitdir)/sysinit.target.wants
+       ( cd $(DESTDIR)$(systemunitdir)/sysinit.target.wants && \
+               rm -f systemd-vconsole-setup.service && \
+               $(LN_S) ../systemd-vconsole-setup.service systemd-vconsole-setup.service )
+
+INSTALL_DATA_HOOKS += \
+       vconsole-install-data-hook
+
+MANPAGES += \
+       man/vconsole.conf.5
+
+EXTRA_DIST += \
+       units/systemd-vconsole-setup.service.in
+endif
 
 # ------------------------------------------------------------------------------
-cdrom_id_SOURCES = src/cdrom_id/cdrom_id.c
-cdrom_id_LDADD = libudev-private.la
-pkglibexec_PROGRAMS += cdrom_id
-dist_udevrules_DATA += src/cdrom_id/60-cdrom_id.rules
+if ENABLE_READAHEAD
+systemd_readahead_collect_SOURCES = \
+       src/readahead/readahead-collect.c \
+       src/readahead/readahead-common.c
+
+systemd_readahead_collect_LDADD = \
+       libsystemd-basic.la \
+       libsystemd-daemon.la \
+       $(UDEV_LIBS)
+
+systemd_readahead_collect_CFLAGS = \
+       $(AM_CFLAGS) \
+       $(UDEV_CFLAGS)
+
+systemd_readahead_replay_SOURCES = \
+       src/readahead/readahead-replay.c \
+       src/readahead/readahead-common.c
+
+systemd_readahead_replay_CFLAGS = \
+       $(AM_CFLAGS) \
+       $(UDEV_CFLAGS)
+
+systemd_readahead_replay_LDADD = \
+       libsystemd-basic.la \
+       libsystemd-daemon.la \
+       $(UDEV_LIBS)
+
+rootlibexec_PROGRAMS += \
+       systemd-readahead-collect \
+       systemd-readahead-replay
+
+dist_systemunit_DATA += \
+       units/systemd-readahead-done.timer
+
+nodist_systemunit_DATA += \
+       units/systemd-readahead-collect.service \
+       units/systemd-readahead-replay.service \
+       units/systemd-readahead-done.service
+
+EXTRA_DIST += \
+       src/systemd/sd-readahead.h \
+       src/readahead/readahead-common.h \
+       units/systemd-readahead-collect.service.in \
+       units/systemd-readahead-replay.service.in \
+       units/systemd-readahead-done.service.in
+
+MANPAGES += \
+       man/sd_readahead.3 \
+       man/sd-readahead.7
+endif
 
 # ------------------------------------------------------------------------------
-collect_SOURCES = src/collect/collect.c
-collect_LDADD = libudev-private.la
-pkglibexec_PROGRAMS += collect
+if ENABLE_QUOTACHECK
+rootlibexec_PROGRAMS += \
+       systemd-quotacheck
+
+nodist_systemunit_DATA += \
+       units/quotacheck.service
+
+EXTRA_DIST += \
+       units/quotacheck.service.in
+
+systemd_quotacheck_SOURCES = \
+       src/quotacheck.c
+
+systemd_quotacheck_LDADD = \
+       libsystemd-basic.la
+endif
 
 # ------------------------------------------------------------------------------
-scsi_id_SOURCES =\
-       src/scsi_id/scsi_id.c \
-       src/scsi_id/scsi_serial.c \
-       src/scsi_id/scsi.h \
-       src/scsi_id/scsi_id.h
-scsi_id_LDADD = libudev-private.la
-pkglibexec_PROGRAMS += scsi_id
-dist_man_MANS += src/scsi_id/scsi_id.8
-EXTRA_DIST += src/scsi_id/README
+if ENABLE_RANDOMSEED
+rootlibexec_PROGRAMS += \
+       systemd-random-seed
+
+nodist_systemunit_DATA += \
+       units/systemd-random-seed-save.service \
+       units/systemd-random-seed-load.service
+
+EXTRA_DIST += \
+       units/systemd-random-seed-save.service.in \
+       units/systemd-random-seed-load.service.in
+
+systemd_random_seed_SOURCES = \
+       src/random-seed.c
+
+systemd_random_seed_LDADD = \
+       libsystemd-basic.la
+
+randomseed-install-data-hook:
+       $(MKDIR_P) -m 0755 \
+               $(DESTDIR)$(systemunitdir)/shutdown.target.wants \
+               $(DESTDIR)$(systemunitdir)/sysinit.target.wants
+       ( cd $(DESTDIR)$(systemunitdir)/shutdown.target.wants && \
+               rm -f systemd-random-seed-save.service && \
+               $(LN_S) ../systemd-random-seed-save.service systemd-random-seed-save.service )
+       ( cd $(DESTDIR)$(systemunitdir)/sysinit.target.wants && \
+               rm -f systemd-random-seed-load.service && \
+               $(LN_S) ../systemd-random-seed-load.service systemd-random-seed-load.service )
+
+INSTALL_DATA_HOOKS += \
+       randomseed-install-data-hook
+endif
+
+# ------------------------------------------------------------------------------
+if HAVE_LIBCRYPTSETUP
+rootlibexec_PROGRAMS += \
+       systemd-cryptsetup
+
+systemgenerator_PROGRAMS += \
+       systemd-cryptsetup-generator
+
+dist_systemunit_DATA += \
+       units/cryptsetup.target
+
+systemd_cryptsetup_SOURCES = \
+       src/cryptsetup/cryptsetup.c \
+       src/ask-password-api.c
+
+systemd_cryptsetup_CFLAGS = \
+       $(AM_CFLAGS) \
+       $(LIBCRYPTSETUP_CFLAGS) \
+       $(UDEV_CFLAGS)
+
+systemd_cryptsetup_LDADD = \
+       $(LIBCRYPTSETUP_LIBS) \
+       $(UDEV_LIBS) \
+       libsystemd-basic.la
+
+systemd_cryptsetup_generator_SOURCES = \
+       src/cryptsetup/cryptsetup-generator.c \
+       src/unit-name.c
+
+systemd_cryptsetup_generator_LDADD = \
+       libsystemd-basic.la
+
+cryptsetup-install-data-hook:
+       $(MKDIR_P) -m 0755 \
+               $(DESTDIR)$(systemunitdir)/sysinit.target.wants
+       ( cd $(DESTDIR)$(systemunitdir)/sysinit.target.wants && \
+               rm -f cryptsetup.target && \
+               $(LN_S) ../cryptsetup.target cryptsetup.target )
+
+INSTALL_DATA_HOOKS += \
+       cryptsetup-install-data-hook
+endif
 
 # ------------------------------------------------------------------------------
-v4l_id_SOURCES = src/v4l_id/v4l_id.c
-v4l_id_LDADD = libudev-private.la
-pkglibexec_PROGRAMS += v4l_id
-dist_udevrules_DATA += src/v4l_id/60-persistent-v4l.rules
+if ENABLE_HOSTNAMED
+systemd_hostnamed_SOURCES = \
+       src/hostname/hostnamed.c \
+       src/dbus-common.c \
+       src/polkit.c
+
+systemd_hostnamed_CFLAGS = \
+       $(AM_CFLAGS) \
+       $(DBUS_CFLAGS)
+
+systemd_hostnamed_LDADD = \
+       libsystemd-basic.la \
+       libsystemd-daemon.la \
+       $(DBUS_LIBS)
+
+rootlibexec_PROGRAMS += \
+       systemd-hostnamed
+
+nodist_systemunit_DATA += \
+       units/systemd-hostnamed.service
+
+dist_dbuspolicy_DATA += \
+       src/hostname/org.freedesktop.hostname1.conf
+
+dist_dbussystemservice_DATA += \
+       src/hostname/org.freedesktop.hostname1.service
+
+polkitpolicy_in_files += \
+       src/hostname/org.freedesktop.hostname1.policy.in
+
+dbusinterface_DATA += \
+       org.freedesktop.hostname1.xml
+
+org.freedesktop.hostname1.xml: systemd-hostnamed
+       $(AM_V_GEN)$(LIBTOOL) --mode=execute $(OBJCOPY) -O binary -j introspect.hostname1 $< $@.tmp && \
+               $(STRINGS) $@.tmp | $(AWK) -f $(srcdir)/introspect.awk | \
+               $(DBUS_PREPROCESS) -o $@ - && rm $@.tmp
+
+hostnamed-install-data-hook:
+       ( cd $(DESTDIR)$(systemunitdir) && \
+               rm -f dbus-org.freedesktop.hostname1.service && \
+               $(LN_S) systemd-hostnamed.service dbus-org.freedesktop.hostname1.service )
+
+INSTALL_DATA_HOOKS += \
+       hostnamed-install-data-hook
+
+EXTRA_DIST += \
+       units/systemd-hostnamed.service.in
+endif
 
 # ------------------------------------------------------------------------------
-accelerometer_SOURCES = src/accelerometer/accelerometer.c
-accelerometer_LDADD = libudev-private.la -lm
-pkglibexec_PROGRAMS += accelerometer
-dist_udevrules_DATA += src/accelerometer/61-accelerometer.rules
+if ENABLE_LOCALED
+systemd_localed_SOURCES = \
+       src/locale/localed.c \
+       src/dbus-common.c \
+       src/polkit.c
+
+systemd_localed_CFLAGS = \
+       $(AM_CFLAGS) \
+       $(DBUS_CFLAGS)
+
+systemd_localed_LDADD = \
+       libsystemd-basic.la \
+       libsystemd-daemon.la \
+       $(DBUS_LIBS)
+
+nodist_systemunit_DATA += \
+       units/systemd-localed.service
+
+rootlibexec_PROGRAMS += \
+       systemd-localed
+
+dist_dbuspolicy_DATA += \
+       src/locale/org.freedesktop.locale1.conf
+
+dist_dbussystemservice_DATA += \
+       src/locale/org.freedesktop.locale1.service
+
+polkitpolicy_in_files += \
+       src/locale/org.freedesktop.locale1.policy.in
+
+dbusinterface_DATA += \
+       org.freedesktop.locale1.xml
+
+org.freedesktop.locale1.xml: systemd-localed
+       $(AM_V_GEN)$(LIBTOOL) --mode=execute $(OBJCOPY) -O binary -j introspect.locale1 $< $@.tmp && \
+               $(STRINGS) $@.tmp | $(AWK) -f $(srcdir)/introspect.awk | \
+               $(DBUS_PREPROCESS) -o $@ - && rm $@.tmp
+
+localed-install-data-hook:
+       ( cd $(DESTDIR)$(systemunitdir) && \
+               rm -f dbus-org.freedesktop.locale1.service && \
+               $(LN_S) systemd-localed.service dbus-org.freedesktop.locale1.service )
+
+INSTALL_DATA_HOOKS += \
+       localed-install-data-hook
+
+EXTRA_DIST += \
+       units/systemd-localed.service.in
+
+dist_pkgdata_DATA = \
+       src/locale/kbd-model-map
+
+dist_noinst_SCRIPT = \
+       src/locale/generate-kbd-model-map
+
+update-kbd-model-map:
+       src/locale/generate-kbd-model-map > src/locale/kbd-model-map
+
+endif
 
 # ------------------------------------------------------------------------------
-if ENABLE_GUDEV
-SUBDIRS += src/gudev/docs
-
-libgudev_includedir=$(includedir)/gudev-1.0/gudev
-libgudev_include_HEADERS = \
-       src/gudev/gudev.h \
-       src/gudev/gudevenums.h \
-       src/gudev/gudevenumtypes.h \
-       src/gudev/gudevtypes.h \
-       src/gudev/gudevclient.h \
-       src/gudev/gudevdevice.h \
-       src/gudev/gudevenumerator.h
-
-lib_LTLIBRARIES += libgudev-1.0.la
-
-pkgconfig_DATA += src/gudev/gudev-1.0.pc
-EXTRA_DIST += src/gudev/gudev-1.0.pc.in
-CLEANFILES += src/gudev/gudev-1.0.pc
-
-libgudev_1_0_la_SOURCES = \
-       src/gudev/gudevenums.h \
-       src/gudev/gudevenumtypes.h \
-       src/gudev/gudevenumtypes.h\
-       src/gudev/gudevtypes.h \
-       src/gudev/gudevclient.h \
-       src/gudev/gudevclient.c \
-       src/gudev/gudevdevice.h \
-       src/gudev/gudevdevice.c \
-       src/gudev/gudevenumerator.h \
-       src/gudev/gudevenumerator.c \
-       src/gudev/gudevprivate.h
-
-nodist_libgudev_1_0_la_SOURCES = \
-       src/gudev/gudevmarshal.h \
-       src/gudev/gudevmarshal.c \
-       src/gudev/gudevenumtypes.h \
-       src/gudev/gudevenumtypes.c
-BUILT_SOURCES += $(nodist_libgudev_1_0_la_SOURCES)
-
-libgudev_1_0_la_CPPFLAGS = \
-       $(AM_CPPFLAGS) \
-       -I$(top_builddir)/src\
-       -I$(top_srcdir)/src\
-       -I$(top_builddir)/src/gudev \
-       -I$(top_srcdir)/src/gudev \
-       -D_POSIX_PTHREAD_SEMANTICS -D_REENTRANT \
-       -D_GUDEV_COMPILATION \
-       -DG_LOG_DOMAIN=\"GUdev\"
-
-libgudev_1_0_la_CFLAGS = \
-       -fvisibility=default \
-       $(GLIB_CFLAGS)
-
-libgudev_1_0_la_LIBADD = libudev.la $(GLIB_LIBS)
-
-libgudev_1_0_la_LDFLAGS = \
-       -version-info $(LIBGUDEV_CURRENT):$(LIBGUDEV_REVISION):$(LIBGUDEV_AGE) \
-       -export-dynamic -no-undefined \
-       -export-symbols-regex '^g_udev_.*'
+if ENABLE_TIMEDATED
+systemd_timedated_SOURCES = \
+       src/timedate/timedated.c \
+       src/dbus-common.c \
+       src/polkit.c
+
+systemd_timedated_CFLAGS = \
+       $(AM_CFLAGS) \
+       $(DBUS_CFLAGS)
+
+systemd_timedated_LDADD = \
+       libsystemd-basic.la \
+       libsystemd-daemon.la \
+       $(DBUS_LIBS)
+
+rootlibexec_PROGRAMS += \
+       systemd-timedated
+
+dist_dbussystemservice_DATA += \
+       src/timedate/org.freedesktop.timedate1.service
+
+dist_dbuspolicy_DATA += \
+       src/timedate/org.freedesktop.timedate1.conf
+
+nodist_systemunit_DATA += \
+       units/systemd-timedated.service
+
+polkitpolicy_in_files += \
+       src/timedate/org.freedesktop.timedate1.policy.in
+
+org.freedesktop.timedate1.xml: systemd-timedated
+       $(AM_V_GEN)$(LIBTOOL) --mode=execute $(OBJCOPY) -O binary -j introspect.timedate1 $< $@.tmp && \
+               $(STRINGS) $@.tmp | $(AWK) -f $(srcdir)/introspect.awk | \
+               $(DBUS_PREPROCESS) -o $@ - && rm $@.tmp
+
+dbusinterface_DATA += \
+       org.freedesktop.timedate1.xml
+
+timedated-install-data-hook:
+       ( cd $(DESTDIR)$(systemunitdir) && \
+               rm -f dbus-org.freedesktop.timedate1.service  && \
+               $(LN_S) systemd-timedated.service dbus-org.freedesktop.timedate1.service )
+
+INSTALL_DATA_HOOKS += \
+       timedated-install-data-hook
 
 EXTRA_DIST += \
-       src/gudev/COPYING \
-       src/gudev/gudevmarshal.list \
-       src/gudev/gudevenumtypes.h.template \
-       src/gudev/gudevenumtypes.c.template \
-       src/gudev/gjs-example.js \
-       src/gudev/seed-example-enum.js \
-       src/gudev/seed-example.js
-
-src/gudev/gudevmarshal.h: src/gudev/gudevmarshal.list
-       $(AM_V_GEN)glib-genmarshal $< --prefix=g_udev_marshal --header > $@
-
-src/gudev/gudevmarshal.c: src/gudev/gudevmarshal.list
-       $(AM_V_GEN)echo "#include \"gudevmarshal.h\"" > $@ && \
-       glib-genmarshal $< --prefix=g_udev_marshal --body >> $@
-
-src/gudev/gudevenumtypes.h: src/gudev/gudevenumtypes.h.template src/gudev/gudevenums.h
-       $(AM_V_GEN)glib-mkenums --template $^ > \
-           $@.tmp && mv $@.tmp $@
-
-src/gudev/gudevenumtypes.c: src/gudev/gudevenumtypes.c.template src/gudev/gudevenums.h
-       $(AM_V_GEN)glib-mkenums --template $^ > \
-           $@.tmp && mv $@.tmp $@
-
-if ENABLE_INTROSPECTION
-src/gudev/GUdev-1.0.gir: libgudev-1.0.la $(G_IR_SCANNER)
-       $(AM_V_GEN)$(G_IR_SCANNER) -v \
-               --warn-all \
-               --namespace GUdev \
-               --nsversion=1.0 \
-               --include=GObject-2.0 \
-               --library=gudev-1.0 \
-               --library-path=$(top_builddir)/src \
-               --library-path=$(top_builddir)/src/gudev \
-               --output $@ \
-               --pkg=glib-2.0 \
-               --pkg=gobject-2.0 \
-               --pkg-export=gudev-1.0 \
-               --c-include=gudev/gudev.h \
-               -I$(top_srcdir)/src/\
-               -I$(top_builddir)/src/\
-               -D_GUDEV_COMPILATION \
-               -D_GUDEV_WORK_AROUND_DEV_T_BUG \
-               $(top_srcdir)/src/gudev/gudev.h \
-               $(top_srcdir)/src/gudev/gudevtypes.h \
-               $(top_srcdir)/src/gudev/gudevenums.h \
-               $(or $(wildcard $(top_builddir)/src/gudev/gudevenumtypes.h),$(top_srcdir)/src/gudev/gudevenumtypes.h) \
-               $(top_srcdir)/src/gudev/gudevclient.h \
-               $(top_srcdir)/src/gudev/gudevdevice.h \
-               $(top_srcdir)/src/gudev/gudevenumerator.h \
-               $(top_srcdir)/src/gudev/gudevclient.c \
-               $(top_srcdir)/src/gudev/gudevdevice.c \
-               $(top_srcdir)/src/gudev/gudevenumerator.c
-
-src/gudev/GUdev-1.0.typelib: src/gudev/GUdev-1.0.gir $(G_IR_COMPILER)
-       $(AM_V_GEN)g-ir-compiler $< -o $@
-
-girdir = $(GIRDIR)
-gir_DATA = src/gudev/GUdev-1.0.gir
-
-typelibsdir = $(GIRTYPELIBDIR)
-typelibs_DATA = src/gudev/GUdev-1.0.typelib
-
-CLEANFILES += $(gir_DATA) $(typelibs_DATA)
-endif # ENABLE_INTROSPECTION
-
-# move lib from $(libdir) to $(rootlib_execdir) and update devel link, if needed
-libgudev-install-move-hook:
-       if test "$(libdir)" != "$(rootlib_execdir)"; then \
-               mkdir -p $(DESTDIR)$(rootlib_execdir) && \
-               so_img_name=$$(readlink $(DESTDIR)$(libdir)/libgudev-1.0.so) && \
+       units/systemd-timedated.service.in
+endif
+
+# ------------------------------------------------------------------------------
+if ENABLE_LOGIND
+systemd_logind_SOURCES = \
+       src/login/logind.c \
+       src/login/logind-dbus.c \
+       src/login/logind-device.c \
+       src/login/logind-seat.c \
+       src/login/logind-seat-dbus.c \
+       src/login/logind-session.c \
+       src/login/logind-session-dbus.c \
+       src/login/logind-user.c \
+       src/login/logind-user-dbus.c \
+       src/dbus-common.c \
+       src/dbus-loop.c \
+       src/cgroup-util.c \
+       src/polkit.c
+
+nodist_systemd_logind_SOURCES = \
+       src/login/logind-gperf.c
+
+if HAVE_ACL
+systemd_logind_SOURCES += \
+       src/login/logind-acl.c \
+       src/acl-util.c
+endif
+
+systemd_logind_CFLAGS = \
+       $(AM_CFLAGS) \
+       $(DBUS_CFLAGS) \
+       $(UDEV_CFLAGS) \
+       $(ACL_CFLAGS)
+
+systemd_logind_LDADD = \
+       libsystemd-basic.la \
+       libsystemd-daemon.la \
+       $(DBUS_LIBS) \
+       $(UDEV_LIBS) \
+       $(ACL_LIBS)
+
+systemd_user_sessions_SOURCES = \
+       src/login/user-sessions.c \
+       src/cgroup-util.c
+
+systemd_user_sessions_LDADD = \
+       libsystemd-basic.la
+
+rootlibexec_PROGRAMS += \
+       systemd-logind \
+       systemd-user-sessions
+
+loginctl_SOURCES = \
+       src/login/loginctl.c \
+       src/login/sysfs-show.c \
+       src/dbus-common.c \
+       src/cgroup-show.c \
+       src/cgroup-util.c \
+       src/pager.c
+
+loginctl_CFLAGS = \
+       $(AM_CFLAGS) \
+       $(DBUS_CFLAGS) \
+       $(UDEV_CFLAGS)
+
+loginctl_LDADD = \
+       libsystemd-basic.la \
+       $(DBUS_LIBS) \
+       $(UDEV_LIBS)
+
+rootbin_PROGRAMS += \
+       loginctl
+
+test_login_SOURCES = \
+       src/login/test-login.c
+
+test_login_LDADD = \
+       libsystemd-basic.la \
+       libsystemd-login.la
+
+noinst_PROGRAMS += \
+       test-login
+
+libsystemd_login_la_SOURCES = \
+       src/login/sd-login.c \
+       src/cgroup-util.c
+
+libsystemd_login_la_CFLAGS = \
+       $(AM_CFLAGS) \
+       -fvisibility=hidden
+
+libsystemd_login_la_LDFLAGS = \
+       -shared \
+       -version-info $(LIBSYSTEMD_LOGIN_CURRENT):$(LIBSYSTEMD_LOGIN_REVISION):$(LIBSYSTEMD_LOGIN_AGE) \
+       -Wl,--version-script=$(top_srcdir)/src/login/libsystemd-login.sym
+
+libsystemd_login_la_LIBADD = \
+       libsystemd-basic.la
+
+if HAVE_PAM
+pam_systemd_la_SOURCES = \
+       src/login/pam-module.c \
+       src/dbus-common.c
+
+pam_systemd_la_CFLAGS = \
+       $(AM_CFLAGS) \
+       $(PAM_CFLAGS) \
+       $(DBUS_CFLAGS) \
+       -fvisibility=hidden
+
+pam_systemd_la_LDFLAGS = \
+       -module \
+       -export-dynamic \
+       -avoid-version \
+       -shared \
+       -export-symbols-regex '^pam_sm_.*'
+
+pam_systemd_la_LIBADD = \
+       libsystemd-basic.la \
+       libsystemd-daemon.la \
+       $(PAM_LIBS) \
+       $(DBUS_LIBS)
+
+pamlib_LTLIBRARIES = \
+       pam_systemd.la
+endif
+
+# move lib from $(libdir) to $(rootlibdir) and update devel link, if needed
+libsystemd-login-install-hook:
+       if test "$(libdir)" != "$(rootlibdir)"; then \
+               mkdir -p $(DESTDIR)$(rootlibdir) && \
+               so_img_name=$$(readlink $(DESTDIR)$(libdir)/libsystemd-login.so) && \
                so_img_rel_target_prefix=$$(echo $(libdir) | sed 's,\(^/\|\)[^/][^/]*,..,g') && \
-               ln -sf $$so_img_rel_target_prefix$(rootlib_execdir)/$$so_img_name $(DESTDIR)$(libdir)/libgudev-1.0.so && \
-               mv $(DESTDIR)$(libdir)/libgudev-1.0.so.* $(DESTDIR)$(rootlib_execdir); \
+               ln -sf $$so_img_rel_target_prefix$(rootlibdir)/$$so_img_name $(DESTDIR)$(libdir)/libsystemd-login.so && \
+               mv $(DESTDIR)$(libdir)/libsystemd-login.so.* $(DESTDIR)$(rootlibdir); \
        fi
 
-libgudev-uninstall-move-hook:
-       rm -f $(DESTDIR)$(rootlib_execdir)/libgudev-1.0.so*
+INSTALL_EXEC_HOOKS += \
+       libsystemd-login-install-hook
+
+libsystemd-login-uninstall-hook:
+       rm -f $(DESTDIR)$(rootlibdir)/libsystemd-login.so*
+
+UNINSTALL_EXEC_HOOKS += \
+       libsystemd-login-uninstall-hook
+
+nodist_systemunit_DATA += \
+       units/systemd-logind.service \
+       units/systemd-user-sessions.service
 
-INSTALL_EXEC_HOOKS += libgudev-install-move-hook
-UNINSTALL_EXEC_HOOKS += libgudev-uninstall-move-hook
+dist_dbussystemservice_DATA += \
+       src/login/org.freedesktop.login1.service
+
+dist_dbuspolicy_DATA += \
+       src/login/org.freedesktop.login1.conf
+
+dist_pkgsysconf_DATA += \
+       src/login/logind.conf
+
+pkginclude_HEADERS += \
+       src/systemd/sd-login.h
+
+lib_LTLIBRARIES += \
+       libsystemd-login.la
+
+pkgconfiglib_DATA += \
+       src/login/libsystemd-login.pc
+
+polkitpolicy_in_files += \
+       src/login/org.freedesktop.login1.policy.in
+
+logind-install-data-hook:
+       $(MKDIR_P) -m 0755 \
+               $(DESTDIR)$(systemunitdir)/multi-user.target.wants \
+                $(DESTDIR)$(localstatedir)/lib/systemd
+       ( cd $(DESTDIR)$(systemunitdir) && \
+               rm -f dbus-org.freedesktop.login1.service && \
+               $(LN_S) systemd-logind.service dbus-org.freedesktop.login1.service)
+       ( cd $(DESTDIR)$(systemunitdir)/multi-user.target.wants && \
+               rm -f systemd-logind.service systemd-user-sessions.service && \
+               $(LN_S) ../systemd-logind.service systemd-logind.service && \
+               $(LN_S) ../systemd-user-sessions.service systemd-user-sessions.service )
+
+INSTALL_DATA_HOOKS += \
+       logind-install-data-hook
+
+systemd_multi_seat_x_SOURCES = \
+       src/login/multi-seat-x.c
+
+systemd_multi_seat_x_CFLAGS = \
+       $(AM_CFLAGS) \
+       $(UDEV_CFLAGS)
+
+systemd_multi_seat_x_LDADD = \
+       libsystemd-basic.la \
+       $(UDEV_LIBS)
+
+rootlibexec_PROGRAMS += \
+       systemd-multi-seat-x
+
+systemd_uaccess_SOURCES = \
+       src/login/uaccess.c
+
+if HAVE_ACL
+systemd_uaccess_SOURCES += \
+       src/login/logind-acl.c \
+       src/acl-util.c
 endif
 
-# ------------------------------------------------------------------------------
-if ENABLE_KEYMAP
-keymap_SOURCES = src/keymap/keymap.c
-keymap_CPPFLAGS = $(AM_CPPFLAGS) -I src/keymap
-nodist_keymap_SOURCES = \
-       src/keymap/keys-from-name.h \
-       src/keymap/keys-to-name.h
-BUILT_SOURCES += $(nodist_keymap_SOURCES)
+systemd_uaccess_CFLAGS = \
+       $(AM_CFLAGS) \
+       $(UDEV_CFLAGS) \
+       $(ACL_CFLAGS)
 
-pkglibexec_PROGRAMS += keymap
-dist_doc_DATA = src/keymap/README.keymap.txt
+systemd_uaccess_LDADD = \
+       libsystemd-basic.la \
+       libsystemd-daemon.la \
+       libsystemd-login.la \
+       $(UDEV_LIBS) \
+       $(ACL_LIBS)
+
+rootlibexec_PROGRAMS += \
+       systemd-uaccess
 
 dist_udevrules_DATA += \
-       src/keymap/95-keymap.rules \
-       src/keymap/95-keyboard-force-release.rules
+       src/login/70-uaccess.rules
 
-dist_udevhome_SCRIPTS += src/keymap/findkeyboards
-udevhome_SCRIPTS += src/keymap/keyboard-force-release.sh
+dist_udevrules_DATA += \
+       src/login/71-seat.rules
+
+nodist_udevrules_DATA += \
+       src/login/73-seat-late.rules
+
+MANPAGES += \
+       man/logind.conf.5 \
+       man/sd-login.7 \
+       man/loginctl.1 \
+       man/sd_login_monitor_new.3 \
+       man/sd_pid_get_session.3 \
+       man/sd_uid_get_state.3 \
+       man/sd_session_is_active.3 \
+       man/sd_seat_get_active.3 \
+       man/sd_get_seats.3
+
+MANPAGES_ALIAS += \
+       man/sd_login_monitor_unref.3 \
+       man/sd_login_monitor_flush.3 \
+       man/sd_login_monitor_get_fd.3 \
+       man/sd_session_get_uid.3 \
+       man/sd_session_get_seat.3 \
+       man/sd_session_get_service.3 \
+       man/sd_session_get_type.3 \
+       man/sd_session_get_class.3 \
+       man/sd_session_get_display.3 \
+       man/sd_pid_get_owner_uid.3 \
+       man/sd_pid_get_unit.3 \
+       man/sd_uid_is_on_seat.3 \
+       man/sd_uid_get_sessions.3 \
+       man/sd_uid_get_seats.3 \
+       man/sd_seat_get_sessions.3 \
+       man/sd_seat_can_multi_session.3 \
+       man/sd_get_sessions.3 \
+       man/sd_get_uids.3
+
+man/sd_login_monitor_unref.3: man/sd_login_monitor_new.3
+man/sd_login_monitor_flush.3: man/sd_login_monitor_new.3
+man/sd_login_monitor_get_fd.3: man/sd_login_monitor_new.3
+man/sd_session_get_uid.3: man/sd_session_is_active.3
+man/sd_session_get_seat.3: man/sd_session_is_active.3
+man/sd_session_get_service.3: man/sd_session_is_active.3
+man/sd_session_get_type.3: man/sd_session_is_active.3
+man/sd_session_get_class.3: man/sd_session_is_active.3
+man/sd_session_get_display.3: man/sd_session_is_active.3
+man/sd_pid_get_owner_uid.3: man/sd_pid_get_session.3
+man/sd_pid_get_unit.3: man/sd_pid_get_session.3
+man/sd_uid_is_on_seat.3: man/sd_uid_get_state.3
+man/sd_uid_get_sessions.3: man/sd_uid_get_state.3
+man/sd_uid_get_seats.3: man/sd_uid_get_state.3
+man/sd_seat_get_sessions.3: man/sd_seat_get_active.3
+man/sd_seat_can_multi_session.3: man/sd_seat_get_active.3
+man/sd_get_sessions.3: man/sd_get_seats.3
+man/sd_get_uids.3: man/sd_get_seats.3
 
 EXTRA_DIST += \
-       src/keymap/check-keymaps.sh \
-       src/keymap/keyboard-force-release.sh.in
+       src/login/logind-gperf.gperf \
+       src/login/libsystemd-login.pc.in \
+       src/login/libsystemd-login.sym \
+       src/login/logind.h \
+       src/login/logind-device.h \
+       src/login/logind-seat.h \
+       src/login/logind-session.h \
+       src/login/logind-user.h \
+       src/login/logind-acl.h \
+       src/login/73-seat-late.rules.in \
+       units/systemd-logind.service.in \
+       units/systemd-user-sessions.service.in
 
 CLEANFILES += \
-       src/keymap/keys.txt \
-       src/keymap/keys-from-name.gperf \
-       src/keymap/keyboard-force-release.sh
-
-udevkeymapdir = $(libexecdir)/udev/keymaps
-dist_udevkeymap_DATA = \
-       src/keymap/keymaps/acer \
-       src/keymap/keymaps/acer-aspire_5720 \
-       src/keymap/keymaps/acer-aspire_8930 \
-       src/keymap/keymaps/acer-aspire_5920g \
-       src/keymap/keymaps/acer-aspire_6920 \
-       src/keymap/keymaps/acer-travelmate_c300 \
-       src/keymap/keymaps/asus \
-       src/keymap/keymaps/compaq-e_evo \
-       src/keymap/keymaps/dell \
-       src/keymap/keymaps/dell-latitude-xt2 \
-       src/keymap/keymaps/everex-xt5000 \
-       src/keymap/keymaps/fujitsu-amilo_li_2732 \
-       src/keymap/keymaps/fujitsu-amilo_pa_2548 \
-       src/keymap/keymaps/fujitsu-amilo_pro_edition_v3505 \
-       src/keymap/keymaps/fujitsu-amilo_pro_v3205 \
-       src/keymap/keymaps/fujitsu-amilo_si_1520 \
-       src/keymap/keymaps/fujitsu-esprimo_mobile_v5 \
-       src/keymap/keymaps/fujitsu-esprimo_mobile_v6 \
-       src/keymap/keymaps/genius-slimstar-320 \
-       src/keymap/keymaps/hewlett-packard \
-       src/keymap/keymaps/hewlett-packard-2510p_2530p \
-       src/keymap/keymaps/hewlett-packard-compaq_elitebook \
-       src/keymap/keymaps/hewlett-packard-pavilion \
-       src/keymap/keymaps/hewlett-packard-presario-2100 \
-       src/keymap/keymaps/hewlett-packard-tablet \
-       src/keymap/keymaps/hewlett-packard-tx2 \
-       src/keymap/keymaps/ibm-thinkpad-usb-keyboard-trackpoint \
-       src/keymap/keymaps/inventec-symphony_6.0_7.0 \
-       src/keymap/keymaps/lenovo-3000 \
-       src/keymap/keymaps/lenovo-ideapad \
-       src/keymap/keymaps/lenovo-thinkpad-usb-keyboard-trackpoint \
-       src/keymap/keymaps/lenovo-thinkpad_x6_tablet \
-       src/keymap/keymaps/lenovo-thinkpad_x200_tablet \
-       src/keymap/keymaps/lg-x110 \
-       src/keymap/keymaps/logitech-wave \
-       src/keymap/keymaps/logitech-wave-cordless \
-       src/keymap/keymaps/logitech-wave-pro-cordless \
-       src/keymap/keymaps/maxdata-pro_7000 \
-       src/keymap/keymaps/medion-fid2060 \
-       src/keymap/keymaps/medionnb-a555 \
-       src/keymap/keymaps/micro-star \
-       src/keymap/keymaps/module-asus-w3j \
-       src/keymap/keymaps/module-ibm \
-       src/keymap/keymaps/module-lenovo \
-       src/keymap/keymaps/module-sony \
-       src/keymap/keymaps/module-sony-old \
-       src/keymap/keymaps/module-sony-vgn \
-       src/keymap/keymaps/olpc-xo \
-       src/keymap/keymaps/onkyo \
-       src/keymap/keymaps/oqo-model2 \
-       src/keymap/keymaps/samsung-other \
-       src/keymap/keymaps/samsung-90x3a \
-       src/keymap/keymaps/samsung-sq1us \
-       src/keymap/keymaps/samsung-sx20s \
-       src/keymap/keymaps/toshiba-satellite_a100 \
-       src/keymap/keymaps/toshiba-satellite_a110 \
-       src/keymap/keymaps/toshiba-satellite_m30x \
-       src/keymap/keymaps/zepto-znote
-
-udevkeymapforcereldir = $(libexecdir)/udev/keymaps/force-release
-dist_udevkeymapforcerel_DATA = \
-       src/keymap/force-release-maps/dell-touchpad \
-       src/keymap/force-release-maps/hp-other \
-       src/keymap/force-release-maps/samsung-other \
-       src/keymap/force-release-maps/samsung-90x3a \
-       src/keymap/force-release-maps/common-volume-keys
-
-src/keymap/keys.txt: $(INCLUDE_PREFIX)/linux/input.h
-       $(AM_V_at)mkdir -p src/keymap
-       $(AM_V_GEN)$(AWK) '/^#define.*KEY_[^ ]+[ \t]+[0-9]/ { if ($$2 != "KEY_MAX") { print $$2 } }' < $< | sed 's/^KEY_COFFEE$$/KEY_SCREENLOCK/' > $@
-
-src/keymap/keys-from-name.gperf: src/keymap/keys.txt
-       $(AM_V_GEN)$(AWK) 'BEGIN{ print "struct key { const char* name; unsigned short id; };"; print "%null-strings"; print "%%";} { print $$1 ", " $$1 }' < $< > $@
-
-src/keymap/keys-from-name.h: src/keymap/keys-from-name.gperf Makefile
-       $(AM_V_GEN)$(GPERF) -L ANSI-C -t --ignore-case -N lookup_key -H hash_key_name -p -C < $< > $@
-
-src/keymap/keys-to-name.h: src/keymap/keys.txt Makefile
-       $(AM_V_GEN)$(AWK) 'BEGIN{ print "const char* const key_names[KEY_CNT] = { "} { print "[" $$1 "] = \"" $$1 "\"," } END{print "};"}' < $< > $@
-
-keymaps-distcheck-hook: src/keymap/keys.txt
-       $(top_srcdir)/src/keymap/check-keymaps.sh $(top_srcdir) $^
-DISTCHECK_HOOKS += keymaps-distcheck-hook
-endif
-
-if ENABLE_MTD_PROBE
+       src/login/logind-gperf.c \
+       src/login/73-seat-late.rules
+endif
 # ------------------------------------------------------------------------------
-mtd_probe_SOURCES =  \
-       src/mtd_probe/mtd_probe.c \
-       src/mtd_probe/mtd_probe.h \
-       src/mtd_probe/probe_smartmedia.c
-mtd_probe_CPPFLAGS = $(AM_CPPFLAGS)
-dist_udevrules_DATA += src/mtd_probe/75-probe_mtd.rules
-pkglibexec_PROGRAMS += mtd_probe
+
+SED_PROCESS = \
+       $(AM_V_GEN)$(MKDIR_P) $(dir $@) && \
+       $(SED)  -e 's,@rootlibexecdir\@,$(rootlibexecdir),g' \
+               -e 's,@rootbindir\@,$(rootbindir),g' \
+               -e 's,@bindir\@,$(bindir),g' \
+               -e 's,@SYSTEMCTL\@,$(rootbindir)/systemctl,g' \
+               -e 's,@SYSTEMD_NOTIFY\@,$(rootbindir)/systemd-notify,g' \
+               -e 's,@pkgsysconfdir\@,$(pkgsysconfdir),g' \
+               -e 's,@pkgdatadir\@,$(pkgdatadir),g' \
+               -e 's,@pkglibexecdir\@,$(pkglibexecdir),g' \
+               -e 's,@systemunitdir\@,$(systemunitdir),g' \
+               -e 's,@userunitdir\@,$(userunitdir),g' \
+               -e 's,@PACKAGE_VERSION\@,$(PACKAGE_VERSION),g' \
+               -e 's,@PACKAGE_NAME\@,$(PACKAGE_NAME),g' \
+               -e 's,@PACKAGE_URL\@,$(PACKAGE_URL),g' \
+               -e 's,@prefix\@,$(prefix),g' \
+               -e 's,@exec_prefix\@,$(exec_prefix),g' \
+               -e 's,@libdir\@,$(libdir),g' \
+               -e 's,@includedir\@,$(includedir),g' \
+               < $< > $@ || rm $@
+
+units/%: units/%.in Makefile
+       $(SED_PROCESS)
+
+man/%: man/%.in Makefile
+       $(SED_PROCESS)
+
+sysctl.d/%: sysctl.d/%.in Makefile
+       $(SED_PROCESS)
+
+%.pc: %.pc.in Makefile
+       $(SED_PROCESS)
+
+src/%.policy.in: src/%.policy.in.in Makefile
+       $(SED_PROCESS)
+
+src/%.rules: src/%.rules.in Makefile
+       $(SED_PROCESS)
+
+src/%.c: src/%.gperf
+       $(AM_V_GEN)$(MKDIR_P) $(dir $@) && \
+       $(GPERF) < $< > $@
+
+src/%: src/%.m4
+       $(AM_V_GEN)$(MKDIR_P) $(dir $@) && \
+       $(M4) -P $(M4_DEFINES) < $< > $@ || rm $@
+
+src/load-fragment-gperf-nulstr.c: src/load-fragment-gperf.gperf
+       $(AM_V_GEN)$(MKDIR_P) $(dir $@) && \
+       $(AWK) 'BEGIN{ keywords=0 ; FS="," ; print "extern const char load_fragment_gperf_nulstr[];" ; print "const char load_fragment_gperf_nulstr[] ="} ; keyword==1 { print "\"" $$1 "\\0\"" } ; /%%/ { keyword=1} ; END { print ";" }' < $< > $@ || rm $@
+
+M4_PROCESS_SYSTEM = \
+       $(AM_V_GEN)$(MKDIR_P) $(dir $@) && \
+       $(M4) -P $(M4_DEFINES) -DFOR_SYSTEM=1 < $< > $@ || rm $@
+
+M4_PROCESS_USER = \
+       $(AM_V_GEN)$(MKDIR_P) $(dir $@) && \
+       $(M4) -P $(M4_DEFINES) -DFOR_USER=1 < $< > $@ || rm $@
+
+units/%: units/%.m4 Makefile
+       $(M4_PROCESS_SYSTEM)
+
+units/user/%: units/%.m4 Makefile
+       $(M4_PROCESS_USER)
+
+CLEANFILES += \
+       $(nodist_systemunit_DATA) \
+       $(nodist_userunit_DATA) \
+       $(nodist_man_MANS) \
+       $(pkgconfigdata_DATA) \
+       $(pkgconfiglib_DATA) \
+       $(nodist_polkitpolicy_DATA) \
+       src/load-fragment-gperf.gperf \
+       src/load-fragment-gperf.c \
+       src/load-fragment-gperf-nulstr.c \
+       src/99-systemd.rules
+
+if HAVE_XSLTPROC
+XSLTPROC_FLAGS = \
+       --nonet \
+       --stringparam funcsynopsis.style ansi
+
+XSLTPROC_PROCESS_MAN = \
+       $(AM_V_GEN)$(MKDIR_P) $(dir $@) && \
+       $(XSLTPROC) -o $@ $(XSLTPROC_FLAGS) http://docbook.sourceforge.net/release/xsl/current/manpages/docbook.xsl $<
+
+XSLTPROC_PROCESS_HTML = \
+       $(AM_V_GEN)$(MKDIR_P) $(dir $@) && \
+       $(XSLTPROC) -o $@ $(XSLTPROC_FLAGS) $(srcdir)/man/custom-html.xsl $<
+
+man/%.1: man/%.xml
+       $(XSLTPROC_PROCESS_MAN)
+
+man/%.3: man/%.xml
+       $(XSLTPROC_PROCESS_MAN)
+
+man/%.5: man/%.xml
+       $(XSLTPROC_PROCESS_MAN)
+
+man/%.7: man/%.xml
+       $(XSLTPROC_PROCESS_MAN)
+
+man/%.8: man/%.xml
+       $(XSLTPROC_PROCESS_MAN)
+
+man/%.html: man/%.xml
+       $(XSLTPROC_PROCESS_HTML)
+
+CLEANFILES += \
+       $(dist_man_MANS) \
+       ${XML_FILES:.xml=.html}
 endif
 
-# ------------------------------------------------------------------------------
-if ENABLE_RULE_GENERATOR
-dist_udevhome_SCRIPTS += \
-       src/rule_generator/write_cd_rules \
-       src/rule_generator/write_net_rules
+DBUS_PREPROCESS = $(CPP) -P $(DBUS_CFLAGS) -imacros dbus/dbus-protocol.h
 
-dist_udevhome_DATA += \
-       src/rule_generator/rule_generator.functions
+org.freedesktop.systemd1.%.xml: systemd
+       $(AM_V_GEN)$(LIBTOOL) --mode=execute $(OBJCOPY) -O binary -j introspect.$* $< $@.tmp && \
+               $(STRINGS) $@.tmp | $(AWK) -f $(srcdir)/introspect.awk | \
+               $(DBUS_PREPROCESS) -o $@ - && rm $@.tmp
 
-dist_udevrules_DATA += \
-       src/rule_generator/75-cd-aliases-generator.rules \
-       src/rule_generator/75-persistent-net-generator.rules
+CLEANFILES += \
+       $(dbusinterface_DATA)
+
+systemd-install-data-hook:
+       $(MKDIR_P) -m 0755 \
+               $(DESTDIR)$(tmpfilesdir) \
+               $(DESTDIR)$(sysconfdir)/tmpfiles.d \
+               $(DESTDIR)$(prefix)/lib/modules-load.d \
+               $(DESTDIR)$(sysconfdir)/modules-load.d \
+               $(DESTDIR)$(prefix)/lib/sysctl.d \
+               $(DESTDIR)$(sysconfdir)/sysctl.d \
+               $(DESTDIR)$(systemshutdowndir) \
+               $(DESTDIR)$(systemgeneratordir) \
+               $(DESTDIR)$(usergeneratordir)
+       $(MKDIR_P) -m 0755 \
+               $(DESTDIR)$(systemunitdir) \
+               $(DESTDIR)$(userunitdir) \
+               $(DESTDIR)$(systemunitdir)/sysinit.target.wants \
+               $(DESTDIR)$(systemunitdir)/sockets.target.wants \
+               $(DESTDIR)$(systemunitdir)/basic.target.wants \
+               $(DESTDIR)$(systemunitdir)/shutdown.target.wants \
+               $(DESTDIR)$(systemunitdir)/local-fs.target.wants \
+               $(DESTDIR)$(systemunitdir)/runlevel1.target.wants \
+               $(DESTDIR)$(systemunitdir)/runlevel2.target.wants \
+               $(DESTDIR)$(systemunitdir)/runlevel3.target.wants \
+               $(DESTDIR)$(systemunitdir)/runlevel4.target.wants \
+               $(DESTDIR)$(systemunitdir)/runlevel5.target.wants \
+               $(DESTDIR)$(systemunitdir)/multi-user.target.wants \
+               $(DESTDIR)$(systemunitdir)/graphical.target.wants \
+               $(DESTDIR)$(pkgsysconfdir)/system \
+               $(DESTDIR)$(pkgsysconfdir)/system/sysinit.target.wants \
+               $(DESTDIR)$(pkgsysconfdir)/system/local-fs.target.wants \
+               $(DESTDIR)$(pkgsysconfdir)/system/multi-user.target.wants \
+               $(DESTDIR)$(pkgsysconfdir)/system/getty.target.wants \
+               $(DESTDIR)$(pkgsysconfdir)/user \
+               $(DESTDIR)$(dbussessionservicedir) \
+               $(DESTDIR)$(sysconfdir)/xdg/systemd
+       ( cd $(DESTDIR)$(sysconfdir)/xdg/systemd/ && \
+               rm -f user && \
+               $(LN_S) $(pkgsysconfdir)/user user )
+       ( cd $(DESTDIR)$(systemunitdir)/sockets.target.wants && \
+               rm -f systemd-initctl.socket systemd-shutdownd.socket && \
+               $(LN_S) ../systemd-initctl.socket systemd-initctl.socket && \
+               $(LN_S) ../systemd-shutdownd.socket systemd-shutdownd.socket )
+       ( cd $(DESTDIR)$(systemunitdir)/runlevel1.target.wants && \
+               rm -f systemd-update-utmp-runlevel.service && \
+               $(LN_S) ../systemd-update-utmp-runlevel.service systemd-update-utmp-runlevel.service )
+       ( cd $(DESTDIR)$(systemunitdir)/runlevel2.target.wants && \
+               rm -f systemd-update-utmp-runlevel.service && \
+               $(LN_S) ../systemd-update-utmp-runlevel.service systemd-update-utmp-runlevel.service )
+       ( cd $(DESTDIR)$(systemunitdir)/runlevel3.target.wants && \
+               rm -f systemd-update-utmp-runlevel.service && \
+               $(LN_S) ../systemd-update-utmp-runlevel.service systemd-update-utmp-runlevel.service )
+       ( cd $(DESTDIR)$(systemunitdir)/runlevel4.target.wants && \
+               rm -f systemd-update-utmp-runlevel.service && \
+               $(LN_S) ../systemd-update-utmp-runlevel.service systemd-update-utmp-runlevel.service )
+       ( cd $(DESTDIR)$(systemunitdir)/runlevel5.target.wants && \
+               rm -f systemd-update-utmp-runlevel.service && \
+               $(LN_S) ../systemd-update-utmp-runlevel.service systemd-update-utmp-runlevel.service )
+       ( cd $(DESTDIR)$(systemunitdir)/shutdown.target.wants && \
+               rm -f systemd-update-utmp-shutdown.service && \
+               $(LN_S) ../systemd-update-utmp-shutdown.service systemd-update-utmp-shutdown.service )
+       ( cd $(DESTDIR)$(systemunitdir)/local-fs.target.wants && \
+               rm -f systemd-remount-api-vfs.service \
+                       fsck-root.service \
+                       remount-rootfs.service \
+                       tmp.mount && \
+               $(LN_S) ../systemd-remount-api-vfs.service systemd-remount-api-vfs.service && \
+               $(LN_S) ../fsck-root.service fsck-root.service && \
+               $(LN_S) ../remount-rootfs.service remount-rootfs.service && \
+               $(LN_S) ../tmp.mount tmp.mount )
+       ( cd $(DESTDIR)$(userunitdir) && \
+               rm -f shutdown.target sockets.target bluetooth.target printer.target sound.target && \
+               $(LN_S) $(systemunitdir)/shutdown.target shutdown.target && \
+               $(LN_S) $(systemunitdir)/sockets.target sockets.target && \
+               $(LN_S) $(systemunitdir)/bluetooth.target bluetooth.target && \
+               $(LN_S) $(systemunitdir)/printer.target printer.target && \
+               $(LN_S) $(systemunitdir)/sound.target sound.target )
+       ( cd $(DESTDIR)$(systemunitdir) && \
+               rm -f runlevel0.target runlevel1.target runlevel2.target runlevel3.target runlevel4.target runlevel5.target runlevel6.target && \
+               $(LN_S) poweroff.target runlevel0.target && \
+               $(LN_S) rescue.target runlevel1.target && \
+               $(LN_S) multi-user.target runlevel2.target && \
+               $(LN_S) multi-user.target runlevel3.target && \
+               $(LN_S) multi-user.target runlevel4.target && \
+               $(LN_S) graphical.target runlevel5.target && \
+               $(LN_S) reboot.target runlevel6.target )
+       ( cd $(DESTDIR)$(systemunitdir) && \
+               rm -f default.target ctrl-alt-del.target autovt@.service && \
+               $(LN_S) graphical.target default.target && \
+               $(LN_S) reboot.target ctrl-alt-del.target && \
+               $(LN_S) getty@.service autovt@.service )
+       ( cd $(DESTDIR)$(systemunitdir)/multi-user.target.wants && \
+               rm -f getty.target systemd-ask-password-wall.path && \
+               $(LN_S) ../getty.target getty.target && \
+               $(LN_S) ../systemd-ask-password-wall.path systemd-ask-password-wall.path)
+       ( cd $(DESTDIR)$(pkgsysconfdir)/system/getty.target.wants && \
+               rm -f getty@tty1.service && \
+               $(LN_S) $(systemunitdir)/getty@.service getty@tty1.service )
+       ( cd $(DESTDIR)$(pkgsysconfdir)/system/multi-user.target.wants && \
+               rm -f remote-fs.target && \
+               $(LN_S) $(systemunitdir)/remote-fs.target remote-fs.target )
+       ( cd $(DESTDIR)$(systemunitdir)/sysinit.target.wants && \
+               rm -f dev-hugepages.mount \
+                       dev-mqueue.mount \
+                       sys-kernel-config.mount \
+                       sys-kernel-debug.mount \
+                       sys-fs-fuse-connections.mount \
+                       systemd-modules-load.service \
+                       systemd-tmpfiles-setup.service \
+                       systemd-sysctl.service \
+                       systemd-ask-password-console.path && \
+               $(LN_S) ../dev-hugepages.mount dev-hugepages.mount && \
+               $(LN_S) ../dev-mqueue.mount dev-mqueue.mount && \
+               $(LN_S) ../sys-kernel-config.mount sys-kernel-config.mount && \
+               $(LN_S) ../sys-kernel-debug.mount sys-kernel-debug.mount && \
+               $(LN_S) ../sys-fs-fuse-connections.mount sys-fs-fuse-connections.mount && \
+               $(LN_S) ../systemd-modules-load.service systemd-modules-load.service && \
+               $(LN_S) ../systemd-tmpfiles-setup.service systemd-tmpfiles-setup.service && \
+               $(LN_S) ../systemd-sysctl.service systemd-sysctl.service && \
+               $(LN_S) ../systemd-ask-password-console.path systemd-ask-password-console.path )
+       ( cd $(DESTDIR)$(systemunitdir)/basic.target.wants && \
+               rm -f systemd-tmpfiles-clean.timer && \
+               $(LN_S) ../systemd-tmpfiles-clean.timer systemd-tmpfiles-clean.timer )
+       ( cd $(DESTDIR)$(dbussessionservicedir) && \
+               rm -f org.freedesktop.systemd1.service && \
+               $(LN_S) ../system-services/org.freedesktop.systemd1.service org.freedesktop.systemd1.service )
+if HAVE_PLYMOUTH
+       $(MKDIR_P) -m 0755 \
+               $(DESTDIR)$(SYSTEM_SYSVINIT_PATH) \
+               $(DESTDIR)$(systemunitdir)/reboot.target.wants \
+               $(DESTDIR)$(systemunitdir)/kexec.target.wants \
+               $(DESTDIR)$(systemunitdir)/poweroff.target.wants \
+               $(DESTDIR)$(systemunitdir)/halt.target.wants
+       ( cd $(DESTDIR)$(systemunitdir)/sysinit.target.wants && \
+               rm -f plymouth-start.service plymouth-read-write.service && \
+               $(LN_S) ../plymouth-start.service plymouth-start.service && \
+               $(LN_S) ../plymouth-read-write.service plymouth-read-write.service )
+       ( cd $(DESTDIR)$(systemunitdir)/multi-user.target.wants && \
+               rm -f plymouth-quit.service plymouth-quit-wait.service && \
+               $(LN_S) ../plymouth-quit.service plymouth-quit.service && \
+               $(LN_S) ../plymouth-quit-wait.service plymouth-quit-wait.service )
+       ( cd $(DESTDIR)$(systemunitdir)/reboot.target.wants && \
+               rm -f plymouth-reboot.service && \
+               $(LN_S) ../plymouth-reboot.service plymouth-reboot.service )
+       ( cd $(DESTDIR)$(systemunitdir)/kexec.target.wants && \
+               rm -f plymouth-kexec.service && \
+               $(LN_S) ../plymouth-kexec.service plymouth-kexec.service )
+       ( cd $(DESTDIR)$(systemunitdir)/poweroff.target.wants && \
+               rm -f plymouth-poweroff.service && \
+               $(LN_S) ../plymouth-poweroff.service plymouth-poweroff.service )
+       ( cd $(DESTDIR)$(systemunitdir)/halt.target.wants && \
+               rm -f plymouth-halt.service && \
+               $(LN_S) ../plymouth-halt.service plymouth-halt.service )
+endif
+if TARGET_MEEGO
+       $(MKDIR_P) -m 0755 $(DESTDIR)$(systemunitdir)/final.target.wants
+       ( cd $(DESTDIR)$(systemunitdir)/multi-user.target.wants && \
+               rm -f network.target && \
+               $(LN_S) $(systemunitdir)/network.target network.target )
+       ( cd $(DESTDIR)$(pkgsysconfdir)/system/sysinit.target.wants && \
+               rm -f * )
+       ( cd $(DESTDIR)$(pkgsysconfdir)/system/local-fs.target.wants && \
+               rm -f * )
+       ( cd $(DESTDIR)$(pkgsysconfdir)/system/multi-user.target.wants && \
+               rm -f * )
+       ( cd $(DESTDIR)$(pkgsysconfdir)/system/getty.target.wants && \
+               rm -f * )
 endif
 
-# ------------------------------------------------------------------------------
-if ENABLE_FLOPPY
-create_floppy_devices_SOURCES = src/floppy/create_floppy_devices.c
-create_floppy_devices_LDADD = libudev-private.la
-pkglibexec_PROGRAMS += create_floppy_devices
-dist_udevrules_DATA += src/floppy/60-floppy.rules
+if TARGET_FEDORA
+       $(MKDIR_P) -m 0755 $(DESTDIR)$(systemunitdir)/final.target.wants
+       ( cd $(DESTDIR)$(systemunitdir)/final.target.wants && \
+               rm -f halt-local.service && \
+               $(LN_S) $(systemunitdir)/halt-local.service halt-local.service )
+       ( cd $(DESTDIR)$(systemunitdir) && \
+               rm -f display-manager.service single.service && \
+               $(LN_S) prefdm.service display-manager.service && \
+               $(LN_S) rescue.service single.service )
+       ( cd $(DESTDIR)$(systemunitdir)/graphical.target.wants && \
+               rm -f display-manager.service && \
+               $(LN_S) $(systemunitdir)/display-manager.service display-manager.service )
 endif
 
-# ------------------------------------------------------------------------------
-clean-local:
-       rm -rf udev-test-install
+if TARGET_MANDRIVA
+       $(MKDIR_P) -m 0755 $(DESTDIR)$(systemunitdir)/final.target.wants
+       ( cd $(DESTDIR)$(systemunitdir)/final.target.wants && \
+               rm -f halt-local.service && \
+               $(LN_S) $(systemunitdir)/halt-local.service halt-local.service )
+       ( cd $(DESTDIR)$(systemunitdir) && \
+               rm -f display-manager.service dm.service single.service && \
+               $(LN_S) prefdm.service display-manager.service && \
+               $(LN_S) prefdm.service dm.service && \
+               $(LN_S) rescue.service single.service )
+       ( cd $(DESTDIR)$(systemunitdir)/graphical.target.wants && \
+               rm -f display-manager.service && \
+               $(LN_S) $(systemunitdir)/display-manager.service display-manager.service )
+endif
 
-distclean-local:
-       rm -rf autom4te.cache
+if TARGET_DEBIAN_OR_UBUNTU
+       ( cd $(DESTDIR)$(systemunitdir) && \
+               rm -f runlevel5.target && \
+               $(LN_S) multi-user.target runlevel5.target )
+endif
 
-EXTRA_DIST += \
-       $(TESTS) \
-       test/rule-syntax-check.py
+if TARGET_SUSE
+       $(MKDIR_P) -m 0755 $(DESTDIR)$(systemunitdir)/final.target.wants
+       ( cd $(DESTDIR)$(systemunitdir) && \
+               rm -f local.service && \
+               $(LN_S) rc-local.service local.service )
+       ( cd $(DESTDIR)$(systemunitdir)/final.target.wants && \
+               rm -f halt-local.service && \
+               $(LN_S) $(systemunitdir)/halt-local.service halt-local.service )
+endif
 
-CLEANFILES += \
-       $(BUILT_SOURCES)
+if TARGET_MAGEIA
+       $(MKDIR_P) -m 0755 $(DESTDIR)$(systemunitdir)/final.target.wants
+       ( cd $(DESTDIR)$(systemunitdir)/final.target.wants && \
+               rm -f halt-local.service && \
+               $(LN_S) $(systemunitdir)/halt-local.service halt-local.service )
+       ( cd $(DESTDIR)$(systemunitdir) && \
+               rm -f display-manager.service && \
+               $(LN_S) prefdm.service display-manager.service && \
+               $(LN_S) prefdm.service dm.service )
+       ( cd $(DESTDIR)$(systemunitdir)/graphical.target.wants && \
+               rm -f display-manager.service && \
+               $(LN_S) $(systemunitdir)/display-manager.service display-manager.service )
+endif
 
 install-exec-hook: $(INSTALL_EXEC_HOOKS)
 
-install-data-hook: $(INSTALL_DATA_HOOKS)
-
 uninstall-hook: $(UNINSTALL_EXEC_HOOKS)
 
-distcheck-hook: $(DISTCHECK_HOOKS)
-
-distclean-local: $(DISTCLEAN_LOCAL_HOOKS)
+install-data-hook: systemd-install-data-hook $(INSTALL_DATA_HOOKS)
 
-# ------------------------------------------------------------------------------
-PREVIOUS_VERSION = `expr $(VERSION) - 1`
-changelog:
-       @ head -1 ChangeLog | grep -q "to v$(PREVIOUS_VERSION)"
-       @ mv ChangeLog ChangeLog.tmp
-       @ echo "Summary of changes from v$(PREVIOUS_VERSION) to v$(VERSION)" >> ChangeLog
-       @ echo "============================================" >> ChangeLog
-       @ echo >> ChangeLog
-       @ git log --pretty=short $(PREVIOUS_VERSION)..HEAD | git shortlog  >> ChangeLog
-       @ echo >> ChangeLog
-       @ cat ChangeLog
-       @ cat ChangeLog.tmp >> ChangeLog
-       @ rm ChangeLog.tmp
-
-test-install:
-       rm -rf $(PWD)/udev-test-install/
-       make DESTDIR=$(PWD)/udev-test-install install
-       tree $(PWD)/udev-test-install/
-
-git-release:
-       head -1 ChangeLog | grep -q "to v$(VERSION)"
-       head -1 NEWS | grep -q "udev $(VERSION)"
-       git commit -a -m "release $(VERSION)"
-       git tag -m "udev $(VERSION)" -s $(VERSION)
-       git gc --prune=0
-
-git-sync:
-       git push
-       git push --tags
-
-tar-sync:
-       rm -f udev-$(VERSION).tar.sign
-       xz -d -c udev-$(VERSION).tar.xz | gpg --armor --detach-sign --output udev-$(VERSION).tar.sign
-       kup put udev-$(VERSION).tar.xz  udev-$(VERSION).tar.sign /pub/linux/utils/kernel/hotplug/
-
-doc-sync:
-       for i in src/*.html; do rm -f $$i.sign; gpg --armor --detach-sign --output=$$i.sign $$i; done
-       for i in src/*.html; do echo $$i; kup put $$i $$i.sign /pub/linux/utils/kernel/hotplug/udev/; done
-       for i in src/docs/html/*.{html,css,png}; do rm -f $$i.sign; gpg --armor --detach-sign --output=$$i.sign $$i; done
-       for i in src/docs/html/*.{html,css,png}; do echo $$i; kup put $$i $$i.sign /pub/linux/utils/kernel/hotplug/libudev/; done
-       for i in src/gudev/docs/html/*.{html,css,png}; do rm -f $$i.sign; gpg --armor --detach-sign --output=$$i.sign $$i; done
-       for i in src/gudev/docs/html/*.{html,css,png}; do echo $$i; kup put $$i $$i.sign /pub/linux/utils/kernel/hotplug/gudev/; done
+DISTCHECK_CONFIGURE_FLAGS = \
+       --with-dbuspolicydir=$$dc_install_base/$(dbuspolicydir) \
+       --with-dbussessionservicedir=$$dc_install_base/$(dbussessionservicedir) \
+       --with-dbussystemservicedir=$$dc_install_base/$(dbussystemservicedir) \
+       --with-dbusinterfacedir=$$dc_install_base/$(dbusinterfacedir) \
+       --with-udevrulesdir=$$dc_install_base/$(udevrulesdir) \
+       --with-pamlibdir=$$dc_install_base/$(pamlibdir) \
+        --with-rootprefix=$$dc_install_base \
+        --disable-split-usr
+
+upload: all distcheck
+       cp -v systemd-$(VERSION).tar.xz /home/lennart/git.fedora/systemd/
+       scp systemd-$(VERSION).tar.xz fdo:/srv/www.freedesktop.org/www/software/systemd/
+       scp man/*.html fdo:/srv/www.freedesktop.org/www/software/systemd/man/
+       scp man/*.html tango:public/systemd-man/
+
+git-tag:
+       git tag "v$(VERSION)" -m "systemd $(VERSION)"
diff --git a/NEWS b/NEWS
index f4f6f4e3273ed0af13405421a5937fddd2b9ba2c..c82e637d0ecc9bb80220ccf913c66b4436586a82 100644 (file)
--- a/NEWS
+++ b/NEWS
-udev 182
-========
-Rules files in /etc/udev/rules.s/ with the same name as rules files in
-/run/udev/rules.d/ now always have precedence. The stack of files is now:
-/usr/lib (package), /run (runtime, auto-generated), /etc (admin), while
-the later ones override the earlier ones. In other words: the admin has
-always the last say.
+systemd System and Service Manager
 
-USB auto-suspend is now enabled by default for some built-in USB HID
-devices.
+CHANGES WITH:
+        * systemd-logingctl and systemd-journalctl have been renamed
+          to logingctl and journalctl to match systemctl.
 
-/dev/disk/by-path/ links are no longer created for ATA devices behind
-an 'ATA transport class', the logic to extract predictable numbers does
-not exist in the kernel at this moment.
+        * The config files: /etc/systemd/systemd-logind.conf and
+          /etc/systemd/systemd-journald.conf have been renamed to
+          logind.conf and journald.conf. Package updates should rename
+          the files to the new names on upgrade.
 
-/dev/disk/by-id/scsi-* compatibility links are no longer created for
-ATA devices, they have their own ata-* prefix.
+CHANGES WITH 44:
+        * This is mostly a bugfix release
 
-The s390 rule to set mode == 0666 for /dev/z90crypt is is removed from
-the udev tree and will be part of s390utils (or alternatively could be
-done by the kernel driver itself).
+        * Support optional initialization of the machine ID from the
+          KVM or container configured UUID.
 
-The udev-acl tool is no longer provided, it will be part of a future
-ConsoleKit release. On systemd systems, advanced ConsoleKit and udev-acl
-functionality are provided by systemd.
+        * Support immediate reboots with "systemctl reboot -ff"
 
-udev 181
-========
-Require kmod version 5.
+        * Show /etc/os-release data in systemd-analyze output
 
-Provide /dev/cdrom symlink for /dev/sr0.
+        * Many bugfixes for the journal, including endianess fixes and
+          ensuring that disk space enforcement works
 
-udev 180
-========
-Fix for ID_PART_ENTRY_* property names, added by the blkid built-in. The
-fix is needed for udisk2 to operate properly.
+        * sd-login.h is C++ comptaible again
 
-Fix for skipped rule execution when the kernel has removed the device
-node in /dev again, before the event was even started. The fix is needed
-to run device-mapper/LVM events properly.
+        * Extend the /etc/os-release format on request of the Debian
+          folks
 
-Fix for the man page installation, which was skipped when xsltproc was not
-installed.
+        * We now refuse non-UTF8 strings used in various configuration
+          and unit files. This is done to ensure we don't pass invalid
+          data over D-Bus or expose it elsewhere.
 
-udev 179
-========
-Bugfix for $name resolution, which broke at least some keymap handling.
+        * Register Mimo USB Screens as suitable for automatic seat
+          configuration
 
-udev 178
-========
-Bugfix for the firmware loading behavior with kernel modules which
-try to load firmware in the module_init() path. The blocked event
-runs into a timout now, which should allow the firmware to be loaded.
+        * Read SELinux client context from journal clients in a race
+          free fashion
 
-Bugfix for a wrong DEVNAME= export, which breaks at least the udev-acl
-tool.
+        * Reorder configuration file lookup order. /etc now always
+          overrides /run in order to allow the administrator to always
+          and unconditionally override vendor supplied or
+          automatically generated data.
 
-Bugfix for missing ID_ properties for GPT partitions.
+        * The various user visible bits of the journal now have man
+          pages. We still lack man pages for the journal API calls
+          however.
 
-The RUN+="socket:.." option is deprecated and should not be used. A warning
-during rules parsing is printed now. Services which listen to udev events,
-need to subscribe to the netlink messages with libudev and not let udev block
-in the rules execution until the message is delivered.
+        * We now ship all man pages in HTML format again in the
+          tarball.
 
-udev 177
-========
-Bugfix for rule_generator instalation.
+        Contributions from: Dave Reisner, Dirk Eibach, Frederic
+        Crozat, Harald Hoyer, Kay Sievers, Lennart Poettering, Marti
+        Raudsepp, Michal Schmidt, Shawn Landden, Tero Roponen, Thierry
+        Reding
 
-udev 176
-========
-The 'devtmpfs' filesystem is required now, udev will not create or delete
-device nodes anymore, it only adjusts permissions and ownership of device
-nodes and maintains additional symlinks.
+CHANGES WITH 43:
+        * This is mostly a bugfix release
 
-A writable /run directory (ususally tmpfs) is required now for a fully
-functional udev, there is no longer a fallback to /dev/.udev.
+        * systems lacking /etc/os-release  are no longer supported.
 
-The default 'configure' install locations have changed. Packages for systems
-with the historic / vs. /usr split need to be adapted, otherwise udev will
-be installed in /usr and not work properly. Example configuration options
-to install things the traditional way are in INSTALL.
+        * Various functionality updates to libsystemd-login.so
 
-The default install location of the 'udevadm' tool moved from 'sbin'
-to /usr/bin. Some tools expect udevadm in 'sbin', a symlink to udevadm
-needs to be manually created if needed, or --bindir=/sbin be specified.
+        * Track class of PAM logins to distuingish greeters from
+          normal user logins.
 
-The expected value of '--libexecdir=' has changed and must no longer contain
-the 'udev' directory.
+        Contributions from: Kay Sievers, Lennart Poettering, Michael
+        Biebl
 
-Kernel modules are now loaded directly by linking udev to 'libkmod'. The
-'modprobe' tool is no longer executed by udev.
+CHANGES WITH 42:
+        * This is an important bugfix release for v41.
 
-The 'blkid' tool is no longer executed from udev rules. Udev links
-directly to libblkid now.
+        * Building man pages is now optional which should be useful
+          for those building systemd from git but unwilling to install
+          xsltproc.
 
-Firmware is loaded natively by udev now, the external 'firmware' binary
-is no longer used.
+        * Watchdog support for supervising services is now usable. In
+          a future release support for hardware watchdogs
+          (i.e. /dev/watchdog) will be added building on this.
 
-All built-in tools can be listed and tested with 'udevadm test-builtin'.
+        * Service start rate limiting is now configurable and can be
+          turned off per service. When a start rate limit is hit a
+          reboot can automatically be triggered.
 
-The 'udevadm control --reload-rules' option has been renamed to '--reload'.
-It now also reloads the kernel module configuration.
+        * New CanReboot(), CanPowerOff() bus calls in systemd-logind.
 
-The systemd socket files use PassCredentials=yes, which is available in
-systemd version 38.
+        Contributions from: Benjamin Franzke, Bill Nottingham,
+        Frederic Crozat, Lennart Poettering, Michael Olbrich, Michal
+        Schmidt, MichaÅ‚ Górny, Piotr DrÄ…g
 
-The udev build system only creates a .xz tarball now.
+CHANGES WITH 41:
+        * The systemd binary is installed /usr/lib/systemd/systemd now;
+          An existing /sbin/init symlink needs to be adapted with the
+          package update.
 
-All tabs in the source code used for indentation are replaced by spaces now. :)
+        * The code that loads kernel modules has been ported to invoke
+          libkmod directly, instead of modprobe. This means we do not
+          support systems with module-init-tools anymore.
 
-udev 175
-========
-Bugfixes.
+        * Watchdog support is now already useful, but still not
+          complete.
 
-udev 174
-========
-Bugfixes.
+        * A new kernel command line option systemd.setenv= is
+          understood to set system wide environment variables
+          dynamically at boot.
 
-The udev daemon moved to /lib/udev/udevd. Non-systemd init systems
-and non-dracut initramfs image generators need to change the init
-scripts. Alternatively the udev build needs to move udevd back to
-/sbin or create a symlink in /sbin, which is not done by default.
+       * We now limit the set of capabilities of systemd-journald.
 
-The path_id, usb_id, input_id tools are built-in commands now and
-the stand-alone tools do not exist anymore. Static lists of file in
-initramfs generators need to be updated. For testing, the commands
-can still be executed standalone with 'udevadm test-builtin <cmd>'.
+        * We now set SIGPIPE to ignore by default, since it only is
+          useful in shell pipelines, and has little use in general
+          code. This can be disabled with IgnoreSIPIPE=no in unit
+          files.
 
-The fusectl filesystem is no longer mounted directly from udev.
-Systemd systems will take care of mounting fusectl and configfs
-now. Non-systemd systems need to ship their own rule if they
-need these filesystems auto-mounted.
-
-The long deprecated keys: SYSFS=, ID=, BUS= have been removed.
+        Contributions from: Benjamin Franzke, Kay Sievers, Lennart
+        Poettering, Michael Olbrich, Michal Schmidt, Tom Gundersen,
+        William Douglas
 
-The support for 'udevadm trigger --type=failed, and the
-RUN{fail_event_on_error} attribute was removed.
+CHANGES WITH 40:
+        * This is mostly a bugfix release
 
-The udev control socket is now created in /run/udev/control
-and no longer as an abstract namespace one.
+        * We now expose the reason why a service failed in the
+          "Result" D-Bus property.
 
-The rules to create persistent network interface and cdrom link
-rules automatically in /etc/udev/rules.d/ have been disabled by
-default. Explicit configuration will be required for these use
-cases, udev will no longer try to write any persistent system
-configuration from a device hotplug path.
+        * Rudimentary service watchdog support (will be completed over
+          the next few releases.)
 
-udev 173
-========
-Bugfixes.
-
-The udev-acl extra is no longer enabled by default now. To enable it,
---enable-udev_acl needs to be given at ./configure time. On systemd
-systems, the udev-acl rules prevent it from running as the functionality
-has moved to systemd.
+        * When systemd forks off in order execute some service we will
+          now immediately changes its argv[0] to reflect which process
+          it will execute. This is useful to minimize the time window
+          with a generic argv[0], which makes bootcharts more useful
 
-udev 172
-========
-Bugfixes.
-
-Udev now enables kernel media-presence polling if available. Part
-of udisks optical drive tray-handling moved to cdrom_id: The tray
-is locked as soon as a media is detected to enable the receiving
-of media-eject-request events. Media-eject-request events will
-eject the media.
-
-Libudev enumerate is now able to enumerate a subtree of a given
-device.
-
-The mobile-action-modeswitch modeswitch tool was deleted. The
-functionality is provided by usb_modeswitch now.
+        Contributions from: Alvaro Soliverez, Chris Paulson-Ellis, Kay
+        Sievers, Lennart Poettering, Michael Olbrich, Michal Schmidt,
+        Mike Kazantsev, Ray Strode
 
-udev 171
-========
-Bugfixes.
+CHANGES WITH 39:
+        * This is mostly a test release, but incorporates many
+          bugfixes.
 
-The systemd service files require systemd version 28. The systemd
-socket activation make it possible now to start 'udevd' and 'udevadm
-trigger' in parallel.
+        * New systemd-cgtop tool to show control groups by their
+          resource usage.
 
-udev 170
-========
-Fix bug in control message handling, which can lead to a failing
-udevadm control --exit. Thanks to Jürg Billeter for help tracking
-it down.
-
-udev 169
-========
-Bugfixes.
-
-We require at least Linux kernel 2.6.32 now. Some platforms might
-require a later kernel that supports accept4() and similar, or
-need to backport the trivial syscall wiring to the older kernels.
-
-The hid2hci tool moved to the bluez package and was removed.
-
-Many of the extras can be --enable/--disabled at ./configure
-time. The --disable-extras option was removed. Some extras have
-been disabled by default. The current options and their defaults
-can be checked with './configure --help'.
-
-udev 168
-========
-Bugfixes.
-
-Udev logs a warning now if /run is not writable at udevd
-startup. It will still fall back to /dev/.udev, but this is
-now considered a bug.
-
-The running udev daemon can now cleanly shut down with:
-  udevadm control --exit
-
-Udev in initramfs should clean the state of the udev database
-with: udevadm info --cleanup-db which will remove all state left
-behind from events/rules in initramfs. If initramfs uses
---cleanup-db and device-mapper/LVM, the rules in initramfs need
-to add OPTIONS+="db_persist" for all dm devices. This will
-prevent removal of the udev database for these devices.
-
-Spawned programs by PROGRAM/IMPORT/RUN now have a hard timeout of
-120 seconds per process. If that timeout is reached the spawned
-process will be killed. The event timeout can be overwritten with
-udev rules.
-
-If systemd is used, udev gets now activated by netlink data.
-Systemd will bind the netlink socket which will buffer all data.
-If needed, such setup allows a seemless update of the udev daemon,
-where no event can be lost during a udevd update/restart.
-Packages need to make sure to: systemctl stop udev.socket udev.service
-or 'mask' udev.service during the upgrade to prevent any unwanted
-auto-spawning of udevd.
-This version of udev conflicts with systemd version below 25. The
-unchanged service files will not wirk correctly.
-
-udev 167
-========
-Bugfixes.
-
-The udev runtime data moved from /dev/.udev/ to /run/udev/. The
-/run mountpoint is supposed to be a tmpfs mounted during early boot,
-available and writable to for all tools at any time during bootup,
-it replaces /var/run/, which should become a symlink some day.
-
-If /run does not exist, or is not writable, udev will fall back using
-/dev/.udev/.
-
-On systemd systems with initramfs and LVM used, packagers must
-make sure, that the systemd and initramfs versions match. The initramfs
-needs to create the /run mountpoint for udev to store the data, and
-mount this tmpfs to /run in the rootfs, so the that the udev database
-is preserved for the udev version started in the rootfs.
-
-The command 'udevadm info --convert-db' is gone. The udev daemon
-itself, at startup, converts any old database version if necessary.
-
-The systemd services files have been reorganized. The udev control
-socket is bound by systemd and passed to the started udev daemon.
-The udev-settle.service is no longer active by default. Services which
-can not handle hotplug setups properly need to actively pull it in, to
-act like a barrier. Alternatively the settle service can be unconditionally
-'systemctl'enabled, and act like a barrier for basic.target.
-
-The fstab_import callout is no longer built or installed. Udev
-should not be used to mount, does not watch changes to fstab, and
-should not mirror fstab values in the udev database.
-
-udev 166
-========
-Bugfixes.
-
-New and updated keymaps.
-
-udev 165
-========
-Bugfixes.
-
-The udev database has changed, After installation of a new udev
-version, 'udevadm info --convert-db' should be called, to let the new
-udev/libudev version read the already stored data.
-
-udevadm now supports quoting of property values, and prefixing of
-key names:
-  $ udevadm info --export --export-prefix=MY_ --query=property -n sda
-  MY_MAJOR='259'
-  MY_MINOR='0'
-  MY_DEVNAME='/dev/sda'
-  MY_DEVTYPE='disk'
-  ...
-
-libudev now supports:
-  udev_device_get_is_initialized()
-  udev_enumerate_add_match_is_initialized()
-to be able to skip devices the kernel has created , but udev has
-not already handled.
-
-libudev now supports:
-  udev_device_get_usec_since_initialized()
-to retrieve the "age" of a udev device record.
-
-GUdev supports a more generic GUdevEnumerator class, udev TAG
-handling, device initialization and timestamp now.
-
-The counterpart of /sys/dev/{char,block}/$major:$minor,
-/dev/{char,block}/$major:$minor symlinks are now unconditionally
-created, even when no rule files exist.
-
-New and updated keymaps.
-
-udev 164
-========
-Bugfixes.
-
-GUdev moved from /usr to /.
-
-udev 163
-========
-Bugfixes.
-
-udev 162
-========
-Bugfixes.
-
-Persistent network naming rules are disabled inside of Qemu/KVM now.
-
-New and updated keymaps.
-
-Udev gets unconditionally enabled on systemd installations now. There
-is no longer the need to to run 'systemctl enable udev.service'.
-
-udev 161
-========
-Bugfixes.
-
-udev 160
-========
-Bugfixes.
-
-udev 159
-========
-Bugfixes.
-
-New and fixed keymaps.
-
-Install systemd service files if applicable.
-
-udev 158
-========
-Bugfixes.
-
-All distribution specific rules are removed from the udev source tree,
-most of them are no longer needed. The Gentoo rules which allow to support
-older kernel versions, which are not covered by the default rules anymore
-has moved to rules/misc/30-kernel-compat.rules.
-
-udev 157
-========
-Bugfixes.
-
-The option --debug-trace and the environemnt variable UDEVD_MAX_CHILDS=
-was removed from udevd.
-
-Udevd now checks the kernel commandline for the following variables:
-  udev.log-priority=<syslog priority>
-  udev.children-max=<maximum number of workers>
-  udev.exec-delay=<seconds to delay the execution of RUN=>
-to help debuging coldplug setups where the loading of a kernel
-module crashes the system.
-
-The subdirectory in the source tree rules/packages has been renamed to
-rules/arch, anc contains only architecture specific rules now.
-
-udev 156
-========
-Bugfixes.
-
-udev 155
-========
-Bugfixes.
-
-Now the udev daemon itself, does on startup:
-  - copy the content of /lib/udev/devices to /dev
-  - create the standard symlinks like /dev/std{in,out,err},
-    /dev/core, /dev/fd, ...
-  - use static node information provided by kernel modules
-    and creates these nodes to allow module on-demand loading
-  - possibly apply permissions to all ststic nodes from udev
-    rules which are annotated to match a static node
-
-The default mode for a device node is 0600 now to match the kernel
-created devtmpfs defaults. If GROUP= is specified and no MODE= is
-given the default will be 0660.
-
-udev 154
-========
-Bugfixes.
-
-Udev now gradually starts to pass control over the primary device nodes
-and their names to the kernel, and will in the end only manage the
-permissions of the node, and possibly create additional symlinks.
-As a first step NAME="" will be ignored, and NAME= setings with names
-other than the kernel provided name will result in a logged warning.
-Kernels that don't provide device names, or devtmpfs is not used, will
-still work as they did before, but it is strongly recommended to use
-only the same names for the primary device node as the recent kernel
-provides for all devices.
-
-udev 153
-========
-Fix broken firmware loader search path.
-
-udev 152
-========
-Bugfixes.
-
-"udevadm trigger" defaults to "change" events now instead of "add"
-events. The "udev boot script" might need to add "--action=add" to
-the trigger command if not already there, in case the initial coldplug
-events are expected as "add" events.
-
-The option "all_partitons" was removed from udev. This should not be
-needed for usual hardware. Udev can not safely make assumptions
-about non-existing partition major/minor numbers, and therefore no
-longer provide this unreliable and unsafe option.
-
-The option "ignore_remove" was removed from udev. With devtmpfs
-udev passed control over device nodes to the kernel. This option
-should not be needed, or can not work as advertised. Neither
-udev nor the kernel will remove device nodes which are copied from
-the /lib/udev/devices/ directory.
-
-All "add|change" matches are replaced by "!remove" in the rules and
-in the udev logic. All types of events will update possible symlinks
-and permissions, only "remove" is handled special now.
-
-The modem modeswitch extra was removed and the external usb_modeswitch
-program should be used instead.
-
-New and fixed keymaps.
-
-udev 151
-========
-Bugfixes.
-
-udev 150
-========
-Bugfixes.
-
-Kernels with SYSFS_DEPRECATED=y are not supported since a while. Many users
-depend on the current sysfs layout and the information not available in the
-deprecated layout. All remaining support for the deprecated sysfs layout is
-removed now.
-
-udev 149
-========
-Fix for a possible endless loop in the new input_id program.
-
-udev 148
-========
-Bugfixes.
-
-The option "ignore_device" does no longer exist. There is no way to
-ignore an event, as libudev events can not be suppressed by rules.
-It only prevented RUN keys from being executed, which results in an
-inconsistent behavior in current setups.
-
-BUS=, SYSFS{}=, ID= are long deprecated and should be SUBSYSTEM(S)=,
-ATTR(S){}=, KERNEL(S)=. It will cause a warning once for every rule
-file from now on.
-
-The support for the deprecated IDE devices has been removed from the
-default set of rules. Distros who still care about non-libata drivers
-need to add the rules to the compat rules file.
-
-The ID_CLASS property on input devices has been replaced by the more accurate
-set of flags ID_INPUT_{KEYBOARD,KEY,MOUSE,TOUCHPAD,TABLET,JOYSTICK}. These are
-determined by the new "input_id" prober now. Some devices, such as touchpads,
-can have several classes. So if you previously had custom udev rules which e. g.
-checked for ENV{ID_CLASS}=="kbd", you need to replace this with
-ENV{ID_INPUT_KEYBOARD}=="?*".
-
-udev 147
-========
-Bugfixes.
-
-To support DEVPATH strings larger than the maximum file name length, the
-private udev database format has changed. If some software still reads the
-private files in /dev/.udev/, which it shouldn't, now it's time to fix it.
-Please do not port anything to the new format again, everything in /dev/.udev
-is and always was private to udev, and may and will change any time without
-prior notice.
-
-Multiple devices claiming the same names in /dev are limited to symlinks
-only now. Mixing identical symlink names and node names is not supported.
-This reduces the amount of data in the database significantly.
-
-NAME="%k" causes a warning now. It's is and always was completely superfluous.
-It will break kernel supplied DEVNAMEs and therefore it needs to be removed
-from all rules.
-
-Most NAME= instructions got removed. Kernel 2.6.31 supplies the needed names
-if they are not the default. To support older kernels, the NAME= rules need to
-be added to the compat rules file.
-
-Symlinks to udevadm with the old command names are no longer resolved to
-the udevadm commands.
-
-The udev-acl tool got adopted to changes in ConsoleKit. Version 0.4.1 is
-required now.
-
-The option "last_rule" does no longer exist. Its use breaks too many
-things which expect to be run from independent later rules, and is an idication
-that something needs to be fixed properly instead.
-
-The gudev API is no longer marked as experimental,
-G_UDEV_API_IS_SUBJECT_TO_CHANGE is no longer needed. The gudev introspection
-is enabled by default now. Various projects already depend on introspection
-information to bind dynamic languages to the gudev interfaces.
-
-udev 146
-========
-Bugfixes.
-
-The udevadm trigger "--retry-failed" option, which is replaced since quite
-a while by "--type=failed" is removed.
-
-The failed tracking was not working at all for a few releases. The RUN
-option "ignore_error" is replaced by a "fail_event_on_error" option, and the
-default is not to track any failing RUN executions.
-
-New keymaps, new modem, hid2hci updated.
-
-udev 145
-========
-Fix possible crash in udevd when worker processes are busy, rules are
-changed at the same time, and workers get killed to reload the rules.
-
-udev 144
-========
-Bugfixes.
-
-Properties set with ENV{.FOO}="bar" are marked private by starting the
-name with a '.'. They will not be stored in the database, and not be
-exported with the event.
-
-Firmware files are looked up in:
-  /lib/firmware/updates/$(uname -r)
-  /lib/firmware/updates
-  /lib/firmware/$(uname -r)
-  /lib/firmware"
-now.
-
-ATA devices switched the property from ID_BUS=scsi to ID_BUS=ata.
-ata_id, instead of scsi_id, is the default tool now for ATA devices.
-
-udev 143
-========
-Bugfixes.
-
-The configure options have changed because another library needs to be
-installed in a different location. Instead of exec_prefix and udev_prefix,
-libdir, rootlibdir and libexecdir are used. The Details are explained in
-the README file.
-
-Event processes now get re-used after they handled an event. This reduces
-the number of forks and the pressure on the CPU significantly, because
-cloned event processes no longer cause page faults in the main daemon.
-After the events have settled, a few worker processes stay around for
-future events, all others get cleaned up.
-
-To be able to use signalfd(), udev depends on kernel version 2.6.25 now.
-Also inotify support is mandatory now to run udev.
-
-The format of the queue exported by the udev damon has changed. There is
-no longer a /dev/.udev/queue/ directory. The current event queue can be
-accessed with udevadm settle and libudedv.
-
-Libudev does not have the unstable API header anymore. From now on,
-incompatible changes will be handled by bumping the library major version.
-
-To build udev from the git tree gtk-doc is needed now. The tarballs will
-build without it and contain the pre-built documentation. An online copy
-is available here:
-  http://www.kernel.org/pub/linux/utils/kernel/hotplug/libudev/
-
-The tools from the udev-extras repository have been merged into the main
-udev repository. Some of the extras have larger external dependencies, and
-they can be disabled with the configure switch --disable-extras.
-
-udev 142
-========
-Bugfixes.
-
-The program vol_id and the library libvolume_id are removed from the
-repository. Libvolume_id is merged with libblkid from the util-linux-ng
-package. Persistent disk links for label and uuid depend on the
-util-linux-ng version (2.15) of blkid now. Older versions of blkid
-can not be used with udev.
-
-Libudev allows to subscribe to udev events. To prevent unwanted messages
-to be delivered, and waking up the subscribing process, a filter can be
-installed, to drop messages inside a kernel socket filter. The filters
-match on the <subsytem>:<devtype> properties of the device.
-    This is part of the ongoing effort to replace HAL, and switch current
-users over to directly use libudev.
-    Libudev is still marked as experimental, and its interface might
-eventually change if needed, but no major changes of the currently exported
-interface are expected anymore, and a first stable release should happen
-soon.
-
-A too old kernel (2.6.21) or a kernel with CONFIG_SYSFS_DEPRECATED
-is not supported since while and udevd will log an error message at
-startup. It should still be able to boot-up, but advanced rules and system
-services which depend on the information not available in the old sysfs
-format will fail to work correctly.
-
-DVB device naming is supplied by the kernel now. In case older kernels
-need to be supported, the old shell script should be added to a compat
-rules file.
-
-udev 141
-========
-Bugfixes.
-
-The processed udev events get send back to the netlink socket. Libudev
-provides access to these events. This is work-in-progress, to replace
-the DeviceKit daemon functionality directly with libudev. There are
-upcoming kernel changes to allow non-root users to subcribe to these
-events.
-
-udev 140
-========
-Bugfixes.
-
-"udevadm settle" now optionally accepts a range of events to wait for,
-instead of waiting for "all" events.
-
-udev 139
-========
-Bugfixes.
-
-The installed watch for block device metadata changes is now removed
-during event hadling, because some (broken) tools may be called from udev
-rules and (wrongly) open the device with write access. After the finished
-event handling the watch is restored.
-
-udev 138
-========
-Bugfixes.
-
-Device nodes can be watched for changes with inotify with OPTIONS="watch".
-If closed after being opened for writing, a "change" uevent will occur.
-/dev/disk/by-{label,uuid}/* symlinks will be automatically updated.
-
-udev 137
-========
-Bugfixes.
-
-The udevadm test command has no longer a --force option, nodes and symlinks
-are always updated with a test run now.
-
-The udevd daemon can be started with --resolve-names=never to avoid all user
-and group lookups (e.g. in cut-down systems) or --resolve-names=late to
-lookup user and groups every time events are handled.
-
-udev 136
-========
-Bugfixes.
-
-We are currently merging the Ubuntu rules in the udev default rules,
-and get one step closer to provide a common Linux /dev setup, regarding
-device names, symlinks, and default device permissions. On udev startup,
-we now expect the following groups to be resolvable to their ids with
-glibc's getgrnam():
-  disk, cdrom, floppy, tape, audio, video, lp, tty, dialout, kmem.
-LDAP setups need to make sure, that these groups are always resolvable at
-bootup, with only the rootfs mounted, and without network access available.
-
-Some systems may need to add some new, currently not used groups, or need
-to add some users to new groups, but the cost of this change is minimal,
-compared to the pain the current, rather random, differences between the
-various distributions cause for upstream projects and third-party vendors.
-
-In general, "normal" users who log into a machine should never be a member
-of any such group, but the device-access should be managed by dynamic ACLs,
-which get added and removed for the specific users on login/logout and
-session activity/inactivity. These groups are only provided for custom setups,
-and mainly system services, to allow proper privilege separation.
-A video-streaming daemon uid would be a member of "audio" and "video", to get
-access to the sound and video devices, but no "normal" user should ever belong
-to the "audio" group, because he could listen to the built-in microphone with
-any ssh-session established from the other side of the world.
-
-/dev/serial/by-{id,path}/ now contains links for ttyUSB devices,
-which do not depend on the kernel device name. As usual, unique
-devices - only a single one per product connected, or a real
-USB serial number in the device - are always found with the same
-name in the by-id/ directory.
-Completely identical devices may overwrite their names in by-id/
-and can only be found reliably in the by-path/ directory. Devices
-specified by by-path/ must not change their connection, like the
-USB port number they are plugged in, to keep their name.
-
-To support some advanced features, Linux 2.6.22 is the oldest supported
-version now. The kernel config with enabled SYSFS_DEPRECATED is no longer
-supported. Older kernels should still work, and devices nodes should be
-reliably created, but some rules and libudev will not work correctly because
-the old kernels do not provide the expected information or interfaces.
-
-udev 135
-========
-Bugfixes.
-
-Fix for a possible segfault while swapping network interface names in udev
-versions 131-134.
-
-udev 134
-========
-Bugfixes.
-
-The group "video" is part of the default rules now.
-
-udev 133
-========
-Bugfix for kernels using SYSFS_DEPRECATED* option and finding parent
-block devices in some cases. No common distro uses this option anymore,
-and we do not get enough testing for this and recent udev versions. If
-this option is not needed to run some old distro with a new kernel,
-it should be disabled in the kernel config.
-
-Bugfix for the $links substitution variable, which may crash if no links
-are created. This should not happen in usual setups because we always
-create /dev/{block,char}/ links.
-
-The strings of the parsed rules, which are kept in memory, no longer
-contain duplicate entries, or duplicate tails of strings. This, and the
-new rules parsing/matching code reduces the total in-memory size of
-a huge distro rule sets to 0.08 MB, compared to the 1.2MB of udev
-version 130.
-
-The export of DEVTYPE=disk/partition got removed from the default
-rules. This value is available from the kernel. The pnp shell script
-modprobe hack is removed from the default rules. ACPI devices have _proper_
-modalias support and take care of the same functionality.
-Installations which support old kernels, but install current default
-udev rules may want to add that to the compat rules file.
-
-Libvolume_id now always probes for all known filesystems, and does not
-stop at the first match. Some filesystems are marked as "exclusive probe",
-and if any other filesytem type matches at the same time, libvolume_id
-will, by default, not return any probing result. This is intended to prevent
-mis-detection with conflicting left-over signatures found from earlier
-file system formats. That way, we no longer depend on the probe-order
-in case of multiple competing signatures. In some setups the kernel allows
-to mount a volume with just the old filesystem signature still in place.
-This may damage the new filesystem and cause data-loss, just by mounting
-it. Because volume_id can not decide which one the correct signature is,
-the wrong signatures need to be removed manually from the volume, or the
-volume needs to be reformatted, to enable filesystem detection and possible
-auto-mounting.
-
-udev 132
-========
-Fix segfault if compiled without optimization and dbg() does not get
-compiled out and uses variables which are not available.
-
-udev 131
-========
-Bugfixes. (And maybe new bugs. :))
-
-The rule matching engine got converted from a rule list to a token
-array which reduced the in-memory rules representation of a full
-featured distros with thousends of udev rules from 1.2MB to 0.12 MB.
-Limits like 5 ENV and ATTR matches, and one single instance for most
-other keys per rule are gone.
-
-The NAME assignment is no longer special cased. If later rules assign
-a NAME value again, the former value will be overwritten. As usual
-for most other keys, the NAME value can be protected by doing a final
-assignment with NAME:="<value>".
-
-All udev code now uses libudev, which is also exported. The library
-is still under development, marked as experimental, and its interface
-may change as long as the DeviceKit integration is not finished.
-
-Many thanks to Alan Jenkins for his continuous help, and finding and
-optimizing some of the computing expensive parts.
-
-udev 130
-========
-Bugfixes.
-
-Kernel devices and device nodes are connected now by reverse indizes in
-/sys and /dev. A device number retrieved by a stat() or similar, the
-kernel device directory can be found by looking up:
-  /sys/dev/{block,char}/<maj>:<min>
-and the device node of the same device by looking up:
-  /dev/{block,char}/<maj>:<min>
-
-udev 129
-========
-Fix recently introduced bug, which caused a compilation without large
-file support, where vol_id does not recognize raid signatures at the end
-of a volume.
-
-Firewire disks now create both, by-id/scsi-* and by-id/ieee-* links.
-Seems some kernel versions prevent the creation of the ieee-* links,
-so people used the scsi-* link which disappeared now.
-
-More libudev work. Almost all udevadm functionality comes from libudev
-now.
-
-udevadm trigger has a new option --type, which allows to trigger events
-for "devices", for "subsystems", or "failed" devices. The old option
---retry-failed" still works, but is no longer mentioned in the man page.
-
-udev 128
-========
-Bugfixes.
-
-The udevadm info --device-id-of-file= output has changed to use
-the obvious format. Possible current users should use the --export
-option which is not affected.
-
-The old udev commands symlinks to udevadm are not installed, if
-these symlinks are used, a warning is printed.
-
-udev 127
-========
-Bugfixes.
-
-Optical drive's media is no longer probed for raid signatures,
-reading the end of the device causes some devices to malfunction.
-Also the offset of the last session found is used now to probe
-for the filesystem.
-
-The volume_id library got a major version number update to 1,
-some deprecated functions are removed.
-
-A shared library "libudev" gets installed now to provide access
-to udev device information. DeviceKit, the successor of HAL, will
-need this library to access the udev database and search sysfs for
-devices.
-The library is currently in an experimental state, also the API is
-expected to change, as long as the DeviceKit integration is not
-finished.
-
-udev 126
-========
-We use ./configure now. See INSTALL for details. Current
-options are:
-    --prefix=
-        "/usr" - prefix for man pages, include files
-    --exec-prefix=
-        "" - the root filesystem, prefix for libs and binaries
-    --sysconfdir=
-        "/etc"
-    --with-libdir-name=
-        "lib" - directory name for libraries, not a path name
-        multilib 64bit systems may use "lib64" instead of "lib"
-    --enable-debug
-        compile-in verbose debug messages
-    --disable-logging
-        disable all logging and compile-out all log strings
-    --with-selinux
-        link against SELInux libraries, to set the expected context
-        for created files
-
-In the default rules, the group "disk" gets permissions 0660 instead
-of 0640. One small step closer to unify distro rules. Some day, all
-distros hopefully end up with the same set of rules.
-
-No symlinks to udevadm are installed anymore, if they are still needed,
-they should be provided by the package.
-
-udev 125
-========
-Bugfixes.
-
-Default udev rules, which are not supposed to be edited by the user, should
-be placed in /lib/udev/rules.d/ now, to make it clear that they are private to
-the udev package and will be replaced with an update. Udev will pick up rule
-files from:
-  /lib/udev/rules.d/  - default installed rules
-  /etc/udev/rules.d/  - user rules + on-the-fly generated rules
-  /dev/.udev/rules.d/ - temporary non-persistent rules created after bootup
-It does not matter in which directory a rule file lives, all files are sorted
-in lexical order.
-
-To help creating /dev/root, we have now:
-  $ udevadm info --export --export-prefix="ROOT_" --device-id-of-file=/
-  ROOT_MAJOR=8
-  ROOT_MINOR=5
-In case the current --device-id-of-file is already used, please switch to
-the --export format version, it saves the output parsing and the old
-format will be changed to use ':' as a separator, like the format in the
-sysfs 'dev' file.
-
-udev 124
-========
-Fix cdrom_id to properly recognize blank media.
-
-udev 123
-========
-Bugfixes.
-
-Tape drive id-data is queried from /dev/bsg/* instead of the tape
-nodes. This avoids rewinding tapes on open().
-
-udev 122
-========
-Bugfixes.
-
-The symlinks udevcontrol and udevtrigger are no longer installed by
-the Makefile.
-
-The scsi_id program does not depend on sysfs anymore. It can speak
-SGv4 now, so /dev/bsg/* device nodes can be used, to query SCSI device
-data, which should solve some old problems with tape devices, where
-we better do not open all tape device nodes to identify the device.
-
-udev 121
-========
-Many bugfixes.
-
-The cdrom_id program is replaced by an advanced version, which can
-detect most common device types, and also properties of the inserted
-media. This is part of moving some basic functionality from HAL into
-udev (and the kernel).
-
-udev 120
-========
-Bugfixes.
-
-The last WAIT_FOR_SYSFS rule is removed from the default rules.
-
-The symlinks to udevadm for the debugging tools: udevmonitor and
-udevtest are no longer created.
-
-The symlinks to the udevadm man page for the old tool names are
-no longer created.
-
-Abstract namespace sockets paths in RUN+="socket:@<path>" rules,
-should be prefixed with '@' to indicate that the path is not a
-real file.
-
-udev 119
-========
-Bugfixes.
-
-udev 118
-========
-Bugfixes.
-
-Udevstart is removed from the tree, it did not get installed for
-a long time now, and is long replaced by trigger and settle.
-
-udev 117
-========
-Bugfixes.
-
-All udev tools are merged into a single binary called udevadm.
-The old names of the tools are built-in commands in udevadm now.
-Symlinks to udevadm, with the names of the old tools, provide
-the same functionality as the standalone tools. There is also
-only a single udevadm.8 man page left for all tools.
-
-Tools like mkinitramfs should be checked, if they need to include
-udevadm in the list of files.
-
-udev 116
-========
-Bugfixes.
-
-udev 115
-========
-Bugfixes.
-
-The etc/udev/rules.d/ directory now contains a default set of basic
-udev rules. This initial version is the result of a rules file merge
-of Fedora and openSUSE. For these both distros only a few specific
-rules are left in their own file, named after the distro. Rules which
-are optionally installed, because they are only valid for a specific
-architecture, or rules for subsystems which are not always used are
-in etc/udev/packages/.
-
-udev 114
-========
-Bugfixes.
-
-Dynamic rules can be created in /dev/.udev/rules.d/ to trigger
-actions by dynamically created rules.
-
-SYMLINK=="<value>" matches agains the entries in the list of
-currently defined symlinks. The links are not created in the
-filesystem at that point in time, but the values can be matched.
+        * Linking against libacl for ACLs is optional again. If
+          disabled, support tracking device access for active logins
+          goes becomes unavailable, and so does access to the user
+          journals by the respective users.
 
-RUN{ignore_error}+="<program>" will ignore any exit code from the
-program and not record as a failed event.
+        * If a group "adm" exists, journal files are automatically
+          owned by them, thus allow members of this group full access
+          to the system journal as well as all user journals.
 
-udev 113
-========
-Bugfixes.
+        * The journal now stores the SELinux context of the logging
+          client for all entries.
 
-Final merge of patches/features from the Ubuntu package.
+        * Add C++ inclusion guards to all public headers
 
-udev 112
-========
-Bugfixes.
-
-Control characters in filesystem label strings are no longer silenty
-removed, but hex-encoded, to be able to uniquely identify the device
-by its symlink in /dev/disk/by-label/.
-If libvolume_id is used by mount(8), LABEL= will work as expected,
-if slashes or other characters are used in the label string.
-
-To test the existence of a file, TEST=="<file>" and TEST!="<file>"
-can be specified now. The TEST key accepts an optional mode mask
-TEST{0100}=="<is executable file>".
-
-Scsi_id now supports a mode without expecting scsi-specific sysfs
-entries to allow the extraction of cciss-device persistent properties.
-
-udev 111
-========
-Bugfixes.
-
-In the future, we may see uuid's which are just simple character
-strings (see the DDF Raid Specification). For that reason vol_id now
-exports ID_FS_UUID_SAFE, just like ID_FS_LABEL_SAFE. For things like
-the creation of symlinks, the *_SAFE values ensure, that no control
-or whitespace characters are used in the filename.
-
-Possible users of libvolume_id, please use the volume_id_get_* functions.
-The public struct will go away in a future release of the library.
-
-udev 110
-========
-Bugfixes.
-
-Removal of useless extras/eventrecorder.sh.
-
-udev 109
-========
-Bugfixes.
-
-udev 108
-========
-Bugfixes.
-
-The directory multiplexer for dev.d/ and hotplug.d are finally removed
-from the udev package.
-
-udev 107
-========
-Bugfixes.
-
-Symlinks can have priorities now, the priority is assigned to the device
-and specified with OPTIONS="link_priority=100". Devices with higher
-priorities overwrite the symlinks of devices with lower priorities.
-If the device that currently owns the link, goes away, the symlink
-will be removed, and recreated, pointing to the next device with the
-highest actual priority. This should make /dev/disk/by-{label,uuid,id}
-more reliable, if multiple devices contain the same metadata and overwrite
-these symlinks.
-
-The dasd_id program is removed from the udev tree, and dasdinfo, with the
-needed rules, are part of the s390-tools now.
-
-Please add KERNEL=="[0-9]*:[0-9]*" to the scsi wait-for-sysfs rule,
-we may get the scsi sysfs mess fixed some day, and this will only catch
-the devices we are looking for.
-
-USB serial numbers for storage devices have the target:lun now appended,
-to make it possibble to distinguish broken multi-lun devices with all
-the same SCSI identifiers.
-
-Note: The extra "run_directory" which searches and executes stuff in
-/etc/hotplug.d/ and /etc/dev.d/ is long deprecated, and will be removed
-with the next release. Make sure, that you don't use it anymore, or
-provides your own implementation of that inefficient stuff.
-We are tired of reports about a "slow udev", because these directories
-contain stuff, that runs with _every_ event, instead of using rules,
-that run programs only for the matching events.
-
-udev 106
-========
-Bugfixes.
-
-udev 105
-========
-Bugfixes.
-
-DRIVER== will match only for devices that actually have a real
-driver. DRIVERS== must be used, if parent devices should be
-included in the match.
-
-Libvolume_id's "linux_raid" detection needed another fix.
-
-udev 104
-========
-Bugfixes.
-
-udev 103
-========
-Add additional check to volume_id detection of via_raid, cause
-some company decided to put a matching pattern all over the empty
-storage area of their music players.
-
-udev 102
-========
-Fix path_id for SAS devices.
-
-udev 101
-========
-The udev daemon can be started with --debug-trace now, which will
-execute all events serialized to get a chance to catch a possible
-action that crashes the box.
-
-A warning is logged, if PHYSDEV* keys, the "device" link, or a parent
-device attribute like $attr{../file} is used, only WAIT_FOR_SYSFS rules
-are excluded from the warning. Referencing parent attributes directly
-may break when something in the kernel driver model changes. Udev will
-just find the attribute by walking up the parent chain.
-
-Udevtrigger now sorts the list of devices depending on the device
-dependency, so a "usb" device is triggered after the parent "pci"
-device.
-
-udev 100
-========
-Revert persistent-storage ata-serial '_' '-' replacement.
-
-udev 099
-========
-Bugfixes.
-
-Udevtrigger can now filter the list of devices to be triggered. Matches
-for subsystems or sysfs attributes can be specified.
-
-The entries in /dev/.udev/queue and /dev/.udev/failed have changed to
-zero-sized files to avoid pointing to /sys and confuse broken tools which
-scan the /dev directory. To retry failed events, udevtrigger --retry-failed
-should be used now.
-
-The rules and scripts to create udev rules for persistent network
-devices and optical drives are in the extras/rules_generator directory
-now. If you use something similar, please consider replacing your own
-version with this, to share the support effort. The rule_generator
-installs its own rules into /etc/udev/rules.d.
-
-The cdrom_id tool installs its own rule now in /etc/udev/rules.d, cause
-the rule_generator depends on cdrom_id to be called in an earlier rule.
-
-udev 098
-========
-Bugfixes.
-
-Renaming of some key names (the old names still work):
-BUS -> SUBSYSTEMS, ID -> KERNELS, SYSFS -> ATTRS, DRIVER -> DRIVERS.
-(The behavior of the key DRIVER will change soon in one of the next
-releases, to match only the event device, please switch to DRIVERS
-instead. If DRIVER is used, it will behave like DRIVERS, but an error
-is logged.
-With the new key names, we have a more consistent and simpler scheme.
-We can match the properties of the event device only, with: KERNEL,
-SUBSYSTEM, ATTR, DRIVER. Or include all the parent devices in the match,
-with: KERNELS, SUBSYSTEMS, ATTRS, DRIVERS. ID, BUS, SYSFS, DRIVER are no
-longer mentioned in the man page and should be switched in the rule
-files.
-
-ATTR{file}="value" can be used now, to write to a sysfs file of the
-event device. Instead of:
-  ..., SYSFS{type}=="0|7|14", RUN+="/bin/sh -c 'echo 60 > /sys$$DEVPATH/timeout'"
-we now can do:
-  ..., ATTR{type}=="0|7|14", ATTR{timeout}="60"
-
-All the PHYSDEV* keys are deprecated and will be removed from a
-future kernel:
-  PHYDEVPATH -    is the path of a parent device and should not be
-                  needed at all.
-  PHYSDEVBUS -    is just a SUBSYSTEM value of a parent, and can be
-                  matched with SUBSYSTEMS==
-  PHYSDEVDRIVER - for bus devices it is available as ENV{DRIVER}.
-                  Newer kernels will have DRIVER in the environment,
-                  for older kernels udev puts in. Class device will
-                  no longer carry this property of a parent and
-                  DRIVERS== can be used to match such a parent value.
-Note that ENV{DRIVER} is only available for a few bus devices, where
-the driver is already bound at device event time. On coldplug, the
-events for a lot devices are already bound to a driver, and they will have
-that value set. But on hotplug, at the time the kernel creates the device,
-it can't know what driver may claim the device after that, therefore
-in most cases it will be empty.
-
-Failed events should now be re-triggered with:
-   udevtrigger --retry-failed.
-Please switch to this command, so we keep the details of the /dev/.udev/failed/
-files private to the udev tools. We may need to switch the current symlink
-target, cause some obviously broken tools try to scan all files in /dev
-including /dev/.udev/, find the links to /sys and end up stat()'ing sysfs files
-million times. This takes ages on slow boxes.
-
-The udevinfo attribute walk (-a) now works with giving a device node
-name (-n) instead of a devpath (-p). The query now always works, also when
-no database file was created by udev.
-
-The built-in /etc/passwd /etc/group parser is removed, we always depend on
-getpwnam() and getgrnam() now. One of the next releases will depend on
-fnmatch() and may use getopt_long().
-
-udev 097
-========
-Bugfixes and small improvements.
-
-udev 096
-========
-Fix path_id for recent kernels.
-
-udev 095
-========
-%e is finally gone.
-
-Added support for swapping network interface names, by temporarily
-renaming the device and wait for the target name to become free.
-
-udev 094
-========
-The built-in MODALIAS key and substitution is removed.
-
-udev 093
-========
-The binary firmware helper is replaced by the usual simple
-shell script. Udevsend is removed from the tree.
-
-udev 092
-========
-Bugfix release.
-
-udev 091
-========
-Some more keys require the correct use of '==' and '=' depending
-on the kind of operation beeing an assignment or a match. Rules
-with invalid operations are skipped and logged to syslog. Please
-test with udevtest if the parsing of your rules throws errors and
-fix possibly broken rules.
-
-udev 090
-========
-Provide "udevsettle" to wait for all current udev events to finish.
-It also watches the current kernel netlink queue by comparing the
-even sequence number to make sure that there are no current pending
-events that have not already arrived in the daemon.
-
-udev 089
-========
-Fix rule to skip persistent rules for removable IDE devices, which
-also skipped optical IDE drives.
-
-All *_id program are installed in /lib/udev/ by default now.
-
-No binary is stripped anymore as this should be done in the
-packaging process and not at build time.
-
-libvolume_id is provided as a shared library now and vol_id is
-linked against it. Also one of the next HAL versions will require
-this library, and the HAL build process will also require the
-header file to be installed. The copy of the same code in HAL will
-be removed to have only a single copy left on the system.
-
-udev 088
-========
-Add persistent links for SCSI tapes. The rules file is renamed
-to 60-persistent-storage.rules.
-
-Create persistent path for usb devices. Can be used for all sorts
-of devices that can't be distinguished by other properties like
-multiple identical keyboards and mice connected to the same box.
-
-Provide "udevtrigger" program to request events on coldplug. The
-shell script is much too slow with thousends of devices.
-
-udev 087
-========
-Fix persistent disk rules to exclude removable IDE drives.
-
-Warn if %e, $modalias or MODALIAS is used.
-
-udev 086
-========
-Fix queue export, which wasn't correct for subsequent add/remove
-events for the same device.
-
-udev 085
-========
-Fix cramfs detection on big endian.
-
-Make WAIT_FOR_SYSFS usable in "normal" rules and silent if the whole
-device goes away.
-
-udev 084
-========
-If BUS== and SYSFS{}== have been used in the same rule, the sysfs
-attributes were only checked at the parent device that matched the
-by BUS requested subsystem. Fix it to also look at the device we
-received the event for.
-
-Build variable CROSS has changed to CROSS_COMPILE to match the kernel
-build name.
-
-udev 083
-========
-Fix a bug where NAME="" would prevent RUN from beeing executed.
-
-RUN="/bin/program" does not longer automatically add the subsystem
-as the first parameter. This is from the days of /sbin/hotplug
-which is dead now and it's just confusing to need to add a space at
-the end of the program name to prevent this.
-If you use rules that need the subsystem as the first parameter,
-like the old "udev_run_hotlugd" and "udev_run_devd", add the subsystem
-to the key like RUN+="/bin/program $env{SUBSYSTEM}".
-
-udev 082
-========
-The udev man page has moved to udev(7) as it does not describe a command
-anymore. The programs udev, udevstart and udevsend are no longer installed
-by default and must be copied manually, if they should be installed or
-included in a package.
-
-Fix a bug where "ignore_device" could run earlier collected RUN keys before
-the ignore rule was applied.
-
-More preparation for future sysfs changes. usb_id and scsi_id no longer
-depend on a magic order of devices in the /devices chain. Specific devices
-should be requested by their subsytem.
-
-This will always find the scsi parent device without depending on a specific
-path position:
-  dev = sysfs_device_get(devpath);
-  dev_usb = sysfs_device_get_parent_with_subsystem(dev, "scsi");
-
-The "device" link in the current sysfs layout will be automatically
-_resolved_ as a parent and in the new sysfs layout it will just _be_ the
-parent in the devpath. If a device is requested by it's symlink, like all
-class devices in the new sysfs layout will look like, it gets automatically
-resolved and substituted with the real devpath and not the symlink path.
-
-Note:
-A similar logic must be applied to _all_ sysfs users, including
-scripts, that search along parent devices in sysfs. The explicit use of
-the "device" link must be avoided. With the future sysfs layout all
-DEVPATH's will start with /devices/ and have a "subsystem" symlink poiting
-back to the "class" or the "bus". The layout of the parent devices in
-/devices is not necessarily expected to be stable across kernel releases and
-searching for parents by their subsystem should make sysfs users tolerant
-for changed parent chains.
-
-udev 081
-========
-Prepare udev to work with the experimental kernel patch, that moves
-/sys/class devices to /sys/devices and /sys/block to /sys/class/block.
-
-Clarify BUS, ID, $id usage and fix $id behavior. This prepares for
-moving the class devices to /sys/devices.
-
-Thanks again to Marco for help finding a hopefully nice compromise
-to make %b simpler and working again.
-
-udev 080
-========
-Complete removal of libsysfs, replaced by simple helper functions
-which are much simpler and a bit faster. The udev daemon operatesentirely
-on event parameters and does not use sysfs for simple rules anymore.
-Please report any new bugs/problems, that may be caused by this big
-change. They will be fixed immediately.
-
-The enumeration format character '%e' is deprecated and will be
-removed sometimes from a future udev version. It never worked correctly
-outside of udevstart, so we can't use it with the new parallel
-coldplug. A simple enumeration is as useless as the devfs naming
-scheme, just get rid of both if you still use it.
-
-MODALIAS and $modalias is not needed and will be removed from one of
-the next udev versions, replace it in all rules with ENV{MODALIAS} or
-the sysfs "modalias" value.
-
-Thanks a lot to Marco for all his help on finding and fixing bugs.
-
-udev 079
-========
-Let scsi_id request libata drive serial numbers from page 0x80.
-
-Renamed etc/udev/persistent.rules to persistent-disk.rules and
-added /dev/disk/by-name/* for device mapper device names.
-
-Removed %e from the man page. It never worked reliably outside
-of udevstart and udevstart is no longer recommended to use.
-
-udev 078
-========
-Symlinks are now exported to the event environment. Hopefully it's no
-longer needed to run udevinfo from an event process, like it was
-mentioned on the hotplug list:
-  UDEV  [1134776873.702967] add@/block/sdb
-  ...
-  DEVNAME=/dev/sdb
-  DEVLINKS=/dev/disk/by-id/usb-IBM_Memory_Key_0218B301030027E8 /dev/disk/by-path/usb-0218B301030027E8:0:0:0
-
-udev 077
-========
-Fix a problem if udevsend is used as the hotplug handler and tries to use
-syslog, which causes a "vc" event loop. 2.6.15 will make udevsend obsolete
-and this kind of problems will hopefully go away soon.
-
-udev 076
-========
-All built-in logic to work around bad sysfs timing is removed with this
-version. The need to wait for sysfs files is almost fixed with a kernel
-version that doesn't work with this udev version anyway. Until we fix
-the timing of the "bus" link creation, the former integrated logic should
-be emulated by a rule placed before all other rules:
-  ACTION=="add", DEVPATH=="/devices/*", ENV{PHYSDEVBUS}=="?*", WAIT_FOR_SYSFS="bus"
-
-The option "udev_db" does no longer exist. All udev state will be in
-/$udev_root/.udev/ now, there is no longer an option to set this
-to anything else.
-If the init script or something else used this value, just depend on
-this hardcoded path. But remember _all_content_ of this directory is
-still private to udev and can change at any time.
-
-Default location for rule sripts and helper programs is now: /lib/udev/.
-Everything that is not useful on the commandline should go into this
-directory. Some of the helpers in the extras folder are installed there
-now. The rules need to be changed, to find the helpers there.
-
-Also /lib/udev/devices is recommended as a directory where packages or
-the user can place real device nodes, which get copied over to /dev at
-every boot. This should replace the various solutions with custom config
-files.
-
-Udevsend does no longer start the udev daemon. This must be done with
-the init script that prepares /dev on tmpfs and creates the initial nodes,
-before starting the daemon.
-
-udev 075
-========
-Silent a too verbose error logging for the old hotplug.d/ dev.d/
-emulation.
-
-The copy of klibc is removed. A systemwide installed version of klibc
-should be used to build a klibc udev now.
-
-udev 074
-========
-NAME="" will not create any nodes, but execute RUN keys. To completely
-ignore an event the OPTION "ignore_device" should be used.
-
-After removal of the reorder queue, events with a TIMEOUT can be executed
-without any queuing now.
-
-udev 073
-========
-Fixed bug in udevd, if inotify is not available. We depend on netlink
-uevents now, kernels without that event source will not work with that
-version of udev anymore.
-
-udev 072
-========
-The rule parsing happens now in the daemon once at startup, all udev
-event processes inherit the already parsed rules from the daemon.
-It is shipped with SUSE10.0 and reduces heavily the system load at
-startup. The option to save precompiled rules and let the udev process
-pick the them up is removed, as it's no longer needed.
-
-Kernel 2.6.15 will have symlinks at /class/input pointing to the real
-device. Libsysfs is changed to "translate" the requested link into the
-real device path, as it would happen with the hotplug event. Otherwise
-device removal and the udev database will not work.
-
-Using 'make STRIPCMD=' will leave the binaries unstripped for debugging
-and packaging.
-
-A few improvements for vol_id, the filesytem probing code.
-
-udev 071
-========
-Fix a stupid typo in extras/run_directory for "make install".
-
-scsi_id creates the temporary devnode now in /dev for usage with a
-non-writable /tmp directory.
-
-The uevent kernel socket buffer can carry app. 50.000 events now,
-let's see who can break this again. :)
-
-The upcoming kernel will have a new input driver core integration.
-Some class devices are now symlinks to the real device. libsysfs
-needs a fix for this to work correctly. Udevstart of older udev
-versions will _not_ create these devices!
-
-udev 070
-========
-Fix a 'install' target in the Makefile, that prevents EXTRAS from
-beeing installed.
-
-udev 069
-========
-A bunch of mostly trivial bugfixes. From now on no node name or
-symlink name can contain any character than plain whitelisted ascii
-characters or validated utf8 byte-streams. This is needed for the
-/dev/disk/by-label/* links, because we import untrusted data and
-export it to the filesystem.
-
-udev 068
-========
-More bugfixes. If udevd was started from the kernel, we don't
-have stdin/stdout/stderr, which broke the forked tools in some
-situations.
-
-udev 067
-========
-Bugfix. udevstart event ordering was broken for a long time.
-The new run_program() uncovered it, because /dev/null was not
-available while we try to run external programs.
-Now udevstart should create it before we run anything.
-
-udev 066
-========
-Minor bugfixes and some distro rules updates. If you don't have the
-persistent disk rules in /dev/disk/by-*/* on your distro, just
-grab it from here. :)
-
-udev 065
-========
-We can use socket communication now to pass events from udev to
-other programs:
-  RUN+="socket:/org/freedesktop/hal/udev_event"
-will pass the whole udev event to the HAL daemon without the need
-for a forked helper. (See ChangeLog for udevmonitor, as an example)
-
-udev 064
-========
-Mostly bugfixes and see ChangeLog.
-
-The test for the existence of an environment value should be
-switched from:
-  ENV{KEY}=="*" to ENV{KEY}=="?*"
-because "*" will not fail anymore, if the key does not exist or
-is empty.
-
-udev 063
-========
-Bugfixes and a few tweaks described in the ChangeLog.
-
-udev 062
-========
-Mostly a Bugfix release.
-
-Added WAIT_FOR_SYSFS="<attribute>" to be able to fight against the sysfs
-timing with custom rules.
-
-udev 061
-========
-We changed the  internal rule storage format. Our large rule files took
-2 MB of RAM, with the change we are down to 99kB.
-
-If the device-node has been created with default name and no symlink or
-options are to remenber, it is not longer stored in the udevdb. HAL will
-need to be updated to work correctly with that change.
-
-To overrride optimization flags, OPTFLAGS may be used now.
-
-udev 060
-========
-Bugfix release.
-
-udev 059
-========
-Major changes happened with this release. The goal is to take over the
-complete kernel-event handling and provide a more efficient way to dispatch
-kernel events. Replacing most of the current shell script logic and the
-kernel forked helper with a netlink-daemon and a rule-based event handling.
-
-o udevd listens to netlink events now. The first valid netlink event
-  will make udevd ignore any message from udevsend that contains a
-  SEQNUM, to avoid duplicate events. The forked events can be disabled
-  with:
-    echo "" > /proc/sys/kernel/hotplug
-  For full support, the broken input-subsytem needs to be fixed, not to
-  bypass the driver core.
-
-o /etc/dev.d/ + /etc/hotplug.d/ directory multiplexing is completely
-  removed from udev itself and must be emulated by calling small
-  helper binaries provided in the extras folder:
-    make EXTRAS=extras/run_directory/
-  will build udev_run_devd and udev_run_hotplugd, which can be called
-  from a rule if needed:
-    RUN+="/sbin/udev_run_hotplugd"
-  The recommended way to handle this is to convert all the calls from
-  the directories to explicit udev rules and get completely rid of the
-  multiplexing. (To catch a ttyUSB event, you now no longer need to
-  fork and exit 300 tty script instances you are not interested in, it
-  is just one rule that matches exactly the device.)
-
-o udev handles now _all_ events not just events for class and block
-  devices, this way it is possible to control the complete event
-  behavior with udev rules. Especially useful for rules like:
-    ACTION="add", DEVPATH="/devices/*", MODALIAS=="?*", RUN+="/sbin/modprobe $modalias"
-
-o As used in the modalias rule, udev supports now textual
-  substitution placeholder along with the usual format chars. This
-  needs to be documented, for now it's only visible in udev_rules_parse.c.
-
-o The rule keys support now more operations. This is documented in the
-  man page. It is possible to add values to list-keys like the SYMLINK
-  and RUN list with KEY+="value" and to clear the list by assigning KEY="".
-  Also "final"-assignments are supported by using KEY:="value", which will
-  prevent changing the key by any later rule.
-
-o kernel 2.6.12 has the "detached_state" attribute removed from
-  sysfs, which was used to recognize sysfs population. We switched that
-  to wait for the "bus" link, which is only available in kernels after 2.6.11.
-  Running this udev version on older kernels may cause a short delay for
-  some events.
-
-o To provide infrastructure for persistent device naming, the id programs:
-  scsi_id, vol_id (former udev_volume_id), and ata_id (new) are able now
-  to export the probed data in environment key format:
-    pim:~ # /sbin/ata_id --export /dev/hda
-    ID_MODEL=HTS726060M9AT00
-    ID_SERIAL=MRH401M4G6UM9B
-    ID_REVISION=MH4OA6BA
-
-  The following rules:
-    KERNEL="hd*[!0-9]", IMPORT="/sbin/ata_id --export $tempnode"
-    KERNEL="hd*[!0-9]", ENV{ID_SERIAL}=="?*", SYMLINK+="disk/by-id/$env{ID_MODEL}_$env{ID_SERIAL}"
-
-  Will create:
-    kay@pim:~> tree /dev/disk
-    /dev/disk
-    |-- by-id
-    |   |-- HTS726060M9AT00_MRH401M4G6UM9B -> ../../hda
-    |   `-- IBM-Memory_Key -> ../../sda
-    |-- by-label
-    |   |-- swap -> ../../hda1
-    |   |-- date -> ../../sda1
-    |   `-- home -> ../../hda3
-    `-- by-uuid
-        |-- 2E08712B0870F2E7 -> ../../hda3
-        |-- 9352cfef-7687-47bc-a2a3-34cf136f72e1 -> ../../hda1
-        |-- E845-7A89 -> ../../sda1
-        `-- b2a61681-3812-4f13-a4ff-920d70604299 -> ../../hda2
-
-  The IMPORT= operation will import these keys in the environment and make
-  it available for later PROGRAM= and RUN= executed programs. The keys are
-  also stored in the udevdb and can be queried from there with one of the
-  next udev versions.
-
-o A few binaries are silently added to the repository, which can be used
-  to replay kernel events from initramfs instead of using coldplug. udevd
-  can be instructed now to queue-up events while the stored events from
-  initramfs are filled into the udevd-queue. This code is still under
-  development and there is no documentation now besides the code itself.
-  The additional binaries get compiled, but are not installed by default.
-
-o There is also a temporary fix for a performance problem where too many
-  events happen in parallel and every event needs to parse the rules.
-  udev can now read precompiled rules stored on disk. This is likely to be
-  replaced by a more elegant solution in a future udev version.
-
-udev 058
-========
-With kernel version 2.6.12, the sysfs file "detached_state" was removed.
-Fix for libsysfs not to expect this file was added.
-
-udev 057
-========
-All rules are applied now, but only the first matching rule with a NAME-key
-will be applied. All later rules with NAME-key are completely ignored. This
-way system supplied symlinks or permissions gets applied to user-defined
-naming rules.
-
-Note:
-Please check your rules setup, if you may need to add OPTIONS="last_rule"
-to some rules, to keep the old behavior.
-
-The rules are read on "remove"-events too. That makes is possible to match
-with keys that are available on remove (KERNEL, SUBSYSTEM, ID, ENV, ...) to
-instruct udev to ignore an event (OPTIONS="ignore_device").
-The new ACTION-key may be used to let a rule act only at a "remove"-event.
-
-The new RUN-key supports rule-based execution of programs after device-node
-handling. This is meant as a general replacement for the dev.d/-directories
-to give fine grained control over the execution of programs.
-
-The %s{}-sysfs format char replacement values are searched at any of the
-devices in the device chain now, not only at the class-device.
-
-We support log priority levels now. The value udev_log in udev.conf is used
-to determine what is printed to syslog. This makes it possible to
-run a version with compiled-in debug messages in a production environment
-which is sometimes needed to find a bug.
-It is still possible to supress the inclusion of _any_ syslog usage with
-USE_LOG=false to create the smallest possible binaries if needed.
-The configured udev_log value can be overridden with the environment variable
-UDEV_LOG.
-
-udev 056
-========
-Possible use of a system-wide klibc:
-  make USE_KLIBC=true KLCC=/usr/bin/klcc all
-will link against an external klibc and our own version will be ignored.
-
-udev 055
-========
-We support an unlimited count of symlinks now.
-
-If USE_STATIC=true is passed to a glibc build, we link statically and use
-a built-in userdb parser to resolve user and group names.
-
-The PLACE= key is gone. It can be replaced by an ID= for a long time, because
-we walk up the chain of physical devices to find a match.
-
-The KEY="<value>" format supports '=', '==', '!=,' , '+=' now. This makes it
-easy to skip certain attribute matches without composing rules with weird
-character class negations like:
-  KERNEL="[!s][!c][!d]*"
-this can now be replaced with:
-  KERNEL!="scd*"
-The current simple '=' is still supported, and should work as it does today,
-but existing rules should be converted if possible, to be better readable.
-
-We have new ENV{}== key now, to match against a maximum of 5 environment
-variables.
-
-udevstart is its own binary again, because we don't need co carry this araound
-with every forked event.
+        * New output mode "cat" in the journal to print only text
+          messages, without any meta data like date or time.
+
+        * Include tiny X server wrapper as a temporary stop-gap to
+          teach XOrg udev display enumeration. This is used by display
+          managers such as gdm, and will go away as soon as XOrg
+          learned native udev hotplugging for display devices.
+
+        * Add new systemd-cat tool for executing arbitrary programs
+          with STDERR/STDOUT connected to the journal. Can also act as
+          BSD logger replacement, and does so by default.
+
+        * Optionally store all locally generated coredumps in the
+          journal along with meta data.
+
+        * systemd-tmpfiles learnt four new commands: n, L, c, b, for
+          writing short strings to files (for usage for /sys), and for
+          creating symlinks, character and block device nodes.
+
+        * New unit file option ControlGroupPersistent= to make cgroups
+          persistent, following the mechanisms outlined in
+          http://www.freedesktop.org/wiki/Software/systemd/PaxControlGroups
+
+        * Support multiple local RTCs in a sane way
+
+        * No longer monopolize IO when replaying readahead data on
+          rotating disks, since we might starve non-file-system IO to
+          death, since fanotify() will not see accesses done by blkid,
+          or fsck.
+
+        * Don't show kernel threads in systemd-cgls anymore, unless
+          requested with new -k switch.
+
+        Contributions from: Dan Horák, Kay Sievers, Lennart
+        Poettering, Michal Schmidt
+
+CHANGES WITH 38:
+        * This is mostly a test release, but incorporates many
+          bugfixes.
+
+        * The git repository moved to:
+          git://anongit.freedesktop.org/systemd/systemd
+          ssh://git.freedesktop.org/git/systemd/systemd
+
+        * First release with the journal
+          http://0pointer.de/blog/projects/the-journal.html
+
+        * The journal replaces both systemd-kmsg-syslogd and
+          systemd-stdout-bridge.
+
+        * New sd_pid_get_unit() API call in libsystemd-logind
+
+        * Many systemadm clean-ups
+
+        * Introduce remote-fs-pre.target which is ordered before all
+          remote mounts and may be used to start services before all
+          remote mounts.
+
+        * Added Mageia support
+
+        * Add bash completion for systemd-loginctl
+
+        * Actively monitor PID file creation for daemons which exit in
+          the parent process before having finished writing the PID
+          file in the daemon process. Daemons which do this need to be
+          fixed (i.e. PID file creation must have finished before the
+          parent exits), but we now react a bit more gracefully to them.
+
+        * Add colourful boot output, mimicking the well-known output
+          of existing distributions.
+
+        * New option PassCredentials= for socket units, for
+          compatibility with a recent kernel ABI breakage.
+
+        * /etc/rc.local is now hooked in via a generator binary, and
+          thus will no longer act as synchronization point during
+          boot.
+
+        * systemctl list-unit-files now supports --root=.
+
+        * systemd-tmpfiles now understands two new commands: z, Z for
+          relabelling files according to the SELinux database. This is
+          useful to apply SELinux labels to specific files in /sys,
+          among other things.
+
+        * Output of SysV services is now forwarded to both the console
+          and the journal by default, not only just the console.
+
+        * New man pages for all APIs from libsystemd-login.
+
+        * The build tree got reorganized and a the build system is a
+          lot more modular allowing embedded setups to specifically
+          select the components of systemd they are interested in.
+
+        * Support for Linux systems lacking the kernel VT subsystem is
+          restored.
+
+        * configure's --with-rootdir= got renamed to
+          --with-rootprefix= to follow the naming used by udev and
+          kmod
+
+        * Unless specified otherwise we'll now install to /usr instead
+          of /usr/local by default.
+
+        * Processes with '@' in argv[0][0] are now excluded from the
+          final shut-down killing spree, following the logic explained
+          in:
+          http://www.freedesktop.org/wiki/Software/systemd/RootStorageDaemons
+
+        * All processes remaining in a service cgroup when we enter
+          the START or START_PRE states are now killed with
+          SIGKILL. That means it is no longer possible to spawn
+          background processes from ExecStart= lines (which was never
+          supported anyway, and bad style).
+
+        * New PropagateReloadTo=/PropagateReloadFrom= options to bind
+          reloading of units together.
+
+        Contributions from: Bill Nottingham, Daniel Walsh, Dave
+        Reisner, Dexter Morgan, Gregs Gregs, Jonathan Nieder, Kay
+        Sievers, Lennart Poettering, Michael Biebl, Michal Schmidt,
+        MichaÅ‚ Górny, Ran Benita, Thomas Jarosch, Tim Waugh, Tollef
+        Fog Heen, Tom Gundersen, Zbigniew JÄ™drzejewski-Szmek
diff --git a/README b/README
index 38459c6b226141408e3b7d2d33c32324dd42c822..6b0eb51ecd7c0bfa107a4f44cce04f46077b0ed6 100644 (file)
--- a/README
+++ b/README
-udev - Linux userspace device management
-
-Integrating udev in the system has complex dependencies and may differ from
-distribution to distribution. A system may not be able to boot up or work
-reliably without a properly installed udev version. The upstream udev project
-does not recommend replacing a distro's udev installation with the upstream
-version.
-
-The upstream udev project's set of default rules may require a most recent
-kernel release to work properly.
-
-Tools and rules shipped by udev are not public API and may change at any time.
-Never call any private tool in /usr/lib/udev from any external application; it
-might just go away in the next release. Access to udev information is only offered
-by udevadm and libudev. Tools and rules in /usr/lib/udev and the entire contents
-of the /run/udev directory are private to udev and do change whenever needed.
-
-Requirements:
-  - Version 2.6.34 of the Linux kernel with sysfs, procfs, signalfd, inotify,
-    unix domain sockets, networking and hotplug enabled
-
-  - Some architectures might need a later kernel, that supports accept4(),
-    or need to backport the accept4() syscall wiring in the kernel.
-
-  - These options are required:
-      CONFIG_DEVTMPFS=y
-      CONFIG_HOTPLUG=y
-      CONFIG_INOTIFY_USER=y
-      CONFIG_NET=y
-      CONFIG_PROC_FS=y
-      CONFIG_SIGNALFD=y
-      CONFIG_SYSFS=y
-      CONFIG_SYSFS_DEPRECATED*=n
-      CONFIG_UEVENT_HELPER_PATH=""
-
-  - These options might be needed:
-      CONFIG_BLK_DEV_BSG=y (SCSI devices)
-      CONFIG_TMPFS_POSIX_ACL=y (user ACLs for device nodes)
-
-  - The /dev directory needs the 'devtmpfs' filesystem mounted.
-    Udev only manages the permissions and ownership of the
-    kernel-provided device nodes, and possibly creates additional symlinks.
-
-  - Udev requires /run to be writable, which is usually done by mounting a
-    'tmpfs' filesystem.
-
-  - This version of udev does not work properly with the CONFIG_SYSFS_DEPRECATED*
-    option enabled.
-
-  - The deprecated hotplug helper /sbin/hotplug should be disabled in the
-    kernel configuration, it is not needed today, and may render the system
-    unusable because the kernel may create too many processes in parallel
-    so that the system runs out-of-memory.
-
-  - The proc filesystem must be mounted on /proc, and the sysfs filesystem must
-    be mounted at /sys. No other locations are supported by a standard
-    udev installation.
-
-  - The default rule sset requires the following group names resolvable at udev startup:
-      disk, cdrom, floppy, tape, audio, video, lp, tty, dialout, and kmem.
-    Especially in LDAP setups, it is required that getgrnam() be able to resolve
-    these group names with only the rootfs mounted and while no network is
-    available.
-
-  - Some udev extras have external dependencies like:
-      libglib2, usbutils, pciutils, and gperf.
-    All these extras can be disabled with configure options.
-
-Setup:
-  - The udev daemon should be started to handle device events sent by the kernel.
-    During bootup, the events for already existing devices can be replayed, so
-    that they are configured by udev. The systemd service files contain the
-    needed commands to start the udev daemon and the coldplug sequence.
-
-  - Restarting the daemon never applies any rules to existing devices.
-
-  - New/changed rule files are picked up automatically; there is usually no
-    daemon restart or signal needed.
-
-Operation:
-  - Based on events the kernel sends out on device creation/removal, udev
-    creates/removes device nodes and symlinks in the /dev directory.
-
-  - All kernel events are matched against a set of specified rules, which
-    possibly hook into the event processing and load required kernel
-    modules to set up devices. For all devices, the kernel exports a major/minor
-    number; if needed, udev creates a device node with the default kernel
-    device name. If specified, udev applies permissions/ownership to the device
-    node, creates additional symlinks pointing to the node, and executes
-    programs to handle the device.
-
-  - The events udev handles, and the information udev merges into its device
-    database, can be accessed with libudev:
-      http://www.kernel.org/pub/linux/utils/kernel/hotplug/libudev/
-      http://www.kernel.org/pub/linux/utils/kernel/hotplug/gudev/
-
-For more details about udev and udev rules, see the udev man pages:
-      http://www.kernel.org/pub/linux/utils/kernel/hotplug/udev/
-
-Please direct any comment/question to the linux-hotplug mailing list at:
-  linux-hotplug@vger.kernel.org
+systemd System and Service Manager
+
+DETAILS:
+        http://0pointer.de/blog/projects/systemd.html
+
+WEB SITE:
+        http://www.freedesktop.org/wiki/Software/systemd
+
+GIT:
+        git://anongit.freedesktop.org/systemd/systemd
+        ssh://git.freedesktop.org/git/systemd/systemd
+
+GITWEB:
+        http://cgit.freedesktop.org/systemd/systemd
+
+MAILING LIST:
+        http://lists.freedesktop.org/mailman/listinfo/systemd-devel
+        http://lists.freedesktop.org/mailman/listinfo/systemd-commits
+
+IRC:
+        #systemd on irc.freenode.org
+
+BUG REPORTS:
+        https://bugs.freedesktop.org/enter_bug.cgi?product=systemd
+
+AUTHOR:
+        Lennart Poettering with major support from Kay Sievers
+
+LICENSE:
+        GPLv2+ for all code, except sd-daemon.[ch] and
+        sd-readahead.[ch] which are MIT
+
+REQUIREMENTS:
+        Linux kernel >= 2.6.39
+                with devtmpfs
+                with cgroups (but it's OK to disable all controllers)
+                optional but strongly recommended: autofs4, ipv6
+        libudev >= 172
+        dbus >= 1.4.0
+        libcap
+        PAM >= 1.1.2 (optional)
+        libcryptsetup (optional)
+        libaudit (optional)
+        libselinux (optional)
+        tcpwrappers (optional)
+
+        When you build from git you need the following additional dependencies:
+
+        docbook-xsl
+        xsltproc
+        automake
+        autoconf
+        libtool
+        gperf
+        make, gcc, and similar tools
+
+        During runtime you need the following dependencies:
+
+        util-linux > v2.18 (requires fsck -l, agetty -s)
+        sulogin (from sysvinit-tools, optional but recommended)
+        plymouth (optional)
+        dracut (optional)
+
+        When systemd-hostnamed is used it is strongly recommended to
+        install nss-myhostname to ensure that in a world of
+        dynamically changing hostnames the hostname stays resolveable
+        under all circumstances. In fact, systemd-hostnamed will warn
+        if nss-myhostname is not installed. Packagers are encouraged to
+        add a dependency on nss-myhostname to the package that
+        includes systemd-hostnamed.
+
+        Note that D-Bus can link against libsystemd-login.so, which
+        results in a cyclic build dependency. To accomodate for this
+        please build D-Bus without systemd first, then build systemd,
+        then rebuild D-Bus with systemd support.
+
+WARNINGS:
+        systemd will warn you during boot if /etc/mtab is not a
+        symlink to /proc/mounts. Please ensure that /etc/mtab is a
+        proper symlink.
+
+        systemd will warn you during boot if /usr is on a different
+        file system than /. While in systemd itself very little will
+        break if /usr is on a separate partition many of its
+        dependencies very likely will break sooner or later in one
+        form or another. For example udev rules tend to refer to
+        binaries in /usr, binaries that link to libraries in /usr or
+        binaries that refer to data files in /usr. Since these
+        breakages are not always directly visible systemd will warn
+        about this, since this kind of file system setup is not really
+        supported anymore by the basic set of Linux OS components.
+
+        For more information on this issue consult
+        http://freedesktop.org/wiki/Software/systemd/separate-usr-is-broken
+
+ENGINEERING AND CONSULTING SERVICES:
+        ProFUSION <http://profusion.mobi> offers professional
+        engineering and consulting services for systemd for embedded
+        and other use. Please contact Gustavo Barbieri
+        <barbieri@profusion.mobi> for more information.
+
+        Disclaimer: This notice is not a recommendation or official
+        endorsement. However, ProFUSION's upstream work has been very
+        beneficial for the systemd project.
diff --git a/TODO b/TODO
index 8b8b9c8f8c12c261cf3350b480fccf0bcc413929..4f3b157040c0515152fe1ea979f3c7151241fdc3 100644 (file)
--- a/TODO
+++ b/TODO
- - find a way to tell udev to not cancel firmware
-   requests in initramfs
+Bugfixes:
 
- - scsi_id -> sg3_utils?
+* swap units that are activated by one name but shown in the kernel under another are semi-broken
 
- - make gtk-doc optional like kmod
+* make anaconda write timeout=0 for encrypted devices
 
- - move /usr/lib/udev/devices/ to tmpfiles
+* make sure timeouts are applied to Type=oneshot services.
 
- - trigger --subsystem-match=usb/usb_device
+* Dangling symlinks of .automount unit files in .wants/ directories, set up
+  automount points even when the original .automount file did not exist
+  anymore. Only the .mount unit was still around.
 
- - kill rules_generator
+* make polkit checks async
 
- - have a $attrs{} ?
+* properly handle .mount unit state tracking when two mount points are stacked one on top of another on the exact same mount point.
 
- - remove RUN+="socket:"
+Features:
 
- - libudev.so.1
-     - symbol versioning
-     - return object with *_unref()
-     - udev_monitor_from_socket()
-     - udev_queue_get_failed_list_entry()
+* allow configuration of console width/height in vconsole.conf
+
+* PrivateTmp should apply to both /tmp and /var/tmp
+
+* fstab should take priority over units in /usr
+
+* cleanup syslog 'priority' vs. 'level' wording
+
+* journal: if mmap() fails for mapping window try to unmap a a few older maps
+
+* add flag file for shutdownd so that clients can check whether a shutdown is queued
+
+* dbus upstream still refers to dbus.target and shouldn't
+
+* when a service has the same env var set twice we actually store it twice and return that in systemctl show -p... We should only show the last setting
+
+* add man page documenting all kernel cmdline options, including stuff like fsck.mode=
+
+* show getty in container mode, not sulogin
+
+* support container_ttys=
+
+* journald: make configurable "store-on-var", "store-on-run", "dont-store", "auto"
+  (store-persistent, store-volatile?)
+
+* Add ConditionReadWriteFileSystem= so that systemd-sysctl doesn't get executed when /proc/sys is read-only
+
+* unset container= and container_uuid= for child processes
+
+* when bind mounting /etc/machine-id, do so from /run/machine-id
+
+* introduce mix of BindTo and Requisite
+
+* journalctl: show multiline log messages sanely, expand tabs, and show all valid utf8 messages
+
+* introduce NeedsMounts= or so to create .mount dependencies automatically for a specific path
+
+* add DeleteSocketsOnStop=yes|no option to socket units
+
+* add shutdown inhibit API for usage by libvirt and friends
+
+* journal: store euid in journal if it differs from uid
+
+* support chrony in addition to ntpd in timedated
+
+* document crypttab(5)
+
+* There's currently no way to cancel fsck (used to be possible via C-c or c on the console)
+
+* hook up /dev/watchdog with main event loop for embedded, server uses
+
+* when dumping cgroup contents, include main/control PID of a service, explicitly
+
+* keep an eye on https://bugzilla.gnome.org/show_bug.cgi?id=670100
+
+* D-Bus: always pass cred data along each message
+
+* journal: allow turning off logging entirely
+
+* journal: sanely deal with entries which are larger than the individual file size, but where the componets would fit
+
+* add command to systemctl to plot dependency graph as tree (see rhbz 795365)
+
+* make logind reserve tty10 or so for text logins, so that gdm never picks it up
+
+* add option to sockets to avoid activation. Instead just drop packets/connections, see http://cyberelk.net/tim/2012/02/15/portreserve-systemd-solution/
+
+* isolate for getty is still broken, due to logind
+
+* default unix qlen is too small (10). bump sysctl? add sockopt?
+
+* support units generated by a generator and placed in /run/systemd/system/; the directory is
+  currently ignored because it is empty before the generatores are executed
+
+* Possibly, detect whether SysV init scripts can do reloading by looking for "echo Usage:" lines
+
+* figure out whether we should leave dbus around during shutdown
+
+* add interface to allow immediate rotation of the journal, and even flushing.
+
+* dbus: in fedora, make the machine a symlink to /etc/machine-id
+
+* journald: reuse XZ context
+
+* logind: add equivalent to sd_pid_get_owner_uid() to the D-Bus API
+
+* write RPM spec macros for presets
+
+* journal: write man pages for API
+
+* journal: OR matches are borked
+
+* journal: extend hash tables as we go
+
+* journal: API for looking for retrieving "all values of this field"
+
+* journal: deal nicely with byte-by-byte copied files, especially regards header
+
+* journal: local deserializer of export mode, http server
+
+* journal: message catalog
+
+* journal: forward-secure signatures
+
+* document the exit codes when services fail before they are exec()ed
+
+* rework namespace support, don't use pivot_root, and mount things after creating the namespace, not before
+
+* systemctl journal command
+
+* journalctl: --cursor support, priority filtering
+
+* systemctl status: show coredumps
+
+* systemctl status: show whether journal was rotated since service started
+
+* save coredump in Windows/Mozilla minidump format
+
+* support crash reporting operation modes (https://live.gnome.org/GnomeOS/Design/Whiteboards/ProblemReporting)
+
+* journal: allow per-entry control on /var vs. /run (think incognito browser mode)
+
+* clean up session cgroups that remain after logout (think sshd), but eventually run empty
+
+* support "systemctl stop foobar@.service" to stop all units matching a certain template
+
+* move to LGPL2+
+
+* logind: allow showing logout dialog from system
+
+* document that %% can be used to write % in a string that is specifier extended
+
+* when an instanced service exits, remove its parent cgroup too if possible.
+
+* Make libselinux, libattr, libcap, libdl dependencies only of the tools which actually need them.
+
+* as Tom Gundersen pointed out there's a always a dep loop if people use crypto file systems with random keys
+
+* unset container=, container_uuid= in PID1?
+
+* automatically escape unit names passed on the service (i.e. think "systemctl start serial-getty.service@serial/by-path/jshdfjsdfhkjh" being automatically escaped as necessary.
+
+* if we can not get user quota for tmpfs, mount a separate tmpfs instance
+  for every user in /run/user/$USER with a configured maximum size
+
+* default to actual 32bit PIDs, via /proc/sys/kernel/pid_max
+
+* add an option to make mounts private/shareable and so on, enable this for root by default
+
+* be able to specify a forced restart of service A where service B depends on, in case B
+  needs to be auto-respawned?
+
+* Something is wrong with symlink handling of "autovt@.service" in "systemctl list-unit-files"
+
+* when a bus name of a service disappears from the bus make sure to queue further activation requests
+
+* something like ConditionExec= or ExecStartPre= without failure state
+
+* tmpfiles: apply "x" on "D" too (see patch from William Douglas)
+
+* don't set $HOME in services unless requested
+
+* hide PAM/TCPWrap options in fragment parser when compile time disabled
+
+* when we automatically restart a service, ensure we restart its rdeps, too.
+
+* allow Type=simple with PIDFile=
+  https://bugzilla.redhat.com/show_bug.cgi?id=723942
+
+* move PAM code into its own binary
+
+* warn if the user stops a service but not its associated socket
+
+* logind: spawn user@..service on login
+
+* logind: non-local X11 server handling
+
+* implement Register= switch in .socket units to enable registration
+  in Avahi, RPC and other socket registration services.
+
+* make sure systemd-ask-password-wall does not shutdown systemd-ask-password-console too early
+
+* readahead: use BTRFS_IOC_DEFRAG_RANGE instead of BTRFS_IOC_DEFRAG ioctl, with START_IO
+
+* readahead: check whether a btrfs volume includes ssd by checking mount flag "ssd"
+
+* support sd_notify() style notification when reload begins (RELOADING=1), reload is finished (READY=1), and add ReloadSignal= then to use in combination
+
+* support sd_notify() style notification when shutting down, to make auto-exit bus services work (STOPPING=1)
+
+* verify that the AF_UNIX sockets of a service in the fs still exist
+  when we start a service in order to avoid confusion when a user
+  assumes starting a service is enough to make it accessible
+
+* Make it possible to set the keymap independently from the font on
+  the kernel cmdline. Right now setting one resets also the other.
+
+* move nss-myhostname into systemd
+
+* and a dbus call to generate target from current state
+
+* drop /.readahead on bigger upgrades with yum
+
+* add inode nr check to readahead to suppress preloading changed files
+
+* add support for /bin/mount -s
+
+* GC unreferenced jobs (such as .device jobs)
+
+* when failing to start a service due to ratelimiting, try again later, if restart=always is set
+
+* write blog stories about:
+  - enabling dbus services
+  - status update
+  - how to make changes to sysctl and sysfs attributes
+  - remote access
+  - how to pass throw-away units to systemd, or dynamically change properties of existing units
+  - how to integrate cgconfig and suchlike with systemd
+  - resource control in systemd
+
+* allow port=0 in .socket units
+
+* move readahead files into /var, look for them with .path units
+
+* teach dbus to activate all services it finds in /etc/systemd/services/org-*.service
+
+* support systemd.mask= on the kernel command line.
+
+* when key file cannot be found, read it from kbd in cryptsetup
+
+* reuse mkdtemp namespace dirs in /tmp?
+
+* recreate systemd's D-Bus private socket file on SIGUSR2
+
+* Support --test based on current system state
+
+* investigate whether the gnome pty helper should be moved into systemd, to provide cgroup support.
+
+* maybe introduce ExecRestartPre=
+
+* configurable jitter for timer events
+
+* timer events with system resume
+
+* timer events on calendar time
+
+* dot output for --test showing the 'initial transaction'
+
+* calendar time support in timer, iCalendar semantics for the timer stuff (RFC2445)
+    http://git.kernel.org/?p=linux/kernel/git/torvalds/linux-2.6.git;a=commit;h=99ee5315dac6211e972fa3f23bcc9a0343ff58c4
+
+* implicitly import "defaults" settings file into all types
+* exec settings override
+* writable cgroups dbus properties for live changes
+
+* read config fragments for all units from /lib/systemd/system/foobar.service.d/ to override/extend specific settings
+
+* port over to LISTEN_FDS/LISTEN_PID:
+   - rpcbind (/var/run/rpcbind.sock!) HAVEPATCH
+   - cups     HAVEPATCH
+   - postfix, saslauthd
+   - apache/samba
+   - libvirtd (/var/run/libvirt/libvirt-sock-ro)
+   - bluetoothd (/var/run/sdp! @/org/bluez/audio!)
+   - distccd
+
+* auditd service files
+
+* fingerprint.target, wireless.target, gps.target, netdevice.target
+
+* io priority during initialization
+
+* systemctl list-jobs - show dependencies
+
+* add systemctl switch to dump transaction without executing it
+
+* suspend, resume support?
+
+* drop cap bounding set in readahead and other services
+
+External:
+
+* dbus:
+   - get process transport into dbus for systemctl -P/-H (PENDING)
+   - dbus --user
+   - natively watch for dbus-*.service symlinks (PENDING)
+   - allow specification of socket mode/umask when allocating DBusServer
+   - allow disabling of fd passing when connecting a AF_UNIX connection
+   - allow disabling of UID passing for AUTH EXTERNAL
+
+* systemd --user
+    PR_SET_CHILD_REAPER patch: https://lkml.org/lkml/2011/7/28/426
+    (patch in linux-next, on the way to the next kernel)
+
+* fix alsa mixer restore to not print error when no config is stored
+
+* gnome-shell python script/glxinfo/is-accelerated must die
+
+* make cryptsetup lower --iter-time
+
+* patch kernel for xattr support in /dev, /proc/, /sys and /sys/fs/cgroup?
+
+* NTP: the kernel's 11-minutes-mode syncs the system time to the RTC, but only
+  in an ~30 minutes window. It does not adjust larger differences. Find a way
+  to tell the kernel, to always do a full time sync when the RTC is in UTC and
+  we are in 11-minutes-mode. When we trust the system time to NTP we also want
+  the RTC to sync up.
+
+* patch kernel for cpu feature modalias for autoloading aes/kvm/...
+    (patches in linux-next, on the way to the next kernel)
+
+* kernel: add device_type = "fb", "fbcon" to class "graphics"
+
+Regularly:
+
+* look for close() vs. close_nointr() vs. close_nointr_nofail()
+
+* check for strerror(r) instead of strerror(-r)
+
+* Use PR_SET_PROCTITLE_AREA if it becomes available in the kernel
+
+* %m in printf() instead of strerror();
+
+* pahole
+
+* set_put(), hashmap_put() return values check. i.e. == 0 doesn't free()!
index 55ee03afd14b562438d5f67030d7e336dd7a36a3..9ca53772a4dd8cebb92712635c1cc481f0701274 100755 (executable)
@@ -1,44 +1,56 @@
-#!/bin/sh -e
+#!/bin/bash
+
+#  This file is part of systemd.
+#
+#  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 <http://www.gnu.org/licenses/>.
 
 if [ -f .git/hooks/pre-commit.sample -a ! -f .git/hooks/pre-commit ] ; then
-        cp -p .git/hooks/pre-commit.sample .git/hooks/pre-commit && \
-        chmod +x .git/hooks/pre-commit && \
-        echo "Activated pre-commit hook."
+    cp -p .git/hooks/pre-commit.sample .git/hooks/pre-commit && \
+    chmod +x .git/hooks/pre-commit && \
+    echo "Activated pre-commit hook."
 fi
 
-gtkdocize
-autoreconf --install --symlink
+intltoolize --force --automake
+autoreconf --force --install --symlink
 
 libdir() {
-        echo $(cd $1/$(gcc -print-multi-os-directory); pwd)
+    echo $(cd $1/$(gcc -print-multi-os-directory); pwd)
 }
 
-args="$args \
---prefix=/usr \
+args="\
 --sysconfdir=/etc \
+--localstatedir=/var \
 --libdir=$(libdir /usr/lib) \
---with-selinux \
---enable-gtk-doc"
+--libexecdir=/usr/lib"
 
-if [ -L /bin ]; then
-args="$args \
---libexecdir=/usr/lib \
---with-systemdsystemunitdir=/usr/lib/systemd/system \
-"
-else
+if [ ! -L /bin ]; then
 args="$args \
 --with-rootprefix= \
----with-rootlibdir=$(libdir /lib) \
---bindir=/sbin \
---libexecdir=/lib \
---with-systemdsystemunitdir=/lib/systemd/system \
+--with-rootlibdir=$(libdir /lib) \
 "
 fi
 
-echo
-echo "----------------------------------------------------------------"
-echo "Initialized build system. For a common configuration please run:"
-echo "----------------------------------------------------------------"
-echo
-echo "./configure CFLAGS='-g -O1' $args"
-echo
+if [ "x$1" != "xc" ]; then
+    echo
+    echo "----------------------------------------------------------------"
+    echo "Initialized build system. For a common configuration please run:"
+    echo "----------------------------------------------------------------"
+    echo
+    echo "./configure CFLAGS='-g -O0' $args"
+    echo
+else
+    echo ./configure CFLAGS='-g -O0' $args
+    ./configure CFLAGS='-g -O0' $args
+    make clean
+fi
index b31b62f28915e17b3fa4da99e35e21daf1e5c64d..9a9a7892355442b040c3cdab23a43795aaf52ab0 100644 (file)
-AC_PREREQ(2.60)
-AC_INIT([udev],
-       [182],
-       [linux-hotplug@vger.kernel.org],
-       [udev],
-       [http://www.kernel.org/pub/linux/utils/kernel/hotplug/udev.html])
-AC_CONFIG_SRCDIR([src/udevd.c])
-AC_CONFIG_AUX_DIR([build-aux])
-AM_INIT_AUTOMAKE([check-news foreign 1.11 -Wall -Wno-portability silent-rules tar-pax no-dist-gzip dist-xz subdir-objects])
+#  This file is part 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 <http://www.gnu.org/licenses/>.
+
+AC_PREREQ(2.63)
+
+AC_INIT([systemd],[44],[systemd-devel@lists.freedesktop.org])
+AC_CONFIG_SRCDIR([src/main.c])
+AC_CONFIG_MACRO_DIR([m4])
+AC_CONFIG_HEADERS([config.h])
 AC_USE_SYSTEM_EXTENSIONS
 AC_SYS_LARGEFILE
-AC_CONFIG_MACRO_DIR([m4])
+AC_PREFIX_DEFAULT([/usr])
+AM_INIT_AUTOMAKE([foreign 1.11 -Wall -Wno-portability silent-rules tar-pax no-dist-gzip dist-xz subdir-objects check-news])
+
+AC_SUBST(PACKAGE_URL, [http://www.freedesktop.org/wiki/Software/systemd])
+
+AC_CANONICAL_HOST
+AC_DEFINE_UNQUOTED([CANONICAL_HOST], "$host", [Canonical host string.])
+AS_IF([test "x$host_cpu" = "xmips" || test "x$host_cpu" = "xmipsel" ||
+       test "x$host_cpu" = "xmips64" || test "x$host_cpu" = "xmips64el"],
+      [AC_DEFINE(ARCH_MIPS, [], [Whether on mips arch])])
+
 AM_SILENT_RULES([yes])
-LT_INIT([disable-static])
-AC_PROG_AWK
-AC_PROG_SED
+
+# i18n stuff for the PolicyKit policy files
+IT_PROG_INTLTOOL([0.40.0])
+
+GETTEXT_PACKAGE=systemd
+AC_SUBST(GETTEXT_PACKAGE)
+
 AC_PROG_MKDIR_P
-GTK_DOC_CHECK(1.10)
-AC_PREFIX_DEFAULT([/usr])
+AC_PROG_LN_S
+AC_PROG_SED
+AC_PROG_GREP
+AC_PROG_AWK
+
+AC_PROG_CC
+AC_PROG_CC_C99
+AM_PROG_CC_C_O
+AC_PROG_GCC_TRADITIONAL
+
+AC_CHECK_TOOL(OBJCOPY, objcopy)
+AC_CHECK_TOOL(STRINGS, strings)
+AC_CHECK_TOOL(GPERF, gperf)
+if test -z "$GPERF" ; then
+        AC_MSG_ERROR([*** gperf not found])
+fi
+
+CC_CHECK_FLAGS_APPEND([with_cflags], [CFLAGS], [\
+        -pipe \
+        -Wall \
+        -W \
+        -Wextra \
+        -Wno-inline \
+        -Wvla \
+        -Wundef \
+        -Wformat=2 \
+        -Wlogical-op \
+        -Wsign-compare \
+        -Wformat-security \
+        -Wmissing-include-dirs \
+        -Wformat-nonliteral \
+        -Wold-style-definition \
+        -Wpointer-arith \
+        -Winit-self \
+        -Wdeclaration-after-statement \
+        -Wfloat-equal \
+        -Wmissing-prototypes \
+        -Wstrict-prototypes \
+        -Wredundant-decls \
+        -Wmissing-declarations \
+        -Wmissing-noreturn \
+        -Wshadow \
+        -Wendif-labels \
+        -Wcast-align \
+        -Wstrict-aliasing=2 \
+        -Wwrite-strings \
+        -Wno-long-long \
+        -Wno-overlength-strings \
+        -Wno-unused-parameter \
+        -Wno-missing-field-initializers \
+        -Wno-unused-result \
+        -Werror=overflow \
+        -Wp,-D_FORTIFY_SOURCE=2 \
+        -ffast-math \
+        -fno-common \
+        -fdiagnostics-show-option \
+        -fno-strict-aliasing \
+        -fvisibility=hidden \
+        -ffunction-sections \
+        -fdata-sections])
+AC_SUBST([WARNINGFLAGS], $with_cflags)
+
+CC_CHECK_FLAGS_APPEND([with_ldflags], [LDFLAGS], [\
+        -Wl,--as-needed \
+        -Wl,--gc-sections])
+AC_SUBST([GCLDFLAGS], $with_ldflags)
+
+LT_PREREQ(2.2)
+LT_INIT
+
+AC_SEARCH_LIBS([clock_gettime], [rt], [], [AC_MSG_ERROR([*** POSIX RT library not found])])
+AC_SEARCH_LIBS([dlsym], [dl], [], [AC_MSG_ERROR([*** Dynamic linking loader library not found])])
+
+save_LIBS="$LIBS"
+LIBS=
+AC_SEARCH_LIBS([cap_init], [cap], [], [AC_MSG_ERROR([*** POSIX caps library not found])])
+AC_CHECK_HEADERS([sys/capability.h], [], [AC_MSG_ERROR([*** POSIX caps headers not found])])
+CAP_LIBS="$LIBS"
+LIBS="$save_LIBS"
+AC_SUBST(CAP_LIBS)
+
+# This makes sure pkg.m4 is available.
+m4_pattern_forbid([^_?PKG_[A-Z_]+$],[*** pkg.m4 missing, please install pkg-config])
+
+PKG_CHECK_MODULES(UDEV, [ libudev >= 172 ])
+PKG_CHECK_MODULES(DBUS, [ dbus-1 >= 1.3.2 ])
+PKG_CHECK_MODULES(KMOD, [ libkmod >= 5 ])
+
+have_ima=yes
+AC_ARG_ENABLE([ima], AS_HELP_STRING([--disable-ima],[Disable optional IMA support]),
+                [case "${enableval}" in
+                        yes) have_ima=yes ;;
+                        no) have_ima=no ;;
+                        *) AC_MSG_ERROR(bad value ${enableval} for --disable-ima) ;;
+                esac],
+                [have_ima=yes])
+
+if test "x${have_ima}" != xno ; then
+        AC_DEFINE(HAVE_IMA, 1, [Define if IMA is available])
+fi
+
+have_selinux=no
+AC_ARG_ENABLE(selinux, AS_HELP_STRING([--disable-selinux], [Disable optional SELINUX support]))
+if test "x$enable_selinux" != "xno"; then
+        PKG_CHECK_MODULES(SELINUX, [ libselinux ],
+                [AC_DEFINE(HAVE_SELINUX, 1, [Define if SELinux is available]) have_selinux=yes], have_selinux=no)
+        if test "x$have_selinux" = xno -a "x$enable_selinux" = xyes; then
+                AC_MSG_ERROR([*** SELinux support requested but libraries not found])
+        fi
+fi
+AM_CONDITIONAL(HAVE_SELINUX, [test "$have_selinux" = "yes"])
+
+have_xz=no
+AC_ARG_ENABLE(xz, AS_HELP_STRING([--disable-xz], [Disable optional XZ support]))
+if test "x$enable_xz" != "xno"; then
+        PKG_CHECK_MODULES(XZ, [ liblzma ],
+                [AC_DEFINE(HAVE_XZ, 1, [Define if XZ is available]) have_xz=yes], have_xz=no)
+        if test "x$have_xz" = xno -a "x$enable_xz" = xyes; then
+                AC_MSG_ERROR([*** Xz support requested but libraries not found])
+        fi
+fi
+AM_CONDITIONAL(HAVE_XZ, [test "$have_xz" = "yes"])
+
+AC_ARG_ENABLE([tcpwrap],
+        AS_HELP_STRING([--disable-tcpwrap],[Disable optional TCP wrappers support]),
+                [case "${enableval}" in
+                        yes) have_tcpwrap=yes ;;
+                        no) have_tcpwrap=no ;;
+                        *) AC_MSG_ERROR(bad value ${enableval} for --disable-tcpwrap) ;;
+                esac],
+                [have_tcpwrap=auto])
+
+if test "x${have_tcpwrap}" != xno ; then
+        ACX_LIBWRAP
+        if test "x${LIBWRAP_LIBS}" = x ; then
+                if test "x$have_tcpwrap" = xyes ; then
+                        AC_MSG_ERROR([*** TCP wrappers support not found.])
+                fi
+                have_tcpwrap=no
+        else
+                have_tcpwrap=yes
+        fi
+else
+        LIBWRAP_LIBS=
+fi
+AC_SUBST(LIBWRAP_LIBS)
+
+AC_ARG_ENABLE([pam],
+        AS_HELP_STRING([--disable-pam],[Disable optional PAM support]),
+                [case "${enableval}" in
+                        yes) have_pam=yes ;;
+                        no) have_pam=no ;;
+                        *) AC_MSG_ERROR(bad value ${enableval} for --disable-pam) ;;
+                esac],
+                [have_pam=auto])
+
+if test "x${have_pam}" != xno ; then
+        AC_CHECK_HEADERS(
+                [security/pam_modules.h security/pam_modutil.h security/pam_ext.h],
+                [have_pam=yes],
+                [if test "x$have_pam" = xyes ; then
+                        AC_MSG_ERROR([*** PAM headers not found.])
+                fi])
+
+        AC_CHECK_LIB(
+                [pam],
+                [pam_syslog],
+                [have_pam=yes],
+                [if test "x$have_pam" = xyes ; then
+                        AC_MSG_ERROR([*** libpam not found.])
+                fi])
+
+        if test "x$have_pam" = xyes ; then
+                PAM_LIBS="-lpam -lpam_misc"
+                AC_DEFINE(HAVE_PAM, 1, [PAM available])
+        else
+                have_pam=no
+        fi
+else
+        PAM_LIBS=
+fi
+AC_SUBST(PAM_LIBS)
+AM_CONDITIONAL([HAVE_PAM], [test "x$have_pam" != xno])
+
+AC_ARG_ENABLE([acl],
+        AS_HELP_STRING([--disable-acl],[Disable optional ACL support]),
+                [case "${enableval}" in
+                        yes) have_acl=yes ;;
+                        no) have_acl=no ;;
+                        *) AC_MSG_ERROR(bad value ${enableval} for --disable-acl) ;;
+                esac],
+                [have_acl=auto])
+
+if test "x${have_acl}" != xno ; then
+        AC_CHECK_HEADERS(
+                [sys/acl.h acl/libacl.h],
+                [have_acl=yes],
+                [if test "x$have_acl" = xyes ; then
+                        AC_MSG_ERROR([*** ACL headers not found.])
+                fi])
+
+        AC_CHECK_LIB(
+                [acl],
+                [acl_get_file],
+                [have_acl=yes],
+                [if test "x$have_acl" = xyes ; then
+                        AC_MSG_ERROR([*** libacl not found.])
+                fi])
+
+        if test "x$have_acl" = xyes ; then
+                ACL_LIBS="-lacl"
+                AC_DEFINE(HAVE_ACL, 1, [ACL available])
+        else
+                have_acl=no
+        fi
+else
+        ACL_LIBS=
+fi
+AC_SUBST(ACL_LIBS)
+AM_CONDITIONAL([HAVE_ACL], [test "x$have_acl" != xno])
+
+AC_ARG_ENABLE([audit],
+        AS_HELP_STRING([--disable-audit],[Disable optional AUDIT support]),
+                [case "${enableval}" in
+                        yes) have_audit=yes ;;
+                        no) have_audit=no ;;
+                        *) AC_MSG_ERROR(bad value ${enableval} for --disable-audit) ;;
+                esac],
+                [have_audit=auto])
+
+if test "x${have_audit}" != xno ; then
+        AC_CHECK_HEADERS(
+                [libaudit.h],
+                [have_audit=yes],
+                [if test "x$have_audit" = xyes ; then
+                        AC_MSG_ERROR([*** AUDIT headers not found.])
+                fi])
+
+        AC_CHECK_LIB(
+                [audit],
+                [audit_open],
+                [have_audit=yes],
+                [if test "x$have_audit" = xyes ; then
+                        AC_MSG_ERROR([*** libaudit not found.])
+                fi])
+
+        if test "x$have_audit" = xyes ; then
+                AUDIT_LIBS="-laudit"
+                AC_DEFINE(HAVE_AUDIT, 1, [AUDIT available])
+        else
+                have_audit=no
+        fi
+else
+        AUDIT_LIBS=
+fi
+AC_SUBST(AUDIT_LIBS)
+
+have_libcryptsetup=no
+AC_ARG_ENABLE(libcryptsetup, AS_HELP_STRING([--disable-libcryptsetup], [disable libcryptsetup tools]))
+if test "x$enable_libcryptsetup" != "xno"; then
+        PKG_CHECK_MODULES(LIBCRYPTSETUP, [ libcryptsetup ],
+                [AC_DEFINE(HAVE_LIBCRYPTSETUP, 1, [Define if libcryptsetup is available]) have_libcryptsetup=yes], have_libcryptsetup=no)
+        if test "x$have_libcryptsetup" = xno -a "x$enable_libcryptsetup" = xyes; then
+                AC_MSG_ERROR([*** libcryptsetup support requested but libraries not found])
+        fi
+fi
+AM_CONDITIONAL(HAVE_LIBCRYPTSETUP, [test "$have_libcryptsetup" = "yes"])
+
+have_binfmt=no
+AC_ARG_ENABLE(binfmt, AS_HELP_STRING([--disable-binfmt], [disable binfmt tool]))
+if test "x$enable_binfmt" != "xno"; then
+        have_binfmt=yes
+fi
+AM_CONDITIONAL(ENABLE_BINFMT, [test "$have_binfmt" = "yes"])
+
+have_vconsole=no
+AC_ARG_ENABLE(vconsole, AS_HELP_STRING([--disable-vconsole], [disable vconsole tool]))
+if test "x$enable_vconsole" != "xno"; then
+        have_vconsole=yes
+fi
+AM_CONDITIONAL(ENABLE_VCONSOLE, [test "$have_vconsole" = "yes"])
+
+have_readahead=no
+AC_ARG_ENABLE(readahead, AS_HELP_STRING([--disable-readahead], [disable readahead tools]))
+if test "x$enable_readahead" != "xno"; then
+        have_readahead=yes
+fi
+AM_CONDITIONAL(ENABLE_READAHEAD, [test "$have_readahead" = "yes"])
+
+have_quotacheck=no
+AC_ARG_ENABLE(quotacheck, AS_HELP_STRING([--disable-quotacheck], [disable quotacheck tools]))
+if test "x$enable_quotacheck" != "xno"; then
+        have_quotacheck=yes
+fi
+AM_CONDITIONAL(ENABLE_QUOTACHECK, [test "$have_quotacheck" = "yes"])
+
+have_randomseed=no
+AC_ARG_ENABLE(randomseed, AS_HELP_STRING([--disable-randomseed], [disable randomseed tools]))
+if test "x$enable_randomseed" != "xno"; then
+        have_randomseed=yes
+fi
+AM_CONDITIONAL(ENABLE_RANDOMSEED, [test "$have_randomseed" = "yes"])
+
+have_logind=no
+AC_ARG_ENABLE(logind, AS_HELP_STRING([--disable-logind], [disable login daemon]))
+if test "x$enable_logind" != "xno"; then
+        have_logind=yes
+fi
+AM_CONDITIONAL(ENABLE_LOGIND, [test "$have_logind" = "yes"])
+AS_IF([test "$have_logind" = "yes"], [ AC_DEFINE(HAVE_LOGIND, [1], [Logind support available]) ])
+
+have_hostnamed=no
+AC_ARG_ENABLE(hostnamed, AS_HELP_STRING([--disable-hostnamed], [disable hostname daemon]))
+if test "x$enable_hostnamed" != "xno"; then
+        have_hostnamed=yes
+fi
+AM_CONDITIONAL(ENABLE_HOSTNAMED, [test "$have_hostnamed" = "yes"])
+
+have_timedated=no
+AC_ARG_ENABLE(timedated, AS_HELP_STRING([--disable-timedated], [disable timedate daemon]))
+if test "x$enable_timedated" != "xno"; then
+        have_timedated=yes
+fi
+AM_CONDITIONAL(ENABLE_TIMEDATED, [test "$have_timedated" = "yes"])
+
+have_localed=no
+AC_ARG_ENABLE(localed, AS_HELP_STRING([--disable-localed], [disable locale daemon]))
+if test "x$enable_localed" != "xno"; then
+        have_localed=yes
+fi
+AM_CONDITIONAL(ENABLE_LOCALED, [test "$have_localed" = "yes"])
+
+have_coredump=no
+AC_ARG_ENABLE(coredump, AS_HELP_STRING([--disable-coredump], [disable coredump hook]))
+if test "x$enable_coredump" != "xno"; then
+        have_coredump=yes
+fi
+AM_CONDITIONAL(ENABLE_COREDUMP, [test "$have_coredump" = "yes"])
+
+have_manpages=no
+AC_ARG_ENABLE(manpages, AS_HELP_STRING([--disable-manpages], [disable manpages]))
+if test "x$enable_manpages" != "xno"; then
+        have_manpages=yes
+fi
+AM_CONDITIONAL(ENABLE_MANPAGES, [test "$have_manpages" = "yes"])
 
 AC_PATH_PROG([XSLTPROC], [xsltproc])
 AM_CONDITIONAL(HAVE_XSLTPROC, test x"$XSLTPROC" != x)
 
-AC_SEARCH_LIBS([clock_gettime], [rt], [], [AC_MSG_ERROR([POSIX RT library not found])])
+AC_PATH_PROG([M4], [m4])
+
+AC_ARG_WITH(distro, AS_HELP_STRING([--with-distro=DISTRO],[Specify the distribution to target: One of fedora, suse, debian, ubuntu, arch, gentoo, slackware, altlinux, mandriva, meego, mageia, angstrom or other]))
+if test "z$with_distro" = "z"; then
+        if test "$cross_compiling" = yes; then
+                AC_MSG_WARN([Target distribution cannot be reliably detected when cross-compiling. You should specify it with --with-distro (see $0 --help for recognized distros)])
+        else
+                with_distro=$($GREP '^ID=' /etc/os-release | $SED 's/ID=//');
+        fi
+        if test "z$with_distro" = "z"; then
+                with_distro=other
+        fi
+fi
+with_distro=`echo ${with_distro} | tr '[[:upper:]]' '[[:lower:]]' `
+AC_DEFINE_UNQUOTED(DISTRIBUTION, ["${with_distro}"], [Target Distribution])
 
-PKG_CHECK_MODULES(BLKID, blkid >= 2.20)
+# Location of the init scripts as mandated by LSB
+SYSTEM_SYSVINIT_PATH=/etc/init.d
+SYSTEM_SYSVRCND_PATH=/etc/rc.d
 
-PKG_CHECK_MODULES(KMOD, libkmod >= 5)
+M4_DEFINES=
+have_plymouth=no
+
+case $with_distro in
+        fedora)
+                SYSTEM_SYSVINIT_PATH=/etc/rc.d/init.d
+                AC_DEFINE(TARGET_FEDORA, [], [Target is Fedora/RHEL])
+                M4_DEFINES=-DTARGET_FEDORA=1
+                have_plymouth=yes
+                ;;
+        opensuse|suse)
+                SYSTEM_SYSVRCND_PATH=/etc/init.d
+                AC_DEFINE(TARGET_SUSE, [], [Target is openSUSE/SLE])
+                M4_DEFINES=-DTARGET_SUSE=1
+                have_plymouth=yes
+                ;;
+        debian)
+                SYSTEM_SYSVRCND_PATH=/etc
+                AC_DEFINE(TARGET_DEBIAN, [], [Target is Debian])
+                M4_DEFINES=-DTARGET_DEBIAN=1
+                ;;
+        ubuntu)
+                SYSTEM_SYSVRCND_PATH=/etc
+                AC_DEFINE(TARGET_UBUNTU, [], [Target is Ubuntu])
+                M4_DEFINES=-DTARGET_UBUNTU=1
+                ;;
+        arch)
+                SYSTEM_SYSVINIT_PATH=/etc/rc.d
+                SYSTEM_SYSVRCND_PATH=/etc
+                AC_DEFINE(TARGET_ARCH, [], [Target is ArchLinux])
+                M4_DEFINES=-DTARGET_ARCH=1
+                ;;
+        gentoo)
+                SYSTEM_SYSVINIT_PATH=
+                SYSTEM_SYSVRCND_PATH=
+                AC_DEFINE(TARGET_GENTOO, [], [Target is Gentoo])
+                M4_DEFINES=-DTARGET_GENTOO=1
+                ;;
+        slackware)
+                SYSTEM_SYSVINIT_PATH=/etc/rc.d/init.d
+                AC_DEFINE(TARGET_SLACKWARE, [], [Target is Slackware])
+                M4_DEFINES=-DTARGET_SLACKWARE=1
+                ;;
+        frugalware)
+                SYSTEM_SYSVINIT_PATH=/etc/rc.d
+                AC_DEFINE(TARGET_FRUGALWARE, [], [Target is Frugalware])
+                M4_DEFINES=-DTARGET_FRUGALWARE=1
+                have_plymouth=yes
+                ;;
+        altlinux)
+                SYSTEM_SYSVINIT_PATH=/etc/rc.d/init.d
+                AC_DEFINE(TARGET_ALTLINUX, [], [Target is ALTLinux])
+                M4_DEFINES=-DTARGET_ALTLINUX=1
+                have_plymouth=yes
+                ;;
+        mandriva)
+                SYSTEM_SYSVINIT_PATH=/etc/rc.d/init.d
+                AC_DEFINE(TARGET_MANDRIVA, [], [Target is Mandriva])
+                M4_DEFINES=-DTARGET_MANDRIVA=1
+                have_plymouth=yes
+                ;;
+        meego)
+                SYSTEM_SYSVINIT_PATH=
+                SYSTEM_SYSVRCND_PATH=
+                AC_DEFINE(TARGET_MEEGO, [], [Target is MeeGo])
+                M4_DEFINES=-DTARGET_MEEGO=1
+                ;;
+        angstrom)
+                SYSTEM_SYSVRCND_PATH=/etc
+                AC_DEFINE(TARGET_ANGSTROM, [], [Target is Ã…ngström])
+                M4_DEFINES=-DTARGET_ANGSTROM=1
+                ;;
+        mageia)
+                SYSTEM_SYSVINIT_PATH=/etc/rc.d/init.d
+                AC_DEFINE(TARGET_MAGEIA, [], [Target is Mageia])
+                M4_DISTRO_FLAG=-DTARGET_MAGEIA=1
+                have_plymouth=yes
+                ;;
+        other)
+                ;;
+        *)
+                AC_MSG_ERROR([Your distribution (${with_distro}) is not yet supported, SysV init scripts could not be found! (patches welcome); you can specify --with-distro=other to skip this check])
+                ;;
+esac
+
+AC_ARG_WITH([sysvinit-path],
+        [AS_HELP_STRING([--with-sysvinit-path=PATH],
+                [Specify the path to where the SysV init scripts are located @<:@default=based on distro@:>@])],
+        [SYSTEM_SYSVINIT_PATH="$withval"],
+        [])
+
+AC_ARG_WITH([sysvrcd-path],
+        [AS_HELP_STRING([--with-sysvrcd-path=PATH],
+                [Specify the path to the base directory for the SysV rcN.d directories @<:@default=based on distro@:>@])],
+        [SYSTEM_SYSVRCND_PATH="$withval"],
+        [])
+
+AC_SUBST(SYSTEM_SYSVINIT_PATH)
+AC_SUBST(SYSTEM_SYSVRCND_PATH)
+AC_SUBST(M4_DEFINES)
+
+if test "x${SYSTEM_SYSVINIT_PATH}" != "x" -a "x${SYSTEM_SYSVRCND_PATH}" != "x"; then
+        AC_DEFINE(HAVE_SYSV_COMPAT, [], [SysV init scripts and rcN.d links are supported.])
+        SYSTEM_SYSV_COMPAT="yes"
+        M4_DEFINES="$M4_DEFINES -DHAVE_SYSV_COMPAT"
+elif test "x${SYSTEM_SYSVINIT_PATH}" != "x" -o "x${SYSTEM_SYSVRCND_PATH}" != "x"; then
+        AC_MSG_ERROR([*** You need both --with-sysvinit-path=PATH and --with-sysvrcd-path=PATH to enable SysV compatibility support, or both empty to disable it.])
+else
+        SYSTEM_SYSV_COMPAT="no"
+fi
+
+AC_ARG_WITH([tty-gid],
+        [AS_HELP_STRING([--with-tty-gid=GID],
+                [Specify the numeric GID of the 'tty' group])],
+        [AC_DEFINE_UNQUOTED(TTY_GID, [$withval], [GID of the 'tty' group])],
+        [])
+
+AC_ARG_ENABLE(plymouth, AS_HELP_STRING([--enable-plymouth], [enable plymouth support]))
+if test -n "$enable_plymouth"; then
+        have_plymouth="$enable_plymouth"
+fi
+
+AM_CONDITIONAL(TARGET_FEDORA, test x"$with_distro" = xfedora)
+AM_CONDITIONAL(TARGET_SUSE, test x"$with_distro" = xsuse)
+AM_CONDITIONAL(TARGET_DEBIAN, test x"$with_distro" = xdebian)
+AM_CONDITIONAL(TARGET_UBUNTU, test x"$with_distro" = xubuntu)
+AM_CONDITIONAL(TARGET_DEBIAN_OR_UBUNTU, test x"$with_distro" = xdebian -o x"$with_distro" = xubuntu)
+AM_CONDITIONAL(TARGET_ARCH, test x"$with_distro" = xarch)
+AM_CONDITIONAL(TARGET_GENTOO, test x"$with_distro" = xgentoo)
+AM_CONDITIONAL(TARGET_SLACKWARE, test x"$with_distro" = xslackware)
+AM_CONDITIONAL(TARGET_FRUGALWARE, test x"$with_distro" = xfrugalware)
+AM_CONDITIONAL(TARGET_ALTLINUX, test x"$with_distro" = xaltlinux)
+AM_CONDITIONAL(TARGET_MANDRIVA, test x"$with_distro" = xmandriva)
+AM_CONDITIONAL(TARGET_MEEGO, test x"$with_distro" = xmeego)
+AM_CONDITIONAL(TARGET_ANGSTROM, test x"$with_distro" = xangstrom)
+AM_CONDITIONAL(TARGET_MAGEIA, test x"$with_distro" = xmageia)
+
+AM_CONDITIONAL(HAVE_PLYMOUTH, test "$have_plymouth" = "yes")
+AM_CONDITIONAL(HAVE_SYSV_COMPAT, test "$SYSTEM_SYSV_COMPAT" = "yes")
+
+AC_ARG_WITH([dbuspolicydir],
+        AS_HELP_STRING([--with-dbuspolicydir=DIR], [D-Bus policy directory]),
+        [],
+        [with_dbuspolicydir=`pkg-config --variable=sysconfdir dbus-1`/dbus-1/system.d])
+
+AC_ARG_WITH([dbussessionservicedir],
+        AS_HELP_STRING([--with-dbussessionservicedir=DIR], [D-Bus session service directory]),
+        [],
+        [with_dbussessionservicedir=`pkg-config --variable=session_bus_services_dir dbus-1`])
+
+AC_ARG_WITH([dbussystemservicedir],
+        AS_HELP_STRING([--with-dbussystemservicedir=DIR], [D-Bus system service directory]),
+        [],
+        [with_dbussystemservicedir=`pkg-config --variable=session_bus_services_dir dbus-1`/../system-services])
+
+AC_ARG_WITH([dbusinterfacedir],
+        AS_HELP_STRING([--with-dbusinterfacedir=DIR], [D-Bus interface directory]),
+        [],
+        [with_dbusinterfacedir=`pkg-config --variable=session_bus_services_dir dbus-1`/../interfaces])
+
+AC_ARG_WITH([udevrulesdir],
+        AS_HELP_STRING([--with-udevrulesdir=DIR], [Directory for udev rules]),
+        [],
+        [with_udevrulesdir=`pkg-config --variable=udevdir udev`/rules.d])
 
 AC_ARG_WITH([rootprefix],
-       AS_HELP_STRING([--with-rootprefix=DIR], [rootfs directory prefix for config files and kernel modules]),
-       [], [with_rootprefix=${ac_default_prefix}])
-AC_SUBST([rootprefix], [$with_rootprefix])
+        AS_HELP_STRING([--with-rootprefix=DIR], [rootfs directory prefix for config files and kernel modules]),
+        [], [with_rootprefix=${ac_default_prefix}])
 
 AC_ARG_WITH([rootlibdir],
-       AS_HELP_STRING([--with-rootlibdir=DIR], [rootfs directory to install shared libraries]),
-       [], [with_rootlibdir=$libdir])
-AC_SUBST([rootlib_execdir], [$with_rootlibdir])
-
-AC_ARG_WITH([selinux],
-       AS_HELP_STRING([--with-selinux], [enable SELinux support]),
-       [], [with_selinux=no])
-AS_IF([test "x$with_selinux" = "xyes"], [
-       LIBS_save=$LIBS
-       AC_CHECK_LIB(selinux, getprevcon,
-              [],
-              AC_MSG_ERROR([SELinux selected but libselinux not found]))
-       LIBS=$LIBS_save
-       SELINUX_LIBS="-lselinux -lsepol"
-       AC_DEFINE(WITH_SELINUX, [1] ,[SELinux support.])
-])
-AC_SUBST([SELINUX_LIBS])
-AM_CONDITIONAL(WITH_SELINUX, [test "x$with_selinux" = "xyes"])
-
-AC_ARG_ENABLE([debug],
-       AS_HELP_STRING([--enable-debug], [enable debug messages @<:@default=disabled@:>@]),
-       [], [enable_debug=no])
-AS_IF([test "x$enable_debug" = "xyes"], [ AC_DEFINE(ENABLE_DEBUG, [1], [Debug messages.]) ])
-
-AC_ARG_ENABLE([logging],
-       AS_HELP_STRING([--disable-logging], [disable system logging @<:@default=enabled@:>@]),
-       [], enable_logging=yes)
-AS_IF([test "x$enable_logging" = "xyes"], [ AC_DEFINE(ENABLE_LOGGING, [1], [System logging.]) ])
-
-AC_ARG_ENABLE([manpages],
-        AS_HELP_STRING([--disable-manpages], [disable man pages @<:@default=enabled@:>@]),
-        [], enable_manpages=yes)
-AM_CONDITIONAL([ENABLE_MANPAGES], [test "x$enable_manpages" = "xyes"])
-
-if test "x$cross_compiling" = "xno" ; then
-       AC_CHECK_FILES([/usr/share/pci.ids], [pciids=/usr/share/pci.ids])
-       AC_CHECK_FILES([/usr/share/hwdata/pci.ids], [pciids=/usr/share/hwdata/pci.ids])
-       AC_CHECK_FILES([/usr/share/misc/pci.ids], [pciids=/usr/share/misc/pci.ids])
-fi
-
-AC_ARG_WITH(usb-ids-path,
-       [AS_HELP_STRING([--with-usb-ids-path=DIR], [Path to usb.ids file])],
-       [USB_DATABASE=${withval}],
-       [if test -n "$usbids" ; then
-              USB_DATABASE="$usbids"
-       else
-              PKG_CHECK_MODULES(USBUTILS, usbutils >= 0.82)
-              AC_SUBST([USB_DATABASE], [$($PKG_CONFIG --variable=usbids usbutils)])
-       fi])
-AC_MSG_CHECKING([for USB database location])
-AC_MSG_RESULT([$USB_DATABASE])
-AC_SUBST(USB_DATABASE)
-
-AC_ARG_WITH(pci-ids-path,
-       [AS_HELP_STRING([--with-pci-ids-path=DIR], [Path to pci.ids file])],
-       [PCI_DATABASE=${withval}],
-       [if test -n "$pciids" ; then
-              PCI_DATABASE="$pciids"
-       else
-              AC_MSG_ERROR([pci.ids not found, try --with-pci-ids-path=])
-       fi])
-AC_MSG_CHECKING([for PCI database location])
-AC_MSG_RESULT([$PCI_DATABASE])
-AC_SUBST(PCI_DATABASE)
-
-AC_ARG_WITH(firmware-path,
-       AS_HELP_STRING([--with-firmware-path=DIR[[[:DIR[...]]]]],
-          [Firmware search path (default=ROOTPREFIX/lib/firmware/updates:ROOTPREFIX/lib/firmware)]),
-       [], [with_firmware_path="$rootprefix/lib/firmware/updates:$rootprefix/lib/firmware"])
-OLD_IFS=$IFS
-IFS=:
-for i in $with_firmware_path; do
-       if test "x${FIRMWARE_PATH}" = "x"; then
-              FIRMWARE_PATH="\\\"${i}/\\\""
-       else
-              FIRMWARE_PATH="${FIRMWARE_PATH}, \\\"${i}/\\\""
-       fi
-done
-IFS=$OLD_IFS
-AC_SUBST([FIRMWARE_PATH], [$FIRMWARE_PATH])
-
-AC_ARG_WITH([systemdsystemunitdir],
-       AS_HELP_STRING([--with-systemdsystemunitdir=DIR], [Directory for systemd service files]),
-       [], [with_systemdsystemunitdir=$($PKG_CONFIG --variable=systemdsystemunitdir systemd)])
-AS_IF([test "x$with_systemdsystemunitdir" != "xno"], [ AC_SUBST([systemdsystemunitdir], [$with_systemdsystemunitdir]) ])
-AM_CONDITIONAL(WITH_SYSTEMD, [test -n "$with_systemdsystemunitdir" -a "x$with_systemdsystemunitdir" != "xno" ])
-
-# ------------------------------------------------------------------------------
-# GUdev - libudev gobject interface
-# ------------------------------------------------------------------------------
-AC_ARG_ENABLE([gudev],
-       AS_HELP_STRING([--disable-gudev], [disable Gobject libudev support @<:@default=enabled@:>@]),
-       [], [enable_gudev=yes])
-AS_IF([test "x$enable_gudev" = "xyes"], [ PKG_CHECK_MODULES([GLIB], [glib-2.0 >= 2.22.0 gobject-2.0 >= 2.22.0]) ])
-
-AC_ARG_ENABLE([introspection],
-       AS_HELP_STRING([--disable-introspection], [disable GObject introspection @<:@default=enabled@:>@]),
-       [], [enable_introspection=yes])
-AS_IF([test "x$enable_introspection" = "xyes"], [
-       PKG_CHECK_MODULES([INTROSPECTION], [gobject-introspection-1.0 >= 0.6.2])
-       AC_DEFINE([ENABLE_INTROSPECTION], [1], [enable GObject introspection support])
-       AC_SUBST([G_IR_SCANNER], [$($PKG_CONFIG --variable=g_ir_scanner gobject-introspection-1.0)])
-       AC_SUBST([G_IR_COMPILER], [$($PKG_CONFIG --variable=g_ir_compiler gobject-introspection-1.0)])
-       AC_SUBST([G_IR_GENERATE], [$($PKG_CONFIG --variable=g_ir_generate gobject-introspection-1.0)])
-       AC_SUBST([GIRDIR], [$($PKG_CONFIG --define-variable=datadir=${datadir} --variable=girdir gobject-introspection-1.0)])
-       AC_SUBST([GIRTYPELIBDIR], [$($PKG_CONFIG --define-variable=libdir=${libdir} --variable=typelibdir gobject-introspection-1.0)])
-])
-AM_CONDITIONAL([ENABLE_INTROSPECTION], [test "x$enable_introspection" = "xyes"])
-AM_CONDITIONAL([ENABLE_GUDEV], [test "x$enable_gudev" = "xyes"])
-
-# ------------------------------------------------------------------------------
-# keymap - map custom hardware's multimedia keys
-# ------------------------------------------------------------------------------
-AC_ARG_ENABLE([keymap],
-       AS_HELP_STRING([--disable-keymap], [disable keymap fixup support @<:@default=enabled@:>@]),
-       [], [enable_keymap=yes])
-AS_IF([test "x$enable_keymap" = "xyes"], [
-       AC_PATH_PROG([GPERF], [gperf])
-       if test -z "$GPERF"; then
-              AC_MSG_ERROR([gperf is needed])
-       fi
-
-       AC_CHECK_HEADER([linux/input.h], [:], AC_MSG_ERROR([kernel headers not found]))
-       AC_SUBST([INCLUDE_PREFIX], [$(echo '#include <linux/input.h>' | eval $ac_cpp -E - | sed -n '/linux\/input.h/ {s:.*"\(.*\)/linux/input.h".*:\1:; p; q}')])
-])
-AM_CONDITIONAL([ENABLE_KEYMAP], [test "x$enable_keymap" = "xyes"])
-
-# ------------------------------------------------------------------------------
-# mtd_probe - autoloads FTL module for mtd devices
-# ------------------------------------------------------------------------------
-AC_ARG_ENABLE([mtd_probe],
-       AS_HELP_STRING([--disable-mtd_probe], [disable MTD support @<:@default=enabled@:>@]),
-       [], [enable_mtd_probe=yes])
-AM_CONDITIONAL([ENABLE_MTD_PROBE], [test "x$enable_mtd_probe" = "xyes"])
-
-# ------------------------------------------------------------------------------
-# rule_generator - persistent network and optical device rule generator
-# ------------------------------------------------------------------------------
-AC_ARG_ENABLE([rule_generator],
-       AS_HELP_STRING([--enable-rule_generator], [enable persistent network + cdrom links support @<:@default=disabled@:>@]),
-       [], [enable_rule_generator=no])
-AM_CONDITIONAL([ENABLE_RULE_GENERATOR], [test "x$enable_rule_generator" = "xyes"])
-
-# ------------------------------------------------------------------------------
-# create_floppy_devices - historical floppy kernel device nodes (/dev/fd0h1440, ...)
-# ------------------------------------------------------------------------------
-AC_ARG_ENABLE([floppy],
-       AS_HELP_STRING([--enable-floppy], [enable legacy floppy support @<:@default=disabled@:>@]),
-       [], [enable_floppy=no])
-AM_CONDITIONAL([ENABLE_FLOPPY], [test "x$enable_floppy" = "xyes"])
-
-my_CFLAGS="-Wall \
--Wmissing-declarations -Wmissing-prototypes \
--Wnested-externs -Wpointer-arith \
--Wpointer-arith -Wsign-compare -Wchar-subscripts \
--Wstrict-prototypes -Wshadow \
--Wformat-security -Wtype-limits"
-AC_SUBST([my_CFLAGS])
-
-AC_CONFIG_HEADERS(config.h)
-AC_CONFIG_FILES([
-       Makefile
-       src/docs/Makefile
-       src/docs/version.xml
-       src/gudev/docs/Makefile
-       src/gudev/docs/version.xml
+        AS_HELP_STRING([--with-rootlibdir=DIR], [Root directory for libraries necessary for boot]),
+        [],
+        [with_rootlibdir=${libdir}])
+
+AC_ARG_WITH([pamlibdir],
+        AS_HELP_STRING([--with-pamlibdir=DIR], [Directory for PAM modules]),
+        [],
+        [with_pamlibdir=${with_rootlibdir}/security])
+
+AC_ARG_ENABLE([split-usr],
+        AS_HELP_STRING([--enable-split-usr], [Assume that /bin, /sbin aren\'t symlinks into /usr]),
+        [],
+        [AS_IF([test "x${ac_default_prefix}" != "x${with_rootprefix}"], [
+                enable_split_usr=yes
+        ], [
+                enable_split_usr=no
+        ])])
+
+AS_IF([test "x${enable_split_usr}" = "xyes"], [
+        AC_DEFINE(HAVE_SPLIT_USR, 1, [Define if /bin, /sbin aren't symlinks into /usr])
 ])
 
+AC_SUBST([dbuspolicydir], [$with_dbuspolicydir])
+AC_SUBST([dbussessionservicedir], [$with_dbussessionservicedir])
+AC_SUBST([dbussystemservicedir], [$with_dbussystemservicedir])
+AC_SUBST([dbusinterfacedir], [$with_dbusinterfacedir])
+AC_SUBST([udevrulesdir], [$with_udevrulesdir])
+AC_SUBST([pamlibdir], [$with_pamlibdir])
+AC_SUBST([rootprefix], [$with_rootprefix])
+AC_SUBST([rootlibdir], [$with_rootlibdir])
+
+AC_CONFIG_FILES([Makefile po/Makefile.in])
 AC_OUTPUT
 AC_MSG_RESULT([
-        $PACKAGE $VERSION
-        ========
+        $PACKAGE_NAME $VERSION
 
+        Distribution:            ${with_distro}
+        SysV compatibility:      ${SYSTEM_SYSV_COMPAT}
+        SysV init scripts:       ${SYSTEM_SYSVINIT_PATH}
+        SysV rc?.d directories:  ${SYSTEM_SYSVRCND_PATH}
+        libcryptsetup:           ${have_libcryptsetup}
+        tcpwrap:                 ${have_tcpwrap}
+        PAM:                     ${have_pam}
+        AUDIT:                   ${have_audit}
+        IMA:                     ${have_ima}
+        SELinux:                 ${have_selinux}
+        XZ:                      ${have_xz}
+        ACL:                     ${have_acl}
+        binfmt:                  ${have_binfmt}
+        vconsole:                ${have_vconsole}
+        readahead:               ${have_readahead}
+        quotacheck:              ${have_quotacheck}
+        randomseed:              ${have_randomseed}
+        logind:                  ${have_logind}
+        hostnamed:               ${have_hostnamed}
+        timedated:               ${have_timedated}
+        localed:                 ${have_localed}
+        coredump:                ${have_coredump}
+        plymouth:                ${have_plymouth}
         prefix:                  ${prefix}
-        rootprefix:              ${rootprefix}
-        sysconfdir:              ${sysconfdir}
-        bindir:                  ${bindir}
-        libdir:                  ${libdir}
-        rootlibdir:              ${rootlib_execdir}
-        libexecdir:              ${libexecdir}
-        datarootdir:             ${datarootdir}
-        mandir:                  ${mandir}
-        includedir:              ${includedir}
-        include_prefix:          ${INCLUDE_PREFIX}
-        systemdsystemunitdir:    ${systemdsystemunitdir}
-        firmware path:           ${FIRMWARE_PATH}
-        usb.ids:                 ${USB_DATABASE}
-        pci.ids:                 ${PCI_DATABASE}
-
-        compiler:                ${CC}
-        cflags:                  ${CFLAGS}
-        ldflags:                 ${LDFLAGS}
-        xsltproc:                ${XSLTPROC}
-        gperf:                   ${GPERF}
-
-        logging:                 ${enable_logging}
-        debug:                   ${enable_debug}
-        selinux:                 ${with_selinux}
-
-        man pages                ${enable_manpages}
-        gudev:                   ${enable_gudev}
-        gintrospection:          ${enable_introspection}
-        keymap:                  ${enable_keymap}
-        mtd_probe:               ${enable_mtd_probe}
-        rule_generator:          ${enable_rule_generator}
-        floppy:                  ${enable_floppy}
+        rootprefix:              ${with_rootprefix}
+        libexec dir:             ${libexecdir}
+        lib dir:                 ${libdir}
+        rootlib dir:             ${with_rootlibdir}
+        PAM modules dir:         ${with_pamlibdir}
+        udev rules dir:          ${with_udevrulesdir}
+        D-Bus policy dir:        ${with_dbuspolicydir}
+        D-Bus session dir:       ${with_dbussessionservicedir}
+        D-Bus system dir:        ${with_dbussystemservicedir}
+        D-Bus interfaces dir:    ${with_dbusinterfacedir}
+        Split /usr:              ${enable_split_usr}
+        man pages:               ${have_manpages}
 ])
diff --git a/introspect.awk b/introspect.awk
new file mode 100644 (file)
index 0000000..5931913
--- /dev/null
@@ -0,0 +1,13 @@
+BEGIN {
+       print "<!DOCTYPE node PUBLIC DBUS_INTROSPECT_1_0_XML_PUBLIC_IDENTIFIER"
+       print "DBUS_INTROSPECT_1_0_XML_SYSTEM_IDENTIFIER>"
+       print "<node>"
+}
+
+// {
+       print
+}
+
+END {
+       print "</node>"
+}
index 0ca2c03722e7da076794b3fbb1d5967fe18babc7..55eaa803a1b3a698c8703828dc8ddc6545d360c0 100644 (file)
@@ -1,4 +1,6 @@
+intltool.m4
 libtool.m4
-lt*m4
-gtk-doc.m4
-
+ltoptions.m4
+ltsugar.m4
+ltversion.m4
+lt~obsolete.m4
diff --git a/m4/acx_libwrap.m4 b/m4/acx_libwrap.m4
new file mode 100644 (file)
index 0000000..ccf8afc
--- /dev/null
@@ -0,0 +1,19 @@
+AC_DEFUN([ACX_LIBWRAP], [
+LIBWRAP_LIBS=
+saved_LIBS="$LIBS"
+LIBS="$LIBS -lwrap"
+AC_MSG_CHECKING([for tcpwrap library and headers])
+AC_LINK_IFELSE(
+[AC_LANG_PROGRAM(
+[#include <tcpd.h>
+#include <syslog.h>
+int allow_severity = LOG_INFO;
+int deny_severity = LOG_WARNING;],
+[struct request_info *req;
+return hosts_access (req);])],
+[AC_DEFINE(HAVE_LIBWRAP, [], [Have tcpwrap?])
+LIBWRAP_LIBS="-lwrap"
+AC_MSG_RESULT(yes)],
+[AC_MSG_RESULT(no)])
+LIBS="$saved_LIBS"
+])
diff --git a/m4/attributes.m4 b/m4/attributes.m4
new file mode 100644 (file)
index 0000000..e354375
--- /dev/null
@@ -0,0 +1,288 @@
+dnl Macros to check the presence of generic (non-typed) symbols.
+dnl Copyright (c) 2006-2008 Diego Pettenò <flameeyes@gmail.com>
+dnl Copyright (c) 2006-2008 xine project
+dnl Copyright (c) 2012 Lucas De Marchi <lucas.de.marchi@gmail.com>
+dnl
+dnl This program is free software; you can redistribute it and/or modify
+dnl it under the terms of the GNU General Public License as published by
+dnl the Free Software Foundation; either version 2, or (at your option)
+dnl any later version.
+dnl
+dnl This program is distributed in the hope that it will be useful,
+dnl but WITHOUT ANY WARRANTY; without even the implied warranty of
+dnl MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+dnl GNU General Public License for more details.
+dnl
+dnl You should have received a copy of the GNU General Public License
+dnl along with this program; if not, write to the Free Software
+dnl Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
+dnl 02110-1301, USA.
+dnl
+dnl As a special exception, the copyright owners of the
+dnl macro gives unlimited permission to copy, distribute and modify the
+dnl configure scripts that are the output of Autoconf when processing the
+dnl Macro. You need not follow the terms of the GNU General Public
+dnl License when using or distributing such scripts, even though portions
+dnl of the text of the Macro appear in them. The GNU General Public
+dnl License (GPL) does govern all other use of the material that
+dnl constitutes the Autoconf Macro.
+dnl
+dnl This special exception to the GPL applies to versions of the
+dnl Autoconf Macro released by this project. When you make and
+dnl distribute a modified version of the Autoconf Macro, you may extend
+dnl this special exception to the GPL to apply to your modified version as
+dnl well.
+
+dnl Check if FLAG in ENV-VAR is supported by compiler and append it
+dnl to WHERE-TO-APPEND variable
+dnl CC_CHECK_FLAG_APPEND([WHERE-TO-APPEND], [ENV-VAR], [FLAG])
+
+AC_DEFUN([CC_CHECK_FLAG_APPEND], [
+  AC_CACHE_CHECK([if $CC supports flag $3 in envvar $2],
+                 AS_TR_SH([cc_cv_$2_$3]),
+                [eval "AS_TR_SH([cc_save_$2])='${$2}'"
+                 eval "AS_TR_SH([$2])='$3'"
+                 AC_COMPILE_IFELSE([AC_LANG_SOURCE([int a = 0; int main(void) { return a; } ])],
+                                    [eval "AS_TR_SH([cc_cv_$2_$3])='yes'"],
+                                    [eval "AS_TR_SH([cc_cv_$2_$3])='no'"])
+                 eval "AS_TR_SH([$2])='$cc_save_$2'"])
+
+  AS_IF([eval test x$]AS_TR_SH([cc_cv_$2_$3])[ = xyes],
+        [eval "$1='${$1} $3'"])
+])
+
+dnl CC_CHECK_FLAGS_APPEND([WHERE-TO-APPEND], [ENV-VAR], [FLAG1 FLAG2])
+AC_DEFUN([CC_CHECK_FLAGS_APPEND], [
+  for flag in $3; do
+    CC_CHECK_FLAG_APPEND($1, $2, $flag)
+  done
+])
+
+dnl Check if the flag is supported by linker (cacheable)
+dnl CC_CHECK_LDFLAGS([FLAG], [ACTION-IF-FOUND],[ACTION-IF-NOT-FOUND])
+
+AC_DEFUN([CC_CHECK_LDFLAGS], [
+  AC_CACHE_CHECK([if $CC supports $1 flag],
+    AS_TR_SH([cc_cv_ldflags_$1]),
+    [ac_save_LDFLAGS="$LDFLAGS"
+     LDFLAGS="$LDFLAGS $1"
+     AC_LINK_IFELSE([int main() { return 1; }],
+       [eval "AS_TR_SH([cc_cv_ldflags_$1])='yes'"],
+       [eval "AS_TR_SH([cc_cv_ldflags_$1])="])
+     LDFLAGS="$ac_save_LDFLAGS"
+    ])
+
+  AS_IF([eval test x$]AS_TR_SH([cc_cv_ldflags_$1])[ = xyes],
+    [$2], [$3])
+])
+
+dnl define the LDFLAGS_NOUNDEFINED variable with the correct value for
+dnl the current linker to avoid undefined references in a shared object.
+AC_DEFUN([CC_NOUNDEFINED], [
+  dnl We check $host for which systems to enable this for.
+  AC_REQUIRE([AC_CANONICAL_HOST])
+
+  case $host in
+     dnl FreeBSD (et al.) does not complete linking for shared objects when pthreads
+     dnl are requested, as different implementations are present; to avoid problems
+     dnl use -Wl,-z,defs only for those platform not behaving this way.
+     *-freebsd* | *-openbsd*) ;;
+     *)
+        dnl First of all check for the --no-undefined variant of GNU ld. This allows
+        dnl for a much more readable commandline, so that people can understand what
+        dnl it does without going to look for what the heck -z defs does.
+        for possible_flags in "-Wl,--no-undefined" "-Wl,-z,defs"; do
+          CC_CHECK_LDFLAGS([$possible_flags], [LDFLAGS_NOUNDEFINED="$possible_flags"])
+         break
+        done
+       ;;
+  esac
+
+  AC_SUBST([LDFLAGS_NOUNDEFINED])
+])
+
+dnl Check for a -Werror flag or equivalent. -Werror is the GCC
+dnl and ICC flag that tells the compiler to treat all the warnings
+dnl as fatal. We usually need this option to make sure that some
+dnl constructs (like attributes) are not simply ignored.
+dnl
+dnl Other compilers don't support -Werror per se, but they support
+dnl an equivalent flag:
+dnl  - Sun Studio compiler supports -errwarn=%all
+AC_DEFUN([CC_CHECK_WERROR], [
+  AC_CACHE_CHECK(
+    [for $CC way to treat warnings as errors],
+    [cc_cv_werror],
+    [CC_CHECK_CFLAGS_SILENT([-Werror], [cc_cv_werror=-Werror],
+      [CC_CHECK_CFLAGS_SILENT([-errwarn=%all], [cc_cv_werror=-errwarn=%all])])
+    ])
+])
+
+AC_DEFUN([CC_CHECK_ATTRIBUTE], [
+  AC_REQUIRE([CC_CHECK_WERROR])
+  AC_CACHE_CHECK([if $CC supports __attribute__(( ifelse([$2], , [$1], [$2]) ))],
+    AS_TR_SH([cc_cv_attribute_$1]),
+    [ac_save_CFLAGS="$CFLAGS"
+     CFLAGS="$CFLAGS $cc_cv_werror"
+     AC_COMPILE_IFELSE([AC_LANG_SOURCE([$3])],
+       [eval "AS_TR_SH([cc_cv_attribute_$1])='yes'"],
+       [eval "AS_TR_SH([cc_cv_attribute_$1])='no'"])
+     CFLAGS="$ac_save_CFLAGS"
+    ])
+
+  AS_IF([eval test x$]AS_TR_SH([cc_cv_attribute_$1])[ = xyes],
+    [AC_DEFINE(
+       AS_TR_CPP([SUPPORT_ATTRIBUTE_$1]), 1,
+         [Define this if the compiler supports __attribute__(( ifelse([$2], , [$1], [$2]) ))]
+         )
+     $4],
+    [$5])
+])
+
+AC_DEFUN([CC_ATTRIBUTE_CONSTRUCTOR], [
+  CC_CHECK_ATTRIBUTE(
+    [constructor],,
+    [void __attribute__((constructor)) ctor() { int a; }],
+    [$1], [$2])
+])
+
+AC_DEFUN([CC_ATTRIBUTE_FORMAT], [
+  CC_CHECK_ATTRIBUTE(
+    [format], [format(printf, n, n)],
+    [void __attribute__((format(printf, 1, 2))) printflike(const char *fmt, ...) { fmt = (void *)0; }],
+    [$1], [$2])
+])
+
+AC_DEFUN([CC_ATTRIBUTE_FORMAT_ARG], [
+  CC_CHECK_ATTRIBUTE(
+    [format_arg], [format_arg(printf)],
+    [char *__attribute__((format_arg(1))) gettextlike(const char *fmt) { fmt = (void *)0; }],
+    [$1], [$2])
+])
+
+AC_DEFUN([CC_ATTRIBUTE_VISIBILITY], [
+  CC_CHECK_ATTRIBUTE(
+    [visibility_$1], [visibility("$1")],
+    [void __attribute__((visibility("$1"))) $1_function() { }],
+    [$2], [$3])
+])
+
+AC_DEFUN([CC_ATTRIBUTE_NONNULL], [
+  CC_CHECK_ATTRIBUTE(
+    [nonnull], [nonnull()],
+    [void __attribute__((nonnull())) some_function(void *foo, void *bar) { foo = (void*)0; bar = (void*)0; }],
+    [$1], [$2])
+])
+
+AC_DEFUN([CC_ATTRIBUTE_UNUSED], [
+  CC_CHECK_ATTRIBUTE(
+    [unused], ,
+    [void some_function(void *foo, __attribute__((unused)) void *bar);],
+    [$1], [$2])
+])
+
+AC_DEFUN([CC_ATTRIBUTE_SENTINEL], [
+  CC_CHECK_ATTRIBUTE(
+    [sentinel], ,
+    [void some_function(void *foo, ...) __attribute__((sentinel));],
+    [$1], [$2])
+])
+
+AC_DEFUN([CC_ATTRIBUTE_DEPRECATED], [
+  CC_CHECK_ATTRIBUTE(
+    [deprecated], ,
+    [void some_function(void *foo, ...) __attribute__((deprecated));],
+    [$1], [$2])
+])
+
+AC_DEFUN([CC_ATTRIBUTE_ALIAS], [
+  CC_CHECK_ATTRIBUTE(
+    [alias], [weak, alias],
+    [void other_function(void *foo) { }
+     void some_function(void *foo) __attribute__((weak, alias("other_function")));],
+    [$1], [$2])
+])
+
+AC_DEFUN([CC_ATTRIBUTE_MALLOC], [
+  CC_CHECK_ATTRIBUTE(
+    [malloc], ,
+    [void * __attribute__((malloc)) my_alloc(int n);],
+    [$1], [$2])
+])
+
+AC_DEFUN([CC_ATTRIBUTE_PACKED], [
+  CC_CHECK_ATTRIBUTE(
+    [packed], ,
+    [struct astructure { char a; int b; long c; void *d; } __attribute__((packed));],
+    [$1], [$2])
+])
+
+AC_DEFUN([CC_ATTRIBUTE_CONST], [
+  CC_CHECK_ATTRIBUTE(
+    [const], ,
+    [int __attribute__((const)) twopow(int n) { return 1 << n; } ],
+    [$1], [$2])
+])
+
+AC_DEFUN([CC_FLAG_VISIBILITY], [
+  AC_REQUIRE([CC_CHECK_WERROR])
+  AC_CACHE_CHECK([if $CC supports -fvisibility=hidden],
+    [cc_cv_flag_visibility],
+    [cc_flag_visibility_save_CFLAGS="$CFLAGS"
+     CFLAGS="$CFLAGS $cc_cv_werror"
+     CC_CHECK_CFLAGS_SILENT([-fvisibility=hidden],
+       cc_cv_flag_visibility='yes',
+       cc_cv_flag_visibility='no')
+     CFLAGS="$cc_flag_visibility_save_CFLAGS"])
+
+  AS_IF([test "x$cc_cv_flag_visibility" = "xyes"],
+    [AC_DEFINE([SUPPORT_FLAG_VISIBILITY], 1,
+       [Define this if the compiler supports the -fvisibility flag])
+     $1],
+    [$2])
+])
+
+AC_DEFUN([CC_FUNC_EXPECT], [
+  AC_REQUIRE([CC_CHECK_WERROR])
+  AC_CACHE_CHECK([if compiler has __builtin_expect function],
+    [cc_cv_func_expect],
+    [ac_save_CFLAGS="$CFLAGS"
+     CFLAGS="$CFLAGS $cc_cv_werror"
+     AC_COMPILE_IFELSE([AC_LANG_SOURCE(
+       [int some_function() {
+        int a = 3;
+        return (int)__builtin_expect(a, 3);
+       }])],
+       [cc_cv_func_expect=yes],
+       [cc_cv_func_expect=no])
+     CFLAGS="$ac_save_CFLAGS"
+    ])
+
+  AS_IF([test "x$cc_cv_func_expect" = "xyes"],
+    [AC_DEFINE([SUPPORT__BUILTIN_EXPECT], 1,
+     [Define this if the compiler supports __builtin_expect() function])
+     $1],
+    [$2])
+])
+
+AC_DEFUN([CC_ATTRIBUTE_ALIGNED], [
+  AC_REQUIRE([CC_CHECK_WERROR])
+  AC_CACHE_CHECK([highest __attribute__ ((aligned ())) supported],
+    [cc_cv_attribute_aligned],
+    [ac_save_CFLAGS="$CFLAGS"
+     CFLAGS="$CFLAGS $cc_cv_werror"
+     for cc_attribute_align_try in 64 32 16 8 4 2; do
+        AC_COMPILE_IFELSE([AC_LANG_SOURCE([
+          int main() {
+            static char c __attribute__ ((aligned($cc_attribute_align_try))) = 0;
+            return c;
+          }])], [cc_cv_attribute_aligned=$cc_attribute_align_try; break])
+     done
+     CFLAGS="$ac_save_CFLAGS"
+  ])
+
+  if test "x$cc_cv_attribute_aligned" != "x"; then
+     AC_DEFINE_UNQUOTED([ATTRIBUTE_ALIGNED_MAX], [$cc_cv_attribute_aligned],
+       [Define the highest alignment supported])
+  fi
+])
diff --git a/man/Makefile b/man/Makefile
new file mode 120000 (symlink)
index 0000000..bd10475
--- /dev/null
@@ -0,0 +1 @@
+../src/Makefile
\ No newline at end of file
diff --git a/man/binfmt.d.xml b/man/binfmt.d.xml
new file mode 100644 (file)
index 0000000..f5ec805
--- /dev/null
@@ -0,0 +1,111 @@
+<?xml version="1.0"?>
+<!--*-nxml-*-->
+<!DOCTYPE refentry PUBLIC "-//OASIS//DTD DocBook XML V4.2//EN" "http://www.oasis-open.org/docbook/xml/4.2/docbookx.dtd">
+<!--
+  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 <http://www.gnu.org/licenses/>.
+-->
+<refentry id="binfmt.d">
+
+        <refentryinfo>
+                <title>binfmt.d</title>
+                <productname>systemd</productname>
+
+                <authorgroup>
+                        <author>
+                                <contrib>Developer</contrib>
+                                <firstname>Lennart</firstname>
+                                <surname>Poettering</surname>
+                                <email>lennart@poettering.net</email>
+                        </author>
+                </authorgroup>
+        </refentryinfo>
+
+        <refmeta>
+                <refentrytitle>binfmt.d</refentrytitle>
+                <manvolnum>5</manvolnum>
+        </refmeta>
+
+        <refnamediv>
+                <refname>binfmt.d</refname>
+                <refpurpose>Configure additional binary formats at boot</refpurpose>
+        </refnamediv>
+
+        <refsynopsisdiv>
+                <para><filename>/etc/binfmt.d/*.conf</filename></para>
+                <para><filename>/run/binfmt.d/*.conf</filename></para>
+                <para><filename>/usr/lib/binfmt.d/*.conf</filename></para>
+        </refsynopsisdiv>
+
+        <refsect1>
+                <title>Description</title>
+
+               <para><command>systemd</command> uses
+               files from the above directories to configure
+               additional binary formats to register during boot in
+               the kernel.</para>
+        </refsect1>
+
+        <refsect1>
+               <title>Configuration Format</title>
+
+                <para>Each file contains a list of binfmt_misc kernel
+                binary format rules. Consult <ulink
+                url="http://www.kernel.org/doc/Documentation/binfmt_misc.txt">binfmt_misc.txt</ulink>
+                for more information on registration of additional
+                binary formats and how to write rules.</para>
+
+                <para>Empty lines and lines beginning with ; and # are
+                ignored. Note that this means you may not use ; and #
+                as delimiter in binary format rules.</para>
+
+                <para>Each configuration file is named in the style of
+                <filename>&lt;program&gt;.conf</filename>.
+                Files in <filename>/etc/</filename> overwrite
+                files with the same name in <filename>/usr/lib/</filename>.
+                Files in <filename>/run</filename> overwrite files with
+                the same name in <filename>/etc/</filename> and
+                <filename>/usr/lib/</filename>. Packages should install their
+                configuration files in <filename>/usr/lib/</filename>, files
+                in <filename>/etc/</filename> are reserved for the local
+                administration, which possibly decides to overwrite the
+                configurations installed from packages. All files are sorted
+                by filename in alphabetical order, regardless in which of the
+                directories they reside, to ensure that a specific
+                configuration file takes precedence over another file with
+                an alphabetically later name.</para>
+        </refsect1>
+
+        <refsect1>
+                <title>Example</title>
+                <example>
+                        <title>/etc/binfmt.d/wine.conf example:</title>
+
+                        <programlisting># Start WINE on Windows executables
+:DOSWin:M::MZ::/usr/bin/wine:</programlisting>
+                </example>
+        </refsect1>
+
+        <refsect1>
+                <title>See Also</title>
+                <para>
+                        <citerefentry><refentrytitle>systemd</refentrytitle><manvolnum>1</manvolnum></citerefentry>,
+                        <citerefentry><refentrytitle>wine</refentrytitle><manvolnum>8</manvolnum></citerefentry>
+                </para>
+        </refsect1>
+
+</refentry>
diff --git a/man/custom-html.xsl b/man/custom-html.xsl
new file mode 100644 (file)
index 0000000..2d2f458
--- /dev/null
@@ -0,0 +1,29 @@
+<?xml version='1.0'?> <!--*-nxml-*-->
+
+<!--
+  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 <http://www.gnu.org/licenses/>.
+-->
+
+<xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform" version="1.0">
+
+<xsl:import href="http://docbook.sourceforge.net/release/xsl/current/html/docbook.xsl"/>
+
+<!-- Switch things to UTF-8, ISO-8859-1 is soo yesteryear -->
+<xsl:output method="html" encoding="UTF-8" indent="no"/>
+
+</xsl:stylesheet>
diff --git a/man/daemon.xml b/man/daemon.xml
new file mode 100644 (file)
index 0000000..997ee5b
--- /dev/null
@@ -0,0 +1,948 @@
+<?xml version='1.0'?> <!--*-nxml-*-->
+<!DOCTYPE refentry PUBLIC "-//OASIS//DTD DocBook XML V4.2//EN"
+        "http://www.oasis-open.org/docbook/xml/4.2/docbookx.dtd">
+
+<!--
+  This file is part 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 <http://www.gnu.org/licenses/>.
+-->
+
+<refentry id="daemon">
+
+        <refentryinfo>
+                <title>daemon</title>
+                <productname>systemd</productname>
+
+                <authorgroup>
+                        <author>
+                                <contrib>Developer</contrib>
+                                <firstname>Lennart</firstname>
+                                <surname>Poettering</surname>
+                                <email>lennart@poettering.net</email>
+                        </author>
+                </authorgroup>
+        </refentryinfo>
+
+        <refmeta>
+                <refentrytitle>daemon</refentrytitle>
+                <manvolnum>7</manvolnum>
+        </refmeta>
+
+        <refnamediv>
+                <refname>daemon</refname>
+                <refpurpose>Writing and Packaging System Daemons</refpurpose>
+        </refnamediv>
+
+        <refsect1>
+                <title>Description</title>
+
+                <para>A daemon is a service process that runs in the
+                background and supervises the system or provides
+                functionality to other processes. Traditionally,
+                daemons are implemented following a scheme originating
+                in SysV Unix. Modern daemons should follow a simpler
+                yet more powerful scheme (here called "new-style"
+                daemons), as implemented by
+                <citerefentry><refentrytitle>systemd</refentrytitle><manvolnum>1</manvolnum></citerefentry>. This
+                manual page covers both schemes, and in
+                particular includes recommendations for daemons that
+                shall be included in the systemd init system.</para>
+
+                <refsect2>
+                        <title>SysV Daemons</title>
+
+                        <para>When a traditional SysV daemon
+                        starts, it should execute the following steps
+                        as part of the initialization. Note that these
+                        steps are unnecessary for new-style daemons (see below),
+                        and should only be implemented if compatibility
+                        with SysV is essential.</para>
+
+                        <orderedlist>
+                                <listitem><para>Close all open file
+                                descriptors except STDIN, STDOUT,
+                                STDERR (i.e. the first three file
+                                descriptors 0, 1, 2). This ensures
+                                that no accidentally passed file
+                                descriptor stays around in the daemon
+                                process. On Linux this is best
+                                implemented by iterating through
+                                <filename>/proc/self/fd</filename>,
+                                with a fallback of iterating from file
+                                descriptor 3 to the value returned by
+                                <function>getrlimit()</function> for
+                                RLIMIT_NOFILE.</para></listitem>
+
+                                <listitem><para>Reset all signal
+                                handlers to their default. This is
+                                best done by iterating through the
+                                available signals up to the limit of
+                                _NSIG and resetting them to
+                                SIG_DFL.</para></listitem>
+
+                                <listitem><para>Reset the signal mask
+                                using
+                                <function>sigprocmask()</function>.</para></listitem>
+
+                                <listitem><para>Sanitize the
+                                environment block, removing or
+                                resetting environment variables that
+                                might negatively impact daemon
+                                runtime.</para></listitem>
+
+                                <listitem><para>Call <function>fork()</function>,
+                                to create a background
+                                process.</para></listitem>
+
+                                <listitem><para>In the child, call
+                                <function>setsid()</function> to
+                                detach from any terminal and create an
+                                independent session.</para></listitem>
+
+                                <listitem><para>In the child, call
+                                <function>fork()</function> again, to
+                                ensure the daemon can never re-acquire
+                                a terminal again.</para></listitem>
+
+                                <listitem><para>Call <function>exit()</function> in the
+                                first child, so that only the second
+                                child (the actual daemon process)
+                                stays around. This ensures that the
+                                daemon process is reparented to
+                                init/PID 1, as all daemons should
+                                be.</para></listitem>
+
+                                <listitem><para>In the daemon process,
+                                connect <filename>/dev/null</filename>
+                                to STDIN, STDOUT,
+                                STDERR.</para></listitem>
+
+                                <listitem><para>In the daemon process,
+                                reset the umask to 0, so that the file
+                                modes passed to <function>open()</function>, <function>mkdir()</function> and
+                                suchlike directly control the access
+                                mode of the created files and
+                                directories.</para></listitem>
+
+                                <listitem><para>In the daemon process,
+                                change the current directory to the
+                                root directory (/), in order to avoid
+                                that the daemon involuntarily
+                                blocks mount points from being
+                                unmounted.</para></listitem>
+
+                                <listitem><para>In the daemon process,
+                                write the daemon PID (as returned by
+                                <function>getpid()</function>) to a
+                                PID file, for example
+                                <filename>/var/run/foobar.pid</filename>
+                                (for a hypothetical daemon "foobar"),
+                                to ensure that the daemon cannot be
+                                started more than once. This must be
+                                implemented in race-free fashion so
+                                that the PID file is only updated when
+                                at the same time it is verified that
+                                the PID previously stored in the PID
+                                file no longer exists or belongs to a
+                                foreign process. Commonly some kind of
+                                file locking is employed to implement
+                                this logic.</para></listitem>
+
+                                <listitem><para>In the daemon process,
+                                drop privileges, if possible and
+                                applicable.</para></listitem>
+
+                                <listitem><para>From the daemon
+                                process notify the original process
+                                started that initialization is
+                                complete. This can be implemented via
+                                an unnamed pipe or similar
+                                communication channel that is created
+                                before the first
+                                <function>fork()</function> and hence
+                                available in both the original and the
+                                daemon process.</para></listitem>
+
+                                <listitem><para>Call
+                                <function>exit()</function> in the
+                                original process. The process that
+                                invoked the daemon must be able to
+                                rely that this
+                                <function>exit()</function> happens
+                                after initialization is complete and
+                                all external communication channels
+                                established and
+                                accessible.</para></listitem>
+                        </orderedlist>
+
+                        <para>The BSD <function>daemon()</function> function should not be
+                        used, as it implements only a subset of these steps.</para>
+
+                        <para>A daemon that needs to provide
+                        compatibility with SysV systems should
+                        implement the scheme pointed out
+                        above. However, it is recommended to make this
+                        behaviour optional and configurable via a
+                        command line argument, to ease debugging as
+                        well as to simplify integration into systems
+                        using systemd.</para>
+                </refsect2>
+
+                <refsect2>
+                        <title>New-Style Daemons</title>
+
+                        <para>Modern services for Linux should be
+                        implemented as new-style daemons. This makes it
+                        easier to supervise and control them at
+                        runtime and simplifies their
+                        implementation.</para>
+
+                        <para>For developing a new-style daemon none
+                        of the initialization steps recommended for
+                        SysV daemons need to be implemented. New-style
+                        init systems such as systemd make all of them
+                        redundant. Moreover, since some of these steps
+                        interfere with process monitoring, file
+                        descriptor passing and other functionality of
+                        the init system it is recommended not to
+                        execute them when run as new-style
+                        service.</para>
+
+                        <para>Note that new-style init systems
+                        guarantee execution of daemon processes in
+                        clean process contexts: it is guaranteed that
+                        the environment block is sanitized, that the
+                        signal handlers and mask is reset and that no
+                        left-over file descriptors are passed. Daemons
+                        will be executed in their own session, and
+                        STDIN/STDOUT/STDERR connected to
+                        <filename>/dev/null</filename> unless
+                        otherwise configured. The umask is reset.</para>
+
+                        <para>It is recommended for new-style daemons
+                        to implement the following:</para>
+
+                        <orderedlist>
+                                <listitem><para>If SIGTERM is
+                                received, shut down the daemon and
+                                exit cleanly.</para></listitem>
+
+                                <listitem><para>If SIGHUP is received,
+                                reload the configuration files, if
+                                this applies.</para></listitem>
+
+                                <listitem><para>Provide a correct exit
+                                code from the main daemon process, as
+                                this is used by the init system to
+                                detect service errors and problems. It
+                                is recommended to follow the exit code
+                                scheme as defined in the <ulink
+                                url="http://refspecs.freestandards.org/LSB_3.1.1/LSB-Core-generic/LSB-Core-generic/iniscrptact.html">LSB
+                                recommendations for SysV init
+                                scripts</ulink>.</para></listitem>
+
+                                <listitem><para>If possible and
+                                applicable expose the daemon's control
+                                interface via the D-Bus IPC system and
+                                grab a bus name as last step of
+                                initialization.</para></listitem>
+
+                                <listitem><para>For integration in
+                                systemd, provide a
+                                <filename>.service</filename> unit
+                                file that carries information about
+                                starting, stopping and otherwise
+                                maintaining the daemon. See
+                                <citerefentry><refentrytitle>systemd.service</refentrytitle><manvolnum>5</manvolnum></citerefentry>
+                                for details.</para></listitem>
+
+                                <listitem><para>As much as possible,
+                                rely on the init systemd's
+                                functionality to limit the access of
+                                the daemon to files, services and
+                                other resources. i.e. in the case of
+                                systemd, rely on systemd's resource
+                                limit control instead of implementing
+                                your own, rely on systemd's privilege
+                                dropping code instead of implementing
+                                it in the daemon, and similar. See
+                                <citerefentry><refentrytitle>systemd.exec</refentrytitle><manvolnum>5</manvolnum></citerefentry>
+                                for the available
+                                controls.</para></listitem>
+
+                                <listitem><para>If D-Bus is used, make
+                                your daemon bus-activatable, via
+                                supplying a D-Bus service activation
+                                configuration file. This has multiple
+                                advantages: your daemon may be started
+                                lazily on-demand; it may be started in
+                                parallel to other daemons requiring it
+                                -- which maximizes parallelization and
+                                boot-up speed; your daemon can be
+                                restarted on failure, without losing
+                                any bus requests, as the bus queues
+                                requests for activatable services. See
+                                below for details.</para></listitem>
+
+                                <listitem><para>If your daemon
+                                provides services to other local
+                                processes or remote clients via a
+                                socket, it should be made
+                                socket-activatable following the
+                                scheme pointed out below. Like D-Bus
+                                activation this enables on-demand
+                                starting of services as well as it
+                                allows improved parallelization of
+                                service start-up. Also, for state-less
+                                protocols (such as syslog, DNS) a
+                                daemon implementing socket-based
+                                activation can be restarted without
+                                losing a single request. See below for
+                                details.</para></listitem>
+
+                                <listitem><para>If applicable a daemon
+                                should notify the init system about
+                                startup completion or status updates
+                                via the
+                                <citerefentry><refentrytitle>sd_notify</refentrytitle><manvolnum>3</manvolnum></citerefentry>
+                                interface.</para></listitem>
+
+                                <listitem><para>Instead of using the
+                                <function>syslog()</function> call to log directly to the
+                                system syslog service, a new-style daemon may
+                                choose to simply log to STDERR via
+                                <function>fprintf()</function>, which is then forwarded to
+                                syslog by the init system. If log
+                                priorities are necessary these can be
+                                encoded by prefixing individual log
+                                lines with strings like "&lt;4&gt;"
+                                (for log priority 4 "WARNING" in the
+                                syslog priority scheme), following a
+                                similar style as the Linux kernel's
+                                <function>printk()</function> priority system. In fact,
+                                using this style of logging also
+                                enables the init system to optionally
+                                direct all application logging to the
+                                kernel log buffer (kmsg), as
+                                accessible via
+                                <citerefentry><refentrytitle>dmesg</refentrytitle><manvolnum>1</manvolnum></citerefentry>. This
+                                kind of logging may be enabled by
+                                setting
+                                <varname>StandardError=syslog</varname>
+                                in the service unit file. For details
+                                see
+                                <citerefentry><refentrytitle>sd-daemon</refentrytitle><manvolnum>7</manvolnum></citerefentry>
+                                and
+                                <citerefentry><refentrytitle>systemd.exec</refentrytitle><manvolnum>5</manvolnum></citerefentry>.</para></listitem>
+
+                        </orderedlist>
+
+                        <para>These recommendations are similar but
+                        not identical to the <ulink
+                        url="http://developer.apple.com/mac/library/documentation/MacOSX/Conceptual/BPSystemStartup/Articles/LaunchOnDemandDaemons.html#//apple_ref/doc/uid/TP40001762-104738">Apple
+                        MacOS X Daemon Requirements</ulink>.</para>
+                </refsect2>
+
+        </refsect1>
+        <refsect1>
+                <title>Activation</title>
+
+                <para>New-style init systems provide multiple
+                additional mechanisms to activate services, as
+                detailed below. It is common that services are
+                configured to be activated via more than one mechanism
+                at the same time. An example for systemd:
+                <filename>bluetoothd.service</filename> might get
+                activated either when Bluetooth hardware is plugged
+                in, or when an application accesses its programming
+                interfaces via D-Bus. Or, a print server daemon might
+                get activated when traffic arrives at an IPP port, or
+                when a printer is plugged in, or when a file is queued
+                in the printer spool directory. Even for services that
+                are intended to be started on system bootup
+                unconditionally it is a good idea to implement some of
+                the various activation schemes outlined below, in
+                order to maximize parallelization: if a daemon
+                implements a D-Bus service or listening socket,
+                implementing the full bus and socket activation scheme
+                allows starting of the daemon with its clients in
+                parallel (which speeds up boot-up), since all its
+                communication channels are established already, and no
+                request is lost because client requests will be queued
+                by the bus system (in case of D-Bus) or the kernel (in
+                case of sockets), until the activation is
+                completed.</para>
+
+                <refsect2>
+                        <title>Activation on Boot</title>
+
+                        <para>Old-style daemons are usually activated
+                        exclusively on boot (and manually by the
+                        administrator) via SysV init scripts, as
+                        detailed in the <ulink
+                        url="http://refspecs.freestandards.org/LSB_3.1.1/LSB-Core-generic/LSB-Core-generic/iniscrptact.html">LSB
+                        Linux Standard Base Core
+                        Specification</ulink>. This method of
+                        activation is supported ubiquitously on Linux
+                        init systems, both old-style and new-style
+                        systems. Among other issues SysV init scripts
+                        have the disadvantage of involving shell
+                        scripts in the boot process. New-style init
+                        systems generally employ updated versions of
+                        activation, both during boot-up and during
+                        runtime and using more minimal service
+                        description files.</para>
+
+                        <para>In systemd, if the developer or
+                        administrator wants to make sure a service or
+                        other unit is activated automatically on boot
+                        it is recommended to place a symlink to the
+                        unit file in the <filename>.wants/</filename>
+                        directory of either
+                        <filename>multi-user.target</filename> or
+                        <filename>graphical.target</filename>, which
+                        are normally used as boot targets at system
+                        startup. See
+                        <citerefentry><refentrytitle>systemd.unit</refentrytitle><manvolnum>5</manvolnum></citerefentry>
+                        for details about the
+                        <filename>.wants/</filename> directories, and
+                        <citerefentry><refentrytitle>systemd.special</refentrytitle><manvolnum>7</manvolnum></citerefentry>
+                        for details about the two boot targets.</para>
+
+                </refsect2>
+
+                <refsect2>
+                        <title>Socket-Based Activation</title>
+
+                        <para>In order to maximize the possible
+                        parallelization and robustness and simplify
+                        configuration and development, it is
+                        recommended for all new-style daemons that
+                        communicate via listening sockets to employ
+                        socket-based activation. In a socket-based
+                        activation scheme the creation and binding of
+                        the listening socket as primary communication
+                        channel of daemons to local (and sometimes
+                        remote) clients is moved out of the daemon
+                        code and into the init system. Based on
+                        per-daemon configuration the init system
+                        installs the sockets and then hands them off
+                        to the spawned process as soon as the
+                        respective daemon is to be started.
+                        Optionally activation of the service can be
+                        delayed until the first inbound traffic
+                        arrives at the socket, to implement on-demand
+                        activation of daemons. However, the primary
+                        advantage of this scheme is that all providers
+                        and all consumers of the sockets can be
+                        started in parallel as soon as all sockets
+                        are established. In addition to that daemons
+                        can be restarted with losing only a minimal
+                        number of client transactions or even any
+                        client request at all (the latter is
+                        particularly true for state-less protocols,
+                        such as DNS or syslog), because the socket
+                        stays bound and accessible during the restart,
+                        and all requests are queued while the daemon
+                        cannot process them.</para>
+
+                        <para>New-style daemons which support socket
+                        activation must be able to receive their
+                        sockets from the init system, instead of of
+                        creating and binding them themselves. For
+                        details about the programming interfaces for
+                        this scheme provided by systemd see
+                        <citerefentry><refentrytitle>sd_listen_fds</refentrytitle><manvolnum>3</manvolnum></citerefentry>
+                        and
+                        <citerefentry><refentrytitle>sd-daemon</refentrytitle><manvolnum>7</manvolnum></citerefentry>. For
+                        details about porting existing daemons to
+                        socket-based activation see below. With
+                        minimal effort it is possible to implement
+                        socket-based activation in addition to
+                        traditional internal socket creation in the
+                        same codebase in order to support both
+                        new-style and old-style init systems from the
+                        same daemon binary.</para>
+
+                        <para>systemd implements socket-based
+                        activation via <filename>.socket</filename>
+                        units, which are described in
+                        <citerefentry><refentrytitle>systemd.socket</refentrytitle><manvolnum>5</manvolnum></citerefentry>. When
+                        configuring socket units for socket-based
+                        activation it is essential that all listening
+                        sockets are pulled in by the special target
+                        unit <filename>sockets.target</filename>. It
+                        is recommended to place a
+                        <varname>WantedBy=sockets.target</varname>
+                        directive in the <literal>[Install]</literal>
+                        section, to automatically add such a
+                        dependency on installation of a socket
+                        unit. Unless
+                        <varname>DefaultDependencies=no</varname> is
+                        set the necessary ordering dependencies are
+                        implicitly created for all socket units. For
+                        more information about
+                        <filename>sockets.target</filename> see
+                        <citerefentry><refentrytitle>systemd.special</refentrytitle><manvolnum>7</manvolnum></citerefentry>. It
+                        is not necessary or recommended to place any
+                        additional dependencies on socket units (for
+                        example from
+                        <filename>multi-user.target</filename> or
+                        suchlike) when one is installed in
+                        <filename>sockets.target</filename>.</para>
+                </refsect2>
+
+                <refsect2>
+                        <title>Bus-Based Activation</title>
+
+                        <para>When the D-Bus IPC system is used for
+                        communication with clients, new-style daemons
+                        should employ bus activation so that they are
+                        automatically activated when a client
+                        application accesses their IPC
+                        interfaces. This is configured in D-Bus
+                        service files (not to be confused with systemd
+                        service unit files!). To ensure that D-Bus
+                        uses systemd to start-up and maintain the
+                        daemon use the
+                        <varname>SystemdService=</varname> directive
+                        in these service files, to configure the
+                        matching systemd service for a D-Bus
+                        service. e.g.: for a D-Bus service whose D-Bus
+                        activation file is named
+                        <filename>org.freedesktop.RealtimeKit.service</filename>,
+                        make sure to set
+                        <varname>SystemdService=rtkit-daemon.service</varname>
+                        in that file, to bind it to the systemd
+                        service
+                        <filename>rtkit-daemon.service</filename>. This
+                        is needed to make sure that the daemon is
+                        started in a race-free fashion when activated
+                        via multiple mechanisms simultaneously.</para>
+                </refsect2>
+
+                <refsect2>
+                        <title>Device-Based Activation</title>
+
+                        <para>Often, daemons that manage a particular
+                        type of hardware should be activated only when
+                        the hardware of the respective kind is plugged
+                        in or otherwise becomes available. In a
+                        new-style init system it is possible to bind
+                        activation to hardware plug/unplug events. In
+                        systemd, kernel devices appearing in the
+                        sysfs/udev device tree can be exposed as units
+                        if they are tagged with the string
+                        "<literal>systemd</literal>". Like any other
+                        kind of unit they may then pull in other units
+                        when activated (i.e. Plugged in) and thus
+                        implement device-based activation. Systemd
+                        dependencies may be encoded in the udev
+                        database via the
+                        <varname>SYSTEMD_WANTS=</varname>
+                        property. See
+                        <citerefentry><refentrytitle>systemd.device</refentrytitle><manvolnum>5</manvolnum></citerefentry>
+                        for details. Often it is nicer to pull in
+                        services from devices only indirectly via
+                        dedicated targets. Example: instead of pulling
+                        in <filename>bluetoothd.service</filename>
+                        from all the various bluetooth dongles and
+                        other hardware available, pull in
+                        bluetooth.target from them and
+                        <filename>bluetoothd.service</filename> from
+                        that target. This provides for nicer
+                        abstraction and gives administrators the
+                        option to enable
+                        <filename>bluetoothd.service</filename> via
+                        controlling a
+                        <filename>bluetooth.target.wants/</filename>
+                        symlink uniformly with a command like
+                        <command>enable</command> of
+                        <citerefentry><refentrytitle>systemctl</refentrytitle><manvolnum>1</manvolnum></citerefentry>
+                        instead of manipulating the udev
+                        ruleset.</para>
+                </refsect2>
+
+                <refsect2>
+                        <title>Path-Based Activation</title>
+
+                        <para>Often, runtime of daemons processing
+                        spool files or directories (such as a printing
+                        system) can be delayed until these file system
+                        objects change state, or become
+                        non-empty. New-style init systems provide a
+                        way to bind service activation to file system
+                        changes. systemd implements this scheme via
+                        path-based activation configured in
+                        <filename>.path</filename> units, as outlined
+                        in
+                        <citerefentry><refentrytitle>systemd.path</refentrytitle><manvolnum>5</manvolnum></citerefentry>.</para>
+                </refsect2>
+
+                <refsect2>
+                        <title>Timer-Based Activation</title>
+
+                        <para>Some daemons that implement clean-up
+                        jobs that are intended to be executed in
+                        regular intervals benefit from timer-based
+                        activation. In systemd, this is implemented
+                        via <filename>.timer</filename> units, as
+                        described in
+                        <citerefentry><refentrytitle>systemd.timer</refentrytitle><manvolnum>5</manvolnum></citerefentry>.</para>
+                </refsect2>
+
+                <refsect2>
+                        <title>Other Forms of Activation</title>
+
+                        <para>Other forms of activation have been
+                        suggested and implemented in some
+                        systems. However, often there are simpler or
+                        better alternatives, or they can be put
+                        together of combinations of the schemes
+                        above. Example: sometimes it appears useful to
+                        start daemons or <filename>.socket</filename>
+                        units when a specific IP address is configured
+                        on a network interface, because network
+                        sockets shall be bound to the
+                        address. However, an alternative to implement
+                        this is by utilizing the Linux IP_FREEBIND
+                        socket option, as accessible via
+                        <varname>FreeBind=yes</varname> in systemd
+                        socket files (see
+                        <citerefentry><refentrytitle>systemd.socket</refentrytitle><manvolnum>5</manvolnum></citerefentry>
+                        for details). This option, when enabled,
+                        allows sockets to be bound to a non-local, not
+                        configured IP address, and hence allows
+                        bindings to a particular IP address before it
+                        actually becomes available, making such an
+                        explicit dependency to the configured address
+                        redundant. Another often suggested trigger for
+                        service activation is low system
+                        load. However, here too, a more convincing
+                        approach might be to make proper use of
+                        features of the operating system: in
+                        particular, the CPU or IO scheduler of
+                        Linux. Instead of scheduling jobs from
+                        userspace based on monitoring the OS
+                        scheduler, it is advisable to leave the
+                        scheduling of processes to the OS scheduler
+                        itself. systemd provides fine-grained access
+                        to the CPU and IO schedulers. If a process
+                        executed by the init system shall not
+                        negatively impact the amount of CPU or IO
+                        bandwidth available to other processes, it
+                        should be configured with
+                        <varname>CPUSchedulingPolicy=idle</varname>
+                        and/or
+                        <varname>IOSchedulingClass=idle</varname>. Optionally,
+                        this may be combined with timer-based
+                        activation to schedule background jobs during
+                        runtime and with minimal impact on the system,
+                        and remove it from the boot phase
+                        itself.</para>
+                </refsect2>
+
+        </refsect1>
+        <refsect1>
+                <title>Integration with Systemd</title>
+
+                <refsect2>
+                        <title>Writing Systemd Unit Files</title>
+
+                        <para>When writing systemd unit files, it is
+                        recommended to consider the following
+                        suggestions:</para>
+
+                        <orderedlist>
+                                <listitem><para>If possible do not use
+                                the <varname>Type=forking</varname>
+                                setting in service files. But if you
+                                do, make sure to set the PID file path
+                                using <varname>PIDFile=</varname>. See
+                                <citerefentry><refentrytitle>systemd.service</refentrytitle><manvolnum>5</manvolnum></citerefentry>
+                                for details.</para></listitem>
+
+                                <listitem><para>If your daemon
+                                registers a D-Bus name on the bus,
+                                make sure to use
+                                <varname>Type=dbus</varname> in the
+                                service file if
+                                possible.</para></listitem>
+
+                                <listitem><para>Make sure to set a
+                                good human-readable description string
+                                with
+                                <varname>Description=</varname>.</para></listitem>
+
+                                <listitem><para>Do not disable
+                                <varname>DefaultDependencies=</varname>,
+                                unless you really know what you do and
+                                your unit is involved in early boot or
+                                late system shutdown.</para></listitem>
+
+                                <listitem><para>Normally, little if
+                                any dependencies should need to
+                                be defined explicitly. However, if you
+                                do configure explicit dependencies, only refer to
+                                unit names listed on
+                                <citerefentry><refentrytitle>systemd.special</refentrytitle><manvolnum>7</manvolnum></citerefentry>
+                                or names introduced by your own
+                                package to keep the unit file
+                                operating
+                                system-independent.</para></listitem>
+
+                                <listitem><para>Make sure to include
+                                an <literal>[Install]</literal>
+                                section including installation
+                                information for the unit file. See
+                                <citerefentry><refentrytitle>systemd.unit</refentrytitle><manvolnum>5</manvolnum></citerefentry>
+                                for details. To activate your service
+                                on boot make sure to add a
+                                <varname>WantedBy=multi-user.target</varname>
+                                or
+                                <varname>WantedBy=graphical.target</varname>
+                                directive. To activate your socket on
+                                boot, make sure to add
+                                <varname>WantedBy=sockets.target</varname>. Usually
+                                you also want to make sure that when
+                                your service is installed your socket
+                                is installed too, hence add
+                                <varname>Also=foo.socket</varname> in
+                                your service file
+                                <filename>foo.service</filename>, for
+                                a hypothetical program
+                                <filename>foo</filename>.</para></listitem>
+
+                        </orderedlist>
+                </refsect2>
+
+                <refsect2>
+                        <title>Installing Systemd Service Files</title>
+
+                        <para>At the build installation time
+                        (e.g. <command>make install</command> during
+                        package build) packages are recommended to
+                        install their systemd unit files in the
+                        directory returned by <command>pkg-config
+                        systemd
+                        --variable=systemdsystemunitdir</command> (for
+                        system services), resp. <command>pkg-config
+                        systemd
+                        --variable=systemduserunitdir</command>
+                        (for user services). This will make the
+                        services available in the system on explicit
+                        request but not activate them automatically
+                        during boot. Optionally, during package
+                        installation (e.g. <command>rpm -i</command>
+                        by the administrator) symlinks should be
+                        created in the systemd configuration
+                        directories via the <command>enable</command>
+                        command of the
+                        <citerefentry><refentrytitle>systemctl</refentrytitle><manvolnum>1</manvolnum></citerefentry>
+                        tool, to activate them automatically on
+                        boot.</para>
+
+                        <para>Packages using
+                        <citerefentry><refentrytitle>autoconf</refentrytitle><manvolnum>1</manvolnum></citerefentry>
+                        are recommended to use a configure script
+                        excerpt like the following to determine the
+                        unit installation path during source
+                        configuration:</para>
+
+                        <programlisting>PKG_PROG_PKG_CONFIG
+AC_ARG_WITH([systemdsystemunitdir],
+        AS_HELP_STRING([--with-systemdsystemunitdir=DIR], [Directory for systemd service files]),
+        [], [with_systemdsystemunitdir=$($PKG_CONFIG --variable=systemdsystemunitdir systemd)])
+if test "x$with_systemdsystemunitdir" != xno; then
+        AC_SUBST([systemdsystemunitdir], [$with_systemdsystemunitdir])
+fi
+AM_CONDITIONAL(HAVE_SYSTEMD, [test -n "$with_systemdsystemunitdir" -a "x$with_systemdsystemunitdir" != xno ])</programlisting>
+
+                        <para>This snippet allows automatic
+                        installation of the unit files on systemd
+                        machines, and optionally allows their
+                        installation even on machines lacking
+                        systemd. (Modification of this snippet for the
+                        user unit directory is left as an exercise for the
+                        reader.)</para>
+
+                        <para>Additionally, to ensure that
+                        <command>make distcheck</command> continues to
+                        work, it is recommended to add the following
+                        to the top-level <filename>Makefile.am</filename>
+                        file in
+                        <citerefentry><refentrytitle>automake</refentrytitle><manvolnum>1</manvolnum></citerefentry>-based
+                        projects:</para>
+
+                        <programlisting>DISTCHECK_CONFIGURE_FLAGS = \
+        --with-systemdsystemunitdir=$$dc_install_base/$(systemdsystemunitdir)</programlisting>
+
+                        <para>Finally, unit files should be installed in the system with an automake excerpt like the following:</para>
+
+                        <programlisting>if HAVE_SYSTEMD
+systemdsystemunit_DATA = \
+        foobar.socket \
+        foobar.service
+endif</programlisting>
+
+                        <para>In the
+                        <citerefentry><refentrytitle>rpm</refentrytitle><manvolnum>8</manvolnum></citerefentry>
+                        <filename>.spec</filename> file use a snippet like
+                        the following to enable/disable the service
+                        during installation/deinstallation. Consult
+                        the packaging guidelines of your distribution
+                        for details and the equivalent for other
+                        package managers:</para>
+
+                        <programlisting>%post
+if [ $1 -eq 1 ]; then
+        # On install (not upgrade), enable (but don't start) the
+        # units by default
+        /bin/systemctl enable foobar.service foobar.socket >/dev/null 2>&amp;1 || :
+
+        # Alternatively, just call
+        # /bin/systemctl daemon-reload >/dev/null 2>&amp;1 || :
+        # here, if the daemon should not be enabled by default on
+        # installation
+fi
+
+%preun
+if [ $1 -eq 0 ]; then
+        # On uninstall (not upgrade), disable and stop the units
+        /bin/systemctl --no-reload disable foobar.service foobar.socket >/dev/null 2>&amp;1 || :
+        /bin/systemctl stop foobar.service foobar.socket >/dev/null 2>&amp;1 || :
+fi
+
+%postun
+# Reload init system configuration, to make systemd honour changed
+# or deleted unit files
+/bin/systemctl daemon-reload >/dev/null 2>&amp;1 || :
+if [ $1 -ge 1 ] ; then
+        # On upgrade (not uninstall), optionally, restart the daemon
+        /bin/systemctl try-restart foobar.service >/dev/null 2>&amp;1 || :
+fi</programlisting>
+
+                        <para>Depending on whether your service should
+                        or should not be started/stopped/restarted
+                        during package installation, deinstallation or
+                        upgrade, a different set of commands may be
+                        specified. See
+                        <citerefentry><refentrytitle>systemctl</refentrytitle><manvolnum>1</manvolnum></citerefentry>
+                        for details.</para>
+
+                        <para>To facilitate upgrades from a package
+                        version that shipped only SysV init scripts to
+                        a package version that ships both a SysV init
+                        script and a native systemd service file, use
+                        a fragment like the following:</para>
+
+                        <programlisting>%triggerun -- foobar &lt; 0.47.11-1
+if /sbin/chkconfig --level 5 foobar ; then
+        /bin/systemctl --no-reload enable foobar.service foobar.socket >/dev/null 2>&amp;1 || :
+fi</programlisting>
+
+                        <para>Where 0.47.11-1 is the first package
+                        version that includes the native unit
+                        file. This fragment will ensure that the first
+                        time the unit file is installed it will be
+                        enabled if and only if the SysV init script is
+                        enabled, thus making sure that the enable
+                        status is not changed. Note that
+                        <command>chkconfig</command> is a command
+                        specific to Fedora which can be used to check
+                        whether a SysV init script is enabled. Other
+                        operating systems will have to use different
+                        commands here.</para>
+                </refsect2>
+        </refsect1>
+
+        <refsect1>
+                <title>Porting Existing Daemons</title>
+
+                <para>Since new-style init systems such as systemd are
+                compatible with traditional SysV init systems it is
+                not strictly necessary to port existing daemons to the
+                new style. However doing so offers additional
+                functionality to the daemons as well as simplifying
+                integration into new-style init systems.</para>
+
+                <para>To port an existing SysV compatible daemon the
+                following steps are recommended:</para>
+
+                <orderedlist>
+                        <listitem><para>If not already implemented,
+                        add an optional command line switch to the
+                        daemon to disable daemonization. This is
+                        useful not only for using the daemon in
+                        new-style init systems, but also to ease
+                        debugging.</para></listitem>
+
+                        <listitem><para>If the daemon offers
+                        interfaces to other software running on the
+                        local system via local AF_UNIX sockets,
+                        consider implementing socket-based activation
+                        (see above). Usually a minimal patch is
+                        sufficient to implement this: Extend the
+                        socket creation in the daemon code so that
+                        <citerefentry><refentrytitle>sd_listen_fds</refentrytitle><manvolnum>3</manvolnum></citerefentry>
+                        is checked for already passed sockets
+                        first. If sockets are passed (i.e. when
+                        <function>sd_listen_fds()</function> returns a
+                        positive value), skip the socket creation step
+                        and use the passed sockets. Secondly, ensure
+                        that the file-system socket nodes for local
+                        AF_UNIX sockets used in the socket-based
+                        activation are not removed when the daemon
+                        shuts down, if sockets have been
+                        passed. Third, if the daemon normally closes
+                        all remaining open file descriptors as part of
+                        its initialization, the sockets passed from
+                        the init system must be spared. Since
+                        new-style init systems guarantee that no
+                        left-over file descriptors are passed to
+                        executed processes, it might be a good choice
+                        to simply skip the closing of all remaining
+                        open file descriptors if sockets are
+                        passed.</para></listitem>
+
+                        <listitem><para>Write and install a systemd
+                        unit file for the service (and the sockets if
+                        socket-based activation is used, as well as a
+                        path unit file, if the daemon processes a
+                        spool directory), see above for
+                        details.</para></listitem>
+
+                        <listitem><para>If the daemon exposes
+                        interfaces via D-Bus, write and install a
+                        D-Bus activation file for the service, see
+                        above for details.</para></listitem>
+                </orderedlist>
+        </refsect1>
+
+        <refsect1>
+                <title>See Also</title>
+                <para>
+                        <citerefentry><refentrytitle>systemd</refentrytitle><manvolnum>1</manvolnum></citerefentry>,
+                        <citerefentry><refentrytitle>sd-daemon</refentrytitle><manvolnum>7</manvolnum></citerefentry>,
+                        <citerefentry><refentrytitle>sd_listen_fds</refentrytitle><manvolnum>3</manvolnum></citerefentry>,
+                        <citerefentry><refentrytitle>sd_notify</refentrytitle><manvolnum>3</manvolnum></citerefentry>,
+                        <citerefentry><refentrytitle>daemon</refentrytitle><manvolnum>3</manvolnum></citerefentry>,
+                        <citerefentry><refentrytitle>systemd.service</refentrytitle><manvolnum>5</manvolnum></citerefentry>
+                </para>
+        </refsect1>
+
+</refentry>
diff --git a/man/halt.xml b/man/halt.xml
new file mode 100644 (file)
index 0000000..97a53ba
--- /dev/null
@@ -0,0 +1,181 @@
+<?xml version='1.0'?> <!--*-nxml-*-->
+<!DOCTYPE refentry PUBLIC "-//OASIS//DTD DocBook XML V4.2//EN"
+        "http://www.oasis-open.org/docbook/xml/4.2/docbookx.dtd">
+
+<!--
+  This file is part 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 <http://www.gnu.org/licenses/>.
+-->
+
+<refentry id="halt">
+
+        <refentryinfo>
+                <title>halt</title>
+                <productname>systemd</productname>
+
+                <authorgroup>
+                        <author>
+                                <contrib>Developer</contrib>
+                                <firstname>Lennart</firstname>
+                                <surname>Poettering</surname>
+                                <email>lennart@poettering.net</email>
+                        </author>
+                </authorgroup>
+        </refentryinfo>
+
+        <refmeta>
+                <refentrytitle>halt</refentrytitle>
+                <manvolnum>8</manvolnum>
+        </refmeta>
+
+        <refnamediv>
+                <refname>halt</refname>
+                <refname>poweroff</refname>
+                <refname>reboot</refname>
+                <refpurpose>Halt, power-off or reboot the machine</refpurpose>
+        </refnamediv>
+
+        <refsynopsisdiv>
+                <cmdsynopsis>
+                        <command>halt <arg choice="opt" rep="repeat">OPTIONS</arg></command>
+                </cmdsynopsis>
+                <cmdsynopsis>
+                        <command>poweroff <arg choice="opt" rep="repeat">OPTIONS</arg></command>
+                </cmdsynopsis>
+                <cmdsynopsis>
+                        <command>reboot <arg choice="opt" rep="repeat">OPTIONS</arg></command>
+                </cmdsynopsis>
+        </refsynopsisdiv>
+
+        <refsect1>
+                <title>Description</title>
+
+                <para><command>halt</command>,
+                <command>poweroff</command>, <command>reboot</command>
+                may be used to halt, power-off or reboot the
+                machine.</para>
+
+        </refsect1>
+
+        <refsect1>
+                <title>Options</title>
+
+                <para>The following options are understood:</para>
+
+                <variablelist>
+                        <varlistentry>
+                                <term><option>--help</option></term>
+
+                                <listitem><para>Prints a short help
+                                text and exits.</para></listitem>
+                        </varlistentry>
+
+                        <varlistentry>
+                                <term><option>--halt</option></term>
+
+                                <listitem><para>Halt the machine,
+                                regardless which one of the three
+                                commands is invoked.</para></listitem>
+                        </varlistentry>
+
+                        <varlistentry>
+                                <term><option>-p</option></term>
+                                <term><option>--poweroff</option></term>
+
+                                <listitem><para>Power-off the machine,
+                                regardless which one of the three
+                                commands is invoked.</para></listitem>
+                        </varlistentry>
+
+                        <varlistentry>
+                                <term><option>--reboot</option></term>
+
+                                <listitem><para>Reboot the machine,
+                                regardless which one of the three
+                                commands is invoked.</para></listitem>
+                        </varlistentry>
+
+                        <varlistentry>
+                                <term><option>-f</option></term>
+                                <term><option>--force</option></term>
+
+                                <listitem><para>Force immediate halt,
+                                power-off, reboot. Don't contact the
+                                init system.</para></listitem>
+                        </varlistentry>
+
+                        <varlistentry>
+                                <term><option>-w</option></term>
+                                <term><option>--wtmp-only</option></term>
+
+                                <listitem><para>Only write wtmp
+                                shutdown entry, don't actually halt,
+                                power-off, reboot.</para></listitem>
+                        </varlistentry>
+
+                        <varlistentry>
+                                <term><option>-d</option></term>
+                                <term><option>--no-wtmp</option></term>
+
+                                <listitem><para>Don't write wtmp
+                                shutdown entry.</para></listitem>
+                        </varlistentry>
+
+                        <varlistentry>
+                                <term><option>-n</option></term>
+                                <term><option>--no-sync</option></term>
+
+                                <listitem><para>Don't sync hard disks/storage media before
+                                halt, power-off,
+                                reboot.</para></listitem>
+                        </varlistentry>
+
+                        <varlistentry>
+                                <term><option>--no-wall</option></term>
+
+                                <listitem><para>Don't send wall
+                                message before
+                                halt, power-off, reboot.</para></listitem>
+                        </varlistentry>
+                </variablelist>
+        </refsect1>
+
+        <refsect1>
+                <title>Exit status</title>
+
+                <para>On success 0 is returned, a non-zero failure
+                code otherwise.</para>
+        </refsect1>
+
+        <refsect1>
+                <title>Notes</title>
+
+                <para>These are legacy commands available for
+                compatibility only.</para>
+        </refsect1>
+
+        <refsect1>
+                <title>See Also</title>
+                <para>
+                        <citerefentry><refentrytitle>systemd</refentrytitle><manvolnum>1</manvolnum></citerefentry>,
+                        <citerefentry><refentrytitle>systemctl</refentrytitle><manvolnum>1</manvolnum></citerefentry>,
+                        <citerefentry><refentrytitle>shutdown</refentrytitle><manvolnum>8</manvolnum></citerefentry>,
+                        <citerefentry><refentrytitle>wall</refentrytitle><manvolnum>1</manvolnum></citerefentry>
+                </para>
+        </refsect1>
+
+</refentry>
diff --git a/man/hostname.xml b/man/hostname.xml
new file mode 100644 (file)
index 0000000..1acda1a
--- /dev/null
@@ -0,0 +1,95 @@
+<?xml version='1.0'?> <!--*-nxml-*-->
+<?xml-stylesheet type="text/xsl" href="http://docbook.sourceforge.net/release/xsl/current/xhtml/docbook.xsl"?>
+<!DOCTYPE refentry PUBLIC "-//OASIS//DTD DocBook XML V4.2//EN"
+        "http://www.oasis-open.org/docbook/xml/4.2/docbookx.dtd">
+
+<!--
+  This file is part 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 <http://www.gnu.org/licenses/>.
+-->
+
+<refentry id="hostname">
+        <refentryinfo>
+                <title>/etc/hostname</title>
+                <productname>systemd</productname>
+
+                <authorgroup>
+                        <author>
+                                <contrib>Developer</contrib>
+                                <firstname>Lennart</firstname>
+                                <surname>Poettering</surname>
+                                <email>lennart@poettering.net</email>
+                        </author>
+                </authorgroup>
+        </refentryinfo>
+
+        <refmeta>
+                <refentrytitle>hostname</refentrytitle>
+                <manvolnum>5</manvolnum>
+        </refmeta>
+
+        <refnamediv>
+                <refname>hostname</refname>
+                <refpurpose>Local host name configuration file</refpurpose>
+        </refnamediv>
+
+        <refsynopsisdiv>
+                <para><filename>/etc/hostname</filename></para>
+        </refsynopsisdiv>
+
+        <refsect1>
+                <title>Description</title>
+
+                <para>The <filename>/etc/hostname</filename> file
+                configures the name of the local system that is set
+                during boot, with the
+                <citerefentry><refentrytitle>sethostname</refentrytitle><manvolnum>2</manvolnum></citerefentry>
+                system call. It should contain a single
+                newline-terminated host name string. The
+                host name may be a free-form string up to 64 characters
+                in length, however it is recommended that it consists
+                only of 7bit ASCII lower-case characters and no spaces or dots,
+                and limits itself to the format allowed for DNS domain
+                name labels, even though this is not a
+                strict requirement.</para>
+
+                <para>Depending on the operating system other
+                configuration files might be checked for configuration
+                of the host name as well, however only as fallback.</para>
+        </refsect1>
+
+        <refsect1>
+                <title>History</title>
+
+                <para>The simple configuration file format of
+                <filename>/etc/hostname</filename> originates from
+                Debian GNU/Linux.</para>
+        </refsect1>
+
+        <refsect1>
+                  <title>See Also</title>
+                  <para>
+                          <citerefentry><refentrytitle>systemd</refentrytitle><manvolnum>1</manvolnum></citerefentry>,
+                          <citerefentry><refentrytitle>sethostname</refentrytitle><manvolnum>2</manvolnum></citerefentry>,
+                          <citerefentry><refentrytitle>hostname</refentrytitle><manvolnum>1</manvolnum></citerefentry>,
+                          <citerefentry><refentrytitle>hostname</refentrytitle><manvolnum>7</manvolnum></citerefentry>,
+                          <citerefentry><refentrytitle>machine-id</refentrytitle><manvolnum>5</manvolnum></citerefentry>,
+                          <citerefentry><refentrytitle>machine-info</refentrytitle><manvolnum>5</manvolnum></citerefentry>
+                  </para>
+        </refsect1>
+
+</refentry>
diff --git a/man/journalctl.xml b/man/journalctl.xml
new file mode 100644 (file)
index 0000000..f6e46cf
--- /dev/null
@@ -0,0 +1,260 @@
+<?xml version='1.0'?> <!--*-nxml-*-->
+<!DOCTYPE refentry PUBLIC "-//OASIS//DTD DocBook XML V4.2//EN"
+        "http://www.oasis-open.org/docbook/xml/4.2/docbookx.dtd">
+
+<!--
+  This file is part of systemd.
+
+  Copyright 2012 Lennart Poettering
+
+  systemd is free software; you can redistribute it and/or modify it
+  under the terms of the GNU 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 <http://www.gnu.org/licenses/>.
+-->
+
+<refentry id="journalctl">
+
+        <refentryinfo>
+                <title>journalctl</title>
+                <productname>systemd</productname>
+
+                <authorgroup>
+                        <author>
+                                <contrib>Developer</contrib>
+                                <firstname>Lennart</firstname>
+                                <surname>Poettering</surname>
+                                <email>lennart@poettering.net</email>
+                        </author>
+                </authorgroup>
+        </refentryinfo>
+
+        <refmeta>
+                <refentrytitle>journalctl</refentrytitle>
+                <manvolnum>1</manvolnum>
+        </refmeta>
+
+        <refnamediv>
+                <refname>journalctl</refname>
+                <refpurpose>Query the systemd journal</refpurpose>
+        </refnamediv>
+
+        <refsynopsisdiv>
+                <cmdsynopsis>
+                        <command>journalctl <arg choice="opt" rep="repeat">OPTIONS</arg> <arg choice="opt">MATCH</arg></command>
+                </cmdsynopsis>
+        </refsynopsisdiv>
+
+        <refsect1>
+                <title>Description</title>
+
+                <para><command>journalctl</command> may be
+                used to query the contents of the
+                <citerefentry><refentrytitle>systemd</refentrytitle><manvolnum>1</manvolnum></citerefentry>
+                journal.</para>
+
+                <para>If called without parameter will show the full
+                contents of the journal, starting with the oldest
+                entry collected.</para>
+
+                <para>If a match argument is passed the output is
+                filtered accordingly. A match is in the format
+                <literal>FIELD=VALUE</literal>,
+                e.g. <literal>_SYSTEMD_UNIT=httpd.service</literal>.</para>
+
+                <para>Output is interleaved from all accessible
+                journal files, whether they are rotated or currently
+                being written, and regardless whether they belong to the
+                system itself or are accessible user journals.</para>
+
+                <para>All users are granted access to their private
+                per-user journals. However, by default only root and
+                users who are members of the <literal>adm</literal>
+                group get access to the system journal and the
+                journals of other users.</para>
+        </refsect1>
+
+        <refsect1>
+                <title>Options</title>
+
+                <para>The following options are understood:</para>
+
+                <variablelist>
+                        <varlistentry>
+                                <term><option>--help</option></term>
+                                <term><option>-h</option></term>
+
+                                <listitem><para>Prints a short help
+                                text and exits.</para></listitem>
+                        </varlistentry>
+
+                        <varlistentry>
+                                <term><option>--version</option></term>
+
+                                <listitem><para>Prints a short version
+                                string and exits.</para></listitem>
+                        </varlistentry>
+
+                        <varlistentry>
+                                <term><option>--no-pager</option></term>
+
+                               <listitem><para>Do not pipe output into a
+                               pager.</para></listitem>
+                       </varlistentry>
+
+                        <varlistentry>
+                                <term><option>--all</option></term>
+                                <term><option>-a</option></term>
+
+                                <listitem><para>Show all fields in
+                                full, even if they include unprintable
+                                characters or are very
+                                long.</para></listitem>
+                        </varlistentry>
+
+                        <varlistentry>
+                                <term><option>--follow</option></term>
+                                <term><option>-f</option></term>
+
+                                <listitem><para>Show only most recent
+                                journal entries, and continously print
+                                new entries as they are appended to
+                                the journal.</para></listitem>
+                        </varlistentry>
+
+                        <varlistentry>
+                                <term><option>--lines=</option></term>
+                                <term><option>-n</option></term>
+
+                                <listitem><para>Controls the number of
+                                journal lines to show, counting from
+                                the most recent ones. Takes a positive
+                                integer argument. In follow mode
+                                defaults to 10, otherwise is unset
+                                thus not limiting how many lines are
+                                shown.</para></listitem>
+                        </varlistentry>
+
+                        <varlistentry>
+                                <term><option>--no-tail</option></term>
+
+                                <listitem><para>Show all stored output
+                                lines, even in follow mode. Undoes the
+                                effect of
+                                <option>--lines=</option>.</para></listitem>
+                        </varlistentry>
+
+                        <varlistentry>
+                                <term><option>--output=</option></term>
+                                <term><option>-o</option></term>
+
+                                <listitem><para>Controls the
+                                formatting of the journal entries that are
+                                shown. Takes one of
+                                <literal>short</literal>,
+                                <literal>short-monotonic</literal>,
+                                <literal>verbose</literal>,
+                                <literal>export</literal>,
+                                <literal>json</literal>,
+                                <literal>cat</literal>. <literal>short</literal>
+                                is the default and generates an output
+                                that is mostly identical to the
+                                formatting of classic syslog log
+                                files, showing one line per journal
+                                entry. <literal>short-monotonic</literal>
+                                is very similar but shows monotonic
+                                timestamps instead of wallclock
+                                timestamps. <literal>verbose</literal>
+                                shows the full structered entry items
+                                with all
+                                fiels. <literal>export</literal>
+                                serializes the journal into a binary
+                                (but mostly text-based) stream
+                                suitable for backups and network
+                                transfer. <literal>json</literal>
+                                formats entries as JSON data
+                                structures. <literal>cat</literal>
+                                generates a very terse output only
+                                showing the actual message of each
+                                journal entry with no meta data, not
+                                even a timestamp.</para></listitem>
+                        </varlistentry>
+
+                        <varlistentry>
+                                <term><option>--quiet</option></term>
+                                <term><option>-q</option></term>
+
+                                <listitem><para>Suppresses any warning
+                                message regarding inaccessable system
+                                journals when run as normal
+                                user.</para></listitem>
+                        </varlistentry>
+
+                        <varlistentry>
+                                <term><option>--local</option></term>
+                                <term><option>-l</option></term>
+
+                                <listitem><para>Show only locally
+                                generated messages.</para></listitem>
+                        </varlistentry>
+
+                        <varlistentry>
+                                <term><option>--new-id128</option></term>
+
+                                <listitem><para>Instead of showing
+                                journal contents generate a new 128
+                                bit ID suitable for identifying
+                                messages. This is intended for usage
+                                by developers who need a new
+                                identifier for a new message they
+                                introduce and want to make
+                                recognizable. Will print the new ID in
+                                three different formats which can be
+                                copied into source code or
+                                similar.</para></listitem>
+                        </varlistentry>
+
+                </variablelist>
+        </refsect1>
+
+        <refsect1>
+                <title>Exit status</title>
+
+                <para>On success 0 is returned, a non-zero failure
+                code otherwise.</para>
+        </refsect1>
+
+        <refsect1>
+                <title>Environment</title>
+
+                <variablelist>
+                        <varlistentry>
+                                <term><varname>$SYSTEMD_PAGER</varname></term>
+                                <listitem><para>Pager to use when
+                                <option>--no-pager</option> is not given;
+                                overrides <varname>$PAGER</varname>.  Setting
+                                this to an empty string or the value
+                                <literal>cat</literal> is equivalent to passing
+                                <option>--no-pager</option>.</para></listitem>
+                        </varlistentry>
+                </variablelist>
+        </refsect1>
+
+        <refsect1>
+                <title>See Also</title>
+                <para>
+                        <citerefentry><refentrytitle>systemd</refentrytitle><manvolnum>1</manvolnum></citerefentry>,
+                        <citerefentry><refentrytitle>systemctl</refentrytitle><manvolnum>1</manvolnum></citerefentry>,
+                        <citerefentry><refentrytitle>journald.conf</refentrytitle><manvolnum>5</manvolnum></citerefentry>
+                </para>
+        </refsect1>
+
+</refentry>
diff --git a/man/journald.conf.xml b/man/journald.conf.xml
new file mode 100644 (file)
index 0000000..a9b0f66
--- /dev/null
@@ -0,0 +1,254 @@
+<?xml version='1.0'?> <!--*-nxml-*-->
+<?xml-stylesheet type="text/xsl" href="http://docbook.sourceforge.net/release/xsl/current/xhtml/docbook.xsl"?>
+<!DOCTYPE refentry PUBLIC "-//OASIS//DTD DocBook XML V4.2//EN"
+        "http://www.oasis-open.org/docbook/xml/4.2/docbookx.dtd">
+
+<!--
+  This file is part 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 <http://www.gnu.org/licenses/>.
+-->
+
+<refentry id="journald.conf">
+        <refentryinfo>
+                <title>journald.conf</title>
+                <productname>systemd</productname>
+
+                <authorgroup>
+                        <author>
+                                <contrib>Developer</contrib>
+                                <firstname>Lennart</firstname>
+                                <surname>Poettering</surname>
+                                <email>lennart@poettering.net</email>
+                        </author>
+                </authorgroup>
+        </refentryinfo>
+
+        <refmeta>
+                <refentrytitle>journald.conf</refentrytitle>
+                <manvolnum>5</manvolnum>
+        </refmeta>
+
+        <refnamediv>
+                <refname>journald.conf</refname>
+                <refpurpose>Journal service configuration file</refpurpose>
+        </refnamediv>
+
+        <refsynopsisdiv>
+                <para><filename>journald.conf</filename></para>
+        </refsynopsisdiv>
+
+        <refsect1>
+                <title>Description</title>
+
+                <para>This files configures various parameters of the systemd journal service.</para>
+
+        </refsect1>
+
+        <refsect1>
+                <title>Options</title>
+
+                <para>All options are configured in the
+                <literal>[Journal]</literal> section:</para>
+
+                <variablelist>
+
+                        <varlistentry>
+                                <term><varname>Compress=</varname></term>
+
+                                <listitem><para>Takes a boolean
+                                value. If enabled (the default) data
+                                objects that shall be stored in the
+                                journal and are larger than a certain
+                                threshold are compressed with the XZ
+                                compression algorithm before they are
+                                written to the file
+                                system.</para></listitem>
+                        </varlistentry>
+
+                        <varlistentry>
+                                <term><varname>RateLimitInterval=</varname></term>
+                                <term><varname>RateLimitBurst=</varname></term>
+
+                                <listitem><para>Configures the rate
+                                limiting that is applied to all
+                                messages generated on the system. If
+                                in the time interval defined by
+                                <varname>RateLimitInterval=</varname>
+                                more messages than specified in
+                                <varname>RateLimitBurst=</varname> are
+                                logged by a service all further
+                                messages within the interval are
+                                dropped, until the interval is over. A
+                                message about the number of dropped
+                                messages is generated. This rate
+                                limiting is applied per-service, so
+                                that two services which log do not
+                                interfere with each other's
+                                limit. Defaults to 100 messages in
+                                10s. The time specification for
+                                <varname>RateLimitInterval=</varname>
+                                may be specified in the following
+                                units: <literal>s</literal>,
+                                <literal>min</literal>,
+                                <literal>h</literal>,
+                                <literal>ms</literal>,
+                                <literal>us</literal>. To turn off any
+                                kind of rate limiting, set either
+                                value to 0.</para></listitem>
+                        </varlistentry>
+
+                        <varlistentry>
+                                <term><varname>SystemMaxUse=</varname></term>
+                                <term><varname>SystemKeepFree=</varname></term>
+                                <term><varname>SystemMaxFileSize=</varname></term>
+                                <term><varname>SystemMinFileSize=</varname></term>
+                                <term><varname>RuntimeMaxUse=</varname></term>
+                                <term><varname>RuntimeKeepFree=</varname></term>
+                                <term><varname>RuntimeMaxFileSize=</varname></term>
+                                <term><varname>RuntimeMinFileSize=</varname></term>
+
+                                <listitem><para>Enforce size limits on
+                                the journal files stored. The options
+                                prefixed with
+                                <literal>System</literal> apply to the
+                                journal files when stored on a
+                                persistant file system, more
+                                specifically
+                                <filename>/var/log/journal</filename>. The
+                                options prefixed with
+                                <literal>Runtime</literal> apply to
+                                the journal files when stored on a
+                                volatile in-memory file system, more
+                                specifically
+                                <filename>/run/log/journal</filename>. The
+                                former is used only when
+                                <filename>/var</filename> is mounted,
+                                writable and the directory
+                                <filename>/var/log/journal</filename>
+                                exists. Otherwise only the latter
+                                applies. Note that this means that
+                                during early boot and if the
+                                administrator disabled persistant
+                                logging only the latter options apply,
+                                while the former apply if persistant
+                                logging is enabled and the system is
+                                fully booted
+                                up. <varname>SystemMaxUse=</varname>
+                                and <varname>RuntimeMaxUse=</varname>
+                                control how much disk space the
+                                journal may use up at
+                                maximum. Defaults to 10% of the size
+                                of the respective file
+                                system. <varname>SystemKeepFree=</varname>
+                                and
+                                <varname>RuntimeKeepFree=</varname>
+                                control how much disk space the
+                                journal shall always leave free for
+                                other uses if less than the disk space
+                                configured in
+                                <varname>SystemMaxUse=</varname> and
+                                <varname>RuntimeMaxUse=</varname> is
+                                available. Defaults to 5% of the size
+                                of the respective file
+                                system. <varname>SystemMaxFileSize=</varname>
+                                and
+                                <varname>RuntimeMaxFileSize=</varname>
+                                control how large individual journal
+                                files may grow at maximum. This
+                                influences the granularity in which
+                                disk space is made available through
+                                rotation, i.e. deletion of historic
+                                data. Defaults to one eigth of the
+                                values configured with
+                                <varname>SystemMaxUse=</varname> and
+                                <varname>RuntimeMaxUse=</varname>, so
+                                that usually seven rotated journal
+                                files are kept as
+                                history. <varname>SystemMinFileSize=</varname>
+                                and
+                                <varname>RuntimeMinFileSize=</varname>
+                                control how large individual journal
+                                files grow at minimum. Defaults to
+                                64K. Specify values in bytes or use
+                                K, M, G, T, P, E as units for the
+                                specified sizes. Note that size limits
+                                are enforced synchronously to journal
+                                files as they are extended, and need
+                                no explicit rotation step triggered by
+                                time.</para></listitem>
+                        </varlistentry>
+
+                        <varlistentry>
+                                <term><varname>ForwardToSyslog=</varname></term>
+                                <term><varname>ForwardToKMsg=</varname></term>
+                                <term><varname>ForwardToConsole=</varname></term>
+
+                                <listitem><para>Control whether log
+                                messages received by the journal
+                                daemon shall be forwarded to a
+                                traditional syslog daemon, to the
+                                kernel log buffer (kmsg), or to the
+                                system console. These options take
+                                boolean arguments. If forwarding to
+                                syslog is enabled but no syslog daemon
+                                is running the respective option has
+                                no effect. By default only forwarding
+                                to syslog is enabled. These settings
+                                may be overriden at boot time with the
+                                kernel command line options
+                                <literal>systemd_journald.forward_to_syslog=</literal>,
+                                <literal>systemd_journald.forward_to_kmsg=</literal>
+                                and
+                                <literal>systemd_journald.forward_to_console=</literal>. If
+                                forwarding to the kernel log buffer and
+                                <varname>ImportKernel=</varname> is
+                                enabled at the same time care is taken
+                                to avoid logging loops. It is safe to
+                                use these options in combination.
+                                </para></listitem>
+                        </varlistentry>
+
+                        <varlistentry>
+                                <term><varname>ImportKernel=</varname></term>
+
+                                <listitem><para>Controls whether
+                                kernel log messages shall be stored in
+                                the journal. Takes a boolean argument
+                                and defaults to enabled. Note that
+                                currently only one userspace service
+                                can read kernel messages at a time,
+                                which means that kernel log message
+                                reading might get corrupted if it
+                                is enabled in more than one service,
+                                for example in both the journal and a
+                                traditional syslog service.
+                                </para></listitem>
+                        </varlistentry>
+                </variablelist>
+
+        </refsect1>
+
+        <refsect1>
+                  <title>See Also</title>
+                  <para>
+                          <citerefentry><refentrytitle>systemd</refentrytitle><manvolnum>1</manvolnum></citerefentry>,
+                          <citerefentry><refentrytitle>journalctl</refentrytitle><manvolnum>1</manvolnum></citerefentry>,
+                          <citerefentry><refentrytitle>systemd.conf</refentrytitle><manvolnum>5</manvolnum></citerefentry>
+                  </para>
+        </refsect1>
+
+</refentry>
diff --git a/man/locale.conf.xml b/man/locale.conf.xml
new file mode 100644 (file)
index 0000000..3723997
--- /dev/null
@@ -0,0 +1,146 @@
+<?xml version='1.0'?> <!--*-nxml-*-->
+<?xml-stylesheet type="text/xsl" href="http://docbook.sourceforge.net/release/xsl/current/xhtml/docbook.xsl"?>
+<!DOCTYPE refentry PUBLIC "-//OASIS//DTD DocBook XML V4.2//EN"
+        "http://www.oasis-open.org/docbook/xml/4.2/docbookx.dtd">
+
+<!--
+  This file is part 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 <http://www.gnu.org/licenses/>.
+-->
+
+<refentry id="locale.conf">
+        <refentryinfo>
+                <title>locale.conf</title>
+                <productname>systemd</productname>
+
+                <authorgroup>
+                        <author>
+                                <contrib>Developer</contrib>
+                                <firstname>Lennart</firstname>
+                                <surname>Poettering</surname>
+                                <email>lennart@poettering.net</email>
+                        </author>
+                </authorgroup>
+        </refentryinfo>
+
+        <refmeta>
+                <refentrytitle>locale.conf</refentrytitle>
+                <manvolnum>5</manvolnum>
+        </refmeta>
+
+        <refnamediv>
+                <refname>locale.conf</refname>
+                <refpurpose>configuration file for locale settings</refpurpose>
+        </refnamediv>
+
+        <refsynopsisdiv>
+                <para><filename>/etc/locale.conf</filename></para>
+        </refsynopsisdiv>
+
+        <refsect1>
+                <title>Description</title>
+
+                <para>The <filename>/etc/locale.conf</filename> file
+                configures system-wide locale settings.</para>
+
+                <para>The basic file format of
+                <filename>locale.conf</filename> is a
+                newline-separated list of environment-like
+                shell-compatible variable assignments. It is possible
+                to source the configuration from shell scripts,
+                however, beyond mere variable assignments no shell
+                features are supported, allowing applications to read
+                the file without implementing a shell compatible
+                execution engine.</para>
+
+                <para>Note that the kernel command line options
+                <varname>locale.LANG=</varname>,
+                <varname>locale.LANGUAGE=</varname>,
+                <varname>locale.LC_CTYPE=</varname>,
+                <varname>locale.LC_NUMERIC=</varname>,
+                <varname>locale.LC_TIME=</varname>,
+                <varname>locale.LC_COLLATE=</varname>,
+                <varname>locale.LC_MONETARY=</varname>,
+                <varname>locale.LC_MESSAGES=</varname>,
+                <varname>locale.LC_PAPER=</varname>,
+                <varname>locale.LC_NAME=</varname>,
+                <varname>locale.LC_ADDRESS=</varname>,
+                <varname>locale.LC_TELEPHONE=</varname>,
+                <varname>locale.LC_MEASUREMENT=</varname>,
+                <varname>locale.LC_IDENTIFICATION=</varname> may be
+                used to override the locale settings at boot.</para>
+
+                <para>The locale settings configured in
+                <filename>/etc/locale.conf</filename> are system-wide
+                and are inherited by every service or user, unless
+                overridden or unset by individual programs or
+                individual users.</para>
+
+                <para>Depending on the operating system other
+                configuration files might be checked for locale
+                configuration as well, however only as
+                fallback.</para>
+        </refsect1>
+
+        <refsect1>
+                <title>Options</title>
+
+                <para>The following locale settings may be set using
+                <filename>/etc/locale.conf</filename>:
+                <varname>LANG=</varname>,
+                <varname>LANGUAGE=</varname>,
+                <varname>LC_CTYPE=</varname>,
+                <varname>LC_NUMERIC=</varname>,
+                <varname>LC_TIME=</varname>,
+                <varname>LC_COLLATE=</varname>,
+                <varname>LC_MONETARY=</varname>,
+                <varname>LC_MESSAGES=</varname>,
+                <varname>LC_PAPER=</varname>,
+                <varname>LC_NAME=</varname>,
+                <varname>LC_ADDRESS=</varname>,
+                <varname>LC_TELEPHONE=</varname>,
+                <varname>LC_MEASUREMENT=</varname>,
+                <varname>LC_IDENTIFICATION=</varname>. Note that
+                <varname>LC_ALL</varname> may not be be configured in
+                this file. For details about the meaning and semantics
+                of these settings, refer to
+                <citerefentry><refentrytitle>locale</refentrytitle><manvolnum>7</manvolnum></citerefentry>.</para>
+        </refsect1>
+
+        <refsect1>
+                <title>Example</title>
+
+                <example>
+                        <title>German locale with English messages</title>
+
+                        <para><filename>/etc/locale.conf:</filename></para>
+
+                        <programlisting>LANG=de_DE.UTF-8
+LC_MESSAGES=C</programlisting>
+                </example>
+
+        </refsect1>
+
+        <refsect1>
+                  <title>See Also</title>
+                  <para>
+                          <citerefentry><refentrytitle>systemd</refentrytitle><manvolnum>1</manvolnum></citerefentry>,
+                          <citerefentry><refentrytitle>locale</refentrytitle><manvolnum>7</manvolnum></citerefentry>
+                  </para>
+        </refsect1>
+
+</refentry>
diff --git a/man/loginctl.xml b/man/loginctl.xml
new file mode 100644 (file)
index 0000000..af1d631
--- /dev/null
@@ -0,0 +1,457 @@
+<?xml version='1.0'?> <!--*-nxml-*-->
+<!DOCTYPE refentry PUBLIC "-//OASIS//DTD DocBook XML V4.2//EN"
+        "http://www.oasis-open.org/docbook/xml/4.2/docbookx.dtd">
+
+<!--
+  This file is part 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 <http://www.gnu.org/licenses/>.
+-->
+
+<refentry id="loginctl">
+
+        <refentryinfo>
+                <title>loginctl</title>
+                <productname>systemd</productname>
+
+                <authorgroup>
+                        <author>
+                                <contrib>Developer</contrib>
+                                <firstname>Lennart</firstname>
+                                <surname>Poettering</surname>
+                                <email>lennart@poettering.net</email>
+                        </author>
+                </authorgroup>
+        </refentryinfo>
+
+        <refmeta>
+                <refentrytitle>loginctl</refentrytitle>
+                <manvolnum>1</manvolnum>
+        </refmeta>
+
+        <refnamediv>
+                <refname>loginctl</refname>
+                <refpurpose>Control the systemd login manager</refpurpose>
+        </refnamediv>
+
+        <refsynopsisdiv>
+                <cmdsynopsis>
+                        <command>loginctl <arg choice="opt" rep="repeat">OPTIONS</arg> <arg choice="req">COMMAND</arg> <arg choice="opt" rep="repeat">NAME</arg></command>
+                </cmdsynopsis>
+        </refsynopsisdiv>
+
+        <refsect1>
+                <title>Description</title>
+
+                <para><command>loginctl</command> may be used to
+                introspect and control the state of the
+                <citerefentry><refentrytitle>systemd</refentrytitle><manvolnum>1</manvolnum></citerefentry>
+                login manager.</para>
+        </refsect1>
+
+        <refsect1>
+                <title>Options</title>
+
+                <para>The following options are understood:</para>
+
+                <variablelist>
+                        <varlistentry>
+                                <term><option>--help</option></term>
+                                <term><option>-h</option></term>
+
+                                <listitem><para>Prints a short help
+                                text and exits.</para></listitem>
+                        </varlistentry>
+
+                        <varlistentry>
+                                <term><option>--version</option></term>
+
+                                <listitem><para>Prints a short version
+                                string and exits.</para></listitem>
+                        </varlistentry>
+
+                        <varlistentry>
+                                <term><option>--property=</option></term>
+                                <term><option>-p</option></term>
+
+                                <listitem><para>When showing
+                                session/user/ properties, limit
+                                display to certain properties as
+                                specified as argument. If not
+                                specified all set properties are
+                                shown. The argument should be a
+                                property name, such as
+                                <literal>Sessions</literal>. If
+                                specified more than once all
+                                properties with the specified names
+                                are shown.</para></listitem>
+                        </varlistentry>
+
+                        <varlistentry>
+                                <term><option>--all</option></term>
+                                <term><option>-a</option></term>
+
+                                <listitem><para>When showing
+                                unit/job/manager properties, show all
+                                properties regardless whether they are
+                                set or not.</para></listitem>
+                        </varlistentry>
+
+
+                        <varlistentry>
+                                <term><option>--no-pager</option></term>
+
+                               <listitem><para>Do not pipe output into a
+                               pager.</para></listitem>
+                       </varlistentry>
+
+                        <varlistentry>
+                                <term><option>--kill-who=</option></term>
+
+                                <listitem><para>When used with
+                                <command>kill-session</command>,
+                                choose which processes to kill. Must
+                                be one of <option>leader</option>, or
+                                <option>all</option> to select whether
+                                to kill only the leader process of the
+                                session or all processes of the
+                                session. If omitted defaults to
+                                <option>all</option>.</para></listitem>
+                        </varlistentry>
+
+                        <varlistentry>
+                                <term><option>--signal=</option></term>
+                                <term><option>-s</option></term>
+
+                                <listitem><para>When used with
+                                <command>kill-session</command> or
+                                <command>kill-user</command>, choose
+                                which signal to send to selected
+                                processes. Must be one of the well
+                                known signal specifiers such as
+                                SIGTERM, SIGINT or SIGSTOP. If omitted
+                                defaults to
+                                <option>SIGTERM</option>.</para></listitem>
+                        </varlistentry>
+
+                        <varlistentry>
+                                <term><option>-H</option></term>
+                                <term><option>--host</option></term>
+
+                                <listitem><para>Execute operation
+                                remotely. Specify a hostname, or
+                                username and hostname separated by @,
+                                to connect to. This will use SSH to
+                                talk to the remote login manager
+                                instance.</para></listitem>
+                        </varlistentry>
+
+                        <varlistentry>
+                                <term><option>-P</option></term>
+                                <term><option>--privileged</option></term>
+
+                                <listitem><para>Acquire privileges via
+                                PolicyKit before executing the
+                                operation.</para></listitem>
+                        </varlistentry>
+                </variablelist>
+
+                <para>The following commands are understood:</para>
+
+                <variablelist>
+                        <varlistentry>
+                                <term><command>list-sessions</command></term>
+
+                                <listitem><para>List current sessions.</para></listitem>
+                        </varlistentry>
+
+                        <varlistentry>
+                                <term><command>session-status [ID...]</command></term>
+
+                                <listitem><para>Show terse runtime
+                                status information about one or more
+                                sessions. This function is intended to
+                                generate human-readable output. If you
+                                are looking for computer-parsable
+                                output, use
+                                <command>show-session</command>
+                                instead.</para></listitem>
+                        </varlistentry>
+
+                        <varlistentry>
+                                <term><command>show-session [ID...]</command></term>
+
+                                <listitem><para>Show properties of one
+                                or more sessions or the manager
+                                itself. If no argument is specified
+                                properties of the manager will be
+                                shown. If a session ID is specified
+                                properties of the session is shown. By
+                                default, empty properties are
+                                suppressed. Use <option>--all</option>
+                                to show those too. To select specific
+                                properties to show use
+                                <option>--property=</option>. This
+                                command is intended to be used
+                                whenever computer-parsable output is
+                                required. Use
+                                <command>session-status</command> if
+                                you are looking for formatted
+                                human-readable
+                                output.</para></listitem>
+                        </varlistentry>
+
+                        <varlistentry>
+                                <term><command>activate [ID...]</command></term>
+
+                                <listitem><para>Activate one or more
+                                sessions. This brings one or more
+                                sessions into the foreground, if
+                                another session is currently in the
+                                foreground on the respective
+                                seat.</para></listitem>
+                        </varlistentry>
+
+                        <varlistentry>
+                                <term><command>lock-session [ID...]</command></term>
+                                <term><command>unlock-session [ID...]</command></term>
+
+                                <listitem><para>Activates/deactivates
+                                the screen lock on one or more
+                                sessions, if the session supports it.</para></listitem>
+                        </varlistentry>
+
+                        <varlistentry>
+                                <term><command>terminate-session [ID...]</command></term>
+
+                                <listitem><para>Terminates a
+                                session. This kills all processes of
+                                the session and deallocates all
+                                resources attached to the
+                                session.</para></listitem>
+                        </varlistentry>
+
+                        <varlistentry>
+                                <term><command>kill-session [ID...]</command></term>
+
+                                <listitem><para>Send a signal to one
+                                or more processes of the session. Use
+                                <option>--kill-who=</option> to select
+                                which process to kill. Use
+                                <option>--signal=</option> to select
+                                the signal to send.</para></listitem>
+                        </varlistentry>
+
+                        <varlistentry>
+                                <term><command>list-users</command></term>
+
+                                <listitem><para>List currently logged
+                                in users.</para></listitem>
+                        </varlistentry>
+
+                        <varlistentry>
+                                <term><command>user-status [USER...]</command></term>
+
+                                <listitem><para>Show terse runtime
+                                status information about one or more
+                                logged in users. This function is
+                                intended to generate human-readable
+                                output. If you are looking for
+                                computer-parsable output, use
+                                <command>show-user</command>
+                                instead. Users may be specified by
+                                their usernames or numeric user
+                                IDs.</para></listitem>
+                        </varlistentry>
+
+                        <varlistentry>
+                                <term><command>show-user [USER...]</command></term>
+
+                                <listitem><para>Show properties of one
+                                or more users or the manager
+                                itself. If no argument is specified
+                                properties of the manager will be
+                                shown. If a user is specified
+                                properties of the user is shown. By
+                                default, empty properties are
+                                suppressed. Use <option>--all</option>
+                                to show those too. To select specific
+                                properties to show use
+                                <option>--property=</option>. This
+                                command is intended to be used
+                                whenever computer-parsable output is
+                                required. Use
+                                <command>user-status</command> if
+                                you are looking for formatted
+                                human-readable
+                                output.</para></listitem>
+                        </varlistentry>
+
+                        <varlistentry>
+                                <term><command>enable-linger [USER...]</command></term>
+                                <term><command>disable-linger [USER...]</command></term>
+
+                                <listitem><para>Enable/disable user
+                                lingering for one or more users. If
+                                enabled for a specific user a user
+                                manager is spawned for him/her at
+                                boot, and kept around after
+                                logouts. This allows users who aren't
+                                logged in to run long-running
+                                services.</para></listitem>
+                        </varlistentry>
+
+                        <varlistentry>
+                                <term><command>terminate-user [USER...]</command></term>
+
+                                <listitem><para>Terminates all
+                                sessions of a user. This kills all
+                                processes of all sessions of the user
+                                and deallocates all runtime resources
+                                attached to the
+                                user.</para></listitem>
+                        </varlistentry>
+
+                        <varlistentry>
+                                <term><command>kill-user [USER...]</command></term>
+
+                                <listitem><para>Send a signal to all
+                                processes of a user. Use
+                                <option>--signal=</option> to select
+                                the signal to send.</para></listitem>
+                        </varlistentry>
+
+                        <varlistentry>
+                                <term><command>list-seats</command></term>
+
+                                <listitem><para>List currently
+                                available seats on the local
+                                system.</para></listitem>
+                        </varlistentry>
+
+                        <varlistentry>
+                                <term><command>seat-status [NAME...]</command></term>
+
+                                <listitem><para>Show terse runtime
+                                status information about one or more
+                                seats. This function is
+                                intended to generate human-readable
+                                output. If you are looking for
+                                computer-parsable output, use
+                                <command>show-seat</command>
+                                instead.</para></listitem>
+                        </varlistentry>
+
+                        <varlistentry>
+                                <term><command>show-seat [NAME...]</command></term>
+
+                                <listitem><para>Show properties of one
+                                or more seats or the manager
+                                itself. If no argument is specified
+                                properties of the manager will be
+                                shown. If a seat is specified
+                                properties of the seat are shown. By
+                                default, empty properties are
+                                suppressed. Use <option>--all</option>
+                                to show those too. To select specific
+                                properties to show use
+                                <option>--property=</option>. This
+                                command is intended to be used
+                                whenever computer-parsable output is
+                                required. Use
+                                <command>seat-status</command> if you
+                                are looking for formatted
+                                human-readable
+                                output.</para></listitem>
+                        </varlistentry>
+
+                        <varlistentry>
+                                <term><command>attach [NAME] [DEVICE...]</command></term>
+
+                                <listitem><para>Attach one or more
+                                devices to a seat. The devices should
+                                be specified via device paths in the
+                                <filename>/sys</filename> file
+                                system. To create a new seat attach at
+                                least one graphics card to a
+                                previously unused seat names. seat
+                                names may consist only of a-z, A-Z,
+                                0-9, "-" and "_" and must be prefixed
+                                with "seat". To drop assignment of a
+                                device to a specific seat just
+                                reassign it to a different seat, or
+                                use
+                                <command>flush-devices</command>.</para></listitem>
+                        </varlistentry>
+
+                        <varlistentry>
+                                <term><command>flush-devices</command></term>
+
+                                <listitem><para>Removes all device
+                                assignments previously created with
+                                <command>attach</command>. After this
+                                call only automatically generated
+                                seats will remain and all seat
+                                hardware is assigned to
+                                them.</para></listitem>
+                        </varlistentry>
+
+                        <varlistentry>
+                                <term><command>terminate-seat [NAME...]</command></term>
+
+                                <listitem><para>Terminates all
+                                sessions on a seat. This kills all
+                                processes of all sessions on a seat and
+                                deallocates all runtime resources
+                                attached to them.</para></listitem>
+                        </varlistentry>
+                </variablelist>
+
+        </refsect1>
+
+        <refsect1>
+                <title>Exit status</title>
+
+                <para>On success 0 is returned, a non-zero failure
+                code otherwise.</para>
+        </refsect1>
+
+        <refsect1>
+                <title>Environment</title>
+
+                <variablelist>
+                        <varlistentry>
+                                <term><varname>$SYSTEMD_PAGER</varname></term>
+                                <listitem><para>Pager to use when
+                                <option>--no-pager</option> is not given;
+                                overrides <varname>$PAGER</varname>.  Setting
+                                this to an empty string or the value
+                                <literal>cat</literal> is equivalent to passing
+                                <option>--no-pager</option>.</para></listitem>
+                        </varlistentry>
+                </variablelist>
+        </refsect1>
+
+        <refsect1>
+                <title>See Also</title>
+                <para>
+                        <citerefentry><refentrytitle>systemd</refentrytitle><manvolnum>1</manvolnum></citerefentry>,
+                        <citerefentry><refentrytitle>systemctl</refentrytitle><manvolnum>1</manvolnum></citerefentry>,
+                        <citerefentry><refentrytitle>logind.conf</refentrytitle><manvolnum>5</manvolnum></citerefentry>
+                </para>
+        </refsect1>
+
+</refentry>
diff --git a/man/logind.conf.xml b/man/logind.conf.xml
new file mode 100644 (file)
index 0000000..950f81f
--- /dev/null
@@ -0,0 +1,175 @@
+<?xml version='1.0'?> <!--*-nxml-*-->
+<?xml-stylesheet type="text/xsl" href="http://docbook.sourceforge.net/release/xsl/current/xhtml/docbook.xsl"?>
+<!DOCTYPE refentry PUBLIC "-//OASIS//DTD DocBook XML V4.2//EN"
+        "http://www.oasis-open.org/docbook/xml/4.2/docbookx.dtd">
+
+<!--
+  This file is part 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 <http://www.gnu.org/licenses/>.
+-->
+
+<refentry id="logind.conf">
+        <refentryinfo>
+                <title>logind.conf</title>
+                <productname>systemd</productname>
+
+                <authorgroup>
+                        <author>
+                                <contrib>Developer</contrib>
+                                <firstname>Lennart</firstname>
+                                <surname>Poettering</surname>
+                                <email>lennart@poettering.net</email>
+                        </author>
+                </authorgroup>
+        </refentryinfo>
+
+        <refmeta>
+                <refentrytitle>logind.conf</refentrytitle>
+                <manvolnum>5</manvolnum>
+        </refmeta>
+
+        <refnamediv>
+                <refname>logind.conf</refname>
+                <refpurpose>Login manager configuration file</refpurpose>
+        </refnamediv>
+
+        <refsynopsisdiv>
+                <para><filename>logind.conf</filename></para>
+        </refsynopsisdiv>
+
+        <refsect1>
+                <title>Description</title>
+
+                <para>This files configures various parameters of the systemd login manager.</para>
+
+        </refsect1>
+
+        <refsect1>
+                <title>Options</title>
+
+                <para>All options are configured in the
+                <literal>[Login]</literal> section:</para>
+
+                <variablelist>
+
+                        <varlistentry>
+                                <term><varname>NAutoVTs=</varname></term>
+
+                                <listitem><para>Takes a positive
+                                integer. How many virtual terminals to
+                                allocate by default and when switched
+                                to autospawn <literal>autovt</literal>
+                                services on (if they are otherwise
+                                unused). These services are
+                                instantiated from a template of
+                                <filename>autovt@.service</filename>
+                                with the virtual terminal TTY name,
+                                e.g. <filename>autovt@tty4.service</filename>. By
+                                default
+                                <filename>autovt@.service</filename>
+                                is linked to
+                                <filename>getty@.service</filename>,
+                                i.e. login prompts are started
+                                dynamically as the user switches to
+                                unused virtual terminals, and this
+                                parameter hence controls how many
+                                gettys are available on the virtual
+                                terminals. Defaults to 6. When set to
+                                0, automatic spawning of
+                                <literal>autovt</literal> services is
+                                disabled.</para></listitem>
+                        </varlistentry>
+
+                        <varlistentry>
+                                <term><varname>KillUserProcesses=</varname></term>
+
+                                <listitem><para>Takes a boolean
+                                argument. Configures whether the
+                                processes of a user should be killed
+                                when she or he completely logs out (i.e. after
+                                her/his last session ended). Defaults to
+                                <literal>no</literal>.</para></listitem>
+                        </varlistentry>
+
+                        <varlistentry>
+                                <term><varname>KillOnlyUsers=</varname></term>
+                                <term><varname>KillExcludeUsers=</varname></term>
+
+                                <listitem><para>These settings take
+                                space separated lists of user names
+                                that influence the effect of
+                                <varname>KillUserProcesses=</varname>. If
+                                not empty only processes of users
+                                listed in
+                                <varname>KillOnlyUsers</varname> will
+                                be killed when they log out
+                                entirely. Processes of users listed in
+                                <varname>KillExcludeUsers=</varname>
+                                are excluded from being
+                                killed. <varname>KillExcludeUsers=</varname>
+                                defaults to <literal>root</literal>
+                                and takes precedence over
+                                <varname>KillOnlyUsers=</varname>
+                                which defaults to the empty list.</para></listitem>
+                        </varlistentry>
+
+                        <varlistentry>
+                                <term><varname>Controllers=</varname></term>
+                                <term><varname>ResetControllers=</varname></term>
+
+                                <listitem><para>These settings control
+                                the default control group hierarchies
+                                users logging are added to. When
+                                logging in users will get private
+                                control groups in all hierarchies
+                                listed in
+                                <varname>Controllers=</varname> and be
+                                reset to the root control group in all
+                                hierarchies listed in
+                                <varname>ResetControllers=</varname>. <varname>Controllers=</varname>
+                                defaults to the empty list,
+                                <varname>ResetControllers=</varname>
+                                defaults to
+                                <literal>cpu</literal>.</para></listitem>
+                        </varlistentry>
+                </variablelist>
+
+                <para>Note that setting
+                <varname>KillUserProcesses=1</varname> will break tools
+                like
+                <citerefentry><refentrytitle>screen</refentrytitle><manvolnum>1</manvolnum></citerefentry>.</para>
+
+                <para>Note that <varname>KillUserProcesses=1</varname>
+                is a weaker version of
+                <varname>kill-session-processes=1</varname> which may
+                be configured per-service for
+                <citerefentry><refentrytitle>pam_systemd</refentrytitle><manvolnum>8</manvolnum></citerefentry>. The
+                latter kills processes of a session as soon as it
+                ends, the former kills processes as soon as the last
+                session of the user ends.</para>
+        </refsect1>
+
+        <refsect1>
+                  <title>See Also</title>
+                  <para>
+                          <citerefentry><refentrytitle>systemd</refentrytitle><manvolnum>1</manvolnum></citerefentry>,
+                          <citerefentry><refentrytitle>loginctl</refentrytitle><manvolnum>1</manvolnum></citerefentry>,
+                          <citerefentry><refentrytitle>systemd.conf</refentrytitle><manvolnum>5</manvolnum></citerefentry>
+                  </para>
+        </refsect1>
+
+</refentry>
diff --git a/man/machine-id.xml b/man/machine-id.xml
new file mode 100644 (file)
index 0000000..97c622c
--- /dev/null
@@ -0,0 +1,143 @@
+<?xml version='1.0'?> <!--*-nxml-*-->
+<?xml-stylesheet type="text/xsl" href="http://docbook.sourceforge.net/release/xsl/current/xhtml/docbook.xsl"?>
+<!DOCTYPE refentry PUBLIC "-//OASIS//DTD DocBook XML V4.2//EN"
+        "http://www.oasis-open.org/docbook/xml/4.2/docbookx.dtd">
+
+<!--
+  This file is part 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 <http://www.gnu.org/licenses/>.
+-->
+
+<refentry id="machine-id">
+        <refentryinfo>
+                <title>/etc/machine-id</title>
+                <productname>systemd</productname>
+
+                <authorgroup>
+                        <author>
+                                <contrib>Developer</contrib>
+                                <firstname>Lennart</firstname>
+                                <surname>Poettering</surname>
+                                <email>lennart@poettering.net</email>
+                        </author>
+                </authorgroup>
+        </refentryinfo>
+
+        <refmeta>
+                <refentrytitle>machine-id</refentrytitle>
+                <manvolnum>5</manvolnum>
+        </refmeta>
+
+        <refnamediv>
+                <refname>machine-id</refname>
+                <refpurpose>local machine ID configuration file</refpurpose>
+        </refnamediv>
+
+        <refsynopsisdiv>
+                <para><filename>/etc/machine-id</filename></para>
+        </refsynopsisdiv>
+
+        <refsect1>
+                <title>Description</title>
+
+                <para>The <filename>/etc/machine-id</filename> file
+                contains the unique machine id of the local system
+                that is set during installation. The machine ID is a
+                single newline-terminated, hexadecimal, lowercase 32
+                character machine ID string. (When decoded from
+                hexadecimal this corresponds with a 16 byte/128 bit
+                string.)</para>
+
+                <para>The machine ID is usually generated from a
+                random source during system installation and stays
+                constant for all subsequent boots. Optionally, for
+                stateless systems it is generated during runtime at
+                boot if it is found to be empty.</para>
+
+                <para>The machine ID does not change based on user
+                configuration, or when hardware is replaced.</para>
+
+                <para>This machine ID adheres to the same format and
+                logic as the D-Bus machine ID.</para>
+
+                <para>Programs may use this ID to identify the host
+                with a globally unique ID in the network, that does
+                not change even if the local network configuration
+                changes. Due to this and its greater length it is
+                a more useful replacement for the
+                <citerefentry><refentrytitle>gethostid</refentrytitle><manvolnum>3</manvolnum></citerefentry>
+                call POSIX specifies.</para>
+
+                <para>The
+                <citerefentry><refentrytitle>systemd-machine-id-setup</refentrytitle><manvolnum>1</manvolnum></citerefentry>
+                tool may be used by installer tools to initialize the
+                machine ID at install time.</para>
+        </refsect1>
+
+        <refsect1>
+                <title>Relation to OSF UUIDs</title>
+
+                <para>Note that the machine ID historically is not an
+                OSF UUID as defined by <ulink
+                url="http://tools.ietf.org/html/rfc4122">RFC
+                4122</ulink>, nor a Microsoft GUID. Starting with
+                systemd v30 newly generated machine IDs however do
+                qualify as v4 UUIDs.</para>
+
+                <para>In order to maintain compatibility with existing
+                installations, an application requiring a UUID should
+                decode the machine ID, and then apply the following
+                operations to turn it into a valid OSF v4 UUID. With
+                <literal>id</literal> being an unsigned character
+                array:</para>
+
+                <programlisting>/* Set UUID version to 4 --- truly random generation */
+id[6] = (id[6] &amp; 0x0F) | 0x40;
+/* Set the UUID variant to DCE */
+id[8] = (id[8] &amp; 0x3F) | 0x80;</programlisting>
+
+                <para>(This code is inspired by
+                <literal>generate_random_uuid()</literal> of
+                <filename>drivers/char/random.c</filename> from the
+                kernel sources.)</para>
+
+        </refsect1>
+
+        <refsect1>
+                <title>History</title>
+
+                <para>The simple configuration file format of
+                <filename>/etc/machine-id</filename> originates in the
+                <filename>/var/lib/dbus/machine-id</filename> file
+                introduced by D-Bus. In fact this latter file might be a
+                symlink to
+                <varname>/etc/machine-id</varname>.</para>
+        </refsect1>
+
+        <refsect1>
+                  <title>See Also</title>
+                  <para>
+                          <citerefentry><refentrytitle>systemd</refentrytitle><manvolnum>1</manvolnum></citerefentry>,
+                          <citerefentry><refentrytitle>systemd-machine-id-setup</refentrytitle><manvolnum>1</manvolnum></citerefentry>,
+                          <citerefentry><refentrytitle>gethostid</refentrytitle><manvolnum>3</manvolnum></citerefentry>,
+                          <citerefentry><refentrytitle>hostname</refentrytitle><manvolnum>5</manvolnum></citerefentry>,
+                          <citerefentry><refentrytitle>machine-info</refentrytitle><manvolnum>5</manvolnum></citerefentry>,
+                          <citerefentry><refentrytitle>os-release</refentrytitle><manvolnum>5</manvolnum></citerefentry>
+                  </para>
+        </refsect1>
+
+</refentry>
diff --git a/man/machine-info.xml b/man/machine-info.xml
new file mode 100644 (file)
index 0000000..240da25
--- /dev/null
@@ -0,0 +1,147 @@
+<?xml version='1.0'?> <!--*-nxml-*-->
+<?xml-stylesheet type="text/xsl" href="http://docbook.sourceforge.net/release/xsl/current/xhtml/docbook.xsl"?>
+<!DOCTYPE refentry PUBLIC "-//OASIS//DTD DocBook XML V4.2//EN"
+        "http://www.oasis-open.org/docbook/xml/4.2/docbookx.dtd">
+
+<!--
+  This file is part 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 <http://www.gnu.org/licenses/>.
+-->
+
+<refentry id="machine-info">
+        <refentryinfo>
+                <title>machine-info</title>
+                <productname>systemd</productname>
+
+                <authorgroup>
+                        <author>
+                                <contrib>Developer</contrib>
+                                <firstname>Lennart</firstname>
+                                <surname>Poettering</surname>
+                                <email>lennart@poettering.net</email>
+                        </author>
+                </authorgroup>
+        </refentryinfo>
+
+        <refmeta>
+                <refentrytitle>machine-info</refentrytitle>
+                <manvolnum>5</manvolnum>
+        </refmeta>
+
+        <refnamediv>
+                <refname>machine-info</refname>
+                <refpurpose>Local machine information file</refpurpose>
+        </refnamediv>
+
+        <refsynopsisdiv>
+                <para><filename>/etc/machine-info</filename></para>
+        </refsynopsisdiv>
+
+        <refsect1>
+                <title>Description</title>
+
+                <para>The <filename>/etc/machine-info</filename> file
+                contains machine meta data.</para>
+
+                <para>The basic file format of
+                <filename>machine-info</filename> is a
+                newline-separated list of environment-like
+                shell-compatible variable assignments. It is possible
+                to source the configuration from shell scripts,
+                however, beyond mere variable assignments no shell
+                features are supported, allowing applications to read
+                the file without implementing a shell compatible
+                execution engine.</para>
+
+                <para><filename>/etc/machine-info</filename> contains
+                meta data about the machine that is set by the user or
+                administrator.</para>
+
+                <para>Depending on the operating system other
+                configuration files might be checked for machine
+                information as well, however only as fallback.</para>
+        </refsect1>
+
+        <refsect1>
+                <title>Options</title>
+
+                <para>The following machine meta data parameters may
+                be set using
+                <filename>/etc/machine-info</filename>:</para>
+
+                <variablelist>
+
+                        <varlistentry>
+                                <term><varname>PRETTY_HOSTNAME=</varname></term>
+
+                                <listitem><para>A pretty
+                                human-readable UTF8 machine identifier
+                                string. This should contain a name
+                                like <literal>Lennart's
+                                Laptop</literal> which is useful to
+                                present to the user and does not
+                                suffer by the syntax limitations of
+                                internet domain names. If possible the
+                                internet host name as configured in
+                                <filename>/etc/hostname</filename>
+                                should be kept similar to this
+                                one. Example: if this value is
+                                <literal>Lennart's Computer</literal>
+                                an Internet host name of
+                                <literal>lennarts-computer</literal>
+                                might be a good choice. If this
+                                parameter is not set an application
+                                should fall back to the Internet host
+                                name for presentation
+                                purposes.</para></listitem>
+                        </varlistentry>
+
+                        <varlistentry>
+                                <term><varname>ICON_NAME=</varname></term>
+
+                                <listitem><para>An icon identifying
+                                this machine according to the <ulink
+                                url="http://standards.freedesktop.org/icon-naming-spec/icon-naming-spec-latest.html">XDG
+                                Icon Naming Specification</ulink>. If
+                                this parameter is not set an
+                                application should fall back to
+                                <literal>computer</literal> or a
+                                similar icon name.</para></listitem>
+                        </varlistentry>
+
+                </variablelist>
+
+        </refsect1>
+
+        <refsect1>
+                <title>Example</title>
+
+                <programlisting>PRETTY_HOSTNAME="Lennart's Computer"
+ICON_NAME=computer-laptop</programlisting>
+        </refsect1>
+
+        <refsect1>
+                  <title>See Also</title>
+                  <para>
+                          <citerefentry><refentrytitle>systemd</refentrytitle><manvolnum>1</manvolnum></citerefentry>,
+                          <citerefentry><refentrytitle>os-release</refentrytitle><manvolnum>5</manvolnum></citerefentry>,
+                          <citerefentry><refentrytitle>hostname</refentrytitle><manvolnum>5</manvolnum></citerefentry>,
+                          <citerefentry><refentrytitle>machine-id</refentrytitle><manvolnum>5</manvolnum></citerefentry>
+                  </para>
+        </refsect1>
+
+</refentry>
diff --git a/man/modules-load.d.xml b/man/modules-load.d.xml
new file mode 100644 (file)
index 0000000..e2f7d5c
--- /dev/null
@@ -0,0 +1,112 @@
+<?xml version="1.0"?>
+<!--*-nxml-*-->
+<!DOCTYPE refentry PUBLIC "-//OASIS//DTD DocBook XML V4.2//EN" "http://www.oasis-open.org/docbook/xml/4.2/docbookx.dtd">
+<!--
+  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 <http://www.gnu.org/licenses/>.
+-->
+<refentry id="modules-load.d">
+
+        <refentryinfo>
+                <title>modules-load.d</title>
+                <productname>systemd</productname>
+
+                <authorgroup>
+                        <author>
+                                <contrib>Developer</contrib>
+                                <firstname>Lennart</firstname>
+                                <surname>Poettering</surname>
+                                <email>lennart@poettering.net</email>
+                        </author>
+                </authorgroup>
+        </refentryinfo>
+
+        <refmeta>
+                <refentrytitle>modules-load.d</refentrytitle>
+                <manvolnum>5</manvolnum>
+        </refmeta>
+
+        <refnamediv>
+                <refname>modules-load.d</refname>
+                <refpurpose>Configure kernel modules to load at boot</refpurpose>
+        </refnamediv>
+
+        <refsynopsisdiv>
+                <para><filename>/etc/modules-load.d/*.conf</filename></para>
+                <para><filename>/run/modules-load.d/*.conf</filename></para>
+                <para><filename>/usr/lib/modules-load.d/*.conf</filename></para>
+        </refsynopsisdiv>
+
+        <refsect1>
+                <title>Description</title>
+
+               <para><command>systemd</command> uses
+               files from the above directories to configure
+               kernel modules to load during boot in a static list.
+               Each configuration file is named in the style of
+               <filename>/etc/modules-load.d/&lt;program&gt;.conf</filename>. Note
+               that it is usually a better idea to use the automatic
+               module loading by PCI ID, by DMI ID or similar
+               triggers configured in the kernel modules themselves
+               instead of relying on static configuration like
+               this.</para>
+        </refsect1>
+
+        <refsect1>
+               <title>Configuration Format</title>
+
+               <para>The configuration files should simply contain a
+               list of kernel module names to load, separated by
+               newlines. Empty lines and lines whose first
+               non-whitespace character is # or ; are ignored.</para>
+
+                <para>Each configuration file is named in the style of
+                <filename>&lt;program&gt;.conf</filename>.
+                Files in <filename>/etc/</filename> overwrite
+                files with the same name in <filename>/usr/lib/</filename>.
+                Files in <filename>/run</filename> overwrite files with
+                the same name in <filename>/etc/</filename> and
+                <filename>/usr/lib/</filename>. Packages should install their
+                configuration files in <filename>/usr/lib/</filename>, files
+                in <filename>/etc/</filename> are reserved for the local
+                administration, which possibly decides to overwrite the
+                configurations installed from packages. All files are sorted
+                by filename in alphabetical order, regardless in which of the
+                directories they reside, to ensure that a specific
+                configuration file takes precedence over another file with
+                an alphabetically later name.</para>
+        </refsect1>
+
+        <refsect1>
+                <title>Example</title>
+                <example>
+                        <title>/etc/modules-load.d/virtio-net.conf example:</title>
+
+                        <programlisting># Load virtio-net.ko at boot
+virtio-net</programlisting>
+                </example>
+        </refsect1>
+
+        <refsect1>
+                <title>See Also</title>
+                <para>
+                        <citerefentry><refentrytitle>systemd</refentrytitle><manvolnum>1</manvolnum></citerefentry>,
+                        <citerefentry><refentrytitle>modprobe</refentrytitle><manvolnum>8</manvolnum></citerefentry>
+                </para>
+        </refsect1>
+
+</refentry>
diff --git a/man/os-release.xml b/man/os-release.xml
new file mode 100644 (file)
index 0000000..ff8fdf1
--- /dev/null
@@ -0,0 +1,350 @@
+<?xml version='1.0'?> <!--*-nxml-*-->
+<?xml-stylesheet type="text/xsl" href="http://docbook.sourceforge.net/release/xsl/current/xhtml/docbook.xsl"?>
+<!DOCTYPE refentry PUBLIC "-//OASIS//DTD DocBook XML V4.2//EN"
+        "http://www.oasis-open.org/docbook/xml/4.2/docbookx.dtd">
+
+<!--
+  This file is part 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 <http://www.gnu.org/licenses/>.
+-->
+
+<refentry id="os-release">
+        <refentryinfo>
+                <title>os-release</title>
+                <productname>systemd</productname>
+
+                <authorgroup>
+                        <author>
+                                <contrib>Developer</contrib>
+                                <firstname>Lennart</firstname>
+                                <surname>Poettering</surname>
+                                <email>lennart@poettering.net</email>
+                        </author>
+                </authorgroup>
+        </refentryinfo>
+
+        <refmeta>
+                <refentrytitle>os-release</refentrytitle>
+                <manvolnum>5</manvolnum>
+        </refmeta>
+
+        <refnamediv>
+                <refname>os-release</refname>
+                <refpurpose>Operating system identification</refpurpose>
+        </refnamediv>
+
+        <refsynopsisdiv>
+                <para><filename>/etc/os-release</filename></para>
+        </refsynopsisdiv>
+
+        <refsect1>
+                <title>Description</title>
+
+                <para>The <filename>/etc/os-release</filename> file
+                contains operating system identification data.</para>
+
+                <para>The basic file format of
+                <filename>os-release</filename> is a newline-separated
+                list of environment-like shell-compatible variable
+                assignments. It is possible to source the
+                configuration from shell scripts, however, beyond mere
+                variable assignments no shell features are supported
+                (this means variable expansion is explicitly not
+                supported), allowing applications to read the file
+                without implementing a shell compatible execution
+                engine. Variable assignment values should be enclosed
+                in double or single quotes if they include spaces,
+                semicolons or other special characters outside of A-Z,
+                a-z, 0-9. All strings should be in UTF-8 format, and
+                non-printable characters should not be used. If double
+                or single quotes or backslashes are to be used within
+                variable assignments they should be escaped with
+                backslashes, following shell style. It is not
+                supported to concatenate multiple individually quoted
+                strings. Lines beginning with "#" shall be ignored as
+                comments.</para>
+
+                <para><filename>/etc/os-release</filename> contains
+                data that is defined by the operating system vendor
+                and should not be changed by the administrator.</para>
+
+                <para>As this file only encodes names and identifiers
+                it should not be localized.</para>
+
+                <para>The file <filename>/etc/os-release</filename> might
+                be a symlink to another file, but it is important that
+                the file is available from earliest boot on, and hence
+                must be located on the root file system.</para>
+
+                <para>For a longer rationale for
+                <filename>/etc/os-release</filename> please refer to
+                the <ulink
+                url="http://0pointer.de/blog/projects/os-release">Announcement of <filename>/etc/os-release</filename></ulink>.</para>
+        </refsect1>
+
+        <refsect1>
+                <title>Options</title>
+
+                <para>The following OS identifications parameters may be set using
+                <filename>/etc/os-release</filename>:</para>
+
+                <variablelist>
+
+                        <varlistentry>
+                                <term><varname>NAME=</varname></term>
+
+                                <listitem><para>A string identifying
+                                the operating system, without a
+                                version component, and suitable for
+                                presentation to the user. If not set
+                                defaults to
+                                <literal>NAME=Linux</literal>. Example:
+                                <literal>NAME=Fedora</literal> or
+                                <literal>NAME="Debian
+                                GNU/Linux"</literal>.</para></listitem>
+                        </varlistentry>
+
+                        <varlistentry>
+                                <term><varname>VERSION=</varname></term>
+
+                                <listitem><para>A string identifying
+                                the operating system version,
+                                excluding any OS name information,
+                                possibly including a release code
+                                name, and suitable for presentation to
+                                the user. This field is
+                                optional. Example:
+                                <literal>VERSION=17</literal> or
+                                <literal>VERSION="17 (Beefy
+                                Miracle)"</literal>.</para></listitem>
+                        </varlistentry>
+
+                        <varlistentry>
+                                <term><varname>ID=</varname></term>
+
+                                <listitem><para>A lower-case string
+                                (no spaces or other characters outside
+                                of 0-9, a-z, ".", "_" and "-")
+                                identifying the operating system,
+                                excluding any version information and
+                                suitable for processing by scripts or
+                                usage in generated file names. If not
+                                set defaults to
+                                <literal>ID=linux</literal>. Example:
+                                <literal>ID=fedora</literal> or
+                                <literal>ID=debian</literal>.</para></listitem>
+                        </varlistentry>
+
+                        <varlistentry>
+                                <term><varname>ID_LIKE=</varname></term>
+
+                                <listitem><para>A space-separated list
+                                of operating system identifiers in the
+                                same syntax as the
+                                <varname>ID=</varname> setting. Should
+                                list identifiers of operating systems
+                                that are closely related to the local
+                                operating system in regards to
+                                packaging and programming interfaces,
+                                for example listing one or more
+                                OS identifiers the local
+                                OS is a derivative from. An
+                                OS should generally only list other OS
+                                identifiers it itself is a derivative
+                                from, and not any OSes that
+                                are derived from it, but symmetric
+                                relationships are possible. Build
+                                scripts and similar should check this
+                                variable if they need to identify the
+                                local operating system and the value
+                                of <varname>ID=</varname> is not
+                                recognized. Operating systems should
+                                be listed in order of how closely the
+                                local operating system relates to the
+                                listed ones, starting with the
+                                closest. This field is
+                                optional. Example: for an operating
+                                system with
+                                <literal>ID=centos</literal> an
+                                assignment of <literal>ID_LIKE="rhel
+                                fedora"</literal> would be
+                                appropriate. For an operating system
+                                with <literal>ID=ubuntu</literal> an
+                                assignment of
+                                <literal>ID_LIKE=debian</literal> is
+                                appropriate.</para></listitem>
+                        </varlistentry>
+
+                        <varlistentry>
+                                <term><varname>VERSION_ID=</varname></term>
+
+                                <listitem><para>A lower-case string
+                                (mostly numeric, no spaces or other
+                                characters outside of 0-9, a-z, ".",
+                                "_" and "-") identifying the operating
+                                system version, excluding any OS name
+                                information or release code name, and
+                                suitable for processing by scripts or
+                                usage in generated file names. This
+                                field is optional. Example:
+                                <literal>VERSION_ID=17</literal> or
+                                <literal>VERSION_ID=11.04</literal>.</para></listitem>
+                        </varlistentry>
+
+                        <varlistentry>
+                                <term><varname>PRETTY_NAME=</varname></term>
+
+                                <listitem><para>A pretty operating
+                                system name in a format suitable for
+                                presentation to the user. May or may
+                                not contain a release code name or OS
+                                version of some kind, as suitable. If
+                                not set defaults to
+                                <literal>PRETTY_NAME="Linux"</literal>. Example:
+                                <literal>PRETTY_NAME="Fedora 17 (Beefy
+                                Miracle)"</literal>.</para></listitem>
+                        </varlistentry>
+
+                        <varlistentry>
+                                <term><varname>ANSI_COLOR=</varname></term>
+
+                                <listitem><para>A suggested
+                                presentation color when showing the
+                                OS name on the console. This
+                                should be specified as string suitable
+                                for inclusion in the ESC [ m
+                                ANSI/ECMA-48 escape code for setting
+                                graphical rendition. This field is
+                                optional. Example:
+                                <literal>ANSI_COLOR="0;31"</literal>
+                                for red, or
+                                <literal>ANSI_COLOR="1;34"</literal>
+                                for light blue.</para></listitem>
+                        </varlistentry>
+
+                        <varlistentry>
+                                <term><varname>CPE_NAME=</varname></term>
+
+                                <listitem><para>A CPE name for the
+                                operating system, following the <ulink
+                                url="http://cpe.mitre.org/specification/">Common
+                                Platform Enumeration
+                                Specification</ulink> as proposed by
+                                the MITRE Corporation. This field
+                                is optional. Example:
+                                <literal>CPE_NAME="cpe:/o:fedoraproject:fedora:17"</literal>
+                                </para></listitem>
+                        </varlistentry>
+
+                        <varlistentry>
+                                <term><varname>HOME_URL=</varname></term>
+                                <term><varname>SUPPORT_URL=</varname></term>
+                                <term><varname>BUG_REPORT_URL=</varname></term>
+
+                                <listitem><para>Links to resources on
+                                the Internet related the operating
+                                system. <varname>HOME_URL=</varname>
+                                should refer to the homepage of the of
+                                operating system, or alternatively
+                                some homepage of the specific version
+                                of the operating
+                                system. <varname>SUPPORT_URL=</varname>
+                                should refer to the main support page
+                                for the operating system, if there is
+                                any. This is primarily intended for
+                                operating systems which vendors
+                                provide support
+                                for. <varname>BUG_REPORT_URL=</varname>
+                                should refer to the main bug reporting
+                                page for the operating system, if
+                                there is any. This is primarily
+                                intended for operating systems that
+                                rely on community QA. These settings
+                                are optional, and providing only some
+                                of these settings is common. These
+                                URLs are intended to be exposed in
+                                "About this system" UIs behind links
+                                with captions such as "About this
+                                Operating System", "Obtain Support"
+                                resp. "Report a Bug". The values should
+                                be in <ulink
+                                url="https://tools.ietf.org/html/rfc3986">RFC3986
+                                format</ulink>, and should be
+                                <literal>http:</literal> or
+                                <literal>https:</literal> URLs, and
+                                possibly <literal>mailto:</literal> or
+                                <literal>tel:</literal>. Only one URL
+                                shall be listed in each setting. If
+                                multiple resources need to be
+                                referenced it is recommended to
+                                provide an online landing page linking
+                                all available resources. Examples:
+                                <literal>HOME_URL="https://fedoraproject.org/"</literal>
+                                and
+                                <literal>BUG_REPORT_URL="https://bugzilla.redhat.com/"</literal></para></listitem>
+                        </varlistentry>
+
+
+                </variablelist>
+
+                <para>If you are reading this file from C code or a
+                shell script to determine the OS or a specific version
+                of it, use the ID and VERSION_ID fields, possibly with
+                ID_LIKE as fallback for ID. When looking for an OS
+                identification string for presentation to the user use
+                the PRETTY_NAME field.</para>
+
+                <para>Note that operating system vendors may choose
+                not to provide version information, for example to
+                accommodate for rolling releases. In this case VERSION
+                and VERSION_ID may be unset. Applications should not
+                rely on these fields to be set.</para>
+
+                <para>Operating system vendors may extend the file
+                format and introduce new fields. It is highly
+                recommended to prefix new fields with an OS specific
+                name in order to avoid name clashes. Applications
+                reading this file must ignore unknown fields. Example:
+                <literal>DEBIAN_BTS="debbugs://bugs.debian.org/"</literal></para>
+        </refsect1>
+
+        <refsect1>
+                <title>Example</title>
+
+                <programlisting>NAME=Fedora
+VERSION="17 (Beefy Miracle)"
+ID=fedora
+VERSION_ID=17
+PRETTY_NAME="Fedora 17 (Beefy Miracle)"
+ANSI_COLOR="0;34"
+CPE_NAME="cpe:/o:fedoraproject:fedora:17"
+HOME_URL="https://fedoraproject.org/"
+BUG_REPORT_URL="https://bugzilla.redhat.com/"</programlisting>
+        </refsect1>
+
+        <refsect1>
+                  <title>See Also</title>
+                  <para>
+                          <citerefentry><refentrytitle>systemd</refentrytitle><manvolnum>1</manvolnum></citerefentry>,
+                          <citerefentry><refentrytitle>lsb_release</refentrytitle><manvolnum>1</manvolnum></citerefentry>,
+                          <citerefentry><refentrytitle>hostname</refentrytitle><manvolnum>5</manvolnum></citerefentry>,
+                          <citerefentry><refentrytitle>machine-id</refentrytitle><manvolnum>5</manvolnum></citerefentry>,
+                          <citerefentry><refentrytitle>machine-info</refentrytitle><manvolnum>5</manvolnum></citerefentry>
+                  </para>
+        </refsect1>
+
+</refentry>
diff --git a/man/pam_systemd.xml b/man/pam_systemd.xml
new file mode 100644 (file)
index 0000000..c07b46b
--- /dev/null
@@ -0,0 +1,316 @@
+<?xml version='1.0'?> <!--*-nxml-*-->
+<!DOCTYPE refentry PUBLIC "-//OASIS//DTD DocBook XML V4.2//EN"
+        "http://www.oasis-open.org/docbook/xml/4.2/docbookx.dtd">
+
+<!--
+  This file is part 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 <http://www.gnu.org/licenses/>.
+-->
+
+<refentry id="pam_systemd">
+
+        <refentryinfo>
+                <title>pam_systemd</title>
+                <productname>systemd</productname>
+
+                <authorgroup>
+                        <author>
+                                <contrib>Developer</contrib>
+                                <firstname>Lennart</firstname>
+                                <surname>Poettering</surname>
+                                <email>lennart@poettering.net</email>
+                        </author>
+                </authorgroup>
+        </refentryinfo>
+
+        <refmeta>
+                <refentrytitle>pam_systemd</refentrytitle>
+                <manvolnum>8</manvolnum>
+        </refmeta>
+
+        <refnamediv>
+                <refname>pam_systemd</refname>
+                <refpurpose>Register user sessions in the systemd control group hierarchy</refpurpose>
+        </refnamediv>
+
+        <refsynopsisdiv>
+                <cmdsynopsis>
+                        <command>pam_systemd.so</command>
+                </cmdsynopsis>
+        </refsynopsisdiv>
+
+        <refsect1>
+                <title>Description</title>
+
+                <para><command>pam_systemd</command> registers user
+                sessions in the systemd control group
+                hierarchy.</para>
+
+                <para>On login, this module ensures the following:</para>
+
+                <orderedlist>
+                        <listitem><para>If it does not exist yet, the
+                        user runtime directory
+                        <filename>/run/user/$USER</filename> is
+                        created and its ownership changed to the user
+                        that is logging in.</para></listitem>
+
+                        <listitem><para>The
+                        <varname>$XDG_SESSION_ID</varname> environment
+                        variable is initialized. If auditing is
+                        available and
+                        <command>pam_loginuid.so</command> run before
+                        this module (which is highly recommended), the
+                        variable is initialized from the auditing
+                        session id
+                        (<filename>/proc/self/sessionid</filename>). Otherwise
+                        an independent session counter is
+                        used.</para></listitem>
+
+                        <listitem><para>A new control group
+                        <filename>/user/$USER/$XDG_SESSION_ID</filename>
+                        is created and the login process moved into
+                        it.</para></listitem>
+                </orderedlist>
+
+                <para>On logout, this module ensures the following:</para>
+
+                <orderedlist>
+                        <listitem><para>If
+                        <varname>$XDG_SESSION_ID</varname> is set and
+                        <option>kill-session-processes=1</option> specified, all
+                        remaining processes in the
+                        <filename>/user/$USER/$XDG_SESSION_ID</filename>
+                        control group are killed and the control group
+                        is removed.</para></listitem>
+
+                        <listitem><para>If last subgroup of the
+                        <filename>/user/$USER</filename> control group
+                        was removed the
+                        <varname>$XDG_RUNTIME_DIR</varname> directory
+                        and all its contents are
+                        removed, too.</para></listitem>
+                </orderedlist>
+
+                <para>If the system was not booted up with systemd as
+                init system, this module does nothing and immediately
+                returns PAM_SUCCESS.</para>
+
+        </refsect1>
+
+        <refsect1>
+                <title>Options</title>
+
+                <para>The following options are understood:</para>
+
+                <variablelist>
+                        <varlistentry>
+                                <term><option>kill-session-processes=</option></term>
+
+                                <listitem><para>Takes a boolean
+                                argument. If true, all processes
+                                created by the user during his session
+                                and from his session will be
+                                terminated when he logs out from his
+                                session.</para></listitem>
+                        </varlistentry>
+
+                        <varlistentry>
+                                <term><option>kill-only-users=</option></term>
+
+                                <listitem><para>Takes a comma
+                                separated list of user names or
+                                numeric user ids as argument. If this
+                                option is used the effect of the
+                                <option>kill-session-processes=</option> options
+                                will apply only to the listed
+                                users. If this option is not used the
+                                option applies to all local
+                                users. Note that
+                                <option>kill-exclude-users=</option>
+                                takes precedence over this list and is
+                                hence subtracted from the list
+                                specified here.</para></listitem>
+                        </varlistentry>
+
+                        <varlistentry>
+                                <term><option>kill-exclude-users=</option></term>
+
+                                <listitem><para>Takes a comma
+                                separated list of user names or
+                                numeric user ids as argument. Users
+                                listed in this argument will not be
+                                subject to the effect of
+                                <option>kill-session-processes=</option>.  Note
+                                that that this option takes precedence
+                                over
+                                <option>kill-only-users=</option>, and
+                                hence whatever is listed for
+                                <option>kill-exclude-users=</option>
+                                is guaranteed to never be killed by
+                                this PAM module, independent of any
+                                other configuration
+                                setting.</para></listitem>
+                        </varlistentry>
+
+                        <varlistentry>
+                                <term><option>controllers=</option></term>
+
+                                <listitem><para>Takes a comma
+                                separated list of control group
+                                controllers in which hierarchies a
+                                user/session control group will be
+                                created by default for each user
+                                logging in, in addition to the control
+                                group in the named 'name=systemd'
+                                hierarchy. If omitted, defaults to an
+                                empty list.</para></listitem>
+                        </varlistentry>
+
+                        <varlistentry>
+                                <term><option>reset-controllers=</option></term>
+
+                                <listitem><para>Takes a comma
+                                separated list of control group
+                                controllers in which hierarchies the
+                                logged in processes will be reset to
+                                the root control
+                                group.</para></listitem>
+                        </varlistentry>
+
+                        <varlistentry>
+                                <term><option>debug=</option></term>
+
+                                <listitem><para>Takes a boolean
+                                argument. If yes, the module will log
+                                debugging information as it
+                                operates.</para></listitem>
+                        </varlistentry>
+                </variablelist>
+
+                <para>Note that setting
+                <varname>kill-session-processes=1</varname> will break tools
+                like
+                <citerefentry><refentrytitle>screen</refentrytitle><manvolnum>1</manvolnum></citerefentry>.</para>
+
+                <para>Note that
+                <varname>kill-session-processes=1</varname> is a
+                stricter version of
+                <varname>KillUserProcesses=1</varname> which may be
+                configured system-wide in
+                <citerefentry><refentrytitle>logind.conf</refentrytitle><manvolnum>5</manvolnum></citerefentry>. The
+                former kills processes of a session as soon as it
+                ends, the latter kills processes as soon as the last
+                session of the user ends.</para>
+
+                <para>If the options are omitted they default to
+                <option>kill-session-processes=0</option>,
+                <option>kill-only-users=</option>,
+                <option>kill-exclude-users=</option>,
+                <option>controllers=</option>,
+                <option>reset-controllers=</option>,
+                <option>debug=no</option>.</para>
+        </refsect1>
+
+        <refsect1>
+                <title>Module Types Provided</title>
+
+                <para>Only <option>session</option> is provided.</para>
+        </refsect1>
+
+        <refsect1>
+                <title>Environment</title>
+
+                <para>The following environment variables are set for the processes of the user's session:</para>
+
+                <variablelist>
+                        <varlistentry>
+                                <term><varname>$XDG_SESSION_ID</varname></term>
+
+                                <listitem><para>A session identifier,
+                                suitable to be used in file names. The
+                                string itself should be considered
+                                opaque, although often it is just the
+                                audit session ID as reported by
+                                <filename>/proc/self/sessionid</filename>. Each
+                                ID will be assigned only once during
+                                machine uptime. It may hence be used
+                                to uniquely label files or other
+                                resources of this
+                                session.</para></listitem>
+                        </varlistentry>
+
+                        <varlistentry>
+                                <term><varname>$XDG_RUNTIME_DIR</varname></term>
+
+                                <listitem><para>Path to a user-private
+                                user-writable directory that is bound
+                                to the user login time on the
+                                machine. It is automatically created
+                                the first time a user logs in and
+                                removed on his final logout. If a user
+                                logs in twice at the same time, both
+                                sessions will see the same
+                                <varname>$XDG_RUNTIME_DIR</varname>
+                                and the same contents. If a user logs
+                                in once, then logs out again, and logs
+                                in again, the directory contents will
+                                have been lost in between, but
+                                applications should not rely on this
+                                behaviour and must be able to deal with
+                                stale files. To store session-private
+                                data in this directory the user should
+                                include the value of <varname>$XDG_SESSION_ID</varname>
+                                in the filename. This directory shall
+                                be used for runtime file system
+                                objects such as AF_UNIX sockets,
+                                FIFOs, PID files and similar. It is
+                                guaranteed that this directory is
+                                local and offers the greatest possible
+                                file system feature set the
+                                operating system
+                                provides.</para></listitem>
+                        </varlistentry>
+                </variablelist>
+        </refsect1>
+
+        <refsect1>
+                <title>Example</title>
+
+                <programlisting>#%PAM-1.0
+auth       required     pam_unix.so
+auth       required     pam_nologin.so
+account    required     pam_unix.so
+password   required     pam_unix.so
+session    required     pam_unix.so
+session    required     pam_loginuid.so
+session    required     pam_systemd.so kill-session-processes=1</programlisting>
+        </refsect1>
+
+        <refsect1>
+                <title>See Also</title>
+                <para>
+                        <citerefentry><refentrytitle>pam.conf</refentrytitle><manvolnum>5</manvolnum></citerefentry>,
+                        <citerefentry><refentrytitle>pam.d</refentrytitle><manvolnum>5</manvolnum></citerefentry>,
+                        <citerefentry><refentrytitle>pam</refentrytitle><manvolnum>8</manvolnum></citerefentry>,
+                        <citerefentry><refentrytitle>pam_loginuid</refentrytitle><manvolnum>8</manvolnum></citerefentry>,
+                        <citerefentry><refentrytitle>logind.conf</refentrytitle><manvolnum>5</manvolnum></citerefentry>,
+                        <citerefentry><refentrytitle>systemd</refentrytitle><manvolnum>1</manvolnum></citerefentry>
+                </para>
+        </refsect1>
+
+</refentry>
diff --git a/man/runlevel.xml b/man/runlevel.xml
new file mode 100644 (file)
index 0000000..160d1b1
--- /dev/null
@@ -0,0 +1,154 @@
+<?xml version='1.0'?> <!--*-nxml-*-->
+<!DOCTYPE refentry PUBLIC "-//OASIS//DTD DocBook XML V4.2//EN"
+        "http://www.oasis-open.org/docbook/xml/4.2/docbookx.dtd">
+
+<!--
+  This file is part 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 <http://www.gnu.org/licenses/>.
+-->
+
+<refentry id="runlevel">
+
+        <refentryinfo>
+                <title>runlevel</title>
+                <productname>systemd</productname>
+
+                <authorgroup>
+                        <author>
+                                <contrib>Developer</contrib>
+                                <firstname>Lennart</firstname>
+                                <surname>Poettering</surname>
+                                <email>lennart@poettering.net</email>
+                        </author>
+                </authorgroup>
+        </refentryinfo>
+
+        <refmeta>
+                <refentrytitle>runlevel</refentrytitle>
+                <manvolnum>8</manvolnum>
+        </refmeta>
+
+        <refnamediv>
+                <refname>runlevel</refname>
+                <refpurpose>Print previous and current SysV runlevel</refpurpose>
+        </refnamediv>
+
+        <refsynopsisdiv>
+                <cmdsynopsis>
+                        <command>runlevel <arg choice="opt" rep="repeat">options</arg></command>
+                </cmdsynopsis>
+        </refsynopsisdiv>
+
+        <refsect1>
+                <title>Description</title>
+
+                <para><command>runlevel</command> prints the previous
+                and current SysV runlevel if they are known.</para>
+
+                <para>The two runlevel characters are separated by a
+                single space character. If a runlevel cannot be
+                determined, N is printed instead. If neither can be
+                determined, the word "unknown" is printed.</para>
+
+                <para>Unless overridden in the environment, this will
+                check the utmp database for recent runlevel
+                changes.</para>
+        </refsect1>
+
+        <refsect1>
+                <title>Options</title>
+
+                <para>The following option is understood:</para>
+
+                <variablelist>
+                        <varlistentry>
+                                <term><option>--help</option></term>
+
+                                <listitem><para>Prints a short help
+                                text and exits.</para></listitem>
+                        </varlistentry>
+                </variablelist>
+
+        </refsect1>
+
+        <refsect1>
+                <title>Exit status</title>
+
+                <para>If one or both runlevels could be determined, 0
+                is returned, a non-zero failure code otherwise.</para>
+
+        </refsect1>
+
+        <refsect1>
+                <title>Environment</title>
+
+                <variablelist>
+                        <varlistentry>
+                                <term><varname>$RUNLEVEL</varname></term>
+
+                                <listitem><para>If
+                                <varname>$RUNLEVEL</varname> is set,
+                                <command>runlevel</command> will print
+                                this value as current runlevel and
+                                ignore utmp.</para></listitem>
+                        </varlistentry>
+
+                        <varlistentry>
+                                <term><varname>$PREVLEVEL</varname></term>
+
+                                <listitem><para>If
+                                <varname>$PREVLEVEL</varname> is set
+                                <command>runlevel</command> will print
+                                this value as previous runlevel and
+                                ignore utmp.</para></listitem>
+                        </varlistentry>
+                </variablelist>
+        </refsect1>
+
+        <refsect1>
+                <title>Files</title>
+
+                <variablelist>
+                        <varlistentry>
+                                <term><varname>/var/run/utmp</varname></term>
+
+                                <listitem><para>The utmp database
+                                <command>runlevel</command> reads the
+                                previous and current runlevel
+                                from.</para></listitem>
+
+                        </varlistentry>
+                </variablelist>
+        </refsect1>
+
+        <refsect1>
+                <title>Notes</title>
+
+                <para>This is a legacy command available for compatibility
+                only. It should not be used anymore, as the concept of
+                runlevels is obsolete.</para>
+        </refsect1>
+
+        <refsect1>
+                <title>See Also</title>
+                <para>
+                        <citerefentry><refentrytitle>systemd</refentrytitle><manvolnum>1</manvolnum></citerefentry>,
+                        <citerefentry><refentrytitle>systemctl</refentrytitle><manvolnum>1</manvolnum></citerefentry>
+                </para>
+        </refsect1>
+
+</refentry>
diff --git a/man/sd-daemon.xml b/man/sd-daemon.xml
new file mode 100644 (file)
index 0000000..4ea88e4
--- /dev/null
@@ -0,0 +1,172 @@
+<?xml version='1.0'?> <!--*-nxml-*-->
+<!DOCTYPE refentry PUBLIC "-//OASIS//DTD DocBook XML V4.2//EN"
+        "http://www.oasis-open.org/docbook/xml/4.2/docbookx.dtd">
+
+<!--
+  This file is part 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 <http://www.gnu.org/licenses/>.
+-->
+
+<refentry id="sd-daemon">
+
+        <refentryinfo>
+                <title>sd-daemon</title>
+                <productname>systemd</productname>
+
+                <authorgroup>
+                        <author>
+                                <contrib>Developer</contrib>
+                                <firstname>Lennart</firstname>
+                                <surname>Poettering</surname>
+                                <email>lennart@poettering.net</email>
+                        </author>
+                </authorgroup>
+        </refentryinfo>
+
+        <refmeta>
+                <refentrytitle>sd-daemon</refentrytitle>
+                <manvolnum>7</manvolnum>
+        </refmeta>
+
+        <refnamediv>
+                <refname>sd-daemon</refname>
+                <refpurpose>Reference implementation of APIs for
+                new-style daemons</refpurpose>
+        </refnamediv>
+
+        <refsynopsisdiv>
+                <funcsynopsis>
+                        <funcsynopsisinfo>#include &lt;systemd/sd-daemon.h&gt;</funcsynopsisinfo>
+                </funcsynopsis>
+
+                <cmdsynopsis>
+                        <command>pkg-config --cflags --libs libsystemd-daemon</command>
+                </cmdsynopsis>
+
+        </refsynopsisdiv>
+
+        <refsect1>
+                <title>Description</title>
+
+                <para><filename>sd-daemon.c</filename> and
+                <filename>sd-daemon.h</filename> provide a reference
+                implementation of various APIs for new-style daemons,
+                as implemented by the
+                <citerefentry><refentrytitle>systemd</refentrytitle><manvolnum>1</manvolnum></citerefentry>
+                init system.</para>
+
+                <para>See
+                <citerefentry><refentrytitle>sd_listen_fds</refentrytitle><manvolnum>3</manvolnum></citerefentry>,
+                <citerefentry><refentrytitle>sd_notify</refentrytitle><manvolnum>3</manvolnum></citerefentry>,
+                <citerefentry><refentrytitle>sd_booted</refentrytitle><manvolnum>3</manvolnum></citerefentry>,
+                <citerefentry><refentrytitle>sd_is_fifo</refentrytitle><manvolnum>3</manvolnum></citerefentry>
+                for more information about the functions
+                implemented. In addition to these functions a couple
+                of logging prefixes are defined as macros:</para>
+
+                <programlisting>#define SD_EMERG   "&lt;0&gt;"  /* system is unusable */
+#define SD_ALERT   "&lt;1&gt;"  /* action must be taken immediately */
+#define SD_CRIT    "&lt;2&gt;"  /* critical conditions */
+#define SD_ERR     "&lt;3&gt;"  /* error conditions */
+#define SD_WARNING "&lt;4&gt;"  /* warning conditions */
+#define SD_NOTICE  "&lt;5&gt;"  /* normal but significant condition */
+#define SD_INFO    "&lt;6&gt;"  /* informational */
+#define SD_DEBUG   "&lt;7&gt;"  /* debug-level messages */</programlisting>
+
+                <para>These prefixes are intended to be used in
+                conjunction with STDERR-based logging as implemented
+                by systemd. If a systemd service definition file is
+                configured with <varname>StandardError=syslog</varname>
+                or <varname>StandardError=kmsg</varname> these
+                prefixes can be used to encode a log level in lines
+                printed. This is similar to the kernel
+                <function>printk()</function>-style logging. See
+                <citerefentry><refentrytitle>klogctl</refentrytitle><manvolnum>2</manvolnum></citerefentry>
+                for more information.</para>
+
+                <para>The log levels are identical to
+                <citerefentry><refentrytitle>syslog</refentrytitle><manvolnum>3</manvolnum></citerefentry>'s
+                log level system. To use these prefixes simply prefix
+                every line with one of these strings. A line that is
+                not prefixed will be logged at the default log level
+                SD_INFO.</para>
+
+                <example>
+                        <title>Hello World</title>
+
+                        <para>A daemon may log with the log level
+                        NOTICE by issuing this call:</para>
+
+                        <programlisting>fprintf(stderr, SD_NOTICE "Hello World!\n");</programlisting>
+                </example>
+        </refsect1>
+
+        <refsect1>
+                <title>Notes</title>
+
+                <para>These interfaces are provided by the reference
+                implementation of APIs for new-style daemons and
+                distributed with the systemd package. The algorithms
+                they implement are simple, and can easily be
+                reimplemented in daemons if it is important to support
+                this interface without using the reference
+                implementation. See the respective function man pages
+                for details.</para>
+
+                <para>In addition, for details about the algorithms
+                check the liberally licensed reference implementation
+                sources:
+                <ulink url="http://cgit.freedesktop.org/systemd/systemd/plain/src/sd-daemon.c"/>
+                resp. <ulink url="http://cgit.freedesktop.org/systemd/systemd/plain/src/systemd/sd-daemon.h"/></para>
+
+                <para>These APIs are implemented in the reference
+                implementation's <filename>sd-daemon.c</filename> and
+                <filename>sd-daemon.h</filename> files. These
+                interfaces are available as shared library, which can
+                be compiled and linked to with the
+                <literal>libsystemd-daemon</literal>
+                <citerefentry><refentrytitle>pkg-config</refentrytitle><manvolnum>1</manvolnum></citerefentry>
+                file. Alternatively, applications consuming these APIs
+                may copy the implementation into their source tree,
+                either verbatim or in excerpts.</para>
+
+                <para>The functions directly related to new-style
+                daemons become NOPs when -DDISABLE_SYSTEMD is set
+                during compilation and the reference implementation is
+                used as drop-in files. In addition, if
+                <filename>sd-daemon.c</filename> is compiled on
+                non-Linux systems they become NOPs.</para>
+        </refsect1>
+
+        <refsect1>
+                <title>See Also</title>
+                <para>
+                        <citerefentry><refentrytitle>systemd</refentrytitle><manvolnum>1</manvolnum></citerefentry>,
+                        <citerefentry><refentrytitle>sd_listen_fds</refentrytitle><manvolnum>3</manvolnum></citerefentry>,
+                        <citerefentry><refentrytitle>sd_notify</refentrytitle><manvolnum>3</manvolnum></citerefentry>,
+                        <citerefentry><refentrytitle>sd_booted</refentrytitle><manvolnum>3</manvolnum></citerefentry>,
+                        <citerefentry><refentrytitle>sd_is_fifo</refentrytitle><manvolnum>3</manvolnum></citerefentry>,
+                        <citerefentry><refentrytitle>daemon</refentrytitle><manvolnum>7</manvolnum></citerefentry>,
+                        <citerefentry><refentrytitle>systemd.service</refentrytitle><manvolnum>5</manvolnum></citerefentry>,
+                        <citerefentry><refentrytitle>systemd.socket</refentrytitle><manvolnum>5</manvolnum></citerefentry>,
+                        <citerefentry><refentrytitle>fprintf</refentrytitle><manvolnum>3</manvolnum></citerefentry>,
+                        <citerefentry><refentrytitle>sd-readahead</refentrytitle><manvolnum>7</manvolnum></citerefentry>,
+                        <citerefentry><refentrytitle>pkg-config</refentrytitle><manvolnum>1</manvolnum></citerefentry>
+                </para>
+        </refsect1>
+
+</refentry>
diff --git a/man/sd-login.xml b/man/sd-login.xml
new file mode 100644 (file)
index 0000000..3fc0e16
--- /dev/null
@@ -0,0 +1,146 @@
+<?xml version='1.0'?> <!--*-nxml-*-->
+<!DOCTYPE refentry PUBLIC "-//OASIS//DTD DocBook XML V4.2//EN"
+        "http://www.oasis-open.org/docbook/xml/4.2/docbookx.dtd">
+
+<!--
+  This file is part 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 <http://www.gnu.org/licenses/>.
+-->
+
+<refentry id="sd-login">
+
+        <refentryinfo>
+                <title>sd-login</title>
+                <productname>systemd</productname>
+
+                <authorgroup>
+                        <author>
+                                <contrib>Developer</contrib>
+                                <firstname>Lennart</firstname>
+                                <surname>Poettering</surname>
+                                <email>lennart@poettering.net</email>
+                        </author>
+                </authorgroup>
+        </refentryinfo>
+
+        <refmeta>
+                <refentrytitle>sd-login</refentrytitle>
+                <manvolnum>7</manvolnum>
+        </refmeta>
+
+        <refnamediv>
+                <refname>sd-login</refname>
+                <refpurpose>APIs for
+                tracking logins</refpurpose>
+        </refnamediv>
+
+        <refsynopsisdiv>
+                <funcsynopsis>
+                        <funcsynopsisinfo>#include &lt;systemd/sd-login.h&gt;</funcsynopsisinfo>
+                </funcsynopsis>
+
+                <cmdsynopsis>
+                        <command>pkg-config --cflags --libs libsystemd-login</command>
+                </cmdsynopsis>
+        </refsynopsisdiv>
+
+        <refsect1>
+                <title>Description</title>
+
+                <para><filename>sd-login.h</filename> provides APIs to
+                introspect and monitor seat, login session and user
+                status information on the local system. </para>
+
+                <para>See <ulink
+                url="http://www.freedesktop.org/wiki/Software/systemd/multiseat">Multi-Seat
+                on Linux</ulink> for an introduction into multi-seat
+                support on Linux, the background for this set of APIs.</para>
+
+                <para>Note that these APIs only allow purely passive access
+                and monitoring of seats, sessions and users. To
+                actively make changes to the seat configuration,
+                terminate login sessions, or switch session on a seat
+                you need to utilize the D-Bus API of
+                systemd-logind, instead.</para>
+
+                <para>These functions synchronously access data in
+                <filename>/proc</filename>,
+                <filename>/sys/fs/cgroup</filename> and
+                <filename>/run</filename>. All of these are virtual
+                file systems, hence the runtime cost of the accesses
+                is relatively cheap.</para>
+
+                <para>It is possible (and often a very good choice) to
+                mix calls to the synchronous interface of
+                <filename>sd-login.h</filename> with the asynchronous
+                D-Bus interface of systemd-logind. However, if this is
+                done you need to think a bit about possible races
+                since the stream of events from D-Bus and from
+                <filename>sd-login.h</filename> interfaces such as the
+                login monitor are asynchronous and not ordered against
+                each other.</para>
+
+                <para>If the functions return string arrays, these are
+                generally NULL terminated and need to be freed by the
+                caller with the libc
+                <citerefentry><refentrytitle>free</refentrytitle><manvolnum>3</manvolnum></citerefentry>
+                call after use, including the strings referenced
+                therein. Similar, individual strings returned need to
+                be freed, as well.</para>
+
+                <para>As a special exception, instead of an empty
+                string array NULL may be returned, which should be
+                treated equivalent to an empty string array.</para>
+
+                <para>See
+                <citerefentry><refentrytitle>sd_pid_get_session</refentrytitle><manvolnum>3</manvolnum></citerefentry>,
+                <citerefentry><refentrytitle>sd_uid_get_state</refentrytitle><manvolnum>3</manvolnum></citerefentry>,
+                <citerefentry><refentrytitle>sd_session_is_active</refentrytitle><manvolnum>3</manvolnum></citerefentry>,
+                <citerefentry><refentrytitle>sd_seat_get_active</refentrytitle><manvolnum>3</manvolnum></citerefentry>,
+                <citerefentry><refentrytitle>sd_get_seats</refentrytitle><manvolnum>3</manvolnum></citerefentry>,
+                <citerefentry><refentrytitle>sd_login_monitor_new</refentrytitle><manvolnum>3</manvolnum></citerefentry>
+                for more information about the functions
+                implemented.</para>
+        </refsect1>
+
+        <refsect1>
+                <title>Notes</title>
+
+                <para>These APIs are implemented as shared library,
+                which can be compiled and linked to with the
+                <literal>libsystemd-login</literal>
+                <citerefentry><refentrytitle>pkg-config</refentrytitle><manvolnum>1</manvolnum></citerefentry>
+                file.</para>
+        </refsect1>
+
+        <refsect1>
+                <title>See Also</title>
+                <para>
+                        <citerefentry><refentrytitle>systemd</refentrytitle><manvolnum>1</manvolnum></citerefentry>,
+                        <citerefentry><refentrytitle>sd_pid_get_session</refentrytitle><manvolnum>3</manvolnum></citerefentry>,
+                        <citerefentry><refentrytitle>sd_uid_get_state</refentrytitle><manvolnum>3</manvolnum></citerefentry>,
+                        <citerefentry><refentrytitle>sd_session_is_active</refentrytitle><manvolnum>3</manvolnum></citerefentry>,
+                        <citerefentry><refentrytitle>sd_seat_get_active</refentrytitle><manvolnum>3</manvolnum></citerefentry>,
+                        <citerefentry><refentrytitle>sd_get_seats</refentrytitle><manvolnum>3</manvolnum></citerefentry>,
+                        <citerefentry><refentrytitle>sd_login_monitor_new</refentrytitle><manvolnum>3</manvolnum></citerefentry>,
+                        <citerefentry><refentrytitle>sd-daemon</refentrytitle><manvolnum>7</manvolnum></citerefentry>,
+                        <citerefentry><refentrytitle>sd-readahead</refentrytitle><manvolnum>7</manvolnum></citerefentry>,
+                        <citerefentry><refentrytitle>pkg-config</refentrytitle><manvolnum>1</manvolnum></citerefentry>
+                </para>
+        </refsect1>
+
+</refentry>
diff --git a/man/sd-readahead.xml b/man/sd-readahead.xml
new file mode 100644 (file)
index 0000000..7fb8634
--- /dev/null
@@ -0,0 +1,117 @@
+<?xml version='1.0'?> <!--*-nxml-*-->
+<!DOCTYPE refentry PUBLIC "-//OASIS//DTD DocBook XML V4.2//EN"
+        "http://www.oasis-open.org/docbook/xml/4.2/docbookx.dtd">
+
+<!--
+  This file is part 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 <http://www.gnu.org/licenses/>.
+-->
+
+<refentry id="sd-daemon">
+
+        <refentryinfo>
+                <title>sd-readahead</title>
+                <productname>systemd</productname>
+
+                <authorgroup>
+                        <author>
+                                <contrib>Developer</contrib>
+                                <firstname>Lennart</firstname>
+                                <surname>Poettering</surname>
+                                <email>lennart@poettering.net</email>
+                        </author>
+                </authorgroup>
+        </refentryinfo>
+
+        <refmeta>
+                <refentrytitle>sd-readahead</refentrytitle>
+                <manvolnum>7</manvolnum>
+        </refmeta>
+
+        <refnamediv>
+                <refname>sd-readahead</refname>
+                <refpurpose>Reference implementation of APIs for
+                controlling boot-time read-ahead</refpurpose>
+        </refnamediv>
+
+        <refsynopsisdiv>
+                <funcsynopsis>
+                        <funcsynopsisinfo>#include "sd-readahead.h"</funcsynopsisinfo>
+                </funcsynopsis>
+        </refsynopsisdiv>
+
+        <refsect1>
+                <title>Description</title>
+
+                <para><filename>sd-readahead.c</filename> and
+                <filename>sd-readahead.h</filename> provide a
+                reference implementation for APIs for controlling boot-time
+                read-ahead, as implemented by the read-ahead subsystem
+                of the
+                <citerefentry><refentrytitle>systemd</refentrytitle><manvolnum>1</manvolnum></citerefentry>
+                init system.</para>
+
+                <para>See
+                <citerefentry><refentrytitle>sd_readahead</refentrytitle><manvolnum>3</manvolnum></citerefentry>
+                for more information about the function
+                implemented.</para>
+        </refsect1>
+
+        <refsect1>
+                <title>Notes</title>
+
+                <para>This interface is provided by the reference
+                implementation of APIs for controlling boot-time
+                read-ahead and distributed with the systemd
+                package. The algorithms it implements are simple, and
+                can easily be reimplemented in daemons if it is
+                important to support this interface without using the
+                reference implementation. See the respective function
+                man pages for details.</para>
+
+                <para>In addition, for details about the algorithms
+                check the liberally licensed reference implementation
+                sources:
+                <ulink url="http://cgit.freedesktop.org/systemd/systemd/plain/src/readahead/sd-readahead.c"/>
+                resp. <ulink url="http://cgit.freedesktop.org/systemd/systemd/plain/src/systemd/sd-readahead.h"/></para>
+
+                <para>These APIs are implemented in the reference
+                implementation's drop-in
+                <filename>sd-readahead.c</filename> and
+                <filename>sd-readahead.h</filename> files. It is
+                recommended that applications consuming these APIs copy
+                the implementation into their source tree, either
+                verbatim or in excerpts. These interfaces are
+                currently not available in a dynamic library.</para>
+
+                <para>The functions provided by this interface become
+                NOPs when -DDISABLE_SYSTEMD is set during
+                compilation. In addition, if
+                <filename>sd-readhead.c</filename> is compiled on
+                non-Linux systems it becomes NOPs.</para>
+        </refsect1>
+
+        <refsect1>
+                <title>See Also</title>
+                <para>
+                        <citerefentry><refentrytitle>systemd</refentrytitle><manvolnum>1</manvolnum></citerefentry>,
+                        <citerefentry><refentrytitle>sd_readahead</refentrytitle><manvolnum>3</manvolnum></citerefentry>,
+                        <citerefentry><refentrytitle>sd-daemon</refentrytitle><manvolnum>7</manvolnum></citerefentry>
+                </para>
+        </refsect1>
+
+</refentry>
diff --git a/man/sd_booted.xml b/man/sd_booted.xml
new file mode 100644 (file)
index 0000000..141625d
--- /dev/null
@@ -0,0 +1,128 @@
+<?xml version='1.0'?> <!--*-nxml-*-->
+<!DOCTYPE refentry PUBLIC "-//OASIS//DTD DocBook XML V4.2//EN"
+        "http://www.oasis-open.org/docbook/xml/4.2/docbookx.dtd">
+
+<!--
+  This file is part 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 <http://www.gnu.org/licenses/>.
+-->
+
+<refentry id="sd_booted">
+
+        <refentryinfo>
+                <title>sd_booted</title>
+                <productname>systemd</productname>
+
+                <authorgroup>
+                        <author>
+                                <contrib>Developer</contrib>
+                                <firstname>Lennart</firstname>
+                                <surname>Poettering</surname>
+                                <email>lennart@poettering.net</email>
+                        </author>
+                </authorgroup>
+        </refentryinfo>
+
+        <refmeta>
+                <refentrytitle>sd_booted</refentrytitle>
+                <manvolnum>3</manvolnum>
+        </refmeta>
+
+        <refnamediv>
+                <refname>sd_booted</refname>
+                <refpurpose>Test whether the system is running the systemd init system.</refpurpose>
+        </refnamediv>
+
+        <refsynopsisdiv>
+                <funcsynopsis>
+                        <funcsynopsisinfo>#include &lt;systemd/sd-daemon.h&gt;</funcsynopsisinfo>
+
+                        <funcprototype>
+                                <funcdef>int <function>sd_booted</function></funcdef>
+                                <paramdef>void</paramdef>
+                        </funcprototype>
+                </funcsynopsis>
+        </refsynopsisdiv>
+
+        <refsect1>
+                <title>Description</title>
+                <para><function>sd_booted()</function> checks whether
+                the system was booted up using the systemd init system.</para>
+        </refsect1>
+
+        <refsect1>
+                <title>Return Value</title>
+
+                <para>On failure, this call returns a negative
+                errno-style error code. If the system was booted up
+                with systemd as init system, this call returns a
+                positive return value, zero otherwise.</para>
+        </refsect1>
+
+        <refsect1>
+                <title>Notes</title>
+
+                <para>This function is provided by the reference
+                implementation of APIs for new-style daemons and
+                distributed with the systemd package. The algorithm it
+                implements is simple, and can easily be reimplemented
+                in daemons if it is important to support this
+                interface without using the reference
+                implementation.</para>
+
+                <para>Internally, this function checks whether the
+                <filename>/sys/fs/cgroup/systemd</filename> virtual file
+                system is mounted, by comparing the st_dev value of
+                the <function>stat()</function> data of
+                <filename>/sys/fs/cgroup</filename> and
+                <filename>/sys/fs/cgroup/systemd</filename>.</para>
+
+                <para>For details about the algorithm check the
+                liberally licensed reference implementation sources:
+                <ulink url="http://cgit.freedesktop.org/systemd/systemd/plain/src/sd-daemon.c"/>
+                resp. <ulink
+                url="http://cgit.freedesktop.org/systemd/systemd/plain/src/systemd/sd-daemon.h"/></para>
+
+                <para><function>sd_booted()</function> is implemented
+                in the reference implementation's
+                <filename>sd-daemon.c</filename> and
+                <filename>sd-daemon.h</filename> files. These
+                interfaces are available as shared library, which can
+                be compiled and linked to with the
+                <literal>libsystemd-daemon</literal>
+                <citerefentry><refentrytitle>pkg-config</refentrytitle><manvolnum>1</manvolnum></citerefentry>
+                file. Alternatively, applications consuming these APIs
+                may copy the implementation into their source
+                tree. For more details about the reference
+                implementation see
+                <citerefentry><refentrytitle>sd_daemon</refentrytitle><manvolnum>7</manvolnum></citerefentry>.</para>
+
+                <para>If the reference implementation is used as
+                drop-in files and -DDISABLE_SYSTEMD is set during
+                compilation this function will always return 0 and
+                otherwise become a NOP.</para>
+        </refsect1>
+
+        <refsect1>
+                <title>See Also</title>
+                <para>
+                        <citerefentry><refentrytitle>systemd</refentrytitle><manvolnum>1</manvolnum></citerefentry>,
+                        <citerefentry><refentrytitle>sd_daemon</refentrytitle><manvolnum>7</manvolnum></citerefentry>
+                </para>
+        </refsect1>
+
+</refentry>
diff --git a/man/sd_get_seats.xml b/man/sd_get_seats.xml
new file mode 100644 (file)
index 0000000..2ac7650
--- /dev/null
@@ -0,0 +1,127 @@
+<?xml version='1.0'?> <!--*-nxml-*-->
+<!DOCTYPE refentry PUBLIC "-//OASIS//DTD DocBook XML V4.2//EN"
+        "http://www.oasis-open.org/docbook/xml/4.2/docbookx.dtd">
+
+<!--
+  This file is part 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 <http://www.gnu.org/licenses/>.
+-->
+
+<refentry id="sd_get_seats">
+
+        <refentryinfo>
+                <title>sd_get_seats</title>
+                <productname>systemd</productname>
+
+                <authorgroup>
+                        <author>
+                                <contrib>Developer</contrib>
+                                <firstname>Lennart</firstname>
+                                <surname>Poettering</surname>
+                                <email>lennart@poettering.net</email>
+                        </author>
+                </authorgroup>
+        </refentryinfo>
+
+        <refmeta>
+                <refentrytitle>sd_get_seats</refentrytitle>
+                <manvolnum>3</manvolnum>
+        </refmeta>
+
+        <refnamediv>
+                <refname>sd_get_seats</refname>
+                <refname>sd_get_sessions</refname>
+                <refname>sd_get_uids</refname>
+                <refpurpose>Determine available seats, sessions and logged in users</refpurpose>
+        </refnamediv>
+
+        <refsynopsisdiv>
+                <funcsynopsis>
+                        <funcsynopsisinfo>#include &lt;systemd/sd-login.h&gt;</funcsynopsisinfo>
+
+                        <funcprototype>
+                                <funcdef>int <function>sd_get_seats</function></funcdef>
+                                <paramdef>char*** <parameter>seats</parameter></paramdef>
+                        </funcprototype>
+
+                        <funcprototype>
+                                <funcdef>int <function>sd_get_sessions</function></funcdef>
+                                <paramdef>char*** <parameter>sessions</parameter></paramdef>
+                        </funcprototype>
+
+                        <funcprototype>
+                                <funcdef>int <function>sd_get_uids</function></funcdef>
+                                <paramdef>char*** <parameter>sessions</parameter></paramdef>
+                        </funcprototype>
+
+                </funcsynopsis>
+        </refsynopsisdiv>
+
+        <refsect1>
+                <title>Description</title>
+
+                <para><function>sd_get_seats()</function> may be used
+                to determine all currently available local
+                seats. Returns a NULL terminated array of seat
+                identifiers. The returned array and all strings it
+                references need to be freed with the libc
+                <citerefentry><refentrytitle>free</refentrytitle><manvolnum>3</manvolnum></citerefentry>
+                call after use. Note that instead of an empty array
+                NULL may be returned and should be considered
+                equivalent to an empty array.</para>
+
+                <para>Similar, <function>sd_get_sessions()</function> may
+                be used to determine all current login sessions.</para>
+
+                <para>Similar, <function>sd_get_uids()</function> may
+                be used to determine all Unix users who currently have login sessions.</para>
+        </refsect1>
+
+        <refsect1>
+                <title>Return Value</title>
+
+                <para>On success <function>sd_get_seats()</function>,
+                <function>sd_get_sessions()</function> and
+                <function>sd_get_uids()</function> return the number
+                of entries in the arrays. On failure, these calls
+                return a negative errno-style error code.</para>
+        </refsect1>
+
+        <refsect1>
+                <title>Notes</title>
+
+                <para>The <function>sd_get_seats()</function>,
+                <function>sd_get_sessions()</function> and
+                <function>sd_get_uids()</function> interfaces
+                are available as shared library, which can be compiled
+                and linked to with the
+                <literal>libsystemd-login</literal>
+                <citerefentry><refentrytitle>pkg-config</refentrytitle><manvolnum>1</manvolnum></citerefentry>
+                file.</para>
+        </refsect1>
+
+        <refsect1>
+                <title>See Also</title>
+
+                <para>
+                        <citerefentry><refentrytitle>systemd</refentrytitle><manvolnum>1</manvolnum></citerefentry>,
+                        <citerefentry><refentrytitle>sd-login</refentrytitle><manvolnum>7</manvolnum></citerefentry>,
+                        <citerefentry><refentrytitle>sd_session_get_seat</refentrytitle><manvolnum>3</manvolnum></citerefentry>
+                </para>
+        </refsect1>
+
+</refentry>
diff --git a/man/sd_is_fifo.xml b/man/sd_is_fifo.xml
new file mode 100644 (file)
index 0000000..4db5120
--- /dev/null
@@ -0,0 +1,217 @@
+<?xml version='1.0'?> <!--*-nxml-*-->
+<!DOCTYPE refentry PUBLIC "-//OASIS//DTD DocBook XML V4.2//EN"
+        "http://www.oasis-open.org/docbook/xml/4.2/docbookx.dtd">
+
+<!--
+  This file is part 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 <http://www.gnu.org/licenses/>.
+-->
+
+<refentry id="sd_is_fifo">
+
+        <refentryinfo>
+                <title>sd_is_fifo</title>
+                <productname>systemd</productname>
+
+                <authorgroup>
+                        <author>
+                                <contrib>Developer</contrib>
+                                <firstname>Lennart</firstname>
+                                <surname>Poettering</surname>
+                                <email>lennart@poettering.net</email>
+                        </author>
+                </authorgroup>
+        </refentryinfo>
+
+        <refmeta>
+                <refentrytitle>sd_is_fifo</refentrytitle>
+                <manvolnum>3</manvolnum>
+        </refmeta>
+
+        <refnamediv>
+                <refname>sd_is_fifo</refname>
+                <refname>sd_is_socket</refname>
+                <refname>sd_is_socket_inet</refname>
+                <refname>sd_is_socket_unix</refname>
+                <refname>sd_is_mq</refname>
+                <refpurpose>Check the type of a file descriptor</refpurpose>
+        </refnamediv>
+
+        <refsynopsisdiv>
+                <funcsynopsis>
+                        <funcsynopsisinfo>#include &lt;systemd/sd-daemon.h&gt;</funcsynopsisinfo>
+
+                        <funcprototype>
+                                <funcdef>int <function>sd_is_fifo</function></funcdef>
+                                <paramdef>int <parameter>fd</parameter></paramdef>
+                                <paramdef>const char *<parameter>path</parameter></paramdef>
+                        </funcprototype>
+
+                        <funcprototype>
+                                <funcdef>int <function>sd_is_socket</function></funcdef>
+                                <paramdef>int <parameter>fd</parameter></paramdef>
+                                <paramdef>int <parameter>family</parameter></paramdef>
+                                <paramdef>int <parameter>type</parameter></paramdef>
+                                <paramdef>int <parameter>listening</parameter></paramdef>
+                        </funcprototype>
+
+                        <funcprototype>
+                                <funcdef>int <function>sd_is_socket_inet</function></funcdef>
+                                <paramdef>int <parameter>fd</parameter></paramdef>
+                                <paramdef>int <parameter>family</parameter></paramdef>
+                                <paramdef>int <parameter>type</parameter></paramdef>
+                                <paramdef>int <parameter>listening</parameter></paramdef>
+                                <paramdef>uint16_t <parameter>port</parameter></paramdef>
+                        </funcprototype>
+
+                        <funcprototype>
+                                <funcdef>int <function>sd_is_socket_unix</function></funcdef>
+                                <paramdef>int <parameter>fd</parameter></paramdef>
+                                <paramdef>int <parameter>type</parameter></paramdef>
+                                <paramdef>int <parameter>listening</parameter></paramdef>
+                                <paramdef>const char* <parameter>path</parameter></paramdef>
+                                <paramdef>size_t <parameter>length</parameter></paramdef>
+                        </funcprototype>
+
+                        <funcprototype>
+                                <funcdef>int <function>sd_is_mq</function></funcdef>
+                                <paramdef>int <parameter>fd</parameter></paramdef>
+                                <paramdef>const char *<parameter>path</parameter></paramdef>
+                        </funcprototype>
+
+                </funcsynopsis>
+        </refsynopsisdiv>
+
+        <refsect1>
+                <title>Description</title>
+
+                <para><function>sd_is_fifo()</function> may be called
+                to check whether the specified file descriptor refers
+                to a FIFO or pipe. If the <parameter>path</parameter>
+                parameter is not NULL, it is checked whether the FIFO
+                is bound to the specified file system path.</para>
+
+                <para><function>sd_is_socket()</function> may be
+                called to check whether the specified file descriptor
+                refers to a socket. It the
+                <parameter>family</parameter> parameter is not
+                AF_UNSPEC it is checked whether the socket is of the
+                specified family (AF_UNIX, AF_INET, ...). If the
+                <parameter>type</parameter> parameter is not 0 it is
+                checked whether the socket is of the specified type
+                (SOCK_STREAM, SOCK_DGRAM, ...). If the
+                <parameter>listening</parameter> parameter is positive
+                it is checked whether the socket is in accepting mode,
+                i.e. <function>listen()</function> has been called for
+                it. If <parameter>listening</parameter> is 0, it is
+                checked whether the socket is not in this mode. If the
+                parameter is negative, no such check is made. The
+                <parameter>listening</parameter> parameter should only
+                be used for stream sockets and should be set to a
+                negative value otherwise.</para>
+
+                <para><function>sd_is_socket_inet()</function> is
+                similar to <function>sd_is_socket()</function>, but
+                optionally checks the IPv4 or IPv6 port number the
+                socket is bound to, unless <parameter>port</parameter>
+                is zero. For this call <parameter>family</parameter>
+                must be passed as either AF_UNSPEC, AF_INET or
+                AF_INET6.</para>
+
+                <para><function>sd_is_socket_unix()</function> is
+                similar to <function>sd_is_socket()</function>, but
+                optionally checks the AF_UNIX path the socket is bound
+                to, unless the <parameter>path</parameter> parameter
+                is NULL. For normal file system AF_UNIX sockets set
+                the <parameter>length</parameter> parameter to 0. For
+                Linux abstract namespace sockets set the
+                <parameter>length</parameter> to the size of the
+                address, including the initial 0 byte and set
+                <parameter>path</parameter> to the initial 0 byte of
+                the socket address.</para>
+
+                <para><function>sd_is_mq()</function> may be called to
+                check whether the specified file descriptor refers to
+                a POSIX message queue. If the
+                <parameter>path</parameter> parameter is not NULL, it
+                is checked whether the message queue is bound to the
+                specified name.</para>
+        </refsect1>
+
+        <refsect1>
+                <title>Return Value</title>
+
+                <para>On failure, these calls return a negative
+                errno-style error code. If the file descriptor is of
+                the specified type and bound to the specified address
+                a positive return value is returned, otherwise
+                zero.</para>
+        </refsect1>
+
+        <refsect1>
+                <title>Notes</title>
+
+                <para>These functions are provided by the reference
+                implementation of APIs for new-style daemons and
+                distributed with the systemd package. The algorithms
+                they implement are simple, and can easily be
+                reimplemented in daemons if it is important to support
+                this interface without using the reference
+                implementation.</para>
+
+                <para>Internally, these function use a combination of
+                <filename>fstat()</filename> and
+                <filename>getsockname()</filename> to check the file
+                descriptor type and where it is bound to.</para>
+
+                <para>For details about the algorithms check the
+                liberally licensed reference implementation sources:
+                <ulink url="http://cgit.freedesktop.org/systemd/systemd/plain/src/sd-daemon.c"/>
+                resp. <ulink
+                url="http://cgit.freedesktop.org/systemd/systemd/plain/src/systemd/sd-daemon.h"/></para>
+
+                <para><function>sd_is_fifo()</function> and the
+                related functions are implemented in the reference
+                implementation's <filename>sd-daemon.c</filename> and
+                <filename>sd-daemon.h</filename> files. These
+                interfaces are available as shared library, which can
+                be compiled and linked to with the
+                <literal>libsystemd-daemon</literal>
+                <citerefentry><refentrytitle>pkg-config</refentrytitle><manvolnum>1</manvolnum></citerefentry>
+                file. Alternatively, applications consuming these APIs
+                may copy the implementation into their source
+                tree. For more details about the reference
+                implementation see
+                <citerefentry><refentrytitle>sd_daemon</refentrytitle><manvolnum>7</manvolnum></citerefentry>.</para>
+
+                <para>These functions continue to work as described,
+                even if -DDISABLE_SYSTEMD is set during
+                compilation.</para>
+        </refsect1>
+
+        <refsect1>
+                <title>See Also</title>
+                <para>
+                        <citerefentry><refentrytitle>systemd</refentrytitle><manvolnum>1</manvolnum></citerefentry>,
+                        <citerefentry><refentrytitle>sd-daemon</refentrytitle><manvolnum>7</manvolnum></citerefentry>,
+                        <citerefentry><refentrytitle>sd_listen_fds</refentrytitle><manvolnum>3</manvolnum></citerefentry>,
+                        <citerefentry><refentrytitle>systemd.service</refentrytitle><manvolnum>5</manvolnum></citerefentry>,
+                        <citerefentry><refentrytitle>systemd.socket</refentrytitle><manvolnum>5</manvolnum></citerefentry>
+                </para>
+        </refsect1>
+
+</refentry>
diff --git a/man/sd_listen_fds.xml b/man/sd_listen_fds.xml
new file mode 100644 (file)
index 0000000..c3c70a0
--- /dev/null
@@ -0,0 +1,203 @@
+<?xml version='1.0'?> <!--*-nxml-*-->
+<!DOCTYPE refentry PUBLIC "-//OASIS//DTD DocBook XML V4.2//EN"
+        "http://www.oasis-open.org/docbook/xml/4.2/docbookx.dtd">
+
+<!--
+  This file is part 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 <http://www.gnu.org/licenses/>.
+-->
+
+<refentry id="sd_listen_fds">
+
+        <refentryinfo>
+                <title>sd_listen_fds</title>
+                <productname>systemd</productname>
+
+                <authorgroup>
+                        <author>
+                                <contrib>Developer</contrib>
+                                <firstname>Lennart</firstname>
+                                <surname>Poettering</surname>
+                                <email>lennart@poettering.net</email>
+                        </author>
+                </authorgroup>
+        </refentryinfo>
+
+        <refmeta>
+                <refentrytitle>sd_listen_fds</refentrytitle>
+                <manvolnum>3</manvolnum>
+        </refmeta>
+
+        <refnamediv>
+                <refname>sd_listen_fds</refname>
+                <refpurpose>Check for file descriptors passed by the init system.</refpurpose>
+        </refnamediv>
+
+        <refsynopsisdiv>
+                <funcsynopsis>
+                        <funcsynopsisinfo>#include &lt;systemd/sd-daemon.h&gt;</funcsynopsisinfo>
+
+                        <funcsynopsisinfo>#define SD_LISTEN_FDS_START 3</funcsynopsisinfo>
+
+                        <funcprototype>
+                                <funcdef>int <function>sd_listen_fds</function></funcdef>
+                                <paramdef>int <parameter>unset_environment</parameter></paramdef>
+                        </funcprototype>
+                </funcsynopsis>
+        </refsynopsisdiv>
+
+        <refsect1>
+                <title>Description</title>
+
+                <para><function>sd_listen_fds()</function> shall be
+                called by a daemon to check for file descriptors
+                passed by the init system as part of the socket-based
+                activation logic.</para>
+
+                <para>If the <parameter>unset_environment</parameter>
+                parameter is non-zero
+                <function>sd_listen_fds()</function> will unset the
+                <varname>$LISTEN_FDS</varname>/<varname>$LISTEN_PID</varname>
+                environment variables before returning (regardless
+                whether the function call itself succeeded or
+                not). Further calls to
+                <function>sd_listen_fds()</function> will then fail,
+                but the variables are no longer inherited by child
+                processes.</para>
+
+                <para>If a daemon receives more than one file
+                descriptor, they will be passed in the same order as
+                configured in the systemd socket definition
+                file. Nonetheless it is recommended to verify the
+                correct socket types before using them. To simplify
+                this checking the functions
+                <citerefentry><refentrytitle>sd_is_fifo</refentrytitle><manvolnum>3</manvolnum></citerefentry>,
+                <citerefentry><refentrytitle>sd_is_socket</refentrytitle><manvolnum>3</manvolnum></citerefentry>,
+                <citerefentry><refentrytitle>sd_is_socket_inet</refentrytitle><manvolnum>3</manvolnum></citerefentry>,
+                <citerefentry><refentrytitle>sd_is_socket_unix</refentrytitle><manvolnum>3</manvolnum></citerefentry>
+                are provided. In order to maximize flexibility it is
+                recommended to make these checks as loose as possible
+                without allowing incorrect setups. i.e. often the
+                actual port number a socket is bound to matters little
+                for the service to work, hence it should not be
+                verified. On the other hand, whether a socket is a
+                datagram or stream socket matters a lot for the most
+                common program logics and should be checked.</para>
+
+                <para>This function call will set the FD_CLOEXEC flag
+                for all passed file descriptors to avoid further
+                inheritance to children of the calling process.</para>
+        </refsect1>
+
+        <refsect1>
+                <title>Return Value</title>
+
+                <para>On failure, this call returns a negative
+                errno-style error code. If
+                <varname>$LISTEN_FDS</varname>/<varname>$LISTEN_PID</varname>
+                was not set or was not correctly set for this daemon and
+                hence no file descriptors were received, 0 is
+                returned. Otherwise the number of file descriptors
+                passed is returned. The application may find them
+                starting with file descriptor SD_LISTEN_FDS_START,
+                i.e. file descriptor 3.</para>
+        </refsect1>
+
+        <refsect1>
+                <title>Notes</title>
+
+                <para>This function is provided by the reference
+                implementation of APIs for new-style daemons and
+                distributed with the systemd package. The algorithm it
+                implements is simple, and can easily be reimplemented
+                in daemons if it is important to support this
+                interface without using the reference
+                implementation.</para>
+
+                <para>Internally, this function checks whether the
+                <varname>$LISTEN_PID</varname> environment variable
+                equals the daemon PID. If not, it returns
+                immediately. Otherwise it parses the number passed in
+                the <varname>$LISTEN_FDS</varname> environment
+                variable, then sets the FD_CLOEXEC flag for the parsed
+                number of file descriptors starting from
+                SD_LISTEN_FDS_START. Finally it returns the parsed
+                number.</para>
+
+                <para>For details about the algorithm check the
+                liberally licensed reference implementation sources:
+                <ulink url="http://cgit.freedesktop.org/systemd/systemd/plain/src/sd-daemon.c"/>
+                resp. <ulink
+                url="http://cgit.freedesktop.org/systemd/systemd/plain/src/systemd/sd-daemon.h"/></para>
+
+                <para><function>sd_listen_fds()</function> is
+                implemented in the reference implementation's
+                <filename>sd-daemon.c</filename> and
+                <filename>sd-daemon.h</filename> files. These
+                interfaces are available as shared library, which can
+                be compiled and linked to with the
+                <literal>libsystemd-daemon</literal>
+                <citerefentry><refentrytitle>pkg-config</refentrytitle><manvolnum>1</manvolnum></citerefentry>
+                file. Alternatively, applications consuming these APIs
+                may copy the implementation into their source
+                tree. For more details about the reference
+                implementation see
+                <citerefentry><refentrytitle>sd-daemon</refentrytitle><manvolnum>7</manvolnum></citerefentry>.</para>
+
+                <para>If the reference implementation is used as
+                drop-in files and -DDISABLE_SYSTEMD is set during
+                compilation this function will always return 0 and
+                otherwise become a NOP.</para>
+        </refsect1>
+
+        <refsect1>
+                <title>Environment</title>
+
+                <variablelist>
+                        <varlistentry>
+                                <term><varname>$LISTEN_PID</varname></term>
+                                <term><varname>$LISTEN_FDS</varname></term>
+
+                                <listitem><para>Set by the init system
+                                for supervised processes that use
+                                socket-based activation. This
+                                environment variable specifies the
+                                data
+                                <function>sd_listen_fds()</function>
+                                parses. See above for
+                                details.</para></listitem>
+                        </varlistentry>
+                </variablelist>
+        </refsect1>
+
+        <refsect1>
+                <title>See Also</title>
+
+                <para>
+                        <citerefentry><refentrytitle>systemd</refentrytitle><manvolnum>1</manvolnum></citerefentry>,
+                        <citerefentry><refentrytitle>sd-daemon</refentrytitle><manvolnum>7</manvolnum></citerefentry>,
+                        <citerefentry><refentrytitle>sd_is_fifo</refentrytitle><manvolnum>3</manvolnum></citerefentry>,
+                        <citerefentry><refentrytitle>sd_is_socket</refentrytitle><manvolnum>3</manvolnum></citerefentry>,
+                        <citerefentry><refentrytitle>sd_is_socket_inet</refentrytitle><manvolnum>3</manvolnum></citerefentry>,
+                        <citerefentry><refentrytitle>sd_is_socket_unix</refentrytitle><manvolnum>3</manvolnum></citerefentry>,
+                        <citerefentry><refentrytitle>daemon</refentrytitle><manvolnum>7</manvolnum></citerefentry>,
+                        <citerefentry><refentrytitle>systemd.service</refentrytitle><manvolnum>5</manvolnum></citerefentry>,
+                        <citerefentry><refentrytitle>systemd.socket</refentrytitle><manvolnum>5</manvolnum></citerefentry>
+                </para>
+        </refsect1>
+
+</refentry>
diff --git a/man/sd_login_monitor_new.xml b/man/sd_login_monitor_new.xml
new file mode 100644 (file)
index 0000000..de48432
--- /dev/null
@@ -0,0 +1,172 @@
+<?xml version='1.0'?> <!--*-nxml-*-->
+<!DOCTYPE refentry PUBLIC "-//OASIS//DTD DocBook XML V4.2//EN"
+        "http://www.oasis-open.org/docbook/xml/4.2/docbookx.dtd">
+
+<!--
+  This file is part 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 <http://www.gnu.org/licenses/>.
+-->
+
+<refentry id="sd_login_monitor_new">
+
+        <refentryinfo>
+                <title>sd_login_monitor_new</title>
+                <productname>systemd</productname>
+
+                <authorgroup>
+                        <author>
+                                <contrib>Developer</contrib>
+                                <firstname>Lennart</firstname>
+                                <surname>Poettering</surname>
+                                <email>lennart@poettering.net</email>
+                        </author>
+                </authorgroup>
+        </refentryinfo>
+
+        <refmeta>
+                <refentrytitle>sd_login_monitor_new</refentrytitle>
+                <manvolnum>3</manvolnum>
+        </refmeta>
+
+        <refnamediv>
+                <refname>sd_login_monitor_new</refname>
+                <refname>sd_login_monitor_unref</refname>
+                <refname>sd_login_monitor_flush</refname>
+                <refname>sd_login_monitor_get_fd</refname>
+                <refpurpose>Monitor login sessions, seats and users</refpurpose>
+        </refnamediv>
+
+        <refsynopsisdiv>
+                <funcsynopsis>
+                        <funcsynopsisinfo>#include &lt;systemd/sd-login.h&gt;</funcsynopsisinfo>
+
+                        <funcprototype>
+                                <funcdef>int <function>sd_login_monitor_new</function></funcdef>
+                                <paramdef>const char* <parameter>category</parameter></paramdef>
+                                <paramdef>sd_login_monitor** <parameter>ret</parameter></paramdef>
+                        </funcprototype>
+
+                        <funcprototype>
+                                <funcdef>sd_login_monitor* <function>sd_login_monitor_unref</function></funcdef>
+                                <paramdef>sd_login_monitor* <parameter>m</parameter></paramdef>
+                        </funcprototype>
+
+                        <funcprototype>
+                                <funcdef>int <function>sd_login_monitor_flush</function></funcdef>
+                                <paramdef>sd_login_monitor* <parameter>m</parameter></paramdef>
+                        </funcprototype>
+
+                        <funcprototype>
+                                <funcdef>int <function>sd_login_monitor_get_fd</function></funcdef>
+                                <paramdef>sd_login_monitor* <parameter>m</parameter></paramdef>
+                        </funcprototype>
+
+                </funcsynopsis>
+        </refsynopsisdiv>
+
+        <refsect1>
+                <title>Description</title>
+
+                <para><function>sd_login_monitor_new()</function> may
+                be used to monitor login session, users and seats. Via
+                a monitor object a file descriptor can be integrated
+                into an application defined event loop which is woken
+                up each time a user logs in, logs out or a seat is
+                added or removed, or a session, user, or seat changes
+                state otherwise. The first parameter takes a string
+                which can be either <literal>seat</literal> (to get
+                only notifications about seats being added, removed or
+                changed), <literal>session</literal> (to get only
+                notifications about sessions being created or removed
+                or changed) or <literal>uid</literal> (to get only
+                notifications when a user changes state in respect to
+                logins). If notifications shall be generated in all
+                these conditions, NULL may be passed. Note that in
+                future additional categories may be defined. The
+                second parameter returns a monitor object and needs to
+                be freed with the
+                <function>sd_login_monitor_unref()</function> call
+                after use.</para>
+
+                <para><function>sd_login_monitor_unref()</function>
+                may be used to destroy a monitor object. Note that
+                this will invalidate any file descriptor returned by
+                <function>sd_login_monitor_get_fd()</function>.</para>
+
+                <para><function>sd_login_monitor_flush()</function>
+                may be used to reset the wakeup state of the monitor
+                object. Whenever an event causes the monitor to wake
+                up the event loop via the file descriptor this
+                function needs to be called to reset the wake-up
+                state. If this call is not invoked the file descriptor
+                will immediately wake up the event loop again.</para>
+
+                <para><function>sd_login_monitor_get_fd()</function>
+                may be used to retrieve the file descriptor of the
+                monitor object that may be integrated in an
+                application defined event loop, based around
+                <citerefentry><refentrytitle>poll</refentrytitle><manvolnum>2</manvolnum></citerefentry>
+                or a similar interface. The application should include
+                the returned file descriptor as wake up source for
+                POLLIN events. Whenever a wake-up is triggered the
+                file descriptor needs to be reset via
+                <function>sd_login_monitor_flush()</function>. An
+                application needs to reread the login state with a
+                function like
+                <citerefentry><refentrytitle>sd_get_seats</refentrytitle><manvolnum>3</manvolnum></citerefentry>
+                or similar to determine what changed.</para>
+        </refsect1>
+
+        <refsect1>
+                <title>Return Value</title>
+
+                <para>On success
+                <function>sd_login_monitor_new()</function> and
+                <function>sd_login_monitor_flush()</function> return 0
+                or a positive integer. On success
+                <function>sd_login_monitor_get_fd()</function> returns
+                a Unix file descriptor. On failure, these calls return
+                a negative errno-style error code.</para>
+
+                <para><function>sd_login_monitor_unref()</function>
+                always returns NULL.</para>
+        </refsect1>
+
+        <refsect1>
+                <title>Notes</title>
+
+                <para>The <function>sd_login_monitor_new()</function>,
+                <function>sd_login_monitor_unref()</function>, <function>sd_login_monitor_flush()</function> and
+                <function>sd_login_monitor_get_fd()</function> interfaces
+                are available as shared library, which can be compiled
+                and linked to with the
+                <literal>libsystemd-login</literal>
+                <citerefentry><refentrytitle>pkg-config</refentrytitle><manvolnum>1</manvolnum></citerefentry>
+                file.</para>
+        </refsect1>
+
+        <refsect1>
+                <title>See Also</title>
+
+                <para>
+                        <citerefentry><refentrytitle>systemd</refentrytitle><manvolnum>1</manvolnum></citerefentry>,
+                        <citerefentry><refentrytitle>sd-login</refentrytitle><manvolnum>7</manvolnum></citerefentry>,
+                        <citerefentry><refentrytitle>sd_get_seats</refentrytitle><manvolnum>3</manvolnum></citerefentry>
+                </para>
+        </refsect1>
+
+</refentry>
diff --git a/man/sd_notify.xml b/man/sd_notify.xml
new file mode 100644 (file)
index 0000000..9d9ea41
--- /dev/null
@@ -0,0 +1,312 @@
+<?xml version='1.0'?> <!--*-nxml-*-->
+<!DOCTYPE refentry PUBLIC "-//OASIS//DTD DocBook XML V4.2//EN"
+        "http://www.oasis-open.org/docbook/xml/4.2/docbookx.dtd">
+
+<!--
+  This file is part 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 <http://www.gnu.org/licenses/>.
+-->
+
+<refentry id="sd_notify">
+
+        <refentryinfo>
+                <title>sd_notify</title>
+                <productname>systemd</productname>
+
+                <authorgroup>
+                        <author>
+                                <contrib>Developer</contrib>
+                                <firstname>Lennart</firstname>
+                                <surname>Poettering</surname>
+                                <email>lennart@poettering.net</email>
+                        </author>
+                </authorgroup>
+        </refentryinfo>
+
+        <refmeta>
+                <refentrytitle>sd_notify</refentrytitle>
+                <manvolnum>3</manvolnum>
+        </refmeta>
+
+        <refnamediv>
+                <refname>sd_notify</refname>
+                <refname>sd_notifyf</refname>
+                <refpurpose>Notify init system about start-up completion and other daemon status changes</refpurpose>
+        </refnamediv>
+
+        <refsynopsisdiv>
+                <funcsynopsis>
+                        <funcsynopsisinfo>#include &lt;systemd/sd-daemon.h&gt;</funcsynopsisinfo>
+
+                        <funcprototype>
+                                <funcdef>int <function>sd_notify</function></funcdef>
+                                <paramdef>int <parameter>unset_environment</parameter></paramdef>
+                                <paramdef>const char *<parameter>state</parameter></paramdef>
+                        </funcprototype>
+
+                        <funcprototype>
+                                <funcdef>int <function>sd_notifyf</function></funcdef>
+                                <paramdef>int <parameter>unset_environment</parameter></paramdef>
+                                <paramdef>const char *<parameter>format</parameter></paramdef>
+                                <paramdef>...</paramdef>
+                        </funcprototype>
+                </funcsynopsis>
+        </refsynopsisdiv>
+
+        <refsect1>
+                <title>Description</title>
+                <para><function>sd_notify()</function> shall be called
+                by a daemon to notify the init system about status
+                changes. It can be used to send arbitrary information,
+                encoded in an environment-block-like string. Most
+                importantly it can be used for start-up completion
+                notification.</para>
+
+                <para>If the <parameter>unset_environment</parameter>
+                parameter is non-zero <function>sd_notify()</function>
+                will unset the <varname>$NOTIFY_SOCKET</varname>
+                environment variable before returning (regardless
+                whether the function call itself succeeded or
+                not). Further calls to
+                <function>sd_notify()</function> will then fail, but
+                the variable is no longer inherited by child
+                processes.</para>
+
+                <para>The <parameter>state</parameter> parameter
+                should contain an newline-separated list of variable
+                assignments, similar in style to an environment
+                block. A trailing newline is implied if none is
+                specified. The string may contain any kind of variable
+                assignments, but the following shall be considered
+                well-known:</para>
+
+                <variablelist>
+                        <varlistentry>
+                                <term>READY=1</term>
+
+                                <listitem><para>Tells the init system
+                                that daemon startup is finished. This
+                                is only used by systemd if the service
+                                definition file has Type=notify
+                                set. The passed argument is a boolean
+                                "1" or "0". Since there is little
+                                value in signalling non-readiness, the
+                                only value daemons should send is
+                                "READY=1".</para></listitem>
+                        </varlistentry>
+
+                        <varlistentry>
+                                <term>STATUS=...</term>
+
+                                <listitem><para>Passes a single-line
+                                status string back to the init system
+                                that describes the daemon state. This
+                                is free-form and can be used for
+                                various purposes: general state
+                                feedback, fsck-like programs could
+                                pass completion percentages and
+                                failing programs could pass a human
+                                readable error message. Example:
+                                "STATUS=Completed 66% of file system
+                                check..."</para></listitem>
+                        </varlistentry>
+
+                        <varlistentry>
+                                <term>ERRNO=...</term>
+
+                                <listitem><para>If a daemon fails, the
+                                errno-style error code, formatted as
+                                string. Example: "ERRNO=2" for
+                                ENOENT.</para></listitem>
+                        </varlistentry>
+
+                        <varlistentry>
+                                <term>BUSERROR=...</term>
+
+                                <listitem><para>If a daemon fails, the
+                                D-Bus error-style error code. Example:
+                                "BUSERROR=org.freedesktop.DBus.Error.TimedOut"</para></listitem>
+                        </varlistentry>
+
+                        <varlistentry>
+                                <term>MAINPID=...</term>
+
+                                <listitem><para>The main pid of the
+                                daemon, in case the init system did
+                                not fork off the process
+                                itself. Example:
+                                "MAINPID=4711"</para></listitem>
+                        </varlistentry>
+
+                        <varlistentry>
+                                <term>WATCHDOG=1</term>
+
+                                <listitem><para>Tells systemd to
+                                update the watchdog timestamp.
+                                Services using this feature should do
+                                this in regular intervals. A watchdog
+                                framework can use the timestamps to
+                                detect failed
+                                services.</para></listitem>
+                        </varlistentry>
+                </variablelist>
+
+                <para>It is recommended to prefix variable names that
+                are not shown in the list above with
+                <varname>X_</varname> to avoid namespace
+                clashes.</para>
+
+                <para>Note that systemd will accept status data sent
+                from a daemon only if the
+                <varname>NotifyAccess=</varname> option is correctly
+                set in the service definition file. See
+                <citerefentry><refentrytitle>systemd.service</refentrytitle><manvolnum>5</manvolnum></citerefentry>
+                for details.</para>
+
+                <para><function>sd_notifyf()</function> is similar to
+                <function>sd_notify()</function> but takes a
+                <function>printf()</function>-like format string plus
+                arguments.</para>
+        </refsect1>
+
+        <refsect1>
+                <title>Return Value</title>
+
+                <para>On failure, these calls return a negative
+                errno-style error code. If
+                <varname>$NOTIFY_SOCKET</varname> was not set and
+                hence no status data could be sent, 0 is returned. If
+                the status was sent these functions return with a
+                positive return value. In order to support both, init
+                systems that implement this scheme and those which
+                don't, it is generally recommended to ignore the return
+                value of this call.</para>
+        </refsect1>
+
+        <refsect1>
+                <title>Notes</title>
+
+                <para>These functions are provided by the reference
+                implementation of APIs for new-style daemons and
+                distributed with the systemd package. The algorithms
+                they implement are simple, and can easily be
+                reimplemented in daemons if it is important to support
+                this interface without using the reference
+                implementation.</para>
+
+                <para>Internally, these functions send a single
+                datagram with the state string as payload to the
+                AF_UNIX socket referenced in the
+                <varname>$NOTIFY_SOCKET</varname> environment
+                variable. If the first character of
+                <varname>$NOTIFY_SOCKET</varname> is @ the string is
+                understood as Linux abstract namespace socket. The
+                datagram is accompanied by the process credentials of
+                the sending daemon, using SCM_CREDENTIALS.</para>
+
+                <para>For details about the algorithms check the
+                liberally licensed reference implementation sources:
+                <ulink url="http://cgit.freedesktop.org/systemd/systemd/plain/src/sd-daemon.c"/>
+                resp. <ulink
+                url="http://cgit.freedesktop.org/systemd/systemd/plain/src/systemd/sd-daemon.h"/></para>
+
+                <para><function>sd_notify()</function> and
+                <function>sd_notifyf()</function> are implemented in
+                the reference implementation's
+                <filename>sd-daemon.c</filename> and
+                <filename>sd-daemon.h</filename> files. These
+                interfaces are available as shared library, which can
+                be compiled and linked to with the
+                <literal>libsystemd-daemon</literal>
+                <citerefentry><refentrytitle>pkg-config</refentrytitle><manvolnum>1</manvolnum></citerefentry>
+                file. Alternatively, applications consuming these APIs
+                may copy the implementation into their source tree. For
+                more details about the reference implementation see
+                <citerefentry><refentrytitle>sd_daemon</refentrytitle><manvolnum>7</manvolnum></citerefentry>.</para>
+
+                <para>If the reference implementation is used as
+                drop-in files and -DDISABLE_SYSTEMD is set during
+                compilation these functions will always return 0 and
+                otherwise become a NOP.</para>
+        </refsect1>
+
+        <refsect1>
+                <title>Environment</title>
+
+                <variablelist>
+                        <varlistentry>
+                                <term><varname>$NOTIFY_SOCKET</varname></term>
+
+                                <listitem><para>Set by the init system
+                                for supervised processes for status
+                                and start-up completion
+                                notification. This environment variable
+                                specifies the socket
+                                <function>sd_notify()</function> talks
+                                to. See above for details.</para></listitem>
+                        </varlistentry>
+                </variablelist>
+        </refsect1>
+
+        <refsect1>
+                <title>Examples</title>
+
+                <example>
+                        <title>Start-up Notification</title>
+
+                        <para>When a daemon finished starting up, it
+                        might issue the following call to notify
+                        the init system:</para>
+
+                        <programlisting>sd_notify(0, "READY=1");</programlisting>
+                </example>
+
+                <example>
+                        <title>Extended Start-up Notification</title>
+
+                        <para>A daemon could send the following after
+                        completing initialization:</para>
+
+                        <programlisting>sd_notifyf(0, "READY=1\n"
+              "STATUS=Processing requests...\n"
+              "MAINPID=%lu",
+              (unsigned long) getpid());</programlisting>
+                </example>
+
+                <example>
+                        <title>Error Cause Notification</title>
+
+                        <para>A daemon could send the following shortly before exiting, on failure</para>
+
+                        <programlisting>sd_notifyf(0, "STATUS=Failed to start up: %s\n"
+              "ERRNO=%i",
+              strerror(errno),
+              errno);</programlisting>
+                </example>
+        </refsect1>
+
+        <refsect1>
+                <title>See Also</title>
+                <para>
+                        <citerefentry><refentrytitle>systemd</refentrytitle><manvolnum>1</manvolnum></citerefentry>,
+                        <citerefentry><refentrytitle>sd_daemon</refentrytitle><manvolnum>7</manvolnum></citerefentry>,
+                        <citerefentry><refentrytitle>daemon</refentrytitle><manvolnum>7</manvolnum></citerefentry>,
+                        <citerefentry><refentrytitle>systemd.service</refentrytitle><manvolnum>5</manvolnum></citerefentry>
+                </para>
+        </refsect1>
+
+</refentry>
diff --git a/man/sd_pid_get_session.xml b/man/sd_pid_get_session.xml
new file mode 100644 (file)
index 0000000..94f5330
--- /dev/null
@@ -0,0 +1,160 @@
+<?xml version='1.0'?> <!--*-nxml-*-->
+<!DOCTYPE refentry PUBLIC "-//OASIS//DTD DocBook XML V4.2//EN"
+        "http://www.oasis-open.org/docbook/xml/4.2/docbookx.dtd">
+
+<!--
+  This file is part 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 <http://www.gnu.org/licenses/>.
+-->
+
+<refentry id="sd_pid_get_session">
+
+        <refentryinfo>
+                <title>sd_pid_get_session</title>
+                <productname>systemd</productname>
+
+                <authorgroup>
+                        <author>
+                                <contrib>Developer</contrib>
+                                <firstname>Lennart</firstname>
+                                <surname>Poettering</surname>
+                                <email>lennart@poettering.net</email>
+                        </author>
+                </authorgroup>
+        </refentryinfo>
+
+        <refmeta>
+                <refentrytitle>sd_pid_get_session</refentrytitle>
+                <manvolnum>3</manvolnum>
+        </refmeta>
+
+        <refnamediv>
+                <refname>sd_pid_get_session</refname>
+                <refname>sd_pid_get_unit</refname>
+                <refname>sd_pid_get_owner_uid</refname>
+                <refpurpose>Determine session, service or owner of a session of a specific PID</refpurpose>
+        </refnamediv>
+
+        <refsynopsisdiv>
+                <funcsynopsis>
+                        <funcsynopsisinfo>#include &lt;systemd/sd-login.h&gt;</funcsynopsisinfo>
+
+                        <funcprototype>
+                                <funcdef>int <function>sd_pid_get_session</function></funcdef>
+                                <paramdef>pid_t <parameter>pid</parameter></paramdef>
+                                <paramdef>char** <parameter>session</parameter></paramdef>
+                        </funcprototype>
+
+                        <funcprototype>
+                                <funcdef>int <function>sd_pid_get_unit</function></funcdef>
+                                <paramdef>pid_t <parameter>pid</parameter></paramdef>
+                                <paramdef>char** <parameter>unit</parameter></paramdef>
+                        </funcprototype>
+
+                        <funcprototype>
+                                <funcdef>int <function>sd_pid_get_owner_uid</function></funcdef>
+                                <paramdef>pid_t <parameter>pid</parameter></paramdef>
+                                <paramdef>uid_t* <parameter>uid</parameter></paramdef>
+                        </funcprototype>
+                </funcsynopsis>
+        </refsynopsisdiv>
+
+        <refsect1>
+                <title>Description</title>
+
+                <para><function>sd_pid_get_session()</function> may be
+                used to determine the login session identifier of a
+                process identified by the specified process
+                identifier. The session identifier is a short string,
+                suitable for usage in file system paths. Note that not
+                all processes are part of a login session (e.g. system
+                service processes, user processes that are shared
+                between multiple sessions of the same user, or kernel
+                threads). For processes not being part of a login
+                session this function will fail. The returned string
+                needs to be freed with the libc
+                <citerefentry><refentrytitle>free</refentrytitle><manvolnum>3</manvolnum></citerefentry>
+                call after use.</para>
+
+                <para><function>sd_pid_get_unit()</function> may be
+                used to determine the systemd unit (i.e. system
+                service) identifier of a process identified by the
+                specified process identifier. The unit name is a short
+                string, suitable for usage in file system paths. Note
+                that not all processes are part of a unit/service
+                (e.g. user processes, or kernel threads). For
+                processes not being part of a systemd unit/system
+                service this function will fail. The returned string
+                needs to be freed with the libc
+                <citerefentry><refentrytitle>free</refentrytitle><manvolnum>3</manvolnum></citerefentry>
+                call after use.</para>
+
+                <para><function>sd_pid_get_owner_uid()</function> may
+                be used to determine the Unix user identifier of the
+                owner of the session of a process identified the
+                specified PID. Note that this function will succeed
+                for user processes which are shared between multiple
+                login sessions of the same user, where
+                <function>sd_pid_get_session()</function> will
+                fail. For processes not being part of a login session
+                and not being a shared process of a user this function
+                will fail.</para>
+
+                <para>If the <literal>pid</literal> paramater of any
+                of these functions is passed as 0 the operation is
+                executed for the calling process.</para>
+        </refsect1>
+
+        <refsect1>
+                <title>Return Value</title>
+
+                <para>On success these calls return 0 or a positive
+                integer. On failure, these calls return a negative
+                errno-style error code.</para>
+        </refsect1>
+
+        <refsect1>
+                <title>Notes</title>
+
+                <para>The <function>sd_pid_get_session()</function>,
+                <function>sd_pid_get_pid()</function>, and
+                <function>sd_pid_get_owner_uid()</function> interfaces
+                are available as shared library, which can be compiled
+                and linked to with the
+                <literal>libsystemd-login</literal>
+                <citerefentry><refentrytitle>pkg-config</refentrytitle><manvolnum>1</manvolnum></citerefentry>
+                file.</para>
+
+                <para>Note that the login session identifier as
+                returned by <function>sd_pid_get_session()</function>
+                is completely unrelated to the process session
+                identifier as returned by
+                <citerefentry><refentrytitle>getsid</refentrytitle><manvolnum>2</manvolnum></citerefentry>.</para>
+        </refsect1>
+
+        <refsect1>
+                <title>See Also</title>
+
+                <para>
+                        <citerefentry><refentrytitle>systemd</refentrytitle><manvolnum>1</manvolnum></citerefentry>,
+                        <citerefentry><refentrytitle>sd-login</refentrytitle><manvolnum>7</manvolnum></citerefentry>,
+                        <citerefentry><refentrytitle>sd_session_is_active</refentrytitle><manvolnum>3</manvolnum></citerefentry>,
+                        <citerefentry><refentrytitle>getsid</refentrytitle><manvolnum>2</manvolnum></citerefentry>
+                </para>
+        </refsect1>
+
+</refentry>
diff --git a/man/sd_readahead.xml b/man/sd_readahead.xml
new file mode 100644 (file)
index 0000000..2e7e09c
--- /dev/null
@@ -0,0 +1,178 @@
+<?xml version='1.0'?> <!--*-nxml-*-->
+<!DOCTYPE refentry PUBLIC "-//OASIS//DTD DocBook XML V4.2//EN"
+        "http://www.oasis-open.org/docbook/xml/4.2/docbookx.dtd">
+
+<!--
+  This file is part 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 <http://www.gnu.org/licenses/>.
+-->
+
+<refentry id="sd_notify">
+
+        <refentryinfo>
+                <title>sd_readahead</title>
+                <productname>systemd</productname>
+
+                <authorgroup>
+                        <author>
+                                <contrib>Developer</contrib>
+                                <firstname>Lennart</firstname>
+                                <surname>Poettering</surname>
+                                <email>lennart@poettering.net</email>
+                        </author>
+                </authorgroup>
+        </refentryinfo>
+
+        <refmeta>
+                <refentrytitle>sd_readahead</refentrytitle>
+                <manvolnum>3</manvolnum>
+        </refmeta>
+
+        <refnamediv>
+                <refname>sd_readahead</refname>
+                <refpurpose>Control ongoing disk boot-time read-ahead operations</refpurpose>
+        </refnamediv>
+
+        <refsynopsisdiv>
+                <funcsynopsis>
+                        <funcsynopsisinfo>#include "sd-readahead.h"</funcsynopsisinfo>
+
+                        <funcprototype>
+                                <funcdef>int <function>sd_readahead</function></funcdef>
+                                <paramdef>const char *<parameter>action</parameter></paramdef>
+                        </funcprototype>
+                </funcsynopsis>
+        </refsynopsisdiv>
+
+        <refsect1>
+                <title>Description</title>
+                <para><function>sd_readahead()</function> may be
+                called by programs involved with early boot-up to
+                control ongoing boot-time disk read-ahead operations. It may be
+                used to terminate read-ahead operations in case an
+                uncommon disk access pattern is to be expected and
+                hence read-ahead replay or collection is unlikely to
+                have the desired speed-up effect on the current or
+                future boot-ups.</para>
+
+                <para>The <parameter>action</parameter> should be one
+                of the following strings:</para>
+
+                <variablelist>
+                        <varlistentry>
+                                <term>cancel</term>
+
+                                <listitem><para>Terminates read-ahead
+                                data collection, and drops all
+                                read-ahead data collected during this
+                                boot-up.</para></listitem>
+                        </varlistentry>
+
+                        <varlistentry>
+                                <term>done</term>
+
+                                <listitem><para>Terminates read-ahead
+                                data collection, but keeps all
+                                read-ahead data collected during this
+                                boot-up around for use during
+                                subsequent boot-ups.</para></listitem>
+                        </varlistentry>
+
+                        <varlistentry>
+                                <term>noreplay</term>
+
+                                <listitem><para>Terminates read-ahead
+                                replay.</para></listitem>
+                        </varlistentry>
+
+                </variablelist>
+
+        </refsect1>
+
+        <refsect1>
+                <title>Return Value</title>
+
+                <para>On failure, these calls return a negative
+                errno-style error code. It is generally recommended to
+                ignore the return value of this call.</para>
+        </refsect1>
+
+        <refsect1>
+                <title>Notes</title>
+
+                <para>This function is provided by the reference
+                implementation of APIs for controlling boot-time
+                read-ahead and distributed with the systemd
+                package. The algorithm it implements is simple, and
+                can easily be reimplemented in daemons if it is
+                important to support this interface without using the
+                reference implementation.</para>
+
+                <para>Internally, this function creates a file in
+                <filename>/run/systemd/readahead/</filename> which is
+                then used as flag file to notify the read-ahead
+                subsystem.</para>
+
+                <para>For details about the algorithm check the
+                liberally licensed reference implementation sources:
+                <ulink url="http://cgit.freedesktop.org/systemd/systemd/plain/src/readahead/sd-readahead.c"/>
+                resp. <ulink
+                url="http://cgit.freedesktop.org/systemd/systemd/plain/src/systemd/sd-readahead.h"/></para>
+
+                <para><function>sd_readahead()</function> is
+                implemented in the reference implementation's drop-in
+                <filename>sd-readahead.c</filename> and
+                <filename>sd-readahead.h</filename> files. It is
+                recommended that applications consuming this API copy
+                the implementation into their source tree. For more
+                details about the reference implementation see
+                <citerefentry><refentrytitle>sd-readahead</refentrytitle><manvolnum>7</manvolnum></citerefentry></para>
+
+                <para>If -DDISABLE_SYSTEMD is set during compilation
+                this function will always return 0 and otherwise
+                become a NOP.</para>
+        </refsect1>
+
+        <refsect1>
+                <title>Examples</title>
+
+                <example>
+                        <title>Cancelling all read-ahead operations</title>
+
+                        <para>During boots where SELinux has to
+                        relabel the file system hierarchy, it will
+                        create a large amount of disk accesses that
+                        are not necessary during normal boots. Hence
+                        it is a good idea to disable both read-ahead replay and read-ahead collection.
+                        </para>
+
+                        <programlisting>sd_readahead("cancel");
+sd_readahead("noreplay");</programlisting>
+                </example>
+
+        </refsect1>
+
+        <refsect1>
+                <title>See Also</title>
+                <para>
+                        <citerefentry><refentrytitle>systemd</refentrytitle><manvolnum>1</manvolnum></citerefentry>,
+                        <citerefentry><refentrytitle>sd-readahead</refentrytitle><manvolnum>7</manvolnum></citerefentry>,
+                        <citerefentry><refentrytitle>daemon</refentrytitle><manvolnum>7</manvolnum></citerefentry>
+                </para>
+        </refsect1>
+
+</refentry>
diff --git a/man/sd_seat_get_active.xml b/man/sd_seat_get_active.xml
new file mode 100644 (file)
index 0000000..acc6ee4
--- /dev/null
@@ -0,0 +1,157 @@
+<?xml version='1.0'?> <!--*-nxml-*-->
+<!DOCTYPE refentry PUBLIC "-//OASIS//DTD DocBook XML V4.2//EN"
+        "http://www.oasis-open.org/docbook/xml/4.2/docbookx.dtd">
+
+<!--
+  This file is part 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 <http://www.gnu.org/licenses/>.
+-->
+
+<refentry id="sd_seat_get_active">
+
+        <refentryinfo>
+                <title>sd_seat_get_active</title>
+                <productname>systemd</productname>
+
+                <authorgroup>
+                        <author>
+                                <contrib>Developer</contrib>
+                                <firstname>Lennart</firstname>
+                                <surname>Poettering</surname>
+                                <email>lennart@poettering.net</email>
+                        </author>
+                </authorgroup>
+        </refentryinfo>
+
+        <refmeta>
+                <refentrytitle>sd_seat_get_active</refentrytitle>
+                <manvolnum>3</manvolnum>
+        </refmeta>
+
+        <refnamediv>
+                <refname>sd_seat_get_active</refname>
+                <refname>sd_seat_get_sessions</refname>
+                <refname>sd_seat_can_multi_session</refname>
+                <refpurpose>Determine state of a specific seat</refpurpose>
+        </refnamediv>
+
+        <refsynopsisdiv>
+                <funcsynopsis>
+                        <funcsynopsisinfo>#include &lt;systemd/sd-login.h&gt;</funcsynopsisinfo>
+
+                        <funcprototype>
+                                <funcdef>int <function>sd_seat_get_active</function></funcdef>
+                                <paramdef>const char* <parameter>seat</parameter></paramdef>
+                                <paramdef>char** <parameter>session</parameter></paramdef>
+                                <paramdef>uid_t* <parameter>uid</parameter></paramdef>
+                        </funcprototype>
+
+                        <funcprototype>
+                                <funcdef>int <function>sd_seat_get_sessions</function></funcdef>
+                                <paramdef>const char* <parameter>seat</parameter></paramdef>
+                                <paramdef>char*** <parameter>sessions</parameter></paramdef>
+                                <paramdef>uid_t** <parameter>uid</parameter></paramdef>
+                                <paramdef>unsigned* <parameter>n_uids</parameter></paramdef>
+                        </funcprototype>
+
+                        <funcprototype>
+                                <funcdef>int <function>sd_seat_can_multi_session</function></funcdef>
+                                <paramdef>const char* <parameter>seat</parameter></paramdef>
+                        </funcprototype>
+                </funcsynopsis>
+        </refsynopsisdiv>
+
+        <refsect1>
+                <title>Description</title>
+
+                <para><function>sd_seat_get_active()</function> may be
+                used to determine which session is currently active on
+                a seat, if there is any. Returns the session
+                identifier and the user identifier of the Unix user
+                the session is belonging to. Either the session or the
+                user identifier parameter can be be passed NULL, in
+                case only one of the parameters shall be queried. The
+                returned string needs to be freed with the libc
+                <citerefentry><refentrytitle>free</refentrytitle><manvolnum>3</manvolnum></citerefentry>
+                call after use.</para>
+
+                <para><function>sd_seat_get_sessions()</function> may
+                be used to determine all sessions on the specified
+                seat. Returns two arrays, one (NULL terminated) with
+                the session identifiers of the sessions and one with
+                the user identifiers of the Unix users the sessions
+                belong to. An additional parameter may be used to
+                return the number of entries in the latter array. The
+                two arrays and the latter parameter may be passed as
+                NULL in case these values need not to be
+                determined. The arrays and the strings referenced by
+                them need to be freed with the libc
+                <citerefentry><refentrytitle>free</refentrytitle><manvolnum>3</manvolnum></citerefentry>
+                call after use. Note that instead of an empty array
+                NULL may be returned and should be considered
+                equivalent to an empty array.</para>
+
+                <para><function>sd_seat_can_multi_session()</function>
+                may be used to determine whether a specific seat is
+                capable of multi-session, i.e. allows multiple login
+                sessions in parallel (whith only one being active at a
+                time).</para>
+
+                <para>If the <literal>seat</literal> parameter of any
+                of these functions is passed as NULL the operation is
+                executed for the seat of the session of the calling
+                process, if there is any.</para>
+        </refsect1>
+
+        <refsect1>
+                <title>Return Value</title>
+
+                <para> On success
+                <function>sd_seat_get_active()</function> return
+                return 0 or a positive integer. On success
+                <function>sd_seat_get_sessions()</function> returns
+                the number of entries in the session identifier
+                array. If the test succeeds
+                <function>sd_seat_can_multi_session</function> returns
+                a positive integer, if it fails 0. On failure, these
+                calls return a negative errno-style error code.</para>
+        </refsect1>
+
+        <refsect1>
+                <title>Notes</title>
+
+                <para>The <function>sd_seat_get_active()</function>,
+                <function>sd_seat_get_sessions()</function>, and
+                <function>sd_seat_can_multi_session()</function> interfaces
+                are available as shared library, which can be compiled
+                and linked to with the
+                <literal>libsystemd-login</literal>
+                <citerefentry><refentrytitle>pkg-config</refentrytitle><manvolnum>1</manvolnum></citerefentry>
+                file.</para>
+        </refsect1>
+
+        <refsect1>
+                <title>See Also</title>
+
+                <para>
+                        <citerefentry><refentrytitle>systemd</refentrytitle><manvolnum>1</manvolnum></citerefentry>,
+                        <citerefentry><refentrytitle>sd-login</refentrytitle><manvolnum>7</manvolnum></citerefentry>,
+                        <citerefentry><refentrytitle>sd_session_get_seat</refentrytitle><manvolnum>3</manvolnum></citerefentry>
+                </para>
+        </refsect1>
+
+</refentry>
diff --git a/man/sd_session_is_active.xml b/man/sd_session_is_active.xml
new file mode 100644 (file)
index 0000000..afdeed5
--- /dev/null
@@ -0,0 +1,206 @@
+<?xml version='1.0'?> <!--*-nxml-*-->
+<!DOCTYPE refentry PUBLIC "-//OASIS//DTD DocBook XML V4.2//EN"
+        "http://www.oasis-open.org/docbook/xml/4.2/docbookx.dtd">
+
+<!--
+  This file is part 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 <http://www.gnu.org/licenses/>.
+-->
+
+<refentry id="sd_session_is_active">
+
+        <refentryinfo>
+                <title>sd_session_is_active</title>
+                <productname>systemd</productname>
+
+                <authorgroup>
+                        <author>
+                                <contrib>Developer</contrib>
+                                <firstname>Lennart</firstname>
+                                <surname>Poettering</surname>
+                                <email>lennart@poettering.net</email>
+                        </author>
+                </authorgroup>
+        </refentryinfo>
+
+        <refmeta>
+                <refentrytitle>sd_session_is_active</refentrytitle>
+                <manvolnum>3</manvolnum>
+        </refmeta>
+
+        <refnamediv>
+                <refname>sd_session_is_active</refname>
+                <refname>sd_session_get_uid</refname>
+                <refname>sd_session_get_seat</refname>
+                <refname>sd_session_get_service</refname>
+                <refname>sd_session_get_type</refname>
+                <refname>sd_session_get_class</refname>
+                <refname>sd_session_get_display</refname>
+                <refpurpose>Determine state of a specific session</refpurpose>
+        </refnamediv>
+
+        <refsynopsisdiv>
+                <funcsynopsis>
+                        <funcsynopsisinfo>#include &lt;systemd/sd-login.h&gt;</funcsynopsisinfo>
+
+                        <funcprototype>
+                                <funcdef>int <function>sd_session_is_active</function></funcdef>
+                                <paramdef>const char* <parameter>session</parameter></paramdef>
+                        </funcprototype>
+
+                        <funcprototype>
+                                <funcdef>int <function>sd_session_get_uid</function></funcdef>
+                                <paramdef>const char* <parameter>session</parameter></paramdef>
+                                <paramdef>uid_t* <parameter>uid</parameter></paramdef>
+                        </funcprototype>
+
+                        <funcprototype>
+                                <funcdef>int <function>sd_session_get_seat</function></funcdef>
+                                <paramdef>const char* <parameter>session</parameter></paramdef>
+                                <paramdef>char** <parameter>seat</parameter></paramdef>
+                        </funcprototype>
+
+                        <funcprototype>
+                                <funcdef>int <function>sd_session_get_service</function></funcdef>
+                                <paramdef>const char* <parameter>session</parameter></paramdef>
+                                <paramdef>char** <parameter>service</parameter></paramdef>
+                        </funcprototype>
+
+                        <funcprototype>
+                                <funcdef>int <function>sd_session_get_type</function></funcdef>
+                                <paramdef>const char* <parameter>session</parameter></paramdef>
+                                <paramdef>char** <parameter>type</parameter></paramdef>
+                        </funcprototype>
+
+                        <funcprototype>
+                                <funcdef>int <function>sd_session_get_class</function></funcdef>
+                                <paramdef>const char* <parameter>session</parameter></paramdef>
+                                <paramdef>char** <parameter>class</parameter></paramdef>
+                        </funcprototype>
+
+                        <funcprototype>
+                                <funcdef>int <function>sd_session_get_display</function></funcdef>
+                                <paramdef>const char* <parameter>session</parameter></paramdef>
+                                <paramdef>char** <parameter>display</parameter></paramdef>
+                        </funcprototype>
+                </funcsynopsis>
+        </refsynopsisdiv>
+
+        <refsect1>
+                <title>Description</title>
+
+                <para><function>sd_session_is_active()</function> may
+                be used to determine whether the session identified by
+                the specified session identifier is currently active
+                (i.e. currently in the foreground and available for
+                user input) or not.</para>
+
+                <para><function>sd_session_get_uid()</function> may be
+                used to determine the user identifier of the Unix user the session
+                identified by the specified session identifier belongs
+                to.</para>
+
+                <para><function>sd_session_get_seat()</function> may
+                be used to determine the seat identifier of the seat
+                the session identified by the specified session
+                identifier belongs to. Note that not all sessions are
+                attached to a seat, this call will fail for them. The
+                returned string needs to be freed with the libc
+                <citerefentry><refentrytitle>free</refentrytitle><manvolnum>3</manvolnum></citerefentry>
+                call after use.</para>
+
+                <para><function>sd_session_get_service()</function>
+                may be used to determine the name of the service (as
+                passed during PAM session setup) that registered the
+                session identified by the specified session
+                identifier. The returned string needs to be freed with
+                the libc
+                <citerefentry><refentrytitle>free</refentrytitle><manvolnum>3</manvolnum></citerefentry>
+                call after use.</para>
+
+                <para><function>sd_session_get_type()</function> may
+                be used to determine the type of the session
+                identified by the specified session identifier. The
+                returned string is one of <literal>x11</literal>,
+                <literal>tty</literal> or
+                <literal>unspecified</literal> and needs to be freed
+                with the libc
+                <citerefentry><refentrytitle>free</refentrytitle><manvolnum>3</manvolnum></citerefentry>
+                call after use.</para>
+
+                <para><function>sd_session_get_class()</function> may
+                be used to determine the class of the session
+                identified by the specified session identifier. The
+                returned string is one of <literal>user</literal>,
+                <literal>greeter</literal> or
+                <literal>lock-screen</literal> and needs to be freed
+                with the libc
+                <citerefentry><refentrytitle>free</refentrytitle><manvolnum>3</manvolnum></citerefentry>
+                call after use.</para>
+
+                <para><function>sd_session_get_display()</function>
+                may be used to determine the X11 display of the
+                session identified by the specified session
+                identifier. The returned string is one of needs to be
+                freed with the libc
+                <citerefentry><refentrytitle>free</refentrytitle><manvolnum>3</manvolnum></citerefentry>
+                call after use.</para>
+
+                <para>If the <literal>session</literal> parameter of
+                any of these functions is passed as NULL the operation
+                is executed for the session the calling process is a
+                member of, if there is any.</para>
+        </refsect1>
+
+        <refsect1>
+                <title>Return Value</title>
+
+                <para>If the test succeeds
+                <function>sd_session_is_active()</function> returns a
+                positive integer, if it fails 0.  On success
+                <function>sd_session_get_uid()</function>,
+                <function>sd_session_get_service()</function> and
+                <function>sd_session_get_seat()</function> return 0 or
+                a positive integer. On failure, these calls return a
+                negative errno-style error code.</para>
+        </refsect1>
+
+        <refsect1>
+                <title>Notes</title>
+
+                <para>The <function>sd_session_is_active()</function>,
+                <function>sd_session_get_uid()</function>,
+                <function>sd_session_get_service()</function> and
+                <function>sd_session_get_seat()</function> interfaces
+                are available as shared library, which can be compiled
+                and linked to with the
+                <literal>libsystemd-login</literal>
+                <citerefentry><refentrytitle>pkg-config</refentrytitle><manvolnum>1</manvolnum></citerefentry>
+                file.</para>
+        </refsect1>
+
+        <refsect1>
+                <title>See Also</title>
+
+                <para>
+                        <citerefentry><refentrytitle>systemd</refentrytitle><manvolnum>1</manvolnum></citerefentry>,
+                        <citerefentry><refentrytitle>sd-login</refentrytitle><manvolnum>7</manvolnum></citerefentry>,
+                        <citerefentry><refentrytitle>sd_pid_get_session</refentrytitle><manvolnum>3</manvolnum></citerefentry>
+                </para>
+        </refsect1>
+
+</refentry>
diff --git a/man/sd_uid_get_state.xml b/man/sd_uid_get_state.xml
new file mode 100644 (file)
index 0000000..9249021
--- /dev/null
@@ -0,0 +1,185 @@
+<?xml version='1.0'?> <!--*-nxml-*-->
+<!DOCTYPE refentry PUBLIC "-//OASIS//DTD DocBook XML V4.2//EN"
+        "http://www.oasis-open.org/docbook/xml/4.2/docbookx.dtd">
+
+<!--
+  This file is part 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 <http://www.gnu.org/licenses/>.
+-->
+
+<refentry id="sd_uid_get_state">
+
+        <refentryinfo>
+                <title>sd_uid_get_state</title>
+                <productname>systemd</productname>
+
+                <authorgroup>
+                        <author>
+                                <contrib>Developer</contrib>
+                                <firstname>Lennart</firstname>
+                                <surname>Poettering</surname>
+                                <email>lennart@poettering.net</email>
+                        </author>
+                </authorgroup>
+        </refentryinfo>
+
+        <refmeta>
+                <refentrytitle>sd_uid_get_state</refentrytitle>
+                <manvolnum>3</manvolnum>
+        </refmeta>
+
+        <refnamediv>
+                <refname>sd_uid_get_state</refname>
+                <refname>sd_uid_is_on_seat</refname>
+                <refname>sd_uid_get_sessions</refname>
+                <refname>sd_uid_get_seats</refname>
+                <refpurpose>Determine login state of a specific Unix user ID</refpurpose>
+        </refnamediv>
+
+        <refsynopsisdiv>
+                <funcsynopsis>
+                        <funcsynopsisinfo>#include &lt;systemd/sd-login.h&gt;</funcsynopsisinfo>
+
+                        <funcprototype>
+                                <funcdef>int <function>sd_uid_get_state</function></funcdef>
+                                <paramdef>uid_t <parameter>uid</parameter></paramdef>
+                                <paramdef>char** <parameter>state</parameter></paramdef>
+                        </funcprototype>
+
+                        <funcprototype>
+                                <funcdef>int <function>sd_uid_is_on_seat</function></funcdef>
+                                <paramdef>uid_t <parameter>uid</parameter></paramdef>
+                                <paramdef>int <parameter>require_active</parameter></paramdef>
+                                <paramdef>const char* <parameter>seat</parameter></paramdef>
+                        </funcprototype>
+
+                        <funcprototype>
+                                <funcdef>int <function>sd_uid_get_sessions</function></funcdef>
+                                <paramdef>uid_t <parameter>uid</parameter></paramdef>
+                                <paramdef>int <parameter>require_active</parameter></paramdef>
+                                <paramdef>char*** <parameter>sessions</parameter></paramdef>
+                        </funcprototype>
+
+                        <funcprototype>
+                                <funcdef>int <function>sd_uid_get_seats</function></funcdef>
+                                <paramdef>uid_t <parameter>uid</parameter></paramdef>
+                                <paramdef>int <parameter>require_active</parameter></paramdef>
+                                <paramdef>char*** <parameter>seats</parameter></paramdef>
+                        </funcprototype>
+                </funcsynopsis>
+        </refsynopsisdiv>
+
+        <refsect1>
+                <title>Description</title>
+
+                <para><function>sd_uid_get_state()</function> may be
+                used to determine the login state of a specific Unix
+                user identifier. The following states are currently
+                known: <literal>offline</literal> (user not logged in
+                at all), <literal>lingering</literal> (user not logged
+                in, but some user services running),
+                <literal>online</literal> (user logged in, but not
+                active), <literal>active</literal> (user logged in on
+                an active seat). In the future additional states might
+                be defined, client code should be written to be robust
+                in regards to additional state strings being
+                returned. The returned string needs to be freed with
+                the libc
+                <citerefentry><refentrytitle>free</refentrytitle><manvolnum>3</manvolnum></citerefentry>
+                call after use.</para>
+
+                <para><function>sd_uid_is_on_seat()</function> may be
+                used to determine whether a specific user is logged in
+                or active on a specific seat. Accepts a Unix user
+                identifier and a seat identifier string as
+                parameters. The <parameter>require_active</parameter>
+                parameter is a boolean. If non-zero (true) this
+                function will test if the user is active (i.e. has a
+                session that is in the foreground and accepting user
+                input) on the specified seat, otherwise (false) only
+                if the user is logged in (and possibly inactive) on
+                the specified seat.</para>
+
+                <para><function>sd_uid_get_sessions()</function> may
+                be used to determine the current sessions of the
+                specified user. Acceptes a Unix user identifier as
+                parameter. The <parameter>require_active</parameter>
+                boolean parameter controls whether the returned list
+                shall consist of only those sessions where the user is
+                currently active (true) or where the user is currently
+                logged in at all, possibly inactive (false). The call
+                returns a NULL terminated string array of session
+                identifiers in <parameter>sessions</parameter> which
+                needs to be freed by the caller with the libc
+                <citerefentry><refentrytitle>free</refentrytitle><manvolnum>3</manvolnum></citerefentry>
+                call after use, including all the strings
+                referenced. If the string array parameter is passed as
+                NULL the array will not be filled in, but the return
+                code still indicates the number of current
+                sessions. Note that instead of an empty array NULL may
+                be returned and should be considered equivalent to an
+                empty array.</para>
+
+                <para>Similar, <function>sd_uid_get_seats()</function>
+                may be used to determine the list of seats on which
+                the user currently has sessions. Similar semantics
+                apply, however note that the user may have
+                multiple sessions on the same seat as well as sessions
+                with no attached seat and hence the number of entries
+                in the returned array may differ from the one returned
+                by <function>sd_uid_get_sessions()</function>.</para>
+        </refsect1>
+
+        <refsect1>
+                <title>Return Value</title>
+
+                <para>On success
+                <function>sd_uid_get_state()</function> returns 0 or a
+                positive integer. If the test succeeds
+                <function>sd_uid_is_on_seat()</function> returns a
+                positive integer, if it fails
+                0. <function>sd_uid_get_sessions()</function> and
+                <function>sd_uid_get_seats()</function> return the
+                number of entries in the returned arrays. On failure,
+                these calls return a negative errno-style error
+                code.</para>
+        </refsect1>
+
+        <refsect1>
+                <title>Notes</title>
+
+                <para>The <function>sd_uid_get_state()</function>,
+                <function>sd_uid_is_on_seat()</function>,
+                <function>sd_uid_get_sessions()</function>, and
+                <function>sd_uid_get_seats()</function> interfaces are
+                available as shared library, which can be compiled and
+                linked to with the <literal>libsystemd-login</literal>
+                <citerefentry><refentrytitle>pkg-config</refentrytitle><manvolnum>1</manvolnum></citerefentry>
+                file.</para>
+        </refsect1>
+
+        <refsect1>
+                <title>See Also</title>
+
+                <para>
+                        <citerefentry><refentrytitle>systemd</refentrytitle><manvolnum>1</manvolnum></citerefentry>,
+                        <citerefentry><refentrytitle>sd-login</refentrytitle><manvolnum>7</manvolnum></citerefentry>,
+                        <citerefentry><refentrytitle>sd_pid_get_owner_uid</refentrytitle><manvolnum>3</manvolnum></citerefentry>
+                </para>
+        </refsect1>
+
+</refentry>
diff --git a/man/shutdown.xml b/man/shutdown.xml
new file mode 100644 (file)
index 0000000..c8c4b54
--- /dev/null
@@ -0,0 +1,188 @@
+<?xml version='1.0'?> <!--*-nxml-*-->
+<!DOCTYPE refentry PUBLIC "-//OASIS//DTD DocBook XML V4.2//EN"
+        "http://www.oasis-open.org/docbook/xml/4.2/docbookx.dtd">
+
+<!--
+  This file is part 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 <http://www.gnu.org/licenses/>.
+-->
+
+<refentry id="shutdown">
+
+        <refentryinfo>
+                <title>shutdown</title>
+                <productname>systemd</productname>
+
+                <authorgroup>
+                        <author>
+                                <contrib>Developer</contrib>
+                                <firstname>Lennart</firstname>
+                                <surname>Poettering</surname>
+                                <email>lennart@poettering.net</email>
+                        </author>
+                </authorgroup>
+        </refentryinfo>
+
+        <refmeta>
+                <refentrytitle>shutdown</refentrytitle>
+                <manvolnum>8</manvolnum>
+        </refmeta>
+
+        <refnamediv>
+                <refname>shutdown</refname>
+                <refpurpose>Halt, power-off or reboot the machine</refpurpose>
+        </refnamediv>
+
+        <refsynopsisdiv>
+                <cmdsynopsis>
+                        <command>shutdown <arg choice="opt" rep="repeat">OPTIONS</arg> <arg choice="opt">TIME</arg> <arg choice="opt" rep="repeat">WALL</arg></command>
+                </cmdsynopsis>
+        </refsynopsisdiv>
+
+        <refsect1>
+                <title>Description</title>
+
+                <para><command>shutdown</command> may be used to halt,
+                power-off or reboot the machine.</para>
+
+                <para>The first argument may be a time string (which
+                is usually <literal>now</literal>). Optionally, this
+                may be followed by a wall message to be sent to all
+                logged-in users before going down.</para>
+
+                <para>The time string may either be in the format
+                <literal>hh:mm</literal> for hour/minutes specifying
+                the time to execute the shutdown at, specified in 24h
+                clock format. Alternatively it may be in the syntax
+                <literal>+m</literal> referring to the specified
+                number of minutes m from now. <literal>now</literal>
+                is an alias for <literal>+0</literal>, i.e. for
+                triggering an immediate shutdown. If no time argument
+                is specified, <literal>+1</literal> is
+                implied.</para>
+
+                <para>Note that to specify a wall message you must
+                specify a time argument, too.</para>
+
+                <para>If the time argument is used, 5 minutes
+                before the system goes down the
+                <filename>/etc/nologin</filename> file is created to
+                ensure that further logins shall not be
+                allowed.</para>
+        </refsect1>
+
+        <refsect1>
+                <title>Options</title>
+
+                <para>The following options are understood:</para>
+
+                <variablelist>
+                        <varlistentry>
+                                <term><option>--help</option></term>
+
+                                <listitem><para>Prints a short help
+                                text and exits.</para></listitem>
+                        </varlistentry>
+
+                        <varlistentry>
+                                <term><option>-H</option></term>
+                                <term><option>--halt</option></term>
+
+                                <listitem><para>Halt the machine.</para></listitem>
+                        </varlistentry>
+
+                        <varlistentry>
+                                <term><option>-P</option></term>
+                                <term><option>--poweroff</option></term>
+
+                                <listitem><para>Power-off the
+                                machine (the default).</para></listitem>
+                        </varlistentry>
+
+                        <varlistentry>
+                                <term><option>-r</option></term>
+                                <term><option>--reboot</option></term>
+
+                                <listitem><para>Reboot the
+                                machine.</para></listitem>
+                        </varlistentry>
+
+                        <varlistentry>
+                                <term><option>-h</option></term>
+
+                                <listitem><para>Equivalent to
+                                <option>--poweroff</option>, unless
+                                <option>--halt</option> is
+                                specified.</para></listitem>
+                        </varlistentry>
+
+                        <varlistentry>
+                                <term><option>-k</option></term>
+
+                                <listitem><para>Don't halt, power-off,
+                                reboot, just write wall
+                                message.</para></listitem>
+                        </varlistentry>
+
+                        <varlistentry>
+                                <term><option>--no-wall</option></term>
+
+                                <listitem><para>Don't send wall
+                                message before
+                                halt, power-off, reboot.</para></listitem>
+                        </varlistentry>
+
+                        <varlistentry>
+                                <term><option>-c</option></term>
+
+                                <listitem><para>Cancel a pending
+                                shutdown. This may be used cancel the
+                                effect of an invocation of
+                                <command>shutdown</command> with a
+                                time argument that is not
+                                <literal>+0</literal> or
+                                <literal>now</literal>.</para></listitem>
+                        </varlistentry>
+
+                </variablelist>
+        </refsect1>
+
+        <refsect1>
+                <title>Exit status</title>
+
+                <para>On success 0 is returned, a non-zero failure
+                code otherwise.</para>
+        </refsect1>
+
+        <refsect1>
+                <title>Notes</title>
+
+                <para>This is a legacy command available for
+                compatibility only.</para>
+        </refsect1>
+
+        <refsect1>
+                <title>See Also</title>
+                <para>
+                        <citerefentry><refentrytitle>systemd</refentrytitle><manvolnum>1</manvolnum></citerefentry>,
+                        <citerefentry><refentrytitle>systemctl</refentrytitle><manvolnum>1</manvolnum></citerefentry>,
+                        <citerefentry><refentrytitle>halt</refentrytitle><manvolnum>8</manvolnum></citerefentry>,
+                        <citerefentry><refentrytitle>wall</refentrytitle><manvolnum>1</manvolnum></citerefentry>
+                </para>
+        </refsect1>
+
+</refentry>
diff --git a/man/sysctl.d.xml b/man/sysctl.d.xml
new file mode 100644 (file)
index 0000000..20f2e24
--- /dev/null
@@ -0,0 +1,123 @@
+<?xml version="1.0"?>
+<!--*-nxml-*-->
+<!DOCTYPE refentry PUBLIC "-//OASIS//DTD DocBook XML V4.2//EN" "http://www.oasis-open.org/docbook/xml/4.2/docbookx.dtd">
+<!--
+  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 <http://www.gnu.org/licenses/>.
+-->
+<refentry id="sysctl.d">
+
+        <refentryinfo>
+                <title>sysctl.d</title>
+                <productname>systemd</productname>
+
+                <authorgroup>
+                        <author>
+                                <contrib>Developer</contrib>
+                                <firstname>Lennart</firstname>
+                                <surname>Poettering</surname>
+                                <email>lennart@poettering.net</email>
+                        </author>
+                </authorgroup>
+        </refentryinfo>
+
+        <refmeta>
+                <refentrytitle>sysctl.d</refentrytitle>
+                <manvolnum>5</manvolnum>
+        </refmeta>
+
+        <refnamediv>
+                <refname>sysctl.d</refname>
+                <refpurpose>Configure kernel parameters at boot</refpurpose>
+        </refnamediv>
+
+        <refsynopsisdiv>
+                <para><filename>/etc/sysctl.d/*.conf</filename></para>
+                <para><filename>/run/sysctl.d/*.conf</filename></para>
+                <para><filename>/usr/lib/sysctl.d/*.conf</filename></para>
+        </refsynopsisdiv>
+
+        <refsect1>
+                <title>Description</title>
+
+               <para><command>systemd</command> uses configuration
+               files from the above directories to configure
+               <citerefentry><refentrytitle>sysctl</refentrytitle><manvolnum>8</manvolnum></citerefentry>
+               kernel parameters during boot.</para>
+        </refsect1>
+
+        <refsect1>
+               <title>Configuration Format</title>
+
+               <para>The configuration files contain a list of
+               variable assignments, separated by newlines. Empty
+               lines and lines whose first non-whitespace character
+               is # or ; are ignored.</para>
+
+                <para>Note that both / and . are accepted as label
+                separators within sysctl variable
+                names. <literal>kernel.domainname=foo</literal> and
+                <literal>kernel/domainname=foo</literal> hence are
+                entirely equivalent.</para>
+
+                <para>Each configuration file shall be named in the
+                style of <filename>&lt;program&gt;.conf</filename>.
+                Files in <filename>/run/</filename> override files
+                with the same name in <filename>/usr/lib/</filename>.
+                Files in <filename>/etc</filename> override files with
+                the same name in <filename>/run/</filename> and
+                <filename>/usr/lib/</filename>. Packages should
+                install their configuration files in
+                <filename>/usr/lib/</filename>. Files in
+                <filename>/etc/</filename> are reserved for the local
+                administrator, who may use this logic to override the
+                configuration installed by vendor packages. All
+                configuration files are sorted by their name in
+                alphabetical order, regardless in which of the
+                directories they reside, to guarantee that a specific
+                configuration file takes precedence over another file
+                with an alphabetically earlier name, if both files
+                contain the same variable setting.</para>
+
+                <para>If the administrator wants to disable a
+                configuration file supplied by the vendor the
+                recommended way is to place a symlink to
+                <filename>/dev/null</filename> in
+                <filename>/etc/sysctl.d</filename> carrying with the
+                same name.</para>
+        </refsect1>
+
+        <refsect1>
+                <title>Example</title>
+                <example>
+                        <title>/etc/sysctl.d/domain-name.conf example:</title>
+
+                        <programlisting># Set kernel YP domain name
+kernel.domainname=example.com</programlisting>
+                </example>
+        </refsect1>
+
+        <refsect1>
+                <title>See Also</title>
+                <para>
+                        <citerefentry><refentrytitle>systemd</refentrytitle><manvolnum>1</manvolnum></citerefentry>,
+                        <citerefentry><refentrytitle>sysctl</refentrytitle><manvolnum>8</manvolnum></citerefentry>,
+                        <citerefentry><refentrytitle>sysctl.conf</refentrytitle><manvolnum>5</manvolnum></citerefentry>
+                </para>
+        </refsect1>
+
+</refentry>
diff --git a/man/systemctl.xml b/man/systemctl.xml
new file mode 100644 (file)
index 0000000..167a72b
--- /dev/null
@@ -0,0 +1,1190 @@
+<?xml version='1.0'?> <!--*-nxml-*-->
+<!DOCTYPE refentry PUBLIC "-//OASIS//DTD DocBook XML V4.2//EN"
+        "http://www.oasis-open.org/docbook/xml/4.2/docbookx.dtd">
+
+<!--
+  This file is part 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 <http://www.gnu.org/licenses/>.
+-->
+
+<refentry id="systemctl">
+
+        <refentryinfo>
+                <title>systemctl</title>
+                <productname>systemd</productname>
+
+                <authorgroup>
+                        <author>
+                                <contrib>Developer</contrib>
+                                <firstname>Lennart</firstname>
+                                <surname>Poettering</surname>
+                                <email>lennart@poettering.net</email>
+                        </author>
+                </authorgroup>
+        </refentryinfo>
+
+        <refmeta>
+                <refentrytitle>systemctl</refentrytitle>
+                <manvolnum>1</manvolnum>
+        </refmeta>
+
+        <refnamediv>
+                <refname>systemctl</refname>
+                <refpurpose>Control the systemd system and service manager</refpurpose>
+        </refnamediv>
+
+        <refsynopsisdiv>
+                <cmdsynopsis>
+                        <command>systemctl <arg choice="opt" rep="repeat">OPTIONS</arg> <arg choice="req">COMMAND</arg> <arg choice="opt" rep="repeat">NAME</arg></command>
+                </cmdsynopsis>
+        </refsynopsisdiv>
+
+        <refsect1>
+                <title>Description</title>
+
+                <para><command>systemctl</command> may be used to
+                introspect and control the state of the
+                <citerefentry><refentrytitle>systemd</refentrytitle><manvolnum>1</manvolnum></citerefentry>
+                system and service manager.</para>
+        </refsect1>
+
+        <refsect1>
+                <title>Options</title>
+
+                <para>The following options are understood:</para>
+
+                <variablelist>
+                        <varlistentry>
+                                <term><option>--help</option></term>
+                                <term><option>-h</option></term>
+
+                                <listitem><para>Prints a short help
+                                text and exits.</para></listitem>
+                        </varlistentry>
+
+                        <varlistentry>
+                                <term><option>--version</option></term>
+
+                                <listitem><para>Prints a short version
+                                string and exits.</para></listitem>
+                        </varlistentry>
+
+                        <varlistentry>
+                                <term><option>--type=</option></term>
+                                <term><option>-t</option></term>
+
+                                <listitem><para>When listing units,
+                                limit display to certain unit
+                                types. If not specified units of all
+                                types will be shown. The argument
+                                should be a unit type name such as
+                                <option>service</option>,
+                                <option>socket</option> and
+                                similar.</para></listitem>
+                        </varlistentry>
+
+                        <varlistentry>
+                                <term><option>--property=</option></term>
+                                <term><option>-p</option></term>
+
+                                <listitem><para>When showing
+                                unit/job/manager properties, limit
+                                display to certain properties as
+                                specified as argument. If not
+                                specified all set properties are
+                                shown. The argument should be a
+                                property name, such as
+                                <literal>MainPID</literal>. If
+                                specified more than once all
+                                properties with the specified names
+                                are shown.</para></listitem>
+                        </varlistentry>
+
+                        <varlistentry>
+                                <term><option>--all</option></term>
+                                <term><option>-a</option></term>
+
+                                <listitem><para>When listing units,
+                                show all units, regardless of their
+                                state, including inactive units. When
+                                showing unit/job/manager properties,
+                                show all properties regardless whether
+                                they are set or not.</para></listitem>
+                        </varlistentry>
+
+                        <varlistentry>
+                                <term><option>--failed</option></term>
+
+                                <listitem><para>When listing units,
+                                show only failed units. Do not confuse
+                                with
+                                <option>--fail</option>.</para></listitem>
+                        </varlistentry>
+
+                        <varlistentry>
+                                <term><option>--full</option></term>
+
+                                <listitem><para>Do not ellipsize unit
+                                names and truncate unit descriptions
+                                in the output of
+                                <command>list-units</command> and
+                                <command>list-jobs</command>.</para></listitem>
+                        </varlistentry>
+
+                        <varlistentry>
+                                <term><option>--fail</option></term>
+
+                                <listitem><para>If the requested
+                                operation conflicts with a pending
+                                unfinished job, fail the command. If
+                                this is not specified the requested
+                                operation will replace the pending job,
+                                if necessary. Do not confuse
+                                with
+                                <option>--failed</option>.</para></listitem>
+                        </varlistentry>
+
+                        <varlistentry>
+                                <term><option>--ignore-dependencies</option></term>
+
+                                <listitem><para>When enqueuing a new
+                                job ignore all its dependencies and
+                                execute it immediately. If passed no
+                                required units of the unit passed will
+                                be pulled in, and no ordering
+                                dependencies will be honoured. This is
+                                mostly a debugging and rescue tool for
+                                the administrator and should not be
+                                used by
+                                applications.</para></listitem>
+                        </varlistentry>
+
+                        <varlistentry>
+                                <term><option>--quiet</option></term>
+                                <term><option>-q</option></term>
+
+                                <listitem><para>Suppress output to
+                                STDOUT in
+                                <command>snapshot</command>,
+                                <command>is-active</command>,
+                                <command>enable</command> and
+                                <command>disable</command>.</para></listitem>
+                        </varlistentry>
+
+                        <varlistentry>
+                                <term><option>--no-block</option></term>
+
+                                <listitem><para>Do not synchronously wait for
+                                the requested operation to finish. If this is
+                                not specified the job will be verified,
+                                enqueued and <command>systemctl</command> will
+                                wait until it is completed. By passing this
+                                argument it is only verified and
+                                enqueued.</para></listitem>
+                        </varlistentry>
+
+                        <varlistentry>
+                                <term><option>--no-legend</option></term>
+
+                               <listitem><para>Do not print a legend, i.e.
+                                the column headers and the footer with hints.
+                               </para></listitem>
+                       </varlistentry>
+
+                        <varlistentry>
+                                <term><option>--no-pager</option></term>
+
+                               <listitem><para>Do not pipe output into a
+                               pager.</para></listitem>
+                       </varlistentry>
+
+                        <varlistentry>
+                                <term><option>--system</option></term>
+
+                                <listitem><para>Talk to the systemd
+                                system manager. (Default)</para></listitem>
+                        </varlistentry>
+
+                        <varlistentry>
+                                <term><option>--user</option></term>
+
+                                <listitem><para>Talk to the systemd
+                                manager of the calling user.</para></listitem>
+                        </varlistentry>
+
+                        <varlistentry>
+                                <term><option>--order</option></term>
+                                <term><option>--require</option></term>
+
+                                <listitem><para>When used in
+                                conjunction with the
+                                <command>dot</command> command (see
+                                below), selects which dependencies are
+                                shown in the dependency graph. If
+                                <option>--order</option> is passed
+                                only dependencies of type
+                                <varname>After=</varname> or
+                                <varname>Before=</varname> are
+                                shown. If <option>--require</option>
+                                is passed only dependencies of type
+                                <varname>Requires=</varname>,
+                                <varname>RequiresOverridable=</varname>,
+                                <varname>Requisite=</varname>,
+                                <varname>RequisiteOverridable=</varname>,
+                                <varname>Wants=</varname> and
+                                <varname>Conflicts=</varname> are
+                                shown. If neither is passed, shows
+                                dependencies of all these
+                                types.</para></listitem>
+                        </varlistentry>
+
+                        <varlistentry>
+                                <term><option>--no-wall</option></term>
+
+                                <listitem><para>Don't send wall
+                                message before
+                                halt, power-off, reboot.</para></listitem>
+                        </varlistentry>
+
+                        <varlistentry>
+                                <term><option>--global</option></term>
+
+                                <listitem><para>When used with
+                                <command>enable</command> and
+                                <command>disable</command>, operate on the
+                                global user configuration
+                                directory, thus enabling or disabling
+                                a unit file globally for all future
+                                logins of all users.</para></listitem>
+                        </varlistentry>
+
+                        <varlistentry>
+                                <term><option>--no-reload</option></term>
+
+                                <listitem><para>When used with
+                                <command>enable</command> and
+                                <command>disable</command>, do not
+                                implicitly reload daemon configuration
+                                after executing the
+                                changes.</para></listitem>
+                        </varlistentry>
+
+                        <varlistentry>
+                                <term><option>--no-ask-password</option></term>
+
+                                <listitem><para>When used with
+                                <command>start</command> and related
+                                commands, disables asking for
+                                passwords. Background services may
+                                require input of a password or
+                                passphrase string, for example to
+                                unlock system hard disks or
+                                cryptographic certificates. Unless
+                                this option is specified and the
+                                command is invoked from a terminal
+                                <command>systemctl</command> will
+                                query the user on the terminal for the
+                                necessary secrets. Use this option to
+                                switch this behavior off. In this
+                                case the password must be supplied by
+                                some other means (for example
+                                graphical password agents) or the
+                                service might fail.</para></listitem>
+                        </varlistentry>
+
+                        <varlistentry>
+                                <term><option>--kill-who=</option></term>
+
+                                <listitem><para>When used with
+                                <command>kill</command>, choose which
+                                processes to kill. Must be one of
+                                <option>main</option>,
+                                <option>control</option> or
+                                <option>all</option> to select whether
+                                to kill only the main process of the
+                                unit, the control process or all
+                                processes of the unit. If omitted
+                                defaults to
+                                <option>all</option>.</para></listitem>
+                        </varlistentry>
+
+                        <varlistentry>
+                                <term><option>--signal=</option></term>
+                                <term><option>-s</option></term>
+
+                                <listitem><para>When used with
+                                <command>kill</command>, choose which
+                                signal to send to selected
+                                processes. Must be one of the well
+                                known signal specifiers such as
+                                SIGTERM, SIGINT or SIGSTOP. If
+                                omitted defaults to
+                                <option>SIGTERM</option>.</para></listitem>
+                        </varlistentry>
+
+                        <varlistentry>
+                                <term><option>--force</option></term>
+                                <term><option>-f</option></term>
+
+                                <listitem><para>When used with
+                                <command>enable</command>, override any
+                                existing conflicting
+                                symlinks.</para></listitem>
+
+                                <listitem><para>When used with
+                                <command>halt</command>,
+                                <command>poweroff</command>,
+                                <command>reboot</command> or
+                                <command>kexec</command> execute the
+                                selected operation without shutting
+                                down all units. However, all processes
+                                will be killed forcibly and all file
+                                systems are unmounted or remounted
+                                read-only. This is hence a drastic but
+                                relatively safe option to request an
+                                immediate reboot. If
+                                <option>--force</option> is specified
+                                twice for these operations, they will
+                                be executed immediately without
+                                terminating any processes or umounting
+                                any file systems. Warning: specifying
+                                <option>--force</option> twice with
+                                any of these operations might result
+                                in data loss.</para></listitem>
+                        </varlistentry>
+
+                        <varlistentry>
+                                <term><option>--root=</option></term>
+
+                                <listitem><para>When used with
+                                <command>enable</command>/<command>disable</command>/<command>is-enabled</command> (and
+                                related commands), use alternative
+                                root path when looking for unit
+                                files.</para></listitem>
+                        </varlistentry>
+
+                        <varlistentry>
+                                <term><option>--runtime</option></term>
+
+                                <listitem><para>When used with
+                                <command>enable</command>/<command>disable</command>/<command>is-enabled</command> (and related commands), make
+                                changes only temporarily, so that they
+                                are dropped on the next reboot. This
+                                will have the effect that changes are
+                                not made in subdirectories of
+                                <filename>/etc</filename> but in
+                                <filename>/run</filename>, with
+                                identical immediate effects, however,
+                                since the latter is lost on reboot,
+                                the changes are lost
+                                too.</para></listitem>
+                        </varlistentry>
+
+                        <varlistentry>
+                                <term><option>-H</option></term>
+                                <term><option>--host</option></term>
+
+                                <listitem><para>Execute operation
+                                remotely. Specify a hostname, or
+                                username and hostname separated by @,
+                                to connect to. This will use SSH to
+                                talk to the remote systemd
+                                instance.</para></listitem>
+                        </varlistentry>
+
+                        <varlistentry>
+                                <term><option>-P</option></term>
+                                <term><option>--privileged</option></term>
+
+                                <listitem><para>Acquire privileges via
+                                PolicyKit before executing the
+                                operation.</para></listitem>
+                        </varlistentry>
+
+                        <varlistentry>
+                                <term><option>--lines=</option></term>
+                                <term><option>-n</option></term>
+
+                                <listitem><para>When used with
+                                <command>status</command> controls the
+                                number of journal lines to show,
+                                counting from the most recent
+                                ones. Takes a positive integer
+                                argument. Defaults to
+                                10.</para></listitem>
+                        </varlistentry>
+
+                        <varlistentry>
+                                <term><option>--follow</option></term>
+                                <term><option>-f</option></term>
+
+                                <listitem><para>When used with
+                                <command>status</command> continously
+                                prints new journal entries as they are
+                                appended to the
+                                journal.</para></listitem>
+                        </varlistentry>
+
+                        <varlistentry>
+                                <term><option>--output=</option></term>
+                                <term><option>-o</option></term>
+
+                                <listitem><para>When used with
+                                <command>status</command> controls the
+                                formatting of the journal entries that
+                                are shown. For the available choices
+                                see
+                                <citerefentry><refentrytitle>journalctl</refentrytitle><manvolnum>1</manvolnum></citerefentry>. Defaults
+                                to
+                                <literal>short</literal>.</para></listitem>
+                        </varlistentry>
+
+                </variablelist>
+
+                <para>The following commands are understood:</para>
+
+                <variablelist>
+                        <varlistentry>
+                                <term><command>list-units</command></term>
+
+                                <listitem><para>List known units.</para></listitem>
+                        </varlistentry>
+                        <varlistentry>
+                                <term><command>start [NAME...]</command></term>
+
+                                <listitem><para>Start (activate) one
+                                or more units specified on the command
+                                line.</para></listitem>
+                        </varlistentry>
+                        <varlistentry>
+                                <term><command>stop [NAME...]</command></term>
+
+                                <listitem><para>Stop (deactivate) one
+                                or more units specified on the command
+                                line.</para></listitem>
+                        </varlistentry>
+                        <varlistentry>
+                                <term><command>reload [NAME...]</command></term>
+
+                                <listitem><para>Asks all units listed
+                                on the command line to reload their
+                                configuration. Note that this will
+                                reload the service-specific
+                                configuration, not the unit
+                                configuration file of systemd. If you
+                                want systemd to reload the
+                                configuration file of a unit use the
+                                <command>daemon-reload</command>
+                                command. In other words: for the
+                                example case of Apache, this will
+                                reload Apache's
+                                <filename>httpd.conf</filename> in the
+                                web server, not the
+                                <filename>apache.service</filename>
+                                systemd unit file. </para>
+
+                                <para>This command should not be
+                                confused with the
+                                <command>daemon-reload</command> or
+                                <command>load</command>
+                                commands.</para></listitem>
+
+                        </varlistentry>
+                        <varlistentry>
+                                <term><command>restart [NAME...]</command></term>
+
+                                <listitem><para>Restart one or more
+                                units specified on the command
+                                line. If the units are not running yet
+                                they will be
+                                started.</para></listitem>
+                        </varlistentry>
+                        <varlistentry>
+                                <term><command>try-restart [NAME...]</command></term>
+
+                                <listitem><para>Restart one or more
+                                units specified on the command
+                                line if the units are running. Do
+                                nothing if units are not running.
+                                Note that for compatibility
+                                with Red Hat init scripts
+                                <command>condrestart</command> is
+                                equivalent to this command.</para></listitem>
+                        </varlistentry>
+                        <varlistentry>
+                                <term><command>reload-or-restart [NAME...]</command></term>
+
+                                <listitem><para>Reload one or more
+                                units if they support it. If not,
+                                restart them instead. If the units
+                                are not running yet they will be
+                                started.</para></listitem>
+                        </varlistentry>
+                        <varlistentry>
+                                <term><command>reload-or-try-restart [NAME...]</command></term>
+
+                                <listitem><para>Reload one or more
+                                units if they support it. If not,
+                                restart them instead. Do nothing if
+                                the units are not running. Note that
+                                for compatibility with SysV init
+                                scripts
+                                <command>force-reload</command> is
+                                equivalent to this
+                                command.</para></listitem>
+                        </varlistentry>
+                        <varlistentry>
+                                <term><command>isolate [NAME]</command></term>
+
+                                <listitem><para>Start the unit
+                                specified on the command line and its
+                                dependencies and stop all others.</para>
+
+                                <para>This is similar to changing the
+                                runlevel in a traditional init system. The
+                                <command>isolate</command> command will
+                                immediately stop processes that are not
+                                enabled in the new unit, possibly including
+                                the graphical environment or terminal you
+                                are currently using.</para>
+
+                                <para>Note that this works only on units
+                                where <option>AllowIsolate=</option> is
+                                enabled. See
+                                <citerefentry><refentrytitle>systemd.unit</refentrytitle><manvolnum>5</manvolnum></citerefentry>
+                                for details.</para></listitem>
+                        </varlistentry>
+                        <varlistentry>
+                                <term><command>kill [NAME...]</command></term>
+
+                                <listitem><para>Send a signal to one
+                                or more processes of the unit. Use
+                                <option>--kill-who=</option> to select
+                                which process to kill. Use
+                                <option>--kill-mode=</option> to
+                                select the kill mode and
+                                <option>--signal=</option> to select
+                                the signal to send.</para></listitem>
+                        </varlistentry>
+                        <varlistentry>
+                                <term><command>is-active [NAME...]</command></term>
+
+                                <listitem><para>Check whether any of
+                                the specified units are active
+                                (i.e. running). Returns an exit code
+                                0 if at least one is active, non-zero
+                                otherwise. Unless
+                                <option>--quiet</option> is specified
+                                this will also print the current unit
+                                state to STDOUT.</para></listitem>
+                        </varlistentry>
+                        <varlistentry>
+                                <term><command>status [NAME...|PID...]</command></term>
+
+                                <listitem><para>Show terse runtime
+                                status information about one or more
+                                units, followed by its most recent log
+                                data from the journal. This function
+                                is intended to generate human-readable
+                                output. If you are looking for
+                                computer-parsable output, use
+                                <command>show</command> instead. If a
+                                PID is passed information about the
+                                unit the process of the PID belongs to
+                                is shown.</para></listitem>
+                        </varlistentry>
+                        <varlistentry>
+                                <term><command>show [NAME...|JOB...]</command></term>
+
+                                <listitem><para>Show properties of one
+                                or more units, jobs or the manager
+                                itself. If no argument is specified
+                                properties of the manager will be
+                                shown. If a unit name is specified
+                                properties of the unit is shown, and
+                                if a job id is specified properties of
+                                the job is shown. By default, empty
+                                properties are suppressed. Use
+                                <option>--all</option> to show those
+                                too. To select specific properties to
+                                show use
+                                <option>--property=</option>. This
+                                command is intended to be used
+                                whenever computer-parsable output is
+                                required. Use
+                                <command>status</command> if you are
+                                looking for formatted human-readable
+                                output.</para></listitem>
+                        </varlistentry>
+
+                        <varlistentry>
+                                <term><command>reset-failed [NAME...]</command></term>
+
+                                <listitem><para>Reset the
+                                '<literal>failed</literal>' state of the
+                                specified units, or if no unit name is
+                                passed of all units. When a unit fails
+                                in some way (i.e. process exiting with
+                                non-zero error code, terminating
+                                abnormally or timing out) it will
+                                automatically enter the
+                                '<literal>failed</literal>' state and
+                                its exit code and status is recorded
+                                for introspection by the administrator
+                                until the service is restarted or
+                                reset with this
+                                command.</para></listitem>
+                        </varlistentry>
+
+                        <varlistentry>
+                                <term><command>list-unit-files</command></term>
+
+                                <listitem><para>List installed unit files.
+                                </para></listitem>
+                        </varlistentry>
+
+                        <varlistentry>
+                                <term><command>enable [NAME...]</command></term>
+
+                                <listitem><para>Enable one or more
+                                unit files, as specified on the
+                                command line. This will create a
+                                number of symlinks as encoded in the
+                                <literal>[Install]</literal> sections
+                                of the unit files. After the symlinks
+                                have been created the systemd
+                                configuration is reloaded (in a way
+                                that is equivalent to
+                                <command>daemon-reload</command>) to
+                                ensure the changes are taken into
+                                account immediately. Note that this
+                                does not have the effect that any of
+                                the units enabled are also started at
+                                the same time. If this is desired a
+                                separate <command>start</command>
+                                command must be invoked for the
+                                unit.</para>
+
+                                <para>This command will
+                                print the actions executed. This
+                                output may be suppressed by passing
+                                <option>--quiet</option>.</para>
+
+                                <para>Note that this operation creates
+                                only the suggested symlinks for the
+                                units. While this command is the
+                                recommended way to manipulate the unit
+                                configuration directory, the
+                                administrator is free to make
+                                additional changes manually, by
+                                placing or removing symlinks in the
+                                directory. This is particularly useful
+                                to create configurations that deviate
+                                from the suggested default
+                                installation. In this case the
+                                administrator must make sure to invoke
+                                <command>daemon-reload</command>
+                                manually as necessary, to ensure his
+                                changes are taken into account.</para>
+
+                                <para>Enabling units should not be
+                                confused with starting (activating)
+                                units, as done by the
+                                <command>start</command>
+                                command. Enabling and starting units
+                                is orthogonal: units may be enabled
+                                without being started and started
+                                without being enabled. Enabling simply
+                                hooks the unit into various suggested
+                                places (for example, so that the unit
+                                is automatically started on boot or
+                                when a particular kind of hardware is
+                                plugged in). Starting actually spawns
+                                the daemon process (in case of service
+                                units), or binds the socket (in case
+                                of socket units), and so
+                                on.</para>
+
+                                <para>Depending on whether
+                                <option>--system</option>,
+                                <option>--user</option> or
+                                <option>--global</option> is specified
+                                this enables the unit for the system,
+                                for the calling user only
+                                or for all future logins of all
+                                users. Note that in the latter case no
+                                systemd daemon configuration is
+                                reloaded.</para>
+                                </listitem>
+                        </varlistentry>
+
+                        <varlistentry>
+                                <term><command>disable [NAME...]</command></term>
+
+                                <listitem><para>Disables one or more
+                                units. This removes all symlinks to
+                                the specified unit files from the unit
+                                configuration directory, and hence
+                                undoes the changes made by
+                                <command>enable</command>. Note
+                                however that this removes
+                                all symlinks to the unit files
+                                (i.e. including manual additions), not
+                                just those actually created by
+                                <command>enable</command>. This call
+                                implicitly reloads the systemd daemon
+                                configuration after completing the
+                                disabling of the units. Note that this
+                                command does not implicitly stop the
+                                units that is being disabled. If this
+                                is desired an additional
+                                <command>stop</command>command should
+                                be executed afterwards.</para>
+
+                                <para>This command will print the
+                                actions executed. This output may be
+                                suppressed by passing
+                                <option>--quiet</option>.</para>
+                                </listitem>
+
+                                <para>This command honors
+                                <option>--system</option>,
+                                <option>--user</option>,
+                                <option>--global</option> in a similar
+                                way as
+                                <command>enable</command>.</para>
+                        </varlistentry>
+
+                        <varlistentry>
+                                <term><command>is-enabled [NAME...]</command></term>
+
+                                <listitem><para>Checks whether any of
+                                the specified unit files is enabled
+                                (as with
+                                <command>enable</command>). Returns an
+                                exit code of 0 if at least one is
+                                enabled, non-zero otherwise. Prints
+                                the current enable status. To suppress
+                                this output use
+                                <option>--quiet</option>.</para></listitem>
+                        </varlistentry>
+
+                        <varlistentry>
+                                <term><command>reenable [NAME...]</command></term>
+
+                                <listitem><para>Reenable one or more
+                                unit files, as specified on the
+                                command line. This is a combination of
+                                <command>disable</command> and
+                                <command>enable</command> and is
+                                useful to reset the symlinks a unit is
+                                enabled with to the defaults
+                                configured in the
+                                <literal>[Install]</literal> section
+                                of the unit file.</para>
+                                </listitem>
+                        </varlistentry>
+
+                        <varlistentry>
+                                <term><command>preset [NAME...]</command></term>
+
+                                <listitem><para>Reset one or more unit
+                                files, as specified on the command
+                                line, to the defaults configured in a
+                                preset file. This has the same effect
+                                as <command>disable</command> or
+                                <command>enable</command>, depending
+                                how the unit is listed in the preset
+                                files.</para>
+                                </listitem>
+                        </varlistentry>
+
+                        <varlistentry>
+                                <term><command>mask [NAME...]</command></term>
+
+                                <listitem><para>Mask one or more unit
+                                files, as specified on the command
+                                line. This will link these units to
+                                <filename>/dev/null</filename>, making
+                                it impossible to start them. This is a stronger version
+                                of <command>disable</command>, since
+                                it prohibits all kinds of activation
+                                of the unit, including manual
+                                activation. Use this option with
+                                care.</para>
+                                </listitem>
+                        </varlistentry>
+
+                        <varlistentry>
+                                <term><command>unmask [NAME...]</command></term>
+
+                                <listitem><para>Unmask one or more
+                                unit files, as specified on the
+                                command line. This will undo the
+                                effect of
+                                <command>mask</command>.</para>
+                                </listitem>
+                        </varlistentry>
+
+                        <varlistentry>
+                                <term><command>link [NAME...]</command></term>
+
+                                <listitem><para>Link a unit file that
+                                is not in the unit file search paths
+                                into the unit file search path. This
+                                requires an absolute path to a unit
+                                file. The effect of this can be undone
+                                with <command>disable</command>. The
+                                effect of this command is that a unit
+                                file is available for
+                                <command>start</command> and other
+                                commands although it isn't installed
+                                directly in the unit search
+                                path.</para>
+                                </listitem>
+                        </varlistentry>
+
+                        <varlistentry>
+                                <term><command>load [NAME...]</command></term>
+
+                                <listitem><para>Load one or more units
+                                specified on the command line. This
+                                will simply load their configuration
+                                from disk, but not start them. To
+                                start them you need to use the
+                                <command>start</command> command which
+                                will implicitly load a unit that has
+                                not been loaded yet. Note that systemd
+                                garbage collects loaded units that are
+                                not active or referenced by an active
+                                unit. This means that units loaded
+                                this way will usually not stay loaded
+                                for long. Also note that this command
+                                cannot be used to reload unit
+                                configuration. Use the
+                                <command>daemon-reload</command>
+                                command for that. All in all, this
+                                command is of little use except for
+                                debugging.</para>
+                                <para>This command should not be
+                                confused with the
+                                <command>daemon-reload</command> or
+                                <command>reload</command>
+                                commands.</para></listitem>
+                        </varlistentry>
+                        <varlistentry>
+                                <term><command>list-jobs</command></term>
+
+                                <listitem><para>List jobs that are in progress.</para></listitem>
+                        </varlistentry>
+                        <varlistentry>
+                                <term><command>cancel [JOB...]</command></term>
+
+                                <listitem><para>Cancel one or more
+                                jobs specified on the command line by
+                                their numeric job
+                                IDs. If no job id is specified, cancel all pending jobs.</para></listitem>
+                        </varlistentry>
+                        <varlistentry>
+                                <term><command>dump</command></term>
+
+                                <listitem><para>Dump server
+                                status. This will output a (usually
+                                very long) human readable manager
+                                status dump. Its format is subject to
+                                change without notice and should not
+                                be parsed by
+                                applications.</para></listitem>
+                        </varlistentry>
+                        <varlistentry>
+                                <term><command>dot</command></term>
+
+                                <listitem><para>Generate textual
+                                dependency graph description in dot
+                                format for further processing with the
+                                GraphViz
+                                <citerefentry><refentrytitle>dot</refentrytitle><manvolnum>1</manvolnum></citerefentry>
+                                tool. Use a command line like
+                                <command>systemctl dot | dot -Tsvg >
+                                systemd.svg</command> to generate a
+                                graphical dependency tree. Unless
+                                <option>--order</option> or
+                                <option>--require</option> is passed
+                                the generated graph will show both
+                                ordering and requirement
+                                dependencies.</para></listitem>
+                        </varlistentry>
+                        <varlistentry>
+                                <term><command>snapshot [NAME]</command></term>
+
+                                <listitem><para>Create a snapshot. If
+                                a snapshot name is specified, the new
+                                snapshot will be named after it. If
+                                none is specified an automatic
+                                snapshot name is generated. In either
+                                case, the snapshot name used is
+                                printed to STDOUT, unless
+                                <option>--quiet</option> is
+                                specified.</para>
+
+                                <para>A snapshot refers to a saved
+                                state of the systemd manager. It is
+                                implemented itself as a unit that is
+                                generated dynamically with this
+                                command and has dependencies on all
+                                units active at the time. At a later
+                                time the user may return to this state
+                                by using the
+                                <command>isolate</command> command on
+                                the snapshot unit.</para></listitem>
+
+                                <para>Snapshots are only useful for
+                                saving and restoring which units are
+                                running or are stopped, they do not
+                                save/restore any other
+                                state. Snapshots are dynamic and lost
+                                on reboot.</para>
+                        </varlistentry>
+                        <varlistentry>
+                                <term><command>delete [NAME...]</command></term>
+
+                                <listitem><para>Remove a snapshot
+                                previously created with
+                                <command>snapshot</command>.</para></listitem>
+                        </varlistentry>
+                        <varlistentry>
+                                <term><command>daemon-reload</command></term>
+
+                                <listitem><para>Reload systemd manager
+                                configuration. This will reload all
+                                unit files and recreate the entire
+                                dependency tree. While the daemon is
+                                reloaded, all sockets systemd listens
+                                on on behalf of user configuration will
+                                stay accessible.</para> <para>This
+                                command should not be confused with
+                                the <command>load</command> or
+                                <command>reload</command>
+                                commands.</para></listitem>
+                        </varlistentry>
+                        <varlistentry>
+                                <term><command>daemon-reexec</command></term>
+
+                                <listitem><para>Reexecute the systemd
+                                manager. This will serialize the
+                                manager state, reexecute the process
+                                and deserialize the state again. This
+                                command is of little use except for
+                                debugging and package
+                                upgrades. Sometimes it might be
+                                helpful as a heavy-weight
+                                <command>daemon-reload</command>. While
+                                the daemon is reexecuted all sockets
+                                systemd listens on on behalf of user
+                                configuration will stay
+                                accessible.</para></listitem>
+                        </varlistentry>
+                        <varlistentry>
+                                <term><command>show-environment</command></term>
+
+                                <listitem><para>Dump the systemd
+                                manager environment block. The
+                                environment block will be dumped in
+                                straight-forward form suitable for
+                                sourcing into a shell script. This
+                                environment block will be passed to
+                                all processes the manager
+                                spawns.</para></listitem>
+                        </varlistentry>
+                        <varlistentry>
+                                <term><command>set-environment [NAME=VALUE...]</command></term>
+
+                                <listitem><para>Set one or more
+                                systemd manager environment variables,
+                                as specified on the command
+                                line.</para></listitem>
+                        </varlistentry>
+                        <varlistentry>
+                                <term><command>unset-environment [NAME...]</command></term>
+
+                                <listitem><para>Unset one or more
+                                systemd manager environment
+                                variables. If only a variable name is
+                                specified it will be removed
+                                regardless of its value. If a variable
+                                and a value are specified the variable
+                                is only removed if it has the
+                                specified value.</para></listitem>
+                        </varlistentry>
+                        <varlistentry>
+                                <term><command>default</command></term>
+
+                                <listitem><para>Enter default
+                                mode. This is mostly equivalent to
+                                <command>start
+                                default.target</command>.</para></listitem>
+                        </varlistentry>
+                        <varlistentry>
+                                <term><command>rescue</command></term>
+
+                                <listitem><para>Enter rescue
+                                mode. This is mostly equivalent to
+                                <command>isolate
+                                rescue.target</command> but also
+                                prints a wall message to all
+                                users.</para></listitem>
+                        </varlistentry>
+                        <varlistentry>
+                                <term><command>emergency</command></term>
+
+                                <listitem><para>Enter emergency
+                                mode. This is mostly equivalent to
+                                <command>isolate
+                                emergency.target</command> but also
+                                prints a wall message to all
+                                users.</para></listitem>
+                        </varlistentry>
+                        <varlistentry>
+                                <term><command>halt</command></term>
+
+                                <listitem><para>Shut down and halt the
+                                system. This is mostly equivalent to
+                                <command>start halt.target</command>
+                                but also prints a wall message to all
+                                users.  If combined with
+                                <option>--force</option> shutdown of
+                                all running services is skipped,
+                                however all processes are killed and
+                                all file systems are unmounted or
+                                mounted read-only, immediately
+                                followed by the system halt.  If
+                                <option>--force</option> is specified
+                                twice the the operation is immediately
+                                executed without terminating any
+                                processes or unmounting any file
+                                systems. This may result in data
+                                loss.</para></listitem>
+                        </varlistentry>
+                        <varlistentry>
+                                <term><command>poweroff</command></term>
+
+                                <listitem><para>Shut down and
+                                power-off the system. This is mostly
+                                equivalent to <command>start
+                                poweroff.target</command> but also
+                                prints a wall message to all users. If
+                                combined with <option>--force</option>
+                                shutdown of all running services is
+                                skipped, however all processes are
+                                killed and all file systems are
+                                unmounted or mounted read-only,
+                                immediately followed by the powering
+                                off. If <option>--force</option> is
+                                specified twice the the operation is
+                                immediately executed without
+                                terminating any processes or
+                                unmounting any file systems. This may
+                                result in data loss.</para></listitem>
+                        </varlistentry>
+                        <varlistentry>
+                                <term><command>reboot</command></term>
+
+                                <listitem><para>Shut down and reboot
+                                the system. This is mostly equivalent
+                                to <command>start
+                                reboot.target</command> but also
+                                prints a wall message to all users. If
+                                combined with <option>--force</option>
+                                shutdown of all running services is
+                                skipped, however all processes are
+                                killed and all file systems are
+                                unmounted or mounted read-only,
+                                immediately followed by the reboot. If
+                                <option>--force</option> is specified
+                                twice the the operation is immediately
+                                executed without terminating any
+                                processes or unmounting any file
+                                systems. This may result in data
+                                loss.</para></listitem>
+                        </varlistentry>
+                        <varlistentry>
+                                <term><command>kexec</command></term>
+
+                                <listitem><para>Shut down and reboot
+                                the system via kexec. This is mostly
+                                equivalent to <command>start
+                                kexec.target</command> but also prints
+                                a wall message to all users. If
+                                combined with <option>--force</option>
+                                shutdown of all running services is
+                                skipped, however all processes are killed
+                                and all file systems are unmounted or
+                                mounted read-only, immediately
+                                followed by the
+                                reboot.</para></listitem>
+                        </varlistentry>
+                        <varlistentry>
+                                <term><command>exit</command></term>
+
+                                <listitem><para>Ask the systemd
+                                manager to quit. This is only
+                                supported for user service managers
+                                (i.e. in conjunction with the
+                                <option>--user</option> option) and
+                                will fail otherwise.</para></listitem>
+                        </varlistentry>
+                </variablelist>
+
+        </refsect1>
+
+        <refsect1>
+                <title>Exit status</title>
+
+                <para>On success 0 is returned, a non-zero failure
+                code otherwise.</para>
+        </refsect1>
+
+        <refsect1>
+                <title>Environment</title>
+
+                <variablelist>
+                        <varlistentry>
+                                <term><varname>$SYSTEMD_PAGER</varname></term>
+                                <listitem><para>Pager to use when
+                                <option>--no-pager</option> is not given;
+                                overrides <varname>$PAGER</varname>.  Setting
+                                this to an empty string or the value
+                                <literal>cat</literal> is equivalent to passing
+                                <option>--no-pager</option>.</para></listitem>
+                        </varlistentry>
+                </variablelist>
+        </refsect1>
+
+        <refsect1>
+                <title>See Also</title>
+                <para>
+                        <citerefentry><refentrytitle>systemd</refentrytitle><manvolnum>1</manvolnum></citerefentry>,
+                        <citerefentry><refentrytitle>systemadm</refentrytitle><manvolnum>1</manvolnum></citerefentry>,
+                        <citerefentry><refentrytitle>journalctl</refentrytitle><manvolnum>1</manvolnum></citerefentry>,
+                        <citerefentry><refentrytitle>loginctl</refentrytitle><manvolnum>1</manvolnum></citerefentry>,
+                        <citerefentry><refentrytitle>systemd.unit</refentrytitle><manvolnum>5</manvolnum></citerefentry>,
+                        <citerefentry><refentrytitle>systemd.special</refentrytitle><manvolnum>7</manvolnum></citerefentry>,
+                        <citerefentry><refentrytitle>wall</refentrytitle><manvolnum>1</manvolnum></citerefentry>
+                </para>
+        </refsect1>
+
+</refentry>
diff --git a/man/systemd-ask-password.xml b/man/systemd-ask-password.xml
new file mode 100644 (file)
index 0000000..c607b1a
--- /dev/null
@@ -0,0 +1,183 @@
+<?xml version='1.0'?> <!--*-nxml-*-->
+<!DOCTYPE refentry PUBLIC "-//OASIS//DTD DocBook XML V4.2//EN"
+        "http://www.oasis-open.org/docbook/xml/4.2/docbookx.dtd">
+
+<!--
+  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 <http://www.gnu.org/licenses/>.
+-->
+
+<refentry id="systemd-ask-password">
+
+        <refentryinfo>
+                <title>systemd-ask-password</title>
+                <productname>systemd</productname>
+
+                <authorgroup>
+                        <author>
+                                <contrib>Developer</contrib>
+                                <firstname>Lennart</firstname>
+                                <surname>Poettering</surname>
+                                <email>lennart@poettering.net</email>
+                        </author>
+                </authorgroup>
+        </refentryinfo>
+
+        <refmeta>
+                <refentrytitle>systemd-ask-password</refentrytitle>
+                <manvolnum>1</manvolnum>
+        </refmeta>
+
+        <refnamediv>
+                <refname>systemd-ask-password</refname>
+                <refpurpose>Query the user for a system password</refpurpose>
+        </refnamediv>
+
+        <refsynopsisdiv>
+                <cmdsynopsis>
+                        <command>systemd-ask-password <arg choice="opt" rep="repeat">OPTIONS</arg> <arg choice="opt">MESSAGE</arg></command>
+                </cmdsynopsis>
+        </refsynopsisdiv>
+
+        <refsect1>
+                <title>Description</title>
+
+                <para><command>systemd-ask-password</command> may be
+                used to query a system password or passphrase from the
+                user, using a question message specified on the
+                command line. When run from a TTY it will query a
+                password on the TTY and print it to STDOUT. When run
+                with no TTY or with <option>--no-tty</option> it will
+                query the password system-wide and allow active users
+                to respond via several agents. The latter is
+                only available to privileged processes.</para>
+
+                <para>The purpose of this tool is to query system-wide
+                passwords -- that is passwords not attached to a
+                specific user account. Examples include: unlocking
+                encrypted hard disks when they are plugged in or at
+                boot, entering an SSL certificate passphrase for web
+                and VPN servers.</para>
+
+                <para>Existing agents are: a boot-time password agent
+                asking the user for passwords using Plymouth; a
+                boot-time password agent querying the user directly on
+                the console; an agent requesting password input via a
+                <citerefentry><refentrytitle>wall</refentrytitle><manvolnum>1</manvolnum></citerefentry>
+                message; an agent suitable for running in a GNOME
+                session; a command line agent which can be started
+                temporarily to process queued password requests; a TTY
+                agent that is temporarily spawned during
+                <citerefentry><refentrytitle>systemctl</refentrytitle><manvolnum>1</manvolnum></citerefentry>
+                invocations.</para>
+
+                <para>Additional password agents may be implemented
+                according to the <ulink
+                url="http://www.freedesktop.org/wiki/Software/systemd/PasswordAgents">systemd
+                Password Agent Specification</ulink>.</para>
+
+                <para>If a password is queried on a tty the user may
+                press TAB to hide the asterisks normally shown for
+                each character typed. Pressing Backspace as first key
+                achieves the same effect.</para>
+
+        </refsect1>
+
+        <refsect1>
+                <title>Options</title>
+
+                <para>The following options are understood:</para>
+
+                <variablelist>
+                        <varlistentry>
+                                <term><option>--h</option></term>
+                                <term><option>--help</option></term>
+
+                                <listitem><para>Prints a short help
+                                text and exits.</para></listitem>
+                        </varlistentry>
+
+                        <varlistentry>
+                                <term><option>--icon=</option></term>
+
+                                <listitem><para>Specify an icon name
+                                alongside the password query, which may
+                                be used in all agents supporting
+                                graphical display. The icon name
+                                should follow the <ulink
+                                url="http://standards.freedesktop.org/icon-naming-spec/icon-naming-spec-latest.html">XDG
+                                Icon Naming
+                                Specification</ulink>.</para></listitem>
+                        </varlistentry>
+
+                        <varlistentry>
+                                <term><option>--timeout=</option></term>
+
+                                <listitem><para>Specify the query
+                                timeout in seconds. Defaults to
+                                90s.</para></listitem>
+                        </varlistentry>
+
+                        <varlistentry>
+                                <term><option>--no-tty</option></term>
+
+                                <listitem><para>Never ask for password
+                                on current TTY even if one is
+                                available. Always use agent
+                                system.</para></listitem>
+                        </varlistentry>
+
+                        <varlistentry>
+                                <term><option>--accept-cached</option></term>
+
+                                <listitem><para>If passed accept
+                                cached passwords, i.e. passwords
+                                previously typed in.</para></listitem>
+                        </varlistentry>
+
+                        <varlistentry>
+                                <term><option>--multiple</option></term>
+
+                                <listitem><para>When used in
+                                conjunction with
+                                <option>--accept-cached</option>
+                                accept multiple passwords. This will
+                                output one password per
+                                line.</para></listitem>
+                        </varlistentry>
+                </variablelist>
+
+        </refsect1>
+
+        <refsect1>
+                <title>Exit status</title>
+
+                <para>On success 0 is returned, a non-zero failure
+                code otherwise.</para>
+        </refsect1>
+
+        <refsect1>
+                <title>See Also</title>
+                <para>
+                        <citerefentry><refentrytitle>systemd</refentrytitle><manvolnum>1</manvolnum></citerefentry>,
+                        <citerefentry><refentrytitle>systemctl</refentrytitle><manvolnum>1</manvolnum></citerefentry>,
+                        <citerefentry><refentrytitle>plymouth</refentrytitle><manvolnum>8</manvolnum></citerefentry>,
+                        <citerefentry><refentrytitle>wall</refentrytitle><manvolnum>1</manvolnum></citerefentry>
+                </para>
+        </refsect1>
+
+</refentry>
diff --git a/man/systemd-cat.xml b/man/systemd-cat.xml
new file mode 100644 (file)
index 0000000..350a345
--- /dev/null
@@ -0,0 +1,205 @@
+<?xml version='1.0'?> <!--*-nxml-*-->
+<!DOCTYPE refentry PUBLIC "-//OASIS//DTD DocBook XML V4.2//EN"
+        "http://www.oasis-open.org/docbook/xml/4.2/docbookx.dtd">
+
+<!--
+  This file is part of systemd.
+
+  Copyright 2012 Lennart Poettering
+
+  systemd is free software; you can redistribute it and/or modify it
+  under the terms of the GNU 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 <http://www.gnu.org/licenses/>.
+-->
+
+<refentry id="systemd-cat">
+
+        <refentryinfo>
+                <title>systemd-cat</title>
+                <productname>systemd</productname>
+
+                <authorgroup>
+                        <author>
+                                <contrib>Developer</contrib>
+                                <firstname>Lennart</firstname>
+                                <surname>Poettering</surname>
+                                <email>lennart@poettering.net</email>
+                        </author>
+                </authorgroup>
+        </refentryinfo>
+
+        <refmeta>
+                <refentrytitle>systemd-cat</refentrytitle>
+                <manvolnum>1</manvolnum>
+        </refmeta>
+
+        <refnamediv>
+                <refname>systemd-cat</refname>
+                <refpurpose>Connect a pipeline or program's output with the journal</refpurpose>
+        </refnamediv>
+
+        <refsynopsisdiv>
+                <cmdsynopsis>
+                        <command>systemd-cat <arg choice="opt" rep="repeat">OPTIONS</arg> <arg>COMMAND</arg> <arg choice="opt" rep="repeat">ARGUMENTS</arg></command>
+                </cmdsynopsis>
+                <cmdsynopsis>
+                        <command>systemd-cat <arg choice="opt" rep="repeat">OPTIONS</arg></command>
+                </cmdsynopsis>
+        </refsynopsisdiv>
+
+        <refsect1>
+                <title>Description</title>
+
+                <para><command>systemd-cat</command> may be used to
+                connect STDOUT and STDERR of a process with the
+                journal, or as a filter tool in a shell pipeline to
+                pass the output the previous pipeline element
+                generates to the journal.</para>
+
+                <para>If no parameter is passed
+                <command>systemd-cat</command> will write
+                everything it reads from standard input (STDIN) to the journal.</para>
+
+                <para>If parameters are passed they are executed as
+                command line with standard output (STDOUT) and standard
+                error output (STDERR) connected to the journal, so
+                that all it writes is stored in the journal.</para>
+        </refsect1>
+
+        <refsect1>
+                <title>Options</title>
+
+                <para>The following options are understood:</para>
+
+                <variablelist>
+                        <varlistentry>
+                                <term><option>--h</option></term>
+                                <term><option>--help</option></term>
+
+                                <listitem><para>Prints a short help
+                                text and exits.</para></listitem>
+                        </varlistentry>
+
+                        <varlistentry>
+                                <term><option>--version</option></term>
+
+                                <listitem><para>Prints a short version
+                                string and exits.</para></listitem>
+                        </varlistentry>
+
+                        <varlistentry>
+                                <term><option>-t</option></term>
+                                <term><option>--identifier=</option></term>
+
+                                <listitem><para>Specify a short string
+                                that is used to identify the logging
+                                tool. If not specified no identifying
+                                string is written to the journal.</para></listitem>
+                        </varlistentry>
+
+                        <varlistentry>
+                                <term><option>-p</option></term>
+                                <term><option>--priority=</option></term>
+
+                                <listitem><para>Specify the default
+                                priority level for the logged
+                                messages. Pass one of
+                                <literal>emerg</literal>,
+                                <literal>alert</literal>,
+                                <literal>crit</literal>,
+                                <literal>err</literal>,
+                                <literal>warning</literal>,
+                                <literal>notice</literal>,
+                                <literal>info</literal>,
+                                <literal>debug</literal>, resp. a
+                                value between 0 and 7 (corresponding
+                                to the same named levels). These
+                                priority values are the same as
+                                defined by
+                                <citerefentry><refentrytitle>syslog</refentrytitle><manvolnum>3</manvolnum></citerefentry>. Defaults
+                                to <literal>info</literal>. Note that
+                                this simply controls the default,
+                                individual lines may be logged with
+                                different levels if they are prefixed
+                                accordingly. For details see
+                                <option>--level-prefix=</option>
+                                below.</para></listitem>
+                        </varlistentry>
+
+                        <varlistentry>
+                                <term><option>--level-prefix=</option></term>
+
+                                <listitem><para>Controls whether lines
+                                read are parsed for syslog priority
+                                level prefixes. If enabled (the
+                                default) a line prefixed with a
+                                priority prefix such as
+                                <literal>&lt;5&gt;</literal> is logged
+                                at priority 5
+                                (<literal>notice</literal>), and
+                                similar for the other priority
+                                levels. Takes a boolean
+                                argument.</para></listitem>
+                        </varlistentry>
+
+                </variablelist>
+
+        </refsect1>
+
+        <refsect1>
+                <title>Exit status</title>
+
+                <para>On success 0 is returned, a non-zero failure
+                code otherwise.</para>
+        </refsect1>
+
+        <refsect1>
+                <title>Examples</title>
+
+                <example>
+                        <title>Invoke a program</title>
+
+                        <para>This calls <filename>/bin/ls</filename>
+                        with STDOUT/STDERR connected to the
+                        journal:</para>
+
+                        <programlisting># systemd-cat ls</programlisting>
+                </example>
+
+                <example>
+                        <title>Usage in a shell pipeline</title>
+
+                        <para>This builds a shell pipeline also
+                        invoking <filename>/bin/ls</filename> and
+                        writes the output it generates to the
+                        journal:</para>
+
+                        <programlisting># ls | systemd-cat</programlisting>
+                </example>
+
+                <para>Even though the two examples have very similar
+                effects the first is preferable since only one process
+                is running at a time, and both STDOUT and STDERR are
+                captured while in the second example only STDOUT is
+                captured.</para>
+        </refsect1>
+
+        <refsect1>
+                <title>See Also</title>
+                <para>
+                        <citerefentry><refentrytitle>systemd</refentrytitle><manvolnum>1</manvolnum></citerefentry>,
+                        <citerefentry><refentrytitle>systemctl</refentrytitle><manvolnum>1</manvolnum></citerefentry>,
+                        <citerefentry><refentrytitle>logger</refentrytitle><manvolnum>1</manvolnum></citerefentry>
+                </para>
+        </refsect1>
+
+</refentry>
diff --git a/man/systemd-cgls.xml b/man/systemd-cgls.xml
new file mode 100644 (file)
index 0000000..1e53147
--- /dev/null
@@ -0,0 +1,123 @@
+<?xml version='1.0'?> <!--*-nxml-*-->
+<!DOCTYPE refentry PUBLIC "-//OASIS//DTD DocBook XML V4.2//EN"
+        "http://www.oasis-open.org/docbook/xml/4.2/docbookx.dtd">
+
+<!--
+  This file is part 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 <http://www.gnu.org/licenses/>.
+-->
+
+<refentry id="systemd-cgls">
+
+        <refentryinfo>
+                <title>systemd-cgls</title>
+                <productname>systemd</productname>
+
+                <authorgroup>
+                        <author>
+                                <contrib>Developer</contrib>
+                                <firstname>Lennart</firstname>
+                                <surname>Poettering</surname>
+                                <email>lennart@poettering.net</email>
+                        </author>
+                </authorgroup>
+        </refentryinfo>
+
+        <refmeta>
+                <refentrytitle>systemd-cgls</refentrytitle>
+                <manvolnum>1</manvolnum>
+        </refmeta>
+
+        <refnamediv>
+                <refname>systemd-cgls</refname>
+                <refpurpose>Recursively show control group contents</refpurpose>
+        </refnamediv>
+
+        <refsynopsisdiv>
+                <cmdsynopsis>
+                        <command>systemd-cgls <arg choice="opt" rep="repeat">OPTIONS</arg> <arg choice="opt" rep="repeat">CGROUP</arg></command>
+                </cmdsynopsis>
+        </refsynopsisdiv>
+
+        <refsect1>
+                <title>Description</title>
+
+                <para><command>systemd-cgls</command> recursively
+                shows the contents of the selected Linux control group
+                hierarchy in a tree. If arguments are specified shows
+                all member processes of the specified control groups
+                plus all their subgroups and their members. The
+                control groups may either be specified by their full
+                file paths or are assumed in the systemd control group
+                hierarchy. If no argument is specified and the current
+                working directory is beneath the control group mount
+                point <filename>/sys/fs/cgroup</filename> shows the contents
+                of the control group the working directory refers
+                to. Otherwise the full systemd control group hierarchy
+                is shown.</para>
+        </refsect1>
+
+        <refsect1>
+                <title>Options</title>
+
+                <para>The following options are understood:</para>
+
+                <variablelist>
+                        <varlistentry>
+                                <term><option>-h</option></term>
+                                <term><option>--help</option></term>
+
+                                <listitem><para>Prints a short help
+                                text and exits.</para></listitem>
+                        </varlistentry>
+
+                        <varlistentry>
+                                <term><option>--no-pager</option></term>
+
+                               <listitem><para>Do not pipe output into a
+                               pager.</para></listitem>
+                       </varlistentry>
+
+                        <varlistentry>
+                                <term><option>-k</option></term>
+
+                                <listitem><para>Include kernel
+                                threads in output.</para></listitem>
+                        </varlistentry>
+
+                </variablelist>
+
+        </refsect1>
+
+        <refsect1>
+                <title>Exit status</title>
+
+                <para>On success 0 is returned, a non-zero failure
+                code otherwise.</para>
+        </refsect1>
+
+        <refsect1>
+                <title>See Also</title>
+                <para>
+                        <citerefentry><refentrytitle>systemd</refentrytitle><manvolnum>1</manvolnum></citerefentry>,
+                        <citerefentry><refentrytitle>systemctl</refentrytitle><manvolnum>1</manvolnum></citerefentry>,
+                        <citerefentry><refentrytitle>systemd-cgtop</refentrytitle><manvolnum>1</manvolnum></citerefentry>,
+                        <citerefentry><refentrytitle>ps</refentrytitle><manvolnum>1</manvolnum></citerefentry>
+                </para>
+        </refsect1>
+
+</refentry>
diff --git a/man/systemd-cgtop.xml b/man/systemd-cgtop.xml
new file mode 100644 (file)
index 0000000..2d67ae5
--- /dev/null
@@ -0,0 +1,246 @@
+<?xml version='1.0'?> <!--*-nxml-*-->
+<!DOCTYPE refentry PUBLIC "-//OASIS//DTD DocBook XML V4.2//EN"
+        "http://www.oasis-open.org/docbook/xml/4.2/docbookx.dtd">
+
+<!--
+  This file is part of systemd.
+
+  Copyright 2012 Lennart Poettering
+
+  systemd is free software; you can redistribute it and/or modify it
+  under the terms of the GNU 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 <http://www.gnu.org/licenses/>.
+-->
+
+<refentry id="systemd-cgtop">
+
+        <refentryinfo>
+                <title>systemd-cgtop</title>
+                <productname>systemd</productname>
+
+                <authorgroup>
+                        <author>
+                                <contrib>Developer</contrib>
+                                <firstname>Lennart</firstname>
+                                <surname>Poettering</surname>
+                                <email>lennart@poettering.net</email>
+                        </author>
+                </authorgroup>
+        </refentryinfo>
+
+        <refmeta>
+                <refentrytitle>systemd-cgtop</refentrytitle>
+                <manvolnum>1</manvolnum>
+        </refmeta>
+
+        <refnamediv>
+                <refname>systemd-cgtop</refname>
+                <refpurpose>Show top control groups by their resource usage</refpurpose>
+        </refnamediv>
+
+        <refsynopsisdiv>
+                <cmdsynopsis>
+                        <command>systemd-cgtop <arg choice="opt" rep="repeat">OPTIONS</arg></command>
+                </cmdsynopsis>
+        </refsynopsisdiv>
+
+        <refsect1>
+                <title>Description</title>
+
+                <para><command>systemd-cgtop</command> shows the top
+                control groups of the local Linux control group
+                hierarchy, ordered by their CPU, memory and disk I/O load. The
+                display is refreshed in regular intervals (by default
+                every 1s), similar in style to
+                <citerefentry><refentrytitle>top</refentrytitle><manvolnum>1</manvolnum></citerefentry>.</para>
+
+                <para>Resource usage is only accounted for control
+                groups in the relevant hierarchy, i.e. CPU usage is
+                only accounted for control groups in the
+                <literal>cpuacct</literal> hierarchy, memory usage
+                only for those in <literal>memory</literal> and disk
+                I/O usage for those in
+                <literal>blkio</literal>. <citerefentry><refentrytitle>systemd</refentrytitle><manvolnum>1</manvolnum></citerefentry>
+                by default places all services in their own control
+                group in the <literal>cpuacct</literal> hierarchy, but
+                not in <literal>memory</literal> nor
+                <literal>blkio</literal>. If resource monitoring for
+                these resources is required it is recommended to add
+                <literal>blkio</literal> and <literal>memory</literal>
+                to the <varname>DefaultControllers=</varname> setting
+                in <filename>/etc/systemd/system.conf</filename> (see
+                <citerefentry><refentrytitle>systemd.conf</refentrytitle><manvolnum>5</manvolnum></citerefentry>
+                for details). Alternatively, it is possible to enable
+                resource accounting individually for services, by
+                making use of the <varname>ControlGroup=</varname>
+                option in the unit files (See
+                <citerefentry><refentrytitle>systemd.exec</refentrytitle><manvolnum>5</manvolnum></citerefentry>
+                for details).</para>
+
+                <para>To emphasize this: unless
+                <literal>blkio</literal> and <literal>memory</literal>
+                are enabled for the services in question with either
+                of the options suggested above no resource accounting
+                will be available for system services and the data shown
+                by <command>systemd-cgtop</command> will be
+                incomplete.</para>
+        </refsect1>
+
+        <refsect1>
+                <title>Options</title>
+
+                <para>The following options are understood:</para>
+
+                <variablelist>
+                        <varlistentry>
+                                <term><option>-h</option></term>
+                                <term><option>--help</option></term>
+
+                                <listitem><para>Prints a short help
+                                text and exits.</para></listitem>
+                        </varlistentry>
+
+                        <varlistentry>
+                                <term><option>-p</option></term>
+
+                                <listitem><para>Order by control group
+                                path name.</para></listitem>
+                        </varlistentry>
+
+                        <varlistentry>
+                                <term><option>-t</option></term>
+
+                                <listitem><para>Order by number of
+                                tasks in control
+                                group (i.e. threads and processes).</para></listitem>
+                        </varlistentry>
+
+                        <varlistentry>
+                                <term><option>-c</option></term>
+
+                                <listitem><para>Order by CPU load.</para></listitem>
+                        </varlistentry>
+
+                        <varlistentry>
+                                <term><option>-m</option></term>
+
+                                <listitem><para>Order by memory usage.</para></listitem>
+                        </varlistentry>
+
+                        <varlistentry>
+                                <term><option>-i</option></term>
+
+                                <listitem><para>Order by disk I/O load.</para></listitem>
+                        </varlistentry>
+
+                        <varlistentry>
+                                <term><option>-d</option></term>
+                                <term><option>--delay=</option></term>
+
+                                <listitem><para>Specify refresh delay
+                                in seconds (or if one of
+                                <literal>ms</literal>,
+                                <literal>us</literal>,
+                                <literal>min</literal> is specified as
+                                unit in this time
+                                unit).</para></listitem>
+                        </varlistentry>
+
+                        <varlistentry>
+                                <term><option>--depth=</option></term>
+
+                                <listitem><para>Maximum control group
+                                tree traversal depth. Specifies how
+                                deep <command>systemd-cgtop</command>
+                                shall traverse the control group
+                                hierarchies. If 0 is specified only
+                                the root group is monitored, for 1
+                                only the first level of control groups
+                                is monitored, and so on. Defaults to
+                                2.</para></listitem>
+                        </varlistentry>
+
+                </variablelist>
+
+        </refsect1>
+
+
+        <refsect1>
+                <title>Keys</title>
+
+                <para><command>systemd-cgtop</command> is an
+                interactive tool and may be controlled via user input
+                using the following keys:</para>
+
+                <variablelist>
+                        <varlistentry>
+                                <term>h</term>
+
+                                <listitem><para>Shows a short help text.</para></listitem>
+                        </varlistentry>
+
+                        <varlistentry>
+                                <term>SPACE</term>
+
+                                <listitem><para>Immediately refresh output.</para></listitem>
+                        </varlistentry>
+
+                        <varlistentry>
+                                <term>q</term>
+
+                                <listitem><para>Terminate the program.</para></listitem>
+                        </varlistentry>
+
+
+                        <varlistentry>
+                                <term>p</term>
+                                <term>t</term>
+                                <term>c</term>
+                                <term>m</term>
+                                <term>i</term>
+
+                                <listitem><para>Change ordering of control groups
+                                by path, number of tasks, CPU load,
+                                memory usage resp. IO
+                                load.</para></listitem>
+                        </varlistentry>
+
+                        <varlistentry>
+                                <term>+</term>
+                                <term>-</term>
+
+                                <listitem><para>Increase,
+                                resp. decrease refresh
+                                delay.</para></listitem>
+                        </varlistentry>
+
+                </variablelist>
+        </refsect1>
+
+        <refsect1>
+                <title>Exit status</title>
+
+                <para>On success 0 is returned, a non-zero failure
+                code otherwise.</para>
+        </refsect1>
+
+        <refsect1>
+                <title>See Also</title>
+                <para>
+                        <citerefentry><refentrytitle>systemd</refentrytitle><manvolnum>1</manvolnum></citerefentry>,
+                        <citerefentry><refentrytitle>systemctl</refentrytitle><manvolnum>1</manvolnum></citerefentry>,
+                        <citerefentry><refentrytitle>systemd-cgls</refentrytitle><manvolnum>1</manvolnum></citerefentry>,
+                        <citerefentry><refentrytitle>top</refentrytitle><manvolnum>1</manvolnum></citerefentry>
+                </para>
+        </refsect1>
+
+</refentry>
diff --git a/man/systemd-machine-id-setup.xml b/man/systemd-machine-id-setup.xml
new file mode 100644 (file)
index 0000000..49b92f6
--- /dev/null
@@ -0,0 +1,114 @@
+<?xml version='1.0'?> <!--*-nxml-*-->
+<!DOCTYPE refentry PUBLIC "-//OASIS//DTD DocBook XML V4.2//EN"
+        "http://www.oasis-open.org/docbook/xml/4.2/docbookx.dtd">
+
+<!--
+  This file is part of systemd.
+
+  Copyright 2012 Lennart Poettering
+
+  systemd is free software; you can redistribute it and/or modify it
+  under the terms of the GNU 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 <http://www.gnu.org/licenses/>.
+-->
+
+<refentry id="systemd-machine-id-setup">
+
+        <refentryinfo>
+                <title>systemd-machine-id-setup</title>
+                <productname>systemd</productname>
+
+                <authorgroup>
+                        <author>
+                                <contrib>Developer</contrib>
+                                <firstname>Lennart</firstname>
+                                <surname>Poettering</surname>
+                                <email>lennart@poettering.net</email>
+                        </author>
+                </authorgroup>
+        </refentryinfo>
+
+        <refmeta>
+                <refentrytitle>systemd-machine-id-setup</refentrytitle>
+                <manvolnum>1</manvolnum>
+        </refmeta>
+
+        <refnamediv>
+                <refname>systemd-machine-id-setup</refname>
+                <refpurpose>Initialize the machine ID in <filename>/etc/machine-id</filename></refpurpose>
+        </refnamediv>
+
+        <refsynopsisdiv>
+                <cmdsynopsis>
+                        <command>systemd-machine-id-setup</command>
+                </cmdsynopsis>
+        </refsynopsisdiv>
+
+        <refsect1>
+                <title>Description</title>
+
+                <para><command>systemd-machine-id-setup</command> may
+                be used by system installer tools to initialize the
+                machine ID stored in
+                <filename>/etc/machine-id</filename> at install time
+                with a randomly generated ID. See
+                <citerefentry><refentrytitle>machine-id</refentrytitle><manvolnum>5</manvolnum></citerefentry>
+                for more information about this file.</para>
+
+                <para>This tool will execute no operation if
+                <filename>/etc/machine-id</filename> is already
+                initialized.</para>
+
+                <para>If a valid D-Bus machine ID is already
+                configured for the system the D-Bus machine ID is
+                copied and used to initialize the machine ID in
+                <filename>/etc/machine-id</filename>.</para>
+
+                <para>If run inside a KVM virtual machine and a UUID
+                is passed via the <option>-uuid</option> option this
+                UUID is used to initialize the machine ID instead of a
+                randomly generated one. The caller must ensure that the
+                UUID passed is sufficiently unique and is different
+                for every booted instanced of the VM.</para>
+
+                <para>Similar, if run inside a Linux container
+                environment and a UUID is set for the container this
+                is used to initialize the machine ID. For details see
+                the documentation of the <ulink
+                url="http://www.freedesktop.org/wiki/Software/systemd/ContainerInterface">Container
+                Interface</ulink>.</para>
+
+        </refsect1>
+
+        <refsect1>
+                <title>Options</title>
+
+                <para>This tool does not take any options or arguments.</para>
+        </refsect1>
+
+        <refsect1>
+                <title>Exit status</title>
+
+                <para>On success 0 is returned, a non-zero failure
+                code otherwise.</para>
+        </refsect1>
+
+        <refsect1>
+                <title>See Also</title>
+                <para>
+                        <citerefentry><refentrytitle>systemd</refentrytitle><manvolnum>1</manvolnum></citerefentry>,
+                        <citerefentry><refentrytitle>machine-id</refentrytitle><manvolnum>5</manvolnum></citerefentry>,
+                        <citerefentry><refentrytitle>dbus-uuidgen</refentrytitle><manvolnum>1</manvolnum></citerefentry>
+                </para>
+        </refsect1>
+
+</refentry>
diff --git a/man/systemd-notify.xml b/man/systemd-notify.xml
new file mode 100644 (file)
index 0000000..c5ffafe
--- /dev/null
@@ -0,0 +1,218 @@
+<?xml version='1.0'?> <!--*-nxml-*-->
+<!DOCTYPE refentry PUBLIC "-//OASIS//DTD DocBook XML V4.2//EN"
+        "http://www.oasis-open.org/docbook/xml/4.2/docbookx.dtd">
+
+<!--
+  This file is part 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 <http://www.gnu.org/licenses/>.
+-->
+
+<refentry id="systemd-notify">
+
+        <refentryinfo>
+                <title>systemd-notify</title>
+                <productname>systemd</productname>
+
+                <authorgroup>
+                        <author>
+                                <contrib>Developer</contrib>
+                                <firstname>Lennart</firstname>
+                                <surname>Poettering</surname>
+                                <email>lennart@poettering.net</email>
+                        </author>
+                </authorgroup>
+        </refentryinfo>
+
+        <refmeta>
+                <refentrytitle>systemd-notify</refentrytitle>
+                <manvolnum>1</manvolnum>
+        </refmeta>
+
+        <refnamediv>
+                <refname>systemd-notify</refname>
+                <refpurpose>Notify init system about start-up completion and other daemon status changes</refpurpose>
+        </refnamediv>
+
+        <refsynopsisdiv>
+                <cmdsynopsis>
+                        <command>systemd-notify <arg choice="opt" rep="repeat">OPTIONS</arg> <arg choice="opt" rep="repeat">VARIABLE=VALUE</arg></command>
+                </cmdsynopsis>
+        </refsynopsisdiv>
+
+        <refsect1>
+                <title>Description</title>
+
+                <para><command>systemd-notify</command> may be
+                called by daemon scripts to notify the init system
+                about status changes. It can be used to send arbitrary
+                information, encoded in an environment-block-like list
+                of strings. Most importantly it can be used for
+                start-up completion notification.</para>
+
+                <para>This is mostly just a wrapper around
+                <function>sd_notify()</function> and makes this
+                functionality available to shell scripts. For details
+                see
+                <citerefentry><refentrytitle>sd_notify</refentrytitle><manvolnum>3</manvolnum></citerefentry>.</para>
+
+                <para>The command line may carry a list of
+                environment variables to send as part of the status
+                update.</para>
+
+                <para>Note that systemd will refuse reception of
+                status updates from this command unless
+                <varname>NotifyAccess=all</varname> is set for the
+                service unit this command is called from.</para>
+
+        </refsect1>
+
+        <refsect1>
+                <title>Options</title>
+
+                <para>The following options are understood:</para>
+
+                <variablelist>
+                        <varlistentry>
+                                <term><option>--h</option></term>
+                                <term><option>--help</option></term>
+
+                                <listitem><para>Prints a short help
+                                text and exits.</para></listitem>
+                        </varlistentry>
+
+                        <varlistentry>
+                                <term><option>--version</option></term>
+
+                                <listitem><para>Prints a short version
+                                string and exits.</para></listitem>
+                        </varlistentry>
+
+                        <varlistentry>
+                                <term><option>--ready</option></term>
+
+                                <listitem><para>Inform the init system
+                                about service start-up
+                                completion. This is equivalent to
+                                <command>systemd-notify
+                                READY=1</command>. For details about
+                                the semantics of this option see
+                                <citerefentry><refentrytitle>sd_notify</refentrytitle><manvolnum>3</manvolnum></citerefentry>.</para></listitem>
+                        </varlistentry>
+
+                        <varlistentry>
+                                <term><option>--pid=</option></term>
+
+                                <listitem><para>Inform the init system
+                                about the main PID of the
+                                daemon. Takes a PID as argument. If
+                                the argument is omitted the PID of the
+                                process that invoked
+                                <command>systemd-notify</command> is
+                                used. This is equivalent to
+                                <command>systemd-notify
+                                MAINPID=$PID</command>. For details
+                                about the semantics of this option see
+                                <citerefentry><refentrytitle>sd_notify</refentrytitle><manvolnum>3</manvolnum></citerefentry>.</para></listitem>
+                        </varlistentry>
+
+                        <varlistentry>
+                                <term><option>--status=</option></term>
+
+                                <listitem><para>Send a free-form
+                                status string for the daemon to the
+                                init systemd. This option takes the
+                                status string as argument. This is
+                                equivalent to <command>systemd-notify
+                                STATUS=...</command>. For details
+                                about the semantics of this option see
+                                <citerefentry><refentrytitle>sd_notify</refentrytitle><manvolnum>3</manvolnum></citerefentry>.</para></listitem>
+                        </varlistentry>
+
+                        <varlistentry>
+                                <term><option>--booted</option></term>
+
+                                <listitem><para>Returns 0 if the
+                                system was booted up with systemd,
+                                non-zero otherwise. If this option is
+                                passed no message is sent. This option
+                                is hence unrelated to the other
+                                options. For details about the
+                                semantics of this option see
+                                <citerefentry><refentrytitle>sd_booted</refentrytitle><manvolnum>3</manvolnum></citerefentry>.</para></listitem>
+                        </varlistentry>
+
+                        <varlistentry>
+                                <term><option>--readahead=</option></term>
+
+                                <listitem><para>Controls disk
+                                read-ahead operations. The argument
+                                must be a string, and either "cancel",
+                                "done" or "noreplay". For details
+                                about the semantics of this option see
+                                <citerefentry><refentrytitle>sd_readahead</refentrytitle><manvolnum>3</manvolnum></citerefentry>.</para></listitem>
+                        </varlistentry>
+                </variablelist>
+
+        </refsect1>
+
+        <refsect1>
+                <title>Exit status</title>
+
+                <para>On success 0 is returned, a non-zero failure
+                code otherwise.</para>
+        </refsect1>
+
+        <refsect1>
+                <title>Example</title>
+
+                <example>
+                        <title>Start-up Notification and Status Updates</title>
+
+                        <para>A simple shell daemon that sends
+                        start-up notifications after having set up its
+                        communication channel. During runtime it sends
+                        further status updates to the init
+                        system:</para>
+
+                        <programlisting>#!/bin/bash
+
+mkfifo /tmp/waldo
+systemd-notify --ready --status="Waiting for data..."
+
+while : ; do
+        read a &lt; /tmp/waldo
+        systemd-notify --status="Processing $a"
+
+        # Do something with $a ...
+
+        systemd-notify --status="Waiting for data..."
+done</programlisting>
+                </example>
+        </refsect1>
+
+        <refsect1>
+                <title>See Also</title>
+                <para>
+                        <citerefentry><refentrytitle>systemd</refentrytitle><manvolnum>1</manvolnum></citerefentry>,
+                        <citerefentry><refentrytitle>systemctl</refentrytitle><manvolnum>1</manvolnum></citerefentry>,
+                        <citerefentry><refentrytitle>systemd.unit</refentrytitle><manvolnum>5</manvolnum></citerefentry>,
+                        <citerefentry><refentrytitle>sd_notify</refentrytitle><manvolnum>3</manvolnum></citerefentry>,
+                        <citerefentry><refentrytitle>sd_booted</refentrytitle><manvolnum>3</manvolnum></citerefentry>
+                </para>
+        </refsect1>
+
+</refentry>
diff --git a/man/systemd-nspawn.xml b/man/systemd-nspawn.xml
new file mode 100644 (file)
index 0000000..dbd2ff5
--- /dev/null
@@ -0,0 +1,215 @@
+<?xml version='1.0'?> <!--*-nxml-*-->
+<!DOCTYPE refentry PUBLIC "-//OASIS//DTD DocBook XML V4.2//EN"
+        "http://www.oasis-open.org/docbook/xml/4.2/docbookx.dtd">
+
+<!--
+  This file is part 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 <http://www.gnu.org/licenses/>.
+-->
+
+<refentry id="systemd-nspawn">
+
+        <refentryinfo>
+                <title>systemd-nspawn</title>
+                <productname>systemd</productname>
+
+                <authorgroup>
+                        <author>
+                                <contrib>Developer</contrib>
+                                <firstname>Lennart</firstname>
+                                <surname>Poettering</surname>
+                                <email>lennart@poettering.net</email>
+                        </author>
+                </authorgroup>
+        </refentryinfo>
+
+        <refmeta>
+                <refentrytitle>systemd-nspawn</refentrytitle>
+                <manvolnum>1</manvolnum>
+        </refmeta>
+
+        <refnamediv>
+                <refname>systemd-nspawn</refname>
+                <refpurpose>Spawn a namespace container for debugging, testing and building</refpurpose>
+        </refnamediv>
+
+        <refsynopsisdiv>
+                <cmdsynopsis>
+                        <command>systemd-nspawn <arg choice="opt" rep="repeat">OPTIONS</arg> <arg choice="opt">COMMAND</arg> <arg choice="opt" rep="repeat">ARGS</arg></command>
+                </cmdsynopsis>
+        </refsynopsisdiv>
+
+        <refsect1>
+                <title>Description</title>
+
+                <para><command>systemd-nspawn</command> may be used to
+                run a command or OS in a light-weight namespace
+                container. In many ways it is similar to
+                <citerefentry><refentrytitle>chroot</refentrytitle><manvolnum>1</manvolnum></citerefentry>,
+                but more powerful since it fully virtualizes the file
+                system hierarchy, as well as the process tree, the
+                various IPC subsystems and the host and domain
+                name.</para>
+
+                <para><command>systemd-nspawn</command> limits access
+                to various kernel interfaces in the container to
+                read-only, such as <filename>/sys</filename>,
+                <filename>/proc/sys</filename> or
+                <filename>/sys/fs/selinux</filename>. Network
+                interfaces and the system clock may not be changed
+                from within the container. Device nodes may not be
+                created. The host system cannot be rebooted and kernel
+                modules may not be loaded from within the
+                container.</para>
+
+                <para>Note that even though these security precautions
+                are taken <command>systemd-nspawn</command> is not
+                suitable for secure container setups. Many of the
+                security features may be circumvented and are hence
+                primarily useful to avoid accidental changes to the
+                host system from the container. The intended use of
+                this program is debugging and testing as well as
+                building of packages, distributions and software
+                involved with boot and systems management.</para>
+
+                <para>In contrast to
+                <citerefentry><refentrytitle>chroot</refentrytitle><manvolnum>1</manvolnum></citerefentry>
+                <command>systemd-nspawn</command> may be used to boot
+                full Linux-based operating systems in a
+                container.</para>
+
+                <para>Use a tool like
+                <citerefentry><refentrytitle>debootstrap</refentrytitle><manvolnum>8</manvolnum></citerefentry> or <citerefentry><refentrytitle>mock</refentrytitle><manvolnum>1</manvolnum></citerefentry>
+                to set up an OS directory tree suitable as file system
+                hierarchy for <command>systemd-nspawn</command> containers.</para>
+
+                <para>Note that <command>systemd-nspawn</command> will
+                mount file systems private to the container to
+                <filename>/dev</filename>,
+                <filename>/run</filename> and similar. These will
+                not be visible outside of the container, and their
+                contents will be lost when the container exits.</para>
+
+                <para>Note that running two
+                <command>systemd-nspawn</command> containers from the
+                same directory tree will not make processes in them
+                see each other. The PID namespace separation of the
+                two containers is complete and the containers will
+                share very few runtime objects except for the
+                underlying file system.</para>
+        </refsect1>
+
+        <refsect1>
+                <title>Options</title>
+
+                <para>If no arguments are passed the container is set
+                up and a shell started in it, otherwise the passed
+                command and arguments are executed in it. The
+                following options are understood:</para>
+
+                <variablelist>
+                        <varlistentry>
+                                <term><option>--help</option></term>
+                                <term><option>-h</option></term>
+
+                                <listitem><para>Prints a short help
+                                text and exits.</para></listitem>
+                        </varlistentry>
+
+                        <varlistentry>
+                                <term><option>--directory=</option></term>
+                                <term><option>-D</option></term>
+
+                                <listitem><para>Directory to use as
+                                file system root for the namespace
+                                container. If omitted the current
+                                directory will be
+                                used.</para></listitem>
+                        </varlistentry>
+
+                        <varlistentry>
+                                <term><option>--user=</option></term>
+                                <term><option>-u</option></term>
+
+                                <listitem><para>Run the command
+                                under specified user, create home
+                                directory and cd into it. As rest
+                                of systemd-nspawn, this is not
+                                the security feature and limits
+                                against accidental changes only.
+                                </para></listitem>
+                        </varlistentry>
+
+                        <varlistentry>
+                                <term><option>--private-network</option></term>
+
+                                <listitem><para>Turn off networking in
+                                the container. This makes all network
+                                interfaces unavailable in the
+                                container, with the exception of the
+                                loopback device.</para></listitem>
+                        </varlistentry>
+
+                </variablelist>
+
+        </refsect1>
+
+        <refsect1>
+                <title>Example 1</title>
+
+                <programlisting># debootstrap --arch=amd64 unstable debian-tree/
+# systemd-nspawn -D debian-tree/</programlisting>
+
+                <para>This installs a minimal Debian unstable
+                distribution into the directory
+                <filename>debian-tree/</filename> and then spawns a
+                shell in a namespace container in it.</para>
+
+        </refsect1>
+
+        <refsect1>
+                <title>Example 2</title>
+
+                <programlisting># mock --init
+# systemd-nspawn -D /var/lib/mock/fedora-rawhide-x86_64/root/ /sbin/init systemd.log_level=debug</programlisting>
+
+                <para>This installs a minimal Fedora distribution into
+                a subdirectory of <filename>/var/lib/mock/</filename>
+                and then boots an OS in a namespace container in it,
+                with systemd as init system, configured for debug
+                logging.</para>
+
+        </refsect1>
+
+        <refsect1>
+                <title>Exit status</title>
+
+                <para>The exit code of the program executed in the
+                container is returned.</para>
+        </refsect1>
+
+        <refsect1>
+                <title>See Also</title>
+                <para>
+                        <citerefentry><refentrytitle>systemd</refentrytitle><manvolnum>1</manvolnum></citerefentry>,
+                        <citerefentry><refentrytitle>chroot</refentrytitle><manvolnum>1</manvolnum></citerefentry>,
+                        <citerefentry><refentrytitle>debootstrap</refentrytitle><manvolnum>8</manvolnum></citerefentry>,
+                        <citerefentry><refentrytitle>mock</refentrytitle><manvolnum>1</manvolnum></citerefentry>
+                </para>
+        </refsect1>
+
+</refentry>
diff --git a/man/systemd-tmpfiles.xml b/man/systemd-tmpfiles.xml
new file mode 100644 (file)
index 0000000..bbb80b2
--- /dev/null
@@ -0,0 +1,152 @@
+<?xml version='1.0'?> <!--*-nxml-*-->
+<!DOCTYPE refentry PUBLIC "-//OASIS//DTD DocBook XML V4.2//EN"
+        "http://www.oasis-open.org/docbook/xml/4.2/docbookx.dtd">
+
+<!--
+  This file is part 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 <http://www.gnu.org/licenses/>.
+-->
+
+<refentry id="systemd-tmpfiles">
+
+        <refentryinfo>
+                <title>systemd-tmpfiles</title>
+                <productname>systemd</productname>
+
+                <authorgroup>
+                        <author>
+                                <contrib>Developer</contrib>
+                                <firstname>Lennart</firstname>
+                                <surname>Poettering</surname>
+                                <email>lennart@poettering.net</email>
+                        </author>
+                </authorgroup>
+        </refentryinfo>
+
+        <refmeta>
+                <refentrytitle>systemd-tmpfiles</refentrytitle>
+                <manvolnum>8</manvolnum>
+        </refmeta>
+
+        <refnamediv>
+                <refname>systemd-tmpfiles</refname>
+                <refpurpose>Creates, deletes and cleans up volatile
+                and temporary files and directories.</refpurpose>
+        </refnamediv>
+
+        <refsynopsisdiv>
+                <cmdsynopsis>
+                        <command>systemd-tmpfiles <arg choice="opt" rep="repeat">OPTIONS</arg> <arg choice="opt" rep="repeat">CONFIGURATION FILE</arg></command>
+                </cmdsynopsis>
+        </refsynopsisdiv>
+
+        <refsect1>
+                <title>Description</title>
+
+                <para><command>systemd-tmpfiles</command> creates,
+                deletes and cleans up volatile and temporary files and
+                directories, based on the configuration from
+                <filename>/etc/tmpfiles.d/</filename>. See
+                <citerefentry><refentrytitle>tmpfiles.d</refentrytitle><manvolnum>5</manvolnum></citerefentry>
+                for more details on these files.</para>
+
+                <para>If invoked with no arguments applies all
+                directives from all configuration files in
+                <filename>/etc/tmpfiles.d/*.conf</filename>. If one or
+                more absolute file names are passed on the command
+                line only the directives in these files are
+                applied.</para>
+        </refsect1>
+
+        <refsect1>
+                <title>Options</title>
+
+                <para>The following options are understood:</para>
+
+                <variablelist>
+
+                        <varlistentry>
+                                <term><option>--create</option></term>
+                                <listitem><para>If this option is passed all
+                                files and directories marked with f,
+                                F, d, D in the configuration files are
+                                created. Files and directories marked with z,
+                                Z have their ownership, access mode and security
+                                labels set.</para></listitem>
+                        </varlistentry>
+
+                        <varlistentry>
+                                <term><option>--clean</option></term>
+                                <listitem><para>If this option is
+                                passed all files and directories with
+                                an age parameter configured will be
+                                cleaned up.</para></listitem>
+                        </varlistentry>
+
+                        <varlistentry>
+                                <term><option>--remove</option></term>
+                                <listitem><para>If this option is
+                                passed all files and directories marked
+                                with r, R in the configuration files
+                                are removed.</para></listitem>
+                        </varlistentry>
+                        <varlistentry>
+                                <term><option>--prefix=PATH</option></term>
+                                <listitem><para>Only apply rules that
+                                apply to paths with the specified
+                                prefix.</para></listitem>
+                        </varlistentry>
+
+
+                        <varlistentry>
+                                <term><option>--help</option></term>
+
+                                <listitem><para>Prints a short help
+                                text and exits.</para></listitem>
+                        </varlistentry>
+
+                </variablelist>
+
+                <para>It is possible to combine
+                <option>--create</option>, <option>--clean</option>,
+                and <option>--remove</option> in one invocation. For
+                example, during boot the following command line is
+                executed to ensure that all temporary and volatile
+                directories are removed and created according to the
+                configuration file:</para>
+
+                <programlisting>systemd-tmpfiles --remove --create</programlisting>
+
+        </refsect1>
+
+        <refsect1>
+                <title>Exit status</title>
+
+                <para>On success 0 is returned, a non-zero failure
+                code otherwise.</para>
+        </refsect1>
+
+        <refsect1>
+                <title>See Also</title>
+                <para>
+                        <citerefentry><refentrytitle>systemd</refentrytitle><manvolnum>1</manvolnum></citerefentry>,
+                        <citerefentry><refentrytitle>tmpfiles.d</refentrytitle><manvolnum>5</manvolnum></citerefentry>,
+                        <citerefentry><refentrytitle>tmpwatch</refentrytitle><manvolnum>8</manvolnum></citerefentry>
+                </para>
+        </refsect1>
+
+</refentry>
diff --git a/man/systemd.automount.xml b/man/systemd.automount.xml
new file mode 100644 (file)
index 0000000..754d1e3
--- /dev/null
@@ -0,0 +1,167 @@
+<?xml version='1.0'?> <!--*-nxml-*-->
+<?xml-stylesheet type="text/xsl" href="http://docbook.sourceforge.net/release/xsl/current/xhtml/docbook.xsl"?>
+<!DOCTYPE refentry PUBLIC "-//OASIS//DTD DocBook XML V4.2//EN"
+        "http://www.oasis-open.org/docbook/xml/4.2/docbookx.dtd">
+
+<!--
+  This file is part 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 <http://www.gnu.org/licenses/>.
+-->
+
+<refentry id="systemd.automount">
+        <refentryinfo>
+                <title>systemd.automount</title>
+                <productname>systemd</productname>
+
+                <authorgroup>
+                        <author>
+                                <contrib>Developer</contrib>
+                                <firstname>Lennart</firstname>
+                                <surname>Poettering</surname>
+                                <email>lennart@poettering.net</email>
+                        </author>
+                </authorgroup>
+        </refentryinfo>
+
+        <refmeta>
+                <refentrytitle>systemd.automount</refentrytitle>
+                <manvolnum>5</manvolnum>
+        </refmeta>
+
+        <refnamediv>
+                <refname>systemd.automount</refname>
+                <refpurpose>systemd automount configuration files</refpurpose>
+        </refnamediv>
+
+        <refsynopsisdiv>
+                <para><filename>systemd.automount</filename></para>
+        </refsynopsisdiv>
+
+        <refsect1>
+                <title>Description</title>
+
+                <para>A unit configuration file whose name ends in
+                <filename>.automount</filename> encodes information
+                about a file system automount point controlled and
+                supervised by systemd.</para>
+
+                <para>This man page lists the configuration options
+                specific to this unit type. See
+                <citerefentry><refentrytitle>systemd.unit</refentrytitle><manvolnum>5</manvolnum></citerefentry>
+                for the common options of all unit configuration
+                files. The common configuration items are configured
+                in the generic [Unit] and [Install] sections. The
+                automount specific configuration options are configured
+                in the [Automount] section.</para>
+
+                <para>Automount units must be named after the
+                automount directories they control. Example: the
+                automount point <filename>/home/lennart</filename>
+                must be configured in a unit file
+                <filename>home-lennart.automount</filename>. For
+                details about the escaping logic used to convert a
+                file system path to a unit name see
+                <citerefentry><refentrytitle>systemd.unit</refentrytitle><manvolnum>5</manvolnum></citerefentry>.</para>
+
+                <para>For each automount unit file a matching mount
+                unit file (see
+                <citerefentry><refentrytitle>systemd.mount</refentrytitle><manvolnum>5</manvolnum></citerefentry>
+                for details) must exist which is activated when the
+                automount path is accessed. Example: if an automount
+                unit <filename>home-lennart.automount</filename> is
+                active and the user accesses
+                <filename>/home/lennart</filename> the mount unit
+                <filename>home-lennart.mount</filename> will be
+                activated.</para>
+
+                <para>Automount units may be used to implement
+                on-demand mounting as well as parallelized mounting of
+                file systems.</para>
+
+                <para>If an automount point is beneath another mount
+                point in the file system hierarchy a dependency
+                between both units is created automatically.</para>
+        </refsect1>
+
+        <refsect1>
+                <title><filename>fstab</filename></title>
+
+                <para>Automount units may either be configured via unit
+                files, or via <filename>/etc/fstab</filename> (see
+                <citerefentry><refentrytitle>fstab</refentrytitle><manvolnum>5</manvolnum></citerefentry>
+                for details).</para>
+
+                <para>For details how systemd parses
+                <filename>/etc/fstab</filename> see
+                <citerefentry><refentrytitle>systemd.mount</refentrytitle><manvolnum>5</manvolnum></citerefentry>.</para>
+
+                <para>If an automount point is configured in both
+                <filename>/etc/fstab</filename> and a unit file the
+                configuration in the latter takes precedence.</para>
+        </refsect1>
+
+        <refsect1>
+                <title>Options</title>
+
+                <para>Automount files must include an [Automount]
+                section, which carries information about the file
+                system automount points it supervises. The options
+                specific to the [Automount] section of automount units
+                are the following:</para>
+
+                <variablelist>
+
+                        <varlistentry>
+                                <term><varname>Where=</varname></term>
+                                <listitem><para>Takes an absolute path
+                                of a directory of the automount
+                                point. If the automount point is not
+                                existing at time of the automount
+                                point is installed it is created. This
+                                string must be reflected in the unit
+                                file name. (See above.) This option is
+                                mandatory.</para></listitem>
+                        </varlistentry>
+
+                        <varlistentry>
+                                <term><varname>DirectoryMode=</varname></term>
+                                <listitem><para>Directories of
+                                automount points (and any parent
+                                directories) are automatically created
+                                if needed. This option specifies the
+                                file system access mode used when
+                                creating these directories. Takes an
+                                access mode in octal
+                                notation. Defaults to
+                                0755.</para></listitem>
+                        </varlistentry>
+                </variablelist>
+        </refsect1>
+
+        <refsect1>
+                  <title>See Also</title>
+                  <para>
+                          <citerefentry><refentrytitle>systemd</refentrytitle><manvolnum>1</manvolnum></citerefentry>,
+                          <citerefentry><refentrytitle>systemctl</refentrytitle><manvolnum>8</manvolnum></citerefentry>,
+                          <citerefentry><refentrytitle>systemd.unit</refentrytitle><manvolnum>5</manvolnum></citerefentry>,
+                          <citerefentry><refentrytitle>systemd.mount</refentrytitle><manvolnum>5</manvolnum></citerefentry>,
+                          <citerefentry><refentrytitle>mount</refentrytitle><manvolnum>8</manvolnum></citerefentry>,
+                          <citerefentry><refentrytitle>automount</refentrytitle><manvolnum>8</manvolnum></citerefentry>
+                  </para>
+        </refsect1>
+
+</refentry>
diff --git a/man/systemd.conf.xml b/man/systemd.conf.xml
new file mode 100644 (file)
index 0000000..ba144da
--- /dev/null
@@ -0,0 +1,162 @@
+<?xml version='1.0'?> <!--*-nxml-*-->
+<?xml-stylesheet type="text/xsl" href="http://docbook.sourceforge.net/release/xsl/current/xhtml/docbook.xsl"?>
+<!DOCTYPE refentry PUBLIC "-//OASIS//DTD DocBook XML V4.2//EN"
+        "http://www.oasis-open.org/docbook/xml/4.2/docbookx.dtd">
+
+<!--
+  This file is part 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 <http://www.gnu.org/licenses/>.
+-->
+
+<refentry id="systemd.conf">
+        <refentryinfo>
+                <title>systemd.conf</title>
+                <productname>systemd</productname>
+
+                <authorgroup>
+                        <author>
+                                <contrib>Developer</contrib>
+                                <firstname>Lennart</firstname>
+                                <surname>Poettering</surname>
+                                <email>lennart@poettering.net</email>
+                        </author>
+                </authorgroup>
+        </refentryinfo>
+
+        <refmeta>
+                <refentrytitle>systemd.conf</refentrytitle>
+                <manvolnum>5</manvolnum>
+        </refmeta>
+
+        <refnamediv>
+                <refname>systemd.conf</refname>
+                <refpurpose>systemd manager configuration file</refpurpose>
+        </refnamediv>
+
+        <refsynopsisdiv>
+                <para><filename>system.conf</filename></para>
+                <para><filename>user.conf</filename></para>
+        </refsynopsisdiv>
+
+        <refsect1>
+                <title>Description</title>
+
+                <para>When run as system instance systemd reads the
+                configuration file <filename>system.conf</filename>,
+                otherwise <filename>user.conf</filename>. These
+                configuration files contain a few settings controlling
+                basic manager operations.</para>
+
+        </refsect1>
+
+        <refsect1>
+                <title>Options</title>
+
+                <para>All options are configured in the
+                <literal>[Manager]</literal> section:</para>
+
+                <variablelist>
+
+                        <varlistentry>
+                                <term><varname>LogLevel=</varname></term>
+                                <term><varname>LogTarget=</varname></term>
+                                <term><varname>LogColor=</varname></term>
+                                <term><varname>LogLocation=</varname></term>
+                                <term><varname>DumpCore=yes</varname></term>
+                                <term><varname>CrashShell=no</varname></term>
+                                <term><varname>ShowStatus=yes</varname></term>
+                                <term><varname>SysVConsole=yes</varname></term>
+                                <term><varname>CrashChVT=1</varname></term>
+                                <term><varname>DefaultStandardOutput=journal</varname></term>
+                                <term><varname>DefaultStandardError=inherit</varname></term>
+
+                                <listitem><para>Configures various
+                                parameters of basic manager
+                                operation. These options may be
+                                overridden by the respective command
+                                line arguments. See
+                                <citerefentry><refentrytitle>systemd</refentrytitle><manvolnum>1</manvolnum></citerefentry>
+                                for details about these command line
+                                arguments.</para></listitem>
+                        </varlistentry>
+
+                        <varlistentry>
+                                <term><varname>CPUAffinity=</varname></term>
+
+                                <listitem><para>Configures the initial
+                                CPU affinity for the init
+                                process. Takes a space-separated list
+                                of CPU indexes.</para></listitem>
+                        </varlistentry>
+
+                        <varlistentry>
+                                <term><varname>MountAuto=yes</varname></term>
+                                <term><varname>SwapAuto=yes</varname></term>
+
+                                <listitem><para>Configures whether
+                                systemd should automatically activate
+                                all swap or mounts listed in
+                                <filename>/etc/fstab</filename>, or
+                                whether this job is left to some other
+                                system script.</para></listitem>
+                        </varlistentry>
+
+                        <varlistentry>
+                                <term><varname>DefaultControllers=cpu</varname></term>
+
+                                <listitem><para>Configures in which
+                                cgroup controller hierarchies to
+                                create per-service cgroups
+                                automatically, in addition to the
+                                name=systemd named hierarchy. Defaults
+                                to 'cpu'. Takes a space separated list
+                                of controller names. Pass an empty
+                                string to ensure that systemd does not
+                                touch any hierarchies but its
+                                own.</para></listitem>
+                        </varlistentry>
+
+                        <varlistentry>
+                                <term><varname>JoinControllers=cpu,cpuacct</varname></term>
+
+                                <listitem><para>Configures controllers
+                                that shall be mounted in a single
+                                hierarchy. By default systemd will
+                                mount all controllers which are
+                                enabled in the kernel in individual
+                                hierachies, with the exception of
+                                those listed in this setting. Takes a
+                                space separated list of comma
+                                separated controller names, in order
+                                to allow multiple joined
+                                hierarchies. Defaults to
+                                'cpu,cpuacct'. Pass an empty string to
+                                ensure that systemd mounts all
+                                controllers in separate
+                                hierarchies.</para></listitem>
+                        </varlistentry>
+                </variablelist>
+        </refsect1>
+
+        <refsect1>
+                  <title>See Also</title>
+                  <para>
+                          <citerefentry><refentrytitle>systemd</refentrytitle><manvolnum>1</manvolnum></citerefentry>
+                  </para>
+        </refsect1>
+
+</refentry>
diff --git a/man/systemd.device.xml b/man/systemd.device.xml
new file mode 100644 (file)
index 0000000..63863be
--- /dev/null
@@ -0,0 +1,168 @@
+<?xml version='1.0'?> <!--*-nxml-*-->
+<?xml-stylesheet type="text/xsl" href="http://docbook.sourceforge.net/release/xsl/current/xhtml/docbook.xsl"?>
+<!DOCTYPE refentry PUBLIC "-//OASIS//DTD DocBook XML V4.2//EN"
+        "http://www.oasis-open.org/docbook/xml/4.2/docbookx.dtd">
+
+<!--
+  This file is part 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 <http://www.gnu.org/licenses/>.
+-->
+
+<refentry id="systemd.device">
+        <refentryinfo>
+                <title>systemd.device</title>
+                <productname>systemd</productname>
+
+                <authorgroup>
+                        <author>
+                                <contrib>Developer</contrib>
+                                <firstname>Lennart</firstname>
+                                <surname>Poettering</surname>
+                                <email>lennart@poettering.net</email>
+                        </author>
+                </authorgroup>
+        </refentryinfo>
+
+        <refmeta>
+                <refentrytitle>systemd.device</refentrytitle>
+                <manvolnum>5</manvolnum>
+        </refmeta>
+
+        <refnamediv>
+                <refname>systemd.device</refname>
+                <refpurpose>systemd device configuration files</refpurpose>
+        </refnamediv>
+
+        <refsynopsisdiv>
+                <para><filename>systemd.device</filename></para>
+        </refsynopsisdiv>
+
+        <refsect1>
+                <title>Description</title>
+
+                <para>A unit configuration file whose name ends in
+                <filename>.device</filename> encodes information about
+                a device unit as exposed in the
+                sysfs/<citerefentry><refentrytitle>udev</refentrytitle><manvolnum>7</manvolnum></citerefentry>
+                device tree.</para>
+
+                <para>This unit type has no specific options. See
+                <citerefentry><refentrytitle>systemd.unit</refentrytitle><manvolnum>5</manvolnum></citerefentry>
+                for the common options of all unit configuration
+                files. The common configuration items are configured
+                in the generic <literal>[Unit]</literal> and
+                <literal>[Install]</literal> sections. A separate
+                <literal>[Device]</literal> section does not exist,
+                since no device-specific options may be
+                configured.</para>
+
+                <para>systemd will automatically create dynamic device
+                units for all kernel devices that are marked with the
+                "systemd" udev tag (by default all block and network
+                devices, and a few others). This may be used to define
+                dependencies between devices and other
+                units.</para>
+
+                <para>Device units are named after the
+                <filename>/sys</filename> and
+                <filename>/dev</filename> paths they control. Example:
+                the device <filename>/dev/sda5</filename> is exposed
+                in systemd as <filename>dev-sda5.device</filename>. For
+                details about the escaping logic used to convert a
+                file system path to a unit name see
+                <citerefentry><refentrytitle>systemd.unit</refentrytitle><manvolnum>5</manvolnum></citerefentry>.</para>
+
+        </refsect1>
+
+        <refsect1>
+                <title>The udev Database</title>
+
+                <para>The settings of device units may either be
+                configured via unit files, or directly from the udev
+                database (which is recommended). The following udev
+                properties are understood by systemd:</para>
+
+                <variablelist>
+                        <varlistentry>
+                                <term><varname>SYSTEMD_WANTS=</varname></term>
+                                <listitem><para>Adds dependencies of
+                                type <varname>Wants</varname> from
+                                this unit to all listed units. This
+                                may be used to activate arbitrary
+                                units, when a specific device becomes
+                                available. Note that this and the
+                                other tags are not taken into account
+                                unless the device is tagged with the
+                                "<literal>systemd</literal>" string in
+                                the udev database, because otherwise
+                                the device is not exposed as systemd
+                                unit.</para></listitem>
+                        </varlistentry>
+
+                        <varlistentry>
+                                <term><varname>SYSTEMD_ALIAS=</varname></term>
+                                <listitem><para>Adds an additional
+                                alias name to the device unit. This
+                                must be an absolute path that is
+                                automatically transformed into a unit
+                                name. (See above.)</para></listitem>
+                        </varlistentry>
+
+                        <varlistentry>
+                                <term><varname>SYSTEMD_READY=</varname></term>
+                                <listitem><para>If set to 0 systemd
+                                will consider this device unplugged
+                                even if it shows up in the udev
+                                tree. If this property is unset or set
+                                to 1 the device will be considered
+                                plugged the moment it shows up in the
+                                udev tree. This property has no
+                                influence on the behaviour when a
+                                device disappears from the udev
+                                tree. This option is useful to support
+                                devices that initially show up in an
+                                uninitialized state in the tree, and for
+                                which a changed event is generated the
+                                moment they are fully set
+                                up.</para></listitem>
+                        </varlistentry>
+
+                        <varlistentry>
+                                <term><varname>ID_MODEL_FROM_DATABASE=</varname></term>
+                                <term><varname>ID_MODEL=</varname></term>
+
+                                <listitem><para>If set, this property is
+                                used as description string for the
+                                device unit.</para></listitem>
+                        </varlistentry>
+
+                </variablelist>
+
+
+        </refsect1>
+
+        <refsect1>
+                  <title>See Also</title>
+                  <para>
+                          <citerefentry><refentrytitle>systemd</refentrytitle><manvolnum>1</manvolnum></citerefentry>,
+                          <citerefentry><refentrytitle>systemctl</refentrytitle><manvolnum>8</manvolnum></citerefentry>,
+                          <citerefentry><refentrytitle>systemd.unit</refentrytitle><manvolnum>5</manvolnum></citerefentry>,
+                          <citerefentry><refentrytitle>udev</refentrytitle><manvolnum>7</manvolnum></citerefentry>
+                  </para>
+        </refsect1>
+
+</refentry>
diff --git a/man/systemd.exec.xml b/man/systemd.exec.xml
new file mode 100644 (file)
index 0000000..e6f49c9
--- /dev/null
@@ -0,0 +1,1106 @@
+<?xml version='1.0'?> <!--*-nxml-*-->
+<?xml-stylesheet type="text/xsl" href="http://docbook.sourceforge.net/release/xsl/current/xhtml/docbook.xsl"?>
+<!DOCTYPE refentry PUBLIC "-//OASIS//DTD DocBook XML V4.2//EN"
+        "http://www.oasis-open.org/docbook/xml/4.2/docbookx.dtd">
+
+<!--
+  This file is part 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 <http://www.gnu.org/licenses/>.
+-->
+
+<refentry id="systemd.exec">
+        <refentryinfo>
+                <title>systemd.exec</title>
+                <productname>systemd</productname>
+
+                <authorgroup>
+                        <author>
+                                <contrib>Developer</contrib>
+                                <firstname>Lennart</firstname>
+                                <surname>Poettering</surname>
+                                <email>lennart@poettering.net</email>
+                        </author>
+                </authorgroup>
+        </refentryinfo>
+
+        <refmeta>
+                <refentrytitle>systemd.exec</refentrytitle>
+                <manvolnum>5</manvolnum>
+        </refmeta>
+
+        <refnamediv>
+                <refname>systemd.exec</refname>
+                <refpurpose>systemd execution environment configuration</refpurpose>
+        </refnamediv>
+
+        <refsynopsisdiv>
+                <para><filename>systemd.service</filename>,
+                <filename>systemd.socket</filename>,
+                <filename>systemd.mount</filename>,
+                <filename>systemd.swap</filename></para>
+        </refsynopsisdiv>
+
+        <refsect1>
+                <title>Description</title>
+
+                <para>Unit configuration files for services, sockets,
+                mount points and swap devices share a subset of
+                configuration options which define the execution
+                environment of spawned processes.</para>
+
+                <para>This man page lists the configuration options
+                shared by these four unit types. See
+                <citerefentry><refentrytitle>systemd.unit</refentrytitle><manvolnum>5</manvolnum></citerefentry>
+                for the common options of all unit configuration
+                files, and
+                <citerefentry><refentrytitle>systemd.service</refentrytitle><manvolnum>5</manvolnum></citerefentry>,
+                <citerefentry><refentrytitle>systemd.socket</refentrytitle><manvolnum>5</manvolnum></citerefentry>,
+                <citerefentry><refentrytitle>systemd.swap</refentrytitle><manvolnum>5</manvolnum></citerefentry>
+                and
+                <citerefentry><refentrytitle>systemd.mount</refentrytitle><manvolnum>5</manvolnum></citerefentry>
+                for more information on the specific unit
+                configuration files. The execution specific
+                configuration options are configured in the [Service],
+                [Socket], [Mount] resp. [Swap] section, depending on the unit
+                type.</para>
+        </refsect1>
+
+        <refsect1>
+                <title>Options</title>
+
+                <variablelist>
+
+                        <varlistentry>
+                                <term><varname>WorkingDirectory=</varname></term>
+
+                                <listitem><para>Takes an absolute
+                                directory path. Sets the working
+                                directory for executed
+                                processes.</para></listitem>
+                        </varlistentry>
+
+                        <varlistentry>
+                                <term><varname>RootDirectory=</varname></term>
+
+                                <listitem><para>Takes an absolute
+                                directory path. Sets the root
+                                directory for executed processes, with
+                                the
+                                <citerefentry><refentrytitle>chroot</refentrytitle><manvolnum>2</manvolnum></citerefentry>
+                                system call. If this is used it must
+                                be ensured that the process and all
+                                its auxiliary files are available in
+                                the <function>chroot()</function>
+                                jail.</para></listitem>
+                        </varlistentry>
+
+                        <varlistentry>
+                                <term><varname>User=</varname></term>
+                                <term><varname>Group=</varname></term>
+
+                                <listitem><para>Sets the Unix user
+                                resp. group the processes are executed
+                                as. Takes a single user resp. group
+                                name or ID as argument. If no group is
+                                set the default group of the user is
+                                chosen.</para></listitem>
+                        </varlistentry>
+
+                        <varlistentry>
+                                <term><varname>SupplementaryGroups=</varname></term>
+
+                                <listitem><para>Sets the supplementary
+                                Unix groups the processes are executed
+                                as. This takes a space separated list
+                                of group names or IDs. This option may
+                                be specified more than once in which
+                                case all listed groups are set as
+                                supplementary groups. This option does
+                                not override but extends the list of
+                                supplementary groups configured in the
+                                system group database for the
+                                user.</para></listitem>
+                        </varlistentry>
+
+                        <varlistentry>
+                                <term><varname>Nice=</varname></term>
+
+                                <listitem><para>Sets the default nice
+                                level (scheduling priority) for
+                                executed processes. Takes an integer
+                                between -20 (highest priority) and 19
+                                (lowest priority). See
+                                <citerefentry><refentrytitle>setpriority</refentrytitle><manvolnum>2</manvolnum></citerefentry>
+                                for details.</para></listitem>
+                        </varlistentry>
+
+                        <varlistentry>
+                                <term><varname>OOMScoreAdjust=</varname></term>
+
+                                <listitem><para>Sets the adjustment
+                                level for the Out-Of-Memory killer for
+                                executed processes. Takes an integer
+                                between -1000 (to disable OOM killing
+                                for this process) and 1000 (to make
+                                killing of this process under memory
+                                pressure very likely). See <ulink
+                                url="http://www.kernel.org/doc/Documentation/filesystems/proc.txt">proc.txt</ulink>
+                                for details.</para></listitem>
+                        </varlistentry>
+
+                        <varlistentry>
+                                <term><varname>IOSchedulingClass=</varname></term>
+
+                                <listitem><para>Sets the IO scheduling
+                                class for executed processes. Takes an
+                                integer between 0 and 3 or one of the
+                                strings <option>none</option>,
+                                <option>realtime</option>,
+                                <option>best-effort</option> or
+                                <option>idle</option>. See
+                                <citerefentry><refentrytitle>ioprio_set</refentrytitle><manvolnum>2</manvolnum></citerefentry>
+                                for details.</para></listitem>
+                        </varlistentry>
+
+                        <varlistentry>
+                                <term><varname>IOSchedulingPriority=</varname></term>
+
+                                <listitem><para>Sets the IO scheduling
+                                priority for executed processes. Takes
+                                an integer between 0 (highest
+                                priority) and 7 (lowest priority). The
+                                available priorities depend on the
+                                selected IO scheduling class (see
+                                above). See
+                                <citerefentry><refentrytitle>ioprio_set</refentrytitle><manvolnum>2</manvolnum></citerefentry>
+                                for details.</para></listitem>
+                        </varlistentry>
+
+                        <varlistentry>
+                                <term><varname>CPUSchedulingPolicy=</varname></term>
+
+                                <listitem><para>Sets the CPU
+                                scheduling policy for executed
+                                processes. Takes one of
+                                <option>other</option>,
+                                <option>batch</option>,
+                                <option>idle</option>,
+                                <option>fifo</option> or
+                                <option>rr</option>. See
+                                <citerefentry><refentrytitle>sched_setscheduler</refentrytitle><manvolnum>2</manvolnum></citerefentry>
+                                for details.</para></listitem>
+                        </varlistentry>
+
+                        <varlistentry>
+                                <term><varname>CPUSchedulingPriority=</varname></term>
+
+                                <listitem><para>Sets the CPU
+                                scheduling priority for executed
+                                processes. Takes an integer between 1
+                                (lowest priority) and 99 (highest
+                                priority). The available priority
+                                range depends on the selected CPU
+                                scheduling policy (see above). See
+                                <citerefentry><refentrytitle>sched_setscheduler</refentrytitle><manvolnum>2</manvolnum></citerefentry>
+                                for details.</para></listitem>
+                        </varlistentry>
+
+                        <varlistentry>
+                                <term><varname>CPUSchedulingResetOnFork=</varname></term>
+
+                                <listitem><para>Takes a boolean
+                                argument. If true elevated CPU
+                                scheduling priorities and policies
+                                will be reset when the executed
+                                processes fork, and can hence not leak
+                                into child processes. See
+                                <citerefentry><refentrytitle>sched_setscheduler</refentrytitle><manvolnum>2</manvolnum></citerefentry>
+                                for details. Defaults to false.</para></listitem>
+                        </varlistentry>
+
+                        <varlistentry>
+                                <term><varname>CPUAffinity=</varname></term>
+
+                                <listitem><para>Controls the CPU
+                                affinity of the executed
+                                processes. Takes a space-separated
+                                list of CPU indexes. See
+                                <citerefentry><refentrytitle>sched_setaffinity</refentrytitle><manvolnum>2</manvolnum></citerefentry>
+                                for details.</para></listitem>
+                        </varlistentry>
+
+                        <varlistentry>
+                                <term><varname>UMask=</varname></term>
+
+                                <listitem><para>Controls the file mode
+                                creation mask. Takes an access mode in
+                                octal notation. See
+                                <citerefentry><refentrytitle>umask</refentrytitle><manvolnum>2</manvolnum></citerefentry>
+                                for details. Defaults to
+                                0022.</para></listitem>
+                        </varlistentry>
+
+                        <varlistentry>
+                                <term><varname>Environment=</varname></term>
+
+                                <listitem><para>Sets environment
+                                variables for executed
+                                processes. Takes a space-separated
+                                list of variable assignments. This
+                                option may be specified more than once
+                                in which case all listed variables
+                                will be set. If the same variable is
+                                set twice the later setting will
+                                override the earlier setting. See
+                                <citerefentry><refentrytitle>environ</refentrytitle><manvolnum>7</manvolnum></citerefentry>
+                                for details.</para></listitem>
+                        </varlistentry>
+                        <varlistentry>
+                                <term><varname>EnvironmentFile=</varname></term>
+                                <listitem><para>Similar to
+                                <varname>Environment=</varname> but
+                                reads the environment variables from a
+                                text file. The text file should
+                                contain new-line separated variable
+                                assignments. Empty lines and lines
+                                starting with ; or # will be ignored,
+                                which may be used for commenting. The
+                                parser strips leading and
+                                trailing whitespace from the values
+                                of assignments, unless you use
+                                double quotes (").
+                                The
+                                argument passed should be an absolute
+                                file name, optionally prefixed with
+                                "-", which indicates that if the file
+                                does not exist it won't be read and no
+                                error or warning message is
+                                logged. The files listed with this
+                                directive will be read shortly before
+                                the process is executed. Settings from
+                                these files override settings made
+                                with
+                                <varname>Environment=</varname>. If
+                                the same variable is set twice from
+                                these files the files will be read in
+                                the order they are specified and the
+                                later setting will override the
+                                earlier setting. </para></listitem>
+                        </varlistentry>
+
+                        <varlistentry>
+                                <term><varname>StandardInput=</varname></term>
+                                <listitem><para>Controls where file
+                                descriptor 0 (STDIN) of the executed
+                                processes is connected to. Takes one
+                                of <option>null</option>,
+                                <option>tty</option>,
+                                <option>tty-force</option>,
+                                <option>tty-fail</option> or
+                                <option>socket</option>. If
+                                <option>null</option> is selected
+                                standard input will be connected to
+                                <filename>/dev/null</filename>,
+                                i.e. all read attempts by the process
+                                will result in immediate EOF. If
+                                <option>tty</option> is selected
+                                standard input is connected to a TTY
+                                (as configured by
+                                <varname>TTYPath=</varname>, see
+                                below) and the executed process
+                                becomes the controlling process of the
+                                terminal. If the terminal is already
+                                being controlled by another process the
+                                executed process waits until the current
+                                controlling process releases the
+                                terminal.
+                                <option>tty-force</option>
+                                is similar to <option>tty</option>,
+                                but the executed process is forcefully
+                                and immediately made the controlling
+                                process of the terminal, potentially
+                                removing previous controlling
+                                processes from the
+                                terminal. <option>tty-fail</option> is
+                                similar to <option>tty</option> but if
+                                the terminal already has a controlling
+                                process start-up of the executed
+                                process fails.  The
+                                <option>socket</option> option is only
+                                valid in socket-activated services,
+                                and only when the socket configuration
+                                file (see
+                                <citerefentry><refentrytitle>systemd.socket</refentrytitle><manvolnum>5</manvolnum></citerefentry>
+                                for details) specifies a single socket
+                                only. If this option is set standard
+                                input will be connected to the socket
+                                the service was activated from, which
+                                is primarily useful for compatibility
+                                with daemons designed for use with the
+                                traditional
+                                <citerefentry><refentrytitle>inetd</refentrytitle><manvolnum>8</manvolnum></citerefentry>
+                                daemon. This setting defaults to
+                                <option>null</option>.</para></listitem>
+                        </varlistentry>
+                        <varlistentry>
+                                <term><varname>StandardOutput=</varname></term>
+                                <listitem><para>Controls where file
+                                descriptor 1 (STDOUT) of the executed
+                                processes is connected to. Takes one
+                                of <option>inherit</option>,
+                                <option>null</option>,
+                                <option>tty</option>,
+                                <option>syslog</option>,
+                                <option>kmsg</option>,
+                                <option>journal</option>,
+                                <option>syslog+console</option>,
+                                <option>kmsg+console</option>,
+                                <option>journal+console</option> or
+                                <option>socket</option>. If set to
+                                <option>inherit</option> the file
+                                descriptor of standard input is
+                                duplicated for standard output. If set
+                                to <option>null</option> standard
+                                output will be connected to
+                                <filename>/dev/null</filename>,
+                                i.e. everything written to it will be
+                                lost. If set to <option>tty</option>
+                                standard output will be connected to a
+                                tty (as configured via
+                                <varname>TTYPath=</varname>, see
+                                below). If the TTY is used for output
+                                only the executed process will not
+                                become the controlling process of the
+                                terminal, and will not fail or wait
+                                for other processes to release the
+                                terminal. <option>syslog</option>
+                                connects standard output to the
+                                <citerefentry><refentrytitle>syslog</refentrytitle><manvolnum>3</manvolnum></citerefentry>
+                                system syslog
+                                service. <option>kmsg</option>
+                                connects it with the kernel log buffer
+                                which is accessible via
+                                <citerefentry><refentrytitle>dmesg</refentrytitle><manvolnum>1</manvolnum></citerefentry>. <option>journal</option>
+                                connects it with the journal which is
+                                accessible via
+                                <citerefentry><refentrytitle>journalctl</refentrytitle><manvolnum>1</manvolnum></citerefentry>
+                                (Note that everything that is written
+                                to syslog or kmsg is implicitly stored
+                                in the journal as well, those options
+                                are hence supersets of this
+                                one). <option>syslog+console</option>,
+                                <option>journal+console</option> and
+                                <option>kmsg+console</option> work
+                                similarly but copy the output to the
+                                system console as
+                                well. <option>socket</option> connects
+                                standard output to a socket from
+                                socket activation, semantics are
+                                similar to the respective option of
+                                <varname>StandardInput=</varname>.
+                                This setting defaults to the value set
+                                with
+                                <option>DefaultStandardOutput=</option>
+                                in
+                                <citerefentry><refentrytitle>systemd.conf</refentrytitle><manvolnum>5</manvolnum></citerefentry>,
+                                which defaults to
+                                <option>journal</option>.</para></listitem>
+                        </varlistentry>
+                        <varlistentry>
+                                <term><varname>StandardError=</varname></term>
+                                <listitem><para>Controls where file
+                                descriptor 2 (STDERR) of the executed
+                                processes is connected to. The
+                                available options are identical to
+                                those of
+                                <varname>StandardOutput=</varname>,
+                                with one exception: if set to
+                                <option>inherit</option> the file
+                                descriptor used for standard output is
+                                duplicated for standard error. This
+                                setting defaults to the value set with
+                                <option>DefaultStandardError=</option>
+                                in
+                                <citerefentry><refentrytitle>systemd.conf</refentrytitle><manvolnum>5</manvolnum></citerefentry>,
+                                which defaults to
+                                <option>inherit</option>.</para></listitem>
+                        </varlistentry>
+                        <varlistentry>
+                                <term><varname>TTYPath=</varname></term>
+                                <listitem><para>Sets the terminal
+                                device node to use if standard input,
+                                output or stderr are connected to a
+                                TTY (see above). Defaults to
+                                <filename>/dev/console</filename>.</para></listitem>
+                        </varlistentry>
+                        <varlistentry>
+                                <term><varname>TTYReset=</varname></term>
+                                <listitem><para>Reset the terminal
+                                device specified with
+                                <varname>TTYPath=</varname> before and
+                                after execution. Defaults to
+                                <literal>no</literal>.</para></listitem>
+                        </varlistentry>
+                        <varlistentry>
+                                <term><varname>TTYVHangup=</varname></term>
+                                <listitem><para>Disconnect all clients
+                                which have opened the terminal device
+                                specified with
+                                <varname>TTYPath=</varname>
+                                before and after execution. Defaults
+                                to
+                                <literal>no</literal>.</para></listitem>
+                        </varlistentry>
+                        <varlistentry>
+                                <term><varname>TTYVTDisallocate=</varname></term>
+                                <listitem><para>If the the terminal
+                                device specified with
+                                <varname>TTYPath=</varname> is a
+                                virtual console terminal try to
+                                deallocate the TTY before and after
+                                execution. This ensures that the
+                                screen and scrollback buffer is
+                                cleared. Defaults to
+                                <literal>no</literal>.</para></listitem>
+                        </varlistentry>
+                        <varlistentry>
+                                <term><varname>SyslogIdentifier=</varname></term>
+                                <listitem><para>Sets the process name
+                                to prefix log lines sent to syslog or
+                                the kernel log buffer with. If not set
+                                defaults to the process name of the
+                                executed process. This option is only
+                                useful when
+                                <varname>StandardOutput=</varname> or
+                                <varname>StandardError=</varname> are
+                                set to <option>syslog</option> or
+                                <option>kmsg</option>.</para></listitem>
+                        </varlistentry>
+                        <varlistentry>
+                                <term><varname>SyslogFacility=</varname></term>
+                                <listitem><para>Sets the syslog
+                                facility to use when logging to
+                                syslog. One of <option>kern</option>,
+                                <option>user</option>,
+                                <option>mail</option>,
+                                <option>daemon</option>,
+                                <option>auth</option>,
+                                <option>syslog</option>,
+                                <option>lpr</option>,
+                                <option>news</option>,
+                                <option>uucp</option>,
+                                <option>cron</option>,
+                                <option>authpriv</option>,
+                                <option>ftp</option>,
+                                <option>local0</option>,
+                                <option>local1</option>,
+                                <option>local2</option>,
+                                <option>local3</option>,
+                                <option>local4</option>,
+                                <option>local5</option>,
+                                <option>local6</option> or
+                                <option>local7</option>. See
+                                <citerefentry><refentrytitle>syslog</refentrytitle><manvolnum>3</manvolnum></citerefentry>
+                                for details. This option is only
+                                useful when
+                                <varname>StandardOutput=</varname> or
+                                <varname>StandardError=</varname> are
+                                set to <option>syslog</option>.
+                                Defaults to
+                                <option>daemon</option>.</para></listitem>
+                        </varlistentry>
+                        <varlistentry>
+                                <term><varname>SyslogLevel=</varname></term>
+                                <listitem><para>Default syslog level
+                                to use when logging to syslog or the
+                                kernel log buffer. One of
+                                <option>emerg</option>,
+                                <option>alert</option>,
+                                <option>crit</option>,
+                                <option>err</option>,
+                                <option>warning</option>,
+                                <option>notice</option>,
+                                <option>info</option>,
+                                <option>debug</option>. See
+                                <citerefentry><refentrytitle>syslog</refentrytitle><manvolnum>3</manvolnum></citerefentry>
+                                for details. This option is only
+                                useful when
+                                <varname>StandardOutput=</varname> or
+                                <varname>StandardError=</varname> are
+                                set to <option>syslog</option> or
+                                <option>kmsg</option>. Note that
+                                individual lines output by the daemon
+                                might be prefixed with a different log
+                                level which can be used to override
+                                the default log level specified
+                                here. The interpretation of these
+                                prefixes may be disabled with
+                                <varname>SyslogLevelPrefix=</varname>,
+                                see below. For details see
+                                <citerefentry><refentrytitle>sd-daemon</refentrytitle><manvolnum>7</manvolnum></citerefentry>.
+
+                                Defaults to
+                                <option>info</option>.</para></listitem>
+                        </varlistentry>
+
+                        <varlistentry>
+                                <term><varname>SyslogLevelPrefix=</varname></term>
+                                <listitem><para>Takes a boolean
+                                argument. If true and
+                                <varname>StandardOutput=</varname> or
+                                <varname>StandardError=</varname> are
+                                set to <option>syslog</option> or
+                                <option>kmsg</option> log lines
+                                written by the executed process that
+                                are prefixed with a log level will be
+                                passed on to syslog with this log
+                                level set but the prefix removed. If
+                                set to false, the interpretation of
+                                these prefixes is disabled and the
+                                logged lines are passed on as-is. For
+                                details about this prefixing see
+                                <citerefentry><refentrytitle>sd-daemon</refentrytitle><manvolnum>7</manvolnum></citerefentry>.
+                                Defaults to true.</para></listitem>
+                        </varlistentry>
+
+                        <varlistentry>
+                                <term><varname>TimerSlackNSec=</varname></term>
+                                <listitem><para>Sets the timer slack
+                                in nanoseconds for the executed
+                                processes. The timer slack controls the
+                                accuracy of wake-ups triggered by
+                                timers. See
+                                <citerefentry><refentrytitle>prctl</refentrytitle><manvolnum>2</manvolnum></citerefentry>
+                                for more information. Note that in
+                                contrast to most other time span
+                                definitions this parameter takes an
+                                integer value in nano-seconds and does
+                                not understand any other
+                                units.</para></listitem>
+                        </varlistentry>
+
+                        <varlistentry>
+                                <term><varname>LimitCPU=</varname></term>
+                                <term><varname>LimitFSIZE=</varname></term>
+                                <term><varname>LimitDATA=</varname></term>
+                                <term><varname>LimitSTACK=</varname></term>
+                                <term><varname>LimitCORE=</varname></term>
+                                <term><varname>LimitRSS=</varname></term>
+                                <term><varname>LimitNOFILE=</varname></term>
+                                <term><varname>LimitAS=</varname></term>
+                                <term><varname>LimitNPROC=</varname></term>
+                                <term><varname>LimitMEMLOCK=</varname></term>
+                                <term><varname>LimitLOCKS=</varname></term>
+                                <term><varname>LimitSIGPENDING=</varname></term>
+                                <term><varname>LimitMSGQUEUE=</varname></term>
+                                <term><varname>LimitNICE=</varname></term>
+                                <term><varname>LimitRTPRIO=</varname></term>
+                                <term><varname>LimitRTTIME=</varname></term>
+                                <listitem><para>These settings control
+                                various resource limits for executed
+                                processes. See
+                                <citerefentry><refentrytitle>setrlimit</refentrytitle><manvolnum>2</manvolnum></citerefentry>
+                                for details. Use the string
+                                <varname>infinity</varname> to
+                                configure no limit on a specific
+                                resource.</para></listitem>
+                        </varlistentry>
+
+                        <varlistentry>
+                                <term><varname>PAMName=</varname></term>
+                                <listitem><para>Sets the PAM service
+                                name to set up a session as. If set
+                                the executed process will be
+                                registered as a PAM session under the
+                                specified service name. This is only
+                                useful in conjunction with the
+                                <varname>User=</varname> setting. If
+                                not set no PAM session will be opened
+                                for the executed processes. See
+                                <citerefentry><refentrytitle>pam</refentrytitle><manvolnum>8</manvolnum></citerefentry>
+                                for details.</para></listitem>
+                        </varlistentry>
+
+                        <varlistentry>
+                                <term><varname>TCPWrapName=</varname></term>
+                                <listitem><para>If this is a
+                                socket-activated service this sets the
+                                tcpwrap service name to check the
+                                permission for the current connection
+                                with. This is only useful in
+                                conjunction with socket-activated
+                                services, and stream sockets (TCP) in
+                                particular. It has no effect on other
+                                socket types (e.g. datagram/UDP) and
+                                on processes unrelated to socket-based
+                                activation. If the tcpwrap
+                                verification fails daemon start-up
+                                will fail and the connection is
+                                terminated. See
+                                <citerefentry><refentrytitle>tcpd</refentrytitle><manvolnum>8</manvolnum></citerefentry>
+                                for details. Note that this option may
+                                be used to do access control checks
+                                only. Shell commands and commands
+                                described in
+                                <citerefentry><refentrytitle>hosts_options</refentrytitle><manvolnum>5</manvolnum></citerefentry>
+                                are not supported.</para></listitem>
+                        </varlistentry>
+
+                        <varlistentry>
+                                <term><varname>CapabilityBoundingSet=</varname></term>
+
+                                <listitem><para>Controls which
+                                capabilities to include in the
+                                capability bounding set for the
+                                executed process. See
+                                <citerefentry><refentrytitle>capabilities</refentrytitle><manvolnum>7</manvolnum></citerefentry>
+                                for details. Takes a whitespace
+                                separated list of capability names as
+                                read by
+                                <citerefentry><refentrytitle>cap_from_name</refentrytitle><manvolnum>3</manvolnum></citerefentry>.
+                                Capabilities listed will be included
+                                in the bounding set, all others are
+                                removed. If the list of capabilities
+                                is prefixed with ~ all but the listed
+                                capabilities will be included, the
+                                effect of the assignment
+                                inverted. Note that this option does
+                                not actually set or unset any
+                                capabilities in the effective,
+                                permitted or inherited capability
+                                sets. That's what
+                                <varname>Capabilities=</varname> is
+                                for. If this option is not used the
+                                capability bounding set is not
+                                modified on process execution, hence
+                                no limits on the capabilities of the
+                                process are enforced.</para></listitem>
+                        </varlistentry>
+
+                        <varlistentry>
+                                <term><varname>SecureBits=</varname></term>
+                                <listitem><para>Controls the secure
+                                bits set for the executed process. See
+                                <citerefentry><refentrytitle>capabilities</refentrytitle><manvolnum>7</manvolnum></citerefentry>
+                                for details. Takes a list of strings:
+                                <option>keep-caps</option>,
+                                <option>keep-caps-locked</option>,
+                                <option>no-setuid-fixup</option>,
+                                <option>no-setuid-fixup-locked</option>,
+                                <option>noroot</option> and/or
+                                <option>noroot-locked</option>.
+                                </para></listitem>
+                        </varlistentry>
+
+                        <varlistentry>
+                                <term><varname>Capabilities=</varname></term>
+                                <listitem><para>Controls the
+                                <citerefentry><refentrytitle>capabilities</refentrytitle><manvolnum>7</manvolnum></citerefentry>
+                                set for the executed process. Take a
+                                capability string describing the
+                                effective, permitted and inherited
+                                capability sets as documented in
+                                <citerefentry><refentrytitle>cap_from_text</refentrytitle><manvolnum>3</manvolnum></citerefentry>.
+                                Note that these capability sets are
+                                usually influenced by the capabilities
+                                attached to the executed file. Due to
+                                that
+                                <varname>CapabilityBoundingSet=</varname>
+                                is probably the much more useful
+                                setting.</para></listitem>
+                        </varlistentry>
+
+                        <varlistentry>
+                                <term><varname>ControlGroup=</varname></term>
+
+                                <listitem><para>Controls the control
+                                groups the executed processes shall be
+                                made members of. Takes a
+                                space-separated list of cgroup
+                                identifiers. A cgroup identifier has a
+                                format like
+                                <filename>cpu:/foo/bar</filename>,
+                                where "cpu" identifies the kernel
+                                control group controller used, and
+                                <filename>/foo/bar</filename> is the
+                                control group path. The controller
+                                name and ":" may be omitted in which
+                                case the named systemd control group
+                                hierarchy is implied. Alternatively,
+                                the path and ":" may be omitted, in
+                                which case the default control group
+                                path for this unit is implied. This
+                                option may be used to place executed
+                                processes in arbitrary groups in
+                                arbitrary hierarchies -- which can be
+                                configured externally with additional
+                                execution limits. By default systemd
+                                will place all executed processes in
+                                separate per-unit control groups
+                                (named after the unit) in the systemd
+                                named hierarchy. Since every process
+                                can be in one group per hierarchy only
+                                overriding the control group path in
+                                the named systemd hierarchy will
+                                disable automatic placement in the
+                                default group. This option is
+                                primarily intended to place executed
+                                processes in specific paths in
+                                specific kernel controller
+                                hierarchies. It is however not
+                                recommended to manipulate the service
+                                control group path in the systemd
+                                named hierarchy. For details about
+                                control groups see <ulink
+                                url="http://www.kernel.org/doc/Documentation/cgroups/cgroups.txt">cgroups.txt</ulink>.</para></listitem>
+                        </varlistentry>
+
+                        <varlistentry>
+                                <term><varname>ControlGroupModify=</varname></term>
+                                <listitem><para>Takes a boolean
+                                argument. If true, the control groups
+                                created for this unit will be owned by
+                                the user specified with
+                                <varname>User=</varname> (and the
+                                appropriate group), and he/she can create
+                                subgroups as well as add processes to
+                                the group.</para></listitem>
+                        </varlistentry>
+
+                        <varlistentry>
+                                <term><varname>ControlGroupPersistent=</varname></term>
+                                <listitem><para>Takes a boolean
+                                argument. If true, the control groups
+                                created for this unit will be marked
+                                to be persistent, i.e. systemd will
+                                not remove them when stopping the
+                                unit. The default is false, meaning
+                                that the control groups will be
+                                removed when the unit is stopped. For
+                                details about the semantics of this
+                                logic see <ulink
+                                url="http://www.freedesktop.org/wiki/Software/systemd/PaxControlGroups">PaxControlGroups</ulink>.</para></listitem>
+                        </varlistentry>
+
+                        <varlistentry>
+                                <term><varname>ControlGroupAttribute=</varname></term>
+
+                                <listitem><para>Set a specific control
+                                group attribute for executed
+                                processes, and (if needed) add the the
+                                executed processes to a cgroup in the
+                                hierarchy of the controller the
+                                attribute belongs to. Takes two
+                                space-separated arguments: the
+                                attribute name (syntax is
+                                <literal>cpu.shares</literal> where
+                                <literal>cpu</literal> refers to a
+                                specific controller and
+                                <literal>shares</literal> to the
+                                attribute name), and the attribute
+                                value. Example:
+                                <literal>ControlGroupAttribute=cpu.shares
+                                512</literal>. If this option is used
+                                for an attribute that belongs to a
+                                kernel controller hierarchy the unit
+                                is not already configured to be added
+                                to (for example via the
+                                <literal>ControlGroup=</literal>
+                                option) then the unit will be added to
+                                the controller and the default unit
+                                cgroup path is implied. Thus, using
+                                <varname>ControlGroupAttribute=</varname>
+                                is in most case sufficient to make use
+                                of control group enforcements,
+                                explicit
+                                <varname>ControlGroup=</varname> are
+                                only necessary in case the implied
+                                default control group path for a
+                                service is not desirable. For details
+                                about control group attributes see
+                                <ulink
+                                url="http://www.kernel.org/doc/Documentation/cgroups/cgroups.txt">cgroups.txt</ulink>. This
+                                option may appear more than once, in
+                                order to set multiple control group
+                                attributes.</para></listitem>
+                        </varlistentry>
+
+                        <varlistentry>
+                                <term><varname>CPUShares=</varname></term>
+
+                                <listitem><para>Assign the specified
+                                overall CPU time shares to the
+                                processes executed. Takes an integer
+                                value. This controls the
+                                <literal>cpu.shares</literal> control
+                                group attribute, which defaults to
+                                1024. For details about this control
+                                group attribute see <ulink
+                                url="http://www.kernel.org/doc/Documentation/scheduler/sched-design-CFS.txt">sched-design-CFS.txt</ulink>.</para></listitem>
+                        </varlistentry>
+
+                        <varlistentry>
+                                <term><varname>MemoryLimit=</varname></term>
+                                <term><varname>MemorySoftLimit=</varname></term>
+
+                                <listitem><para>Limit the overall memory usage
+                                of the executed processes to a certain
+                                size. Takes a memory size in bytes. If
+                                the value is suffixed with K, M, G or
+                                T the specified memory size is parsed
+                                as Kilobytes, Megabytes, Gigabytes,
+                                resp. Terabytes (to the base
+                                1024). This controls the
+                                <literal>memory.limit_in_bytes</literal>
+                                and
+                                <literal>memory.soft_limit_in_bytes</literal>
+                                control group attributes. For details
+                                about these control group attributes
+                                see <ulink
+                                url="http://www.kernel.org/doc/Documentation/cgroups/memory.txt">memory.txt</ulink>.</para></listitem>
+                        </varlistentry>
+
+                        <varlistentry>
+                                <term><varname>DeviceAllow=</varname></term>
+                                <term><varname>DeviceDeny=</varname></term>
+
+                                <listitem><para>Control access to
+                                specific device nodes by the executed processes. Takes two
+                                space separated strings: a device node
+                                path (such as
+                                <filename>/dev/null</filename>)
+                                followed by a combination of r, w, m
+                                to control reading, writing resp.
+                                creating of the specific device node
+                                by the unit. This controls the
+                                <literal>devices.allow</literal>
+                                and
+                                <literal>devices.deny</literal>
+                                control group attributes. For details
+                                about these control group attributes
+                                see <ulink
+                                url="http://www.kernel.org/doc/Documentation/cgroups/devices.txt">devices.txt</ulink>.</para></listitem>
+                        </varlistentry>
+
+                        <varlistentry>
+                                <term><varname>BlockIOWeight=</varname></term>
+
+                                <listitem><para>Set the default or
+                                per-device overall block IO weight
+                                value for the executed
+                                processes. Takes either a single
+                                weight value (between 10 and 1000) to
+                                set the default block IO weight, or a
+                                space separated pair of a file path
+                                and a weight value to specify the
+                                device specific weight value (Example:
+                                "/dev/sda 500"). The file path may be
+                                specified as path to a block device
+                                node or as any other file in which
+                                case the backing block device of the
+                                file system of the file is
+                                determined. This controls the
+                                <literal>blkio.weight</literal> and
+                                <literal>blkio.weight_device</literal>
+                                control group attributes, which
+                                default to 1000. Use this option
+                                multiple times to set weights for
+                                multiple devices. For details about
+                                these control group attributes see
+                                <ulink
+                                url="http://www.kernel.org/doc/Documentation/cgroups/blkio-controller.txt">blkio-controller.txt</ulink>.</para></listitem>
+                        </varlistentry>
+
+                        <varlistentry>
+                                <term><varname>BlockIOReadBandwidth=</varname></term>
+                                <term><varname>BlockIOWriteBandwidth=</varname></term>
+
+                                <listitem><para>Set the per-device
+                                overall block IO bandwith limit for
+                                the executed processes. Takes a space
+                                separated pair of a file path and a
+                                bandwith value (in bytes per second)
+                                to specify the device specific
+                                bandwidth. The file path may be
+                                specified as path to a block device
+                                node or as any other file in which
+                                case the backing block device of the
+                                file system of the file is determined.
+                                If the bandwith is suffixed with K, M,
+                                G, or T the specified bandwith is
+                                parsed as Kilobytes, Megabytes,
+                                Gigabytes, resp. Terabytes (Example:
+                                "/dev/disk/by-path/pci-0000:00:1f.2-scsi-0:0:0:0
+                                5M"). This controls the
+                                <literal>blkio.read_bps_device</literal>
+                                and
+                                <literal>blkio.write_bps_device</literal>
+                                control group attributes. Use this
+                                option multiple times to set bandwith
+                                limits for multiple devices. For
+                                details about these control group
+                                attributes see <ulink
+                                url="http://www.kernel.org/doc/Documentation/cgroups/blkio-controller.txt">blkio-controller.txt</ulink>.</para></listitem>
+                        </varlistentry>
+
+                        <varlistentry>
+                                <term><varname>ReadWriteDirectories=</varname></term>
+                                <term><varname>ReadOnlyDirectories=</varname></term>
+                                <term><varname>InaccessibleDirectories=</varname></term>
+
+                                <listitem><para>Sets up a new
+                                file-system name space for executed
+                                processes. These options may be used
+                                to limit access a process might have
+                                to the main file-system
+                                hierarchy. Each setting takes a
+                                space-separated list of absolute
+                                directory paths. Directories listed in
+                                <varname>ReadWriteDirectories=</varname>
+                                are accessible from within the
+                                namespace with the same access rights
+                                as from outside. Directories listed in
+                                <varname>ReadOnlyDirectories=</varname>
+                                are accessible for reading only,
+                                writing will be refused even if the
+                                usual file access controls would
+                                permit this. Directories listed in
+                                <varname>InaccessibleDirectories=</varname>
+                                will be made inaccessible for processes
+                                inside the namespace. Note that
+                                restricting access with these options
+                                does not extend to submounts of a
+                                directory. You must list submounts
+                                separately in these settings to
+                                ensure the same limited access. These
+                                options may be specified more than
+                                once in which case all directories
+                                listed will have limited access from
+                                within the
+                                namespace.</para></listitem>
+                        </varlistentry>
+
+                        <varlistentry>
+                                <term><varname>PrivateTmp=</varname></term>
+
+                                <listitem><para>Takes a boolean
+                                argument. If true sets up a new file
+                                system namespace for the executed
+                                processes and mounts a private
+                                <filename>/tmp</filename> directory
+                                inside it, that is not shared by
+                                processes outside of the
+                                namespace. This is useful to secure
+                                access to temporary files of the
+                                process, but makes sharing between
+                                processes via
+                                <filename>/tmp</filename>
+                                impossible. Defaults to
+                                false.</para></listitem>
+                        </varlistentry>
+
+                        <varlistentry>
+                                <term><varname>PrivateNetwork=</varname></term>
+
+                                <listitem><para>Takes a boolean
+                                argument. If true sets up a new
+                                network namespace for the executed
+                                processes and configures only the
+                                loopback network device
+                                <literal>lo</literal> inside it. No
+                                other network devices will be
+                                available to the executed process.
+                                This is useful to securely turn off
+                                network access by the executed
+                                process. Defaults to
+                                false.</para></listitem>
+                        </varlistentry>
+
+                        <varlistentry>
+                                <term><varname>MountFlags=</varname></term>
+
+                                <listitem><para>Takes a mount
+                                propagation flag:
+                                <option>shared</option>,
+                                <option>slave</option> or
+                                <option>private</option>, which
+                                control whether namespaces set up with
+                                <varname>ReadWriteDirectories=</varname>,
+                                <varname>ReadOnlyDirectories=</varname>
+                                and
+                                <varname>InaccessibleDirectories=</varname>
+                                receive or propagate new mounts
+                                from/to the main namespace. See
+                                <citerefentry><refentrytitle>mount</refentrytitle><manvolnum>1</manvolnum></citerefentry>
+                                for details. Defaults to
+                                <option>shared</option>, i.e. the new
+                                namespace will both receive new mount
+                                points from the main namespace as well
+                                as propagate new mounts to
+                                it.</para></listitem>
+                        </varlistentry>
+
+                        <varlistentry>
+                                <term><varname>UtmpIdentifier=</varname></term>
+
+                                <listitem><para>Takes a a four
+                                character identifier string for an
+                                utmp/wtmp entry for this service. This
+                                should only be set for services such
+                                as <command>getty</command>
+                                implementations where utmp/wtmp
+                                entries must be created and cleared
+                                before and after execution. If the
+                                configured string is longer than four
+                                characters it is truncated and the
+                                terminal four characters are
+                                used. This setting interprets %I style
+                                string replacements. This setting is
+                                unset by default, i.e. no utmp/wtmp
+                                entries are created or cleaned up for
+                                this service.</para></listitem>
+                        </varlistentry>
+
+                        <varlistentry>
+                                <term><varname>IgnoreSIGPIPE=</varname></term>
+
+                                <listitem><para>Takes a boolean
+                                argument. If true causes SIGPIPE to be
+                                ignored in the executed
+                                process. Defaults to true, since
+                                SIGPIPE generally is useful only in
+                                shell pipelines.</para></listitem>
+                        </varlistentry>
+
+                </variablelist>
+        </refsect1>
+
+        <refsect1>
+                  <title>See Also</title>
+                  <para>
+                          <citerefentry><refentrytitle>systemd</refentrytitle><manvolnum>1</manvolnum></citerefentry>,
+                          <citerefentry><refentrytitle>systemctl</refentrytitle><manvolnum>8</manvolnum></citerefentry>,
+                          <citerefentry><refentrytitle>journalctl</refentrytitle><manvolnum>8</manvolnum></citerefentry>,
+                          <citerefentry><refentrytitle>systemd.unit</refentrytitle><manvolnum>5</manvolnum></citerefentry>,
+                          <citerefentry><refentrytitle>systemd.service</refentrytitle><manvolnum>5</manvolnum></citerefentry>,
+                          <citerefentry><refentrytitle>systemd.socket</refentrytitle><manvolnum>5</manvolnum></citerefentry>,
+                          <citerefentry><refentrytitle>systemd.swap</refentrytitle><manvolnum>5</manvolnum></citerefentry>,
+                          <citerefentry><refentrytitle>systemd.mount</refentrytitle><manvolnum>5</manvolnum></citerefentry>
+                  </para>
+        </refsect1>
+
+</refentry>
diff --git a/man/systemd.mount.xml b/man/systemd.mount.xml
new file mode 100644 (file)
index 0000000..8f1cc51
--- /dev/null
@@ -0,0 +1,278 @@
+<?xml version='1.0'?> <!--*-nxml-*-->
+<?xml-stylesheet type="text/xsl" href="http://docbook.sourceforge.net/release/xsl/current/xhtml/docbook.xsl"?>
+<!DOCTYPE refentry PUBLIC "-//OASIS//DTD DocBook XML V4.2//EN"
+        "http://www.oasis-open.org/docbook/xml/4.2/docbookx.dtd">
+
+<!--
+  This file is part 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 <http://www.gnu.org/licenses/>.
+-->
+
+<refentry id="systemd.mount">
+        <refentryinfo>
+                <title>systemd.mount</title>
+                <productname>systemd</productname>
+
+                <authorgroup>
+                        <author>
+                                <contrib>Developer</contrib>
+                                <firstname>Lennart</firstname>
+                                <surname>Poettering</surname>
+                                <email>lennart@poettering.net</email>
+                        </author>
+                </authorgroup>
+        </refentryinfo>
+
+        <refmeta>
+                <refentrytitle>systemd.mount</refentrytitle>
+                <manvolnum>5</manvolnum>
+        </refmeta>
+
+        <refnamediv>
+                <refname>systemd.mount</refname>
+                <refpurpose>systemd mount configuration files</refpurpose>
+        </refnamediv>
+
+        <refsynopsisdiv>
+                <para><filename>systemd.mount</filename></para>
+        </refsynopsisdiv>
+
+        <refsect1>
+                <title>Description</title>
+
+                <para>A unit configuration file whose name ends in
+                <filename>.mount</filename> encodes information about
+                a file system mount point controlled and supervised by
+                systemd.</para>
+
+                <para>This man page lists the configuration options
+                specific to this unit type. See
+                <citerefentry><refentrytitle>systemd.unit</refentrytitle><manvolnum>5</manvolnum></citerefentry>
+                for the common options of all unit configuration
+                files. The common configuration items are configured
+                in the generic [Unit] and [Install] sections. The
+                mount specific configuration options are configured
+                in the [Mount] section.</para>
+
+                <para>Additional options are listed in
+                <citerefentry><refentrytitle>systemd.exec</refentrytitle><manvolnum>5</manvolnum></citerefentry>,
+                which define the execution environment the
+                <citerefentry><refentrytitle>mount</refentrytitle><manvolnum>8</manvolnum></citerefentry>
+                binary is executed in.</para>
+
+                <para>Mount units must be named after the mount point
+                directories they control. Example: the mount point
+                <filename>/home/lennart</filename> must be configured
+                in a unit file
+                <filename>home-lennart.mount</filename>. For details
+                about the escaping logic used to convert a file system
+                path to a unit name see
+                <citerefentry><refentrytitle>systemd.unit</refentrytitle><manvolnum>5</manvolnum></citerefentry>.</para>
+
+                <para>Optionally, a mount unit may be accompanied by
+                an automount unit, to allow on-demand or parallelized
+                mounting. See
+                <citerefentry><refentrytitle>systemd.automount</refentrytitle><manvolnum>5</manvolnum></citerefentry>.</para>
+
+                <para>If an mount point is beneath another mount point
+                in the file system hierarchy, a dependency between both
+                units is created automatically.</para>
+
+                <para>Mount points created at runtime independent on
+                unit files or <filename>/etc/fstab</filename> will be
+                monitored by systemd and appear like any other mount
+                unit in systemd.</para>
+        </refsect1>
+
+        <refsect1>
+                <title><filename>/etc/fstab</filename></title>
+
+                <para>Mount units may either be configured via unit
+                files, or via <filename>/etc/fstab</filename> (see
+                <citerefentry><refentrytitle>fstab</refentrytitle><manvolnum>5</manvolnum></citerefentry>
+                for details).</para>
+
+                <para>When reading <filename>/etc/fstab</filename> a
+                few special mount options are understood by systemd
+                which influence how dependencies are created for mount
+                points from <filename>/etc/fstab</filename>. If
+                <option>MountAuto=yes</option> is set in
+                <filename>system.conf</filename> (which is the
+                default), or if <option>x-systemd.mount</option> is
+                specified as mount option, then systemd will create a
+                dependency of type <option>Wants</option> from either
+                <filename>local-fs.target</filename> or
+                <filename>remote-fs.target</filename>, depending
+                whether the file system is local or remote. If
+                <option>x-systemd.automount</option> is set, an
+                automount unit will be created for the file
+                system. See
+                <citerefentry><refentrytitle>systemd.automount</refentrytitle><manvolnum>5</manvolnum></citerefentry>
+                for details. If
+                <option>x-systemd-device-timeout=</option> is
+                specified it may be used to configure how long systemd
+                should wait for a device to show up before giving up
+                on an entry from
+                <filename>/etc/fstab</filename>. Specify a time in
+                seconds or explicitly specifiy a unit as
+                <literal>s</literal>, <literal>min</literal>,
+                <literal>h</literal>, <literal>ms</literal>.</para>
+
+                <para>If a mount point is configured in both
+                <filename>/etc/fstab</filename> and a unit file, the
+                configuration in the latter takes precedence.</para>
+        </refsect1>
+
+        <refsect1>
+                <title>Options</title>
+
+                <para>Mount files must include a [Mount] section,
+                which carries information about the file system mount points it
+                supervises. A number of options that may be used in
+                this section are shared with other unit types. These
+                options are documented in
+                <citerefentry><refentrytitle>systemd.exec</refentrytitle><manvolnum>5</manvolnum></citerefentry>. The
+                options specific to the [Mount] section of mount
+                units are the following:</para>
+
+                <variablelist>
+
+                        <varlistentry>
+                                <term><varname>What=</varname></term>
+                                <listitem><para>Takes an absolute path
+                                of a device node, file or other
+                                resource to mount. See
+                                <citerefentry><refentrytitle>mount</refentrytitle><manvolnum>8</manvolnum></citerefentry>
+                                for details. If this refers to a
+                                device node, a dependency on the
+                                respective device unit is
+                                automatically created. (See
+                                <citerefentry><refentrytitle>systemd.device</refentrytitle><manvolnum>5</manvolnum></citerefentry> for more information.)
+                                This option is
+                                mandatory.</para></listitem>
+                        </varlistentry>
+
+                        <varlistentry>
+                                <term><varname>Where=</varname></term>
+                                <listitem><para>Takes an absolute path
+                                of a directory of the mount point. If
+                                the mount point is not existing at
+                                time of mounting, it is created. This
+                                string must be reflected in the unit
+                                file name. (See above.) This option is
+                                mandatory.</para></listitem>
+                        </varlistentry>
+
+                        <varlistentry>
+                                <term><varname>Type=</varname></term>
+                                <listitem><para>Takes a string for the
+                                filesystem type. See
+                                <citerefentry><refentrytitle>mount</refentrytitle><manvolnum>8</manvolnum></citerefentry>
+                                for details. This setting is
+                                optional.</para></listitem>
+                        </varlistentry>
+
+                        <varlistentry>
+                                <term><varname>Options=</varname></term>
+
+                                <listitem><para>Mount options to use
+                                when mounting. This takes a comma
+                                separated list of options. This
+                                setting is optional.</para></listitem>
+                        </varlistentry>
+
+                        <varlistentry>
+                                <term><varname>DirectoryMode=</varname></term>
+                                <listitem><para>Directories of mount
+                                points (and any parent directories)
+                                are automatically created if
+                                needed. This option specifies the file
+                                system access mode used when creating
+                                these directories. Takes an access
+                                mode in octal notation. Defaults to
+                                0755.</para></listitem>
+                        </varlistentry>
+
+                        <varlistentry>
+                                <term><varname>TimeoutSec=</varname></term>
+                                <listitem><para>Configures the time to
+                                wait for the mount command to
+                                finish. If a command does not exit
+                                within the configured time the mount
+                                will be considered failed and be shut
+                                down again. All commands still running
+                                will be terminated forcibly via
+                                SIGTERM, and after another delay of
+                                this time with SIGKILL. (See
+                                <option>KillMode=</option> below.)
+                                Takes a unit-less value in seconds, or
+                                a time span value such as "5min
+                                20s". Pass 0 to disable the timeout
+                                logic. Defaults to
+                                90s.</para></listitem>
+                        </varlistentry>
+
+                        <varlistentry>
+                                <term><varname>KillMode=</varname></term>
+                                <listitem><para>Specifies how
+                                processes of this mount shall be
+                                killed. One of
+                                <option>control-group</option>,
+                                <option>process</option>,
+                                <option>none</option>.</para>
+
+                                <para>This option is mostly equivalent
+                                to the <option>KillMode=</option>
+                                option of service files. See
+                                <citerefentry><refentrytitle>systemd.service</refentrytitle><manvolnum>5</manvolnum></citerefentry>
+                                for details.</para></listitem>
+                        </varlistentry>
+
+                        <varlistentry>
+                                <term><varname>KillSignal=</varname></term>
+                                <listitem><para>Specifies which signal
+                                to use when killing a process of this
+                                mount. Defaults to SIGTERM.
+                                </para></listitem>
+                        </varlistentry>
+
+                        <varlistentry>
+                                <term><varname>SendSIGKILL=</varname></term>
+                                <listitem><para>Specifies whether to
+                                send SIGKILL to remaining processes
+                                after a timeout, if the normal
+                                shutdown procedure left processes of
+                                the mount around. Takes a boolean
+                                value. Defaults to "yes".
+                                </para></listitem>
+                        </varlistentry>
+                </variablelist>
+        </refsect1>
+
+        <refsect1>
+                  <title>See Also</title>
+                  <para>
+                          <citerefentry><refentrytitle>systemd</refentrytitle><manvolnum>1</manvolnum></citerefentry>,
+                          <citerefentry><refentrytitle>systemctl</refentrytitle><manvolnum>8</manvolnum></citerefentry>,
+                          <citerefentry><refentrytitle>systemd.unit</refentrytitle><manvolnum>5</manvolnum></citerefentry>,
+                          <citerefentry><refentrytitle>systemd.exec</refentrytitle><manvolnum>5</manvolnum></citerefentry>,
+                          <citerefentry><refentrytitle>systemd.device</refentrytitle><manvolnum>5</manvolnum></citerefentry>,
+                          <citerefentry><refentrytitle>mount</refentrytitle><manvolnum>8</manvolnum></citerefentry>
+                  </para>
+        </refsect1>
+
+</refentry>
diff --git a/man/systemd.path.xml b/man/systemd.path.xml
new file mode 100644 (file)
index 0000000..5b1ff75
--- /dev/null
@@ -0,0 +1,220 @@
+<?xml version='1.0'?> <!--*-nxml-*-->
+<?xml-stylesheet type="text/xsl" href="http://docbook.sourceforge.net/release/xsl/current/xhtml/docbook.xsl"?>
+<!DOCTYPE refentry PUBLIC "-//OASIS//DTD DocBook XML V4.2//EN"
+        "http://www.oasis-open.org/docbook/xml/4.2/docbookx.dtd">
+
+<!--
+  This file is part 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 <http://www.gnu.org/licenses/>.
+-->
+
+<refentry id="systemd.path">
+        <refentryinfo>
+                <title>systemd.path</title>
+                <productname>systemd</productname>
+
+                <authorgroup>
+                        <author>
+                                <contrib>Developer</contrib>
+                                <firstname>Lennart</firstname>
+                                <surname>Poettering</surname>
+                                <email>lennart@poettering.net</email>
+                        </author>
+                </authorgroup>
+        </refentryinfo>
+
+        <refmeta>
+                <refentrytitle>systemd.path</refentrytitle>
+                <manvolnum>5</manvolnum>
+        </refmeta>
+
+        <refnamediv>
+                <refname>systemd.path</refname>
+                <refpurpose>systemd path configuration files</refpurpose>
+        </refnamediv>
+
+        <refsynopsisdiv>
+                <para><filename>systemd.path</filename></para>
+        </refsynopsisdiv>
+
+        <refsect1>
+                <title>Description</title>
+
+                <para>A unit configuration file whose name ends in
+                <filename>.path</filename> encodes information about
+                a path monitored by systemd, for
+                path-based activation.</para>
+
+                <para>This man page lists the configuration options
+                specific to this unit type. See
+                <citerefentry><refentrytitle>systemd.unit</refentrytitle><manvolnum>5</manvolnum></citerefentry>
+                for the common options of all unit configuration
+                files. The common configuration items are configured
+                in the generic [Unit] and [Install] sections. The
+                path specific configuration options are configured in
+                the [Path] section.</para>
+
+                <para>For each path file, a matching unit file must
+                exist, describing the unit to activate when the path
+                changes. By default, a service by the same name as the
+                path (except for the suffix) is activated. Example: a
+                path file <filename>foo.path</filename> activates a
+                matching service <filename>foo.service</filename>. The
+                unit to activate may be controlled by
+                <varname>Unit=</varname> (see below).</para>
+
+                <para>Internally, path units use the
+                <citerefentry><refentrytitle>inotify</refentrytitle><manvolnum>7</manvolnum></citerefentry>
+                API to monitor file systems. Due to that, it suffers by the
+                same limitations as inotify, and for example cannot be
+                used to monitor files or directories changed by other
+                machines on remote NFS file systems.</para>
+
+                <para>If an path unit is beneath another mount
+                point in the file system hierarchy, a dependency
+                between both units is created automatically.</para>
+
+                <para>Unless <varname>DefaultDependencies=</varname>
+                is set to <option>false</option>, path units will
+                implicitly have dependencies of type
+                <varname>Conflicts=</varname> and
+                <varname>Before=</varname> on
+                <filename>shutdown.target</filename>. These ensure
+                that path units are terminated cleanly prior to system
+                shutdown. Only path units involved with early boot or
+                late system shutdown should disable this
+                option.</para>
+        </refsect1>
+
+        <refsect1>
+                <title>Options</title>
+
+                <para>Path files must include a [Path] section,
+                which carries information about the path(s) it
+                monitors. The options specific to the [Path] section
+                of path units are the following:</para>
+
+                <variablelist>
+                        <varlistentry>
+                                <term><varname>PathExists=</varname></term>
+                                <term><varname>PathExistsGlob=</varname></term>
+                                <term><varname>PathChanged=</varname></term>
+                                <term><varname>PathModified=</varname></term>
+                                <term><varname>DirectoryNotEmpty=</varname></term>
+
+                                <listitem><para>Defines paths to
+                                monitor for certain changes:
+                                <varname>PathExists=</varname> may be
+                                used to watch the mere existence of a
+                                file or directory. If the file
+                                specified exists the configured unit
+                                is
+                                activated. <varname>PathExistsGlob=</varname>
+                                works similar, but checks for the
+                                existance of at least one file
+                                matching the globbing pattern
+                                specified. <varname>PathChanged=</varname>
+                                may be used to watch a file or
+                                directory and activate the configured
+                                unit whenever it changes. It is not activated
+                                on every write to the watched file but it is
+                                activated if the file which was open for writing
+                                gets closed. <varname>PathModified=</varname>
+                                is similar, but additionally it is activated
+                                also on simple writes to the watched file.
+
+                                <varname>DirectoryNotEmpty=</varname>
+                                may be used to watch a directory and
+                                activate the configured unit whenever
+                                it contains at least one file.</para>
+
+                                <para>The arguments of these
+                                directives must be absolute file
+                                system paths.</para>
+
+                                <para>Multiple directives may be
+                                combined, of the same and of different
+                                types, to watch multiple paths.</para>
+
+                                <para>If a path is already existing
+                                (in case of
+                                <varname>PathExists=</varname> and
+                                <varname>PathExistsGlob=</varname>) or
+                                a directory already is not empty (in
+                                case of
+                                <varname>DirectoryNotEmpty=</varname>)
+                                at the time the path unit is
+                                activated, then the configured unit is
+                                immediately activated as
+                                well. Something similar does not apply
+                                to <varname>PathChanged=</varname>.
+                                </para></listitem>
+                        </varlistentry>
+                        <varlistentry>
+                                <term><varname>Unit=</varname></term>
+
+                                <listitem><para>The unit to activate
+                                when any of the configured paths
+                                changes. The argument is a unit name,
+                                whose suffix is not
+                                <filename>.path</filename>. If not
+                                specified, this value defaults to a
+                                service that has the same name as the
+                                path unit, except for the suffix. (See
+                                above.) It is recommended that the
+                                unit name that is activated and the
+                                unit name of the path unit are named
+                                identical, except for the
+                                suffix.</para></listitem>
+                        </varlistentry>
+                        <varlistentry>
+                                <term><varname>MakeDirectory=</varname></term>
+
+                                <listitem><para>Takes a boolean
+                                argument. If true the directories to
+                                watch are created before
+                                watching. This option is ignored for
+                                <varname>PathExists=</varname>
+                                settings. Defaults to
+                                <option>false</option>.</para></listitem>
+                        </varlistentry>
+                        <varlistentry>
+                                <term><varname>DirectoryMode=</varname></term>
+
+                                <listitem><para>If
+                                <varname>MakeDirectory=</varname> is
+                                enabled use the mode specified here to
+                                create the directories in
+                                question. Takes an access mode in
+                                octal notation. Defaults to
+                                <option>0755</option>.</para></listitem>
+                        </varlistentry>
+                </variablelist>
+        </refsect1>
+
+        <refsect1>
+                  <title>See Also</title>
+                  <para>
+                          <citerefentry><refentrytitle>systemd</refentrytitle><manvolnum>1</manvolnum></citerefentry>,
+                          <citerefentry><refentrytitle>systemctl</refentrytitle><manvolnum>8</manvolnum></citerefentry>,
+                          <citerefentry><refentrytitle>systemd.unit</refentrytitle><manvolnum>5</manvolnum></citerefentry>,
+                          <citerefentry><refentrytitle>systemd.service</refentrytitle><manvolnum>5</manvolnum></citerefentry>,
+                          <citerefentry><refentrytitle>inotify</refentrytitle><manvolnum>7</manvolnum></citerefentry>
+                  </para>
+        </refsect1>
+
+</refentry>
diff --git a/man/systemd.service.xml b/man/systemd.service.xml
new file mode 100644 (file)
index 0000000..837a992
--- /dev/null
@@ -0,0 +1,837 @@
+<?xml version='1.0'?> <!--*-nxml-*-->
+<?xml-stylesheet type="text/xsl" href="http://docbook.sourceforge.net/release/xsl/current/xhtml/docbook.xsl"?>
+<!DOCTYPE refentry PUBLIC "-//OASIS//DTD DocBook XML V4.2//EN"
+        "http://www.oasis-open.org/docbook/xml/4.2/docbookx.dtd">
+
+<!--
+  This file is part 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 <http://www.gnu.org/licenses/>.
+-->
+
+<refentry id="systemd.service">
+        <refentryinfo>
+                <title>systemd.service</title>
+                <productname>systemd</productname>
+
+                <authorgroup>
+                        <author>
+                                <contrib>Developer</contrib>
+                                <firstname>Lennart</firstname>
+                                <surname>Poettering</surname>
+                                <email>lennart@poettering.net</email>
+                        </author>
+                </authorgroup>
+        </refentryinfo>
+
+        <refmeta>
+                <refentrytitle>systemd.service</refentrytitle>
+                <manvolnum>5</manvolnum>
+        </refmeta>
+
+        <refnamediv>
+                <refname>systemd.service</refname>
+                <refpurpose>systemd service configuration files</refpurpose>
+        </refnamediv>
+
+        <refsynopsisdiv>
+                <para><filename>systemd.service</filename></para>
+        </refsynopsisdiv>
+
+        <refsect1>
+                <title>Description</title>
+
+                <para>A unit configuration file whose name ends in
+                <filename>.service</filename> encodes information
+                about a process controlled and supervised by
+                systemd.</para>
+
+                <para>This man page lists the configuration options
+                specific to this unit type. See
+                <citerefentry><refentrytitle>systemd.unit</refentrytitle><manvolnum>5</manvolnum></citerefentry>
+                for the common options of all unit configuration
+                files. The common configuration items are configured
+                in the generic <literal>[Unit]</literal> and
+                <literal>[Install]</literal> sections. The service
+                specific configuration options are configured in the
+                <literal>[Service]</literal> section.</para>
+
+                <para>Additional options are listed in
+                <citerefentry><refentrytitle>systemd.exec</refentrytitle><manvolnum>5</manvolnum></citerefentry>,
+                which define the execution environment the commands
+                are executed in.</para>
+
+                <para>Unless <varname>DefaultDependencies=</varname>
+                is set to <option>false</option>, service units will
+                implicitly have dependencies of type
+                <varname>Requires=</varname> and
+                <varname>After=</varname> on
+                <filename>basic.target</filename> as well as
+                dependencies of type <varname>Conflicts=</varname> and
+                <varname>Before=</varname> on
+                <filename>shutdown.target</filename>. These ensure
+                that normal service units pull in basic system
+                initialization, and are terminated cleanly prior to
+                system shutdown. Only services involved with early
+                boot or late system shutdown should disable this
+                option.</para>
+
+                <para>If a service is requested under a certain name
+                but no unit configuration file is found, systemd looks
+                for a SysV init script by the same name (with the
+                <filename>.service</filename> suffix removed) and
+                dynamically creates a service unit from that
+                script. This is useful for compatibility with
+                SysV.</para>
+        </refsect1>
+
+        <refsect1>
+                <title>Options</title>
+
+                <para>Service files must include a
+                <literal>[Service]</literal> section, which carries
+                information about the service and the process it
+                supervises. A number of options that may be used in
+                this section are shared with other unit types. These
+                options are documented in
+                <citerefentry><refentrytitle>systemd.exec</refentrytitle><manvolnum>5</manvolnum></citerefentry>. The
+                options specific to the <literal>[Service]</literal>
+                section of service units are the following:</para>
+
+                <variablelist>
+                        <varlistentry>
+                                <term><varname>Type=</varname></term>
+
+                                <listitem><para>Configures the process
+                                start-up type for this service
+                                unit. One of <option>simple</option>,
+                                <option>forking</option>,
+                                <option>oneshot</option>,
+                                <option>dbus</option>,
+                                <option>notify</option>.</para>
+
+                                <para>If set to
+                                <option>simple</option> (the default
+                                value) it is expected that the process
+                                configured with
+                                <varname>ExecStart=</varname> is the
+                                main process of the service. In this
+                                mode, if the process offers
+                                functionality to other processes on
+                                the system its communication channels
+                                should be installed before the daemon
+                                is started up (e.g. sockets set up by
+                                systemd, via socket activation), as
+                                systemd will immediately proceed
+                                starting follow-up units.</para>
+
+                                <para>If set to
+                                <option>forking</option> it is
+                                expected that the process configured
+                                with <varname>ExecStart=</varname>
+                                will call <function>fork()</function>
+                                as part of its start-up. The parent process is
+                                expected to exit when start-up is
+                                complete and all communication
+                                channels set up. The child continues
+                                to run as the main daemon
+                                process. This is the behaviour of
+                                traditional UNIX daemons. If this
+                                setting is used, it is recommended to
+                                also use the
+                                <varname>PIDFile=</varname> option, so
+                                that systemd can identify the main
+                                process of the daemon. systemd will
+                                proceed starting follow-up units as
+                                soon as the parent process
+                                exits.</para>
+
+                                <para>Behaviour of
+                                <option>oneshot</option> is similar
+                                to <option>simple</option>, however
+                                it is expected that the process has to
+                                exit before systemd starts follow-up
+                                units. <varname>RemainAfterExit=</varname>
+                                is particularly useful for this type
+                                of service.</para>
+
+                                <para>Behaviour of
+                                <option>dbus</option> is similar to
+                                <option>simple</option>, however it is
+                                expected that the daemon acquires a
+                                name on the D-Bus bus, as configured
+                                by
+                                <varname>BusName=</varname>. systemd
+                                will proceed starting follow-up units
+                                after the D-Bus bus name has been
+                                acquired. Service units with this
+                                option configured implicitly gain
+                                dependencies on the
+                                <filename>dbus.socket</filename>
+                                unit.</para>
+
+                                <para>Behaviour of
+                                <option>notify</option> is similar to
+                                <option>simple</option>, however it is
+                                expected that the daemon sends a
+                                notification message via
+                                <citerefentry><refentrytitle>sd_notify</refentrytitle><manvolnum>3</manvolnum></citerefentry>
+                                or an equivalent call when it finished
+                                starting up. systemd will proceed
+                                starting follow-up units after this
+                                notification message has been sent. If
+                                this option is used
+                                <varname>NotifyAccess=</varname> (see
+                                below) should be set to open access to
+                                the notification socket provided by
+                                systemd. If
+                                <varname>NotifyAccess=</varname> is
+                                not set, it will be implicitly set to
+                                <option>main</option>.</para>
+                                </listitem>
+                        </varlistentry>
+
+                        <varlistentry>
+                                <term><varname>RemainAfterExit=</varname></term>
+
+                                <listitem><para>Takes a boolean value
+                                that specifies whether the service
+                                shall be considered active even when
+                                all its processes exited. Defaults to
+                                <option>no</option>.</para>
+                                </listitem>
+                        </varlistentry>
+
+                        <varlistentry>
+                                <term><varname>GuessMainPID=</varname></term>
+
+                                <listitem><para>Takes a boolean value
+                                that specifies whether systemd should
+                                try to guess the main PID of a service
+                                should if it cannot be determined
+                                reliably. This option is ignored
+                                unless <option>Type=forking</option>
+                                is set and <option>PIDFile=</option>
+                                is unset because for the other types
+                                or with an explicitly configured PID
+                                file the main PID is always known. The
+                                guessing algorithm might come to
+                                incorrect conclusions if a daemon
+                                consists of more than one process. If
+                                the main PID cannot be determined
+                                failure detection and automatic
+                                restarting of a service will not work
+                                reliably. Defaults to
+                                <option>yes</option>.</para>
+                                </listitem>
+                        </varlistentry>
+
+                        <varlistentry>
+                                <term><varname>PIDFile=</varname></term>
+
+                                <listitem><para>Takes an absolute file
+                                name pointing to the PID file of this
+                                daemon. Use of this option is
+                                recommended for services where
+                                <varname>Type=</varname> is set to
+                                <option>forking</option>. systemd will
+                                read the PID of the main process of
+                                the daemon after start-up of the
+                                service. systemd will not write to the
+                                file configured here.</para>
+                                </listitem>
+                        </varlistentry>
+
+                        <varlistentry>
+                                <term><varname>BusName=</varname></term>
+
+                                <listitem><para>Takes a D-Bus bus
+                                name, where this service is reachable
+                                as. This option is mandatory for
+                                services where
+                                <varname>Type=</varname> is set to
+                                <option>dbus</option>, but its use
+                                is otherwise recommended as well if
+                                the process takes a name on the D-Bus
+                                bus.</para>
+                                </listitem>
+                        </varlistentry>
+
+                        <varlistentry>
+                                <term><varname>ExecStart=</varname></term>
+                                <listitem><para>Takes a command line
+                                that is executed when this service
+                                shall be started up. The first token
+                                of the command line must be an
+                                absolute file name, then followed by
+                                arguments for the process. It is
+                                mandatory to set this option for all
+                                services. This option may not be
+                                specified more than once, except when
+                                <varname>Type=oneshot</varname> is
+                                used in which case more than one
+                                <varname>ExecStart=</varname> line is
+                                accepted which are then invoked one by
+                                one, sequentially in the order they
+                                appear in the unit file.</para>
+
+                                <para>Optionally, if the absolute file
+                                name is prefixed with
+                                <literal>@</literal>, the second token
+                                will be passed as
+                                <literal>argv[0]</literal> to the
+                                executed process, followed by the
+                                further arguments specified. If the
+                                first token is prefixed with
+                                <literal>-</literal> an exit code of
+                                the command normally considered a
+                                failure (i.e. non-zero exit status or
+                                abnormal exit due to signal) is ignored
+                                and considered success. If both
+                                <literal>-</literal> and
+                                <literal>@</literal> are used for the
+                                same command the former must precede
+                                the latter. Unless
+                                <varname>Type=forking</varname> is
+                                set, the process started via this
+                                command line will be considered the
+                                main process of the daemon. The
+                                command line accepts % specifiers as
+                                described in
+                                <citerefentry><refentrytitle>systemd.unit</refentrytitle><manvolnum>5</manvolnum></citerefentry>.</para>
+
+                                <para>On top of that basic environment
+                                variable substitution is
+                                supported. Use
+                                <literal>${FOO}</literal> as part of a
+                                word, or as word of its own on the
+                                command line, in which case it will be
+                                replaced by the value of the
+                                environment variable including all
+                                whitespace it contains, resulting in a
+                                single argument.  Use
+                                <literal>$FOO</literal> as a separate
+                                word on the command line, in which
+                                case it will be replaced by the value
+                                of the environment variable split up
+                                at whitespace, resulting in no or more
+                                arguments. Note that the first
+                                argument (i.e. the program to execute)
+                                may not be a variable, and must be a
+                                literal and absolute path
+                                name.</para></listitem>
+                        </varlistentry>
+
+                        <varlistentry>
+                                <term><varname>ExecStartPre=</varname></term>
+                                <term><varname>ExecStartPost=</varname></term>
+                                <listitem><para>Additional commands
+                                that are executed before (resp. after)
+                                the command in
+                                <varname>ExecStart=</varname>. Multiple
+                                command lines may be concatenated in a
+                                single directive, by separating them
+                                by semicolons (these semicolons must
+                                be passed as separate words). In that
+                                case, the commands are executed one
+                                after the other,
+                                serially. Alternatively, these
+                                directives may be specified more than
+                                once with the same effect. However,
+                                the latter syntax is not recommended
+                                for compatibility with parsers
+                                suitable for XDG
+                                <filename>.desktop</filename> files.
+                                Use of these settings is
+                                optional. Specifier and environment
+                                variable substitution is
+                                supported.</para></listitem>
+                        </varlistentry>
+
+                        <varlistentry>
+                                <term><varname>ExecReload=</varname></term>
+                                <listitem><para>Commands to execute to
+                                trigger a configuration reload in the
+                                service. This argument takes multiple
+                                command lines, following the same
+                                scheme as pointed out for
+                                <varname>ExecStartPre=</varname>
+                                above. Use of this setting is
+                                optional. Specifier and environment
+                                variable substitution is supported
+                                here following the same scheme as for
+                                <varname>ExecStart=</varname>. One
+                                special environment variable is set:
+                                if known <literal>$MAINPID</literal> is
+                                set to the main process of the
+                                daemon, and may be used for command
+                                lines like the following:
+                                <command>/bin/kill -HUP
+                                $MAINPID</command>.</para></listitem>
+                        </varlistentry>
+
+                        <varlistentry>
+                                <term><varname>ExecStop=</varname></term>
+                                <listitem><para>Commands to execute to
+                                stop the service started via
+                                <varname>ExecStart=</varname>. This
+                                argument takes multiple command lines,
+                                following the same scheme as pointed
+                                out for
+                                <varname>ExecStartPre=</varname>
+                                above. Use of this setting is
+                                optional. All processes remaining for
+                                a service after the commands
+                                configured in this option are run are
+                                terminated according to the
+                                <varname>KillMode=</varname> setting
+                                (see below). If this option is not
+                                specified the process is terminated
+                                right-away when service stop is
+                                requested. Specifier and environment
+                                variable substitution is supported
+                                (including
+                                <literal>$MAINPID</literal>, see
+                                above).</para></listitem>
+                        </varlistentry>
+
+                        <varlistentry>
+                                <term><varname>ExecStopPost=</varname></term>
+                                <listitem><para>Additional commands
+                                that are executed after the service
+                                was stopped using the commands
+                                configured in
+                                <varname>ExecStop=</varname>. This
+                                argument takes multiple command lines,
+                                following the same scheme as pointed
+                                out for
+                                <varname>ExecStartPre</varname>. Use
+                                of these settings is
+                                optional. Specifier and environment
+                                variable substitution is
+                                supported.</para></listitem>
+                        </varlistentry>
+
+                        <varlistentry>
+                                <term><varname>RestartSec=</varname></term>
+                                <listitem><para>Configures the time to
+                                sleep before restarting a service (as
+                                configured with
+                                <varname>Restart=</varname>). Takes a
+                                unit-less value in seconds, or a time
+                                span value such as "5min
+                                20s". Defaults to
+                                100ms.</para></listitem>
+                        </varlistentry>
+
+                        <varlistentry>
+                                <term><varname>TimeoutSec=</varname></term>
+                                <listitem><para>Configures the time to
+                                wait for start-up and stop. If a
+                                daemon service does not signal
+                                start-up completion within the
+                                configured time the service will be
+                                considered failed and be shut down
+                                again. If a service is asked to stop
+                                but does not terminate in the
+                                specified time it will be terminated
+                                forcibly via SIGTERM, and after
+                                another delay of this time with
+                                SIGKILL. (See
+                                <varname>KillMode=</varname>
+                                below.) Takes a unit-less value in seconds, or a
+                                time span value such as "5min
+                                20s". Pass 0 to disable the timeout
+                                logic. Defaults to
+                                90s.</para></listitem>
+                        </varlistentry>
+
+                        <varlistentry>
+                                <term><varname>WatchdogSec=</varname></term>
+                                <listitem><para>Configures the
+                                watchdog timeout for a service. This
+                                is activated when the start-up is
+                                completed. The service must call
+                                <citerefentry><refentrytitle>sd_notify</refentrytitle><manvolnum>3</manvolnum></citerefentry>
+                                regularly with "WATCHDOG=1". If the
+                                time between two such calls is larger
+                                than the configured time then the
+                                service is placed in a failure
+                                state. By setting
+                                <varname>Restart=</varname>
+                                to <option>on-failure</option> or
+                                <option>always</option> the service
+                                will be automatically restarted. The
+                                time configured here will be passed to
+                                the executed service process in the
+                                <varname>WATCHDOG_USEC=</varname>
+                                environment variable. If
+                                this option is used
+                                <varname>NotifyAccess=</varname> (see
+                                below) should be set to open access to
+                                the notification socket provided by
+                                systemd. If
+                                <varname>NotifyAccess=</varname> is not
+                                set, it will be implicitly set to
+                                <option>main</option>. Defaults to 0,
+                                which disables this
+                                feature.</para></listitem>
+                        </varlistentry>
+
+                        <varlistentry>
+                                <term><varname>Restart=</varname></term>
+                                <listitem><para>Configures whether the
+                                main service process shall be
+                                restarted when it exits. Takes one of
+                                <option>no</option>,
+                                <option>on-success</option>,
+                                <option>on-failure</option>,
+                                <option>on-abort</option> or
+                                <option>always</option>. If set to
+                                <option>no</option> (the default) the
+                                service will not be restarted when it
+                                exits. If set to
+                                <option>on-success</option> it will be
+                                restarted only when it exited cleanly,
+                                i.e. terminated with an exit code of
+                                0. If set to
+                                <option>on-failure</option> it will be
+                                restarted only when it exited with an
+                                exit code not equalling 0, when
+                                terminated by a signal, when an
+                                operation times out or when the
+                                configured watchdog timeout is
+                                triggered. If set to
+                                <option>on-abort</option> it will be
+                                restarted only if it exits due to
+                                reception of an uncaught signal. If
+                                set to <option>always</option> the
+                                service will be restarted regardless
+                                whether it exited cleanly or not,
+                                got terminated abnormally by a
+                                signal or hit a timeout.</para></listitem>
+                        </varlistentry>
+
+                        <varlistentry>
+                                <term><varname>PermissionsStartOnly=</varname></term>
+                                <listitem><para>Takes a boolean
+                                argument. If true, the permission
+                                related execution options as
+                                configured with
+                                <varname>User=</varname> and similar
+                                options (see
+                                <citerefentry><refentrytitle>systemd.exec</refentrytitle><manvolnum>5</manvolnum></citerefentry>
+                                for more information) are only applied
+                                to the process started with
+                                <varname>ExecStart=</varname>, and not
+                                to the various other
+                                <varname>ExecStartPre=</varname>,
+                                <varname>ExecStartPost=</varname>,
+                                <varname>ExecReload=</varname>,
+                                <varname>ExecStop=</varname>,
+                                <varname>ExecStopPost=</varname>
+                                commands. If false, the setting is
+                                applied to all configured commands the
+                                same way. Defaults to
+                                false.</para></listitem>
+                        </varlistentry>
+
+                        <varlistentry>
+                                <term><varname>RootDirectoryStartOnly=</varname></term>
+                                <listitem><para>Takes a boolean
+                                argument. If true, the root directory
+                                as configured with the
+                                <varname>RootDirectory=</varname>
+                                option (see
+                                <citerefentry><refentrytitle>systemd.exec</refentrytitle><manvolnum>5</manvolnum></citerefentry>
+                                for more information) is only applied
+                                to the process started with
+                                <varname>ExecStart=</varname>, and not
+                                to the various other
+                                <varname>ExecStartPre=</varname>,
+                                <varname>ExecStartPost=</varname>,
+                                <varname>ExecReload=</varname>,
+                                <varname>ExecStop=</varname>,
+                                <varname>ExecStopPost=</varname>
+                                commands. If false, the setting is
+                                applied to all configured commands the
+                                same way. Defaults to
+                                false.</para></listitem>
+                        </varlistentry>
+
+                        <varlistentry>
+                                <term><varname>SysVStartPriority=</varname></term>
+                                <listitem><para>Set the SysV start
+                                priority to use to order this service
+                                in relation to SysV services lacking
+                                LSB headers. This option is only
+                                necessary to fix ordering in relation
+                                to legacy SysV services, that have no
+                                ordering information encoded in the
+                                script headers. As such it should only
+                                be used as temporary compatibility
+                                option, and not be used in new unit
+                                files. Almost always it is a better
+                                choice to add explicit ordering
+                                directives via
+                                <varname>After=</varname> or
+                                <varname>Before=</varname>,
+                                instead. For more details see
+                                <citerefentry><refentrytitle>systemd.unit</refentrytitle><manvolnum>5</manvolnum></citerefentry>. If
+                                used, pass an integer value in the
+                                range 0-99.</para></listitem>
+                        </varlistentry>
+
+                        <varlistentry>
+                                <term><varname>KillMode=</varname></term>
+                                <listitem><para>Specifies how
+                                processes of this service shall be
+                                killed. One of
+                                <option>control-group</option>,
+                                <option>process</option>,
+                                <option>none</option>.</para>
+
+                                <para>If set to
+                                <option>control-group</option> all
+                                remaining processes in the control
+                                group of this service will be
+                                terminated on service stop, after the
+                                stop command (as configured with
+                                <varname>ExecStop=</varname>) is
+                                executed. If set to
+                                <option>process</option> only the main
+                                process itself is killed. If set to
+                                <option>none</option> no process is
+                                killed. In this case only the stop
+                                command will be executed on service
+                                stop, but no process be killed
+                                otherwise. Processes remaining alive
+                                after stop are left in their control
+                                group and the control group continues
+                                to exist after stop unless it is
+                                empty. Defaults to
+                                <option>control-group</option>.</para>
+
+                                <para>Processes will first be
+                                terminated via SIGTERM (unless the
+                                signal to send is changed via
+                                <varname>KillSignal=</varname>). If
+                                then after a delay (configured via the
+                                <varname>TimeoutSec=</varname> option)
+                                processes still remain, the
+                                termination request is repeated with
+                                the SIGKILL signal (unless this is
+                                disabled via the
+                                <varname>SendSIGKILL=</varname>
+                                option). See
+                                <citerefentry><refentrytitle>kill</refentrytitle><manvolnum>2</manvolnum></citerefentry>
+                                for more
+                                information.</para></listitem>
+                        </varlistentry>
+
+                        <varlistentry>
+                                <term><varname>KillSignal=</varname></term>
+                                <listitem><para>Specifies which signal
+                                to use when killing a
+                                service. Defaults to SIGTERM.
+                                </para></listitem>
+                        </varlistentry>
+
+                        <varlistentry>
+                                <term><varname>SendSIGKILL=</varname></term>
+                                <listitem><para>Specifies whether to
+                                send SIGKILL to remaining processes
+                                after a timeout, if the normal
+                                shutdown procedure left processes of
+                                the service around. Takes a boolean
+                                value. Defaults to "yes".
+                                </para></listitem>
+                        </varlistentry>
+
+                        <varlistentry>
+                                <term><varname>NonBlocking=</varname></term>
+                                <listitem><para>Set O_NONBLOCK flag
+                                for all file descriptors passed via
+                                socket-based activation. If true, all
+                                file descriptors >= 3 (i.e. all except
+                                STDIN/STDOUT/STDERR) will have
+                                the O_NONBLOCK flag set and hence are in
+                                non-blocking mode. This option is only
+                                useful in conjunction with a socket
+                                unit, as described in
+                                <citerefentry><refentrytitle>systemd.socket</refentrytitle><manvolnum>5</manvolnum></citerefentry>. Defaults
+                                to false.</para></listitem>
+                        </varlistentry>
+
+                        <varlistentry>
+                                <term><varname>NotifyAccess=</varname></term>
+                                <listitem><para>Controls access to the
+                                service status notification socket, as
+                                accessible via the
+                                <citerefentry><refentrytitle>sd_notify</refentrytitle><manvolnum>3</manvolnum></citerefentry>
+                                call. Takes one of
+                                <option>none</option> (the default),
+                                <option>main</option> or
+                                <option>all</option>. If
+                                <option>none</option> no daemon status
+                                updates are accepted from the service
+                                processes, all status update messages
+                                are ignored. If <option>main</option>
+                                only service updates sent from the
+                                main process of the service are
+                                accepted. If <option>all</option> all
+                                services updates from all members of
+                                the service's control group are
+                                accepted. This option should be set to
+                                open access to the notification socket
+                                when using
+                                <varname>Type=notify</varname> or
+                                <varname>WatchdogUsec=</varname> (see
+                                above). If those options are used but
+                                <varname>NotifyAccess=</varname> not
+                                configured it will be implicitly set
+                                to
+                                <option>main</option>.</para></listitem>
+                        </varlistentry>
+
+                        <varlistentry>
+                                <term><varname>Sockets=</varname></term>
+                                <listitem><para>Specifies the name of
+                                the socket units this service shall
+                                inherit the sockets from when the
+                                service is started. Normally it
+                                should not be necessary to use this
+                                setting as all sockets whose unit
+                                shares the same name as the service
+                                (ignoring the different suffix of course)
+                                are passed to the spawned
+                                process.</para>
+
+                                <para>Note that the same socket may be
+                                passed to multiple processes at the
+                                same time. Also note that a different
+                                service may be activated on incoming
+                                traffic than inherits the sockets. Or
+                                in other words: The
+                                <varname>Service=</varname> setting of
+                                <filename>.socket</filename> units
+                                doesn't have to match the inverse of the
+                                <varname>Sockets=</varname> setting of
+                                the <filename>.service</filename> it
+                                refers to.</para></listitem>
+                        </varlistentry>
+
+                        <varlistentry>
+                                <term><varname>FsckPassNo=</varname></term>
+                                <listitem><para>Set the fsck passno
+                                priority to use to order this service
+                                in relation to other file system
+                                checking services. This option is only
+                                necessary to fix ordering in relation
+                                to fsck jobs automatically created for
+                                all <filename>/etc/fstab</filename>
+                                entries with a value in the fs_passno
+                                column > 0. As such it should only be
+                                used as option for fsck
+                                services. Almost always it is a better
+                                choice to add explicit ordering
+                                directives via
+                                <varname>After=</varname> or
+                                <varname>Before=</varname>,
+                                instead. For more details see
+                                <citerefentry><refentrytitle>systemd.unit</refentrytitle><manvolnum>5</manvolnum></citerefentry>. If
+                                used, pass an integer value in the
+                                same range as
+                                <filename>/etc/fstab</filename>'s
+                                fs_passno column. See
+                                <citerefentry><refentrytitle>fstab</refentrytitle><manvolnum>5</manvolnum></citerefentry>
+                                for details.</para></listitem>
+                        </varlistentry>
+
+                        <varlistentry>
+                                <term><varname>StartLimitInterval=</varname></term>
+                                <term><varname>StartLimitBurst=</varname></term>
+
+                                <listitem><para>Configure service
+                                start rate limiting. By default
+                                services which are started more often
+                                than 5 times within 10s are not
+                                permitted to start any more times
+                                until the 10s interval ends. With
+                                these two options this rate limiting
+                                may be modified. Use
+                                <varname>StartLimitInterval=</varname>
+                                to configure the checking interval
+                                (defaults to 10s, set to 0 to disable
+                                any kind of rate limiting). Use
+                                <varname>StartLimitBurst=</varname> to
+                                configure how many starts per interval
+                                are allowed (defaults to 5). These
+                                configuration options are particularly
+                                useful in conjunction with
+                                <varname>Restart=</varname>.</para></listitem>
+                        </varlistentry>
+
+                        <varlistentry>
+                                <term><varname>StartLimitAction=</varname></term>
+
+                                <listitem><para>Configure the action
+                                to take if the rate limit configured
+                                with
+                                <varname>StartLimitInterval=</varname>
+                                and
+                                <varname>StartLimitBurst=</varname> is
+                                hit. Takes one of
+                                <option>none</option>,
+                                <option>reboot</option>,
+                                <option>reboot-force</option> or
+                                <option>reboot-immediate</option>. If
+                                <option>none</option> is set,
+                                hitting the rate limit will trigger no
+                                action besides that the start will not
+                                be
+                                permitted. <option>reboot</option>
+                                causes a reboot following the normal
+                                shutdown procedure (i.e. equivalent to
+                                <command>systemctl reboot</command>),
+                                <option>reboot-force</option> causes
+                                an forced reboot which will terminate
+                                all processes forcibly but should
+                                cause no dirty file systems on reboot
+                                (i.e. equivalent to <command>systemctl
+                                reboot -f</command>) and
+                                <option>reboot-immediate</option>
+                                causes immediate execution of the
+                                <citerefentry><refentrytitle>reboot</refentrytitle><manvolnum>2</manvolnum></citerefentry>
+                                system call, which might result in
+                                data loss.  Defaults to
+                                <option>none</option>.</para></listitem>
+                        </varlistentry>
+
+                </variablelist>
+        </refsect1>
+
+        <refsect1>
+                  <title>See Also</title>
+                  <para>
+                          <citerefentry><refentrytitle>systemd</refentrytitle><manvolnum>1</manvolnum></citerefentry>,
+                          <citerefentry><refentrytitle>systemctl</refentrytitle><manvolnum>8</manvolnum></citerefentry>,
+                          <citerefentry><refentrytitle>systemd.unit</refentrytitle><manvolnum>5</manvolnum></citerefentry>,
+                          <citerefentry><refentrytitle>systemd.exec</refentrytitle><manvolnum>5</manvolnum></citerefentry>
+                  </para>
+        </refsect1>
+
+</refentry>
diff --git a/man/systemd.snapshot.xml b/man/systemd.snapshot.xml
new file mode 100644 (file)
index 0000000..a3e2322
--- /dev/null
@@ -0,0 +1,87 @@
+<?xml version='1.0'?> <!--*-nxml-*-->
+<?xml-stylesheet type="text/xsl" href="http://docbook.sourceforge.net/release/xsl/current/xhtml/docbook.xsl"?>
+<!DOCTYPE refentry PUBLIC "-//OASIS//DTD DocBook XML V4.2//EN"
+        "http://www.oasis-open.org/docbook/xml/4.2/docbookx.dtd">
+
+<!--
+  This file is part 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 <http://www.gnu.org/licenses/>.
+-->
+
+<refentry id="systemd.snapshot">
+        <refentryinfo>
+                <title>systemd.snapshot</title>
+                <productname>systemd</productname>
+
+                <authorgroup>
+                        <author>
+                                <contrib>Developer</contrib>
+                                <firstname>Lennart</firstname>
+                                <surname>Poettering</surname>
+                                <email>lennart@poettering.net</email>
+                        </author>
+                </authorgroup>
+        </refentryinfo>
+
+        <refmeta>
+                <refentrytitle>systemd.snapshot</refentrytitle>
+                <manvolnum>5</manvolnum>
+        </refmeta>
+
+        <refnamediv>
+                <refname>systemd.snapshot</refname>
+                <refpurpose>systemd snapshot units</refpurpose>
+        </refnamediv>
+
+        <refsynopsisdiv>
+                <para><filename>systemd.snapshot</filename></para>
+        </refsynopsisdiv>
+
+        <refsect1>
+                <title>Description</title>
+
+                <para>Snapshot units are not configured via unit
+                configuration files. Nonetheless they are named
+                similar to filenames. A unit name whose name ends in
+                <filename>.snapshot</filename> refers to a dynamic
+                snapshot of the systemd runtime state.</para>
+
+                <para>Snapshots are not configured on disk but created
+                dynamically via <command>systemctl snapshot</command>
+                (see
+                <citerefentry><refentrytitle>systemctl</refentrytitle><manvolnum>8</manvolnum></citerefentry>
+                for details) or an equivalent command. When created,
+                they will automatically get dependencies on the
+                currently activated units. They act as saved
+                runtime state of the systemd manager. Later on, the
+                user may choose to return to the saved state via
+                <command>systemctl isolate</command>. They are
+                useful to roll back to a defined state after
+                temporarily starting/stopping services or
+                similar.</para>
+        </refsect1>
+
+        <refsect1>
+                  <title>See Also</title>
+                  <para>
+                          <citerefentry><refentrytitle>systemd</refentrytitle><manvolnum>1</manvolnum></citerefentry>,
+                          <citerefentry><refentrytitle>systemctl</refentrytitle><manvolnum>8</manvolnum></citerefentry>,
+                          <citerefentry><refentrytitle>systemd.unit</refentrytitle><manvolnum>5</manvolnum></citerefentry>
+                  </para>
+        </refsect1>
+
+</refentry>
diff --git a/man/systemd.socket.xml b/man/systemd.socket.xml
new file mode 100644 (file)
index 0000000..d9921e4
--- /dev/null
@@ -0,0 +1,674 @@
+<?xml version='1.0'?> <!--*-nxml-*-->
+<?xml-stylesheet type="text/xsl" href="http://docbook.sourceforge.net/release/xsl/current/xhtml/docbook.xsl"?>
+<!DOCTYPE refentry PUBLIC "-//OASIS//DTD DocBook XML V4.2//EN"
+        "http://www.oasis-open.org/docbook/xml/4.2/docbookx.dtd">
+
+<!--
+  This file is part 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 <http://www.gnu.org/licenses/>.
+-->
+
+<refentry id="systemd.socket">
+        <refentryinfo>
+                <title>systemd.socket</title>
+                <productname>systemd</productname>
+
+                <authorgroup>
+                        <author>
+                                <contrib>Developer</contrib>
+                                <firstname>Lennart</firstname>
+                                <surname>Poettering</surname>
+                                <email>lennart@poettering.net</email>
+                        </author>
+                </authorgroup>
+        </refentryinfo>
+
+        <refmeta>
+                <refentrytitle>systemd.socket</refentrytitle>
+                <manvolnum>5</manvolnum>
+        </refmeta>
+
+        <refnamediv>
+                <refname>systemd.socket</refname>
+                <refpurpose>systemd socket configuration files</refpurpose>
+        </refnamediv>
+
+        <refsynopsisdiv>
+                <para><filename>systemd.socket</filename></para>
+        </refsynopsisdiv>
+
+        <refsect1>
+                <title>Description</title>
+
+                <para>A unit configuration file whose name ends in
+                <filename>.socket</filename> encodes information about
+                an IPC or network socket or a file system FIFO
+                controlled and supervised by systemd, for socket-based
+                activation.</para>
+
+                <para>This man page lists the configuration options
+                specific to this unit type. See
+                <citerefentry><refentrytitle>systemd.unit</refentrytitle><manvolnum>5</manvolnum></citerefentry>
+                for the common options of all unit configuration
+                files. The common configuration items are configured
+                in the generic [Unit] and [Install] sections. The
+                socket specific configuration options are configured
+                in the [Socket] section.</para>
+
+                <para>Additional options are listed in
+                <citerefentry><refentrytitle>systemd.exec</refentrytitle><manvolnum>5</manvolnum></citerefentry>,
+                which define the execution environment the
+                <option>ExecStartPre=</option>,
+                <option>ExecStartPost=</option>,
+                <option>ExecStopPre=</option> and
+                <option>ExecStoptPost=</option> commands are executed
+                in.</para>
+
+                <para>For each socket file a matching service file
+                (see
+                <citerefentry><refentrytitle>systemd.service</refentrytitle><manvolnum>5</manvolnum></citerefentry>
+                for details) must exist, describing the service to
+                start on incoming traffic on the socket. Depending on
+                the setting of <option>Accept=</option> (see below),
+                this must either be named like the socket unit, but
+                with the suffix replaced; or it must be a template
+                file named the same way. Example: a socket file
+                <filename>foo.socket</filename> needs a matching
+                service <filename>foo.service</filename> if
+                <option>Accept=false</option> is set. If
+                <option>Accept=true</option> is set a service template
+                file <filename>foo@.service</filename> must exist from
+                which services are instantiated for each incoming
+                connection.</para>
+
+                <para>Unless <varname>DefaultDependencies=</varname>
+                is set to <option>false</option>, socket units will
+                implicitly have dependencies of type
+                <varname>Requires=</varname> and
+                <varname>After=</varname> on
+                <filename>sysinit.target</filename> as well as
+                dependencies of type <varname>Conflicts=</varname> and
+                <varname>Before=</varname> on
+                <filename>shutdown.target</filename>. These ensure
+                that socket units pull in basic system
+                initialization, and are terminated cleanly prior to
+                system shutdown. Only sockets involved with early
+                boot or late system shutdown should disable this
+                option.</para>
+
+                <para>Socket units may be used to implement on-demand
+                starting of services, as well as parallelized starting
+                of services.</para>
+
+                <para>Note that the daemon software configured for
+                socket activation with socket units needs to be able
+                to accept sockets from systemd, either via systemd's
+                native socket passing interface (see
+                <citerefentry><refentrytitle>sd_listen_fds</refentrytitle><manvolnum>3</manvolnum></citerefentry>
+                for details) or via the traditional
+                <citerefentry><refentrytitle>inetd</refentrytitle><manvolnum>8</manvolnum></citerefentry>-style
+                socket passing (i.e. sockets passed in via STDIN and
+                STDOUT, using <varname>StandardInput=socket</varname>
+                in the service file).</para>
+        </refsect1>
+
+        <refsect1>
+                <title>Options</title>
+
+                <para>Socket files must include a [Socket] section,
+                which carries information about the socket or FIFO it
+                supervises. A number of options that may be used in
+                this section are shared with other unit types. These
+                options are documented in
+                <citerefentry><refentrytitle>systemd.exec</refentrytitle><manvolnum>5</manvolnum></citerefentry>. The
+                options specific to the [Socket] section of socket
+                units are the following:</para>
+
+                <variablelist>
+                        <varlistentry>
+                                <term><varname>ListenStream=</varname></term>
+                                <term><varname>ListenDatagram=</varname></term>
+                                <term><varname>ListenSequentialPacket=</varname></term>
+                                <listitem><para>Specifies an address
+                                to listen on for a stream
+                                (SOCK_STREAM), datagram (SOCK_DGRAM)
+                                resp. sequential packet
+                                (SOCK_SEQPACKET) socket. The address
+                                can be written in various formats:</para>
+
+                                <para>If the address starts with a
+                                slash (/), it is read as file system
+                                socket in the AF_UNIX socket
+                                family.</para>
+
+                                <para>If the address starts with an
+                                at symbol (@) it is read as abstract
+                                namespace socket in the AF_UNIX
+                                family. The @ is replaced with a NUL
+                                character before binding. For details
+                                see
+                                <citerefentry><refentrytitle>unix</refentrytitle><manvolnum>7</manvolnum></citerefentry>.</para>
+
+                                <para>If the address string is a
+                                single number it is read as port
+                                number to listen on for both IPv4 and
+                                IPv6.</para>
+
+                                <para>If the address string is a
+                                string in the format v.w.x.y:z it is
+                                read as IPv4 specifier for listening
+                                on an address v.w.x.y on a port
+                                z.</para>
+
+                                <para>If the address string is a
+                                string in the format [x]:y it is read
+                                as IPv6 address x on a port y.</para>
+
+                                <para>Note that SOCK_SEQPACKET
+                                (i.e. <varname>ListenSequentialPacket=</varname>)
+                                is only available for AF_UNIX
+                                sockets. SOCK_STREAM
+                                (i.e. <varname>ListenStream=</varname>)
+                                when used for IP sockets refers to TCP
+                                sockets, SOCK_DGRAM
+                                (i.e. <varname>ListenDatagram=</varname>)
+                                to UDP.</para>
+
+                                <para>These options may be specified
+                                more than once in which case incoming
+                                traffic on any of the sockets will trigger
+                                service activation, and all listed
+                                sockets will be passed to the service,
+                                regardless whether there is incoming
+                                traffic on them or not.</para>
+
+                                <para>If an IP address is used here, it
+                                is often desirable to listen on it
+                                before the interface it is configured
+                                on is up and running, and even
+                                regardless whether it will be up and
+                                running ever at all. To deal with this it is
+                                recommended to set the
+                                <varname>FreeBind=</varname> option
+                                described below.</para></listitem>
+                        </varlistentry>
+
+                        <varlistentry>
+                                <term><varname>ListenFIFO=</varname></term>
+                                <listitem><para>Specifies a file
+                                system FIFO to listen on. This expects
+                                an absolute file system path as
+                                argument. Behaviour otherwise is very
+                                similar to the
+                                <varname>ListenDatagram=</varname>
+                                directive above.</para></listitem>
+                        </varlistentry>
+
+                        <varlistentry>
+                                <term><varname>ListenSpecial=</varname></term>
+                                <listitem><para>Specifies a special
+                                file in the file system to listen
+                                on. This expects an absolute file
+                                system path as argument. Behaviour
+                                otherwise is very similar to the
+                                <varname>ListenFIFO=</varname>
+                                directive above. Use this to open
+                                character device nodes as well as
+                                special files in
+                                <filename>/proc</filename> and
+                                <filename>/sys</filename>.</para></listitem>
+                        </varlistentry>
+
+                        <varlistentry>
+                                <term><varname>ListenNetlink=</varname></term>
+                                <listitem><para>Specifies a Netlink
+                                family to create a socket for to
+                                listen on. This expects a short string
+                                referring to the AF_NETLINK family
+                                name (such as <varname>audit</varname>
+                                or <varname>kobject-uevent</varname>)
+                                as argument, optionally suffixed by a
+                                whitespace followed by a multicast
+                                group integer. Behaviour otherwise is
+                                very similar to the
+                                <varname>ListenDatagram=</varname>
+                                directive above.</para></listitem>
+                        </varlistentry>
+
+                        <varlistentry>
+                                <term><varname>ListenMessageQueue=</varname></term>
+                                <listitem><para>Specifies a POSIX
+                                message queue name to listen on. This
+                                expects a valid message queue name
+                                (i.e. beginning with /). Behaviour
+                                otherwise is very similar to the
+                                <varname>ListenFIFO=</varname>
+                                directive above. On Linux message
+                                queue descriptors are actually file
+                                descriptors and can be inherited
+                                between processes.</para></listitem>
+                        </varlistentry>
+
+                        <varlistentry>
+                                <term><varname>BindIPv6Only=</varname></term>
+                                <listitem><para>Takes a one of
+                                <option>default</option>,
+                                <option>both</option> or
+                                <option>ipv6-only</option>. Controls
+                                the IPV6_V6ONLY socket option (see
+                                <citerefentry><refentrytitle>ipv6</refentrytitle><manvolnum>7</manvolnum></citerefentry>
+                                for details). If
+                                <option>both</option>, IPv6 sockets
+                                bound will be accessible via both IPv4
+                                and IPv6. If
+                                <option>ipv6-only</option>, they will
+                                be accessible via IPv6 only. If
+                                <option>default</option> (which is the
+                                default, surprise!) the system wide
+                                default setting is used, as controlled
+                                by
+                                <filename>/proc/sys/net/ipv6/bindv6only</filename>.</para>
+                                </listitem>
+                        </varlistentry>
+
+                        <varlistentry>
+                                <term><varname>Backlog=</varname></term>
+                                <listitem><para>Takes an unsigned
+                                integer argument. Specifies the number
+                                of connections to queue that have not
+                                been accepted yet. This setting
+                                matters only for stream and sequential
+                                packet sockets. See
+                                <citerefentry><refentrytitle>listen</refentrytitle><manvolnum>2</manvolnum></citerefentry>
+                                for details. Defaults to SOMAXCONN
+                                (128).</para></listitem>
+                        </varlistentry>
+
+                        <varlistentry>
+                                <term><varname>BindToDevice=</varname></term>
+                                <listitem><para>Specifies a network
+                                interface name to bind this socket
+                                to. If set traffic will only be
+                                accepted from the specified network
+                                interfaces. This controls the
+                                SO_BINDTODEVICE socket option (see
+                                <citerefentry><refentrytitle>socket</refentrytitle><manvolnum>7</manvolnum></citerefentry>
+                                for details). If this option is used,
+                                an automatic dependency from this
+                                socket unit on the network interface
+                                device unit
+                                (<citerefentry><refentrytitle>systemd.device</refentrytitle><manvolnum>5</manvolnum></citerefentry>
+                                is created.</para></listitem>
+                        </varlistentry>
+
+                        <varlistentry>
+                                <term><varname>DirectoryMode=</varname></term>
+                                <listitem><para>If listening on a file
+                                system socket of FIFO, the parent
+                                directories are automatically created
+                                if needed. This option specifies the
+                                file system access mode used when
+                                creating these directories. Takes an
+                                access mode in octal
+                                notation. Defaults to
+                                0755.</para></listitem>
+                        </varlistentry>
+
+                        <varlistentry>
+                                <term><varname>SocketMode=</varname></term>
+                                <listitem><para>If listening on a file
+                                system socket of FIFO, this option
+                                specifies the file system access mode
+                                used when creating the file
+                                node. Takes an access mode in octal
+                                notation. Defaults to
+                                0666.</para></listitem>
+                        </varlistentry>
+
+                        <varlistentry>
+                                <term><varname>Accept=</varname></term>
+                                <listitem><para>Takes a boolean
+                                argument. If true, a service instance
+                                is spawned for each incoming
+                                connection and only the connection
+                                socket is passed to it. If false, all
+                                listening sockets themselves are
+                                passed to the started service unit,
+                                and only one service unit is spawned
+                                for all connections (also see
+                                above). This value is ignored for
+                                datagram sockets and FIFOs where
+                                a single service unit unconditionally
+                                handles all incoming traffic. Defaults
+                                to <option>false</option>. For
+                                performance reasons, it is recommended
+                                to write new daemons only in a way
+                                that is suitable for
+                                <option>Accept=false</option>. This
+                                option is mostly useful to allow
+                                daemons designed for usage with
+                                <citerefentry><refentrytitle>inetd</refentrytitle><manvolnum>8</manvolnum></citerefentry>,
+                                to work unmodified with systemd socket
+                                activation.</para></listitem>
+                        </varlistentry>
+
+                        <varlistentry>
+                                <term><varname>MaxConnections=</varname></term>
+                                <listitem><para>The maximum number of
+                                connections to simultaneously run
+                                services instances for, when
+                                <option>Accept=true</option> is
+                                set. If more concurrent connections
+                                are coming in, they will be refused
+                                until at least one existing connection
+                                is terminated. This setting has no
+                                effect for sockets configured with
+                                <option>Accept=no</option> or datagram
+                                sockets. Defaults to
+                                64.</para></listitem>
+                        </varlistentry>
+
+                        <varlistentry>
+                                <term><varname>KeepAlive=</varname></term>
+                                <listitem><para>Takes a boolean
+                                argument. If true, the TCP/IP stack
+                                will send a keep alive message after
+                                2h (depending on the configuration of
+                                <filename>/proc/sys/net/ipv4/tcp_keepalive_time</filename>)
+                                for all TCP streams accepted on this
+                                socket. This controls the SO_KEEPALIVE
+                                socket option (see
+                                <citerefentry><refentrytitle>socket</refentrytitle><manvolnum>7</manvolnum></citerefentry>
+                                and the <ulink
+                                url="http://www.tldp.org/HOWTO/html_single/TCP-Keepalive-HOWTO/">TCP
+                                Keepalive HOWTO</ulink> for details.)
+                                Defaults to
+                                <option>false</option>.</para></listitem>
+                        </varlistentry>
+
+                        <varlistentry>
+                                <term><varname>Priority=</varname></term>
+                                <listitem><para>Takes an integer
+                                argument controlling the priority for
+                                all traffic sent from this
+                                socket. This controls the SO_PRIORITY
+                                socket option (see
+                                <citerefentry><refentrytitle>socket</refentrytitle><manvolnum>7</manvolnum></citerefentry>
+                                for details.).</para></listitem>
+                        </varlistentry>
+
+                        <varlistentry>
+                                <term><varname>ReceiveBuffer=</varname></term>
+                                <term><varname>SendBuffer=</varname></term>
+                                <listitem><para>Takes an integer
+                                argument controlling the receive
+                                resp. send buffer sizes of this
+                                socket. This controls the SO_RCVBUF
+                                resp. SO_SNDBUF socket options (see
+                                <citerefentry><refentrytitle>socket</refentrytitle><manvolnum>7</manvolnum></citerefentry>
+                                for details.).</para></listitem>
+                        </varlistentry>
+
+                        <varlistentry>
+                                <term><varname>IPTOS=</varname></term>
+                                <listitem><para>Takes an integer
+                                argument controlling the IP
+                                Type-Of-Service field for packets
+                                generated from this socket. This
+                                controls the IP_TOS socket option (see
+                                <citerefentry><refentrytitle>ip</refentrytitle><manvolnum>7</manvolnum></citerefentry>
+                                for details.). Either a numeric string
+                                or one of <option>low-delay</option>,
+                                <option>throughput</option>,
+                                <option>reliability</option> or
+                                <option>low-cost</option> may be
+                                specified.</para></listitem>
+                        </varlistentry>
+
+                        <varlistentry>
+                                <term><varname>IPTTL=</varname></term>
+                                <listitem><para>Takes an integer
+                                argument controlling the IPv4
+                                Time-To-Live/IPv6 Hop-Count field for
+                                packets generated from this
+                                socket. This sets the
+                                IP_TTL/IPV6_UNICAST_HOPS socket
+                                options (see
+                                <citerefentry><refentrytitle>ip</refentrytitle><manvolnum>7</manvolnum></citerefentry>
+                                and
+                                <citerefentry><refentrytitle>ipv6</refentrytitle><manvolnum>7</manvolnum></citerefentry>
+                                for details.)</para></listitem>
+                        </varlistentry>
+
+                        <varlistentry>
+                                <term><varname>Mark=</varname></term>
+                                <listitem><para>Takes an integer
+                                value. Controls the firewall mark of
+                                packets generated by this socket. This
+                                can be used in the firewall logic to
+                                filter packets from this socket. This
+                                sets the SO_MARK socket option. See
+                                <citerefentry><refentrytitle>iptables</refentrytitle><manvolnum>8</manvolnum></citerefentry>
+                                for details.</para></listitem>
+                        </varlistentry>
+
+                        <varlistentry>
+                                <term><varname>PipeSize=</varname></term>
+                                <listitem><para>Takes an integer
+                                value. Controls the pipe buffer size
+                                of FIFOs configured in this socket
+                                unit.  See
+                                <citerefentry><refentrytitle>fcntl</refentrytitle><manvolnum>2</manvolnum></citerefentry>
+                                for details.</para></listitem>
+                        </varlistentry>
+
+                        <varlistentry>
+                                <term><varname>MessageQueueMaxMessages=</varname>,
+                                <varname>MessageQueueMessageSize=</varname></term>
+                                <listitem><para>These two settings
+                                take integer values and control the
+                                mq_maxmsg resp. mq_msgsize field when
+                                creating the message queue. Note that
+                                either none or both of these variables
+                                need to be set. See
+                                <citerefentry><refentrytitle>mq_setattr</refentrytitle><manvolnum>3</manvolnum></citerefentry>
+                                for details.</para></listitem>
+                        </varlistentry>
+
+                        <varlistentry>
+                                <term><varname>FreeBind=</varname></term>
+                                <listitem><para>Takes a boolean
+                                value. Controls whether the socket can
+                                be bound to non-local IP
+                                addresses. This is useful to configure
+                                sockets listening on specific IP
+                                addresses before those IP addresses
+                                are successfully configured on a
+                                network interface. This sets the
+                                IP_FREEBIND socket option. For
+                                robustness reasons it is recommended
+                                to use this option whenever you bind a
+                                socket to a specific IP
+                                address. Defaults to <option>false</option>.</para></listitem>
+                        </varlistentry>
+
+                        <varlistentry>
+                                <term><varname>Transparent=</varname></term>
+                                <listitem><para>Takes a boolean
+                                value. Controls the IP_TRANSPARENT
+                                socket option. Defaults to
+                                <option>false</option>.</para></listitem>
+                        </varlistentry>
+
+                        <varlistentry>
+                                <term><varname>Broadcast=</varname></term>
+                                <listitem><para>Takes a boolean
+                                value. This controls the SO_BROADCAST
+                                socket option, which allows broadcast
+                                datagrams to be sent from this
+                                socket. Defaults to
+                                <option>false</option>.</para></listitem>
+                        </varlistentry>
+
+                        <varlistentry>
+                                <term><varname>PassCredentials=</varname></term>
+                                <listitem><para>Takes a boolean
+                                value. This controls the SO_PASSCRED
+                                socket option, which allows AF_UNIX sockets to
+                                receive the credentials of the sending
+                                process in an ancillary message.
+                                Defaults to
+                                <option>false</option>.</para></listitem>
+                        </varlistentry>
+
+                        <varlistentry>
+                                <term><varname>PassSecurity=</varname></term>
+                                <listitem><para>Takes a boolean
+                                value. This controls the SO_PASSSEC
+                                socket option, which allows AF_UNIX
+                                sockets to receive the security
+                                context of the sending process in an
+                                ancillary message.  Defaults to
+                                <option>false</option>.</para></listitem>
+                        </varlistentry>
+
+                        <varlistentry>
+                                <term><varname>TCPCongestion=</varname></term>
+                                <listitem><para>Takes a string
+                                value. Controls the TCP congestion
+                                algorithm used by this socket. Should
+                                be one of "westwood", "veno", "cubic",
+                                "lp" or any other available algorithm
+                                supported by the IP stack. This
+                                setting applies only to stream
+                                sockets.</para></listitem>
+                        </varlistentry>
+
+                        <varlistentry>
+                                <term><varname>ExecStartPre=</varname></term>
+                                <term><varname>ExecStartPost=</varname></term>
+                                <listitem><para>Takes one or more
+                                command lines, which are executed
+                                before (resp. after) the listening
+                                sockets/FIFOs are created and
+                                bound. The first token of the command
+                                line must be an absolute file name,
+                                then followed by arguments for the
+                                process. Multiple command lines may be
+                                specified following the same scheme as
+                                used for
+                                <varname>ExecStartPre=</varname> of
+                                service unit files.</para></listitem>
+                        </varlistentry>
+
+                        <varlistentry>
+                                <term><varname>ExecStopPre=</varname></term>
+                                <term><varname>ExecStopPost=</varname></term>
+                                <listitem><para>Additional commands
+                                that are executed before (resp. after)
+                                the listening sockets/FIFOs are closed
+                                and removed. Multiple command lines
+                                may be specified following the same
+                                scheme as used for
+                                <varname>ExecStartPre=</varname> of
+                                service unit files.</para></listitem>
+                        </varlistentry>
+
+                        <varlistentry>
+                                <term><varname>TimeoutSec=</varname></term>
+                                <listitem><para>Configures the time to
+                                wait for the commands specified in
+                                <varname>ExecStartPre=</varname>,
+                                <varname>ExecStartPost=</varname>,
+                                <varname>ExecStopPre=</varname> and
+                                <varname>ExecStopPost=</varname> to
+                                finish. If a command does not exit
+                                within the configured time, the socket
+                                will be considered failed and be shut
+                                down again. All commands still running,
+                                will be terminated forcibly via
+                                SIGTERM, and after another delay of
+                                this time with SIGKILL. (See
+                                <option>KillMode=</option> below.)
+                                Takes a unit-less value in seconds, or
+                                a time span value such as "5min
+                                20s". Pass 0 to disable the timeout
+                                logic. Defaults to
+                                90s.</para></listitem>
+                        </varlistentry>
+
+                        <varlistentry>
+                                <term><varname>KillMode=</varname></term>
+                                <listitem><para>Specifies how
+                                processes of this socket unit shall be
+                                killed. One of
+                                <option>control-group</option>,
+                                <option>process</option>,
+                                <option>none</option>.</para>
+
+                                <para>This option is mostly equivalent
+                                to the <option>KillMode=</option>
+                                option of service files. See
+                                <citerefentry><refentrytitle>systemd.service</refentrytitle><manvolnum>5</manvolnum></citerefentry>
+                                for details.</para></listitem>
+                        </varlistentry>
+
+                        <varlistentry>
+                                <term><varname>KillSignal=</varname></term>
+                                <listitem><para>Specifies which signal
+                                to use when killing a process of this
+                                socket. Defaults to SIGTERM.
+                                </para></listitem>
+                        </varlistentry>
+
+                        <varlistentry>
+                                <term><varname>SendSIGKILL=</varname></term>
+                                <listitem><para>Specifies whether to
+                                send SIGKILL to remaining processes
+                                after a timeout, if the normal
+                                shutdown procedure left processes of
+                                the socket around. Takes a boolean
+                                value. Defaults to "yes".
+                                </para></listitem>
+                        </varlistentry>
+
+                        <varlistentry>
+                                <term><varname>Service=</varname></term>
+                                <listitem><para>Specifies the service
+                                unit name to activate on incoming
+                                traffic. This defaults to the service
+                                that bears the same name as the socket
+                                (ignoring the different suffixes). In
+                                most cases it should not be necessary
+                                to use this option.</para></listitem>
+                        </varlistentry>
+
+                </variablelist>
+        </refsect1>
+
+        <refsect1>
+                  <title>See Also</title>
+                  <para>
+                          <citerefentry><refentrytitle>systemd</refentrytitle><manvolnum>1</manvolnum></citerefentry>,
+                          <citerefentry><refentrytitle>systemctl</refentrytitle><manvolnum>8</manvolnum></citerefentry>,
+                          <citerefentry><refentrytitle>systemd.unit</refentrytitle><manvolnum>5</manvolnum></citerefentry>,
+                          <citerefentry><refentrytitle>systemd.exec</refentrytitle><manvolnum>5</manvolnum></citerefentry>,
+                          <citerefentry><refentrytitle>systemd.service</refentrytitle><manvolnum>5</manvolnum></citerefentry>
+                  </para>
+        </refsect1>
+
+</refentry>
diff --git a/man/systemd.special.xml b/man/systemd.special.xml
new file mode 100644 (file)
index 0000000..116a43c
--- /dev/null
@@ -0,0 +1,725 @@
+<?xml version='1.0'?> <!--*-nxml-*-->
+<!DOCTYPE refentry PUBLIC "-//OASIS//DTD DocBook XML V4.2//EN"
+        "http://www.oasis-open.org/docbook/xml/4.2/docbookx.dtd">
+
+<!--
+  This file is part 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 <http://www.gnu.org/licenses/>.
+-->
+
+<refentry id="systemd.special">
+
+        <refentryinfo>
+                <title>systemd.special</title>
+                <productname>systemd</productname>
+
+                <authorgroup>
+                        <author>
+                                <contrib>Developer</contrib>
+                                <firstname>Lennart</firstname>
+                                <surname>Poettering</surname>
+                                <email>lennart@poettering.net</email>
+                        </author>
+                </authorgroup>
+        </refentryinfo>
+
+        <refmeta>
+                <refentrytitle>systemd.special</refentrytitle>
+                <manvolnum>7</manvolnum>
+        </refmeta>
+
+        <refnamediv>
+                <refname>systemd.special</refname>
+                <refpurpose>special systemd units</refpurpose>
+        </refnamediv>
+
+        <refsynopsisdiv>
+                <para><filename>basic.target</filename>,
+                <filename>ctrl-alt-del.target</filename>,
+                <filename>dbus.service</filename>,
+                <filename>default.target</filename>,
+                <filename>display-manager.service</filename>,
+                <filename>emergency.target</filename>,
+                <filename>exit.service</filename>,
+                <filename>graphical.target</filename>,
+                <filename>halt.target</filename>,
+                <filename>kbrequest.target</filename>,
+                <filename>local-fs.target</filename>,
+                <filename>local-fs-pre.target</filename>,
+                <filename>mail-transfer-agent.target</filename>,
+                <filename>multi-user.target</filename>,
+                <filename>network.target</filename>,
+                <filename>nss-lookup.target</filename>,
+                <filename>poweroff.target</filename>,
+                <filename>reboot.target</filename>,
+                <filename>remote-fs.target</filename>,
+                <filename>remote-fs-pre.target</filename>,
+                <filename>rescue.target</filename>,
+                <filename>rpcbind.target</filename>,
+                <filename>runlevel2.target</filename>,
+                <filename>runlevel3.target</filename>,
+                <filename>runlevel4.target</filename>,
+                <filename>runlevel5.target</filename>,
+                <filename>shutdown.target</filename>,
+                <filename>sigpwr.target</filename>,
+                <filename>sockets.target</filename>,
+                <filename>swap.target</filename>,
+                <filename>sysinit.target</filename>,
+                <filename>syslog.target</filename>,
+                <filename>systemd-initctl.service</filename>,
+                <filename>systemd-initctl.socket</filename>,
+                <filename>systemd-stdout-syslog-bridge.service</filename>,
+                <filename>systemd-stdout-syslog-bridge.socket</filename>,
+                <filename>time-sync.target</filename>,
+                <filename>umount.target</filename></para>
+        </refsynopsisdiv>
+
+        <refsect1>
+                <title>Description</title>
+
+                <para>A few units are treated specially by
+                systemd. They have special internal semantics and
+                cannot be renamed.</para>
+        </refsect1>
+
+        <refsect1>
+                <title>Special System Units</title>
+
+                <variablelist>
+                        <varlistentry>
+                                <term><filename>basic.target</filename></term>
+                                <listitem>
+                                        <para>A special target unit
+                                        covering early boot-up.</para>
+                                        <para>systemd automatically
+                                        adds dependencies of the types
+                                        Requires and After for this
+                                        target unit to all SysV
+                                        service units configured for
+                                        runlevel 1 to 5.</para>
+                                        <para>Usually this should pull-in
+                                        all sockets, mount points,
+                                        swap devices and other basic
+                                        initialization necessary for
+                                        the general purpose
+                                        daemons. Most normal daemons
+                                        should have dependencies of
+                                        type After and Requires on
+                                        this unit.</para>
+                                </listitem>
+                        </varlistentry>
+                        <varlistentry>
+                                <term><filename>ctrl-alt-del.target</filename></term>
+                                <listitem>
+                                        <para>systemd starts this
+                                        target whenever
+                                        Control+Alt+Del is pressed on
+                                        the console. Usually this
+                                        should be aliased (symlinked)
+                                        to
+                                        <filename>reboot.target</filename>.</para>
+                                </listitem>
+                        </varlistentry>
+                        <varlistentry>
+                                <term><filename>dbus.service</filename></term>
+                                <listitem>
+                                        <para>A special unit for the
+                                        D-Bus system bus. As soon as
+                                        this service is fully started
+                                        up systemd will connect to it
+                                        and register its
+                                        service.</para>
+                                </listitem>
+                        </varlistentry>
+                        <varlistentry>
+                                <term><filename>default.target</filename></term>
+                                <listitem>
+                                        <para>The default unit systemd
+                                        starts at bootup. Usually this
+                                        should be aliased (symlinked)
+                                        to
+                                        <filename>multi-user.target</filename>
+                                        or
+                                        <filename>graphical.target</filename>.</para>
+                                        <para>The default unit systemd
+                                        starts at bootup can be
+                                        overridden with the
+                                        <varname>systemd.unit=</varname>
+                                        kernel command line option.</para>
+                                </listitem>
+                        </varlistentry>
+                        <varlistentry>
+                                <term><filename>display-manager.service</filename></term>
+                                <listitem>
+                                        <para>The display manager
+                                        service. Usually this should
+                                        be aliased (symlinked) to
+                                        <filename>xdm.service</filename>
+                                        or a similar display manager
+                                        service.</para>
+                                        <para>systemd automatically
+                                        adds dependencies of type
+                                        After for this target unit to
+                                        all SysV init script service
+                                        units with a LSB header
+                                        referring to the
+                                        <literal>$x-display-manager</literal>
+                                        facility, for compatibility
+                                        with Debian.</para>
+                                </listitem>
+                        </varlistentry>
+                        <varlistentry>
+                                <term><filename>emergency.target</filename></term>
+                                <listitem>
+                                        <para>A special target unit
+                                        that starts an emergency
+                                        shell on the main
+                                        console. This unit is supposed
+                                        to be used with the kernel
+                                        command line option
+                                        <varname>systemd.unit=</varname>
+                                        and has otherwise little use.
+                                        </para>
+                                </listitem>
+                        </varlistentry>
+                        <varlistentry>
+                                <term><filename>graphical.target</filename></term>
+                                <listitem>
+                                        <para>A special target unit
+                                        for setting up a graphical
+                                        login screen. This pulls in
+                                        <filename>multi-user.target</filename>.</para>
+
+                                        <para>Units that are needed
+                                        for graphical login shall add
+                                        Wants dependencies for their
+                                        unit to this unit (or
+                                        <filename>multi-user.target</filename>)
+                                        during installation.</para>
+                                </listitem>
+                        </varlistentry>
+                        <varlistentry>
+                                <term><filename>halt.target</filename></term>
+                                <listitem>
+                                        <para>A special target unit
+                                        for shutting down and halting the system.</para>
+
+                                        <para>Applications wanting to
+                                        halt the system should start
+                                        this unit.</para>
+                                </listitem>
+                        </varlistentry>
+                        <varlistentry>
+                                <term><filename>kbrequest.target</filename></term>
+                                <listitem>
+                                        <para>systemd starts this
+                                        target whenever Alt+ArrowUp is
+                                        pressed on the console. This
+                                        is a good candidate to be
+                                        aliased (symlinked) to
+                                        <filename>rescue.target</filename>.</para>
+                                </listitem>
+                        </varlistentry>
+                        <varlistentry>
+                                <term><filename>local-fs.target</filename></term>
+                                <listitem>
+                                        <para>systemd automatically
+                                        adds dependencies of type
+                                        After to all mount units that
+                                        refer to local mount points
+                                        for this target unit. In
+                                        addition, systemd adds
+                                        dependencies of type Wants to
+                                        this target unit for those
+                                        mounts listed in
+                                        <filename>/etc/fstab</filename>
+                                        that have the
+                                        <option>auto</option> and
+                                        <option>comment=systemd.mount</option>
+                                        mount options set.</para>
+
+                                        <para>systemd automatically
+                                        adds dependencies of type
+                                        After for this target unit to
+                                        all SysV init script service
+                                        units with an LSB header
+                                        referring to the
+                                        <literal>$local_fs</literal>
+                                        facility.</para>
+                                </listitem>
+                        </varlistentry>
+                        <varlistentry>
+                                <term><filename>local-fs-pre.target</filename></term>
+                                <listitem>
+                                        <para>This target unit is
+                                        automatically ordered before
+                                        all local mount points marked
+                                        with <option>auto</option>
+                                        (see above). It can be used to
+                                        execute certain units before
+                                        all local mounts.</para>
+                                </listitem>
+                        </varlistentry>
+                        <varlistentry>
+                                <term><filename>mail-transfer-agent.target</filename></term>
+                                <listitem>
+                                        <para>The mail transfer agent
+                                        (MTA) service. Usually this
+                                        should pull-in all units
+                                        necessary for
+                                        sending/receiving mails on the
+                                        local host.</para>
+
+                                        <para>systemd automatically
+                                        adds dependencies of type
+                                        After for this target unit to
+                                        all SysV init script service
+                                        units with an LSB header
+                                        referring to the
+                                        <literal>$mail-transfer-agent</literal>
+                                        or
+                                        <literal>$mail-transport-agent</literal>
+                                        facilities, for compatibility
+                                        with Debian.</para>
+                                </listitem>
+                        </varlistentry>
+                        <varlistentry>
+                                <term><filename>multi-user.target</filename></term>
+                                <listitem>
+                                        <para>A special target unit
+                                        for setting up a multi-user
+                                        system (non-graphical). This
+                                        is pulled in by
+                                        <filename>graphical.target</filename>.</para>
+
+                                        <para>Units that are needed
+                                        for a multi-user system shall
+                                        add Wants dependencies to
+                                        this unit for their unit during
+                                        installation.</para>
+                                </listitem>
+                        </varlistentry>
+                        <varlistentry>
+                                <term><filename>network.target</filename></term>
+                                <listitem>
+                                        <para>systemd automatically
+                                        adds dependencies of type
+                                        After for this target unit to
+                                        all SysV init script service
+                                        units with an LSB header
+                                        referring to the
+                                        <literal>$network</literal>
+                                        facility.</para>
+                                </listitem>
+                        </varlistentry>
+                        <varlistentry>
+                                <term><filename>nss-lookup.target</filename></term>
+                                <listitem>
+                                        <para>systemd automatically
+                                        adds dependencies of type
+                                        After for this target unit to
+                                        all SysV init script service
+                                        units with an LSB header
+                                        referring to the
+                                        <literal>$named</literal>
+                                        facility.</para>
+                                </listitem>
+                        </varlistentry>
+                        <varlistentry>
+                                <term><filename>poweroff.target</filename></term>
+                                <listitem>
+                                        <para>A special target unit
+                                        for shutting down and powering off the system.</para>
+
+                                        <para>Applications wanting to
+                                        power off the system should start
+                                        this unit.</para>
+
+                                        <para><filename>runlevel0.target</filename>
+                                        is an alias for this target
+                                        unit, for compatibility with SysV.</para>
+                                </listitem>
+                        </varlistentry>
+                        <varlistentry>
+                                <term><filename>reboot.target</filename></term>
+                                <listitem>
+                                        <para>A special target unit
+                                        for shutting down and rebooting the system.</para>
+
+                                        <para>Applications wanting to
+                                        reboot the system should start
+                                        this unit.</para>
+
+                                        <para><filename>runlevel6.target</filename>
+                                        is an alias for this target
+                                        unit, for compatibility with SysV.</para>
+                                </listitem>
+                        </varlistentry>
+                        <varlistentry>
+                                <term><filename>remote-fs.target</filename></term>
+                                <listitem>
+                                        <para>Similar to
+                                        <filename>local-fs.target</filename>,
+                                        but for remote mount
+                                        points.</para>
+
+                                        <para>systemd automatically
+                                        adds dependencies of type
+                                        After for this target unit to
+                                        all SysV init script service
+                                        units with an LSB header
+                                        referring to the
+                                        <literal>$remote_fs</literal>
+                                        facility.</para>
+                                </listitem>
+                        </varlistentry>
+                        <varlistentry>
+                                <term><filename>remote-fs-pre.target</filename></term>
+                                <listitem>
+                                        <para>This target unit is
+                                        automatically ordered before
+                                        all remote mount points marked
+                                        with <option>auto</option>
+                                        (see above). It can be used to
+                                        execute certain units before
+                                        all remote mounts.</para>
+                                </listitem>
+                        </varlistentry>
+                        <varlistentry>
+                                <term><filename>rescue.target</filename></term>
+                                <listitem>
+                                        <para>A special target unit
+                                        for setting up the base system
+                                        and a rescue shell.</para>
+
+                                        <para><filename>runlevel1.target</filename>
+                                        is an alias for this target
+                                        unit, for compatibility with SysV.</para>
+                                </listitem>
+                        </varlistentry>
+                        <varlistentry>
+                                <term><filename>rpcbind.target</filename></term>
+                                <listitem>
+                                        <para>systemd automatically
+                                        adds dependencies of type
+                                        After for this target unit to
+                                        all SysV init script service
+                                        units with an LSB header
+                                        referring to the
+                                        <literal>$rpcbind</literal>
+                                        facility.</para>
+                                </listitem>
+                        </varlistentry>
+                        <varlistentry>
+                                <term><filename>runlevel2.target</filename></term>
+                                <listitem>
+                                        <para>This is a target that is
+                                        called whenever the SysV
+                                        compatibility code asks for
+                                        runlevel 2. It is a good idea
+                                        to make this an alias for
+                                        (i.e. symlink to)
+                                        <filename>multi-user.target</filename>.</para>
+                                </listitem>
+                        </varlistentry>
+                        <varlistentry>
+                                <term><filename>runlevel3.target</filename></term>
+                                <listitem>
+                                        <para>This is a target that is
+                                        called whenever the SysV
+                                        compatibility code asks for
+                                        runlevel 3. It is a good idea
+                                        to make this an alias for
+                                        (i.e. symlink to)
+                                        <filename>multi-user.target</filename>
+                                        or
+                                        <filename>graphical.target</filename>.</para>
+                                </listitem>
+                        </varlistentry>
+                        <varlistentry>
+                                <term><filename>runlevel4.target</filename></term>
+                                <listitem>
+                                        <para>This is a target that is
+                                        called whenever the SysV
+                                        compatibility code asks for
+                                        runlevel 4. It is a good idea
+                                        to make this an alias for
+                                        (i.e. symlink to)
+                                        <filename>multi-user.target</filename>
+                                        or
+                                        <filename>graphical.target</filename>.</para>
+                                </listitem>
+                        </varlistentry>
+                        <varlistentry>
+                                <term><filename>runlevel5.target</filename></term>
+                                <listitem>
+                                        <para>This is a target that is
+                                        called whenever the SysV
+                                        compatibility code asks for
+                                        runlevel 5. It is a good idea
+                                        to make this an alias for
+                                        (i.e. symlink to)
+                                        <filename>multi-user.target</filename>
+                                        or
+                                        <filename>graphical.target</filename>.</para>
+                                </listitem>
+                        </varlistentry>
+                        <varlistentry>
+                                <term><filename>shutdown.target</filename></term>
+                                <listitem>
+                                        <para>A special target unit
+                                        that terminates the services
+                                        on system shutdown.</para>
+
+                                        <para>Services that shall be
+                                        terminated on system shutdown
+                                        shall add Conflicts
+                                        dependencies to this unit for
+                                        their service unit, which is
+                                        implicitly done when
+                                        <varname>DefaultDependencies=yes</varname>
+                                        is set (the default).</para>
+
+                                        <para>systemd automatically
+                                        adds dependencies of type
+                                        Conflicts to this target unit
+                                        for all SysV init script
+                                        service units that shall be
+                                        terminated in SysV runlevels 0
+                                        or 6.</para>
+                                </listitem>
+                        </varlistentry>
+                        <varlistentry>
+                                <term><filename>sigpwr.target</filename></term>
+                                <listitem>
+                                        <para>A special target that is
+                                        started when systemd receives
+                                        the SIGPWR process signal,
+                                        which is normally sent by the
+                                        kernel or UPS daemons when
+                                        power fails.</para>
+                                </listitem>
+                        </varlistentry>
+                        <varlistentry>
+                                <term><filename>sockets.target</filename></term>
+                                <listitem>
+                                        <para>A special target unit
+                                        that sets up all service
+                                        sockets.</para>
+
+                                        <para>Services that can be
+                                        socket-activated shall add
+                                        Wants dependencies to this
+                                        unit for their socket unit
+                                        during installation.</para>
+                                </listitem>
+                        </varlistentry>
+                        <varlistentry>
+                                <term><filename>swap.target</filename></term>
+                                <listitem>
+                                        <para>Similar to
+                                        <filename>local-fs.target</filename>, but for swap
+                                        partitions and swap
+                                        files.</para>
+                                </listitem>
+                        </varlistentry>
+                        <varlistentry>
+                                <term><filename>sysinit.target</filename></term>
+                                <listitem>
+                                        <para>A special target unit
+                                        covering early boot-up scripts.</para>
+                                        <para>systemd automatically
+                                        adds dependencies of the types
+                                        Wants and After for all
+                                        SysV service units configured
+                                        for runlevels that are not 0
+                                        to 6 to this target unit.
+                                        This covers the special
+                                        boot-up runlevels some
+                                        distributions have, such as S
+                                        or b.</para>
+                                </listitem>
+                        </varlistentry>
+                        <varlistentry>
+                                <term><filename>syslog.target</filename></term>
+                                <listitem>
+                                        <para>systemd automatically
+                                        adds dependencies of type
+                                        After for this target unit to
+                                        all SysV init script service
+                                        units with an LSB header
+                                        referring to the
+                                        <literal>$syslog</literal>
+                                        facility.</para>
+                                </listitem>
+                        </varlistentry>
+                        <varlistentry>
+                                <term><filename>systemd-initctl.service</filename></term>
+                                <listitem>
+                                        <para>This provides
+                                        compatibility with the SysV
+                                        /dev/initctl file system FIFO
+                                        for communication with the
+                                        init system.</para>
+                                        <para>This is a
+                                        socket-activated service, see
+                                        <filename>system-initctl.socket</filename>.</para>
+                                </listitem>
+                        </varlistentry>
+                        <varlistentry>
+                                <term><filename>systemd-initctl.socket</filename></term>
+                                <listitem>
+                                        <para>Socket activation unit
+                                        for
+                                        <filename>system-initctl.service</filename>.</para>
+                                </listitem>
+                        </varlistentry>
+                        <varlistentry>
+                                <term><filename>systemd-stdout-syslog-bridge.service</filename></term>
+                                <listitem>
+                                        <para>This is internally used
+                                        by systemd to provide syslog
+                                        logging to the processes it
+                                        maintains.</para>
+                                        <para>This is a
+                                        socket-activated service, see
+                                        <filename>system-stdout-syslog-bridge.socket</filename>.</para>
+                                </listitem>
+                        </varlistentry>
+                        <varlistentry>
+                                <term><filename>systemd-stdout-syslog-bridge.socket</filename></term>
+                                <listitem>
+                                        <para>Socket activation unit
+                                        for
+                                        <filename>system-stdout-syslog-bridge.service</filename>. systemd
+                                        will automatically add
+                                        dependencies of types Requires
+                                        and After to all units that
+                                        have been configured for
+                                        stdout or stderr to be
+                                        connected to syslog or the
+                                        kernel log buffer.</para>
+                                </listitem>
+                        </varlistentry>
+                         <varlistentry>
+                                <term><filename>systemd-shutdownd.service</filename></term>
+                                <listitem>
+                                        <para>This is internally used
+                                        by
+                                        <citerefentry><refentrytitle>shutdown</refentrytitle><manvolnum>8</manvolnum></citerefentry>
+                                        to implement delayed shutdowns.</para>
+                                        <para>This is a
+                                        socket-activated service, see
+                                        <filename>system-shutdownd.socket</filename>.</para>
+                                </listitem>
+                        </varlistentry>
+                        <varlistentry>
+                                <term><filename>systemd-shutdownd.socket</filename></term>
+                                <listitem>
+                                        <para>Socket activation unit
+                                        for
+                                        <filename>system-shutdownd.service</filename>.</para>
+                                </listitem>
+                        </varlistentry>
+                        <varlistentry>
+                                <term><filename>time-sync.target</filename></term>
+                                <listitem>
+                                        <para>systemd automatically
+                                        adds dependencies of type
+                                        After for this target unit to
+                                        all SysV init script service
+                                        units with an LSB header
+                                        referring to the
+                                        <literal>$time</literal>
+                                        facility.</para>
+                                </listitem>
+                        </varlistentry>
+                        <varlistentry>
+                                <term><filename>umount.target</filename></term>
+                                <listitem>
+                                        <para>A special target unit
+                                        that umounts all mount and
+                                        automount points on system
+                                        shutdown.</para>
+
+                                        <para>Mounts that shall be
+                                        unmounted on system shutdown
+                                        shall add Conflicts
+                                        dependencies to this unit for
+                                        their mount unit, which is
+                                        implicitly done when
+                                        <varname>DefaultDependencies=yes</varname>
+                                        is set (the default).</para>
+                                </listitem>
+                        </varlistentry>
+                </variablelist>
+        </refsect1>
+
+        <refsect1>
+                <title>Special User Units</title>
+
+                <para>When systemd runs as a user instance, the
+                following special units are available, which have
+                similar definitions as their system counterparts:
+                <filename>default.target</filename>,
+                <filename>local-fs.target</filename>,
+                <filename>remote-fs.target</filename>,
+                <filename>shutdown.target</filename>,
+                <filename>sockets.target</filename>,
+                <filename>swap.target</filename>.</para>
+
+                <para>In addition the following special unit is
+                understood only when systemd runs as service instance:</para>
+
+                <variablelist>
+                        <varlistentry>
+                                <term><filename>exit.service</filename></term>
+                                <listitem>
+                                        <para>A special service unit
+                                        for shutting down the
+                                        user service manager.</para>
+
+                                        <para>Applications wanting to
+                                        terminate the user service
+                                        manager should start this
+                                        unit. If systemd receives
+                                        SIGTERM or SIGINT when running
+                                        as user service daemon it will
+                                        start this unit.</para>
+
+                                        <para>Normally, this pulls in
+                                        <filename>shutdown.target</filename>
+                                        which in turn should be
+                                        conflicted by all units that
+                                        want to be shut down on
+                                        user service manager exit.</para>
+                                </listitem>
+                        </varlistentry>
+                </variablelist>
+        </refsect1>
+
+        <refsect1>
+                  <title>See Also</title>
+                  <para>
+                          <citerefentry><refentrytitle>systemd.unit</refentrytitle><manvolnum>5</manvolnum></citerefentry>,
+                          <citerefentry><refentrytitle>systemd.service</refentrytitle><manvolnum>5</manvolnum></citerefentry>,
+                          <citerefentry><refentrytitle>systemd.socket</refentrytitle><manvolnum>5</manvolnum></citerefentry>,
+                          <citerefentry><refentrytitle>systemd.target</refentrytitle><manvolnum>5</manvolnum></citerefentry>
+                  </para>
+        </refsect1>
+
+</refentry>
diff --git a/man/systemd.swap.xml b/man/systemd.swap.xml
new file mode 100644 (file)
index 0000000..ab00f9f
--- /dev/null
@@ -0,0 +1,222 @@
+<?xml version='1.0'?> <!--*-nxml-*-->
+<?xml-stylesheet type="text/xsl" href="http://docbook.sourceforge.net/release/xsl/current/xhtml/docbook.xsl"?>
+<!DOCTYPE refentry PUBLIC "-//OASIS//DTD DocBook XML V4.2//EN"
+        "http://www.oasis-open.org/docbook/xml/4.2/docbookx.dtd">
+
+<!--
+  This file is part 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 <http://www.gnu.org/licenses/>.
+-->
+
+<refentry id="systemd.swap">
+        <refentryinfo>
+                <title>systemd.swap</title>
+                <productname>systemd</productname>
+
+                <authorgroup>
+                        <author>
+                                <contrib>Developer</contrib>
+                                <firstname>Lennart</firstname>
+                                <surname>Poettering</surname>
+                                <email>lennart@poettering.net</email>
+                        </author>
+                </authorgroup>
+        </refentryinfo>
+
+        <refmeta>
+                <refentrytitle>systemd.swap</refentrytitle>
+                <manvolnum>5</manvolnum>
+        </refmeta>
+
+        <refnamediv>
+                <refname>systemd.swap</refname>
+                <refpurpose>systemd swap configuration files</refpurpose>
+        </refnamediv>
+
+        <refsynopsisdiv>
+                <para><filename>systemd.swap</filename></para>
+        </refsynopsisdiv>
+
+        <refsect1>
+                <title>Description</title>
+
+                <para>A unit configuration file whose name ends in
+                <filename>.swap</filename> encodes information about a
+                swap device or file for memory paging controlled and
+                supervised by systemd.</para>
+
+                <para>This man page lists the configuration options
+                specific to this unit type. See
+                <citerefentry><refentrytitle>systemd.unit</refentrytitle><manvolnum>5</manvolnum></citerefentry>
+                for the common options of all unit configuration
+                files. The common configuration items are configured
+                in the generic [Unit] and [Install] sections. The swap
+                specific configuration options are configured in the
+                [Swap] section.</para>
+
+                <para>Swap units must be named after the devices
+                (resp. files) they control. Example: the swap device
+                <filename>/dev/sda5</filename> must be configured in a
+                unit file <filename>dev-sda5.swap</filename>. For
+                details about the escaping logic used to convert a
+                file system path to a unit name see
+                <citerefentry><refentrytitle>systemd.unit</refentrytitle><manvolnum>5</manvolnum></citerefentry>.</para>
+
+                <para>All swap units automatically get the appropriate
+                dependencies on the devices (resp. on the mount points
+                of the files) they are activated from.</para>
+
+                <para>Swap units with
+                <varname>DefaultDependencies=</varname> enabled
+                implicitly acquire a conflicting dependency to
+                <filename>umount.target</filename> so that they are
+                deactivated at shutdown.</para>
+        </refsect1>
+
+        <refsect1>
+                <title><filename>fstab</filename></title>
+
+                <para>Swap units may either be configured via unit
+                files, or via <filename>/etc/fstab</filename> (see
+                <citerefentry><refentrytitle>fstab</refentrytitle><manvolnum>5</manvolnum></citerefentry>
+                for details).</para>
+
+                <para>If a swap device or file is configured in both
+                <filename>/etc/fstab</filename> and a unit file the
+                configuration in the latter takes precedence.</para>
+
+                <para>Unless the <option>noauto</option> option is set
+                for them all swap units configured in
+                <filename>/etc/fstab</filename> are also added as
+                requirements to <filename>swap.target</filename>, so
+                that they are waited for and activated during
+                boot.</para>
+        </refsect1>
+
+        <refsect1>
+                <title>Options</title>
+
+                <para>Swap files must include a [Swap] section, which
+                carries information about the swap device it
+                supervises. A number of options that may be used in
+                this section are shared with other unit types. These
+                options are documented in
+                <citerefentry><refentrytitle>systemd.exec</refentrytitle><manvolnum>5</manvolnum></citerefentry>. The
+                options specific to the [Swap] section of swap units
+                are the following:</para>
+
+                <variablelist>
+
+                        <varlistentry>
+                                <term><varname>What=</varname></term>
+                                <listitem><para>Takes an absolute path
+                                of a device node or file to use for
+                                paging. See
+                                <citerefentry><refentrytitle>swapon</refentrytitle><manvolnum>8</manvolnum></citerefentry>
+                                for details. If this refers to a
+                                device node, a dependency on the
+                                respective device unit is
+                                automatically created. (See
+                                <citerefentry><refentrytitle>systemd.device</refentrytitle><manvolnum>5</manvolnum></citerefentry>
+                                for more information.) If this refers
+                                to a file, a dependency on the
+                                respective mount unit is automatically
+                                created. (See
+                                <citerefentry><refentrytitle>systemd.mount</refentrytitle><manvolnum>5</manvolnum></citerefentry>
+                                for more information.) This option is
+                                mandatory.</para></listitem>
+                        </varlistentry>
+
+                        <varlistentry>
+                                <term><varname>Priority=</varname></term>
+
+                                <listitem><para>Swap priority to use
+                                when activating the swap device or
+                                file. This takes an integer. This
+                                setting is optional.</para></listitem>
+                        </varlistentry>
+
+                        <varlistentry>
+                                <term><varname>TimeoutSec=</varname></term>
+                                <listitem><para>Configures the time to
+                                wait for the swapon command to
+                                finish. If a command does not exit
+                                within the configured time the swap
+                                will be considered failed and be shut
+                                down again. All commands still running
+                                will be terminated forcibly via
+                                SIGTERM, and after another delay of
+                                this time with SIGKILL. (See
+                                <option>KillMode=</option> below.)
+                                Takes a unit-less value in seconds, or
+                                a time span value such as "5min
+                                20s". Pass 0 to disable the timeout
+                                logic. Defaults to
+                                90s.</para></listitem>
+                        </varlistentry>
+
+                        <varlistentry>
+                                <term><varname>KillMode=</varname></term>
+                                <listitem><para>Specifies how
+                                processes of this swap shall be
+                                killed. One of
+                                <option>control-group</option>,
+                                <option>process</option>,
+                                <option>none</option>.</para>
+
+                                <para>This option is mostly equivalent
+                                to the <option>KillMode=</option>
+                                option of service files. See
+                                <citerefentry><refentrytitle>systemd.service</refentrytitle><manvolnum>5</manvolnum></citerefentry>
+                                for details.</para></listitem>
+                        </varlistentry>
+
+                        <varlistentry>
+                                <term><varname>KillSignal=</varname></term>
+                                <listitem><para>Specifies which signal
+                                to use when killing a process of this
+                                swap. Defaults to SIGTERM.
+                                </para></listitem>
+                        </varlistentry>
+
+                        <varlistentry>
+                                <term><varname>SendSIGKILL=</varname></term>
+                                <listitem><para>Specifies whether to
+                                send SIGKILL to remaining processes
+                                after a timeout, if the normal
+                                shutdown procedure left processes of
+                                the swap around. Takes a boolean
+                                value. Defaults to "yes".
+                                </para></listitem>
+                        </varlistentry>
+                </variablelist>
+        </refsect1>
+
+        <refsect1>
+                  <title>See Also</title>
+                  <para>
+                          <citerefentry><refentrytitle>systemd</refentrytitle><manvolnum>1</manvolnum></citerefentry>,
+                          <citerefentry><refentrytitle>systemctl</refentrytitle><manvolnum>8</manvolnum></citerefentry>,
+                          <citerefentry><refentrytitle>systemd.unit</refentrytitle><manvolnum>5</manvolnum></citerefentry>,
+                          <citerefentry><refentrytitle>systemd.exec</refentrytitle><manvolnum>5</manvolnum></citerefentry>,
+                          <citerefentry><refentrytitle>systemd.device</refentrytitle><manvolnum>5</manvolnum></citerefentry>,
+                          <citerefentry><refentrytitle>systemd.mount</refentrytitle><manvolnum>5</manvolnum></citerefentry>,
+                          <citerefentry><refentrytitle>swapon</refentrytitle><manvolnum>8</manvolnum></citerefentry>
+                  </para>
+        </refsect1>
+
+</refentry>
diff --git a/man/systemd.target.xml b/man/systemd.target.xml
new file mode 100644 (file)
index 0000000..6b1dbfb
--- /dev/null
@@ -0,0 +1,108 @@
+<?xml version='1.0'?> <!--*-nxml-*-->
+<?xml-stylesheet type="text/xsl" href="http://docbook.sourceforge.net/release/xsl/current/xhtml/docbook.xsl"?>
+<!DOCTYPE refentry PUBLIC "-//OASIS//DTD DocBook XML V4.2//EN"
+        "http://www.oasis-open.org/docbook/xml/4.2/docbookx.dtd">
+
+<!--
+  This file is part 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 <http://www.gnu.org/licenses/>.
+-->
+
+<refentry id="systemd.target">
+        <refentryinfo>
+                <title>systemd.target</title>
+                <productname>systemd</productname>
+
+                <authorgroup>
+                        <author>
+                                <contrib>Developer</contrib>
+                                <firstname>Lennart</firstname>
+                                <surname>Poettering</surname>
+                                <email>lennart@poettering.net</email>
+                        </author>
+                </authorgroup>
+        </refentryinfo>
+
+        <refmeta>
+                <refentrytitle>systemd.target</refentrytitle>
+                <manvolnum>5</manvolnum>
+        </refmeta>
+
+        <refnamediv>
+                <refname>systemd.target</refname>
+                <refpurpose>systemd target configuration files</refpurpose>
+        </refnamediv>
+
+        <refsynopsisdiv>
+                <para><filename>systemd.target</filename></para>
+        </refsynopsisdiv>
+
+        <refsect1>
+                <title>Description</title>
+
+                <para>A unit configuration file whose name ends in
+                <filename>.target</filename> encodes information about
+                a target unit of systemd, which is used for grouping
+                units and as well-known synchronization points during
+                start-up.</para>
+
+                <para>This unit type has no specific options. See
+                <citerefentry><refentrytitle>systemd.unit</refentrytitle><manvolnum>5</manvolnum></citerefentry>
+                for the common options of all unit configuration
+                files. The common configuration items are configured
+                in the generic [Unit] and [Install] sections. A
+                separate [Target] section does not exist, since no
+                target-specific options may be configured.</para>
+
+                <para>Target units do not offer any additional
+                functionality on top of the generic functionality
+                provided by units. They exist merely to group units via dependencies
+                (useful as boot targets), and to establish
+                standardized names for synchronization points used in
+                dependencies between units. Among other things, target
+                units are a more flexible replacement for SysV
+                runlevels in the classic SysV init system. (And for
+                compatibility reasons special
+                target units such as
+                <filename>runlevel3.target</filename> exist which are used by
+                the SysV runlevel compatibility code in systemd. See
+                <citerefentry><refentrytitle>systemd.special</refentrytitle><manvolnum>7</manvolnum></citerefentry>
+                for details).</para>
+
+                <para>Unless <varname>DefaultDependencies=</varname>
+                is set to <option>false</option>, target units will
+                implicitly complement all configured dependencies of
+                type <varname>Wants=</varname>,
+                <varname>Requires=</varname>,
+                <varname>RequiresOverridable=</varname> with
+                dependencies of type <varname>After=</varname> if the
+                units in question also have
+                <varname>DefaultDependencies=true</varname>.
+                </para>
+        </refsect1>
+
+        <refsect1>
+                  <title>See Also</title>
+                  <para>
+                          <citerefentry><refentrytitle>systemd</refentrytitle><manvolnum>1</manvolnum></citerefentry>,
+                          <citerefentry><refentrytitle>systemctl</refentrytitle><manvolnum>8</manvolnum></citerefentry>,
+                          <citerefentry><refentrytitle>systemd.unit</refentrytitle><manvolnum>5</manvolnum></citerefentry>,
+                          <citerefentry><refentrytitle>systemd.special</refentrytitle><manvolnum>7</manvolnum></citerefentry>
+                  </para>
+        </refsect1>
+
+</refentry>
diff --git a/man/systemd.timer.xml b/man/systemd.timer.xml
new file mode 100644 (file)
index 0000000..9b6b486
--- /dev/null
@@ -0,0 +1,192 @@
+<?xml version='1.0'?> <!--*-nxml-*-->
+<?xml-stylesheet type="text/xsl" href="http://docbook.sourceforge.net/release/xsl/current/xhtml/docbook.xsl"?>
+<!DOCTYPE refentry PUBLIC "-//OASIS//DTD DocBook XML V4.2//EN"
+        "http://www.oasis-open.org/docbook/xml/4.2/docbookx.dtd">
+
+<!--
+  This file is part 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 <http://www.gnu.org/licenses/>.
+-->
+
+<refentry id="systemd.timer">
+        <refentryinfo>
+                <title>systemd.timer</title>
+                <productname>systemd</productname>
+
+                <authorgroup>
+                        <author>
+                                <contrib>Developer</contrib>
+                                <firstname>Lennart</firstname>
+                                <surname>Poettering</surname>
+                                <email>lennart@poettering.net</email>
+                        </author>
+                </authorgroup>
+        </refentryinfo>
+
+        <refmeta>
+                <refentrytitle>systemd.timer</refentrytitle>
+                <manvolnum>5</manvolnum>
+        </refmeta>
+
+        <refnamediv>
+                <refname>systemd.timer</refname>
+                <refpurpose>systemd timer configuration files</refpurpose>
+        </refnamediv>
+
+        <refsynopsisdiv>
+                <para><filename>systemd.timer</filename></para>
+        </refsynopsisdiv>
+
+        <refsect1>
+                <title>Description</title>
+
+                <para>A unit configuration file whose name ends in
+                <filename>.timer</filename> encodes information about
+                a timer controlled and supervised by systemd, for
+                timer-based activation.</para>
+
+                <para>This man page lists the configuration options
+                specific to this unit type. See
+                <citerefentry><refentrytitle>systemd.unit</refentrytitle><manvolnum>5</manvolnum></citerefentry>
+                for the common options of all unit configuration
+                files. The common configuration items are configured
+                in the generic [Unit] and [Install] sections. The
+                timer specific configuration options are configured in
+                the [Timer] section.</para>
+
+                <para>For each timer file, a matching unit file must
+                exist, describing the unit to activate when the timer
+                elapses. By default, a service by the same name as the
+                timer (except for the suffix) is activated. Example: a
+                timer file <filename>foo.timer</filename> activates a
+                matching service <filename>foo.service</filename>. The
+                unit to activate may be controlled by
+                <varname>Unit=</varname> (see below).</para>
+
+                <para>Unless <varname>DefaultDependencies=</varname>
+                is set to <option>false</option>, timer units will
+                implicitly have dependencies of type
+                <varname>Conflicts=</varname> and
+                <varname>Before=</varname> on
+                <filename>shutdown.target</filename>. These ensure
+                that timer units are stopped cleanly prior to system
+                shutdown. Only timer units involved with early boot or
+                late system shutdown should disable this
+                option.</para>
+        </refsect1>
+
+        <refsect1>
+                <title>Options</title>
+
+                <para>Timer files must include a [Timer] section,
+                which carries information about the timer it
+                defines. The options specific to the [Timer] section
+                of timer units are the following:</para>
+
+                <variablelist>
+                        <varlistentry>
+                                <term><varname>OnActiveSec=</varname></term>
+                                <term><varname>OnBootSec=</varname></term>
+                                <term><varname>OnStartupSec=</varname></term>
+                                <term><varname>OnUnitActiveSec=</varname></term>
+                                <term><varname>OnUnitInactiveSec=</varname></term>
+
+                                <listitem><para>Defines timers
+                                relative to different starting points:
+                                <varname>OnActiveSec=</varname> defines a
+                                timer relative to the moment the timer
+                                itself is
+                                activated. <varname>OnBootSec=</varname>
+                                defines a timer relative to when the
+                                machine was booted
+                                up. <varname>OnStartupSec=</varname>
+                                defines a timer relative to when
+                                systemd was
+                                started. <varname>OnUnitActiveSec=</varname>
+                                defines a timer relative to when the
+                                unit the timer is activating was last
+                                activated. <varname>OnUnitInactiveSec=</varname>
+                                defines a timer relative to when the
+                                unit the timer is activating was last
+                                deactivated.</para>
+
+                                <para>Multiple directives may be
+                                combined of the same and of different
+                                types. For example, by combining
+                                <varname>OnBootSec=</varname> and
+                                <varname>OnUnitActiveSec=</varname> it is
+                                possible to define a timer that
+                                elapses in regular intervals and
+                                activates a specific service each
+                                time.</para>
+
+                                <para>The arguments to the directives
+                                are time spans configured in
+                                seconds. Example: "OnBootSec=50" means
+                                50s after boot-up. The argument may
+                                also include time units. Example:
+                                "OnBootSec=5h 30min" means 5 hours and 30
+                                minutes after boot-up. For details
+                                about the syntax of time spans see
+                                <citerefentry><refentrytitle>systemd.unit</refentrytitle><manvolnum>5</manvolnum></citerefentry>.</para>
+
+                                <para>If a timer configured with
+                                <varname>OnBootSec=</varname> or
+                                <varname>OnStartupSec=</varname> is
+                                already in the past when the timer
+                                unit is activated, it will immediately
+                                elapse and the configured unit is
+                                started. This is not the case for
+                                timers defined in the other
+                                directives.</para></listitem>
+
+                                <para>These are monotonic timers,
+                                independent of wall-clock time and timezones. If the
+                                computer is temporarily suspended, the
+                                monotonic clock stops too.</para>
+
+                        </varlistentry>
+                        <varlistentry>
+                                <term><varname>Unit=</varname></term>
+
+                                <listitem><para>The unit to activate
+                                when this timer elapses. The argument is a
+                                unit name, whose suffix is not
+                                <filename>.timer</filename>. If not
+                                specified, this value defaults to a
+                                service that has the same name as the
+                                timer unit, except for the
+                                suffix. (See above.) It is recommended
+                                that the unit name that is activated
+                                and the unit name of the timer unit
+                                are named identically, except for the
+                                suffix.</para></listitem>
+                        </varlistentry>
+                </variablelist>
+        </refsect1>
+
+        <refsect1>
+                  <title>See Also</title>
+                  <para>
+                          <citerefentry><refentrytitle>systemd</refentrytitle><manvolnum>1</manvolnum></citerefentry>,
+                          <citerefentry><refentrytitle>systemctl</refentrytitle><manvolnum>8</manvolnum></citerefentry>,
+                          <citerefentry><refentrytitle>systemd.unit</refentrytitle><manvolnum>5</manvolnum></citerefentry>,
+                          <citerefentry><refentrytitle>systemd.service</refentrytitle><manvolnum>5</manvolnum></citerefentry>
+                  </para>
+        </refsect1>
+
+</refentry>
diff --git a/man/systemd.unit.xml b/man/systemd.unit.xml
new file mode 100644 (file)
index 0000000..3cc126b
--- /dev/null
@@ -0,0 +1,969 @@
+<?xml version='1.0'?> <!--*-nxml-*-->
+<!DOCTYPE refentry PUBLIC "-//OASIS//DTD DocBook XML V4.2//EN"
+        "http://www.oasis-open.org/docbook/xml/4.2/docbookx.dtd">
+
+<!--
+  This file is part 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 <http://www.gnu.org/licenses/>.
+-->
+
+<refentry id="systemd.unit">
+
+        <refentryinfo>
+                <title>systemd.unit</title>
+                <productname>systemd</productname>
+
+                <authorgroup>
+                        <author>
+                                <contrib>Developer</contrib>
+                                <firstname>Lennart</firstname>
+                                <surname>Poettering</surname>
+                                <email>lennart@poettering.net</email>
+                        </author>
+                </authorgroup>
+        </refentryinfo>
+
+        <refmeta>
+                <refentrytitle>systemd.unit</refentrytitle>
+                <manvolnum>5</manvolnum>
+        </refmeta>
+
+        <refnamediv>
+                <refname>systemd.unit</refname>
+                <refpurpose>systemd unit configuration files</refpurpose>
+        </refnamediv>
+
+        <refsynopsisdiv>
+                <para><filename>systemd.service</filename>,
+                <filename>systemd.socket</filename>,
+                <filename>systemd.device</filename>,
+                <filename>systemd.mount</filename>,
+                <filename>systemd.automount</filename>,
+                <filename>systemd.swap</filename>,
+                <filename>systemd.target</filename>,
+                <filename>systemd.path</filename>,
+                <filename>systemd.timer</filename>,
+                <filename>systemd.snapshot</filename></para>
+        </refsynopsisdiv>
+
+        <refsect1>
+                <title>Description</title>
+
+                <para>A unit configuration file encodes information
+                about a service, a socket, a device, a mount point, an
+                automount point, a swap file or partition, a start-up
+                target, a file system path or a timer controlled and
+                supervised by
+                <citerefentry><refentrytitle>systemd</refentrytitle><manvolnum>1</manvolnum></citerefentry>. The
+                syntax is inspired by <ulink
+                url="http://standards.freedesktop.org/desktop-entry-spec/latest/">XDG
+                Desktop Entry Specification</ulink> <filename>.desktop</filename> files, which are in turn
+                inspired by Microsoft Windows
+                <filename>.ini</filename> files.</para>
+
+                <para>This man pages lists the common configuration
+                options of all the unit types. These options need to
+                be configured in the [Unit] resp. [Install]
+                section of the unit files.</para>
+
+                <para>In addition to the generic [Unit] and [Install]
+                sections described here, each unit should have a
+                type-specific section, e.g. [Service] for a service
+                unit. See the respective man pages for more
+                information.</para>
+
+                <para>Unit files may contain additional options on top
+                of those listed here. If systemd encounters an unknown
+                option it will write a warning log message but
+                continue loading the unit. If an option is prefixed
+                with <option>X-</option> it is ignored completely by
+                systemd. Applications may use this to include
+                additional information in the unit files.</para>
+
+                <para>Boolean arguments used in unit files can be
+                written in various formats. For positive settings the
+                strings <option>1</option>, <option>yes</option>,
+                <option>true</option> and <option>on</option> are
+                equivalent. For negative settings the strings
+                <option>0</option>, <option>no</option>,
+                <option>false</option> and <option>off</option> are
+                equivalent.</para>
+
+                <para>Time span values encoded in unit files can be
+                written in various formats. A stand-alone number
+                specifies a time in seconds. If suffixed with a time
+                unit, the unit is honored. A concatenation of
+                multiple values with units is supported, in which case
+                the values are added up. Example: "50" refers to 50
+                seconds; "2min 200ms" refers to 2 minutes plus 200
+                milliseconds, i.e. 120200ms. The following time units
+                are understood: s, min, h, d, w, ms, us.</para>
+
+                <para>Empty lines and lines starting with # or ; are
+                ignored. This may be used for commenting. Lines ending
+                in a backslash are concatenated with the following
+                line while reading and the backslash is replaced by a
+                space character. This may be used to wrap long lines.</para>
+
+                <para>If a line starts with <option>.include</option>
+                followed by a file name, the specified file will be
+                parsed at this point. Make sure that the file that is
+                included has the appropiate section headers before
+                any directives.</para>
+
+                <para>Along with a unit file
+                <filename>foo.service</filename> a directory
+                <filename>foo.service.wants/</filename> may exist. All
+                units symlinked from such a directory are implicitly
+                added as dependencies of type
+                <varname>Wanted=</varname> to the unit. This is useful
+                to hook units into the start-up of other units,
+                without having to modify their unit configuration
+                files. For details about the semantics of
+                <varname>Wanted=</varname> see below. The preferred
+                way to create symlinks in the
+                <filename>.wants/</filename> directory of a service is
+                with the <command>enable</command> command of the
+                <citerefentry><refentrytitle>systemctl</refentrytitle><manvolnum>1</manvolnum></citerefentry>
+                tool which reads information from the [Install]
+                section of unit files. (See below.) A similar
+                functionality exists for <varname>Requires=</varname>
+                type dependencies as well, the directory suffix is
+                <filename>.requires/</filename> in this case.</para>
+
+                <para>Note that while systemd offers a flexible
+                dependency system between units it is recommended to
+                use this functionality only sparsely and instead rely
+                on techniques such as bus-based or socket-based
+                activation which makes dependencies implicit, which
+                both results in a simpler and more flexible
+                system.</para>
+
+                <para>Some unit names reflect paths existing in the
+                file system name space. Example: a device unit
+                <filename>dev-sda.device</filename> refers to a device
+                with the device node <filename>/dev/sda</filename> in
+                the file system namespace. If this applies a special
+                way to escape the path name is used, so that the
+                result is usable as part of a file name. Basically,
+                given a path, "/" is replaced by "-", and all
+                unprintable characters and the "-" are replaced by
+                C-style "\x20" escapes. The root directory "/" is
+                encoded as single dash, while otherwise the initial
+                and ending "/" is removed from all paths during
+                transformation. This escaping is reversible.</para>
+
+                <para>Optionally, units may be instantiated from a
+                template file at runtime. This allows creation of
+                multiple units from a single configuration file. If
+                systemd looks for a unit configuration file it will
+                first search for the literal unit name in the
+                filesystem. If that yields no success and the unit
+                name contains an @ character, systemd will look for a
+                unit template that shares the same name but with the
+                instance string (i.e. the part between the @ character
+                and the suffix) removed. Example: if a service
+                <filename>getty@tty3.service</filename> is requested
+                and no file by that name is found, systemd will look
+                for <filename>getty@.service</filename> and
+                instantiate a service from that configuration file if
+                it is found.</para>
+
+                <para>To refer to the instance string from
+                within the configuration file you may use the special
+                <literal>%i</literal> specifier in many of the
+                configuration options. Other specifiers exist, the
+                full list is:</para>
+
+                <table>
+                  <title>Specifiers available in unit files</title>
+                  <tgroup cols='3' align='left' colsep='1' rowsep='1'>
+                    <colspec colname="spec" />
+                    <colspec colname="mean" />
+                    <colspec colname="detail" />
+                    <thead>
+                      <row>
+                        <entry>Specifier</entry>
+                        <entry>Meaning</entry>
+                        <entry>Details</entry>
+                      </row>
+                    </thead>
+                    <tbody>
+                      <row>
+                        <entry><literal>%n</literal></entry>
+                        <entry>Full unit name</entry>
+                        <entry></entry>
+                      </row>
+                      <row>
+                        <entry><literal>%N</literal></entry>
+                        <entry>Unescaped full unit name</entry>
+                        <entry></entry>
+                      </row>
+                      <row>
+                        <entry><literal>%p</literal></entry>
+                        <entry>Prefix name</entry>
+                        <entry>This refers to the string before the @, i.e. "getty" in the example above, where "tty3" is the instance name.</entry>
+                      </row>
+                      <row>
+                        <entry><literal>%P</literal></entry>
+                        <entry>Unescaped prefix name</entry>
+                        <entry></entry>
+                      </row>
+                      <row>
+                        <entry><literal>%i</literal></entry>
+                        <entry>Instance name</entry>
+                        <entry>This is the string between the @ character and the suffix.</entry>
+                      </row>
+                      <row>
+                        <entry><literal>%I</literal></entry>
+                        <entry>Unescaped instance name</entry>
+                        <entry></entry>
+                      </row>
+                      <row>
+                        <entry><literal>%f</literal></entry>
+                        <entry>Unescaped file name</entry>
+                        <entry>This is either the unescaped instance name (if set) with / prepended (if necessary), or the prefix name similarly prepended with /.</entry>
+                      </row>
+                      <row>
+                        <entry><literal>%c</literal></entry>
+                        <entry>Control group path of the unit</entry>
+                        <entry></entry>
+                      </row>
+                      <row>
+                        <entry><literal>%r</literal></entry>
+                        <entry>Root control group path of systemd</entry>
+                        <entry></entry>
+                      </row>
+                      <row>
+                        <entry><literal>%R</literal></entry>
+                        <entry>Parent directory of the root control group path of systemd</entry>
+                        <entry></entry>
+                      </row>
+                      <row>
+                        <entry><literal>%t</literal></entry>
+                        <entry>Runtime socket dir</entry>
+                        <entry>This is either /run (for the system manager) or $XDG_RUNTIME_DIR (for user managers).</entry>
+                      </row>
+                    </tbody>
+                  </tgroup>
+                </table>
+
+                <para>If a unit file is empty (i.e. has the file size
+                0) or is symlinked to <filename>/dev/null</filename>
+                its configuration will not be loaded and it appears
+                with a load state of <literal>masked</literal>, and
+                cannot be activated. Use this as an effective way to
+                fully disable a unit, making it impossible to start it
+                even manually.</para>
+
+                <para>The unit file format is covered by the
+                <ulink
+                url="http://www.freedesktop.org/wiki/Software/systemd/InterfaceStabilityPromise">Interface
+                Stability Promise</ulink>.</para>
+        </refsect1>
+
+        <refsect1>
+                <title>Options</title>
+
+                <para>Unit file may include a [Unit] section, which
+                carries generic information about the unit that is not
+                dependent on the type of unit:</para>
+
+                <variablelist>
+
+                        <varlistentry>
+                                <term><varname>Description=</varname></term>
+                                <listitem><para>A free-form string
+                                describing the unit. This is intended
+                                for use in UIs to show descriptive
+                                information along with the unit
+                                name.</para></listitem>
+                        </varlistentry>
+
+                        <varlistentry>
+                                <term><varname>Requires=</varname></term>
+
+                                <listitem><para>Configures requirement
+                                dependencies on other units. If this
+                                unit gets activated, the units listed
+                                here will be activated as well. If one
+                                of the other units gets deactivated or
+                                its activation fails, this unit will
+                                be deactivated. This option may be
+                                specified more than once, in which
+                                case requirement dependencies for all
+                                listed names are created. Note that
+                                requirement dependencies do not
+                                influence the order in which services
+                                are started or stopped. This has to be
+                                configured independently with the
+                                <varname>After=</varname> or
+                                <varname>Before=</varname> options. If
+                                a unit
+                                <filename>foo.service</filename>
+                                requires a unit
+                                <filename>bar.service</filename> as
+                                configured with
+                                <varname>Requires=</varname> and no
+                                ordering is configured with
+                                <varname>After=</varname> or
+                                <varname>Before=</varname>, then both
+                                units will be started simultaneously
+                                and without any delay between them if
+                                <filename>foo.service</filename> is
+                                activated. Often it is a better choice
+                                to use <varname>Wants=</varname>
+                                instead of
+                                <varname>Requires=</varname> in order
+                                to achieve a system that is more
+                                robust when dealing with failing
+                                services.</para></listitem>
+                        </varlistentry>
+
+                        <varlistentry>
+                                <term><varname>RequiresOverridable=</varname></term>
+
+                                <listitem><para>Similar to
+                                <varname>Requires=</varname>.
+                                Dependencies listed in
+                                <varname>RequiresOverridable=</varname>
+                                which cannot be fulfilled or fail to
+                                start are ignored if the startup was
+                                explicitly requested by the user. If
+                                the start-up was pulled in indirectly
+                                by some dependency or automatic
+                                start-up of units that is not
+                                requested by the user this dependency
+                                must be fulfilled and otherwise the
+                                transaction fails. Hence, this option
+                                may be used to configure dependencies
+                                that are normally honored unless the
+                                user explicitly starts up the unit, in
+                                which case whether they failed or not
+                                is irrelevant.</para></listitem>
+
+                        </varlistentry>
+                        <varlistentry>
+                                <term><varname>Requisite=</varname></term>
+                                <term><varname>RequisiteOverridable=</varname></term>
+
+                                <listitem><para>Similar to
+                                <varname>Requires=</varname>
+                                resp. <varname>RequiresOverridable=</varname>. However,
+                                if a unit listed here is not started
+                                already it will not be started and the
+                                transaction fails
+                                immediately.</para></listitem>
+                        </varlistentry>
+
+                        <varlistentry>
+                                <term><varname>Wants=</varname></term>
+
+                                <listitem><para>A weaker version of
+                                <varname>Requires=</varname>. A unit
+                                listed in this option will be started
+                                if the configuring unit is. However,
+                                if the listed unit fails to start up
+                                or cannot be added to the transaction
+                                this has no impact on the validity of
+                                the transaction as a whole. This is
+                                the recommended way to hook start-up
+                                of one unit to the start-up of another
+                                unit. Note that dependencies of this
+                                type may also be configured outside of
+                                the unit configuration file by
+                                adding a symlink to a
+                                <filename>.wants/</filename> directory
+                                accompanying the unit file. For
+                                details see above.</para></listitem>
+                        </varlistentry>
+
+                        <varlistentry>
+                                <term><varname>BindTo=</varname></term>
+
+                                <listitem><para>Configures requirement
+                                dependencies, very similar in style to
+                                <varname>Requires=</varname>, however
+                                in addition to this behaviour it also
+                                declares that this unit is stopped
+                                when any of the units listed suddenly
+                                disappears. Units can suddenly,
+                                unexpectedly disappear if a service
+                                terminates on its own choice, a device
+                                is unplugged or a mount point
+                                unmounted without involvement of
+                                systemd.</para></listitem>
+                        </varlistentry>
+
+                        <varlistentry>
+                                <term><varname>Conflicts=</varname></term>
+
+                                <listitem><para>Configures negative
+                                requirement dependencies. If a unit
+                                has a
+                                <varname>Conflicts=</varname> setting
+                                on another unit, starting the former
+                                will stop the latter and vice
+                                versa. Note that this setting is
+                                independent of and orthogonal to the
+                                <varname>After=</varname> and
+                                <varname>Before=</varname> ordering
+                                dependencies.</para>
+
+                                <para>If a unit A that conflicts with
+                                a unit B is scheduled to be started at
+                                the same time as B, the transaction
+                                will either fail (in case both are
+                                required part of the transaction) or
+                                be modified to be fixed (in case one
+                                or both jobs are not a required part
+                                of the transaction). In the latter
+                                case the job that is not the required
+                                will be removed, or in case both are
+                                not required the unit that conflicts
+                                will be started and the unit that is
+                                conflicted is
+                                stopped.</para></listitem>
+                        </varlistentry>
+
+                        <varlistentry>
+                                <term><varname>Before=</varname></term>
+                                <term><varname>After=</varname></term>
+
+                                <listitem><para>Configures ordering
+                                dependencies between units. If a unit
+                                <filename>foo.service</filename>
+                                contains a setting
+                                <option>Before=bar.service</option>
+                                and both units are being started,
+                                <filename>bar.service</filename>'s
+                                start-up is delayed until
+                                <filename>foo.service</filename> is
+                                started up. Note that this setting is
+                                independent of and orthogonal to the
+                                requirement dependencies as configured
+                                by <varname>Requires=</varname>. It is
+                                a common pattern to include a unit
+                                name in both the
+                                <varname>After=</varname> and
+                                <varname>Requires=</varname> option in
+                                which case the unit listed will be
+                                started before the unit that is
+                                configured with these options. This
+                                option may be specified more than
+                                once, in which case ordering
+                                dependencies for all listed names are
+                                created. <varname>After=</varname> is
+                                the inverse of
+                                <varname>Before=</varname>, i.e. while
+                                <varname>After=</varname> ensures that
+                                the configured unit is started after
+                                the listed unit finished starting up,
+                                <varname>Before=</varname> ensures the
+                                opposite, i.e.  that the configured
+                                unit is fully started up before the
+                                listed unit is started. Note that when
+                                two units with an ordering dependency
+                                between them are shut down, the
+                                inverse of the start-up order is
+                                applied. i.e. if a unit is configured
+                                with <varname>After=</varname> on
+                                another unit, the former is stopped
+                                before the latter if both are shut
+                                down. If one unit with an ordering
+                                dependency on another unit is shut
+                                down while the latter is started up,
+                                the shut down is ordered before the
+                                start-up regardless whether the
+                                ordering dependency is actually of
+                                type <varname>After=</varname> or
+                                <varname>Before=</varname>. If two
+                                units have no ordering dependencies
+                                between them they are shut down
+                                resp. started up simultaneously, and
+                                no ordering takes
+                                place. </para></listitem>
+                        </varlistentry>
+
+                        <varlistentry>
+                                <term><varname>OnFailure=</varname></term>
+
+                                <listitem><para>Lists one or more
+                                units that are activated when this
+                                unit enters the
+                                '<literal>failed</literal>'
+                                state.</para></listitem>
+                        </varlistentry>
+
+                        <varlistentry>
+                                <term><varname>PropagateReloadTo=</varname></term>
+                                <term><varname>PropagateReloadFrom=</varname></term>
+
+                                <listitem><para>Lists one or more
+                                units where reload requests on the
+                                unit will be propagated to/on the
+                                other unit will be propagated
+                                from. Issuing a reload request on a
+                                unit will automatically also enqueue a
+                                reload request on all units that the
+                                reload request shall be propagated to
+                                via these two
+                                settings.</para></listitem>
+                        </varlistentry>
+
+                        <varlistentry>
+                                <term><varname>OnFailureIsolate=</varname></term>
+
+                                <listitem><para>Takes a boolean
+                                argument. If <option>true</option> the
+                                unit listed in
+                                <varname>OnFailure=</varname> will be
+                                enqueued in isolation mode, i.e. all
+                                units that are not its dependency will
+                                be stopped. If this is set only a
+                                single unit may be listed in
+                                <varname>OnFailure=</varname>. Defaults
+                                to
+                                <option>false</option>.</para></listitem>
+                        </varlistentry>
+
+                        <varlistentry>
+                                <term><varname>IgnoreOnIsolate=</varname></term>
+
+                                <listitem><para>Takes a boolean
+                                argument. If <option>true</option>
+                                this unit will not be stopped when
+                                isolating another unit. Defaults to
+                                <option>false</option>.</para></listitem>
+                        </varlistentry>
+
+                        <varlistentry>
+                                <term><varname>IgnoreOnSnapshot=</varname></term>
+
+                                <listitem><para>Takes a boolean
+                                argument. If <option>true</option>
+                                this unit will not be included in
+                                snapshots. Defaults to
+                                <option>true</option> for device and
+                                snapshot units, <option>false</option>
+                                for the others.</para></listitem>
+                        </varlistentry>
+
+                        <varlistentry>
+                                <term><varname>StopWhenUnneeded=</varname></term>
+
+                                <listitem><para>Takes a boolean
+                                argument. If <option>true</option>
+                                this unit will be stopped when it is
+                                no longer used. Note that in order to
+                                minimize the work to be executed,
+                                systemd will not stop units by default
+                                unless they are conflicting with other
+                                units, or the user explicitly
+                                requested their shut down. If this
+                                option is set, a unit will be
+                                automatically cleaned up if no other
+                                active unit requires it. Defaults to
+                                <option>false</option>.</para></listitem>
+                        </varlistentry>
+
+                        <varlistentry>
+                                <term><varname>RefuseManualStart=</varname></term>
+                                <term><varname>RefuseManualStop=</varname></term>
+
+                                <listitem><para>Takes a boolean
+                                argument. If <option>true</option>
+                                this unit can only be activated
+                                (resp. deactivated) indirectly. In
+                                this case explicit start-up
+                                (resp. termination) requested by the
+                                user is denied, however if it is
+                                started (resp. stopped) as a
+                                dependency of another unit, start-up
+                                (resp. termination) will succeed. This
+                                is mostly a safety feature to ensure
+                                that the user does not accidentally
+                                activate units that are not intended
+                                to be activated explicitly, and not
+                                accidentally deactivate units that are
+                                not intended to be deactivated.
+                                These options default to
+                                <option>false</option>.</para></listitem>
+                        </varlistentry>
+
+                        <varlistentry>
+                                <term><varname>AllowIsolate=</varname></term>
+
+                                <listitem><para>Takes a boolean
+                                argument. If <option>true</option>
+                                this unit may be used with the
+                                <command>systemctl isolate</command>
+                                command. Otherwise this will be
+                                refused. It probably is a good idea to
+                                leave this disabled except for target
+                                units that shall be used similar to
+                                runlevels in SysV init systems, just
+                                as a precaution to avoid unusable
+                                system states. This option defaults to
+                                <option>false</option>.</para></listitem>
+                        </varlistentry>
+
+                        <varlistentry>
+                                <term><varname>DefaultDependencies=</varname></term>
+
+                                <listitem><para>Takes a boolean
+                                argument. If <option>true</option>
+                                (the default), a few default
+                                dependencies will implicitly be
+                                created for the unit. The actual
+                                dependencies created depend on the
+                                unit type. For example, for service
+                                units, these dependencies ensure that
+                                the service is started only after
+                                basic system initialization is
+                                completed and is properly terminated on
+                                system shutdown. See the respective
+                                man pages for details. Generally, only
+                                services involved with early boot or
+                                late shutdown should set this option
+                                to <option>false</option>. It is
+                                highly recommended to leave this
+                                option enabled for the majority of
+                                common units. If set to
+                                <option>false</option> this option
+                                does not disable all implicit
+                                dependencies, just non-essential
+                                ones.</para></listitem>
+                        </varlistentry>
+
+                        <varlistentry>
+                                <term><varname>JobTimeoutSec=</varname></term>
+
+                                <listitem><para>When clients are
+                                waiting for a job of this unit to
+                                complete, time out after the specified
+                                time. If this time limit is reached
+                                the job will be cancelled, the unit
+                                however will not change state or even
+                                enter the '<literal>failed</literal>'
+                                mode. This value defaults to 0 (job
+                                timeouts disabled), except for device
+                                units. NB: this timeout is independent
+                                from any unit-specific timeout (for
+                                example, the timeout set with
+                                <varname>Timeout=</varname> in service
+                                units) as the job timeout has no
+                                effect on the unit itself, only on the
+                                job that might be pending for it. Or
+                                in other words: unit-specific timeouts
+                                are useful to abort unit state
+                                changes, and revert them. The job
+                                timeout set with this option however
+                                is useful to abort only the job
+                                waiting for the unit state to
+                                change.</para></listitem>
+                        </varlistentry>
+
+                        <varlistentry>
+                                <term><varname>ConditionPathExists=</varname></term>
+                                <term><varname>ConditionPathExistsGlob=</varname></term>
+                                <term><varname>ConditionPathIsDirectory=</varname></term>
+                                <term><varname>ConditionPathIsSymbolicLink=</varname></term>
+                                <term><varname>ConditionPathIsMountPoint=</varname></term>
+                                <term><varname>ConditionDirectoryNotEmpty=</varname></term>
+                                <term><varname>ConditionFileIsExecutable=</varname></term>
+                                <term><varname>ConditionKernelCommandLine=</varname></term>
+                                <term><varname>ConditionVirtualization=</varname></term>
+                                <term><varname>ConditionSecurity=</varname></term>
+                                <term><varname>ConditionCapability=</varname></term>
+                                <term><varname>ConditionNull=</varname></term>
+
+                                <listitem><para>Before starting a unit
+                                verify that the specified condition is
+                                true. With
+                                <varname>ConditionPathExists=</varname>
+                                a file existence condition can be
+                                checked before a unit is started. If
+                                the specified absolute path name does
+                                not exist, startup of a unit will not
+                                actually happen, however the unit is
+                                still useful for ordering purposes in
+                                this case. The condition is checked at
+                                the time the queued start job is to be
+                                executed. If the absolute path name
+                                passed to
+                                <varname>ConditionPathExists=</varname>
+                                is prefixed with an exclamation mark
+                                (!), the test is negated, and the unit
+                                is only started if the path does not
+                                exist.
+                                <varname>ConditionPathExistsGlob=</varname>
+                                works in a similar way, but checks for
+                                the existence of at least one file or
+                                directory matching the specified
+                                globbing
+                                pattern. <varname>ConditionPathIsDirectory=</varname>
+                                is similar to
+                                <varname>ConditionPathExists=</varname>
+                                but verifies whether a certain path
+                                exists and is a
+                                directory. <varname>ConditionPathIsSymbolicLink=</varname>
+                                is similar to
+                                <varname>ConditionPathExists=</varname>
+                                but verifies whether a certain path
+                                exists and is a symbolic
+                                link. <varname>ConditionPathIsMountPoint=</varname>
+                                is similar to
+                                <varname>ConditionPathExists=</varname>
+                                but verifies whether a certain path
+                                exists and is a mount
+                                point. <varname>ConditionFileIsExecutable=</varname>
+                                is similar to
+                                <varname>ConditionPathExists=</varname>
+                                but verifies whether a certain path
+                                exists, is a regular file and marked
+                                executable.
+                                <varname>ConditionDirectoryNotEmpty=</varname>
+                                is similar to
+                                <varname>ConditionPathExists=</varname>
+                                but verifies whether a certain path
+                                exists and is a non-empty
+                                directory. Similarly
+                                <varname>ConditionKernelCommandLine=</varname>
+                                may be used to check whether a
+                                specific kernel command line option is
+                                set (or if prefixed with the
+                                exclamation mark unset). The argument
+                                must either be a single word, or an
+                                assignment (i.e. two words, separated
+                                by the equality sign). In the former
+                                case the kernel command line is
+                                searched for the word appearing as is,
+                                or as left hand side of an
+                                assignment. In the latter case the
+                                exact assignment is looked for with
+                                right and left hand side
+                                matching. <varname>ConditionVirtualization=</varname>
+                                may be used to check whether the
+                                system is executed in a virtualized
+                                environment and optionally test
+                                whether it is a specific
+                                implementation. Takes either boolean
+                                value to check if being executed in
+                                any virtualized environment, or one of
+                                <varname>vm</varname> and
+                                <varname>container</varname> to test
+                                against a specific type of
+                                virtualization solution, or one of
+                                <varname>qemu</varname>,
+                                <varname>kvm</varname>,
+                                <varname>vmware</varname>,
+                                <varname>microsoft</varname>,
+                                <varname>oracle</varname>,
+                                <varname>xen</varname>,
+                                <varname>bochs</varname>,
+                                <varname>chroot</varname>,
+                                <varname>openvz</varname>,
+                                <varname>lxc</varname>,
+                                <varname>lxc-libvirt</varname>,
+                                <varname>systemd-nspawn</varname> to test
+                                against a specific implementation. If
+                                multiple virtualization technologies
+                                are nested only the innermost is
+                                considered. The test may be negated by
+                                prepending an exclamation mark.
+                                <varname>ConditionSecurity=</varname>
+                                may be used to check whether the given
+                                security module is enabled on the
+                                system.  Currently the only recognized
+                                value is <varname>selinux</varname>.
+                                The test may be negated by prepending
+                                an exclamation
+                                mark. <varname>ConditionCapability=</varname>
+                                may be used to check whether the given
+                                capability exists in the capability
+                                bounding set of the service manager
+                                (i.e. this does not check whether
+                                capability is actually available in
+                                the permitted or effective sets, see
+                                <citerefentry><refentrytitle>capabilities</refentrytitle><manvolnum>7</manvolnum></citerefentry>
+                                for details). Pass a capability name
+                                such as <literal>CAP_MKNOD</literal>,
+                                possibly prefixed with an exclamation
+                                mark to negate the check. Finally,
+                                <varname>ConditionNull=</varname> may
+                                be used to add a constant condition
+                                check value to the unit. It takes a
+                                boolean argument. If set to
+                                <varname>false</varname> the condition
+                                will always fail, otherwise
+                                succeed. If multiple conditions are
+                                specified the unit will be executed if
+                                all of them apply (i.e. a logical AND
+                                is applied). Condition checks can be
+                                prefixed with a pipe symbol (|) in
+                                which case a condition becomes a
+                                triggering condition. If at least one
+                                triggering condition is defined for a
+                                unit then the unit will be executed if
+                                at least one of the triggering
+                                conditions apply and all of the
+                                non-triggering conditions. If you
+                                prefix an argument with the pipe
+                                symbol and an exclamation mark the
+                                pipe symbol must be passed first, the
+                                exclamation second. Except for
+                                <varname>ConditionPathIsSymbolicLink=</varname>,
+                                all path checks follow
+                                symlinks.</para></listitem>
+                        </varlistentry>
+
+                        <varlistentry>
+                                <term><varname>Names=</varname></term>
+
+                                <listitem><para>Additional names for
+                                this unit. The names listed here must
+                                have the same suffix (i.e. type) as
+                                the unit file name. This option may be
+                                specified more than once, in which
+                                case all listed names are used. Note
+                                that this option is different from the
+                                <varname>Alias=</varname> option from
+                                the [Install] section mentioned
+                                below. See below for details. Note
+                                that in almost all cases this option
+                                is not what you want. A symlink alias
+                                in the file system is generally
+                                preferable since it can be used as
+                                lookup key. If a unit with a symlinked
+                                alias name is not loaded and needs to
+                                be it is easily found via the
+                                symlink. However, if a unit with an
+                                alias name configured with this
+                                setting is not loaded it will not be
+                                discovered. This settings' only use is
+                                in conjunction with service
+                                instances.</para>
+                                </listitem>
+                        </varlistentry>
+                </variablelist>
+
+                <para>Unit file may include a [Install] section, which
+                carries installation information for the unit. This
+                section is not interpreted by
+                <citerefentry><refentrytitle>systemd</refentrytitle><manvolnum>1</manvolnum></citerefentry>
+                during runtime. It is used exclusively by the
+                <command>enable</command> and
+                <command>disable</command> commands of the
+                <citerefentry><refentrytitle>systemctl</refentrytitle><manvolnum>1</manvolnum></citerefentry>
+                tool during installation of a unit:</para>
+
+                <variablelist>
+                        <varlistentry>
+                                <term><varname>Alias=</varname></term>
+
+                                <listitem><para>Additional names this
+                                unit shall be installed under. The
+                                names listed here must have the same
+                                suffix (i.e. type) as the unit file
+                                name. This option may be specified
+                                more than once, in which case all
+                                listed names are used. At installation
+                                time,
+                                <command>systemctl enable</command>
+                                will create symlinks from these names
+                                to the unit file name. Note that this
+                                is different from the
+                                <varname>Names=</varname> option from
+                                the [Unit] section mentioned above:
+                                The names from
+                                <varname>Names=</varname> apply
+                                unconditionally if the unit is
+                                loaded. The names from
+                                <varname>Alias=</varname> apply only
+                                if the unit has actually been
+                                installed with the
+                                <command>systemctl enable</command>
+                                command.  Also, if systemd searches for a
+                                unit, it will discover symlinked alias
+                                names as configured with
+                                <varname>Alias=</varname>, but not
+                                names configured with
+                                <varname>Names=</varname> only. It is
+                                a common pattern to list a name in
+                                both options. In this case, a unit
+                                will be active under all names if
+                                installed, but also if not installed
+                                but requested explicitly under its
+                                main name.</para></listitem>
+                        </varlistentry>
+
+                        <varlistentry>
+                                <term><varname>WantedBy=</varname></term>
+
+                                <listitem><para>Installs a symlink in
+                                the <filename>.wants/</filename>
+                                subdirectory for a unit. This has the
+                                effect that when the listed unit name
+                                is activated the unit listing it is
+                                activated
+                                too. <command>WantedBy=foo.service</command>
+                                in a service
+                                <filename>bar.service</filename> is
+                                mostly equivalent to
+                                <command>Alias=foo.service.wants/bar.service</command>
+                                in the same file.</para></listitem>
+                        </varlistentry>
+
+                        <varlistentry>
+                                <term><varname>Also=</varname></term>
+
+                                <listitem><para>Additional units to
+                                install when this unit is
+                                installed. If the user requests
+                                installation of a unit with this
+                                option configured,
+                                <command>systemctl enable</command>
+                                will automatically install units
+                                listed in this option as
+                                well.</para></listitem>
+                        </varlistentry>
+                </variablelist>
+
+        </refsect1>
+
+        <refsect1>
+                <title>See Also</title>
+                <para>
+                        <citerefentry><refentrytitle>systemd</refentrytitle><manvolnum>1</manvolnum></citerefentry>,
+                        <citerefentry><refentrytitle>systemctl</refentrytitle><manvolnum>8</manvolnum></citerefentry>,
+                        <citerefentry><refentrytitle>systemd.special</refentrytitle><manvolnum>7</manvolnum></citerefentry>,
+                        <citerefentry><refentrytitle>systemd.service</refentrytitle><manvolnum>5</manvolnum></citerefentry>,
+                        <citerefentry><refentrytitle>systemd.socket</refentrytitle><manvolnum>5</manvolnum></citerefentry>,
+                        <citerefentry><refentrytitle>systemd.device</refentrytitle><manvolnum>5</manvolnum></citerefentry>,
+                        <citerefentry><refentrytitle>systemd.mount</refentrytitle><manvolnum>5</manvolnum></citerefentry>,
+                        <citerefentry><refentrytitle>systemd.automount</refentrytitle><manvolnum>5</manvolnum></citerefentry>,
+                        <citerefentry><refentrytitle>systemd.swap</refentrytitle><manvolnum>5</manvolnum></citerefentry>,
+                        <citerefentry><refentrytitle>systemd.target</refentrytitle><manvolnum>5</manvolnum></citerefentry>,
+                        <citerefentry><refentrytitle>systemd.path</refentrytitle><manvolnum>5</manvolnum></citerefentry>,
+                        <citerefentry><refentrytitle>systemd.timer</refentrytitle><manvolnum>5</manvolnum></citerefentry>,
+                        <citerefentry><refentrytitle>systemd.snapshot</refentrytitle><manvolnum>5</manvolnum></citerefentry>,
+                        <citerefentry><refentrytitle>capabilities</refentrytitle><manvolnum>7</manvolnum></citerefentry>
+                </para>
+        </refsect1>
+
+</refentry>
diff --git a/man/systemd.xml b/man/systemd.xml
new file mode 100644 (file)
index 0000000..aef65e3
--- /dev/null
@@ -0,0 +1,1167 @@
+<?xml version='1.0'?> <!--*-nxml-*-->
+<!DOCTYPE refentry PUBLIC "-//OASIS//DTD DocBook XML V4.2//EN"
+        "http://www.oasis-open.org/docbook/xml/4.2/docbookx.dtd">
+
+<!--
+  This file is part 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 <http://www.gnu.org/licenses/>.
+-->
+
+<refentry id="systemd">
+
+        <refentryinfo>
+                <title>systemd</title>
+                <productname>systemd</productname>
+
+                <authorgroup>
+                        <author>
+                                <contrib>Developer</contrib>
+                                <firstname>Lennart</firstname>
+                                <surname>Poettering</surname>
+                                <email>lennart@poettering.net</email>
+                        </author>
+                </authorgroup>
+        </refentryinfo>
+
+        <refmeta>
+                <refentrytitle>systemd</refentrytitle>
+                <manvolnum>1</manvolnum>
+        </refmeta>
+
+        <refnamediv>
+                <refname>systemd</refname>
+                <refname>init</refname>
+                <refpurpose>systemd System and Service Manager</refpurpose>
+        </refnamediv>
+
+        <refsynopsisdiv>
+                <cmdsynopsis>
+                        <command>systemd <arg choice="opt" rep="repeat">OPTIONS</arg></command>
+                </cmdsynopsis>
+                <cmdsynopsis>
+                        <command>init <arg choice="opt" rep="repeat">OPTIONS</arg> <arg choice="req">COMMAND</arg></command>
+                </cmdsynopsis>
+        </refsynopsisdiv>
+
+        <refsect1>
+                <title>Description</title>
+
+                <para>systemd is a system and service manager for
+                Linux operating systems. When run as first process on
+                boot (as PID 1), it acts as init system that brings
+                up and maintains userspace services.</para>
+
+                <para>For compatibility with SysV, if systemd is called
+                as <command>init</command> and a PID that is not
+                1, it will execute <command>telinit</command> and pass
+                all command line arguments unmodified. That means
+                <command>init</command> and <command>telinit</command>
+                are mostly equivalent when invoked from normal login sessions. See
+                <citerefentry><refentrytitle>telinit</refentrytitle><manvolnum>8</manvolnum></citerefentry>
+                for more information.</para>
+
+                <para>When run as system instance, systemd interprets
+                the configuration file
+                <filename>system.conf</filename>, otherwise
+                <filename>user.conf</filename>. See
+                <citerefentry><refentrytitle>systemd.conf</refentrytitle><manvolnum>5</manvolnum></citerefentry>
+                for more information.</para>
+        </refsect1>
+
+        <refsect1>
+                <title>Options</title>
+
+                <para>The following options are understood:</para>
+
+                <variablelist>
+                        <varlistentry>
+                                <term><option>-h</option></term>
+                                <term><option>--help</option></term>
+
+                                <listitem><para>Prints a short help
+                                text and exits.</para></listitem>
+                        </varlistentry>
+                        <varlistentry>
+                                <term><option>--test</option></term>
+
+                                <listitem><para>Determine startup
+                                sequence, dump it and exit. This is an
+                                option useful for debugging
+                                only.</para></listitem>
+                        </varlistentry>
+                        <varlistentry>
+                                <term><option>--dump-configuration-items</option></term>
+
+                                <listitem><para>Dump understood unit
+                                configuration items. This outputs a
+                                terse but complete list of
+                                configuration items understood in unit
+                                definition files.</para></listitem>
+                        </varlistentry>
+                        <varlistentry>
+                                <term><option>--introspect=</option></term>
+
+                                <listitem><para>Extract D-Bus
+                                interface introspection data. This is
+                                mostly useful at install time
+                                to generate data suitable for the
+                                D-Bus interfaces
+                                repository. Optionally the interface
+                                name for the introspection data may be
+                                specified. If omitted, the
+                                introspection data for all interfaces
+                                is dumped.</para></listitem>
+                        </varlistentry>
+                        <varlistentry>
+                                <term><option>--unit=</option></term>
+
+                                <listitem><para>Set default unit to
+                                activate on startup. If not specified
+                                defaults to
+                                <filename>default.target</filename>.</para></listitem>
+                        </varlistentry>
+                        <varlistentry>
+                                <term><option>--system</option></term>
+                                <term><option>--user</option></term>
+
+                                <listitem><para>Tell systemd to run a
+                                system instance (resp. user
+                                instance), even if the process ID is
+                                not 1 (resp. is 1), i.e. systemd is
+                                not (resp. is) run as init process.
+                                Normally it should not be necessary to
+                                pass these options, as systemd
+                                automatically detects the mode it is
+                                started in. These options are hence of
+                                little use except for debugging. Note
+                                that it is not supported booting and
+                                maintaining a full system with systemd
+                                running in <option>--system</option>
+                                mode, but PID not 1. In practice,
+                                passing <option>--system</option> explicitly is
+                                only useful in conjunction with
+                                <option>--test</option>.</para></listitem>
+                        </varlistentry>
+                        <varlistentry>
+                                <term><option>--dump-core</option></term>
+
+                                <listitem><para>Dump core on
+                                crash. This switch has no effect when
+                                run as user
+                                instance.</para></listitem>
+                        </varlistentry>
+                        <varlistentry>
+                                <term><option>--crash-shell</option></term>
+
+                                <listitem><para>Run shell on
+                                crash. This switch has no effect when
+                                run as user
+                                instance.</para></listitem>
+                        </varlistentry>
+                        <varlistentry>
+                                <term><option>--confirm-spawn</option></term>
+
+                                <listitem><para>Ask for confirmation
+                                when spawning processes. This switch
+                                has no effect when run as user
+                                instance.</para></listitem>
+                        </varlistentry>
+                        <varlistentry>
+                                <term><option>--show-status=</option></term>
+
+                                <listitem><para>Show terse service
+                                status information while booting. This
+                                switch has no effect when run as user
+                                instance. Takes a boolean argument
+                                which may be omitted which is
+                                interpreted as
+                                <option>true</option>.</para></listitem>
+                        </varlistentry>
+                        <varlistentry>
+                                <term><option>--sysv-console=</option></term>
+
+                                <listitem><para>Controls whether
+                                output of SysV init scripts will be
+                                directed to the console. This switch
+                                has no effect when run as user
+                                instance. Takes a boolean argument
+                                which may be omitted which is
+                                interpreted as
+                                <option>true</option>.</para></listitem>
+                        </varlistentry>
+                        <varlistentry>
+                                <term><option>--log-target=</option></term>
+
+                                <listitem><para>Set log
+                                target. Argument must be one of
+                                <option>console</option>,
+                                <option>journal</option>,
+                                <option>syslog</option>,
+                                <option>kmsg</option>,
+                                <option>journal-or-kmsg</option>,
+                                <option>syslog-or-kmsg</option>,
+                                <option>null</option>.</para></listitem>
+                        </varlistentry>
+                        <varlistentry>
+                                <term><option>--log-level=</option></term>
+
+                                <listitem><para>Set log level. As
+                                argument this accepts a numerical log
+                                level or the well-known <citerefentry><refentrytitle>syslog</refentrytitle><manvolnum>3</manvolnum></citerefentry>
+                                symbolic names (lowercase):
+                                <option>emerg</option>,
+                                <option>alert</option>,
+                                <option>crit</option>,
+                                <option>err</option>,
+                                <option>warning</option>,
+                                <option>notice</option>,
+                                <option>info</option>,
+                                <option>debug</option>.</para></listitem>
+                        </varlistentry>
+                        <varlistentry>
+                                <term><option>--log-color=</option></term>
+
+                                <listitem><para>Highlight important
+                                log messages. Argument is a boolean
+                                value. If the argument is omitted it
+                                defaults to
+                                <option>true</option>.</para></listitem>
+                        </varlistentry>
+                        <varlistentry>
+                                <term><option>--log-location=</option></term>
+
+                                <listitem><para>Include code location
+                                in log messages. This is mostly
+                                relevant for debugging
+                                purposes. Argument is a boolean
+                                value. If the argument is omitted
+                                it defaults to
+                                <option>true</option>.</para></listitem>
+                        </varlistentry>
+                        <varlistentry>
+                                <term><option>--default-standard-output=</option></term>
+                                <term><option>--default-standard-error=</option></term>
+
+                                <listitem><para>Sets the default
+                                output resp. error output for all
+                                services and sockets, i.e. controls
+                                the default for
+                                <option>StandardOutput=</option>
+                                resp. <option>StandardError=</option>
+                                (see
+                                <citerefentry><refentrytitle>systemd.exec</refentrytitle><manvolnum>5</manvolnum></citerefentry>
+                                for details). Takes one of
+                                <option>inherit</option>,
+                                <option>null</option>,
+                                <option>tty</option>,
+                                <option>journal</option>,
+                                <option>journal+console</option>,
+                                <option>syslog</option>,
+                                <option>syslog+console</option>,
+                                <option>kmsg</option>,
+                                <option>kmsg+console</option>.  If the
+                                argument is omitted
+                                <option>--default-standard-output=</option>
+                                defaults to <option>journal</option>
+                                and
+                                <option>--default-standard-error=</option>
+                                to
+                                <option>inherit</option>.</para></listitem>
+                        </varlistentry>
+                </variablelist>
+        </refsect1>
+
+        <refsect1>
+                <title>Concepts</title>
+
+                <para>systemd provides a dependency system between
+                various entities called "units". Units encapsulate
+                various objects that are relevant for system boot-up
+                and maintenance. The majority of units are configured
+                in unit configuration files, whose syntax and basic
+                set of options is described in
+                <citerefentry><refentrytitle>systemd.unit</refentrytitle><manvolnum>5</manvolnum></citerefentry>,
+                however some are created automatically from other
+                configuration or dynamically from system state. Units
+                may be 'active' (meaning started, bound, plugged in,
+                ...  depending on the unit type, see below), or
+                'inactive' (meaning stopped, unbound, unplugged, ...),
+                as well as in the process of being activated or
+                deactivated, i.e. between the two states (these states
+                are called 'activating', 'deactivating'). A special
+                'failed' state is available as well which is very
+                similar to 'inactive' and is entered when the service
+                failed in some way (process returned error code on
+                exit, or crashed, or an operation timed out). If this
+                state is entered the cause will be logged, for later
+                reference. Note that the various unit types may have a
+                number of additional substates, which are mapped to
+                the five generalized unit states described
+                here.</para>
+
+                <para>The following unit types are available:</para>
+
+                <orderedlist>
+                        <listitem><para>Service units, which control
+                        daemons and the processes they consist of. For
+                        details see
+                        <citerefentry><refentrytitle>systemd.service</refentrytitle><manvolnum>5</manvolnum></citerefentry>.</para></listitem>
+
+                        <listitem><para>Socket units, which
+                        encapsulate local IPC or network sockets in
+                        the system, useful for socket-based
+                        activation. For details about socket units see
+                        <citerefentry><refentrytitle>systemd.socket</refentrytitle><manvolnum>5</manvolnum></citerefentry>,
+                        for details on socket-based activation and
+                        other forms of activation, see
+                        <citerefentry><refentrytitle>daemon</refentrytitle><manvolnum>7</manvolnum></citerefentry>.</para></listitem>
+
+                        <listitem><para>Target units are useful to
+                        group units, or provide well-known
+                        synchronization points during boot-up, see
+                        <citerefentry><refentrytitle>systemd.target</refentrytitle><manvolnum>5</manvolnum></citerefentry>.</para></listitem>
+
+                        <listitem><para>Device units expose kernel
+                        devices in systemd and may be used to
+                        implement device-based activation. For details
+                        see
+                        <citerefentry><refentrytitle>systemd.device</refentrytitle><manvolnum>5</manvolnum></citerefentry>.</para></listitem>
+
+                        <listitem><para>Mount units control mount
+                        points in the file system, for details see
+                        <citerefentry><refentrytitle>systemd.mount</refentrytitle><manvolnum>5</manvolnum></citerefentry>.</para></listitem>
+
+                        <listitem><para>Automount units provide
+                        automount capabilities, for on-demand mounting
+                        of file systems as well as parallelized
+                        boot-up. See
+                        <citerefentry><refentrytitle>systemd.automount</refentrytitle><manvolnum>5</manvolnum></citerefentry>.</para></listitem>
+
+                        <listitem><para>Snapshot units can be used to
+                        temporarily save the state of the set of
+                        systemd units, which later may be restored by
+                        activating the saved snapshot unit. For more
+                        information see
+                        <citerefentry><refentrytitle>systemd.snapshot</refentrytitle><manvolnum>5</manvolnum></citerefentry>.</para></listitem>
+
+                        <listitem><para>Timer units are useful for
+                        triggering activation of other units based on
+                        timers. You may find details in
+                        <citerefentry><refentrytitle>systemd.timer</refentrytitle><manvolnum>5</manvolnum></citerefentry>.</para></listitem>
+
+                        <listitem><para>Swap units are very similar to
+                        mount units and encapsulate memory swap
+                        partitions or files of the operating
+                        system. They are described in <citerefentry><refentrytitle>systemd.swap</refentrytitle><manvolnum>5</manvolnum></citerefentry>.</para></listitem>
+
+                        <listitem><para>Path units may be used
+                        to activate other services when file system
+                        objects change or are modified. See
+                        <citerefentry><refentrytitle>systemd.path</refentrytitle><manvolnum>5</manvolnum></citerefentry>.</para></listitem>
+
+                </orderedlist>
+
+                <para>Units are named as their configuration
+                files. Some units have special semantics. A detailed
+                list is available in
+                <citerefentry><refentrytitle>systemd.special</refentrytitle><manvolnum>7</manvolnum></citerefentry>.</para>
+
+                <para>systemd knows various kinds of dependencies,
+                including positive and negative requirement
+                dependencies (i.e. <varname>Requires=</varname> and
+                <varname>Conflicts=</varname>) as well as ordering
+                dependencies (<varname>After=</varname> and
+                <varname>Before=</varname>). NB: ordering and
+                requirement dependencies are orthogonal. If only a
+                requirement dependency exists between two units
+                (e.g. <filename>foo.service</filename> requires
+                <filename>bar.service</filename>), but no ordering
+                dependency (e.g. <filename>foo.service</filename>
+                after <filename>bar.service</filename>) and both are
+                requested to start, they will be started in
+                parallel. It is a common pattern that both requirement
+                and ordering dependencies are placed between two
+                units. Also note that the majority of dependencies are
+                implicitly created and maintained by systemd. In most
+                cases it should be unnecessary to declare additional
+                dependencies manually, however it is possible to do
+                this.</para>
+
+                <para>Application programs and units (via
+                dependencies) may request state changes of units. In
+                systemd, these requests are encapsulated as 'jobs' and
+                maintained in a job queue. Jobs may succeed or can
+                fail, their execution is ordered based on the ordering
+                dependencies of the units they have been scheduled
+                for.</para>
+
+                <para>On boot systemd activates the target unit
+                <filename>default.target</filename> whose job is to
+                activate on-boot services and other on-boot units by
+                pulling them in via dependencies. Usually the unit
+                name is just an alias (symlink) for either
+                <filename>graphical.target</filename> (for
+                fully-featured boots into the UI) or
+                <filename>multi-user.target</filename> (for limited
+                console-only boots for use in embedded or server
+                environments, or similar; a subset of
+                graphical.target). However it is at the discretion of
+                the administrator to configure it as an alias to any
+                other target unit. See
+                <citerefentry><refentrytitle>systemd.special</refentrytitle><manvolnum>7</manvolnum></citerefentry>
+                for details about these target units.</para>
+
+                <para>Processes systemd spawns are placed in
+                individual Linux control groups named after the unit
+                which they belong to in the private systemd
+                hierarchy. (see <ulink
+                url="http://www.kernel.org/doc/Documentation/cgroups/cgroups.txt">cgroups.txt</ulink>
+                for more information about control groups, or short
+                "cgroups"). systemd uses this to effectively keep
+                track of processes. Control group information is
+                maintained in the kernel, and is accessible via the
+                file system hierarchy (beneath
+                <filename>/sys/fs/cgroup/systemd/</filename>), or in tools
+                such as
+                <citerefentry><refentrytitle>ps</refentrytitle><manvolnum>1</manvolnum></citerefentry>
+                (<command>ps xawf -eo pid,user,cgroup,args</command>
+                is particularly useful to list all processes and the
+                systemd units they belong to.).</para>
+
+                <para>systemd is compatible with the SysV init system
+                to a large degree: SysV init scripts are supported and
+                simply read as an alternative (though limited)
+                configuration file format. The SysV
+                <filename>/dev/initctl</filename> interface is
+                provided, and compatibility implementations of the
+                various SysV client tools are available. In addition to
+                that, various established Unix functionality such as
+                <filename>/etc/fstab</filename> or the
+                <filename>utmp</filename> database are
+                supported.</para>
+
+                <para>systemd has a minimal transaction system: if a
+                unit is requested to start up or shut down it will add
+                it and all its dependencies to a temporary
+                transaction. Then, it will verify if the transaction
+                is consistent (i.e. whether the ordering of all units
+                is cycle-free). If it is not, systemd will try to fix
+                it up, and removes non-essential jobs from the
+                transaction that might remove the loop. Also, systemd
+                tries to suppress non-essential jobs in the
+                transaction that would stop a running service. Finally
+                it is checked whether the jobs of the transaction
+                contradict jobs that have already been queued, and
+                optionally the transaction is aborted then. If all
+                worked out and the transaction is consistent and
+                minimized in its impact it is merged with all already
+                outstanding jobs and added to the run
+                queue. Effectively this means that before executing a
+                requested operation, systemd will verify that it makes
+                sense, fixing it if possible, and only failing if it
+                really cannot work.</para>
+
+                <para>Systemd contains native implementations of
+                various tasks that need to be executed as part of the
+                boot process. For example, it sets the host name or
+                configures the loopback network device. It also sets
+                up and mounts various API file systems, such as
+                <filename>/sys</filename> or
+                <filename>/proc</filename>.</para>
+
+                <para>For more information about the concepts and
+                ideas behind systemd please refer to the <ulink
+                url="http://0pointer.de/blog/projects/systemd.html">Original
+                Design Document</ulink>.</para>
+
+                <para>Note that some but not all interfaces provided
+                by systemd are covered by the <ulink
+                url="http://www.freedesktop.org/wiki/Software/systemd/InterfaceStabilityPromise">Interface
+                Stability Promise</ulink>.</para>
+        </refsect1>
+
+        <refsect1>
+                <title>Directories</title>
+
+                <variablelist>
+                        <varlistentry>
+                                <term>System unit directories</term>
+
+                                <listitem><para>The systemd system
+                                manager reads unit configuration from
+                                various directories. Packages that
+                                want to install unit files shall place
+                                them in the directory returned by
+                                <command>pkg-config systemd
+                                --variable=systemdsystemunitdir</command>. Other
+                                directories checked are
+                                <filename>/usr/local/lib/systemd/system</filename>
+                                and
+                                <filename>/usr/lib/systemd/system</filename>. User
+                                configuration always takes
+                                precedence. <command>pkg-config
+                                systemd
+                                --variable=systemdsystemconfdir</command>
+                                returns the path of the system
+                                configuration directory. Packages
+                                should alter the content of these
+                                directories only with the
+                                <command>enable</command> and
+                                <command>disable</command> commands of
+                                the
+                                <citerefentry><refentrytitle>systemctl</refentrytitle><manvolnum>1</manvolnum></citerefentry>
+                                tool.</para></listitem>
+                        </varlistentry>
+                </variablelist>
+
+                <variablelist>
+                        <varlistentry>
+                                <term>User unit directories</term>
+
+                                <listitem><para>Similar rules apply
+                                for the user unit
+                                directories. However, here the <ulink
+                                url="http://standards.freedesktop.org/basedir-spec/basedir-spec-latest.html">XDG
+                                Base Directory specification</ulink>
+                                is followed to find
+                                units. Applications should place their
+                                unit files in the directory returned
+                                by <command>pkg-config systemd
+                                --variable=systemduserunitdir</command>. Global
+                                configuration is done in the directory
+                                reported by <command>pkg-config
+                                systemd
+                                --variable=systemduserconfdir</command>. The
+                                <command>enable</command> and
+                                <command>disable</command> commands of
+                                the
+                                <citerefentry><refentrytitle>systemctl</refentrytitle><manvolnum>1</manvolnum></citerefentry>
+                                tool can handle both global (i.e. for
+                                all users) and private (for one user)
+                                enabling/disabling of
+                                units.</para></listitem>
+                        </varlistentry>
+                </variablelist>
+
+                <variablelist>
+                        <varlistentry>
+                                <term>SysV init scripts directory</term>
+
+                                <listitem><para>The location of the
+                                SysV init script directory varies
+                                between distributions. If systemd
+                                cannot find a native unit file for a
+                                requested service, it will look for a
+                                SysV init script of the same name
+                                (with the
+                                <filename>.service</filename> suffix
+                                removed).</para></listitem>
+                        </varlistentry>
+                </variablelist>
+
+                <variablelist>
+                        <varlistentry>
+                                <term>SysV runlevel link farm directory</term>
+
+                                <listitem><para>The location of the
+                                SysV runlevel link farm directory
+                                varies between distributions. systemd
+                                will take the link farm into account
+                                when figuring out whether a service
+                                shall be enabled. Note that a service
+                                unit with a native unit configuration
+                                file cannot be started by activating it
+                                in the SysV runlevel link
+                                farm.</para></listitem>
+                        </varlistentry>
+                </variablelist>
+        </refsect1>
+
+        <refsect1>
+                <title>Signals</title>
+
+                <variablelist>
+                        <varlistentry>
+                                <term>SIGTERM</term>
+
+                                <listitem><para>Upon receiving this
+                                signal the systemd system manager
+                                serializes its state, reexecutes
+                                itself and deserializes the saved
+                                state again. This is mostly equivalent
+                                to <command>systemctl
+                                daemon-reexec</command>.</para>
+
+                                <para>systemd user managers will
+                                start the
+                                <filename>exit.target</filename> unit
+                                when this signal is received. This is
+                                mostly equivalent to
+                                <command>systemctl --user start
+                                exit.target</command>.</para></listitem>
+                        </varlistentry>
+
+                        <varlistentry>
+                                <term>SIGINT</term>
+
+                                <listitem><para>Upon receiving this
+                                signal the systemd system manager will
+                                start the
+                                <filename>ctrl-alt-del.target</filename> unit. This
+                                is mostly equivalent to
+                                <command>systemctl start
+                                ctl-alt-del.target</command>.</para>
+
+                                <para>systemd user managers
+                                treat this signal the same way as
+                                SIGTERM.</para></listitem>
+                        </varlistentry>
+
+                        <varlistentry>
+                                <term>SIGWINCH</term>
+
+                                <listitem><para>When this signal is
+                                received the systemd system manager
+                                will start the
+                                <filename>kbrequest.target</filename>
+                                unit. This is mostly equivalent to
+                                <command>systemctl start
+                                kbrequest.target</command>.</para>
+
+                                <para>This signal is ignored by
+                                systemd user
+                                managers.</para></listitem>
+                        </varlistentry>
+
+                        <varlistentry>
+                                <term>SIGPWR</term>
+
+                                <listitem><para>When this signal is
+                                received the systemd manager
+                                will start the
+                                <filename>sigpwr.target</filename>
+                                unit. This is mostly equivalent to
+                                <command>systemctl start
+                                sigpwr.target</command>.</para></listitem>
+                        </varlistentry>
+
+                        <varlistentry>
+                                <term>SIGUSR1</term>
+
+                                <listitem><para>When this signal is
+                                received the systemd manager will try
+                                to reconnect to the D-Bus
+                                bus.</para></listitem>
+                        </varlistentry>
+
+                        <varlistentry>
+                                <term>SIGUSR2</term>
+
+                                <listitem><para>When this signal is
+                                received the systemd manager will log
+                                its complete state in human readable
+                                form. The data logged is the same as
+                                printed by <command>systemctl
+                                dump</command>.</para></listitem>
+                        </varlistentry>
+
+                        <varlistentry>
+                                <term>SIGHUP</term>
+
+                                <listitem><para>Reloads the complete
+                                daemon configuration. This is mostly
+                                equivalent to <command>systemctl
+                                daemon-reload</command>.</para></listitem>
+                        </varlistentry>
+
+                        <varlistentry>
+                                <term>SIGRTMIN+0</term>
+
+                                <listitem><para>Enters default mode, starts the
+                                <filename>default.target</filename>
+                                unit. This is mostly equivalent to
+                                <command>systemctl start
+                                default.target</command>.</para></listitem>
+                        </varlistentry>
+
+                        <varlistentry>
+                                <term>SIGRTMIN+1</term>
+
+                                <listitem><para>Enters rescue mode,
+                                starts the
+                                <filename>rescue.target</filename>
+                                unit. This is mostly equivalent to
+                                <command>systemctl isolate
+                                rescue.target</command>.</para></listitem>
+                        </varlistentry>
+
+                        <varlistentry>
+                                <term>SIGRTMIN+2</term>
+
+                                <listitem><para>Enters emergency mode,
+                                starts the
+                                <filename>emergency.service</filename>
+                                unit. This is mostly equivalent to
+                                <command>systemctl isolate
+                                emergency.service</command>.</para></listitem>
+                        </varlistentry>
+
+                        <varlistentry>
+                                <term>SIGRTMIN+3</term>
+
+                                <listitem><para>Halts the machine,
+                                starts the
+                                <filename>halt.target</filename>
+                                unit. This is mostly equivalent to
+                                <command>systemctl start
+                                halt.target</command>.</para></listitem>
+                        </varlistentry>
+
+                        <varlistentry>
+                                <term>SIGRTMIN+4</term>
+
+                                <listitem><para>Powers off the machine,
+                                starts the
+                                <filename>poweroff.target</filename>
+                                unit. This is mostly equivalent to
+                                <command>systemctl start
+                                poweroff.target</command>.</para></listitem>
+                        </varlistentry>
+
+                        <varlistentry>
+                                <term>SIGRTMIN+5</term>
+
+                                <listitem><para>Reboots the machine,
+                                starts the
+                                <filename>reboot.target</filename>
+                                unit. This is mostly equivalent to
+                                <command>systemctl start
+                                reboot.target</command>.</para></listitem>
+                        </varlistentry>
+
+                        <varlistentry>
+                                <term>SIGRTMIN+6</term>
+
+                                <listitem><para>Reboots the machine via kexec,
+                                starts the
+                                <filename>kexec.target</filename>
+                                unit. This is mostly equivalent to
+                                <command>systemctl start
+                                kexec.target</command>.</para></listitem>
+                        </varlistentry>
+
+                        <varlistentry>
+                                <term>SIGRTMIN+13</term>
+
+                                <listitem><para>Immediately halts the machine.</para></listitem>
+                        </varlistentry>
+
+                        <varlistentry>
+                                <term>SIGRTMIN+14</term>
+
+                                <listitem><para>Immediately powers off the machine.</para></listitem>
+                        </varlistentry>
+
+                        <varlistentry>
+                                <term>SIGRTMIN+15</term>
+
+                                <listitem><para>Immediately reboots the machine.</para></listitem>
+                        </varlistentry>
+
+                        <varlistentry>
+                                <term>SIGRTMIN+16</term>
+
+                                <listitem><para>Immediately reboots the machine with kexec.</para></listitem>
+                        </varlistentry>
+
+                        <varlistentry>
+                                <term>SIGRTMIN+20</term>
+
+                                <listitem><para>Enables display of
+                                status messages on the console, as
+                                controlled via
+                                <varname>systemd.show_status=1</varname>
+                                on the kernel command
+                                line.</para></listitem>
+                        </varlistentry>
+
+                        <varlistentry>
+                                <term>SIGRTMIN+21</term>
+
+                                <listitem><para>Disables display of
+                                status messages on the console, as
+                                controlled via
+                                <varname>systemd.show_status=0</varname>
+                                on the kernel command
+                                line.</para></listitem>
+                        </varlistentry>
+
+                        <varlistentry>
+                                <term>SIGRTMIN+22</term>
+                                <term>SIGRTMIN+23</term>
+
+                                <listitem><para>Sets the log level to
+                                <literal>debug</literal>
+                                (resp. <literal>info</literal> on
+                                <literal>SIGRTMIN+23</literal>), as
+                                controlled via
+                                <varname>systemd.log_level=debug</varname>
+                                (resp. <varname>systemd.log_level=info</varname>
+                                on <literal>SIGRTMIN+23</literal>) on
+                                the kernel command
+                                line.</para></listitem>
+                        </varlistentry>
+
+                        <varlistentry>
+                                <term>SIGRTMIN+26</term>
+                                <term>SIGRTMIN+27</term>
+                                <term>SIGRTMIN+28</term>
+                                <term>SIGRTMIN+29</term>
+
+                                <listitem><para>Sets the log level to
+                                <literal>journal-or-kmsg</literal>
+                                (resp. <literal>console</literal> on
+                                <literal>SIGRTMIN+27</literal>;
+                                resp. <literal>kmsg</literal> on
+                                <literal>SIGRTMIN+28</literal>;
+                                resp. <literal>syslog-or-kmsg</literal>
+                                on <literal>SIGRTMIN+29</literal>), as
+                                controlled via
+                                <varname>systemd.log_target=journal-or-kmsg</varname>
+                                (resp. <varname>systemd.log_target=console</varname>
+                                on <literal>SIGRTMIN+27</literal>;
+                                resp. <varname>systemd.log_target=kmsg</varname>
+                                on <literal>SIGRTMIN+28</literal>;
+                                resp
+                                <varname>systemd.log_target=syslog-or-kmsg</varname>
+                                on <literal>SIGRTMIN+29</literal>) on
+                                the kernel command
+                                line.</para></listitem>
+                        </varlistentry>
+                </variablelist>
+        </refsect1>
+
+        <refsect1>
+                <title>Environment</title>
+
+                <variablelist>
+                        <varlistentry>
+                                <term><varname>$SYSTEMD_LOG_LEVEL</varname></term>
+                                <listitem><para>systemd reads the
+                                log level from this environment
+                                variable. This can be overridden with
+                                <option>--log-level=</option>.</para></listitem>
+                        </varlistentry>
+
+                        <varlistentry>
+                                <term><varname>$SYSTEMD_LOG_TARGET</varname></term>
+                                <listitem><para>systemd reads the
+                                log target from this environment
+                                variable. This can be overridden with
+                                <option>--log-target=</option>.</para></listitem>
+                        </varlistentry>
+
+                        <varlistentry>
+                                <term><varname>$SYSTEMD_LOG_COLOR</varname></term>
+                                <listitem><para>Controls whether
+                                systemd highlights important log
+                                messages. This can be overridden with
+                                <option>--log-color=</option>.</para></listitem>
+                        </varlistentry>
+
+                        <varlistentry>
+                                <term><varname>$SYSTEMD_LOG_LOCATION</varname></term>
+                                <listitem><para>Controls whether
+                                systemd prints the code location along
+                                with log messages. This can be
+                                overridden with
+                                <option>--log-location=</option>.</para></listitem>
+                        </varlistentry>
+
+                        <varlistentry>
+                                <term><varname>$XDG_CONFIG_HOME</varname></term>
+                                <term><varname>$XDG_CONFIG_DIRS</varname></term>
+                                <term><varname>$XDG_DATA_HOME</varname></term>
+                                <term><varname>$XDG_DATA_DIRS</varname></term>
+
+                                <listitem><para>The systemd user
+                                manager uses these variables in
+                                accordance to the <ulink
+                                url="http://standards.freedesktop.org/basedir-spec/basedir-spec-latest.html">XDG
+                                Base Directory specification</ulink>
+                                to find its configuration.</para></listitem>
+                        </varlistentry>
+
+                        <varlistentry>
+                                <term><varname>$SYSTEMD_UNIT_PATH</varname></term>
+
+                                <listitem><para>Controls where systemd
+                                looks for unit
+                                files.</para></listitem>
+                        </varlistentry>
+
+                        <varlistentry>
+                                <term><varname>$SYSTEMD_SYSVINIT_PATH</varname></term>
+
+                                <listitem><para>Controls where systemd
+                                looks for SysV init scripts.</para></listitem>
+                        </varlistentry>
+
+                        <varlistentry>
+                                <term><varname>$SYSTEMD_SYSVRCND_PATH</varname></term>
+
+                                <listitem><para>Controls where systemd
+                                looks for SysV init script runlevel link
+                                farms.</para></listitem>
+                        </varlistentry>
+
+                        <varlistentry>
+                                <term><varname>$LISTEN_PID</varname></term>
+                                <term><varname>$LISTEN_FDS</varname></term>
+
+                                <listitem><para>Set by systemd for
+                                supervised processes during
+                                socket-based activation. See
+                                <citerefentry><refentrytitle>sd_listen_fds</refentrytitle><manvolnum>3</manvolnum></citerefentry>
+                                for more information.
+                                </para></listitem>
+                        </varlistentry>
+
+                        <varlistentry>
+                                <term><varname>$NOTIFY_SOCKET</varname></term>
+
+                                <listitem><para>Set by systemd for
+                                supervised processes for status and
+                                start-up completion notification. See
+                                <citerefentry><refentrytitle>sd_notify</refentrytitle><manvolnum>3</manvolnum></citerefentry>
+                                for more information.
+                                </para></listitem>
+                        </varlistentry>
+                </variablelist>
+        </refsect1>
+
+        <refsect1>
+                <title>Kernel Command Line</title>
+
+                <para>When run as system instance systemd parses a
+                number of kernel command line
+                arguments<footnote><para>If run inside a Linux
+                container these arguments may be passed as command
+                line arguments to systemd itself, next to any of the
+                command line options listed in the Options section
+                above. If run outside of Linux containers, these
+                arguments are parsed from
+                <filename>/proc/cmdline</filename>
+                instead.</para></footnote>:</para>
+
+                <variablelist>
+                        <varlistentry>
+                                <term><varname>systemd.unit=</varname></term>
+
+                                <listitem><para>Overrides the unit to
+                                activate on boot. Defaults to
+                                <filename>default.target</filename>. This
+                                may be used to temporarily boot into a
+                                different boot unit, for example
+                                <filename>rescue.target</filename> or
+                                <filename>emergency.service</filename>. See
+                                <citerefentry><refentrytitle>systemd.special</refentrytitle><manvolnum>7</manvolnum></citerefentry>
+                                for details about these
+                                units.</para></listitem>
+                        </varlistentry>
+
+                        <varlistentry>
+                                <term><varname>systemd.dump_core=</varname></term>
+
+                                <listitem><para>Takes a boolean
+                                argument. If <option>true</option>
+                                systemd dumps core when it
+                                crashes. Otherwise no core dump is
+                                created. Defaults to
+                                <option>true</option>.</para></listitem>
+                        </varlistentry>
+
+                        <varlistentry>
+                                <term><varname>systemd.crash_shell=</varname></term>
+
+                                <listitem><para>Takes a boolean
+                                argument. If <option>true</option>
+                                systemd spawns a shell when it
+                                crashes. Otherwise no shell is
+                                spawned. Defaults to
+                                <option>false</option>, for security
+                                reasons, as the shell is not protected
+                                by any password
+                                authentication.</para></listitem>
+                        </varlistentry>
+
+                        <varlistentry>
+                                <term><varname>systemd.crash_chvt=</varname></term>
+
+                                <listitem><para>Takes an integer
+                                argument. If positive systemd
+                                activates the specified virtual
+                                terminal when it crashes. Defaults to
+                                <literal>-1</literal>.</para></listitem>
+                        </varlistentry>
+
+                        <varlistentry>
+                                <term><varname>systemd.confirm_spawn=</varname></term>
+
+                                <listitem><para>Takes a boolean
+                                argument. If <option>true</option>
+                                asks for confirmation when spawning
+                                processes. Defaults to
+                                <option>false</option>.</para></listitem>
+                        </varlistentry>
+
+                        <varlistentry>
+                                <term><varname>systemd.show_status=</varname></term>
+
+                                <listitem><para>Takes a boolean
+                                argument. If <option>true</option>
+                                shows terse service status updates on
+                                the console during bootup. Defaults to
+                                <option>true</option>.</para></listitem>
+                        </varlistentry>
+
+                        <varlistentry>
+                                <term><varname>systemd.sysv_console=</varname></term>
+
+                                <listitem><para>Takes a boolean
+                                argument. If <option>true</option>
+                                output of SysV init scripts will be
+                                directed to the console. Defaults to
+                                <option>true</option>, unless
+                                <option>quiet</option> is passed as
+                                kernel command line option in which
+                                case it defaults to
+                                <option>false</option>.</para></listitem>
+                        </varlistentry>
+
+                        <varlistentry>
+                                <term><varname>systemd.log_target=</varname></term>
+                                <term><varname>systemd.log_level=</varname></term>
+                                <term><varname>systemd.log_color=</varname></term>
+                                <term><varname>systemd.log_location=</varname></term>
+
+                                <listitem><para>Controls log output,
+                                with the same effect as the
+                                <varname>$SYSTEMD_LOG_TARGET</varname>, <varname>$SYSTEMD_LOG_LEVEL</varname>, <varname>$SYSTEMD_LOG_COLOR</varname>, <varname>$SYSTEMD_LOG_LOCATION</varname>
+                                environment variables described above.</para></listitem>
+                        </varlistentry>
+
+                        <varlistentry>
+                                <term><varname>systemd.default_standard_output=</varname></term>
+                                <term><varname>systemd.default_standard_error=</varname></term>
+                                <listitem><para>Controls default
+                                standard output/error output for
+                                services, with the same effect as the
+                                <option>--default-standard-output=</option>
+                                resp. <option>--default-standard-error=</option>
+                                command line arguments described
+                                above.</para></listitem>
+                        </varlistentry>
+
+                        <varlistentry>
+                                <term><varname>systemd.setenv=</varname></term>
+
+                                <listitem><para>Takes a string
+                                argument in the form
+                                VARIABLE=VALUE. May be used to set
+                                environment variables for the init
+                                process and all its children at boot
+                                time. May be used more than once to
+                                set multiple variables. If the equal
+                                sign and variable are missing unsets
+                                an environment variable which might be
+                                passed in from the initial ram
+                                disk.</para></listitem>
+                        </varlistentry>
+
+                </variablelist>
+        </refsect1>
+
+        <refsect1>
+                <title>Sockets and FIFOs</title>
+
+                <variablelist>
+                        <varlistentry>
+                                <term><filename>/run/systemd/notify</filename></term>
+
+                                <listitem><para>Daemon status
+                                notification socket. This is an
+                                AF_UNIX datagram socket and is used to
+                                implement the daemon notification
+                                logic as implemented by
+                                <citerefentry><refentrytitle>sd_notify</refentrytitle><manvolnum>3</manvolnum></citerefentry>.</para></listitem>
+
+                        </varlistentry>
+
+                        <varlistentry>
+                                <term><filename>/run/systemd/shutdownd</filename></term>
+
+                                <listitem><para>Used internally by the
+                                <citerefentry><refentrytitle>shutdown</refentrytitle><manvolnum>8</manvolnum></citerefentry>
+                                tool to implement delayed
+                                shutdowns. This is an AF_UNIX datagram
+                                socket.</para></listitem>
+                        </varlistentry>
+
+                        <varlistentry>
+                                <term><filename>/run/systemd/private</filename></term>
+
+                                <listitem><para>Used internally as
+                                communication channel between
+                                <citerefentry><refentrytitle>systemctl</refentrytitle><manvolnum>1</manvolnum></citerefentry>
+                                and the systemd process. This is an
+                                AF_UNIX stream socket. This interface
+                                is private to systemd and should not
+                                be used in external
+                                projects.</para></listitem>
+                        </varlistentry>
+
+                        <varlistentry>
+                                <term><filename>/dev/initctl</filename></term>
+
+                                <listitem><para>Limited compatibility
+                                support for the SysV client interface,
+                                as implemented by the
+                                <filename>systemd-initctl.service</filename>
+                                unit. This is a named pipe in the file
+                                system. This interface is obsolete and
+                                should not be used in new
+                                applications.</para></listitem>
+                        </varlistentry>
+                </variablelist>
+        </refsect1>
+
+        <refsect1>
+                <title>See Also</title>
+                <para>
+                        <citerefentry><refentrytitle>systemctl</refentrytitle><manvolnum>1</manvolnum></citerefentry>,
+                        <citerefentry><refentrytitle>systemadm</refentrytitle><manvolnum>1</manvolnum></citerefentry>,
+                        <citerefentry><refentrytitle>systemd-notify</refentrytitle><manvolnum>1</manvolnum></citerefentry>,
+                        <citerefentry><refentrytitle>daemon</refentrytitle><manvolnum>7</manvolnum></citerefentry>,
+                        <citerefentry><refentrytitle>sd-daemon</refentrytitle><manvolnum>7</manvolnum></citerefentry>,
+                        <citerefentry><refentrytitle>systemd.unit</refentrytitle><manvolnum>5</manvolnum></citerefentry>,
+                        <citerefentry><refentrytitle>systemd.special</refentrytitle><manvolnum>5</manvolnum></citerefentry>,
+                        <citerefentry><refentrytitle>pkg-config</refentrytitle><manvolnum>1</manvolnum></citerefentry>
+                </para>
+        </refsect1>
+
+</refentry>
diff --git a/man/telinit.xml b/man/telinit.xml
new file mode 100644 (file)
index 0000000..fec059a
--- /dev/null
@@ -0,0 +1,195 @@
+<?xml version='1.0'?> <!--*-nxml-*-->
+<!DOCTYPE refentry PUBLIC "-//OASIS//DTD DocBook XML V4.2//EN"
+        "http://www.oasis-open.org/docbook/xml/4.2/docbookx.dtd">
+
+<!--
+  This file is part 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 <http://www.gnu.org/licenses/>.
+-->
+
+<refentry id="telinit">
+
+        <refentryinfo>
+                <title>telinit</title>
+                <productname>systemd</productname>
+
+                <authorgroup>
+                        <author>
+                                <contrib>Developer</contrib>
+                                <firstname>Lennart</firstname>
+                                <surname>Poettering</surname>
+                                <email>lennart@poettering.net</email>
+                        </author>
+                </authorgroup>
+        </refentryinfo>
+
+        <refmeta>
+                <refentrytitle>telinit</refentrytitle>
+                <manvolnum>8</manvolnum>
+        </refmeta>
+
+        <refnamediv>
+                <refname>telinit</refname>
+                <refpurpose>Change SysV runlevel</refpurpose>
+        </refnamediv>
+
+        <refsynopsisdiv>
+                <cmdsynopsis>
+                        <command>telinit <arg choice="opt" rep="repeat">OPTIONS</arg> <arg choice="req">COMMAND</arg></command>
+                </cmdsynopsis>
+        </refsynopsisdiv>
+
+        <refsect1>
+                <title>Description</title>
+
+                <para><command>telinit</command> may be used to change
+                the SysV system runlevel. Since the concept of SysV
+                runlevels is obsolete the runlevel requests
+                will be transparently translated into systemd unit
+                activation requests.</para>
+
+        </refsect1>
+
+        <refsect1>
+                <title>Options</title>
+
+                <para>The following options are understood:</para>
+
+                <variablelist>
+                        <varlistentry>
+                                <term><option>--help</option></term>
+
+                                <listitem><para>Prints a short help
+                                text and exits.</para></listitem>
+                        </varlistentry>
+
+                        <varlistentry>
+                                <term><option>--no-wall</option></term>
+
+                                <listitem><para>Don't send wall
+                                message before
+                                reboot/halt/power-off.</para></listitem>
+                        </varlistentry>
+                </variablelist>
+
+                <para>The following commands are understood:</para>
+
+                <variablelist>
+                        <varlistentry>
+                                <term><command>0</command></term>
+
+                                <listitem><para>Power-off the
+                                machine. This is translated into an
+                                activation request for
+                                <filename>poweroff.target</filename>
+                                and is equivalent to
+                                <command>systemctl
+                                poweroff</command>.</para></listitem>
+                        </varlistentry>
+
+                        <varlistentry>
+                                <term><command>6</command></term>
+
+                                <listitem><para>Reboot the
+                                machine. This is translated into an
+                                activation request for
+                                <filename>reboot.target</filename> and
+                                is equivalent to <command>systemctl
+                                reboot</command>.</para></listitem>
+                        </varlistentry>
+
+                        <varlistentry>
+                                <term><command>2</command></term>
+                                <term><command>3</command></term>
+                                <term><command>4</command></term>
+                                <term><command>5</command></term>
+
+                                <listitem><para>Change the SysV
+                                runlevel. This is translated into an
+                                activation request for
+                                <filename>runlevel2.target</filename>,
+                                <filename>runlevel3.target</filename>,
+                                ... and is equivalent to
+                                <command>systemctl isolate
+                                runlevel2.target</command>,
+                                <command>systemctl isolate
+                                runlevel3.target</command>,
+                                ...</para></listitem>
+                        </varlistentry>
+
+                        <varlistentry>
+                                <term><command>1</command></term>
+                                <term><command>s</command></term>
+                                <term><command>S</command></term>
+
+                                <listitem><para>Change into system
+                                rescue mode. This is translated into
+                                an activation request for
+                                <filename>rescue.target</filename> and
+                                is equivalent to <command>systemctl
+                                rescue</command>.</para></listitem>
+                        </varlistentry>
+
+                        <varlistentry>
+                                <term><command>q</command></term>
+                                <term><command>Q</command></term>
+
+                                <listitem><para>Reload daemon
+                                configuration. This is equivalent to
+                                <command>systemctl
+                                daemon-reload</command>.</para></listitem>
+                        </varlistentry>
+
+                        <varlistentry>
+                                <term><command>u</command></term>
+                                <term><command>U</command></term>
+
+                                <listitem><para>Serialize state,
+                                reexecute daemon and deserialize state
+                                again. This is equivalent to
+                                <command>systemctl
+                                daemon-reexec</command>.</para></listitem>
+                        </varlistentry>
+
+                </variablelist>
+        </refsect1>
+
+        <refsect1>
+                <title>Exit status</title>
+
+                <para>On success 0 is returned, a non-zero failure
+                code otherwise.</para>
+        </refsect1>
+
+        <refsect1>
+                <title>Notes</title>
+
+                <para>This is a legacy command available for compatibility
+                only. It should not be used anymore, as the concept of
+                runlevels is obsolete.</para>
+        </refsect1>
+
+        <refsect1>
+                <title>See Also</title>
+                <para>
+                        <citerefentry><refentrytitle>systemd</refentrytitle><manvolnum>1</manvolnum></citerefentry>,
+                        <citerefentry><refentrytitle>systemctl</refentrytitle><manvolnum>1</manvolnum></citerefentry>,
+                        <citerefentry><refentrytitle>wall</refentrytitle><manvolnum>1</manvolnum></citerefentry>
+                </para>
+        </refsect1>
+
+</refentry>
diff --git a/man/timezone.xml b/man/timezone.xml
new file mode 100644 (file)
index 0000000..4e33279
--- /dev/null
@@ -0,0 +1,90 @@
+<?xml version='1.0'?> <!--*-nxml-*-->
+<?xml-stylesheet type="text/xsl" href="http://docbook.sourceforge.net/release/xsl/current/xhtml/docbook.xsl"?>
+<!DOCTYPE refentry PUBLIC "-//OASIS//DTD DocBook XML V4.2//EN"
+        "http://www.oasis-open.org/docbook/xml/4.2/docbookx.dtd">
+
+<!--
+  This file is part 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 <http://www.gnu.org/licenses/>.
+-->
+
+<refentry id="timezone">
+        <refentryinfo>
+                <title>/etc/timezone</title>
+                <productname>systemd</productname>
+
+                <authorgroup>
+                        <author>
+                                <contrib>Developer</contrib>
+                                <firstname>Lennart</firstname>
+                                <surname>Poettering</surname>
+                                <email>lennart@poettering.net</email>
+                        </author>
+                </authorgroup>
+        </refentryinfo>
+
+        <refmeta>
+                <refentrytitle>timezone</refentrytitle>
+                <manvolnum>5</manvolnum>
+        </refmeta>
+
+        <refnamediv>
+                <refname>timezone</refname>
+                <refpurpose>Local time zone configuration file</refpurpose>
+        </refnamediv>
+
+        <refsynopsisdiv>
+                <para><filename>/etc/timezone</filename></para>
+        </refsynopsisdiv>
+
+        <refsect1>
+                <title>Description</title>
+
+                <para>The <filename>/etc/timezone</filename> file
+                configures the system-wide time zone of the local
+                system that is used by applications for presentation
+                to the user. It should contain a single
+                newline-terminated line consisting of a time zone
+                identifier such as
+                <literal>Europe/Berlin</literal>. The file
+                <filename>/etc/localtime</filename> corresponds with
+                <filename>/etc/timezone</filename> and contains the
+                binary time zone data for the time zone. These files
+                should always be changed simultaneously and kept in
+                sync.</para>
+
+                <para>The time zone may be overridden for individual
+                programs by using the TZ environment variable. See
+                <citerefentry><refentrytitle>environ</refentrytitle><manvolnum>7</manvolnum></citerefentry>.</para>
+        </refsect1>
+
+        <refsect1>
+                <title>History</title>
+
+                <para>The simple configuration file format of
+                <filename>/etc/timezone</filename> originates from
+                Debian GNU/Linux.</para>
+        </refsect1>
+
+        <refsect1>
+                  <title>See Also</title>
+                  <para>
+                          <citerefentry><refentrytitle>systemd</refentrytitle><manvolnum>1</manvolnum></citerefentry>
+                  </para>
+        </refsect1>
+
+</refentry>
diff --git a/man/tmpfiles.d.xml b/man/tmpfiles.d.xml
new file mode 100644 (file)
index 0000000..f70bf0e
--- /dev/null
@@ -0,0 +1,300 @@
+<?xml version="1.0"?>
+<!--*-nxml-*-->
+<!DOCTYPE refentry PUBLIC "-//OASIS//DTD DocBook XML V4.2//EN" "http://www.oasis-open.org/docbook/xml/4.2/docbookx.dtd">
+<!--
+  This file is part of systemd.
+
+  Copyright 2010 Brandon Philips
+
+  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 <http://www.gnu.org/licenses/>.
+-->
+<refentry id="tmpfiles.d">
+
+        <refentryinfo>
+                <title>tmpfiles.d</title>
+                <productname>systemd</productname>
+
+                <authorgroup>
+                        <author>
+                                <contrib>Documentation</contrib>
+                                <firstname>Brandon</firstname>
+                                <surname>Philips</surname>
+                                <email>brandon@ifup.org</email>
+                        </author>
+                </authorgroup>
+        </refentryinfo>
+
+        <refmeta>
+                <refentrytitle>tmpfiles.d</refentrytitle>
+                <manvolnum>5</manvolnum>
+        </refmeta>
+
+        <refnamediv>
+                <refname>tmpfiles.d</refname>
+                <refpurpose>Configuration for creation, deletion and
+                cleaning of volatile and temporary files</refpurpose>
+        </refnamediv>
+
+        <refsynopsisdiv>
+                <para><filename>/etc/tmpfiles.d/*.conf</filename></para>
+                <para><filename>/run/tmpfiles.d/*.conf</filename></para>
+                <para><filename>/usr/lib/tmpfiles.d/*.conf</filename></para>
+        </refsynopsisdiv>
+
+        <refsect1>
+                <title>Description</title>
+
+               <para><command>systemd-tmpfiles</command> uses the
+               configuration files from the above directories to describe the
+               creation, cleaning and removal of volatile and
+               temporary files and directories which usually reside
+               in directories such as <filename>/run</filename>
+               or <filename>/tmp</filename>.</para>
+        </refsect1>
+
+        <refsect1>
+               <title>Configuration Format</title>
+
+                <para>Each configuration file is named in the style of
+                <filename>&lt;program&gt;.conf</filename>.  Files in
+                <filename>/etc/</filename> override files with the
+                same name in <filename>/usr/lib/</filename>.  Files in
+                <filename>/run</filename> override files with the same
+                name in <filename>/etc/</filename> and
+                <filename>/usr/lib/</filename>. Packages should
+                install their configuration files in
+                <filename>/usr/lib/</filename>, files in
+                <filename>/etc/</filename> are reserved for the local
+                administrator, who may choose to override the
+                configurations installed from packages. The list of
+                configuration files are sorted by their filename in
+                alphabetical order, regardless in which of the
+                directories they reside, to guarantee that a
+                configuration file takes precedence over another
+                configuration file with an alphabetically later
+                name.</para>
+
+               <para>The configuration format is one line per path
+               containing action, path, mode, ownership, age and argument
+               fields:</para>
+
+                <programlisting>Type Path        Mode UID  GID  Age Argument
+d    /run/user   0755 root root 10d -
+L    /tmp/foobar -    -    -    -   /dev/null</programlisting>
+
+                <refsect2>
+                        <title>Type</title>
+                        <variablelist>
+                                <varlistentry>
+                                        <term><varname>f</varname></term>
+                                        <listitem><para>Create a file if it doesn't exist yet (optionally writing a short string into it, if the argument parameter is passed)</para></listitem>
+                                </varlistentry>
+
+                                <varlistentry>
+                                        <term><varname>F</varname></term>
+                                        <listitem><para>Create or truncate a file (optionally writing a short string into it, if the argument parameter is passed)</para></listitem>
+                                </varlistentry>
+
+                                <varlistentry>
+                                        <term><varname>w</varname></term>
+                                        <listitem><para>Write the argument parameter to a file, if it exists.</para></listitem>
+                                </varlistentry>
+
+                                <varlistentry>
+                                        <term><varname>d</varname></term>
+                                        <listitem><para>Create a directory if it doesn't exist yet</para></listitem>
+                                </varlistentry>
+
+                                <varlistentry>
+                                        <term><varname>D</varname></term>
+                                        <listitem><para>Create or empty a directory</para></listitem>
+                                </varlistentry>
+
+                                <varlistentry>
+                                        <term><varname>p</varname></term>
+                                        <listitem><para>Create a named pipe (FIFO) if it doesn't exist yet</para></listitem>
+                                </varlistentry>
+
+                                <varlistentry>
+                                        <term><varname>L</varname></term>
+                                        <listitem><para>Create a symlink if it doesn't exist yet</para></listitem>
+                                </varlistentry>
+
+                                <varlistentry>
+                                        <term><varname>c</varname></term>
+                                        <listitem><para>Create a character device node if it doesn't exist yet</para></listitem>
+                                </varlistentry>
+
+                                <varlistentry>
+                                        <term><varname>b</varname></term>
+                                        <listitem><para>Create a block device node if it doesn't exist yet</para></listitem>
+                                </varlistentry>
+
+                                <varlistentry>
+                                        <term><varname>x</varname></term>
+                                        <listitem><para>Ignore a path
+                                        during cleaning. Use this type
+                                        to exclude paths from clean-up
+                                        as controlled with the Age
+                                        parameter. Note that lines of
+                                        this type do not influence the
+                                        effect of r or R lines. Lines
+                                        of this type accept
+                                        shell-style globs in place of
+                                        of normal path
+                                        names.</para></listitem>
+                                </varlistentry>
+
+                                <varlistentry>
+                                        <term><varname>r</varname></term>
+                                        <listitem><para>Remove a file
+                                        or directory if it
+                                        exists. This may not be used
+                                        to remove non-empty
+                                        directories, use R for
+                                        that. Lines of this type
+                                        accept shell-style globs in
+                                        place of normal path
+                                        names.</para></listitem>
+                                </varlistentry>
+
+                                <varlistentry>
+                                        <term><varname>R</varname></term>
+                                        <listitem><para>Recursively
+                                        remove a path and all its
+                                        subdirectories (if it is a
+                                        directory). Lines of this type
+                                        accept shell-style globs in
+                                        place of normal path
+                                        names.</para></listitem>
+                                </varlistentry>
+
+                                <varlistentry>
+                                        <term><varname>z</varname></term>
+                                        <listitem><para>Restore
+                                        SELinux security context label
+                                        and set ownership and access
+                                        mode of a file or directory if
+                                        it exists.  Lines of this type
+                                        accept shell-style globs in
+                                        place of normal path names.
+                                        </para></listitem>
+                                </varlistentry>
+
+                                <varlistentry>
+                                        <term><varname>Z</varname></term>
+                                        <listitem><para>Recursively
+                                        restore SELinux security
+                                        context label and set
+                                        ownership and access mode of a
+                                        path and all its
+                                        subdirectories (if it is a
+                                        directory). Lines of this type
+                                        accept shell-style globs in
+                                        place of normal path
+                                        names.</para></listitem>
+                                </varlistentry>
+                        </variablelist>
+                </refsect2>
+
+                <refsect2>
+                        <title>Mode</title>
+
+                        <para>The file access mode to use when
+                        creating this file or directory. If omitted or
+                        when set to - the default is used: 0755 for
+                        directories, 0644 for all other file
+                        objects. For z, Z lines if omitted or when set
+                        to - the file access mode will not be
+                        modified. This parameter is ignored for x, r,
+                        R, L lines.</para>
+                </refsect2>
+
+                <refsect2>
+                        <title>UID, GID</title>
+
+                        <para>The user and group to use for this file
+                        or directory. This may either be a numeric
+                        user/group ID or a user or group name. If
+                        omitted or when set to - the default 0 (root)
+                        is used. For z, Z lines when omitted or when set to -
+                        the file ownership will not be modified.
+                        These parameters are ignored for x, r, R, L lines.</para>
+                </refsect2>
+
+                <refsect2>
+                        <title>Age</title>
+                        <para>The date field, when set, is used to
+                        decide what files to delete when cleaning. If
+                        a file or directory is older than the current
+                        time minus the age field it is deleted. The
+                        field format is a series of integers each
+                        followed by one of the following
+                        postfixes for the respective time units:</para>
+
+                        <variablelist>
+                                <varlistentry>
+                                <term><varname>s</varname></term>
+                                <term><varname>min</varname></term>
+                                <term><varname>h</varname></term>
+                                <term><varname>d</varname></term>
+                                <term><varname>w</varname></term>
+                                <term><varname>ms</varname></term>
+                                <term><varname>m</varname></term>
+                                <term><varname>us</varname></term></varlistentry>
+                        </variablelist>
+
+                        <para>If multiple integers and units are specified the time
+                        values are summed up.</para>
+
+                        <para>The age field only applies to lines starting with
+                        d, D and x. If omitted or set to - no automatic clean-up
+                        is done.</para>
+                </refsect2>
+
+                <refsect2>
+                        <title>Argument</title>
+
+                        <para>For L lines determines the destination
+                        path of the symlink. For c, b determines the
+                        major/minor of the device node, with major and
+                        minor formatted as integers, separated by :,
+                        e.g. "1:3". For f, F, w may be used to specify
+                        a short string that is written to the file,
+                        suffixed by a newline. Ignored for all other
+                        lines.</para>
+                </refsect2>
+
+        </refsect1>
+
+        <refsect1>
+                <title>Example</title>
+                <example>
+                        <title>/etc/tmpfiles.d/screen.conf example</title>
+                        <para><command>screen</command> needs two directories created at boot with specific modes and ownership.</para>
+
+                        <programlisting>d /var/run/screens  1777 root root 10d
+d /var/run/uscreens 0755 root root 10d12h</programlisting>
+                </example>
+        </refsect1>
+
+        <refsect1>
+                <title>See Also</title>
+                <para>
+                        <citerefentry><refentrytitle>systemd</refentrytitle><manvolnum>1</manvolnum></citerefentry>,
+                        <citerefentry><refentrytitle>systemd-tmpfiles</refentrytitle><manvolnum>8</manvolnum></citerefentry>
+                </para>
+        </refsect1>
+
+</refentry>
diff --git a/man/vconsole.conf.xml b/man/vconsole.conf.xml
new file mode 100644 (file)
index 0000000..a73db00
--- /dev/null
@@ -0,0 +1,146 @@
+<?xml version='1.0'?> <!--*-nxml-*-->
+<?xml-stylesheet type="text/xsl" href="http://docbook.sourceforge.net/release/xsl/current/xhtml/docbook.xsl"?>
+<!DOCTYPE refentry PUBLIC "-//OASIS//DTD DocBook XML V4.2//EN"
+        "http://www.oasis-open.org/docbook/xml/4.2/docbookx.dtd">
+
+<!--
+  This file is part 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 <http://www.gnu.org/licenses/>.
+-->
+
+<refentry id="vconsole.conf">
+        <refentryinfo>
+                <title>vconsole.conf</title>
+                <productname>systemd</productname>
+
+                <authorgroup>
+                        <author>
+                                <contrib>Developer</contrib>
+                                <firstname>Lennart</firstname>
+                                <surname>Poettering</surname>
+                                <email>lennart@poettering.net</email>
+                        </author>
+                </authorgroup>
+        </refentryinfo>
+
+        <refmeta>
+                <refentrytitle>vconsole.conf</refentrytitle>
+                <manvolnum>5</manvolnum>
+        </refmeta>
+
+        <refnamediv>
+                <refname>vconsole.conf</refname>
+                <refpurpose>configuration file for the virtual console</refpurpose>
+        </refnamediv>
+
+        <refsynopsisdiv>
+                <para><filename>/etc/vconsole.conf</filename></para>
+        </refsynopsisdiv>
+
+        <refsect1>
+                <title>Description</title>
+
+                <para>The <filename>/etc/vconsole.conf</filename> file
+                configures the virtual console, i.e. keyboard mapping
+                and console font.</para>
+
+                <para>The basic file format of the
+                <filename>vconsole.conf</filename> is a
+                newline-separated list environment-like
+                shell-compatible variable assignments. It is possible
+                to source the configuration from shell scripts,
+                however, beyond mere variable assignments no shell
+                features are supported, allowing applications to read
+                the file without implementing a shell compatible
+                execution engine.</para>
+
+                <para>Note that the kernel command line options
+                <varname>vconsole.keymap=</varname>,
+                <varname>vconsole.keymap.toggle=</varname>,
+                <varname>vconsole.font=</varname>,
+                <varname>vconsole.font.map=</varname>,
+                <varname>vconsole.font.unimap=</varname> may be used
+                to override the console settings at boot.</para>
+
+                <para>Depending on the operating system other
+                configuration files might be checked for configuration
+                of the virtual console as well, however only as
+                fallback.</para>
+        </refsect1>
+
+        <refsect1>
+                <title>Options</title>
+
+                <para>The following options are understood:</para>
+
+                <variablelist>
+
+                        <varlistentry>
+                                <term><varname>KEYMAP=</varname></term>
+                                <term><varname>KEYMAP_TOGGLE=</varname></term>
+
+                                <listitem><para>Configures the key
+                                mapping table of for they
+                                keyboard. <varname>KEYMAP=</varname>
+                                defaults to <literal>us</literal> if
+                                not set. The
+                                <varname>KEYMAP_TOGGLE=</varname> can
+                                be used to configured a second toggle
+                                keymap and is by default
+                                unset.</para></listitem>
+                        </varlistentry>
+
+                        <varlistentry>
+                                <term><varname>FONT=</varname></term>
+                                <term><varname>FONT_MAP=</varname></term>
+                                <term><varname>FONT_UNIMAP=</varname></term>
+
+                                <listitem><para>Configures the console
+                                font, the console map and the unicode
+                                font map. <varname>FONT=</varname>
+                                defaults to
+                                <literal>latarcyrheb-sun16</literal>.</para></listitem>
+                        </varlistentry>
+
+                </variablelist>
+        </refsect1>
+
+        <refsect1>
+                <title>Example</title>
+
+                <example>
+                        <title>German keyboard and console</title>
+
+                        <para><filename>/etc/vconsole.conf:</filename></para>
+
+                        <programlisting>KEYMAP=de-latin1
+FONT=latarcyrheb-sun16</programlisting>
+                </example>
+
+        </refsect1>
+
+        <refsect1>
+                  <title>See Also</title>
+                  <para>
+                          <citerefentry><refentrytitle>systemd</refentrytitle><manvolnum>1</manvolnum></citerefentry>,
+                          <citerefentry><refentrytitle>loadkeys</refentrytitle><manvolnum>1</manvolnum></citerefentry>,
+                          <citerefentry><refentrytitle>setfont</refentrytitle><manvolnum>8</manvolnum></citerefentry>,
+                          <citerefentry><refentrytitle>locale.conf</refentrytitle><manvolnum>5</manvolnum></citerefentry>
+                  </para>
+        </refsect1>
+
+</refentry>
diff --git a/po/.gitignore b/po/.gitignore
new file mode 100644 (file)
index 0000000..1fa8d3f
--- /dev/null
@@ -0,0 +1,5 @@
+POTFILES
+Makefile.in.in
+.intltool-merge-cache
+Makefile
+systemd.pot
diff --git a/po/POTFILES.in b/po/POTFILES.in
new file mode 100644 (file)
index 0000000..ed2c308
--- /dev/null
@@ -0,0 +1,4 @@
+src/hostname/org.freedesktop.hostname1.policy.in
+src/locale/org.freedesktop.locale1.policy.in
+src/login/org.freedesktop.login1.policy.in
+src/timedate/org.freedesktop.timedate1.policy.in
diff --git a/po/POTFILES.skip b/po/POTFILES.skip
new file mode 100644 (file)
index 0000000..5a5d027
--- /dev/null
@@ -0,0 +1,19 @@
+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/hostname/hostnamed.c
+src/locale/localed.c
+src/org.freedesktop.systemd1.policy.in.in
+src/timedate/timedated.c
+units/systemd-readahead-done.service.in
+units/user@.service.in
diff --git a/po/pl.po b/po/pl.po
new file mode 100644 (file)
index 0000000..2581d01
--- /dev/null
+++ b/po/pl.po
@@ -0,0 +1,175 @@
+# translation of pl.po to Polish
+# Piotr DrÄ…g <piotrdrag@gmail.com>, 2011.
+# Zbigniew JÄ™drzejewski-Szmek <zbyszek@in.waw.pl>, 2011.
+#
+msgid ""
+msgstr ""
+"Project-Id-Version: systemd\n"
+"Report-Msgid-Bugs-To: \n"
+"POT-Creation-Date: 2011-10-14 16:18+0200\n"
+"PO-Revision-Date: 2011-10-14 16:20+0200\n"
+"Last-Translator: Piotr DrÄ…g <piotrdrag@gmail.com>\n"
+"Language-Team: Polish <trans-pl@lists.fedoraproject.org>\n"
+"Language: pl\n"
+"MIME-Version: 1.0\n"
+"Content-Type: text/plain; charset=UTF-8\n"
+"Content-Transfer-Encoding: 8bit\n"
+
+#: ../src/org.freedesktop.hostname1.policy.in.h:1
+msgid "Authentication is required to set local machine information."
+msgstr ""
+"Wymagane jest uwierzytelnienie, aby ustawić informacje o lokalnym komputerze."
+
+#: ../src/org.freedesktop.hostname1.policy.in.h:2
+msgid "Authentication is required to set the local host name."
+msgstr "Wymagane jest uwierzytelnienie, aby ustawić nazwÄ™ lokalnego komputera."
+
+#: ../src/org.freedesktop.hostname1.policy.in.h:3
+msgid ""
+"Authentication is required to set the statically configured local host name, "
+"as well as the pretty host name."
+msgstr ""
+"Wymagane jest uwierzytelnienie, aby ustawić statycznie skonfigurowanÄ… nazwÄ™ "
+"lokalnego komputera, a także jego Å‚adnÄ… nazwÄ™."
+
+#: ../src/org.freedesktop.hostname1.policy.in.h:4
+msgid "Set host name"
+msgstr "Ustawienie nazwy komputera"
+
+#: ../src/org.freedesktop.hostname1.policy.in.h:5
+msgid "Set machine information"
+msgstr "Ustawienie informacji o komputerze"
+
+#: ../src/org.freedesktop.hostname1.policy.in.h:6
+msgid "Set static host name"
+msgstr "Ustawienie statycznej nazwy komputera"
+
+#: ../src/org.freedesktop.locale1.policy.in.h:1
+msgid "Authentication is required to set the system keyboard settings."
+msgstr "Wymagane jest uwierzytelnienie, aby ustawić klawiaturÄ™ systemu."
+
+#: ../src/org.freedesktop.locale1.policy.in.h:2
+msgid "Authentication is required to set the system locale."
+msgstr "Wymagane jest uwierzytelnienie, aby ustawić lokalizacjÄ™ systemu."
+
+#: ../src/org.freedesktop.locale1.policy.in.h:3
+msgid "Set system keyboard settings"
+msgstr "Ustawienie klawiatury systemu"
+
+#: ../src/org.freedesktop.locale1.policy.in.h:4
+msgid "Set system locale"
+msgstr "Ustawienie lokalizacji systemu"
+
+#: ../src/org.freedesktop.login1.policy.in.h:1
+msgid "Allow attaching devices to seats"
+msgstr "Zezwolenie na podÅ‚Ä…czanie urzÄ…dzeÅ„ do stanowisk"
+
+#: ../src/org.freedesktop.login1.policy.in.h:2
+msgid "Allow non-logged-in users to run programs"
+msgstr "Zezwolenie niezalogowanym użytkownikom na uruchamianie programów"
+
+#: ../src/org.freedesktop.login1.policy.in.h:3
+msgid ""
+"Authentication is required to allow a non-logged-in user to run programs"
+msgstr ""
+"Wymagane jest uwierzytelnienie, aby ustawić zezwolić niezalogowanym "
+"użytkownikom na uruchamianie programów"
+
+#: ../src/org.freedesktop.login1.policy.in.h:4
+msgid "Authentication is required to allow attaching a device to a seat"
+msgstr ""
+"Wymagane jest uwierzytelnienie, aby zezwolić na podÅ‚Ä…czenie urzÄ…dzenia do "
+"stanowiska"
+
+#: ../src/org.freedesktop.login1.policy.in.h:5
+msgid "Authentication is required to allow powering off the system"
+msgstr "Wymagane jest uwierzytelnienie, aby zezwolić na wyÅ‚Ä…czanie systemu"
+
+#: ../src/org.freedesktop.login1.policy.in.h:6
+msgid ""
+"Authentication is required to allow powering off the system while other "
+"users are logged in"
+msgstr ""
+"Wymagane jest uwierzytelnienie, aby zezwolić na wyÅ‚Ä…czanie systemu, kiedy sÄ… "
+"zalogowani inni użytkownicy"
+
+#: ../src/org.freedesktop.login1.policy.in.h:7
+msgid "Authentication is required to allow rebooting the system"
+msgstr ""
+"Wymagane jest uwierzytelnienie, aby zezwolić na ponowne uruchamianie systemu"
+
+#: ../src/org.freedesktop.login1.policy.in.h:8
+msgid ""
+"Authentication is required to allow rebooting the system while other users "
+"are logged in"
+msgstr ""
+"Wymagane jest uwierzytelnienie, aby zezwolić na ponowne uruchamianie "
+"systemu, kiedy sÄ… zalogowani inni użytkownicy"
+
+#: ../src/org.freedesktop.login1.policy.in.h:9
+msgid ""
+"Authentication is required to allow resetting how devices are attached to "
+"seats"
+msgstr ""
+"Wymagane jest uwierzytelnienie, aby zezwolić na ponowne ustawianie sposobu "
+"podÅ‚Ä…czenia urzÄ…dzeÅ„ do stanowisk"
+
+#: ../src/org.freedesktop.login1.policy.in.h:10
+msgid "Flush device to seat attachments"
+msgstr "UsuniÄ™cie podÅ‚Ä…czenia urzÄ…dzeÅ„ do stanowisk"
+
+#: ../src/org.freedesktop.login1.policy.in.h:11
+msgid "Power off the system"
+msgstr "WyÅ‚Ä…czenie systemu"
+
+#: ../src/org.freedesktop.login1.policy.in.h:12
+msgid "Power off the system when other users are logged in"
+msgstr "WyÅ‚Ä…czenie systemu, kiedy sÄ… zalogowani inni użytkownicy"
+
+#: ../src/org.freedesktop.login1.policy.in.h:13
+msgid "Reboot the system"
+msgstr "Ponowne uruchomienie systemu"
+
+#: ../src/org.freedesktop.login1.policy.in.h:14
+msgid "Reboot the system when other users are logged in"
+msgstr "Ponowne uruchomienie systemu, kiedy sÄ… zalogowani inni użytkownicy"
+
+#: ../src/org.freedesktop.timedate1.policy.in.h:1
+msgid ""
+"Authentication is required to control whether network time synchronization "
+"shall be enabled."
+msgstr ""
+"Wymagane jest uwierzytelnienie, aby kontrolować, czy wÅ‚Ä…czyć synchronizacjÄ™ "
+"czasu przez sieć."
+
+#: ../src/org.freedesktop.timedate1.policy.in.h:2
+msgid ""
+"Authentication is required to control whether the RTC stores the local or "
+"UTC time."
+msgstr ""
+"Wymagane jest uwierzytelnienie, aby kontrolować, czy RTC przechowuje czas "
+"lokalny lub czas UTC."
+
+#: ../src/org.freedesktop.timedate1.policy.in.h:3
+msgid "Authentication is required to set the system time."
+msgstr "Wymagane jest uwierzytelnienie, aby ustawić czas systemu."
+
+#: ../src/org.freedesktop.timedate1.policy.in.h:4
+msgid "Authentication is required to set the system timezone."
+msgstr "Wymagane jest uwierzytelnienie, aby ustawić strefÄ™ czasowÄ… systemu."
+
+#: ../src/org.freedesktop.timedate1.policy.in.h:5
+msgid "Set RTC to local timezone or UTC"
+msgstr "Ustawienie RTC na lokalnÄ… strefÄ™ czasowÄ… lub strefÄ™ UTC"
+
+#: ../src/org.freedesktop.timedate1.policy.in.h:6
+msgid "Set system time"
+msgstr "Ustawienie czasu systemu"
+
+#: ../src/org.freedesktop.timedate1.policy.in.h:7
+msgid "Set system timezone"
+msgstr "Ustawienie strefy czasowej systemu"
+
+#: ../src/org.freedesktop.timedate1.policy.in.h:8
+msgid "Turn network time synchronization on or off"
+msgstr "WÅ‚Ä…czenie lub wyÅ‚Ä…czenie synchronizacji czasu przez sieć"
index beb8604bc630466404a1477361208e4c130d7007..4b123f86d213ea00aa8426f1946b369ee13cc66f 100644 (file)
@@ -1,5 +1,11 @@
-*.[78]
-*.html
-udev.pc
-libudev.pc
-udev*.service
+/*.pc
+load-fragment-gperf-nulstr.c
+load-fragment-gperf.c
+load-fragment-gperf.gperf
+org.freedesktop.systemd1.policy.in
+org.freedesktop.systemd1.policy
+gnome-ask-password-agent.c
+systemd-interfaces.c
+systemadm.c
+wraplabel.c
+99-systemd.rules
diff --git a/src/99-systemd.rules.in b/src/99-systemd.rules.in
new file mode 100644 (file)
index 0000000..d306f71
--- /dev/null
@@ -0,0 +1,55 @@
+#  This file is part of systemd.
+#
+#  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.
+
+ACTION=="remove", GOTO="systemd_end"
+
+SUBSYSTEM=="tty", KERNEL=="tty[0-9]|tty1[0-2]", TAG+="systemd"
+SUBSYSTEM=="tty", KERNEL=="tty[a-zA-Z]*|hvc*|xvc*|hvsi*", TAG+="systemd"
+
+KERNEL=="vport*", TAG+="systemd"
+
+SUBSYSTEM=="block", KERNEL!="ram*|loop*", TAG+="systemd"
+SUBSYSTEM=="block", KERNEL!="ram*|loop*", ENV{DM_UDEV_DISABLE_OTHER_RULES_FLAG}=="1", ENV{SYSTEMD_READY}="0"
+
+# Ignore encrypted devices with no identified superblock on it, since
+# we are probably still calling mke2fs or mkswap on it.
+
+SUBSYSTEM=="block", KERNEL!="ram*|loop*", ENV{DM_UUID}=="CRYPT-*", ENV{ID_PART_TABLE_TYPE}=="", ENV{ID_FS_USAGE}=="", ENV{SYSTEMD_READY}="0"
+
+# We need a hardware independent way to identify network devices. We
+# use the /sys/subsystem path for this. Current vanilla kernels don't
+# actually support that hierarchy right now, however upcoming kernels
+# will. HAL and udev internally support /sys/subsystem already, hence
+# it should be safe to use this here, too. This is mostly just an
+# identification string for systemd, so whether the path actually is
+# accessible or not does not matter as long as it is unique and in the
+# filesystem namespace.
+#
+# http://git.kernel.org/?p=linux/hotplug/udev.git;a=blob;f=libudev/libudev-enumerate.c;h=da831449dcaf5e936a14409e8e68ab12d30a98e2;hb=HEAD#l742
+
+SUBSYSTEM=="net", KERNEL!="lo", TAG+="systemd", ENV{SYSTEMD_ALIAS}="/sys/subsystem/net/devices/$name"
+SUBSYSTEM=="bluetooth", TAG+="systemd", ENV{SYSTEMD_ALIAS}="/sys/subsystem/bluetooth/devices/%k"
+
+SUBSYSTEM=="bluetooth", TAG+="systemd", ENV{SYSTEMD_WANTS}="bluetooth.target"
+ENV{ID_SMARTCARD_READER}=="*?", TAG+="systemd", ENV{SYSTEMD_WANTS}="smartcard.target"
+SUBSYSTEM=="sound", KERNEL=="card*", TAG+="systemd", ENV{SYSTEMD_WANTS}="sound.target"
+
+SUBSYSTEM=="printer", TAG+="systemd", ENV{SYSTEMD_WANTS}="printer.target"
+SUBSYSTEM=="usb", KERNEL=="lp*", TAG+="systemd", ENV{SYSTEMD_WANTS}="printer.target"
+SUBSYSTEM=="usb", ENV{DEVTYPE}=="usb_device", ENV{ID_USB_INTERFACES}=="*:0701??:*", TAG+="systemd", ENV{SYSTEMD_WANTS}="printer.target"
+
+# Apply sysctl variables to network devices (and only to those) as they appear.
+
+SUBSYSTEM=="net", KERNEL!="lo", RUN+="@rootlibexecdir@/systemd-sysctl --prefix=/proc/sys/net/ipv4/conf/$name --prefix=/proc/sys/net/ipv4/neigh/$name --prefix=/proc/sys/net/ipv6/conf/$name --prefix=/proc/sys/net/ipv6/neigh/$name"
+
+# Asynchronously mount file systems implemented by these modules as
+# soon as they are loaded.
+
+SUBSYSTEM=="module", KERNEL=="fuse", ACTION=="add", TAG+="systemd", ENV{SYSTEMD_WANTS}="sys-fs-fuse-connections.mount"
+SUBSYSTEM=="module", KERNEL=="configfs", ACTION=="add", TAG+="systemd", ENV{SYSTEMD_WANTS}="sys-kernel-config.mount"
+
+LABEL="systemd_end"
diff --git a/src/Makefile b/src/Makefile
new file mode 100644 (file)
index 0000000..bc7e9fa
--- /dev/null
@@ -0,0 +1,28 @@
+#  This file is part of systemd.
+#
+#  Copyright 2010 Lennart Poettering
+#
+#  systemd is free software; you can redistribute it and/or modify it
+#  under the terms of the GNU 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 <http://www.gnu.org/licenses/>.
+
+# This file is a dirty trick to simplify compilation from within
+# emacs. This file is not intended to be distributed. So, don't touch
+# it, even better ignore it!
+
+all:
+       $(MAKE) -C ..
+
+clean:
+       $(MAKE) -C .. clean
+
+.PHONY: all clean
diff --git a/src/ac-power.c b/src/ac-power.c
new file mode 100644 (file)
index 0000000..24a68e7
--- /dev/null
@@ -0,0 +1,111 @@
+/*-*- 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 <http://www.gnu.org/licenses/>.
+***/
+
+#include <stdlib.h>
+#include <stdbool.h>
+#include <errno.h>
+#include <string.h>
+#include <libudev.h>
+
+#include "util.h"
+
+static int on_ac_power(void) {
+        int r;
+
+        struct udev *udev;
+        struct udev_enumerate *e = NULL;
+        struct udev_list_entry *item = NULL, *first = NULL;
+        bool found_offline = false, found_online = false;
+
+        if (!(udev = udev_new())) {
+                r = -ENOMEM;
+                goto finish;
+        }
+
+        if (!(e = udev_enumerate_new(udev))) {
+                r = -ENOMEM;
+                goto finish;
+        }
+
+        if (udev_enumerate_add_match_subsystem(e, "power_supply") < 0) {
+                r = -EIO;
+                goto finish;
+        }
+
+        if (udev_enumerate_scan_devices(e) < 0) {
+                r = -EIO;
+                goto finish;
+        }
+
+        first = udev_enumerate_get_list_entry(e);
+        udev_list_entry_foreach(item, first) {
+                struct udev_device *d;
+                const char *type, *online;
+
+                if (!(d = udev_device_new_from_syspath(udev, udev_list_entry_get_name(item)))) {
+                        r = -ENOMEM;
+                        goto finish;
+                }
+
+                if (!(type = udev_device_get_sysattr_value(d, "type")))
+                        goto next;
+
+                if (!streq(type, "Mains"))
+                        goto next;
+
+                if (!(online = udev_device_get_sysattr_value(d, "online")))
+                        goto next;
+
+                if (streq(online, "1")) {
+                        found_online = true;
+                        break;
+                } else if (streq(online, "0"))
+                        found_offline = true;
+
+        next:
+                udev_device_unref(d);
+        }
+
+        r = found_online || !found_offline;
+
+finish:
+        if (e)
+                udev_enumerate_unref(e);
+
+        if (udev)
+                udev_unref(udev);
+
+        return r;
+}
+
+int main(int argc, char *argv[]) {
+        int r;
+
+        /* This is mostly intended to be used for scripts which want
+         * to detect whether AC power is plugged in or not. */
+
+        if ((r = on_ac_power()) < 0) {
+                log_error("Failed to read AC status: %s", strerror(-r));
+                return EXIT_FAILURE;
+        }
+
+        return r == 0;
+}
diff --git a/src/acl-util.c b/src/acl-util.c
new file mode 100644 (file)
index 0000000..a2a9f9a
--- /dev/null
@@ -0,0 +1,68 @@
+/*-*- 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 <http://www.gnu.org/licenses/>.
+***/
+
+#include <assert.h>
+#include <sys/acl.h>
+#include <acl/libacl.h>
+#include <errno.h>
+#include <stdbool.h>
+
+#include "acl-util.h"
+
+int acl_find_uid(acl_t acl, uid_t uid, acl_entry_t *entry) {
+        acl_entry_t i;
+        int found;
+
+        assert(acl);
+        assert(entry);
+
+        for (found = acl_get_entry(acl, ACL_FIRST_ENTRY, &i);
+             found > 0;
+             found = acl_get_entry(acl, ACL_NEXT_ENTRY, &i)) {
+
+                acl_tag_t tag;
+                uid_t *u;
+                bool b;
+
+                if (acl_get_tag_type(i, &tag) < 0)
+                        return -errno;
+
+                if (tag != ACL_USER)
+                        continue;
+
+                u = acl_get_qualifier(i);
+                if (!u)
+                        return -errno;
+
+                b = *u == uid;
+                acl_free(u);
+
+                if (b) {
+                        *entry = i;
+                        return 1;
+                }
+        }
+
+        if (found < 0)
+                return -errno;
+
+        return 0;
+}
diff --git a/src/acl-util.h b/src/acl-util.h
new file mode 100644 (file)
index 0000000..798ce43
--- /dev/null
@@ -0,0 +1,27 @@
+/*-*- Mode: C; c-basic-offset: 8; indent-tabs-mode: nil -*-*/
+
+#ifndef fooaclutilhfoo
+#define fooaclutilhfoo
+
+/***
+  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 <http://www.gnu.org/licenses/>.
+***/
+
+int acl_find_uid(acl_t acl, uid_t uid, acl_entry_t *entry);
+
+#endif
diff --git a/src/ask-password-api.c b/src/ask-password-api.c
new file mode 100644 (file)
index 0000000..ce2f3cb
--- /dev/null
@@ -0,0 +1,576 @@
+/*-*- 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 <http://www.gnu.org/licenses/>.
+***/
+#include <stdbool.h>
+#include <termios.h>
+#include <unistd.h>
+#include <sys/poll.h>
+#include <sys/inotify.h>
+#include <errno.h>
+#include <fcntl.h>
+#include <sys/socket.h>
+#include <string.h>
+#include <sys/un.h>
+#include <stddef.h>
+#include <sys/signalfd.h>
+
+#include "util.h"
+#include "strv.h"
+
+#include "ask-password-api.h"
+
+static void backspace_chars(int ttyfd, size_t p) {
+
+        if (ttyfd < 0)
+                return;
+
+        while (p > 0) {
+                p--;
+
+                loop_write(ttyfd, "\b \b", 3, false);
+        }
+}
+
+int ask_password_tty(
+                const char *message,
+                usec_t until,
+                const char *flag_file,
+                char **_passphrase) {
+
+        struct termios old_termios, new_termios;
+        char passphrase[LINE_MAX];
+        size_t p = 0;
+        int r, ttyfd = -1, notify = -1;
+        struct pollfd pollfd[2];
+        bool reset_tty = false;
+        bool silent_mode = false;
+        bool dirty = false;
+        enum {
+                POLL_TTY,
+                POLL_INOTIFY
+        };
+
+        assert(message);
+        assert(_passphrase);
+
+        if (flag_file) {
+                if ((notify = inotify_init1(IN_CLOEXEC|IN_NONBLOCK)) < 0) {
+                        r = -errno;
+                        goto finish;
+                }
+
+                if (inotify_add_watch(notify, flag_file, IN_ATTRIB /* for the link count */) < 0) {
+                        r = -errno;
+                        goto finish;
+                }
+        }
+
+        if ((ttyfd = open("/dev/tty", O_RDWR|O_NOCTTY|O_CLOEXEC)) >= 0) {
+
+                if (tcgetattr(ttyfd, &old_termios) < 0) {
+                        r = -errno;
+                        goto finish;
+                }
+
+                loop_write(ttyfd, ANSI_HIGHLIGHT_ON, sizeof(ANSI_HIGHLIGHT_ON)-1, false);
+                loop_write(ttyfd, message, strlen(message), false);
+                loop_write(ttyfd, " ", 1, false);
+                loop_write(ttyfd, ANSI_HIGHLIGHT_OFF, sizeof(ANSI_HIGHLIGHT_OFF)-1, false);
+
+                new_termios = old_termios;
+                new_termios.c_lflag &= ~(ICANON|ECHO);
+                new_termios.c_cc[VMIN] = 1;
+                new_termios.c_cc[VTIME] = 0;
+
+                if (tcsetattr(ttyfd, TCSADRAIN, &new_termios) < 0) {
+                        r = -errno;
+                        goto finish;
+                }
+
+                reset_tty = true;
+        }
+
+        zero(pollfd);
+
+        pollfd[POLL_TTY].fd = ttyfd >= 0 ? ttyfd : STDIN_FILENO;
+        pollfd[POLL_TTY].events = POLLIN;
+        pollfd[POLL_INOTIFY].fd = notify;
+        pollfd[POLL_INOTIFY].events = POLLIN;
+
+        for (;;) {
+                char c;
+                int sleep_for = -1, k;
+                ssize_t n;
+
+                if (until > 0) {
+                        usec_t y;
+
+                        y = now(CLOCK_MONOTONIC);
+
+                        if (y > until) {
+                                r = -ETIME;
+                                goto finish;
+                        }
+
+                        sleep_for = (int) ((until - y) / USEC_PER_MSEC);
+                }
+
+                if (flag_file)
+                        if (access(flag_file, F_OK) < 0) {
+                                r = -errno;
+                                goto finish;
+                        }
+
+                if ((k = poll(pollfd, notify > 0 ? 2 : 1, sleep_for)) < 0) {
+
+                        if (errno == EINTR)
+                                continue;
+
+                        r = -errno;
+                        goto finish;
+                } else if (k == 0) {
+                        r = -ETIME;
+                        goto finish;
+                }
+
+                if (notify > 0 && pollfd[POLL_INOTIFY].revents != 0)
+                        flush_fd(notify);
+
+                if (pollfd[POLL_TTY].revents == 0)
+                        continue;
+
+                if ((n = read(ttyfd >= 0 ? ttyfd : STDIN_FILENO, &c, 1)) < 0) {
+
+                        if (errno == EINTR || errno == EAGAIN)
+                                continue;
+
+                        r = -errno;
+                        goto finish;
+
+                } else if (n == 0)
+                        break;
+
+                if (c == '\n')
+                        break;
+                else if (c == 21) { /* C-u */
+
+                        if (!silent_mode)
+                                backspace_chars(ttyfd, p);
+                        p = 0;
+
+                } else if (c == '\b' || c == 127) {
+
+                        if (p > 0) {
+
+                                if (!silent_mode)
+                                        backspace_chars(ttyfd, 1);
+
+                                p--;
+                        } else if (!dirty && !silent_mode) {
+
+                                silent_mode = true;
+
+                                /* There are two ways to enter silent
+                                 * mode. Either by pressing backspace
+                                 * as first key (and only as first key),
+                                 * or ... */
+                                if (ttyfd >= 0)
+                                        loop_write(ttyfd, "(no echo) ", 10, false);
+
+                        } else if (ttyfd >= 0)
+                                loop_write(ttyfd, "\a", 1, false);
+
+                } else if (c == '\t' && !silent_mode) {
+
+                        backspace_chars(ttyfd, p);
+                        silent_mode = true;
+
+                        /* ... or by pressing TAB at any time. */
+
+                        if (ttyfd >= 0)
+                                loop_write(ttyfd, "(no echo) ", 10, false);
+                } else {
+                        passphrase[p++] = c;
+
+                        if (!silent_mode && ttyfd >= 0)
+                                loop_write(ttyfd, "*", 1, false);
+
+                        dirty = true;
+                }
+        }
+
+        passphrase[p] = 0;
+
+        if (!(*_passphrase = strdup(passphrase))) {
+                r = -ENOMEM;
+                goto finish;
+        }
+
+        r = 0;
+
+finish:
+        if (notify >= 0)
+                close_nointr_nofail(notify);
+
+        if (ttyfd >= 0) {
+
+                if (reset_tty) {
+                        loop_write(ttyfd, "\n", 1, false);
+                        tcsetattr(ttyfd, TCSADRAIN, &old_termios);
+                }
+
+                close_nointr_nofail(ttyfd);
+        }
+
+        return r;
+}
+
+static int create_socket(char **name) {
+        int fd;
+        union {
+                struct sockaddr sa;
+                struct sockaddr_un un;
+        } sa;
+        int one = 1, r;
+        char *c;
+        mode_t u;
+
+        assert(name);
+
+        if ((fd = socket(AF_UNIX, SOCK_DGRAM|SOCK_CLOEXEC|SOCK_NONBLOCK, 0)) < 0) {
+                log_error("socket() failed: %m");
+                return -errno;
+        }
+
+        zero(sa);
+        sa.un.sun_family = AF_UNIX;
+        snprintf(sa.un.sun_path, sizeof(sa.un.sun_path)-1, "/run/systemd/ask-password/sck.%llu", random_ull());
+
+        u = umask(0177);
+        r = bind(fd, &sa.sa, offsetof(struct sockaddr_un, sun_path) + strlen(sa.un.sun_path));
+        umask(u);
+
+        if (r < 0) {
+                r = -errno;
+                log_error("bind() failed: %m");
+                goto fail;
+        }
+
+        if (setsockopt(fd, SOL_SOCKET, SO_PASSCRED, &one, sizeof(one)) < 0) {
+                r = -errno;
+                log_error("SO_PASSCRED failed: %m");
+                goto fail;
+        }
+
+        if (!(c = strdup(sa.un.sun_path))) {
+                r = -ENOMEM;
+                log_error("Out of memory");
+                goto fail;
+        }
+
+        *name = c;
+        return fd;
+
+fail:
+        close_nointr_nofail(fd);
+
+        return r;
+}
+
+int ask_password_agent(
+                const char *message,
+                const char *icon,
+                usec_t until,
+                bool accept_cached,
+                char ***_passphrases) {
+
+        enum {
+                FD_SOCKET,
+                FD_SIGNAL,
+                _FD_MAX
+        };
+
+        char temp[] = "/run/systemd/ask-password/tmp.XXXXXX";
+        char final[sizeof(temp)] = "";
+        int fd = -1, r;
+        FILE *f = NULL;
+        char *socket_name = NULL;
+        int socket_fd = -1, signal_fd = -1;
+        sigset_t mask, oldmask;
+        struct pollfd pollfd[_FD_MAX];
+        mode_t u;
+
+        assert(_passphrases);
+
+        assert_se(sigemptyset(&mask) == 0);
+        sigset_add_many(&mask, SIGINT, SIGTERM, -1);
+        assert_se(sigprocmask(SIG_BLOCK, &mask, &oldmask) == 0);
+
+        mkdir_p("/run/systemd/ask-password", 0755);
+
+        u = umask(0022);
+        fd = mkostemp(temp, O_CLOEXEC|O_CREAT|O_WRONLY);
+        umask(u);
+
+        if (fd < 0) {
+                log_error("Failed to create password file: %m");
+                r = -errno;
+                goto finish;
+        }
+
+        fchmod(fd, 0644);
+
+        if (!(f = fdopen(fd, "w"))) {
+                log_error("Failed to allocate FILE: %m");
+                r = -errno;
+                goto finish;
+        }
+
+        fd = -1;
+
+        if ((signal_fd = signalfd(-1, &mask, SFD_NONBLOCK|SFD_CLOEXEC)) < 0) {
+                log_error("signalfd(): %m");
+                r = -errno;
+                goto finish;
+        }
+
+        if ((socket_fd = create_socket(&socket_name)) < 0) {
+                r = socket_fd;
+                goto finish;
+        }
+
+        fprintf(f,
+                "[Ask]\n"
+                "PID=%lu\n"
+                "Socket=%s\n"
+                "AcceptCached=%i\n"
+                "NotAfter=%llu\n",
+                (unsigned long) getpid(),
+                socket_name,
+                accept_cached ? 1 : 0,
+                (unsigned long long) until);
+
+        if (message)
+                fprintf(f, "Message=%s\n", message);
+
+        if (icon)
+                fprintf(f, "Icon=%s\n", icon);
+
+        fflush(f);
+
+        if (ferror(f)) {
+                log_error("Failed to write query file: %m");
+                r = -errno;
+                goto finish;
+        }
+
+        memcpy(final, temp, sizeof(temp));
+
+        final[sizeof(final)-11] = 'a';
+        final[sizeof(final)-10] = 's';
+        final[sizeof(final)-9] = 'k';
+
+        if (rename(temp, final) < 0) {
+                log_error("Failed to rename query file: %m");
+                r = -errno;
+                goto finish;
+        }
+
+        zero(pollfd);
+        pollfd[FD_SOCKET].fd = socket_fd;
+        pollfd[FD_SOCKET].events = POLLIN;
+        pollfd[FD_SIGNAL].fd = signal_fd;
+        pollfd[FD_SIGNAL].events = POLLIN;
+
+        for (;;) {
+                char passphrase[LINE_MAX+1];
+                struct msghdr msghdr;
+                struct iovec iovec;
+                struct ucred *ucred;
+                union {
+                        struct cmsghdr cmsghdr;
+                        uint8_t buf[CMSG_SPACE(sizeof(struct ucred))];
+                } control;
+                ssize_t n;
+                int k;
+                usec_t t;
+
+                t = now(CLOCK_MONOTONIC);
+
+                if (until > 0 && until <= t) {
+                        log_notice("Timed out");
+                        r = -ETIME;
+                        goto finish;
+                }
+
+                if ((k = poll(pollfd, _FD_MAX, until > 0 ? (int) ((until-t)/USEC_PER_MSEC) : -1)) < 0) {
+
+                        if (errno == EINTR)
+                                continue;
+
+                        log_error("poll() failed: %m");
+                        r = -errno;
+                        goto finish;
+                }
+
+                if (k <= 0) {
+                        log_notice("Timed out");
+                        r = -ETIME;
+                        goto finish;
+                }
+
+                if (pollfd[FD_SIGNAL].revents & POLLIN) {
+                        r = -EINTR;
+                        goto finish;
+                }
+
+                if (pollfd[FD_SOCKET].revents != POLLIN) {
+                        log_error("Unexpected poll() event.");
+                        r = -EIO;
+                        goto finish;
+                }
+
+                zero(iovec);
+                iovec.iov_base = passphrase;
+                iovec.iov_len = sizeof(passphrase);
+
+                zero(control);
+                zero(msghdr);
+                msghdr.msg_iov = &iovec;
+                msghdr.msg_iovlen = 1;
+                msghdr.msg_control = &control;
+                msghdr.msg_controllen = sizeof(control);
+
+                if ((n = recvmsg(socket_fd, &msghdr, 0)) < 0) {
+
+                        if (errno == EAGAIN ||
+                            errno == EINTR)
+                                continue;
+
+                        log_error("recvmsg() failed: %m");
+                        r = -errno;
+                        goto finish;
+                }
+
+                if (n <= 0) {
+                        log_error("Message too short");
+                        continue;
+                }
+
+                if (msghdr.msg_controllen < CMSG_LEN(sizeof(struct ucred)) ||
+                    control.cmsghdr.cmsg_level != SOL_SOCKET ||
+                    control.cmsghdr.cmsg_type != SCM_CREDENTIALS ||
+                    control.cmsghdr.cmsg_len != CMSG_LEN(sizeof(struct ucred))) {
+                        log_warning("Received message without credentials. Ignoring.");
+                        continue;
+                }
+
+                ucred = (struct ucred*) CMSG_DATA(&control.cmsghdr);
+                if (ucred->uid != 0) {
+                        log_warning("Got request from unprivileged user. Ignoring.");
+                        continue;
+                }
+
+                if (passphrase[0] == '+') {
+                        char **l;
+
+                        if (n == 1)
+                                l = strv_new("", NULL);
+                        else
+                                l = strv_parse_nulstr(passphrase+1, n-1);
+                                /* An empty message refers to the empty password */
+
+                        if (!l) {
+                                r = -ENOMEM;
+                                goto finish;
+                        }
+
+                        if (strv_length(l) <= 0) {
+                                strv_free(l);
+                                log_error("Invalid packet");
+                                continue;
+                        }
+
+                        *_passphrases = l;
+
+                } else if (passphrase[0] == '-') {
+                        r = -ECANCELED;
+                        goto finish;
+                } else {
+                        log_error("Invalid packet");
+                        continue;
+                }
+
+                break;
+        }
+
+        r = 0;
+
+finish:
+        if (fd >= 0)
+                close_nointr_nofail(fd);
+
+        if (socket_name) {
+                unlink(socket_name);
+                free(socket_name);
+        }
+
+        if (socket_fd >= 0)
+                close_nointr_nofail(socket_fd);
+
+        if (signal_fd >= 0)
+                close_nointr_nofail(signal_fd);
+
+        if (f)
+                fclose(f);
+
+        unlink(temp);
+
+        if (final[0])
+                unlink(final);
+
+        assert_se(sigprocmask(SIG_SETMASK, &oldmask, NULL) == 0);
+
+        return r;
+}
+
+int ask_password_auto(const char *message, const char *icon, usec_t until, bool accept_cached, char ***_passphrases) {
+        assert(message);
+        assert(_passphrases);
+
+        if (isatty(STDIN_FILENO)) {
+                int r;
+                char *s = NULL, **l = NULL;
+
+                if ((r = ask_password_tty(message, until, NULL, &s)) < 0)
+                        return r;
+
+                l = strv_new(s, NULL);
+                free(s);
+
+                if (!l)
+                        return -ENOMEM;
+
+                *_passphrases = l;
+                return r;
+
+        } else
+                return ask_password_agent(message, icon, until, accept_cached, _passphrases);
+}
diff --git a/src/ask-password-api.h b/src/ask-password-api.h
new file mode 100644 (file)
index 0000000..fec8625
--- /dev/null
@@ -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 <http://www.gnu.org/licenses/>.
+***/
+
+#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/ask-password.c b/src/ask-password.c
new file mode 100644 (file)
index 0000000..5162f62
--- /dev/null
@@ -0,0 +1,184 @@
+/*-*- 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 <http://www.gnu.org/licenses/>.
+***/
+
+#include <sys/socket.h>
+#include <sys/poll.h>
+#include <sys/types.h>
+#include <assert.h>
+#include <string.h>
+#include <errno.h>
+#include <unistd.h>
+#include <fcntl.h>
+#include <sys/un.h>
+#include <sys/stat.h>
+#include <sys/signalfd.h>
+#include <getopt.h>
+#include <termios.h>
+#include <limits.h>
+#include <stddef.h>
+
+#include "log.h"
+#include "macro.h"
+#include "util.h"
+#include "strv.h"
+#include "ask-password-api.h"
+#include "def.h"
+
+static const char *arg_icon = NULL;
+static const char *arg_message = NULL;
+static bool arg_use_tty = true;
+static usec_t arg_timeout = DEFAULT_TIMEOUT_USEC;
+static bool arg_accept_cached = false;
+static bool arg_multiple = false;
+
+static int help(void) {
+
+        printf("%s [OPTIONS...] MESSAGE\n\n"
+               "Query the user for a system passphrase, via the TTY or an UI agent.\n\n"
+               "  -h --help          Show this help\n"
+               "     --icon=NAME     Icon name\n"
+               "     --timeout=SEC   Timeout in sec\n"
+               "     --no-tty        Ask question via agent even on TTY\n"
+               "     --accept-cached Accept cached passwords\n"
+               "     --multiple      List multiple passwords if available\n",
+               program_invocation_short_name);
+
+        return 0;
+}
+
+static int parse_argv(int argc, char *argv[]) {
+
+        enum {
+                ARG_ICON = 0x100,
+                ARG_TIMEOUT,
+                ARG_NO_TTY,
+                ARG_ACCEPT_CACHED,
+                ARG_MULTIPLE
+        };
+
+        static const struct option options[] = {
+                { "help",          no_argument,       NULL, 'h'               },
+                { "icon",          required_argument, NULL, ARG_ICON          },
+                { "timeout",       required_argument, NULL, ARG_TIMEOUT       },
+                { "no-tty",        no_argument,       NULL, ARG_NO_TTY        },
+                { "accept-cached", no_argument,       NULL, ARG_ACCEPT_CACHED },
+                { "multiple",      no_argument,       NULL, ARG_MULTIPLE      },
+                { NULL,            0,                 NULL, 0                 }
+        };
+
+        int c;
+
+        assert(argc >= 0);
+        assert(argv);
+
+        while ((c = getopt_long(argc, argv, "h", options, NULL)) >= 0) {
+
+                switch (c) {
+
+                case 'h':
+                        help();
+                        return 0;
+
+                case ARG_ICON:
+                        arg_icon = optarg;
+                        break;
+
+                case ARG_TIMEOUT:
+                        if (parse_usec(optarg, &arg_timeout) < 0) {
+                                log_error("Failed to parse --timeout parameter %s", optarg);
+                                return -EINVAL;
+                        }
+                        break;
+
+                case ARG_NO_TTY:
+                        arg_use_tty = false;
+                        break;
+
+                case ARG_ACCEPT_CACHED:
+                        arg_accept_cached = true;
+                        break;
+
+                case ARG_MULTIPLE:
+                        arg_multiple = true;
+                        break;
+
+                case '?':
+                        return -EINVAL;
+
+                default:
+                        log_error("Unknown option code %c", c);
+                        return -EINVAL;
+                }
+        }
+
+        if (optind != argc-1) {
+                help();
+                return -EINVAL;
+        }
+
+        arg_message = argv[optind];
+        return 1;
+}
+
+int main(int argc, char *argv[]) {
+        int r;
+        usec_t timeout;
+
+        log_parse_environment();
+        log_open();
+
+        if ((r = parse_argv(argc, argv)) <= 0)
+                goto finish;
+
+        if (arg_timeout > 0)
+                timeout = now(CLOCK_MONOTONIC) + arg_timeout;
+        else
+                timeout = 0;
+
+        if (arg_use_tty && isatty(STDIN_FILENO)) {
+                char *password = NULL;
+
+                if ((r = ask_password_tty(arg_message, timeout, NULL, &password)) >= 0) {
+                        puts(password);
+                        free(password);
+                }
+
+        } else {
+                char **l;
+
+                if ((r = ask_password_agent(arg_message, arg_icon, timeout, arg_accept_cached, &l)) >= 0) {
+                        char **p;
+
+                        STRV_FOREACH(p, l) {
+                                puts(*p);
+
+                                if (!arg_multiple)
+                                        break;
+                        }
+
+                        strv_free(l);
+                }
+        }
+
+finish:
+
+        return r < 0 ? EXIT_FAILURE : EXIT_SUCCESS;
+}
diff --git a/src/automount.c b/src/automount.c
new file mode 100644 (file)
index 0000000..cf2fb60
--- /dev/null
@@ -0,0 +1,887 @@
+/*-*- 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 <http://www.gnu.org/licenses/>.
+***/
+
+#include <errno.h>
+#include <limits.h>
+#include <sys/mount.h>
+#include <unistd.h>
+#include <fcntl.h>
+#include <sys/epoll.h>
+#include <sys/stat.h>
+#include <linux/auto_fs4.h>
+#include <linux/auto_dev-ioctl.h>
+
+#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"
+
+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(&param);
+        if (ioctl(m->dev_autofs_fd, AUTOFS_DEV_IOCTL_VERSION, &param) < 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(&param);
+        param.ioctlfd = ioctl_fd;
+
+        if (ioctl(dev_autofs_fd, AUTOFS_DEV_IOCTL_PROTOVER, &param) < 0)
+                return -errno;
+
+        major = param.protover.version;
+
+        init_autofs_dev_ioctl(&param);
+        param.ioctlfd = ioctl_fd;
+
+        if (ioctl(dev_autofs_fd, AUTOFS_DEV_IOCTL_PROTOSUBVER, &param) < 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(&param);
+        param.ioctlfd = ioctl_fd;
+        param.timeout.timeout = sec;
+
+        if (ioctl(dev_autofs_fd, AUTOFS_DEV_IOCTL_TIMEOUT, &param) < 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(&param);
+        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, &param) < 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
new file mode 100644 (file)
index 0000000..19baee2
--- /dev/null
@@ -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 <http://www.gnu.org/licenses/>.
+***/
+
+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/binfmt/Makefile b/src/binfmt/Makefile
new file mode 120000 (symlink)
index 0000000..d0b0e8e
--- /dev/null
@@ -0,0 +1 @@
+../Makefile
\ No newline at end of file
diff --git a/src/binfmt/binfmt.c b/src/binfmt/binfmt.c
new file mode 100644 (file)
index 0000000..3c8d815
--- /dev/null
@@ -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 <http://www.gnu.org/licenses/>.
+***/
+
+#include <stdlib.h>
+#include <stdbool.h>
+#include <errno.h>
+#include <string.h>
+#include <stdio.h>
+#include <limits.h>
+#include <stdarg.h>
+
+#include "log.h"
+#include "hashmap.h"
+#include "strv.h"
+#include "util.h"
+
+static int delete_rule(const char *rule) {
+        char *x, *fn = NULL, *e;
+        int r;
+
+        assert(rule[0]);
+
+        if (!(x = strdup(rule)))
+                return -ENOMEM;
+
+        e = strchrnul(x+1, x[0]);
+        *e = 0;
+
+        asprintf(&fn, "/proc/sys/fs/binfmt_misc/%s", x+1);
+        free(x);
+
+        if (!fn)
+                return -ENOMEM;
+
+        r = write_one_line_file(fn, "-1");
+        free(fn);
+
+        return r;
+}
+
+static int apply_rule(const char *rule) {
+        int r;
+
+        delete_rule(rule);
+
+        if ((r = write_one_line_file("/proc/sys/fs/binfmt_misc/register", rule)) < 0) {
+                log_error("Failed to add binary format: %s", strerror(-r));
+                return r;
+        }
+
+        return 0;
+}
+
+static int apply_file(const char *path, bool ignore_enoent) {
+        FILE *f;
+        int r = 0;
+
+        assert(path);
+
+        if (!(f = fopen(path, "re"))) {
+                if (ignore_enoent && errno == ENOENT)
+                        return 0;
+
+                log_error("Failed to open file '%s', ignoring: %m", path);
+                return -errno;
+        }
+
+        log_debug("apply: %s\n", path);
+        while (!feof(f)) {
+                char l[LINE_MAX], *p;
+                int k;
+
+                if (!fgets(l, sizeof(l), f)) {
+                        if (feof(f))
+                                break;
+
+                        log_error("Failed to read file '%s', ignoring: %m", path);
+                        r = -errno;
+                        goto finish;
+                }
+
+                p = strstrip(l);
+
+                if (!*p)
+                        continue;
+
+                if (strchr(COMMENTS, *p))
+                        continue;
+
+                if ((k = apply_rule(p)) < 0 && r == 0)
+                        r = k;
+        }
+
+finish:
+        fclose(f);
+
+        return r;
+}
+
+int main(int argc, char *argv[]) {
+        int r = 0;
+
+        log_set_target(LOG_TARGET_AUTO);
+        log_parse_environment();
+        log_open();
+
+        umask(0022);
+
+        if (argc > 1) {
+                int i;
+
+                for (i = 1; i < argc; i++) {
+                        int k;
+
+                        k = apply_file(argv[i], false);
+                        if (k < 0 && r == 0)
+                                r = k;
+                }
+        } else {
+                char **files, **f;
+
+                r = conf_files_list(&files, ".conf",
+                                    "/etc/binfmt.d",
+                                    "/run/binfmt.d",
+                                    "/usr/local/lib/binfmt.d",
+                                    "/usr/lib/binfmt.d",
+#ifdef HAVE_SPLIT_USR
+                                    "/lib/binfmt.d",
+#endif
+                                    NULL);
+                if (r < 0) {
+                        log_error("Failed to enumerate binfmt.d files: %s", strerror(-r));
+                        goto finish;
+                }
+
+                /* Flush out all rules */
+                write_one_line_file("/proc/sys/fs/binfmt_misc/status", "-1");
+
+                STRV_FOREACH(f, files) {
+                        int k;
+
+                        k = apply_file(*f, true);
+                        if (k < 0 && r == 0)
+                                r = k;
+                }
+
+                strv_free(files);
+        }
+finish:
+        return r < 0 ? EXIT_FAILURE : EXIT_SUCCESS;
+}
diff --git a/src/bridge.c b/src/bridge.c
new file mode 100644 (file)
index 0000000..bfb38a8
--- /dev/null
@@ -0,0 +1,367 @@
+/*-*- 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 <http://www.gnu.org/licenses/>.
+***/
+
+#include <sys/socket.h>
+#include <sys/un.h>
+#include <sys/types.h>
+#include <fcntl.h>
+#include <unistd.h>
+#include <string.h>
+#include <errno.h>
+#include <sys/epoll.h>
+#include <stddef.h>
+
+#include "log.h"
+#include "util.h"
+#include "socket-util.h"
+
+#define BUFFER_SIZE (64*1024)
+#define EXTRA_SIZE 16
+
+static bool initial_nul = false;
+static bool auth_over = false;
+
+static void format_uid(char *buf, size_t l) {
+        char text[20 + 1]; /* enough space for a 64bit integer plus NUL */
+        unsigned j;
+
+        assert(l > 0);
+
+        snprintf(text, sizeof(text)-1, "%llu", (unsigned long long) geteuid());
+        text[sizeof(text)-1] = 0;
+
+        memset(buf, 0, l);
+
+        for (j = 0; text[j] && j*2+2 < l; j++) {
+                buf[j*2]   = hexchar(text[j] >> 4);
+                buf[j*2+1] = hexchar(text[j] & 0xF);
+        }
+
+        buf[j*2] = 0;
+}
+
+static size_t patch_in_line(char *line, size_t l, size_t left) {
+        size_t r;
+
+        if (line[0] == 0 && !initial_nul) {
+                initial_nul = true;
+                line += 1;
+                l -= 1;
+                r = 1;
+        } else
+                r = 0;
+
+        if (l == 5 && strncmp(line, "BEGIN", 5) == 0) {
+                r += l;
+                auth_over = true;
+
+        } else if (l == 17 && strncmp(line, "NEGOTIATE_UNIX_FD", 17) == 0) {
+                memmove(line + 13, line + 17, left);
+                memcpy(line, "NEGOTIATE_NOP", 13);
+                r += 13;
+
+        } else if (l >= 14 && strncmp(line, "AUTH EXTERNAL ", 14) == 0) {
+                char uid[20*2 + 1];
+                size_t len;
+
+                format_uid(uid, sizeof(uid));
+                len = strlen(uid);
+                assert(len <= EXTRA_SIZE);
+
+                memmove(line + 14 + len, line + l, left);
+                memcpy(line + 14, uid, len);
+
+                r += 14 + len;
+        } else
+                r += l;
+
+        return r;
+}
+
+static size_t patch_in_buffer(char* in_buffer, size_t *in_buffer_full) {
+        size_t i, good = 0;
+
+        if (*in_buffer_full <= 0)
+                return *in_buffer_full;
+
+        /* If authentication is done, we don't touch anything anymore */
+        if (auth_over)
+                return *in_buffer_full;
+
+        if (*in_buffer_full < 2)
+                return 0;
+
+        for (i = 0; i <= *in_buffer_full - 2; i ++) {
+
+                /* Fully lines can be send on */
+                if (in_buffer[i] == '\r' && in_buffer[i+1] == '\n') {
+                        if (i > good) {
+                                size_t old_length, new_length;
+
+                                old_length = i - good;
+                                new_length = patch_in_line(in_buffer+good, old_length, *in_buffer_full - i);
+                                *in_buffer_full = *in_buffer_full + new_length - old_length;
+
+                                good += new_length + 2;
+
+                        } else
+                                good = i+2;
+                }
+
+                if (auth_over)
+                        break;
+        }
+
+        return good;
+}
+
+int main(int argc, char *argv[]) {
+        int r = EXIT_FAILURE, fd = -1, ep = -1;
+        union sockaddr_union sa;
+        char in_buffer[BUFFER_SIZE+EXTRA_SIZE], out_buffer[BUFFER_SIZE+EXTRA_SIZE];
+        size_t in_buffer_full = 0, out_buffer_full = 0;
+        struct epoll_event stdin_ev, stdout_ev, fd_ev;
+        bool stdin_readable = false, stdout_writable = false, fd_readable = false, fd_writable = false;
+        bool stdin_rhup = false, stdout_whup = false, fd_rhup = false, fd_whup = false;
+
+        if (argc > 1) {
+                log_error("This program takes no argument.");
+                return EXIT_FAILURE;
+        }
+
+        log_set_target(LOG_TARGET_JOURNAL_OR_KMSG);
+        log_parse_environment();
+        log_open();
+
+        if ((fd = socket(AF_UNIX, SOCK_STREAM|SOCK_CLOEXEC|SOCK_NONBLOCK, 0)) < 0) {
+                log_error("Failed to create socket: %s", strerror(errno));
+                goto finish;
+        }
+
+        zero(sa);
+        sa.un.sun_family = AF_UNIX;
+        strncpy(sa.un.sun_path, "/run/dbus/system_bus_socket", sizeof(sa.un.sun_path));
+
+        if (connect(fd, &sa.sa, offsetof(struct sockaddr_un, sun_path) + strlen(sa.un.sun_path)) < 0) {
+                log_error("Failed to connect: %m");
+                goto finish;
+        }
+
+        fd_nonblock(STDIN_FILENO, 1);
+        fd_nonblock(STDOUT_FILENO, 1);
+
+        if ((ep = epoll_create1(EPOLL_CLOEXEC)) < 0) {
+                log_error("Failed to create epoll: %m");
+                goto finish;
+        }
+
+        zero(stdin_ev);
+        stdin_ev.events = EPOLLIN|EPOLLET;
+        stdin_ev.data.fd = STDIN_FILENO;
+
+        zero(stdout_ev);
+        stdout_ev.events = EPOLLOUT|EPOLLET;
+        stdout_ev.data.fd = STDOUT_FILENO;
+
+        zero(fd_ev);
+        fd_ev.events = EPOLLIN|EPOLLOUT|EPOLLET;
+        fd_ev.data.fd = fd;
+
+        if (epoll_ctl(ep, EPOLL_CTL_ADD, STDIN_FILENO, &stdin_ev) < 0 ||
+            epoll_ctl(ep, EPOLL_CTL_ADD, STDOUT_FILENO, &stdout_ev) < 0 ||
+            epoll_ctl(ep, EPOLL_CTL_ADD, fd, &fd_ev) < 0) {
+                log_error("Failed to regiser fds in epoll: %m");
+                goto finish;
+        }
+
+        do {
+                struct epoll_event ev[16];
+                ssize_t k;
+                int i, nfds;
+
+                if ((nfds = epoll_wait(ep, ev, ELEMENTSOF(ev), -1)) < 0) {
+
+                        if (errno == EINTR || errno == EAGAIN)
+                                continue;
+
+                        log_error("epoll_wait(): %m");
+                        goto finish;
+                }
+
+                assert(nfds >= 1);
+
+                for (i = 0; i < nfds; i++) {
+                        if (ev[i].data.fd == STDIN_FILENO) {
+
+                                if (!stdin_rhup && (ev[i].events & (EPOLLHUP|EPOLLIN)))
+                                        stdin_readable = true;
+
+                        } else if (ev[i].data.fd == STDOUT_FILENO) {
+
+                                if (ev[i].events & EPOLLHUP) {
+                                        stdout_writable = false;
+                                        stdout_whup = true;
+                                }
+
+                                if (!stdout_whup && (ev[i].events & EPOLLOUT))
+                                        stdout_writable = true;
+
+                        } else if (ev[i].data.fd == fd) {
+
+                                if (ev[i].events & EPOLLHUP) {
+                                        fd_writable = false;
+                                        fd_whup = true;
+                                }
+
+                                if (!fd_rhup && (ev[i].events & (EPOLLHUP|EPOLLIN)))
+                                        fd_readable = true;
+
+                                if (!fd_whup && (ev[i].events & EPOLLOUT))
+                                        fd_writable = true;
+                        }
+                }
+
+                while ((stdin_readable && in_buffer_full <= 0) ||
+                       (fd_writable && patch_in_buffer(in_buffer, &in_buffer_full) > 0) ||
+                       (fd_readable && out_buffer_full <= 0) ||
+                       (stdout_writable && out_buffer_full > 0)) {
+
+                        size_t in_buffer_good = 0;
+
+                        if (stdin_readable && in_buffer_full < BUFFER_SIZE) {
+
+                                if ((k = read(STDIN_FILENO, in_buffer + in_buffer_full, BUFFER_SIZE - in_buffer_full)) < 0) {
+
+                                        if (errno == EAGAIN)
+                                                stdin_readable = false;
+                                        else if (errno == EPIPE || errno == ECONNRESET)
+                                                k = 0;
+                                        else {
+                                                log_error("read(): %m");
+                                                goto finish;
+                                        }
+                                } else
+                                        in_buffer_full += (size_t) k;
+
+                                if (k == 0) {
+                                        stdin_rhup = true;
+                                        stdin_readable = false;
+                                        shutdown(STDIN_FILENO, SHUT_RD);
+                                        close_nointr_nofail(STDIN_FILENO);
+                                }
+                        }
+
+                        in_buffer_good = patch_in_buffer(in_buffer, &in_buffer_full);
+
+                        if (fd_writable && in_buffer_good > 0) {
+
+                                if ((k = write(fd, in_buffer, in_buffer_good)) < 0) {
+
+                                        if (errno == EAGAIN)
+                                                fd_writable = false;
+                                        else if (errno == EPIPE || errno == ECONNRESET) {
+                                                fd_whup = true;
+                                                fd_writable = false;
+                                                shutdown(fd, SHUT_WR);
+                                        } else {
+                                                log_error("write(): %m");
+                                                goto finish;
+                                        }
+
+                                } else {
+                                        assert(in_buffer_full >= (size_t) k);
+                                        memmove(in_buffer, in_buffer + k, in_buffer_full - k);
+                                        in_buffer_full -= k;
+                                }
+                        }
+
+                        if (fd_readable && out_buffer_full < BUFFER_SIZE) {
+
+                                if ((k = read(fd, out_buffer + out_buffer_full, BUFFER_SIZE - out_buffer_full)) < 0) {
+
+                                        if (errno == EAGAIN)
+                                                fd_readable = false;
+                                        else if (errno == EPIPE || errno == ECONNRESET)
+                                                k = 0;
+                                        else {
+                                                log_error("read(): %m");
+                                                goto finish;
+                                        }
+                                }  else
+                                        out_buffer_full += (size_t) k;
+
+                                if (k == 0) {
+                                        fd_rhup = true;
+                                        fd_readable = false;
+                                        shutdown(fd, SHUT_RD);
+                                }
+                        }
+
+                        if (stdout_writable && out_buffer_full > 0) {
+
+                                if ((k = write(STDOUT_FILENO, out_buffer, out_buffer_full)) < 0) {
+
+                                        if (errno == EAGAIN)
+                                                stdout_writable = false;
+                                        else if (errno == EPIPE || errno == ECONNRESET) {
+                                                stdout_whup = true;
+                                                stdout_writable = false;
+                                                shutdown(STDOUT_FILENO, SHUT_WR);
+                                                close_nointr(STDOUT_FILENO);
+                                        } else {
+                                                log_error("write(): %m");
+                                                goto finish;
+                                        }
+
+                                } else {
+                                        assert(out_buffer_full >= (size_t) k);
+                                        memmove(out_buffer, out_buffer + k, out_buffer_full - k);
+                                        out_buffer_full -= k;
+                                }
+                        }
+                }
+
+                if (stdin_rhup && in_buffer_full <= 0 && !fd_whup) {
+                        fd_whup = true;
+                        fd_writable = false;
+                        shutdown(fd, SHUT_WR);
+                }
+
+                if (fd_rhup && out_buffer_full <= 0 && !stdout_whup) {
+                        stdout_whup = true;
+                        stdout_writable = false;
+                        shutdown(STDOUT_FILENO, SHUT_WR);
+                        close_nointr(STDOUT_FILENO);
+                }
+
+        } while (!stdout_whup || !fd_whup);
+
+        r = EXIT_SUCCESS;
+
+finish:
+        if (fd >= 0)
+                close_nointr_nofail(fd);
+
+        if (ep >= 0)
+                close_nointr_nofail(ep);
+
+        return r;
+}
diff --git a/src/build.h b/src/build.h
new file mode 100644 (file)
index 0000000..0619013
--- /dev/null
@@ -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 <http://www.gnu.org/licenses/>.
+***/
+
+#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
new file mode 100644 (file)
index 0000000..82d4e99
--- /dev/null
@@ -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 <http://www.gnu.org/licenses/>.
+***/
+
+#include <string.h>
+#include <dbus/dbus.h>
+
+#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/cgls.c b/src/cgls.c
new file mode 100644 (file)
index 0000000..d5417f9
--- /dev/null
@@ -0,0 +1,164 @@
+/*-*- 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 <http://www.gnu.org/licenses/>.
+***/
+
+#include <limits.h>
+#include <stdio.h>
+#include <unistd.h>
+#include <errno.h>
+#include <getopt.h>
+#include <string.h>
+
+#include "cgroup-show.h"
+#include "cgroup-util.h"
+#include "log.h"
+#include "util.h"
+#include "pager.h"
+
+static bool arg_no_pager = false;
+static bool arg_kernel_threads = false;
+
+static void help(void) {
+
+        printf("%s [OPTIONS...] [CGROUP...]\n\n"
+               "Recursively show control group contents.\n\n"
+               "  -h --help           Show this help\n"
+               "     --no-pager       Do not pipe output into a pager\n"
+               "  -k                  Include kernel threads in output\n",
+               program_invocation_short_name);
+}
+
+static int parse_argv(int argc, char *argv[]) {
+
+        enum {
+                ARG_NO_PAGER = 0x100
+        };
+
+        static const struct option options[] = {
+                { "help",      no_argument,       NULL, 'h'          },
+                { "no-pager",  no_argument,       NULL, ARG_NO_PAGER },
+                { NULL,        0,                 NULL, 0            }
+        };
+
+        int c;
+
+        assert(argc >= 1);
+        assert(argv);
+
+        while ((c = getopt_long(argc, argv, "hk", options, NULL)) >= 0) {
+
+                switch (c) {
+
+                case 'h':
+                        help();
+                        return 0;
+
+                case ARG_NO_PAGER:
+                        arg_no_pager = true;
+                        break;
+
+                case 'k':
+                        arg_kernel_threads = true;
+                        break;
+
+                case '?':
+                        return -EINVAL;
+
+                default:
+                        log_error("Unknown option code %c", c);
+                        return -EINVAL;
+                }
+        }
+
+        return 1;
+}
+
+int main(int argc, char *argv[]) {
+        int r = 0, retval = EXIT_FAILURE;
+
+        log_parse_environment();
+        log_open();
+
+        r = parse_argv(argc, argv);
+        if (r < 0)
+                goto finish;
+        else if (r == 0) {
+                retval = EXIT_SUCCESS;
+                goto finish;
+        }
+
+        if (!arg_no_pager)
+                pager_open();
+
+        if (optind < argc) {
+                unsigned i;
+
+                for (i = (unsigned) optind; i < (unsigned) argc; i++) {
+                        int q;
+                        printf("%s:\n", argv[i]);
+
+                        q = show_cgroup_by_path(argv[i], NULL, 0, arg_kernel_threads);
+                        if (q < 0)
+                                r = q;
+                }
+
+        } else {
+                char *p;
+
+                p = get_current_dir_name();
+                if (!p) {
+                        log_error("Cannot determine current working directory: %m");
+                        goto finish;
+                }
+
+                if (path_startswith(p, "/sys/fs/cgroup")) {
+                        printf("Working Directory %s:\n", p);
+                        r = show_cgroup_by_path(p, NULL, 0, arg_kernel_threads);
+                } else {
+                        char *root = NULL;
+                        const char *t = NULL;
+
+                        r = cg_get_by_pid(SYSTEMD_CGROUP_CONTROLLER, 1, &root);
+                        if (r < 0)
+                                t = "/";
+                        else {
+                                if (endswith(root, "/system"))
+                                        root[strlen(root)-7] = 0;
+
+                                t = root[0] ? root : "/";
+                        }
+
+                        r = show_cgroup(SYSTEMD_CGROUP_CONTROLLER, t, NULL, 0, arg_kernel_threads);
+                        free(root);
+                }
+
+                free(p);
+        }
+
+        if (r < 0)
+                log_error("Failed to list cgroup tree: %s", strerror(-r));
+
+        retval = EXIT_SUCCESS;
+
+finish:
+        pager_close();
+
+        return retval;
+}
diff --git a/src/cgroup-attr.c b/src/cgroup-attr.c
new file mode 100644 (file)
index 0000000..474a686
--- /dev/null
@@ -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 <http://www.gnu.org/licenses/>.
+***/
+
+#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
new file mode 100644 (file)
index 0000000..63a73b8
--- /dev/null
@@ -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 <http://www.gnu.org/licenses/>.
+***/
+
+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-show.c b/src/cgroup-show.c
new file mode 100644 (file)
index 0000000..ee2a241
--- /dev/null
@@ -0,0 +1,261 @@
+/*-*- 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 <http://www.gnu.org/licenses/>.
+***/
+
+#include <stdio.h>
+#include <string.h>
+#include <dirent.h>
+#include <errno.h>
+
+#include "util.h"
+#include "macro.h"
+#include "cgroup-util.h"
+#include "cgroup-show.h"
+
+static int compare(const void *a, const void *b) {
+        const pid_t *p = a, *q = b;
+
+        if (*p < *q)
+                return -1;
+        if (*p > *q)
+                return 1;
+        return 0;
+}
+
+static unsigned ilog10(unsigned long ul) {
+        int n = 0;
+
+        while (ul > 0) {
+                n++;
+                ul /= 10;
+        }
+
+        return n;
+}
+
+static int show_cgroup_one_by_path(const char *path, const char *prefix, unsigned n_columns, bool more, bool kernel_threads) {
+        char *fn;
+        FILE *f;
+        size_t n = 0, n_allocated = 0;
+        pid_t *pids = NULL;
+        char *p;
+        pid_t pid, biggest = 0;
+        int r;
+
+        if (n_columns <= 0)
+                n_columns = columns();
+
+        if (!prefix)
+                prefix = "";
+
+        if ((r = cg_fix_path(path, &p)) < 0)
+                return r;
+
+        r = asprintf(&fn, "%s/cgroup.procs", p);
+        free(p);
+
+        if (r < 0)
+                return -ENOMEM;
+
+        f = fopen(fn, "re");
+        free(fn);
+
+        if (!f)
+                return -errno;
+
+        while ((r = cg_read_pid(f, &pid)) > 0) {
+
+                if (!kernel_threads && is_kernel_thread(pid) > 0)
+                        continue;
+
+                if (n >= n_allocated) {
+                        pid_t *npids;
+
+                        n_allocated = MAX(16U, n*2U);
+
+                        if (!(npids = realloc(pids, sizeof(pid_t) * n_allocated))) {
+                                r = -ENOMEM;
+                                goto finish;
+                        }
+
+                        pids = npids;
+                }
+
+                assert(n < n_allocated);
+                pids[n++] = pid;
+
+                if (pid > biggest)
+                        biggest = pid;
+        }
+
+        if (r < 0)
+                goto finish;
+
+        if (n > 0) {
+                unsigned i, m;
+
+                /* Filter duplicates */
+                m = 0;
+                for (i = 0; i < n; i++) {
+                        unsigned j;
+
+                        for (j = i+1; j < n; j++)
+                                if (pids[i] == pids[j])
+                                        break;
+
+                        if (j >= n)
+                                pids[m++] = pids[i];
+                }
+                n = m;
+
+                /* And sort */
+                qsort(pids, n, sizeof(pid_t), compare);
+
+                if (n_columns > 8)
+                        n_columns -= 8;
+                else
+                        n_columns = 20;
+
+                for (i = 0; i < n; i++) {
+                        char *t = NULL;
+
+                        get_process_cmdline(pids[i], n_columns, true, &t);
+
+                        printf("%s%s %*lu %s\n",
+                               prefix,
+                               (more || i < n-1) ? "\342\224\234" : "\342\224\224",
+                               (int) ilog10(biggest),
+                               (unsigned long) pids[i],
+                               strna(t));
+
+                        free(t);
+                }
+        }
+
+        r = 0;
+
+finish:
+        free(pids);
+
+        if (f)
+                fclose(f);
+
+        return r;
+}
+
+int show_cgroup_by_path(const char *path, const char *prefix, unsigned n_columns, bool kernel_threads) {
+        DIR *d;
+        char *last = NULL;
+        char *p1 = NULL, *p2 = NULL, *fn = NULL, *gn = NULL;
+        bool shown_pids = false;
+        int r;
+
+        if (n_columns <= 0)
+                n_columns = columns();
+
+        if (!prefix)
+                prefix = "";
+
+        if ((r = cg_fix_path(path, &fn)) < 0)
+                return r;
+
+        if (!(d = opendir(fn))) {
+                free(fn);
+                return -errno;
+        }
+
+        while ((r = cg_read_subgroup(d, &gn)) > 0) {
+
+                if (!shown_pids) {
+                        show_cgroup_one_by_path(path, prefix, n_columns, true, kernel_threads);
+                        shown_pids = true;
+                }
+
+                if (last) {
+                        printf("%s\342\224\234 %s\n", prefix, file_name_from_path(last));
+
+                        if (!p1)
+                                if (!(p1 = strappend(prefix, "\342\224\202 "))) {
+                                        r = -ENOMEM;
+                                        goto finish;
+                                }
+
+                        show_cgroup_by_path(last, p1, n_columns-2, kernel_threads);
+
+                        free(last);
+                        last = NULL;
+                }
+
+                r = asprintf(&last, "%s/%s", fn, gn);
+                free(gn);
+
+                if (r < 0) {
+                        r = -ENOMEM;
+                        goto finish;
+                }
+        }
+
+        if (r < 0)
+                goto finish;
+
+        if (!shown_pids)
+                show_cgroup_one_by_path(path, prefix, n_columns, !!last, kernel_threads);
+
+        if (last) {
+                printf("%s\342\224\224 %s\n", prefix, file_name_from_path(last));
+
+                if (!p2)
+                        if (!(p2 = strappend(prefix, "  "))) {
+                                r = -ENOMEM;
+                                goto finish;
+                        }
+
+                show_cgroup_by_path(last, p2, n_columns-2, kernel_threads);
+        }
+
+        r = 0;
+
+finish:
+        free(p1);
+        free(p2);
+        free(last);
+        free(fn);
+
+        closedir(d);
+
+        return r;
+}
+
+int show_cgroup(const char *controller, const char *path, const char *prefix, unsigned n_columns, bool kernel_threads) {
+        char *p;
+        int r;
+
+        assert(controller);
+        assert(path);
+
+        r = cg_get_path(controller, path, NULL, &p);
+        if (r < 0)
+                return r;
+
+        r = show_cgroup_by_path(p, prefix, n_columns, kernel_threads);
+        free(p);
+
+        return r;
+}
diff --git a/src/cgroup-show.h b/src/cgroup-show.h
new file mode 100644 (file)
index 0000000..992e17b
--- /dev/null
@@ -0,0 +1,30 @@
+/*-*- Mode: C; c-basic-offset: 8; indent-tabs-mode: nil -*-*/
+
+#ifndef foocgroupshowhfoo
+#define foocgroupshowhfoo
+
+/***
+  This file is part 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 <http://www.gnu.org/licenses/>.
+***/
+
+#include <stdbool.h>
+
+int show_cgroup_by_path(const char *path, const char *prefix, unsigned columns, bool kernel_threads);
+int show_cgroup(const char *controller, const char *path, const char *prefix, unsigned columns, bool kernel_threads);
+
+#endif
diff --git a/src/cgroup-util.c b/src/cgroup-util.c
new file mode 100644 (file)
index 0000000..904d300
--- /dev/null
@@ -0,0 +1,1111 @@
+/*-*- 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 <http://www.gnu.org/licenses/>.
+***/
+
+#include <errno.h>
+#include <unistd.h>
+#include <signal.h>
+#include <string.h>
+#include <stdlib.h>
+#include <dirent.h>
+#include <sys/stat.h>
+#include <sys/types.h>
+#include <ftw.h>
+
+#include "cgroup-util.h"
+#include "log.h"
+#include "set.h"
+#include "macro.h"
+#include "util.h"
+
+int cg_enumerate_processes(const char *controller, const char *path, FILE **_f) {
+        char *fs;
+        int r;
+        FILE *f;
+
+        assert(controller);
+        assert(path);
+        assert(_f);
+
+        if ((r = cg_get_path(controller, path, "cgroup.procs", &fs)) < 0)
+                return r;
+
+        f = fopen(fs, "re");
+        free(fs);
+
+        if (!f)
+                return -errno;
+
+        *_f = f;
+        return 0;
+}
+
+int cg_enumerate_tasks(const char *controller, const char *path, FILE **_f) {
+        char *fs;
+        int r;
+        FILE *f;
+
+        assert(controller);
+        assert(path);
+        assert(_f);
+
+        if ((r = cg_get_path(controller, path, "tasks", &fs)) < 0)
+                return r;
+
+        f = fopen(fs, "re");
+        free(fs);
+
+        if (!f)
+                return -errno;
+
+        *_f = f;
+        return 0;
+}
+
+int cg_read_pid(FILE *f, pid_t *_pid) {
+        unsigned long ul;
+
+        /* Note that the cgroup.procs might contain duplicates! See
+         * cgroups.txt for details. */
+
+        errno = 0;
+        if (fscanf(f, "%lu", &ul) != 1) {
+
+                if (feof(f))
+                        return 0;
+
+                return errno ? -errno : -EIO;
+        }
+
+        if (ul <= 0)
+                return -EIO;
+
+        *_pid = (pid_t) ul;
+        return 1;
+}
+
+int cg_enumerate_subgroups(const char *controller, const char *path, DIR **_d) {
+        char *fs;
+        int r;
+        DIR *d;
+
+        assert(controller);
+        assert(path);
+        assert(_d);
+
+        /* This is not recursive! */
+
+        if ((r = cg_get_path(controller, path, NULL, &fs)) < 0)
+                return r;
+
+        d = opendir(fs);
+        free(fs);
+
+        if (!d)
+                return -errno;
+
+        *_d = d;
+        return 0;
+}
+
+int cg_read_subgroup(DIR *d, char **fn) {
+        struct dirent *de;
+
+        assert(d);
+
+        errno = 0;
+        while ((de = readdir(d))) {
+                char *b;
+
+                if (de->d_type != DT_DIR)
+                        continue;
+
+                if (streq(de->d_name, ".") ||
+                    streq(de->d_name, ".."))
+                        continue;
+
+                if (!(b = strdup(de->d_name)))
+                        return -ENOMEM;
+
+                *fn = b;
+                return 1;
+        }
+
+        if (errno)
+                return -errno;
+
+        return 0;
+}
+
+int cg_rmdir(const char *controller, const char *path, bool honour_sticky) {
+        char *p;
+        int r;
+
+        r = cg_get_path(controller, path, NULL, &p);
+        if (r < 0)
+                return r;
+
+        if (honour_sticky) {
+                char *tasks;
+
+                /* If the sticky bit is set don't remove the directory */
+
+                tasks = strappend(p, "/tasks");
+                if (!tasks) {
+                        free(p);
+                        return -ENOMEM;
+                }
+
+                r = file_is_priv_sticky(tasks);
+                free(tasks);
+
+                if (r > 0) {
+                        free(p);
+                        return 0;
+                }
+        }
+
+        r = rmdir(p);
+        free(p);
+
+        return (r < 0 && errno != ENOENT) ? -errno : 0;
+}
+
+int cg_kill(const char *controller, const char *path, int sig, bool sigcont, bool ignore_self, Set *s) {
+        bool done = false;
+        int r, ret = 0;
+        pid_t my_pid;
+        FILE *f = NULL;
+        Set *allocated_set = NULL;
+
+        assert(controller);
+        assert(path);
+        assert(sig >= 0);
+
+        /* This goes through the tasks list and kills them all. This
+         * is repeated until no further processes are added to the
+         * tasks list, to properly handle forking processes */
+
+        if (!s)
+                if (!(s = allocated_set = set_new(trivial_hash_func, trivial_compare_func)))
+                        return -ENOMEM;
+
+        my_pid = getpid();
+
+        do {
+                pid_t pid = 0;
+                done = true;
+
+                if ((r = cg_enumerate_processes(controller, path, &f)) < 0) {
+                        if (ret >= 0 && r != -ENOENT)
+                                ret = r;
+
+                        goto finish;
+                }
+
+                while ((r = cg_read_pid(f, &pid)) > 0) {
+
+                        if (pid == my_pid && ignore_self)
+                                continue;
+
+                        if (set_get(s, LONG_TO_PTR(pid)) == LONG_TO_PTR(pid))
+                                continue;
+
+                        /* If we haven't killed this process yet, kill
+                         * it */
+                        if (kill(pid, sig) < 0) {
+                                if (ret >= 0 && errno != ESRCH)
+                                        ret = -errno;
+                        } else if (ret == 0) {
+
+                                if (sigcont)
+                                        kill(pid, SIGCONT);
+
+                                ret = 1;
+                        }
+
+                        done = false;
+
+                        if ((r = set_put(s, LONG_TO_PTR(pid))) < 0) {
+                                if (ret >= 0)
+                                        ret = r;
+
+                                goto finish;
+                        }
+                }
+
+                if (r < 0) {
+                        if (ret >= 0)
+                                ret = r;
+
+                        goto finish;
+                }
+
+                fclose(f);
+                f = NULL;
+
+                /* To avoid racing against processes which fork
+                 * quicker than we can kill them we repeat this until
+                 * no new pids need to be killed. */
+
+        } while (!done);
+
+finish:
+        if (allocated_set)
+                set_free(allocated_set);
+
+        if (f)
+                fclose(f);
+
+        return ret;
+}
+
+int cg_kill_recursive(const char *controller, const char *path, int sig, bool sigcont, bool ignore_self, bool rem, Set *s) {
+        int r, ret = 0;
+        DIR *d = NULL;
+        char *fn;
+        Set *allocated_set = NULL;
+
+        assert(path);
+        assert(controller);
+        assert(sig >= 0);
+
+        if (!s)
+                if (!(s = allocated_set = set_new(trivial_hash_func, trivial_compare_func)))
+                        return -ENOMEM;
+
+        ret = cg_kill(controller, path, sig, sigcont, ignore_self, s);
+
+        if ((r = cg_enumerate_subgroups(controller, path, &d)) < 0) {
+                if (ret >= 0 && r != -ENOENT)
+                        ret = r;
+
+                goto finish;
+        }
+
+        while ((r = cg_read_subgroup(d, &fn)) > 0) {
+                char *p = NULL;
+
+                r = asprintf(&p, "%s/%s", path, fn);
+                free(fn);
+
+                if (r < 0) {
+                        if (ret >= 0)
+                                ret = -ENOMEM;
+
+                        goto finish;
+                }
+
+                r = cg_kill_recursive(controller, p, sig, sigcont, ignore_self, rem, s);
+                free(p);
+
+                if (r != 0 && ret >= 0)
+                        ret = r;
+        }
+
+        if (r < 0 && ret >= 0)
+                ret = r;
+
+        if (rem)
+                if ((r = cg_rmdir(controller, path, true)) < 0) {
+                        if (ret >= 0 &&
+                            r != -ENOENT &&
+                            r != -EBUSY)
+                                ret = r;
+                }
+
+finish:
+        if (d)
+                closedir(d);
+
+        if (allocated_set)
+                set_free(allocated_set);
+
+        return ret;
+}
+
+int cg_kill_recursive_and_wait(const char *controller, const char *path, bool rem) {
+        unsigned i;
+
+        assert(path);
+        assert(controller);
+
+        /* This safely kills all processes; first it sends a SIGTERM,
+         * then checks 8 times after 200ms whether the group is now
+         * empty, then kills everything that is left with SIGKILL and
+         * finally checks 5 times after 200ms each whether the group
+         * is finally empty. */
+
+        for (i = 0; i < 15; i++) {
+                int sig, r;
+
+                if (i <= 0)
+                        sig = SIGTERM;
+                else if (i == 9)
+                        sig = SIGKILL;
+                else
+                        sig = 0;
+
+                if ((r = cg_kill_recursive(controller, path, sig, true, true, rem, NULL)) <= 0)
+                        return r;
+
+                usleep(200 * USEC_PER_MSEC);
+        }
+
+        return 0;
+}
+
+int cg_migrate(const char *controller, const char *from, const char *to, bool ignore_self) {
+        bool done = false;
+        Set *s;
+        int r, ret = 0;
+        pid_t my_pid;
+        FILE *f = NULL;
+
+        assert(controller);
+        assert(from);
+        assert(to);
+
+        if (!(s = set_new(trivial_hash_func, trivial_compare_func)))
+                return -ENOMEM;
+
+        my_pid = getpid();
+
+        do {
+                pid_t pid = 0;
+                done = true;
+
+                if ((r = cg_enumerate_tasks(controller, from, &f)) < 0) {
+                        if (ret >= 0 && r != -ENOENT)
+                                ret = r;
+
+                        goto finish;
+                }
+
+                while ((r = cg_read_pid(f, &pid)) > 0) {
+
+                        /* This might do weird stuff if we aren't a
+                         * single-threaded program. However, we
+                         * luckily know we are not */
+                        if (pid == my_pid && ignore_self)
+                                continue;
+
+                        if (set_get(s, LONG_TO_PTR(pid)) == LONG_TO_PTR(pid))
+                                continue;
+
+                        if ((r = cg_attach(controller, to, pid)) < 0) {
+                                if (ret >= 0 && r != -ESRCH)
+                                        ret = r;
+                        } else if (ret == 0)
+                                ret = 1;
+
+                        done = false;
+
+                        if ((r = set_put(s, LONG_TO_PTR(pid))) < 0) {
+                                if (ret >= 0)
+                                        ret = r;
+
+                                goto finish;
+                        }
+                }
+
+                if (r < 0) {
+                        if (ret >= 0)
+                                ret = r;
+
+                        goto finish;
+                }
+
+                fclose(f);
+                f = NULL;
+
+        } while (!done);
+
+finish:
+        set_free(s);
+
+        if (f)
+                fclose(f);
+
+        return ret;
+}
+
+int cg_migrate_recursive(const char *controller, const char *from, const char *to, bool ignore_self, bool rem) {
+        int r, ret = 0;
+        DIR *d = NULL;
+        char *fn;
+
+        assert(controller);
+        assert(from);
+        assert(to);
+
+        ret = cg_migrate(controller, from, to, ignore_self);
+
+        if ((r = cg_enumerate_subgroups(controller, from, &d)) < 0) {
+                if (ret >= 0 && r != -ENOENT)
+                        ret = r;
+                goto finish;
+        }
+
+        while ((r = cg_read_subgroup(d, &fn)) > 0) {
+                char *p = NULL;
+
+                r = asprintf(&p, "%s/%s", from, fn);
+                free(fn);
+
+                if (r < 0) {
+                        if (ret >= 0)
+                                ret = -ENOMEM;
+
+                        goto finish;
+                }
+
+                r = cg_migrate_recursive(controller, p, to, ignore_self, rem);
+                free(p);
+
+                if (r != 0 && ret >= 0)
+                        ret = r;
+        }
+
+        if (r < 0 && ret >= 0)
+                ret = r;
+
+        if (rem)
+                if ((r = cg_rmdir(controller, from, true)) < 0) {
+                        if (ret >= 0 &&
+                            r != -ENOENT &&
+                            r != -EBUSY)
+                                ret = r;
+                }
+
+finish:
+        if (d)
+                closedir(d);
+
+        return ret;
+}
+
+int cg_get_path(const char *controller, const char *path, const char *suffix, char **fs) {
+        const char *p;
+        char *t;
+        static __thread bool good = false;
+
+        assert(controller);
+        assert(fs);
+
+        if (_unlikely_(!good)) {
+                int r;
+
+                r = path_is_mount_point("/sys/fs/cgroup", false);
+                if (r <= 0)
+                        return r < 0 ? r : -ENOENT;
+
+                /* Cache this to save a few stat()s */
+                good = true;
+        }
+
+        if (isempty(controller))
+                return -EINVAL;
+
+        /* This is a very minimal lookup from controller names to
+         * paths. Since we have mounted most hierarchies ourselves
+         * should be kinda safe, but eventually we might want to
+         * extend this to have a fallback to actually check
+         * /proc/mounts. Might need caching then. */
+
+        if (streq(controller, SYSTEMD_CGROUP_CONTROLLER))
+                p = "systemd";
+        else if (startswith(controller, "name="))
+                p = controller + 5;
+        else
+                p = controller;
+
+        if (path && suffix)
+                t = join("/sys/fs/cgroup/", p, "/", path, "/", suffix, NULL);
+        else if (path)
+                t = join("/sys/fs/cgroup/", p, "/", path, NULL);
+        else if (suffix)
+                t = join("/sys/fs/cgroup/", p, "/", suffix, NULL);
+        else
+                t = join("/sys/fs/cgroup/", p, NULL);
+
+        if (!t)
+                return -ENOMEM;
+
+        path_kill_slashes(t);
+
+        *fs = t;
+        return 0;
+}
+
+static int trim_cb(const char *path, const struct stat *sb, int typeflag, struct FTW *ftwbuf) {
+        char *p;
+        bool is_sticky;
+
+        if (typeflag != FTW_DP)
+                return 0;
+
+        if (ftwbuf->level < 1)
+                return 0;
+
+        p = strappend(path, "/tasks");
+        if (!p) {
+                errno = ENOMEM;
+                return 1;
+        }
+
+        is_sticky = file_is_priv_sticky(p) > 0;
+        free(p);
+
+        if (is_sticky)
+                return 0;
+
+        rmdir(path);
+        return 0;
+}
+
+int cg_trim(const char *controller, const char *path, bool delete_root) {
+        char *fs;
+        int r = 0;
+
+        assert(controller);
+        assert(path);
+
+        r = cg_get_path(controller, path, NULL, &fs);
+        if (r < 0)
+                return r;
+
+        errno = 0;
+        if (nftw(fs, trim_cb, 64, FTW_DEPTH|FTW_MOUNT|FTW_PHYS) < 0)
+                r = errno ? -errno : -EIO;
+
+        if (delete_root) {
+                bool is_sticky;
+                char *p;
+
+                p = strappend(fs, "/tasks");
+                if (!p) {
+                        free(fs);
+                        return -ENOMEM;
+                }
+
+                is_sticky = file_is_priv_sticky(p) > 0;
+                free(p);
+
+                if (!is_sticky)
+                        if (rmdir(fs) < 0 && errno != ENOENT) {
+                                if (r == 0)
+                                        r = -errno;
+                        }
+        }
+
+        free(fs);
+
+        return r;
+}
+
+int cg_delete(const char *controller, const char *path) {
+        char *parent;
+        int r;
+
+        assert(controller);
+        assert(path);
+
+        if ((r = parent_of_path(path, &parent)) < 0)
+                return r;
+
+        r = cg_migrate_recursive(controller, path, parent, false, true);
+        free(parent);
+
+        return r == -ENOENT ? 0 : r;
+}
+
+int cg_create(const char *controller, const char *path) {
+        char *fs;
+        int r;
+
+        assert(controller);
+        assert(path);
+
+        if ((r = cg_get_path(controller, path, NULL, &fs)) < 0)
+                return r;
+
+        r = mkdir_parents(fs, 0755);
+
+        if (r >= 0) {
+                if (mkdir(fs, 0755) >= 0)
+                        r = 1;
+                else if (errno == EEXIST)
+                        r = 0;
+                else
+                        r = -errno;
+        }
+
+        free(fs);
+
+        return r;
+}
+
+int cg_attach(const char *controller, const char *path, pid_t pid) {
+        char *fs;
+        int r;
+        char c[32];
+
+        assert(controller);
+        assert(path);
+        assert(pid >= 0);
+
+        if ((r = cg_get_path(controller, path, "tasks", &fs)) < 0)
+                return r;
+
+        if (pid == 0)
+                pid = getpid();
+
+        snprintf(c, sizeof(c), "%lu\n", (unsigned long) pid);
+        char_array_0(c);
+
+        r = write_one_line_file(fs, c);
+        free(fs);
+
+        return r;
+}
+
+int cg_create_and_attach(const char *controller, const char *path, pid_t pid) {
+        int r, q;
+
+        assert(controller);
+        assert(path);
+        assert(pid >= 0);
+
+        if ((r = cg_create(controller, path)) < 0)
+                return r;
+
+        if ((q = cg_attach(controller, path, pid)) < 0)
+                return q;
+
+        /* This does not remove the cgroup on failure */
+
+        return r;
+}
+
+int cg_set_group_access(const char *controller, const char *path, mode_t mode, uid_t uid, gid_t gid) {
+        char *fs;
+        int r;
+
+        assert(controller);
+        assert(path);
+
+        if (mode != (mode_t) -1)
+                mode &= 0777;
+
+        r = cg_get_path(controller, path, NULL, &fs);
+        if (r < 0)
+                return r;
+
+        r = chmod_and_chown(fs, mode, uid, gid);
+        free(fs);
+
+        return r;
+}
+
+int cg_set_task_access(const char *controller, const char *path, mode_t mode, uid_t uid, gid_t gid, int sticky) {
+        char *fs;
+        int r;
+
+        assert(controller);
+        assert(path);
+
+        if (mode == (mode_t) -1 && uid == (uid_t) -1 && gid == (gid_t) -1 && sticky < 0)
+                return 0;
+
+        if (mode != (mode_t) -1)
+                mode &= 0666;
+
+        r = cg_get_path(controller, path, "tasks", &fs);
+        if (r < 0)
+                return r;
+
+        if (sticky >= 0 && mode != (mode_t) -1)
+                /* Both mode and sticky param are passed */
+                mode |= (sticky ? S_ISVTX : 0);
+        else if ((sticky >= 0 && mode == (mode_t) -1) ||
+                 (mode != (mode_t) -1 && sticky < 0)) {
+                struct stat st;
+
+                /* Only one param is passed, hence read the current
+                 * mode from the file itself */
+
+                r = lstat(fs, &st);
+                if (r < 0) {
+                        free(fs);
+                        return -errno;
+                }
+
+                if (mode == (mode_t) -1)
+                        /* No mode set, we just shall set the sticky bit */
+                        mode = (st.st_mode & ~S_ISVTX) | (sticky ? S_ISVTX : 0);
+                else
+                        /* Only mode set, leave sticky bit untouched */
+                        mode = (st.st_mode & ~0777) | mode;
+        }
+
+        r = chmod_and_chown(fs, mode, uid, gid);
+        free(fs);
+
+        return r;
+}
+
+int cg_get_by_pid(const char *controller, pid_t pid, char **path) {
+        int r;
+        char *p = NULL;
+        FILE *f;
+        char *fs;
+        size_t cs;
+
+        assert(controller);
+        assert(path);
+        assert(pid >= 0);
+
+        if (pid == 0)
+                pid = getpid();
+
+        if (asprintf(&fs, "/proc/%lu/cgroup", (unsigned long) pid) < 0)
+                return -ENOMEM;
+
+        f = fopen(fs, "re");
+        free(fs);
+
+        if (!f)
+                return errno == ENOENT ? -ESRCH : -errno;
+
+        cs = strlen(controller);
+
+        while (!feof(f)) {
+                char line[LINE_MAX];
+                char *l;
+
+                errno = 0;
+                if (!(fgets(line, sizeof(line), f))) {
+                        if (feof(f))
+                                break;
+
+                        r = errno ? -errno : -EIO;
+                        goto finish;
+                }
+
+                truncate_nl(line);
+
+                if (!(l = strchr(line, ':')))
+                        continue;
+
+                l++;
+                if (strncmp(l, controller, cs) != 0)
+                        continue;
+
+                if (l[cs] != ':')
+                        continue;
+
+                if (!(p = strdup(l + cs + 1))) {
+                        r = -ENOMEM;
+                        goto finish;
+                }
+
+                *path = p;
+                r = 0;
+                goto finish;
+        }
+
+        r = -ENOENT;
+
+finish:
+        fclose(f);
+
+        return r;
+}
+
+int cg_install_release_agent(const char *controller, const char *agent) {
+        char *fs = NULL, *contents = NULL, *line = NULL, *sc;
+        int r;
+
+        assert(controller);
+        assert(agent);
+
+        if ((r = cg_get_path(controller, NULL, "release_agent", &fs)) < 0)
+                return r;
+
+        if ((r = read_one_line_file(fs, &contents)) < 0)
+                goto finish;
+
+        sc = strstrip(contents);
+        if (sc[0] == 0) {
+
+                if (asprintf(&line, "%s\n", agent) < 0) {
+                        r = -ENOMEM;
+                        goto finish;
+                }
+
+                if ((r = write_one_line_file(fs, line)) < 0)
+                        goto finish;
+
+        } else if (!streq(sc, agent)) {
+                r = -EEXIST;
+                goto finish;
+        }
+
+        free(fs);
+        fs = NULL;
+        if ((r = cg_get_path(controller, NULL, "notify_on_release", &fs)) < 0)
+                goto finish;
+
+        free(contents);
+        contents = NULL;
+        if ((r = read_one_line_file(fs, &contents)) < 0)
+                goto finish;
+
+        sc = strstrip(contents);
+
+        if (streq(sc, "0")) {
+                if ((r = write_one_line_file(fs, "1\n")) < 0)
+                        goto finish;
+
+                r = 1;
+        } else if (!streq(sc, "1")) {
+                r = -EIO;
+                goto finish;
+        } else
+                r = 0;
+
+finish:
+        free(fs);
+        free(contents);
+        free(line);
+
+        return r;
+}
+
+int cg_is_empty(const char *controller, const char *path, bool ignore_self) {
+        pid_t pid = 0;
+        int r;
+        FILE *f = NULL;
+        bool found = false;
+
+        assert(controller);
+        assert(path);
+
+        if ((r = cg_enumerate_tasks(controller, path, &f)) < 0)
+                return r == -ENOENT ? 1 : r;
+
+        while ((r = cg_read_pid(f, &pid)) > 0) {
+
+                if (ignore_self && pid == getpid())
+                        continue;
+
+                found = true;
+                break;
+        }
+
+        fclose(f);
+
+        if (r < 0)
+                return r;
+
+        return !found;
+}
+
+int cg_is_empty_recursive(const char *controller, const char *path, bool ignore_self) {
+        int r;
+        DIR *d = NULL;
+        char *fn;
+
+        assert(controller);
+        assert(path);
+
+        if ((r = cg_is_empty(controller, path, ignore_self)) <= 0)
+                return r;
+
+        if ((r = cg_enumerate_subgroups(controller, path, &d)) < 0)
+                return r == -ENOENT ? 1 : r;
+
+        while ((r = cg_read_subgroup(d, &fn)) > 0) {
+                char *p = NULL;
+
+                r = asprintf(&p, "%s/%s", path, fn);
+                free(fn);
+
+                if (r < 0) {
+                        r = -ENOMEM;
+                        goto finish;
+                }
+
+                r = cg_is_empty_recursive(controller, p, ignore_self);
+                free(p);
+
+                if (r <= 0)
+                        goto finish;
+        }
+
+        if (r >= 0)
+                r = 1;
+
+finish:
+
+        if (d)
+                closedir(d);
+
+        return r;
+}
+
+int cg_split_spec(const char *spec, char **controller, char **path) {
+        const char *e;
+        char *t = NULL, *u = NULL;
+
+        assert(spec);
+        assert(controller || path);
+
+        if (*spec == '/') {
+
+                if (path) {
+                        if (!(t = strdup(spec)))
+                                return -ENOMEM;
+
+                        *path = t;
+                }
+
+                if (controller)
+                        *controller = NULL;
+
+                return 0;
+        }
+
+        if (!(e = strchr(spec, ':'))) {
+
+                if (strchr(spec, '/') || spec[0] == 0)
+                        return -EINVAL;
+
+                if (controller) {
+                        if (!(t = strdup(spec)))
+                                return -ENOMEM;
+
+                        *controller = t;
+                }
+
+                if (path)
+                        *path = NULL;
+
+                return 0;
+        }
+
+        if (e[1] != '/' ||
+            e == spec ||
+            memchr(spec, '/', e-spec))
+                return -EINVAL;
+
+        if (controller)
+                if (!(t = strndup(spec, e-spec)))
+                        return -ENOMEM;
+
+        if (path)
+                if (!(u = strdup(e+1))) {
+                        free(t);
+                        return -ENOMEM;
+                }
+
+        if (controller)
+                *controller = t;
+
+        if (path)
+                *path = u;
+
+        return 0;
+}
+
+int cg_join_spec(const char *controller, const char *path, char **spec) {
+        assert(controller);
+        assert(path);
+
+        if (!path_is_absolute(path) ||
+            controller[0] == 0 ||
+            strchr(controller, ':') ||
+            strchr(controller, '/'))
+                return -EINVAL;
+
+        if (asprintf(spec, "%s:%s", controller, path) < 0)
+                return -ENOMEM;
+
+        return 0;
+}
+
+int cg_fix_path(const char *path, char **result) {
+        char *t, *c, *p;
+        int r;
+
+        assert(path);
+        assert(result);
+
+        /* First check if it already is a filesystem path */
+        if (path_is_absolute(path) &&
+            path_startswith(path, "/sys/fs/cgroup") &&
+            access(path, F_OK) >= 0) {
+
+                if (!(t = strdup(path)))
+                        return -ENOMEM;
+
+                *result = t;
+                return 0;
+        }
+
+        /* Otherwise treat it as cg spec */
+        if ((r = cg_split_spec(path, &c, &p)) < 0)
+                return r;
+
+        r = cg_get_path(c ? c : SYSTEMD_CGROUP_CONTROLLER, p ? p : "/", NULL, result);
+        free(c);
+        free(p);
+
+        return r;
+}
+
+int cg_get_user_path(char **path) {
+        char *root, *p;
+
+        assert(path);
+
+        /* Figure out the place to put user cgroups below. We use the
+         * same as PID 1 has but with the "/system" suffix replaced by
+         * "/user" */
+
+        if (cg_get_by_pid(SYSTEMD_CGROUP_CONTROLLER, 1, &root) < 0)
+                p = strdup("/user");
+        else {
+                if (endswith(root, "/system"))
+                        root[strlen(root) - 7] = 0;
+                else if (streq(root, "/"))
+                        root[0] = 0;
+
+                p = strappend(root, "/user");
+                free(root);
+        }
+
+        if (!p)
+                return -ENOMEM;
+
+        *path = p;
+        return 0;
+}
diff --git a/src/cgroup-util.h b/src/cgroup-util.h
new file mode 100644 (file)
index 0000000..37e4255
--- /dev/null
@@ -0,0 +1,72 @@
+/*-*- Mode: C; c-basic-offset: 8; indent-tabs-mode: nil -*-*/
+
+#ifndef foocgrouputilhfoo
+#define foocgrouputilhfoo
+
+/***
+  This file is part 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 <http://www.gnu.org/licenses/>.
+***/
+
+#include <sys/types.h>
+#include <stdio.h>
+#include <dirent.h>
+
+#include "set.h"
+#include "def.h"
+
+int cg_enumerate_processes(const char *controller, const char *path, FILE **_f);
+int cg_enumerate_tasks(const char *controller, const char *path, FILE **_f);
+int cg_read_pid(FILE *f, pid_t *_pid);
+
+int cg_enumerate_subgroups(const char *controller, const char *path, DIR **_d);
+int cg_read_subgroup(DIR *d, char **fn);
+
+int cg_kill(const char *controller, const char *path, int sig, bool sigcont, bool ignore_self, Set *s);
+int cg_kill_recursive(const char *controller, const char *path, int sig, bool sigcont, bool ignore_self, bool remove, Set *s);
+int cg_kill_recursive_and_wait(const char *controller, const char *path, bool remove);
+
+int cg_migrate(const char *controller, const char *from, const char *to, bool ignore_self);
+int cg_migrate_recursive(const char *controller, const char *from, const char *to, bool ignore_self, bool remove);
+
+int cg_split_spec(const char *spec, char **controller, char **path);
+int cg_join_spec(const char *controller, const char *path, char **spec);
+int cg_fix_path(const char *path, char **result);
+
+int cg_get_path(const char *controller, const char *path, const char *suffix, char **fs);
+int cg_get_by_pid(const char *controller, pid_t pid, char **path);
+
+int cg_trim(const char *controller, const char *path, bool delete_root);
+
+int cg_rmdir(const char *controller, const char *path, bool honour_sticky);
+int cg_delete(const char *controller, const char *path);
+
+int cg_create(const char *controller, const char *path);
+int cg_attach(const char *controller, const char *path, pid_t pid);
+int cg_create_and_attach(const char *controller, const char *path, pid_t pid);
+
+int cg_set_group_access(const char *controller, const char *path, mode_t mode, uid_t uid, gid_t gid);
+int cg_set_task_access(const char *controller, const char *path, mode_t mode, uid_t uid, gid_t gid, int sticky);
+
+int cg_install_release_agent(const char *controller, const char *agent);
+
+int cg_is_empty(const char *controller, const char *path, bool ignore_self);
+int cg_is_empty_recursive(const char *controller, const char *path, bool ignore_self);
+
+int cg_get_user_path(char **path);
+
+#endif
diff --git a/src/cgroup.c b/src/cgroup.c
new file mode 100644 (file)
index 0000000..1f6139e
--- /dev/null
@@ -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 <http://www.gnu.org/licenses/>.
+***/
+
+#include <errno.h>
+#include <assert.h>
+#include <unistd.h>
+#include <sys/types.h>
+#include <signal.h>
+#include <sys/mount.h>
+#include <fcntl.h>
+
+#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, &current)) < 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
new file mode 100644 (file)
index 0000000..5faa7dc
--- /dev/null
@@ -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 <http://www.gnu.org/licenses/>.
+***/
+
+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/cgroups-agent.c b/src/cgroups-agent.c
new file mode 100644 (file)
index 0000000..1bbc882
--- /dev/null
@@ -0,0 +1,101 @@
+/*-*- 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 <http://www.gnu.org/licenses/>.
+***/
+
+#include <dbus/dbus.h>
+
+#include <stdlib.h>
+
+#include "log.h"
+#include "dbus-common.h"
+
+int main(int argc, char *argv[]) {
+        DBusError error;
+        DBusConnection *bus = NULL;
+        DBusMessage *m = NULL;
+        int r = EXIT_FAILURE;
+
+        dbus_error_init(&error);
+
+        if (argc != 2) {
+                log_error("Incorrect number of arguments.");
+                goto finish;
+        }
+
+        log_set_target(LOG_TARGET_AUTO);
+        log_parse_environment();
+        log_open();
+
+        /* We send this event to the private D-Bus socket and then the
+         * system instance will forward this to the system bus. We do
+         * this to avoid an activation loop when we start dbus when we
+         * are called when the dbus service is shut down. */
+
+        if (!(bus = dbus_connection_open_private("unix:path=/run/systemd/private", &error))) {
+#ifndef LEGACY
+                dbus_error_free(&error);
+
+                /* Retry with the pre v21 socket name, to ease upgrades */
+                if (!(bus = dbus_connection_open_private("unix:abstract=/org/freedesktop/systemd1/private", &error))) {
+#endif
+                        log_error("Failed to get D-Bus connection: %s", bus_error_message(&error));
+                        goto finish;
+                }
+#ifndef LEGACY
+        }
+#endif
+
+        if (bus_check_peercred(bus) < 0) {
+                log_error("Bus owner not root.");
+                goto finish;
+        }
+
+        if (!(m = dbus_message_new_signal("/org/freedesktop/systemd1/agent", "org.freedesktop.systemd1.Agent", "Released"))) {
+                log_error("Could not allocate signal message.");
+                goto finish;
+        }
+
+        if (!dbus_message_append_args(m,
+                                      DBUS_TYPE_STRING, &argv[1],
+                                      DBUS_TYPE_INVALID)) {
+                log_error("Could not attach group information to signal message.");
+                goto finish;
+        }
+
+        if (!dbus_connection_send(bus, m, NULL)) {
+                log_error("Failed to send signal message on private connection.");
+                goto finish;
+        }
+
+        r = EXIT_SUCCESS;
+
+finish:
+        if (bus) {
+                dbus_connection_flush(bus);
+                dbus_connection_close(bus);
+                dbus_connection_unref(bus);
+        }
+
+        if (m)
+                dbus_message_unref(m);
+
+        dbus_error_free(&error);
+        return r;
+}
diff --git a/src/cgtop.c b/src/cgtop.c
new file mode 100644 (file)
index 0000000..8b8617d
--- /dev/null
@@ -0,0 +1,729 @@
+/*-*- Mode: C; c-basic-offset: 8; indent-tabs-mode: nil -*-*/
+
+/***
+  This file is part of systemd.
+
+  Copyright 2012 Lennart Poettering
+
+  systemd is free software; you can redistribute it and/or modify it
+  under the terms of the GNU 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 <http://www.gnu.org/licenses/>.
+***/
+
+#include <errno.h>
+#include <string.h>
+#include <stdlib.h>
+#include <unistd.h>
+#include <alloca.h>
+#include <getopt.h>
+
+#include "util.h"
+#include "hashmap.h"
+#include "cgroup-util.h"
+
+typedef struct Group {
+        char *path;
+
+        bool n_tasks_valid:1;
+        bool cpu_valid:1;
+        bool memory_valid:1;
+        bool io_valid:1;
+
+        unsigned n_tasks;
+
+        unsigned cpu_iteration;
+        uint64_t cpu_usage;
+        struct timespec cpu_timestamp;
+        double cpu_fraction;
+
+        uint64_t memory;
+
+        unsigned io_iteration;
+        uint64_t io_input, io_output;
+        struct timespec io_timestamp;
+        uint64_t io_input_bps, io_output_bps;
+} Group;
+
+static unsigned arg_depth = 2;
+static usec_t arg_delay = 1*USEC_PER_SEC;
+
+static enum {
+        ORDER_PATH,
+        ORDER_TASKS,
+        ORDER_CPU,
+        ORDER_MEMORY,
+        ORDER_IO
+} arg_order = ORDER_CPU;
+
+static void group_free(Group *g) {
+        assert(g);
+
+        free(g->path);
+        free(g);
+}
+
+static void group_hashmap_clear(Hashmap *h) {
+        Group *g;
+
+        while ((g = hashmap_steal_first(h)))
+                group_free(g);
+}
+
+static void group_hashmap_free(Hashmap *h) {
+        group_hashmap_clear(h);
+        hashmap_free(h);
+}
+
+static int process(const char *controller, const char *path, Hashmap *a, Hashmap *b, unsigned iteration) {
+        Group *g;
+        int r;
+        FILE *f;
+        pid_t pid;
+        unsigned n;
+
+        assert(controller);
+        assert(path);
+        assert(a);
+
+        g = hashmap_get(a, path);
+        if (!g) {
+                g = hashmap_get(b, path);
+                if (!g) {
+                        g = new0(Group, 1);
+                        if (!g)
+                                return -ENOMEM;
+
+                        g->path = strdup(path);
+                        if (!g->path) {
+                                group_free(g);
+                                return -ENOMEM;
+                        }
+
+                        r = hashmap_put(a, g->path, g);
+                        if (r < 0) {
+                                group_free(g);
+                                return r;
+                        }
+                } else {
+                        assert_se(hashmap_move_one(a, b, path) == 0);
+                        g->cpu_valid = g->memory_valid = g->io_valid = g->n_tasks_valid = false;
+                }
+        }
+
+        /* Regardless which controller, let's find the maximum number
+         * of processes in any of it */
+
+        r = cg_enumerate_tasks(controller, path, &f);
+        if (r < 0)
+                return r;
+
+        n = 0;
+        while (cg_read_pid(f, &pid) > 0)
+                n++;
+        fclose(f);
+
+        if (n > 0) {
+                if (g->n_tasks_valid)
+                        g->n_tasks = MAX(g->n_tasks, n);
+                else
+                        g->n_tasks = n;
+
+                g->n_tasks_valid = true;
+        }
+
+        if (streq(controller, "cpuacct")) {
+                uint64_t new_usage;
+                char *p, *v;
+                struct timespec ts;
+
+                r = cg_get_path(controller, path, "cpuacct.usage", &p);
+                if (r < 0)
+                        return r;
+
+                r = read_one_line_file(p, &v);
+                free(p);
+                if (r < 0)
+                        return r;
+
+                r = safe_atou64(v, &new_usage);
+                free(v);
+                if (r < 0)
+                        return r;
+
+                assert_se(clock_gettime(CLOCK_MONOTONIC, &ts) == 0);
+
+                if (g->cpu_iteration == iteration - 1) {
+                        uint64_t x, y;
+
+                        x = ((uint64_t) ts.tv_sec * 1000000000ULL + (uint64_t) ts.tv_nsec) -
+                                ((uint64_t) g->cpu_timestamp.tv_sec * 1000000000ULL + (uint64_t) g->cpu_timestamp.tv_nsec);
+
+                        y = new_usage - g->cpu_usage;
+
+                        if (y > 0) {
+                                g->cpu_fraction = (double) y / (double) x;
+                                g->cpu_valid = true;
+                        }
+                }
+
+                g->cpu_usage = new_usage;
+                g->cpu_timestamp = ts;
+                g->cpu_iteration = iteration;
+
+        } else if (streq(controller, "memory")) {
+                char *p, *v;
+
+                r = cg_get_path(controller, path, "memory.usage_in_bytes", &p);
+                if (r < 0)
+                        return r;
+
+                r = read_one_line_file(p, &v);
+                free(p);
+                if (r < 0)
+                        return r;
+
+                r = safe_atou64(v, &g->memory);
+                free(v);
+                if (r < 0)
+                        return r;
+
+                if (g->memory > 0)
+                        g->memory_valid = true;
+
+        } else if (streq(controller, "blkio")) {
+                char *p;
+                uint64_t wr = 0, rd = 0;
+                struct timespec ts;
+
+                r = cg_get_path(controller, path, "blkio.io_service_bytes", &p);
+                if (r < 0)
+                        return r;
+
+                f = fopen(p, "re");
+                free(p);
+
+                if (!f)
+                        return -errno;
+
+                for (;;) {
+                        char line[LINE_MAX], *l;
+                        uint64_t k, *q;
+
+                        if (!fgets(line, sizeof(line), f))
+                                break;
+
+                        l = strstrip(line);
+                        l += strcspn(l, WHITESPACE);
+                        l += strspn(l, WHITESPACE);
+
+                        if (first_word(l, "Read")) {
+                                l += 4;
+                                q = &rd;
+                        } else if (first_word(l, "Write")) {
+                                l += 5;
+                                q = &wr;
+                        } else
+                                continue;
+
+                        l += strspn(l, WHITESPACE);
+                        r = safe_atou64(l, &k);
+                        if (r < 0)
+                                continue;
+
+                        *q += k;
+                }
+
+                fclose(f);
+
+                assert_se(clock_gettime(CLOCK_MONOTONIC, &ts) == 0);
+
+                if (g->io_iteration == iteration - 1) {
+                        uint64_t x, yr, yw;
+
+                        x = ((uint64_t) ts.tv_sec * 1000000000ULL + (uint64_t) ts.tv_nsec) -
+                                ((uint64_t) g->io_timestamp.tv_sec * 1000000000ULL + (uint64_t) g->io_timestamp.tv_nsec);
+
+                        yr = rd - g->io_input;
+                        yw = wr - g->io_output;
+
+                        if (yr > 0 || yw > 0) {
+                                g->io_input_bps = (yr * 1000000000ULL) / x;
+                                g->io_output_bps = (yw * 1000000000ULL) / x;
+                                g->io_valid = true;
+
+                        }
+                }
+
+                g->io_input = rd;
+                g->io_output = wr;
+                g->io_timestamp = ts;
+                g->io_iteration = iteration;
+        }
+
+        return 0;
+}
+
+static int refresh_one(
+                const char *controller,
+                const char *path,
+                Hashmap *a,
+                Hashmap *b,
+                unsigned iteration,
+                unsigned depth) {
+
+        DIR *d = NULL;
+        int r;
+
+        assert(controller);
+        assert(path);
+        assert(a);
+
+        if (depth > arg_depth)
+                return 0;
+
+        r = process(controller, path, a, b, iteration);
+        if (r < 0)
+                return r;
+
+        r = cg_enumerate_subgroups(controller, path, &d);
+        if (r < 0) {
+                if (r == ENOENT)
+                        return 0;
+
+                return r;
+        }
+
+        for (;;) {
+                char *fn, *p;
+
+                r = cg_read_subgroup(d, &fn);
+                if (r <= 0)
+                        goto finish;
+
+                p = join(path, "/", fn, NULL);
+                free(fn);
+
+                if (!p) {
+                        r = -ENOMEM;
+                        goto finish;
+                }
+
+                path_kill_slashes(p);
+
+                r = refresh_one(controller, p, a, b, iteration, depth + 1);
+                free(p);
+
+                if (r < 0)
+                        goto finish;
+        }
+
+finish:
+        if (d)
+                closedir(d);
+
+        return r;
+}
+
+static int refresh(Hashmap *a, Hashmap *b, unsigned iteration) {
+        int r;
+
+        assert(a);
+
+        r = refresh_one("name=systemd", "/", a, b, iteration, 0);
+        if (r < 0)
+                return r;
+
+        r = refresh_one("cpuacct", "/", a, b, iteration, 0);
+        if (r < 0)
+                return r;
+
+        r = refresh_one("memory", "/", a, b, iteration, 0);
+        if (r < 0)
+                return r;
+
+        return refresh_one("blkio", "/", a, b, iteration, 0);
+}
+
+static int group_compare(const void*a, const void *b) {
+        const Group *x = *(Group**)a, *y = *(Group**)b;
+
+        if (path_startswith(y->path, x->path))
+                return -1;
+        if (path_startswith(x->path, y->path))
+                return 1;
+
+        if (arg_order == ORDER_CPU) {
+                if (x->cpu_valid && y->cpu_valid) {
+
+                        if (x->cpu_fraction > y->cpu_fraction)
+                                return -1;
+                        else if (x->cpu_fraction < y->cpu_fraction)
+                                return 1;
+                } else if (x->cpu_valid)
+                        return -1;
+                else if (y->cpu_valid)
+                        return 1;
+        }
+
+        if (arg_order == ORDER_TASKS) {
+
+                if (x->n_tasks_valid && y->n_tasks_valid) {
+                        if (x->n_tasks > y->n_tasks)
+                                return -1;
+                        else if (x->n_tasks < y->n_tasks)
+                                return 1;
+                } else if (x->n_tasks_valid)
+                        return -1;
+                else if (y->n_tasks_valid)
+                        return 1;
+        }
+
+        if (arg_order == ORDER_MEMORY) {
+                if (x->memory_valid && y->memory_valid) {
+                        if (x->memory > y->memory)
+                                return -1;
+                        else if (x->memory < y->memory)
+                                return 1;
+                } else if (x->memory_valid)
+                        return -1;
+                else if (y->memory_valid)
+                        return 1;
+        }
+
+        if (arg_order == ORDER_IO) {
+                if (x->io_valid && y->io_valid) {
+                        if (x->io_input_bps + x->io_output_bps > y->io_input_bps + y->io_output_bps)
+                                return -1;
+                        else if (x->io_input_bps + x->io_output_bps < y->io_input_bps + y->io_output_bps)
+                                return 1;
+                } else if (x->io_valid)
+                        return -1;
+                else if (y->io_valid)
+                        return 1;
+        }
+
+        return strcmp(x->path, y->path);
+}
+
+static int display(Hashmap *a) {
+        Iterator i;
+        Group *g;
+        Group **array;
+        unsigned rows, n = 0, j;
+
+        assert(a);
+
+        /* Set cursor to top left corner and clear screen */
+        fputs("\033[H"
+              "\033[2J", stdout);
+
+        array = alloca(sizeof(Group*) * hashmap_size(a));
+
+        HASHMAP_FOREACH(g, a, i)
+                if (g->n_tasks_valid || g->cpu_valid || g->memory_valid || g->io_valid)
+                        array[n++] = g;
+
+        qsort(array, n, sizeof(Group*), group_compare);
+
+        rows = fd_lines(STDOUT_FILENO);
+        if (rows <= 0)
+                rows = 25;
+
+        printf("%s%-37s%s %s%7s%s %s%6s%s %s%8s%s %s%8s%s %s%8s%s\n\n",
+               arg_order == ORDER_PATH   ? ANSI_HIGHLIGHT_ON : "", "Path",     arg_order == ORDER_PATH   ? ANSI_HIGHLIGHT_OFF : "",
+               arg_order == ORDER_TASKS  ? ANSI_HIGHLIGHT_ON : "", "Tasks",    arg_order == ORDER_TASKS  ? ANSI_HIGHLIGHT_OFF : "",
+               arg_order == ORDER_CPU    ? ANSI_HIGHLIGHT_ON : "", "%CPU",     arg_order == ORDER_CPU    ? ANSI_HIGHLIGHT_OFF : "",
+               arg_order == ORDER_MEMORY ? ANSI_HIGHLIGHT_ON : "", "Memory",   arg_order == ORDER_MEMORY ? ANSI_HIGHLIGHT_OFF : "",
+               arg_order == ORDER_IO     ? ANSI_HIGHLIGHT_ON : "", "Input/s",  arg_order == ORDER_IO     ? ANSI_HIGHLIGHT_OFF : "",
+               arg_order == ORDER_IO     ? ANSI_HIGHLIGHT_ON : "", "Output/s", arg_order == ORDER_IO     ? ANSI_HIGHLIGHT_OFF : "");
+
+        for (j = 0; j < n; j++) {
+                char *p;
+                char m[FORMAT_BYTES_MAX];
+
+                if (j + 5 > rows)
+                        break;
+
+                g = array[j];
+
+                p = ellipsize(g->path, 37, 33);
+                printf("%-37s", p ? p : g->path);
+                free(p);
+
+                if (g->n_tasks_valid)
+                        printf(" %7u", g->n_tasks);
+                else
+                        fputs("       -", stdout);
+
+                if (g->cpu_valid)
+                        printf(" %6.1f", g->cpu_fraction*100);
+                else
+                        fputs("      -", stdout);
+
+                if (g->memory_valid)
+                        printf(" %8s", format_bytes(m, sizeof(m), g->memory));
+                else
+                        fputs("        -", stdout);
+
+                if (g->io_valid) {
+                        printf(" %8s",
+                               format_bytes(m, sizeof(m), g->io_input_bps));
+                        printf(" %8s",
+                               format_bytes(m, sizeof(m), g->io_output_bps));
+                } else
+                        fputs("        -        -", stdout);
+
+                putchar('\n');
+        }
+
+        return 0;
+}
+
+static void help(void) {
+
+        printf("%s [OPTIONS...]\n\n"
+               "Show top control groups by their resource usage.\n\n"
+               "  -h --help           Show this help\n"
+               "  -p                  Order by path\n"
+               "  -t                  Order by number of tasks\n"
+               "  -c                  Order by CPU load\n"
+               "  -m                  Order by memory load\n"
+               "  -i                  Order by IO load\n"
+               "  -d --delay=DELAY    Specify delay\n"
+               "     --depth=DEPTH    Maximum traversal depth (default: 2)\n",
+               program_invocation_short_name);
+}
+
+static int parse_argv(int argc, char *argv[]) {
+
+        enum {
+                ARG_DEPTH = 0x100
+        };
+
+        static const struct option options[] = {
+                { "help",  no_argument,       NULL, 'h'       },
+                { "delay", required_argument, NULL, 'd'       },
+                { "depth", required_argument, NULL, ARG_DEPTH },
+                { NULL,    0,                 NULL, 0         }
+        };
+
+        int c;
+        int r;
+
+        assert(argc >= 1);
+        assert(argv);
+
+        while ((c = getopt_long(argc, argv, "hptcmid:", options, NULL)) >= 0) {
+
+                switch (c) {
+
+                case 'h':
+                        help();
+                        return 0;
+
+                case ARG_DEPTH:
+                        r = safe_atou(optarg, &arg_depth);
+                        if (r < 0) {
+                                log_error("Failed to parse depth parameter.");
+                                return -EINVAL;
+                        }
+
+                        break;
+
+                case 'd':
+                        r = parse_usec(optarg, &arg_delay);
+                        if (r < 0 || arg_delay <= 0) {
+                                log_error("Failed to parse delay parameter.");
+                                return -EINVAL;
+                        }
+
+                        break;
+
+                case 'p':
+                        arg_order = ORDER_PATH;
+                        break;
+
+                case 't':
+                        arg_order = ORDER_TASKS;
+                        break;
+
+                case 'c':
+                        arg_order = ORDER_CPU;
+                        break;
+
+                case 'm':
+                        arg_order = ORDER_MEMORY;
+                        break;
+
+                case 'i':
+                        arg_order = ORDER_IO;
+                        break;
+
+                case '?':
+                        return -EINVAL;
+
+                default:
+                        log_error("Unknown option code %c", c);
+                        return -EINVAL;
+                }
+        }
+
+        if (optind < argc) {
+                log_error("Too many arguments.");
+                return -EINVAL;
+        }
+
+        return 1;
+}
+
+int main(int argc, char *argv[]) {
+        int r;
+        Hashmap *a = NULL, *b = NULL;
+        unsigned iteration = 0;
+        usec_t last_refresh = 0;
+        bool quit = false, immediate_refresh = false;
+
+        log_parse_environment();
+        log_open();
+
+        r = parse_argv(argc, argv);
+        if (r <= 0)
+                goto finish;
+
+        a = hashmap_new(string_hash_func, string_compare_func);
+        b = hashmap_new(string_hash_func, string_compare_func);
+        if (!a || !b) {
+                log_error("Out of memory");
+                r = -ENOMEM;
+                goto finish;
+        }
+
+        while (!quit) {
+                Hashmap *c;
+                usec_t t;
+                char key;
+                char h[FORMAT_TIMESPAN_MAX];
+
+                t = now(CLOCK_MONOTONIC);
+
+                if (t >= last_refresh + arg_delay || immediate_refresh) {
+
+                        r = refresh(a, b, iteration++);
+                        if (r < 0)
+                                goto finish;
+
+                        group_hashmap_clear(b);
+
+                        c = a;
+                        a = b;
+                        b = c;
+
+                        last_refresh = t;
+                        immediate_refresh = false;
+                }
+
+                r = display(b);
+                if (r < 0)
+                        goto finish;
+
+                r = read_one_char(stdin, &key, last_refresh + arg_delay - t, NULL);
+                if (r == -ETIMEDOUT)
+                        continue;
+                if (r < 0) {
+                        log_error("Couldn't read key: %s", strerror(-r));
+                        goto finish;
+                }
+
+                fputs("\r \r", stdout);
+                fflush(stdout);
+
+                switch (key) {
+
+                case ' ':
+                        immediate_refresh = true;
+                        break;
+
+                case 'q':
+                        quit = true;
+                        break;
+
+                case 'p':
+                        arg_order = ORDER_PATH;
+                        break;
+
+                case 't':
+                        arg_order = ORDER_TASKS;
+                        break;
+
+                case 'c':
+                        arg_order = ORDER_CPU;
+                        break;
+
+                case 'm':
+                        arg_order = ORDER_MEMORY;
+                        break;
+
+                case 'i':
+                        arg_order = ORDER_IO;
+                        break;
+
+                case '+':
+                        if (arg_delay < USEC_PER_SEC)
+                                arg_delay += USEC_PER_MSEC*250;
+                        else
+                                arg_delay += USEC_PER_SEC;
+
+                        fprintf(stdout, "\nIncreased delay to %s.", format_timespan(h, sizeof(h), arg_delay));
+                        fflush(stdout);
+                        sleep(1);
+                        break;
+
+                case '-':
+                        if (arg_delay <= USEC_PER_MSEC*500)
+                                arg_delay = USEC_PER_MSEC*250;
+                        else if (arg_delay < USEC_PER_MSEC*1250)
+                                arg_delay -= USEC_PER_MSEC*250;
+                        else
+                                arg_delay -= USEC_PER_SEC;
+
+                        fprintf(stdout, "\nDecreased delay to %s.", format_timespan(h, sizeof(h), arg_delay));
+                        fflush(stdout);
+                        sleep(1);
+                        break;
+
+                case '?':
+                case 'h':
+                        fprintf(stdout,
+                                "\t<" ANSI_HIGHLIGHT_ON "P" ANSI_HIGHLIGHT_OFF "> By path; <" ANSI_HIGHLIGHT_ON "T" ANSI_HIGHLIGHT_OFF "> By tasks; <" ANSI_HIGHLIGHT_ON "C" ANSI_HIGHLIGHT_OFF "> By CPU; <" ANSI_HIGHLIGHT_ON "M" ANSI_HIGHLIGHT_OFF "> By memory; <" ANSI_HIGHLIGHT_ON "I" ANSI_HIGHLIGHT_OFF "> By I/O\n"
+                                "\t<" ANSI_HIGHLIGHT_ON "Q" ANSI_HIGHLIGHT_OFF "> Quit; <" ANSI_HIGHLIGHT_ON "+" ANSI_HIGHLIGHT_OFF "> Increase delay; <" ANSI_HIGHLIGHT_ON "-" ANSI_HIGHLIGHT_OFF "> Decrease delay; <" ANSI_HIGHLIGHT_ON "SPACE" ANSI_HIGHLIGHT_OFF "> Refresh");
+                        fflush(stdout);
+                        sleep(3);
+                        break;
+
+                default:
+                        fprintf(stdout, "\nUnknown key '%c'. Ignoring.", key);
+                        fflush(stdout);
+                        sleep(1);
+                        break;
+                }
+        }
+
+        log_info("Exiting.");
+
+        r = 0;
+
+finish:
+        group_hashmap_free(a);
+        group_hashmap_free(b);
+
+        return r < 0 ? EXIT_FAILURE : EXIT_SUCCESS;
+}
diff --git a/src/condition.c b/src/condition.c
new file mode 100644 (file)
index 0000000..2b51a16
--- /dev/null
@@ -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 <http://www.gnu.org/licenses/>.
+***/
+
+#include <stdlib.h>
+#include <errno.h>
+#include <string.h>
+#include <unistd.h>
+#include <sys/capability.h>
+
+#ifdef HAVE_SELINUX
+#include <selinux/selinux.h>
+#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
new file mode 100644 (file)
index 0000000..71b1c67
--- /dev/null
@@ -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 <http://www.gnu.org/licenses/>.
+***/
+
+#include <stdbool.h>
+
+#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/conf-parser.c b/src/conf-parser.c
new file mode 100644 (file)
index 0000000..a9b0113
--- /dev/null
@@ -0,0 +1,852 @@
+/*-*- 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 <http://www.gnu.org/licenses/>.
+***/
+
+#include <string.h>
+#include <stdio.h>
+#include <errno.h>
+#include <assert.h>
+#include <stdlib.h>
+
+#include "conf-parser.h"
+#include "util.h"
+#include "macro.h"
+#include "strv.h"
+#include "log.h"
+#include "utf8.h"
+
+int config_item_table_lookup(
+                void *table,
+                const char *section,
+                const char *lvalue,
+                ConfigParserCallback *func,
+                int *ltype,
+                void **data,
+                void *userdata) {
+
+        ConfigTableItem *t;
+
+        assert(table);
+        assert(lvalue);
+        assert(func);
+        assert(ltype);
+        assert(data);
+
+        for (t = table; t->lvalue; t++) {
+
+                if (!streq(lvalue, t->lvalue))
+                        continue;
+
+                if (!streq_ptr(section, t->section))
+                        continue;
+
+                *func = t->parse;
+                *ltype = t->ltype;
+                *data = t->data;
+                return 1;
+        }
+
+        return 0;
+}
+
+int config_item_perf_lookup(
+                void *table,
+                const char *section,
+                const char *lvalue,
+                ConfigParserCallback *func,
+                int *ltype,
+                void **data,
+                void *userdata) {
+
+        ConfigPerfItemLookup lookup = (ConfigPerfItemLookup) table;
+        const ConfigPerfItem *p;
+
+        assert(table);
+        assert(lvalue);
+        assert(func);
+        assert(ltype);
+        assert(data);
+
+        if (!section)
+                p = lookup(lvalue, strlen(lvalue));
+        else {
+                char *key;
+
+                key = join(section, ".", lvalue, NULL);
+                if (!key)
+                        return -ENOMEM;
+
+                p = lookup(key, strlen(key));
+                free(key);
+        }
+
+        if (!p)
+                return 0;
+
+        *func = p->parse;
+        *ltype = p->ltype;
+        *data = (uint8_t*) userdata + p->offset;
+        return 1;
+}
+
+/* Run the user supplied parser for an assignment */
+static int next_assignment(
+                const char *filename,
+                unsigned line,
+                ConfigItemLookup lookup,
+                void *table,
+                const char *section,
+                const char *lvalue,
+                const char *rvalue,
+                bool relaxed,
+                void *userdata) {
+
+        ConfigParserCallback func = NULL;
+        int ltype = 0;
+        void *data = NULL;
+        int r;
+
+        assert(filename);
+        assert(line > 0);
+        assert(lookup);
+        assert(lvalue);
+        assert(rvalue);
+
+        r = lookup(table, section, lvalue, &func, &ltype, &data, userdata);
+        if (r < 0)
+                return r;
+
+        if (r > 0) {
+                if (func)
+                        return func(filename, line, section, lvalue, ltype, rvalue, data, userdata);
+
+                return 0;
+        }
+
+        /* Warn about unknown non-extension fields. */
+        if (!relaxed && !startswith(lvalue, "X-"))
+                log_info("[%s:%u] Unknown lvalue '%s' in section '%s'. Ignoring.", filename, line, lvalue, section);
+
+        return 0;
+}
+
+/* Parse a variable assignment line */
+static int parse_line(
+                const char *filename,
+                unsigned line,
+                const char *sections,
+                ConfigItemLookup lookup,
+                void *table,
+                bool relaxed,
+                char **section,
+                char *l,
+                void *userdata) {
+
+        char *e;
+
+        assert(filename);
+        assert(line > 0);
+        assert(lookup);
+        assert(l);
+
+        l = strstrip(l);
+
+        if (!*l)
+                return 0;
+
+        if (strchr(COMMENTS, *l))
+                return 0;
+
+        if (startswith(l, ".include ")) {
+                char *fn;
+                int r;
+
+                fn = file_in_same_dir(filename, strstrip(l+9));
+                if (!fn)
+                        return -ENOMEM;
+
+                r = config_parse(fn, NULL, sections, lookup, table, relaxed, userdata);
+                free(fn);
+
+                return r;
+        }
+
+        if (*l == '[') {
+                size_t k;
+                char *n;
+
+                k = strlen(l);
+                assert(k > 0);
+
+                if (l[k-1] != ']') {
+                        log_error("[%s:%u] Invalid section header.", filename, line);
+                        return -EBADMSG;
+                }
+
+                n = strndup(l+1, k-2);
+                if (!n)
+                        return -ENOMEM;
+
+                if (sections && !nulstr_contains(sections, n)) {
+
+                        if (!relaxed)
+                                log_info("[%s:%u] Unknown section '%s'. Ignoring.", filename, line, n);
+
+                        free(n);
+                        *section = NULL;
+                } else {
+                        free(*section);
+                        *section = n;
+                }
+
+                return 0;
+        }
+
+        if (sections && !*section) {
+
+                if (!relaxed)
+                        log_info("[%s:%u] Assignment outside of section. Ignoring.", filename, line);
+
+                return 0;
+        }
+
+        e = strchr(l, '=');
+        if (!e) {
+                log_error("[%s:%u] Missing '='.", filename, line);
+                return -EBADMSG;
+        }
+
+        *e = 0;
+        e++;
+
+        return next_assignment(
+                        filename,
+                        line,
+                        lookup,
+                        table,
+                        *section,
+                        strstrip(l),
+                        strstrip(e),
+                        relaxed,
+                        userdata);
+}
+
+/* Go through the file and parse each line */
+int config_parse(
+                const char *filename,
+                FILE *f,
+                const char *sections,
+                ConfigItemLookup lookup,
+                void *table,
+                bool relaxed,
+                void *userdata) {
+
+        unsigned line = 0;
+        char *section = NULL;
+        int r;
+        bool ours = false;
+        char *continuation = NULL;
+
+        assert(filename);
+        assert(lookup);
+
+        if (!f) {
+                f = fopen(filename, "re");
+                if (!f) {
+                        r = -errno;
+                        log_error("Failed to open configuration file '%s': %s", filename, strerror(-r));
+                        goto finish;
+                }
+
+                ours = true;
+        }
+
+        while (!feof(f)) {
+                char l[LINE_MAX], *p, *c = NULL, *e;
+                bool escaped = false;
+
+                if (!fgets(l, sizeof(l), f)) {
+                        if (feof(f))
+                                break;
+
+                        r = -errno;
+                        log_error("Failed to read configuration file '%s': %s", filename, strerror(-r));
+                        goto finish;
+                }
+
+                truncate_nl(l);
+
+                if (continuation) {
+                        c = strappend(continuation, l);
+                        if (!c) {
+                                r = -ENOMEM;
+                                goto finish;
+                        }
+
+                        free(continuation);
+                        continuation = NULL;
+                        p = c;
+                } else
+                        p = l;
+
+                for (e = p; *e; e++) {
+                        if (escaped)
+                                escaped = false;
+                        else if (*e == '\\')
+                                escaped = true;
+                }
+
+                if (escaped) {
+                        *(e-1) = ' ';
+
+                        if (c)
+                                continuation = c;
+                        else {
+                                continuation = strdup(l);
+                                if (!continuation) {
+                                        r = -ENOMEM;
+                                        goto finish;
+                                }
+                        }
+
+                        continue;
+                }
+
+                r = parse_line(filename,
+                                ++line,
+                                sections,
+                                lookup,
+                                table,
+                                relaxed,
+                                &section,
+                                p,
+                                userdata);
+                free(c);
+
+                if (r < 0)
+                        goto finish;
+        }
+
+        r = 0;
+
+finish:
+        free(section);
+        free(continuation);
+
+        if (f && ours)
+                fclose(f);
+
+        return r;
+}
+
+int config_parse_int(
+                const char *filename,
+                unsigned line,
+                const char *section,
+                const char *lvalue,
+                int ltype,
+                const char *rvalue,
+                void *data,
+                void *userdata) {
+
+        int *i = data;
+        int r;
+
+        assert(filename);
+        assert(lvalue);
+        assert(rvalue);
+        assert(data);
+
+        if ((r = safe_atoi(rvalue, i)) < 0) {
+                log_error("[%s:%u] Failed to parse numeric value, ingoring: %s", filename, line, rvalue);
+                return 0;
+        }
+
+        return 0;
+}
+
+int config_parse_long(
+                const char *filename,
+                unsigned line,
+                const char *section,
+                const char *lvalue,
+                int ltype,
+                const char *rvalue,
+                void *data,
+                void *userdata) {
+
+        long *i = data;
+        int r;
+
+        assert(filename);
+        assert(lvalue);
+        assert(rvalue);
+        assert(data);
+
+        if ((r = safe_atoli(rvalue, i)) < 0) {
+                log_error("[%s:%u] Failed to parse numeric value, ignoring: %s", filename, line, rvalue);
+                return 0;
+        }
+
+        return 0;
+}
+
+int config_parse_uint64(
+                const char *filename,
+                unsigned line,
+                const char *section,
+                const char *lvalue,
+                int ltype,
+                const char *rvalue,
+                void *data,
+                void *userdata) {
+
+        uint64_t *u = data;
+        int r;
+
+        assert(filename);
+        assert(lvalue);
+        assert(rvalue);
+        assert(data);
+
+        if ((r = safe_atou64(rvalue, u)) < 0) {
+                log_error("[%s:%u] Failed to parse numeric value, ignoring: %s", filename, line, rvalue);
+                return 0;
+        }
+
+        return 0;
+}
+
+int config_parse_unsigned(
+                const char *filename,
+                unsigned line,
+                const char *section,
+                const char *lvalue,
+                int ltype,
+                const char *rvalue,
+                void *data,
+                void *userdata) {
+
+        unsigned *u = data;
+        int r;
+
+        assert(filename);
+        assert(lvalue);
+        assert(rvalue);
+        assert(data);
+
+        if ((r = safe_atou(rvalue, u)) < 0) {
+                log_error("[%s:%u] Failed to parse numeric value: %s", filename, line, rvalue);
+                return r;
+        }
+
+        return 0;
+}
+
+int config_parse_bytes_size(
+                const char *filename,
+                unsigned line,
+                const char *section,
+                const char *lvalue,
+                int ltype,
+                const char *rvalue,
+                void *data,
+                void *userdata) {
+
+        size_t *sz = data;
+        off_t o;
+
+        assert(filename);
+        assert(lvalue);
+        assert(rvalue);
+        assert(data);
+
+        if (parse_bytes(rvalue, &o) < 0 || (off_t) (size_t) o != o) {
+                log_error("[%s:%u] Failed to parse byte value, ignoring: %s", filename, line, rvalue);
+                return 0;
+        }
+
+        *sz = (size_t) o;
+        return 0;
+}
+
+
+int config_parse_bytes_off(
+                const char *filename,
+                unsigned line,
+                const char *section,
+                const char *lvalue,
+                int ltype,
+                const char *rvalue,
+                void *data,
+                void *userdata) {
+
+        off_t *bytes = data;
+
+        assert(filename);
+        assert(lvalue);
+        assert(rvalue);
+        assert(data);
+
+        assert_cc(sizeof(off_t) == sizeof(uint64_t));
+
+        if (parse_bytes(rvalue, bytes) < 0) {
+                log_error("[%s:%u] Failed to parse bytes value, ignoring: %s", filename, line, rvalue);
+                return 0;
+        }
+
+        return 0;
+}
+
+int config_parse_bool(
+                const char *filename,
+                unsigned line,
+                const char *section,
+                const char *lvalue,
+                int ltype,
+                const char *rvalue,
+                void *data,
+                void *userdata) {
+
+        int k;
+        bool *b = data;
+
+        assert(filename);
+        assert(lvalue);
+        assert(rvalue);
+        assert(data);
+
+        if ((k = parse_boolean(rvalue)) < 0) {
+                log_error("[%s:%u] Failed to parse boolean value, ignoring: %s", filename, line, rvalue);
+                return 0;
+        }
+
+        *b = !!k;
+        return 0;
+}
+
+int config_parse_tristate(
+                const char *filename,
+                unsigned line,
+                const char *section,
+                const char *lvalue,
+                int ltype,
+                const char *rvalue,
+                void *data,
+                void *userdata) {
+
+        int k;
+        int *b = data;
+
+        assert(filename);
+        assert(lvalue);
+        assert(rvalue);
+        assert(data);
+
+        /* Tristates are like booleans, but can also take the 'default' value, i.e. "-1" */
+
+        k = parse_boolean(rvalue);
+        if (k < 0) {
+                log_error("[%s:%u] Failed to parse boolean value, ignoring: %s", filename, line, rvalue);
+                return 0;
+        }
+
+        *b = !!k;
+        return 0;
+}
+
+int config_parse_string(
+                const char *filename,
+                unsigned line,
+                const char *section,
+                const char *lvalue,
+                int ltype,
+                const char *rvalue,
+                void *data,
+                void *userdata) {
+
+        char **s = data;
+        char *n;
+
+        assert(filename);
+        assert(lvalue);
+        assert(rvalue);
+        assert(data);
+
+        n = cunescape(rvalue);
+        if (!n)
+                return -ENOMEM;
+
+        if (!utf8_is_valid(n)) {
+                log_error("[%s:%u] String is not UTF-8 clean, ignoring assignment: %s", filename, line, rvalue);
+                free(n);
+                return 0;
+        }
+
+        free(*s);
+        if (*n)
+                *s = n;
+        else {
+                free(n);
+                *s = NULL;
+        }
+
+        return 0;
+}
+
+int config_parse_path(
+                const char *filename,
+                unsigned line,
+                const char *section,
+                const char *lvalue,
+                int ltype,
+                const char *rvalue,
+                void *data,
+                void *userdata) {
+
+        char **s = data;
+        char *n;
+
+        assert(filename);
+        assert(lvalue);
+        assert(rvalue);
+        assert(data);
+
+        if (!utf8_is_valid(rvalue)) {
+                log_error("[%s:%u] Path is not UTF-8 clean, ignoring assignment: %s", filename, line, rvalue);
+                return 0;
+        }
+
+        if (!path_is_absolute(rvalue)) {
+                log_error("[%s:%u] Not an absolute path, ignoring: %s", filename, line, rvalue);
+                return 0;
+        }
+
+        n = strdup(rvalue);
+        if (!n)
+                return -ENOMEM;
+
+        path_kill_slashes(n);
+
+        free(*s);
+        *s = n;
+
+        return 0;
+}
+
+int config_parse_strv(
+                const char *filename,
+                unsigned line,
+                const char *section,
+                const char *lvalue,
+                int ltype,
+                const char *rvalue,
+                void *data,
+                void *userdata) {
+
+        char*** sv = data;
+        char **n;
+        char *w;
+        unsigned k;
+        size_t l;
+        char *state;
+        int r;
+
+        assert(filename);
+        assert(lvalue);
+        assert(rvalue);
+        assert(data);
+
+        k = strv_length(*sv);
+        FOREACH_WORD_QUOTED(w, l, rvalue, state)
+                k++;
+
+        n = new(char*, k+1);
+        if (!n)
+                return -ENOMEM;
+
+        if (*sv)
+                for (k = 0; (*sv)[k]; k++)
+                        n[k] = (*sv)[k];
+        else
+                k = 0;
+
+        FOREACH_WORD_QUOTED(w, l, rvalue, state) {
+                n[k] = cunescape_length(w, l);
+                if (!n[k]) {
+                        r = -ENOMEM;
+                        goto fail;
+                }
+
+                if (!utf8_is_valid(n[k])) {
+                        log_error("[%s:%u] String is not UTF-8 clean, ignoring assignment: %s", filename, line, rvalue);
+                        free(n[k]);
+                        continue;
+                }
+
+                k++;
+        }
+
+        n[k] = NULL;
+        free(*sv);
+        *sv = n;
+
+        return 0;
+
+fail:
+        for (; k > 0; k--)
+                free(n[k-1]);
+        free(n);
+
+        return r;
+}
+
+int config_parse_path_strv(
+                const char *filename,
+                unsigned line,
+                const char *section,
+                const char *lvalue,
+                int ltype,
+                const char *rvalue,
+                void *data,
+                void *userdata) {
+
+        char*** sv = data;
+        char **n;
+        char *w;
+        unsigned k;
+        size_t l;
+        char *state;
+        int r;
+
+        assert(filename);
+        assert(lvalue);
+        assert(rvalue);
+        assert(data);
+
+        k = strv_length(*sv);
+        FOREACH_WORD_QUOTED(w, l, rvalue, state)
+                k++;
+
+        n = new(char*, k+1);
+        if (!n)
+                return -ENOMEM;
+
+        k = 0;
+        if (*sv)
+                for (; (*sv)[k]; k++)
+                        n[k] = (*sv)[k];
+
+        FOREACH_WORD_QUOTED(w, l, rvalue, state) {
+                n[k] = strndup(w, l);
+                if (!n[k]) {
+                        r = -ENOMEM;
+                        goto fail;
+                }
+
+                if (!utf8_is_valid(n[k])) {
+                        log_error("[%s:%u] Path is not UTF-8 clean, ignoring assignment: %s", filename, line, rvalue);
+                        free(n[k]);
+                        continue;
+                }
+
+                if (!path_is_absolute(n[k])) {
+                        log_error("[%s:%u] Not an absolute path, ignoring: %s", filename, line, rvalue);
+                        free(n[k]);
+                        continue;
+                }
+
+                path_kill_slashes(n[k]);
+                k++;
+        }
+
+        n[k] = NULL;
+        free(*sv);
+        *sv = n;
+
+        return 0;
+
+fail:
+        for (; k > 0; k--)
+                free(n[k-1]);
+        free(n);
+
+        return r;
+}
+
+int config_parse_usec(
+                const char *filename,
+                unsigned line,
+                const char *section,
+                const char *lvalue,
+                int ltype,
+                const char *rvalue,
+                void *data,
+                void *userdata) {
+
+        usec_t *usec = data;
+
+        assert(filename);
+        assert(lvalue);
+        assert(rvalue);
+        assert(data);
+
+        if (parse_usec(rvalue, usec) < 0) {
+                log_error("[%s:%u] Failed to parse time value, ignoring: %s", filename, line, rvalue);
+                return 0;
+        }
+
+        return 0;
+}
+
+int config_parse_mode(
+                const char *filename,
+                unsigned line,
+                const char *section,
+                const char *lvalue,
+                int ltype,
+                const char *rvalue,
+                void *data,
+                void *userdata) {
+
+        mode_t *m = data;
+        long l;
+        char *x = NULL;
+
+        assert(filename);
+        assert(lvalue);
+        assert(rvalue);
+        assert(data);
+
+        errno = 0;
+        l = strtol(rvalue, &x, 8);
+        if (!x || *x || errno) {
+                log_error("[%s:%u] Failed to parse mode value, ignoring: %s", filename, line, rvalue);
+                return 0;
+        }
+
+        if (l < 0000 || l > 07777) {
+                log_error("[%s:%u] mode value out of range, ignoring: %s", filename, line, rvalue);
+                return 0;
+        }
+
+        *m = (mode_t) l;
+        return 0;
+}
diff --git a/src/conf-parser.h b/src/conf-parser.h
new file mode 100644 (file)
index 0000000..be7d708
--- /dev/null
@@ -0,0 +1,135 @@
+/*-*- Mode: C; c-basic-offset: 8; indent-tabs-mode: nil -*-*/
+
+#ifndef fooconfparserhfoo
+#define fooconfparserhfoo
+
+/***
+  This file is part 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 <http://www.gnu.org/licenses/>.
+***/
+
+#include <stdio.h>
+#include <stdbool.h>
+
+/* An abstract parser for simple, line based, shallow configuration
+ * files consisting of variable assignments only. */
+
+/* Prototype for a parser for a specific configuration setting */
+typedef int (*ConfigParserCallback)(
+                const char *filename,
+                unsigned line,
+                const char *section,
+                const char *lvalue,
+                int ltype,
+                const char *rvalue,
+                void *data,
+                void *userdata);
+
+/* Wraps information for parsing a specific configuration variable, to
+ * be stored in a simple array */
+typedef struct ConfigTableItem {
+        const char *section;            /* Section */
+        const char *lvalue;             /* Name of the variable */
+        ConfigParserCallback parse;     /* Function that is called to parse the variable's value */
+        int ltype;                      /* Distinguish different variables passed to the same callback */
+        void *data;                     /* Where to store the variable's data */
+} ConfigTableItem;
+
+/* Wraps information for parsing a specific configuration variable, to
+ * ve srored in a gperf perfect hashtable */
+typedef struct ConfigPerfItem {
+        const char *section_and_lvalue; /* Section + "." + name of the variable */
+        ConfigParserCallback parse;     /* Function that is called to parse the variable's value */
+        int ltype;                      /* Distinguish different variables passed to the same callback */
+        size_t offset;                  /* Offset where to store data, from the beginning of userdata */
+} ConfigPerfItem;
+
+/* Prototype for a low-level gperf lookup function */
+typedef const ConfigPerfItem* (*ConfigPerfItemLookup)(const char *section_and_lvalue, unsigned length);
+
+/* Prototype for a generic high-level lookup function */
+typedef int (*ConfigItemLookup)(
+                void *table,
+                const char *section,
+                const char *lvalue,
+                ConfigParserCallback *func,
+                int *ltype,
+                void **data,
+                void *userdata);
+
+/* Linear table search implementation of ConfigItemLookup, based on
+ * ConfigTableItem arrays */
+int config_item_table_lookup(void *table, const char *section, const char *lvalue, ConfigParserCallback *func, int *ltype, void **data, void *userdata);
+
+/* gperf implementation of ConfigItemLookup, based on gperf
+ * ConfigPerfItem tables */
+int config_item_perf_lookup(void *table, const char *section, const char *lvalue, ConfigParserCallback *func, int *ltype, void **data, void *userdata);
+
+int config_parse(
+                const char *filename,
+                FILE *f,
+                const char *sections,  /* nulstr */
+                ConfigItemLookup lookup,
+                void *table,
+                bool relaxed,
+                void *userdata);
+
+/* Generic parsers */
+int config_parse_int(const char *filename, unsigned line, const char *section, const char *lvalue, int ltype, const char *rvalue, void *data, void *userdata);
+int config_parse_unsigned(const char *filename, unsigned line, const char *section, const char *lvalue, int ltype, const char *rvalue, void *data, void *userdata);
+int config_parse_long(const char *filename, unsigned line, const char *section, const char *lvalue, int ltype, const char *rvalue, void *data, void *userdata);
+int config_parse_uint64(const char *filename, unsigned line, const char *section, const char *lvalue, int ltype, const char *rvalue, void *data, void *userdata);
+int config_parse_bytes_size(const char *filename, unsigned line, const char *section, const char *lvalue, int ltype, const char *rvalue, void *data, void *userdata);
+int config_parse_bytes_off(const char *filename, unsigned line, const char *section, const char *lvalue, int ltype, const char *rvalue, void *data, void *userdata);
+int config_parse_bool(const char *filename, unsigned line, const char *section, const char *lvalue, int ltype, const char *rvalue, void *data, void *userdata);
+int config_parse_tristate(const char *filename, unsigned line, const char *section, const char *lvalue, int ltype, const char *rvalue, void *data, void *userdata);
+int config_parse_string(const char *filename, unsigned line, const char *section, const char *lvalue, int ltype, const char *rvalue, void *data, void *userdata);
+int config_parse_path(const char *filename, unsigned line, const char *section, const char *lvalue, int ltype, const char *rvalue, void *data, void *userdata);
+int config_parse_strv(const char *filename, unsigned line, const char *section, const char *lvalue, int ltype, const char *rvalue, void *data, void *userdata);
+int config_parse_path_strv(const char *filename, unsigned line, const char *section, const char *lvalue, int ltype, const char *rvalue, void *data, void *userdata);
+int config_parse_usec(const char *filename, unsigned line, const char *section, const char *lvalue, int ltype, const char *rvalue, void *data, void *userdata);
+int config_parse_mode(const char *filename, unsigned line, const char *section, const char *lvalue, int ltype, const char *rvalue, void *data, void *userdata);
+
+#define DEFINE_CONFIG_PARSE_ENUM(function,name,type,msg)                \
+        int function(                                                   \
+                        const char *filename,                           \
+                        unsigned line,                                  \
+                        const char *section,                            \
+                        const char *lvalue,                             \
+                        int ltype,                                      \
+                        const char *rvalue,                             \
+                        void *data,                                     \
+                        void *userdata) {                               \
+                                                                        \
+                type *i = data, x;                                      \
+                                                                        \
+                assert(filename);                                       \
+                assert(lvalue);                                         \
+                assert(rvalue);                                         \
+                assert(data);                                           \
+                                                                        \
+                if ((x = name##_from_string(rvalue)) < 0) {             \
+                        log_error("[%s:%u] " msg ", ignoring: %s", filename, line, rvalue); \
+                        return 0;                                       \
+                }                                                       \
+                                                                        \
+                *i = x;                                                 \
+                                                                        \
+                return 0;                                               \
+        }
+
+#endif
diff --git a/src/cryptsetup/Makefile b/src/cryptsetup/Makefile
new file mode 120000 (symlink)
index 0000000..d0b0e8e
--- /dev/null
@@ -0,0 +1 @@
+../Makefile
\ No newline at end of file
diff --git a/src/cryptsetup/cryptsetup-generator.c b/src/cryptsetup/cryptsetup-generator.c
new file mode 100644 (file)
index 0000000..ba59b49
--- /dev/null
@@ -0,0 +1,298 @@
+/*-*- 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 <http://www.gnu.org/licenses/>.
+***/
+
+#include <string.h>
+#include <errno.h>
+#include <unistd.h>
+
+#include "log.h"
+#include "util.h"
+#include "unit-name.h"
+
+const char *arg_dest = "/tmp";
+
+static bool has_option(const char *haystack, const char *needle) {
+        const char *f = haystack;
+        size_t l;
+
+        assert(needle);
+
+        if (!haystack)
+                return false;
+
+        l = strlen(needle);
+
+        while ((f = strstr(f, needle))) {
+
+                if (f > haystack && f[-1] != ',') {
+                        f++;
+                        continue;
+                }
+
+                if (f[l] != 0 && f[l] != ',') {
+                        f++;
+                        continue;
+                }
+
+                return true;
+        }
+
+        return false;
+}
+
+static int create_disk(
+                const char *name,
+                const char *device,
+                const char *password,
+                const char *options) {
+
+        char *p = NULL, *n = NULL, *d = NULL, *u = NULL, *from = NULL, *to = NULL, *e = NULL;
+        int r;
+        FILE *f = NULL;
+        bool noauto, nofail;
+
+        assert(name);
+        assert(device);
+
+        noauto = has_option(options, "noauto");
+        nofail = has_option(options, "nofail");
+
+        if (!(n = unit_name_build_escape("cryptsetup", name, ".service"))) {
+                r = -ENOMEM;
+                log_error("Failed to allocate unit name.");
+                goto fail;
+        }
+
+        if (asprintf(&p, "%s/%s", arg_dest, n) < 0) {
+                r = -ENOMEM;
+                log_error("Failed to allocate unit file name.");
+                goto fail;
+        }
+
+        if (!(u = fstab_node_to_udev_node(device))) {
+                r = -ENOMEM;
+                log_error("Failed to allocate device node.");
+                goto fail;
+        }
+
+        if (!(d = unit_name_from_path(u, ".device"))) {
+                r = -ENOMEM;
+                log_error("Failed to allocate device name.");
+                goto fail;
+        }
+
+        if (!(f = fopen(p, "wxe"))) {
+                r = -errno;
+                log_error("Failed to create unit file: %m");
+                goto fail;
+        }
+
+        fprintf(f,
+                "[Unit]\n"
+                "Description=Cryptography Setup for %%I\n"
+                "Conflicts=umount.target\n"
+                "DefaultDependencies=no\n"
+                "BindTo=%s dev-mapper-%%i.device\n"
+                "After=systemd-readahead-collect.service systemd-readahead-replay.service %s\n"
+                "Before=umount.target\n",
+                d, d);
+
+        if (!nofail)
+                fprintf(f,
+                        "Before=cryptsetup.target\n");
+
+        if (password && (streq(password, "/dev/urandom") ||
+                         streq(password, "/dev/random") ||
+                         streq(password, "/dev/hw_random")))
+                fprintf(f,
+                        "After=systemd-random-seed-load.service\n");
+        else
+                fprintf(f,
+                        "Before=local-fs.target\n");
+
+        fprintf(f,
+                "\n[Service]\n"
+                "Type=oneshot\n"
+                "RemainAfterExit=yes\n"
+                "TimeoutSec=0\n" /* the binary handles timeouts anyway */
+                "ExecStart=" SYSTEMD_CRYPTSETUP_PATH " attach '%s' '%s' '%s' '%s'\n"
+                "ExecStop=" SYSTEMD_CRYPTSETUP_PATH " detach '%s'\n",
+                name, u, strempty(password), strempty(options),
+                name);
+
+        if (has_option(options, "tmp"))
+                fprintf(f,
+                        "ExecStartPost=/sbin/mke2fs '/dev/mapper/%s'\n",
+                        name);
+
+        if (has_option(options, "swap"))
+                fprintf(f,
+                        "ExecStartPost=/sbin/mkswap '/dev/mapper/%s'\n",
+                        name);
+
+        fflush(f);
+
+        if (ferror(f)) {
+                r = -errno;
+                log_error("Failed to write file: %m");
+                goto fail;
+        }
+
+        if (asprintf(&from, "../%s", n) < 0) {
+                r = -ENOMEM;
+                goto fail;
+        }
+
+        if (!noauto) {
+
+                if (asprintf(&to, "%s/%s.wants/%s", arg_dest, d, n) < 0) {
+                        r = -ENOMEM;
+                        goto fail;
+                }
+
+                mkdir_parents(to, 0755);
+
+                if (symlink(from, to) < 0) {
+                        log_error("Failed to create symlink '%s' to '%s': %m", from, to);
+                        r = -errno;
+                        goto fail;
+                }
+
+                free(to);
+                to = NULL;
+
+                if (!nofail)
+                        asprintf(&to, "%s/cryptsetup.target.requires/%s", arg_dest, n);
+                else
+                        asprintf(&to, "%s/cryptsetup.target.wants/%s", arg_dest, n);
+
+                if (!to) {
+                        r = -ENOMEM;
+                        goto fail;
+                }
+
+                mkdir_parents(to, 0755);
+
+                if (symlink(from, to) < 0) {
+                        log_error("Failed to create symlink '%s' to '%s': %m", from, to);
+                        r = -errno;
+                        goto fail;
+                }
+        }
+
+        free(to);
+        to = NULL;
+
+        e = unit_name_escape(name);
+        if (asprintf(&to, "%s/dev-mapper-%s.device.requires/%s", arg_dest, e, n) < 0) {
+                r = -ENOMEM;
+                goto fail;
+        }
+
+        mkdir_parents(to, 0755);
+
+        if (symlink(from, to) < 0) {
+                log_error("Failed to create symlink '%s' to '%s': %m", from, to);
+                r = -errno;
+                goto fail;
+        }
+
+        r = 0;
+
+fail:
+        free(p);
+        free(n);
+        free(d);
+        free(e);
+
+        free(from);
+        free(to);
+
+        if (f)
+                fclose(f);
+
+        return r;
+}
+
+int main(int argc, char *argv[]) {
+        FILE *f;
+        int r = EXIT_SUCCESS;
+        unsigned n = 0;
+
+        if (argc > 2) {
+                log_error("This program takes one or no arguments.");
+                return EXIT_FAILURE;
+        }
+
+        if (argc > 1)
+                arg_dest = argv[1];
+
+        log_set_target(LOG_TARGET_AUTO);
+        log_parse_environment();
+        log_open();
+
+        umask(0022);
+
+        if (!(f = fopen("/etc/crypttab", "re"))) {
+
+                if (errno == ENOENT)
+                        r = EXIT_SUCCESS;
+                else {
+                        r = EXIT_FAILURE;
+                        log_error("Failed to open /etc/crypttab: %m");
+                }
+
+                goto finish;
+        }
+
+        for (;;) {
+                char line[LINE_MAX], *l;
+                char *name = NULL, *device = NULL, *password = NULL, *options = NULL;
+                int k;
+
+                if (!(fgets(line, sizeof(line), f)))
+                        break;
+
+                n++;
+
+                l = strstrip(line);
+                if (*l == '#' || *l == 0)
+                        continue;
+
+                if ((k = sscanf(l, "%ms %ms %ms %ms", &name, &device, &password, &options)) < 2 || k > 4) {
+                        log_error("Failed to parse /etc/crypttab:%u, ignoring.", n);
+                        r = EXIT_FAILURE;
+                        goto next;
+                }
+
+                if (create_disk(name, device, password, options) < 0)
+                        r = EXIT_FAILURE;
+
+        next:
+                free(name);
+                free(device);
+                free(password);
+                free(options);
+        }
+
+finish:
+        return r;
+}
diff --git a/src/cryptsetup/cryptsetup.c b/src/cryptsetup/cryptsetup.c
new file mode 100644 (file)
index 0000000..ac7b6d6
--- /dev/null
@@ -0,0 +1,529 @@
+/*-*- 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 <http://www.gnu.org/licenses/>.
+***/
+
+#include <string.h>
+#include <errno.h>
+#include <sys/mman.h>
+#include <mntent.h>
+
+#include <libcryptsetup.h>
+#include <libudev.h>
+
+#include "log.h"
+#include "util.h"
+#include "strv.h"
+#include "ask-password-api.h"
+#include "def.h"
+
+static const char *opt_type = NULL; /* LUKS1 or PLAIN */
+static char *opt_cipher = NULL;
+static unsigned opt_key_size = 0;
+static char *opt_hash = NULL;
+static unsigned opt_tries = 0;
+static bool opt_readonly = false;
+static bool opt_verify = false;
+static usec_t opt_timeout = DEFAULT_TIMEOUT_USEC;
+
+/* Options Debian's crypttab knows we don't:
+
+    offset=
+    skip=
+    precheck=
+    check=
+    checkargs=
+    noearly=
+    loud=
+    keyscript=
+*/
+
+static int parse_one_option(const char *option) {
+        assert(option);
+
+        /* Handled outside of this tool */
+        if (streq(option, "noauto"))
+                return 0;
+
+        if (startswith(option, "cipher=")) {
+                char *t;
+
+                if (!(t = strdup(option+7)))
+                        return -ENOMEM;
+
+                free(opt_cipher);
+                opt_cipher = t;
+
+        } else if (startswith(option, "size=")) {
+
+                if (safe_atou(option+5, &opt_key_size) < 0) {
+                        log_error("size= parse failure, ignoring.");
+                        return 0;
+                }
+
+        } else if (startswith(option, "hash=")) {
+                char *t;
+
+                if (!(t = strdup(option+5)))
+                        return -ENOMEM;
+
+                free(opt_hash);
+                opt_hash = t;
+
+        } else if (startswith(option, "tries=")) {
+
+                if (safe_atou(option+6, &opt_tries) < 0) {
+                        log_error("tries= parse failure, ignoring.");
+                        return 0;
+                }
+
+        } else if (streq(option, "readonly"))
+                opt_readonly = true;
+        else if (streq(option, "verify"))
+                opt_verify = true;
+        else if (streq(option, "luks"))
+                opt_type = CRYPT_LUKS1;
+        else if (streq(option, "plain") ||
+                 streq(option, "swap") ||
+                 streq(option, "tmp"))
+                opt_type = CRYPT_PLAIN;
+        else if (startswith(option, "timeout=")) {
+
+                if (parse_usec(option+8, &opt_timeout) < 0) {
+                        log_error("timeout= parse failure, ignoring.");
+                        return 0;
+                }
+
+        } else if (!streq(option, "none"))
+                log_error("Encountered unknown /etc/crypttab option '%s', ignoring.", option);
+
+        return 0;
+}
+
+static int parse_options(const char *options) {
+        char *state;
+        char *w;
+        size_t l;
+
+        assert(options);
+
+        FOREACH_WORD_SEPARATOR(w, l, options, ",", state) {
+                char *o;
+                int r;
+
+                if (!(o = strndup(w, l)))
+                        return -ENOMEM;
+
+                r = parse_one_option(o);
+                free(o);
+
+                if (r < 0)
+                        return r;
+        }
+
+        return 0;
+}
+
+static void log_glue(int level, const char *msg, void *usrptr) {
+        log_debug("%s", msg);
+}
+
+static char *disk_description(const char *path) {
+        struct udev *udev = NULL;
+        struct udev_device *device = NULL;
+        struct stat st;
+        char *description = NULL;
+        const char *model;
+
+        assert(path);
+
+        if (stat(path, &st) < 0)
+                return NULL;
+
+        if (!S_ISBLK(st.st_mode))
+                return NULL;
+
+        if (!(udev = udev_new()))
+                return NULL;
+
+        if (!(device = udev_device_new_from_devnum(udev, 'b', st.st_rdev)))
+                goto finish;
+
+        if ((model = udev_device_get_property_value(device, "ID_MODEL_FROM_DATABASE")) ||
+            (model = udev_device_get_property_value(device, "ID_MODEL")) ||
+            (model = udev_device_get_property_value(device, "DM_NAME")))
+                description = strdup(model);
+
+finish:
+        if (device)
+                udev_device_unref(device);
+
+        if (udev)
+                udev_unref(udev);
+
+        return description;
+}
+
+static char *disk_mount_point(const char *label) {
+        char *mp = NULL, *device = NULL;
+        FILE *f = NULL;
+        struct mntent *m;
+
+        /* Yeah, we don't support native systemd unit files here for now */
+
+        if (asprintf(&device, "/dev/mapper/%s", label) < 0)
+                goto finish;
+
+        if (!(f = setmntent("/etc/fstab", "r")))
+                goto finish;
+
+        while ((m = getmntent(f)))
+                if (path_equal(m->mnt_fsname, device)) {
+                        mp = strdup(m->mnt_dir);
+                        break;
+                }
+
+finish:
+        if (f)
+                endmntent(f);
+
+        free(device);
+
+        return mp;
+}
+
+static int help(void) {
+
+        printf("%s attach VOLUME SOURCEDEVICE [PASSWORD] [OPTIONS]\n"
+               "%s detach VOLUME\n\n"
+               "Attaches or detaches an encrypted block device.\n",
+               program_invocation_short_name,
+               program_invocation_short_name);
+
+        return 0;
+}
+
+int main(int argc, char *argv[]) {
+        int r = EXIT_FAILURE;
+        struct crypt_device *cd = NULL;
+        char **passwords = NULL, *truncated_cipher = NULL;
+        const char *cipher = NULL, *cipher_mode = NULL, *hash = NULL, *name = NULL;
+        char *description = NULL, *name_buffer = NULL, *mount_point = NULL;
+        unsigned keyfile_size = 0;
+
+        if (argc <= 1) {
+                help();
+                return EXIT_SUCCESS;
+        }
+
+        if (argc < 3) {
+                log_error("This program requires at least two arguments.");
+                return EXIT_FAILURE;
+        }
+
+        log_set_target(LOG_TARGET_AUTO);
+        log_parse_environment();
+        log_open();
+
+        umask(0022);
+
+        if (streq(argv[1], "attach")) {
+                uint32_t flags = 0;
+                int k;
+                unsigned try;
+                const char *key_file = NULL;
+                usec_t until;
+                crypt_status_info status;
+
+                /* Arguments: systemd-cryptsetup attach VOLUME SOURCE-DEVICE [PASSWORD] [OPTIONS] */
+
+                if (argc < 4) {
+                        log_error("attach requires at least two arguments.");
+                        goto finish;
+                }
+
+                if (argc >= 5 &&
+                    argv[4][0] &&
+                    !streq(argv[4], "-") &&
+                    !streq(argv[4], "none")) {
+
+                        if (!path_is_absolute(argv[4]))
+                                log_error("Password file path %s is not absolute. Ignoring.", argv[4]);
+                        else
+                                key_file = argv[4];
+                }
+
+                if (argc >= 6 && argv[5][0] && !streq(argv[5], "-"))
+                        parse_options(argv[5]);
+
+                /* A delicious drop of snake oil */
+                mlockall(MCL_FUTURE);
+
+                description = disk_description(argv[3]);
+                mount_point = disk_mount_point(argv[2]);
+
+                if (description && streq(argv[2], description)) {
+                        /* If the description string is simply the
+                         * volume name, then let's not show this
+                         * twice */
+                        free(description);
+                        description = NULL;
+                }
+
+                if (mount_point && description)
+                        asprintf(&name_buffer, "%s (%s) on %s", description, argv[2], mount_point);
+                else if (mount_point)
+                        asprintf(&name_buffer, "%s on %s", argv[2], mount_point);
+                else if (description)
+                        asprintf(&name_buffer, "%s (%s)", description, argv[2]);
+
+                name = name_buffer ? name_buffer : argv[2];
+
+                if ((k = crypt_init(&cd, argv[3]))) {
+                        log_error("crypt_init() failed: %s", strerror(-k));
+                        goto finish;
+                }
+
+                crypt_set_log_callback(cd, log_glue, NULL);
+
+                status = crypt_status(cd, argv[2]);
+                if (status == CRYPT_ACTIVE || status == CRYPT_BUSY) {
+                        log_info("Volume %s already active.", argv[2]);
+                        r = EXIT_SUCCESS;
+                        goto finish;
+                }
+
+                if (opt_readonly)
+                        flags |= CRYPT_ACTIVATE_READONLY;
+
+                if (opt_timeout > 0)
+                        until = now(CLOCK_MONOTONIC) + opt_timeout;
+                else
+                        until = 0;
+
+                opt_tries = opt_tries > 0 ? opt_tries : 3;
+                opt_key_size = (opt_key_size > 0 ? opt_key_size : 256);
+                hash = opt_hash ? opt_hash : "ripemd160";
+
+                if (opt_cipher) {
+                        size_t l;
+
+                        l = strcspn(opt_cipher, "-");
+
+                        if (!(truncated_cipher = strndup(opt_cipher, l))) {
+                                log_error("Out of memory");
+                                goto finish;
+                        }
+
+                        cipher = truncated_cipher;
+                        cipher_mode = opt_cipher[l] ? opt_cipher+l+1 : "plain";
+                } else {
+                        cipher = "aes";
+                        cipher_mode = "cbc-essiv:sha256";
+                }
+
+                for (try = 0; try < opt_tries; try++) {
+                        bool pass_volume_key = false;
+
+                        strv_free(passwords);
+                        passwords = NULL;
+
+                        if (!key_file) {
+                                char *text;
+                                char **p;
+
+                                if (asprintf(&text, "Please enter passphrase for disk %s!", name) < 0) {
+                                        log_error("Out of memory");
+                                        goto finish;
+                                }
+
+                                k = ask_password_auto(text, "drive-harddisk", until, try == 0 && !opt_verify, &passwords);
+                                free(text);
+
+                                if (k < 0) {
+                                        log_error("Failed to query password: %s", strerror(-k));
+                                        goto finish;
+                                }
+
+                                if (opt_verify) {
+                                        char **passwords2 = NULL;
+
+                                        assert(strv_length(passwords) == 1);
+
+                                        if (asprintf(&text, "Please enter passphrase for disk %s! (verification)", name) < 0) {
+                                                log_error("Out of memory");
+                                                goto finish;
+                                        }
+
+                                        k = ask_password_auto(text, "drive-harddisk", until, false, &passwords2);
+                                        free(text);
+
+                                        if (k < 0) {
+                                                log_error("Failed to query verification password: %s", strerror(-k));
+                                                goto finish;
+                                        }
+
+                                        assert(strv_length(passwords2) == 1);
+
+                                        if (!streq(passwords[0], passwords2[0])) {
+                                                log_warning("Passwords did not match, retrying.");
+                                                strv_free(passwords2);
+                                                continue;
+                                        }
+
+                                        strv_free(passwords2);
+                                }
+
+                                strv_uniq(passwords);
+
+                                STRV_FOREACH(p, passwords) {
+                                        char *c;
+
+                                        if (strlen(*p)+1 >= opt_key_size)
+                                                continue;
+
+                                        /* Pad password if necessary */
+                                        if (!(c = new(char, opt_key_size))) {
+                                                log_error("Out of memory.");
+                                                goto finish;
+                                        }
+
+                                        strncpy(c, *p, opt_key_size);
+                                        free(*p);
+                                        *p = c;
+                                }
+                        }
+
+                        k = 0;
+
+                        if (!opt_type || streq(opt_type, CRYPT_LUKS1))
+                                k = crypt_load(cd, CRYPT_LUKS1, NULL);
+
+                        if ((!opt_type && k < 0) || streq_ptr(opt_type, CRYPT_PLAIN)) {
+                                struct crypt_params_plain params;
+
+                                zero(params);
+                                params.hash = hash;
+
+                                /* In contrast to what the name
+                                 * crypt_setup() might suggest this
+                                 * doesn't actually format anything,
+                                 * it just configures encryption
+                                 * parameters when used for plain
+                                 * mode. */
+                                k = crypt_format(cd, CRYPT_PLAIN,
+                                                 cipher,
+                                                 cipher_mode,
+                                                 NULL,
+                                                 NULL,
+                                                 opt_key_size / 8,
+                                                 &params);
+
+                                pass_volume_key = streq(hash, "plain");
+
+                               /* for CRYPT_PLAIN limit reads
+                                * from keyfile to key length */
+                                keyfile_size = opt_key_size / 8;
+                        }
+
+                        if (k < 0) {
+                                log_error("Loading of cryptographic parameters failed: %s", strerror(-k));
+                                goto finish;
+                        }
+
+                        log_info("Set cipher %s, mode %s, key size %i bits for device %s.",
+                                 crypt_get_cipher(cd),
+                                 crypt_get_cipher_mode(cd),
+                                 crypt_get_volume_key_size(cd)*8,
+                                 argv[3]);
+
+                        if (key_file)
+                                k = crypt_activate_by_keyfile(cd, argv[2], CRYPT_ANY_SLOT, key_file, keyfile_size, flags);
+                        else {
+                                char **p;
+
+                                STRV_FOREACH(p, passwords) {
+
+                                        if (pass_volume_key)
+                                                k = crypt_activate_by_volume_key(cd, argv[2], *p, opt_key_size, flags);
+                                        else
+                                                k = crypt_activate_by_passphrase(cd, argv[2], CRYPT_ANY_SLOT, *p, strlen(*p), flags);
+
+                                        if (k >= 0)
+                                                break;
+                                }
+                        }
+
+                        if (k >= 0)
+                                break;
+
+                        if (k != -EPERM) {
+                                log_error("Failed to activate: %s", strerror(-k));
+                                goto finish;
+                        }
+
+                        log_warning("Invalid passphrase.");
+                }
+
+                if (try >= opt_tries) {
+                        log_error("Too many attempts.");
+                        r = EXIT_FAILURE;
+                        goto finish;
+                }
+
+        } else if (streq(argv[1], "detach")) {
+                int k;
+
+                if ((k = crypt_init_by_name(&cd, argv[2]))) {
+                        log_error("crypt_init() failed: %s", strerror(-k));
+                        goto finish;
+                }
+
+                crypt_set_log_callback(cd, log_glue, NULL);
+
+                if ((k = crypt_deactivate(cd, argv[2])) < 0) {
+                        log_error("Failed to deactivate: %s", strerror(-k));
+                        goto finish;
+                }
+
+        } else {
+                log_error("Unknown verb %s.", argv[1]);
+                goto finish;
+        }
+
+        r = EXIT_SUCCESS;
+
+finish:
+
+        if (cd)
+                crypt_free(cd);
+
+        free(opt_cipher);
+        free(opt_hash);
+
+        free(truncated_cipher);
+
+        strv_free(passwords);
+
+        free(description);
+        free(mount_point);
+        free(name_buffer);
+
+        return r;
+}
diff --git a/src/dbus-automount.c b/src/dbus-automount.c
new file mode 100644 (file)
index 0000000..8e45f81
--- /dev/null
@@ -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 <http://www.gnu.org/licenses/>.
+***/
+
+#include <errno.h>
+
+#include "dbus-unit.h"
+#include "dbus-automount.h"
+#include "dbus-common.h"
+
+#define BUS_AUTOMOUNT_INTERFACE                                      \
+        " <interface name=\"org.freedesktop.systemd1.Automount\">\n" \
+        "  <property name=\"Where\" type=\"s\" access=\"read\"/>\n"  \
+        "  <property name=\"DirectoryMode\" type=\"u\" access=\"read\"/>\n" \
+        "  <property name=\"Result\" type=\"s\" access=\"read\"/>\n"    \
+        " </interface>\n"
+
+#define INTROSPECTION                                                \
+        DBUS_INTROSPECT_1_0_XML_DOCTYPE_DECL_NODE                    \
+        "<node>\n"                                                   \
+        BUS_UNIT_INTERFACE                                           \
+        BUS_AUTOMOUNT_INTERFACE                                      \
+        BUS_PROPERTIES_INTERFACE                                     \
+        BUS_PEER_INTERFACE                                           \
+        BUS_INTROSPECTABLE_INTERFACE                                 \
+        "</node>\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
new file mode 100644 (file)
index 0000000..2fc8345
--- /dev/null
@@ -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 <http://www.gnu.org/licenses/>.
+***/
+
+#include <dbus/dbus.h>
+
+#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-common.c b/src/dbus-common.c
new file mode 100644 (file)
index 0000000..2905ac3
--- /dev/null
@@ -0,0 +1,1092 @@
+/*-*- 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 <http://www.gnu.org/licenses/>.
+***/
+
+#include <assert.h>
+#include <sys/socket.h>
+#include <errno.h>
+#include <unistd.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <dbus/dbus.h>
+#include <string.h>
+#include <sys/epoll.h>
+
+#include "log.h"
+#include "dbus-common.h"
+#include "util.h"
+#include "def.h"
+#include "strv.h"
+
+int bus_check_peercred(DBusConnection *c) {
+        int fd;
+        struct ucred ucred;
+        socklen_t l;
+
+        assert(c);
+
+        assert_se(dbus_connection_get_unix_fd(c, &fd));
+
+        l = sizeof(struct ucred);
+        if (getsockopt(fd, SOL_SOCKET, SO_PEERCRED, &ucred, &l) < 0) {
+                log_error("SO_PEERCRED failed: %m");
+                return -errno;
+        }
+
+        if (l != sizeof(struct ucred)) {
+                log_error("SO_PEERCRED returned wrong size.");
+                return -E2BIG;
+        }
+
+        if (ucred.uid != 0 && ucred.uid != geteuid())
+                return -EPERM;
+
+        return 1;
+}
+
+static int sync_auth(DBusConnection *bus, DBusError *error) {
+        usec_t begin, tstamp;
+
+        assert(bus);
+
+        /* This complexity should probably move into D-Bus itself:
+         *
+         * https://bugs.freedesktop.org/show_bug.cgi?id=35189 */
+
+        begin = tstamp = now(CLOCK_MONOTONIC);
+        for (;;) {
+
+                if (tstamp > begin + DEFAULT_TIMEOUT_USEC)
+                        break;
+
+                if (dbus_connection_get_is_authenticated(bus))
+                        break;
+
+                if (!dbus_connection_read_write_dispatch(bus, ((begin + DEFAULT_TIMEOUT_USEC - tstamp) + USEC_PER_MSEC - 1) / USEC_PER_MSEC))
+                        break;
+
+                tstamp = now(CLOCK_MONOTONIC);
+        }
+
+        if (!dbus_connection_get_is_connected(bus)) {
+                dbus_set_error_const(error, DBUS_ERROR_NO_SERVER, "Connection terminated during authentication.");
+                return -ECONNREFUSED;
+        }
+
+        if (!dbus_connection_get_is_authenticated(bus)) {
+                dbus_set_error_const(error, DBUS_ERROR_TIMEOUT, "Failed to authenticate in time.");
+                return -EACCES;
+        }
+
+        return 0;
+}
+
+int bus_connect(DBusBusType t, DBusConnection **_bus, bool *_private, DBusError *error) {
+        DBusConnection *bus = NULL;
+        int r;
+        bool private = true;
+
+        assert(_bus);
+
+        if (geteuid() == 0 && t == DBUS_BUS_SYSTEM) {
+                /* If we are root, then let's talk directly to the
+                 * system instance, instead of going via the bus */
+
+                bus = dbus_connection_open_private("unix:path=/run/systemd/private", error);
+                if (!bus)
+                        return -EIO;
+
+        } else {
+                if (t == DBUS_BUS_SESSION) {
+                        const char *e;
+
+                        /* If we are supposed to talk to the instance,
+                         * try via XDG_RUNTIME_DIR first, then
+                         * fallback to normal bus access */
+
+                        e = getenv("XDG_RUNTIME_DIR");
+                        if (e) {
+                                char *p;
+
+                                if (asprintf(&p, "unix:path=%s/systemd/private", e) < 0)
+                                        return -ENOMEM;
+
+                                bus = dbus_connection_open_private(p, NULL);
+                                free(p);
+                        }
+                }
+
+                if (!bus) {
+                        bus = dbus_bus_get_private(t, error);
+                        if (!bus)
+                                return -EIO;
+
+                        private = false;
+                }
+        }
+
+        dbus_connection_set_exit_on_disconnect(bus, FALSE);
+
+        if (private) {
+                if (bus_check_peercred(bus) < 0) {
+                        dbus_connection_close(bus);
+                        dbus_connection_unref(bus);
+
+                        dbus_set_error_const(error, DBUS_ERROR_ACCESS_DENIED, "Failed to verify owner of bus.");
+                        return -EACCES;
+                }
+        }
+
+        r = sync_auth(bus, error);
+        if (r < 0) {
+                dbus_connection_close(bus);
+                dbus_connection_unref(bus);
+                return r;
+        }
+
+        if (_private)
+                *_private = private;
+
+        *_bus = bus;
+        return 0;
+}
+
+int bus_connect_system_ssh(const char *user, const char *host, DBusConnection **_bus, DBusError *error) {
+        DBusConnection *bus;
+        char *p = NULL;
+        int r;
+
+        assert(_bus);
+        assert(user || host);
+
+        if (user && host)
+                asprintf(&p, "unixexec:path=ssh,argv1=-xT,argv2=%s@%s,argv3=systemd-stdio-bridge", user, host);
+        else if (user)
+                asprintf(&p, "unixexec:path=ssh,argv1=-xT,argv2=%s@localhost,argv3=systemd-stdio-bridge", user);
+        else if (host)
+                asprintf(&p, "unixexec:path=ssh,argv1=-xT,argv2=%s,argv3=systemd-stdio-bridge", host);
+
+        if (!p) {
+                dbus_set_error_const(error, DBUS_ERROR_NO_MEMORY, NULL);
+                return -ENOMEM;
+        }
+
+        bus = dbus_connection_open_private(p, error);
+        free(p);
+
+        if (!bus)
+                return -EIO;
+
+        dbus_connection_set_exit_on_disconnect(bus, FALSE);
+
+        if ((r = sync_auth(bus, error)) < 0) {
+                dbus_connection_close(bus);
+                dbus_connection_unref(bus);
+                return r;
+        }
+
+        if (!dbus_bus_register(bus, error)) {
+                dbus_connection_close(bus);
+                dbus_connection_unref(bus);
+                return r;
+        }
+
+        *_bus = bus;
+        return 0;
+}
+
+int bus_connect_system_polkit(DBusConnection **_bus, DBusError *error) {
+        DBusConnection *bus;
+        int r;
+
+        assert(_bus);
+
+        /* Don't bother with PolicyKit if we are root */
+        if (geteuid() == 0)
+                return bus_connect(DBUS_BUS_SYSTEM, _bus, NULL, error);
+
+        bus = dbus_connection_open_private("unixexec:path=pkexec,argv1=" SYSTEMD_STDIO_BRIDGE_BINARY_PATH, error);
+        if (!bus)
+                return -EIO;
+
+        dbus_connection_set_exit_on_disconnect(bus, FALSE);
+
+        if ((r = sync_auth(bus, error)) < 0) {
+                dbus_connection_close(bus);
+                dbus_connection_unref(bus);
+                return r;
+        }
+
+        if (!dbus_bus_register(bus, error)) {
+                dbus_connection_close(bus);
+                dbus_connection_unref(bus);
+                return r;
+        }
+
+        *_bus = bus;
+        return 0;
+}
+
+const char *bus_error_message(const DBusError *error) {
+        assert(error);
+
+        /* Sometimes the D-Bus server is a little bit too verbose with
+         * its error messages, so let's override them here */
+        if (dbus_error_has_name(error, DBUS_ERROR_ACCESS_DENIED))
+                return "Access denied";
+
+        return error->message;
+}
+
+DBusHandlerResult bus_default_message_handler(
+                DBusConnection *c,
+                DBusMessage *message,
+                const char *introspection,
+                const char *interfaces,
+                const BusBoundProperties *bound_properties) {
+
+        DBusError error;
+        DBusMessage *reply = NULL;
+        int r;
+
+        assert(c);
+        assert(message);
+
+        dbus_error_init(&error);
+
+        if (dbus_message_is_method_call(message, "org.freedesktop.DBus.Introspectable", "Introspect") && introspection) {
+
+                if (!(reply = dbus_message_new_method_return(message)))
+                        goto oom;
+
+                if (!dbus_message_append_args(reply, DBUS_TYPE_STRING, &introspection, DBUS_TYPE_INVALID))
+                        goto oom;
+
+        } else if (dbus_message_is_method_call(message, "org.freedesktop.DBus.Properties", "Get") && bound_properties) {
+                const char *interface, *property;
+                const BusBoundProperties *bp;
+                const BusProperty *p;
+                void *data;
+                DBusMessageIter iter, sub;
+
+                if (!dbus_message_get_args(
+                            message,
+                            &error,
+                            DBUS_TYPE_STRING, &interface,
+                            DBUS_TYPE_STRING, &property,
+                            DBUS_TYPE_INVALID))
+                        return bus_send_error_reply(c, message, &error, -EINVAL);
+
+                for (bp = bound_properties; bp->interface; bp++) {
+                        if (!streq(bp->interface, interface))
+                                continue;
+
+                        for (p = bp->properties; p->property; p++)
+                                if (streq(p->property, property))
+                                        goto get_prop;
+                }
+
+                /* no match */
+                if (!nulstr_contains(interfaces, interface))
+                        dbus_set_error_const(&error, DBUS_ERROR_UNKNOWN_INTERFACE, "Unknown interface");
+                else
+                        dbus_set_error_const(&error, DBUS_ERROR_UNKNOWN_PROPERTY, "Unknown property");
+
+                return bus_send_error_reply(c, message, &error, -EINVAL);
+
+get_prop:
+                reply = dbus_message_new_method_return(message);
+                if (!reply)
+                        goto oom;
+
+                dbus_message_iter_init_append(reply, &iter);
+
+                if (!dbus_message_iter_open_container(&iter, DBUS_TYPE_VARIANT, p->signature, &sub))
+                        goto oom;
+
+                data = (char*)bp->base + p->offset;
+                if (p->indirect)
+                        data = *(void**)data;
+                r = p->append(&sub, property, data);
+                if (r < 0) {
+                        if (r == -ENOMEM)
+                                goto oom;
+
+                        dbus_message_unref(reply);
+                        return bus_send_error_reply(c, message, NULL, r);
+                }
+
+                if (!dbus_message_iter_close_container(&iter, &sub))
+                        goto oom;
+
+        } else if (dbus_message_is_method_call(message, "org.freedesktop.DBus.Properties", "GetAll") && bound_properties) {
+                const char *interface;
+                const BusBoundProperties *bp;
+                const BusProperty *p;
+                DBusMessageIter iter, sub, sub2, sub3;
+
+                if (!dbus_message_get_args(
+                            message,
+                            &error,
+                            DBUS_TYPE_STRING, &interface,
+                            DBUS_TYPE_INVALID))
+                        return bus_send_error_reply(c, message, &error, -EINVAL);
+
+                if (interface[0] && !nulstr_contains(interfaces, interface)) {
+                        dbus_set_error_const(&error, DBUS_ERROR_UNKNOWN_INTERFACE, "Unknown interface");
+                        return bus_send_error_reply(c, message, &error, -EINVAL);
+                }
+
+                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, "{sv}", &sub))
+                        goto oom;
+
+                for (bp = bound_properties; bp->interface; bp++) {
+                        if (interface[0] && !streq(bp->interface, interface))
+                                continue;
+
+                        for (p = bp->properties; p->property; p++) {
+                                void *data;
+
+                                if (!dbus_message_iter_open_container(&sub, DBUS_TYPE_DICT_ENTRY, NULL, &sub2) ||
+                                    !dbus_message_iter_append_basic(&sub2, DBUS_TYPE_STRING, &p->property) ||
+                                    !dbus_message_iter_open_container(&sub2, DBUS_TYPE_VARIANT, p->signature, &sub3))
+                                        goto oom;
+
+                                data = (char*)bp->base + p->offset;
+                                if (p->indirect)
+                                        data = *(void**)data;
+                                r = p->append(&sub3, p->property, data);
+                                if (r < 0) {
+                                        if (r == -ENOMEM)
+                                                goto oom;
+
+                                        dbus_message_unref(reply);
+                                        return bus_send_error_reply(c, message, NULL, r);
+                                }
+
+                                if (!dbus_message_iter_close_container(&sub2, &sub3) ||
+                                    !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.DBus.Properties", "Set") && bound_properties) {
+                const char *interface, *property;
+                DBusMessageIter iter;
+                const BusBoundProperties *bp;
+                const BusProperty *p;
+                DBusMessageIter sub;
+                char *sig;
+
+                if (!dbus_message_iter_init(message, &iter) ||
+                    dbus_message_iter_get_arg_type(&iter) != DBUS_TYPE_STRING)
+                        return bus_send_error_reply(c, message, NULL, -EINVAL);
+
+                dbus_message_iter_get_basic(&iter, &interface);
+
+                if (!dbus_message_iter_next(&iter) ||
+                    dbus_message_iter_get_arg_type(&iter) != DBUS_TYPE_STRING)
+                        return bus_send_error_reply(c, message, NULL, -EINVAL);
+
+                dbus_message_iter_get_basic(&iter, &property);
+
+                if (!dbus_message_iter_next(&iter) ||
+                    dbus_message_iter_get_arg_type(&iter) != DBUS_TYPE_VARIANT ||
+                    dbus_message_iter_has_next(&iter))
+                        return bus_send_error_reply(c, message, NULL, -EINVAL);
+
+                for (bp = bound_properties; bp->interface; bp++) {
+                        if (!streq(bp->interface, interface))
+                                continue;
+
+                        for (p = bp->properties; p->property; p++)
+                                if (streq(p->property, property))
+                                        goto set_prop;
+                }
+
+                /* no match */
+                if (!nulstr_contains(interfaces, interface))
+                        dbus_set_error_const(&error, DBUS_ERROR_UNKNOWN_INTERFACE, "Unknown interface");
+                else
+                        dbus_set_error_const(&error, DBUS_ERROR_UNKNOWN_PROPERTY, "Unknown property");
+
+                return bus_send_error_reply(c, message, &error, -EINVAL);
+
+set_prop:
+                if (!p->set) {
+                        dbus_set_error_const(&error, DBUS_ERROR_PROPERTY_READ_ONLY, "Property read-only");
+                        return bus_send_error_reply(c, message, &error, -EINVAL);
+                }
+
+                dbus_message_iter_recurse(&iter, &sub);
+
+                sig = dbus_message_iter_get_signature(&sub);
+                if (!sig)
+                        goto oom;
+
+                if (!streq(sig, p->signature)) {
+                        dbus_free(sig);
+                        return bus_send_error_reply(c, message, NULL, -EINVAL);
+                }
+
+                dbus_free(sig);
+
+                r = p->set(&sub, property);
+                if (r < 0) {
+                        if (r == -ENOMEM)
+                                goto oom;
+                        return bus_send_error_reply(c, message, NULL, r);
+                }
+
+                reply = dbus_message_new_method_return(message);
+                if (!reply)
+                        goto oom;
+        } else {
+                const char *interface = dbus_message_get_interface(message);
+
+                if (!interface || !nulstr_contains(interfaces, interface)) {
+                        dbus_set_error_const(&error, DBUS_ERROR_UNKNOWN_INTERFACE, "Unknown interface");
+                        return bus_send_error_reply(c, message, &error, -EINVAL);
+                }
+        }
+
+        if (reply) {
+                if (!dbus_connection_send(c, reply, NULL))
+                        goto oom;
+
+                dbus_message_unref(reply);
+                return DBUS_HANDLER_RESULT_HANDLED;
+        }
+
+        return DBUS_HANDLER_RESULT_NOT_YET_HANDLED;
+
+oom:
+        if (reply)
+                dbus_message_unref(reply);
+
+        dbus_error_free(&error);
+
+        return DBUS_HANDLER_RESULT_NEED_MEMORY;
+}
+
+int bus_property_append_string(DBusMessageIter *i, const char *property, void *data) {
+        const char *t = data;
+
+        assert(i);
+        assert(property);
+
+        if (!t)
+                t = "";
+
+        if (!dbus_message_iter_append_basic(i, DBUS_TYPE_STRING, &t))
+                return -ENOMEM;
+
+        return 0;
+}
+
+int bus_property_append_strv(DBusMessageIter *i, const char *property, void *data) {
+        char **t = data;
+
+        assert(i);
+        assert(property);
+
+        return bus_append_strv_iter(i, t);
+}
+
+int bus_property_append_bool(DBusMessageIter *i, const char *property, void *data) {
+        bool *b = data;
+        dbus_bool_t db;
+
+        assert(i);
+        assert(property);
+        assert(b);
+
+        db = *b;
+
+        if (!dbus_message_iter_append_basic(i, DBUS_TYPE_BOOLEAN, &db))
+                return -ENOMEM;
+
+        return 0;
+}
+
+int bus_property_append_tristate_false(DBusMessageIter *i, const char *property, void *data) {
+        int *b = data;
+        dbus_bool_t db;
+
+        assert(i);
+        assert(property);
+        assert(b);
+
+        db = *b > 0;
+
+        if (!dbus_message_iter_append_basic(i, DBUS_TYPE_BOOLEAN, &db))
+                return -ENOMEM;
+
+        return 0;
+}
+
+int bus_property_append_uint64(DBusMessageIter *i, const char *property, void *data) {
+        assert(i);
+        assert(property);
+        assert(data);
+
+        /* Let's ensure that usec_t is actually 64bit, and hence this
+         * function can be used for usec_t */
+        assert_cc(sizeof(uint64_t) == sizeof(usec_t));
+
+        if (!dbus_message_iter_append_basic(i, DBUS_TYPE_UINT64, data))
+                return -ENOMEM;
+
+        return 0;
+}
+
+int bus_property_append_uint32(DBusMessageIter *i, const char *property, void *data) {
+        assert(i);
+        assert(property);
+        assert(data);
+
+        /* Let's ensure that pid_t, mode_t, uid_t, gid_t are actually
+         * 32bit, and hence this function can be used for
+         * pid_t/mode_t/uid_t/gid_t */
+        assert_cc(sizeof(uint32_t) == sizeof(pid_t));
+        assert_cc(sizeof(uint32_t) == sizeof(mode_t));
+        assert_cc(sizeof(uint32_t) == sizeof(unsigned));
+        assert_cc(sizeof(uint32_t) == sizeof(uid_t));
+        assert_cc(sizeof(uint32_t) == sizeof(gid_t));
+
+        if (!dbus_message_iter_append_basic(i, DBUS_TYPE_UINT32, data))
+                return -ENOMEM;
+
+        return 0;
+}
+
+int bus_property_append_int32(DBusMessageIter *i, const char *property, void *data) {
+        assert(i);
+        assert(property);
+        assert(data);
+
+        assert_cc(sizeof(int32_t) == sizeof(int));
+
+        if (!dbus_message_iter_append_basic(i, DBUS_TYPE_INT32, data))
+                return -ENOMEM;
+
+        return 0;
+}
+
+int bus_property_append_size(DBusMessageIter *i, const char *property, void *data) {
+        uint64_t u;
+
+        assert(i);
+        assert(property);
+        assert(data);
+
+        u = (uint64_t) *(size_t*) data;
+
+        if (!dbus_message_iter_append_basic(i, DBUS_TYPE_UINT64, &u))
+                return -ENOMEM;
+
+        return 0;
+}
+
+int bus_property_append_ul(DBusMessageIter *i, const char *property, void *data) {
+        uint64_t u;
+
+        assert(i);
+        assert(property);
+        assert(data);
+
+        u = (uint64_t) *(unsigned long*) data;
+
+        if (!dbus_message_iter_append_basic(i, DBUS_TYPE_UINT64, &u))
+                return -ENOMEM;
+
+        return 0;
+}
+
+int bus_property_append_long(DBusMessageIter *i, const char *property, void *data) {
+        int64_t l;
+
+        assert(i);
+        assert(property);
+        assert(data);
+
+        l = (int64_t) *(long*) data;
+
+        if (!dbus_message_iter_append_basic(i, DBUS_TYPE_INT64, &l))
+                return -ENOMEM;
+
+        return 0;
+}
+
+const char *bus_errno_to_dbus(int error) {
+
+        switch(error) {
+
+        case -EINVAL:
+                return DBUS_ERROR_INVALID_ARGS;
+
+        case -ENOMEM:
+                return DBUS_ERROR_NO_MEMORY;
+
+        case -EPERM:
+        case -EACCES:
+                return DBUS_ERROR_ACCESS_DENIED;
+
+        case -ESRCH:
+                return DBUS_ERROR_UNIX_PROCESS_ID_UNKNOWN;
+
+        case -ENOENT:
+                return DBUS_ERROR_FILE_NOT_FOUND;
+
+        case -EEXIST:
+                return DBUS_ERROR_FILE_EXISTS;
+
+        case -ETIMEDOUT:
+        case -ETIME:
+                return DBUS_ERROR_TIMEOUT;
+
+        case -EIO:
+                return DBUS_ERROR_IO_ERROR;
+
+        case -ENETRESET:
+        case -ECONNABORTED:
+        case -ECONNRESET:
+                return DBUS_ERROR_DISCONNECTED;
+        }
+
+        return DBUS_ERROR_FAILED;
+}
+
+DBusHandlerResult bus_send_error_reply(DBusConnection *c, DBusMessage *message, DBusError *berror, int error) {
+        DBusMessage *reply = NULL;
+        const char *name, *text;
+
+        if (berror && dbus_error_is_set(berror)) {
+                name = berror->name;
+                text = berror->message;
+        } else {
+                name = bus_errno_to_dbus(error);
+                text = strerror(-error);
+        }
+
+        if (!(reply = dbus_message_new_error(message, name, text)))
+                goto oom;
+
+        if (!dbus_connection_send(c, reply, NULL))
+                goto oom;
+
+        dbus_message_unref(reply);
+
+        if (berror)
+                dbus_error_free(berror);
+
+        return DBUS_HANDLER_RESULT_HANDLED;
+
+oom:
+        if (reply)
+                dbus_message_unref(reply);
+
+        if (berror)
+                dbus_error_free(berror);
+
+        return DBUS_HANDLER_RESULT_NEED_MEMORY;
+}
+
+DBusMessage* bus_properties_changed_new(const char *path, const char *interface, const char *properties) {
+        DBusMessage *m;
+        DBusMessageIter iter, sub;
+        const char *i;
+
+        assert(interface);
+        assert(properties);
+
+        if (!(m = dbus_message_new_signal(path, "org.freedesktop.DBus.Properties", "PropertiesChanged")))
+                goto oom;
+
+        dbus_message_iter_init_append(m, &iter);
+
+        /* We won't send any property values, since they might be
+         * large and sometimes not cheap to generated */
+
+        if (!dbus_message_iter_append_basic(&iter, DBUS_TYPE_STRING, &interface) ||
+            !dbus_message_iter_open_container(&iter, DBUS_TYPE_ARRAY, "{sv}", &sub) ||
+            !dbus_message_iter_close_container(&iter, &sub) ||
+            !dbus_message_iter_open_container(&iter, DBUS_TYPE_ARRAY, "s", &sub))
+                goto oom;
+
+        NULSTR_FOREACH(i, properties)
+                if (!dbus_message_iter_append_basic(&sub, DBUS_TYPE_STRING, &i))
+                        goto oom;
+
+        if (!dbus_message_iter_close_container(&iter, &sub))
+                goto oom;
+
+        return m;
+
+oom:
+        if (m)
+                dbus_message_unref(m);
+
+        return NULL;
+}
+
+uint32_t bus_flags_to_events(DBusWatch *bus_watch) {
+        unsigned flags;
+        uint32_t events = 0;
+
+        assert(bus_watch);
+
+        /* no watch flags for disabled watches */
+        if (!dbus_watch_get_enabled(bus_watch))
+                return 0;
+
+        flags = dbus_watch_get_flags(bus_watch);
+
+        if (flags & DBUS_WATCH_READABLE)
+                events |= EPOLLIN;
+        if (flags & DBUS_WATCH_WRITABLE)
+                events |= EPOLLOUT;
+
+        return events | EPOLLHUP | EPOLLERR;
+}
+
+unsigned bus_events_to_flags(uint32_t events) {
+        unsigned flags = 0;
+
+        if (events & EPOLLIN)
+                flags |= DBUS_WATCH_READABLE;
+        if (events & EPOLLOUT)
+                flags |= DBUS_WATCH_WRITABLE;
+        if (events & EPOLLHUP)
+                flags |= DBUS_WATCH_HANGUP;
+        if (events & EPOLLERR)
+                flags |= DBUS_WATCH_ERROR;
+
+        return flags;
+}
+
+int bus_parse_strv(DBusMessage *m, char ***_l) {
+        DBusMessageIter iter;
+
+        assert(m);
+        assert(_l);
+
+        if (!dbus_message_iter_init(m, &iter))
+                return -EINVAL;
+
+        return bus_parse_strv_iter(&iter, _l);
+}
+
+int bus_parse_strv_iter(DBusMessageIter *iter, char ***_l) {
+        DBusMessageIter sub;
+        unsigned n = 0, i = 0;
+        char **l;
+
+        assert(iter);
+        assert(_l);
+
+        if (dbus_message_iter_get_arg_type(iter) != DBUS_TYPE_ARRAY ||
+            dbus_message_iter_get_element_type(iter) != DBUS_TYPE_STRING)
+            return -EINVAL;
+
+        dbus_message_iter_recurse(iter, &sub);
+
+        while (dbus_message_iter_get_arg_type(&sub) != DBUS_TYPE_INVALID) {
+                n++;
+                dbus_message_iter_next(&sub);
+        }
+
+        if (!(l = new(char*, n+1)))
+                return -ENOMEM;
+
+        dbus_message_iter_recurse(iter, &sub);
+
+        while (dbus_message_iter_get_arg_type(&sub) != DBUS_TYPE_INVALID) {
+                const char *s;
+
+                assert_se(dbus_message_iter_get_arg_type(&sub) == DBUS_TYPE_STRING);
+                dbus_message_iter_get_basic(&sub, &s);
+
+                if (!(l[i++] = strdup(s))) {
+                        strv_free(l);
+                        return -ENOMEM;
+                }
+
+                dbus_message_iter_next(&sub);
+        }
+
+        assert(i == n);
+        l[i] = NULL;
+
+        if (_l)
+                *_l = l;
+
+        return 0;
+}
+
+int bus_append_strv_iter(DBusMessageIter *iter, char **l) {
+        DBusMessageIter sub;
+
+        assert(iter);
+
+        if (!dbus_message_iter_open_container(iter, DBUS_TYPE_ARRAY, "s", &sub))
+                return -ENOMEM;
+
+        STRV_FOREACH(l, l)
+                if (!dbus_message_iter_append_basic(&sub, DBUS_TYPE_STRING, l))
+                        return -ENOMEM;
+
+        if (!dbus_message_iter_close_container(iter, &sub))
+                return -ENOMEM;
+
+        return 0;
+}
+
+int bus_iter_get_basic_and_next(DBusMessageIter *iter, int type, void *data, bool next) {
+
+        assert(iter);
+        assert(data);
+
+        if (dbus_message_iter_get_arg_type(iter) != type)
+                return -EIO;
+
+        dbus_message_iter_get_basic(iter, data);
+
+        if (!dbus_message_iter_next(iter) != !next)
+                return -EIO;
+
+        return 0;
+}
+
+int generic_print_property(const char *name, DBusMessageIter *iter, bool all) {
+        assert(name);
+        assert(iter);
+
+        switch (dbus_message_iter_get_arg_type(iter)) {
+
+        case DBUS_TYPE_STRING: {
+                const char *s;
+                dbus_message_iter_get_basic(iter, &s);
+
+                if (all || !isempty(s))
+                        printf("%s=%s\n", name, s);
+
+                return 1;
+        }
+
+        case DBUS_TYPE_BOOLEAN: {
+                dbus_bool_t b;
+
+                dbus_message_iter_get_basic(iter, &b);
+                printf("%s=%s\n", name, yes_no(b));
+
+                return 1;
+        }
+
+        case DBUS_TYPE_UINT64: {
+                uint64_t u;
+                dbus_message_iter_get_basic(iter, &u);
+
+                /* Yes, heuristics! But we can change this check
+                 * should it turn out to not be sufficient */
+
+                if (endswith(name, "Timestamp")) {
+                        char timestamp[FORMAT_TIMESTAMP_MAX], *t;
+
+                        t = format_timestamp(timestamp, sizeof(timestamp), u);
+                        if (t || all)
+                                printf("%s=%s\n", name, strempty(t));
+
+                } else if (strstr(name, "USec")) {
+                        char timespan[FORMAT_TIMESPAN_MAX];
+
+                        printf("%s=%s\n", name, format_timespan(timespan, sizeof(timespan), u));
+                } else
+                        printf("%s=%llu\n", name, (unsigned long long) u);
+
+                return 1;
+        }
+
+        case DBUS_TYPE_UINT32: {
+                uint32_t u;
+                dbus_message_iter_get_basic(iter, &u);
+
+                if (strstr(name, "UMask") || strstr(name, "Mode"))
+                        printf("%s=%04o\n", name, u);
+                else
+                        printf("%s=%u\n", name, (unsigned) u);
+
+                return 1;
+        }
+
+        case DBUS_TYPE_INT32: {
+                int32_t i;
+                dbus_message_iter_get_basic(iter, &i);
+
+                printf("%s=%i\n", name, (int) i);
+                return 1;
+        }
+
+        case DBUS_TYPE_DOUBLE: {
+                double d;
+                dbus_message_iter_get_basic(iter, &d);
+
+                printf("%s=%g\n", name, d);
+                return 1;
+        }
+
+        case DBUS_TYPE_ARRAY:
+
+                if (dbus_message_iter_get_element_type(iter) == DBUS_TYPE_STRING) {
+                        DBusMessageIter sub;
+                        bool space = false;
+
+                        dbus_message_iter_recurse(iter, &sub);
+                        if (all ||
+                            dbus_message_iter_get_arg_type(&sub) != DBUS_TYPE_INVALID) {
+                                printf("%s=", name);
+
+                                while (dbus_message_iter_get_arg_type(&sub) != DBUS_TYPE_INVALID) {
+                                        const char *s;
+
+                                        assert(dbus_message_iter_get_arg_type(&sub) == DBUS_TYPE_STRING);
+                                        dbus_message_iter_get_basic(&sub, &s);
+                                        printf("%s%s", space ? " " : "", s);
+
+                                        space = true;
+                                        dbus_message_iter_next(&sub);
+                                }
+
+                                puts("");
+                        }
+
+                        return 1;
+
+                } else if (dbus_message_iter_get_element_type(iter) == DBUS_TYPE_BYTE) {
+                        DBusMessageIter sub;
+
+                        dbus_message_iter_recurse(iter, &sub);
+                        if (all ||
+                            dbus_message_iter_get_arg_type(&sub) != DBUS_TYPE_INVALID) {
+                                printf("%s=", name);
+
+                                while (dbus_message_iter_get_arg_type(&sub) != DBUS_TYPE_INVALID) {
+                                        uint8_t u;
+
+                                        assert(dbus_message_iter_get_arg_type(&sub) == DBUS_TYPE_BYTE);
+                                        dbus_message_iter_get_basic(&sub, &u);
+                                        printf("%02x", u);
+
+                                        dbus_message_iter_next(&sub);
+                                }
+
+                                puts("");
+                        }
+
+                        return 1;
+                }
+
+                break;
+        }
+
+        return 0;
+}
+
+static void release_name_pending_cb(DBusPendingCall *pending, void *userdata) {
+        DBusMessage *reply;
+        DBusConnection *bus = userdata;
+
+        assert_se(reply = dbus_pending_call_steal_reply(pending));
+        dbus_message_unref(reply);
+
+        dbus_connection_close(bus);
+}
+
+void bus_async_unregister_and_exit(DBusConnection *bus, const char *name) {
+        DBusMessage *m = NULL;
+        DBusPendingCall *pending = NULL;
+
+        assert(bus);
+
+        /* We unregister the name here, but we continue to process
+         * requests, until we get the response for it, so that all
+         * requests are guaranteed to be processed. */
+
+        m = dbus_message_new_method_call(
+                        DBUS_SERVICE_DBUS,
+                        DBUS_PATH_DBUS,
+                        DBUS_INTERFACE_DBUS,
+                        "ReleaseName");
+        if (!m)
+                goto oom;
+
+        if (!dbus_message_append_args(
+                            m,
+                            DBUS_TYPE_STRING,
+                            &name,
+                            DBUS_TYPE_INVALID))
+                goto oom;
+
+        if (!dbus_connection_send_with_reply(bus, m, &pending, -1))
+                goto oom;
+
+        if (!dbus_pending_call_set_notify(pending, release_name_pending_cb, bus, NULL))
+                goto oom;
+
+        dbus_message_unref(m);
+        dbus_pending_call_unref(pending);
+
+        return;
+
+oom:
+        log_error("Out of memory");
+
+        if (pending) {
+                dbus_pending_call_cancel(pending);
+                dbus_pending_call_unref(pending);
+        }
+
+        if (m)
+                dbus_message_unref(m);
+}
+
+DBusHandlerResult bus_exit_idle_filter(DBusConnection *bus, DBusMessage *m, void *userdata) {
+        usec_t *remain_until = userdata;
+
+        assert(bus);
+        assert(m);
+        assert(remain_until);
+
+        /* Everytime we get a new message we reset out timeout */
+        *remain_until = now(CLOCK_MONOTONIC) + DEFAULT_EXIT_USEC;
+
+        if (dbus_message_is_signal(m, DBUS_INTERFACE_LOCAL, "Disconnected"))
+                dbus_connection_close(bus);
+
+        return DBUS_HANDLER_RESULT_NOT_YET_HANDLED;
+}
diff --git a/src/dbus-common.h b/src/dbus-common.h
new file mode 100644 (file)
index 0000000..15811a7
--- /dev/null
@@ -0,0 +1,183 @@
+/*-*- Mode: C; c-basic-offset: 8; indent-tabs-mode: nil -*-*/
+
+#ifndef foodbuscommonhfoo
+#define foodbuscommonhfoo
+
+/***
+  This file is part 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 <http://www.gnu.org/licenses/>.
+***/
+
+#include <dbus/dbus.h>
+
+#ifndef DBUS_ERROR_UNKNOWN_OBJECT
+#define DBUS_ERROR_UNKNOWN_OBJECT "org.freedesktop.DBus.Error.UnknownObject"
+#endif
+
+#ifndef DBUS_ERROR_UNKNOWN_INTERFACE
+#define DBUS_ERROR_UNKNOWN_INTERFACE "org.freedesktop.DBus.Error.UnknownInterface"
+#endif
+
+#ifndef DBUS_ERROR_UNKNOWN_PROPERTY
+#define DBUS_ERROR_UNKNOWN_PROPERTY "org.freedesktop.DBus.Error.UnknownProperty"
+#endif
+
+#ifndef DBUS_ERROR_PROPERTY_READ_ONLY
+#define DBUS_ERROR_PROPERTY_READ_ONLY "org.freedesktop.DBus.Error.PropertyReadOnly"
+#endif
+
+#define BUS_PROPERTIES_INTERFACE                                        \
+        " <interface name=\"org.freedesktop.DBus.Properties\">\n"       \
+        "  <method name=\"Get\">\n"                                     \
+        "   <arg name=\"interface\" direction=\"in\" type=\"s\"/>\n"    \
+        "   <arg name=\"property\" direction=\"in\" type=\"s\"/>\n"     \
+        "   <arg name=\"value\" direction=\"out\" type=\"v\"/>\n"       \
+        "  </method>\n"                                                 \
+        "  <method name=\"GetAll\">\n"                                  \
+        "   <arg name=\"interface\" direction=\"in\" type=\"s\"/>\n"    \
+        "   <arg name=\"properties\" direction=\"out\" type=\"a{sv}\"/>\n" \
+        "  </method>\n"                                                 \
+        "  <method name=\"Set\">\n"                                     \
+        "   <arg name=\"interface\" direction=\"in\" type=\"s\"/>\n"    \
+        "   <arg name=\"property\" direction=\"in\" type=\"s\"/>\n"     \
+        "   <arg name=\"value\" direction=\"in\" type=\"v\"/>\n"        \
+        "  </method>\n"                                                 \
+        "  <signal name=\"PropertiesChanged\">\n"                       \
+        "   <arg type=\"s\" name=\"interface\"/>\n"                     \
+        "   <arg type=\"a{sv}\" name=\"changed_properties\"/>\n"        \
+        "   <arg type=\"as\" name=\"invalidated_properties\"/>\n"       \
+        "  </signal>\n"                                                 \
+        " </interface>\n"
+
+#define BUS_INTROSPECTABLE_INTERFACE                                    \
+        " <interface name=\"org.freedesktop.DBus.Introspectable\">\n"   \
+        "  <method name=\"Introspect\">\n"                              \
+        "   <arg name=\"data\" type=\"s\" direction=\"out\"/>\n"        \
+        "  </method>\n"                                                 \
+        " </interface>\n"
+
+#define BUS_PEER_INTERFACE                                              \
+        "<interface name=\"org.freedesktop.DBus.Peer\">\n"              \
+        " <method name=\"Ping\"/>\n"                                    \
+        " <method name=\"GetMachineId\">\n"                             \
+        "  <arg type=\"s\" name=\"machine_uuid\" direction=\"out\"/>\n" \
+        " </method>\n"                                                  \
+        "</interface>\n"
+
+#define BUS_GENERIC_INTERFACES_LIST             \
+        "org.freedesktop.DBus.Properties\0"     \
+        "org.freedesktop.DBus.Introspectable\0" \
+        "org.freedesktop.DBus.Peer\0"
+
+int bus_check_peercred(DBusConnection *c);
+
+int bus_connect(DBusBusType t, DBusConnection **_bus, bool *private_bus, DBusError *error);
+
+int bus_connect_system_ssh(const char *user, const char *host, DBusConnection **_bus, DBusError *error);
+int bus_connect_system_polkit(DBusConnection **_bus, DBusError *error);
+
+const char *bus_error_message(const DBusError *error);
+
+typedef int (*BusPropertyCallback)(DBusMessageIter *iter, const char *property, void *data);
+typedef int (*BusPropertySetCallback)(DBusMessageIter *iter, const char *property);
+
+typedef struct BusProperty {
+        const char *property;            /* name of the property */
+        BusPropertyCallback append;      /* Function that is called to serialize this property */
+        const char *signature;
+        const uint16_t offset;           /* Offset from BusBoundProperties::base address to the property data.
+                                          * uint16_t is sufficient, because we have no structs too big.
+                                          * -Werror=overflow will catch it if this does not hold. */
+        bool indirect;                   /* data is indirect, ie. not base+offset, but *(base+offset) */
+        BusPropertySetCallback set;      /* Optional: Function that is called to set this property */
+} BusProperty;
+
+typedef struct BusBoundProperties {
+        const char *interface;           /* interface of the properties */
+        const BusProperty *properties;   /* array of properties, ended by a NULL-filled element */
+        const void *const base;          /* base pointer to which the offset must be added to reach data */
+} BusBoundProperties;
+
+DBusHandlerResult bus_send_error_reply(
+                DBusConnection *c,
+                DBusMessage *message,
+                DBusError *bus_error,
+                int error);
+
+DBusHandlerResult bus_default_message_handler(
+                DBusConnection *c,
+                DBusMessage *message,
+                const char *introspection,
+                const char *interfaces,
+                const BusBoundProperties *bound_properties);
+
+int bus_property_append_string(DBusMessageIter *i, const char *property, void *data);
+int bus_property_append_strv(DBusMessageIter *i, const char *property, void *data);
+int bus_property_append_bool(DBusMessageIter *i, const char *property, void *data);
+int bus_property_append_tristate_false(DBusMessageIter *i, const char *property, void *data);
+int bus_property_append_int32(DBusMessageIter *i, const char *property, void *data);
+int bus_property_append_uint32(DBusMessageIter *i, const char *property, void *data);
+int bus_property_append_uint64(DBusMessageIter *i, const char *property, void *data);
+int bus_property_append_size(DBusMessageIter *i, const char *property, void *data);
+int bus_property_append_ul(DBusMessageIter *i, const char *property, void *data);
+int bus_property_append_long(DBusMessageIter *i, const char *property, void *data);
+
+#define bus_property_append_int bus_property_append_int32
+#define bus_property_append_pid bus_property_append_uint32
+#define bus_property_append_uid bus_property_append_uint32
+#define bus_property_append_gid bus_property_append_uint32
+#define bus_property_append_mode bus_property_append_uint32
+#define bus_property_append_unsigned bus_property_append_uint32
+#define bus_property_append_usec bus_property_append_uint64
+
+#define DEFINE_BUS_PROPERTY_APPEND_ENUM(function,name,type)             \
+        int function(DBusMessageIter *i, const char *property, void *data) { \
+                const char *value;                                      \
+                type *field = data;                                     \
+                                                                        \
+                assert(i);                                              \
+                assert(property);                                       \
+                                                                        \
+                value = name##_to_string(*field);                       \
+                                                                        \
+                if (!dbus_message_iter_append_basic(i, DBUS_TYPE_STRING, &value)) \
+                        return -ENOMEM;                                 \
+                                                                        \
+                return 0;                                               \
+        }
+
+const char *bus_errno_to_dbus(int error);
+
+DBusMessage* bus_properties_changed_new(const char *path, const char *interface, const char *properties);
+
+uint32_t bus_flags_to_events(DBusWatch *bus_watch);
+unsigned bus_events_to_flags(uint32_t events);
+
+int bus_parse_strv(DBusMessage *m, char ***_l);
+int bus_parse_strv_iter(DBusMessageIter *iter, char ***_l);
+
+int bus_append_strv_iter(DBusMessageIter *iter, char **l);
+
+int bus_iter_get_basic_and_next(DBusMessageIter *iter, int type, void *data, bool next);
+
+int generic_print_property(const char *name, DBusMessageIter *iter, bool all);
+
+void bus_async_unregister_and_exit(DBusConnection *bus, const char *name);
+
+DBusHandlerResult bus_exit_idle_filter(DBusConnection *bus, DBusMessage *m, void *userdata);
+
+#endif
diff --git a/src/dbus-device.c b/src/dbus-device.c
new file mode 100644 (file)
index 0000000..b39fb9d
--- /dev/null
@@ -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 <http://www.gnu.org/licenses/>.
+***/
+
+#include "dbus-unit.h"
+#include "dbus-device.h"
+#include "dbus-common.h"
+
+#define BUS_DEVICE_INTERFACE                                            \
+        " <interface name=\"org.freedesktop.systemd1.Device\">\n"       \
+        "  <property name=\"SysFSPath\" type=\"s\" access=\"read\"/>\n" \
+        " </interface>\n"
+
+#define INTROSPECTION                                                   \
+        DBUS_INTROSPECT_1_0_XML_DOCTYPE_DECL_NODE                       \
+        "<node>\n"                                                      \
+        BUS_UNIT_INTERFACE                                              \
+        BUS_DEVICE_INTERFACE                                            \
+        BUS_PROPERTIES_INTERFACE                                        \
+        BUS_PEER_INTERFACE                                              \
+        BUS_INTROSPECTABLE_INTERFACE                                    \
+        "</node>\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
new file mode 100644 (file)
index 0000000..fba270b
--- /dev/null
@@ -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 <http://www.gnu.org/licenses/>.
+***/
+
+#include <dbus/dbus.h>
+
+#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
new file mode 100644 (file)
index 0000000..1fd2b21
--- /dev/null
@@ -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 <http://www.gnu.org/licenses/>.
+***/
+
+#include <errno.h>
+#include <dbus/dbus.h>
+#include <sys/prctl.h>
+
+#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
new file mode 100644 (file)
index 0000000..03cd69d
--- /dev/null
@@ -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 <http://www.gnu.org/licenses/>.
+***/
+
+#include <dbus/dbus.h>
+
+#include "manager.h"
+#include "dbus-common.h"
+
+#define BUS_EXEC_STATUS_INTERFACE(prefix)                               \
+        "  <property name=\"" prefix "StartTimestamp\" type=\"t\" access=\"read\"/>\n" \
+        "  <property name=\"" prefix "StartTimestampMonotonic\" type=\"t\" access=\"read\"/>\n" \
+        "  <property name=\"" prefix "ExitTimestamp\" type=\"t\" access=\"read\"/>\n" \
+        "  <property name=\"" prefix "ExitTimestampMonotonic\" type=\"t\" access=\"read\"/>\n" \
+        "  <property name=\"" prefix "PID\" type=\"u\" access=\"read\"/>\n" \
+        "  <property name=\"" prefix "Code\" type=\"i\" access=\"read\"/>\n" \
+        "  <property name=\"" prefix "Status\" type=\"i\" access=\"read\"/>\n"
+
+#define BUS_EXEC_CONTEXT_INTERFACE                                      \
+        "  <property name=\"Environment\" type=\"as\" access=\"read\"/>\n" \
+        "  <property name=\"UMask\" type=\"u\" access=\"read\"/>\n"     \
+        "  <property name=\"LimitCPU\" type=\"t\" access=\"read\"/>\n"  \
+        "  <property name=\"LimitFSIZE\" type=\"t\" access=\"read\"/>\n" \
+        "  <property name=\"LimitDATA\" type=\"t\" access=\"read\"/>\n" \
+        "  <property name=\"LimitSTACK\" type=\"t\" access=\"read\"/>\n" \
+        "  <property name=\"LimitCORE\" type=\"t\" access=\"read\"/>\n" \
+        "  <property name=\"LimitRSS\" type=\"t\" access=\"read\"/>\n"  \
+        "  <property name=\"LimitNOFILE\" type=\"t\" access=\"read\"/>\n" \
+        "  <property name=\"LimitAS\" type=\"t\" access=\"read\"/>\n"   \
+        "  <property name=\"LimitNPROC\" type=\"t\" access=\"read\"/>\n" \
+        "  <property name=\"LimitMEMLOCK\" type=\"t\" access=\"read\"/>\n" \
+        "  <property name=\"LimitLOCKS\" type=\"t\" access=\"read\"/>\n" \
+        "  <property name=\"LimitSIGPENDING\" type=\"t\" access=\"read\"/>\n" \
+        "  <property name=\"LimitMSGQUEUE\" type=\"t\" access=\"read\"/>\n" \
+        "  <property name=\"LimitNICE\" type=\"t\" access=\"read\"/>\n" \
+        "  <property name=\"LimitRTPRIO\" type=\"t\" access=\"read\"/>\n" \
+        "  <property name=\"LimitRTTIME\" type=\"t\" access=\"read\"/>\n" \
+        "  <property name=\"WorkingDirectory\" type=\"s\" access=\"read\"/>\n" \
+        "  <property name=\"RootDirectory\" type=\"s\" access=\"read\"/>\n" \
+        "  <property name=\"OOMScoreAdjust\" type=\"i\" access=\"read\"/>\n" \
+        "  <property name=\"Nice\" type=\"i\" access=\"read\"/>\n" \
+        "  <property name=\"IOScheduling\" type=\"i\" access=\"read\"/>\n" \
+        "  <property name=\"CPUSchedulingPolicy\" type=\"i\" access=\"read\"/>\n" \
+        "  <property name=\"CPUSchedulingPriority\" type=\"i\" access=\"read\"/>\n" \
+        "  <property name=\"CPUAffinity\" type=\"ay\" access=\"read\"/>\n" \
+        "  <property name=\"TimerSlackNS\" type=\"t\" access=\"read\"/>\n" \
+        "  <property name=\"CPUSchedulingResetOnFork\" type=\"b\" access=\"read\"/>\n" \
+        "  <property name=\"NonBlocking\" type=\"b\" access=\"read\"/>\n" \
+        "  <property name=\"StandardInput\" type=\"s\" access=\"read\"/>\n" \
+        "  <property name=\"StandardOutput\" type=\"s\" access=\"read\"/>\n" \
+        "  <property name=\"StandardError\" type=\"s\" access=\"read\"/>\n" \
+        "  <property name=\"TTYPath\" type=\"s\" access=\"read\"/>\n"   \
+        "  <property name=\"TTYReset\" type=\"b\" access=\"read\"/>\n"   \
+        "  <property name=\"TTYVHangup\" type=\"b\" access=\"read\"/>\n"   \
+        "  <property name=\"TTYVTDisallocate\" type=\"b\" access=\"read\"/>\n"   \
+        "  <property name=\"SyslogPriority\" type=\"i\" access=\"read\"/>\n" \
+        "  <property name=\"SyslogIdentifier\" type=\"s\" access=\"read\"/>\n" \
+        "  <property name=\"SyslogLevelPrefix\" type=\"b\" access=\"read\"/>\n" \
+        "  <property name=\"Capabilities\" type=\"s\" access=\"read\"/>\n" \
+        "  <property name=\"SecureBits\" type=\"i\" access=\"read\"/>\n" \
+        "  <property name=\"CapabilityBoundingSet\" type=\"t\" access=\"read\"/>\n" \
+        "  <property name=\"User\" type=\"s\" access=\"read\"/>\n"      \
+        "  <property name=\"Group\" type=\"s\" access=\"read\"/>\n"     \
+        "  <property name=\"SupplementaryGroups\" type=\"as\" access=\"read\"/>\n" \
+        "  <property name=\"TCPWrapName\" type=\"s\" access=\"read\"/>\n" \
+        "  <property name=\"PAMName\" type=\"s\" access=\"read\"/>\n"   \
+        "  <property name=\"ReadWriteDirectories\" type=\"as\" access=\"read\"/>\n" \
+        "  <property name=\"ReadOnlyDirectories\" type=\"as\" access=\"read\"/>\n" \
+        "  <property name=\"InaccessibleDirectories\" type=\"as\" access=\"read\"/>\n" \
+        "  <property name=\"MountFlags\" type=\"t\" access=\"read\"/>\n" \
+        "  <property name=\"PrivateTmp\" type=\"b\" access=\"read\"/>\n" \
+        "  <property name=\"SameProcessGroup\" type=\"b\" access=\"read\"/>\n" \
+        "  <property name=\"KillMode\" type=\"s\" access=\"read\"/>\n"  \
+        "  <property name=\"KillSignal\" type=\"i\" access=\"read\"/>\n" \
+        "  <property name=\"UtmpIdentifier\" type=\"s\" access=\"read\"/>\n" \
+        "  <property name=\"ControlGroupModify\" type=\"b\" access=\"read\"/>\n" \
+        "  <property name=\"ControlGroupPersistent\" type=\"b\" access=\"read\"/>\n" \
+        "  <property name=\"PrivateNetwork\" type=\"b\" access=\"read\"/>\n" \
+        "  <property name=\"IgnoreSIGPIPE\" type=\"b\" access=\"read\"/>\n"
+
+#define BUS_EXEC_COMMAND_INTERFACE(name)                             \
+        "  <property name=\"" name "\" type=\"a(sasbttuii)\" access=\"read\"/>\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
new file mode 100644 (file)
index 0000000..ab6d610
--- /dev/null
@@ -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 <http://www.gnu.org/licenses/>.
+***/
+
+#include <errno.h>
+
+#include "dbus.h"
+#include "log.h"
+#include "dbus-job.h"
+#include "dbus-common.h"
+
+#define BUS_JOB_INTERFACE                                             \
+        " <interface name=\"org.freedesktop.systemd1.Job\">\n"        \
+        "  <method name=\"Cancel\"/>\n"                               \
+        "  <property name=\"Id\" type=\"u\" access=\"read\"/>\n"      \
+        "  <property name=\"Unit\" type=\"(so)\" access=\"read\"/>\n" \
+        "  <property name=\"JobType\" type=\"s\" access=\"read\"/>\n" \
+        "  <property name=\"State\" type=\"s\" access=\"read\"/>\n"   \
+        " </interface>\n"
+
+#define INTROSPECTION                                                 \
+        DBUS_INTROSPECT_1_0_XML_DOCTYPE_DECL_NODE                     \
+        "<node>\n"                                                    \
+        BUS_JOB_INTERFACE                                             \
+        BUS_PROPERTIES_INTERFACE                                      \
+        BUS_PEER_INTERFACE                                            \
+        BUS_INTROSPECTABLE_INTERFACE                                  \
+        "</node>\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
+                              "<node>\n", f);
+
+                        fputs(BUS_INTROSPECTABLE_INTERFACE, f);
+                        fputs(BUS_PEER_INTERFACE, f);
+
+                        HASHMAP_FOREACH(j, m->jobs, i)
+                                fprintf(f, "<node name=\"job/%lu\"/>", (unsigned long) j->id);
+
+                        fputs("</node>\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
new file mode 100644 (file)
index 0000000..103c2ff
--- /dev/null
@@ -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 <http://www.gnu.org/licenses/>.
+***/
+
+#include <dbus/dbus.h>
+
+#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.c b/src/dbus-loop.c
new file mode 100644 (file)
index 0000000..8eb1d17
--- /dev/null
@@ -0,0 +1,263 @@
+/*-*- 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 <http://www.gnu.org/licenses/>.
+***/
+
+#include <stdbool.h>
+#include <assert.h>
+#include <sys/epoll.h>
+#include <string.h>
+#include <errno.h>
+#include <sys/timerfd.h>
+#include <unistd.h>
+
+#include "dbus-loop.h"
+#include "dbus-common.h"
+#include "util.h"
+
+/* Minimal implementation of the dbus loop which integrates all dbus
+ * events into a single epoll fd which we can triviall integrate with
+ * other loops. Note that this is not used in the main systemd daemon
+ * since we run a more elaborate mainloop there. */
+
+typedef struct EpollData {
+        int fd;
+        void *object;
+        bool is_timeout:1;
+        bool fd_is_dupped:1;
+} EpollData;
+
+static dbus_bool_t add_watch(DBusWatch *watch, void *data) {
+        EpollData *e;
+        struct epoll_event ev;
+
+        assert(watch);
+
+        e = new0(EpollData, 1);
+        if (!e)
+                return FALSE;
+
+        e->fd = dbus_watch_get_unix_fd(watch);
+        e->object = watch;
+        e->is_timeout = false;
+
+        zero(ev);
+        ev.events = bus_flags_to_events(watch);
+        ev.data.ptr = e;
+
+        if (epoll_ctl(PTR_TO_INT(data), EPOLL_CTL_ADD, e->fd, &ev) < 0) {
+
+                if (errno != EEXIST) {
+                        free(e);
+                        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(). */
+
+                e->fd = dup(e->fd);
+                if (e->fd < 0) {
+                        free(e);
+                        return FALSE;
+                }
+
+                if (epoll_ctl(PTR_TO_INT(data), EPOLL_CTL_ADD, e->fd, &ev) < 0) {
+                        close_nointr_nofail(e->fd);
+                        free(e);
+                        return FALSE;
+                }
+
+                e->fd_is_dupped = true;
+        }
+
+        dbus_watch_set_data(watch, e, NULL);
+
+        return TRUE;
+}
+
+static void remove_watch(DBusWatch *watch, void *data) {
+        EpollData *e;
+
+        assert(watch);
+
+        e = dbus_watch_get_data(watch);
+        if (!e)
+                return;
+
+        assert_se(epoll_ctl(PTR_TO_INT(data), EPOLL_CTL_DEL, e->fd, NULL) >= 0);
+
+        if (e->fd_is_dupped)
+                close_nointr_nofail(e->fd);
+
+        free(e);
+}
+
+static void toggle_watch(DBusWatch *watch, void *data) {
+        EpollData *e;
+        struct epoll_event ev;
+
+        assert(watch);
+
+        e = dbus_watch_get_data(watch);
+        if (!e)
+                return;
+
+        zero(ev);
+        ev.events = bus_flags_to_events(watch);
+        ev.data.ptr = e;
+
+        assert_se(epoll_ctl(PTR_TO_INT(data), EPOLL_CTL_MOD, e->fd, &ev) == 0);
+}
+
+static int timeout_arm(EpollData *e) {
+        struct itimerspec its;
+
+        assert(e);
+        assert(e->is_timeout);
+
+        zero(its);
+
+        if (dbus_timeout_get_enabled(e->object)) {
+                timespec_store(&its.it_value, dbus_timeout_get_interval(e->object) * USEC_PER_MSEC);
+                its.it_interval = its.it_value;
+        }
+
+        if (timerfd_settime(e->fd, 0, &its, NULL) < 0)
+                return -errno;
+
+        return 0;
+}
+
+static dbus_bool_t add_timeout(DBusTimeout *timeout, void *data) {
+        EpollData *e;
+        struct epoll_event ev;
+
+        assert(timeout);
+
+        e = new0(EpollData, 1);
+        if (!e)
+                return FALSE;
+
+        e->fd = timerfd_create(CLOCK_MONOTONIC, TFD_NONBLOCK|TFD_CLOEXEC);
+        if (e->fd < 0)
+                goto fail;
+
+        e->object = timeout;
+        e->is_timeout = true;
+
+        if (timeout_arm(e) < 0)
+                goto fail;
+
+        zero(ev);
+        ev.events = EPOLLIN;
+        ev.data.ptr = e;
+
+        if (epoll_ctl(PTR_TO_INT(data), EPOLL_CTL_ADD, e->fd, &ev) < 0)
+                goto fail;
+
+        dbus_timeout_set_data(timeout, e, NULL);
+
+        return TRUE;
+
+fail:
+        if (e->fd >= 0)
+                close_nointr_nofail(e->fd);
+
+        free(e);
+        return FALSE;
+}
+
+static void remove_timeout(DBusTimeout *timeout, void *data) {
+        EpollData *e;
+
+        assert(timeout);
+
+        e = dbus_timeout_get_data(timeout);
+        if (!e)
+                return;
+
+        assert_se(epoll_ctl(PTR_TO_INT(data), EPOLL_CTL_DEL, e->fd, NULL) >= 0);
+        close_nointr_nofail(e->fd);
+        free(e);
+}
+
+static void toggle_timeout(DBusTimeout *timeout, void *data) {
+        EpollData *e;
+        int r;
+
+        assert(timeout);
+
+        e = dbus_timeout_get_data(timeout);
+        if (!e)
+                return;
+
+        r = timeout_arm(e);
+        if (r < 0)
+                log_error("Failed to rearm timer: %s", strerror(-r));
+}
+
+int bus_loop_open(DBusConnection *c) {
+        int fd;
+
+        assert(c);
+
+        fd = epoll_create1(EPOLL_CLOEXEC);
+        if (fd < 0)
+                return -errno;
+
+        if (!dbus_connection_set_watch_functions(c, add_watch, remove_watch, toggle_watch, INT_TO_PTR(fd), NULL) ||
+            !dbus_connection_set_timeout_functions(c, add_timeout, remove_timeout, toggle_timeout, INT_TO_PTR(fd), NULL)) {
+                close_nointr_nofail(fd);
+                return -ENOMEM;
+        }
+
+        return fd;
+}
+
+int bus_loop_dispatch(int fd) {
+        int n;
+        struct epoll_event event;
+        EpollData *d;
+
+        assert(fd >= 0);
+
+        zero(event);
+
+        n = epoll_wait(fd, &event, 1, 0);
+        if (n < 0)
+                return errno == EAGAIN || errno == EINTR ? 0 : -errno;
+
+        assert_se(d = event.data.ptr);
+
+        if (d->is_timeout) {
+                DBusTimeout *t = d->object;
+
+                if (dbus_timeout_get_enabled(t))
+                        dbus_timeout_handle(t);
+        } else {
+                DBusWatch *w = d->object;
+
+                if (dbus_watch_get_enabled(w))
+                        dbus_watch_handle(w, bus_events_to_flags(event.events));
+        }
+
+        return 0;
+}
diff --git a/src/dbus-loop.h b/src/dbus-loop.h
new file mode 100644 (file)
index 0000000..0bbdfe5
--- /dev/null
@@ -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 <http://www.gnu.org/licenses/>.
+***/
+
+#include <dbus/dbus.h>
+
+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
new file mode 100644 (file)
index 0000000..6d272cb
--- /dev/null
@@ -0,0 +1,1537 @@
+/*-*- 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 <http://www.gnu.org/licenses/>.
+***/
+
+#include <errno.h>
+#include <unistd.h>
+
+#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                                     \
+        " <interface name=\"org.freedesktop.systemd1.Manager\">\n"
+
+#define BUS_MANAGER_INTERFACE_METHODS                                   \
+        "  <method name=\"GetUnit\">\n"                                 \
+        "   <arg name=\"name\" type=\"s\" direction=\"in\"/>\n"         \
+        "   <arg name=\"unit\" type=\"o\" direction=\"out\"/>\n"        \
+        "  </method>\n"                                                 \
+        "  <method name=\"GetUnitByPID\">\n"                            \
+        "   <arg name=\"pid\" type=\"u\" direction=\"in\"/>\n"          \
+        "   <arg name=\"unit\" type=\"o\" direction=\"out\"/>\n"        \
+        "  </method>\n"                                                 \
+        "  <method name=\"LoadUnit\">\n"                                \
+        "   <arg name=\"name\" type=\"s\" direction=\"in\"/>\n"         \
+        "   <arg name=\"unit\" type=\"o\" direction=\"out\"/>\n"        \
+        "  </method>\n"                                                 \
+        "  <method name=\"StartUnit\">\n"                               \
+        "   <arg name=\"name\" type=\"s\" direction=\"in\"/>\n"         \
+        "   <arg name=\"mode\" type=\"s\" direction=\"in\"/>\n"         \
+        "   <arg name=\"job\" type=\"o\" direction=\"out\"/>\n"         \
+        "  </method>\n"                                                 \
+        "  <method name=\"StartUnitReplace\">\n"                        \
+        "   <arg name=\"old_unit\" type=\"s\" direction=\"in\"/>\n"     \
+        "   <arg name=\"new_unit\" type=\"s\" direction=\"in\"/>\n"     \
+        "   <arg name=\"mode\" type=\"s\" direction=\"in\"/>\n"         \
+        "   <arg name=\"job\" type=\"o\" direction=\"out\"/>\n"         \
+        "  </method>\n"                                                 \
+        "  <method name=\"StopUnit\">\n"                                \
+        "   <arg name=\"name\" type=\"s\" direction=\"in\"/>\n"         \
+        "   <arg name=\"mode\" type=\"s\" direction=\"in\"/>\n"         \
+        "   <arg name=\"job\" type=\"o\" direction=\"out\"/>\n"         \
+        "  </method>\n"                                                 \
+        "  <method name=\"ReloadUnit\">\n"                              \
+        "   <arg name=\"name\" type=\"s\" direction=\"in\"/>\n"         \
+        "   <arg name=\"mode\" type=\"s\" direction=\"in\"/>\n"         \
+        "   <arg name=\"job\" type=\"o\" direction=\"out\"/>\n"         \
+        "  </method>\n"                                                 \
+        "  <method name=\"RestartUnit\">\n"                             \
+        "   <arg name=\"name\" type=\"s\" direction=\"in\"/>\n"         \
+        "   <arg name=\"mode\" type=\"s\" direction=\"in\"/>\n"         \
+        "   <arg name=\"job\" type=\"o\" direction=\"out\"/>\n"         \
+        "  </method>\n"                                                 \
+        "  <method name=\"TryRestartUnit\">\n"                          \
+        "   <arg name=\"name\" type=\"s\" direction=\"in\"/>\n"         \
+        "   <arg name=\"mode\" type=\"s\" direction=\"in\"/>\n"         \
+        "   <arg name=\"job\" type=\"o\" direction=\"out\"/>\n"         \
+        "  </method>\n"                                                 \
+        "  <method name=\"ReloadOrRestartUnit\">\n"                     \
+        "   <arg name=\"name\" type=\"s\" direction=\"in\"/>\n"         \
+        "   <arg name=\"mode\" type=\"s\" direction=\"in\"/>\n"         \
+        "   <arg name=\"job\" type=\"o\" direction=\"out\"/>\n"         \
+        "  </method>\n"                                                 \
+        "  <method name=\"ReloadOrTryRestartUnit\">\n"                  \
+        "   <arg name=\"name\" type=\"s\" direction=\"in\"/>\n"         \
+        "   <arg name=\"mode\" type=\"s\" direction=\"in\"/>\n"         \
+        "   <arg name=\"job\" type=\"o\" direction=\"out\"/>\n"         \
+        "  </method>\n"                                                 \
+        "  <method name=\"KillUnit\">\n"                                \
+        "   <arg name=\"name\" type=\"s\" direction=\"in\"/>\n"         \
+        "   <arg name=\"who\" type=\"s\" direction=\"in\"/>\n"          \
+        "   <arg name=\"mode\" type=\"s\" direction=\"in\"/>\n"         \
+        "   <arg name=\"signal\" type=\"i\" direction=\"in\"/>\n"       \
+        "  </method>\n"                                                 \
+        "  <method name=\"ResetFailedUnit\">\n"                         \
+        "   <arg name=\"name\" type=\"s\" direction=\"in\"/>\n"         \
+        "  </method>\n"                                                 \
+        "  <method name=\"GetJob\">\n"                                  \
+        "   <arg name=\"id\" type=\"u\" direction=\"in\"/>\n"           \
+        "   <arg name=\"job\" type=\"o\" direction=\"out\"/>\n"         \
+        "  </method>\n"                                                 \
+        "  <method name=\"ClearJobs\"/>\n"                              \
+        "  <method name=\"ResetFailed\"/>\n"                            \
+        "  <method name=\"ListUnits\">\n"                               \
+        "   <arg name=\"units\" type=\"a(ssssssouso)\" direction=\"out\"/>\n" \
+        "  </method>\n"                                                 \
+        "  <method name=\"ListJobs\">\n"                                \
+        "   <arg name=\"jobs\" type=\"a(usssoo)\" direction=\"out\"/>\n" \
+        "  </method>\n"                                                 \
+        "  <method name=\"Subscribe\"/>\n"                              \
+        "  <method name=\"Unsubscribe\"/>\n"                            \
+        "  <method name=\"Dump\"/>\n"                                   \
+        "  <method name=\"CreateSnapshot\">\n"                          \
+        "   <arg name=\"name\" type=\"s\" direction=\"in\"/>\n"         \
+        "   <arg name=\"cleanup\" type=\"b\" direction=\"in\"/>\n"      \
+        "   <arg name=\"unit\" type=\"o\" direction=\"out\"/>\n"        \
+        "  </method>\n"                                                 \
+        "  <method name=\"Reload\"/>\n"                                 \
+        "  <method name=\"Reexecute\"/>\n"                              \
+        "  <method name=\"Exit\"/>\n"                                   \
+        "  <method name=\"Reboot\"/>\n"                                 \
+        "  <method name=\"PowerOff\"/>\n"                               \
+        "  <method name=\"Halt\"/>\n"                                   \
+        "  <method name=\"KExec\"/>\n"                                  \
+        "  <method name=\"SetEnvironment\">\n"                          \
+        "   <arg name=\"names\" type=\"as\" direction=\"in\"/>\n"       \
+        "  </method>\n"                                                 \
+        "  <method name=\"UnsetEnvironment\">\n"                        \
+        "   <arg name=\"names\" type=\"as\" direction=\"in\"/>\n"       \
+        "  </method>\n"                                                 \
+        "  <method name=\"UnsetAndSetEnvironment\">\n"                  \
+        "   <arg name=\"unset\" type=\"as\" direction=\"in\"/>\n"       \
+        "   <arg name=\"set\" type=\"as\" direction=\"in\"/>\n"         \
+        "  </method>\n"                                                 \
+        "  <method name=\"ListUnitFiles\">\n"                            \
+        "   <arg name=\"changes\" type=\"a(ss)\" direction=\"out\"/>\n" \
+        "  </method>\n"                                                 \
+        "  <method name=\"GetUnitFileState\">\n"                        \
+        "   <arg name=\"file\" type=\"s\" direction=\"in\"/>\n"         \
+        "   <arg name=\"state\" type=\"s\" direction=\"out\"/>\n"       \
+        "  </method>\n"                                                 \
+        "  <method name=\"EnableUnitFiles\">\n"                         \
+        "   <arg name=\"files\" type=\"as\" direction=\"in\"/>\n"       \
+        "   <arg name=\"runtime\" type=\"b\" direction=\"in\"/>\n"      \
+        "   <arg name=\"force\" type=\"b\" direction=\"in\"/>\n"        \
+        "   <arg name=\"carries_install_info\" type=\"b\" direction=\"out\"/>\n" \
+        "   <arg name=\"changes\" type=\"a(sss)\" direction=\"out\"/>\n" \
+        "  </method>\n"                                                 \
+        "  <method name=\"DisableUnitFiles\">\n"                        \
+        "   <arg name=\"files\" type=\"as\" direction=\"in\"/>\n"       \
+        "   <arg name=\"runtime\" type=\"b\" direction=\"in\"/>\n"      \
+        "   <arg name=\"changes\" type=\"a(sss)\" direction=\"out\"/>\n" \
+        "  </method>\n"                                                 \
+        "  <method name=\"ReenableUnitFiles\">\n"                       \
+        "   <arg name=\"files\" type=\"as\" direction=\"in\"/>\n"       \
+        "   <arg name=\"runtime\" type=\"b\" direction=\"in\"/>\n"      \
+        "   <arg name=\"force\" type=\"b\" direction=\"in\"/>\n"        \
+        "   <arg name=\"carries_install_info\" type=\"b\" direction=\"out\"/>\n" \
+        "   <arg name=\"changes\" type=\"a(sss)\" direction=\"out\"/>\n" \
+        "  </method>\n"                                                 \
+        "  <method name=\"LinkUnitFiles\">\n"                           \
+        "   <arg name=\"files\" type=\"as\" direction=\"in\"/>\n"       \
+        "   <arg name=\"runtime\" type=\"b\" direction=\"in\"/>\n"      \
+        "   <arg name=\"force\" type=\"b\" direction=\"in\"/>\n"        \
+        "   <arg name=\"changes\" type=\"a(sss)\" direction=\"out\"/>\n" \
+        "  </method>\n"                                                 \
+        "  <method name=\"PresetUnitFiles\">\n"                         \
+        "   <arg name=\"files\" type=\"as\" direction=\"in\"/>\n"       \
+        "   <arg name=\"runtime\" type=\"b\" direction=\"in\"/>\n"      \
+        "   <arg name=\"force\" type=\"b\" direction=\"in\"/>\n"        \
+        "   <arg name=\"carries_install_info\" type=\"b\" direction=\"out\"/>\n" \
+        "   <arg name=\"changes\" type=\"a(sss)\" direction=\"out\"/>\n" \
+        "  </method>\n"                                                 \
+        "  <method name=\"MaskUnitFiles\">\n"                           \
+        "   <arg name=\"files\" type=\"as\" direction=\"in\"/>\n"       \
+        "   <arg name=\"runtime\" type=\"b\" direction=\"in\"/>\n"      \
+        "   <arg name=\"force\" type=\"b\" direction=\"in\"/>\n"        \
+        "   <arg name=\"changes\" type=\"a(sss)\" direction=\"out\"/>\n" \
+        "  </method>\n"                                                 \
+        "  <method name=\"UnmaskUnitFiles\">\n"                         \
+        "   <arg name=\"files\" type=\"as\" direction=\"in\"/>\n"       \
+        "   <arg name=\"runtime\" type=\"b\" direction=\"in\"/>\n"      \
+        "   <arg name=\"changes\" type=\"a(sss)\" direction=\"out\"/>\n" \
+        "  </method>\n"
+
+#define BUS_MANAGER_INTERFACE_SIGNALS                                   \
+        "  <signal name=\"UnitNew\">\n"                                 \
+        "   <arg name=\"id\" type=\"s\"/>\n"                            \
+        "   <arg name=\"unit\" type=\"o\"/>\n"                          \
+        "  </signal>\n"                                                 \
+        "  <signal name=\"UnitRemoved\">\n"                             \
+        "   <arg name=\"id\" type=\"s\"/>\n"                            \
+        "   <arg name=\"unit\" type=\"o\"/>\n"                          \
+        "  </signal>\n"                                                 \
+        "  <signal name=\"JobNew\">\n"                                  \
+        "   <arg name=\"id\" type=\"u\"/>\n"                            \
+        "   <arg name=\"job\" type=\"o\"/>\n"                           \
+        "  </signal>\n"                                                 \
+        "  <signal name=\"JobRemoved\">\n"                              \
+        "   <arg name=\"id\" type=\"u\"/>\n"                            \
+        "   <arg name=\"job\" type=\"o\"/>\n"                           \
+        "   <arg name=\"result\" type=\"s\"/>\n"                        \
+        "  </signal>"                                                   \
+        "  <signal name=\"StartupFinished\">\n"                         \
+        "   <arg name=\"kernel\" type=\"t\"/>\n"                        \
+        "   <arg name=\"initrd\" type=\"t\"/>\n"                        \
+        "   <arg name=\"userspace\" type=\"t\"/>\n"                     \
+        "   <arg name=\"total\" type=\"t\"/>\n"                         \
+        "  </signal>"                                                   \
+        "  <signal name=\"UnitFilesChanged\"/>\n"
+
+#define BUS_MANAGER_INTERFACE_PROPERTIES_GENERAL                        \
+        "  <property name=\"Version\" type=\"s\" access=\"read\"/>\n"   \
+        "  <property name=\"Distribution\" type=\"s\" access=\"read\"/>\n" \
+        "  <property name=\"Features\" type=\"s\" access=\"read\"/>\n"  \
+        "  <property name=\"Tainted\" type=\"s\" access=\"read\"/>\n"   \
+        "  <property name=\"RunningAs\" type=\"s\" access=\"read\"/>\n" \
+        "  <property name=\"InitRDTimestamp\" type=\"t\" access=\"read\"/>\n" \
+        "  <property name=\"InitRDTimestampMonotonic\" type=\"t\" access=\"read\"/>\n" \
+        "  <property name=\"StartupTimestamp\" type=\"t\" access=\"read\"/>\n" \
+        "  <property name=\"StartupTimestampMonotonic\" type=\"t\" access=\"read\"/>\n" \
+        "  <property name=\"FinishTimestamp\" type=\"t\" access=\"read\"/>\n" \
+        "  <property name=\"FinishTimestampMonotonic\" type=\"t\" access=\"read\"/>\n" \
+        "  <property name=\"LogLevel\" type=\"s\" access=\"readwrite\"/>\n"  \
+        "  <property name=\"LogTarget\" type=\"s\" access=\"readwrite\"/>\n" \
+        "  <property name=\"NNames\" type=\"u\" access=\"read\"/>\n"    \
+        "  <property name=\"NJobs\" type=\"u\" access=\"read\"/>\n"     \
+        "  <property name=\"NInstalledJobs\" type=\"u\" access=\"read\"/>\n" \
+        "  <property name=\"NFailedJobs\" type=\"u\" access=\"read\"/>\n" \
+        "  <property name=\"Progress\" type=\"d\" access=\"read\"/>\n"  \
+        "  <property name=\"Environment\" type=\"as\" access=\"read\"/>\n" \
+        "  <property name=\"ConfirmSpawn\" type=\"b\" access=\"read\"/>\n" \
+        "  <property name=\"ShowStatus\" type=\"b\" access=\"read\"/>\n" \
+        "  <property name=\"UnitPath\" type=\"as\" access=\"read\"/>\n" \
+        "  <property name=\"NotifySocket\" type=\"s\" access=\"read\"/>\n" \
+        "  <property name=\"ControlGroupHierarchy\" type=\"s\" access=\"read\"/>\n" \
+        "  <property name=\"MountAuto\" type=\"b\" access=\"read\"/>\n" \
+        "  <property name=\"SwapAuto\" type=\"b\" access=\"read\"/>\n"  \
+        "  <property name=\"DefaultControllers\" type=\"as\" access=\"read\"/>\n" \
+        "  <property name=\"DefaultStandardOutput\" type=\"s\" access=\"read\"/>\n" \
+        "  <property name=\"DefaultStandardError\" type=\"s\" access=\"read\"/>\n"
+
+#ifdef HAVE_SYSV_COMPAT
+#define BUS_MANAGER_INTERFACE_PROPERTIES_SYSV                           \
+        "  <property name=\"SysVConsole\" type=\"b\" access=\"read\"/>\n" \
+        "  <property name=\"SysVInitPath\" type=\"as\" access=\"read\"/>\n" \
+        "  <property name=\"SysVRcndPath\" type=\"as\" access=\"read\"/>\n"
+#else
+#define BUS_MANAGER_INTERFACE_PROPERTIES_SYSV
+#endif
+
+#define BUS_MANAGER_INTERFACE_END                                       \
+        " </interface>\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                       \
+        "<node>\n"                                                      \
+        BUS_MANAGER_INTERFACE                                           \
+        BUS_PROPERTIES_INTERFACE                                        \
+        BUS_PEER_INTERFACE                                              \
+        BUS_INTROSPECTABLE_INTERFACE
+
+#define INTROSPECTION_END                                               \
+        "</node>\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) {
+        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) {
+        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)           },
+#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, "<node name=\"unit/%s\"/>", p);
+                        free(p);
+                }
+
+                HASHMAP_FOREACH(j, m->jobs, i)
+                        fprintf(f, "<node name=\"job/%lu\"/>", (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
new file mode 100644 (file)
index 0000000..2eb2fbb
--- /dev/null
@@ -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 <http://www.gnu.org/licenses/>.
+***/
+
+#include <dbus/dbus.h>
+
+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
new file mode 100644 (file)
index 0000000..35d6ea7
--- /dev/null
@@ -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 <http://www.gnu.org/licenses/>.
+***/
+
+#include <errno.h>
+
+#include "dbus-unit.h"
+#include "dbus-mount.h"
+#include "dbus-execute.h"
+#include "dbus-common.h"
+
+#define BUS_MOUNT_INTERFACE                                             \
+        " <interface name=\"org.freedesktop.systemd1.Mount\">\n"        \
+        "  <property name=\"Where\" type=\"s\" access=\"read\"/>\n"     \
+        "  <property name=\"What\" type=\"s\" access=\"read\"/>\n"      \
+        "  <property name=\"Options\" type=\"s\" access=\"read\"/>\n"   \
+        "  <property name=\"Type\" type=\"s\" access=\"read\"/>\n"      \
+        "  <property name=\"TimeoutUSec\" type=\"t\" access=\"read\"/>\n" \
+        BUS_EXEC_COMMAND_INTERFACE("ExecMount")                         \
+        BUS_EXEC_COMMAND_INTERFACE("ExecUnmount")                       \
+        BUS_EXEC_COMMAND_INTERFACE("ExecRemount")                       \
+        BUS_EXEC_CONTEXT_INTERFACE                                      \
+        "  <property name=\"ControlPID\" type=\"u\" access=\"read\"/>\n" \
+        "  <property name=\"DirectoryMode\" type=\"u\" access=\"read\"/>\n" \
+        "  <property name=\"Result\" type=\"s\" access=\"read\"/>\n"    \
+        " </interface>\n"
+
+#define INTROSPECTION                                                   \
+        DBUS_INTROSPECT_1_0_XML_DOCTYPE_DECL_NODE                       \
+        "<node>\n"                                                      \
+        BUS_UNIT_INTERFACE                                              \
+        BUS_MOUNT_INTERFACE                                             \
+        BUS_PROPERTIES_INTERFACE                                        \
+        BUS_PEER_INTERFACE                                              \
+        BUS_INTROSPECTABLE_INTERFACE                                    \
+        "</node>\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
new file mode 100644 (file)
index 0000000..b5613fa
--- /dev/null
@@ -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 <http://www.gnu.org/licenses/>.
+***/
+
+#include <dbus/dbus.h>
+
+#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
new file mode 100644 (file)
index 0000000..5506784
--- /dev/null
@@ -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 <http://www.gnu.org/licenses/>.
+***/
+
+#include <errno.h>
+
+#include "dbus-unit.h"
+#include "dbus-path.h"
+#include "dbus-execute.h"
+#include "dbus-common.h"
+
+#define BUS_PATH_INTERFACE                                              \
+        " <interface name=\"org.freedesktop.systemd1.Path\">\n"         \
+        "  <property name=\"Unit\" type=\"s\" access=\"read\"/>\n"      \
+        "  <property name=\"Paths\" type=\"a(ss)\" access=\"read\"/>\n" \
+        "  <property name=\"MakeDirectory\" type=\"b\" access=\"read\"/>\n" \
+        "  <property name=\"DirectoryMode\" type=\"u\" access=\"read\"/>\n" \
+        "  <property name=\"Result\" type=\"s\" access=\"read\"/>\n"    \
+        " </interface>\n"
+
+#define INTROSPECTION                                                   \
+        DBUS_INTROSPECT_1_0_XML_DOCTYPE_DECL_NODE                       \
+        "<node>\n"                                                      \
+        BUS_UNIT_INTERFACE                                              \
+        BUS_PATH_INTERFACE                                              \
+        BUS_PROPERTIES_INTERFACE                                        \
+        BUS_PEER_INTERFACE                                              \
+        BUS_INTROSPECTABLE_INTERFACE                                    \
+        "</node>\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
new file mode 100644 (file)
index 0000000..2888400
--- /dev/null
@@ -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 <http://www.gnu.org/licenses/>.
+***/
+
+#include <dbus/dbus.h>
+
+#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
new file mode 100644 (file)
index 0000000..7809164
--- /dev/null
@@ -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 <http://www.gnu.org/licenses/>.
+***/
+
+#include <errno.h>
+
+#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                            \
+        "  <property name=\"SysVStartPriority\" type=\"i\" access=\"read\"/>\n" \
+        "  <property name=\"SysVRunLevels\" type=\"s\" access=\"read\"/>\n" \
+        "  <property name=\"SysVPath\" type=\"s\" access=\"read\"/>\n"
+#else
+#define BUS_SERVICE_SYSV_INTERFACE_FRAGMENT ""
+#endif
+
+#define BUS_SERVICE_INTERFACE                                           \
+        " <interface name=\"org.freedesktop.systemd1.Service\">\n"      \
+        "  <property name=\"Type\" type=\"s\" access=\"read\"/>\n"      \
+        "  <property name=\"Restart\" type=\"s\" access=\"read\"/>\n"   \
+        "  <property name=\"PIDFile\" type=\"s\" access=\"read\"/>\n"   \
+        "  <property name=\"NotifyAccess\" type=\"s\" access=\"read\"/>\n" \
+        "  <property name=\"RestartUSec\" type=\"t\" access=\"read\"/>\n" \
+        "  <property name=\"TimeoutUSec\" type=\"t\" access=\"read\"/>\n" \
+        "  <property name=\"WatchdogUSec\" type=\"t\" access=\"read\"/>\n" \
+        "  <property name=\"WatchdogTimestamp\" type=\"t\" access=\"read\"/>\n" \
+        "  <property name=\"WatchdogTimestampMonotonic\" type=\"t\" access=\"read\"/>\n" \
+        "  <property name=\"StartLimitInterval\" type=\"t\" access=\"read\"/>\n" \
+        "  <property name=\"StartLimitBurst\" type=\"u\" access=\"read\"/>\n" \
+        "  <property name=\"StartLimitAction\" type=\"s\" access=\"read\"/>\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                                      \
+        "  <property name=\"PermissionsStartOnly\" type=\"b\" access=\"read\"/>\n" \
+        "  <property name=\"RootDirectoryStartOnly\" type=\"b\" access=\"read\"/>\n" \
+        "  <property name=\"RemainAfterExit\" type=\"b\" access=\"read\"/>\n" \
+        BUS_EXEC_STATUS_INTERFACE("ExecMain")                           \
+        "  <property name=\"MainPID\" type=\"u\" access=\"read\"/>\n"   \
+        "  <property name=\"ControlPID\" type=\"u\" access=\"read\"/>\n" \
+        "  <property name=\"BusName\" type=\"s\" access=\"read\"/>\n"   \
+        "  <property name=\"StatusText\" type=\"s\" access=\"read\"/>\n" \
+        "  <property name=\"FsckPassNo\" type=\"i\" access=\"read\"/>\n" \
+        "  <property name=\"Result\" type=\"s\" access=\"read\"/>\n"    \
+        BUS_SERVICE_SYSV_INTERFACE_FRAGMENT                             \
+       " </interface>\n"
+
+#define INTROSPECTION                                                   \
+        DBUS_INTROSPECT_1_0_XML_DOCTYPE_DECL_NODE                       \
+        "<node>\n"                                                      \
+        BUS_UNIT_INTERFACE                                              \
+        BUS_SERVICE_INTERFACE                                           \
+        BUS_PROPERTIES_INTERFACE                                        \
+        BUS_PEER_INTERFACE                                              \
+        BUS_INTROSPECTABLE_INTERFACE                                    \
+        "</node>\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 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)       },
+        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
new file mode 100644 (file)
index 0000000..d6eab65
--- /dev/null
@@ -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 <http://www.gnu.org/licenses/>.
+***/
+
+#include <dbus/dbus.h>
+
+#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
new file mode 100644 (file)
index 0000000..e69388a
--- /dev/null
@@ -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 <http://www.gnu.org/licenses/>.
+***/
+
+#include "dbus-unit.h"
+#include "dbus-snapshot.h"
+#include "dbus-common.h"
+
+#define BUS_SNAPSHOT_INTERFACE                                          \
+        " <interface name=\"org.freedesktop.systemd1.Snapshot\">\n"     \
+        "  <method name=\"Remove\"/>\n"                                 \
+        "  <property name=\"Cleanup\" type=\"b\" access=\"read\"/>\n"   \
+        " </interface>\n"
+
+#define INTROSPECTION                                                   \
+        DBUS_INTROSPECT_1_0_XML_DOCTYPE_DECL_NODE                       \
+        "<node>\n"                                                      \
+        BUS_UNIT_INTERFACE                                              \
+        BUS_SNAPSHOT_INTERFACE                                          \
+        BUS_PROPERTIES_INTERFACE                                        \
+        BUS_PEER_INTERFACE                                              \
+        BUS_INTROSPECTABLE_INTERFACE                                    \
+        "</node>\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
new file mode 100644 (file)
index 0000000..0b82279
--- /dev/null
@@ -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 <http://www.gnu.org/licenses/>.
+***/
+
+#include <dbus/dbus.h>
+
+#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
new file mode 100644 (file)
index 0000000..2e3342c
--- /dev/null
@@ -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 <http://www.gnu.org/licenses/>.
+***/
+
+#include <errno.h>
+
+#include "dbus-unit.h"
+#include "dbus-socket.h"
+#include "dbus-execute.h"
+#include "dbus-common.h"
+
+#define BUS_SOCKET_INTERFACE                                            \
+        " <interface name=\"org.freedesktop.systemd1.Socket\">\n"       \
+        "  <property name=\"BindIPv6Only\" type=\"b\" access=\"read\"/>\n" \
+        "  <property name=\"Backlog\" type=\"u\" access=\"read\"/>\n"   \
+        "  <property name=\"TimeoutUSec\" type=\"t\" access=\"read\"/>\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                                      \
+        "  <property name=\"ControlPID\" type=\"u\" access=\"read\"/>\n" \
+        "  <property name=\"BindToDevice\" type=\"s\" access=\"read\"/>\n" \
+        "  <property name=\"DirectoryMode\" type=\"u\" access=\"read\"/>\n" \
+        "  <property name=\"SocketMode\" type=\"u\" access=\"read\"/>\n" \
+        "  <property name=\"Accept\" type=\"b\" access=\"read\"/>\n"    \
+        "  <property name=\"KeepAlive\" type=\"b\" access=\"read\"/>\n" \
+        "  <property name=\"Priority\" type=\"i\" access=\"read\"/>\n"  \
+        "  <property name=\"ReceiveBuffer\" type=\"t\" access=\"read\"/>\n" \
+        "  <property name=\"SendBuffer\" type=\"t\" access=\"read\"/>\n" \
+        "  <property name=\"IPTOS\" type=\"i\" access=\"read\"/>\n"     \
+        "  <property name=\"IPTTL\" type=\"i\" access=\"read\"/>\n"     \
+        "  <property name=\"PipeSize\" type=\"t\" access=\"read\"/>\n"  \
+        "  <property name=\"FreeBind\" type=\"b\" access=\"read\"/>\n"  \
+        "  <property name=\"Transparent\" type=\"b\" access=\"read\"/>\n" \
+        "  <property name=\"Broadcast\" type=\"b\" access=\"read\"/>\n" \
+        "  <property name=\"PassCredentials\" type=\"b\" access=\"read\"/>\n" \
+        "  <property name=\"PassSecurity\" type=\"b\" access=\"read\"/>\n" \
+        "  <property name=\"Mark\" type=\"i\" access=\"read\"/>\n"      \
+        "  <property name=\"MaxConnections\" type=\"u\" access=\"read\"/>\n" \
+        "  <property name=\"NAccepted\" type=\"u\" access=\"read\"/>\n" \
+        "  <property name=\"NConnections\" type=\"u\" access=\"read\"/>\n" \
+        "  <property name=\"MessageQueueMaxMessages\" type=\"x\" access=\"read\"/>\n" \
+        "  <property name=\"MessageQueueMessageSize\" type=\"x\" access=\"read\"/>\n" \
+        "  <property name=\"Result\" type=\"s\" access=\"read\"/>\n"    \
+        " </interface>\n"                                               \
+
+#define INTROSPECTION                                                   \
+        DBUS_INTROSPECT_1_0_XML_DOCTYPE_DECL_NODE                       \
+        "<node>\n"                                                      \
+        BUS_UNIT_INTERFACE                                              \
+        BUS_SOCKET_INTERFACE                                            \
+        BUS_PROPERTIES_INTERFACE                                        \
+        BUS_PEER_INTERFACE                                              \
+        BUS_INTROSPECTABLE_INTERFACE                                    \
+        "</node>\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
new file mode 100644 (file)
index 0000000..069a2f5
--- /dev/null
@@ -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 <http://www.gnu.org/licenses/>.
+***/
+
+#include <dbus/dbus.h>
+
+#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
new file mode 100644 (file)
index 0000000..09cd1e8
--- /dev/null
@@ -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 <http://www.gnu.org/licenses/>.
+***/
+
+#include <errno.h>
+
+#include "dbus-unit.h"
+#include "dbus-swap.h"
+#include "dbus-execute.h"
+#include "dbus-common.h"
+
+#define BUS_SWAP_INTERFACE                                              \
+        " <interface name=\"org.freedesktop.systemd1.Swap\">\n"         \
+        "  <property name=\"What\" type=\"s\" access=\"read\"/>\n"      \
+        "  <property name=\"Priority\" type=\"i\" access=\"read\"/>\n"  \
+        "  <property name=\"TimeoutUSec\" type=\"t\" access=\"read\"/>\n" \
+        BUS_EXEC_COMMAND_INTERFACE("ExecActivate")                      \
+        BUS_EXEC_COMMAND_INTERFACE("ExecDeactivate")                    \
+        BUS_EXEC_CONTEXT_INTERFACE                                      \
+        "  <property name=\"ControlPID\" type=\"u\" access=\"read\"/>\n" \
+        "  <property name=\"Result\" type=\"s\" access=\"read\"/>\n"    \
+        " </interface>\n"
+
+#define INTROSPECTION                                                   \
+        DBUS_INTROSPECT_1_0_XML_DOCTYPE_DECL_NODE                       \
+        "<node>\n"                                                      \
+        BUS_UNIT_INTERFACE                                              \
+        BUS_SWAP_INTERFACE                                              \
+        BUS_PROPERTIES_INTERFACE                                        \
+        BUS_PEER_INTERFACE                                              \
+        BUS_INTROSPECTABLE_INTERFACE                                    \
+        "</node>\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
new file mode 100644 (file)
index 0000000..15b9147
--- /dev/null
@@ -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 <http://www.gnu.org/licenses/>.
+***/
+
+#include <dbus/dbus.h>
+
+#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
new file mode 100644 (file)
index 0000000..55cf862
--- /dev/null
@@ -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 <http://www.gnu.org/licenses/>.
+***/
+
+#include <errno.h>
+
+#include "dbus-unit.h"
+#include "dbus-target.h"
+#include "dbus-common.h"
+
+#define BUS_TARGET_INTERFACE                                            \
+        " <interface name=\"org.freedesktop.systemd1.Target\">\n"       \
+        " </interface>\n"
+
+#define INTROSPECTION                                                   \
+        DBUS_INTROSPECT_1_0_XML_DOCTYPE_DECL_NODE                       \
+        "<node>\n"                                                      \
+        BUS_UNIT_INTERFACE                                              \
+        BUS_TARGET_INTERFACE                                            \
+        BUS_PROPERTIES_INTERFACE                                        \
+        BUS_PEER_INTERFACE                                              \
+        BUS_INTROSPECTABLE_INTERFACE                                    \
+        "</node>\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
new file mode 100644 (file)
index 0000000..13d3876
--- /dev/null
@@ -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 <http://www.gnu.org/licenses/>.
+***/
+
+#include <dbus/dbus.h>
+
+#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
new file mode 100644 (file)
index 0000000..b396aed
--- /dev/null
@@ -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 <http://www.gnu.org/licenses/>.
+***/
+
+#include <errno.h>
+
+#include "dbus-unit.h"
+#include "dbus-timer.h"
+#include "dbus-execute.h"
+#include "dbus-common.h"
+
+#define BUS_TIMER_INTERFACE                                             \
+        " <interface name=\"org.freedesktop.systemd1.Timer\">\n"        \
+        "  <property name=\"Unit\" type=\"s\" access=\"read\"/>\n"      \
+        "  <property name=\"Timers\" type=\"a(stt)\" access=\"read\"/>\n" \
+        "  <property name=\"NextElapseUSec\" type=\"t\" access=\"read\"/>\n" \
+        "  <property name=\"Result\" type=\"s\" access=\"read\"/>\n"    \
+        " </interface>\n"
+
+#define INTROSPECTION                                                   \
+        DBUS_INTROSPECT_1_0_XML_DOCTYPE_DECL_NODE                       \
+        "<node>\n"                                                      \
+        BUS_UNIT_INTERFACE                                              \
+        BUS_TIMER_INTERFACE                                             \
+        BUS_PROPERTIES_INTERFACE                                        \
+        BUS_PEER_INTERFACE                                              \
+        BUS_INTROSPECTABLE_INTERFACE                                    \
+        "</node>\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
new file mode 100644 (file)
index 0000000..e692e12
--- /dev/null
@@ -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 <http://www.gnu.org/licenses/>.
+***/
+
+#include <dbus/dbus.h>
+
+#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
new file mode 100644 (file)
index 0000000..c7532c7
--- /dev/null
@@ -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 <http://www.gnu.org/licenses/>.
+***/
+
+#include <errno.h>
+
+#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
+                              "<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, "<node name=\"%s\"/>", p);
+                                free(p);
+                        }
+
+                        fputs("</node>\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
new file mode 100644 (file)
index 0000000..4f19a80
--- /dev/null
@@ -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 <http://www.gnu.org/licenses/>.
+***/
+
+#include <dbus/dbus.h>
+
+#include "manager.h"
+#include "dbus-common.h"
+
+#define BUS_UNIT_INTERFACE \
+        " <interface name=\"org.freedesktop.systemd1.Unit\">\n"         \
+        "  <method name=\"Start\">\n"                                   \
+        "   <arg name=\"mode\" type=\"s\" direction=\"in\"/>\n"         \
+        "   <arg name=\"job\" type=\"o\" direction=\"out\"/>\n"         \
+        "  </method>\n"                                                 \
+        "  <method name=\"Stop\">\n"                                    \
+        "   <arg name=\"mode\" type=\"s\" direction=\"in\"/>\n"         \
+        "   <arg name=\"job\" type=\"o\" direction=\"out\"/>\n"         \
+        "  </method>\n"                                                 \
+        "  <method name=\"Reload\">\n"                                  \
+        "   <arg name=\"mode\" type=\"s\" direction=\"in\"/>\n"         \
+        "   <arg name=\"job\" type=\"o\" direction=\"out\"/>\n"         \
+        "  </method>\n"                                                 \
+        "  <method name=\"Restart\">\n"                                 \
+        "   <arg name=\"mode\" type=\"s\" direction=\"in\"/>\n"         \
+        "   <arg name=\"job\" type=\"o\" direction=\"out\"/>\n"         \
+        "  </method>\n"                                                 \
+        "  <method name=\"TryRestart\">\n"                              \
+        "   <arg name=\"mode\" type=\"s\" direction=\"in\"/>\n"         \
+        "   <arg name=\"job\" type=\"o\" direction=\"out\"/>\n"         \
+        "  </method>\n"                                                 \
+        "  <method name=\"ReloadOrRestart\">\n"                         \
+        "   <arg name=\"mode\" type=\"s\" direction=\"in\"/>\n"         \
+        "   <arg name=\"job\" type=\"o\" direction=\"out\"/>\n"         \
+        "  </method>\n"                                                 \
+        "  <method name=\"ReloadOrTryRestart\">\n"                      \
+        "   <arg name=\"mode\" type=\"s\" direction=\"in\"/>\n"         \
+        "   <arg name=\"job\" type=\"o\" direction=\"out\"/>\n"         \
+        "  </method>\n"                                                 \
+        "  <method name=\"Kill\">\n"                                    \
+        "   <arg name=\"who\" type=\"s\" direction=\"in\"/>\n"          \
+        "   <arg name=\"mode\" type=\"s\" direction=\"in\"/>\n"         \
+        "   <arg name=\"signal\" type=\"i\" direction=\"in\"/>\n"       \
+        "  </method>\n"                                                 \
+        "  <method name=\"ResetFailed\"/>\n"                            \
+        "  <property name=\"Id\" type=\"s\" access=\"read\"/>\n"        \
+        "  <property name=\"Names\" type=\"as\" access=\"read\"/>\n"    \
+        "  <property name=\"Following\" type=\"s\" access=\"read\"/>\n" \
+        "  <property name=\"Requires\" type=\"as\" access=\"read\"/>\n" \
+        "  <property name=\"RequiresOverridable\" type=\"as\" access=\"read\"/>\n" \
+        "  <property name=\"Requisite\" type=\"as\" access=\"read\"/>\n" \
+        "  <property name=\"RequisiteOverridable\" type=\"as\" access=\"read\"/>\n" \
+        "  <property name=\"Wants\" type=\"as\" access=\"read\"/>\n"    \
+        "  <property name=\"BindTo\" type=\"as\" access=\"read\"/>\n"    \
+        "  <property name=\"RequiredBy\" type=\"as\" access=\"read\"/>\n" \
+        "  <property name=\"RequiredByOverridable\" type=\"as\" access=\"read\"/>\n" \
+        "  <property name=\"WantedBy\" type=\"as\" access=\"read\"/>\n" \
+        "  <property name=\"BoundBy\" type=\"as\" access=\"read\"/>\n" \
+        "  <property name=\"Conflicts\" type=\"as\" access=\"read\"/>\n" \
+        "  <property name=\"ConflictedBy\" type=\"as\" access=\"read\"/>\n" \
+        "  <property name=\"Before\" type=\"as\" access=\"read\"/>\n"   \
+        "  <property name=\"After\" type=\"as\" access=\"read\"/>\n"    \
+        "  <property name=\"OnFailure\" type=\"as\" access=\"read\"/>\n"    \
+        "  <property name=\"Triggers\" type=\"as\" access=\"read\"/>\n"    \
+        "  <property name=\"TriggeredBy\" type=\"as\" access=\"read\"/>\n"    \
+        "  <property name=\"PropagateReloadTo\" type=\"as\" access=\"read\"/>\n" \
+        "  <property name=\"PropagateReloadFrom\" type=\"as\" access=\"read\"/>\n" \
+        "  <property name=\"Description\" type=\"s\" access=\"read\"/>\n" \
+        "  <property name=\"LoadState\" type=\"s\" access=\"read\"/>\n" \
+        "  <property name=\"ActiveState\" type=\"s\" access=\"read\"/>\n" \
+        "  <property name=\"SubState\" type=\"s\" access=\"read\"/>\n"  \
+        "  <property name=\"FragmentPath\" type=\"s\" access=\"read\"/>\n" \
+        "  <property name=\"UnitFileState\" type=\"s\" access=\"read\"/>\n" \
+        "  <property name=\"InactiveExitTimestamp\" type=\"t\" access=\"read\"/>\n" \
+        "  <property name=\"InactiveExitTimestampMonotonic\" type=\"t\" access=\"read\"/>\n" \
+        "  <property name=\"ActiveEnterTimestamp\" type=\"t\" access=\"read\"/>\n" \
+        "  <property name=\"ActiveEnterTimestampMonotonic\" type=\"t\" access=\"read\"/>\n" \
+        "  <property name=\"ActiveExitTimestamp\" type=\"t\" access=\"read\"/>\n" \
+        "  <property name=\"ActiveExitTimestampMonotonic\" type=\"t\" access=\"read\"/>\n" \
+        "  <property name=\"InactiveEnterTimestamp\" type=\"t\" access=\"read\"/>\n" \
+        "  <property name=\"InactiveEnterTimestampMonotonic\" type=\"t\" access=\"read\"/>\n" \
+        "  <property name=\"CanStart\" type=\"b\" access=\"read\"/>\n"  \
+        "  <property name=\"CanStop\" type=\"b\" access=\"read\"/>\n"   \
+        "  <property name=\"CanReload\" type=\"b\" access=\"read\"/>\n" \
+        "  <property name=\"CanIsolate\" type=\"b\" access=\"read\"/>\n" \
+        "  <property name=\"Job\" type=\"(uo)\" access=\"read\"/>\n"    \
+        "  <property name=\"StopWhenUnneeded\" type=\"b\" access=\"read\"/>\n" \
+        "  <property name=\"RefuseManualStart\" type=\"b\" access=\"read\"/>\n" \
+        "  <property name=\"RefuseManualStop\" type=\"b\" access=\"read\"/>\n" \
+        "  <property name=\"AllowIsolate\" type=\"b\" access=\"read\"/>\n" \
+        "  <property name=\"DefaultDependencies\" type=\"b\" access=\"read\"/>\n" \
+        "  <property name=\"OnFailureIsolate\" type=\"b\" access=\"read\"/>\n" \
+        "  <property name=\"IgnoreOnIsolate\" type=\"b\" access=\"read\"/>\n" \
+        "  <property name=\"IgnoreOnSnapshot\" type=\"b\" access=\"read\"/>\n" \
+        "  <property name=\"DefaultControlGroup\" type=\"s\" access=\"read\"/>\n" \
+        "  <property name=\"ControlGroup\" type=\"as\" access=\"read\"/>\n" \
+        "  <property name=\"ControlGroupAttributes\" type=\"a(sss)\" access=\"read\"/>\n" \
+        "  <property name=\"NeedDaemonReload\" type=\"b\" access=\"read\"/>\n" \
+        "  <property name=\"JobTimeoutUSec\" type=\"t\" access=\"read\"/>\n" \
+        "  <property name=\"ConditionTimestamp\" type=\"t\" access=\"read\"/>\n" \
+        "  <property name=\"ConditionTimestampMonotonic\" type=\"t\" access=\"read\"/>\n" \
+        "  <property name=\"ConditionResult\" type=\"b\" access=\"read\"/>\n" \
+        "  <property name=\"LoadError\" type=\"(ss)\" access=\"read\"/>\n" \
+        " </interface>\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
new file mode 100644 (file)
index 0000000..8e6e9fd
--- /dev/null
@@ -0,0 +1,1482 @@
+/*-*- 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 <http://www.gnu.org/licenses/>.
+***/
+
+#include <sys/epoll.h>
+#include <sys/timerfd.h>
+#include <errno.h>
+#include <unistd.h>
+#include <dbus/dbus.h>
+
+#include "dbus.h"
+#include "log.h"
+#include "strv.h"
+#include "cgroup.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
new file mode 100644 (file)
index 0000000..bd539d0
--- /dev/null
@@ -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 <http://www.gnu.org/licenses/>.
+***/
+
+#include <dbus/dbus.h>
+
+#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/def.h b/src/def.h
new file mode 100644 (file)
index 0000000..20aaa7c
--- /dev/null
+++ b/src/def.h
@@ -0,0 +1,37 @@
+/*-*- Mode: C; c-basic-offset: 8; indent-tabs-mode: nil -*-*/
+
+#ifndef foodefhfoo
+#define foodefhfoo
+
+/***
+  This file is part 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 <http://www.gnu.org/licenses/>.
+***/
+
+#include "util.h"
+
+#define DEFAULT_TIMEOUT_USEC (90*USEC_PER_SEC)
+#define DEFAULT_RESTART_USEC (100*USEC_PER_MSEC)
+
+#define DEFAULT_EXIT_USEC (5*USEC_PER_MINUTE)
+
+#define SYSTEMD_CGROUP_CONTROLLER "name=systemd"
+
+#define SIGNALS_CRASH_HANDLER SIGSEGV,SIGILL,SIGFPE,SIGBUS,SIGQUIT,SIGABRT
+#define SIGNALS_IGNORE SIGKILL,SIGPIPE
+
+#endif
diff --git a/src/detect-virt.c b/src/detect-virt.c
new file mode 100644 (file)
index 0000000..79cad5d
--- /dev/null
@@ -0,0 +1,48 @@
+/*-*- 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 <http://www.gnu.org/licenses/>.
+***/
+
+#include <stdlib.h>
+#include <stdbool.h>
+#include <errno.h>
+#include <string.h>
+
+#include "util.h"
+#include "virt.h"
+
+int main(int argc, char *argv[]) {
+        Virtualization r;
+        const char *id;
+
+        /* This is mostly intended to be used for scripts which want
+         * to detect whether we are being run in a virtualized
+         * environment or not */
+
+        r = detect_virtualization(&id);
+        if (r < 0) {
+                log_error("Failed to check for virtualization: %s", strerror(-r));
+                return EXIT_FAILURE;
+        }
+
+        if (r > 0)
+                puts(id);
+
+        return r > 0 ? EXIT_SUCCESS : EXIT_FAILURE;
+}
diff --git a/src/device.c b/src/device.c
new file mode 100644 (file)
index 0000000..0575379
--- /dev/null
@@ -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 <http://www.gnu.org/licenses/>.
+***/
+
+#include <errno.h>
+#include <sys/epoll.h>
+#include <libudev.h>
+
+#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
new file mode 100644 (file)
index 0000000..a05c3d3
--- /dev/null
@@ -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 <http://www.gnu.org/licenses/>.
+***/
+
+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
new file mode 100644 (file)
index 0000000..dab4856
--- /dev/null
@@ -0,0 +1,2112 @@
+/*-*- 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 <http://www.gnu.org/licenses/>.
+***/
+
+#include <assert.h>
+#include <dirent.h>
+#include <errno.h>
+#include <fcntl.h>
+#include <unistd.h>
+#include <string.h>
+#include <signal.h>
+#include <sys/socket.h>
+#include <sys/un.h>
+#include <sys/prctl.h>
+#include <linux/sched.h>
+#include <sys/types.h>
+#include <sys/stat.h>
+#include <grp.h>
+#include <pwd.h>
+#include <sys/mount.h>
+#include <linux/fs.h>
+#include <linux/oom.h>
+
+#ifdef HAVE_PAM
+#include <security/pam_appl.h>
+#endif
+
+#include "execute.h"
+#include "strv.h"
+#include "macro.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), &param) < 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
new file mode 100644 (file)
index 0000000..0d7e7dd
--- /dev/null
@@ -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 <http://www.gnu.org/licenses/>.
+***/
+
+typedef struct ExecStatus ExecStatus;
+typedef struct ExecCommand ExecCommand;
+typedef struct ExecContext ExecContext;
+
+#include <linux/types.h>
+#include <sys/time.h>
+#include <sys/resource.h>
+#include <sys/capability.h>
+#include <stdbool.h>
+#include <stdio.h>
+#include <sched.h>
+
+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/exit-status.c b/src/exit-status.c
new file mode 100644 (file)
index 0000000..ab8907d
--- /dev/null
@@ -0,0 +1,180 @@
+/*-*- 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 <http://www.gnu.org/licenses/>.
+***/
+
+#include <stdlib.h>
+#include <sys/wait.h>
+
+#include "exit-status.h"
+
+const char* exit_status_to_string(ExitStatus status, ExitStatusLevel level) {
+
+        /* We cast to int here, so that -Wenum doesn't complain that
+         * EXIT_SUCCESS/EXIT_FAILURE aren't in the enum */
+
+        switch ((int) status) {
+
+        case EXIT_SUCCESS:
+                return "SUCCESS";
+
+        case EXIT_FAILURE:
+                return "FAILURE";
+        }
+
+
+        if (level == EXIT_STATUS_SYSTEMD || level == EXIT_STATUS_LSB) {
+                switch ((int) status) {
+
+                case EXIT_CHDIR:
+                        return "CHDIR";
+
+                case EXIT_NICE:
+                        return "NICE";
+
+                case EXIT_FDS:
+                        return "FDS";
+
+                case EXIT_EXEC:
+                        return "EXEC";
+
+                case EXIT_MEMORY:
+                        return "MEMORY";
+
+                case EXIT_LIMITS:
+                        return "LIMITS";
+
+                case EXIT_OOM_ADJUST:
+                        return "OOM_ADJUST";
+
+                case EXIT_SIGNAL_MASK:
+                        return "SIGNAL_MASK";
+
+                case EXIT_STDIN:
+                        return "STDIN";
+
+                case EXIT_STDOUT:
+                        return "STDOUT";
+
+                case EXIT_CHROOT:
+                        return "CHROOT";
+
+                case EXIT_IOPRIO:
+                        return "IOPRIO";
+
+                case EXIT_TIMERSLACK:
+                        return "TIMERSLACK";
+
+                case EXIT_SECUREBITS:
+                        return "SECUREBITS";
+
+                case EXIT_SETSCHEDULER:
+                        return "SETSCHEDULER";
+
+                case EXIT_CPUAFFINITY:
+                        return "CPUAFFINITY";
+
+                case EXIT_GROUP:
+                        return "GROUP";
+
+                case EXIT_USER:
+                        return "USER";
+
+                case EXIT_CAPABILITIES:
+                        return "CAPABILITIES";
+
+                case EXIT_CGROUP:
+                        return "CGROUP";
+
+                case EXIT_SETSID:
+                        return "SETSID";
+
+                case EXIT_CONFIRM:
+                        return "CONFIRM";
+
+                case EXIT_STDERR:
+                        return "STDERR";
+
+                case EXIT_TCPWRAP:
+                        return "TCPWRAP";
+
+                case EXIT_PAM:
+                        return "PAM";
+
+                case EXIT_NETWORK:
+                        return "NETWORK";
+
+                case EXIT_NAMESPACE:
+                        return "NAMESPACE";
+                }
+        }
+
+        if (level == EXIT_STATUS_LSB) {
+                switch ((int) status) {
+
+                case EXIT_INVALIDARGUMENT:
+                        return "INVALIDARGUMENT";
+
+                case EXIT_NOTIMPLEMENTED:
+                        return "NOTIMPLEMENTED";
+
+                case EXIT_NOPERMISSION:
+                        return "NOPERMISSION";
+
+                case EXIT_NOTINSTALLED:
+                        return "NOTINSSTALLED";
+
+                case EXIT_NOTCONFIGURED:
+                        return "NOTCONFIGURED";
+
+                case EXIT_NOTRUNNING:
+                        return "NOTRUNNING";
+                }
+        }
+
+        return NULL;
+}
+
+
+bool is_clean_exit(int code, int status) {
+
+        if (code == CLD_EXITED)
+                return status == 0;
+
+        /* If a daemon does not implement handlers for some of the
+         * signals that's not considered an unclean shutdown */
+        if (code == CLD_KILLED)
+                return
+                        status == SIGHUP ||
+                        status == SIGINT ||
+                        status == SIGTERM ||
+                        status == SIGPIPE;
+
+        return false;
+}
+
+bool is_clean_exit_lsb(int code, int status) {
+
+        if (is_clean_exit(code, status))
+                return true;
+
+        return
+                code == CLD_EXITED &&
+                (status == EXIT_NOTINSTALLED || status == EXIT_NOTCONFIGURED);
+}
diff --git a/src/exit-status.h b/src/exit-status.h
new file mode 100644 (file)
index 0000000..44ef879
--- /dev/null
@@ -0,0 +1,85 @@
+/*-*- Mode: C; c-basic-offset: 8; indent-tabs-mode: nil -*-*/
+
+#ifndef fooexitstatushfoo
+#define fooexitstatushfoo
+
+/***
+  This file is part 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 <http://www.gnu.org/licenses/>.
+***/
+
+#include <stdbool.h>
+
+typedef enum ExitStatus {
+        /* EXIT_SUCCESS defined by libc */
+        /* EXIT_FAILURE defined by libc */
+        EXIT_INVALIDARGUMENT = 2,
+        EXIT_NOTIMPLEMENTED = 3,
+        EXIT_NOPERMISSION = 4,
+        EXIT_NOTINSTALLED = 5,
+        EXIT_NOTCONFIGURED = 6,
+        EXIT_NOTRUNNING = 7,
+
+        /* The LSB suggests that error codes >= 200 are "reserved". We
+         * use them here under the assumption that they hence are
+         * unused by init scripts.
+         *
+         * http://refspecs.freestandards.org/LSB_3.1.0/LSB-Core-generic/LSB-Core-generic/iniscrptact.html */
+
+        EXIT_CHDIR = 200,
+        EXIT_NICE,
+        EXIT_FDS,
+        EXIT_EXEC,
+        EXIT_MEMORY,
+        EXIT_LIMITS,
+        EXIT_OOM_ADJUST,
+        EXIT_SIGNAL_MASK,
+        EXIT_STDIN,
+        EXIT_STDOUT,
+        EXIT_CHROOT,   /* 210 */
+        EXIT_IOPRIO,
+        EXIT_TIMERSLACK,
+        EXIT_SECUREBITS,
+        EXIT_SETSCHEDULER,
+        EXIT_CPUAFFINITY,
+        EXIT_GROUP,
+        EXIT_USER,
+        EXIT_CAPABILITIES,
+        EXIT_CGROUP,
+        EXIT_SETSID,   /* 220 */
+        EXIT_CONFIRM,
+        EXIT_STDERR,
+        EXIT_TCPWRAP,
+        EXIT_PAM,
+        EXIT_NETWORK,
+        EXIT_NAMESPACE
+
+} ExitStatus;
+
+typedef enum ExitStatusLevel {
+        EXIT_STATUS_MINIMAL,
+        EXIT_STATUS_SYSTEMD,
+        EXIT_STATUS_LSB,
+        EXIT_STATUS_FULL = EXIT_STATUS_LSB
+} ExitStatusLevel;
+
+const char* exit_status_to_string(ExitStatus status, ExitStatusLevel level);
+
+bool is_clean_exit(int code, int status);
+bool is_clean_exit_lsb(int code, int status);
+
+#endif
diff --git a/src/fdset.c b/src/fdset.c
new file mode 100644 (file)
index 0000000..e67fe6f
--- /dev/null
@@ -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 <http://www.gnu.org/licenses/>.
+***/
+
+#include <errno.h>
+#include <dirent.h>
+#include <fcntl.h>
+#include <unistd.h>
+
+#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
new file mode 100644 (file)
index 0000000..044a9e6
--- /dev/null
@@ -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 <http://www.gnu.org/licenses/>.
+***/
+
+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/fsck.c b/src/fsck.c
new file mode 100644 (file)
index 0000000..d3ac83c
--- /dev/null
@@ -0,0 +1,406 @@
+/*-*- 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 <http://www.gnu.org/licenses/>.
+***/
+
+#include <stdio.h>
+#include <stdbool.h>
+#include <string.h>
+#include <errno.h>
+#include <unistd.h>
+#include <fcntl.h>
+#include <sys/file.h>
+
+#include <libudev.h>
+#include <dbus/dbus.h>
+
+#include "util.h"
+#include "dbus-common.h"
+#include "special.h"
+#include "bus-errors.h"
+#include "virt.h"
+
+static bool arg_skip = false;
+static bool arg_force = false;
+static bool arg_show_progress = false;
+
+static void start_target(const char *target, bool isolate) {
+        DBusMessage *m = NULL, *reply = NULL;
+        DBusError error;
+        const char *mode, *basic_target = "basic.target";
+        DBusConnection *bus = NULL;
+
+        assert(target);
+
+        dbus_error_init(&error);
+
+        if (bus_connect(DBUS_BUS_SYSTEM, &bus, NULL, &error) < 0) {
+                log_error("Failed to get D-Bus connection: %s", bus_error_message(&error));
+                goto finish;
+        }
+
+        if (isolate)
+                mode = "isolate";
+        else
+                mode = "replace";
+
+        log_info("Running request %s/start/%s", target, mode);
+
+        if (!(m = dbus_message_new_method_call("org.freedesktop.systemd1", "/org/freedesktop/systemd1", "org.freedesktop.systemd1.Manager", "StartUnitReplace"))) {
+                log_error("Could not allocate message.");
+                goto finish;
+        }
+
+        /* Start these units only if we can replace base.target with it */
+
+        if (!dbus_message_append_args(m,
+                                      DBUS_TYPE_STRING, &basic_target,
+                                      DBUS_TYPE_STRING, &target,
+                                      DBUS_TYPE_STRING, &mode,
+                                      DBUS_TYPE_INVALID)) {
+                log_error("Could not attach target and flag information to message.");
+                goto finish;
+        }
+
+        if (!(reply = dbus_connection_send_with_reply_and_block(bus, m, -1, &error))) {
+
+                /* Don't print a warning if we aren't called during
+                 * startup */
+                if (!dbus_error_has_name(&error, BUS_ERROR_NO_SUCH_JOB))
+                        log_error("Failed to start unit: %s", bus_error_message(&error));
+
+                goto finish;
+        }
+
+finish:
+        if (m)
+                dbus_message_unref(m);
+
+        if (reply)
+                dbus_message_unref(reply);
+
+        if (bus) {
+                dbus_connection_flush(bus);
+                dbus_connection_close(bus);
+                dbus_connection_unref(bus);
+        }
+
+        dbus_error_free(&error);
+}
+
+static int parse_proc_cmdline(void) {
+        char *line, *w, *state;
+        int r;
+        size_t l;
+
+        if (detect_container(NULL) > 0)
+                return 0;
+
+        if ((r = read_one_line_file("/proc/cmdline", &line)) < 0) {
+                log_warning("Failed to read /proc/cmdline, ignoring: %s", strerror(-r));
+                return 0;
+        }
+
+        FOREACH_WORD_QUOTED(w, l, line, state) {
+
+                if (strneq(w, "fsck.mode=auto", l))
+                        arg_force = arg_skip = false;
+                else if (strneq(w, "fsck.mode=force", l))
+                        arg_force = true;
+                else if (strneq(w, "fsck.mode=skip", l))
+                        arg_skip = true;
+                else if (startswith(w, "fsck.mode"))
+                        log_warning("Invalid fsck.mode= parameter. Ignoring.");
+#if defined(TARGET_FEDORA) || defined(TARGET_MANDRIVA) || defined(TARGET_MAGEIA)
+                else if (strneq(w, "fastboot", l))
+                        arg_skip = true;
+                else if (strneq(w, "forcefsck", l))
+                        arg_force = true;
+#endif
+        }
+
+        free(line);
+        return 0;
+}
+
+static void test_files(void) {
+        if (access("/fastboot", F_OK) >= 0)
+                arg_skip = true;
+
+        if (access("/forcefsck", F_OK) >= 0)
+                arg_force = true;
+
+        if (access("/run/systemd/show-status", F_OK) >= 0 || plymouth_running())
+                arg_show_progress = true;
+}
+
+static double percent(int pass, unsigned long cur, unsigned long max) {
+        /* Values stolen from e2fsck */
+
+        static const int pass_table[] = {
+                0, 70, 90, 92, 95, 100
+        };
+
+        if (pass <= 0)
+                return 0.0;
+
+        if ((unsigned) pass >= ELEMENTSOF(pass_table) || max == 0)
+                return 100.0;
+
+        return (double) pass_table[pass-1] +
+                ((double) pass_table[pass] - (double) pass_table[pass-1]) *
+                (double) cur / (double) max;
+}
+
+static int process_progress(int fd) {
+        FILE *f, *console;
+        usec_t last = 0;
+        bool locked = false;
+        int clear = 0;
+
+        f = fdopen(fd, "r");
+        if (!f) {
+                close_nointr_nofail(fd);
+                return -errno;
+        }
+
+        console = fopen("/dev/console", "w");
+        if (!console) {
+                fclose(f);
+                return -ENOMEM;
+        }
+
+        while (!feof(f)) {
+                int pass, m;
+                unsigned long cur, max;
+                char *device;
+                double p;
+                usec_t t;
+
+                if (fscanf(f, "%i %lu %lu %ms", &pass, &cur, &max, &device) != 4)
+                        break;
+
+                /* Only show one progress counter at max */
+                if (!locked) {
+                        if (flock(fileno(console), LOCK_EX|LOCK_NB) < 0) {
+                                free(device);
+                                continue;
+                        }
+
+                        locked = true;
+                }
+
+                /* Only update once every 50ms */
+                t = now(CLOCK_MONOTONIC);
+                if (last + 50 * USEC_PER_MSEC > t)  {
+                        free(device);
+                        continue;
+                }
+
+                last = t;
+
+                p = percent(pass, cur, max);
+                fprintf(console, "\r%s: fsck %3.1f%% complete...\r%n", device, p, &m);
+                fflush(console);
+
+                free(device);
+
+                if (m > clear)
+                        clear = m;
+        }
+
+        if (clear > 0) {
+                unsigned j;
+
+                fputc('\r', console);
+                for (j = 0; j < (unsigned) clear; j++)
+                        fputc(' ', console);
+                fputc('\r', console);
+                fflush(console);
+        }
+
+        fclose(f);
+        fclose(console);
+        return 0;
+}
+
+int main(int argc, char *argv[]) {
+        const char *cmdline[9];
+        int i = 0, r = EXIT_FAILURE, q;
+        pid_t pid;
+        siginfo_t status;
+        struct udev *udev = NULL;
+        struct udev_device *udev_device = NULL;
+        const char *device;
+        bool root_directory;
+        int progress_pipe[2] = { -1, -1 };
+        char dash_c[2+10+1];
+
+        if (argc > 2) {
+                log_error("This program expects one or no arguments.");
+                return EXIT_FAILURE;
+        }
+
+        log_set_target(LOG_TARGET_AUTO);
+        log_parse_environment();
+        log_open();
+
+        umask(0022);
+
+        parse_proc_cmdline();
+        test_files();
+
+        if (!arg_force && arg_skip)
+                return 0;
+
+        if (argc > 1) {
+                device = argv[1];
+                root_directory = false;
+        } else {
+                struct stat st;
+                struct timespec times[2];
+
+                /* Find root device */
+
+                if (stat("/", &st) < 0) {
+                        log_error("Failed to stat() the root directory: %m");
+                        goto finish;
+                }
+
+                /* Virtual root devices don't need an fsck */
+                if (major(st.st_dev) == 0)
+                        return 0;
+
+                /* check if we are already writable */
+                times[0] = st.st_atim;
+                times[1] = st.st_mtim;
+                if (utimensat(AT_FDCWD, "/", times, 0) == 0) {
+                        log_info("Root directory is writable, skipping check.");
+                        return 0;
+                }
+
+                if (!(udev = udev_new())) {
+                        log_error("Out of memory");
+                        goto finish;
+                }
+
+                if (!(udev_device = udev_device_new_from_devnum(udev, 'b', st.st_dev))) {
+                        log_error("Failed to detect root device.");
+                        goto finish;
+                }
+
+                if (!(device = udev_device_get_devnode(udev_device))) {
+                        log_error("Failed to detect device node of root directory.");
+                        goto finish;
+                }
+
+                root_directory = true;
+        }
+
+        if (arg_show_progress)
+                if (pipe(progress_pipe) < 0) {
+                        log_error("pipe(): %m");
+                        goto finish;
+                }
+
+        cmdline[i++] = "/sbin/fsck";
+        cmdline[i++] = "-a";
+        cmdline[i++] = "-T";
+        cmdline[i++] = "-l";
+
+        if (!root_directory)
+                cmdline[i++] = "-M";
+
+        if (arg_force)
+                cmdline[i++] = "-f";
+
+        if (progress_pipe[1] >= 0) {
+                snprintf(dash_c, sizeof(dash_c), "-C%i", progress_pipe[1]);
+                char_array_0(dash_c);
+                cmdline[i++] = dash_c;
+        }
+
+        cmdline[i++] = device;
+        cmdline[i++] = NULL;
+
+        pid = fork();
+        if (pid < 0) {
+                log_error("fork(): %m");
+                goto finish;
+        } else if (pid == 0) {
+                /* Child */
+                if (progress_pipe[0] >= 0)
+                        close_nointr_nofail(progress_pipe[0]);
+                execv(cmdline[0], (char**) cmdline);
+                _exit(8); /* Operational error */
+        }
+
+        if (progress_pipe[1] >= 0) {
+                close_nointr_nofail(progress_pipe[1]);
+                progress_pipe[1] = -1;
+        }
+
+        if (progress_pipe[0] >= 0) {
+                process_progress(progress_pipe[0]);
+                progress_pipe[0] = -1;
+        }
+
+        q = wait_for_terminate(pid, &status);
+        if (q < 0) {
+                log_error("waitid(): %s", strerror(-q));
+                goto finish;
+        }
+
+        if (status.si_code != CLD_EXITED || (status.si_status & ~1)) {
+
+                if (status.si_code == CLD_KILLED || status.si_code == CLD_DUMPED)
+                        log_error("fsck terminated by signal %s.", signal_to_string(status.si_status));
+                else if (status.si_code == CLD_EXITED)
+                        log_error("fsck failed with error code %i.", status.si_status);
+                else
+                        log_error("fsck failed due to unknown reason.");
+
+                if (status.si_code == CLD_EXITED && (status.si_status & 2) && root_directory)
+                        /* System should be rebooted. */
+                        start_target(SPECIAL_REBOOT_TARGET, false);
+                else if (status.si_code == CLD_EXITED && (status.si_status & 6))
+                        /* Some other problem */
+                        start_target(SPECIAL_EMERGENCY_TARGET, true);
+                else {
+                        r = EXIT_SUCCESS;
+                        log_warning("Ignoring error.");
+                }
+
+        } else
+                r = EXIT_SUCCESS;
+
+        if (status.si_code == CLD_EXITED && (status.si_status & 1))
+                touch("/run/systemd/quotacheck");
+
+finish:
+        if (udev_device)
+                udev_device_unref(udev_device);
+
+        if (udev)
+                udev_unref(udev);
+
+        close_pipe(progress_pipe);
+
+        return r;
+}
diff --git a/src/getty-generator.c b/src/getty-generator.c
new file mode 100644 (file)
index 0000000..7fac43a
--- /dev/null
@@ -0,0 +1,182 @@
+/*-*- 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 <http://www.gnu.org/licenses/>.
+***/
+
+#include <string.h>
+#include <errno.h>
+#include <unistd.h>
+
+#include "log.h"
+#include "util.h"
+#include "unit-name.h"
+#include "virt.h"
+
+static const char *arg_dest = "/tmp";
+
+static int add_symlink(const char *fservice, const char *tservice) {
+        char *from = NULL, *to = NULL;
+        int r;
+
+        assert(fservice);
+        assert(tservice);
+
+        asprintf(&from, SYSTEM_DATA_UNIT_PATH "/%s", fservice);
+        asprintf(&to, "%s/getty.target.wants/%s", arg_dest, tservice);
+
+        if (!from || !to) {
+                log_error("Out of memory");
+                r = -ENOMEM;
+                goto finish;
+        }
+
+        mkdir_parents(to, 0755);
+
+        r = symlink(from, to);
+        if (r < 0) {
+                if (errno == EEXIST)
+                        /* In case console=hvc0 is passed this will very likely result in EEXIST */
+                        r = 0;
+                else {
+                        log_error("Failed to create symlink from %s to %s: %m", from, to);
+                        r = -errno;
+                }
+        }
+
+finish:
+
+        free(from);
+        free(to);
+
+        return r;
+}
+
+static int add_serial_getty(const char *tty) {
+        char *n;
+        int r;
+
+        assert(tty);
+
+        log_debug("Automatically adding serial getty for /dev/%s.", tty);
+
+        n = unit_name_replace_instance("serial-getty@.service", tty);
+        if (!n) {
+                log_error("Out of memory");
+                return -ENOMEM;
+        }
+
+        r = add_symlink("serial-getty@.service", n);
+        free(n);
+
+        return r;
+}
+
+int main(int argc, char *argv[]) {
+
+        static const char virtualization_consoles[] =
+                "hvc0\0"
+                "xvc0\0"
+                "hvsi0\0";
+
+        int r = EXIT_SUCCESS;
+        char *active;
+        const char *j;
+
+        if (argc > 2) {
+                log_error("This program takes one or no arguments.");
+                return EXIT_FAILURE;
+        }
+
+        log_set_target(LOG_TARGET_AUTO);
+        log_parse_environment();
+        log_open();
+
+        umask(0022);
+
+        if (argc > 1)
+            arg_dest = argv[1];
+
+        if (detect_container(NULL) > 0) {
+                log_debug("Automatically adding console shell.");
+
+                if (add_symlink("console-shell.service", "console-shell.service") < 0)
+                        r = EXIT_FAILURE;
+
+                /* Don't add any further magic if we are in a container */
+                goto finish;
+        }
+
+        if (read_one_line_file("/sys/class/tty/console/active", &active) >= 0) {
+                const char *tty;
+
+                tty = strrchr(active, ' ');
+                if (tty)
+                        tty ++;
+                else
+                        tty = active;
+
+                /* Automatically add in a serial getty on the kernel
+                 * console */
+                if (tty_is_vc(tty))
+                        free(active);
+                else {
+                        int k;
+
+                        /* We assume that gettys on virtual terminals are
+                         * started via manual configuration and do this magic
+                         * only for non-VC terminals. */
+
+                        k = add_serial_getty(tty);
+                        free(active);
+
+                        if (k < 0) {
+                                r = EXIT_FAILURE;
+                                goto finish;
+                        }
+                }
+        }
+
+        /* Automatically add in a serial getty on the first
+         * virtualizer console */
+        NULSTR_FOREACH(j, virtualization_consoles) {
+                char *p;
+                int k;
+
+                if (asprintf(&p, "/sys/class/tty/%s", j) < 0) {
+                        log_error("Out of memory");
+                        r = EXIT_FAILURE;
+                        goto finish;
+                }
+
+                k = access(p, F_OK);
+                free(p);
+
+                if (k < 0)
+                        continue;
+
+                k = add_serial_getty(j);
+                if (k < 0) {
+                        r = EXIT_FAILURE;
+                        goto finish;
+                }
+        }
+
+finish:
+        return r;
+}
diff --git a/src/hashmap.c b/src/hashmap.c
new file mode 100644 (file)
index 0000000..6928118
--- /dev/null
@@ -0,0 +1,731 @@
+/*-*- 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 <http://www.gnu.org/licenses/>.
+***/
+
+#include <assert.h>
+#include <stdlib.h>
+#include <string.h>
+#include <errno.h>
+
+#include "util.h"
+#include "hashmap.h"
+#include "macro.h"
+
+#define NBUCKETS 127
+
+struct hashmap_entry {
+        const void *key;
+        void *value;
+        struct hashmap_entry *bucket_next, *bucket_previous;
+        struct hashmap_entry *iterate_next, *iterate_previous;
+};
+
+struct Hashmap {
+        hash_func_t hash_func;
+        compare_func_t compare_func;
+
+        struct hashmap_entry *iterate_list_head, *iterate_list_tail;
+        unsigned n_entries;
+
+        bool from_pool;
+};
+
+#define BY_HASH(h) ((struct hashmap_entry**) ((uint8_t*) (h) + ALIGN(sizeof(Hashmap))))
+
+struct pool {
+        struct pool *next;
+        unsigned n_tiles;
+        unsigned n_used;
+};
+
+static struct pool *first_hashmap_pool = NULL;
+static void *first_hashmap_tile = NULL;
+
+static struct pool *first_entry_pool = NULL;
+static void *first_entry_tile = NULL;
+
+static void* allocate_tile(struct pool **first_pool, void **first_tile, size_t tile_size) {
+        unsigned i;
+
+        if (*first_tile) {
+                void *r;
+
+                r = *first_tile;
+                *first_tile = * (void**) (*first_tile);
+                return r;
+        }
+
+        if (_unlikely_(!*first_pool) || _unlikely_((*first_pool)->n_used >= (*first_pool)->n_tiles)) {
+                unsigned n;
+                size_t size;
+                struct pool *p;
+
+                n = *first_pool ? (*first_pool)->n_tiles : 0;
+                n = MAX(512U, n * 2);
+                size = PAGE_ALIGN(ALIGN(sizeof(struct pool)) + n*tile_size);
+                n = (size - ALIGN(sizeof(struct pool))) / tile_size;
+
+                p = malloc(size);
+                if (!p)
+                        return NULL;
+
+                p->next = *first_pool;
+                p->n_tiles = n;
+                p->n_used = 0;
+
+                *first_pool = p;
+        }
+
+        i = (*first_pool)->n_used++;
+
+        return ((uint8_t*) (*first_pool)) + ALIGN(sizeof(struct pool)) + i*tile_size;
+}
+
+static void deallocate_tile(void **first_tile, void *p) {
+        * (void**) p = *first_tile;
+        *first_tile = p;
+}
+
+#ifndef __OPTIMIZE__
+
+static void drop_pool(struct pool *p) {
+        while (p) {
+                struct pool *n;
+                n = p->next;
+                free(p);
+                p = n;
+        }
+}
+
+__attribute__((destructor)) static void cleanup_pool(void) {
+        /* Be nice to valgrind */
+
+        drop_pool(first_hashmap_pool);
+        drop_pool(first_entry_pool);
+}
+
+#endif
+
+unsigned string_hash_func(const void *p) {
+        unsigned hash = 5381;
+        const signed char *c;
+
+        /* DJB's hash function */
+
+        for (c = p; *c; c++)
+                hash = (hash << 5) + hash + (unsigned) *c;
+
+        return hash;
+}
+
+int string_compare_func(const void *a, const void *b) {
+        return strcmp(a, b);
+}
+
+unsigned trivial_hash_func(const void *p) {
+        return PTR_TO_UINT(p);
+}
+
+int trivial_compare_func(const void *a, const void *b) {
+        return a < b ? -1 : (a > b ? 1 : 0);
+}
+
+Hashmap *hashmap_new(hash_func_t hash_func, compare_func_t compare_func) {
+        bool b;
+        Hashmap *h;
+        size_t size;
+
+        b = is_main_thread();
+
+        size = ALIGN(sizeof(Hashmap)) + NBUCKETS * sizeof(struct hashmap_entry*);
+
+        if (b) {
+                h = allocate_tile(&first_hashmap_pool, &first_hashmap_tile, size);
+                if (!h)
+                        return NULL;
+
+                memset(h, 0, size);
+        } else {
+                h = malloc0(size);
+
+                if (!h)
+                        return NULL;
+        }
+
+        h->hash_func = hash_func ? hash_func : trivial_hash_func;
+        h->compare_func = compare_func ? compare_func : trivial_compare_func;
+
+        h->n_entries = 0;
+        h->iterate_list_head = h->iterate_list_tail = NULL;
+
+        h->from_pool = b;
+
+        return h;
+}
+
+int hashmap_ensure_allocated(Hashmap **h, hash_func_t hash_func, compare_func_t compare_func) {
+        assert(h);
+
+        if (*h)
+                return 0;
+
+        if (!(*h = hashmap_new(hash_func, compare_func)))
+                return -ENOMEM;
+
+        return 0;
+}
+
+static void link_entry(Hashmap *h, struct hashmap_entry *e, unsigned hash) {
+        assert(h);
+        assert(e);
+
+        /* Insert into hash table */
+        e->bucket_next = BY_HASH(h)[hash];
+        e->bucket_previous = NULL;
+        if (BY_HASH(h)[hash])
+                BY_HASH(h)[hash]->bucket_previous = e;
+        BY_HASH(h)[hash] = e;
+
+        /* Insert into iteration list */
+        e->iterate_previous = h->iterate_list_tail;
+        e->iterate_next = NULL;
+        if (h->iterate_list_tail) {
+                assert(h->iterate_list_head);
+                h->iterate_list_tail->iterate_next = e;
+        } else {
+                assert(!h->iterate_list_head);
+                h->iterate_list_head = e;
+        }
+        h->iterate_list_tail = e;
+
+        h->n_entries++;
+        assert(h->n_entries >= 1);
+}
+
+static void unlink_entry(Hashmap *h, struct hashmap_entry *e, unsigned hash) {
+        assert(h);
+        assert(e);
+
+        /* Remove from iteration list */
+        if (e->iterate_next)
+                e->iterate_next->iterate_previous = e->iterate_previous;
+        else
+                h->iterate_list_tail = e->iterate_previous;
+
+        if (e->iterate_previous)
+                e->iterate_previous->iterate_next = e->iterate_next;
+        else
+                h->iterate_list_head = e->iterate_next;
+
+        /* Remove from hash table bucket list */
+        if (e->bucket_next)
+                e->bucket_next->bucket_previous = e->bucket_previous;
+
+        if (e->bucket_previous)
+                e->bucket_previous->bucket_next = e->bucket_next;
+        else
+                BY_HASH(h)[hash] = e->bucket_next;
+
+        assert(h->n_entries >= 1);
+        h->n_entries--;
+}
+
+static void remove_entry(Hashmap *h, struct hashmap_entry *e) {
+        unsigned hash;
+
+        assert(h);
+        assert(e);
+
+        hash = h->hash_func(e->key) % NBUCKETS;
+
+        unlink_entry(h, e, hash);
+
+        if (h->from_pool)
+                deallocate_tile(&first_entry_tile, e);
+        else
+                free(e);
+}
+
+void hashmap_free(Hashmap*h) {
+
+        if (!h)
+                return;
+
+        hashmap_clear(h);
+
+        if (h->from_pool)
+                deallocate_tile(&first_hashmap_tile, h);
+        else
+                free(h);
+}
+
+void hashmap_free_free(Hashmap *h) {
+        void *p;
+
+        while ((p = hashmap_steal_first(h)))
+                free(p);
+
+        hashmap_free(h);
+}
+
+void hashmap_clear(Hashmap *h) {
+        if (!h)
+                return;
+
+        while (h->iterate_list_head)
+                remove_entry(h, h->iterate_list_head);
+}
+
+static struct hashmap_entry *hash_scan(Hashmap *h, unsigned hash, const void *key) {
+        struct hashmap_entry *e;
+        assert(h);
+        assert(hash < NBUCKETS);
+
+        for (e = BY_HASH(h)[hash]; e; e = e->bucket_next)
+                if (h->compare_func(e->key, key) == 0)
+                        return e;
+
+        return NULL;
+}
+
+int hashmap_put(Hashmap *h, const void *key, void *value) {
+        struct hashmap_entry *e;
+        unsigned hash;
+
+        assert(h);
+
+        hash = h->hash_func(key) % NBUCKETS;
+
+        if ((e = hash_scan(h, hash, key))) {
+
+                if (e->value == value)
+                        return 0;
+
+                return -EEXIST;
+        }
+
+        if (h->from_pool)
+                e = allocate_tile(&first_entry_pool, &first_entry_tile, sizeof(struct hashmap_entry));
+        else
+                e = new(struct hashmap_entry, 1);
+
+        if (!e)
+                return -ENOMEM;
+
+        e->key = key;
+        e->value = value;
+
+        link_entry(h, e, hash);
+
+        return 1;
+}
+
+int hashmap_replace(Hashmap *h, const void *key, void *value) {
+        struct hashmap_entry *e;
+        unsigned hash;
+
+        assert(h);
+
+        hash = h->hash_func(key) % NBUCKETS;
+
+        if ((e = hash_scan(h, hash, key))) {
+                e->key = key;
+                e->value = value;
+                return 0;
+        }
+
+        return hashmap_put(h, key, value);
+}
+
+void* hashmap_get(Hashmap *h, const void *key) {
+        unsigned hash;
+        struct hashmap_entry *e;
+
+        if (!h)
+                return NULL;
+
+        hash = h->hash_func(key) % NBUCKETS;
+
+        if (!(e = hash_scan(h, hash, key)))
+                return NULL;
+
+        return e->value;
+}
+
+void* hashmap_remove(Hashmap *h, const void *key) {
+        struct hashmap_entry *e;
+        unsigned hash;
+        void *data;
+
+        if (!h)
+                return NULL;
+
+        hash = h->hash_func(key) % NBUCKETS;
+
+        if (!(e = hash_scan(h, hash, key)))
+                return NULL;
+
+        data = e->value;
+        remove_entry(h, e);
+
+        return data;
+}
+
+int hashmap_remove_and_put(Hashmap *h, const void *old_key, const void *new_key, void *value) {
+        struct hashmap_entry *e;
+        unsigned old_hash, new_hash;
+
+        if (!h)
+                return -ENOENT;
+
+        old_hash = h->hash_func(old_key) % NBUCKETS;
+        if (!(e = hash_scan(h, old_hash, old_key)))
+                return -ENOENT;
+
+        new_hash = h->hash_func(new_key) % NBUCKETS;
+        if (hash_scan(h, new_hash, new_key))
+                return -EEXIST;
+
+        unlink_entry(h, e, old_hash);
+
+        e->key = new_key;
+        e->value = value;
+
+        link_entry(h, e, new_hash);
+
+        return 0;
+}
+
+int hashmap_remove_and_replace(Hashmap *h, const void *old_key, const void *new_key, void *value) {
+        struct hashmap_entry *e, *k;
+        unsigned old_hash, new_hash;
+
+        if (!h)
+                return -ENOENT;
+
+        old_hash = h->hash_func(old_key) % NBUCKETS;
+        if (!(e = hash_scan(h, old_hash, old_key)))
+                return -ENOENT;
+
+        new_hash = h->hash_func(new_key) % NBUCKETS;
+
+        if ((k = hash_scan(h, new_hash, new_key)))
+                if (e != k)
+                        remove_entry(h, k);
+
+        unlink_entry(h, e, old_hash);
+
+        e->key = new_key;
+        e->value = value;
+
+        link_entry(h, e, new_hash);
+
+        return 0;
+}
+
+void* hashmap_remove_value(Hashmap *h, const void *key, void *value) {
+        struct hashmap_entry *e;
+        unsigned hash;
+
+        if (!h)
+                return NULL;
+
+        hash = h->hash_func(key) % NBUCKETS;
+
+        if (!(e = hash_scan(h, hash, key)))
+                return NULL;
+
+        if (e->value != value)
+                return NULL;
+
+        remove_entry(h, e);
+
+        return value;
+}
+
+void *hashmap_iterate(Hashmap *h, Iterator *i, const void **key) {
+        struct hashmap_entry *e;
+
+        assert(i);
+
+        if (!h)
+                goto at_end;
+
+        if (*i == ITERATOR_LAST)
+                goto at_end;
+
+        if (*i == ITERATOR_FIRST && !h->iterate_list_head)
+                goto at_end;
+
+        e = *i == ITERATOR_FIRST ? h->iterate_list_head : (struct hashmap_entry*) *i;
+
+        if (e->iterate_next)
+                *i = (Iterator) e->iterate_next;
+        else
+                *i = ITERATOR_LAST;
+
+        if (key)
+                *key = e->key;
+
+        return e->value;
+
+at_end:
+        *i = ITERATOR_LAST;
+
+        if (key)
+                *key = NULL;
+
+        return NULL;
+}
+
+void *hashmap_iterate_backwards(Hashmap *h, Iterator *i, const void **key) {
+        struct hashmap_entry *e;
+
+        assert(i);
+
+        if (!h)
+                goto at_beginning;
+
+        if (*i == ITERATOR_FIRST)
+                goto at_beginning;
+
+        if (*i == ITERATOR_LAST && !h->iterate_list_tail)
+                goto at_beginning;
+
+        e = *i == ITERATOR_LAST ? h->iterate_list_tail : (struct hashmap_entry*) *i;
+
+        if (e->iterate_previous)
+                *i = (Iterator) e->iterate_previous;
+        else
+                *i = ITERATOR_FIRST;
+
+        if (key)
+                *key = e->key;
+
+        return e->value;
+
+at_beginning:
+        *i = ITERATOR_FIRST;
+
+        if (key)
+                *key = NULL;
+
+        return NULL;
+}
+
+void *hashmap_iterate_skip(Hashmap *h, const void *key, Iterator *i) {
+        unsigned hash;
+        struct hashmap_entry *e;
+
+        if (!h)
+                return NULL;
+
+        hash = h->hash_func(key) % NBUCKETS;
+
+        if (!(e = hash_scan(h, hash, key)))
+                return NULL;
+
+        *i = (Iterator) e;
+
+        return e->value;
+}
+
+void* hashmap_first(Hashmap *h) {
+
+        if (!h)
+                return NULL;
+
+        if (!h->iterate_list_head)
+                return NULL;
+
+        return h->iterate_list_head->value;
+}
+
+void* hashmap_first_key(Hashmap *h) {
+
+        if (!h)
+                return NULL;
+
+        if (!h->iterate_list_head)
+                return NULL;
+
+        return (void*) h->iterate_list_head->key;
+}
+
+void* hashmap_last(Hashmap *h) {
+
+        if (!h)
+                return NULL;
+
+        if (!h->iterate_list_tail)
+                return NULL;
+
+        return h->iterate_list_tail->value;
+}
+
+void* hashmap_steal_first(Hashmap *h) {
+        void *data;
+
+        if (!h)
+                return NULL;
+
+        if (!h->iterate_list_head)
+                return NULL;
+
+        data = h->iterate_list_head->value;
+        remove_entry(h, h->iterate_list_head);
+
+        return data;
+}
+
+void* hashmap_steal_first_key(Hashmap *h) {
+        void *key;
+
+        if (!h)
+                return NULL;
+
+        if (!h->iterate_list_head)
+                return NULL;
+
+        key = (void*) h->iterate_list_head->key;
+        remove_entry(h, h->iterate_list_head);
+
+        return key;
+}
+
+unsigned hashmap_size(Hashmap *h) {
+
+        if (!h)
+                return 0;
+
+        return h->n_entries;
+}
+
+bool hashmap_isempty(Hashmap *h) {
+
+        if (!h)
+                return true;
+
+        return h->n_entries == 0;
+}
+
+int hashmap_merge(Hashmap *h, Hashmap *other) {
+        struct hashmap_entry *e;
+
+        assert(h);
+
+        if (!other)
+                return 0;
+
+        for (e = other->iterate_list_head; e; e = e->iterate_next) {
+                int r;
+
+                if ((r = hashmap_put(h, e->key, e->value)) < 0)
+                        if (r != -EEXIST)
+                                return r;
+        }
+
+        return 0;
+}
+
+void hashmap_move(Hashmap *h, Hashmap *other) {
+        struct hashmap_entry *e, *n;
+
+        assert(h);
+
+        /* The same as hashmap_merge(), but every new item from other
+         * is moved to h. This function is guaranteed to succeed. */
+
+        if (!other)
+                return;
+
+        for (e = other->iterate_list_head; e; e = n) {
+                unsigned h_hash, other_hash;
+
+                n = e->iterate_next;
+
+                h_hash = h->hash_func(e->key) % NBUCKETS;
+
+                if (hash_scan(h, h_hash, e->key))
+                        continue;
+
+                other_hash = other->hash_func(e->key) % NBUCKETS;
+
+                unlink_entry(other, e, other_hash);
+                link_entry(h, e, h_hash);
+        }
+}
+
+int hashmap_move_one(Hashmap *h, Hashmap *other, const void *key) {
+        unsigned h_hash, other_hash;
+        struct hashmap_entry *e;
+
+        if (!other)
+                return 0;
+
+        assert(h);
+
+        h_hash = h->hash_func(key) % NBUCKETS;
+        if (hash_scan(h, h_hash, key))
+                return -EEXIST;
+
+        other_hash = other->hash_func(key) % NBUCKETS;
+        if (!(e = hash_scan(other, other_hash, key)))
+                return -ENOENT;
+
+        unlink_entry(other, e, other_hash);
+        link_entry(h, e, h_hash);
+
+        return 0;
+}
+
+Hashmap *hashmap_copy(Hashmap *h) {
+        Hashmap *copy;
+
+        assert(h);
+
+        if (!(copy = hashmap_new(h->hash_func, h->compare_func)))
+                return NULL;
+
+        if (hashmap_merge(copy, h) < 0) {
+                hashmap_free(copy);
+                return NULL;
+        }
+
+        return copy;
+}
+
+char **hashmap_get_strv(Hashmap *h) {
+        char **sv;
+        Iterator it;
+        char *item;
+        int n;
+
+        sv = new(char*, h->n_entries+1);
+        if (!sv)
+                return NULL;
+
+        n = 0;
+        HASHMAP_FOREACH(item, h, it)
+                sv[n++] = item;
+        sv[n] = NULL;
+
+        return sv;
+}
diff --git a/src/hashmap.h b/src/hashmap.h
new file mode 100644 (file)
index 0000000..ab4363a
--- /dev/null
@@ -0,0 +1,91 @@
+/*-*- Mode: C; c-basic-offset: 8; indent-tabs-mode: nil -*-*/
+
+#ifndef foohashmaphfoo
+#define foohashmaphfoo
+
+/***
+  This file is part 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 <http://www.gnu.org/licenses/>.
+***/
+
+#include <stdbool.h>
+
+/* Pretty straightforward hash table implementation. As a minor
+ * optimization a NULL hashmap object will be treated as empty hashmap
+ * for all read operations. That way it is not necessary to
+ * instantiate an object for each Hashmap use. */
+
+typedef struct Hashmap Hashmap;
+typedef struct _IteratorStruct _IteratorStruct;
+typedef _IteratorStruct* Iterator;
+
+#define ITERATOR_FIRST ((Iterator) 0)
+#define ITERATOR_LAST ((Iterator) -1)
+
+typedef unsigned (*hash_func_t)(const void *p);
+typedef int (*compare_func_t)(const void *a, const void *b);
+
+unsigned string_hash_func(const void *p);
+int string_compare_func(const void *a, const void *b);
+
+unsigned trivial_hash_func(const void *p);
+int trivial_compare_func(const void *a, const void *b);
+
+Hashmap *hashmap_new(hash_func_t hash_func, compare_func_t compare_func);
+void hashmap_free(Hashmap *h);
+void hashmap_free_free(Hashmap *h);
+Hashmap *hashmap_copy(Hashmap *h);
+int hashmap_ensure_allocated(Hashmap **h, hash_func_t hash_func, compare_func_t compare_func);
+
+int hashmap_put(Hashmap *h, const void *key, void *value);
+int hashmap_replace(Hashmap *h, const void *key, void *value);
+void* hashmap_get(Hashmap *h, const void *key);
+void* hashmap_remove(Hashmap *h, const void *key);
+void* hashmap_remove_value(Hashmap *h, const void *key, void *value);
+int hashmap_remove_and_put(Hashmap *h, const void *old_key, const void *new_key, void *value);
+int hashmap_remove_and_replace(Hashmap *h, const void *old_key, const void *new_key, void *value);
+
+int hashmap_merge(Hashmap *h, Hashmap *other);
+void hashmap_move(Hashmap *h, Hashmap *other);
+int hashmap_move_one(Hashmap *h, Hashmap *other, const void *key);
+
+unsigned hashmap_size(Hashmap *h);
+bool hashmap_isempty(Hashmap *h);
+
+void *hashmap_iterate(Hashmap *h, Iterator *i, const void **key);
+void *hashmap_iterate_backwards(Hashmap *h, Iterator *i, const void **key);
+void *hashmap_iterate_skip(Hashmap *h, const void *key, Iterator *i);
+
+void hashmap_clear(Hashmap *h);
+void *hashmap_steal_first(Hashmap *h);
+void *hashmap_steal_first_key(Hashmap *h);
+void* hashmap_first(Hashmap *h);
+void* hashmap_first_key(Hashmap *h);
+void* hashmap_last(Hashmap *h);
+
+char **hashmap_get_strv(Hashmap *h);
+
+#define HASHMAP_FOREACH(e, h, i) \
+        for ((i) = ITERATOR_FIRST, (e) = hashmap_iterate((h), &(i), NULL); (e); (e) = hashmap_iterate((h), &(i), NULL))
+
+#define HASHMAP_FOREACH_KEY(e, k, h, i) \
+        for ((i) = ITERATOR_FIRST, (e) = hashmap_iterate((h), &(i), (const void**) &(k)); (e); (e) = hashmap_iterate((h), &(i), (const void**) &(k)))
+
+#define HASHMAP_FOREACH_BACKWARDS(e, h, i) \
+        for ((i) = ITERATOR_LAST, (e) = hashmap_iterate_backwards((h), &(i), NULL); (e); (e) = hashmap_iterate_backwards((h), &(i), NULL))
+
+#endif
diff --git a/src/hostname-setup.c b/src/hostname-setup.c
new file mode 100644 (file)
index 0000000..2c2f10c
--- /dev/null
@@ -0,0 +1,187 @@
+/*-*- 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 <http://www.gnu.org/licenses/>.
+***/
+
+#include <unistd.h>
+#include <stdio.h>
+#include <errno.h>
+#include <string.h>
+#include <stdlib.h>
+
+#include "hostname-setup.h"
+#include "macro.h"
+#include "util.h"
+#include "log.h"
+
+#if defined(TARGET_FEDORA) || defined(TARGET_ALTLINUX) || defined(TARGET_MANDRIVA) || defined(TARGET_MEEGO) || defined(TARGET_MAGEIA)
+#define FILENAME "/etc/sysconfig/network"
+#elif defined(TARGET_SUSE) || defined(TARGET_SLACKWARE)
+#define FILENAME "/etc/HOSTNAME"
+#elif defined(TARGET_ARCH)
+#define FILENAME "/etc/rc.conf"
+#elif defined(TARGET_GENTOO)
+#define FILENAME "/etc/conf.d/hostname"
+#endif
+
+static int read_and_strip_hostname(const char *path, char **hn) {
+        char *s;
+        int r;
+
+        assert(path);
+        assert(hn);
+
+        if ((r = read_one_line_file(path, &s)) < 0)
+                return r;
+
+        hostname_cleanup(s);
+
+        if (isempty(s)) {
+                free(s);
+                return -ENOENT;
+        }
+
+        *hn = s;
+
+        return 0;
+}
+
+static int read_distro_hostname(char **hn) {
+
+#if defined(TARGET_FEDORA) || defined(TARGET_ARCH) || defined(TARGET_GENTOO) || defined(TARGET_ALTLINUX) || defined(TARGET_MANDRIVA) || defined(TARGET_MEEGO) || defined(TARGET_MAGEIA)
+        int r;
+        FILE *f;
+
+        assert(hn);
+
+        if (!(f = fopen(FILENAME, "re")))
+                return -errno;
+
+        for (;;) {
+                char line[LINE_MAX];
+                char *s, *k;
+
+                if (!fgets(line, sizeof(line), f)) {
+                        if (feof(f))
+                                break;
+
+                        r = -errno;
+                        goto finish;
+                }
+
+                s = strstrip(line);
+
+                if (!startswith_no_case(s, "HOSTNAME="))
+                        continue;
+
+                if (!(k = strdup(s+9))) {
+                        r = -ENOMEM;
+                        goto finish;
+                }
+
+                hostname_cleanup(k);
+
+                if (isempty(k)) {
+                        free(k);
+                        r = -ENOENT;
+                        goto finish;
+                }
+
+                *hn = k;
+                r = 0;
+                goto finish;
+        }
+
+        r = -ENOENT;
+
+finish:
+        fclose(f);
+        return r;
+
+#elif defined(TARGET_SUSE) || defined(TARGET_SLACKWARE)
+        return read_and_strip_hostname(FILENAME, hn);
+#else
+        return -ENOENT;
+#endif
+}
+
+static int read_hostname(char **hn) {
+        int r;
+
+        assert(hn);
+
+        /* First, try to load the generic hostname configuration file,
+         * that we support on all distributions */
+
+        if ((r = read_and_strip_hostname("/etc/hostname", hn)) < 0) {
+
+                if (r == -ENOENT)
+                        return read_distro_hostname(hn);
+
+                return r;
+        }
+
+        return 0;
+}
+
+int hostname_setup(void) {
+        int r;
+        char *b = NULL;
+        const char *hn = NULL;
+
+        if ((r = read_hostname(&b)) < 0) {
+                if (r == -ENOENT)
+                        log_info("No hostname configured.");
+                else
+                        log_warning("Failed to read configured hostname: %s", strerror(-r));
+
+                hn = NULL;
+        } else
+                hn = b;
+
+        if (!hn) {
+                /* Don't override the hostname if it is unset and not
+                 * explicitly configured */
+
+                char *old_hostname = NULL;
+
+                if ((old_hostname = gethostname_malloc())) {
+                        bool already_set;
+
+                        already_set = old_hostname[0] != 0;
+                        free(old_hostname);
+
+                        if (already_set)
+                                goto finish;
+                }
+
+                hn = "localhost";
+        }
+
+        if (sethostname(hn, strlen(hn)) < 0) {
+                log_warning("Failed to set hostname to <%s>: %m", hn);
+                r = -errno;
+        } else
+                log_info("Set hostname to <%s>.", hn);
+
+finish:
+        free(b);
+
+        return r;
+}
diff --git a/src/hostname-setup.h b/src/hostname-setup.h
new file mode 100644 (file)
index 0000000..ff11df9
--- /dev/null
@@ -0,0 +1,27 @@
+/*-*- Mode: C; c-basic-offset: 8; indent-tabs-mode: nil -*-*/
+
+#ifndef foohostnamesetuphfoo
+#define foohostnamesetuphfoo
+
+/***
+  This file is part 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 <http://www.gnu.org/licenses/>.
+***/
+
+int hostname_setup(void);
+
+#endif
diff --git a/src/hostname/.gitignore b/src/hostname/.gitignore
new file mode 100644 (file)
index 0000000..1ff281b
--- /dev/null
@@ -0,0 +1 @@
+org.freedesktop.hostname1.policy
diff --git a/src/hostname/Makefile b/src/hostname/Makefile
new file mode 120000 (symlink)
index 0000000..d0b0e8e
--- /dev/null
@@ -0,0 +1 @@
+../Makefile
\ No newline at end of file
diff --git a/src/hostname/hostnamed.c b/src/hostname/hostnamed.c
new file mode 100644 (file)
index 0000000..ad72440
--- /dev/null
@@ -0,0 +1,629 @@
+/*-*- 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 <http://www.gnu.org/licenses/>.
+***/
+
+#include <dbus/dbus.h>
+
+#include <errno.h>
+#include <string.h>
+#include <unistd.h>
+#include <dlfcn.h>
+
+#include "util.h"
+#include "strv.h"
+#include "dbus-common.h"
+#include "polkit.h"
+#include "def.h"
+#include "virt.h"
+
+#define INTERFACE \
+        " <interface name=\"org.freedesktop.hostname1\">\n"             \
+        "  <property name=\"Hostname\" type=\"s\" access=\"read\"/>\n"  \
+        "  <property name=\"StaticHostname\" type=\"s\" access=\"read\"/>\n" \
+        "  <property name=\"PrettyHostname\" type=\"s\" access=\"read\"/>\n" \
+        "  <property name=\"IconName\" type=\"s\" access=\"read\"/>\n"  \
+        "  <method name=\"SetHostname\">\n"                             \
+        "   <arg name=\"name\" type=\"s\" direction=\"in\"/>\n"         \
+        "   <arg name=\"user_interaction\" type=\"b\" direction=\"in\"/>\n" \
+        "  </method>\n"                                                 \
+        "  <method name=\"SetStaticHostname\">\n"                       \
+        "   <arg name=\"name\" type=\"s\" direction=\"in\"/>\n"         \
+        "   <arg name=\"user_interaction\" type=\"b\" direction=\"in\"/>\n" \
+        "  </method>\n"                                                 \
+        "  <method name=\"SetPrettyHostname\">\n"                       \
+        "   <arg name=\"name\" type=\"s\" direction=\"in\"/>\n"         \
+        "   <arg name=\"user_interaction\" type=\"b\" direction=\"in\"/>\n" \
+        "  </method>\n"                                                 \
+        "  <method name=\"SetIconName\">\n"                             \
+        "   <arg name=\"name\" type=\"s\" direction=\"in\"/>\n"         \
+        "   <arg name=\"user_interaction\" type=\"b\" direction=\"in\"/>\n" \
+        "  </method>\n"                                                 \
+        " </interface>\n"
+
+#define INTROSPECTION                                                   \
+        DBUS_INTROSPECT_1_0_XML_DOCTYPE_DECL_NODE                       \
+        "<node>\n"                                                      \
+        INTERFACE                                                       \
+        BUS_PROPERTIES_INTERFACE                                        \
+        BUS_INTROSPECTABLE_INTERFACE                                    \
+        BUS_PEER_INTERFACE                                              \
+        "</node>\n"
+
+#define INTERFACES_LIST                         \
+        BUS_GENERIC_INTERFACES_LIST             \
+        "org.freedesktop.hostname1\0"
+
+const char hostname_interface[] _introspect_("hostname1") = INTERFACE;
+
+enum {
+        PROP_HOSTNAME,
+        PROP_STATIC_HOSTNAME,
+        PROP_PRETTY_HOSTNAME,
+        PROP_ICON_NAME,
+        _PROP_MAX
+};
+
+static char *data[_PROP_MAX] = {
+        NULL,
+        NULL,
+        NULL,
+        NULL
+};
+
+static usec_t remain_until = 0;
+
+static void free_data(void) {
+        int p;
+
+        for (p = 0; p < _PROP_MAX; p++) {
+                free(data[p]);
+                data[p] = NULL;
+        }
+}
+
+static int read_data(void) {
+        int r;
+
+        free_data();
+
+        data[PROP_HOSTNAME] = gethostname_malloc();
+        if (!data[PROP_HOSTNAME])
+                return -ENOMEM;
+
+        r = read_one_line_file("/etc/hostname", &data[PROP_STATIC_HOSTNAME]);
+        if (r < 0 && r != -ENOENT)
+                return r;
+
+        r = parse_env_file("/etc/machine-info", NEWLINE,
+                           "PRETTY_HOSTNAME", &data[PROP_PRETTY_HOSTNAME],
+                           "ICON_NAME", &data[PROP_ICON_NAME],
+                           NULL);
+        if (r < 0 && r != -ENOENT)
+                return r;
+
+        return 0;
+}
+
+static bool check_nss(void) {
+
+        void *dl;
+
+        if ((dl = dlopen("libnss_myhostname.so.2", RTLD_LAZY))) {
+                dlclose(dl);
+                return true;
+        }
+
+        return false;
+}
+
+static const char* fallback_icon_name(void) {
+
+#if defined(__i386__) || defined(__x86_64__)
+        int r;
+        char *type;
+        unsigned t;
+#endif
+
+        if (detect_virtualization(NULL) > 0)
+                return "computer-vm";
+
+#if defined(__i386__) || defined(__x86_64__)
+        r = read_one_line_file("/sys/class/dmi/id/chassis_type", &type);
+        if (r < 0)
+                return NULL;
+
+        r = safe_atou(type, &t);
+        free(type);
+
+        if (r < 0)
+                return NULL;
+
+        /* We only list the really obvious cases here. The DMI data is
+           unreliable enough, so let's not do any additional guesswork
+           on top of that.
+
+           See the SMBIOS Specification 2.7.1 section 7.4.1 for
+           details about the values listed here:
+
+           http://www.dmtf.org/sites/default/files/standards/documents/DSP0134_2.7.1.pdf
+         */
+
+        switch (t) {
+
+        case 0x3:
+        case 0x4:
+        case 0x6:
+        case 0x7:
+                return "computer-desktop";
+
+        case 0x9:
+        case 0xA:
+        case 0xE:
+                return "computer-laptop";
+
+        case 0x11:
+        case 0x1C:
+                return "computer-server";
+        }
+
+#endif
+        return NULL;
+}
+
+static int write_data_hostname(void) {
+        const char *hn;
+
+        if (isempty(data[PROP_HOSTNAME]))
+                hn = "localhost";
+        else
+                hn = data[PROP_HOSTNAME];
+
+        if (sethostname(hn, strlen(hn)) < 0)
+                return -errno;
+
+        return 0;
+}
+
+static int write_data_static_hostname(void) {
+
+        if (isempty(data[PROP_STATIC_HOSTNAME])) {
+
+                if (unlink("/etc/hostname") < 0)
+                        return errno == ENOENT ? 0 : -errno;
+
+                return 0;
+        }
+
+        return write_one_line_file_atomic("/etc/hostname", data[PROP_STATIC_HOSTNAME]);
+}
+
+static int write_data_other(void) {
+
+        static const char * const name[_PROP_MAX] = {
+                [PROP_PRETTY_HOSTNAME] = "PRETTY_HOSTNAME",
+                [PROP_ICON_NAME] = "ICON_NAME"
+        };
+
+        char **l = NULL;
+        int r, p;
+
+        r = load_env_file("/etc/machine-info", &l);
+        if (r < 0 && r != -ENOENT)
+                return r;
+
+        for (p = 2; p < _PROP_MAX; p++) {
+                char *t, **u;
+
+                assert(name[p]);
+
+                if (isempty(data[p]))  {
+                        strv_env_unset(l, name[p]);
+                        continue;
+                }
+
+                if (asprintf(&t, "%s=%s", name[p], strempty(data[p])) < 0) {
+                        strv_free(l);
+                        return -ENOMEM;
+                }
+
+                u = strv_env_set(l, t);
+                free(t);
+                strv_free(l);
+
+                if (!u)
+                        return -ENOMEM;
+                l = u;
+        }
+
+        if (strv_isempty(l)) {
+
+                if (unlink("/etc/machine-info") < 0)
+                        return errno == ENOENT ? 0 : -errno;
+
+                return 0;
+        }
+
+        r = write_env_file("/etc/machine-info", l);
+        strv_free(l);
+
+        return r;
+}
+
+static int bus_hostname_append_icon_name(DBusMessageIter *i, const char *property, void *userdata) {
+        const char *name;
+
+        assert(i);
+        assert(property);
+
+        if (isempty(data[PROP_ICON_NAME]))
+                name = fallback_icon_name();
+        else
+                name = data[PROP_ICON_NAME];
+
+        return bus_property_append_string(i, property, (void*) name);
+}
+
+static const BusProperty bus_hostname_properties[] = {
+        { "Hostname",       bus_property_append_string,    "s", sizeof(data[0])*PROP_HOSTNAME,        true },
+        { "StaticHostname", bus_property_append_string,    "s", sizeof(data[0])*PROP_STATIC_HOSTNAME, true },
+        { "PrettyHostname", bus_property_append_string,    "s", sizeof(data[0])*PROP_PRETTY_HOSTNAME, true },
+        { "IconName",       bus_hostname_append_icon_name, "s", sizeof(data[0])*PROP_ICON_NAME,       true },
+        { NULL, }
+};
+
+static const BusBoundProperties bps[] = {
+        { "org.freedesktop.hostname1", bus_hostname_properties, data },
+        { NULL, }
+};
+
+static DBusHandlerResult hostname_message_handler(
+                DBusConnection *connection,
+                DBusMessage *message,
+                void *userdata) {
+
+
+        DBusMessage *reply = NULL, *changed = NULL;
+        DBusError error;
+        int r;
+
+        assert(connection);
+        assert(message);
+
+        dbus_error_init(&error);
+
+        if (dbus_message_is_method_call(message, "org.freedesktop.hostname1", "SetHostname")) {
+                const char *name;
+                dbus_bool_t interactive;
+
+                if (!dbus_message_get_args(
+                                    message,
+                                    &error,
+                                    DBUS_TYPE_STRING, &name,
+                                    DBUS_TYPE_BOOLEAN, &interactive,
+                                    DBUS_TYPE_INVALID))
+                        return bus_send_error_reply(connection, message, &error, -EINVAL);
+
+                if (isempty(name))
+                        name = data[PROP_STATIC_HOSTNAME];
+
+                if (isempty(name))
+                        name = "localhost";
+
+                if (!hostname_is_valid(name))
+                        return bus_send_error_reply(connection, message, NULL, -EINVAL);
+
+                if (!streq_ptr(name, data[PROP_HOSTNAME])) {
+                        char *h;
+
+                        r = verify_polkit(connection, message, "org.freedesktop.hostname1.set-hostname", interactive, NULL, &error);
+                        if (r < 0)
+                                return bus_send_error_reply(connection, message, &error, r);
+
+                        h = strdup(name);
+                        if (!h)
+                                goto oom;
+
+                        free(data[PROP_HOSTNAME]);
+                        data[PROP_HOSTNAME] = h;
+
+                        r = write_data_hostname();
+                        if (r < 0) {
+                                log_error("Failed to set host name: %s", strerror(-r));
+                                return bus_send_error_reply(connection, message, NULL, r);
+                        }
+
+                        log_info("Changed host name to '%s'", strempty(data[PROP_HOSTNAME]));
+
+                        changed = bus_properties_changed_new(
+                                        "/org/freedesktop/hostname1",
+                                        "org.freedesktop.hostname1",
+                                        "Hostname\0");
+                        if (!changed)
+                                goto oom;
+                }
+
+        } else if (dbus_message_is_method_call(message, "org.freedesktop.hostname1", "SetStaticHostname")) {
+                const char *name;
+                dbus_bool_t interactive;
+
+                if (!dbus_message_get_args(
+                                    message,
+                                    &error,
+                                    DBUS_TYPE_STRING, &name,
+                                    DBUS_TYPE_BOOLEAN, &interactive,
+                                    DBUS_TYPE_INVALID))
+                        return bus_send_error_reply(connection, message, &error, -EINVAL);
+
+                if (isempty(name))
+                        name = NULL;
+
+                if (!streq_ptr(name, data[PROP_STATIC_HOSTNAME])) {
+
+                        r = verify_polkit(connection, message, "org.freedesktop.hostname1.set-static-hostname", interactive, NULL, &error);
+                        if (r < 0)
+                                return bus_send_error_reply(connection, message, &error, r);
+
+                        if (isempty(name)) {
+                                free(data[PROP_STATIC_HOSTNAME]);
+                                data[PROP_STATIC_HOSTNAME] = NULL;
+                        } else {
+                                char *h;
+
+                                if (!hostname_is_valid(name))
+                                        return bus_send_error_reply(connection, message, NULL, -EINVAL);
+
+                                h = strdup(name);
+                                if (!h)
+                                        goto oom;
+
+                                free(data[PROP_STATIC_HOSTNAME]);
+                                data[PROP_STATIC_HOSTNAME] = h;
+                        }
+
+                        r = write_data_static_hostname();
+                        if (r < 0) {
+                                log_error("Failed to write static host name: %s", strerror(-r));
+                                return bus_send_error_reply(connection, message, NULL, r);
+                        }
+
+                        log_info("Changed static host name to '%s'", strempty(data[PROP_STATIC_HOSTNAME]));
+
+                        changed = bus_properties_changed_new(
+                                        "/org/freedesktop/hostname1",
+                                        "org.freedesktop.hostname1",
+                                        "StaticHostname\0");
+                        if (!changed)
+                                goto oom;
+                }
+
+        } else if (dbus_message_is_method_call(message, "org.freedesktop.hostname1", "SetPrettyHostname") ||
+                   dbus_message_is_method_call(message, "org.freedesktop.hostname1", "SetIconName")) {
+
+                const char *name;
+                dbus_bool_t interactive;
+                int k;
+
+                if (!dbus_message_get_args(
+                                    message,
+                                    &error,
+                                    DBUS_TYPE_STRING, &name,
+                                    DBUS_TYPE_BOOLEAN, &interactive,
+                                    DBUS_TYPE_INVALID))
+                        return bus_send_error_reply(connection, message, &error, -EINVAL);
+
+                if (isempty(name))
+                        name = NULL;
+
+                k = streq(dbus_message_get_member(message), "SetPrettyHostname") ? PROP_PRETTY_HOSTNAME : PROP_ICON_NAME;
+
+                if (!streq_ptr(name, data[k])) {
+
+                        /* Since the pretty hostname should always be
+                         * changed at the same time as the static one,
+                         * use the same policy action for both... */
+
+                        r = verify_polkit(connection, message, k == PROP_PRETTY_HOSTNAME ?
+                                          "org.freedesktop.hostname1.set-static-hostname" :
+                                          "org.freedesktop.hostname1.set-machine-info", interactive, NULL, &error);
+                        if (r < 0)
+                                return bus_send_error_reply(connection, message, &error, r);
+
+                        if (isempty(name)) {
+                                free(data[k]);
+                                data[k] = NULL;
+                        } else {
+                                char *h;
+
+                                h = strdup(name);
+                                if (!h)
+                                        goto oom;
+
+                                free(data[k]);
+                                data[k] = h;
+                        }
+
+                        r = write_data_other();
+                        if (r < 0) {
+                                log_error("Failed to write machine info: %s", strerror(-r));
+                                return bus_send_error_reply(connection, message, NULL, r);
+                        }
+
+                        log_info("Changed %s to '%s'", k == PROP_PRETTY_HOSTNAME ? "pretty host name" : "icon name", strempty(data[k]));
+
+                        changed = bus_properties_changed_new(
+                                        "/org/freedesktop/hostname1",
+                                        "org.freedesktop.hostname1",
+                                        k == PROP_PRETTY_HOSTNAME ? "PrettyHostname\0" : "IconName\0");
+                        if (!changed)
+                                goto oom;
+                }
+
+        } else
+                return bus_default_message_handler(connection, message, INTROSPECTION, INTERFACES_LIST, bps);
+
+        if (!(reply = dbus_message_new_method_return(message)))
+                goto oom;
+
+        if (!dbus_connection_send(connection, reply, NULL))
+                goto oom;
+
+        dbus_message_unref(reply);
+        reply = NULL;
+
+        if (changed) {
+
+                if (!dbus_connection_send(connection, changed, NULL))
+                        goto oom;
+
+                dbus_message_unref(changed);
+        }
+
+        return DBUS_HANDLER_RESULT_HANDLED;
+
+oom:
+        if (reply)
+                dbus_message_unref(reply);
+
+        if (changed)
+                dbus_message_unref(changed);
+
+        dbus_error_free(&error);
+
+        return DBUS_HANDLER_RESULT_NEED_MEMORY;
+}
+
+static int connect_bus(DBusConnection **_bus) {
+        static const DBusObjectPathVTable hostname_vtable = {
+                .message_function = hostname_message_handler
+        };
+        DBusError error;
+        DBusConnection *bus = NULL;
+        int r;
+
+        assert(_bus);
+
+        dbus_error_init(&error);
+
+        bus = dbus_bus_get_private(DBUS_BUS_SYSTEM, &error);
+        if (!bus) {
+                log_error("Failed to get system D-Bus connection: %s", bus_error_message(&error));
+                r = -ECONNREFUSED;
+                goto fail;
+        }
+
+        dbus_connection_set_exit_on_disconnect(bus, FALSE);
+
+        if (!dbus_connection_register_object_path(bus, "/org/freedesktop/hostname1", &hostname_vtable, NULL) ||
+            !dbus_connection_add_filter(bus, bus_exit_idle_filter, &remain_until, NULL)) {
+                log_error("Not enough memory");
+                r = -ENOMEM;
+                goto fail;
+        }
+
+        r = dbus_bus_request_name(bus, "org.freedesktop.hostname1", DBUS_NAME_FLAG_DO_NOT_QUEUE, &error);
+        if (dbus_error_is_set(&error)) {
+                log_error("Failed to register name on bus: %s", bus_error_message(&error));
+                r = -EEXIST;
+                goto fail;
+        }
+
+        if (r != DBUS_REQUEST_NAME_REPLY_PRIMARY_OWNER) {
+                log_error("Failed to acquire name.");
+                r = -EEXIST;
+                goto fail;
+        }
+
+        if (_bus)
+                *_bus = bus;
+
+        return 0;
+
+fail:
+        dbus_connection_close(bus);
+        dbus_connection_unref(bus);
+
+        dbus_error_free(&error);
+
+        return r;
+}
+
+int main(int argc, char *argv[]) {
+        int r;
+        DBusConnection *bus = NULL;
+        bool exiting = false;
+
+        log_set_target(LOG_TARGET_AUTO);
+        log_parse_environment();
+        log_open();
+
+        umask(0022);
+
+        if (argc == 2 && streq(argv[1], "--introspect")) {
+                fputs(DBUS_INTROSPECT_1_0_XML_DOCTYPE_DECL_NODE
+                      "<node>\n", stdout);
+                fputs(hostname_interface, stdout);
+                fputs("</node>\n", stdout);
+                return 0;
+        }
+
+        if (argc != 1) {
+                log_error("This program takes no arguments.");
+                r = -EINVAL;
+                goto finish;
+        }
+
+        if (!check_nss())
+                log_warning("Warning: nss-myhostname is not installed. Changing the local hostname might make it unresolveable. Please install nss-myhostname!");
+
+        r = read_data();
+        if (r < 0) {
+                log_error("Failed to read hostname data: %s", strerror(-r));
+                goto finish;
+        }
+
+        r = connect_bus(&bus);
+        if (r < 0)
+                goto finish;
+
+        remain_until = now(CLOCK_MONOTONIC) + DEFAULT_EXIT_USEC;
+        for (;;) {
+
+                if (!dbus_connection_read_write_dispatch(bus, exiting ? -1 : (int) (DEFAULT_EXIT_USEC/USEC_PER_MSEC)))
+                        break;
+
+                if (!exiting && remain_until < now(CLOCK_MONOTONIC)) {
+                        exiting = true;
+                        bus_async_unregister_and_exit(bus, "org.freedesktop.hostname1");
+                }
+        }
+
+        r = 0;
+
+finish:
+        free_data();
+
+        if (bus) {
+                dbus_connection_flush(bus);
+                dbus_connection_close(bus);
+                dbus_connection_unref(bus);
+        }
+
+        return r < 0 ? EXIT_FAILURE : EXIT_SUCCESS;
+}
diff --git a/src/hostname/org.freedesktop.hostname1.conf b/src/hostname/org.freedesktop.hostname1.conf
new file mode 100644 (file)
index 0000000..eb241c0
--- /dev/null
@@ -0,0 +1,27 @@
+<?xml version="1.0"?> <!--*-nxml-*-->
+<!DOCTYPE busconfig PUBLIC "-//freedesktop//DTD D-BUS Bus Configuration 1.0//EN"
+        "http://www.freedesktop.org/standards/dbus/1.0/busconfig.dtd">
+
+<!--
+  This file is part of systemd.
+
+  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.
+-->
+
+<busconfig>
+
+        <policy user="root">
+                <allow own="org.freedesktop.hostname1"/>
+                <allow send_destination="org.freedesktop.hostname1"/>
+                <allow receive_sender="org.freedesktop.hostname1"/>
+        </policy>
+
+        <policy context="default">
+                <allow send_destination="org.freedesktop.hostname1"/>
+                <allow receive_sender="org.freedesktop.hostname1"/>
+        </policy>
+
+</busconfig>
diff --git a/src/hostname/org.freedesktop.hostname1.policy.in b/src/hostname/org.freedesktop.hostname1.policy.in
new file mode 100644 (file)
index 0000000..7d56b22
--- /dev/null
@@ -0,0 +1,49 @@
+<?xml version="1.0" encoding="UTF-8"?> <!--*-nxml-*-->
+<!DOCTYPE policyconfig PUBLIC "-//freedesktop//DTD PolicyKit Policy Configuration 1.0//EN"
+        "http://www.freedesktop.org/standards/PolicyKit/1/policyconfig.dtd">
+
+<!--
+  This file is part of systemd.
+
+  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.
+-->
+
+<policyconfig>
+
+        <vendor>The systemd Project</vendor>
+        <vendor_url>http://www.freedesktop.org/wiki/Software/systemd</vendor_url>
+
+        <action id="org.freedesktop.hostname1.set-hostname">
+                <_description>Set host name</_description>
+                <_message>Authentication is required to set the local host name.</_message>
+                <defaults>
+                        <allow_any>auth_admin_keep</allow_any>
+                        <allow_inactive>auth_admin_keep</allow_inactive>
+                        <allow_active>auth_admin_keep</allow_active>
+                </defaults>
+        </action>
+
+        <action id="org.freedesktop.hostname1.set-static-hostname">
+                <_description>Set static host name</_description>
+                <_message>Authentication is required to set the statically configured local host name, as well as the pretty host name.</_message>
+                <defaults>
+                        <allow_any>auth_admin_keep</allow_any>
+                        <allow_inactive>auth_admin_keep</allow_inactive>
+                        <allow_active>auth_admin_keep</allow_active>
+                </defaults>
+        </action>
+
+        <action id="org.freedesktop.hostname1.set-machine-info">
+                <_description>Set machine information</_description>
+                <_message>Authentication is required to set local machine information.</_message>
+                <defaults>
+                        <allow_any>auth_admin_keep</allow_any>
+                        <allow_inactive>auth_admin_keep</allow_inactive>
+                        <allow_active>auth_admin_keep</allow_active>
+                </defaults>
+        </action>
+
+</policyconfig>
diff --git a/src/hostname/org.freedesktop.hostname1.service b/src/hostname/org.freedesktop.hostname1.service
new file mode 100644 (file)
index 0000000..42e4adb
--- /dev/null
@@ -0,0 +1,12 @@
+#  This file is part of systemd.
+#
+#  systemd is free software; you can redistribute it and/or modify it
+#  under the terms of the GNU General Public License as published by
+#  the Free Software Foundation; either version 2 of the License, or
+#  (at your option) any later version.
+
+[D-BUS Service]
+Name=org.freedesktop.hostname1
+Exec=/bin/false
+User=root
+SystemdService=dbus-org.freedesktop.hostname1.service
diff --git a/src/ima-setup.c b/src/ima-setup.c
new file mode 100644 (file)
index 0000000..03e43dc
--- /dev/null
@@ -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 <http://www.gnu.org/licenses/>.
+***/
+
+#include <unistd.h>
+#include <stdio.h>
+#include <errno.h>
+#include <string.h>
+#include <stdlib.h>
+#include <fcntl.h>
+#include <sys/stat.h>
+#include <sys/mman.h>
+
+#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
new file mode 100644 (file)
index 0000000..7d677cf
--- /dev/null
@@ -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 <http://www.gnu.org/licenses/>.
+***/
+
+int ima_setup(void);
+
+#endif
diff --git a/src/initctl.c b/src/initctl.c
new file mode 100644 (file)
index 0000000..53d03a9
--- /dev/null
@@ -0,0 +1,451 @@
+/*-*- 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 <http://www.gnu.org/licenses/>.
+***/
+
+#include <sys/socket.h>
+#include <sys/types.h>
+#include <assert.h>
+#include <time.h>
+#include <string.h>
+#include <stdio.h>
+#include <errno.h>
+#include <unistd.h>
+#include <sys/poll.h>
+#include <sys/epoll.h>
+#include <sys/un.h>
+#include <fcntl.h>
+#include <ctype.h>
+
+#include <dbus/dbus.h>
+#include <systemd/sd-daemon.h>
+
+#include "util.h"
+#include "log.h"
+#include "list.h"
+#include "initreq.h"
+#include "special.h"
+#include "dbus-common.h"
+#include "def.h"
+
+#define SERVER_FD_MAX 16
+#define TIMEOUT_MSEC ((int) (DEFAULT_EXIT_USEC/USEC_PER_MSEC))
+
+typedef struct Fifo Fifo;
+
+typedef struct Server {
+        int epoll_fd;
+
+        LIST_HEAD(Fifo, fifos);
+        unsigned n_fifos;
+
+        DBusConnection *bus;
+
+        bool quit;
+} Server;
+
+struct Fifo {
+        Server *server;
+
+        int fd;
+
+        struct init_request buffer;
+        size_t bytes_read;
+
+        LIST_FIELDS(Fifo, fifo);
+};
+
+static const char *translate_runlevel(int runlevel, bool *isolate) {
+        static const struct {
+                const int runlevel;
+                const char *special;
+                bool isolate;
+        } table[] = {
+                { '0', SPECIAL_POWEROFF_TARGET,  false },
+                { '1', SPECIAL_RESCUE_TARGET,    true  },
+                { 's', SPECIAL_RESCUE_TARGET,    true  },
+                { 'S', SPECIAL_RESCUE_TARGET,    true  },
+                { '2', SPECIAL_RUNLEVEL2_TARGET, true  },
+                { '3', SPECIAL_RUNLEVEL3_TARGET, true  },
+                { '4', SPECIAL_RUNLEVEL4_TARGET, true  },
+                { '5', SPECIAL_RUNLEVEL5_TARGET, true  },
+                { '6', SPECIAL_REBOOT_TARGET,    false },
+        };
+
+        unsigned i;
+
+        assert(isolate);
+
+        for (i = 0; i < ELEMENTSOF(table); i++)
+                if (table[i].runlevel == runlevel) {
+                        *isolate = table[i].isolate;
+                        if (runlevel == '6' && kexec_loaded())
+                                return SPECIAL_KEXEC_TARGET;
+                        return table[i].special;
+                }
+
+        return NULL;
+}
+
+static void change_runlevel(Server *s, int runlevel) {
+        const char *target;
+        DBusMessage *m = NULL, *reply = NULL;
+        DBusError error;
+        const char *mode;
+        bool isolate = false;
+
+        assert(s);
+
+        dbus_error_init(&error);
+
+        if (!(target = translate_runlevel(runlevel, &isolate))) {
+                log_warning("Got request for unknown runlevel %c, ignoring.", runlevel);
+                goto finish;
+        }
+
+        if (isolate)
+                mode = "isolate";
+        else
+                mode = "replace";
+
+        log_debug("Running request %s/start/%s", target, mode);
+
+        if (!(m = dbus_message_new_method_call("org.freedesktop.systemd1", "/org/freedesktop/systemd1", "org.freedesktop.systemd1.Manager", "StartUnit"))) {
+                log_error("Could not allocate message.");
+                goto finish;
+        }
+
+        if (!dbus_message_append_args(m,
+                                      DBUS_TYPE_STRING, &target,
+                                      DBUS_TYPE_STRING, &mode,
+                                      DBUS_TYPE_INVALID)) {
+                log_error("Could not attach target and flag information to message.");
+                goto finish;
+        }
+
+        if (!(reply = dbus_connection_send_with_reply_and_block(s->bus, m, -1, &error))) {
+                log_error("Failed to start unit: %s", bus_error_message(&error));
+                goto finish;
+        }
+
+finish:
+        if (m)
+                dbus_message_unref(m);
+
+        if (reply)
+                dbus_message_unref(reply);
+
+        dbus_error_free(&error);
+}
+
+static void request_process(Server *s, const struct init_request *req) {
+        assert(s);
+        assert(req);
+
+        if (req->magic != INIT_MAGIC) {
+                log_error("Got initctl request with invalid magic. Ignoring.");
+                return;
+        }
+
+        switch (req->cmd) {
+
+        case INIT_CMD_RUNLVL:
+                if (!isprint(req->runlevel))
+                        log_error("Got invalid runlevel. Ignoring.");
+                else
+                        switch (req->runlevel) {
+
+                        /* we are async anyway, so just use kill for reexec/reload */
+                        case 'u':
+                        case 'U':
+                                if (kill(1, SIGTERM) < 0)
+                                        log_error("kill() failed: %m");
+
+                                /* The bus connection will be
+                                 * terminated if PID 1 is reexecuted,
+                                 * hence let's just exit here, and
+                                 * rely on that we'll be restarted on
+                                 * the next request */
+                                s->quit = true;
+                                break;
+
+                        case 'q':
+                        case 'Q':
+                                if (kill(1, SIGHUP) < 0)
+                                        log_error("kill() failed: %m");
+                                break;
+
+                        default:
+                                change_runlevel(s, req->runlevel);
+                        }
+                return;
+
+        case INIT_CMD_POWERFAIL:
+        case INIT_CMD_POWERFAILNOW:
+        case INIT_CMD_POWEROK:
+                log_warning("Received UPS/power initctl request. This is not implemented in systemd. Upgrade your UPS daemon!");
+                return;
+
+        case INIT_CMD_CHANGECONS:
+                log_warning("Received console change initctl request. This is not implemented in systemd.");
+                return;
+
+        case INIT_CMD_SETENV:
+        case INIT_CMD_UNSETENV:
+                log_warning("Received environment initctl request. This is not implemented in systemd.");
+                return;
+
+        default:
+                log_warning("Received unknown initctl request. Ignoring.");
+                return;
+        }
+}
+
+static int fifo_process(Fifo *f) {
+        ssize_t l;
+
+        assert(f);
+
+        errno = EIO;
+        if ((l = read(f->fd, ((uint8_t*) &f->buffer) + f->bytes_read, sizeof(f->buffer) - f->bytes_read)) <= 0) {
+
+                if (errno == EAGAIN)
+                        return 0;
+
+                log_warning("Failed to read from fifo: %s", strerror(errno));
+                return -1;
+        }
+
+        f->bytes_read += l;
+        assert(f->bytes_read <= sizeof(f->buffer));
+
+        if (f->bytes_read == sizeof(f->buffer)) {
+                request_process(f->server, &f->buffer);
+                f->bytes_read = 0;
+        }
+
+        return 0;
+}
+
+static void fifo_free(Fifo *f) {
+        assert(f);
+
+        if (f->server) {
+                assert(f->server->n_fifos > 0);
+                f->server->n_fifos--;
+                LIST_REMOVE(Fifo, fifo, f->server->fifos, f);
+        }
+
+        if (f->fd >= 0) {
+                if (f->server)
+                        epoll_ctl(f->server->epoll_fd, EPOLL_CTL_DEL, f->fd, NULL);
+
+                close_nointr_nofail(f->fd);
+        }
+
+        free(f);
+}
+
+static void server_done(Server *s) {
+        assert(s);
+
+        while (s->fifos)
+                fifo_free(s->fifos);
+
+        if (s->epoll_fd >= 0)
+                close_nointr_nofail(s->epoll_fd);
+
+        if (s->bus) {
+                dbus_connection_flush(s->bus);
+                dbus_connection_close(s->bus);
+                dbus_connection_unref(s->bus);
+        }
+}
+
+static int server_init(Server *s, unsigned n_sockets) {
+        int r;
+        unsigned i;
+        DBusError error;
+
+        assert(s);
+        assert(n_sockets > 0);
+
+        dbus_error_init(&error);
+
+        zero(*s);
+
+        if ((s->epoll_fd = epoll_create1(EPOLL_CLOEXEC)) < 0) {
+                r = -errno;
+                log_error("Failed to create epoll object: %s", strerror(errno));
+                goto fail;
+        }
+
+        for (i = 0; i < n_sockets; i++) {
+                struct epoll_event ev;
+                Fifo *f;
+                int fd;
+
+                fd = SD_LISTEN_FDS_START+i;
+
+                if ((r = sd_is_fifo(fd, NULL)) < 0) {
+                        log_error("Failed to determine file descriptor type: %s", strerror(-r));
+                        goto fail;
+                }
+
+                if (!r) {
+                        log_error("Wrong file descriptor type.");
+                        r = -EINVAL;
+                        goto fail;
+                }
+
+                if (!(f = new0(Fifo, 1))) {
+                        r = -ENOMEM;
+                        log_error("Failed to create fifo object: %s", strerror(errno));
+                        goto fail;
+                }
+
+                f->fd = -1;
+
+                zero(ev);
+                ev.events = EPOLLIN;
+                ev.data.ptr = f;
+                if (epoll_ctl(s->epoll_fd, EPOLL_CTL_ADD, fd, &ev) < 0) {
+                        r = -errno;
+                        fifo_free(f);
+                        log_error("Failed to add fifo fd to epoll object: %s", strerror(errno));
+                        goto fail;
+                }
+
+                f->fd = fd;
+                LIST_PREPEND(Fifo, fifo, s->fifos, f);
+                f->server = s;
+                s->n_fifos ++;
+        }
+
+        if (bus_connect(DBUS_BUS_SYSTEM, &s->bus, NULL, &error) < 0) {
+                log_error("Failed to get D-Bus connection: %s", bus_error_message(&error));
+                goto fail;
+        }
+
+        return 0;
+
+fail:
+        server_done(s);
+
+        dbus_error_free(&error);
+        return r;
+}
+
+static int process_event(Server *s, struct epoll_event *ev) {
+        int r;
+        Fifo *f;
+
+        assert(s);
+
+        if (!(ev->events & EPOLLIN)) {
+                log_info("Got invalid event from epoll. (3)");
+                return -EIO;
+        }
+
+        f = (Fifo*) ev->data.ptr;
+
+        if ((r = fifo_process(f)) < 0) {
+                log_info("Got error on fifo: %s", strerror(-r));
+                fifo_free(f);
+                return r;
+        }
+
+        return 0;
+}
+
+int main(int argc, char *argv[]) {
+        Server server;
+        int r = EXIT_FAILURE, n;
+
+        if (getppid() != 1) {
+                log_error("This program should be invoked by init only.");
+                return EXIT_FAILURE;
+        }
+
+        if (argc > 1) {
+                log_error("This program does not take arguments.");
+                return EXIT_FAILURE;
+        }
+
+        log_set_target(LOG_TARGET_AUTO);
+        log_parse_environment();
+        log_open();
+
+        umask(0022);
+
+        if ((n = sd_listen_fds(true)) < 0) {
+                log_error("Failed to read listening file descriptors from environment: %s", strerror(-r));
+                return EXIT_FAILURE;
+        }
+
+        if (n <= 0 || n > SERVER_FD_MAX) {
+                log_error("No or too many file descriptors passed.");
+                return EXIT_FAILURE;
+        }
+
+        if (server_init(&server, (unsigned) n) < 0)
+                return EXIT_FAILURE;
+
+        log_debug("systemd-initctl running as pid %lu", (unsigned long) getpid());
+
+        sd_notify(false,
+                  "READY=1\n"
+                  "STATUS=Processing requests...");
+
+        while (!server.quit) {
+                struct epoll_event event;
+                int k;
+
+                if ((k = epoll_wait(server.epoll_fd,
+                                    &event, 1,
+                                    TIMEOUT_MSEC)) < 0) {
+
+                        if (errno == EINTR)
+                                continue;
+
+                        log_error("epoll_wait() failed: %s", strerror(errno));
+                        goto fail;
+                }
+
+                if (k <= 0)
+                        break;
+
+                if (process_event(&server, &event) < 0)
+                        goto fail;
+        }
+
+        r = EXIT_SUCCESS;
+
+        log_debug("systemd-initctl stopped as pid %lu", (unsigned long) getpid());
+
+fail:
+        sd_notify(false,
+                  "STATUS=Shutting down...");
+
+        server_done(&server);
+
+        dbus_shutdown();
+
+        return r;
+}
diff --git a/src/initreq.h b/src/initreq.h
new file mode 100644 (file)
index 0000000..859042c
--- /dev/null
@@ -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 <sys/param.h>
+
+#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/install.c b/src/install.c
new file mode 100644 (file)
index 0000000..9256116
--- /dev/null
@@ -0,0 +1,1953 @@
+/*-*- 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 <http://www.gnu.org/licenses/>.
+***/
+
+#include <errno.h>
+#include <fcntl.h>
+#include <unistd.h>
+#include <string.h>
+#include <fnmatch.h>
+
+#include "util.h"
+#include "hashmap.h"
+#include "set.h"
+#include "path-lookup.h"
+#include "strv.h"
+#include "unit-name.h"
+#include "install.h"
+#include "conf-parser.h"
+
+typedef struct {
+        char *name;
+        char *path;
+
+        char **aliases;
+        char **wanted_by;
+} InstallInfo;
+
+typedef struct {
+        Hashmap *will_install;
+        Hashmap *have_installed;
+} InstallContext;
+
+static int lookup_paths_init_from_scope(LookupPaths *paths, UnitFileScope scope) {
+        assert(paths);
+        assert(scope >= 0);
+        assert(scope < _UNIT_FILE_SCOPE_MAX);
+
+        zero(*paths);
+
+        return lookup_paths_init(paths,
+                                 scope == UNIT_FILE_SYSTEM ? MANAGER_SYSTEM : MANAGER_USER,
+                                 scope == UNIT_FILE_USER);
+}
+
+static int get_config_path(UnitFileScope scope, bool runtime, const char *root_dir, char **ret) {
+        char *p = NULL;
+        int r;
+
+        assert(scope >= 0);
+        assert(scope < _UNIT_FILE_SCOPE_MAX);
+        assert(ret);
+
+        switch (scope) {
+
+        case UNIT_FILE_SYSTEM:
+
+                if (root_dir && runtime)
+                        asprintf(&p, "%s/run/systemd/system", root_dir);
+                else if (runtime)
+                        p = strdup("/run/systemd/system");
+                else if (root_dir)
+                        asprintf(&p, "%s/%s", root_dir, SYSTEM_CONFIG_UNIT_PATH);
+                else
+                        p = strdup(SYSTEM_CONFIG_UNIT_PATH);
+
+                break;
+
+        case UNIT_FILE_GLOBAL:
+
+                if (root_dir)
+                        return -EINVAL;
+
+                if (runtime)
+                        p = strdup("/run/systemd/user");
+                else
+                        p = strdup(USER_CONFIG_UNIT_PATH);
+                break;
+
+        case UNIT_FILE_USER:
+
+                if (root_dir || runtime)
+                        return -EINVAL;
+
+                r = user_config_home(&p);
+                if (r <= 0)
+                        return r < 0 ? r : -ENOENT;
+
+                break;
+
+        default:
+                assert_not_reached("Bad scope");
+        }
+
+        if (!p)
+                return -ENOMEM;
+
+        *ret = p;
+        return 0;
+}
+
+static int add_file_change(
+                UnitFileChange **changes,
+                unsigned *n_changes,
+                UnitFileChangeType type,
+                const char *path,
+                const char *source) {
+
+        UnitFileChange *c;
+        unsigned i;
+
+        assert(path);
+        assert(!changes == !n_changes);
+
+        if (!changes)
+                return 0;
+
+        c = realloc(*changes, (*n_changes + 1) * sizeof(UnitFileChange));
+        if (!c)
+                return -ENOMEM;
+
+        *changes = c;
+        i = *n_changes;
+
+        c[i].type = type;
+        c[i].path = strdup(path);
+        if (!c[i].path)
+                return -ENOMEM;
+
+        if (source) {
+                c[i].source = strdup(source);
+                if (!c[i].source) {
+                        free(c[i].path);
+                        return -ENOMEM;
+                }
+        } else
+                c[i].source = NULL;
+
+        *n_changes = i+1;
+        return 0;
+}
+
+static int mark_symlink_for_removal(
+                Set **remove_symlinks_to,
+                const char *p) {
+
+        char *n;
+        int r;
+
+        assert(p);
+
+        r = set_ensure_allocated(remove_symlinks_to, string_hash_func, string_compare_func);
+        if (r < 0)
+                return r;
+
+        n = strdup(p);
+        if (!n)
+                return -ENOMEM;
+
+        path_kill_slashes(n);
+
+        r = set_put(*remove_symlinks_to, n);
+        if (r < 0) {
+                free(n);
+                return r == -EEXIST ? 0 : r;
+        }
+
+        return 0;
+}
+
+static int remove_marked_symlinks_fd(
+                Set *remove_symlinks_to,
+                int fd,
+                const char *path,
+                const char *config_path,
+                bool *deleted,
+                UnitFileChange **changes,
+                unsigned *n_changes) {
+
+        int r = 0;
+        DIR *d;
+        struct dirent buffer, *de;
+
+        assert(remove_symlinks_to);
+        assert(fd >= 0);
+        assert(path);
+        assert(config_path);
+        assert(deleted);
+
+        d = fdopendir(fd);
+        if (!d) {
+                close_nointr_nofail(fd);
+                return -errno;
+        }
+
+        rewinddir(d);
+
+        for (;;) {
+                int k;
+
+                k = readdir_r(d, &buffer, &de);
+                if (k != 0) {
+                        r = -errno;
+                        break;
+                }
+
+                if (!de)
+                        break;
+
+                if (ignore_file(de->d_name))
+                        continue;
+
+                dirent_ensure_type(d, de);
+
+                if (de->d_type == DT_DIR) {
+                        int nfd, q;
+                        char *p;
+
+                        nfd = openat(fd, de->d_name, O_RDONLY|O_NONBLOCK|O_DIRECTORY|O_CLOEXEC|O_NOFOLLOW);
+                        if (nfd < 0) {
+                                if (errno == ENOENT)
+                                        continue;
+
+                                if (r == 0)
+                                        r = -errno;
+                                continue;
+                        }
+
+                        p = path_make_absolute(de->d_name, path);
+                        if (!p) {
+                                close_nointr_nofail(nfd);
+                                r = -ENOMEM;
+                                break;
+                        }
+
+                        /* This will close nfd, regardless whether it succeeds or not */
+                        q = remove_marked_symlinks_fd(remove_symlinks_to, nfd, p, config_path, deleted, changes, n_changes);
+                        free(p);
+
+                        if (r == 0)
+                                r = q;
+
+                } else if (de->d_type == DT_LNK) {
+                        char *p, *dest;
+                        int q;
+                        bool found;
+
+                        p = path_make_absolute(de->d_name, path);
+                        if (!p) {
+                                r = -ENOMEM;
+                                break;
+                        }
+
+                        q = readlink_and_canonicalize(p, &dest);
+                        if (q < 0) {
+                                free(p);
+
+                                if (q == -ENOENT)
+                                        continue;
+
+                                if (r == 0)
+                                        r = q;
+                                continue;
+                        }
+
+                        found =
+                                set_get(remove_symlinks_to, dest) ||
+                                set_get(remove_symlinks_to, file_name_from_path(dest));
+
+                        if (found) {
+
+                                if (unlink(p) < 0 && errno != ENOENT) {
+
+                                        if (r == 0)
+                                                r = -errno;
+                                } else {
+                                        rmdir_parents(p, config_path);
+                                        path_kill_slashes(p);
+
+                                        add_file_change(changes, n_changes, UNIT_FILE_UNLINK, p, NULL);
+
+                                        if (!set_get(remove_symlinks_to, p)) {
+
+                                                q = mark_symlink_for_removal(&remove_symlinks_to, p);
+                                                if (q < 0) {
+                                                        if (r == 0)
+                                                                r = q;
+                                                } else
+                                                        *deleted = true;
+                                        }
+                                }
+                        }
+
+                        free(p);
+                        free(dest);
+                }
+        }
+
+        closedir(d);
+
+        return r;
+}
+
+static int remove_marked_symlinks(
+                Set *remove_symlinks_to,
+                const char *config_path,
+                UnitFileChange **changes,
+                unsigned *n_changes) {
+
+        int fd, r = 0;
+        bool deleted;
+
+        assert(config_path);
+
+        if (set_size(remove_symlinks_to) <= 0)
+                return 0;
+
+        fd = open(config_path, O_RDONLY|O_NONBLOCK|O_DIRECTORY|O_CLOEXEC|O_NOFOLLOW);
+        if (fd < 0)
+                return -errno;
+
+        do {
+                int q, cfd;
+                deleted = false;
+
+                cfd = dup(fd);
+                if (cfd < 0) {
+                        r = -errno;
+                        break;
+                }
+
+                /* This takes possession of cfd and closes it */
+                q = remove_marked_symlinks_fd(remove_symlinks_to, cfd, config_path, config_path, &deleted, changes, n_changes);
+                if (r == 0)
+                        r = q;
+        } while (deleted);
+
+        close_nointr_nofail(fd);
+
+        return r;
+}
+
+static int find_symlinks_fd(
+                const char *name,
+                int fd,
+                const char *path,
+                const char *config_path,
+                bool *same_name_link) {
+
+        int r = 0;
+        DIR *d;
+        struct dirent buffer, *de;
+
+        assert(name);
+        assert(fd >= 0);
+        assert(path);
+        assert(config_path);
+        assert(same_name_link);
+
+        d = fdopendir(fd);
+        if (!d) {
+                close_nointr_nofail(fd);
+                return -errno;
+        }
+
+        for (;;) {
+                int k;
+
+                k = readdir_r(d, &buffer, &de);
+                if (k != 0) {
+                        r = -errno;
+                        break;
+                }
+
+                if (!de)
+                        break;
+
+                if (ignore_file(de->d_name))
+                        continue;
+
+                dirent_ensure_type(d, de);
+
+                if (de->d_type == DT_DIR) {
+                        int nfd, q;
+                        char *p;
+
+                        nfd = openat(fd, de->d_name, O_RDONLY|O_NONBLOCK|O_DIRECTORY|O_CLOEXEC|O_NOFOLLOW);
+                        if (nfd < 0) {
+                                if (errno == ENOENT)
+                                        continue;
+
+                                if (r == 0)
+                                        r = -errno;
+                                continue;
+                        }
+
+                        p = path_make_absolute(de->d_name, path);
+                        if (!p) {
+                                close_nointr_nofail(nfd);
+                                r = -ENOMEM;
+                                break;
+                        }
+
+                        /* This will close nfd, regardless whether it succeeds or not */
+                        q = find_symlinks_fd(name, nfd, p, config_path, same_name_link);
+                        free(p);
+
+                        if (q > 0) {
+                                r = 1;
+                                break;
+                        }
+
+                        if (r == 0)
+                                r = q;
+
+                } else if (de->d_type == DT_LNK) {
+                        char *p, *dest;
+                        bool found_path, found_dest, b = false;
+                        int q;
+
+                        /* Acquire symlink name */
+                        p = path_make_absolute(de->d_name, path);
+                        if (!p) {
+                                r = -ENOMEM;
+                                break;
+                        }
+
+                        /* Acquire symlink destination */
+                        q = readlink_and_canonicalize(p, &dest);
+                        if (q < 0) {
+                                free(p);
+
+                                if (q == -ENOENT)
+                                        continue;
+
+                                if (r == 0)
+                                        r = q;
+                                continue;
+                        }
+
+                        /* Check if the symlink itself matches what we
+                         * are looking for */
+                        if (path_is_absolute(name))
+                                found_path = path_equal(p, name);
+                        else
+                                found_path = streq(de->d_name, name);
+
+                        /* Check if what the symlink points to
+                         * matches what we are looking for */
+                        if (path_is_absolute(name))
+                                found_dest = path_equal(dest, name);
+                        else
+                                found_dest = streq(file_name_from_path(dest), name);
+
+                        free(dest);
+
+                        if (found_path && found_dest) {
+                                char *t;
+
+                                /* Filter out same name links in the main
+                                 * config path */
+                                t = path_make_absolute(name, config_path);
+                                if (!t) {
+                                        free(p);
+                                        r = -ENOMEM;
+                                        break;
+                                }
+
+                                b = path_equal(t, p);
+                                free(t);
+                        }
+
+                        free(p);
+
+                        if (b)
+                                *same_name_link = true;
+                        else if (found_path || found_dest) {
+                                r = 1;
+                                break;
+                        }
+                }
+        }
+
+        closedir(d);
+
+        return r;
+}
+
+static int find_symlinks(
+                const char *name,
+                const char *config_path,
+                bool *same_name_link) {
+
+        int fd;
+
+        assert(name);
+        assert(config_path);
+        assert(same_name_link);
+
+        fd = open(config_path, O_RDONLY|O_NONBLOCK|O_DIRECTORY|O_CLOEXEC|O_NOFOLLOW);
+        if (fd < 0)
+                return -errno;
+
+        /* This takes possession of fd and closes it */
+        return find_symlinks_fd(name, fd, config_path, config_path, same_name_link);
+}
+
+static int find_symlinks_in_scope(
+                UnitFileScope scope,
+                const char *root_dir,
+                const char *name,
+                UnitFileState *state) {
+
+        int r;
+        char *path;
+        bool same_name_link_runtime = false, same_name_link = false;
+
+        assert(scope >= 0);
+        assert(scope < _UNIT_FILE_SCOPE_MAX);
+        assert(name);
+
+        if (scope == UNIT_FILE_SYSTEM || scope == UNIT_FILE_GLOBAL) {
+
+                /* First look in runtime config path */
+                r = get_config_path(scope, true, root_dir, &path);
+                if (r < 0)
+                        return r;
+
+                r = find_symlinks(name, path, &same_name_link_runtime);
+                free(path);
+
+                if (r < 0)
+                        return r;
+                else if (r > 0) {
+                        *state = UNIT_FILE_ENABLED_RUNTIME;
+                        return r;
+                }
+        }
+
+        /* Then look in the normal config path */
+        r = get_config_path(scope, false, root_dir, &path);
+        if (r < 0)
+                return r;
+
+        r = find_symlinks(name, path, &same_name_link);
+        free(path);
+
+        if (r < 0)
+                return r;
+        else if (r > 0) {
+                *state = UNIT_FILE_ENABLED;
+                return r;
+        }
+
+        /* Hmm, we didn't find it, but maybe we found the same name
+         * link? */
+        if (same_name_link_runtime) {
+                *state = UNIT_FILE_LINKED_RUNTIME;
+                return 1;
+        } else if (same_name_link) {
+                *state = UNIT_FILE_LINKED;
+                return 1;
+        }
+
+        return 0;
+}
+
+int unit_file_mask(
+                UnitFileScope scope,
+                bool runtime,
+                const char *root_dir,
+                char *files[],
+                bool force,
+                UnitFileChange **changes,
+                unsigned *n_changes) {
+
+        char **i, *prefix;
+        int r;
+
+        assert(scope >= 0);
+        assert(scope < _UNIT_FILE_SCOPE_MAX);
+
+        r = get_config_path(scope, runtime, root_dir, &prefix);
+        if (r < 0)
+                return r;
+
+        STRV_FOREACH(i, files) {
+                char *path;
+
+                if (!unit_name_is_valid_no_type(*i, true)) {
+                        if (r == 0)
+                                r = -EINVAL;
+                        continue;
+                }
+
+                path = path_make_absolute(*i, prefix);
+                if (!path) {
+                        r = -ENOMEM;
+                        break;
+                }
+
+                if (symlink("/dev/null", path) >= 0) {
+                        add_file_change(changes, n_changes, UNIT_FILE_SYMLINK, path, "/dev/null");
+
+                        free(path);
+                        continue;
+                }
+
+                if (errno == EEXIST) {
+
+                        if (null_or_empty_path(path) > 0) {
+                                free(path);
+                                continue;
+                        }
+
+                        if (force) {
+                                unlink(path);
+
+                                if (symlink("/dev/null", path) >= 0) {
+
+                                        add_file_change(changes, n_changes, UNIT_FILE_UNLINK, path, NULL);
+                                        add_file_change(changes, n_changes, UNIT_FILE_SYMLINK, path, "/dev/null");
+
+                                        free(path);
+                                        continue;
+                                }
+                        }
+
+                        if (r == 0)
+                                r = -EEXIST;
+                } else {
+                        if (r == 0)
+                                r = -errno;
+                }
+
+                free(path);
+        }
+
+        free(prefix);
+
+        return r;
+}
+
+int unit_file_unmask(
+                UnitFileScope scope,
+                bool runtime,
+                const char *root_dir,
+                char *files[],
+                UnitFileChange **changes,
+                unsigned *n_changes) {
+
+        char **i, *config_path = NULL;
+        int r, q;
+        Set *remove_symlinks_to = NULL;
+
+        assert(scope >= 0);
+        assert(scope < _UNIT_FILE_SCOPE_MAX);
+
+        r = get_config_path(scope, runtime, root_dir, &config_path);
+        if (r < 0)
+                goto finish;
+
+        STRV_FOREACH(i, files) {
+                char *path;
+
+                if (!unit_name_is_valid_no_type(*i, true)) {
+                        if (r == 0)
+                                r = -EINVAL;
+                        continue;
+                }
+
+                path = path_make_absolute(*i, config_path);
+                if (!path) {
+                        r = -ENOMEM;
+                        break;
+                }
+
+                q = null_or_empty_path(path);
+                if (q > 0) {
+                        if (unlink(path) >= 0) {
+                                mark_symlink_for_removal(&remove_symlinks_to, path);
+                                add_file_change(changes, n_changes, UNIT_FILE_UNLINK, path, NULL);
+
+                                free(path);
+                                continue;
+                        }
+
+                        q = -errno;
+                }
+
+                if (q != -ENOENT && r == 0)
+                        r = q;
+
+                free(path);
+        }
+
+
+finish:
+        q = remove_marked_symlinks(remove_symlinks_to, config_path, changes, n_changes);
+        if (r == 0)
+                r = q;
+
+        set_free_free(remove_symlinks_to);
+        free(config_path);
+
+        return r;
+}
+
+int unit_file_link(
+                UnitFileScope scope,
+                bool runtime,
+                const char *root_dir,
+                char *files[],
+                bool force,
+                UnitFileChange **changes,
+                unsigned *n_changes) {
+
+        LookupPaths paths;
+        char **i, *config_path = NULL;
+        int r, q;
+
+        assert(scope >= 0);
+        assert(scope < _UNIT_FILE_SCOPE_MAX);
+
+        zero(paths);
+
+        r = lookup_paths_init_from_scope(&paths, scope);
+        if (r < 0)
+                return r;
+
+        r = get_config_path(scope, runtime, root_dir, &config_path);
+        if (r < 0)
+                goto finish;
+
+        STRV_FOREACH(i, files) {
+                char *path, *fn;
+                struct stat st;
+
+                fn = file_name_from_path(*i);
+
+                if (!path_is_absolute(*i) ||
+                    !unit_name_is_valid_no_type(fn, true)) {
+                        if (r == 0)
+                                r = -EINVAL;
+                        continue;
+                }
+
+                if (lstat(*i, &st) < 0) {
+                        if (r == 0)
+                                r = -errno;
+                        continue;
+                }
+
+                if (!S_ISREG(st.st_mode)) {
+                        r = -ENOENT;
+                        continue;
+                }
+
+                q = in_search_path(*i, paths.unit_path);
+                if (q < 0) {
+                        r = q;
+                        break;
+                }
+
+                if (q > 0)
+                        continue;
+
+                path = path_make_absolute(fn, config_path);
+                if (!path) {
+                        r = -ENOMEM;
+                        break;
+                }
+
+                if (symlink(*i, path) >= 0) {
+                        add_file_change(changes, n_changes, UNIT_FILE_SYMLINK, path, *i);
+
+                        free(path);
+                        continue;
+                }
+
+                if (errno == EEXIST) {
+                        char *dest = NULL;
+
+                        q = readlink_and_make_absolute(path, &dest);
+
+                        if (q < 0 && errno != ENOENT) {
+                                free(path);
+
+                                if (r == 0)
+                                        r = q;
+
+                                continue;
+                        }
+
+                        if (q >= 0 && path_equal(dest, *i)) {
+                                free(dest);
+                                free(path);
+                                continue;
+                        }
+
+                        free(dest);
+
+                        if (force) {
+                                unlink(path);
+
+                                if (symlink(*i, path) >= 0) {
+
+                                        add_file_change(changes, n_changes, UNIT_FILE_UNLINK, path, NULL);
+                                        add_file_change(changes, n_changes, UNIT_FILE_SYMLINK, path, *i);
+
+                                        free(path);
+                                        continue;
+                                }
+                        }
+
+                        if (r == 0)
+                                r = -EEXIST;
+                } else {
+                        if (r == 0)
+                                r = -errno;
+                }
+
+                free(path);
+        }
+
+                finish:
+        lookup_paths_free(&paths);
+        free(config_path);
+
+        return r;
+}
+
+void unit_file_list_free(Hashmap *h) {
+        UnitFileList *i;
+
+        while ((i = hashmap_steal_first(h))) {
+                free(i->path);
+                free(i);
+        }
+
+        hashmap_free(h);
+}
+
+void unit_file_changes_free(UnitFileChange *changes, unsigned n_changes) {
+        unsigned i;
+
+        assert(changes || n_changes == 0);
+
+        if (!changes)
+                return;
+
+        for (i = 0; i < n_changes; i++) {
+                free(changes[i].path);
+                free(changes[i].source);
+        }
+
+        free(changes);
+}
+
+static void install_info_free(InstallInfo *i) {
+        assert(i);
+
+        free(i->name);
+        free(i->path);
+        strv_free(i->aliases);
+        strv_free(i->wanted_by);
+        free(i);
+}
+
+static void install_info_hashmap_free(Hashmap *m) {
+        InstallInfo *i;
+
+        if (!m)
+                return;
+
+        while ((i = hashmap_steal_first(m)))
+                install_info_free(i);
+
+        hashmap_free(m);
+}
+
+static void install_context_done(InstallContext *c) {
+        assert(c);
+
+        install_info_hashmap_free(c->will_install);
+        install_info_hashmap_free(c->have_installed);
+
+        c->will_install = c->have_installed = NULL;
+}
+
+static int install_info_add(
+                InstallContext *c,
+                const char *name,
+                const char *path) {
+        InstallInfo *i = NULL;
+        int r;
+
+        assert(c);
+        assert(name || path);
+
+        if (!name)
+                name = file_name_from_path(path);
+
+        if (!unit_name_is_valid_no_type(name, true))
+                return -EINVAL;
+
+        if (hashmap_get(c->have_installed, name) ||
+            hashmap_get(c->will_install, name))
+                return 0;
+
+        r = hashmap_ensure_allocated(&c->will_install, string_hash_func, string_compare_func);
+        if (r < 0)
+                return r;
+
+        i = new0(InstallInfo, 1);
+        if (!i)
+                return -ENOMEM;
+
+        i->name = strdup(name);
+        if (!i->name) {
+                r = -ENOMEM;
+                goto fail;
+        }
+
+        if (path) {
+                i->path = strdup(path);
+                if (!i->path) {
+                        r = -ENOMEM;
+                        goto fail;
+                }
+        }
+
+        r = hashmap_put(c->will_install, i->name, i);
+        if (r < 0)
+                goto fail;
+
+        return 0;
+
+fail:
+        if (i)
+                install_info_free(i);
+
+        return r;
+}
+
+static int install_info_add_auto(
+                InstallContext *c,
+                const char *name_or_path) {
+
+        assert(c);
+        assert(name_or_path);
+
+        if (path_is_absolute(name_or_path))
+                return install_info_add(c, NULL, name_or_path);
+        else
+                return install_info_add(c, name_or_path, NULL);
+}
+
+static int config_parse_also(
+                const char *filename,
+                unsigned line,
+                const char *section,
+                const char *lvalue,
+                int ltype,
+                const char *rvalue,
+                void *data,
+                void *userdata) {
+
+        char *w;
+        size_t l;
+        char *state;
+        InstallContext *c = data;
+
+        assert(filename);
+        assert(lvalue);
+        assert(rvalue);
+
+        FOREACH_WORD_QUOTED(w, l, rvalue, state) {
+                char *n;
+                int r;
+
+                n = strndup(w, l);
+                if (!n)
+                        return -ENOMEM;
+
+                r = install_info_add(c, n, NULL);
+                if (r < 0) {
+                        free(n);
+                        return r;
+                }
+
+                free(n);
+        }
+
+        return 0;
+}
+
+static int unit_file_load(
+                InstallContext *c,
+                InstallInfo *info,
+                const char *path,
+                bool allow_symlink) {
+
+        const ConfigTableItem items[] = {
+                { "Install", "Alias",    config_parse_strv, 0, &info->aliases   },
+                { "Install", "WantedBy", config_parse_strv, 0, &info->wanted_by },
+                { "Install", "Also",     config_parse_also, 0, c                },
+                { NULL, NULL, NULL, 0, NULL }
+        };
+
+        int fd;
+        FILE *f;
+        int r;
+
+        assert(c);
+        assert(info);
+        assert(path);
+
+        fd = open(path, O_RDONLY|O_CLOEXEC|O_NOCTTY|(allow_symlink ? 0 : O_NOFOLLOW));
+        if (fd < 0)
+                return -errno;
+
+        f = fdopen(fd, "re");
+        if (!f) {
+                close_nointr_nofail(fd);
+                return -ENOMEM;
+        }
+
+        r = config_parse(path, f, NULL, config_item_table_lookup, (void*) items, true, info);
+        fclose(f);
+        if (r < 0)
+                return r;
+
+        return strv_length(info->aliases) + strv_length(info->wanted_by);
+}
+
+static int unit_file_search(
+                InstallContext *c,
+                InstallInfo *info,
+                LookupPaths *paths,
+                const char *root_dir,
+                bool allow_symlink) {
+
+        char **p;
+        int r;
+
+        assert(c);
+        assert(info);
+        assert(paths);
+
+        if (info->path)
+                return unit_file_load(c, info, info->path, allow_symlink);
+
+        assert(info->name);
+
+        STRV_FOREACH(p, paths->unit_path) {
+                char *path = NULL;
+
+                if (isempty(root_dir))
+                        asprintf(&path, "%s/%s", *p, info->name);
+                else
+                        asprintf(&path, "%s/%s/%s", root_dir, *p, info->name);
+
+                if (!path)
+                        return -ENOMEM;
+
+                r = unit_file_load(c, info, path, allow_symlink);
+
+                if (r >= 0)
+                        info->path = path;
+                else
+                        free(path);
+
+                if (r != -ENOENT && r != -ELOOP)
+                        return r;
+        }
+
+        return -ENOENT;
+}
+
+static int unit_file_can_install(
+                LookupPaths *paths,
+                const char *root_dir,
+                const char *name,
+                bool allow_symlink) {
+
+        InstallContext c;
+        InstallInfo *i;
+        int r;
+
+        assert(paths);
+        assert(name);
+
+        zero(c);
+
+        r = install_info_add_auto(&c, name);
+        if (r < 0)
+                return r;
+
+        assert_se(i = hashmap_first(c.will_install));
+
+        r = unit_file_search(&c, i, paths, root_dir, allow_symlink);
+
+        if (r >= 0)
+                r = strv_length(i->aliases) + strv_length(i->wanted_by);
+
+        install_context_done(&c);
+
+        return r;
+}
+
+static int create_symlink(
+                const char *old_path,
+                const char *new_path,
+                bool force,
+                UnitFileChange **changes,
+                unsigned *n_changes) {
+
+        char *dest;
+        int r;
+
+        assert(old_path);
+        assert(new_path);
+
+        mkdir_parents(new_path, 0755);
+
+        if (symlink(old_path, new_path) >= 0) {
+                add_file_change(changes, n_changes, UNIT_FILE_SYMLINK, new_path, old_path);
+                return 0;
+        }
+
+        if (errno != EEXIST)
+                return -errno;
+
+        r = readlink_and_make_absolute(new_path, &dest);
+        if (r < 0)
+                return r;
+
+        if (path_equal(dest, old_path)) {
+                free(dest);
+                return 0;
+        }
+
+        free(dest);
+
+        if (force)
+                return -EEXIST;
+
+        unlink(new_path);
+
+        if (symlink(old_path, new_path) >= 0) {
+                add_file_change(changes, n_changes, UNIT_FILE_UNLINK, new_path, NULL);
+                add_file_change(changes, n_changes, UNIT_FILE_SYMLINK, new_path, old_path);
+                return 0;
+        }
+
+        return -errno;
+}
+
+static int install_info_symlink_alias(
+                InstallInfo *i,
+                const char *config_path,
+                bool force,
+                UnitFileChange **changes,
+                unsigned *n_changes) {
+
+        char **s;
+        int r = 0, q;
+
+        assert(i);
+        assert(config_path);
+
+        STRV_FOREACH(s, i->aliases) {
+                char *alias_path;
+
+                alias_path = path_make_absolute(*s, config_path);
+
+                if (!alias_path)
+                        return -ENOMEM;
+
+                q = create_symlink(i->path, alias_path, force, changes, n_changes);
+                free(alias_path);
+
+                if (r == 0)
+                        r = q;
+        }
+
+        return r;
+}
+
+static int install_info_symlink_wants(
+                InstallInfo *i,
+                const char *config_path,
+                bool force,
+                UnitFileChange **changes,
+                unsigned *n_changes) {
+
+        char **s;
+        int r = 0, q;
+
+        assert(i);
+        assert(config_path);
+
+        STRV_FOREACH(s, i->wanted_by) {
+                char *path;
+
+                if (!unit_name_is_valid_no_type(*s, true)) {
+                        r = -EINVAL;
+                        continue;
+                }
+
+                if (asprintf(&path, "%s/%s.wants/%s", config_path, *s, i->name) < 0)
+                        return -ENOMEM;
+
+                q = create_symlink(i->path, path, force, changes, n_changes);
+                free(path);
+
+                if (r == 0)
+                        r = q;
+        }
+
+        return r;
+}
+
+static int install_info_symlink_link(
+                InstallInfo *i,
+                LookupPaths *paths,
+                const char *config_path,
+                bool force,
+                UnitFileChange **changes,
+                unsigned *n_changes) {
+
+        int r;
+        char *path;
+
+        assert(i);
+        assert(paths);
+        assert(config_path);
+        assert(i->path);
+
+        r = in_search_path(i->path, paths->unit_path);
+        if (r != 0)
+                return r;
+
+        if (asprintf(&path, "%s/%s", config_path, i->name) < 0)
+                return -ENOMEM;
+
+        r = create_symlink(i->path, path, force, changes, n_changes);
+        free(path);
+
+        return r;
+}
+
+static int install_info_apply(
+                InstallInfo *i,
+                LookupPaths *paths,
+                const char *config_path,
+                bool force,
+                UnitFileChange **changes,
+                unsigned *n_changes) {
+
+        int r, q;
+
+        assert(i);
+        assert(paths);
+        assert(config_path);
+
+        r = install_info_symlink_alias(i, config_path, force, changes, n_changes);
+
+        q = install_info_symlink_wants(i, config_path, force, changes, n_changes);
+        if (r == 0)
+                r = q;
+
+        q = install_info_symlink_link(i, paths, config_path, force, changes, n_changes);
+        if (r == 0)
+                r = q;
+
+        return r;
+}
+
+static int install_context_apply(
+                InstallContext *c,
+                LookupPaths *paths,
+                const char *config_path,
+                const char *root_dir,
+                bool force,
+                UnitFileChange **changes,
+                unsigned *n_changes) {
+
+        InstallInfo *i;
+        int r = 0, q;
+
+        assert(c);
+        assert(paths);
+        assert(config_path);
+
+        while ((i = hashmap_first(c->will_install))) {
+
+                q = hashmap_ensure_allocated(&c->have_installed, string_hash_func, string_compare_func);
+                if (q < 0)
+                        return q;
+
+                assert_se(hashmap_move_one(c->have_installed, c->will_install, i->name) == 0);
+
+                q = unit_file_search(c, i, paths, root_dir, false);
+                if (q < 0) {
+                        if (r >= 0)
+                                r = q;
+
+                        return r;
+                } else if (r >= 0)
+                        r += q;
+
+                q = install_info_apply(i, paths, config_path, force, changes, n_changes);
+                if (r >= 0 && q < 0)
+                        r = q;
+        }
+
+        return r;
+}
+
+static int install_context_mark_for_removal(
+                InstallContext *c,
+                LookupPaths *paths,
+                Set **remove_symlinks_to,
+                const char *config_path,
+                const char *root_dir) {
+
+        InstallInfo *i;
+        int r = 0, q;
+
+        assert(c);
+        assert(paths);
+        assert(config_path);
+
+        /* Marks all items for removal */
+
+        while ((i = hashmap_first(c->will_install))) {
+
+                q = hashmap_ensure_allocated(&c->have_installed, string_hash_func, string_compare_func);
+                if (q < 0)
+                        return q;
+
+                assert_se(hashmap_move_one(c->have_installed, c->will_install, i->name) == 0);
+
+                q = unit_file_search(c, i, paths, root_dir, false);
+                if (q < 0) {
+                        if (r >= 0)
+                                r = q;
+
+                        return r;
+                } else if (r >= 0)
+                        r += q;
+
+                q = mark_symlink_for_removal(remove_symlinks_to, i->name);
+                if (r >= 0 && q < 0)
+                        r = q;
+        }
+
+        return r;
+}
+
+int unit_file_enable(
+                UnitFileScope scope,
+                bool runtime,
+                const char *root_dir,
+                char *files[],
+                bool force,
+                UnitFileChange **changes,
+                unsigned *n_changes) {
+
+        LookupPaths paths;
+        InstallContext c;
+        char **i, *config_path = NULL;
+        int r;
+
+        assert(scope >= 0);
+        assert(scope < _UNIT_FILE_SCOPE_MAX);
+
+        zero(paths);
+        zero(c);
+
+        r = lookup_paths_init_from_scope(&paths, scope);
+        if (r < 0)
+                return r;
+
+        r = get_config_path(scope, runtime, root_dir, &config_path);
+        if (r < 0)
+                goto finish;
+
+        STRV_FOREACH(i, files) {
+                r = install_info_add_auto(&c, *i);
+                if (r < 0)
+                        goto finish;
+        }
+
+        /* This will return the number of symlink rules that were
+        supposed to be created, not the ones actually created. This is
+        useful to determine whether the passed files hat any
+        installation data at all. */
+        r = install_context_apply(&c, &paths, config_path, root_dir, force, changes, n_changes);
+
+finish:
+        install_context_done(&c);
+        lookup_paths_free(&paths);
+        free(config_path);
+
+        return r;
+}
+
+int unit_file_disable(
+                UnitFileScope scope,
+                bool runtime,
+                const char *root_dir,
+                char *files[],
+                UnitFileChange **changes,
+                unsigned *n_changes) {
+
+        LookupPaths paths;
+        InstallContext c;
+        char **i, *config_path = NULL;
+        Set *remove_symlinks_to = NULL;
+        int r, q;
+
+        assert(scope >= 0);
+        assert(scope < _UNIT_FILE_SCOPE_MAX);
+
+        zero(paths);
+        zero(c);
+
+        r = lookup_paths_init_from_scope(&paths, scope);
+        if (r < 0)
+                return r;
+
+        r = get_config_path(scope, runtime, root_dir, &config_path);
+        if (r < 0)
+                goto finish;
+
+        STRV_FOREACH(i, files) {
+                r = install_info_add_auto(&c, *i);
+                if (r < 0)
+                        goto finish;
+        }
+
+        r = install_context_mark_for_removal(&c, &paths, &remove_symlinks_to, config_path, root_dir);
+
+        q = remove_marked_symlinks(remove_symlinks_to, config_path, changes, n_changes);
+        if (r == 0)
+                r = q;
+
+finish:
+        install_context_done(&c);
+        lookup_paths_free(&paths);
+        set_free_free(remove_symlinks_to);
+        free(config_path);
+
+        return r;
+}
+
+int unit_file_reenable(
+                UnitFileScope scope,
+                bool runtime,
+                const char *root_dir,
+                char *files[],
+                bool force,
+                UnitFileChange **changes,
+                unsigned *n_changes) {
+
+        LookupPaths paths;
+        InstallContext c;
+        char **i, *config_path = NULL;
+        Set *remove_symlinks_to = NULL;
+        int r, q;
+
+        assert(scope >= 0);
+        assert(scope < _UNIT_FILE_SCOPE_MAX);
+
+        zero(paths);
+        zero(c);
+
+        r = lookup_paths_init_from_scope(&paths, scope);
+        if (r < 0)
+                return r;
+
+        r = get_config_path(scope, runtime, root_dir, &config_path);
+        if (r < 0)
+                goto finish;
+
+        STRV_FOREACH(i, files) {
+                r = mark_symlink_for_removal(&remove_symlinks_to, *i);
+                if (r < 0)
+                        goto finish;
+
+                r = install_info_add_auto(&c, *i);
+                if (r < 0)
+                        goto finish;
+        }
+
+        r = remove_marked_symlinks(remove_symlinks_to, config_path, changes, n_changes);
+
+        /* Returns number of symlinks that where supposed to be installed. */
+        q = install_context_apply(&c, &paths, config_path, root_dir, force, changes, n_changes);
+        if (r == 0)
+                r = q;
+
+finish:
+        lookup_paths_free(&paths);
+        install_context_done(&c);
+        set_free_free(remove_symlinks_to);
+        free(config_path);
+
+        return r;
+}
+
+UnitFileState unit_file_get_state(
+                UnitFileScope scope,
+                const char *root_dir,
+                const char *name) {
+
+        LookupPaths paths;
+        UnitFileState state = _UNIT_FILE_STATE_INVALID;
+        char **i, *path = NULL;
+        int r;
+
+        assert(scope >= 0);
+        assert(scope < _UNIT_FILE_SCOPE_MAX);
+        assert(name);
+
+        zero(paths);
+
+        if (root_dir && scope != UNIT_FILE_SYSTEM)
+                return -EINVAL;
+
+        if (!unit_name_is_valid_no_type(name, true))
+                return -EINVAL;
+
+        r = lookup_paths_init_from_scope(&paths, scope);
+        if (r < 0)
+                return r;
+
+        STRV_FOREACH(i, paths.unit_path) {
+                struct stat st;
+
+                free(path);
+                path = NULL;
+
+                if (root_dir)
+                        asprintf(&path, "%s/%s/%s", root_dir, *i, name);
+                else
+                        asprintf(&path, "%s/%s", *i, name);
+
+                if (!path) {
+                        r = -ENOMEM;
+                        goto finish;
+                }
+
+                if (lstat(path, &st) < 0) {
+                        r = -errno;
+                        if (errno == ENOENT)
+                                continue;
+
+                        goto finish;
+                }
+
+                if (!S_ISREG(st.st_mode) && !S_ISLNK(st.st_mode)) {
+                        r = -ENOENT;
+                        goto finish;
+                }
+
+                r = null_or_empty_path(path);
+                if (r < 0 && r != -ENOENT)
+                        goto finish;
+                else if (r > 0) {
+                        state = path_startswith(*i, "/run") ?
+                                UNIT_FILE_MASKED_RUNTIME : UNIT_FILE_MASKED;
+                        r = 0;
+                        goto finish;
+                }
+
+                r = find_symlinks_in_scope(scope, root_dir, name, &state);
+                if (r < 0) {
+                        goto finish;
+                } else if (r > 0) {
+                        r = 0;
+                        goto finish;
+                }
+
+                r = unit_file_can_install(&paths, root_dir, path, true);
+                if (r < 0 && errno != -ENOENT)
+                        goto finish;
+                else if (r > 0) {
+                        state = UNIT_FILE_DISABLED;
+                        r = 0;
+                        goto finish;
+                } else if (r == 0) {
+                        state = UNIT_FILE_STATIC;
+                        r = 0;
+                        goto finish;
+                }
+        }
+
+finish:
+        lookup_paths_free(&paths);
+        free(path);
+
+        return r < 0 ? r : state;
+}
+
+int unit_file_query_preset(UnitFileScope scope, const char *name) {
+        char **files, **i;
+        int r;
+
+        assert(scope >= 0);
+        assert(scope < _UNIT_FILE_SCOPE_MAX);
+        assert(name);
+
+        if (scope == UNIT_FILE_SYSTEM)
+                r = conf_files_list(&files, ".preset",
+                                    "/etc/systemd/system.preset",
+                                    "/usr/local/lib/systemd/system.preset",
+                                    "/usr/lib/systemd/system.preset",
+                                    "/lib/systemd/system.preset",
+                                    NULL);
+        else if (scope == UNIT_FILE_GLOBAL)
+                r = conf_files_list(&files, ".preset",
+                                    "/etc/systemd/user.preset",
+                                    "/usr/local/lib/systemd/user.preset",
+                                    "/usr/lib/systemd/user.preset",
+                                    NULL);
+        else
+                return 1;
+
+        if (r < 0)
+                return r;
+
+        STRV_FOREACH(i, files) {
+                FILE *f;
+
+                f = fopen(*i, "re");
+                if (!f) {
+                        if (errno == ENOENT)
+                                continue;
+
+                        r = -errno;
+                        goto finish;
+                }
+
+                for (;;) {
+                        char line[LINE_MAX], *l;
+
+                        if (!fgets(line, sizeof(line), f))
+                                break;
+
+                        l = strstrip(line);
+                        if (!*l)
+                                continue;
+
+                        if (strchr(COMMENTS, *l))
+                                continue;
+
+                        if (first_word(l, "enable")) {
+                                l += 6;
+                                l += strspn(l, WHITESPACE);
+
+                                if (fnmatch(l, name, FNM_NOESCAPE) == 0) {
+                                        r = 1;
+                                        fclose(f);
+                                        goto finish;
+                                }
+                        } else if (first_word(l, "disable")) {
+                                l += 7;
+                                l += strspn(l, WHITESPACE);
+
+                                if (fnmatch(l, name, FNM_NOESCAPE) == 0) {
+                                        r = 0;
+                                        fclose(f);
+                                        goto finish;
+                                }
+                        } else
+                                log_debug("Couldn't parse line '%s'", l);
+                }
+
+                fclose(f);
+        }
+
+        /* Default is "enable" */
+        r = 1;
+
+finish:
+        strv_free(files);
+
+        return r;
+}
+
+int unit_file_preset(
+                UnitFileScope scope,
+                bool runtime,
+                const char *root_dir,
+                char *files[],
+                bool force,
+                UnitFileChange **changes,
+                unsigned *n_changes) {
+
+        LookupPaths paths;
+        InstallContext plus, minus;
+        char **i, *config_path = NULL;
+        Set *remove_symlinks_to = NULL;
+        int r, q;
+
+        assert(scope >= 0);
+        assert(scope < _UNIT_FILE_SCOPE_MAX);
+
+        zero(paths);
+        zero(plus);
+        zero(minus);
+
+        r = lookup_paths_init_from_scope(&paths, scope);
+        if (r < 0)
+                return r;
+
+        r = get_config_path(scope, runtime, root_dir, &config_path);
+        if (r < 0)
+                goto finish;
+
+        STRV_FOREACH(i, files) {
+
+                if (!unit_name_is_valid_no_type(*i, true)) {
+                        r = -EINVAL;
+                        goto finish;
+                }
+
+                r = unit_file_query_preset(scope, *i);
+                if (r < 0)
+                        goto finish;
+
+                if (r)
+                        r = install_info_add_auto(&plus, *i);
+                else
+                        r = install_info_add_auto(&minus, *i);
+
+                if (r < 0)
+                        goto finish;
+        }
+
+        r = install_context_mark_for_removal(&minus, &paths, &remove_symlinks_to, config_path, root_dir);
+
+        q = remove_marked_symlinks(remove_symlinks_to, config_path, changes, n_changes);
+        if (r == 0)
+                r = q;
+
+        /* Returns number of symlinks that where supposed to be installed. */
+        q = install_context_apply(&plus, &paths, config_path, root_dir, force, changes, n_changes);
+        if (r == 0)
+                r = q;
+
+finish:
+        lookup_paths_free(&paths);
+        install_context_done(&plus);
+        install_context_done(&minus);
+        set_free_free(remove_symlinks_to);
+        free(config_path);
+
+        return r;
+}
+
+int unit_file_get_list(
+                UnitFileScope scope,
+                const char *root_dir,
+                Hashmap *h) {
+
+        LookupPaths paths;
+        char **i, *buf = NULL;
+        DIR *d = NULL;
+        int r;
+
+        assert(scope >= 0);
+        assert(scope < _UNIT_FILE_SCOPE_MAX);
+        assert(h);
+
+        zero(paths);
+
+        if (root_dir && scope != UNIT_FILE_SYSTEM)
+                return -EINVAL;
+
+        r = lookup_paths_init_from_scope(&paths, scope);
+        if (r < 0)
+                return r;
+
+        STRV_FOREACH(i, paths.unit_path) {
+                struct dirent buffer, *de;
+                const char *units_dir;
+
+                free(buf);
+                buf = NULL;
+
+                if (root_dir) {
+                        if (asprintf(&buf, "%s/%s", root_dir, *i) < 0) {
+                                r = -ENOMEM;
+                                goto finish;
+                        }
+                        units_dir = buf;
+                } else
+                        units_dir = *i;
+
+                if (d)
+                        closedir(d);
+
+                d = opendir(units_dir);
+                if (!d) {
+                        if (errno == ENOENT)
+                                continue;
+
+                        r = -errno;
+                        goto finish;
+                }
+
+                for (;;) {
+                        UnitFileList *f;
+
+                        r = readdir_r(d, &buffer, &de);
+                        if (r != 0) {
+                                r = -r;
+                                goto finish;
+                        }
+
+                        if (!de)
+                                break;
+
+                        if (ignore_file(de->d_name))
+                                continue;
+
+                        if (!unit_name_is_valid_no_type(de->d_name, true))
+                                continue;
+
+                        if (hashmap_get(h, de->d_name))
+                                continue;
+
+                        r = dirent_ensure_type(d, de);
+                        if (r < 0) {
+                                if (r == -ENOENT)
+                                        continue;
+
+                                goto finish;
+                        }
+
+                        if (de->d_type != DT_LNK && de->d_type != DT_REG)
+                                continue;
+
+                        f = new0(UnitFileList, 1);
+                        if (!f) {
+                                r = -ENOMEM;
+                                goto finish;
+                        }
+
+                        f->path = path_make_absolute(de->d_name, units_dir);
+                        if (!f->path) {
+                                free(f);
+                                r = -ENOMEM;
+                                goto finish;
+                        }
+
+                        r = null_or_empty_path(f->path);
+                        if (r < 0 && r != -ENOENT) {
+                                free(f->path);
+                                free(f);
+                                goto finish;
+                        } else if (r > 0) {
+                                f->state =
+                                        path_startswith(*i, "/run") ?
+                                        UNIT_FILE_MASKED_RUNTIME : UNIT_FILE_MASKED;
+                                goto found;
+                        }
+
+                        r = find_symlinks_in_scope(scope, root_dir, de->d_name, &f->state);
+                        if (r < 0) {
+                                free(f->path);
+                                free(f);
+                                goto finish;
+                        } else if (r > 0)
+                                goto found;
+
+                        r = unit_file_can_install(&paths, root_dir, f->path, true);
+                        if (r < 0) {
+                                free(f->path);
+                                free(f);
+                                goto finish;
+                        } else if (r > 0) {
+                                f->state = UNIT_FILE_DISABLED;
+                                goto found;
+                        } else {
+                                f->state = UNIT_FILE_STATIC;
+                                goto found;
+                        }
+
+                        free(f->path);
+                        free(f);
+                        continue;
+
+                found:
+                        r = hashmap_put(h, file_name_from_path(f->path), f);
+                        if (r < 0) {
+                                free(f->path);
+                                free(f);
+                                goto finish;
+                        }
+                }
+        }
+
+finish:
+        lookup_paths_free(&paths);
+        free(buf);
+
+        if (d)
+                closedir(d);
+
+        return r;
+}
+
+static const char* const unit_file_state_table[_UNIT_FILE_STATE_MAX] = {
+        [UNIT_FILE_ENABLED] = "enabled",
+        [UNIT_FILE_ENABLED_RUNTIME] = "enabled-runtie",
+        [UNIT_FILE_LINKED] = "linked",
+        [UNIT_FILE_LINKED_RUNTIME] = "linked-runtime",
+        [UNIT_FILE_MASKED] = "masked",
+        [UNIT_FILE_MASKED_RUNTIME] = "masked-runtime",
+        [UNIT_FILE_STATIC] = "static",
+        [UNIT_FILE_DISABLED] = "disabled"
+};
+
+DEFINE_STRING_TABLE_LOOKUP(unit_file_state, UnitFileState);
+
+static const char* const unit_file_change_type_table[_UNIT_FILE_CHANGE_TYPE_MAX] = {
+        [UNIT_FILE_SYMLINK] = "symlink",
+        [UNIT_FILE_UNLINK] = "unlink",
+};
+
+DEFINE_STRING_TABLE_LOOKUP(unit_file_change_type, UnitFileChangeType);
diff --git a/src/install.h b/src/install.h
new file mode 100644 (file)
index 0000000..0505a82
--- /dev/null
@@ -0,0 +1,89 @@
+/*-*- Mode: C; c-basic-offset: 8; indent-tabs-mode: nil -*-*/
+
+#ifndef fooinstallhfoo
+#define fooinstallhfoo
+
+/***
+  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 <http://www.gnu.org/licenses/>.
+***/
+
+#include "hashmap.h"
+
+typedef enum UnitFileScope {
+        UNIT_FILE_SYSTEM,
+        UNIT_FILE_GLOBAL,
+        UNIT_FILE_USER,
+        _UNIT_FILE_SCOPE_MAX,
+        _UNIT_FILE_SCOPE_INVALID = -1
+} UnitFileScope;
+
+typedef enum UnitFileState {
+        UNIT_FILE_ENABLED,
+        UNIT_FILE_ENABLED_RUNTIME,
+        UNIT_FILE_LINKED,
+        UNIT_FILE_LINKED_RUNTIME,
+        UNIT_FILE_MASKED,
+        UNIT_FILE_MASKED_RUNTIME,
+        UNIT_FILE_STATIC,
+        UNIT_FILE_DISABLED,
+        _UNIT_FILE_STATE_MAX,
+        _UNIT_FILE_STATE_INVALID = -1
+} UnitFileState;
+
+typedef enum UnitFileChangeType {
+        UNIT_FILE_SYMLINK,
+        UNIT_FILE_UNLINK,
+        _UNIT_FILE_CHANGE_TYPE_MAX,
+        _UNIT_FILE_CHANGE_TYPE_INVALID = -1
+} UnitFileChangeType;
+
+typedef struct UnitFileChange {
+        UnitFileChangeType type;
+        char *path;
+        char *source;
+} UnitFileChange;
+
+typedef struct UnitFileList {
+        char *path;
+        UnitFileState state;
+} UnitFileList;
+
+int unit_file_enable(UnitFileScope scope, bool runtime, const char *root_dir, char *files[], bool force, UnitFileChange **changes, unsigned *n_changes);
+int unit_file_disable(UnitFileScope scope, bool runtime, const char *root_dir, char *files[], UnitFileChange **changes, unsigned *n_changes);
+int unit_file_reenable(UnitFileScope scope, bool runtime, const char *root_dir, char *files[], bool force, UnitFileChange **changes, unsigned *n_changes);
+int unit_file_link(UnitFileScope scope, bool runtime, const char *root_dir, char *files[], bool force, UnitFileChange **changes, unsigned *n_changes);
+int unit_file_preset(UnitFileScope scope, bool runtime, const char *root_dir, char *files[], bool force, UnitFileChange **changes, unsigned *n_changes);
+int unit_file_mask(UnitFileScope scope, bool runtime, const char *root_dir, char *files[], bool force, UnitFileChange **changes, unsigned *n_changes);
+int unit_file_unmask(UnitFileScope scope, bool runtime, const char *root_dir, char *files[], UnitFileChange **changes, unsigned *n_changes);
+
+UnitFileState unit_file_get_state(UnitFileScope scope, const char *root_dir, const char *filename);
+
+int unit_file_get_list(UnitFileScope scope, const char *root_dir, Hashmap *h);
+
+void unit_file_list_free(Hashmap *h);
+void unit_file_changes_free(UnitFileChange *changes, unsigned n_changes);
+
+int unit_file_query_preset(UnitFileScope scope, const char *name);
+
+const char *unit_file_state_to_string(UnitFileState s);
+UnitFileState unit_file_state_from_string(const char *s);
+
+const char *unit_file_change_type_to_string(UnitFileChangeType s);
+UnitFileChangeType unit_file_change_type_from_string(const char *s);
+
+#endif
diff --git a/src/ioprio.h b/src/ioprio.h
new file mode 100644 (file)
index 0000000..9800fc2
--- /dev/null
@@ -0,0 +1,57 @@
+#ifndef IOPRIO_H
+#define IOPRIO_H
+
+/* This is minimal version of Linux' linux/ioprio.h header file, which
+ * is licensed GPL2 */
+
+#include <unistd.h>
+#include <sys/syscall.h>
+
+/*
+ * Gives us 8 prio classes with 13-bits of data for each class
+ */
+#define IOPRIO_BITS             (16)
+#define IOPRIO_CLASS_SHIFT      (13)
+#define IOPRIO_PRIO_MASK        ((1UL << IOPRIO_CLASS_SHIFT) - 1)
+
+#define IOPRIO_PRIO_CLASS(mask) ((mask) >> IOPRIO_CLASS_SHIFT)
+#define IOPRIO_PRIO_DATA(mask)  ((mask) & IOPRIO_PRIO_MASK)
+#define IOPRIO_PRIO_VALUE(class, data)  (((class) << IOPRIO_CLASS_SHIFT) | data)
+
+#define ioprio_valid(mask)      (IOPRIO_PRIO_CLASS((mask)) != IOPRIO_CLASS_NONE)
+
+/*
+ * These are the io priority groups as implemented by CFQ. RT is the realtime
+ * class, it always gets premium service. BE is the best-effort scheduling
+ * class, the default for any process. IDLE is the idle scheduling class, it
+ * is only served when no one else is using the disk.
+ */
+enum {
+        IOPRIO_CLASS_NONE,
+        IOPRIO_CLASS_RT,
+        IOPRIO_CLASS_BE,
+        IOPRIO_CLASS_IDLE,
+};
+
+/*
+ * 8 best effort priority levels are supported
+ */
+#define IOPRIO_BE_NR    (8)
+
+enum {
+        IOPRIO_WHO_PROCESS = 1,
+        IOPRIO_WHO_PGRP,
+        IOPRIO_WHO_USER,
+};
+
+static inline int ioprio_set(int which, int who, int ioprio)
+{
+        return syscall(__NR_ioprio_set, which, who, ioprio);
+}
+
+static inline int ioprio_get(int which, int who)
+{
+        return syscall(__NR_ioprio_get, which, who);
+}
+
+#endif
diff --git a/src/job.c b/src/job.c
new file mode 100644 (file)
index 0000000..8e944b3
--- /dev/null
+++ b/src/job.c
@@ -0,0 +1,754 @@
+/*-*- 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 <http://www.gnu.org/licenses/>.
+***/
+
+#include <assert.h>
+#include <errno.h>
+#include <sys/timerfd.h>
+#include <sys/epoll.h>
+
+#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;
+}
+
+static bool types_match(JobType a, JobType b, JobType c, JobType d) {
+        return
+                (a == c && b == d) ||
+                (a == d && b == c);
+}
+
+int job_type_merge(JobType *a, JobType b) {
+        if (*a == b)
+                return 0;
+
+        /* 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 */
+
+        if (types_match(*a, b, JOB_START, JOB_VERIFY_ACTIVE))
+                *a = JOB_START;
+        else if (types_match(*a, b, JOB_START, JOB_RELOAD) ||
+                 types_match(*a, b, JOB_START, JOB_RELOAD_OR_START) ||
+                 types_match(*a, b, JOB_VERIFY_ACTIVE, JOB_RELOAD_OR_START) ||
+                 types_match(*a, b, JOB_RELOAD, JOB_RELOAD_OR_START))
+                *a = JOB_RELOAD_OR_START;
+        else if (types_match(*a, b, JOB_START, JOB_RESTART) ||
+                 types_match(*a, b, JOB_START, JOB_TRY_RESTART) ||
+                 types_match(*a, b, JOB_VERIFY_ACTIVE, JOB_RESTART) ||
+                 types_match(*a, b, JOB_RELOAD, JOB_RESTART) ||
+                 types_match(*a, b, JOB_RELOAD_OR_START, JOB_RESTART) ||
+                 types_match(*a, b, JOB_RELOAD_OR_START, JOB_TRY_RESTART) ||
+                 types_match(*a, b, JOB_RESTART, JOB_TRY_RESTART))
+                *a = JOB_RESTART;
+        else if (types_match(*a, b, JOB_VERIFY_ACTIVE, JOB_RELOAD))
+                *a = JOB_RELOAD;
+        else if (types_match(*a, b, JOB_VERIFY_ACTIVE, JOB_TRY_RESTART) ||
+                 types_match(*a, b, JOB_RELOAD, JOB_TRY_RESTART))
+                *a = JOB_TRY_RESTART;
+        else
+                return -EEXIST;
+
+        return 0;
+}
+
+bool job_type_is_mergeable(JobType a, JobType b) {
+        return job_type_merge(&a, b) >= 0;
+}
+
+bool job_type_is_superset(JobType a, JobType b) {
+
+        /* Checks whether operation a is a "superset" of b in its
+         * actions */
+
+        if (a == b)
+                return true;
+
+        switch (a) {
+                case JOB_START:
+                        return b == JOB_VERIFY_ACTIVE;
+
+                case JOB_RELOAD:
+                        return
+                                b == JOB_VERIFY_ACTIVE;
+
+                case JOB_RELOAD_OR_START:
+                        return
+                                b == JOB_RELOAD ||
+                                b == JOB_START ||
+                                b == JOB_VERIFY_ACTIVE;
+
+                case JOB_RESTART:
+                        return
+                                b == JOB_START ||
+                                b == JOB_VERIFY_ACTIVE ||
+                                b == JOB_RELOAD ||
+                                b == JOB_RELOAD_OR_START ||
+                                b == JOB_TRY_RESTART;
+
+                case JOB_TRY_RESTART:
+                        return
+                                b == JOB_VERIFY_ACTIVE ||
+                                b == JOB_RELOAD;
+                default:
+                        return false;
+
+        }
+}
+
+bool job_type_is_conflicting(JobType a, JobType b) {
+        assert(a >= 0 && a < _JOB_TYPE_MAX);
+        assert(b >= 0 && b < _JOB_TYPE_MAX);
+
+        return (a == JOB_STOP) != (b == JOB_STOP);
+}
+
+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
new file mode 100644 (file)
index 0000000..2121426
--- /dev/null
+++ b/src/job.h
@@ -0,0 +1,179 @@
+/*-*- 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 <http://www.gnu.org/licenses/>.
+***/
+
+#include <stdbool.h>
+#include <inttypes.h>
+
+typedef struct Job Job;
+typedef struct JobDependency JobDependency;
+typedef enum JobType JobType;
+typedef enum JobState JobState;
+typedef enum JobMode JobMode;
+typedef enum JobResult JobResult;
+
+#include "manager.h"
+#include "unit.h"
+#include "hashmap.h"
+#include "list.h"
+
+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);
+
+int job_type_merge(JobType *a, JobType b);
+bool job_type_is_mergeable(JobType a, JobType b);
+bool job_type_is_superset(JobType a, JobType b);
+bool job_type_is_conflicting(JobType a, JobType 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/journal/.gitignore b/src/journal/.gitignore
new file mode 100644 (file)
index 0000000..d6a7946
--- /dev/null
@@ -0,0 +1,2 @@
+/journald-gperf.c
+/libsystemd-journal.pc
diff --git a/src/journal/Makefile b/src/journal/Makefile
new file mode 120000 (symlink)
index 0000000..d0b0e8e
--- /dev/null
@@ -0,0 +1 @@
+../Makefile
\ No newline at end of file
diff --git a/src/journal/cat.c b/src/journal/cat.c
new file mode 100644 (file)
index 0000000..f0a6666
--- /dev/null
@@ -0,0 +1,182 @@
+/*-*- Mode: C; c-basic-offset: 8; indent-tabs-mode: nil -*-*/
+
+/***
+  This file is part of systemd.
+
+  Copyright 2012 Lennart Poettering
+
+  systemd is free software; you can redistribute it and/or modify it
+  under the terms of the GNU 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 <http://www.gnu.org/licenses/>.
+***/
+
+#include <stdio.h>
+#include <getopt.h>
+#include <assert.h>
+#include <unistd.h>
+#include <stdlib.h>
+#include <errno.h>
+#include <sys/fcntl.h>
+
+#include <systemd/sd-journal.h>
+
+#include "util.h"
+#include "build.h"
+
+static char *arg_identifier = NULL;
+static int arg_priority = LOG_INFO;
+static bool arg_level_prefix = true;
+
+static int help(void) {
+
+        printf("%s [OPTIONS...] {COMMAND} ...\n\n"
+               "Execute process with stdout/stderr connected to the journal.\n\n"
+               "  -h --help               Show this help\n"
+               "     --version            Show package version\n"
+               "  -t --identifier=STRING  Set syslog identifier\n"
+               "  -p --priority=PRIORITY  Set priority value (0..7)\n"
+               "     --level-prefix=BOOL  Control whether level prefix shall be parsed\n",
+               program_invocation_short_name);
+
+        return 0;
+}
+
+static int parse_argv(int argc, char *argv[]) {
+
+        enum {
+                ARG_VERSION = 0x100,
+                ARG_LEVEL_PREFIX
+        };
+
+        static const struct option options[] = {
+                { "help",         no_argument,       NULL, 'h'              },
+                { "version",      no_argument,       NULL, ARG_VERSION      },
+                { "identifier",   required_argument, NULL, 't'              },
+                { "priority",     required_argument, NULL, 'p'              },
+                { "level-prefix", required_argument, NULL, ARG_LEVEL_PREFIX },
+                { NULL,           0,                 NULL, 0                }
+        };
+
+        int c;
+
+        assert(argc >= 0);
+        assert(argv);
+
+        while ((c = getopt_long(argc, argv, "+ht:p:", options, NULL)) >= 0) {
+
+                switch (c) {
+
+                case 'h':
+                        help();
+                        return 0;
+
+                case ARG_VERSION:
+                        puts(PACKAGE_STRING);
+                        puts(DISTRIBUTION);
+                        puts(SYSTEMD_FEATURES);
+                        return 0;
+
+                case 't':
+                        free(arg_identifier);
+                        if (isempty(optarg))
+                                arg_identifier = NULL;
+                        else {
+                                arg_identifier = strdup(optarg);
+                                if (!arg_identifier) {
+                                        log_error("Out of memory.");
+                                        return -ENOMEM;
+                                }
+                        }
+                        break;
+
+                case 'p':
+                        arg_priority = log_level_from_string(optarg);
+                        if (arg_priority < 0) {
+                                log_error("Failed to parse priority value.");
+                                return arg_priority;
+                        }
+                        break;
+
+                case ARG_LEVEL_PREFIX: {
+                        int k;
+
+                        k = parse_boolean(optarg);
+                        if (k < 0) {
+                                log_error("Failed to parse level prefix value.");
+                                return k;
+                        }
+                        arg_level_prefix = k;
+                        break;
+                }
+
+                default:
+                        log_error("Unknown option code %c", c);
+                        return -EINVAL;
+                }
+        }
+
+        return 1;
+}
+
+int main(int argc, char *argv[]) {
+        int r, fd = -1, saved_stderr = -1;
+
+        log_parse_environment();
+        log_open();
+
+        r = parse_argv(argc, argv);
+        if (r <= 0)
+                goto finish;
+
+        fd = sd_journal_stream_fd(arg_identifier, arg_priority, arg_level_prefix);
+        if (fd < 0) {
+                log_error("Failed to create stream fd: %s", strerror(-fd));
+                r = fd;
+                goto finish;
+        }
+
+        saved_stderr = fcntl(STDERR_FILENO, F_DUPFD_CLOEXEC, 3);
+
+        if (dup3(fd, STDOUT_FILENO, 0) < 0 ||
+            dup3(fd, STDERR_FILENO, 0) < 0) {
+                log_error("Failed to duplicate fd: %m");
+                r = -errno;
+                goto finish;
+        }
+
+        if (fd >= 3)
+                close_nointr_nofail(fd);
+
+        fd = -1;
+
+        if (argc <= optind)
+                execl("/bin/cat", "/bin/cat", NULL);
+        else
+                execvp(argv[optind], argv + optind);
+
+        r = -errno;
+
+        /* Let's try to restore a working stderr, so we can print the error message */
+        if (saved_stderr >= 0)
+                dup3(saved_stderr, STDERR_FILENO, 0);
+
+        log_error("Failed to execute process: %s", strerror(-r));
+
+finish:
+        if (fd >= 0)
+                close_nointr_nofail(fd);
+
+        if (saved_stderr >= 0)
+                close_nointr_nofail(saved_stderr);
+
+        return r < 0 ? EXIT_FAILURE : EXIT_SUCCESS;
+}
diff --git a/src/journal/compress.c b/src/journal/compress.c
new file mode 100644 (file)
index 0000000..ff90658
--- /dev/null
@@ -0,0 +1,208 @@
+/*-*- 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 <http://www.gnu.org/licenses/>.
+***/
+
+#include <assert.h>
+#include <stdlib.h>
+#include <string.h>
+#include <lzma.h>
+
+#include "compress.h"
+
+bool compress_blob(const void *src, uint64_t src_size, void *dst, uint64_t *dst_size) {
+        lzma_stream s = LZMA_STREAM_INIT;
+        lzma_ret ret;
+        bool b = false;
+
+        assert(src);
+        assert(src_size > 0);
+        assert(dst);
+        assert(dst_size);
+
+        /* Returns false if we couldn't compress the data or the
+         * compressed result is longer than the original */
+
+        ret = lzma_easy_encoder(&s, LZMA_PRESET_DEFAULT, LZMA_CHECK_NONE);
+        if (ret != LZMA_OK)
+                return false;
+
+        s.next_in = src;
+        s.avail_in = src_size;
+        s.next_out = dst;
+        s.avail_out = src_size;
+
+        /* Does it fit? */
+        if (lzma_code(&s, LZMA_FINISH) != LZMA_STREAM_END)
+                goto fail;
+
+        /* Is it actually shorter? */
+        if (s.avail_out == 0)
+                goto fail;
+
+        *dst_size = src_size - s.avail_out;
+        b = true;
+
+fail:
+        lzma_end(&s);
+
+        return b;
+}
+
+bool uncompress_blob(const void *src, uint64_t src_size,
+                     void **dst, uint64_t *dst_alloc_size, uint64_t* dst_size) {
+
+        lzma_stream s = LZMA_STREAM_INIT;
+        lzma_ret ret;
+        bool b = false;
+
+        assert(src);
+        assert(src_size > 0);
+        assert(dst);
+        assert(dst_alloc_size);
+        assert(dst_size);
+        assert(*dst_alloc_size == 0 || *dst);
+
+        ret = lzma_stream_decoder(&s, UINT64_MAX, 0);
+        if (ret != LZMA_OK)
+                return false;
+
+        if (*dst_alloc_size <= src_size) {
+                void *p;
+
+                p = realloc(*dst, src_size*2);
+                if (!p)
+                        return false;
+
+                *dst = p;
+                *dst_alloc_size = src_size*2;
+        }
+
+        s.next_in = src;
+        s.avail_in = src_size;
+
+        s.next_out = *dst;
+        s.avail_out = *dst_alloc_size;
+
+        for (;;) {
+                void *p;
+
+                ret = lzma_code(&s, LZMA_FINISH);
+
+                if (ret == LZMA_STREAM_END)
+                        break;
+
+                if (ret != LZMA_OK)
+                        goto fail;
+
+                p = realloc(*dst, *dst_alloc_size*2);
+                if (!p)
+                        goto fail;
+
+                s.next_out = (uint8_t*) p + ((uint8_t*) s.next_out - (uint8_t*) *dst);
+                s.avail_out += *dst_alloc_size;
+
+                *dst = p;
+                *dst_alloc_size *= 2;
+        }
+
+        *dst_size = *dst_alloc_size - s.avail_out;
+        b = true;
+
+fail:
+        lzma_end(&s);
+
+        return b;
+}
+
+bool uncompress_startswith(const void *src, uint64_t src_size,
+                           void **buffer, uint64_t *buffer_size,
+                           const void *prefix, uint64_t prefix_len,
+                           uint8_t extra) {
+
+        lzma_stream s = LZMA_STREAM_INIT;
+        lzma_ret ret;
+        bool b = false;
+
+        /* Checks whether the uncompressed blob starts with the
+         * mentioned prefix. The byte extra needs to follow the
+         * prefix */
+
+        assert(src);
+        assert(src_size > 0);
+        assert(buffer);
+        assert(buffer_size);
+        assert(prefix);
+        assert(*buffer_size == 0 || *buffer);
+
+        ret = lzma_stream_decoder(&s, UINT64_MAX, 0);
+        if (ret != LZMA_OK)
+                return false;
+
+        if (*buffer_size <= prefix_len) {
+                void *p;
+
+                p = realloc(*buffer, prefix_len*2);
+                if (!p)
+                        return false;
+
+                *buffer = p;
+                *buffer_size = prefix_len*2;
+        }
+
+        s.next_in = src;
+        s.avail_in = src_size;
+
+        s.next_out = *buffer;
+        s.avail_out = *buffer_size;
+
+        for (;;) {
+                void *p;
+
+                ret = lzma_code(&s, LZMA_FINISH);
+
+                if (ret != LZMA_STREAM_END && ret != LZMA_OK)
+                        goto fail;
+
+                if ((*buffer_size - s.avail_out > prefix_len) &&
+                    memcmp(*buffer, prefix, prefix_len) == 0 &&
+                    ((const uint8_t*) *buffer)[prefix_len] == extra)
+                        break;
+
+                if (ret == LZMA_STREAM_END)
+                        goto fail;
+
+                p = realloc(*buffer, *buffer_size*2);
+                if (!p)
+                        goto fail;
+
+                s.next_out = (uint8_t*) p + ((uint8_t*) s.next_out - (uint8_t*) *buffer);
+                s.avail_out += *buffer_size;
+
+                *buffer = p;
+                *buffer_size *= 2;
+        }
+
+        b = true;
+
+fail:
+        lzma_end(&s);
+
+        return b;
+}
diff --git a/src/journal/compress.h b/src/journal/compress.h
new file mode 100644 (file)
index 0000000..f187a6e
--- /dev/null
@@ -0,0 +1,38 @@
+/*-*- Mode: C; c-basic-offset: 8; indent-tabs-mode: nil -*-*/
+
+#ifndef foocompresshfoo
+#define foocompresshfoo
+
+/***
+  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 <http://www.gnu.org/licenses/>.
+***/
+
+#include <inttypes.h>
+#include <stdbool.h>
+
+bool compress_blob(const void *src, uint64_t src_size, void *dst, uint64_t *dst_size);
+
+bool uncompress_blob(const void *src, uint64_t src_size,
+                     void **dst, uint64_t *dst_alloc_size, uint64_t* dst_size);
+
+bool uncompress_startswith(const void *src, uint64_t src_size,
+                           void **buffer, uint64_t *buffer_size,
+                           const void *prefix, uint64_t prefix_len,
+                           uint8_t extra);
+
+#endif
diff --git a/src/journal/coredump.c b/src/journal/coredump.c
new file mode 100644 (file)
index 0000000..7dea66e
--- /dev/null
@@ -0,0 +1,271 @@
+/*-*- Mode: C; c-basic-offset: 8; indent-tabs-mode: nil -*-*/
+
+/***
+  This file is part of systemd.
+
+  Copyright 2012 Lennart Poettering
+
+  systemd is free software; you can redistribute it and/or modify it
+  under the terms of the GNU 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 <http://www.gnu.org/licenses/>.
+***/
+
+#include <errno.h>
+#include <unistd.h>
+#include <stdio.h>
+#include <sys/prctl.h>
+
+#include <systemd/sd-journal.h>
+#include <systemd/sd-login.h>
+
+#include "log.h"
+#include "util.h"
+#include "special.h"
+
+#define COREDUMP_MAX (24*1024*1024)
+
+enum {
+        ARG_PID = 1,
+        ARG_UID,
+        ARG_GID,
+        ARG_SIGNAL,
+        ARG_TIMESTAMP,
+        ARG_COMM,
+        _ARG_MAX
+};
+
+static int divert_coredump(void) {
+        FILE *f;
+        int r;
+
+        log_info("Detected coredump of the journal daemon itself, diverting coredump to /var/lib/systemd/coredump/.");
+
+        mkdir_p("/var/lib/systemd/coredump", 0755);
+
+        f = fopen("/var/lib/systemd/coredump/core.systemd-journald", "we");
+        if (!f) {
+                log_error("Failed to create coredump file: %m");
+                return -errno;
+        }
+
+        for (;;) {
+                uint8_t buffer[4096];
+                size_t l, q;
+
+                l = fread(buffer, 1, sizeof(buffer), stdin);
+                if (l <= 0) {
+                        if (ferror(f)) {
+                                log_error("Failed to read coredump: %m");
+                                r = -errno;
+                                goto finish;
+                        }
+
+                        r = 0;
+                        break;
+                }
+
+                q = fwrite(buffer, 1, l, f);
+                if (q != l) {
+                        log_error("Failed to write coredump: %m");
+                        r = -errno;
+                        goto finish;
+                }
+        }
+
+        fflush(f);
+
+        if (ferror(f)) {
+                log_error("Failed to write coredump: %m");
+                r = -errno;
+        }
+
+finish:
+        fclose(f);
+        return r;
+}
+
+int main(int argc, char* argv[]) {
+        int r, j = 0;
+        char *p = NULL;
+        ssize_t n;
+        pid_t pid;
+        uid_t uid;
+        gid_t gid;
+        struct iovec iovec[14];
+        char *core_pid = NULL, *core_uid = NULL, *core_gid = NULL, *core_signal = NULL,
+                *core_timestamp = NULL, *core_comm = NULL, *core_exe = NULL, *core_unit = NULL,
+                *core_session = NULL, *core_message = NULL, *core_cmdline = NULL, *t;
+
+        prctl(PR_SET_DUMPABLE, 0);
+
+        if (argc != _ARG_MAX) {
+                log_set_target(LOG_TARGET_JOURNAL_OR_KMSG);
+                log_open();
+
+                log_error("Invalid number of arguments passed from kernel.");
+                r = -EINVAL;
+                goto finish;
+        }
+
+        r = parse_pid(argv[ARG_PID], &pid);
+        if (r < 0) {
+                log_set_target(LOG_TARGET_JOURNAL_OR_KMSG);
+                log_open();
+
+                log_error("Failed to parse PID.");
+                goto finish;
+        }
+
+        if (sd_pid_get_unit(pid, &t) >= 0) {
+
+                if (streq(t, SPECIAL_JOURNALD_SERVICE)) {
+                        /* Make sure we don't make use of the journal,
+                         * if it's the journal which is crashing */
+                        log_set_target(LOG_TARGET_KMSG);
+                        log_open();
+
+                        r = divert_coredump();
+                        goto finish;
+                }
+
+                core_unit = strappend("COREDUMP_UNIT=", t);
+                free(t);
+
+                if (core_unit)
+                        IOVEC_SET_STRING(iovec[j++], core_unit);
+        }
+
+        /* OK, now we know it's not the journal, hence make use of
+         * it */
+        log_set_target(LOG_TARGET_JOURNAL_OR_KMSG);
+        log_open();
+
+        r = parse_uid(argv[ARG_UID], &uid);
+        if (r < 0) {
+                log_error("Failed to parse UID.");
+                goto finish;
+        }
+
+        r = parse_gid(argv[ARG_GID], &gid);
+        if (r < 0) {
+                log_error("Failed to parse GID.");
+                goto finish;
+        }
+
+        core_pid = strappend("COREDUMP_PID=", argv[ARG_PID]);
+        if (core_pid)
+                IOVEC_SET_STRING(iovec[j++], core_pid);
+
+        core_uid = strappend("COREDUMP_UID=", argv[ARG_UID]);
+        if (core_uid)
+                IOVEC_SET_STRING(iovec[j++], core_uid);
+
+        core_gid = strappend("COREDUMP_GID=", argv[ARG_GID]);
+        if (core_gid)
+                IOVEC_SET_STRING(iovec[j++], core_gid);
+
+        core_signal = strappend("COREDUMP_SIGNAL=", argv[ARG_SIGNAL]);
+        if (core_signal)
+                IOVEC_SET_STRING(iovec[j++], core_signal);
+
+        core_comm = strappend("COREDUMP_COMM=", argv[ARG_COMM]);
+        if (core_comm)
+                IOVEC_SET_STRING(iovec[j++], core_comm);
+
+        if (sd_pid_get_session(pid, &t) >= 0) {
+                core_session = strappend("COREDUMP_SESSION=", t);
+                free(t);
+
+                if (core_session)
+                        IOVEC_SET_STRING(iovec[j++], core_session);
+        }
+
+        if (get_process_exe(pid, &t) >= 0) {
+                core_exe = strappend("COREDUMP_EXE=", t);
+                free(t);
+
+                if (core_exe)
+                        IOVEC_SET_STRING(iovec[j++], core_exe);
+        }
+
+        if (get_process_cmdline(pid, LINE_MAX, false, &t) >= 0) {
+                core_cmdline = strappend("COREDUMP_CMDLINE=", t);
+                free(t);
+
+                if (core_cmdline)
+                        IOVEC_SET_STRING(iovec[j++], core_cmdline);
+        }
+
+        core_timestamp = join("COREDUMP_TIMESTAMP=", argv[ARG_TIMESTAMP], "000000", NULL);
+        if (core_timestamp)
+                IOVEC_SET_STRING(iovec[j++], core_timestamp);
+
+        IOVEC_SET_STRING(iovec[j++], "MESSAGE_ID=fc2e22bc6ee647b6b90729ab34a250b1");
+        IOVEC_SET_STRING(iovec[j++], "PRIORITY=2");
+
+        core_message = join("MESSAGE=Process ", argv[ARG_PID], " (", argv[ARG_COMM], ") dumped core.", NULL);
+        if (core_message)
+                IOVEC_SET_STRING(iovec[j++], core_message);
+
+        /* Now, let's drop privileges to become the user who owns the
+         * segfaulted process and allocate the coredump memory under
+         * his uid. This also ensures that the credentials journald
+         * will see are the ones of the coredumping user, thus making
+         * sure the user himself gets access to the core dump. */
+
+        if (setresgid(gid, gid, gid) < 0 ||
+            setresuid(uid, uid, uid) < 0) {
+                log_error("Failed to drop privileges: %m");
+                r = -errno;
+                goto finish;
+        }
+
+        p = malloc(9 + COREDUMP_MAX);
+        if (!p) {
+                log_error("Out of memory");
+                r = -ENOMEM;
+                goto finish;
+        }
+
+        memcpy(p, "COREDUMP=", 9);
+
+        n = loop_read(STDIN_FILENO, p + 9, COREDUMP_MAX, false);
+        if (n < 0) {
+                log_error("Failed to read core dump data: %s", strerror(-n));
+                r = (int) n;
+                goto finish;
+        }
+
+        iovec[j].iov_base = p;
+        iovec[j].iov_len = 9 + n;
+        j++;
+
+        r = sd_journal_sendv(iovec, j);
+        if (r < 0)
+                log_error("Failed to send coredump: %s", strerror(-r));
+
+finish:
+        free(p);
+        free(core_pid);
+        free(core_uid);
+        free(core_gid);
+        free(core_signal);
+        free(core_timestamp);
+        free(core_comm);
+        free(core_exe);
+        free(core_cmdline);
+        free(core_unit);
+        free(core_session);
+        free(core_message);
+
+        return r < 0 ? EXIT_FAILURE : EXIT_SUCCESS;
+}
diff --git a/src/journal/journal-def.h b/src/journal/journal-def.h
new file mode 100644 (file)
index 0000000..9cb8051
--- /dev/null
@@ -0,0 +1,165 @@
+/*-*- Mode: C; c-basic-offset: 8; indent-tabs-mode: nil -*-*/
+
+#ifndef foojournaldefhfoo
+#define foojournaldefhfoo
+
+/***
+  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 <http://www.gnu.org/licenses/>.
+***/
+
+#include "sparse-endian.h"
+
+#include <systemd/sd-id128.h>
+
+#include "macro.h"
+
+typedef struct Header Header;
+typedef struct ObjectHeader ObjectHeader;
+typedef union Object Object;
+typedef struct DataObject DataObject;
+typedef struct FieldObject FieldObject;
+typedef struct EntryObject EntryObject;
+typedef struct HashTableObject HashTableObject;
+typedef struct EntryArrayObject EntryArrayObject;
+typedef struct EntryItem EntryItem;
+typedef struct HashItem HashItem;
+
+/* Object types */
+enum {
+        OBJECT_UNUSED,
+        OBJECT_DATA,
+        OBJECT_FIELD,
+        OBJECT_ENTRY,
+        OBJECT_DATA_HASH_TABLE,
+        OBJECT_FIELD_HASH_TABLE,
+        OBJECT_ENTRY_ARRAY,
+        _OBJECT_TYPE_MAX
+};
+
+/* Object flags */
+enum {
+        OBJECT_COMPRESSED = 1
+};
+
+_packed_ struct ObjectHeader {
+        uint8_t type;
+        uint8_t flags;
+        uint8_t reserved[6];
+        le64_t size;
+        uint8_t payload[];
+};
+
+_packed_ struct DataObject {
+        ObjectHeader object;
+        le64_t hash;
+        le64_t next_hash_offset;
+        le64_t next_field_offset;
+        le64_t entry_offset; /* the first array entry we store inline */
+        le64_t entry_array_offset;
+        le64_t n_entries;
+        uint8_t payload[];
+};
+
+_packed_ struct FieldObject {
+        ObjectHeader object;
+        le64_t hash;
+        le64_t next_hash_offset;
+        le64_t head_data_offset;
+        le64_t tail_data_offset;
+        uint8_t payload[];
+};
+
+_packed_ struct EntryItem {
+        le64_t object_offset;
+        le64_t hash;
+};
+
+_packed_ struct EntryObject {
+        ObjectHeader object;
+        le64_t seqnum;
+        le64_t realtime;
+        le64_t monotonic;
+        sd_id128_t boot_id;
+        le64_t xor_hash;
+        EntryItem items[];
+};
+
+_packed_ struct HashItem {
+        le64_t head_hash_offset;
+        le64_t tail_hash_offset;
+};
+
+_packed_ struct HashTableObject {
+        ObjectHeader object;
+        HashItem items[];
+};
+
+_packed_ struct EntryArrayObject {
+        ObjectHeader object;
+        le64_t next_entry_array_offset;
+        le64_t items[];
+};
+
+union Object {
+        ObjectHeader object;
+        DataObject data;
+        FieldObject field;
+        EntryObject entry;
+        HashTableObject hash_table;
+        EntryArrayObject entry_array;
+};
+
+enum {
+        STATE_OFFLINE,
+        STATE_ONLINE,
+        STATE_ARCHIVED
+};
+
+/* Header flags */
+enum {
+        HEADER_INCOMPATIBLE_COMPRESSED = 1
+};
+
+_packed_ struct Header {
+        uint8_t signature[8]; /* "LPKSHHRH" */
+        uint32_t compatible_flags;
+        uint32_t incompatible_flags;
+        uint8_t state;
+        uint8_t reserved[7];
+        sd_id128_t file_id;
+        sd_id128_t machine_id;
+        sd_id128_t boot_id;
+        sd_id128_t seqnum_id;
+        le64_t arena_offset;
+        le64_t arena_size;
+        le64_t data_hash_table_offset;     /* for looking up data objects */
+        le64_t data_hash_table_size;
+        le64_t field_hash_table_offset;     /* for looking up field objects */
+        le64_t field_hash_table_size;
+        le64_t tail_object_offset;
+        le64_t n_objects;
+        le64_t n_entries;
+        le64_t seqnum;
+        le64_t first_seqnum;
+        le64_t entry_array_offset;
+        le64_t head_entry_realtime;
+        le64_t tail_entry_realtime;
+        le64_t tail_entry_monotonic;
+};
+
+#endif
diff --git a/src/journal/journal-file.c b/src/journal/journal-file.c
new file mode 100644 (file)
index 0000000..be92c90
--- /dev/null
@@ -0,0 +1,2274 @@
+/*-*- 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 <http://www.gnu.org/licenses/>.
+***/
+
+#include <sys/mman.h>
+#include <errno.h>
+#include <sys/uio.h>
+#include <unistd.h>
+#include <sys/statvfs.h>
+#include <fcntl.h>
+#include <stddef.h>
+
+#include "journal-def.h"
+#include "journal-file.h"
+#include "lookup3.h"
+#include "compress.h"
+
+#define DEFAULT_DATA_HASH_TABLE_SIZE (2047ULL*16ULL)
+#define DEFAULT_FIELD_HASH_TABLE_SIZE (2047ULL*16ULL)
+
+#define DEFAULT_WINDOW_SIZE (8ULL*1024ULL*1024ULL)
+
+#define COMPRESSION_SIZE_THRESHOLD (512ULL)
+
+/* This is the minimum journal file size */
+#define JOURNAL_FILE_SIZE_MIN (64ULL*1024ULL)                  /* 64 KiB */
+
+/* These are the lower and upper bounds if we deduce the max_use value
+ * from the file system size */
+#define DEFAULT_MAX_USE_LOWER (1ULL*1024ULL*1024ULL)           /* 1 MiB */
+#define DEFAULT_MAX_USE_UPPER (4ULL*1024ULL*1024ULL*1024ULL)   /* 4 GiB */
+
+/* This is the upper bound if we deduce max_size from max_use */
+#define DEFAULT_MAX_SIZE_UPPER (128ULL*1024ULL*1024ULL)        /* 128 MiB */
+
+/* This is the upper bound if we deduce the keep_free value from the
+ * file system size */
+#define DEFAULT_KEEP_FREE_UPPER (4ULL*1024ULL*1024ULL*1024ULL) /* 4 GiB */
+
+/* This is the keep_free value when we can't determine the system
+ * size */
+#define DEFAULT_KEEP_FREE (1024ULL*1024ULL)                    /* 1 MB */
+
+static const char signature[] = { 'L', 'P', 'K', 'S', 'H', 'H', 'R', 'H' };
+
+#define ALIGN64(x) (((x) + 7ULL) & ~7ULL)
+
+void journal_file_close(JournalFile *f) {
+        int t;
+
+        assert(f);
+
+        if (f->header && f->writable)
+                f->header->state = STATE_OFFLINE;
+
+
+        for (t = 0; t < _WINDOW_MAX; t++)
+                if (f->windows[t].ptr)
+                        munmap(f->windows[t].ptr, f->windows[t].size);
+
+        if (f->fd >= 0)
+                close_nointr_nofail(f->fd);
+
+        free(f->path);
+
+#ifdef HAVE_XZ
+        free(f->compress_buffer);
+#endif
+
+        free(f);
+}
+
+static int journal_file_init_header(JournalFile *f, JournalFile *template) {
+        Header h;
+        ssize_t k;
+        int r;
+
+        assert(f);
+
+        zero(h);
+        memcpy(h.signature, signature, 8);
+        h.arena_offset = htole64(ALIGN64(sizeof(h)));
+
+        r = sd_id128_randomize(&h.file_id);
+        if (r < 0)
+                return r;
+
+        if (template) {
+                h.seqnum_id = template->header->seqnum_id;
+                h.seqnum = template->header->seqnum;
+        } else
+                h.seqnum_id = h.file_id;
+
+        k = pwrite(f->fd, &h, sizeof(h), 0);
+        if (k < 0)
+                return -errno;
+
+        if (k != sizeof(h))
+                return -EIO;
+
+        return 0;
+}
+
+static int journal_file_refresh_header(JournalFile *f) {
+        int r;
+        sd_id128_t boot_id;
+
+        assert(f);
+
+        r = sd_id128_get_machine(&f->header->machine_id);
+        if (r < 0)
+                return r;
+
+        r = sd_id128_get_boot(&boot_id);
+        if (r < 0)
+                return r;
+
+        if (sd_id128_equal(boot_id, f->header->boot_id))
+                f->tail_entry_monotonic_valid = true;
+
+        f->header->boot_id = boot_id;
+
+        f->header->state = STATE_ONLINE;
+
+        __sync_synchronize();
+
+        return 0;
+}
+
+static int journal_file_verify_header(JournalFile *f) {
+        assert(f);
+
+        if (memcmp(f->header, signature, 8))
+                return -EBADMSG;
+
+#ifdef HAVE_XZ
+        if ((le64toh(f->header->incompatible_flags) & ~HEADER_INCOMPATIBLE_COMPRESSED) != 0)
+                return -EPROTONOSUPPORT;
+#else
+        if (f->header->incompatible_flags != 0)
+                return -EPROTONOSUPPORT;
+#endif
+
+        if ((uint64_t) f->last_stat.st_size < (le64toh(f->header->arena_offset) + le64toh(f->header->arena_size)))
+                return -ENODATA;
+
+        if (f->writable) {
+                uint8_t state;
+                sd_id128_t machine_id;
+                int r;
+
+                r = sd_id128_get_machine(&machine_id);
+                if (r < 0)
+                        return r;
+
+                if (!sd_id128_equal(machine_id, f->header->machine_id))
+                        return -EHOSTDOWN;
+
+                state = f->header->state;
+
+                if (state == STATE_ONLINE)
+                        log_debug("Journal file %s is already online. Assuming unclean closing. Ignoring.", f->path);
+                else if (state == STATE_ARCHIVED)
+                        return -ESHUTDOWN;
+                else if (state != STATE_OFFLINE)
+                        log_debug("Journal file %s has unknown state %u. Ignoring.", f->path, state);
+        }
+
+        return 0;
+}
+
+static int journal_file_allocate(JournalFile *f, uint64_t offset, uint64_t size) {
+        uint64_t old_size, new_size;
+
+        assert(f);
+
+        /* We assume that this file is not sparse, and we know that
+         * for sure, since we always call posix_fallocate()
+         * ourselves */
+
+        old_size =
+                le64toh(f->header->arena_offset) +
+                le64toh(f->header->arena_size);
+
+        new_size = PAGE_ALIGN(offset + size);
+        if (new_size < le64toh(f->header->arena_offset))
+                new_size = le64toh(f->header->arena_offset);
+
+        if (new_size <= old_size)
+                return 0;
+
+        if (f->metrics.max_size > 0 &&
+            new_size > f->metrics.max_size)
+                return -E2BIG;
+
+        if (new_size > f->metrics.min_size &&
+            f->metrics.keep_free > 0) {
+                struct statvfs svfs;
+
+                if (fstatvfs(f->fd, &svfs) >= 0) {
+                        uint64_t available;
+
+                        available = svfs.f_bfree * svfs.f_bsize;
+
+                        if (available >= f->metrics.keep_free)
+                                available -= f->metrics.keep_free;
+                        else
+                                available = 0;
+
+                        if (new_size - old_size > available)
+                                return -E2BIG;
+                }
+        }
+
+        /* Note that the glibc fallocate() fallback is very
+           inefficient, hence we try to minimize the allocation area
+           as we can. */
+        if (posix_fallocate(f->fd, old_size, new_size - old_size) < 0)
+                return -errno;
+
+        if (fstat(f->fd, &f->last_stat) < 0)
+                return -errno;
+
+        f->header->arena_size = htole64(new_size - le64toh(f->header->arena_offset));
+
+        return 0;
+}
+
+static int journal_file_map(
+                JournalFile *f,
+                uint64_t offset,
+                uint64_t size,
+                void **_window,
+                uint64_t *_woffset,
+                uint64_t *_wsize,
+                void **ret) {
+
+        uint64_t woffset, wsize;
+        void *window;
+
+        assert(f);
+        assert(size > 0);
+        assert(ret);
+
+        woffset = offset & ~((uint64_t) page_size() - 1ULL);
+        wsize = size + (offset - woffset);
+        wsize = PAGE_ALIGN(wsize);
+
+        /* Avoid SIGBUS on invalid accesses */
+        if (woffset + wsize > (uint64_t) PAGE_ALIGN(f->last_stat.st_size))
+                return -EADDRNOTAVAIL;
+
+        window = mmap(NULL, wsize, f->prot, MAP_SHARED, f->fd, woffset);
+        if (window == MAP_FAILED)
+                return -errno;
+
+        if (_window)
+                *_window = window;
+
+        if (_woffset)
+                *_woffset = woffset;
+
+        if (_wsize)
+                *_wsize = wsize;
+
+        *ret = (uint8_t*) window + (offset - woffset);
+
+        return 0;
+}
+
+static int journal_file_move_to(JournalFile *f, int wt, uint64_t offset, uint64_t size, void **ret) {
+        void *p = NULL;
+        uint64_t delta;
+        int r;
+        Window *w;
+
+        assert(f);
+        assert(ret);
+        assert(wt >= 0);
+        assert(wt < _WINDOW_MAX);
+
+        if (offset + size > (uint64_t) f->last_stat.st_size) {
+                /* Hmm, out of range? Let's refresh the fstat() data
+                 * first, before we trust that check. */
+
+                if (fstat(f->fd, &f->last_stat) < 0 ||
+                    offset + size > (uint64_t) f->last_stat.st_size)
+                        return -EADDRNOTAVAIL;
+        }
+
+        w = f->windows + wt;
+
+        if (_likely_(w->ptr &&
+                     w->offset <= offset &&
+                     w->offset + w->size >= offset + size)) {
+
+                *ret = (uint8_t*) w->ptr + (offset - w->offset);
+                return 0;
+        }
+
+        if (w->ptr) {
+                if (munmap(w->ptr, w->size) < 0)
+                        return -errno;
+
+                w->ptr = NULL;
+                w->size = w->offset = 0;
+        }
+
+        if (size < DEFAULT_WINDOW_SIZE) {
+                /* If the default window size is larger then what was
+                 * asked for extend the mapping a bit in the hope to
+                 * minimize needed remappings later on. We add half
+                 * the window space before and half behind the
+                 * requested mapping */
+
+                delta = (DEFAULT_WINDOW_SIZE - size) / 2;
+
+                if (delta > offset)
+                        delta = offset;
+
+                offset -= delta;
+                size = DEFAULT_WINDOW_SIZE;
+        } else
+                delta = 0;
+
+        if (offset + size > (uint64_t) f->last_stat.st_size)
+                size = (uint64_t) f->last_stat.st_size - offset;
+
+        if (size <= 0)
+                return -EADDRNOTAVAIL;
+
+        r = journal_file_map(f,
+                             offset, size,
+                             &w->ptr, &w->offset, &w->size,
+                             &p);
+
+        if (r < 0)
+                return r;
+
+        *ret = (uint8_t*) p + delta;
+        return 0;
+}
+
+static bool verify_hash(Object *o) {
+        uint64_t h1, h2;
+
+        assert(o);
+
+        if (o->object.type == OBJECT_DATA && !(o->object.flags & OBJECT_COMPRESSED)) {
+                h1 = le64toh(o->data.hash);
+                h2 = hash64(o->data.payload, le64toh(o->object.size) - offsetof(Object, data.payload));
+        } else if (o->object.type == OBJECT_FIELD) {
+                h1 = le64toh(o->field.hash);
+                h2 = hash64(o->field.payload, le64toh(o->object.size) - offsetof(Object, field.payload));
+        } else
+                return true;
+
+        return h1 == h2;
+}
+
+int journal_file_move_to_object(JournalFile *f, int type, uint64_t offset, Object **ret) {
+        int r;
+        void *t;
+        Object *o;
+        uint64_t s;
+
+        assert(f);
+        assert(ret);
+        assert(type < _OBJECT_TYPE_MAX);
+
+        r = journal_file_move_to(f, type >= 0 ? type : WINDOW_UNKNOWN, offset, sizeof(ObjectHeader), &t);
+        if (r < 0)
+                return r;
+
+        o = (Object*) t;
+        s = le64toh(o->object.size);
+
+        if (s < sizeof(ObjectHeader))
+                return -EBADMSG;
+
+        if (type >= 0 && o->object.type != type)
+                return -EBADMSG;
+
+        if (s > sizeof(ObjectHeader)) {
+                r = journal_file_move_to(f, o->object.type, offset, s, &t);
+                if (r < 0)
+                        return r;
+
+                o = (Object*) t;
+        }
+
+        if (!verify_hash(o))
+                return -EBADMSG;
+
+        *ret = o;
+        return 0;
+}
+
+static uint64_t journal_file_seqnum(JournalFile *f, uint64_t *seqnum) {
+        uint64_t r;
+
+        assert(f);
+
+        r = le64toh(f->header->seqnum) + 1;
+
+        if (seqnum) {
+                /* If an external seqnum counter was passed, we update
+                 * both the local and the external one, and set it to
+                 * the maximum of both */
+
+                if (*seqnum + 1 > r)
+                        r = *seqnum + 1;
+
+                *seqnum = r;
+        }
+
+        f->header->seqnum = htole64(r);
+
+        if (f->header->first_seqnum == 0)
+                f->header->first_seqnum = htole64(r);
+
+        return r;
+}
+
+static int journal_file_append_object(JournalFile *f, int type, uint64_t size, Object **ret, uint64_t *offset) {
+        int r;
+        uint64_t p;
+        Object *tail, *o;
+        void *t;
+
+        assert(f);
+        assert(size >= sizeof(ObjectHeader));
+        assert(offset);
+        assert(ret);
+
+        p = le64toh(f->header->tail_object_offset);
+        if (p == 0)
+                p = le64toh(f->header->arena_offset);
+        else {
+                r = journal_file_move_to_object(f, -1, p, &tail);
+                if (r < 0)
+                        return r;
+
+                p += ALIGN64(le64toh(tail->object.size));
+        }
+
+        r = journal_file_allocate(f, p, size);
+        if (r < 0)
+                return r;
+
+        r = journal_file_move_to(f, type, p, size, &t);
+        if (r < 0)
+                return r;
+
+        o = (Object*) t;
+
+        zero(o->object);
+        o->object.type = type;
+        o->object.size = htole64(size);
+
+        f->header->tail_object_offset = htole64(p);
+        f->header->n_objects = htole64(le64toh(f->header->n_objects) + 1);
+
+        *ret = o;
+        *offset = p;
+
+        return 0;
+}
+
+static int journal_file_setup_data_hash_table(JournalFile *f) {
+        uint64_t s, p;
+        Object *o;
+        int r;
+
+        assert(f);
+
+        s = DEFAULT_DATA_HASH_TABLE_SIZE;
+        r = journal_file_append_object(f,
+                                       OBJECT_DATA_HASH_TABLE,
+                                       offsetof(Object, hash_table.items) + s,
+                                       &o, &p);
+        if (r < 0)
+                return r;
+
+        memset(o->hash_table.items, 0, s);
+
+        f->header->data_hash_table_offset = htole64(p + offsetof(Object, hash_table.items));
+        f->header->data_hash_table_size = htole64(s);
+
+        return 0;
+}
+
+static int journal_file_setup_field_hash_table(JournalFile *f) {
+        uint64_t s, p;
+        Object *o;
+        int r;
+
+        assert(f);
+
+        s = DEFAULT_FIELD_HASH_TABLE_SIZE;
+        r = journal_file_append_object(f,
+                                       OBJECT_FIELD_HASH_TABLE,
+                                       offsetof(Object, hash_table.items) + s,
+                                       &o, &p);
+        if (r < 0)
+                return r;
+
+        memset(o->hash_table.items, 0, s);
+
+        f->header->field_hash_table_offset = htole64(p + offsetof(Object, hash_table.items));
+        f->header->field_hash_table_size = htole64(s);
+
+        return 0;
+}
+
+static int journal_file_map_data_hash_table(JournalFile *f) {
+        uint64_t s, p;
+        void *t;
+        int r;
+
+        assert(f);
+
+        p = le64toh(f->header->data_hash_table_offset);
+        s = le64toh(f->header->data_hash_table_size);
+
+        r = journal_file_move_to(f,
+                                 WINDOW_DATA_HASH_TABLE,
+                                 p, s,
+                                 &t);
+        if (r < 0)
+                return r;
+
+        f->data_hash_table = t;
+        return 0;
+}
+
+static int journal_file_map_field_hash_table(JournalFile *f) {
+        uint64_t s, p;
+        void *t;
+        int r;
+
+        assert(f);
+
+        p = le64toh(f->header->field_hash_table_offset);
+        s = le64toh(f->header->field_hash_table_size);
+
+        r = journal_file_move_to(f,
+                                 WINDOW_FIELD_HASH_TABLE,
+                                 p, s,
+                                 &t);
+        if (r < 0)
+                return r;
+
+        f->field_hash_table = t;
+        return 0;
+}
+
+static int journal_file_link_data(JournalFile *f, Object *o, uint64_t offset, uint64_t hash) {
+        uint64_t p, h;
+        int r;
+
+        assert(f);
+        assert(o);
+        assert(offset > 0);
+        assert(o->object.type == OBJECT_DATA);
+
+        /* This might alter the window we are looking at */
+
+        o->data.next_hash_offset = o->data.next_field_offset = 0;
+        o->data.entry_offset = o->data.entry_array_offset = 0;
+        o->data.n_entries = 0;
+
+        h = hash % (le64toh(f->header->data_hash_table_size) / sizeof(HashItem));
+        p = le64toh(f->data_hash_table[h].head_hash_offset);
+        if (p == 0) {
+                /* Only entry in the hash table is easy */
+                f->data_hash_table[h].head_hash_offset = htole64(offset);
+        } else {
+                /* Move back to the previous data object, to patch in
+                 * pointer */
+
+                r = journal_file_move_to_object(f, OBJECT_DATA, p, &o);
+                if (r < 0)
+                        return r;
+
+                o->data.next_hash_offset = htole64(offset);
+        }
+
+        f->data_hash_table[h].tail_hash_offset = htole64(offset);
+
+        return 0;
+}
+
+int journal_file_find_data_object_with_hash(
+                JournalFile *f,
+                const void *data, uint64_t size, uint64_t hash,
+                Object **ret, uint64_t *offset) {
+
+        uint64_t p, osize, h;
+        int r;
+
+        assert(f);
+        assert(data || size == 0);
+
+        osize = offsetof(Object, data.payload) + size;
+
+        if (f->header->data_hash_table_size == 0)
+                return -EBADMSG;
+
+        h = hash % (le64toh(f->header->data_hash_table_size) / sizeof(HashItem));
+        p = le64toh(f->data_hash_table[h].head_hash_offset);
+
+        while (p > 0) {
+                Object *o;
+
+                r = journal_file_move_to_object(f, OBJECT_DATA, p, &o);
+                if (r < 0)
+                        return r;
+
+                if (le64toh(o->data.hash) != hash)
+                        goto next;
+
+                if (o->object.flags & OBJECT_COMPRESSED) {
+#ifdef HAVE_XZ
+                        uint64_t l, rsize;
+
+                        l = le64toh(o->object.size);
+                        if (l <= offsetof(Object, data.payload))
+                                return -EBADMSG;
+
+                        l -= offsetof(Object, data.payload);
+
+                        if (!uncompress_blob(o->data.payload, l, &f->compress_buffer, &f->compress_buffer_size, &rsize))
+                                return -EBADMSG;
+
+                        if (rsize == size &&
+                            memcmp(f->compress_buffer, data, size) == 0) {
+
+                                if (ret)
+                                        *ret = o;
+
+                                if (offset)
+                                        *offset = p;
+
+                                return 1;
+                        }
+#else
+                        return -EPROTONOSUPPORT;
+#endif
+
+                } else if (le64toh(o->object.size) == osize &&
+                           memcmp(o->data.payload, data, size) == 0) {
+
+                        if (ret)
+                                *ret = o;
+
+                        if (offset)
+                                *offset = p;
+
+                        return 1;
+                }
+
+        next:
+                p = le64toh(o->data.next_hash_offset);
+        }
+
+        return 0;
+}
+
+int journal_file_find_data_object(
+                JournalFile *f,
+                const void *data, uint64_t size,
+                Object **ret, uint64_t *offset) {
+
+        uint64_t hash;
+
+        assert(f);
+        assert(data || size == 0);
+
+        hash = hash64(data, size);
+
+        return journal_file_find_data_object_with_hash(f,
+                                                       data, size, hash,
+                                                       ret, offset);
+}
+
+static int journal_file_append_data(
+                JournalFile *f,
+                const void *data, uint64_t size,
+                Object **ret, uint64_t *offset) {
+
+        uint64_t hash, p;
+        uint64_t osize;
+        Object *o;
+        int r;
+        bool compressed = false;
+
+        assert(f);
+        assert(data || size == 0);
+
+        hash = hash64(data, size);
+
+        r = journal_file_find_data_object_with_hash(f, data, size, hash, &o, &p);
+        if (r < 0)
+                return r;
+        else if (r > 0) {
+
+                if (ret)
+                        *ret = o;
+
+                if (offset)
+                        *offset = p;
+
+                return 0;
+        }
+
+        osize = offsetof(Object, data.payload) + size;
+        r = journal_file_append_object(f, OBJECT_DATA, osize, &o, &p);
+        if (r < 0)
+                return r;
+
+        o->data.hash = htole64(hash);
+
+#ifdef HAVE_XZ
+        if (f->compress &&
+            size >= COMPRESSION_SIZE_THRESHOLD) {
+                uint64_t rsize;
+
+                compressed = compress_blob(data, size, o->data.payload, &rsize);
+
+                if (compressed) {
+                        o->object.size = htole64(offsetof(Object, data.payload) + rsize);
+                        o->object.flags |= OBJECT_COMPRESSED;
+
+                        f->header->incompatible_flags = htole32(le32toh(f->header->incompatible_flags) | HEADER_INCOMPATIBLE_COMPRESSED);
+
+                        log_debug("Compressed data object %lu -> %lu", (unsigned long) size, (unsigned long) rsize);
+                }
+        }
+#endif
+
+        if (!compressed)
+                memcpy(o->data.payload, data, size);
+
+        r = journal_file_link_data(f, o, p, hash);
+        if (r < 0)
+                return r;
+
+        /* The linking might have altered the window, so let's
+         * refresh our pointer */
+        r = journal_file_move_to_object(f, OBJECT_DATA, p, &o);
+        if (r < 0)
+                return r;
+
+        if (ret)
+                *ret = o;
+
+        if (offset)
+                *offset = p;
+
+        return 0;
+}
+
+uint64_t journal_file_entry_n_items(Object *o) {
+        assert(o);
+        assert(o->object.type == OBJECT_ENTRY);
+
+        return (le64toh(o->object.size) - offsetof(Object, entry.items)) / sizeof(EntryItem);
+}
+
+static uint64_t journal_file_entry_array_n_items(Object *o) {
+        assert(o);
+        assert(o->object.type == OBJECT_ENTRY_ARRAY);
+
+        return (le64toh(o->object.size) - offsetof(Object, entry_array.items)) / sizeof(uint64_t);
+}
+
+static int link_entry_into_array(JournalFile *f,
+                                 le64_t *first,
+                                 le64_t *idx,
+                                 uint64_t p) {
+        int r;
+        uint64_t n = 0, ap = 0, q, i, a, hidx;
+        Object *o;
+
+        assert(f);
+        assert(first);
+        assert(idx);
+        assert(p > 0);
+
+        a = le64toh(*first);
+        i = hidx = le64toh(*idx);
+        while (a > 0) {
+
+                r = journal_file_move_to_object(f, OBJECT_ENTRY_ARRAY, a, &o);
+                if (r < 0)
+                        return r;
+
+                n = journal_file_entry_array_n_items(o);
+                if (i < n) {
+                        o->entry_array.items[i] = htole64(p);
+                        *idx = htole64(hidx + 1);
+                        return 0;
+                }
+
+                i -= n;
+                ap = a;
+                a = le64toh(o->entry_array.next_entry_array_offset);
+        }
+
+        if (hidx > n)
+                n = (hidx+1) * 2;
+        else
+                n = n * 2;
+
+        if (n < 4)
+                n = 4;
+
+        r = journal_file_append_object(f, OBJECT_ENTRY_ARRAY,
+                                       offsetof(Object, entry_array.items) + n * sizeof(uint64_t),
+                                       &o, &q);
+        if (r < 0)
+                return r;
+
+        o->entry_array.items[i] = htole64(p);
+
+        if (ap == 0)
+                *first = htole64(q);
+        else {
+                r = journal_file_move_to_object(f, OBJECT_ENTRY_ARRAY, ap, &o);
+                if (r < 0)
+                        return r;
+
+                o->entry_array.next_entry_array_offset = htole64(q);
+        }
+
+        *idx = htole64(hidx + 1);
+
+        return 0;
+}
+
+static int link_entry_into_array_plus_one(JournalFile *f,
+                                          le64_t *extra,
+                                          le64_t *first,
+                                          le64_t *idx,
+                                          uint64_t p) {
+
+        int r;
+
+        assert(f);
+        assert(extra);
+        assert(first);
+        assert(idx);
+        assert(p > 0);
+
+        if (*idx == 0)
+                *extra = htole64(p);
+        else {
+                le64_t i;
+
+                i = htole64(le64toh(*idx) - 1);
+                r = link_entry_into_array(f, first, &i, p);
+                if (r < 0)
+                        return r;
+        }
+
+        *idx = htole64(le64toh(*idx) + 1);
+        return 0;
+}
+
+static int journal_file_link_entry_item(JournalFile *f, Object *o, uint64_t offset, uint64_t i) {
+        uint64_t p;
+        int r;
+        assert(f);
+        assert(o);
+        assert(offset > 0);
+
+        p = le64toh(o->entry.items[i].object_offset);
+        if (p == 0)
+                return -EINVAL;
+
+        r = journal_file_move_to_object(f, OBJECT_DATA, p, &o);
+        if (r < 0)
+                return r;
+
+        return link_entry_into_array_plus_one(f,
+                                              &o->data.entry_offset,
+                                              &o->data.entry_array_offset,
+                                              &o->data.n_entries,
+                                              offset);
+}
+
+static int journal_file_link_entry(JournalFile *f, Object *o, uint64_t offset) {
+        uint64_t n, i;
+        int r;
+
+        assert(f);
+        assert(o);
+        assert(offset > 0);
+        assert(o->object.type == OBJECT_ENTRY);
+
+        __sync_synchronize();
+
+        /* Link up the entry itself */
+        r = link_entry_into_array(f,
+                                  &f->header->entry_array_offset,
+                                  &f->header->n_entries,
+                                  offset);
+        if (r < 0)
+                return r;
+
+        /* log_debug("=> %s seqnr=%lu n_entries=%lu", f->path, (unsigned long) o->entry.seqnum, (unsigned long) f->header->n_entries); */
+
+        if (f->header->head_entry_realtime == 0)
+                f->header->head_entry_realtime = o->entry.realtime;
+
+        f->header->tail_entry_realtime = o->entry.realtime;
+        f->header->tail_entry_monotonic = o->entry.monotonic;
+
+        f->tail_entry_monotonic_valid = true;
+
+        /* Link up the items */
+        n = journal_file_entry_n_items(o);
+        for (i = 0; i < n; i++) {
+                r = journal_file_link_entry_item(f, o, offset, i);
+                if (r < 0)
+                        return r;
+        }
+
+        return 0;
+}
+
+static int journal_file_append_entry_internal(
+                JournalFile *f,
+                const dual_timestamp *ts,
+                uint64_t xor_hash,
+                const EntryItem items[], unsigned n_items,
+                uint64_t *seqnum,
+                Object **ret, uint64_t *offset) {
+        uint64_t np;
+        uint64_t osize;
+        Object *o;
+        int r;
+
+        assert(f);
+        assert(items || n_items == 0);
+        assert(ts);
+
+        osize = offsetof(Object, entry.items) + (n_items * sizeof(EntryItem));
+
+        r = journal_file_append_object(f, OBJECT_ENTRY, osize, &o, &np);
+        if (r < 0)
+                return r;
+
+        o->entry.seqnum = htole64(journal_file_seqnum(f, seqnum));
+        memcpy(o->entry.items, items, n_items * sizeof(EntryItem));
+        o->entry.realtime = htole64(ts->realtime);
+        o->entry.monotonic = htole64(ts->monotonic);
+        o->entry.xor_hash = htole64(xor_hash);
+        o->entry.boot_id = f->header->boot_id;
+
+        r = journal_file_link_entry(f, o, np);
+        if (r < 0)
+                return r;
+
+        if (ret)
+                *ret = o;
+
+        if (offset)
+                *offset = np;
+
+        return 0;
+}
+
+void journal_file_post_change(JournalFile *f) {
+        assert(f);
+
+        /* inotify() does not receive IN_MODIFY events from file
+         * accesses done via mmap(). After each access we hence
+         * trigger IN_MODIFY by truncating the journal file to its
+         * current size which triggers IN_MODIFY. */
+
+        __sync_synchronize();
+
+        if (ftruncate(f->fd, f->last_stat.st_size) < 0)
+                log_error("Failed to to truncate file to its own size: %m");
+}
+
+int journal_file_append_entry(JournalFile *f, const dual_timestamp *ts, const struct iovec iovec[], unsigned n_iovec, uint64_t *seqnum, Object **ret, uint64_t *offset) {
+        unsigned i;
+        EntryItem *items;
+        int r;
+        uint64_t xor_hash = 0;
+        struct dual_timestamp _ts;
+
+        assert(f);
+        assert(iovec || n_iovec == 0);
+
+        if (!f->writable)
+                return -EPERM;
+
+        if (!ts) {
+                dual_timestamp_get(&_ts);
+                ts = &_ts;
+        }
+
+        if (f->tail_entry_monotonic_valid &&
+            ts->monotonic < le64toh(f->header->tail_entry_monotonic))
+                return -EINVAL;
+
+        items = alloca(sizeof(EntryItem) * n_iovec);
+
+        for (i = 0; i < n_iovec; i++) {
+                uint64_t p;
+                Object *o;
+
+                r = journal_file_append_data(f, iovec[i].iov_base, iovec[i].iov_len, &o, &p);
+                if (r < 0)
+                        return r;
+
+                xor_hash ^= le64toh(o->data.hash);
+                items[i].object_offset = htole64(p);
+                items[i].hash = o->data.hash;
+        }
+
+        r = journal_file_append_entry_internal(f, ts, xor_hash, items, n_iovec, seqnum, ret, offset);
+
+        journal_file_post_change(f);
+
+        return r;
+}
+
+static int generic_array_get(JournalFile *f,
+                             uint64_t first,
+                             uint64_t i,
+                             Object **ret, uint64_t *offset) {
+
+        Object *o;
+        uint64_t p = 0, a;
+        int r;
+
+        assert(f);
+
+        a = first;
+        while (a > 0) {
+                uint64_t n;
+
+                r = journal_file_move_to_object(f, OBJECT_ENTRY_ARRAY, a, &o);
+                if (r < 0)
+                        return r;
+
+                n = journal_file_entry_array_n_items(o);
+                if (i < n) {
+                        p = le64toh(o->entry_array.items[i]);
+                        break;
+                }
+
+                i -= n;
+                a = le64toh(o->entry_array.next_entry_array_offset);
+        }
+
+        if (a <= 0 || p <= 0)
+                return 0;
+
+        r = journal_file_move_to_object(f, OBJECT_ENTRY, p, &o);
+        if (r < 0)
+                return r;
+
+        if (ret)
+                *ret = o;
+
+        if (offset)
+                *offset = p;
+
+        return 1;
+}
+
+static int generic_array_get_plus_one(JournalFile *f,
+                                      uint64_t extra,
+                                      uint64_t first,
+                                      uint64_t i,
+                                      Object **ret, uint64_t *offset) {
+
+        Object *o;
+
+        assert(f);
+
+        if (i == 0) {
+                int r;
+
+                r = journal_file_move_to_object(f, OBJECT_ENTRY, extra, &o);
+                if (r < 0)
+                        return r;
+
+                if (ret)
+                        *ret = o;
+
+                if (offset)
+                        *offset = extra;
+
+                return 1;
+        }
+
+        return generic_array_get(f, first, i-1, ret, offset);
+}
+
+enum {
+        TEST_FOUND,
+        TEST_LEFT,
+        TEST_RIGHT
+};
+
+static int generic_array_bisect(JournalFile *f,
+                                uint64_t first,
+                                uint64_t n,
+                                uint64_t needle,
+                                int (*test_object)(JournalFile *f, uint64_t p, uint64_t needle),
+                                direction_t direction,
+                                Object **ret,
+                                uint64_t *offset,
+                                uint64_t *idx) {
+
+        uint64_t a, p, t = 0, i = 0, last_p = 0;
+        bool subtract_one = false;
+        Object *o, *array = NULL;
+        int r;
+
+        assert(f);
+        assert(test_object);
+
+        a = first;
+        while (a > 0) {
+                uint64_t left, right, k, lp;
+
+                r = journal_file_move_to_object(f, OBJECT_ENTRY_ARRAY, a, &array);
+                if (r < 0)
+                        return r;
+
+                k = journal_file_entry_array_n_items(array);
+                right = MIN(k, n);
+                if (right <= 0)
+                        return 0;
+
+                i = right - 1;
+                lp = p = le64toh(array->entry_array.items[i]);
+                if (p <= 0)
+                        return -EBADMSG;
+
+                r = test_object(f, p, needle);
+                if (r < 0)
+                        return r;
+
+                if (r == TEST_FOUND)
+                        r = direction == DIRECTION_DOWN ? TEST_RIGHT : TEST_LEFT;
+
+                if (r == TEST_RIGHT) {
+                        left = 0;
+                        right -= 1;
+                        for (;;) {
+                                if (left == right) {
+                                        if (direction == DIRECTION_UP)
+                                                subtract_one = true;
+
+                                        i = left;
+                                        goto found;
+                                }
+
+                                assert(left < right);
+
+                                i = (left + right) / 2;
+                                p = le64toh(array->entry_array.items[i]);
+                                if (p <= 0)
+                                        return -EBADMSG;
+
+                                r = test_object(f, p, needle);
+                                if (r < 0)
+                                        return r;
+
+                                if (r == TEST_FOUND)
+                                        r = direction == DIRECTION_DOWN ? TEST_RIGHT : TEST_LEFT;
+
+                                if (r == TEST_RIGHT)
+                                        right = i;
+                                else
+                                        left = i + 1;
+                        }
+                }
+
+                if (k > n)
+                        return 0;
+
+                last_p = lp;
+
+                n -= k;
+                t += k;
+                a = le64toh(array->entry_array.next_entry_array_offset);
+        }
+
+        return 0;
+
+found:
+        if (subtract_one && t == 0 && i == 0)
+                return 0;
+
+        if (subtract_one && i == 0)
+                p = last_p;
+        else if (subtract_one)
+                p = le64toh(array->entry_array.items[i-1]);
+        else
+                p = le64toh(array->entry_array.items[i]);
+
+        r = journal_file_move_to_object(f, OBJECT_ENTRY, p, &o);
+        if (r < 0)
+                return r;
+
+        if (ret)
+                *ret = o;
+
+        if (offset)
+                *offset = p;
+
+        if (idx)
+                *idx = t + i - (subtract_one ? 1 : 0);
+
+        return 1;
+}
+
+static int generic_array_bisect_plus_one(JournalFile *f,
+                                         uint64_t extra,
+                                         uint64_t first,
+                                         uint64_t n,
+                                         uint64_t needle,
+                                         int (*test_object)(JournalFile *f, uint64_t p, uint64_t needle),
+                                         direction_t direction,
+                                         Object **ret,
+                                         uint64_t *offset,
+                                         uint64_t *idx) {
+
+        int r;
+
+        assert(f);
+        assert(test_object);
+
+        if (n <= 0)
+                return 0;
+
+        /* This bisects the array in object 'first', but first checks
+         * an extra  */
+        r = test_object(f, extra, needle);
+        if (r < 0)
+                return r;
+        else if (r == TEST_FOUND) {
+                Object *o;
+
+                r = journal_file_move_to_object(f, OBJECT_ENTRY, extra, &o);
+                if (r < 0)
+                        return r;
+
+                if (ret)
+                        *ret = o;
+
+                if (offset)
+                        *offset = extra;
+
+                if (idx)
+                        *idx = 0;
+
+                return 1;
+        } else if (r == TEST_RIGHT)
+                return 0;
+
+        r = generic_array_bisect(f, first, n-1, needle, test_object, direction, ret, offset, idx);
+
+        if (r > 0)
+                (*idx) ++;
+
+        return r;
+}
+
+static int test_object_seqnum(JournalFile *f, uint64_t p, uint64_t needle) {
+        Object *o;
+        int r;
+
+        assert(f);
+        assert(p > 0);
+
+        r = journal_file_move_to_object(f, OBJECT_ENTRY, p, &o);
+        if (r < 0)
+                return r;
+
+        if (le64toh(o->entry.seqnum) == needle)
+                return TEST_FOUND;
+        else if (le64toh(o->entry.seqnum) < needle)
+                return TEST_LEFT;
+        else
+                return TEST_RIGHT;
+}
+
+int journal_file_move_to_entry_by_seqnum(
+                JournalFile *f,
+                uint64_t seqnum,
+                direction_t direction,
+                Object **ret,
+                uint64_t *offset) {
+
+        return generic_array_bisect(f,
+                                    le64toh(f->header->entry_array_offset),
+                                    le64toh(f->header->n_entries),
+                                    seqnum,
+                                    test_object_seqnum,
+                                    direction,
+                                    ret, offset, NULL);
+}
+
+static int test_object_realtime(JournalFile *f, uint64_t p, uint64_t needle) {
+        Object *o;
+        int r;
+
+        assert(f);
+        assert(p > 0);
+
+        r = journal_file_move_to_object(f, OBJECT_ENTRY, p, &o);
+        if (r < 0)
+                return r;
+
+        if (le64toh(o->entry.realtime) == needle)
+                return TEST_FOUND;
+        else if (le64toh(o->entry.realtime) < needle)
+                return TEST_LEFT;
+        else
+                return TEST_RIGHT;
+}
+
+int journal_file_move_to_entry_by_realtime(
+                JournalFile *f,
+                uint64_t realtime,
+                direction_t direction,
+                Object **ret,
+                uint64_t *offset) {
+
+        return generic_array_bisect(f,
+                                    le64toh(f->header->entry_array_offset),
+                                    le64toh(f->header->n_entries),
+                                    realtime,
+                                    test_object_realtime,
+                                    direction,
+                                    ret, offset, NULL);
+}
+
+static int test_object_monotonic(JournalFile *f, uint64_t p, uint64_t needle) {
+        Object *o;
+        int r;
+
+        assert(f);
+        assert(p > 0);
+
+        r = journal_file_move_to_object(f, OBJECT_ENTRY, p, &o);
+        if (r < 0)
+                return r;
+
+        if (le64toh(o->entry.monotonic) == needle)
+                return TEST_FOUND;
+        else if (le64toh(o->entry.monotonic) < needle)
+                return TEST_LEFT;
+        else
+                return TEST_RIGHT;
+}
+
+int journal_file_move_to_entry_by_monotonic(
+                JournalFile *f,
+                sd_id128_t boot_id,
+                uint64_t monotonic,
+                direction_t direction,
+                Object **ret,
+                uint64_t *offset) {
+
+        char t[8+32+1] = "_BOOT_ID=";
+        Object *o;
+        int r;
+
+        sd_id128_to_string(boot_id, t + 8);
+
+        r = journal_file_find_data_object(f, t, strlen(t), &o, NULL);
+        if (r < 0)
+                return r;
+        else if (r == 0)
+                return -ENOENT;
+
+        return generic_array_bisect_plus_one(f,
+                                             le64toh(o->data.entry_offset),
+                                             le64toh(o->data.entry_array_offset),
+                                             le64toh(o->data.n_entries),
+                                             monotonic,
+                                             test_object_monotonic,
+                                             direction,
+                                             ret, offset, NULL);
+}
+
+static int test_object_offset(JournalFile *f, uint64_t p, uint64_t needle) {
+        assert(f);
+        assert(p > 0);
+
+        if (p == needle)
+                return TEST_FOUND;
+        else if (p < needle)
+                return TEST_LEFT;
+        else
+                return TEST_RIGHT;
+}
+
+int journal_file_next_entry(
+                JournalFile *f,
+                Object *o, uint64_t p,
+                direction_t direction,
+                Object **ret, uint64_t *offset) {
+
+        uint64_t i, n;
+        int r;
+
+        assert(f);
+        assert(p > 0 || !o);
+
+        n = le64toh(f->header->n_entries);
+        if (n <= 0)
+                return 0;
+
+        if (!o)
+                i = direction == DIRECTION_DOWN ? 0 : n - 1;
+        else {
+                if (o->object.type != OBJECT_ENTRY)
+                        return -EINVAL;
+
+                r = generic_array_bisect(f,
+                                         le64toh(f->header->entry_array_offset),
+                                         le64toh(f->header->n_entries),
+                                         p,
+                                         test_object_offset,
+                                         DIRECTION_DOWN,
+                                         NULL, NULL,
+                                         &i);
+                if (r <= 0)
+                        return r;
+
+                if (direction == DIRECTION_DOWN) {
+                        if (i >= n - 1)
+                                return 0;
+
+                        i++;
+                } else {
+                        if (i <= 0)
+                                return 0;
+
+                        i--;
+                }
+        }
+
+        /* And jump to it */
+        return generic_array_get(f,
+                                 le64toh(f->header->entry_array_offset),
+                                 i,
+                                 ret, offset);
+}
+
+int journal_file_skip_entry(
+                JournalFile *f,
+                Object *o, uint64_t p,
+                int64_t skip,
+                Object **ret, uint64_t *offset) {
+
+        uint64_t i, n;
+        int r;
+
+        assert(f);
+        assert(o);
+        assert(p > 0);
+
+        if (o->object.type != OBJECT_ENTRY)
+                return -EINVAL;
+
+        r = generic_array_bisect(f,
+                                 le64toh(f->header->entry_array_offset),
+                                 le64toh(f->header->n_entries),
+                                 p,
+                                 test_object_offset,
+                                 DIRECTION_DOWN,
+                                 NULL, NULL,
+                                 &i);
+        if (r <= 0)
+                return r;
+
+        /* Calculate new index */
+        if (skip < 0) {
+                if ((uint64_t) -skip >= i)
+                        i = 0;
+                else
+                        i = i - (uint64_t) -skip;
+        } else
+                i  += (uint64_t) skip;
+
+        n = le64toh(f->header->n_entries);
+        if (n <= 0)
+                return -EBADMSG;
+
+        if (i >= n)
+                i = n-1;
+
+        return generic_array_get(f,
+                                 le64toh(f->header->entry_array_offset),
+                                 i,
+                                 ret, offset);
+}
+
+int journal_file_next_entry_for_data(
+                JournalFile *f,
+                Object *o, uint64_t p,
+                uint64_t data_offset,
+                direction_t direction,
+                Object **ret, uint64_t *offset) {
+
+        uint64_t n, i;
+        int r;
+        Object *d;
+
+        assert(f);
+        assert(p > 0 || !o);
+
+        r = journal_file_move_to_object(f, OBJECT_DATA, data_offset, &d);
+        if (r < 0)
+                return r;
+
+        n = le64toh(d->data.n_entries);
+        if (n <= 0)
+                return n;
+
+        if (!o)
+                i = direction == DIRECTION_DOWN ? 0 : n - 1;
+        else {
+                if (o->object.type != OBJECT_ENTRY)
+                        return -EINVAL;
+
+                r = generic_array_bisect_plus_one(f,
+                                                  le64toh(d->data.entry_offset),
+                                                  le64toh(d->data.entry_array_offset),
+                                                  le64toh(d->data.n_entries),
+                                                  p,
+                                                  test_object_offset,
+                                                  DIRECTION_DOWN,
+                                                  NULL, NULL,
+                                                  &i);
+
+                if (r <= 0)
+                        return r;
+
+                if (direction == DIRECTION_DOWN) {
+                        if (i >= n - 1)
+                                return 0;
+
+                        i++;
+                } else {
+                        if (i <= 0)
+                                return 0;
+
+                        i--;
+                }
+
+        }
+
+        return generic_array_get_plus_one(f,
+                                          le64toh(d->data.entry_offset),
+                                          le64toh(d->data.entry_array_offset),
+                                          i,
+                                          ret, offset);
+}
+
+int journal_file_move_to_entry_by_seqnum_for_data(
+                JournalFile *f,
+                uint64_t data_offset,
+                uint64_t seqnum,
+                direction_t direction,
+                Object **ret, uint64_t *offset) {
+
+        Object *d;
+        int r;
+
+        r = journal_file_move_to_object(f, OBJECT_DATA, data_offset, &d);
+        if (r <= 0)
+                return r;
+
+        return generic_array_bisect_plus_one(f,
+                                             le64toh(d->data.entry_offset),
+                                             le64toh(d->data.entry_array_offset),
+                                             le64toh(d->data.n_entries),
+                                             seqnum,
+                                             test_object_seqnum,
+                                             direction,
+                                             ret, offset, NULL);
+}
+
+int journal_file_move_to_entry_by_realtime_for_data(
+                JournalFile *f,
+                uint64_t data_offset,
+                uint64_t realtime,
+                direction_t direction,
+                Object **ret, uint64_t *offset) {
+
+        Object *d;
+        int r;
+
+        r = journal_file_move_to_object(f, OBJECT_DATA, data_offset, &d);
+        if (r <= 0)
+                return r;
+
+        return generic_array_bisect_plus_one(f,
+                                             le64toh(d->data.entry_offset),
+                                             le64toh(d->data.entry_array_offset),
+                                             le64toh(d->data.n_entries),
+                                             realtime,
+                                             test_object_realtime,
+                                             direction,
+                                             ret, offset, NULL);
+}
+
+void journal_file_dump(JournalFile *f) {
+        char a[33], b[33], c[33];
+        Object *o;
+        int r;
+        uint64_t p;
+
+        assert(f);
+
+        printf("File Path: %s\n"
+               "File ID: %s\n"
+               "Machine ID: %s\n"
+               "Boot ID: %s\n"
+               "Arena size: %llu\n"
+               "Objects: %lu\n"
+               "Entries: %lu\n",
+               f->path,
+               sd_id128_to_string(f->header->file_id, a),
+               sd_id128_to_string(f->header->machine_id, b),
+               sd_id128_to_string(f->header->boot_id, c),
+               (unsigned long long) le64toh(f->header->arena_size),
+               (unsigned long) le64toh(f->header->n_objects),
+               (unsigned long) le64toh(f->header->n_entries));
+
+        p = le64toh(f->header->arena_offset);
+        while (p != 0) {
+                r = journal_file_move_to_object(f, -1, p, &o);
+                if (r < 0)
+                        goto fail;
+
+                switch (o->object.type) {
+
+                case OBJECT_UNUSED:
+                        printf("Type: OBJECT_UNUSED\n");
+                        break;
+
+                case OBJECT_DATA:
+                        printf("Type: OBJECT_DATA\n");
+                        break;
+
+                case OBJECT_ENTRY:
+                        printf("Type: OBJECT_ENTRY %llu %llu %llu\n",
+                               (unsigned long long) le64toh(o->entry.seqnum),
+                               (unsigned long long) le64toh(o->entry.monotonic),
+                               (unsigned long long) le64toh(o->entry.realtime));
+                        break;
+
+                case OBJECT_FIELD_HASH_TABLE:
+                        printf("Type: OBJECT_FIELD_HASH_TABLE\n");
+                        break;
+
+                case OBJECT_DATA_HASH_TABLE:
+                        printf("Type: OBJECT_DATA_HASH_TABLE\n");
+                        break;
+
+                case OBJECT_ENTRY_ARRAY:
+                        printf("Type: OBJECT_ENTRY_ARRAY\n");
+                        break;
+                }
+
+                if (o->object.flags & OBJECT_COMPRESSED)
+                        printf("Flags: COMPRESSED\n");
+
+                if (p == le64toh(f->header->tail_object_offset))
+                        p = 0;
+                else
+                        p = p + ALIGN64(le64toh(o->object.size));
+        }
+
+        return;
+fail:
+        log_error("File corrupt");
+}
+
+int journal_file_open(
+                const char *fname,
+                int flags,
+                mode_t mode,
+                JournalFile *template,
+                JournalFile **ret) {
+
+        JournalFile *f;
+        int r;
+        bool newly_created = false;
+
+        assert(fname);
+
+        if ((flags & O_ACCMODE) != O_RDONLY &&
+            (flags & O_ACCMODE) != O_RDWR)
+                return -EINVAL;
+
+        if (!endswith(fname, ".journal"))
+                return -EINVAL;
+
+        f = new0(JournalFile, 1);
+        if (!f)
+                return -ENOMEM;
+
+        f->fd = -1;
+        f->flags = flags;
+        f->mode = mode;
+        f->writable = (flags & O_ACCMODE) != O_RDONLY;
+        f->prot = prot_from_flags(flags);
+
+        if (template) {
+                f->metrics = template->metrics;
+                f->compress = template->compress;
+        }
+
+        f->path = strdup(fname);
+        if (!f->path) {
+                r = -ENOMEM;
+                goto fail;
+        }
+
+        f->fd = open(f->path, f->flags|O_CLOEXEC, f->mode);
+        if (f->fd < 0) {
+                r = -errno;
+                goto fail;
+        }
+
+        if (fstat(f->fd, &f->last_stat) < 0) {
+                r = -errno;
+                goto fail;
+        }
+
+        if (f->last_stat.st_size == 0 && f->writable) {
+                newly_created = true;
+
+                r = journal_file_init_header(f, template);
+                if (r < 0)
+                        goto fail;
+
+                if (fstat(f->fd, &f->last_stat) < 0) {
+                        r = -errno;
+                        goto fail;
+                }
+        }
+
+        if (f->last_stat.st_size < (off_t) sizeof(Header)) {
+                r = -EIO;
+                goto fail;
+        }
+
+        f->header = mmap(NULL, PAGE_ALIGN(sizeof(Header)), prot_from_flags(flags), MAP_SHARED, f->fd, 0);
+        if (f->header == MAP_FAILED) {
+                f->header = NULL;
+                r = -errno;
+                goto fail;
+        }
+
+        if (!newly_created) {
+                r = journal_file_verify_header(f);
+                if (r < 0)
+                        goto fail;
+        }
+
+        if (f->writable) {
+                r = journal_file_refresh_header(f);
+                if (r < 0)
+                        goto fail;
+        }
+
+        if (newly_created) {
+
+                r = journal_file_setup_field_hash_table(f);
+                if (r < 0)
+                        goto fail;
+
+                r = journal_file_setup_data_hash_table(f);
+                if (r < 0)
+                        goto fail;
+        }
+
+        r = journal_file_map_field_hash_table(f);
+        if (r < 0)
+                goto fail;
+
+        r = journal_file_map_data_hash_table(f);
+        if (r < 0)
+                goto fail;
+
+        if (ret)
+                *ret = f;
+
+        return 0;
+
+fail:
+        journal_file_close(f);
+
+        return r;
+}
+
+int journal_file_rotate(JournalFile **f) {
+        char *p;
+        size_t l;
+        JournalFile *old_file, *new_file = NULL;
+        int r;
+
+        assert(f);
+        assert(*f);
+
+        old_file = *f;
+
+        if (!old_file->writable)
+                return -EINVAL;
+
+        if (!endswith(old_file->path, ".journal"))
+                return -EINVAL;
+
+        l = strlen(old_file->path);
+
+        p = new(char, l + 1 + 32 + 1 + 16 + 1 + 16 + 1);
+        if (!p)
+                return -ENOMEM;
+
+        memcpy(p, old_file->path, l - 8);
+        p[l-8] = '@';
+        sd_id128_to_string(old_file->header->seqnum_id, p + l - 8 + 1);
+        snprintf(p + l - 8 + 1 + 32, 1 + 16 + 1 + 16 + 8 + 1,
+                 "-%016llx-%016llx.journal",
+                 (unsigned long long) le64toh((*f)->header->seqnum),
+                 (unsigned long long) le64toh((*f)->header->tail_entry_realtime));
+
+        r = rename(old_file->path, p);
+        free(p);
+
+        if (r < 0)
+                return -errno;
+
+        old_file->header->state = STATE_ARCHIVED;
+
+        r = journal_file_open(old_file->path, old_file->flags, old_file->mode, old_file, &new_file);
+        journal_file_close(old_file);
+
+        *f = new_file;
+        return r;
+}
+
+int journal_file_open_reliably(
+                const char *fname,
+                int flags,
+                mode_t mode,
+                JournalFile *template,
+                JournalFile **ret) {
+
+        int r;
+        size_t l;
+        char *p;
+
+        r = journal_file_open(fname, flags, mode, template, ret);
+        if (r != -EBADMSG && /* corrupted */
+            r != -ENODATA && /* truncated */
+            r != -EHOSTDOWN && /* other machine */
+            r != -EPROTONOSUPPORT) /* incompatible feature */
+                return r;
+
+        if ((flags & O_ACCMODE) == O_RDONLY)
+                return r;
+
+        if (!(flags & O_CREAT))
+                return r;
+
+        /* The file is corrupted. Rotate it away and try it again (but only once) */
+
+        l = strlen(fname);
+        if (asprintf(&p, "%.*s@%016llx-%016llx.journal~",
+                     (int) (l-8), fname,
+                     (unsigned long long) now(CLOCK_REALTIME),
+                     random_ull()) < 0)
+                return -ENOMEM;
+
+        r = rename(fname, p);
+        free(p);
+        if (r < 0)
+                return -errno;
+
+        log_warning("File %s corrupted, renaming and replacing.", fname);
+
+        return journal_file_open(fname, flags, mode, template, ret);
+}
+
+struct vacuum_info {
+        off_t usage;
+        char *filename;
+
+        uint64_t realtime;
+        sd_id128_t seqnum_id;
+        uint64_t seqnum;
+
+        bool have_seqnum;
+};
+
+static int vacuum_compare(const void *_a, const void *_b) {
+        const struct vacuum_info *a, *b;
+
+        a = _a;
+        b = _b;
+
+        if (a->have_seqnum && b->have_seqnum &&
+            sd_id128_equal(a->seqnum_id, b->seqnum_id)) {
+                if (a->seqnum < b->seqnum)
+                        return -1;
+                else if (a->seqnum > b->seqnum)
+                        return 1;
+                else
+                        return 0;
+        }
+
+        if (a->realtime < b->realtime)
+                return -1;
+        else if (a->realtime > b->realtime)
+                return 1;
+        else if (a->have_seqnum && b->have_seqnum)
+                return memcmp(&a->seqnum_id, &b->seqnum_id, 16);
+        else
+                return strcmp(a->filename, b->filename);
+}
+
+int journal_directory_vacuum(const char *directory, uint64_t max_use, uint64_t min_free) {
+        DIR *d;
+        int r = 0;
+        struct vacuum_info *list = NULL;
+        unsigned n_list = 0, n_allocated = 0, i;
+        uint64_t sum = 0;
+
+        assert(directory);
+
+        if (max_use <= 0)
+                return 0;
+
+        d = opendir(directory);
+        if (!d)
+                return -errno;
+
+        for (;;) {
+                int k;
+                struct dirent buf, *de;
+                size_t q;
+                struct stat st;
+                char *p;
+                unsigned long long seqnum, realtime;
+                sd_id128_t seqnum_id;
+                bool have_seqnum;
+
+                k = readdir_r(d, &buf, &de);
+                if (k != 0) {
+                        r = -k;
+                        goto finish;
+                }
+
+                if (!de)
+                        break;
+
+                if (fstatat(dirfd(d), de->d_name, &st, AT_SYMLINK_NOFOLLOW) < 0)
+                        continue;
+
+                if (!S_ISREG(st.st_mode))
+                        continue;
+
+                q = strlen(de->d_name);
+
+                if (endswith(de->d_name, ".journal")) {
+
+                        /* Vacuum archived files */
+
+                        if (q < 1 + 32 + 1 + 16 + 1 + 16 + 8)
+                                continue;
+
+                        if (de->d_name[q-8-16-1] != '-' ||
+                            de->d_name[q-8-16-1-16-1] != '-' ||
+                            de->d_name[q-8-16-1-16-1-32-1] != '@')
+                                continue;
+
+                        p = strdup(de->d_name);
+                        if (!p) {
+                                r = -ENOMEM;
+                                goto finish;
+                        }
+
+                        de->d_name[q-8-16-1-16-1] = 0;
+                        if (sd_id128_from_string(de->d_name + q-8-16-1-16-1-32, &seqnum_id) < 0) {
+                                free(p);
+                                continue;
+                        }
+
+                        if (sscanf(de->d_name + q-8-16-1-16, "%16llx-%16llx.journal", &seqnum, &realtime) != 2) {
+                                free(p);
+                                continue;
+                        }
+
+                        have_seqnum = true;
+
+                } else if (endswith(de->d_name, ".journal~")) {
+                        unsigned long long tmp;
+
+                        /* Vacuum corrupted files */
+
+                        if (q < 1 + 16 + 1 + 16 + 8 + 1)
+                                continue;
+
+                        if (de->d_name[q-1-8-16-1] != '-' ||
+                            de->d_name[q-1-8-16-1-16-1] != '@')
+                                continue;
+
+                        p = strdup(de->d_name);
+                        if (!p) {
+                                r = -ENOMEM;
+                                goto finish;
+                        }
+
+                        if (sscanf(de->d_name + q-1-8-16-1-16, "%16llx-%16llx.journal~", &realtime, &tmp) != 2) {
+                                free(p);
+                                continue;
+                        }
+
+                        have_seqnum = false;
+                } else
+                        continue;
+
+                if (n_list >= n_allocated) {
+                        struct vacuum_info *j;
+
+                        n_allocated = MAX(n_allocated * 2U, 8U);
+                        j = realloc(list, n_allocated * sizeof(struct vacuum_info));
+                        if (!j) {
+                                free(p);
+                                r = -ENOMEM;
+                                goto finish;
+                        }
+
+                        list = j;
+                }
+
+                list[n_list].filename = p;
+                list[n_list].usage = 512UL * (uint64_t) st.st_blocks;
+                list[n_list].seqnum = seqnum;
+                list[n_list].realtime = realtime;
+                list[n_list].seqnum_id = seqnum_id;
+                list[n_list].have_seqnum = have_seqnum;
+
+                sum += list[n_list].usage;
+
+                n_list ++;
+        }
+
+        qsort(list, n_list, sizeof(struct vacuum_info), vacuum_compare);
+
+        for(i = 0; i < n_list; i++) {
+                struct statvfs ss;
+
+                if (fstatvfs(dirfd(d), &ss) < 0) {
+                        r = -errno;
+                        goto finish;
+                }
+
+                if (sum <= max_use &&
+                    (uint64_t) ss.f_bavail * (uint64_t) ss.f_bsize >= min_free)
+                        break;
+
+                if (unlinkat(dirfd(d), list[i].filename, 0) >= 0) {
+                        log_info("Deleted archived journal %s/%s.", directory, list[i].filename);
+                        sum -= list[i].usage;
+                } else if (errno != ENOENT)
+                        log_warning("Failed to delete %s/%s: %m", directory, list[i].filename);
+        }
+
+finish:
+        for (i = 0; i < n_list; i++)
+                free(list[i].filename);
+
+        free(list);
+
+        if (d)
+                closedir(d);
+
+        return r;
+}
+
+int journal_file_copy_entry(JournalFile *from, JournalFile *to, Object *o, uint64_t p, uint64_t *seqnum, Object **ret, uint64_t *offset) {
+        uint64_t i, n;
+        uint64_t q, xor_hash = 0;
+        int r;
+        EntryItem *items;
+        dual_timestamp ts;
+
+        assert(from);
+        assert(to);
+        assert(o);
+        assert(p);
+
+        if (!to->writable)
+                return -EPERM;
+
+        ts.monotonic = le64toh(o->entry.monotonic);
+        ts.realtime = le64toh(o->entry.realtime);
+
+        if (to->tail_entry_monotonic_valid &&
+            ts.monotonic < le64toh(to->header->tail_entry_monotonic))
+                return -EINVAL;
+
+        if (ts.realtime < le64toh(to->header->tail_entry_realtime))
+                return -EINVAL;
+
+        n = journal_file_entry_n_items(o);
+        items = alloca(sizeof(EntryItem) * n);
+
+        for (i = 0; i < n; i++) {
+                uint64_t l, h;
+                le64_t le_hash;
+                size_t t;
+                void *data;
+                Object *u;
+
+                q = le64toh(o->entry.items[i].object_offset);
+                le_hash = o->entry.items[i].hash;
+
+                r = journal_file_move_to_object(from, OBJECT_DATA, q, &o);
+                if (r < 0)
+                        return r;
+
+                if (le_hash != o->data.hash)
+                        return -EBADMSG;
+
+                l = le64toh(o->object.size) - offsetof(Object, data.payload);
+                t = (size_t) l;
+
+                /* We hit the limit on 32bit machines */
+                if ((uint64_t) t != l)
+                        return -E2BIG;
+
+                if (o->object.flags & OBJECT_COMPRESSED) {
+#ifdef HAVE_XZ
+                        uint64_t rsize;
+
+                        if (!uncompress_blob(o->data.payload, l, &from->compress_buffer, &from->compress_buffer_size, &rsize))
+                                return -EBADMSG;
+
+                        data = from->compress_buffer;
+                        l = rsize;
+#else
+                        return -EPROTONOSUPPORT;
+#endif
+                } else
+                        data = o->data.payload;
+
+                r = journal_file_append_data(to, data, l, &u, &h);
+                if (r < 0)
+                        return r;
+
+                xor_hash ^= le64toh(u->data.hash);
+                items[i].object_offset = htole64(h);
+                items[i].hash = u->data.hash;
+
+                r = journal_file_move_to_object(from, OBJECT_ENTRY, p, &o);
+                if (r < 0)
+                        return r;
+        }
+
+        return journal_file_append_entry_internal(to, &ts, xor_hash, items, n, seqnum, ret, offset);
+}
+
+void journal_default_metrics(JournalMetrics *m, int fd) {
+        uint64_t fs_size = 0;
+        struct statvfs ss;
+        char a[FORMAT_BYTES_MAX], b[FORMAT_BYTES_MAX], c[FORMAT_BYTES_MAX], d[FORMAT_BYTES_MAX];
+
+        assert(m);
+        assert(fd >= 0);
+
+        if (fstatvfs(fd, &ss) >= 0)
+                fs_size = ss.f_frsize * ss.f_blocks;
+
+        if (m->max_use == (uint64_t) -1) {
+
+                if (fs_size > 0) {
+                        m->max_use = PAGE_ALIGN(fs_size / 10); /* 10% of file system size */
+
+                        if (m->max_use > DEFAULT_MAX_USE_UPPER)
+                                m->max_use = DEFAULT_MAX_USE_UPPER;
+
+                        if (m->max_use < DEFAULT_MAX_USE_LOWER)
+                                m->max_use = DEFAULT_MAX_USE_LOWER;
+                } else
+                        m->max_use = DEFAULT_MAX_USE_LOWER;
+        } else {
+                m->max_use = PAGE_ALIGN(m->max_use);
+
+                if (m->max_use < JOURNAL_FILE_SIZE_MIN*2)
+                        m->max_use = JOURNAL_FILE_SIZE_MIN*2;
+        }
+
+        if (m->max_size == (uint64_t) -1) {
+                m->max_size = PAGE_ALIGN(m->max_use / 8); /* 8 chunks */
+
+                if (m->max_size > DEFAULT_MAX_SIZE_UPPER)
+                        m->max_size = DEFAULT_MAX_SIZE_UPPER;
+        } else
+                m->max_size = PAGE_ALIGN(m->max_size);
+
+        if (m->max_size < JOURNAL_FILE_SIZE_MIN)
+                m->max_size = JOURNAL_FILE_SIZE_MIN;
+
+        if (m->max_size*2 > m->max_use)
+                m->max_use = m->max_size*2;
+
+        if (m->min_size == (uint64_t) -1)
+                m->min_size = JOURNAL_FILE_SIZE_MIN;
+        else {
+                m->min_size = PAGE_ALIGN(m->min_size);
+
+                if (m->min_size < JOURNAL_FILE_SIZE_MIN)
+                        m->min_size = JOURNAL_FILE_SIZE_MIN;
+
+                if (m->min_size > m->max_size)
+                        m->max_size = m->min_size;
+        }
+
+        if (m->keep_free == (uint64_t) -1) {
+
+                if (fs_size > 0) {
+                        m->keep_free = PAGE_ALIGN(fs_size / 20); /* 5% of file system size */
+
+                        if (m->keep_free > DEFAULT_KEEP_FREE_UPPER)
+                                m->keep_free = DEFAULT_KEEP_FREE_UPPER;
+
+                } else
+                        m->keep_free = DEFAULT_KEEP_FREE;
+        }
+
+        log_info("Fixed max_use=%s max_size=%s min_size=%s keep_free=%s",
+                 format_bytes(a, sizeof(a), m->max_use),
+                 format_bytes(b, sizeof(b), m->max_size),
+                 format_bytes(c, sizeof(c), m->min_size),
+                 format_bytes(d, sizeof(d), m->keep_free));
+}
diff --git a/src/journal/journal-file.h b/src/journal/journal-file.h
new file mode 100644 (file)
index 0000000..57d66ca
--- /dev/null
@@ -0,0 +1,128 @@
+/*-*- Mode: C; c-basic-offset: 8; indent-tabs-mode: nil -*-*/
+
+#ifndef foojournalfilehfoo
+#define foojournalfilehfoo
+
+/***
+  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 <http://www.gnu.org/licenses/>.
+***/
+
+#include <inttypes.h>
+
+#include <systemd/sd-id128.h>
+
+#include "sparse-endian.h"
+#include "journal-def.h"
+#include "util.h"
+
+typedef struct Window {
+        void *ptr;
+        uint64_t offset;
+        uint64_t size;
+} Window;
+
+enum {
+        WINDOW_UNKNOWN = OBJECT_UNUSED,
+        WINDOW_DATA = OBJECT_DATA,
+        WINDOW_ENTRY = OBJECT_ENTRY,
+        WINDOW_DATA_HASH_TABLE = OBJECT_DATA_HASH_TABLE,
+        WINDOW_FIELD_HASH_TABLE = OBJECT_FIELD_HASH_TABLE,
+        WINDOW_ENTRY_ARRAY = OBJECT_ENTRY_ARRAY,
+        WINDOW_HEADER,
+        _WINDOW_MAX
+};
+
+typedef struct JournalMetrics {
+        uint64_t max_use;
+        uint64_t max_size;
+        uint64_t min_size;
+        uint64_t keep_free;
+} JournalMetrics;
+
+typedef struct JournalFile {
+        int fd;
+        char *path;
+        struct stat last_stat;
+        mode_t mode;
+        int flags;
+        int prot;
+        bool writable;
+        bool tail_entry_monotonic_valid;
+
+        Header *header;
+        HashItem *data_hash_table;
+        HashItem *field_hash_table;
+
+        Window windows[_WINDOW_MAX];
+
+        uint64_t current_offset;
+
+        JournalMetrics metrics;
+
+        bool compress;
+
+#ifdef HAVE_XZ
+        void *compress_buffer;
+        uint64_t compress_buffer_size;
+#endif
+} JournalFile;
+
+typedef enum direction {
+        DIRECTION_UP,
+        DIRECTION_DOWN
+} direction_t;
+
+int journal_file_open(const char *fname, int flags, mode_t mode, JournalFile *template, JournalFile **ret);
+void journal_file_close(JournalFile *j);
+
+int journal_file_open_reliably(const char *fname, int flags, mode_t mode, JournalFile *template, JournalFile **ret);
+
+int journal_file_move_to_object(JournalFile *f, int type, uint64_t offset, Object **ret);
+
+uint64_t journal_file_entry_n_items(Object *o);
+
+int journal_file_append_entry(JournalFile *f, const dual_timestamp *ts, const struct iovec iovec[], unsigned n_iovec, uint64_t *seqno, Object **ret, uint64_t *offset);
+
+int journal_file_find_data_object(JournalFile *f, const void *data, uint64_t size, Object **ret, uint64_t *offset);
+int journal_file_find_data_object_with_hash(JournalFile *f, const void *data, uint64_t size, uint64_t hash, Object **ret, uint64_t *offset);
+
+int journal_file_next_entry(JournalFile *f, Object *o, uint64_t p, direction_t direction, Object **ret, uint64_t *offset);
+int journal_file_skip_entry(JournalFile *f, Object *o, uint64_t p, int64_t skip, Object **ret, uint64_t *offset);
+
+int journal_file_next_entry_for_data(JournalFile *f, Object *o, uint64_t p, uint64_t data_offset, direction_t direction, Object **ret, uint64_t *offset);
+
+int journal_file_move_to_entry_by_seqnum(JournalFile *f, uint64_t seqnum, direction_t direction, Object **ret, uint64_t *offset);
+int journal_file_move_to_entry_by_realtime(JournalFile *f, uint64_t realtime, direction_t direction, Object **ret, uint64_t *offset);
+int journal_file_move_to_entry_by_monotonic(JournalFile *f, sd_id128_t boot_id, uint64_t monotonic, direction_t direction, Object **ret, uint64_t *offset);
+
+int journal_file_move_to_entry_by_seqnum_for_data(JournalFile *f, uint64_t data_offset, uint64_t seqnum, direction_t direction, Object **ret, uint64_t *offset);
+int journal_file_move_to_entry_by_realtime_for_data(JournalFile *f, uint64_t data_offset, uint64_t realtime, direction_t direction, Object **ret, uint64_t *offset);
+
+int journal_file_copy_entry(JournalFile *from, JournalFile *to, Object *o, uint64_t p, uint64_t *seqnum, Object **ret, uint64_t *offset);
+
+void journal_file_dump(JournalFile *f);
+
+int journal_file_rotate(JournalFile **f);
+
+int journal_directory_vacuum(const char *directory, uint64_t max_use, uint64_t min_free);
+
+void journal_file_post_change(JournalFile *f);
+
+void journal_default_metrics(JournalMetrics *m, int fd);
+
+#endif
diff --git a/src/journal/journal-internal.h b/src/journal/journal-internal.h
new file mode 100644 (file)
index 0000000..17f1d31
--- /dev/null
@@ -0,0 +1,84 @@
+/*-*- Mode: C; c-basic-offset: 8; indent-tabs-mode: nil -*-*/
+
+#ifndef foojournalinternalhfoo
+#define foojournalinternalhfoo
+
+/***
+  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 <http://www.gnu.org/licenses/>.
+***/
+
+#include <sys/types.h>
+#include <inttypes.h>
+#include <stdbool.h>
+
+#include <systemd/sd-id128.h>
+
+#include "list.h"
+
+typedef struct Match Match;
+
+struct Match {
+        char *data;
+        size_t size;
+        le64_t le_hash;
+
+        LIST_FIELDS(Match, matches);
+};
+
+typedef enum location_type {
+        LOCATION_HEAD,
+        LOCATION_TAIL,
+        LOCATION_DISCRETE
+} location_type_t;
+
+typedef struct Location {
+        location_type_t type;
+
+        uint64_t seqnum;
+        sd_id128_t seqnum_id;
+        bool seqnum_set;
+
+        uint64_t realtime;
+        bool realtime_set;
+
+        uint64_t monotonic;
+        sd_id128_t boot_id;
+        bool monotonic_set;
+
+        uint64_t xor_hash;
+        bool xor_hash_set;
+} Location;
+
+struct sd_journal {
+        int flags;
+
+        Hashmap *files;
+
+        Location current_location;
+        JournalFile *current_file;
+        uint64_t current_field;
+
+        int inotify_fd;
+        Hashmap *inotify_wd_dirs;
+        Hashmap *inotify_wd_roots;
+
+        LIST_HEAD(Match, matches);
+        unsigned n_matches;
+};
+
+#endif
diff --git a/src/journal/journal-rate-limit.c b/src/journal/journal-rate-limit.c
new file mode 100644 (file)
index 0000000..243ff2a
--- /dev/null
@@ -0,0 +1,275 @@
+/*-*- 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 <http://www.gnu.org/licenses/>.
+***/
+
+#include <string.h>
+#include <errno.h>
+
+#include "journal-rate-limit.h"
+#include "list.h"
+#include "util.h"
+#include "hashmap.h"
+
+#define POOLS_MAX 5
+#define BUCKETS_MAX 127
+#define GROUPS_MAX 2047
+
+static const int priority_map[] = {
+        [LOG_EMERG]   = 0,
+        [LOG_ALERT]   = 0,
+        [LOG_CRIT]    = 0,
+        [LOG_ERR]     = 1,
+        [LOG_WARNING] = 2,
+        [LOG_NOTICE]  = 3,
+        [LOG_INFO]    = 3,
+        [LOG_DEBUG]   = 4
+};
+
+typedef struct JournalRateLimitPool JournalRateLimitPool;
+typedef struct JournalRateLimitGroup JournalRateLimitGroup;
+
+struct JournalRateLimitPool {
+        usec_t begin;
+        unsigned num;
+        unsigned suppressed;
+};
+
+struct JournalRateLimitGroup {
+        JournalRateLimit *parent;
+
+        char *id;
+        JournalRateLimitPool pools[POOLS_MAX];
+        unsigned hash;
+
+        LIST_FIELDS(JournalRateLimitGroup, bucket);
+        LIST_FIELDS(JournalRateLimitGroup, lru);
+};
+
+struct JournalRateLimit {
+        usec_t interval;
+        unsigned burst;
+
+        JournalRateLimitGroup* buckets[BUCKETS_MAX];
+        JournalRateLimitGroup *lru, *lru_tail;
+
+        unsigned n_groups;
+};
+
+JournalRateLimit *journal_rate_limit_new(usec_t interval, unsigned burst) {
+        JournalRateLimit *r;
+
+        assert(interval > 0 || burst == 0);
+
+        r = new0(JournalRateLimit, 1);
+        if (!r)
+                return NULL;
+
+        r->interval = interval;
+        r->burst = burst;
+
+        return r;
+}
+
+static void journal_rate_limit_group_free(JournalRateLimitGroup *g) {
+        assert(g);
+
+        if (g->parent) {
+                assert(g->parent->n_groups > 0);
+
+                if (g->parent->lru_tail == g)
+                        g->parent->lru_tail = g->lru_prev;
+
+                LIST_REMOVE(JournalRateLimitGroup, lru, g->parent->lru, g);
+                LIST_REMOVE(JournalRateLimitGroup, bucket, g->parent->buckets[g->hash % BUCKETS_MAX], g);
+
+                g->parent->n_groups --;
+        }
+
+        free(g->id);
+        free(g);
+}
+
+void journal_rate_limit_free(JournalRateLimit *r) {
+        assert(r);
+
+        while (r->lru)
+                journal_rate_limit_group_free(r->lru);
+
+        free(r);
+}
+
+static bool journal_rate_limit_group_expired(JournalRateLimitGroup *g, usec_t ts) {
+        unsigned i;
+
+        assert(g);
+
+        for (i = 0; i < POOLS_MAX; i++)
+                if (g->pools[i].begin + g->parent->interval >= ts)
+                        return false;
+
+        return true;
+}
+
+static void journal_rate_limit_vacuum(JournalRateLimit *r, usec_t ts) {
+        assert(r);
+
+        /* Makes room for at least one new item, but drop all
+         * expored items too. */
+
+        while (r->n_groups >= GROUPS_MAX ||
+               (r->lru_tail && journal_rate_limit_group_expired(r->lru_tail, ts)))
+                journal_rate_limit_group_free(r->lru_tail);
+}
+
+static JournalRateLimitGroup* journal_rate_limit_group_new(JournalRateLimit *r, const char *id, usec_t ts) {
+        JournalRateLimitGroup *g;
+
+        assert(r);
+        assert(id);
+
+        g = new0(JournalRateLimitGroup, 1);
+        if (!g)
+                return NULL;
+
+        g->id = strdup(id);
+        if (!g->id)
+                goto fail;
+
+        g->hash = string_hash_func(g->id);
+
+        journal_rate_limit_vacuum(r, ts);
+
+        LIST_PREPEND(JournalRateLimitGroup, bucket, r->buckets[g->hash % BUCKETS_MAX], g);
+        LIST_PREPEND(JournalRateLimitGroup, lru, r->lru, g);
+        if (!g->lru_next)
+                r->lru_tail = g;
+        r->n_groups ++;
+
+        g->parent = r;
+        return g;
+
+fail:
+        journal_rate_limit_group_free(g);
+        return NULL;
+}
+
+static uint64_t u64log2(uint64_t n) {
+        unsigned r;
+
+        if (n <= 1)
+                return 0;
+
+        r = 0;
+        for (;;) {
+                n = n >> 1;
+                if (!n)
+                        return r;
+                r++;
+        }
+}
+
+static unsigned burst_modulate(unsigned burst, uint64_t available) {
+        unsigned k;
+
+        /* Modulates the burst rate a bit with the amount of available
+         * disk space */
+
+        k = u64log2(available);
+
+        /* 1MB */
+        if (k <= 20)
+                return burst;
+
+        burst = (burst * (k-20)) / 4;
+
+        /*
+         * Example:
+         *
+         *      <= 1MB = rate * 1
+         *        16MB = rate * 2
+         *       256MB = rate * 3
+         *         4GB = rate * 4
+         *        64GB = rate * 5
+         *         1TB = rate * 6
+         */
+
+        return burst;
+}
+
+int journal_rate_limit_test(JournalRateLimit *r, const char *id, int priority, uint64_t available) {
+        unsigned h;
+        JournalRateLimitGroup *g;
+        JournalRateLimitPool *p;
+        unsigned burst;
+        usec_t ts;
+
+        assert(id);
+
+        if (!r)
+                return 1;
+
+        if (r->interval == 0 || r->burst == 0)
+                return 1;
+
+        burst = burst_modulate(r->burst, available);
+
+        ts = now(CLOCK_MONOTONIC);
+
+        h = string_hash_func(id);
+        g = r->buckets[h % BUCKETS_MAX];
+
+        LIST_FOREACH(bucket, g, g)
+                if (streq(g->id, id))
+                        break;
+
+        if (!g) {
+                g = journal_rate_limit_group_new(r, id, ts);
+                if (!g)
+                        return -ENOMEM;
+        }
+
+        p = &g->pools[priority_map[priority]];
+
+        if (p->begin <= 0) {
+                p->suppressed = 0;
+                p->num = 1;
+                p->begin = ts;
+                return 1;
+        }
+
+        if (p->begin + r->interval < ts) {
+                unsigned s;
+
+                s = p->suppressed;
+                p->suppressed = 0;
+                p->num = 1;
+                p->begin = ts;
+
+                return 1 + s;
+        }
+
+        if (p->num <= burst) {
+                p->num++;
+                return 1;
+        }
+
+        p->suppressed++;
+        return 0;
+}
diff --git a/src/journal/journal-rate-limit.h b/src/journal/journal-rate-limit.h
new file mode 100644 (file)
index 0000000..2bbdd5f
--- /dev/null
@@ -0,0 +1,34 @@
+/*-*- Mode: C; c-basic-offset: 8; indent-tabs-mode: nil -*-*/
+
+#ifndef foojournalratelimithfoo
+#define foojournalratelimithfoo
+
+/***
+  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 <http://www.gnu.org/licenses/>.
+***/
+
+#include "macro.h"
+#include "util.h"
+
+typedef struct JournalRateLimit JournalRateLimit;
+
+JournalRateLimit *journal_rate_limit_new(usec_t interval, unsigned burst);
+void journal_rate_limit_free(JournalRateLimit *r);
+int journal_rate_limit_test(JournalRateLimit *r, const char *id, int priority, uint64_t available);
+
+#endif
diff --git a/src/journal/journal-send.c b/src/journal/journal-send.c
new file mode 100644 (file)
index 0000000..32e94af
--- /dev/null
@@ -0,0 +1,516 @@
+/*-*- 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 <http://www.gnu.org/licenses/>.
+***/
+
+#include <sys/socket.h>
+#include <sys/un.h>
+#include <errno.h>
+#include <stddef.h>
+#include <unistd.h>
+#include <fcntl.h>
+
+#define SD_JOURNAL_SUPPRESS_LOCATION
+
+#include "sd-journal.h"
+#include "util.h"
+#include "socket-util.h"
+
+#define SNDBUF_SIZE (8*1024*1024)
+
+/* We open a single fd, and we'll share it with the current process,
+ * all its threads, and all its subprocesses. This means we need to
+ * initialize it atomically, and need to operate on it atomically
+ * never assuming we are the only user */
+
+static int journal_fd(void) {
+        int fd;
+        static int fd_plus_one = 0;
+
+retry:
+        if (fd_plus_one > 0)
+                return fd_plus_one - 1;
+
+        fd = socket(AF_UNIX, SOCK_DGRAM|SOCK_CLOEXEC, 0);
+        if (fd < 0)
+                return -errno;
+
+        fd_inc_sndbuf(fd, SNDBUF_SIZE);
+
+        if (!__sync_bool_compare_and_swap(&fd_plus_one, 0, fd+1)) {
+                close_nointr_nofail(fd);
+                goto retry;
+        }
+
+        return fd;
+}
+
+_public_ int sd_journal_print(int priority, const char *format, ...) {
+        int r;
+        va_list ap;
+
+        va_start(ap, format);
+        r = sd_journal_printv(priority, format, ap);
+        va_end(ap);
+
+        return r;
+}
+
+_public_ int sd_journal_printv(int priority, const char *format, va_list ap) {
+        char buffer[8 + LINE_MAX], p[11];
+        struct iovec iov[2];
+
+        if (priority < 0 || priority > 7)
+                return -EINVAL;
+
+        if (!format)
+                return -EINVAL;
+
+        snprintf(p, sizeof(p), "PRIORITY=%i", priority & LOG_PRIMASK);
+        char_array_0(p);
+
+        memcpy(buffer, "MESSAGE=", 8);
+        vsnprintf(buffer+8, sizeof(buffer) - 8, format, ap);
+        char_array_0(buffer);
+
+        zero(iov);
+        IOVEC_SET_STRING(iov[0], buffer);
+        IOVEC_SET_STRING(iov[1], p);
+
+        return sd_journal_sendv(iov, 2);
+}
+
+static int fill_iovec_sprintf(const char *format, va_list ap, int extra, struct iovec **_iov) {
+        int r, n = 0, i, j;
+        struct iovec *iov = NULL;
+        int saved_errno;
+
+        assert(_iov);
+        saved_errno = errno;
+
+        if (extra > 0) {
+                n = MAX(extra * 2, extra + 4);
+                iov = malloc0(n * sizeof(struct iovec));
+                if (!iov) {
+                        r = -ENOMEM;
+                        goto fail;
+                }
+
+                i = extra;
+        } else
+                i = 0;
+
+        while (format) {
+                struct iovec *c;
+                char *buffer;
+
+                if (i >= n) {
+                        n = MAX(i*2, 4);
+                        c = realloc(iov, n * sizeof(struct iovec));
+                        if (!c) {
+                                r = -ENOMEM;
+                                goto fail;
+                        }
+
+                        iov = c;
+                }
+
+                if (vasprintf(&buffer, format, ap) < 0) {
+                        r = -ENOMEM;
+                        goto fail;
+                }
+
+                IOVEC_SET_STRING(iov[i++], buffer);
+
+                format = va_arg(ap, char *);
+        }
+
+        *_iov = iov;
+
+        errno = saved_errno;
+        return i;
+
+fail:
+        for (j = 0; j < i; j++)
+                free(iov[j].iov_base);
+
+        free(iov);
+
+        errno = saved_errno;
+        return r;
+}
+
+_public_ int sd_journal_send(const char *format, ...) {
+        int r, i, j;
+        va_list ap;
+        struct iovec *iov = NULL;
+
+        va_start(ap, format);
+        i = fill_iovec_sprintf(format, ap, 0, &iov);
+        va_end(ap);
+
+        if (_unlikely_(i < 0)) {
+                r = i;
+                goto finish;
+        }
+
+        r = sd_journal_sendv(iov, i);
+
+finish:
+        for (j = 0; j < i; j++)
+                free(iov[j].iov_base);
+
+        free(iov);
+
+        return r;
+}
+
+_public_ int sd_journal_sendv(const struct iovec *iov, int n) {
+        int fd, buffer_fd;
+        struct iovec *w;
+        uint64_t *l;
+        int r, i, j = 0;
+        struct msghdr mh;
+        struct sockaddr_un sa;
+        ssize_t k;
+        int saved_errno;
+        union {
+                struct cmsghdr cmsghdr;
+                uint8_t buf[CMSG_SPACE(sizeof(int))];
+        } control;
+        struct cmsghdr *cmsg;
+        /* We use /dev/shm instead of /tmp here, since we want this to
+         * be a tmpfs, and one that is available from early boot on
+         * and where unprivileged users can create files. */
+        char path[] = "/dev/shm/journal.XXXXXX";
+
+        if (_unlikely_(!iov))
+                return -EINVAL;
+
+        if (_unlikely_(n <= 0))
+                return -EINVAL;
+
+        saved_errno = errno;
+
+        w = alloca(sizeof(struct iovec) * n * 5);
+        l = alloca(sizeof(uint64_t) * n);
+
+        for (i = 0; i < n; i++) {
+                char *c, *nl;
+
+                if (_unlikely_(!iov[i].iov_base || iov[i].iov_len <= 1)) {
+                        r = -EINVAL;
+                        goto finish;
+                }
+
+                c = memchr(iov[i].iov_base, '=', iov[i].iov_len);
+                if (_unlikely_(!c || c == iov[i].iov_base)) {
+                        r = -EINVAL;
+                        goto finish;
+                }
+
+                nl = memchr(iov[i].iov_base, '\n', iov[i].iov_len);
+                if (nl) {
+                        if (_unlikely_(nl < c)) {
+                                r = -EINVAL;
+                                goto finish;
+                        }
+
+                        /* Already includes a newline? Bummer, then
+                         * let's write the variable name, then a
+                         * newline, then the size (64bit LE), followed
+                         * by the data and a final newline */
+
+                        w[j].iov_base = iov[i].iov_base;
+                        w[j].iov_len = c - (char*) iov[i].iov_base;
+                        j++;
+
+                        IOVEC_SET_STRING(w[j++], "\n");
+
+                        l[i] = htole64(iov[i].iov_len - (c - (char*) iov[i].iov_base) - 1);
+                        w[j].iov_base = &l[i];
+                        w[j].iov_len = sizeof(uint64_t);
+                        j++;
+
+                        w[j].iov_base = c + 1;
+                        w[j].iov_len = iov[i].iov_len - (c - (char*) iov[i].iov_base) - 1;
+                        j++;
+
+                } else
+                        /* Nothing special? Then just add the line and
+                         * append a newline */
+                        w[j++] = iov[i];
+
+                IOVEC_SET_STRING(w[j++], "\n");
+        }
+
+        fd = journal_fd();
+        if (_unlikely_(fd < 0)) {
+                r = fd;
+                goto finish;
+        }
+
+        zero(sa);
+        sa.sun_family = AF_UNIX;
+        strncpy(sa.sun_path, "/run/systemd/journal/socket", sizeof(sa.sun_path));
+
+        zero(mh);
+        mh.msg_name = &sa;
+        mh.msg_namelen = offsetof(struct sockaddr_un, sun_path) + strlen(sa.sun_path);
+        mh.msg_iov = w;
+        mh.msg_iovlen = j;
+
+        k = sendmsg(fd, &mh, MSG_NOSIGNAL);
+        if (k >= 0) {
+                r = 0;
+                goto finish;
+        }
+
+        if (errno != EMSGSIZE && errno != ENOBUFS) {
+                r = -errno;
+                goto finish;
+        }
+
+        /* Message doesn't fit... Let's dump the data in a temporary
+         * file and just pass a file descriptor of it to the other
+         * side */
+
+        buffer_fd = mkostemp(path, O_CLOEXEC|O_RDWR);
+        if (buffer_fd < 0) {
+                r = -errno;
+                goto finish;
+        }
+
+        if (unlink(path) < 0) {
+                close_nointr_nofail(buffer_fd);
+                r = -errno;
+                goto finish;
+        }
+
+        n = writev(buffer_fd, w, j);
+        if (n < 0) {
+                close_nointr_nofail(buffer_fd);
+                r = -errno;
+                goto finish;
+        }
+
+        mh.msg_iov = NULL;
+        mh.msg_iovlen = 0;
+
+        zero(control);
+        mh.msg_control = &control;
+        mh.msg_controllen = sizeof(control);
+
+        cmsg = CMSG_FIRSTHDR(&mh);
+        cmsg->cmsg_level = SOL_SOCKET;
+        cmsg->cmsg_type = SCM_RIGHTS;
+        cmsg->cmsg_len = CMSG_LEN(sizeof(int));
+        memcpy(CMSG_DATA(cmsg), &buffer_fd, sizeof(int));
+
+        mh.msg_controllen = cmsg->cmsg_len;
+
+        k = sendmsg(fd, &mh, MSG_NOSIGNAL);
+        close_nointr_nofail(buffer_fd);
+
+        if (k < 0) {
+                r = -errno;
+                goto finish;
+        }
+
+        r = 0;
+
+finish:
+        errno = saved_errno;
+
+        return r;
+}
+
+_public_ int sd_journal_stream_fd(const char *identifier, int priority, int level_prefix) {
+        union sockaddr_union sa;
+        int fd;
+        char *header;
+        size_t l;
+        ssize_t r;
+
+        if (priority < 0 || priority > 7)
+                return -EINVAL;
+
+        fd = socket(AF_UNIX, SOCK_STREAM|SOCK_CLOEXEC, 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(union sockaddr_union, 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;
+        }
+
+        fd_inc_sndbuf(fd, SNDBUF_SIZE);
+
+        if (!identifier)
+                identifier = "";
+
+        l = strlen(identifier);
+        header = alloca(l + 1 + 2 + 2 + 2 + 2 + 2);
+
+        memcpy(header, identifier, l);
+        header[l++] = '\n';
+        header[l++] = '0' + priority;
+        header[l++] = '\n';
+        header[l++] = '0' + !!level_prefix;
+        header[l++] = '\n';
+        header[l++] = '0';
+        header[l++] = '\n';
+        header[l++] = '0';
+        header[l++] = '\n';
+        header[l++] = '0';
+        header[l++] = '\n';
+
+        r = loop_write(fd, header, l, false);
+        if (r < 0) {
+                close_nointr_nofail(fd);
+                return (int) r;
+        }
+
+        if ((size_t) r != l) {
+                close_nointr_nofail(fd);
+                return -errno;
+        }
+
+        return fd;
+}
+
+_public_ int sd_journal_print_with_location(int priority, const char *file, const char *line, const char *func, const char *format, ...) {
+        int r;
+        va_list ap;
+
+        va_start(ap, format);
+        r = sd_journal_printv_with_location(priority, file, line, func, format, ap);
+        va_end(ap);
+
+        return r;
+}
+
+_public_ int sd_journal_printv_with_location(int priority, const char *file, const char *line, const char *func, const char *format, va_list ap) {
+        char buffer[8 + LINE_MAX], p[11];
+        struct iovec iov[5];
+        char *f;
+        size_t fl;
+
+        if (priority < 0 || priority > 7)
+                return -EINVAL;
+
+        if (_unlikely_(!format))
+                return -EINVAL;
+
+        snprintf(p, sizeof(p), "PRIORITY=%i", priority & LOG_PRIMASK);
+        char_array_0(p);
+
+        memcpy(buffer, "MESSAGE=", 8);
+        vsnprintf(buffer+8, sizeof(buffer) - 8, format, ap);
+        char_array_0(buffer);
+
+        /* func is initialized from __func__ which is not a macro, but
+         * a static const char[], hence cannot easily be prefixed with
+         * CODE_FUNC=, hence let's do it manually here. */
+        fl = strlen(func);
+        f = alloca(fl + 10);
+        memcpy(f, "CODE_FUNC=", 10);
+        memcpy(f + 10, func, fl + 1);
+
+        zero(iov);
+        IOVEC_SET_STRING(iov[0], buffer);
+        IOVEC_SET_STRING(iov[1], p);
+        IOVEC_SET_STRING(iov[2], file);
+        IOVEC_SET_STRING(iov[3], line);
+        IOVEC_SET_STRING(iov[4], f);
+
+        return sd_journal_sendv(iov, ELEMENTSOF(iov));
+}
+
+_public_ int sd_journal_send_with_location(const char *file, const char *line, const char *func, const char *format, ...) {
+        int r, i, j;
+        va_list ap;
+        struct iovec *iov = NULL;
+        char *f;
+        size_t fl;
+
+        va_start(ap, format);
+        i = fill_iovec_sprintf(format, ap, 3, &iov);
+        va_end(ap);
+
+        if (_unlikely_(i < 0)) {
+                r = i;
+                goto finish;
+        }
+
+        fl = strlen(func);
+        f = alloca(fl + 10);
+        memcpy(f, "CODE_FUNC=", 10);
+        memcpy(f + 10, func, fl + 1);
+
+        IOVEC_SET_STRING(iov[0], file);
+        IOVEC_SET_STRING(iov[1], line);
+        IOVEC_SET_STRING(iov[2], f);
+
+        r = sd_journal_sendv(iov, i);
+
+finish:
+        for (j = 3; j < i; j++)
+                free(iov[j].iov_base);
+
+        free(iov);
+
+        return r;
+}
+
+_public_ int sd_journal_sendv_with_location(const char *file, const char *line, const char *func, const struct iovec *iov, int n) {
+        struct iovec *niov;
+        char *f;
+        size_t fl;
+
+        if (_unlikely_(!iov))
+                return -EINVAL;
+
+        if (_unlikely_(n <= 0))
+                return -EINVAL;
+
+        niov = alloca(sizeof(struct iovec) * (n + 3));
+        memcpy(niov, iov, sizeof(struct iovec) * n);
+
+        fl = strlen(func);
+        f = alloca(fl + 10);
+        memcpy(f, "CODE_FUNC=", 10);
+        memcpy(f + 10, func, fl + 1);
+
+        IOVEC_SET_STRING(niov[n++], file);
+        IOVEC_SET_STRING(niov[n++], line);
+        IOVEC_SET_STRING(niov[n++], f);
+
+        return sd_journal_sendv(niov, n);
+}
diff --git a/src/journal/journalctl.c b/src/journal/journalctl.c
new file mode 100644 (file)
index 0000000..01dceca
--- /dev/null
@@ -0,0 +1,327 @@
+/*-*- 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 <http://www.gnu.org/licenses/>.
+***/
+
+#include <fcntl.h>
+#include <errno.h>
+#include <stddef.h>
+#include <string.h>
+#include <stdio.h>
+#include <unistd.h>
+#include <stdlib.h>
+#include <sys/poll.h>
+#include <time.h>
+#include <getopt.h>
+
+#include <systemd/sd-journal.h>
+
+#include "log.h"
+#include "util.h"
+#include "build.h"
+#include "pager.h"
+#include "logs-show.h"
+
+static OutputMode arg_output = OUTPUT_SHORT;
+static bool arg_follow = false;
+static bool arg_show_all = false;
+static bool arg_no_pager = false;
+static int arg_lines = -1;
+static bool arg_no_tail = false;
+static bool arg_new_id128 = false;
+static bool arg_quiet = false;
+static bool arg_local = false;
+
+static int help(void) {
+
+        printf("%s [OPTIONS...] {COMMAND} ...\n\n"
+               "Send control commands to or query the journal.\n\n"
+               "  -h --help           Show this help\n"
+               "     --version        Show package version\n"
+               "     --no-pager       Do not pipe output into a pager\n"
+               "  -a --all            Show all fields, including long and unprintable\n"
+               "  -f --follow         Follow journal\n"
+               "  -n --lines=INTEGER  Journal entries to show\n"
+               "     --no-tail        Show all lines, even in follow mode\n"
+               "  -o --output=STRING  Change journal output mode (short, short-monotonic,\n"
+               "                      verbose, export, json, cat)\n"
+               "  -q --quiet          Don't show privilege warning\n"
+               "     --new-id128      Generate a new 128 Bit id\n"
+               "  -l --local          Only local entries\n",
+               program_invocation_short_name);
+
+        return 0;
+}
+
+static int parse_argv(int argc, char *argv[]) {
+
+        enum {
+                ARG_VERSION = 0x100,
+                ARG_NO_PAGER,
+                ARG_NO_TAIL,
+                ARG_NEW_ID128
+        };
+
+        static const struct option options[] = {
+                { "help",      no_argument,       NULL, 'h'           },
+                { "version" ,  no_argument,       NULL, ARG_VERSION   },
+                { "no-pager",  no_argument,       NULL, ARG_NO_PAGER  },
+                { "follow",    no_argument,       NULL, 'f'           },
+                { "output",    required_argument, NULL, 'o'           },
+                { "all",       no_argument,       NULL, 'a'           },
+                { "lines",     required_argument, NULL, 'n'           },
+                { "no-tail",   no_argument,       NULL, ARG_NO_TAIL   },
+                { "new-id128", no_argument,       NULL, ARG_NEW_ID128 },
+                { "quiet",     no_argument,       NULL, 'q'           },
+                { "local",     no_argument,       NULL, 'l'           },
+                { NULL,        0,                 NULL, 0             }
+        };
+
+        int c, r;
+
+        assert(argc >= 0);
+        assert(argv);
+
+        while ((c = getopt_long(argc, argv, "hfo:an:ql", options, NULL)) >= 0) {
+
+                switch (c) {
+
+                case 'h':
+                        help();
+                        return 0;
+
+                case ARG_VERSION:
+                        puts(PACKAGE_STRING);
+                        puts(DISTRIBUTION);
+                        puts(SYSTEMD_FEATURES);
+                        return 0;
+
+                case ARG_NO_PAGER:
+                        arg_no_pager = true;
+                        break;
+
+                case 'f':
+                        arg_follow = true;
+                        break;
+
+                case 'o':
+                        arg_output =  output_mode_from_string(optarg);
+                        if (arg_output < 0) {
+                                log_error("Unknown output '%s'.", optarg);
+                                return -EINVAL;
+                        }
+
+                        break;
+
+                case 'a':
+                        arg_show_all = true;
+                        break;
+
+                case 'n':
+                        r = safe_atoi(optarg, &arg_lines);
+                        if (r < 0 || arg_lines < 0) {
+                                log_error("Failed to parse lines '%s'", optarg);
+                                return -EINVAL;
+                        }
+                        break;
+
+                case ARG_NO_TAIL:
+                        arg_no_tail = true;
+                        break;
+
+                case ARG_NEW_ID128:
+                        arg_new_id128 = true;
+                        break;
+
+                case 'q':
+                        arg_quiet = true;
+                        break;
+
+                case 'l':
+                        arg_local = true;
+                        break;
+
+                case '?':
+                        return -EINVAL;
+
+                default:
+                        log_error("Unknown option code %c", c);
+                        return -EINVAL;
+                }
+        }
+
+        if (arg_follow && !arg_no_tail && arg_lines < 0)
+                arg_lines = 10;
+
+        return 1;
+}
+
+static int generate_new_id128(void) {
+        sd_id128_t id;
+        int r;
+        unsigned i;
+
+        r = sd_id128_randomize(&id);
+        if (r < 0) {
+                log_error("Failed to generate ID: %s", strerror(-r));
+                return r;
+        }
+
+        printf("As string:\n"
+               SD_ID128_FORMAT_STR "\n\n"
+               "As UUID:\n"
+               "%02x%02x%02x%02x-%02x%02x-%02x%02x-%02x%02x-%02x%02x%02x%02x%02x%02x\n\n"
+               "As macro:\n"
+              "#define MESSAGE_XYZ SD_ID128_MAKE(",
+               SD_ID128_FORMAT_VAL(id),
+               SD_ID128_FORMAT_VAL(id));
+
+        for (i = 0; i < 16; i++)
+                printf("%02x%s", id.bytes[i], i != 15 ? "," : "");
+
+        fputs(")\n", stdout);
+
+        return 0;
+}
+
+int main(int argc, char *argv[]) {
+        int r, i, fd;
+        sd_journal *j = NULL;
+        unsigned line = 0;
+        bool need_seek = false;
+
+        log_parse_environment();
+        log_open();
+
+        r = parse_argv(argc, argv);
+        if (r <= 0)
+                goto finish;
+
+        if (arg_new_id128) {
+                r = generate_new_id128();
+                goto finish;
+        }
+
+#ifdef HAVE_ACL
+        if (!arg_quiet && geteuid() != 0 && in_group("adm") <= 0)
+                log_warning("Showing user generated messages only. Users in the group 'adm' can see all messages. Pass -q to turn this message off.");
+#endif
+
+        r = sd_journal_open(&j, arg_local ? SD_JOURNAL_LOCAL_ONLY : 0);
+        if (r < 0) {
+                log_error("Failed to open journal: %s", strerror(-r));
+                goto finish;
+        }
+
+        for (i = optind; i < argc; i++) {
+                r = sd_journal_add_match(j, argv[i], strlen(argv[i]));
+                if (r < 0) {
+                        log_error("Failed to add match: %s", strerror(-r));
+                        goto finish;
+                }
+        }
+
+        fd = sd_journal_get_fd(j);
+        if (fd < 0) {
+                log_error("Failed to get wakeup fd: %s", strerror(-fd));
+                goto finish;
+        }
+
+        if (arg_lines >= 0) {
+                r = sd_journal_seek_tail(j);
+                if (r < 0) {
+                        log_error("Failed to seek to tail: %s", strerror(-r));
+                        goto finish;
+                }
+
+                r = sd_journal_previous_skip(j, arg_lines);
+        } else {
+                r = sd_journal_seek_head(j);
+                if (r < 0) {
+                        log_error("Failed to seek to head: %s", strerror(-r));
+                        goto finish;
+                }
+
+                r = sd_journal_next(j);
+        }
+
+        if (r < 0) {
+                log_error("Failed to iterate through journal: %s", strerror(-r));
+                goto finish;
+        }
+
+        if (!arg_no_pager && !arg_follow) {
+                columns();
+                pager_open();
+        }
+
+        if (arg_output == OUTPUT_JSON) {
+                fputc('[', stdout);
+                fflush(stdout);
+        }
+
+        for (;;) {
+                for (;;) {
+                        if (need_seek) {
+                                r = sd_journal_next(j);
+                                if (r < 0) {
+                                        log_error("Failed to iterate through journal: %s", strerror(-r));
+                                        goto finish;
+                                }
+                        }
+
+                        if (r == 0)
+                                break;
+
+                        line ++;
+
+                        r = output_journal(j, arg_output, line, 0, arg_show_all);
+                        if (r < 0)
+                                goto finish;
+
+                        need_seek = true;
+                }
+
+                if (!arg_follow)
+                        break;
+
+                r = fd_wait_for_event(fd, POLLIN, (usec_t) -1);
+                if (r < 0) {
+                        log_error("Couldn't wait for event: %s", strerror(-r));
+                        goto finish;
+                }
+
+                r = sd_journal_process(j);
+                if (r < 0) {
+                        log_error("Failed to process: %s", strerror(-r));
+                        goto finish;
+                }
+        }
+
+        if (arg_output == OUTPUT_JSON)
+                fputs("\n]\n", stdout);
+
+finish:
+        if (j)
+                sd_journal_close(j);
+
+        pager_close();
+
+        return r < 0 ? EXIT_FAILURE : EXIT_SUCCESS;
+}
diff --git a/src/journal/journald-gperf.gperf b/src/journal/journald-gperf.gperf
new file mode 100644 (file)
index 0000000..a56f6d9
--- /dev/null
@@ -0,0 +1,31 @@
+%{
+#include <stddef.h>
+#include "conf-parser.h"
+#include "journald.h"
+%}
+struct ConfigPerfItem;
+%null_strings
+%language=ANSI-C
+%define slot-name section_and_lvalue
+%define hash-function-name journald_gperf_hash
+%define lookup-function-name journald_gperf_lookup
+%readonly-tables
+%omit-struct-type
+%struct-type
+%includes
+%%
+Journal.RateLimitInterval,  config_parse_usec,      0, offsetof(Server, rate_limit_interval)
+Journal.RateLimitBurst,     config_parse_unsigned,  0, offsetof(Server, rate_limit_burst)
+Journal.Compress,           config_parse_bool,      0, offsetof(Server, compress)
+Journal.SystemMaxUse,       config_parse_bytes_off, 0, offsetof(Server, system_metrics.max_use)
+Journal.SystemMaxFileSize,  config_parse_bytes_off, 0, offsetof(Server, system_metrics.max_size)
+Journal.SystemMinFileSize,  config_parse_bytes_off, 0, offsetof(Server, system_metrics.min_size)
+Journal.SystemKeepFree,     config_parse_bytes_off, 0, offsetof(Server, system_metrics.keep_free)
+Journal.RuntimeMaxUse,      config_parse_bytes_off, 0, offsetof(Server, runtime_metrics.max_use)
+Journal.RuntimeMaxFileSize, config_parse_bytes_off, 0, offsetof(Server, runtime_metrics.max_size)
+Journal.RuntimeMinFileSize, config_parse_bytes_off, 0, offsetof(Server, runtime_metrics.min_size)
+Journal.RuntimeKeepFree,    config_parse_bytes_off, 0, offsetof(Server, runtime_metrics.keep_free)
+Journal.ForwardToSyslog,    config_parse_bool,      0, offsetof(Server, forward_to_syslog)
+Journal.ForwardToKMsg,      config_parse_bool,      0, offsetof(Server, forward_to_kmsg)
+Journal.ForwardToConsole,   config_parse_bool,      0, offsetof(Server, forward_to_console)
+Journal.ImportKernel,       config_parse_bool,      0, offsetof(Server, import_proc_kmsg)
diff --git a/src/journal/journald.c b/src/journal/journald.c
new file mode 100644 (file)
index 0000000..555d74f
--- /dev/null
@@ -0,0 +1,2821 @@
+/*-*- 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 <http://www.gnu.org/licenses/>.
+***/
+
+#include <sys/epoll.h>
+#include <sys/socket.h>
+#include <errno.h>
+#include <sys/signalfd.h>
+#include <unistd.h>
+#include <fcntl.h>
+#include <stddef.h>
+#include <sys/ioctl.h>
+#include <linux/sockios.h>
+#include <sys/statvfs.h>
+
+#include <systemd/sd-journal.h>
+#include <systemd/sd-login.h>
+#include <systemd/sd-messages.h>
+#include <systemd/sd-daemon.h>
+
+#include "hashmap.h"
+#include "journal-file.h"
+#include "socket-util.h"
+#include "cgroup-util.h"
+#include "list.h"
+#include "journal-rate-limit.h"
+#include "journal-internal.h"
+#include "conf-parser.h"
+#include "journald.h"
+#include "virt.h"
+#include "missing.h"
+
+#ifdef HAVE_ACL
+#include <sys/acl.h>
+#include <acl/libacl.h>
+#include "acl-util.h"
+#endif
+
+#ifdef HAVE_SELINUX
+#include <selinux/selinux.h>
+#endif
+
+#define USER_JOURNALS_MAX 1024
+#define STDOUT_STREAMS_MAX 4096
+
+#define DEFAULT_RATE_LIMIT_INTERVAL (10*USEC_PER_SEC)
+#define DEFAULT_RATE_LIMIT_BURST 200
+
+#define RECHECK_AVAILABLE_SPACE_USEC (30*USEC_PER_SEC)
+
+#define RECHECK_VAR_AVAILABLE_USEC (30*USEC_PER_SEC)
+
+#define N_IOVEC_META_FIELDS 17
+
+#define ENTRY_SIZE_MAX (1024*1024*32)
+
+typedef enum StdoutStreamState {
+        STDOUT_STREAM_IDENTIFIER,
+        STDOUT_STREAM_PRIORITY,
+        STDOUT_STREAM_LEVEL_PREFIX,
+        STDOUT_STREAM_FORWARD_TO_SYSLOG,
+        STDOUT_STREAM_FORWARD_TO_KMSG,
+        STDOUT_STREAM_FORWARD_TO_CONSOLE,
+        STDOUT_STREAM_RUNNING
+} StdoutStreamState;
+
+struct StdoutStream {
+        Server *server;
+        StdoutStreamState state;
+
+        int fd;
+
+        struct ucred ucred;
+#ifdef HAVE_SELINUX
+        security_context_t security_context;
+#endif
+
+        char *identifier;
+        int priority;
+        bool level_prefix:1;
+        bool forward_to_syslog:1;
+        bool forward_to_kmsg:1;
+        bool forward_to_console:1;
+
+        char buffer[LINE_MAX+1];
+        size_t length;
+
+        LIST_FIELDS(StdoutStream, stdout_stream);
+};
+
+static int server_flush_to_var(Server *s);
+
+static uint64_t available_space(Server *s) {
+        char ids[33], *p;
+        const char *f;
+        sd_id128_t machine;
+        struct statvfs ss;
+        uint64_t sum = 0, avail = 0, ss_avail = 0;
+        int r;
+        DIR *d;
+        usec_t ts;
+        JournalMetrics *m;
+
+        ts = now(CLOCK_MONOTONIC);
+
+        if (s->cached_available_space_timestamp + RECHECK_AVAILABLE_SPACE_USEC > ts)
+                return s->cached_available_space;
+
+        r = sd_id128_get_machine(&machine);
+        if (r < 0)
+                return 0;
+
+        if (s->system_journal) {
+                f = "/var/log/journal/";
+                m = &s->system_metrics;
+        } else {
+                f = "/run/log/journal/";
+                m = &s->runtime_metrics;
+        }
+
+        assert(m);
+
+        p = strappend(f, sd_id128_to_string(machine, ids));
+        if (!p)
+                return 0;
+
+        d = opendir(p);
+        free(p);
+
+        if (!d)
+                return 0;
+
+        if (fstatvfs(dirfd(d), &ss) < 0)
+                goto finish;
+
+        for (;;) {
+                struct stat st;
+                struct dirent buf, *de;
+
+                r = readdir_r(d, &buf, &de);
+                if (r != 0)
+                        break;
+
+                if (!de)
+                        break;
+
+                if (!endswith(de->d_name, ".journal") &&
+                    !endswith(de->d_name, ".journal~"))
+                        continue;
+
+                if (fstatat(dirfd(d), de->d_name, &st, AT_SYMLINK_NOFOLLOW) < 0)
+                        continue;
+
+                if (!S_ISREG(st.st_mode))
+                        continue;
+
+                sum += (uint64_t) st.st_blocks * 512UL;
+        }
+
+        avail = sum >= m->max_use ? 0 : m->max_use - sum;
+
+        ss_avail = ss.f_bsize * ss.f_bavail;
+
+        ss_avail = ss_avail < m->keep_free ? 0 : ss_avail - m->keep_free;
+
+        if (ss_avail < avail)
+                avail = ss_avail;
+
+        s->cached_available_space = avail;
+        s->cached_available_space_timestamp = ts;
+
+finish:
+        closedir(d);
+
+        return avail;
+}
+
+static void server_read_file_gid(Server *s) {
+        const char *adm = "adm";
+        int r;
+
+        assert(s);
+
+        if (s->file_gid_valid)
+                return;
+
+        r = get_group_creds(&adm, &s->file_gid);
+        if (r < 0)
+                log_warning("Failed to resolve 'adm' group: %s", strerror(-r));
+
+        /* if we couldn't read the gid, then it will be 0, but that's
+         * fine and we shouldn't try to resolve the group again, so
+         * let's just pretend it worked right-away. */
+        s->file_gid_valid = true;
+}
+
+static void server_fix_perms(Server *s, JournalFile *f, uid_t uid) {
+        int r;
+#ifdef HAVE_ACL
+        acl_t acl;
+        acl_entry_t entry;
+        acl_permset_t permset;
+#endif
+
+        assert(f);
+
+        server_read_file_gid(s);
+
+        r = fchmod_and_fchown(f->fd, 0640, 0, s->file_gid);
+        if (r < 0)
+                log_warning("Failed to fix access mode/rights on %s, ignoring: %s", f->path, strerror(-r));
+
+#ifdef HAVE_ACL
+        if (uid <= 0)
+                return;
+
+        acl = acl_get_fd(f->fd);
+        if (!acl) {
+                log_warning("Failed to read ACL on %s, ignoring: %m", f->path);
+                return;
+        }
+
+        r = acl_find_uid(acl, uid, &entry);
+        if (r <= 0) {
+
+                if (acl_create_entry(&acl, &entry) < 0 ||
+                    acl_set_tag_type(entry, ACL_USER) < 0 ||
+                    acl_set_qualifier(entry, &uid) < 0) {
+                        log_warning("Failed to patch ACL on %s, ignoring: %m", f->path);
+                        goto finish;
+                }
+        }
+
+        if (acl_get_permset(entry, &permset) < 0 ||
+            acl_add_perm(permset, ACL_READ) < 0 ||
+            acl_calc_mask(&acl) < 0) {
+                log_warning("Failed to patch ACL on %s, ignoring: %m", f->path);
+                goto finish;
+        }
+
+        if (acl_set_fd(f->fd, acl) < 0)
+                log_warning("Failed to set ACL on %s, ignoring: %m", f->path);
+
+finish:
+        acl_free(acl);
+#endif
+}
+
+static JournalFile* find_journal(Server *s, uid_t uid) {
+        char *p;
+        int r;
+        JournalFile *f;
+        char ids[33];
+        sd_id128_t machine;
+
+        assert(s);
+
+        /* We split up user logs only on /var, not on /run. If the
+         * runtime file is open, we write to it exclusively, in order
+         * to guarantee proper order as soon as we flush /run to
+         * /var and close the runtime file. */
+
+        if (s->runtime_journal)
+                return s->runtime_journal;
+
+        if (uid <= 0)
+                return s->system_journal;
+
+        r = sd_id128_get_machine(&machine);
+        if (r < 0)
+                return s->system_journal;
+
+        f = hashmap_get(s->user_journals, UINT32_TO_PTR(uid));
+        if (f)
+                return f;
+
+        if (asprintf(&p, "/var/log/journal/%s/user-%lu.journal", sd_id128_to_string(machine, ids), (unsigned long) uid) < 0)
+                return s->system_journal;
+
+        while (hashmap_size(s->user_journals) >= USER_JOURNALS_MAX) {
+                /* Too many open? Then let's close one */
+                f = hashmap_steal_first(s->user_journals);
+                assert(f);
+                journal_file_close(f);
+        }
+
+        r = journal_file_open_reliably(p, O_RDWR|O_CREAT, 0640, s->system_journal, &f);
+        free(p);
+
+        if (r < 0)
+                return s->system_journal;
+
+        server_fix_perms(s, f, uid);
+
+        r = hashmap_put(s->user_journals, UINT32_TO_PTR(uid), f);
+        if (r < 0) {
+                journal_file_close(f);
+                return s->system_journal;
+        }
+
+        return f;
+}
+
+static void server_rotate(Server *s) {
+        JournalFile *f;
+        void *k;
+        Iterator i;
+        int r;
+
+        log_info("Rotating...");
+
+        if (s->runtime_journal) {
+                r = journal_file_rotate(&s->runtime_journal);
+                if (r < 0)
+                        log_error("Failed to rotate %s: %s", s->runtime_journal->path, strerror(-r));
+                else
+                        server_fix_perms(s, s->runtime_journal, 0);
+        }
+
+        if (s->system_journal) {
+                r = journal_file_rotate(&s->system_journal);
+                if (r < 0)
+                        log_error("Failed to rotate %s: %s", s->system_journal->path, strerror(-r));
+                else
+                        server_fix_perms(s, s->system_journal, 0);
+        }
+
+        HASHMAP_FOREACH_KEY(f, k, s->user_journals, i) {
+                r = journal_file_rotate(&f);
+                if (r < 0)
+                        log_error("Failed to rotate %s: %s", f->path, strerror(-r));
+                else {
+                        hashmap_replace(s->user_journals, k, f);
+                        server_fix_perms(s, s->system_journal, PTR_TO_UINT32(k));
+                }
+        }
+}
+
+static void server_vacuum(Server *s) {
+        char *p;
+        char ids[33];
+        sd_id128_t machine;
+        int r;
+
+        log_info("Vacuuming...");
+
+        r = sd_id128_get_machine(&machine);
+        if (r < 0) {
+                log_error("Failed to get machine ID: %s", strerror(-r));
+                return;
+        }
+
+        sd_id128_to_string(machine, ids);
+
+        if (s->system_journal) {
+                if (asprintf(&p, "/var/log/journal/%s", ids) < 0) {
+                        log_error("Out of memory.");
+                        return;
+                }
+
+                r = journal_directory_vacuum(p, s->system_metrics.max_use, s->system_metrics.keep_free);
+                if (r < 0 && r != -ENOENT)
+                        log_error("Failed to vacuum %s: %s", p, strerror(-r));
+                free(p);
+        }
+
+
+        if (s->runtime_journal) {
+                if (asprintf(&p, "/run/log/journal/%s", ids) < 0) {
+                        log_error("Out of memory.");
+                        return;
+                }
+
+                r = journal_directory_vacuum(p, s->runtime_metrics.max_use, s->runtime_metrics.keep_free);
+                if (r < 0 && r != -ENOENT)
+                        log_error("Failed to vacuum %s: %s", p, strerror(-r));
+                free(p);
+        }
+
+        s->cached_available_space_timestamp = 0;
+}
+
+static char *shortened_cgroup_path(pid_t pid) {
+        int r;
+        char *process_path, *init_path, *path;
+
+        assert(pid > 0);
+
+        r = cg_get_by_pid(SYSTEMD_CGROUP_CONTROLLER, pid, &process_path);
+        if (r < 0)
+                return NULL;
+
+        r = cg_get_by_pid(SYSTEMD_CGROUP_CONTROLLER, 1, &init_path);
+        if (r < 0) {
+                free(process_path);
+                return NULL;
+        }
+
+        if (endswith(init_path, "/system"))
+                init_path[strlen(init_path) - 7] = 0;
+        else if (streq(init_path, "/"))
+                init_path[0] = 0;
+
+        if (startswith(process_path, init_path)) {
+                char *p;
+
+                p = strdup(process_path + strlen(init_path));
+                if (!p) {
+                        free(process_path);
+                        free(init_path);
+                        return NULL;
+                }
+                path = p;
+        } else {
+                path = process_path;
+                process_path = NULL;
+        }
+
+        free(process_path);
+        free(init_path);
+
+        return path;
+}
+
+static void dispatch_message_real(
+                Server *s,
+                struct iovec *iovec, unsigned n, unsigned m,
+                struct ucred *ucred,
+                struct timeval *tv,
+                const char *label, size_t label_len) {
+
+        char *pid = NULL, *uid = NULL, *gid = NULL,
+                *source_time = NULL, *boot_id = NULL, *machine_id = NULL,
+                *comm = NULL, *cmdline = NULL, *hostname = NULL,
+                *audit_session = NULL, *audit_loginuid = NULL,
+                *exe = NULL, *cgroup = NULL, *session = NULL,
+                *owner_uid = NULL, *unit = NULL, *selinux_context = NULL;
+
+        char idbuf[33];
+        sd_id128_t id;
+        int r;
+        char *t;
+        uid_t loginuid = 0, realuid = 0;
+        JournalFile *f;
+        bool vacuumed = false;
+
+        assert(s);
+        assert(iovec);
+        assert(n > 0);
+        assert(n + N_IOVEC_META_FIELDS <= m);
+
+        if (ucred) {
+                uint32_t audit;
+                uid_t owner;
+
+                realuid = ucred->uid;
+
+                if (asprintf(&pid, "_PID=%lu", (unsigned long) ucred->pid) >= 0)
+                        IOVEC_SET_STRING(iovec[n++], pid);
+
+                if (asprintf(&uid, "_UID=%lu", (unsigned long) ucred->uid) >= 0)
+                        IOVEC_SET_STRING(iovec[n++], uid);
+
+                if (asprintf(&gid, "_GID=%lu", (unsigned long) ucred->gid) >= 0)
+                        IOVEC_SET_STRING(iovec[n++], gid);
+
+                r = get_process_comm(ucred->pid, &t);
+                if (r >= 0) {
+                        comm = strappend("_COMM=", t);
+                        free(t);
+
+                        if (comm)
+                                IOVEC_SET_STRING(iovec[n++], comm);
+                }
+
+                r = get_process_exe(ucred->pid, &t);
+                if (r >= 0) {
+                        exe = strappend("_EXE=", t);
+                        free(t);
+
+                        if (exe)
+                                IOVEC_SET_STRING(iovec[n++], exe);
+                }
+
+                r = get_process_cmdline(ucred->pid, LINE_MAX, false, &t);
+                if (r >= 0) {
+                        cmdline = strappend("_CMDLINE=", t);
+                        free(t);
+
+                        if (cmdline)
+                                IOVEC_SET_STRING(iovec[n++], cmdline);
+                }
+
+                r = audit_session_from_pid(ucred->pid, &audit);
+                if (r >= 0)
+                        if (asprintf(&audit_session, "_AUDIT_SESSION=%lu", (unsigned long) audit) >= 0)
+                                IOVEC_SET_STRING(iovec[n++], audit_session);
+
+                r = audit_loginuid_from_pid(ucred->pid, &loginuid);
+                if (r >= 0)
+                        if (asprintf(&audit_loginuid, "_AUDIT_LOGINUID=%lu", (unsigned long) loginuid) >= 0)
+                                IOVEC_SET_STRING(iovec[n++], audit_loginuid);
+
+                t = shortened_cgroup_path(ucred->pid);
+                if (t) {
+                        cgroup = strappend("_SYSTEMD_CGROUP=", t);
+                        free(t);
+
+                        if (cgroup)
+                                IOVEC_SET_STRING(iovec[n++], cgroup);
+                }
+
+                if (sd_pid_get_session(ucred->pid, &t) >= 0) {
+                        session = strappend("_SYSTEMD_SESSION=", t);
+                        free(t);
+
+                        if (session)
+                                IOVEC_SET_STRING(iovec[n++], session);
+                }
+
+                if (sd_pid_get_unit(ucred->pid, &t) >= 0) {
+                        unit = strappend("_SYSTEMD_UNIT=", t);
+                        free(t);
+
+                        if (unit)
+                                IOVEC_SET_STRING(iovec[n++], unit);
+                }
+
+                if (sd_pid_get_owner_uid(ucred->uid, &owner) >= 0)
+                        if (asprintf(&owner_uid, "_SYSTEMD_OWNER_UID=%lu", (unsigned long) owner) >= 0)
+                                IOVEC_SET_STRING(iovec[n++], owner_uid);
+
+#ifdef HAVE_SELINUX
+                if (label) {
+                        selinux_context = malloc(sizeof("_SELINUX_CONTEXT=") + label_len);
+                        if (selinux_context) {
+                                memcpy(selinux_context, "_SELINUX_CONTEXT=", sizeof("_SELINUX_CONTEXT=")-1);
+                                memcpy(selinux_context+sizeof("_SELINUX_CONTEXT=")-1, label, label_len);
+                                selinux_context[sizeof("_SELINUX_CONTEXT=")-1+label_len] = 0;
+                                IOVEC_SET_STRING(iovec[n++], selinux_context);
+                        }
+                } else {
+                        security_context_t con;
+
+                        if (getpidcon(ucred->pid, &con) >= 0) {
+                                selinux_context = strappend("_SELINUX_CONTEXT=", con);
+                                if (selinux_context)
+                                        IOVEC_SET_STRING(iovec[n++], selinux_context);
+
+                                freecon(con);
+                        }
+                }
+#endif
+        }
+
+        if (tv) {
+                if (asprintf(&source_time, "_SOURCE_REALTIME_TIMESTAMP=%llu",
+                             (unsigned long long) timeval_load(tv)) >= 0)
+                        IOVEC_SET_STRING(iovec[n++], source_time);
+        }
+
+        /* Note that strictly speaking storing the boot id here is
+         * redundant since the entry includes this in-line
+         * anyway. However, we need this indexed, too. */
+        r = sd_id128_get_boot(&id);
+        if (r >= 0)
+                if (asprintf(&boot_id, "_BOOT_ID=%s", sd_id128_to_string(id, idbuf)) >= 0)
+                        IOVEC_SET_STRING(iovec[n++], boot_id);
+
+        r = sd_id128_get_machine(&id);
+        if (r >= 0)
+                if (asprintf(&machine_id, "_MACHINE_ID=%s", sd_id128_to_string(id, idbuf)) >= 0)
+                        IOVEC_SET_STRING(iovec[n++], machine_id);
+
+        t = gethostname_malloc();
+        if (t) {
+                hostname = strappend("_HOSTNAME=", t);
+                free(t);
+                if (hostname)
+                        IOVEC_SET_STRING(iovec[n++], hostname);
+        }
+
+        assert(n <= m);
+
+        server_flush_to_var(s);
+
+retry:
+        f = find_journal(s, realuid == 0 ? 0 : loginuid);
+        if (!f)
+                log_warning("Dropping message, as we can't find a place to store the data.");
+        else {
+                r = journal_file_append_entry(f, NULL, iovec, n, &s->seqnum, NULL, NULL);
+
+                if ((r == -E2BIG || /* hit limit */
+                     r == -EFBIG || /* hit fs limit */
+                     r == -EDQUOT || /* quota hit */
+                     r == -ENOSPC || /* disk full */
+                     r == -EBADMSG || /* corrupted */
+                     r == -ENODATA || /* truncated */
+                     r == -EHOSTDOWN || /* other machine */
+                     r == -EPROTONOSUPPORT) && /* unsupported feature */
+                    !vacuumed) {
+
+                        if (r == -E2BIG)
+                                log_info("Allocation limit reached, rotating.");
+                        else
+                                log_warning("Journal file corrupted, rotating.");
+
+                        server_rotate(s);
+                        server_vacuum(s);
+                        vacuumed = true;
+
+                        log_info("Retrying write.");
+                        goto retry;
+                }
+
+                if (r < 0)
+                        log_error("Failed to write entry, ignoring: %s", strerror(-r));
+        }
+
+        free(pid);
+        free(uid);
+        free(gid);
+        free(comm);
+        free(exe);
+        free(cmdline);
+        free(source_time);
+        free(boot_id);
+        free(machine_id);
+        free(hostname);
+        free(audit_session);
+        free(audit_loginuid);
+        free(cgroup);
+        free(session);
+        free(owner_uid);
+        free(unit);
+        free(selinux_context);
+}
+
+static void driver_message(Server *s, sd_id128_t message_id, const char *format, ...) {
+        char mid[11 + 32 + 1];
+        char buffer[16 + LINE_MAX + 1];
+        struct iovec iovec[N_IOVEC_META_FIELDS + 4];
+        int n = 0;
+        va_list ap;
+        struct ucred ucred;
+
+        assert(s);
+        assert(format);
+
+        IOVEC_SET_STRING(iovec[n++], "PRIORITY=5");
+        IOVEC_SET_STRING(iovec[n++], "_TRANSPORT=driver");
+
+        memcpy(buffer, "MESSAGE=", 8);
+        va_start(ap, format);
+        vsnprintf(buffer + 8, sizeof(buffer) - 8, format, ap);
+        va_end(ap);
+        char_array_0(buffer);
+        IOVEC_SET_STRING(iovec[n++], buffer);
+
+        snprintf(mid, sizeof(mid), "MESSAGE_ID=" SD_ID128_FORMAT_STR, SD_ID128_FORMAT_VAL(message_id));
+        char_array_0(mid);
+        IOVEC_SET_STRING(iovec[n++], mid);
+
+        zero(ucred);
+        ucred.pid = getpid();
+        ucred.uid = getuid();
+        ucred.gid = getgid();
+
+        dispatch_message_real(s, iovec, n, ELEMENTSOF(iovec), &ucred, NULL, NULL, 0);
+}
+
+static void dispatch_message(Server *s,
+                             struct iovec *iovec, unsigned n, unsigned m,
+                             struct ucred *ucred,
+                             struct timeval *tv,
+                             const char *label, size_t label_len,
+                             int priority) {
+        int rl;
+        char *path = NULL, *c;
+
+        assert(s);
+        assert(iovec || n == 0);
+
+        if (n == 0)
+                return;
+
+        if (!ucred)
+                goto finish;
+
+        path = shortened_cgroup_path(ucred->pid);
+        if (!path)
+                goto finish;
+
+        /* example: /user/lennart/3/foobar
+         *          /system/dbus.service/foobar
+         *
+         * So let's cut of everything past the third /, since that is
+         * wher user directories start */
+
+        c = strchr(path, '/');
+        if (c) {
+                c = strchr(c+1, '/');
+                if (c) {
+                        c = strchr(c+1, '/');
+                        if (c)
+                                *c = 0;
+                }
+        }
+
+        rl = journal_rate_limit_test(s->rate_limit, path, priority & LOG_PRIMASK, available_space(s));
+
+        if (rl == 0) {
+                free(path);
+                return;
+        }
+
+        /* Write a suppression message if we suppressed something */
+        if (rl > 1)
+                driver_message(s, SD_MESSAGE_JOURNAL_DROPPED, "Suppressed %u messages from %s", rl - 1, path);
+
+        free(path);
+
+finish:
+        dispatch_message_real(s, iovec, n, m, ucred, tv, label, label_len);
+}
+
+static void forward_syslog_iovec(Server *s, const struct iovec *iovec, unsigned n_iovec, struct ucred *ucred, struct timeval *tv) {
+        struct msghdr msghdr;
+        struct cmsghdr *cmsg;
+        union {
+                struct cmsghdr cmsghdr;
+                uint8_t buf[CMSG_SPACE(sizeof(struct ucred))];
+        } control;
+        union sockaddr_union sa;
+
+        assert(s);
+        assert(iovec);
+        assert(n_iovec > 0);
+
+        zero(msghdr);
+        msghdr.msg_iov = (struct iovec*) iovec;
+        msghdr.msg_iovlen = n_iovec;
+
+        zero(sa);
+        sa.un.sun_family = AF_UNIX;
+        strncpy(sa.un.sun_path, "/run/systemd/journal/syslog", sizeof(sa.un.sun_path));
+        msghdr.msg_name = &sa;
+        msghdr.msg_namelen = offsetof(union sockaddr_union, un.sun_path) + strlen(sa.un.sun_path);
+
+        if (ucred) {
+                zero(control);
+                msghdr.msg_control = &control;
+                msghdr.msg_controllen = sizeof(control);
+
+                cmsg = CMSG_FIRSTHDR(&msghdr);
+                cmsg->cmsg_level = SOL_SOCKET;
+                cmsg->cmsg_type = SCM_CREDENTIALS;
+                cmsg->cmsg_len = CMSG_LEN(sizeof(struct ucred));
+                memcpy(CMSG_DATA(cmsg), ucred, sizeof(struct ucred));
+                msghdr.msg_controllen = cmsg->cmsg_len;
+        }
+
+        /* Forward the syslog message we received via /dev/log to
+         * /run/systemd/syslog. Unfortunately we currently can't set
+         * the SO_TIMESTAMP auxiliary data, and hence we don't. */
+
+        if (sendmsg(s->syslog_fd, &msghdr, MSG_NOSIGNAL) >= 0)
+                return;
+
+        /* The socket is full? I guess the syslog implementation is
+         * too slow, and we shouldn't wait for that... */
+        if (errno == EAGAIN)
+                return;
+
+        if (ucred && errno == ESRCH) {
+                struct ucred u;
+
+                /* Hmm, presumably the sender process vanished
+                 * by now, so let's fix it as good as we
+                 * can, and retry */
+
+                u = *ucred;
+                u.pid = getpid();
+                memcpy(CMSG_DATA(cmsg), &u, sizeof(struct ucred));
+
+                if (sendmsg(s->syslog_fd, &msghdr, MSG_NOSIGNAL) >= 0)
+                        return;
+
+                if (errno == EAGAIN)
+                        return;
+        }
+
+        log_debug("Failed to forward syslog message: %m");
+}
+
+static void forward_syslog_raw(Server *s, const char *buffer, struct ucred *ucred, struct timeval *tv) {
+        struct iovec iovec;
+
+        assert(s);
+        assert(buffer);
+
+        IOVEC_SET_STRING(iovec, buffer);
+        forward_syslog_iovec(s, &iovec, 1, ucred, tv);
+}
+
+static void forward_syslog(Server *s, int priority, const char *identifier, const char *message, struct ucred *ucred, struct timeval *tv) {
+        struct iovec iovec[5];
+        char header_priority[6], header_time[64], header_pid[16];
+        int n = 0;
+        time_t t;
+        struct tm *tm;
+        char *ident_buf = NULL;
+
+        assert(s);
+        assert(priority >= 0);
+        assert(priority <= 999);
+        assert(message);
+
+        /* First: priority field */
+        snprintf(header_priority, sizeof(header_priority), "<%i>", priority);
+        char_array_0(header_priority);
+        IOVEC_SET_STRING(iovec[n++], header_priority);
+
+        /* Second: timestamp */
+        t = tv ? tv->tv_sec : ((time_t) (now(CLOCK_REALTIME) / USEC_PER_SEC));
+        tm = localtime(&t);
+        if (!tm)
+                return;
+        if (strftime(header_time, sizeof(header_time), "%h %e %T ", tm) <= 0)
+                return;
+        IOVEC_SET_STRING(iovec[n++], header_time);
+
+        /* Third: identifier and PID */
+        if (ucred) {
+                if (!identifier) {
+                        get_process_comm(ucred->pid, &ident_buf);
+                        identifier = ident_buf;
+                }
+
+                snprintf(header_pid, sizeof(header_pid), "[%lu]: ", (unsigned long) ucred->pid);
+                char_array_0(header_pid);
+
+                if (identifier)
+                        IOVEC_SET_STRING(iovec[n++], identifier);
+
+                IOVEC_SET_STRING(iovec[n++], header_pid);
+        } else if (identifier) {
+                IOVEC_SET_STRING(iovec[n++], identifier);
+                IOVEC_SET_STRING(iovec[n++], ": ");
+        }
+
+        /* Fourth: message */
+        IOVEC_SET_STRING(iovec[n++], message);
+
+        forward_syslog_iovec(s, iovec, n, ucred, tv);
+
+        free(ident_buf);
+}
+
+static int fixup_priority(int priority) {
+
+        if ((priority & LOG_FACMASK) == 0)
+                return (priority & LOG_PRIMASK) | LOG_USER;
+
+        return priority;
+}
+
+static void forward_kmsg(Server *s, int priority, const char *identifier, const char *message, struct ucred *ucred) {
+        struct iovec iovec[5];
+        char header_priority[6], header_pid[16];
+        int n = 0;
+        char *ident_buf = NULL;
+        int fd;
+
+        assert(s);
+        assert(priority >= 0);
+        assert(priority <= 999);
+        assert(message);
+
+        /* Never allow messages with kernel facility to be written to
+         * kmsg, regardless where the data comes from. */
+        priority = fixup_priority(priority);
+
+        /* First: priority field */
+        snprintf(header_priority, sizeof(header_priority), "<%i>", priority);
+        char_array_0(header_priority);
+        IOVEC_SET_STRING(iovec[n++], header_priority);
+
+        /* Second: identifier and PID */
+        if (ucred) {
+                if (!identifier) {
+                        get_process_comm(ucred->pid, &ident_buf);
+                        identifier = ident_buf;
+                }
+
+                snprintf(header_pid, sizeof(header_pid), "[%lu]: ", (unsigned long) ucred->pid);
+                char_array_0(header_pid);
+
+                if (identifier)
+                        IOVEC_SET_STRING(iovec[n++], identifier);
+
+                IOVEC_SET_STRING(iovec[n++], header_pid);
+        } else if (identifier) {
+                IOVEC_SET_STRING(iovec[n++], identifier);
+                IOVEC_SET_STRING(iovec[n++], ": ");
+        }
+
+        /* Fourth: message */
+        IOVEC_SET_STRING(iovec[n++], message);
+        IOVEC_SET_STRING(iovec[n++], "\n");
+
+        fd = open("/dev/kmsg", O_WRONLY|O_NOCTTY|O_CLOEXEC);
+        if (fd < 0) {
+                log_debug("Failed to open /dev/kmsg for logging: %s", strerror(errno));
+                goto finish;
+        }
+
+        if (writev(fd, iovec, n) < 0)
+                log_debug("Failed to write to /dev/kmsg for logging: %s", strerror(errno));
+
+        close_nointr_nofail(fd);
+
+finish:
+        free(ident_buf);
+}
+
+static void forward_console(Server *s, const char *identifier, const char *message, struct ucred *ucred) {
+        struct iovec iovec[4];
+        char header_pid[16];
+        int n = 0, fd;
+        char *ident_buf = NULL;
+
+        assert(s);
+        assert(message);
+
+        /* First: identifier and PID */
+        if (ucred) {
+                if (!identifier) {
+                        get_process_comm(ucred->pid, &ident_buf);
+                        identifier = ident_buf;
+                }
+
+                snprintf(header_pid, sizeof(header_pid), "[%lu]: ", (unsigned long) ucred->pid);
+                char_array_0(header_pid);
+
+                if (identifier)
+                        IOVEC_SET_STRING(iovec[n++], identifier);
+
+                IOVEC_SET_STRING(iovec[n++], header_pid);
+        } else if (identifier) {
+                IOVEC_SET_STRING(iovec[n++], identifier);
+                IOVEC_SET_STRING(iovec[n++], ": ");
+        }
+
+        /* Third: message */
+        IOVEC_SET_STRING(iovec[n++], message);
+        IOVEC_SET_STRING(iovec[n++], "\n");
+
+        fd = open_terminal("/dev/console", O_WRONLY|O_NOCTTY|O_CLOEXEC);
+        if (fd < 0) {
+                log_debug("Failed to open /dev/console for logging: %s", strerror(errno));
+                goto finish;
+        }
+
+        if (writev(fd, iovec, n) < 0)
+                log_debug("Failed to write to /dev/console for logging: %s", strerror(errno));
+
+        close_nointr_nofail(fd);
+
+finish:
+        free(ident_buf);
+}
+
+static void read_identifier(const char **buf, char **identifier, char **pid) {
+        const char *p;
+        char *t;
+        size_t l, e;
+
+        assert(buf);
+        assert(identifier);
+        assert(pid);
+
+        p = *buf;
+
+        p += strspn(p, WHITESPACE);
+        l = strcspn(p, WHITESPACE);
+
+        if (l <= 0 ||
+            p[l-1] != ':')
+                return;
+
+        e = l;
+        l--;
+
+        if (p[l-1] == ']') {
+                size_t k = l-1;
+
+                for (;;) {
+
+                        if (p[k] == '[') {
+                                t = strndup(p+k+1, l-k-2);
+                                if (t)
+                                        *pid = t;
+
+                                l = k;
+                                break;
+                        }
+
+                        if (k == 0)
+                                break;
+
+                        k--;
+                }
+        }
+
+        t = strndup(p, l);
+        if (t)
+                *identifier = t;
+
+        *buf = p + e;
+        *buf += strspn(*buf, WHITESPACE);
+}
+
+static void process_syslog_message(Server *s, const char *buf, struct ucred *ucred, struct timeval *tv, const char *label, size_t label_len) {
+        char *message = NULL, *syslog_priority = NULL, *syslog_facility = NULL, *syslog_identifier = NULL, *syslog_pid = NULL;
+        struct iovec iovec[N_IOVEC_META_FIELDS + 6];
+        unsigned n = 0;
+        int priority = LOG_USER | LOG_INFO;
+        char *identifier = NULL, *pid = NULL;
+
+        assert(s);
+        assert(buf);
+
+        if (s->forward_to_syslog)
+                forward_syslog_raw(s, buf, ucred, tv);
+
+        parse_syslog_priority((char**) &buf, &priority);
+        skip_syslog_date((char**) &buf);
+        read_identifier(&buf, &identifier, &pid);
+
+        if (s->forward_to_kmsg)
+                forward_kmsg(s, priority, identifier, buf, ucred);
+
+        if (s->forward_to_console)
+                forward_console(s, identifier, buf, ucred);
+
+        IOVEC_SET_STRING(iovec[n++], "_TRANSPORT=syslog");
+
+        if (asprintf(&syslog_priority, "PRIORITY=%i", priority & LOG_PRIMASK) >= 0)
+                IOVEC_SET_STRING(iovec[n++], syslog_priority);
+
+        if (priority & LOG_FACMASK)
+                if (asprintf(&syslog_facility, "SYSLOG_FACILITY=%i", LOG_FAC(priority)) >= 0)
+                        IOVEC_SET_STRING(iovec[n++], syslog_facility);
+
+        if (identifier) {
+                syslog_identifier = strappend("SYSLOG_IDENTIFIER=", identifier);
+                if (syslog_identifier)
+                        IOVEC_SET_STRING(iovec[n++], syslog_identifier);
+        }
+
+        if (pid) {
+                syslog_pid = strappend("SYSLOG_PID=", pid);
+                if (syslog_pid)
+                        IOVEC_SET_STRING(iovec[n++], syslog_pid);
+        }
+
+        message = strappend("MESSAGE=", buf);
+        if (message)
+                IOVEC_SET_STRING(iovec[n++], message);
+
+        dispatch_message(s, iovec, n, ELEMENTSOF(iovec), ucred, tv, label, label_len, priority);
+
+        free(message);
+        free(identifier);
+        free(pid);
+        free(syslog_priority);
+        free(syslog_facility);
+        free(syslog_identifier);
+}
+
+static bool valid_user_field(const char *p, size_t l) {
+        const char *a;
+
+        /* We kinda enforce POSIX syntax recommendations for
+           environment variables here, but make a couple of additional
+           requirements.
+
+           http://pubs.opengroup.org/onlinepubs/000095399/basedefs/xbd_chap08.html */
+
+        /* No empty field names */
+        if (l <= 0)
+                return false;
+
+        /* Don't allow names longer than 64 chars */
+        if (l > 64)
+                return false;
+
+        /* Variables starting with an underscore are protected */
+        if (p[0] == '_')
+                return false;
+
+        /* Don't allow digits as first character */
+        if (p[0] >= '0' && p[0] <= '9')
+                return false;
+
+        /* Only allow A-Z0-9 and '_' */
+        for (a = p; a < p + l; a++)
+                if (!((*a >= 'A' && *a <= 'Z') ||
+                      (*a >= '0' && *a <= '9') ||
+                      *a == '_'))
+                        return false;
+
+        return true;
+}
+
+static void process_native_message(
+                Server *s,
+                const void *buffer, size_t buffer_size,
+                struct ucred *ucred,
+                struct timeval *tv,
+                const char *label, size_t label_len) {
+
+        struct iovec *iovec = NULL;
+        unsigned n = 0, m = 0, j, tn = (unsigned) -1;
+        const char *p;
+        size_t remaining;
+        int priority = LOG_INFO;
+        char *identifier = NULL, *message = NULL;
+
+        assert(s);
+        assert(buffer || n == 0);
+
+        p = buffer;
+        remaining = buffer_size;
+
+        while (remaining > 0) {
+                const char *e, *q;
+
+                e = memchr(p, '\n', remaining);
+
+                if (!e) {
+                        /* Trailing noise, let's ignore it, and flush what we collected */
+                        log_debug("Received message with trailing noise, ignoring.");
+                        break;
+                }
+
+                if (e == p) {
+                        /* Entry separator */
+                        dispatch_message(s, iovec, n, m, ucred, tv, label, label_len, priority);
+                        n = 0;
+                        priority = LOG_INFO;
+
+                        p++;
+                        remaining--;
+                        continue;
+                }
+
+                if (*p == '.' || *p == '#') {
+                        /* Ignore control commands for now, and
+                         * comments too. */
+                        remaining -= (e - p) + 1;
+                        p = e + 1;
+                        continue;
+                }
+
+                /* A property follows */
+
+                if (n+N_IOVEC_META_FIELDS >= m) {
+                        struct iovec *c;
+                        unsigned u;
+
+                        u = MAX((n+N_IOVEC_META_FIELDS+1) * 2U, 4U);
+                        c = realloc(iovec, u * sizeof(struct iovec));
+                        if (!c) {
+                                log_error("Out of memory");
+                                break;
+                        }
+
+                        iovec = c;
+                        m = u;
+                }
+
+                q = memchr(p, '=', e - p);
+                if (q) {
+                        if (valid_user_field(p, q - p)) {
+                                size_t l;
+
+                                l = e - p;
+
+                                /* If the field name starts with an
+                                 * underscore, skip the variable,
+                                 * since that indidates a trusted
+                                 * field */
+                                iovec[n].iov_base = (char*) p;
+                                iovec[n].iov_len = l;
+                                n++;
+
+                                /* We need to determine the priority
+                                 * of this entry for the rate limiting
+                                 * logic */
+                                if (l == 10 &&
+                                    memcmp(p, "PRIORITY=", 9) == 0 &&
+                                    p[9] >= '0' && p[9] <= '9')
+                                        priority = (priority & LOG_FACMASK) | (p[9] - '0');
+
+                                else if (l == 17 &&
+                                         memcmp(p, "SYSLOG_FACILITY=", 16) == 0 &&
+                                         p[16] >= '0' && p[16] <= '9')
+                                        priority = (priority & LOG_PRIMASK) | ((p[16] - '0') << 3);
+
+                                else if (l == 18 &&
+                                         memcmp(p, "SYSLOG_FACILITY=", 16) == 0 &&
+                                         p[16] >= '0' && p[16] <= '9' &&
+                                         p[17] >= '0' && p[17] <= '9')
+                                        priority = (priority & LOG_PRIMASK) | (((p[16] - '0')*10 + (p[17] - '0')) << 3);
+
+                                else if (l >= 12 &&
+                                         memcmp(p, "SYSLOG_IDENTIFIER=", 11) == 0) {
+                                        char *t;
+
+                                        t = strndup(p + 11, l - 11);
+                                        if (t) {
+                                                free(identifier);
+                                                identifier = t;
+                                        }
+                                } else if (l >= 8 &&
+                                           memcmp(p, "MESSAGE=", 8) == 0) {
+                                        char *t;
+
+                                        t = strndup(p + 8, l - 8);
+                                        if (t) {
+                                                free(message);
+                                                message = t;
+                                        }
+                                }
+                        }
+
+                        remaining -= (e - p) + 1;
+                        p = e + 1;
+                        continue;
+                } else {
+                        le64_t l_le;
+                        uint64_t l;
+                        char *k;
+
+                        if (remaining < e - p + 1 + sizeof(uint64_t) + 1) {
+                                log_debug("Failed to parse message, ignoring.");
+                                break;
+                        }
+
+                        memcpy(&l_le, e + 1, sizeof(uint64_t));
+                        l = le64toh(l_le);
+
+                        if (remaining < e - p + 1 + sizeof(uint64_t) + l + 1 ||
+                            e[1+sizeof(uint64_t)+l] != '\n') {
+                                log_debug("Failed to parse message, ignoring.");
+                                break;
+                        }
+
+                        k = malloc((e - p) + 1 + l);
+                        if (!k) {
+                                log_error("Out of memory");
+                                break;
+                        }
+
+                        memcpy(k, p, e - p);
+                        k[e - p] = '=';
+                        memcpy(k + (e - p) + 1, e + 1 + sizeof(uint64_t), l);
+
+                        if (valid_user_field(p, e - p)) {
+                                iovec[n].iov_base = k;
+                                iovec[n].iov_len = (e - p) + 1 + l;
+                                n++;
+                        } else
+                                free(k);
+
+                        remaining -= (e - p) + 1 + sizeof(uint64_t) + l + 1;
+                        p = e + 1 + sizeof(uint64_t) + l + 1;
+                }
+        }
+
+        if (n <= 0)
+                goto finish;
+
+        tn = n++;
+        IOVEC_SET_STRING(iovec[tn], "_TRANSPORT=journal");
+
+        if (message) {
+                if (s->forward_to_syslog)
+                        forward_syslog(s, priority, identifier, message, ucred, tv);
+
+                if (s->forward_to_kmsg)
+                        forward_kmsg(s, priority, identifier, message, ucred);
+
+                if (s->forward_to_console)
+                        forward_console(s, identifier, message, ucred);
+        }
+
+        dispatch_message(s, iovec, n, m, ucred, tv, label, label_len, priority);
+
+finish:
+        for (j = 0; j < n; j++)  {
+                if (j == tn)
+                        continue;
+
+                if (iovec[j].iov_base < buffer ||
+                    (const uint8_t*) iovec[j].iov_base >= (const uint8_t*) buffer + buffer_size)
+                        free(iovec[j].iov_base);
+        }
+
+        free(iovec);
+        free(identifier);
+        free(message);
+}
+
+static void process_native_file(
+                Server *s,
+                int fd,
+                struct ucred *ucred,
+                struct timeval *tv,
+                const char *label, size_t label_len) {
+
+        struct stat st;
+        void *p;
+        ssize_t n;
+
+        assert(s);
+        assert(fd >= 0);
+
+        /* Data is in the passed file, since it didn't fit in a
+         * datagram. We can't map the file here, since clients might
+         * then truncate it and trigger a SIGBUS for us. So let's
+         * stupidly read it */
+
+        if (fstat(fd, &st) < 0) {
+                log_error("Failed to stat passed file, ignoring: %m");
+                return;
+        }
+
+        if (!S_ISREG(st.st_mode)) {
+                log_error("File passed is not regular. Ignoring.");
+                return;
+        }
+
+        if (st.st_size <= 0)
+                return;
+
+        if (st.st_size > ENTRY_SIZE_MAX) {
+                log_error("File passed too large. Ignoring.");
+                return;
+        }
+
+        p = malloc(st.st_size);
+        if (!p) {
+                log_error("Out of memory");
+                return;
+        }
+
+        n = pread(fd, p, st.st_size, 0);
+        if (n < 0)
+                log_error("Failed to read file, ignoring: %s", strerror(-n));
+        else if (n > 0)
+                process_native_message(s, p, n, ucred, tv, label, label_len);
+
+        free(p);
+}
+
+static int stdout_stream_log(StdoutStream *s, const char *p) {
+        struct iovec iovec[N_IOVEC_META_FIELDS + 5];
+        char *message = NULL, *syslog_priority = NULL, *syslog_facility = NULL, *syslog_identifier = NULL;
+        unsigned n = 0;
+        int priority;
+        char *label = NULL;
+        size_t label_len = 0;
+
+        assert(s);
+        assert(p);
+
+        if (isempty(p))
+                return 0;
+
+        priority = s->priority;
+
+        if (s->level_prefix)
+                parse_syslog_priority((char**) &p, &priority);
+
+        if (s->forward_to_syslog || s->server->forward_to_syslog)
+                forward_syslog(s->server, fixup_priority(priority), s->identifier, p, &s->ucred, NULL);
+
+        if (s->forward_to_kmsg || s->server->forward_to_kmsg)
+                forward_kmsg(s->server, priority, s->identifier, p, &s->ucred);
+
+        if (s->forward_to_console || s->server->forward_to_console)
+                forward_console(s->server, s->identifier, p, &s->ucred);
+
+        IOVEC_SET_STRING(iovec[n++], "_TRANSPORT=stdout");
+
+        if (asprintf(&syslog_priority, "PRIORITY=%i", priority & LOG_PRIMASK) >= 0)
+                IOVEC_SET_STRING(iovec[n++], syslog_priority);
+
+        if (priority & LOG_FACMASK)
+                if (asprintf(&syslog_facility, "SYSLOG_FACILITY=%i", LOG_FAC(priority)) >= 0)
+                        IOVEC_SET_STRING(iovec[n++], syslog_facility);
+
+        if (s->identifier) {
+                syslog_identifier = strappend("SYSLOG_IDENTIFIER=", s->identifier);
+                if (syslog_identifier)
+                        IOVEC_SET_STRING(iovec[n++], syslog_identifier);
+        }
+
+        message = strappend("MESSAGE=", p);
+        if (message)
+                IOVEC_SET_STRING(iovec[n++], message);
+
+#ifdef HAVE_SELINUX
+        if (s->security_context) {
+                label = (char*) s->security_context;
+                label_len = strlen((char*) s->security_context);
+        }
+#endif
+
+        dispatch_message(s->server, iovec, n, ELEMENTSOF(iovec), &s->ucred, NULL, label, label_len, priority);
+
+        free(message);
+        free(syslog_priority);
+        free(syslog_facility);
+        free(syslog_identifier);
+
+        return 0;
+}
+
+static int stdout_stream_line(StdoutStream *s, char *p) {
+        int r;
+
+        assert(s);
+        assert(p);
+
+        p = strstrip(p);
+
+        switch (s->state) {
+
+        case STDOUT_STREAM_IDENTIFIER:
+                if (isempty(p))
+                        s->identifier = NULL;
+                else  {
+                        s->identifier = strdup(p);
+                        if (!s->identifier) {
+                                log_error("Out of memory");
+                                return -ENOMEM;
+                        }
+                }
+
+                s->state = STDOUT_STREAM_PRIORITY;
+                return 0;
+
+        case STDOUT_STREAM_PRIORITY:
+                r = safe_atoi(p, &s->priority);
+                if (r < 0 || s->priority <= 0 || s->priority >= 999) {
+                        log_warning("Failed to parse log priority line.");
+                        return -EINVAL;
+                }
+
+                s->state = STDOUT_STREAM_LEVEL_PREFIX;
+                return 0;
+
+        case STDOUT_STREAM_LEVEL_PREFIX:
+                r = parse_boolean(p);
+                if (r < 0) {
+                        log_warning("Failed to parse level prefix line.");
+                        return -EINVAL;
+                }
+
+                s->level_prefix = !!r;
+                s->state = STDOUT_STREAM_FORWARD_TO_SYSLOG;
+                return 0;
+
+        case STDOUT_STREAM_FORWARD_TO_SYSLOG:
+                r = parse_boolean(p);
+                if (r < 0) {
+                        log_warning("Failed to parse forward to syslog line.");
+                        return -EINVAL;
+                }
+
+                s->forward_to_syslog = !!r;
+                s->state = STDOUT_STREAM_FORWARD_TO_KMSG;
+                return 0;
+
+        case STDOUT_STREAM_FORWARD_TO_KMSG:
+                r = parse_boolean(p);
+                if (r < 0) {
+                        log_warning("Failed to parse copy to kmsg line.");
+                        return -EINVAL;
+                }
+
+                s->forward_to_kmsg = !!r;
+                s->state = STDOUT_STREAM_FORWARD_TO_CONSOLE;
+                return 0;
+
+        case STDOUT_STREAM_FORWARD_TO_CONSOLE:
+                r = parse_boolean(p);
+                if (r < 0) {
+                        log_warning("Failed to parse copy to console line.");
+                        return -EINVAL;
+                }
+
+                s->forward_to_console = !!r;
+                s->state = STDOUT_STREAM_RUNNING;
+                return 0;
+
+        case STDOUT_STREAM_RUNNING:
+                return stdout_stream_log(s, p);
+        }
+
+        assert_not_reached("Unknown stream state");
+}
+
+static int stdout_stream_scan(StdoutStream *s, bool force_flush) {
+        char *p;
+        size_t remaining;
+        int r;
+
+        assert(s);
+
+        p = s->buffer;
+        remaining = s->length;
+        for (;;) {
+                char *end;
+                size_t skip;
+
+                end = memchr(p, '\n', remaining);
+                if (end)
+                        skip = end - p + 1;
+                else if (remaining >= sizeof(s->buffer) - 1) {
+                        end = p + sizeof(s->buffer) - 1;
+                        skip = remaining;
+                } else
+                        break;
+
+                *end = 0;
+
+                r = stdout_stream_line(s, p);
+                if (r < 0)
+                        return r;
+
+                remaining -= skip;
+                p += skip;
+        }
+
+        if (force_flush && remaining > 0) {
+                p[remaining] = 0;
+                r = stdout_stream_line(s, p);
+                if (r < 0)
+                        return r;
+
+                p += remaining;
+                remaining = 0;
+        }
+
+        if (p > s->buffer) {
+                memmove(s->buffer, p, remaining);
+                s->length = remaining;
+        }
+
+        return 0;
+}
+
+static int stdout_stream_process(StdoutStream *s) {
+        ssize_t l;
+        int r;
+
+        assert(s);
+
+        l = read(s->fd, s->buffer+s->length, sizeof(s->buffer)-1-s->length);
+        if (l < 0) {
+
+                if (errno == EAGAIN)
+                        return 0;
+
+                log_warning("Failed to read from stream: %m");
+                return -errno;
+        }
+
+        if (l == 0) {
+                r = stdout_stream_scan(s, true);
+                if (r < 0)
+                        return r;
+
+                return 0;
+        }
+
+        s->length += l;
+        r = stdout_stream_scan(s, false);
+        if (r < 0)
+                return r;
+
+        return 1;
+
+}
+
+static void stdout_stream_free(StdoutStream *s) {
+        assert(s);
+
+        if (s->server) {
+                assert(s->server->n_stdout_streams > 0);
+                s->server->n_stdout_streams --;
+                LIST_REMOVE(StdoutStream, stdout_stream, s->server->stdout_streams, s);
+        }
+
+        if (s->fd >= 0) {
+                if (s->server)
+                        epoll_ctl(s->server->epoll_fd, EPOLL_CTL_DEL, s->fd, NULL);
+
+                close_nointr_nofail(s->fd);
+        }
+
+#ifdef HAVE_SELINUX
+        if (s->security_context)
+                freecon(s->security_context);
+#endif
+
+        free(s->identifier);
+        free(s);
+}
+
+static int stdout_stream_new(Server *s) {
+        StdoutStream *stream;
+        int fd, r;
+        socklen_t len;
+        struct epoll_event ev;
+
+        assert(s);
+
+        fd = accept4(s->stdout_fd, NULL, NULL, SOCK_NONBLOCK|SOCK_CLOEXEC);
+        if (fd < 0) {
+                if (errno == EAGAIN)
+                        return 0;
+
+                log_error("Failed to accept stdout connection: %m");
+                return -errno;
+        }
+
+        if (s->n_stdout_streams >= STDOUT_STREAMS_MAX) {
+                log_warning("Too many stdout streams, refusing connection.");
+                close_nointr_nofail(fd);
+                return 0;
+        }
+
+        stream = new0(StdoutStream, 1);
+        if (!stream) {
+                log_error("Out of memory.");
+                close_nointr_nofail(fd);
+                return -ENOMEM;
+        }
+
+        stream->fd = fd;
+
+        len = sizeof(stream->ucred);
+        if (getsockopt(fd, SOL_SOCKET, SO_PEERCRED, &stream->ucred, &len) < 0) {
+                log_error("Failed to determine peer credentials: %m");
+                r = -errno;
+                goto fail;
+        }
+
+#ifdef HAVE_SELINUX
+        if (getpeercon(fd, &stream->security_context) < 0)
+                log_error("Failed to determine peer security context.");
+#endif
+
+        if (shutdown(fd, SHUT_WR) < 0) {
+                log_error("Failed to shutdown writing side of socket: %m");
+                r = -errno;
+                goto fail;
+        }
+
+        zero(ev);
+        ev.data.ptr = stream;
+        ev.events = EPOLLIN;
+        if (epoll_ctl(s->epoll_fd, EPOLL_CTL_ADD, fd, &ev) < 0) {
+                log_error("Failed to add stream to event loop: %m");
+                r = -errno;
+                goto fail;
+        }
+
+        stream->server = s;
+        LIST_PREPEND(StdoutStream, stdout_stream, s->stdout_streams, stream);
+        s->n_stdout_streams ++;
+
+        return 0;
+
+fail:
+        stdout_stream_free(stream);
+        return r;
+}
+
+static int parse_kernel_timestamp(char **_p, usec_t *t) {
+        usec_t r;
+        int k, i;
+        char *p;
+
+        assert(_p);
+        assert(*_p);
+        assert(t);
+
+        p = *_p;
+
+        if (strlen(p) < 14 || p[0] != '[' || p[13] != ']' || p[6] != '.')
+                return 0;
+
+        r = 0;
+
+        for (i = 1; i <= 5; i++) {
+                r *= 10;
+
+                if (p[i] == ' ')
+                        continue;
+
+                k = undecchar(p[i]);
+                if (k < 0)
+                        return 0;
+
+                r += k;
+        }
+
+        for (i = 7; i <= 12; i++) {
+                r *= 10;
+
+                k = undecchar(p[i]);
+                if (k < 0)
+                        return 0;
+
+                r += k;
+        }
+
+        *t = r;
+        *_p += 14;
+        *_p += strspn(*_p, WHITESPACE);
+
+        return 1;
+}
+
+static void proc_kmsg_line(Server *s, const char *p) {
+        struct iovec iovec[N_IOVEC_META_FIELDS + 7];
+        char *message = NULL, *syslog_priority = NULL, *syslog_pid = NULL, *syslog_facility = NULL, *syslog_identifier = NULL, *source_time = NULL;
+        int priority = LOG_KERN | LOG_INFO;
+        unsigned n = 0;
+        usec_t usec;
+        char *identifier = NULL, *pid = NULL;
+
+        assert(s);
+        assert(p);
+
+        if (isempty(p))
+                return;
+
+        parse_syslog_priority((char **) &p, &priority);
+
+        if (s->forward_to_kmsg && (priority & LOG_FACMASK) != LOG_KERN)
+                return;
+
+        if (parse_kernel_timestamp((char **) &p, &usec) > 0) {
+                if (asprintf(&source_time, "_SOURCE_MONOTONIC_TIMESTAMP=%llu",
+                             (unsigned long long) usec) >= 0)
+                        IOVEC_SET_STRING(iovec[n++], source_time);
+        }
+
+        IOVEC_SET_STRING(iovec[n++], "_TRANSPORT=kernel");
+
+        if (asprintf(&syslog_priority, "PRIORITY=%i", priority & LOG_PRIMASK) >= 0)
+                IOVEC_SET_STRING(iovec[n++], syslog_priority);
+
+        if ((priority & LOG_FACMASK) == LOG_KERN) {
+
+                if (s->forward_to_syslog)
+                        forward_syslog(s, priority, "kernel", p, NULL, NULL);
+
+                IOVEC_SET_STRING(iovec[n++], "SYSLOG_IDENTIFIER=kernel");
+        } else {
+                read_identifier(&p, &identifier, &pid);
+
+                if (s->forward_to_syslog)
+                        forward_syslog(s, priority, identifier, p, NULL, NULL);
+
+                if (identifier) {
+                        syslog_identifier = strappend("SYSLOG_IDENTIFIER=", identifier);
+                        if (syslog_identifier)
+                                IOVEC_SET_STRING(iovec[n++], syslog_identifier);
+                }
+
+                if (pid) {
+                        syslog_pid = strappend("SYSLOG_PID=", pid);
+                        if (syslog_pid)
+                                IOVEC_SET_STRING(iovec[n++], syslog_pid);
+                }
+
+                if (asprintf(&syslog_facility, "SYSLOG_FACILITY=%i", LOG_FAC(priority)) >= 0)
+                        IOVEC_SET_STRING(iovec[n++], syslog_facility);
+        }
+
+        message = strappend("MESSAGE=", p);
+        if (message)
+                IOVEC_SET_STRING(iovec[n++], message);
+
+        dispatch_message(s, iovec, n, ELEMENTSOF(iovec), NULL, NULL, NULL, 0, priority);
+
+        free(message);
+        free(syslog_priority);
+        free(syslog_identifier);
+        free(syslog_pid);
+        free(syslog_facility);
+        free(source_time);
+        free(identifier);
+        free(pid);
+}
+
+static void proc_kmsg_scan(Server *s) {
+        char *p;
+        size_t remaining;
+
+        assert(s);
+
+        p = s->proc_kmsg_buffer;
+        remaining = s->proc_kmsg_length;
+        for (;;) {
+                char *end;
+                size_t skip;
+
+                end = memchr(p, '\n', remaining);
+                if (end)
+                        skip = end - p + 1;
+                else if (remaining >= sizeof(s->proc_kmsg_buffer) - 1) {
+                        end = p + sizeof(s->proc_kmsg_buffer) - 1;
+                        skip = remaining;
+                } else
+                        break;
+
+                *end = 0;
+
+                proc_kmsg_line(s, p);
+
+                remaining -= skip;
+                p += skip;
+        }
+
+        if (p > s->proc_kmsg_buffer) {
+                memmove(s->proc_kmsg_buffer, p, remaining);
+                s->proc_kmsg_length = remaining;
+        }
+}
+
+static int system_journal_open(Server *s) {
+        int r;
+        char *fn;
+        sd_id128_t machine;
+        char ids[33];
+
+        r = sd_id128_get_machine(&machine);
+        if (r < 0)
+                return r;
+
+        sd_id128_to_string(machine, ids);
+
+        if (!s->system_journal) {
+
+                /* First try to create the machine path, but not the prefix */
+                fn = strappend("/var/log/journal/", ids);
+                if (!fn)
+                        return -ENOMEM;
+                (void) mkdir(fn, 0755);
+                free(fn);
+
+                /* The create the system journal file */
+                fn = join("/var/log/journal/", ids, "/system.journal", NULL);
+                if (!fn)
+                        return -ENOMEM;
+
+                r = journal_file_open_reliably(fn, O_RDWR|O_CREAT, 0640, NULL, &s->system_journal);
+                free(fn);
+
+                if (r >= 0) {
+                        journal_default_metrics(&s->system_metrics, s->system_journal->fd);
+
+                        s->system_journal->metrics = s->system_metrics;
+                        s->system_journal->compress = s->compress;
+
+                        server_fix_perms(s, s->system_journal, 0);
+                } else if (r < 0) {
+
+                        if (r != -ENOENT && r != -EROFS)
+                                log_warning("Failed to open system journal: %s", strerror(-r));
+
+                        r = 0;
+                }
+        }
+
+        if (!s->runtime_journal) {
+
+                fn = join("/run/log/journal/", ids, "/system.journal", NULL);
+                if (!fn)
+                        return -ENOMEM;
+
+                if (s->system_journal) {
+
+                        /* Try to open the runtime journal, but only
+                         * if it already exists, so that we can flush
+                         * it into the system journal */
+
+                        r = journal_file_open(fn, O_RDWR, 0640, NULL, &s->runtime_journal);
+                        free(fn);
+
+                        if (r < 0) {
+                                if (r != -ENOENT)
+                                        log_warning("Failed to open runtime journal: %s", strerror(-r));
+
+                                r = 0;
+                        }
+
+                } else {
+
+                        /* OK, we really need the runtime journal, so create
+                         * it if necessary. */
+
+                        (void) mkdir_parents(fn, 0755);
+                        r = journal_file_open_reliably(fn, O_RDWR|O_CREAT, 0640, NULL, &s->runtime_journal);
+                        free(fn);
+
+                        if (r < 0) {
+                                log_error("Failed to open runtime journal: %s", strerror(-r));
+                                return r;
+                        }
+                }
+
+                if (s->runtime_journal) {
+                        journal_default_metrics(&s->runtime_metrics, s->runtime_journal->fd);
+
+                        s->runtime_journal->metrics = s->runtime_metrics;
+                        s->runtime_journal->compress = s->compress;
+
+                        server_fix_perms(s, s->runtime_journal, 0);
+                }
+        }
+
+        return r;
+}
+
+static int server_flush_to_var(Server *s) {
+        char path[] = "/run/log/journal/xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx";
+        Object *o = NULL;
+        int r;
+        sd_id128_t machine;
+        sd_journal *j;
+        usec_t ts;
+
+        assert(s);
+
+        if (!s->runtime_journal)
+                return 0;
+
+        ts = now(CLOCK_MONOTONIC);
+        if (s->var_available_timestamp + RECHECK_VAR_AVAILABLE_USEC > ts)
+                return 0;
+
+        s->var_available_timestamp = ts;
+
+        system_journal_open(s);
+
+        if (!s->system_journal)
+                return 0;
+
+        log_info("Flushing to /var...");
+
+        r = sd_id128_get_machine(&machine);
+        if (r < 0) {
+                log_error("Failed to get machine id: %s", strerror(-r));
+                return r;
+        }
+
+        r = sd_journal_open(&j, SD_JOURNAL_RUNTIME_ONLY);
+        if (r < 0) {
+                log_error("Failed to read runtime journal: %s", strerror(-r));
+                return r;
+        }
+
+        SD_JOURNAL_FOREACH(j) {
+                JournalFile *f;
+
+                f = j->current_file;
+                assert(f && f->current_offset > 0);
+
+                r = journal_file_move_to_object(f, OBJECT_ENTRY, f->current_offset, &o);
+                if (r < 0) {
+                        log_error("Can't read entry: %s", strerror(-r));
+                        goto finish;
+                }
+
+                r = journal_file_copy_entry(f, s->system_journal, o, f->current_offset, NULL, NULL, NULL);
+                if (r == -E2BIG) {
+                        log_info("Allocation limit reached.");
+
+                        journal_file_post_change(s->system_journal);
+                        server_rotate(s);
+                        server_vacuum(s);
+
+                        r = journal_file_copy_entry(f, s->system_journal, o, f->current_offset, NULL, NULL, NULL);
+                }
+
+                if (r < 0) {
+                        log_error("Can't write entry: %s", strerror(-r));
+                        goto finish;
+                }
+        }
+
+finish:
+        journal_file_post_change(s->system_journal);
+
+        journal_file_close(s->runtime_journal);
+        s->runtime_journal = NULL;
+
+        if (r >= 0) {
+                sd_id128_to_string(machine, path + 17);
+                rm_rf(path, false, true, false);
+        }
+
+        return r;
+}
+
+static int server_read_proc_kmsg(Server *s) {
+        ssize_t l;
+        assert(s);
+        assert(s->proc_kmsg_fd >= 0);
+
+        l = read(s->proc_kmsg_fd, s->proc_kmsg_buffer + s->proc_kmsg_length, sizeof(s->proc_kmsg_buffer) - 1 - s->proc_kmsg_length);
+        if (l < 0) {
+
+                if (errno == EAGAIN || errno == EINTR)
+                        return 0;
+
+                log_error("Failed to read from kernel: %m");
+                return -errno;
+        }
+
+        s->proc_kmsg_length += l;
+
+        proc_kmsg_scan(s);
+        return 1;
+}
+
+static int server_flush_proc_kmsg(Server *s) {
+        int r;
+
+        assert(s);
+
+        if (s->proc_kmsg_fd < 0)
+                return 0;
+
+        log_info("Flushing /proc/kmsg...");
+
+        for (;;) {
+                r = server_read_proc_kmsg(s);
+                if (r < 0)
+                        return r;
+
+                if (r == 0)
+                        break;
+        }
+
+        return 0;
+}
+
+static int process_event(Server *s, struct epoll_event *ev) {
+        assert(s);
+
+        if (ev->data.fd == s->signal_fd) {
+                struct signalfd_siginfo sfsi;
+                ssize_t n;
+
+                if (ev->events != EPOLLIN) {
+                        log_info("Got invalid event from epoll.");
+                        return -EIO;
+                }
+
+                n = read(s->signal_fd, &sfsi, sizeof(sfsi));
+                if (n != sizeof(sfsi)) {
+
+                        if (n >= 0)
+                                return -EIO;
+
+                        if (errno == EINTR || errno == EAGAIN)
+                                return 1;
+
+                        return -errno;
+                }
+
+                if (sfsi.ssi_signo == SIGUSR1) {
+                        server_flush_to_var(s);
+                        return 0;
+                }
+
+                log_debug("Received SIG%s", signal_to_string(sfsi.ssi_signo));
+                return 0;
+
+        } else if (ev->data.fd == s->proc_kmsg_fd) {
+                int r;
+
+                if (ev->events != EPOLLIN) {
+                        log_info("Got invalid event from epoll.");
+                        return -EIO;
+                }
+
+                r = server_read_proc_kmsg(s);
+                if (r < 0)
+                        return r;
+
+                return 1;
+
+        } else if (ev->data.fd == s->native_fd ||
+                   ev->data.fd == s->syslog_fd) {
+
+                if (ev->events != EPOLLIN) {
+                        log_info("Got invalid event from epoll.");
+                        return -EIO;
+                }
+
+                for (;;) {
+                        struct msghdr msghdr;
+                        struct iovec iovec;
+                        struct ucred *ucred = NULL;
+                        struct timeval *tv = NULL;
+                        struct cmsghdr *cmsg;
+                        char *label = NULL;
+                        size_t label_len = 0;
+                        union {
+                                struct cmsghdr cmsghdr;
+
+                                /* We use NAME_MAX space for the
+                                 * SELinux label here. The kernel
+                                 * currently enforces no limit, but
+                                 * according to suggestions from the
+                                 * SELinux people this will change and
+                                 * it will probably be identical to
+                                 * NAME_MAX. For now we use that, but
+                                 * this should be updated one day when
+                                 * the final limit is known.*/
+                                uint8_t buf[CMSG_SPACE(sizeof(struct ucred)) +
+                                            CMSG_SPACE(sizeof(struct timeval)) +
+                                            CMSG_SPACE(sizeof(int)) + /* fd */
+                                            CMSG_SPACE(NAME_MAX)]; /* selinux label */
+                        } control;
+                        ssize_t n;
+                        int v;
+                        int *fds = NULL;
+                        unsigned n_fds = 0;
+
+                        if (ioctl(ev->data.fd, SIOCINQ, &v) < 0) {
+                                log_error("SIOCINQ failed: %m");
+                                return -errno;
+                        }
+
+                        if (s->buffer_size < (size_t) v) {
+                                void *b;
+                                size_t l;
+
+                                l = MAX(LINE_MAX + (size_t) v, s->buffer_size * 2);
+                                b = realloc(s->buffer, l+1);
+
+                                if (!b) {
+                                        log_error("Couldn't increase buffer.");
+                                        return -ENOMEM;
+                                }
+
+                                s->buffer_size = l;
+                                s->buffer = b;
+                        }
+
+                        zero(iovec);
+                        iovec.iov_base = s->buffer;
+                        iovec.iov_len = s->buffer_size;
+
+                        zero(control);
+                        zero(msghdr);
+                        msghdr.msg_iov = &iovec;
+                        msghdr.msg_iovlen = 1;
+                        msghdr.msg_control = &control;
+                        msghdr.msg_controllen = sizeof(control);
+
+                        n = recvmsg(ev->data.fd, &msghdr, MSG_DONTWAIT|MSG_CMSG_CLOEXEC);
+                        if (n < 0) {
+
+                                if (errno == EINTR || errno == EAGAIN)
+                                        return 1;
+
+                                log_error("recvmsg() failed: %m");
+                                return -errno;
+                        }
+
+                        for (cmsg = CMSG_FIRSTHDR(&msghdr); cmsg; cmsg = CMSG_NXTHDR(&msghdr, cmsg)) {
+
+                                if (cmsg->cmsg_level == SOL_SOCKET &&
+                                    cmsg->cmsg_type == SCM_CREDENTIALS &&
+                                    cmsg->cmsg_len == CMSG_LEN(sizeof(struct ucred)))
+                                        ucred = (struct ucred*) CMSG_DATA(cmsg);
+                                else if (cmsg->cmsg_level == SOL_SOCKET &&
+                                         cmsg->cmsg_type == SCM_SECURITY) {
+                                        label = (char*) CMSG_DATA(cmsg);
+                                        label_len = cmsg->cmsg_len - CMSG_LEN(0);
+                                } else if (cmsg->cmsg_level == SOL_SOCKET &&
+                                         cmsg->cmsg_type == SO_TIMESTAMP &&
+                                         cmsg->cmsg_len == CMSG_LEN(sizeof(struct timeval)))
+                                        tv = (struct timeval*) CMSG_DATA(cmsg);
+                                else if (cmsg->cmsg_level == SOL_SOCKET &&
+                                         cmsg->cmsg_type == SCM_RIGHTS) {
+                                        fds = (int*) CMSG_DATA(cmsg);
+                                        n_fds = (cmsg->cmsg_len - CMSG_LEN(0)) / sizeof(int);
+                                }
+                        }
+
+                        if (ev->data.fd == s->syslog_fd) {
+                                char *e;
+
+                                if (n > 0 && n_fds == 0) {
+                                        e = memchr(s->buffer, '\n', n);
+                                        if (e)
+                                                *e = 0;
+                                        else
+                                                s->buffer[n] = 0;
+
+                                        process_syslog_message(s, strstrip(s->buffer), ucred, tv, label, label_len);
+                                } else if (n_fds > 0)
+                                        log_warning("Got file descriptors via syslog socket. Ignoring.");
+
+                        } else {
+                                if (n > 0 && n_fds == 0)
+                                        process_native_message(s, s->buffer, n, ucred, tv, label, label_len);
+                                else if (n == 0 && n_fds == 1)
+                                        process_native_file(s, fds[0], ucred, tv, label, label_len);
+                                else if (n_fds > 0)
+                                        log_warning("Got too many file descriptors via native socket. Ignoring.");
+                        }
+
+                        close_many(fds, n_fds);
+                }
+
+                return 1;
+
+        } else if (ev->data.fd == s->stdout_fd) {
+
+                if (ev->events != EPOLLIN) {
+                        log_info("Got invalid event from epoll.");
+                        return -EIO;
+                }
+
+                stdout_stream_new(s);
+                return 1;
+
+        } else {
+                StdoutStream *stream;
+
+                if ((ev->events|EPOLLIN|EPOLLHUP) != (EPOLLIN|EPOLLHUP)) {
+                        log_info("Got invalid event from epoll.");
+                        return -EIO;
+                }
+
+                /* If it is none of the well-known fds, it must be an
+                 * stdout stream fd. Note that this is a bit ugly here
+                 * (since we rely that none of the well-known fds
+                 * could be interpreted as pointer), but nonetheless
+                 * safe, since the well-known fds would never get an
+                 * fd > 4096, i.e. beyond the first memory page */
+
+                stream = ev->data.ptr;
+
+                if (stdout_stream_process(stream) <= 0)
+                        stdout_stream_free(stream);
+
+                return 1;
+        }
+
+        log_error("Unknown event.");
+        return 0;
+}
+
+static int open_syslog_socket(Server *s) {
+        union sockaddr_union sa;
+        int one, r;
+        struct epoll_event ev;
+
+        assert(s);
+
+        if (s->syslog_fd < 0) {
+
+                s->syslog_fd = socket(AF_UNIX, SOCK_DGRAM|SOCK_CLOEXEC|SOCK_NONBLOCK, 0);
+                if (s->syslog_fd < 0) {
+                        log_error("socket() failed: %m");
+                        return -errno;
+                }
+
+                zero(sa);
+                sa.un.sun_family = AF_UNIX;
+                strncpy(sa.un.sun_path, "/dev/log", sizeof(sa.un.sun_path));
+
+                unlink(sa.un.sun_path);
+
+                r = bind(s->syslog_fd, &sa.sa, offsetof(union sockaddr_union, un.sun_path) + strlen(sa.un.sun_path));
+                if (r < 0) {
+                        log_error("bind() failed: %m");
+                        return -errno;
+                }
+
+                chmod(sa.un.sun_path, 0666);
+        } else
+                fd_nonblock(s->syslog_fd, 1);
+
+        one = 1;
+        r = setsockopt(s->syslog_fd, SOL_SOCKET, SO_PASSCRED, &one, sizeof(one));
+        if (r < 0) {
+                log_error("SO_PASSCRED failed: %m");
+                return -errno;
+        }
+
+#ifdef HAVE_SELINUX
+        one = 1;
+        r = setsockopt(s->syslog_fd, SOL_SOCKET, SO_PASSSEC, &one, sizeof(one));
+        if (r < 0)
+                log_warning("SO_PASSSEC failed: %m");
+#endif
+
+        one = 1;
+        r = setsockopt(s->syslog_fd, SOL_SOCKET, SO_TIMESTAMP, &one, sizeof(one));
+        if (r < 0) {
+                log_error("SO_TIMESTAMP failed: %m");
+                return -errno;
+        }
+
+        zero(ev);
+        ev.events = EPOLLIN;
+        ev.data.fd = s->syslog_fd;
+        if (epoll_ctl(s->epoll_fd, EPOLL_CTL_ADD, s->syslog_fd, &ev) < 0) {
+                log_error("Failed to add syslog server fd to epoll object: %m");
+                return -errno;
+        }
+
+        return 0;
+}
+
+static int open_native_socket(Server*s) {
+        union sockaddr_union sa;
+        int one, r;
+        struct epoll_event ev;
+
+        assert(s);
+
+        if (s->native_fd < 0) {
+
+                s->native_fd = socket(AF_UNIX, SOCK_DGRAM|SOCK_CLOEXEC|SOCK_NONBLOCK, 0);
+                if (s->native_fd < 0) {
+                        log_error("socket() failed: %m");
+                        return -errno;
+                }
+
+                zero(sa);
+                sa.un.sun_family = AF_UNIX;
+                strncpy(sa.un.sun_path, "/run/systemd/journal/socket", sizeof(sa.un.sun_path));
+
+                unlink(sa.un.sun_path);
+
+                r = bind(s->native_fd, &sa.sa, offsetof(union sockaddr_union, un.sun_path) + strlen(sa.un.sun_path));
+                if (r < 0) {
+                        log_error("bind() failed: %m");
+                        return -errno;
+                }
+
+                chmod(sa.un.sun_path, 0666);
+        } else
+                fd_nonblock(s->native_fd, 1);
+
+        one = 1;
+        r = setsockopt(s->native_fd, SOL_SOCKET, SO_PASSCRED, &one, sizeof(one));
+        if (r < 0) {
+                log_error("SO_PASSCRED failed: %m");
+                return -errno;
+        }
+
+#ifdef HAVE_SELINUX
+        one = 1;
+        r = setsockopt(s->syslog_fd, SOL_SOCKET, SO_PASSSEC, &one, sizeof(one));
+        if (r < 0)
+                log_warning("SO_PASSSEC failed: %m");
+#endif
+
+        one = 1;
+        r = setsockopt(s->native_fd, SOL_SOCKET, SO_TIMESTAMP, &one, sizeof(one));
+        if (r < 0) {
+                log_error("SO_TIMESTAMP failed: %m");
+                return -errno;
+        }
+
+        zero(ev);
+        ev.events = EPOLLIN;
+        ev.data.fd = s->native_fd;
+        if (epoll_ctl(s->epoll_fd, EPOLL_CTL_ADD, s->native_fd, &ev) < 0) {
+                log_error("Failed to add native server fd to epoll object: %m");
+                return -errno;
+        }
+
+        return 0;
+}
+
+static int open_stdout_socket(Server *s) {
+        union sockaddr_union sa;
+        int r;
+        struct epoll_event ev;
+
+        assert(s);
+
+        if (s->stdout_fd < 0) {
+
+                s->stdout_fd = socket(AF_UNIX, SOCK_STREAM|SOCK_CLOEXEC|SOCK_NONBLOCK, 0);
+                if (s->stdout_fd < 0) {
+                        log_error("socket() failed: %m");
+                        return -errno;
+                }
+
+                zero(sa);
+                sa.un.sun_family = AF_UNIX;
+                strncpy(sa.un.sun_path, "/run/systemd/journal/stdout", sizeof(sa.un.sun_path));
+
+                unlink(sa.un.sun_path);
+
+                r = bind(s->stdout_fd, &sa.sa, offsetof(union sockaddr_union, un.sun_path) + strlen(sa.un.sun_path));
+                if (r < 0) {
+                        log_error("bind() failed: %m");
+                        return -errno;
+                }
+
+                chmod(sa.un.sun_path, 0666);
+
+                if (listen(s->stdout_fd, SOMAXCONN) < 0) {
+                        log_error("liste() failed: %m");
+                        return -errno;
+                }
+        } else
+                fd_nonblock(s->stdout_fd, 1);
+
+        zero(ev);
+        ev.events = EPOLLIN;
+        ev.data.fd = s->stdout_fd;
+        if (epoll_ctl(s->epoll_fd, EPOLL_CTL_ADD, s->stdout_fd, &ev) < 0) {
+                log_error("Failed to add stdout server fd to epoll object: %m");
+                return -errno;
+        }
+
+        return 0;
+}
+
+static int open_proc_kmsg(Server *s) {
+        struct epoll_event ev;
+
+        assert(s);
+
+        if (!s->import_proc_kmsg)
+                return 0;
+
+
+        s->proc_kmsg_fd = open("/proc/kmsg", O_CLOEXEC|O_NONBLOCK|O_NOCTTY);
+        if (s->proc_kmsg_fd < 0) {
+                log_warning("Failed to open /proc/kmsg, ignoring: %m");
+                return 0;
+        }
+
+        zero(ev);
+        ev.events = EPOLLIN;
+        ev.data.fd = s->proc_kmsg_fd;
+        if (epoll_ctl(s->epoll_fd, EPOLL_CTL_ADD, s->proc_kmsg_fd, &ev) < 0) {
+                log_error("Failed to add /proc/kmsg fd to epoll object: %m");
+                return -errno;
+        }
+
+        return 0;
+}
+
+static int open_signalfd(Server *s) {
+        sigset_t mask;
+        struct epoll_event ev;
+
+        assert(s);
+
+        assert_se(sigemptyset(&mask) == 0);
+        sigset_add_many(&mask, SIGINT, SIGTERM, SIGUSR1, -1);
+        assert_se(sigprocmask(SIG_SETMASK, &mask, NULL) == 0);
+
+        s->signal_fd = signalfd(-1, &mask, SFD_NONBLOCK|SFD_CLOEXEC);
+        if (s->signal_fd < 0) {
+                log_error("signalfd(): %m");
+                return -errno;
+        }
+
+        zero(ev);
+        ev.events = EPOLLIN;
+        ev.data.fd = s->signal_fd;
+
+        if (epoll_ctl(s->epoll_fd, EPOLL_CTL_ADD, s->signal_fd, &ev) < 0) {
+                log_error("epoll_ctl(): %m");
+                return -errno;
+        }
+
+        return 0;
+}
+
+static int server_parse_proc_cmdline(Server *s) {
+        char *line, *w, *state;
+        int r;
+        size_t l;
+
+        if (detect_container(NULL) > 0)
+                return 0;
+
+        r = read_one_line_file("/proc/cmdline", &line);
+        if (r < 0) {
+                log_warning("Failed to read /proc/cmdline, ignoring: %s", strerror(-r));
+                return 0;
+        }
+
+        FOREACH_WORD_QUOTED(w, l, line, state) {
+                char *word;
+
+                word = strndup(w, l);
+                if (!word) {
+                        r = -ENOMEM;
+                        goto finish;
+                }
+
+                if (startswith(word, "systemd_journald.forward_to_syslog=")) {
+                        r = parse_boolean(word + 35);
+                        if (r < 0)
+                                log_warning("Failed to parse forward to syslog switch %s. Ignoring.", word + 35);
+                        else
+                                s->forward_to_syslog = r;
+                } else if (startswith(word, "systemd_journald.forward_to_kmsg=")) {
+                        r = parse_boolean(word + 33);
+                        if (r < 0)
+                                log_warning("Failed to parse forward to kmsg switch %s. Ignoring.", word + 33);
+                        else
+                                s->forward_to_kmsg = r;
+                } else if (startswith(word, "systemd_journald.forward_to_console=")) {
+                        r = parse_boolean(word + 36);
+                        if (r < 0)
+                                log_warning("Failed to parse forward to console switch %s. Ignoring.", word + 36);
+                        else
+                                s->forward_to_console = r;
+                }
+
+                free(word);
+        }
+
+        r = 0;
+
+finish:
+        free(line);
+        return r;
+}
+
+static int server_parse_config_file(Server *s) {
+        FILE *f;
+        const char *fn;
+        int r;
+
+        assert(s);
+
+        fn = "/etc/systemd/journald.conf";
+        f = fopen(fn, "re");
+        if (!f) {
+                if (errno == ENOENT)
+                        return 0;
+
+                log_warning("Failed to open configuration file %s: %m", fn);
+                return -errno;
+        }
+
+        r = config_parse(fn, f, "Journal\0", config_item_perf_lookup, (void*) journald_gperf_lookup, false, s);
+        if (r < 0)
+                log_warning("Failed to parse configuration file: %s", strerror(-r));
+
+        fclose(f);
+
+        return r;
+}
+
+static int server_init(Server *s) {
+        int n, r, fd;
+
+        assert(s);
+
+        zero(*s);
+        s->syslog_fd = s->native_fd = s->stdout_fd = s->signal_fd = s->epoll_fd = s->proc_kmsg_fd = -1;
+        s->compress = true;
+
+        s->rate_limit_interval = DEFAULT_RATE_LIMIT_INTERVAL;
+        s->rate_limit_burst = DEFAULT_RATE_LIMIT_BURST;
+
+        s->forward_to_syslog = true;
+        s->import_proc_kmsg = true;
+
+        memset(&s->system_metrics, 0xFF, sizeof(s->system_metrics));
+        memset(&s->runtime_metrics, 0xFF, sizeof(s->runtime_metrics));
+
+        server_parse_config_file(s);
+        server_parse_proc_cmdline(s);
+
+        s->user_journals = hashmap_new(trivial_hash_func, trivial_compare_func);
+        if (!s->user_journals) {
+                log_error("Out of memory.");
+                return -ENOMEM;
+        }
+
+        s->epoll_fd = epoll_create1(EPOLL_CLOEXEC);
+        if (s->epoll_fd < 0) {
+                log_error("Failed to create epoll object: %m");
+                return -errno;
+        }
+
+        n = sd_listen_fds(true);
+        if (n < 0) {
+                log_error("Failed to read listening file descriptors from environment: %s", strerror(-n));
+                return n;
+        }
+
+        for (fd = SD_LISTEN_FDS_START; fd < SD_LISTEN_FDS_START + n; fd++) {
+
+                if (sd_is_socket_unix(fd, SOCK_DGRAM, -1, "/run/systemd/journal/socket", 0) > 0) {
+
+                        if (s->native_fd >= 0) {
+                                log_error("Too many native sockets passed.");
+                                return -EINVAL;
+                        }
+
+                        s->native_fd = fd;
+
+                } else if (sd_is_socket_unix(fd, SOCK_STREAM, 1, "/run/systemd/journal/stdout", 0) > 0) {
+
+                        if (s->stdout_fd >= 0) {
+                                log_error("Too many stdout sockets passed.");
+                                return -EINVAL;
+                        }
+
+                        s->stdout_fd = fd;
+
+                } else if (sd_is_socket_unix(fd, SOCK_DGRAM, -1, "/dev/log", 0) > 0) {
+
+                        if (s->syslog_fd >= 0) {
+                                log_error("Too many /dev/log sockets passed.");
+                                return -EINVAL;
+                        }
+
+                        s->syslog_fd = fd;
+
+                } else {
+                        log_error("Unknown socket passed.");
+                        return -EINVAL;
+                }
+        }
+
+        r = open_syslog_socket(s);
+        if (r < 0)
+                return r;
+
+        r = open_native_socket(s);
+        if (r < 0)
+                return r;
+
+        r = open_stdout_socket(s);
+        if (r < 0)
+                return r;
+
+        r = open_proc_kmsg(s);
+        if (r < 0)
+                return r;
+
+        r = open_signalfd(s);
+        if (r < 0)
+                return r;
+
+        s->rate_limit = journal_rate_limit_new(s->rate_limit_interval, s->rate_limit_burst);
+        if (!s->rate_limit)
+                return -ENOMEM;
+
+        r = system_journal_open(s);
+        if (r < 0)
+                return r;
+
+        return 0;
+}
+
+static void server_done(Server *s) {
+        JournalFile *f;
+        assert(s);
+
+        while (s->stdout_streams)
+                stdout_stream_free(s->stdout_streams);
+
+        if (s->system_journal)
+                journal_file_close(s->system_journal);
+
+        if (s->runtime_journal)
+                journal_file_close(s->runtime_journal);
+
+        while ((f = hashmap_steal_first(s->user_journals)))
+                journal_file_close(f);
+
+        hashmap_free(s->user_journals);
+
+        if (s->epoll_fd >= 0)
+                close_nointr_nofail(s->epoll_fd);
+
+        if (s->signal_fd >= 0)
+                close_nointr_nofail(s->signal_fd);
+
+        if (s->syslog_fd >= 0)
+                close_nointr_nofail(s->syslog_fd);
+
+        if (s->native_fd >= 0)
+                close_nointr_nofail(s->native_fd);
+
+        if (s->stdout_fd >= 0)
+                close_nointr_nofail(s->stdout_fd);
+
+        if (s->proc_kmsg_fd >= 0)
+                close_nointr_nofail(s->proc_kmsg_fd);
+
+        if (s->rate_limit)
+                journal_rate_limit_free(s->rate_limit);
+
+        free(s->buffer);
+}
+
+int main(int argc, char *argv[]) {
+        Server server;
+        int r;
+
+        /* if (getppid() != 1) { */
+        /*         log_error("This program should be invoked by init only."); */
+        /*         return EXIT_FAILURE; */
+        /* } */
+
+        if (argc > 1) {
+                log_error("This program does not take arguments.");
+                return EXIT_FAILURE;
+        }
+
+        log_set_target(LOG_TARGET_CONSOLE);
+        log_set_facility(LOG_SYSLOG);
+        log_parse_environment();
+        log_open();
+
+        umask(0022);
+
+        r = server_init(&server);
+        if (r < 0)
+                goto finish;
+
+        server_vacuum(&server);
+        server_flush_to_var(&server);
+        server_flush_proc_kmsg(&server);
+
+        log_debug("systemd-journald running as pid %lu", (unsigned long) getpid());
+        driver_message(&server, SD_MESSAGE_JOURNAL_START, "Journal started");
+
+        sd_notify(false,
+                  "READY=1\n"
+                  "STATUS=Processing requests...");
+
+        for (;;) {
+                struct epoll_event event;
+
+                r = epoll_wait(server.epoll_fd, &event, 1, -1);
+                if (r < 0) {
+
+                        if (errno == EINTR)
+                                continue;
+
+                        log_error("epoll_wait() failed: %m");
+                        r = -errno;
+                        goto finish;
+                } else if (r == 0)
+                        break;
+
+                r = process_event(&server, &event);
+                if (r < 0)
+                        goto finish;
+                else if (r == 0)
+                        break;
+        }
+
+        log_debug("systemd-journald stopped as pid %lu", (unsigned long) getpid());
+        driver_message(&server, SD_MESSAGE_JOURNAL_STOP, "Journal stopped");
+
+finish:
+        sd_notify(false,
+                  "STATUS=Shutting down...");
+
+        server_done(&server);
+
+        return r < 0 ? EXIT_FAILURE : EXIT_SUCCESS;
+}
diff --git a/src/journal/journald.conf b/src/journal/journald.conf
new file mode 100644 (file)
index 0000000..710b0aa
--- /dev/null
@@ -0,0 +1,25 @@
+#  This file is part of systemd.
+#
+#  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.
+#
+# See system-journald.conf(5) for details
+
+[Journal]
+#Compress=yes
+#RateLimitInterval=10s
+#RateLimitBurst=200
+#SystemMaxUse=
+#SystemKeepFree=
+#SystemMaxFileSize=
+#SystemMinFileSize=
+#RuntimeMaxUse=
+#RuntimeKeepFree=
+#RuntimeMaxFileSize=
+#RuntimeMinFileSize=
+#ForwardToSyslog=yes
+#ForwardToKMsg=no
+#ForwardToConsole=no
+#ImportKernel=yes
diff --git a/src/journal/journald.h b/src/journal/journald.h
new file mode 100644 (file)
index 0000000..6160991
--- /dev/null
@@ -0,0 +1,86 @@
+/*-*- Mode: C; c-basic-offset: 8; indent-tabs-mode: nil -*-*/
+
+#ifndef foojournaldhfoo
+#define foojournaldhfoo
+
+/***
+  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 <http://www.gnu.org/licenses/>.
+***/
+
+#include <inttypes.h>
+#include <sys/types.h>
+#include <stdbool.h>
+
+#include "journal-file.h"
+#include "hashmap.h"
+#include "util.h"
+#include "journal-rate-limit.h"
+#include "list.h"
+
+typedef struct StdoutStream StdoutStream;
+
+typedef struct Server {
+        int epoll_fd;
+        int signal_fd;
+        int syslog_fd;
+        int native_fd;
+        int stdout_fd;
+        int proc_kmsg_fd;
+
+        JournalFile *runtime_journal;
+        JournalFile *system_journal;
+        Hashmap *user_journals;
+
+        uint64_t seqnum;
+
+        char *buffer;
+        size_t buffer_size;
+
+        JournalRateLimit *rate_limit;
+        usec_t rate_limit_interval;
+        unsigned rate_limit_burst;
+
+        JournalMetrics runtime_metrics;
+        JournalMetrics system_metrics;
+
+        bool compress;
+
+        bool forward_to_kmsg;
+        bool forward_to_syslog;
+        bool forward_to_console;
+
+        bool import_proc_kmsg;
+        char proc_kmsg_buffer[LINE_MAX+1];
+        size_t proc_kmsg_length;
+
+        uint64_t cached_available_space;
+        usec_t cached_available_space_timestamp;
+
+        uint64_t var_available_timestamp;
+
+        gid_t file_gid;
+        bool file_gid_valid;
+
+        LIST_HEAD(StdoutStream, stdout_streams);
+        unsigned n_stdout_streams;
+} Server;
+
+/* gperf lookup function */
+const struct ConfigPerfItem* journald_gperf_lookup(const char *key, unsigned length);
+
+#endif
diff --git a/src/journal/libsystemd-journal.pc.in b/src/journal/libsystemd-journal.pc.in
new file mode 100644 (file)
index 0000000..13cc820
--- /dev/null
@@ -0,0 +1,19 @@
+#  This file is part of systemd.
+#
+#  systemd is free software; you can redistribute it and/or modify it
+#  under the terms of the GNU General Public License as published by
+#  the Free Software Foundation; either version 2 of the License, or
+#  (at your option) any later version.
+
+prefix=@prefix@
+exec_prefix=@exec_prefix@
+libdir=@libdir@
+includedir=@includedir@
+
+Name: systemd
+Description: systemd Journal Utility Library
+URL: @PACKAGE_URL@
+Version: @PACKAGE_VERSION@
+Requires: libsystemd-id128 = @PACKAGE_VERSION@
+Libs: -L${libdir} -lsystemd-journal
+Cflags: -I${includedir}
diff --git a/src/journal/libsystemd-journal.sym b/src/journal/libsystemd-journal.sym
new file mode 100644 (file)
index 0000000..cd434ae
--- /dev/null
@@ -0,0 +1,53 @@
+/***
+  This file is part of systemd.
+
+  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.
+***/
+
+/* Original symbols from systemd v38 */
+
+LIBSYSTEMD_JOURNAL_38 {
+global:
+        sd_journal_print;
+        sd_journal_printv;
+        sd_journal_send;
+        sd_journal_sendv;
+        sd_journal_stream_fd;
+        sd_journal_open;
+        sd_journal_close;
+        sd_journal_previous;
+        sd_journal_next;
+        sd_journal_previous_skip;
+        sd_journal_next_skip;
+        sd_journal_get_realtime_usec;
+        sd_journal_get_monotonic_usec;
+        sd_journal_get_data;
+        sd_journal_enumerate_data;
+        sd_journal_restart_data;
+        sd_journal_add_match;
+        sd_journal_flush_matches;
+        sd_journal_seek_head;
+        sd_journal_seek_tail;
+        sd_journal_seek_monotonic_usec;
+        sd_journal_seek_realtime_usec;
+        sd_journal_seek_cursor;
+        sd_journal_get_cursor;
+        sd_journal_query_unique;
+        sd_journal_enumerate_unique;
+        sd_journal_restart_unique;
+        sd_journal_get_fd;
+        sd_journal_process;
+local:
+        *;
+};
+
+LIBSYSTEMD_JOURNAL_45 {
+global:
+        sd_journal_print_with_location;
+        sd_journal_printv_with_location;
+        sd_journal_send_with_location;
+        sd_journal_sendv_with_location;
+} LIBSYSTEMD_JOURNAL_38;
diff --git a/src/journal/lookup3.c b/src/journal/lookup3.c
new file mode 100644 (file)
index 0000000..b90093a
--- /dev/null
@@ -0,0 +1,1003 @@
+/* Slightly modified by Lennart Poettering, to avoid name clashes, and
+ * unexport a few functions. */
+
+#include "lookup3.h"
+
+/*
+-------------------------------------------------------------------------------
+lookup3.c, by Bob Jenkins, May 2006, Public Domain.
+
+These are functions for producing 32-bit hashes for hash table lookup.
+hashword(), hashlittle(), hashlittle2(), hashbig(), mix(), and final()
+are externally useful functions.  Routines to test the hash are included
+if SELF_TEST is defined.  You can use this free for any purpose.  It's in
+the public domain.  It has no warranty.
+
+You probably want to use hashlittle().  hashlittle() and hashbig()
+hash byte arrays.  hashlittle() is is faster than hashbig() on
+little-endian machines.  Intel and AMD are little-endian machines.
+On second thought, you probably want hashlittle2(), which is identical to
+hashlittle() except it returns two 32-bit hashes for the price of one.
+You could implement hashbig2() if you wanted but I haven't bothered here.
+
+If you want to find a hash of, say, exactly 7 integers, do
+  a = i1;  b = i2;  c = i3;
+  mix(a,b,c);
+  a += i4; b += i5; c += i6;
+  mix(a,b,c);
+  a += i7;
+  final(a,b,c);
+then use c as the hash value.  If you have a variable length array of
+4-byte integers to hash, use hashword().  If you have a byte array (like
+a character string), use hashlittle().  If you have several byte arrays, or
+a mix of things, see the comments above hashlittle().
+
+Why is this so big?  I read 12 bytes at a time into 3 4-byte integers,
+then mix those integers.  This is fast (you can do a lot more thorough
+mixing with 12*3 instructions on 3 integers than you can with 3 instructions
+on 1 byte), but shoehorning those bytes into integers efficiently is messy.
+-------------------------------------------------------------------------------
+*/
+/* #define SELF_TEST 1 */
+
+#include <stdio.h>      /* defines printf for tests */
+#include <time.h>       /* defines time_t for timings in the test */
+#include <stdint.h>     /* defines uint32_t etc */
+#include <sys/param.h>  /* attempt to define endianness */
+#ifdef linux
+# include <endian.h>    /* attempt to define endianness */
+#endif
+
+/*
+ * My best guess at if you are big-endian or little-endian.  This may
+ * need adjustment.
+ */
+#if (defined(__BYTE_ORDER) && defined(__LITTLE_ENDIAN) && \
+     __BYTE_ORDER == __LITTLE_ENDIAN) || \
+    (defined(i386) || defined(__i386__) || defined(__i486__) || \
+     defined(__i586__) || defined(__i686__) || defined(vax) || defined(MIPSEL))
+# define HASH_LITTLE_ENDIAN 1
+# define HASH_BIG_ENDIAN 0
+#elif (defined(__BYTE_ORDER) && defined(__BIG_ENDIAN) && \
+       __BYTE_ORDER == __BIG_ENDIAN) || \
+      (defined(sparc) || defined(POWERPC) || defined(mc68000) || defined(sel))
+# define HASH_LITTLE_ENDIAN 0
+# define HASH_BIG_ENDIAN 1
+#else
+# define HASH_LITTLE_ENDIAN 0
+# define HASH_BIG_ENDIAN 0
+#endif
+
+#define hashsize(n) ((uint32_t)1<<(n))
+#define hashmask(n) (hashsize(n)-1)
+#define rot(x,k) (((x)<<(k)) | ((x)>>(32-(k))))
+
+/*
+-------------------------------------------------------------------------------
+mix -- mix 3 32-bit values reversibly.
+
+This is reversible, so any information in (a,b,c) before mix() is
+still in (a,b,c) after mix().
+
+If four pairs of (a,b,c) inputs are run through mix(), or through
+mix() in reverse, there are at least 32 bits of the output that
+are sometimes the same for one pair and different for another pair.
+This was tested for:
+* pairs that differed by one bit, by two bits, in any combination
+  of top bits of (a,b,c), or in any combination of bottom bits of
+  (a,b,c).
+* "differ" is defined as +, -, ^, or ~^.  For + and -, I transformed
+  the output delta to a Gray code (a^(a>>1)) so a string of 1's (as
+  is commonly produced by subtraction) look like a single 1-bit
+  difference.
+* the base values were pseudorandom, all zero but one bit set, or
+  all zero plus a counter that starts at zero.
+
+Some k values for my "a-=c; a^=rot(c,k); c+=b;" arrangement that
+satisfy this are
+    4  6  8 16 19  4
+    9 15  3 18 27 15
+   14  9  3  7 17  3
+Well, "9 15 3 18 27 15" didn't quite get 32 bits diffing
+for "differ" defined as + with a one-bit base and a two-bit delta.  I
+used http://burtleburtle.net/bob/hash/avalanche.html to choose
+the operations, constants, and arrangements of the variables.
+
+This does not achieve avalanche.  There are input bits of (a,b,c)
+that fail to affect some output bits of (a,b,c), especially of a.  The
+most thoroughly mixed value is c, but it doesn't really even achieve
+avalanche in c.
+
+This allows some parallelism.  Read-after-writes are good at doubling
+the number of bits affected, so the goal of mixing pulls in the opposite
+direction as the goal of parallelism.  I did what I could.  Rotates
+seem to cost as much as shifts on every machine I could lay my hands
+on, and rotates are much kinder to the top and bottom bits, so I used
+rotates.
+-------------------------------------------------------------------------------
+*/
+#define mix(a,b,c) \
+{ \
+  a -= c;  a ^= rot(c, 4);  c += b; \
+  b -= a;  b ^= rot(a, 6);  a += c; \
+  c -= b;  c ^= rot(b, 8);  b += a; \
+  a -= c;  a ^= rot(c,16);  c += b; \
+  b -= a;  b ^= rot(a,19);  a += c; \
+  c -= b;  c ^= rot(b, 4);  b += a; \
+}
+
+/*
+-------------------------------------------------------------------------------
+final -- final mixing of 3 32-bit values (a,b,c) into c
+
+Pairs of (a,b,c) values differing in only a few bits will usually
+produce values of c that look totally different.  This was tested for
+* pairs that differed by one bit, by two bits, in any combination
+  of top bits of (a,b,c), or in any combination of bottom bits of
+  (a,b,c).
+* "differ" is defined as +, -, ^, or ~^.  For + and -, I transformed
+  the output delta to a Gray code (a^(a>>1)) so a string of 1's (as
+  is commonly produced by subtraction) look like a single 1-bit
+  difference.
+* the base values were pseudorandom, all zero but one bit set, or
+  all zero plus a counter that starts at zero.
+
+These constants passed:
+ 14 11 25 16 4 14 24
+ 12 14 25 16 4 14 24
+and these came close:
+  4  8 15 26 3 22 24
+ 10  8 15 26 3 22 24
+ 11  8 15 26 3 22 24
+-------------------------------------------------------------------------------
+*/
+#define final(a,b,c) \
+{ \
+  c ^= b; c -= rot(b,14); \
+  a ^= c; a -= rot(c,11); \
+  b ^= a; b -= rot(a,25); \
+  c ^= b; c -= rot(b,16); \
+  a ^= c; a -= rot(c,4);  \
+  b ^= a; b -= rot(a,14); \
+  c ^= b; c -= rot(b,24); \
+}
+
+/*
+--------------------------------------------------------------------
+ This works on all machines.  To be useful, it requires
+ -- that the key be an array of uint32_t's, and
+ -- that the length be the number of uint32_t's in the key
+
+ The function hashword() is identical to hashlittle() on little-endian
+ machines, and identical to hashbig() on big-endian machines,
+ except that the length has to be measured in uint32_ts rather than in
+ bytes.  hashlittle() is more complicated than hashword() only because
+ hashlittle() has to dance around fitting the key bytes into registers.
+--------------------------------------------------------------------
+*/
+uint32_t jenkins_hashword(
+const uint32_t *k,                   /* the key, an array of uint32_t values */
+size_t          length,               /* the length of the key, in uint32_ts */
+uint32_t        initval)         /* the previous hash, or an arbitrary value */
+{
+  uint32_t a,b,c;
+
+  /* Set up the internal state */
+  a = b = c = 0xdeadbeef + (((uint32_t)length)<<2) + initval;
+
+  /*------------------------------------------------- handle most of the key */
+  while (length > 3)
+  {
+    a += k[0];
+    b += k[1];
+    c += k[2];
+    mix(a,b,c);
+    length -= 3;
+    k += 3;
+  }
+
+  /*------------------------------------------- handle the last 3 uint32_t's */
+  switch(length)                     /* all the case statements fall through */
+  {
+  case 3 : c+=k[2];
+  case 2 : b+=k[1];
+  case 1 : a+=k[0];
+    final(a,b,c);
+  case 0:     /* case 0: nothing left to add */
+    break;
+  }
+  /*------------------------------------------------------ report the result */
+  return c;
+}
+
+
+/*
+--------------------------------------------------------------------
+hashword2() -- same as hashword(), but take two seeds and return two
+32-bit values.  pc and pb must both be nonnull, and *pc and *pb must
+both be initialized with seeds.  If you pass in (*pb)==0, the output
+(*pc) will be the same as the return value from hashword().
+--------------------------------------------------------------------
+*/
+void jenkins_hashword2 (
+const uint32_t *k,                   /* the key, an array of uint32_t values */
+size_t          length,               /* the length of the key, in uint32_ts */
+uint32_t       *pc,                      /* IN: seed OUT: primary hash value */
+uint32_t       *pb)               /* IN: more seed OUT: secondary hash value */
+{
+  uint32_t a,b,c;
+
+  /* Set up the internal state */
+  a = b = c = 0xdeadbeef + ((uint32_t)(length<<2)) + *pc;
+  c += *pb;
+
+  /*------------------------------------------------- handle most of the key */
+  while (length > 3)
+  {
+    a += k[0];
+    b += k[1];
+    c += k[2];
+    mix(a,b,c);
+    length -= 3;
+    k += 3;
+  }
+
+  /*------------------------------------------- handle the last 3 uint32_t's */
+  switch(length)                     /* all the case statements fall through */
+  {
+  case 3 : c+=k[2];
+  case 2 : b+=k[1];
+  case 1 : a+=k[0];
+    final(a,b,c);
+  case 0:     /* case 0: nothing left to add */
+    break;
+  }
+  /*------------------------------------------------------ report the result */
+  *pc=c; *pb=b;
+}
+
+
+/*
+-------------------------------------------------------------------------------
+hashlittle() -- hash a variable-length key into a 32-bit value
+  k       : the key (the unaligned variable-length array of bytes)
+  length  : the length of the key, counting by bytes
+  initval : can be any 4-byte value
+Returns a 32-bit value.  Every bit of the key affects every bit of
+the return value.  Two keys differing by one or two bits will have
+totally different hash values.
+
+The best hash table sizes are powers of 2.  There is no need to do
+mod a prime (mod is sooo slow!).  If you need less than 32 bits,
+use a bitmask.  For example, if you need only 10 bits, do
+  h = (h & hashmask(10));
+In which case, the hash table should have hashsize(10) elements.
+
+If you are hashing n strings (uint8_t **)k, do it like this:
+  for (i=0, h=0; i<n; ++i) h = hashlittle( k[i], len[i], h);
+
+By Bob Jenkins, 2006.  bob_jenkins@burtleburtle.net.  You may use this
+code any way you wish, private, educational, or commercial.  It's free.
+
+Use for hash table lookup, or anything where one collision in 2^^32 is
+acceptable.  Do NOT use for cryptographic purposes.
+-------------------------------------------------------------------------------
+*/
+
+uint32_t jenkins_hashlittle( const void *key, size_t length, uint32_t initval)
+{
+  uint32_t a,b,c;                                          /* internal state */
+  union { const void *ptr; size_t i; } u;     /* needed for Mac Powerbook G4 */
+
+  /* Set up the internal state */
+  a = b = c = 0xdeadbeef + ((uint32_t)length) + initval;
+
+  u.ptr = key;
+  if (HASH_LITTLE_ENDIAN && ((u.i & 0x3) == 0)) {
+    const uint32_t *k = (const uint32_t *)key;         /* read 32-bit chunks */
+
+    /*------ all but last block: aligned reads and affect 32 bits of (a,b,c) */
+    while (length > 12)
+    {
+      a += k[0];
+      b += k[1];
+      c += k[2];
+      mix(a,b,c);
+      length -= 12;
+      k += 3;
+    }
+
+    /*----------------------------- handle the last (probably partial) block */
+    /*
+     * "k[2]&0xffffff" actually reads beyond the end of the string, but
+     * then masks off the part it's not allowed to read.  Because the
+     * string is aligned, the masked-off tail is in the same word as the
+     * rest of the string.  Every machine with memory protection I've seen
+     * does it on word boundaries, so is OK with this.  But VALGRIND will
+     * still catch it and complain.  The masking trick does make the hash
+     * noticably faster for short strings (like English words).
+     */
+#ifndef VALGRIND
+
+    switch(length)
+    {
+    case 12: c+=k[2]; b+=k[1]; a+=k[0]; break;
+    case 11: c+=k[2]&0xffffff; b+=k[1]; a+=k[0]; break;
+    case 10: c+=k[2]&0xffff; b+=k[1]; a+=k[0]; break;
+    case 9 : c+=k[2]&0xff; b+=k[1]; a+=k[0]; break;
+    case 8 : b+=k[1]; a+=k[0]; break;
+    case 7 : b+=k[1]&0xffffff; a+=k[0]; break;
+    case 6 : b+=k[1]&0xffff; a+=k[0]; break;
+    case 5 : b+=k[1]&0xff; a+=k[0]; break;
+    case 4 : a+=k[0]; break;
+    case 3 : a+=k[0]&0xffffff; break;
+    case 2 : a+=k[0]&0xffff; break;
+    case 1 : a+=k[0]&0xff; break;
+    case 0 : return c;              /* zero length strings require no mixing */
+    }
+
+#else /* make valgrind happy */
+
+    k8 = (const uint8_t *)k;
+    switch(length)
+    {
+    case 12: c+=k[2]; b+=k[1]; a+=k[0]; break;
+    case 11: c+=((uint32_t)k8[10])<<16;  /* fall through */
+    case 10: c+=((uint32_t)k8[9])<<8;    /* fall through */
+    case 9 : c+=k8[8];                   /* fall through */
+    case 8 : b+=k[1]; a+=k[0]; break;
+    case 7 : b+=((uint32_t)k8[6])<<16;   /* fall through */
+    case 6 : b+=((uint32_t)k8[5])<<8;    /* fall through */
+    case 5 : b+=k8[4];                   /* fall through */
+    case 4 : a+=k[0]; break;
+    case 3 : a+=((uint32_t)k8[2])<<16;   /* fall through */
+    case 2 : a+=((uint32_t)k8[1])<<8;    /* fall through */
+    case 1 : a+=k8[0]; break;
+    case 0 : return c;
+    }
+
+#endif /* !valgrind */
+
+  } else if (HASH_LITTLE_ENDIAN && ((u.i & 0x1) == 0)) {
+    const uint16_t *k = (const uint16_t *)key;         /* read 16-bit chunks */
+    const uint8_t  *k8;
+
+    /*--------------- all but last block: aligned reads and different mixing */
+    while (length > 12)
+    {
+      a += k[0] + (((uint32_t)k[1])<<16);
+      b += k[2] + (((uint32_t)k[3])<<16);
+      c += k[4] + (((uint32_t)k[5])<<16);
+      mix(a,b,c);
+      length -= 12;
+      k += 6;
+    }
+
+    /*----------------------------- handle the last (probably partial) block */
+    k8 = (const uint8_t *)k;
+    switch(length)
+    {
+    case 12: c+=k[4]+(((uint32_t)k[5])<<16);
+             b+=k[2]+(((uint32_t)k[3])<<16);
+             a+=k[0]+(((uint32_t)k[1])<<16);
+             break;
+    case 11: c+=((uint32_t)k8[10])<<16;     /* fall through */
+    case 10: c+=k[4];
+             b+=k[2]+(((uint32_t)k[3])<<16);
+             a+=k[0]+(((uint32_t)k[1])<<16);
+             break;
+    case 9 : c+=k8[8];                      /* fall through */
+    case 8 : b+=k[2]+(((uint32_t)k[3])<<16);
+             a+=k[0]+(((uint32_t)k[1])<<16);
+             break;
+    case 7 : b+=((uint32_t)k8[6])<<16;      /* fall through */
+    case 6 : b+=k[2];
+             a+=k[0]+(((uint32_t)k[1])<<16);
+             break;
+    case 5 : b+=k8[4];                      /* fall through */
+    case 4 : a+=k[0]+(((uint32_t)k[1])<<16);
+             break;
+    case 3 : a+=((uint32_t)k8[2])<<16;      /* fall through */
+    case 2 : a+=k[0];
+             break;
+    case 1 : a+=k8[0];
+             break;
+    case 0 : return c;                     /* zero length requires no mixing */
+    }
+
+  } else {                        /* need to read the key one byte at a time */
+    const uint8_t *k = (const uint8_t *)key;
+
+    /*--------------- all but the last block: affect some 32 bits of (a,b,c) */
+    while (length > 12)
+    {
+      a += k[0];
+      a += ((uint32_t)k[1])<<8;
+      a += ((uint32_t)k[2])<<16;
+      a += ((uint32_t)k[3])<<24;
+      b += k[4];
+      b += ((uint32_t)k[5])<<8;
+      b += ((uint32_t)k[6])<<16;
+      b += ((uint32_t)k[7])<<24;
+      c += k[8];
+      c += ((uint32_t)k[9])<<8;
+      c += ((uint32_t)k[10])<<16;
+      c += ((uint32_t)k[11])<<24;
+      mix(a,b,c);
+      length -= 12;
+      k += 12;
+    }
+
+    /*-------------------------------- last block: affect all 32 bits of (c) */
+    switch(length)                   /* all the case statements fall through */
+    {
+    case 12: c+=((uint32_t)k[11])<<24;
+    case 11: c+=((uint32_t)k[10])<<16;
+    case 10: c+=((uint32_t)k[9])<<8;
+    case 9 : c+=k[8];
+    case 8 : b+=((uint32_t)k[7])<<24;
+    case 7 : b+=((uint32_t)k[6])<<16;
+    case 6 : b+=((uint32_t)k[5])<<8;
+    case 5 : b+=k[4];
+    case 4 : a+=((uint32_t)k[3])<<24;
+    case 3 : a+=((uint32_t)k[2])<<16;
+    case 2 : a+=((uint32_t)k[1])<<8;
+    case 1 : a+=k[0];
+             break;
+    case 0 : return c;
+    }
+  }
+
+  final(a,b,c);
+  return c;
+}
+
+
+/*
+ * hashlittle2: return 2 32-bit hash values
+ *
+ * This is identical to hashlittle(), except it returns two 32-bit hash
+ * values instead of just one.  This is good enough for hash table
+ * lookup with 2^^64 buckets, or if you want a second hash if you're not
+ * happy with the first, or if you want a probably-unique 64-bit ID for
+ * the key.  *pc is better mixed than *pb, so use *pc first.  If you want
+ * a 64-bit value do something like "*pc + (((uint64_t)*pb)<<32)".
+ */
+void jenkins_hashlittle2(
+  const void *key,       /* the key to hash */
+  size_t      length,    /* length of the key */
+  uint32_t   *pc,        /* IN: primary initval, OUT: primary hash */
+  uint32_t   *pb)        /* IN: secondary initval, OUT: secondary hash */
+{
+  uint32_t a,b,c;                                          /* internal state */
+  union { const void *ptr; size_t i; } u;     /* needed for Mac Powerbook G4 */
+
+  /* Set up the internal state */
+  a = b = c = 0xdeadbeef + ((uint32_t)length) + *pc;
+  c += *pb;
+
+  u.ptr = key;
+  if (HASH_LITTLE_ENDIAN && ((u.i & 0x3) == 0)) {
+    const uint32_t *k = (const uint32_t *)key;         /* read 32-bit chunks */
+
+    /*------ all but last block: aligned reads and affect 32 bits of (a,b,c) */
+    while (length > 12)
+    {
+      a += k[0];
+      b += k[1];
+      c += k[2];
+      mix(a,b,c);
+      length -= 12;
+      k += 3;
+    }
+
+    /*----------------------------- handle the last (probably partial) block */
+    /*
+     * "k[2]&0xffffff" actually reads beyond the end of the string, but
+     * then masks off the part it's not allowed to read.  Because the
+     * string is aligned, the masked-off tail is in the same word as the
+     * rest of the string.  Every machine with memory protection I've seen
+     * does it on word boundaries, so is OK with this.  But VALGRIND will
+     * still catch it and complain.  The masking trick does make the hash
+     * noticably faster for short strings (like English words).
+     */
+#ifndef VALGRIND
+
+    switch(length)
+    {
+    case 12: c+=k[2]; b+=k[1]; a+=k[0]; break;
+    case 11: c+=k[2]&0xffffff; b+=k[1]; a+=k[0]; break;
+    case 10: c+=k[2]&0xffff; b+=k[1]; a+=k[0]; break;
+    case 9 : c+=k[2]&0xff; b+=k[1]; a+=k[0]; break;
+    case 8 : b+=k[1]; a+=k[0]; break;
+    case 7 : b+=k[1]&0xffffff; a+=k[0]; break;
+    case 6 : b+=k[1]&0xffff; a+=k[0]; break;
+    case 5 : b+=k[1]&0xff; a+=k[0]; break;
+    case 4 : a+=k[0]; break;
+    case 3 : a+=k[0]&0xffffff; break;
+    case 2 : a+=k[0]&0xffff; break;
+    case 1 : a+=k[0]&0xff; break;
+    case 0 : *pc=c; *pb=b; return;  /* zero length strings require no mixing */
+    }
+
+#else /* make valgrind happy */
+
+    k8 = (const uint8_t *)k;
+    switch(length)
+    {
+    case 12: c+=k[2]; b+=k[1]; a+=k[0]; break;
+    case 11: c+=((uint32_t)k8[10])<<16;  /* fall through */
+    case 10: c+=((uint32_t)k8[9])<<8;    /* fall through */
+    case 9 : c+=k8[8];                   /* fall through */
+    case 8 : b+=k[1]; a+=k[0]; break;
+    case 7 : b+=((uint32_t)k8[6])<<16;   /* fall through */
+    case 6 : b+=((uint32_t)k8[5])<<8;    /* fall through */
+    case 5 : b+=k8[4];                   /* fall through */
+    case 4 : a+=k[0]; break;
+    case 3 : a+=((uint32_t)k8[2])<<16;   /* fall through */
+    case 2 : a+=((uint32_t)k8[1])<<8;    /* fall through */
+    case 1 : a+=k8[0]; break;
+    case 0 : *pc=c; *pb=b; return;  /* zero length strings require no mixing */
+    }
+
+#endif /* !valgrind */
+
+  } else if (HASH_LITTLE_ENDIAN && ((u.i & 0x1) == 0)) {
+    const uint16_t *k = (const uint16_t *)key;         /* read 16-bit chunks */
+    const uint8_t  *k8;
+
+    /*--------------- all but last block: aligned reads and different mixing */
+    while (length > 12)
+    {
+      a += k[0] + (((uint32_t)k[1])<<16);
+      b += k[2] + (((uint32_t)k[3])<<16);
+      c += k[4] + (((uint32_t)k[5])<<16);
+      mix(a,b,c);
+      length -= 12;
+      k += 6;
+    }
+
+    /*----------------------------- handle the last (probably partial) block */
+    k8 = (const uint8_t *)k;
+    switch(length)
+    {
+    case 12: c+=k[4]+(((uint32_t)k[5])<<16);
+             b+=k[2]+(((uint32_t)k[3])<<16);
+             a+=k[0]+(((uint32_t)k[1])<<16);
+             break;
+    case 11: c+=((uint32_t)k8[10])<<16;     /* fall through */
+    case 10: c+=k[4];
+             b+=k[2]+(((uint32_t)k[3])<<16);
+             a+=k[0]+(((uint32_t)k[1])<<16);
+             break;
+    case 9 : c+=k8[8];                      /* fall through */
+    case 8 : b+=k[2]+(((uint32_t)k[3])<<16);
+             a+=k[0]+(((uint32_t)k[1])<<16);
+             break;
+    case 7 : b+=((uint32_t)k8[6])<<16;      /* fall through */
+    case 6 : b+=k[2];
+             a+=k[0]+(((uint32_t)k[1])<<16);
+             break;
+    case 5 : b+=k8[4];                      /* fall through */
+    case 4 : a+=k[0]+(((uint32_t)k[1])<<16);
+             break;
+    case 3 : a+=((uint32_t)k8[2])<<16;      /* fall through */
+    case 2 : a+=k[0];
+             break;
+    case 1 : a+=k8[0];
+             break;
+    case 0 : *pc=c; *pb=b; return;  /* zero length strings require no mixing */
+    }
+
+  } else {                        /* need to read the key one byte at a time */
+    const uint8_t *k = (const uint8_t *)key;
+
+    /*--------------- all but the last block: affect some 32 bits of (a,b,c) */
+    while (length > 12)
+    {
+      a += k[0];
+      a += ((uint32_t)k[1])<<8;
+      a += ((uint32_t)k[2])<<16;
+      a += ((uint32_t)k[3])<<24;
+      b += k[4];
+      b += ((uint32_t)k[5])<<8;
+      b += ((uint32_t)k[6])<<16;
+      b += ((uint32_t)k[7])<<24;
+      c += k[8];
+      c += ((uint32_t)k[9])<<8;
+      c += ((uint32_t)k[10])<<16;
+      c += ((uint32_t)k[11])<<24;
+      mix(a,b,c);
+      length -= 12;
+      k += 12;
+    }
+
+    /*-------------------------------- last block: affect all 32 bits of (c) */
+    switch(length)                   /* all the case statements fall through */
+    {
+    case 12: c+=((uint32_t)k[11])<<24;
+    case 11: c+=((uint32_t)k[10])<<16;
+    case 10: c+=((uint32_t)k[9])<<8;
+    case 9 : c+=k[8];
+    case 8 : b+=((uint32_t)k[7])<<24;
+    case 7 : b+=((uint32_t)k[6])<<16;
+    case 6 : b+=((uint32_t)k[5])<<8;
+    case 5 : b+=k[4];
+    case 4 : a+=((uint32_t)k[3])<<24;
+    case 3 : a+=((uint32_t)k[2])<<16;
+    case 2 : a+=((uint32_t)k[1])<<8;
+    case 1 : a+=k[0];
+             break;
+    case 0 : *pc=c; *pb=b; return;  /* zero length strings require no mixing */
+    }
+  }
+
+  final(a,b,c);
+  *pc=c; *pb=b;
+}
+
+
+
+/*
+ * hashbig():
+ * This is the same as hashword() on big-endian machines.  It is different
+ * from hashlittle() on all machines.  hashbig() takes advantage of
+ * big-endian byte ordering.
+ */
+uint32_t jenkins_hashbig( const void *key, size_t length, uint32_t initval)
+{
+  uint32_t a,b,c;
+  union { const void *ptr; size_t i; } u; /* to cast key to (size_t) happily */
+
+  /* Set up the internal state */
+  a = b = c = 0xdeadbeef + ((uint32_t)length) + initval;
+
+  u.ptr = key;
+  if (HASH_BIG_ENDIAN && ((u.i & 0x3) == 0)) {
+    const uint32_t *k = (const uint32_t *)key;         /* read 32-bit chunks */
+
+    /*------ all but last block: aligned reads and affect 32 bits of (a,b,c) */
+    while (length > 12)
+    {
+      a += k[0];
+      b += k[1];
+      c += k[2];
+      mix(a,b,c);
+      length -= 12;
+      k += 3;
+    }
+
+    /*----------------------------- handle the last (probably partial) block */
+    /*
+     * "k[2]<<8" actually reads beyond the end of the string, but
+     * then shifts out the part it's not allowed to read.  Because the
+     * string is aligned, the illegal read is in the same word as the
+     * rest of the string.  Every machine with memory protection I've seen
+     * does it on word boundaries, so is OK with this.  But VALGRIND will
+     * still catch it and complain.  The masking trick does make the hash
+     * noticably faster for short strings (like English words).
+     */
+#ifndef VALGRIND
+
+    switch(length)
+    {
+    case 12: c+=k[2]; b+=k[1]; a+=k[0]; break;
+    case 11: c+=k[2]&0xffffff00; b+=k[1]; a+=k[0]; break;
+    case 10: c+=k[2]&0xffff0000; b+=k[1]; a+=k[0]; break;
+    case 9 : c+=k[2]&0xff000000; b+=k[1]; a+=k[0]; break;
+    case 8 : b+=k[1]; a+=k[0]; break;
+    case 7 : b+=k[1]&0xffffff00; a+=k[0]; break;
+    case 6 : b+=k[1]&0xffff0000; a+=k[0]; break;
+    case 5 : b+=k[1]&0xff000000; a+=k[0]; break;
+    case 4 : a+=k[0]; break;
+    case 3 : a+=k[0]&0xffffff00; break;
+    case 2 : a+=k[0]&0xffff0000; break;
+    case 1 : a+=k[0]&0xff000000; break;
+    case 0 : return c;              /* zero length strings require no mixing */
+    }
+
+#else  /* make valgrind happy */
+
+    k8 = (const uint8_t *)k;
+    switch(length)                   /* all the case statements fall through */
+    {
+    case 12: c+=k[2]; b+=k[1]; a+=k[0]; break;
+    case 11: c+=((uint32_t)k8[10])<<8;  /* fall through */
+    case 10: c+=((uint32_t)k8[9])<<16;  /* fall through */
+    case 9 : c+=((uint32_t)k8[8])<<24;  /* fall through */
+    case 8 : b+=k[1]; a+=k[0]; break;
+    case 7 : b+=((uint32_t)k8[6])<<8;   /* fall through */
+    case 6 : b+=((uint32_t)k8[5])<<16;  /* fall through */
+    case 5 : b+=((uint32_t)k8[4])<<24;  /* fall through */
+    case 4 : a+=k[0]; break;
+    case 3 : a+=((uint32_t)k8[2])<<8;   /* fall through */
+    case 2 : a+=((uint32_t)k8[1])<<16;  /* fall through */
+    case 1 : a+=((uint32_t)k8[0])<<24; break;
+    case 0 : return c;
+    }
+
+#endif /* !VALGRIND */
+
+  } else {                        /* need to read the key one byte at a time */
+    const uint8_t *k = (const uint8_t *)key;
+
+    /*--------------- all but the last block: affect some 32 bits of (a,b,c) */
+    while (length > 12)
+    {
+      a += ((uint32_t)k[0])<<24;
+      a += ((uint32_t)k[1])<<16;
+      a += ((uint32_t)k[2])<<8;
+      a += ((uint32_t)k[3]);
+      b += ((uint32_t)k[4])<<24;
+      b += ((uint32_t)k[5])<<16;
+      b += ((uint32_t)k[6])<<8;
+      b += ((uint32_t)k[7]);
+      c += ((uint32_t)k[8])<<24;
+      c += ((uint32_t)k[9])<<16;
+      c += ((uint32_t)k[10])<<8;
+      c += ((uint32_t)k[11]);
+      mix(a,b,c);
+      length -= 12;
+      k += 12;
+    }
+
+    /*-------------------------------- last block: affect all 32 bits of (c) */
+    switch(length)                   /* all the case statements fall through */
+    {
+    case 12: c+=k[11];
+    case 11: c+=((uint32_t)k[10])<<8;
+    case 10: c+=((uint32_t)k[9])<<16;
+    case 9 : c+=((uint32_t)k[8])<<24;
+    case 8 : b+=k[7];
+    case 7 : b+=((uint32_t)k[6])<<8;
+    case 6 : b+=((uint32_t)k[5])<<16;
+    case 5 : b+=((uint32_t)k[4])<<24;
+    case 4 : a+=k[3];
+    case 3 : a+=((uint32_t)k[2])<<8;
+    case 2 : a+=((uint32_t)k[1])<<16;
+    case 1 : a+=((uint32_t)k[0])<<24;
+             break;
+    case 0 : return c;
+    }
+  }
+
+  final(a,b,c);
+  return c;
+}
+
+
+#ifdef SELF_TEST
+
+/* used for timings */
+void driver1()
+{
+  uint8_t buf[256];
+  uint32_t i;
+  uint32_t h=0;
+  time_t a,z;
+
+  time(&a);
+  for (i=0; i<256; ++i) buf[i] = 'x';
+  for (i=0; i<1; ++i)
+  {
+    h = hashlittle(&buf[0],1,h);
+  }
+  time(&z);
+  if (z-a > 0) printf("time %d %.8x\n", z-a, h);
+}
+
+/* check that every input bit changes every output bit half the time */
+#define HASHSTATE 1
+#define HASHLEN   1
+#define MAXPAIR 60
+#define MAXLEN  70
+void driver2()
+{
+  uint8_t qa[MAXLEN+1], qb[MAXLEN+2], *a = &qa[0], *b = &qb[1];
+  uint32_t c[HASHSTATE], d[HASHSTATE], i=0, j=0, k, l, m=0, z;
+  uint32_t e[HASHSTATE],f[HASHSTATE],g[HASHSTATE],h[HASHSTATE];
+  uint32_t x[HASHSTATE],y[HASHSTATE];
+  uint32_t hlen;
+
+  printf("No more than %d trials should ever be needed \n",MAXPAIR/2);
+  for (hlen=0; hlen < MAXLEN; ++hlen)
+  {
+    z=0;
+    for (i=0; i<hlen; ++i)  /*----------------------- for each input byte, */
+    {
+      for (j=0; j<8; ++j)   /*------------------------ for each input bit, */
+      {
+        for (m=1; m<8; ++m) /*------------ for serveral possible initvals, */
+        {
+          for (l=0; l<HASHSTATE; ++l)
+            e[l]=f[l]=g[l]=h[l]=x[l]=y[l]=~((uint32_t)0);
+
+          /*---- check that every output bit is affected by that input bit */
+          for (k=0; k<MAXPAIR; k+=2)
+          {
+            uint32_t finished=1;
+            /* keys have one bit different */
+            for (l=0; l<hlen+1; ++l) {a[l] = b[l] = (uint8_t)0;}
+            /* have a and b be two keys differing in only one bit */
+            a[i] ^= (k<<j);
+            a[i] ^= (k>>(8-j));
+             c[0] = hashlittle(a, hlen, m);
+            b[i] ^= ((k+1)<<j);
+            b[i] ^= ((k+1)>>(8-j));
+             d[0] = hashlittle(b, hlen, m);
+            /* check every bit is 1, 0, set, and not set at least once */
+            for (l=0; l<HASHSTATE; ++l)
+            {
+              e[l] &= (c[l]^d[l]);
+              f[l] &= ~(c[l]^d[l]);
+              g[l] &= c[l];
+              h[l] &= ~c[l];
+              x[l] &= d[l];
+              y[l] &= ~d[l];
+              if (e[l]|f[l]|g[l]|h[l]|x[l]|y[l]) finished=0;
+            }
+            if (finished) break;
+          }
+          if (k>z) z=k;
+          if (k==MAXPAIR)
+          {
+             printf("Some bit didn't change: ");
+             printf("%.8x %.8x %.8x %.8x %.8x %.8x  ",
+                    e[0],f[0],g[0],h[0],x[0],y[0]);
+             printf("i %d j %d m %d len %d\n", i, j, m, hlen);
+          }
+          if (z==MAXPAIR) goto done;
+        }
+      }
+    }
+   done:
+    if (z < MAXPAIR)
+    {
+      printf("Mix success  %2d bytes  %2d initvals  ",i,m);
+      printf("required  %d  trials\n", z/2);
+    }
+  }
+  printf("\n");
+}
+
+/* Check for reading beyond the end of the buffer and alignment problems */
+void driver3()
+{
+  uint8_t buf[MAXLEN+20], *b;
+  uint32_t len;
+  uint8_t q[] = "This is the time for all good men to come to the aid of their country...";
+  uint32_t h;
+  uint8_t qq[] = "xThis is the time for all good men to come to the aid of their country...";
+  uint32_t i;
+  uint8_t qqq[] = "xxThis is the time for all good men to come to the aid of their country...";
+  uint32_t j;
+  uint8_t qqqq[] = "xxxThis is the time for all good men to come to the aid of their country...";
+  uint32_t ref,x,y;
+  uint8_t *p;
+
+  printf("Endianness.  These lines should all be the same (for values filled in):\n");
+  printf("%.8x                            %.8x                            %.8x\n",
+         hashword((const uint32_t *)q, (sizeof(q)-1)/4, 13),
+         hashword((const uint32_t *)q, (sizeof(q)-5)/4, 13),
+         hashword((const uint32_t *)q, (sizeof(q)-9)/4, 13));
+  p = q;
+  printf("%.8x %.8x %.8x %.8x %.8x %.8x %.8x %.8x %.8x %.8x %.8x %.8x\n",
+         hashlittle(p, sizeof(q)-1, 13), hashlittle(p, sizeof(q)-2, 13),
+         hashlittle(p, sizeof(q)-3, 13), hashlittle(p, sizeof(q)-4, 13),
+         hashlittle(p, sizeof(q)-5, 13), hashlittle(p, sizeof(q)-6, 13),
+         hashlittle(p, sizeof(q)-7, 13), hashlittle(p, sizeof(q)-8, 13),
+         hashlittle(p, sizeof(q)-9, 13), hashlittle(p, sizeof(q)-10, 13),
+         hashlittle(p, sizeof(q)-11, 13), hashlittle(p, sizeof(q)-12, 13));
+  p = &qq[1];
+  printf("%.8x %.8x %.8x %.8x %.8x %.8x %.8x %.8x %.8x %.8x %.8x %.8x\n",
+         hashlittle(p, sizeof(q)-1, 13), hashlittle(p, sizeof(q)-2, 13),
+         hashlittle(p, sizeof(q)-3, 13), hashlittle(p, sizeof(q)-4, 13),
+         hashlittle(p, sizeof(q)-5, 13), hashlittle(p, sizeof(q)-6, 13),
+         hashlittle(p, sizeof(q)-7, 13), hashlittle(p, sizeof(q)-8, 13),
+         hashlittle(p, sizeof(q)-9, 13), hashlittle(p, sizeof(q)-10, 13),
+         hashlittle(p, sizeof(q)-11, 13), hashlittle(p, sizeof(q)-12, 13));
+  p = &qqq[2];
+  printf("%.8x %.8x %.8x %.8x %.8x %.8x %.8x %.8x %.8x %.8x %.8x %.8x\n",
+         hashlittle(p, sizeof(q)-1, 13), hashlittle(p, sizeof(q)-2, 13),
+         hashlittle(p, sizeof(q)-3, 13), hashlittle(p, sizeof(q)-4, 13),
+         hashlittle(p, sizeof(q)-5, 13), hashlittle(p, sizeof(q)-6, 13),
+         hashlittle(p, sizeof(q)-7, 13), hashlittle(p, sizeof(q)-8, 13),
+         hashlittle(p, sizeof(q)-9, 13), hashlittle(p, sizeof(q)-10, 13),
+         hashlittle(p, sizeof(q)-11, 13), hashlittle(p, sizeof(q)-12, 13));
+  p = &qqqq[3];
+  printf("%.8x %.8x %.8x %.8x %.8x %.8x %.8x %.8x %.8x %.8x %.8x %.8x\n",
+         hashlittle(p, sizeof(q)-1, 13), hashlittle(p, sizeof(q)-2, 13),
+         hashlittle(p, sizeof(q)-3, 13), hashlittle(p, sizeof(q)-4, 13),
+         hashlittle(p, sizeof(q)-5, 13), hashlittle(p, sizeof(q)-6, 13),
+         hashlittle(p, sizeof(q)-7, 13), hashlittle(p, sizeof(q)-8, 13),
+         hashlittle(p, sizeof(q)-9, 13), hashlittle(p, sizeof(q)-10, 13),
+         hashlittle(p, sizeof(q)-11, 13), hashlittle(p, sizeof(q)-12, 13));
+  printf("\n");
+
+  /* check that hashlittle2 and hashlittle produce the same results */
+  i=47; j=0;
+  hashlittle2(q, sizeof(q), &i, &j);
+  if (hashlittle(q, sizeof(q), 47) != i)
+    printf("hashlittle2 and hashlittle mismatch\n");
+
+  /* check that hashword2 and hashword produce the same results */
+  len = 0xdeadbeef;
+  i=47, j=0;
+  hashword2(&len, 1, &i, &j);
+  if (hashword(&len, 1, 47) != i)
+    printf("hashword2 and hashword mismatch %x %x\n",
+           i, hashword(&len, 1, 47));
+
+  /* check hashlittle doesn't read before or after the ends of the string */
+  for (h=0, b=buf+1; h<8; ++h, ++b)
+  {
+    for (i=0; i<MAXLEN; ++i)
+    {
+      len = i;
+      for (j=0; j<i; ++j) *(b+j)=0;
+
+      /* these should all be equal */
+      ref = hashlittle(b, len, (uint32_t)1);
+      *(b+i)=(uint8_t)~0;
+      *(b-1)=(uint8_t)~0;
+      x = hashlittle(b, len, (uint32_t)1);
+      y = hashlittle(b, len, (uint32_t)1);
+      if ((ref != x) || (ref != y))
+      {
+        printf("alignment error: %.8x %.8x %.8x %d %d\n",ref,x,y,
+               h, i);
+      }
+    }
+  }
+}
+
+/* check for problems with nulls */
+ void driver4()
+{
+  uint8_t buf[1];
+  uint32_t h,i,state[HASHSTATE];
+
+
+  buf[0] = ~0;
+  for (i=0; i<HASHSTATE; ++i) state[i] = 1;
+  printf("These should all be different\n");
+  for (i=0, h=0; i<8; ++i)
+  {
+    h = hashlittle(buf, 0, h);
+    printf("%2ld  0-byte strings, hash is  %.8x\n", i, h);
+  }
+}
+
+void driver5()
+{
+  uint32_t b,c;
+  b=0, c=0, hashlittle2("", 0, &c, &b);
+  printf("hash is %.8lx %.8lx\n", c, b);   /* deadbeef deadbeef */
+  b=0xdeadbeef, c=0, hashlittle2("", 0, &c, &b);
+  printf("hash is %.8lx %.8lx\n", c, b);   /* bd5b7dde deadbeef */
+  b=0xdeadbeef, c=0xdeadbeef, hashlittle2("", 0, &c, &b);
+  printf("hash is %.8lx %.8lx\n", c, b);   /* 9c093ccd bd5b7dde */
+  b=0, c=0, hashlittle2("Four score and seven years ago", 30, &c, &b);
+  printf("hash is %.8lx %.8lx\n", c, b);   /* 17770551 ce7226e6 */
+  b=1, c=0, hashlittle2("Four score and seven years ago", 30, &c, &b);
+  printf("hash is %.8lx %.8lx\n", c, b);   /* e3607cae bd371de4 */
+  b=0, c=1, hashlittle2("Four score and seven years ago", 30, &c, &b);
+  printf("hash is %.8lx %.8lx\n", c, b);   /* cd628161 6cbea4b3 */
+  c = hashlittle("Four score and seven years ago", 30, 0);
+  printf("hash is %.8lx\n", c);   /* 17770551 */
+  c = hashlittle("Four score and seven years ago", 30, 1);
+  printf("hash is %.8lx\n", c);   /* cd628161 */
+}
+
+
+int main()
+{
+  driver1();   /* test that the key is hashed: used for timings */
+  driver2();   /* test that whole key is hashed thoroughly */
+  driver3();   /* test that nothing but the key is hashed */
+  driver4();   /* test hashing multiple buffers (all buffers are null) */
+  driver5();   /* test the hash against known vectors */
+  return 1;
+}
+
+#endif  /* SELF_TEST */
diff --git a/src/journal/lookup3.h b/src/journal/lookup3.h
new file mode 100644 (file)
index 0000000..31cc2f5
--- /dev/null
@@ -0,0 +1,25 @@
+/*-*- Mode: C; c-basic-offset: 8; indent-tabs-mode: nil -*-*/
+
+#ifndef foolookup3hfoo
+#define foolookup3hfoo
+
+#include <inttypes.h>
+#include <sys/types.h>
+
+uint32_t jenkins_hashword(const uint32_t *k, size_t length, uint32_t initval);
+void jenkins_hashword2(const uint32_t *k, size_t length, uint32_t *pc, uint32_t *pb);
+
+uint32_t jenkins_hashlittle(const void *key, size_t length, uint32_t initval);
+void jenkins_hashlittle2(const void *key, size_t length, uint32_t *pc, uint32_t *pb);
+
+uint32_t jenkins_hashbig(const void *key, size_t length, uint32_t initval);
+
+static inline uint64_t hash64(const void *data, size_t length) {
+        uint32_t a = 0, b = 0;
+
+        jenkins_hashlittle2(data, length, &a, &b);
+
+        return ((uint64_t) a << 32ULL) | (uint64_t) b;
+}
+
+#endif
diff --git a/src/journal/sd-journal.c b/src/journal/sd-journal.c
new file mode 100644 (file)
index 0000000..92ba578
--- /dev/null
@@ -0,0 +1,1636 @@
+/*-*- 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 <http://www.gnu.org/licenses/>.
+***/
+
+#include <errno.h>
+#include <fcntl.h>
+#include <stddef.h>
+#include <unistd.h>
+#include <sys/inotify.h>
+
+#include "sd-journal.h"
+#include "journal-def.h"
+#include "journal-file.h"
+#include "hashmap.h"
+#include "list.h"
+#include "lookup3.h"
+#include "compress.h"
+#include "journal-internal.h"
+
+#define JOURNAL_FILES_MAX 1024
+
+static void detach_location(sd_journal *j) {
+        Iterator i;
+        JournalFile *f;
+
+        assert(j);
+
+        j->current_file = NULL;
+        j->current_field = 0;
+
+        HASHMAP_FOREACH(f, j->files, i)
+                f->current_offset = 0;
+}
+
+static void reset_location(sd_journal *j) {
+        assert(j);
+
+        detach_location(j);
+        zero(j->current_location);
+}
+
+static void init_location(Location *l, JournalFile *f, Object *o) {
+        assert(l);
+        assert(f);
+        assert(o->object.type == OBJECT_ENTRY);
+
+        l->type = LOCATION_DISCRETE;
+        l->seqnum = le64toh(o->entry.seqnum);
+        l->seqnum_id = f->header->seqnum_id;
+        l->realtime = le64toh(o->entry.realtime);
+        l->monotonic = le64toh(o->entry.monotonic);
+        l->boot_id = o->entry.boot_id;
+        l->xor_hash = le64toh(o->entry.xor_hash);
+
+        l->seqnum_set = l->realtime_set = l->monotonic_set = l->xor_hash_set = true;
+}
+
+static void set_location(sd_journal *j, JournalFile *f, Object *o, uint64_t offset) {
+        assert(j);
+        assert(f);
+        assert(o);
+
+        init_location(&j->current_location, f, o);
+
+        j->current_file = f;
+        j->current_field = 0;
+
+        f->current_offset = offset;
+}
+
+static int same_field(const void *_a, size_t s, const void *_b, size_t t) {
+        const uint8_t *a = _a, *b = _b;
+        size_t j;
+        bool a_good = false, b_good = false, different = false;
+
+        for (j = 0; j < s && j < t; j++) {
+
+                if (a[j] == '=')
+                        a_good = true;
+                if (b[j] == '=')
+                        b_good = true;
+                if (a[j] != b[j])
+                        different = true;
+
+                if (a_good && b_good)
+                        return different ? 0 : 1;
+        }
+
+        return -EINVAL;
+}
+
+_public_ int sd_journal_add_match(sd_journal *j, const void *data, size_t size) {
+        Match *m, *after = NULL;
+        le64_t le_hash;
+
+        if (!j)
+                return -EINVAL;
+        if (!data)
+                return -EINVAL;
+        if (size <= 0)
+                return -EINVAL;
+
+        le_hash = htole64(hash64(data, size));
+
+        LIST_FOREACH(matches, m, j->matches) {
+                int r;
+
+                if (m->le_hash == le_hash &&
+                    m->size == size &&
+                    memcmp(m->data, data, size) == 0)
+                        return 0;
+
+                r = same_field(data, size, m->data, m->size);
+                if (r < 0)
+                        return r;
+                else if (r > 0)
+                        after = m;
+        }
+
+        m = new0(Match, 1);
+        if (!m)
+                return -ENOMEM;
+
+        m->size = size;
+
+        m->data = malloc(m->size);
+        if (!m->data) {
+                free(m);
+                return -ENOMEM;
+        }
+
+        memcpy(m->data, data, size);
+        m->le_hash = le_hash;
+
+        /* Matches for the same fields we order adjacent to each
+         * other */
+        LIST_INSERT_AFTER(Match, matches, j->matches, after, m);
+        j->n_matches ++;
+
+        detach_location(j);
+
+        return 0;
+}
+
+_public_ void sd_journal_flush_matches(sd_journal *j) {
+        if (!j)
+                return;
+
+        while (j->matches) {
+                Match *m = j->matches;
+
+                LIST_REMOVE(Match, matches, j->matches, m);
+                free(m->data);
+                free(m);
+        }
+
+        j->n_matches = 0;
+
+        detach_location(j);
+}
+
+static int compare_order(JournalFile *af, Object *ao,
+                         JournalFile *bf, Object *bo) {
+
+        uint64_t a, b;
+
+        assert(af);
+        assert(ao);
+        assert(bf);
+        assert(bo);
+
+        /* We operate on two different files here, hence we can access
+         * two objects at the same time, which we normally can't.
+         *
+         * If contents and timestamps match, these entries are
+         * identical, even if the seqnum does not match */
+
+        if (sd_id128_equal(ao->entry.boot_id, bo->entry.boot_id) &&
+            ao->entry.monotonic == bo->entry.monotonic &&
+            ao->entry.realtime == bo->entry.realtime &&
+            ao->entry.xor_hash == bo->entry.xor_hash)
+                return 0;
+
+        if (sd_id128_equal(af->header->seqnum_id, bf->header->seqnum_id)) {
+
+                /* If this is from the same seqnum source, compare
+                 * seqnums */
+                a = le64toh(ao->entry.seqnum);
+                b = le64toh(bo->entry.seqnum);
+
+                if (a < b)
+                        return -1;
+                if (a > b)
+                        return 1;
+
+                /* Wow! This is weird, different data but the same
+                 * seqnums? Something is borked, but let's make the
+                 * best of it and compare by time. */
+        }
+
+        if (sd_id128_equal(ao->entry.boot_id, bo->entry.boot_id)) {
+
+                /* If the boot id matches compare monotonic time */
+                a = le64toh(ao->entry.monotonic);
+                b = le64toh(bo->entry.monotonic);
+
+                if (a < b)
+                        return -1;
+                if (a > b)
+                        return 1;
+        }
+
+        /* Otherwise compare UTC time */
+        a = le64toh(ao->entry.realtime);
+        b = le64toh(ao->entry.realtime);
+
+        if (a < b)
+                return -1;
+        if (a > b)
+                return 1;
+
+        /* Finally, compare by contents */
+        a = le64toh(ao->entry.xor_hash);
+        b = le64toh(ao->entry.xor_hash);
+
+        if (a < b)
+                return -1;
+        if (a > b)
+                return 1;
+
+        return 0;
+}
+
+static int compare_with_location(JournalFile *af, Object *ao, Location *l) {
+        uint64_t a;
+
+        assert(af);
+        assert(ao);
+        assert(l);
+        assert(l->type == LOCATION_DISCRETE);
+
+        if (l->monotonic_set &&
+            sd_id128_equal(ao->entry.boot_id, l->boot_id) &&
+            l->realtime_set &&
+            le64toh(ao->entry.realtime) == l->realtime &&
+            l->xor_hash_set &&
+            le64toh(ao->entry.xor_hash) == l->xor_hash)
+                return 0;
+
+        if (l->seqnum_set &&
+            sd_id128_equal(af->header->seqnum_id, l->seqnum_id)) {
+
+                a = le64toh(ao->entry.seqnum);
+
+                if (a < l->seqnum)
+                        return -1;
+                if (a > l->seqnum)
+                        return 1;
+        }
+
+        if (l->monotonic_set &&
+            sd_id128_equal(ao->entry.boot_id, l->boot_id)) {
+
+                a = le64toh(ao->entry.monotonic);
+
+                if (a < l->monotonic)
+                        return -1;
+                if (a > l->monotonic)
+                        return 1;
+        }
+
+        if (l->realtime_set) {
+
+                a = le64toh(ao->entry.realtime);
+
+                if (a < l->realtime)
+                        return -1;
+                if (a > l->realtime)
+                        return 1;
+        }
+
+        if (l->xor_hash_set) {
+                a = le64toh(ao->entry.xor_hash);
+
+                if (a < l->xor_hash)
+                        return -1;
+                if (a > l->xor_hash)
+                        return 1;
+        }
+
+        return 0;
+}
+
+static int find_location(sd_journal *j, JournalFile *f, direction_t direction, Object **ret, uint64_t *offset) {
+        Object *o = NULL;
+        uint64_t p = 0;
+        int r;
+
+        assert(j);
+
+        if (!j->matches) {
+                /* No matches is simple */
+
+                if (j->current_location.type == LOCATION_HEAD)
+                        r = journal_file_next_entry(f, NULL, 0, DIRECTION_DOWN, &o, &p);
+                else if (j->current_location.type == LOCATION_TAIL)
+                        r = journal_file_next_entry(f, NULL, 0, DIRECTION_UP, &o, &p);
+                else if (j->current_location.seqnum_set &&
+                         sd_id128_equal(j->current_location.seqnum_id, f->header->seqnum_id))
+                        r = journal_file_move_to_entry_by_seqnum(f, j->current_location.seqnum, direction, &o, &p);
+                else if (j->current_location.monotonic_set) {
+                        r = journal_file_move_to_entry_by_monotonic(f, j->current_location.boot_id, j->current_location.monotonic, direction, &o, &p);
+
+                        if (r == -ENOENT) {
+                                /* boot id unknown in this file */
+                                if (j->current_location.realtime_set)
+                                        r = journal_file_move_to_entry_by_realtime(f, j->current_location.realtime, direction, &o, &p);
+                                else
+                                        r = journal_file_next_entry(f, NULL, 0, direction, &o, &p);
+                        }
+                } else if (j->current_location.realtime_set)
+                        r = journal_file_move_to_entry_by_realtime(f, j->current_location.realtime, direction, &o, &p);
+                else
+                        r = journal_file_next_entry(f, NULL, 0, direction, &o, &p);
+
+                if (r <= 0)
+                        return r;
+
+        } else  {
+                Match *m, *term_match = NULL;
+                Object *to = NULL;
+                uint64_t tp = 0;
+
+                /* We have matches, first, let's jump to the monotonic
+                 * position if we have any, since it implies a
+                 * match. */
+
+                if (j->current_location.type == LOCATION_DISCRETE &&
+                    j->current_location.monotonic_set) {
+
+                        r = journal_file_move_to_entry_by_monotonic(f, j->current_location.boot_id, j->current_location.monotonic, direction, &o, &p);
+                        if (r <= 0)
+                                return r == -ENOENT ? 0 : r;
+                }
+
+                LIST_FOREACH(matches, m, j->matches) {
+                        Object *c, *d;
+                        uint64_t cp, dp;
+
+                        r = journal_file_find_data_object_with_hash(f, m->data, m->size, le64toh(m->le_hash), &d, &dp);
+                        if (r <= 0)
+                                return r;
+
+                        if (j->current_location.type == LOCATION_HEAD)
+                                r = journal_file_next_entry_for_data(f, NULL, 0, dp, DIRECTION_DOWN, &c, &cp);
+                        else if (j->current_location.type == LOCATION_TAIL)
+                                r = journal_file_next_entry_for_data(f, NULL, 0, dp, DIRECTION_UP, &c, &cp);
+                        else if (j->current_location.seqnum_set &&
+                                 sd_id128_equal(j->current_location.seqnum_id, f->header->seqnum_id))
+                                r = journal_file_move_to_entry_by_seqnum_for_data(f, dp, j->current_location.seqnum, direction, &c, &cp);
+                        else if (j->current_location.realtime_set)
+                                r = journal_file_move_to_entry_by_realtime_for_data(f, dp, j->current_location.realtime, direction, &c, &cp);
+                        else
+                                r = journal_file_next_entry_for_data(f, NULL, 0, dp, direction, &c, &cp);
+
+                        if (r < 0)
+                                return r;
+
+                        if (!term_match) {
+                                term_match = m;
+
+                                if (r > 0) {
+                                        to = c;
+                                        tp = cp;
+                                }
+                        } else if (same_field(term_match->data, term_match->size, m->data, m->size)) {
+
+                                /* Same field as previous match... */
+                                if (r > 0) {
+
+                                        /* Find the earliest of the OR matches */
+
+                                        if (!to ||
+                                            (direction == DIRECTION_DOWN && cp < tp) ||
+                                            (direction == DIRECTION_UP && cp > tp)) {
+                                                to = c;
+                                                tp = cp;
+                                        }
+
+                                }
+
+                        } else {
+
+                                /* Previous term is finished, did anything match? */
+                                if (!to)
+                                        return 0;
+
+                                /* Find the last of the AND matches */
+                                if (!o ||
+                                    (direction == DIRECTION_DOWN && tp > p) ||
+                                    (direction == DIRECTION_UP && tp < p)) {
+                                        o = to;
+                                        p = tp;
+                                }
+
+                                term_match = m;
+
+                                if (r > 0) {
+                                        to = c;
+                                        tp = cp;
+                                } else {
+                                        to = NULL;
+                                        tp = 0;
+                                }
+                        }
+                }
+
+                /* Last term is finished, did anything match? */
+                if (!to)
+                        return 0;
+
+                if (!o ||
+                    (direction == DIRECTION_DOWN && tp > p) ||
+                    (direction == DIRECTION_UP && tp < p)) {
+                        o = to;
+                        p = tp;
+                }
+
+                if (!o)
+                        return 0;
+        }
+
+        if (ret)
+                *ret = o;
+
+        if (offset)
+                *offset = p;
+
+        return 1;
+}
+
+static int next_with_matches(sd_journal *j, JournalFile *f, direction_t direction, Object **ret, uint64_t *offset) {
+        int r;
+        uint64_t cp;
+        Object *c;
+
+        assert(j);
+        assert(f);
+        assert(ret);
+        assert(offset);
+
+        c = *ret;
+        cp = *offset;
+
+        if (!j->matches) {
+                /* No matches is easy */
+
+                r = journal_file_next_entry(f, c, cp, direction, &c, &cp);
+                if (r <= 0)
+                        return r;
+
+                if (ret)
+                        *ret = c;
+                if (offset)
+                        *offset = cp;
+                return 1;
+        }
+
+        /* So there are matches we have to adhere to, let's find the
+         * first entry that matches all of them */
+
+        for (;;) {
+                uint64_t np, n;
+                bool found, term_result = false;
+                Match *m, *term_match = NULL;
+                Object *npo = NULL;
+
+                n = journal_file_entry_n_items(c);
+
+                /* Make sure we don't match the entry we are starting
+                 * from. */
+                found = cp != *offset;
+
+                np = 0;
+                LIST_FOREACH(matches, m, j->matches) {
+                        uint64_t q, k;
+                        Object *qo = NULL;
+
+                        /* Let's check if this is the beginning of a
+                         * new term, i.e. has a different field prefix
+                         * as the preceeding match. */
+                        if (!term_match) {
+                                term_match = m;
+                                term_result = false;
+                        } else if (!same_field(term_match->data, term_match->size, m->data, m->size)) {
+                                if (!term_result)
+                                        found = false;
+
+                                term_match = m;
+                                term_result = false;
+                        }
+
+                        for (k = 0; k < n; k++)
+                                if (c->entry.items[k].hash == m->le_hash)
+                                        break;
+
+                        if (k >= n) {
+                                /* Hmm, didn't find any field that
+                                 * matched this rule, so ignore this
+                                 * match. Go on with next match */
+                                continue;
+                        }
+
+                        term_result = true;
+
+                        /* Hmm, so, this field matched, let's remember
+                         * where we'd have to try next, in case the other
+                         * matches are not OK */
+
+                        r = journal_file_next_entry_for_data(f, c, cp, le64toh(c->entry.items[k].object_offset), direction, &qo, &q);
+                        /* This pointer is invalidated if the window was
+                         * remapped. May need to re-fetch it later */
+                        c = NULL;
+                        if (r < 0)
+                                return r;
+
+                        if (r > 0) {
+
+                                if (direction == DIRECTION_DOWN) {
+                                        if (q > np) {
+                                                np = q;
+                                                npo = qo;
+                                        }
+                                } else {
+                                        if (np == 0 || q < np) {
+                                                np = q;
+                                                npo = qo;
+                                        }
+                                }
+                        }
+                }
+
+                /* Check the last term */
+                if (term_match && !term_result)
+                        found = false;
+
+                /* Did this entry match against all matches? */
+                if (found) {
+                        if (ret) {
+                                if (c == NULL) {
+                                        /* Re-fetch the entry */
+                                        r = journal_file_move_to_object(f, OBJECT_ENTRY, cp, &c);
+                                        if (r < 0)
+                                                return r;
+                                }
+                                *ret = c;
+                        }
+                        if (offset)
+                                *offset = cp;
+                        return 1;
+                }
+
+                /* Did we find a subsequent entry? */
+                if (np == 0)
+                        return 0;
+
+                /* Hmm, ok, this entry only matched partially, so
+                 * let's try another one */
+                cp = np;
+                c = npo;
+        }
+}
+
+static int next_beyond_location(sd_journal *j, JournalFile *f, direction_t direction, Object **ret, uint64_t *offset) {
+        Object *c;
+        uint64_t cp;
+        int compare_value, r;
+
+        assert(j);
+        assert(f);
+
+        if (f->current_offset > 0) {
+                cp = f->current_offset;
+
+                r = journal_file_move_to_object(f, OBJECT_ENTRY, cp, &c);
+                if (r < 0)
+                        return r;
+
+                r = next_with_matches(j, f, direction, &c, &cp);
+                if (r <= 0)
+                        return r;
+
+                compare_value = 1;
+        } else {
+                r = find_location(j, f, direction, &c, &cp);
+                if (r <= 0)
+                        return r;
+
+                compare_value = 0;
+        }
+
+        for (;;) {
+                bool found;
+
+                if (j->current_location.type == LOCATION_DISCRETE) {
+                        int k;
+
+                        k = compare_with_location(f, c, &j->current_location);
+                        if (direction == DIRECTION_DOWN)
+                                found = k >= compare_value;
+                        else
+                                found = k <= -compare_value;
+                } else
+                        found = true;
+
+                if (found) {
+                        if (ret)
+                                *ret = c;
+                        if (offset)
+                                *offset = cp;
+                        return 1;
+                }
+
+                r = next_with_matches(j, f, direction, &c, &cp);
+                if (r <= 0)
+                        return r;
+        }
+}
+
+static int real_journal_next(sd_journal *j, direction_t direction) {
+        JournalFile *f, *new_current = NULL;
+        Iterator i;
+        int r;
+        uint64_t new_offset = 0;
+        Object *new_entry = NULL;
+
+        if (!j)
+                return -EINVAL;
+
+        HASHMAP_FOREACH(f, j->files, i) {
+                Object *o;
+                uint64_t p;
+                bool found;
+
+                r = next_beyond_location(j, f, direction, &o, &p);
+                if (r < 0)
+                        return r;
+                else if (r == 0)
+                        continue;
+
+                if (!new_current)
+                        found = true;
+                else {
+                        int k;
+
+                        k = compare_order(f, o, new_current, new_entry);
+
+                        if (direction == DIRECTION_DOWN)
+                                found = k < 0;
+                        else
+                                found = k > 0;
+                }
+
+                if (found) {
+                        new_current = f;
+                        new_entry = o;
+                        new_offset = p;
+                }
+        }
+
+        if (!new_current)
+                return 0;
+
+        set_location(j, new_current, new_entry, new_offset);
+
+        return 1;
+}
+
+_public_ int sd_journal_next(sd_journal *j) {
+        return real_journal_next(j, DIRECTION_DOWN);
+}
+
+_public_ int sd_journal_previous(sd_journal *j) {
+        return real_journal_next(j, DIRECTION_UP);
+}
+
+static int real_journal_next_skip(sd_journal *j, direction_t direction, uint64_t skip) {
+        int c = 0, r;
+
+        if (!j)
+                return -EINVAL;
+
+        if (skip == 0) {
+                /* If this is not a discrete skip, then at least
+                 * resolve the current location */
+                if (j->current_location.type != LOCATION_DISCRETE)
+                        return real_journal_next(j, direction);
+
+                return 0;
+        }
+
+        do {
+                r = real_journal_next(j, direction);
+                if (r < 0)
+                        return r;
+
+                if (r == 0)
+                        return c;
+
+                skip--;
+                c++;
+        } while (skip > 0);
+
+        return c;
+}
+
+_public_ int sd_journal_next_skip(sd_journal *j, uint64_t skip) {
+        return real_journal_next_skip(j, DIRECTION_DOWN, skip);
+}
+
+_public_ int sd_journal_previous_skip(sd_journal *j, uint64_t skip) {
+        return real_journal_next_skip(j, DIRECTION_UP, skip);
+}
+
+_public_ int sd_journal_get_cursor(sd_journal *j, char **cursor) {
+        Object *o;
+        int r;
+        char bid[33], sid[33];
+
+        if (!j)
+                return -EINVAL;
+        if (!cursor)
+                return -EINVAL;
+
+        if (!j->current_file || j->current_file->current_offset <= 0)
+                return -EADDRNOTAVAIL;
+
+        r = journal_file_move_to_object(j->current_file, OBJECT_ENTRY, j->current_file->current_offset, &o);
+        if (r < 0)
+                return r;
+
+        sd_id128_to_string(j->current_file->header->seqnum_id, sid);
+        sd_id128_to_string(o->entry.boot_id, bid);
+
+        if (asprintf(cursor,
+                     "s=%s;i=%llx;b=%s;m=%llx;t=%llx;x=%llx;p=%s",
+                     sid, (unsigned long long) le64toh(o->entry.seqnum),
+                     bid, (unsigned long long) le64toh(o->entry.monotonic),
+                     (unsigned long long) le64toh(o->entry.realtime),
+                     (unsigned long long) le64toh(o->entry.xor_hash),
+                     file_name_from_path(j->current_file->path)) < 0)
+                return -ENOMEM;
+
+        return 1;
+}
+
+_public_ int sd_journal_seek_cursor(sd_journal *j, const char *cursor) {
+        char *w;
+        size_t l;
+        char *state;
+        unsigned long long seqnum, monotonic, realtime, xor_hash;
+        bool
+                seqnum_id_set = false,
+                seqnum_set = false,
+                boot_id_set = false,
+                monotonic_set = false,
+                realtime_set = false,
+                xor_hash_set = false;
+        sd_id128_t seqnum_id, boot_id;
+
+        if (!j)
+                return -EINVAL;
+        if (!cursor)
+                return -EINVAL;
+
+        FOREACH_WORD_SEPARATOR(w, l, cursor, ";", state) {
+                char *item;
+                int k = 0;
+
+                if (l < 2 || w[1] != '=')
+                        return -EINVAL;
+
+                item = strndup(w, l);
+                if (!item)
+                        return -ENOMEM;
+
+                switch (w[0]) {
+
+                case 's':
+                        seqnum_id_set = true;
+                        k = sd_id128_from_string(w+2, &seqnum_id);
+                        break;
+
+                case 'i':
+                        seqnum_set = true;
+                        if (sscanf(w+2, "%llx", &seqnum) != 1)
+                                k = -EINVAL;
+                        break;
+
+                case 'b':
+                        boot_id_set = true;
+                        k = sd_id128_from_string(w+2, &boot_id);
+                        break;
+
+                case 'm':
+                        monotonic_set = true;
+                        if (sscanf(w+2, "%llx", &monotonic) != 1)
+                                k = -EINVAL;
+                        break;
+
+                case 't':
+                        realtime_set = true;
+                        if (sscanf(w+2, "%llx", &realtime) != 1)
+                                k = -EINVAL;
+                        break;
+
+                case 'x':
+                        xor_hash_set = true;
+                        if (sscanf(w+2, "%llx", &xor_hash) != 1)
+                                k = -EINVAL;
+                        break;
+                }
+
+                free(item);
+
+                if (k < 0)
+                        return k;
+        }
+
+        if ((!seqnum_set || !seqnum_id_set) &&
+            (!monotonic_set || !boot_id_set) &&
+            !realtime_set)
+                return -EINVAL;
+
+        reset_location(j);
+
+        j->current_location.type = LOCATION_DISCRETE;
+
+        if (realtime_set) {
+                j->current_location.realtime = (uint64_t) realtime;
+                j->current_location.realtime_set = true;
+        }
+
+        if (seqnum_set && seqnum_id_set) {
+                j->current_location.seqnum = (uint64_t) seqnum;
+                j->current_location.seqnum_id = seqnum_id;
+                j->current_location.seqnum_set = true;
+        }
+
+        if (monotonic_set && boot_id_set) {
+                j->current_location.monotonic = (uint64_t) monotonic;
+                j->current_location.boot_id = boot_id;
+                j->current_location.monotonic_set = true;
+        }
+
+        if (xor_hash_set) {
+                j->current_location.xor_hash = (uint64_t) xor_hash;
+                j->current_location.xor_hash_set = true;
+        }
+
+        return 0;
+}
+
+_public_ int sd_journal_seek_monotonic_usec(sd_journal *j, sd_id128_t boot_id, uint64_t usec) {
+        if (!j)
+                return -EINVAL;
+
+        reset_location(j);
+        j->current_location.type = LOCATION_DISCRETE;
+        j->current_location.boot_id = boot_id;
+        j->current_location.monotonic = usec;
+        j->current_location.monotonic_set = true;
+
+        return 0;
+}
+
+_public_ int sd_journal_seek_realtime_usec(sd_journal *j, uint64_t usec) {
+        if (!j)
+                return -EINVAL;
+
+        reset_location(j);
+        j->current_location.type = LOCATION_DISCRETE;
+        j->current_location.realtime = usec;
+        j->current_location.realtime_set = true;
+
+        return 0;
+}
+
+_public_ int sd_journal_seek_head(sd_journal *j) {
+        if (!j)
+                return -EINVAL;
+
+        reset_location(j);
+        j->current_location.type = LOCATION_HEAD;
+
+        return 0;
+}
+
+_public_ int sd_journal_seek_tail(sd_journal *j) {
+        if (!j)
+                return -EINVAL;
+
+        reset_location(j);
+        j->current_location.type = LOCATION_TAIL;
+
+        return 0;
+}
+
+static int add_file(sd_journal *j, const char *prefix, const char *dir, const char *filename) {
+        char *fn;
+        int r;
+        JournalFile *f;
+
+        assert(j);
+        assert(prefix);
+        assert(filename);
+
+        if ((j->flags & SD_JOURNAL_SYSTEM_ONLY) &&
+            !startswith(filename, "system.journal"))
+                return 0;
+
+        if (dir)
+                fn = join(prefix, "/", dir, "/", filename, NULL);
+        else
+                fn = join(prefix, "/", filename, NULL);
+
+        if (!fn)
+                return -ENOMEM;
+
+        if (hashmap_get(j->files, fn)) {
+                free(fn);
+                return 0;
+        }
+
+        if (hashmap_size(j->files) >= JOURNAL_FILES_MAX) {
+                log_debug("Too many open journal files, not adding %s, ignoring.", fn);
+                free(fn);
+                return 0;
+        }
+
+        r = journal_file_open(fn, O_RDONLY, 0, NULL, &f);
+        free(fn);
+
+        if (r < 0) {
+                if (errno == ENOENT)
+                        return 0;
+
+                return r;
+        }
+
+        /* journal_file_dump(f); */
+
+        r = hashmap_put(j->files, f->path, f);
+        if (r < 0) {
+                journal_file_close(f);
+                return r;
+        }
+
+        log_debug("File %s got added.", f->path);
+
+        return 0;
+}
+
+static int remove_file(sd_journal *j, const char *prefix, const char *dir, const char *filename) {
+        char *fn;
+        JournalFile *f;
+
+        assert(j);
+        assert(prefix);
+        assert(filename);
+
+        if (dir)
+                fn = join(prefix, "/", dir, "/", filename, NULL);
+        else
+                fn = join(prefix, "/", filename, NULL);
+
+        if (!fn)
+                return -ENOMEM;
+
+        f = hashmap_get(j->files, fn);
+        free(fn);
+
+        if (!f)
+                return 0;
+
+        hashmap_remove(j->files, f->path);
+        journal_file_close(f);
+
+        log_debug("File %s got removed.", f->path);
+        return 0;
+}
+
+static int add_directory(sd_journal *j, const char *prefix, const char *dir) {
+        char *fn;
+        int r;
+        DIR *d;
+        int wd;
+        sd_id128_t id, mid;
+
+        assert(j);
+        assert(prefix);
+        assert(dir);
+
+        if ((j->flags & SD_JOURNAL_LOCAL_ONLY) &&
+            (sd_id128_from_string(dir, &id) < 0 ||
+             sd_id128_get_machine(&mid) < 0 ||
+             !sd_id128_equal(id, mid)))
+            return 0;
+
+        fn = join(prefix, "/", dir, NULL);
+        if (!fn)
+                return -ENOMEM;
+
+        d = opendir(fn);
+
+        if (!d) {
+                free(fn);
+                if (errno == ENOENT)
+                        return 0;
+
+                return -errno;
+        }
+
+        wd = inotify_add_watch(j->inotify_fd, fn,
+                               IN_CREATE|IN_MOVED_TO|IN_MODIFY|IN_ATTRIB|IN_DELETE|
+                               IN_DELETE_SELF|IN_MOVE_SELF|IN_UNMOUNT|
+                               IN_DONT_FOLLOW|IN_ONLYDIR);
+        if (wd > 0) {
+                if (hashmap_put(j->inotify_wd_dirs, INT_TO_PTR(wd), fn) < 0)
+                        inotify_rm_watch(j->inotify_fd, wd);
+                else
+                        fn = NULL;
+        }
+
+        free(fn);
+
+        for (;;) {
+                struct dirent buf, *de;
+
+                r = readdir_r(d, &buf, &de);
+                if (r != 0 || !de)
+                        break;
+
+                if (!dirent_is_file_with_suffix(de, ".journal"))
+                        continue;
+
+                r = add_file(j, prefix, dir, de->d_name);
+                if (r < 0)
+                        log_debug("Failed to add file %s/%s/%s: %s", prefix, dir, de->d_name, strerror(-r));
+        }
+
+        closedir(d);
+
+        log_debug("Directory %s/%s got added.", prefix, dir);
+
+        return 0;
+}
+
+static void remove_directory_wd(sd_journal *j, int wd) {
+        char *p;
+
+        assert(j);
+        assert(wd > 0);
+
+        if (j->inotify_fd >= 0)
+                inotify_rm_watch(j->inotify_fd, wd);
+
+        p = hashmap_remove(j->inotify_wd_dirs, INT_TO_PTR(wd));
+
+        if (p) {
+                log_debug("Directory %s got removed.", p);
+                free(p);
+        }
+}
+
+static void add_root_wd(sd_journal *j, const char *p) {
+        int wd;
+        char *k;
+
+        assert(j);
+        assert(p);
+
+        wd = inotify_add_watch(j->inotify_fd, p,
+                               IN_CREATE|IN_MOVED_TO|IN_MODIFY|IN_ATTRIB|IN_DELETE|
+                               IN_DONT_FOLLOW|IN_ONLYDIR);
+        if (wd <= 0)
+                return;
+
+        k = strdup(p);
+        if (!k || hashmap_put(j->inotify_wd_roots, INT_TO_PTR(wd), k) < 0) {
+                inotify_rm_watch(j->inotify_fd, wd);
+                free(k);
+        }
+}
+
+static void remove_root_wd(sd_journal *j, int wd) {
+        char *p;
+
+        assert(j);
+        assert(wd > 0);
+
+        if (j->inotify_fd >= 0)
+                inotify_rm_watch(j->inotify_fd, wd);
+
+        p = hashmap_remove(j->inotify_wd_roots, INT_TO_PTR(wd));
+
+        if (p) {
+                log_debug("Root %s got removed.", p);
+                free(p);
+        }
+}
+
+_public_ int sd_journal_open(sd_journal **ret, int flags) {
+        sd_journal *j;
+        const char *p;
+        const char search_paths[] =
+                "/run/log/journal\0"
+                "/var/log/journal\0";
+        int r;
+
+        if (!ret)
+                return -EINVAL;
+
+        if (flags & ~(SD_JOURNAL_LOCAL_ONLY|
+                      SD_JOURNAL_RUNTIME_ONLY|
+                      SD_JOURNAL_SYSTEM_ONLY))
+                return -EINVAL;
+
+        j = new0(sd_journal, 1);
+        if (!j)
+                return -ENOMEM;
+
+        j->flags = flags;
+
+        j->inotify_fd = inotify_init1(IN_NONBLOCK|IN_CLOEXEC);
+        if (j->inotify_fd < 0) {
+                r = -errno;
+                goto fail;
+        }
+
+        j->files = hashmap_new(string_hash_func, string_compare_func);
+        if (!j->files) {
+                r = -ENOMEM;
+                goto fail;
+        }
+
+        j->inotify_wd_dirs = hashmap_new(trivial_hash_func, trivial_compare_func);
+        j->inotify_wd_roots = hashmap_new(trivial_hash_func, trivial_compare_func);
+
+        if (!j->inotify_wd_dirs || !j->inotify_wd_roots) {
+                r = -ENOMEM;
+                goto fail;
+        }
+
+        /* We ignore most errors here, since the idea is to only open
+         * what's actually accessible, and ignore the rest. */
+
+        NULSTR_FOREACH(p, search_paths) {
+                DIR *d;
+
+                if ((flags & SD_JOURNAL_RUNTIME_ONLY) &&
+                    !path_startswith(p, "/run"))
+                        continue;
+
+                d = opendir(p);
+                if (!d) {
+                        if (errno != ENOENT)
+                                log_debug("Failed to open %s: %m", p);
+                        continue;
+                }
+
+                add_root_wd(j, p);
+
+                for (;;) {
+                        struct dirent buf, *de;
+                        sd_id128_t id;
+
+                        r = readdir_r(d, &buf, &de);
+                        if (r != 0 || !de)
+                                break;
+
+                        if (dirent_is_file_with_suffix(de, ".journal")) {
+                                r = add_file(j, p, NULL, de->d_name);
+                                if (r < 0)
+                                        log_debug("Failed to add file %s/%s: %s", p, de->d_name, strerror(-r));
+
+                        } else if ((de->d_type == DT_DIR || de->d_type == DT_UNKNOWN) &&
+                                   sd_id128_from_string(de->d_name, &id) >= 0) {
+
+                                r = add_directory(j, p, de->d_name);
+                                if (r < 0)
+                                        log_debug("Failed to add directory %s/%s: %s", p, de->d_name, strerror(-r));
+                        }
+                }
+
+                closedir(d);
+        }
+
+        *ret = j;
+        return 0;
+
+fail:
+        sd_journal_close(j);
+
+        return r;
+};
+
+_public_ void sd_journal_close(sd_journal *j) {
+        if (!j)
+                return;
+
+        if (j->inotify_wd_dirs) {
+                void *k;
+
+                while ((k = hashmap_first_key(j->inotify_wd_dirs)))
+                        remove_directory_wd(j, PTR_TO_INT(k));
+
+                hashmap_free(j->inotify_wd_dirs);
+        }
+
+        if (j->inotify_wd_roots) {
+                void *k;
+
+                while ((k = hashmap_first_key(j->inotify_wd_roots)))
+                        remove_root_wd(j, PTR_TO_INT(k));
+
+                hashmap_free(j->inotify_wd_roots);
+        }
+
+        if (j->files) {
+                JournalFile *f;
+
+                while ((f = hashmap_steal_first(j->files)))
+                        journal_file_close(f);
+
+                hashmap_free(j->files);
+        }
+
+        sd_journal_flush_matches(j);
+
+        if (j->inotify_fd >= 0)
+                close_nointr_nofail(j->inotify_fd);
+
+        free(j);
+}
+
+_public_ int sd_journal_get_realtime_usec(sd_journal *j, uint64_t *ret) {
+        Object *o;
+        JournalFile *f;
+        int r;
+
+        if (!j)
+                return -EINVAL;
+        if (!ret)
+                return -EINVAL;
+
+        f = j->current_file;
+        if (!f)
+                return -EADDRNOTAVAIL;
+
+        if (f->current_offset <= 0)
+                return -EADDRNOTAVAIL;
+
+        r = journal_file_move_to_object(f, OBJECT_ENTRY, f->current_offset, &o);
+        if (r < 0)
+                return r;
+
+        *ret = le64toh(o->entry.realtime);
+        return 0;
+}
+
+_public_ int sd_journal_get_monotonic_usec(sd_journal *j, uint64_t *ret, sd_id128_t *ret_boot_id) {
+        Object *o;
+        JournalFile *f;
+        int r;
+        sd_id128_t id;
+
+        if (!j)
+                return -EINVAL;
+        if (!ret)
+                return -EINVAL;
+
+        f = j->current_file;
+        if (!f)
+                return -EADDRNOTAVAIL;
+
+        if (f->current_offset <= 0)
+                return -EADDRNOTAVAIL;
+
+        r = journal_file_move_to_object(f, OBJECT_ENTRY, f->current_offset, &o);
+        if (r < 0)
+                return r;
+
+        if (ret_boot_id)
+                *ret_boot_id = o->entry.boot_id;
+        else {
+                r = sd_id128_get_boot(&id);
+                if (r < 0)
+                        return r;
+
+                if (!sd_id128_equal(id, o->entry.boot_id))
+                        return -ESTALE;
+        }
+
+        *ret = le64toh(o->entry.monotonic);
+        return 0;
+}
+
+_public_ int sd_journal_get_data(sd_journal *j, const char *field, const void **data, size_t *size) {
+        JournalFile *f;
+        uint64_t i, n;
+        size_t field_length;
+        int r;
+        Object *o;
+
+        if (!j)
+                return -EINVAL;
+        if (!field)
+                return -EINVAL;
+        if (!data)
+                return -EINVAL;
+        if (!size)
+                return -EINVAL;
+
+        if (isempty(field) || strchr(field, '='))
+                return -EINVAL;
+
+        f = j->current_file;
+        if (!f)
+                return -EADDRNOTAVAIL;
+
+        if (f->current_offset <= 0)
+                return -EADDRNOTAVAIL;
+
+        r = journal_file_move_to_object(f, OBJECT_ENTRY, f->current_offset, &o);
+        if (r < 0)
+                return r;
+
+        field_length = strlen(field);
+
+        n = journal_file_entry_n_items(o);
+        for (i = 0; i < n; i++) {
+                uint64_t p, l;
+                le64_t le_hash;
+                size_t t;
+
+                p = le64toh(o->entry.items[i].object_offset);
+                le_hash = o->entry.items[i].hash;
+                r = journal_file_move_to_object(f, OBJECT_DATA, p, &o);
+                if (r < 0)
+                        return r;
+
+                if (le_hash != o->data.hash)
+                        return -EBADMSG;
+
+                l = le64toh(o->object.size) - offsetof(Object, data.payload);
+
+                if (o->object.flags & OBJECT_COMPRESSED) {
+
+#ifdef HAVE_XZ
+                        if (uncompress_startswith(o->data.payload, l,
+                                                  &f->compress_buffer, &f->compress_buffer_size,
+                                                  field, field_length, '=')) {
+
+                                uint64_t rsize;
+
+                                if (!uncompress_blob(o->data.payload, l,
+                                                     &f->compress_buffer, &f->compress_buffer_size, &rsize))
+                                        return -EBADMSG;
+
+                                *data = f->compress_buffer;
+                                *size = (size_t) rsize;
+
+                                return 0;
+                        }
+#else
+                        return -EPROTONOSUPPORT;
+#endif
+
+                } else if (l >= field_length+1 &&
+                           memcmp(o->data.payload, field, field_length) == 0 &&
+                           o->data.payload[field_length] == '=') {
+
+                        t = (size_t) l;
+
+                        if ((uint64_t) t != l)
+                                return -E2BIG;
+
+                        *data = o->data.payload;
+                        *size = t;
+
+                        return 0;
+                }
+
+                r = journal_file_move_to_object(f, OBJECT_ENTRY, f->current_offset, &o);
+                if (r < 0)
+                        return r;
+        }
+
+        return -ENOENT;
+}
+
+_public_ int sd_journal_enumerate_data(sd_journal *j, const void **data, size_t *size) {
+        JournalFile *f;
+        uint64_t p, l, n;
+        le64_t le_hash;
+        int r;
+        Object *o;
+        size_t t;
+
+        if (!j)
+                return -EINVAL;
+        if (!data)
+                return -EINVAL;
+        if (!size)
+                return -EINVAL;
+
+        f = j->current_file;
+        if (!f)
+                return -EADDRNOTAVAIL;
+
+        if (f->current_offset <= 0)
+                return -EADDRNOTAVAIL;
+
+        r = journal_file_move_to_object(f, OBJECT_ENTRY, f->current_offset, &o);
+        if (r < 0)
+                return r;
+
+        n = journal_file_entry_n_items(o);
+        if (j->current_field >= n)
+                return 0;
+
+        p = le64toh(o->entry.items[j->current_field].object_offset);
+        le_hash = o->entry.items[j->current_field].hash;
+        r = journal_file_move_to_object(f, OBJECT_DATA, p, &o);
+        if (r < 0)
+                return r;
+
+        if (le_hash != o->data.hash)
+                return -EBADMSG;
+
+        l = le64toh(o->object.size) - offsetof(Object, data.payload);
+        t = (size_t) l;
+
+        /* We can't read objects larger than 4G on a 32bit machine */
+        if ((uint64_t) t != l)
+                return -E2BIG;
+
+        if (o->object.flags & OBJECT_COMPRESSED) {
+#ifdef HAVE_XZ
+                uint64_t rsize;
+
+                if (!uncompress_blob(o->data.payload, l, &f->compress_buffer, &f->compress_buffer_size, &rsize))
+                        return -EBADMSG;
+
+                *data = f->compress_buffer;
+                *size = (size_t) rsize;
+#else
+                return -EPROTONOSUPPORT;
+#endif
+        } else {
+                *data = o->data.payload;
+                *size = t;
+        }
+
+        j->current_field ++;
+
+        return 1;
+}
+
+_public_ void sd_journal_restart_data(sd_journal *j) {
+        if (!j)
+                return;
+
+        j->current_field = 0;
+}
+
+_public_ int sd_journal_get_fd(sd_journal *j) {
+        if (!j)
+                return -EINVAL;
+
+        return j->inotify_fd;
+}
+
+static void process_inotify_event(sd_journal *j, struct inotify_event *e) {
+        char *p;
+        int r;
+
+        assert(j);
+        assert(e);
+
+        /* Is this a subdirectory we watch? */
+        p = hashmap_get(j->inotify_wd_dirs, INT_TO_PTR(e->wd));
+        if (p) {
+
+                if (!(e->mask & IN_ISDIR) && e->len > 0 && endswith(e->name, ".journal")) {
+
+                        /* Event for a journal file */
+
+                        if (e->mask & (IN_CREATE|IN_MOVED_TO|IN_MODIFY|IN_ATTRIB)) {
+                                r = add_file(j, p, NULL, e->name);
+                                if (r < 0)
+                                        log_debug("Failed to add file %s/%s: %s", p, e->name, strerror(-r));
+                        } else if (e->mask & (IN_DELETE|IN_UNMOUNT)) {
+
+                                r = remove_file(j, p, NULL, e->name);
+                                if (r < 0)
+                                        log_debug("Failed to remove file %s/%s: %s", p, e->name, strerror(-r));
+                        }
+
+                } else if (e->len == 0) {
+
+                        /* Event for the directory itself */
+
+                        if (e->mask & (IN_DELETE_SELF|IN_MOVE_SELF|IN_UNMOUNT))
+                                remove_directory_wd(j, e->wd);
+                }
+
+                return;
+        }
+
+        /* Must be the root directory then? */
+        p = hashmap_get(j->inotify_wd_roots, INT_TO_PTR(e->wd));
+        if (p) {
+                sd_id128_t id;
+
+                if (!(e->mask & IN_ISDIR) && e->len > 0 && endswith(e->name, ".journal")) {
+
+                        /* Event for a journal file */
+
+                        if (e->mask & (IN_CREATE|IN_MOVED_TO|IN_MODIFY|IN_ATTRIB)) {
+                                r = add_file(j, p, NULL, e->name);
+                                if (r < 0)
+                                        log_debug("Failed to add file %s/%s: %s", p, e->name, strerror(-r));
+                        } else if (e->mask & (IN_DELETE|IN_UNMOUNT)) {
+
+                                r = remove_file(j, p, NULL, e->name);
+                                if (r < 0)
+                                        log_debug("Failed to remove file %s/%s: %s", p, e->name, strerror(-r));
+                        }
+
+                } else if ((e->mask & IN_ISDIR) && e->len > 0 && sd_id128_from_string(e->name, &id) >= 0) {
+
+                        /* Event for subdirectory */
+
+                        if (e->mask & (IN_CREATE|IN_MOVED_TO|IN_MODIFY|IN_ATTRIB)) {
+
+                                r = add_directory(j, p, e->name);
+                                if (r < 0)
+                                        log_debug("Failed to add directory %s/%s: %s", p, e->name, strerror(-r));
+                        }
+                }
+
+                return;
+        }
+
+        if (e->mask & IN_IGNORED)
+                return;
+
+        log_warning("Unknown inotify event.");
+}
+
+_public_ int sd_journal_process(sd_journal *j) {
+        uint8_t buffer[sizeof(struct inotify_event) + FILENAME_MAX];
+
+        if (!j)
+                return -EINVAL;
+
+        for (;;) {
+                struct inotify_event *e;
+                ssize_t l;
+
+                l = read(j->inotify_fd, buffer, sizeof(buffer));
+                if (l < 0) {
+                        if (errno == EINTR || errno == EAGAIN)
+                                return 0;
+
+                        return -errno;
+                }
+
+                e = (struct inotify_event*) buffer;
+                while (l > 0) {
+                        size_t step;
+
+                        process_inotify_event(j, e);
+
+                        step = sizeof(struct inotify_event) + e->len;
+                        assert(step <= (size_t) l);
+
+                        e = (struct inotify_event*) ((uint8_t*) e + step);
+                        l -= step;
+                }
+        }
+}
+
+/* _public_ int sd_journal_query_unique(sd_journal *j, const char *field) { */
+/*         if (!j) */
+/*                 return -EINVAL; */
+/*         if (!field) */
+/*                 return -EINVAL; */
+
+/*         return -ENOTSUP; */
+/* } */
+
+/* _public_ int sd_journal_enumerate_unique(sd_journal *j, const void **data, size_t *l) { */
+/*         if (!j) */
+/*                 return -EINVAL; */
+/*         if (!data) */
+/*                 return -EINVAL; */
+/*         if (!l) */
+/*                 return -EINVAL; */
+
+/*         return -ENOTSUP; */
+/* } */
+
+/* _public_ void sd_journal_restart_unique(sd_journal *j) { */
+/*         if (!j) */
+/*                 return; */
+/* } */
diff --git a/src/journal/sparse-endian.h b/src/journal/sparse-endian.h
new file mode 100644 (file)
index 0000000..eb4dbf3
--- /dev/null
@@ -0,0 +1,87 @@
+/* Copyright (c) 2012 Josh Triplett <josh@joshtriplett.org>
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to
+ * deal in the Software without restriction, including without limitation the
+ * rights to use, copy, modify, merge, publish, distribute, sublicense, and/or
+ * sell copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
+ * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
+ * IN THE SOFTWARE.
+ */
+#ifndef SPARSE_ENDIAN_H
+#define SPARSE_ENDIAN_H
+
+#include <endian.h>
+#include <stdint.h>
+
+#ifdef __CHECKER__
+#define __bitwise __attribute__((bitwise))
+#define __force __attribute__((force))
+#else
+#define __bitwise
+#define __force
+#endif
+
+typedef uint16_t __bitwise le16_t;
+typedef uint16_t __bitwise be16_t;
+typedef uint32_t __bitwise le32_t;
+typedef uint32_t __bitwise be32_t;
+typedef uint64_t __bitwise le64_t;
+typedef uint64_t __bitwise be64_t;
+
+#undef htobe16
+#undef htole16
+#undef be16toh
+#undef le16toh
+#undef htobe32
+#undef htole32
+#undef be32toh
+#undef le32toh
+#undef htobe64
+#undef htole64
+#undef be64toh
+#undef le64toh
+
+#if __BYTE_ORDER == __LITTLE_ENDIAN
+#define bswap_16_on_le(x) __bswap_16(x)
+#define bswap_32_on_le(x) __bswap_32(x)
+#define bswap_64_on_le(x) __bswap_64(x)
+#define bswap_16_on_be(x) (x)
+#define bswap_32_on_be(x) (x)
+#define bswap_64_on_be(x) (x)
+#elif __BYTE_ORDER == __BIG_ENDIAN
+#define bswap_16_on_le(x) (x)
+#define bswap_32_on_le(x) (x)
+#define bswap_64_on_le(x) (x)
+#define bswap_16_on_be(x) __bswap_16(x)
+#define bswap_32_on_be(x) __bswap_32(x)
+#define bswap_64_on_be(x) __bswap_64(x)
+#endif
+
+static inline le16_t htole16(uint16_t value) { return (le16_t __force) bswap_16_on_be(value); }
+static inline le32_t htole32(uint32_t value) { return (le32_t __force) bswap_32_on_be(value); }
+static inline le64_t htole64(uint64_t value) { return (le64_t __force) bswap_64_on_be(value); }
+
+static inline be16_t htobe16(uint16_t value) { return (be16_t __force) bswap_16_on_le(value); }
+static inline be32_t htobe32(uint32_t value) { return (be32_t __force) bswap_32_on_le(value); }
+static inline be64_t htobe64(uint64_t value) { return (be64_t __force) bswap_64_on_le(value); }
+
+static inline uint16_t le16toh(le16_t value) { return bswap_16_on_be((uint16_t __force)value); }
+static inline uint32_t le32toh(le32_t value) { return bswap_32_on_be((uint32_t __force)value); }
+static inline uint64_t le64toh(le64_t value) { return bswap_64_on_be((uint64_t __force)value); }
+
+static inline uint16_t be16toh(be16_t value) { return bswap_16_on_le((uint16_t __force)value); }
+static inline uint32_t be32toh(be32_t value) { return bswap_32_on_le((uint32_t __force)value); }
+static inline uint64_t be64toh(be64_t value) { return bswap_64_on_le((uint64_t __force)value); }
+
+#endif /* SPARSE_ENDIAN_H */
diff --git a/src/journal/test-journal-send.c b/src/journal/test-journal-send.c
new file mode 100644 (file)
index 0000000..2f9987d
--- /dev/null
@@ -0,0 +1,32 @@
+/*-*- 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 <http://www.gnu.org/licenses/>.
+***/
+
+#include <systemd/sd-journal.h>
+
+int main(int argc, char *argv[]) {
+        sd_journal_print(LOG_INFO, "piepapo");
+
+        sd_journal_send("MESSAGE=foobar",
+                        "VALUE=%i", 7,
+                        NULL);
+
+        return 0;
+}
diff --git a/src/journal/test-journal.c b/src/journal/test-journal.c
new file mode 100644 (file)
index 0000000..a023509
--- /dev/null
@@ -0,0 +1,120 @@
+/*-*- 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 <http://www.gnu.org/licenses/>.
+***/
+
+#include <fcntl.h>
+#include <unistd.h>
+
+#include <systemd/sd-journal.h>
+
+#include "journal-file.h"
+#include "log.h"
+
+int main(int argc, char *argv[]) {
+        dual_timestamp ts;
+        JournalFile *f;
+        struct iovec iovec;
+        static const char test[] = "test", test2[] = "test2";
+        Object *o;
+        uint64_t p;
+
+        log_set_max_level(LOG_DEBUG);
+
+        unlink("test.journal");
+
+        assert_se(journal_file_open("test.journal", O_RDWR|O_CREAT, 0666, NULL, &f) == 0);
+
+        dual_timestamp_get(&ts);
+
+        iovec.iov_base = (void*) test;
+        iovec.iov_len = strlen(test);
+        assert_se(journal_file_append_entry(f, &ts, &iovec, 1, NULL, NULL, NULL) == 0);
+
+        iovec.iov_base = (void*) test2;
+        iovec.iov_len = strlen(test2);
+        assert_se(journal_file_append_entry(f, &ts, &iovec, 1, NULL, NULL, NULL) == 0);
+
+        iovec.iov_base = (void*) test;
+        iovec.iov_len = strlen(test);
+        assert_se(journal_file_append_entry(f, &ts, &iovec, 1, NULL, NULL, NULL) == 0);
+
+        journal_file_dump(f);
+
+        assert(journal_file_next_entry(f, NULL, 0, DIRECTION_DOWN, &o, &p) == 1);
+        assert(le64toh(o->entry.seqnum) == 1);
+
+        assert(journal_file_next_entry(f, o, p, DIRECTION_DOWN, &o, &p) == 1);
+        assert(le64toh(o->entry.seqnum) == 2);
+
+        assert(journal_file_next_entry(f, o, p, DIRECTION_DOWN, &o, &p) == 1);
+        assert(le64toh(o->entry.seqnum) == 3);
+
+        assert(journal_file_next_entry(f, o, p, DIRECTION_DOWN, &o, &p) == 0);
+
+        assert(journal_file_next_entry(f, NULL, 0, DIRECTION_DOWN, &o, &p) == 1);
+        assert(le64toh(o->entry.seqnum) == 1);
+
+        assert(journal_file_skip_entry(f, o, p, 2, &o, &p) == 1);
+        assert(le64toh(o->entry.seqnum) == 3);
+
+        assert(journal_file_skip_entry(f, o, p, -2, &o, &p) == 1);
+        assert(le64toh(o->entry.seqnum) == 1);
+
+        assert(journal_file_skip_entry(f, o, p, -2, &o, &p) == 1);
+        assert(le64toh(o->entry.seqnum) == 1);
+
+        assert(journal_file_find_data_object(f, test, strlen(test), NULL, &p) == 1);
+        assert(journal_file_next_entry_for_data(f, NULL, 0, p, DIRECTION_DOWN, &o, NULL) == 1);
+        assert(le64toh(o->entry.seqnum) == 1);
+
+        assert(journal_file_next_entry_for_data(f, NULL, 0, p, DIRECTION_UP, &o, NULL) == 1);
+        assert(le64toh(o->entry.seqnum) == 3);
+
+        assert(journal_file_find_data_object(f, test2, strlen(test2), NULL, &p) == 1);
+        assert(journal_file_next_entry_for_data(f, NULL, 0, p, DIRECTION_UP, &o, NULL) == 1);
+        assert(le64toh(o->entry.seqnum) == 2);
+
+        assert(journal_file_next_entry_for_data(f, NULL, 0, p, DIRECTION_DOWN, &o, NULL) == 1);
+        assert(le64toh(o->entry.seqnum) == 2);
+
+        assert(journal_file_find_data_object(f, "quux", 4, NULL, &p) == 0);
+
+        assert(journal_file_move_to_entry_by_seqnum(f, 1, DIRECTION_DOWN, &o, NULL) == 1);
+        assert(le64toh(o->entry.seqnum) == 1);
+
+        assert(journal_file_move_to_entry_by_seqnum(f, 3, DIRECTION_DOWN, &o, NULL) == 1);
+        assert(le64toh(o->entry.seqnum) == 3);
+
+        assert(journal_file_move_to_entry_by_seqnum(f, 2, DIRECTION_DOWN, &o, NULL) == 1);
+        assert(le64toh(o->entry.seqnum) == 2);
+
+        assert(journal_file_move_to_entry_by_seqnum(f, 10, DIRECTION_DOWN, &o, NULL) == 0);
+
+        journal_file_rotate(&f);
+        journal_file_rotate(&f);
+
+        journal_file_close(f);
+
+        journal_directory_vacuum(".", 3000000, 0);
+
+        log_error("Exiting...");
+
+        return 0;
+}
diff --git a/src/kmod-setup.c b/src/kmod-setup.c
new file mode 100644 (file)
index 0000000..debf871
--- /dev/null
@@ -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 <http://www.gnu.org/licenses/>.
+***/
+
+#include <sys/wait.h>
+#include <unistd.h>
+#include <string.h>
+#include <errno.h>
+#include <libkmod.h>
+
+#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
new file mode 100644 (file)
index 0000000..496aef3
--- /dev/null
@@ -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 <http://www.gnu.org/licenses/>.
+***/
+
+int kmod_setup(void);
+
+#endif
diff --git a/src/label.c b/src/label.c
new file mode 100644 (file)
index 0000000..2c887a0
--- /dev/null
@@ -0,0 +1,413 @@
+/*-*- 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 <http://www.gnu.org/licenses/>.
+***/
+
+#include <errno.h>
+#include <sys/stat.h>
+#include <unistd.h>
+#include <malloc.h>
+#include <sys/socket.h>
+#include <sys/un.h>
+
+#include "label.h"
+#include "util.h"
+
+#ifdef HAVE_SELINUX
+#include <selinux/selinux.h>
+#include <selinux/label.h>
+
+static struct selabel_handle *label_hnd = NULL;
+
+static int use_selinux_cached = -1;
+
+static inline bool use_selinux(void) {
+
+        if (use_selinux_cached < 0)
+                use_selinux_cached = is_selinux_enabled() > 0;
+
+        return use_selinux_cached;
+}
+
+void label_retest_selinux(void) {
+        use_selinux_cached = -1;
+}
+
+#endif
+
+int label_init(void) {
+        int r = 0;
+
+#ifdef HAVE_SELINUX
+        usec_t before_timestamp, after_timestamp;
+        struct mallinfo before_mallinfo, after_mallinfo;
+
+        if (!use_selinux())
+                return 0;
+
+        if (label_hnd)
+                return 0;
+
+        before_mallinfo = mallinfo();
+        before_timestamp = now(CLOCK_MONOTONIC);
+
+        label_hnd = selabel_open(SELABEL_CTX_FILE, NULL, 0);
+        if (!label_hnd) {
+                log_full(security_getenforce() == 1 ? LOG_ERR : LOG_DEBUG,
+                         "Failed to initialize SELinux context: %m");
+                r = security_getenforce() == 1 ? -errno : 0;
+        } else  {
+                char timespan[FORMAT_TIMESPAN_MAX];
+                int l;
+
+                after_timestamp = now(CLOCK_MONOTONIC);
+                after_mallinfo = mallinfo();
+
+                l = after_mallinfo.uordblks > before_mallinfo.uordblks ? after_mallinfo.uordblks - before_mallinfo.uordblks : 0;
+
+                log_info("Successfully loaded SELinux database in %s, size on heap is %iK.",
+                         format_timespan(timespan, sizeof(timespan), after_timestamp - before_timestamp),
+                         (l+1023)/1024);
+        }
+#endif
+
+        return r;
+}
+
+int label_fix(const char *path, bool ignore_enoent) {
+        int r = 0;
+
+#ifdef HAVE_SELINUX
+        struct stat st;
+        security_context_t fcon;
+
+        if (!use_selinux() || !label_hnd)
+                return 0;
+
+        r = lstat(path, &st);
+        if (r == 0) {
+                r = selabel_lookup_raw(label_hnd, &fcon, path, st.st_mode);
+
+                /* If there's no label to set, then exit without warning */
+                if (r < 0 && errno == ENOENT)
+                        return 0;
+
+                if (r == 0) {
+                        r = lsetfilecon(path, fcon);
+                        freecon(fcon);
+
+                        /* If the FS doesn't support labels, then exit without warning */
+                        if (r < 0 && errno == ENOTSUP)
+                                return 0;
+                }
+        }
+
+        if (r < 0) {
+                /* Ignore ENOENT in some cases */
+                if (ignore_enoent && errno == ENOENT)
+                        return 0;
+
+                log_full(security_getenforce() == 1 ? LOG_ERR : LOG_DEBUG,
+                         "Unable to fix label of %s: %m", path);
+                r = security_getenforce() == 1 ? -errno : 0;
+        }
+#endif
+
+        return r;
+}
+
+void label_finish(void) {
+
+#ifdef HAVE_SELINUX
+        if (use_selinux() && label_hnd)
+                selabel_close(label_hnd);
+#endif
+}
+
+int label_get_create_label_from_exe(const char *exe, char **label) {
+
+        int r = 0;
+
+#ifdef HAVE_SELINUX
+        security_context_t mycon = NULL, fcon = NULL;
+        security_class_t sclass;
+
+        if (!use_selinux()) {
+                *label = NULL;
+                return 0;
+        }
+
+        r = getcon(&mycon);
+        if (r < 0)
+                goto fail;
+
+        r = getfilecon(exe, &fcon);
+        if (r < 0)
+                goto fail;
+
+        sclass = string_to_security_class("process");
+        r = security_compute_create(mycon, fcon, sclass, (security_context_t *) label);
+        if (r == 0)
+                log_debug("SELinux Socket context for %s will be set to %s", exe, *label);
+
+fail:
+        if (r < 0 && security_getenforce() == 1)
+                r = -errno;
+
+        freecon(mycon);
+        freecon(fcon);
+#endif
+
+        return r;
+}
+
+int label_fifofile_set(const char *path) {
+        int r = 0;
+
+#ifdef HAVE_SELINUX
+        security_context_t filecon = NULL;
+
+        if (!use_selinux() || !label_hnd)
+                return 0;
+
+        r = selabel_lookup_raw(label_hnd, &filecon, path, S_IFIFO);
+        if (r < 0)
+                r = -errno;
+        else if (r == 0) {
+                r = setfscreatecon(filecon);
+                if (r < 0) {
+                        log_error("Failed to set SELinux file context on %s: %m", path);
+                        r = -errno;
+                }
+
+                freecon(filecon);
+        }
+
+        if (r < 0 && security_getenforce() == 0)
+                r = 0;
+#endif
+
+        return r;
+}
+
+int label_symlinkfile_set(const char *path) {
+        int r = 0;
+
+#ifdef HAVE_SELINUX
+        security_context_t filecon = NULL;
+
+        if (!use_selinux() || !label_hnd)
+                return 0;
+
+        r = selabel_lookup_raw(label_hnd, &filecon, path, S_IFLNK);
+        if (r < 0)
+                r = -errno;
+        else if (r == 0) {
+                r = setfscreatecon(filecon);
+                if (r < 0) {
+                        log_error("Failed to set SELinux file context on %s: %m", path);
+                        r = -errno;
+                }
+
+                freecon(filecon);
+        }
+
+        if (r < 0 && security_getenforce() == 0)
+                r = 0;
+#endif
+
+        return r;
+}
+
+int label_socket_set(const char *label) {
+
+#ifdef HAVE_SELINUX
+        if (!use_selinux())
+                return 0;
+
+        if (setsockcreatecon((security_context_t) label) < 0) {
+                log_full(security_getenforce() == 1 ? LOG_ERR : LOG_DEBUG,
+                         "Failed to set SELinux context (%s) on socket: %m", label);
+
+                if (security_getenforce() == 1)
+                        return -errno;
+        }
+#endif
+
+        return 0;
+}
+
+void label_file_clear(void) {
+
+#ifdef HAVE_SELINUX
+        if (!use_selinux())
+                return;
+
+        setfscreatecon(NULL);
+#endif
+}
+
+void label_socket_clear(void) {
+
+#ifdef HAVE_SELINUX
+        if (!use_selinux())
+                return;
+
+        setsockcreatecon(NULL);
+#endif
+}
+
+void label_free(const char *label) {
+
+#ifdef HAVE_SELINUX
+        if (!use_selinux())
+                return;
+
+        freecon((security_context_t) label);
+#endif
+}
+
+int label_mkdir(const char *path, mode_t mode) {
+
+        /* Creates a directory and labels it according to the SELinux policy */
+
+#ifdef HAVE_SELINUX
+        int r;
+        security_context_t fcon = NULL;
+
+        if (!use_selinux() || !label_hnd)
+                goto skipped;
+
+        if (path_is_absolute(path))
+                r = selabel_lookup_raw(label_hnd, &fcon, path, S_IFDIR);
+        else {
+                char *newpath;
+
+                newpath = path_make_absolute_cwd(path);
+                if (!newpath)
+                        return -ENOMEM;
+
+                r = selabel_lookup_raw(label_hnd, &fcon, newpath, S_IFDIR);
+                free(newpath);
+        }
+
+        if (r == 0)
+                r = setfscreatecon(fcon);
+
+        if (r < 0 && errno != ENOENT) {
+                log_error("Failed to set security context %s for %s: %m", fcon, path);
+
+                if (security_getenforce() == 1) {
+                        r = -errno;
+                        goto finish;
+                }
+        }
+
+        r = mkdir(path, mode);
+        if (r < 0)
+                r = -errno;
+
+finish:
+        setfscreatecon(NULL);
+        freecon(fcon);
+
+        return r;
+
+skipped:
+#endif
+        return mkdir(path, mode) < 0 ? -errno : 0;
+}
+
+int label_bind(int fd, const struct sockaddr *addr, socklen_t addrlen) {
+
+        /* Binds a socket and label its file system object according to the SELinux policy */
+
+#ifdef HAVE_SELINUX
+        int r;
+        security_context_t fcon = NULL;
+        const struct sockaddr_un *un;
+        char *path = NULL;
+
+        assert(fd >= 0);
+        assert(addr);
+        assert(addrlen >= sizeof(sa_family_t));
+
+        if (!use_selinux() || !label_hnd)
+                goto skipped;
+
+        /* Filter out non-local sockets */
+        if (addr->sa_family != AF_UNIX)
+                goto skipped;
+
+        /* Filter out anonymous sockets */
+        if (addrlen < sizeof(sa_family_t) + 1)
+                goto skipped;
+
+        /* Filter out abstract namespace sockets */
+        un = (const struct sockaddr_un*) addr;
+        if (un->sun_path[0] == 0)
+                goto skipped;
+
+        path = strndup(un->sun_path, addrlen - offsetof(struct sockaddr_un, sun_path));
+        if (!path)
+                return -ENOMEM;
+
+        if (path_is_absolute(path))
+                r = selabel_lookup_raw(label_hnd, &fcon, path, S_IFSOCK);
+        else {
+                char *newpath;
+
+                newpath = path_make_absolute_cwd(path);
+
+                if (!newpath) {
+                        free(path);
+                        return -ENOMEM;
+                }
+
+                r = selabel_lookup_raw(label_hnd, &fcon, newpath, S_IFSOCK);
+                free(newpath);
+        }
+
+        if (r == 0)
+                r = setfscreatecon(fcon);
+
+        if (r < 0 && errno != ENOENT) {
+                log_error("Failed to set security context %s for %s: %m", fcon, path);
+
+                if (security_getenforce() == 1) {
+                        r = -errno;
+                        goto finish;
+                }
+        }
+
+        r = bind(fd, addr, addrlen);
+        if (r < 0)
+                r = -errno;
+
+finish:
+        setfscreatecon(NULL);
+        freecon(fcon);
+        free(path);
+
+        return r;
+
+skipped:
+#endif
+        return bind(fd, addr, addrlen) < 0 ? -errno : 0;
+}
diff --git a/src/label.h b/src/label.h
new file mode 100644 (file)
index 0000000..ead4483
--- /dev/null
@@ -0,0 +1,51 @@
+/*-*- Mode: C; c-basic-offset: 8; indent-tabs-mode: nil -*-*/
+
+#ifndef foolabelhfoo
+#define foolabelhfoo
+
+/***
+  This file is part 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 <http://www.gnu.org/licenses/>.
+***/
+
+#include <sys/types.h>
+#include <stdbool.h>
+#include <sys/socket.h>
+
+int label_init(void);
+void label_finish(void);
+
+int label_fix(const char *path, bool ignore_enoent);
+
+int label_socket_set(const char *label);
+void label_socket_clear(void);
+
+int label_fifofile_set(const char *path);
+int label_symlinkfile_set(const char *path);
+void label_file_clear(void);
+
+void label_free(const char *label);
+
+int label_get_create_label_from_exe(const char *exe, char **label);
+
+int label_mkdir(const char *path, mode_t mode);
+
+void label_retest_selinux(void);
+
+int label_bind(int fd, const struct sockaddr *addr, socklen_t addrlen);
+
+#endif
diff --git a/src/libsystemd-daemon.pc.in b/src/libsystemd-daemon.pc.in
new file mode 100644 (file)
index 0000000..8bb3a74
--- /dev/null
@@ -0,0 +1,19 @@
+#  Permission is hereby granted, free of charge, to any person
+#  obtaining a copy of this software and associated documentation files
+#  (the "Software"), to deal in the Software without restriction,
+#  including without limitation the rights to use, copy, modify, merge,
+#  publish, distribute, sublicense, and/or sell copies of the Software,
+#  and to permit persons to whom the Software is furnished to do so,
+#  subject to the following conditions:
+
+prefix=@prefix@
+exec_prefix=@exec_prefix@
+libdir=@libdir@
+includedir=@includedir@
+
+Name: systemd
+Description: systemd Daemon Utility Library
+URL: @PACKAGE_URL@
+Version: @PACKAGE_VERSION@
+Libs: -L${libdir} -lsystemd-daemon
+Cflags: -I${includedir}
diff --git a/src/libsystemd-daemon.sym b/src/libsystemd-daemon.sym
new file mode 100644 (file)
index 0000000..f440238
--- /dev/null
@@ -0,0 +1,27 @@
+/***
+  Permission is hereby granted, free of charge, to any person
+  obtaining a copy of this software and associated documentation files
+  (the "Software"), to deal in the Software without restriction,
+  including without limitation the rights to use, copy, modify, merge,
+  publish, distribute, sublicense, and/or sell copies of the Software,
+  and to permit persons to whom the Software is furnished to do so,
+  subject to the following conditions:
+***/
+
+/* Original symbols from systemd v31 */
+
+LIBSYSTEMD_DAEMON_31 {
+global:
+        sd_booted;
+        sd_is_fifo;
+        sd_is_mq;
+        sd_is_socket;
+        sd_is_socket_inet;
+        sd_is_socket_unix;
+        sd_is_special;
+        sd_listen_fds;
+        sd_notify;
+        sd_notifyf;
+local:
+        *;
+};
diff --git a/src/libsystemd-id128.pc.in b/src/libsystemd-id128.pc.in
new file mode 100644 (file)
index 0000000..4d984fd
--- /dev/null
@@ -0,0 +1,18 @@
+#  This file is part of systemd.
+#
+#  systemd is free software; you can redistribute it and/or modify it
+#  under the terms of the GNU General Public License as published by
+#  the Free Software Foundation; either version 2 of the License, or
+#  (at your option) any later version.
+
+prefix=@prefix@
+exec_prefix=@exec_prefix@
+libdir=@libdir@
+includedir=@includedir@
+
+Name: systemd
+Description: systemd 128 Bit ID Utility Library
+URL: @PACKAGE_URL@
+Version: @PACKAGE_VERSION@
+Libs: -L${libdir} -lsystemd-id128
+Cflags: -I${includedir}
diff --git a/src/libsystemd-id128.sym b/src/libsystemd-id128.sym
new file mode 100644 (file)
index 0000000..2373fe6
--- /dev/null
@@ -0,0 +1,21 @@
+/***
+  This file is part of systemd.
+
+  systemd is free software; you can redistribute it and/or modify it
+  under the terms of the GNU General Public License as published by
+  the Free Software Foundation; either version 2 of the License, or
+  (at your option) any later version.
+***/
+
+/* Original symbols from systemd v38 */
+
+LIBSYSTEMD_ID128_38 {
+global:
+        sd_id128_to_string;
+        sd_id128_from_string;
+        sd_id128_randomize;
+        sd_id128_get_machine;
+        sd_id128_get_boot;
+local:
+        *;
+};
diff --git a/src/linux/Makefile b/src/linux/Makefile
new file mode 120000 (symlink)
index 0000000..d0b0e8e
--- /dev/null
@@ -0,0 +1 @@
+../Makefile
\ No newline at end of file
diff --git a/src/linux/auto_dev-ioctl.h b/src/linux/auto_dev-ioctl.h
new file mode 100644 (file)
index 0000000..850f39b
--- /dev/null
@@ -0,0 +1,229 @@
+/*
+ * Copyright 2008 Red Hat, Inc. All rights reserved.
+ * Copyright 2008 Ian Kent <raven@themaw.net>
+ *
+ * This file is part of the Linux kernel and is made available under
+ * the terms of the GNU General Public License, version 2, or at your
+ * option, any later version, incorporated herein by reference.
+ */
+
+#ifndef _LINUX_AUTO_DEV_IOCTL_H
+#define _LINUX_AUTO_DEV_IOCTL_H
+
+#include <linux/auto_fs.h>
+
+#ifdef __KERNEL__
+#include <linux/string.h>
+#else
+#include <string.h>
+#endif /* __KERNEL__ */
+
+#define AUTOFS_DEVICE_NAME             "autofs"
+
+#define AUTOFS_DEV_IOCTL_VERSION_MAJOR 1
+#define AUTOFS_DEV_IOCTL_VERSION_MINOR 0
+
+#define AUTOFS_DEVID_LEN               16
+
+#define AUTOFS_DEV_IOCTL_SIZE          sizeof(struct autofs_dev_ioctl)
+
+/*
+ * An ioctl interface for autofs mount point control.
+ */
+
+struct args_protover {
+       __u32   version;
+};
+
+struct args_protosubver {
+       __u32   sub_version;
+};
+
+struct args_openmount {
+       __u32   devid;
+};
+
+struct args_ready {
+       __u32   token;
+};
+
+struct args_fail {
+       __u32   token;
+       __s32   status;
+};
+
+struct args_setpipefd {
+       __s32   pipefd;
+};
+
+struct args_timeout {
+       __u64   timeout;
+};
+
+struct args_requester {
+       __u32   uid;
+       __u32   gid;
+};
+
+struct args_expire {
+       __u32   how;
+};
+
+struct args_askumount {
+       __u32   may_umount;
+};
+
+struct args_ismountpoint {
+       union {
+               struct args_in {
+                       __u32   type;
+               } in;
+               struct args_out {
+                       __u32   devid;
+                       __u32   magic;
+               } out;
+       };
+};
+
+/*
+ * All the ioctls use this structure.
+ * When sending a path size must account for the total length
+ * of the chunk of memory otherwise is is the size of the
+ * structure.
+ */
+
+struct autofs_dev_ioctl {
+       __u32 ver_major;
+       __u32 ver_minor;
+       __u32 size;             /* total size of data passed in
+                                * including this struct */
+       __s32 ioctlfd;          /* automount command fd */
+
+       /* Command parameters */
+
+       union {
+               struct args_protover            protover;
+               struct args_protosubver         protosubver;
+               struct args_openmount           openmount;
+               struct args_ready               ready;
+               struct args_fail                fail;
+               struct args_setpipefd           setpipefd;
+               struct args_timeout             timeout;
+               struct args_requester           requester;
+               struct args_expire              expire;
+               struct args_askumount           askumount;
+               struct args_ismountpoint        ismountpoint;
+       };
+
+       char path[0];
+};
+
+static inline void init_autofs_dev_ioctl(struct autofs_dev_ioctl *in)
+{
+       memset(in, 0, sizeof(struct autofs_dev_ioctl));
+       in->ver_major = AUTOFS_DEV_IOCTL_VERSION_MAJOR;
+       in->ver_minor = AUTOFS_DEV_IOCTL_VERSION_MINOR;
+       in->size = sizeof(struct autofs_dev_ioctl);
+       in->ioctlfd = -1;
+       return;
+}
+
+/*
+ * If you change this make sure you make the corresponding change
+ * to autofs-dev-ioctl.c:lookup_ioctl()
+ */
+enum {
+       /* Get various version info */
+       AUTOFS_DEV_IOCTL_VERSION_CMD = 0x71,
+       AUTOFS_DEV_IOCTL_PROTOVER_CMD,
+       AUTOFS_DEV_IOCTL_PROTOSUBVER_CMD,
+
+       /* Open mount ioctl fd */
+       AUTOFS_DEV_IOCTL_OPENMOUNT_CMD,
+
+       /* Close mount ioctl fd */
+       AUTOFS_DEV_IOCTL_CLOSEMOUNT_CMD,
+
+       /* Mount/expire status returns */
+       AUTOFS_DEV_IOCTL_READY_CMD,
+       AUTOFS_DEV_IOCTL_FAIL_CMD,
+
+       /* Activate/deactivate autofs mount */
+       AUTOFS_DEV_IOCTL_SETPIPEFD_CMD,
+       AUTOFS_DEV_IOCTL_CATATONIC_CMD,
+
+       /* Expiry timeout */
+       AUTOFS_DEV_IOCTL_TIMEOUT_CMD,
+
+       /* Get mount last requesting uid and gid */
+       AUTOFS_DEV_IOCTL_REQUESTER_CMD,
+
+       /* Check for eligible expire candidates */
+       AUTOFS_DEV_IOCTL_EXPIRE_CMD,
+
+       /* Request busy status */
+       AUTOFS_DEV_IOCTL_ASKUMOUNT_CMD,
+
+       /* Check if path is a mountpoint */
+       AUTOFS_DEV_IOCTL_ISMOUNTPOINT_CMD,
+};
+
+#define AUTOFS_IOCTL 0x93
+
+#define AUTOFS_DEV_IOCTL_VERSION \
+       _IOWR(AUTOFS_IOCTL, \
+             AUTOFS_DEV_IOCTL_VERSION_CMD, struct autofs_dev_ioctl)
+
+#define AUTOFS_DEV_IOCTL_PROTOVER \
+       _IOWR(AUTOFS_IOCTL, \
+             AUTOFS_DEV_IOCTL_PROTOVER_CMD, struct autofs_dev_ioctl)
+
+#define AUTOFS_DEV_IOCTL_PROTOSUBVER \
+       _IOWR(AUTOFS_IOCTL, \
+             AUTOFS_DEV_IOCTL_PROTOSUBVER_CMD, struct autofs_dev_ioctl)
+
+#define AUTOFS_DEV_IOCTL_OPENMOUNT \
+       _IOWR(AUTOFS_IOCTL, \
+             AUTOFS_DEV_IOCTL_OPENMOUNT_CMD, struct autofs_dev_ioctl)
+
+#define AUTOFS_DEV_IOCTL_CLOSEMOUNT \
+       _IOWR(AUTOFS_IOCTL, \
+             AUTOFS_DEV_IOCTL_CLOSEMOUNT_CMD, struct autofs_dev_ioctl)
+
+#define AUTOFS_DEV_IOCTL_READY \
+       _IOWR(AUTOFS_IOCTL, \
+             AUTOFS_DEV_IOCTL_READY_CMD, struct autofs_dev_ioctl)
+
+#define AUTOFS_DEV_IOCTL_FAIL \
+       _IOWR(AUTOFS_IOCTL, \
+             AUTOFS_DEV_IOCTL_FAIL_CMD, struct autofs_dev_ioctl)
+
+#define AUTOFS_DEV_IOCTL_SETPIPEFD \
+       _IOWR(AUTOFS_IOCTL, \
+             AUTOFS_DEV_IOCTL_SETPIPEFD_CMD, struct autofs_dev_ioctl)
+
+#define AUTOFS_DEV_IOCTL_CATATONIC \
+       _IOWR(AUTOFS_IOCTL, \
+             AUTOFS_DEV_IOCTL_CATATONIC_CMD, struct autofs_dev_ioctl)
+
+#define AUTOFS_DEV_IOCTL_TIMEOUT \
+       _IOWR(AUTOFS_IOCTL, \
+             AUTOFS_DEV_IOCTL_TIMEOUT_CMD, struct autofs_dev_ioctl)
+
+#define AUTOFS_DEV_IOCTL_REQUESTER \
+       _IOWR(AUTOFS_IOCTL, \
+             AUTOFS_DEV_IOCTL_REQUESTER_CMD, struct autofs_dev_ioctl)
+
+#define AUTOFS_DEV_IOCTL_EXPIRE \
+       _IOWR(AUTOFS_IOCTL, \
+             AUTOFS_DEV_IOCTL_EXPIRE_CMD, struct autofs_dev_ioctl)
+
+#define AUTOFS_DEV_IOCTL_ASKUMOUNT \
+       _IOWR(AUTOFS_IOCTL, \
+             AUTOFS_DEV_IOCTL_ASKUMOUNT_CMD, struct autofs_dev_ioctl)
+
+#define AUTOFS_DEV_IOCTL_ISMOUNTPOINT \
+       _IOWR(AUTOFS_IOCTL, \
+             AUTOFS_DEV_IOCTL_ISMOUNTPOINT_CMD, struct autofs_dev_ioctl)
+
+#endif /* _LINUX_AUTO_DEV_IOCTL_H */
diff --git a/src/linux/fanotify.h b/src/linux/fanotify.h
new file mode 100644 (file)
index 0000000..63531a6
--- /dev/null
@@ -0,0 +1,98 @@
+#ifndef _LINUX_FANOTIFY_H
+#define _LINUX_FANOTIFY_H
+
+#include <linux/types.h>
+
+/* the following events that user-space can register for */
+#define FAN_ACCESS             0x00000001      /* File was accessed */
+#define FAN_MODIFY             0x00000002      /* File was modified */
+#define FAN_CLOSE_WRITE                0x00000008      /* Unwrittable file closed */
+#define FAN_CLOSE_NOWRITE      0x00000010      /* Writtable file closed */
+#define FAN_OPEN               0x00000020      /* File was opened */
+
+#define FAN_EVENT_ON_CHILD     0x08000000      /* interested in child events */
+
+/* FIXME currently Q's have no limit.... */
+#define FAN_Q_OVERFLOW         0x00004000      /* Event queued overflowed */
+
+#define FAN_OPEN_PERM          0x00010000      /* File open in perm check */
+#define FAN_ACCESS_PERM                0x00020000      /* File accessed in perm check */
+
+/* helper events */
+#define FAN_CLOSE              (FAN_CLOSE_WRITE | FAN_CLOSE_NOWRITE) /* close */
+
+/* flags used for fanotify_init() */
+#define FAN_CLOEXEC            0x00000001
+#define FAN_NONBLOCK           0x00000002
+
+#define FAN_ALL_INIT_FLAGS     (FAN_CLOEXEC | FAN_NONBLOCK)
+
+/* flags used for fanotify_modify_mark() */
+#define FAN_MARK_ADD           0x00000001
+#define FAN_MARK_REMOVE                0x00000002
+#define FAN_MARK_DONT_FOLLOW   0x00000004
+#define FAN_MARK_ONLYDIR       0x00000008
+#define FAN_MARK_MOUNT         0x00000010
+#define FAN_MARK_IGNORED_MASK  0x00000020
+#define FAN_MARK_IGNORED_SURV_MODIFY   0x00000040
+#define FAN_MARK_FLUSH         0x00000080
+
+#define FAN_ALL_MARK_FLAGS     (FAN_MARK_ADD |\
+                                FAN_MARK_REMOVE |\
+                                FAN_MARK_DONT_FOLLOW |\
+                                FAN_MARK_ONLYDIR |\
+                                FAN_MARK_MOUNT |\
+                                FAN_MARK_IGNORED_MASK |\
+                                FAN_MARK_IGNORED_SURV_MODIFY)
+
+/*
+ * All of the events - we build the list by hand so that we can add flags in
+ * the future and not break backward compatibility.  Apps will get only the
+ * events that they originally wanted.  Be sure to add new events here!
+ */
+#define FAN_ALL_EVENTS (FAN_ACCESS |\
+                       FAN_MODIFY |\
+                       FAN_CLOSE |\
+                       FAN_OPEN)
+
+/*
+ * All events which require a permission response from userspace
+ */
+#define FAN_ALL_PERM_EVENTS (FAN_OPEN_PERM |\
+                            FAN_ACCESS_PERM)
+
+#define FAN_ALL_OUTGOING_EVENTS        (FAN_ALL_EVENTS |\
+                                FAN_ALL_PERM_EVENTS |\
+                                FAN_Q_OVERFLOW)
+
+#define FANOTIFY_METADATA_VERSION      2
+
+struct fanotify_event_metadata {
+       __u32 event_len;
+       __u32 vers;
+       __u64 mask;
+       __s32 fd;
+       __s32 pid;
+} __attribute__ ((packed));
+
+struct fanotify_response {
+       __s32 fd;
+       __u32 response;
+} __attribute__ ((packed));
+
+/* Legit userspace responses to a _PERM event */
+#define FAN_ALLOW      0x01
+#define FAN_DENY       0x02
+
+/* Helper functions to deal with fanotify_event_metadata buffers */
+#define FAN_EVENT_METADATA_LEN (sizeof(struct fanotify_event_metadata))
+
+#define FAN_EVENT_NEXT(meta, len) ((len) -= (meta)->event_len, \
+                                  (struct fanotify_event_metadata*)(((char *)(meta)) + \
+                                  (meta)->event_len))
+
+#define FAN_EVENT_OK(meta, len)        ((long)(len) >= (long)FAN_EVENT_METADATA_LEN && \
+                               (long)(meta)->event_len >= (long)FAN_EVENT_METADATA_LEN && \
+                               (long)(meta)->event_len <= (long)(len))
+
+#endif /* _LINUX_FANOTIFY_H */
diff --git a/src/list.h b/src/list.h
new file mode 100644 (file)
index 0000000..2bec8c9
--- /dev/null
@@ -0,0 +1,128 @@
+/*-*- Mode: C; c-basic-offset: 8; indent-tabs-mode: nil -*-*/
+
+#ifndef foolisthfoo
+#define foolisthfoo
+
+/***
+  This file is part 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 <http://www.gnu.org/licenses/>.
+***/
+
+/* The head of the linked list. Use this in the structure that shall
+ * contain the head of the linked list */
+#define LIST_HEAD(t,name)                                               \
+        t *name
+
+/* The pointers in the linked list's items. Use this in the item structure */
+#define LIST_FIELDS(t,name)                                             \
+        t *name##_next, *name##_prev
+
+/* Initialize the list's head */
+#define LIST_HEAD_INIT(t,head)                                          \
+        do {                                                            \
+                (head) = NULL; }                                        \
+        while(false)
+
+/* Initialize a list item */
+#define LIST_INIT(t,name,item)                                          \
+        do {                                                            \
+                t *_item = (item);                                      \
+                assert(_item);                                          \
+                _item->name##_prev = _item->name##_next = NULL;         \
+        } while(false)
+
+/* Prepend an item to the list */
+#define LIST_PREPEND(t,name,head,item)                                  \
+        do {                                                            \
+                t **_head = &(head), *_item = (item);                   \
+                assert(_item);                                          \
+                if ((_item->name##_next = *_head))                      \
+                        _item->name##_next->name##_prev = _item;        \
+                _item->name##_prev = NULL;                              \
+                *_head = _item;                                         \
+        } while(false)
+
+/* Remove an item from the list */
+#define LIST_REMOVE(t,name,head,item)                                   \
+        do {                                                            \
+                t **_head = &(head), *_item = (item);                   \
+                assert(_item);                                          \
+                if (_item->name##_next)                                 \
+                        _item->name##_next->name##_prev = _item->name##_prev; \
+                if (_item->name##_prev)                                 \
+                        _item->name##_prev->name##_next = _item->name##_next; \
+                else {                                                  \
+                        assert(*_head == _item);                        \
+                        *_head = _item->name##_next;                    \
+                }                                                       \
+                _item->name##_next = _item->name##_prev = NULL;         \
+        } while(false)
+
+/* Find the head of the list */
+#define LIST_FIND_HEAD(t,name,item,head)                                \
+        do {                                                            \
+                t *_item = (item);                                      \
+                assert(_item);                                          \
+                while (_item->name##_prev)                              \
+                       _item = _item->name##_prev;                      \
+                (head) = _item;                                         \
+        } while (false)
+
+/* Find the head of the list */
+#define LIST_FIND_TAIL(t,name,item,tail)                                \
+        do {                                                            \
+                t *_item = (item);                                      \
+                assert(_item);                                          \
+                while (_item->name##_next)                              \
+                        _item = _item->name##_next;                     \
+                (tail) = _item;                                         \
+        } while (false)
+
+/* Insert an item after another one (a = where, b = what) */
+#define LIST_INSERT_AFTER(t,name,head,a,b)                              \
+        do {                                                            \
+                t **_head = &(head), *_a = (a), *_b = (b);              \
+                assert(_b);                                             \
+                if (!_a) {                                              \
+                        if ((_b->name##_next = *_head))                 \
+                                _b->name##_next->name##_prev = _b;      \
+                        _b->name##_prev = NULL;                         \
+                        *_head = _b;                                    \
+                } else {                                                \
+                        if ((_b->name##_next = _a->name##_next))        \
+                                _b->name##_next->name##_prev = _b;      \
+                        _b->name##_prev = _a;                           \
+                        _a->name##_next = _b;                           \
+                }                                                       \
+        } while(false)
+
+#define LIST_JUST_US(name,item)                                         \
+        (!(item)->name##_prev && !(item)->name##_next)                  \
+
+#define LIST_FOREACH(name,i,head)                                       \
+        for ((i) = (head); (i); (i) = (i)->name##_next)
+
+#define LIST_FOREACH_SAFE(name,i,n,head)                                \
+        for ((i) = (head); (i) && (((n) = (i)->name##_next), 1); (i) = (n))
+
+#define LIST_FOREACH_BEFORE(name,i,p)                                   \
+        for ((i) = (p)->name##_prev; (i); (i) = (i)->name##_prev)
+
+#define LIST_FOREACH_AFTER(name,i,p)                                    \
+        for ((i) = (p)->name##_next; (i); (i) = (i)->name##_next)
+
+#endif
diff --git a/src/load-dropin.c b/src/load-dropin.c
new file mode 100644 (file)
index 0000000..d869ee0
--- /dev/null
@@ -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 <http://www.gnu.org/licenses/>.
+***/
+
+#include <dirent.h>
+#include <errno.h>
+
+#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
new file mode 100644 (file)
index 0000000..cf3a799
--- /dev/null
@@ -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 <http://www.gnu.org/licenses/>.
+***/
+
+#include "unit.h"
+
+/* Read service data supplementary drop-in directories */
+
+int unit_load_dropin(Unit *u);
+
+#endif
diff --git a/src/load-fragment-gperf.gperf.m4 b/src/load-fragment-gperf.gperf.m4
new file mode 100644 (file)
index 0000000..4b02e31
--- /dev/null
@@ -0,0 +1,230 @@
+%{
+#include <stddef.h>
+#include "conf-parser.h"
+#include "load-fragment.h"
+#include "missing.h"
+%}
+struct ConfigPerfItem;
+%null_strings
+%language=ANSI-C
+%define slot-name section_and_lvalue
+%define hash-function-name load_fragment_gperf_hash
+%define lookup-function-name load_fragment_gperf_lookup
+%readonly-tables
+%omit-struct-type
+%struct-type
+%includes
+%%
+m4_dnl Define the context options only once
+m4_define(`EXEC_CONTEXT_CONFIG_ITEMS',
+`$1.WorkingDirectory,            config_parse_unit_path_printf,      0,                             offsetof($1, exec_context.working_directory)
+$1.RootDirectory,                config_parse_unit_path_printf,      0,                             offsetof($1, exec_context.root_directory)
+$1.User,                         config_parse_unit_string_printf,    0,                             offsetof($1, exec_context.user)
+$1.Group,                        config_parse_unit_string_printf,    0,                             offsetof($1, exec_context.group)
+$1.SupplementaryGroups,          config_parse_strv,                  0,                             offsetof($1, exec_context.supplementary_groups)
+$1.Nice,                         config_parse_exec_nice,             0,                             offsetof($1, exec_context)
+$1.OOMScoreAdjust,               config_parse_exec_oom_score_adjust, 0,                             offsetof($1, exec_context)
+$1.IOSchedulingClass,            config_parse_exec_io_class,         0,                             offsetof($1, exec_context)
+$1.IOSchedulingPriority,         config_parse_exec_io_priority,      0,                             offsetof($1, exec_context)
+$1.CPUSchedulingPolicy,          config_parse_exec_cpu_sched_policy, 0,                             offsetof($1, exec_context)
+$1.CPUSchedulingPriority,        config_parse_exec_cpu_sched_prio,   0,                             offsetof($1, exec_context)
+$1.CPUSchedulingResetOnFork,     config_parse_bool,                  0,                             offsetof($1, exec_context.cpu_sched_reset_on_fork)
+$1.CPUAffinity,                  config_parse_exec_cpu_affinity,     0,                             offsetof($1, exec_context)
+$1.UMask,                        config_parse_mode,                  0,                             offsetof($1, exec_context.umask)
+$1.Environment,                  config_parse_unit_strv_printf,      0,                             offsetof($1, exec_context.environment)
+$1.EnvironmentFile,              config_parse_unit_env_file,         0,                             offsetof($1, exec_context.environment_files)
+$1.StandardInput,                config_parse_input,                 0,                             offsetof($1, exec_context.std_input)
+$1.StandardOutput,               config_parse_output,                0,                             offsetof($1, exec_context.std_output)
+$1.StandardError,                config_parse_output,                0,                             offsetof($1, exec_context.std_error)
+$1.TTYPath,                      config_parse_unit_path_printf,      0,                             offsetof($1, exec_context.tty_path)
+$1.TTYReset,                     config_parse_bool,                  0,                             offsetof($1, exec_context.tty_reset)
+$1.TTYVHangup,                   config_parse_bool,                  0,                             offsetof($1, exec_context.tty_vhangup)
+$1.TTYVTDisallocate,             config_parse_bool,                  0,                             offsetof($1, exec_context.tty_vt_disallocate)
+$1.SyslogIdentifier,             config_parse_unit_string_printf,    0,                             offsetof($1, exec_context.syslog_identifier)
+$1.SyslogFacility,               config_parse_facility,              0,                             offsetof($1, exec_context.syslog_priority)
+$1.SyslogLevel,                  config_parse_level,                 0,                             offsetof($1, exec_context.syslog_priority)
+$1.SyslogLevelPrefix,            config_parse_bool,                  0,                             offsetof($1, exec_context.syslog_level_prefix)
+$1.Capabilities,                 config_parse_exec_capabilities,     0,                             offsetof($1, exec_context)
+$1.SecureBits,                   config_parse_exec_secure_bits,      0,                             offsetof($1, exec_context)
+$1.CapabilityBoundingSet,        config_parse_exec_bounding_set,     0,                             offsetof($1, exec_context)
+$1.TimerSlackNSec,               config_parse_exec_timer_slack_nsec, 0,                             offsetof($1, exec_context)
+$1.LimitCPU,                     config_parse_limit,                 RLIMIT_CPU,                    offsetof($1, exec_context.rlimit)
+$1.LimitFSIZE,                   config_parse_limit,                 RLIMIT_FSIZE,                  offsetof($1, exec_context.rlimit)
+$1.LimitDATA,                    config_parse_limit,                 RLIMIT_DATA,                   offsetof($1, exec_context.rlimit)
+$1.LimitSTACK,                   config_parse_limit,                 RLIMIT_STACK,                  offsetof($1, exec_context.rlimit)
+$1.LimitCORE,                    config_parse_limit,                 RLIMIT_CORE,                   offsetof($1, exec_context.rlimit)
+$1.LimitRSS,                     config_parse_limit,                 RLIMIT_RSS,                    offsetof($1, exec_context.rlimit)
+$1.LimitNOFILE,                  config_parse_limit,                 RLIMIT_NOFILE,                 offsetof($1, exec_context.rlimit)
+$1.LimitAS,                      config_parse_limit,                 RLIMIT_AS,                     offsetof($1, exec_context.rlimit)
+$1.LimitNPROC,                   config_parse_limit,                 RLIMIT_NPROC,                  offsetof($1, exec_context.rlimit)
+$1.LimitMEMLOCK,                 config_parse_limit,                 RLIMIT_MEMLOCK,                offsetof($1, exec_context.rlimit)
+$1.LimitLOCKS,                   config_parse_limit,                 RLIMIT_LOCKS,                  offsetof($1, exec_context.rlimit)
+$1.LimitSIGPENDING,              config_parse_limit,                 RLIMIT_SIGPENDING,             offsetof($1, exec_context.rlimit)
+$1.LimitMSGQUEUE,                config_parse_limit,                 RLIMIT_MSGQUEUE,               offsetof($1, exec_context.rlimit)
+$1.LimitNICE,                    config_parse_limit,                 RLIMIT_NICE,                   offsetof($1, exec_context.rlimit)
+$1.LimitRTPRIO,                  config_parse_limit,                 RLIMIT_RTPRIO,                 offsetof($1, exec_context.rlimit)
+$1.LimitRTTIME,                  config_parse_limit,                 RLIMIT_RTTIME,                 offsetof($1, exec_context.rlimit)
+$1.ControlGroup,                 config_parse_unit_cgroup,           0,                             0
+$1.ControlGroupAttribute,        config_parse_unit_cgroup_attr,      0,                             0
+$1.CPUShares,                    config_parse_unit_cpu_shares,       0,                             0
+$1.MemoryLimit,                  config_parse_unit_memory_limit,     0,                             0
+$1.MemorySoftLimit,              config_parse_unit_memory_limit,     0,                             0
+$1.DeviceAllow,                  config_parse_unit_device_allow,     0,                             0
+$1.DeviceDeny,                   config_parse_unit_device_allow,     0,                             0
+$1.BlockIOWeight,                config_parse_unit_blkio_weight,     0,                             0
+$1.BlockIOReadBandwidth,         config_parse_unit_blkio_bandwidth,  0,                             0
+$1.BlockIOWriteBandwidth,        config_parse_unit_blkio_bandwidth,  0,                             0
+$1.ReadWriteDirectories,         config_parse_path_strv,             0,                             offsetof($1, exec_context.read_write_dirs)
+$1.ReadOnlyDirectories,          config_parse_path_strv,             0,                             offsetof($1, exec_context.read_only_dirs)
+$1.InaccessibleDirectories,      config_parse_path_strv,             0,                             offsetof($1, exec_context.inaccessible_dirs)
+$1.PrivateTmp,                   config_parse_bool,                  0,                             offsetof($1, exec_context.private_tmp)
+$1.PrivateNetwork,               config_parse_bool,                  0,                             offsetof($1, exec_context.private_network)
+$1.MountFlags,                   config_parse_exec_mount_flags,      0,                             offsetof($1, exec_context)
+$1.TCPWrapName,                  config_parse_unit_string_printf,    0,                             offsetof($1, exec_context.tcpwrap_name)
+$1.PAMName,                      config_parse_unit_string_printf,    0,                             offsetof($1, exec_context.pam_name)
+$1.KillMode,                     config_parse_kill_mode,             0,                             offsetof($1, exec_context.kill_mode)
+$1.KillSignal,                   config_parse_kill_signal,           0,                             offsetof($1, exec_context.kill_signal)
+$1.SendSIGKILL,                  config_parse_bool,                  0,                             offsetof($1, exec_context.send_sigkill)
+$1.IgnoreSIGPIPE,                config_parse_bool,                  0,                             offsetof($1, exec_context.ignore_sigpipe)
+$1.UtmpIdentifier,               config_parse_unit_string_printf,    0,                             offsetof($1, exec_context.utmp_id)
+$1.ControlGroupModify,           config_parse_bool,                  0,                             offsetof($1, exec_context.control_group_modify)
+$1.ControlGroupPersistent,       config_parse_tristate,              0,                             offsetof($1, exec_context.control_group_persistent)'
+)m4_dnl
+Unit.Names,                      config_parse_unit_names,            0,                             0
+Unit.Description,                config_parse_unit_string_printf,    0,                             offsetof(Unit, description)
+Unit.Requires,                   config_parse_unit_deps,             UNIT_REQUIRES,                 0
+Unit.RequiresOverridable,        config_parse_unit_deps,             UNIT_REQUIRES_OVERRIDABLE,     0
+Unit.Requisite,                  config_parse_unit_deps,             UNIT_REQUISITE,                0
+Unit.RequisiteOverridable,       config_parse_unit_deps,             UNIT_REQUISITE_OVERRIDABLE,    0
+Unit.Wants,                      config_parse_unit_deps,             UNIT_WANTS,                    0
+Unit.BindTo,                     config_parse_unit_deps,             UNIT_BIND_TO,                  0
+Unit.Conflicts,                  config_parse_unit_deps,             UNIT_CONFLICTS,                0
+Unit.Before,                     config_parse_unit_deps,             UNIT_BEFORE,                   0
+Unit.After,                      config_parse_unit_deps,             UNIT_AFTER,                    0
+Unit.OnFailure,                  config_parse_unit_deps,             UNIT_ON_FAILURE,               0
+Unit.PropagateReloadTo,          config_parse_unit_deps,             UNIT_PROPAGATE_RELOAD_TO,      0
+Unit.PropagateReloadFrom,        config_parse_unit_deps,             UNIT_PROPAGATE_RELOAD_FROM,    0
+Unit.StopWhenUnneeded,           config_parse_bool,                  0,                             offsetof(Unit, stop_when_unneeded)
+Unit.RefuseManualStart,          config_parse_bool,                  0,                             offsetof(Unit, refuse_manual_start)
+Unit.RefuseManualStop,           config_parse_bool,                  0,                             offsetof(Unit, refuse_manual_stop)
+Unit.AllowIsolate,               config_parse_bool,                  0,                             offsetof(Unit, allow_isolate)
+Unit.DefaultDependencies,        config_parse_bool,                  0,                             offsetof(Unit, default_dependencies)
+Unit.OnFailureIsolate,           config_parse_bool,                  0,                             offsetof(Unit, on_failure_isolate)
+Unit.IgnoreOnIsolate,            config_parse_bool,                  0,                             offsetof(Unit, ignore_on_isolate)
+Unit.IgnoreOnSnapshot,           config_parse_bool,                  0,                             offsetof(Unit, ignore_on_snapshot)
+Unit.JobTimeoutSec,              config_parse_usec,                  0,                             offsetof(Unit, job_timeout)
+Unit.ConditionPathExists,        config_parse_unit_condition_path,   CONDITION_PATH_EXISTS,         0
+Unit.ConditionPathExistsGlob,    config_parse_unit_condition_path,   CONDITION_PATH_EXISTS_GLOB,    0
+Unit.ConditionPathIsDirectory,   config_parse_unit_condition_path,   CONDITION_PATH_IS_DIRECTORY,   0
+Unit.ConditionPathIsSymbolicLink,config_parse_unit_condition_path,   CONDITION_PATH_IS_SYMBOLIC_LINK,0
+Unit.ConditionPathIsMountPoint,  config_parse_unit_condition_path,   CONDITION_PATH_IS_MOUNT_POINT, 0
+Unit.ConditionDirectoryNotEmpty, config_parse_unit_condition_path,   CONDITION_DIRECTORY_NOT_EMPTY, 0
+Unit.ConditionFileIsExecutable,  config_parse_unit_condition_path,   CONDITION_FILE_IS_EXECUTABLE,  0
+Unit.ConditionKernelCommandLine, config_parse_unit_condition_string, CONDITION_KERNEL_COMMAND_LINE, 0
+Unit.ConditionVirtualization,    config_parse_unit_condition_string, CONDITION_VIRTUALIZATION,      0
+Unit.ConditionSecurity,          config_parse_unit_condition_string, CONDITION_SECURITY,            0
+Unit.ConditionCapability,        config_parse_unit_condition_string, CONDITION_CAPABILITY,          0
+Unit.ConditionNull,              config_parse_unit_condition_null,   0,                             0
+m4_dnl
+Service.PIDFile,                 config_parse_unit_path_printf,      0,                             offsetof(Service, pid_file)
+Service.ExecStartPre,            config_parse_exec,                  SERVICE_EXEC_START_PRE,        offsetof(Service, exec_command)
+Service.ExecStart,               config_parse_exec,                  SERVICE_EXEC_START,            offsetof(Service, exec_command)
+Service.ExecStartPost,           config_parse_exec,                  SERVICE_EXEC_START_POST,       offsetof(Service, exec_command)
+Service.ExecReload,              config_parse_exec,                  SERVICE_EXEC_RELOAD,           offsetof(Service, exec_command)
+Service.ExecStop,                config_parse_exec,                  SERVICE_EXEC_STOP,             offsetof(Service, exec_command)
+Service.ExecStopPost,            config_parse_exec,                  SERVICE_EXEC_STOP_POST,        offsetof(Service, exec_command)
+Service.RestartSec,              config_parse_usec,                  0,                             offsetof(Service, restart_usec)
+Service.TimeoutSec,              config_parse_usec,                  0,                             offsetof(Service, timeout_usec)
+Service.WatchdogSec,             config_parse_usec,                  0,                             offsetof(Service, watchdog_usec)
+Service.StartLimitInterval,      config_parse_usec,                  0,                             offsetof(Service, start_limit.interval)
+Service.StartLimitBurst,         config_parse_unsigned,              0,                             offsetof(Service, start_limit.burst)
+Service.StartLimitAction,        config_parse_start_limit_action,    0,                             offsetof(Service, start_limit_action)
+Service.Type,                    config_parse_service_type,          0,                             offsetof(Service, type)
+Service.Restart,                 config_parse_service_restart,       0,                             offsetof(Service, restart)
+Service.PermissionsStartOnly,    config_parse_bool,                  0,                             offsetof(Service, permissions_start_only)
+Service.RootDirectoryStartOnly,  config_parse_bool,                  0,                             offsetof(Service, root_directory_start_only)
+Service.RemainAfterExit,         config_parse_bool,                  0,                             offsetof(Service, remain_after_exit)
+Service.GuessMainPID,            config_parse_bool,                  0,                             offsetof(Service, guess_main_pid)
+m4_ifdef(`HAVE_SYSV_COMPAT',
+`Service.SysVStartPriority,      config_parse_sysv_priority,         0,                             offsetof(Service, sysv_start_priority)',
+`Service.SysVStartPriority,      config_parse_warn_compat,           0,                             0')
+Service.NonBlocking,             config_parse_bool,                  0,                             offsetof(Service, exec_context.non_blocking)
+Service.BusName,                 config_parse_unit_string_printf,    0,                             offsetof(Service, bus_name)
+Service.NotifyAccess,            config_parse_notify_access,         0,                             offsetof(Service, notify_access)
+Service.Sockets,                 config_parse_service_sockets,       0,                             0
+Service.FsckPassNo,              config_parse_fsck_passno,           0,                             offsetof(Service, fsck_passno)
+EXEC_CONTEXT_CONFIG_ITEMS(Service)m4_dnl
+m4_dnl
+Socket.ListenStream,             config_parse_socket_listen,         0,                             0
+Socket.ListenDatagram,           config_parse_socket_listen,         0,                             0
+Socket.ListenSequentialPacket,   config_parse_socket_listen,         0,                             0
+Socket.ListenFIFO,               config_parse_socket_listen,         0,                             0
+Socket.ListenNetlink,            config_parse_socket_listen,         0,                             0
+Socket.ListenSpecial,            config_parse_socket_listen,         0,                             0
+Socket.ListenMessageQueue,       config_parse_socket_listen,         0,                             0
+Socket.BindIPv6Only,             config_parse_socket_bind,           0,                             0,
+Socket.Backlog,                  config_parse_unsigned,              0,                             offsetof(Socket, backlog)
+Socket.BindToDevice,             config_parse_socket_bindtodevice,   0,                             0
+Socket.ExecStartPre,             config_parse_exec,                  SOCKET_EXEC_START_PRE,         offsetof(Socket, exec_command)
+Socket.ExecStartPost,            config_parse_exec,                  SOCKET_EXEC_START_POST,        offsetof(Socket, exec_command)
+Socket.ExecStopPre,              config_parse_exec,                  SOCKET_EXEC_STOP_PRE,          offsetof(Socket, exec_command)
+Socket.ExecStopPost,             config_parse_exec,                  SOCKET_EXEC_STOP_POST,         offsetof(Socket, exec_command)
+Socket.TimeoutSec,               config_parse_usec,                  0,                             offsetof(Socket, timeout_usec)
+Socket.DirectoryMode,            config_parse_mode,                  0,                             offsetof(Socket, directory_mode)
+Socket.SocketMode,               config_parse_mode,                  0,                             offsetof(Socket, socket_mode)
+Socket.Accept,                   config_parse_bool,                  0,                             offsetof(Socket, accept)
+Socket.MaxConnections,           config_parse_unsigned,              0,                             offsetof(Socket, max_connections)
+Socket.KeepAlive,                config_parse_bool,                  0,                             offsetof(Socket, keep_alive)
+Socket.Priority,                 config_parse_int,                   0,                             offsetof(Socket, priority)
+Socket.ReceiveBuffer,            config_parse_bytes_size,            0,                             offsetof(Socket, receive_buffer)
+Socket.SendBuffer,               config_parse_bytes_size,            0,                             offsetof(Socket, send_buffer)
+Socket.IPTOS,                    config_parse_ip_tos,                0,                             offsetof(Socket, ip_tos)
+Socket.IPTTL,                    config_parse_int,                   0,                             offsetof(Socket, ip_ttl)
+Socket.Mark,                     config_parse_int,                   0,                             offsetof(Socket, mark)
+Socket.PipeSize,                 config_parse_bytes_size,            0,                             offsetof(Socket, pipe_size)
+Socket.FreeBind,                 config_parse_bool,                  0,                             offsetof(Socket, free_bind)
+Socket.Transparent,              config_parse_bool,                  0,                             offsetof(Socket, transparent)
+Socket.Broadcast,                config_parse_bool,                  0,                             offsetof(Socket, broadcast)
+Socket.PassCredentials,          config_parse_bool,                  0,                             offsetof(Socket, pass_cred)
+Socket.PassSecurity,             config_parse_bool,                  0,                             offsetof(Socket, pass_sec)
+Socket.TCPCongestion,            config_parse_string,                0,                             offsetof(Socket, tcp_congestion)
+Socket.MessageQueueMaxMessages,  config_parse_long,                  0,                             offsetof(Socket, mq_maxmsg)
+Socket.MessageQueueMessageSize,  config_parse_long,                  0,                             offsetof(Socket, mq_msgsize)
+Socket.Service,                  config_parse_socket_service,        0,                             0
+EXEC_CONTEXT_CONFIG_ITEMS(Socket)m4_dnl
+m4_dnl
+Mount.What,                      config_parse_string,                0,                             offsetof(Mount, parameters_fragment.what)
+Mount.Where,                     config_parse_path,                  0,                             offsetof(Mount, where)
+Mount.Options,                   config_parse_string,                0,                             offsetof(Mount, parameters_fragment.options)
+Mount.Type,                      config_parse_string,                0,                             offsetof(Mount, parameters_fragment.fstype)
+Mount.TimeoutSec,                config_parse_usec,                  0,                             offsetof(Mount, timeout_usec)
+Mount.DirectoryMode,             config_parse_mode,                  0,                             offsetof(Mount, directory_mode)
+EXEC_CONTEXT_CONFIG_ITEMS(Mount)m4_dnl
+m4_dnl
+Automount.Where,                 config_parse_path,                  0,                             offsetof(Automount, where)
+Automount.DirectoryMode,         config_parse_mode,                  0,                             offsetof(Automount, directory_mode)
+m4_dnl
+Swap.What,                       config_parse_path,                  0,                             offsetof(Swap, parameters_fragment.what)
+Swap.Priority,                   config_parse_int,                   0,                             offsetof(Swap, parameters_fragment.priority)
+Swap.TimeoutSec,                 config_parse_usec,                  0,                             offsetof(Swap, timeout_usec)
+EXEC_CONTEXT_CONFIG_ITEMS(Swap)m4_dnl
+m4_dnl
+Timer.OnActiveSec,               config_parse_timer,                 0,                             0
+Timer.OnBootSec,                 config_parse_timer,                 0,                             0
+Timer.OnStartupSec,              config_parse_timer,                 0,                             0
+Timer.OnUnitActiveSec,           config_parse_timer,                 0,                             0
+Timer.OnUnitInactiveSec,         config_parse_timer,                 0,                             0
+Timer.Unit,                      config_parse_timer_unit,            0,                             0
+m4_dnl
+Path.PathExists,                 config_parse_path_spec,             0,                             0
+Path.PathExistsGlob,             config_parse_path_spec,             0,                             0
+Path.PathChanged,                config_parse_path_spec,             0,                             0
+Path.PathModified,               config_parse_path_spec,             0,                             0
+Path.DirectoryNotEmpty,          config_parse_path_spec,             0,                             0
+Path.Unit,                       config_parse_path_unit,             0,                             0
+Path.MakeDirectory,              config_parse_bool,                  0,                             offsetof(Path, make_directory)
+Path.DirectoryMode,              config_parse_mode,                  0,                             offsetof(Path, directory_mode)
+m4_dnl The [Install] section is ignored here.
+Install.Alias,                   NULL,                               0,                             0
+Install.WantedBy,                NULL,                               0,                             0
+Install.Also,                    NULL,                               0,                             0
diff --git a/src/load-fragment.c b/src/load-fragment.c
new file mode 100644 (file)
index 0000000..637c82b
--- /dev/null
@@ -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 <http://www.gnu.org/licenses/>.
+***/
+
+#include <linux/oom.h>
+#include <assert.h>
+#include <errno.h>
+#include <string.h>
+#include <unistd.h>
+#include <fcntl.h>
+#include <sched.h>
+#include <sys/prctl.h>
+#include <sys/mount.h>
+#include <linux/fs.h>
+#include <sys/stat.h>
+#include <sys/time.h>
+#include <sys/resource.h>
+
+#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
new file mode 100644 (file)
index 0000000..79fc76d
--- /dev/null
@@ -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 <http://www.gnu.org/licenses/>.
+***/
+
+#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
new file mode 100644 (file)
index 0000000..7f692e9
--- /dev/null
@@ -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 <http://www.gnu.org/licenses/>.
+***/
+
+#include <string.h>
+#include <stdlib.h>
+#include <errno.h>
+
+#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
new file mode 100644 (file)
index 0000000..09a6bc6
--- /dev/null
@@ -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 <http://www.gnu.org/licenses/>.
+***/
+
+int locale_setup(void);
+
+#endif
diff --git a/src/locale/.gitignore b/src/locale/.gitignore
new file mode 100644 (file)
index 0000000..b1e0ba7
--- /dev/null
@@ -0,0 +1 @@
+org.freedesktop.locale1.policy
diff --git a/src/locale/Makefile b/src/locale/Makefile
new file mode 120000 (symlink)
index 0000000..d0b0e8e
--- /dev/null
@@ -0,0 +1 @@
+../Makefile
\ No newline at end of file
diff --git a/src/locale/generate-kbd-model-map b/src/locale/generate-kbd-model-map
new file mode 100755 (executable)
index 0000000..624c517
--- /dev/null
@@ -0,0 +1,33 @@
+#!/usr/bin/python
+
+import sys
+import system_config_keyboard.keyboard_models
+
+def strdash(s):
+        return s.strip() or '-'
+
+def tab_extend(s, n=1):
+        s = strdash(s)
+        k = len(s) // 8
+
+        if k >= n:
+                f = 1
+        else:
+                f = n - k
+
+        return s + '\t'*f
+
+
+models = system_config_keyboard.keyboard_models.KeyboardModels().get_models()
+
+print "# Generated from system-config-keyboard's model list"
+print "# consolelayout\t\txlayout\txmodel\t\txvariant\txoptions"
+
+for key, value in reversed(models.items()):
+        options = "terminate:ctrl_alt_bksp"
+        if value[4]:
+                options += ',' + value[4]
+
+        print ''.join((tab_extend(key, 3), tab_extend(value[1]),
+                       tab_extend(value[2], 2), tab_extend(value[3], 2),
+                       options))
diff --git a/src/locale/kbd-model-map b/src/locale/kbd-model-map
new file mode 100644 (file)
index 0000000..a895880
--- /dev/null
@@ -0,0 +1,72 @@
+# Generated from system-config-keyboard's model list
+# consolelayout                xlayout xmodel          xvariant        xoptions
+sg                     ch      pc105           de_nodeadkeys   terminate:ctrl_alt_bksp
+nl                     nl      pc105           -               terminate:ctrl_alt_bksp
+mk-utf                 mkd,us  pc105           -               terminate:ctrl_alt_bksp,grp:shifts_toggle,grp_led:scroll
+trq                    tr      pc105           -               terminate:ctrl_alt_bksp
+guj                    in,us   pc105           guj             terminate:ctrl_alt_bksp,grp:shifts_toggle,grp_led:scroll
+uk                     gb      pc105           -               terminate:ctrl_alt_bksp
+is-latin1              is      pc105           -               terminate:ctrl_alt_bksp
+de                     de      pc105           -               terminate:ctrl_alt_bksp
+gur                    gur,us  pc105           -               terminate:ctrl_alt_bksp,grp:shifts_toggle,grp_led:scroll
+la-latin1              latam   pc105           -               terminate:ctrl_alt_bksp
+us                     us      pc105+inet      -               terminate:ctrl_alt_bksp
+ko                     kr      pc105           -               terminate:ctrl_alt_bksp
+ro-std                 ro      pc105           std             terminate:ctrl_alt_bksp
+de-latin1              de      pc105           -               terminate:ctrl_alt_bksp
+tml-inscript           in,us   pc105           tam             terminate:ctrl_alt_bksp,grp:shifts_toggle,grp_led:scroll
+slovene                        si      pc105           -               terminate:ctrl_alt_bksp
+hu101                  hu      pc105           qwerty          terminate:ctrl_alt_bksp
+jp106                  jp      jp106           -               terminate:ctrl_alt_bksp
+croat                  hr      pc105           -               terminate:ctrl_alt_bksp
+ben-probhat            in,us   pc105           ben_probhat     terminate:ctrl_alt_bksp,grp:shifts_toggle,grp_led:scroll
+fi-latin1              fi      pc105           -               terminate:ctrl_alt_bksp
+it2                    it      pc105           -               terminate:ctrl_alt_bksp
+hu                     hu      pc105           -               terminate:ctrl_alt_bksp
+sr-latin               rs      pc105           latin           terminate:ctrl_alt_bksp
+fi                     fi      pc105           -               terminate:ctrl_alt_bksp
+fr_CH                  ch      pc105           fr              terminate:ctrl_alt_bksp
+dk-latin1              dk      pc105           -               terminate:ctrl_alt_bksp
+fr                     fr      pc105           -               terminate:ctrl_alt_bksp
+it                     it      pc105           -               terminate:ctrl_alt_bksp
+tml-uni                        in,us   pc105           tam_TAB         terminate:ctrl_alt_bksp,grp:shifts_toggle,grp_led:scroll
+ua-utf                 ua,us   pc105           -               terminate:ctrl_alt_bksp,grp:shifts_toggle,grp_led:scroll
+fr-latin1              fr      pc105           -               terminate:ctrl_alt_bksp
+sg-latin1              ch      pc105           de_nodeadkeys   terminate:ctrl_alt_bksp
+be-latin1              be      pc105           -               terminate:ctrl_alt_bksp
+dk                     dk      pc105           -               terminate:ctrl_alt_bksp
+fr-pc                  fr      pc105           -               terminate:ctrl_alt_bksp
+bg_pho-utf8            bg,us   pc105           ,phonetic       terminate:ctrl_alt_bksp,grp:shifts_toggle,grp_led:scroll
+it-ibm                 it      pc105           -               terminate:ctrl_alt_bksp
+cz-us-qwertz           cz,us   pc105           -               terminate:ctrl_alt_bksp,grp:shifts_toggle,grp_led:scroll
+ar-digits              ara,us  pc105           digits          terminate:ctrl_alt_bksp,grp:shifts_toggle,grp_led:scroll
+br-abnt2               br      abnt2           -               terminate:ctrl_alt_bksp
+ro                     ro      pc105           -               terminate:ctrl_alt_bksp
+us-acentos             us      pc105           intl            terminate:ctrl_alt_bksp
+pt-latin1              pt      pc105           -               terminate:ctrl_alt_bksp
+ro-std-cedilla         ro      pc105           std_cedilla     terminate:ctrl_alt_bksp
+tj                     tj      pc105           -               terminate:ctrl_alt_bksp
+ar-qwerty              ara,us  pc105           qwerty          terminate:ctrl_alt_bksp,grp:shifts_toggle,grp_led:scroll
+ar-azerty-digits       ara,us  pc105           azerty_digits   terminate:ctrl_alt_bksp,grp:shifts_toggle,grp_led:scroll
+ben                    in,us   pc105           ben             terminate:ctrl_alt_bksp,grp:shifts_toggle,grp_led:scroll
+de-latin1-nodeadkeys   de      pc105           nodeadkeys      terminate:ctrl_alt_bksp
+no                     no      pc105           -               terminate:ctrl_alt_bksp
+bg_bds-utf8            bg,us   pc105           -               terminate:ctrl_alt_bksp,grp:shifts_toggle,grp_led:scroll
+dvorak                 us      pc105           dvorak          terminate:ctrl_alt_bksp
+ru                     ru,us   pc105           -               terminate:ctrl_alt_bksp,grp:shifts_toggle,grp_led:scroll
+cz-lat2                        cz      pc105           qwerty          terminate:ctrl_alt_bksp
+pl2                    pl      pc105           -               terminate:ctrl_alt_bksp
+es                     es      pc105           -               terminate:ctrl_alt_bksp
+ro-cedilla             ro      pc105           cedilla         terminate:ctrl_alt_bksp
+ie                     ie      pc105           -               terminate:ctrl_alt_bksp
+ar-azerty              ara,us  pc105           azerty          terminate:ctrl_alt_bksp,grp:shifts_toggle,grp_led:scroll
+ar-qwerty-digits       ara,us  pc105           qwerty_digits   terminate:ctrl_alt_bksp,grp:shifts_toggle,grp_led:scroll
+et                     ee      pc105           -               terminate:ctrl_alt_bksp
+sk-qwerty              sk      pc105           -               terminate:ctrl_alt_bksp,qwerty
+dev                    dev,us  pc105           -               terminate:ctrl_alt_bksp,grp:shifts_toggle,grp_led:scroll
+fr-latin9              fr      pc105           latin9          terminate:ctrl_alt_bksp
+fr_CH-latin1           ch      pc105           fr              terminate:ctrl_alt_bksp
+cf                     ca(fr)  pc105           -               terminate:ctrl_alt_bksp
+sv-latin1              se      pc105           -               terminate:ctrl_alt_bksp
+sr-cy                  rs      pc105           -               terminate:ctrl_alt_bksp
+gr                     gr,us   pc105           -               terminate:ctrl_alt_bksp,grp:shifts_toggle,grp_led:scroll
diff --git a/src/locale/localed.c b/src/locale/localed.c
new file mode 100644 (file)
index 0000000..e9f9f86
--- /dev/null
@@ -0,0 +1,1437 @@
+/*-*- 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 <http://www.gnu.org/licenses/>.
+***/
+
+#include <dbus/dbus.h>
+
+#include <errno.h>
+#include <string.h>
+#include <unistd.h>
+
+#include "util.h"
+#include "strv.h"
+#include "dbus-common.h"
+#include "polkit.h"
+#include "def.h"
+
+#define INTERFACE                                                       \
+        " <interface name=\"org.freedesktop.locale1\">\n"               \
+        "  <property name=\"Locale\" type=\"as\" access=\"read\"/>\n"   \
+        "  <property name=\"VConsoleKeymap\" type=\"s\" access=\"read\"/>\n" \
+        "  <property name=\"VConsoleKeymapToggle\" type=\"s\" access=\"read\"/>\n" \
+        "  <property name=\"X11Layout\" type=\"s\" access=\"read\"/>\n" \
+        "  <property name=\"X11Model\" type=\"s\" access=\"read\"/>\n"  \
+        "  <property name=\"X11Variant\" type=\"s\" access=\"read\"/>\n" \
+        "  <property name=\"X11Options\" type=\"s\" access=\"read\"/>\n" \
+        "  <method name=\"SetLocale\">\n"                               \
+        "   <arg name=\"locale\" type=\"as\" direction=\"in\"/>\n"      \
+        "   <arg name=\"user_interaction\" type=\"b\" direction=\"in\"/>\n" \
+        "  </method>\n"                                                 \
+        "  <method name=\"SetVConsoleKeyboard\">\n"                      \
+        "   <arg name=\"keymap\" type=\"s\" direction=\"in\"/>\n"       \
+        "   <arg name=\"keymap_toggle\" type=\"s\" direction=\"in\"/>\n" \
+        "   <arg name=\"convert\" type=\"b\" direction=\"in\"/>\n"      \
+        "   <arg name=\"user_interaction\" type=\"b\" direction=\"in\"/>\n" \
+        "  </method>\n"                                                 \
+        "  <method name=\"SetX11Keyboard\">\n"                          \
+        "   <arg name=\"layout\" type=\"s\" direction=\"in\"/>\n"       \
+        "   <arg name=\"model\" type=\"s\" direction=\"in\"/>\n"        \
+        "   <arg name=\"variant\" type=\"s\" direction=\"in\"/>\n"      \
+        "   <arg name=\"options\" type=\"s\" direction=\"in\"/>\n"      \
+        "   <arg name=\"convert\" type=\"b\" direction=\"in\"/>\n"      \
+        "   <arg name=\"user_interaction\" type=\"b\" direction=\"in\"/>\n" \
+        "  </method>\n"                                                 \
+        " </interface>\n"
+
+#define INTROSPECTION                                                   \
+        DBUS_INTROSPECT_1_0_XML_DOCTYPE_DECL_NODE                       \
+        "<node>\n"                                                      \
+        INTERFACE                                                       \
+        BUS_PROPERTIES_INTERFACE                                        \
+        BUS_INTROSPECTABLE_INTERFACE                                    \
+        BUS_PEER_INTERFACE                                              \
+        "</node>\n"
+
+#define INTERFACES_LIST                         \
+        BUS_GENERIC_INTERFACES_LIST             \
+        "org.freedesktop.locale1\0"
+
+const char locale_interface[] _introspect_("locale1") = INTERFACE;
+
+enum {
+        /* We don't list LC_ALL here on purpose. People should be
+         * using LANG instead. */
+
+        PROP_LANG,
+        PROP_LANGUAGE,
+        PROP_LC_CTYPE,
+        PROP_LC_NUMERIC,
+        PROP_LC_TIME,
+        PROP_LC_COLLATE,
+        PROP_LC_MONETARY,
+        PROP_LC_MESSAGES,
+        PROP_LC_PAPER,
+        PROP_LC_NAME,
+        PROP_LC_ADDRESS,
+        PROP_LC_TELEPHONE,
+        PROP_LC_MEASUREMENT,
+        PROP_LC_IDENTIFICATION,
+        _PROP_MAX
+};
+
+static const char * const names[_PROP_MAX] = {
+        [PROP_LANG] = "LANG",
+        [PROP_LANGUAGE] = "LANGUAGE",
+        [PROP_LC_CTYPE] = "LC_CTYPE",
+        [PROP_LC_NUMERIC] = "LC_NUMERIC",
+        [PROP_LC_TIME] = "LC_TIME",
+        [PROP_LC_COLLATE] = "LC_COLLATE",
+        [PROP_LC_MONETARY] = "LC_MONETARY",
+        [PROP_LC_MESSAGES] = "LC_MESSAGES",
+        [PROP_LC_PAPER] = "LC_PAPER",
+        [PROP_LC_NAME] = "LC_NAME",
+        [PROP_LC_ADDRESS] = "LC_ADDRESS",
+        [PROP_LC_TELEPHONE] = "LC_TELEPHONE",
+        [PROP_LC_MEASUREMENT] = "LC_MEASUREMENT",
+        [PROP_LC_IDENTIFICATION] = "LC_IDENTIFICATION"
+};
+
+static char *data[_PROP_MAX] = {
+        NULL,
+        NULL,
+        NULL,
+        NULL,
+        NULL,
+        NULL,
+        NULL,
+        NULL,
+        NULL,
+        NULL,
+        NULL,
+        NULL,
+        NULL
+};
+
+typedef struct State {
+        char *x11_layout, *x11_model, *x11_variant, *x11_options;
+        char *vc_keymap, *vc_keymap_toggle;
+} State;
+
+static State state;
+
+static usec_t remain_until = 0;
+
+static int free_and_set(char **s, const char *v) {
+        int r;
+        char *t;
+
+        assert(s);
+
+        r = strdup_or_null(isempty(v) ? NULL : v, &t);
+        if (r < 0)
+                return r;
+
+        free(*s);
+        *s = t;
+
+        return 0;
+}
+
+static void free_data_locale(void) {
+        int p;
+
+        for (p = 0; p < _PROP_MAX; p++) {
+                free(data[p]);
+                data[p] = NULL;
+        }
+}
+
+static void free_data_x11(void) {
+        free(state.x11_layout);
+        free(state.x11_model);
+        free(state.x11_variant);
+        free(state.x11_options);
+
+        state.x11_layout = state.x11_model = state.x11_variant = state.x11_options = NULL;
+}
+
+static void free_data_vconsole(void) {
+        free(state.vc_keymap);
+        free(state.vc_keymap_toggle);
+
+        state.vc_keymap = state.vc_keymap_toggle = NULL;
+}
+
+static void simplify(void) {
+        int p;
+
+        for (p = 1; p < _PROP_MAX; p++)
+                if (isempty(data[p]) || streq_ptr(data[PROP_LANG], data[p])) {
+                        free(data[p]);
+                        data[p] = NULL;
+                }
+}
+
+static int read_data_locale(void) {
+        int r;
+
+        free_data_locale();
+
+        r = parse_env_file("/etc/locale.conf", NEWLINE,
+                           "LANG",              &data[PROP_LANG],
+                           "LANGUAGE",          &data[PROP_LANGUAGE],
+                           "LC_CTYPE",          &data[PROP_LC_CTYPE],
+                           "LC_NUMERIC",        &data[PROP_LC_NUMERIC],
+                           "LC_TIME",           &data[PROP_LC_TIME],
+                           "LC_COLLATE",        &data[PROP_LC_COLLATE],
+                           "LC_MONETARY",       &data[PROP_LC_MONETARY],
+                           "LC_MESSAGES",       &data[PROP_LC_MESSAGES],
+                           "LC_PAPER",          &data[PROP_LC_PAPER],
+                           "LC_NAME",           &data[PROP_LC_NAME],
+                           "LC_ADDRESS",        &data[PROP_LC_ADDRESS],
+                           "LC_TELEPHONE",      &data[PROP_LC_TELEPHONE],
+                           "LC_MEASUREMENT",    &data[PROP_LC_MEASUREMENT],
+                           "LC_IDENTIFICATION", &data[PROP_LC_IDENTIFICATION],
+                           NULL);
+
+        if (r == -ENOENT) {
+                int p;
+
+                /* Fill in what we got passed from systemd. */
+
+                for (p = 0; p < _PROP_MAX; p++) {
+                        char *e, *d;
+
+                        assert(names[p]);
+
+                        e = getenv(names[p]);
+                        if (e) {
+                                d = strdup(e);
+                                if (!d)
+                                        return -ENOMEM;
+                        } else
+                                d = NULL;
+
+                        free(data[p]);
+                        data[p] = d;
+                }
+
+                r = 0;
+        }
+
+        simplify();
+        return r;
+}
+
+static void free_data(void) {
+        free_data_locale();
+        free_data_vconsole();
+        free_data_x11();
+}
+
+static int read_data_vconsole(void) {
+        int r;
+
+        free_data_vconsole();
+
+        r = parse_env_file("/etc/vconsole.conf", NEWLINE,
+                           "KEYMAP",        &state.vc_keymap,
+                           "KEYMAP_TOGGLE", &state.vc_keymap_toggle,
+                           NULL);
+
+        if (r < 0 && r != -ENOENT)
+                return r;
+
+        return 0;
+}
+
+static int read_data_x11(void) {
+        FILE *f;
+        char line[LINE_MAX];
+        bool in_section = false;
+
+        free_data_x11();
+
+        f = fopen("/etc/X11/xorg.conf.d/00-keyboard.conf", "re");
+        if (!f) {
+                if (errno == ENOENT) {
+
+#ifdef TARGET_FEDORA
+                        f = fopen("/etc/X11/xorg.conf.d/00-system-setup-keyboard.conf", "re");
+                        if (!f) {
+                                if (errno == ENOENT)
+                                        return 0;
+                                else
+                                        return -errno;
+                        }
+#else
+                        return 0;
+#endif
+
+                } else
+                          return -errno;
+        }
+
+        while (fgets(line, sizeof(line), f)) {
+                char *l;
+
+                char_array_0(line);
+                l = strstrip(line);
+
+                if (l[0] == 0 || l[0] == '#')
+                        continue;
+
+                if (in_section && first_word(l, "Option")) {
+                        char **a;
+
+                        a = strv_split_quoted(l);
+                        if (!a) {
+                                fclose(f);
+                                return -ENOMEM;
+                        }
+
+                        if (strv_length(a) == 3) {
+
+                                if (streq(a[1], "XkbLayout")) {
+                                        free(state.x11_layout);
+                                        state.x11_layout = a[2];
+                                        a[2] = NULL;
+                                } else if (streq(a[1], "XkbModel")) {
+                                        free(state.x11_model);
+                                        state.x11_model = a[2];
+                                        a[2] = NULL;
+                                } else if (streq(a[1], "XkbVariant")) {
+                                        free(state.x11_variant);
+                                        state.x11_variant = a[2];
+                                        a[2] = NULL;
+                                } else if (streq(a[1], "XkbOptions")) {
+                                        free(state.x11_options);
+                                        state.x11_options = a[2];
+                                        a[2] = NULL;
+                                }
+                        }
+
+                        strv_free(a);
+
+                } else if (!in_section && first_word(l, "Section")) {
+                        char **a;
+
+                        a = strv_split_quoted(l);
+                        if (!a) {
+                                fclose(f);
+                                return -ENOMEM;
+                        }
+
+                        if (strv_length(a) == 2 && streq(a[1], "InputClass"))
+                                in_section = true;
+
+                        strv_free(a);
+                } else if (in_section && first_word(l, "EndSection"))
+                        in_section = false;
+        }
+
+        fclose(f);
+
+        return 0;
+}
+
+static int read_data(void) {
+        int r, q, p;
+
+        r = read_data_locale();
+        q = read_data_vconsole();
+        p = read_data_x11();
+
+        return r < 0 ? r : q < 0 ? q : p;
+}
+
+static int write_data_locale(void) {
+        int r, p;
+        char **l = NULL;
+
+        r = load_env_file("/etc/locale.conf", &l);
+        if (r < 0 && r != -ENOENT)
+                return r;
+
+        for (p = 0; p < _PROP_MAX; p++) {
+                char *t, **u;
+
+                assert(names[p]);
+
+                if (isempty(data[p])) {
+                        l = strv_env_unset(l, names[p]);
+                        continue;
+                }
+
+                if (asprintf(&t, "%s=%s", names[p], data[p]) < 0) {
+                        strv_free(l);
+                        return -ENOMEM;
+                }
+
+                u = strv_env_set(l, t);
+                free(t);
+                strv_free(l);
+
+                if (!u)
+                        return -ENOMEM;
+
+                l = u;
+        }
+
+        if (strv_isempty(l)) {
+                strv_free(l);
+
+                if (unlink("/etc/locale.conf") < 0)
+                        return errno == ENOENT ? 0 : -errno;
+
+                return 0;
+        }
+
+        r = write_env_file("/etc/locale.conf", l);
+        strv_free(l);
+
+        return r;
+}
+
+static void push_data(DBusConnection *bus) {
+        char **l_set = NULL, **l_unset = NULL, **t;
+        int c_set = 0, c_unset = 0, p;
+        DBusError error;
+        DBusMessage *m = NULL, *reply = NULL;
+        DBusMessageIter iter, sub;
+
+        dbus_error_init(&error);
+
+        assert(bus);
+
+        l_set = new0(char*, _PROP_MAX);
+        l_unset = new0(char*, _PROP_MAX);
+        if (!l_set || !l_unset) {
+                log_error("Out of memory");
+                goto finish;
+        }
+
+        for (p = 0; p < _PROP_MAX; p++) {
+                assert(names[p]);
+
+                if (isempty(data[p]))
+                        l_unset[c_set++] = (char*) names[p];
+                else {
+                        char *s;
+
+                        if (asprintf(&s, "%s=%s", names[p], data[p]) < 0) {
+                                log_error("Out of memory");
+                                goto finish;
+                        }
+
+                        l_set[c_unset++] = s;
+                }
+        }
+
+        assert(c_set + c_unset == _PROP_MAX);
+        m = dbus_message_new_method_call("org.freedesktop.systemd1", "/org/freedesktop/systemd1", "org.freedesktop.systemd1.Manager", "UnsetAndSetEnvironment");
+        if (!m) {
+                log_error("Could not allocate message.");
+                goto finish;
+        }
+
+        dbus_message_iter_init_append(m, &iter);
+
+        if (!dbus_message_iter_open_container(&iter, DBUS_TYPE_ARRAY, "s", &sub)) {
+                log_error("Out of memory.");
+                goto finish;
+        }
+
+        STRV_FOREACH(t, l_unset)
+                if (!dbus_message_iter_append_basic(&sub, DBUS_TYPE_STRING, t)) {
+                        log_error("Out of memory.");
+                        goto finish;
+                }
+
+        if (!dbus_message_iter_close_container(&iter, &sub) ||
+            !dbus_message_iter_open_container(&iter, DBUS_TYPE_ARRAY, "s", &sub)) {
+                log_error("Out of memory.");
+                goto finish;
+        }
+
+        STRV_FOREACH(t, l_set)
+                if (!dbus_message_iter_append_basic(&sub, DBUS_TYPE_STRING, t)) {
+                        log_error("Out of memory.");
+                        goto finish;
+                }
+
+        if (!dbus_message_iter_close_container(&iter, &sub)) {
+                log_error("Out of memory.");
+                goto finish;
+        }
+
+        reply = dbus_connection_send_with_reply_and_block(bus, m, -1, &error);
+        if (!reply) {
+                log_error("Failed to set locale information: %s", bus_error_message(&error));
+                goto finish;
+        }
+
+finish:
+        if (m)
+                dbus_message_unref(m);
+
+        if (reply)
+                dbus_message_unref(reply);
+
+        dbus_error_free(&error);
+
+        strv_free(l_set);
+        free(l_unset);
+}
+
+static int write_data_vconsole(void) {
+        int r;
+        char **l = NULL;
+
+        r = load_env_file("/etc/vconsole.conf", &l);
+        if (r < 0 && r != -ENOENT)
+                return r;
+
+        if (isempty(state.vc_keymap))
+                l = strv_env_unset(l, "KEYMAP");
+        else {
+                char *s, **u;
+
+                s = strappend("KEYMAP=", state.vc_keymap);
+                if (!s) {
+                        strv_free(l);
+                        return -ENOMEM;
+                }
+
+                u = strv_env_set(l, s);
+                free(s);
+                strv_free(l);
+
+                if (!u)
+                        return -ENOMEM;
+
+                l = u;
+        }
+
+        if (isempty(state.vc_keymap_toggle))
+                l = strv_env_unset(l, "KEYMAP_TOGGLE");
+        else  {
+                char *s, **u;
+
+                s = strappend("KEYMAP_TOGGLE=", state.vc_keymap_toggle);
+                if (!s) {
+                        strv_free(l);
+                        return -ENOMEM;
+                }
+
+                u = strv_env_set(l, s);
+                free(s);
+                strv_free(l);
+
+                if (!u)
+                        return -ENOMEM;
+
+                l = u;
+        }
+
+        if (strv_isempty(l)) {
+                strv_free(l);
+
+                if (unlink("/etc/vconsole.conf") < 0)
+                        return errno == ENOENT ? 0 : -errno;
+
+                return 0;
+        }
+
+        r = write_env_file("/etc/vconsole.conf", l);
+        strv_free(l);
+
+        return r;
+}
+
+static int write_data_x11(void) {
+        FILE *f;
+        char *temp_path;
+        int r;
+
+        if (isempty(state.x11_layout) &&
+            isempty(state.x11_model) &&
+            isempty(state.x11_variant) &&
+            isempty(state.x11_options)) {
+
+#ifdef TARGET_FEDORA
+                unlink("/etc/X11/xorg.conf.d/00-system-setup-keyboard.conf");
+
+                /* Symlink this to /dev/null, so that s-s-k (if it is
+                 * still running) doesn't recreate this. */
+                symlink("/dev/null", "/etc/X11/xorg.conf.d/00-system-setup-keyboard.conf");
+#endif
+
+                if (unlink("/etc/X11/xorg.conf.d/00-keyboard.conf") < 0)
+                        return errno == ENOENT ? 0 : -errno;
+
+                return 0;
+        }
+
+        mkdir_parents("/etc/X11/xorg.conf.d", 0755);
+
+        r = fopen_temporary("/etc/X11/xorg.conf.d/00-keyboard.conf", &f, &temp_path);
+        if (r < 0)
+                return r;
+
+        fchmod(fileno(f), 0644);
+
+        fputs("# Read and parsed by systemd-localed. It's probably wise not to edit this file\n"
+              "# manually too freely.\n"
+              "Section \"InputClass\"\n"
+              "        Identifier \"system-keyboard\"\n"
+              "        MatchIsKeyboard \"on\"\n", f);
+
+        if (!isempty(state.x11_layout))
+                fprintf(f, "        Option \"XkbLayout\" \"%s\"\n", state.x11_layout);
+
+        if (!isempty(state.x11_model))
+                fprintf(f, "        Option \"XkbModel\" \"%s\"\n", state.x11_model);
+
+        if (!isempty(state.x11_variant))
+                fprintf(f, "        Option \"XkbVariant\" \"%s\"\n", state.x11_variant);
+
+        if (!isempty(state.x11_options))
+                fprintf(f, "        Option \"XkbOptions\" \"%s\"\n", state.x11_options);
+
+        fputs("EndSection\n", f);
+        fflush(f);
+
+        if (ferror(f) || rename(temp_path, "/etc/X11/xorg.conf.d/00-keyboard.conf") < 0) {
+                r = -errno;
+                unlink("/etc/X11/xorg.conf.d/00-keyboard.conf");
+                unlink(temp_path);
+        } else {
+
+#ifdef TARGET_FEDORA
+                unlink("/etc/X11/xorg.conf.d/00-system-setup-keyboard.conf");
+
+                /* Symlink this to /dev/null, so that s-s-k (if it is
+                 * still running) doesn't recreate this. */
+                symlink("/dev/null", "/etc/X11/xorg.conf.d/00-system-setup-keyboard.conf");
+#endif
+
+                r = 0;
+        }
+
+        fclose(f);
+        free(temp_path);
+
+        return r;
+}
+
+static int load_vconsole_keymap(DBusConnection *bus, DBusError *error) {
+        DBusMessage *m = NULL, *reply = NULL;
+        const char *name = "systemd-vconsole-setup.service", *mode = "replace";
+        int r;
+        DBusError _error;
+
+        assert(bus);
+
+        if (!error) {
+                dbus_error_init(&_error);
+                error = &_error;
+        }
+
+        m = dbus_message_new_method_call(
+                        "org.freedesktop.systemd1",
+                        "/org/freedesktop/systemd1",
+                        "org.freedesktop.systemd1.Manager",
+                        "RestartUnit");
+        if (!m) {
+                log_error("Could not allocate message.");
+                r = -ENOMEM;
+                goto finish;
+        }
+
+        if (!dbus_message_append_args(m,
+                                      DBUS_TYPE_STRING, &name,
+                                      DBUS_TYPE_STRING, &mode,
+                                      DBUS_TYPE_INVALID)) {
+                log_error("Could not append arguments to message.");
+                r = -ENOMEM;
+                goto finish;
+        }
+
+        reply = dbus_connection_send_with_reply_and_block(bus, m, -1, error);
+        if (!reply) {
+                log_error("Failed to issue method call: %s", bus_error_message(error));
+                r = -EIO;
+                goto finish;
+        }
+
+        r = 0;
+
+finish:
+        if (m)
+                dbus_message_unref(m);
+
+        if (reply)
+                dbus_message_unref(reply);
+
+        if (error == &_error)
+                dbus_error_free(error);
+
+        return r;
+}
+
+static char *strnulldash(const char *s) {
+        return s == NULL || *s == 0 || (s[0] == '-' && s[1] == 0) ? NULL : (char*) s;
+}
+
+static int read_next_mapping(FILE *f, unsigned *n, char ***a) {
+        assert(f);
+        assert(n);
+        assert(a);
+
+        for (;;) {
+                char line[LINE_MAX];
+                char *l, **b;
+
+                errno = 0;
+                if (!fgets(line, sizeof(line), f)) {
+
+                        if (ferror(f))
+                                return errno ? -errno : -EIO;
+
+                        return 0;
+                }
+
+                (*n) ++;
+
+                l = strstrip(line);
+                if (l[0] == 0 || l[0] == '#')
+                        continue;
+
+                b = strv_split_quoted(l);
+                if (!b)
+                        return -ENOMEM;
+
+                if (strv_length(b) < 5) {
+                        log_error("Invalid line "SYSTEMD_KBD_MODEL_MAP":%u, ignoring.", *n);
+                        strv_free(b);
+                        continue;
+
+                }
+
+                *a = b;
+                return 1;
+        }
+}
+
+static int convert_vconsole_to_x11(DBusConnection *connection) {
+        bool modified = false;
+
+        assert(connection);
+
+        if (isempty(state.vc_keymap)) {
+
+                modified =
+                        !isempty(state.x11_layout) ||
+                        !isempty(state.x11_model) ||
+                        !isempty(state.x11_variant) ||
+                        !isempty(state.x11_options);
+
+                free_data_x11();
+        } else {
+                FILE *f;
+                unsigned n = 0;
+
+                f = fopen(SYSTEMD_KBD_MODEL_MAP, "re");
+                if (!f)
+                        return -errno;
+
+                for (;;) {
+                        char **a;
+                        int r;
+
+                        r = read_next_mapping(f, &n, &a);
+                        if (r < 0) {
+                                fclose(f);
+                                return r;
+                        }
+
+                        if (r == 0)
+                                break;
+
+                        if (!streq(state.vc_keymap, a[0])) {
+                                strv_free(a);
+                                continue;
+                        }
+
+                        if (!streq_ptr(state.x11_layout, strnulldash(a[1])) ||
+                            !streq_ptr(state.x11_model, strnulldash(a[2])) ||
+                            !streq_ptr(state.x11_variant, strnulldash(a[3])) ||
+                            !streq_ptr(state.x11_options, strnulldash(a[4]))) {
+
+                                if (free_and_set(&state.x11_layout, strnulldash(a[1])) < 0 ||
+                                    free_and_set(&state.x11_model, strnulldash(a[2])) < 0 ||
+                                    free_and_set(&state.x11_variant, strnulldash(a[3])) < 0 ||
+                                    free_and_set(&state.x11_options, strnulldash(a[4])) < 0) {
+                                        strv_free(a);
+                                        fclose(f);
+                                        return -ENOMEM;
+                                }
+
+                                modified = true;
+                        }
+
+                        strv_free(a);
+                        break;
+                }
+
+                fclose(f);
+        }
+
+        if (modified) {
+                dbus_bool_t b;
+                DBusMessage *changed;
+                int r;
+
+                r = write_data_x11();
+                if (r < 0)
+                        log_error("Failed to set X11 keyboard layout: %s", strerror(-r));
+
+                changed = bus_properties_changed_new(
+                                "/org/freedesktop/locale1",
+                                "org.freedesktop.locale1",
+                                "X11Layout\0"
+                                "X11Model\0"
+                                "X11Variant\0"
+                                "X11Options\0");
+
+                if (!changed)
+                        return -ENOMEM;
+
+                b = dbus_connection_send(connection, changed, NULL);
+                dbus_message_unref(changed);
+
+                if (!b)
+                        return -ENOMEM;
+        }
+
+        return 0;
+}
+
+static int convert_x11_to_vconsole(DBusConnection *connection) {
+        bool modified = false;
+
+        assert(connection);
+
+        if (isempty(state.x11_layout)) {
+
+                modified =
+                        !isempty(state.vc_keymap) ||
+                        !isempty(state.vc_keymap_toggle);
+
+                free_data_x11();
+        } else {
+                FILE *f;
+                unsigned n = 0;
+                unsigned best_matching = 0;
+                char *new_keymap = NULL;
+
+                f = fopen(SYSTEMD_KBD_MODEL_MAP, "re");
+                if (!f)
+                        return -errno;
+
+                for (;;) {
+                        char **a;
+                        unsigned matching = 0;
+                        int r;
+
+                        r = read_next_mapping(f, &n, &a);
+                        if (r < 0) {
+                                fclose(f);
+                                return r;
+                        }
+
+                        if (r == 0)
+                                break;
+
+                        /* Determine how well matching this entry is */
+                        if (streq_ptr(state.x11_layout, a[1]))
+                                /* If we got an exact match, this is best */
+                                matching = 10;
+                        else {
+                                size_t x;
+
+                                x = strcspn(state.x11_layout, ",");
+
+                                /* We have multiple X layouts, look
+                                 * for an entry that matches our key
+                                 * with the everything but the first
+                                 * layout stripped off. */
+                                if (x > 0 &&
+                                    strlen(a[1]) == x &&
+                                    strncmp(state.x11_layout, a[1], x) == 0)
+                                        matching = 5;
+                                else  {
+                                        size_t w;
+
+                                        /* If that didn't work, strip
+                                         * off the other layouts from
+                                         * the entry, too */
+
+                                        w = strcspn(a[1], ",");
+
+                                        if (x > 0 && x == w &&
+                                            memcmp(state.x11_layout, a[1], x) == 0)
+                                                matching = 1;
+                                }
+                        }
+
+                        if (matching > 0 &&
+                            streq_ptr(state.x11_model, a[2])) {
+                                matching++;
+
+                                if (streq_ptr(state.x11_variant, a[3])) {
+                                        matching++;
+
+                                        if (streq_ptr(state.x11_options, a[4]))
+                                                matching++;
+                                }
+                        }
+
+                        /* The best matching entry so far, then let's
+                         * save that */
+                        if (matching > best_matching) {
+                                best_matching = matching;
+
+                                free(new_keymap);
+                                new_keymap = strdup(a[0]);
+
+                                if (!new_keymap) {
+                                        strv_free(a);
+                                        fclose(f);
+                                        return -ENOMEM;
+                                }
+                        }
+
+                        strv_free(a);
+                }
+
+                fclose(f);
+
+                if (!streq_ptr(state.vc_keymap, new_keymap)) {
+                        free(state.vc_keymap);
+                        state.vc_keymap = new_keymap;
+
+                        free(state.vc_keymap_toggle);
+                        state.vc_keymap_toggle = NULL;
+
+                        modified = true;
+                } else
+                        free(new_keymap);
+        }
+
+        if (modified) {
+                dbus_bool_t b;
+                DBusMessage *changed;
+                int r;
+
+                r = write_data_vconsole();
+                if (r < 0)
+                        log_error("Failed to set virtual console keymap: %s", strerror(-r));
+
+                changed = bus_properties_changed_new(
+                                "/org/freedesktop/locale1",
+                                "org.freedesktop.locale1",
+                                "VConsoleKeymap\0"
+                                "VConsoleKeymapToggle\0");
+
+                if (!changed)
+                        return -ENOMEM;
+
+                b = dbus_connection_send(connection, changed, NULL);
+                dbus_message_unref(changed);
+
+                if (!b)
+                        return -ENOMEM;
+
+                return load_vconsole_keymap(connection, NULL);
+        }
+
+        return 0;
+}
+
+static int append_locale(DBusMessageIter *i, const char *property, void *userdata) {
+        int r, c = 0, p;
+        char **l;
+
+        l = new0(char*, _PROP_MAX+1);
+        if (!l)
+                return -ENOMEM;
+
+        for (p = 0; p < _PROP_MAX; p++) {
+                char *t;
+
+                if (isempty(data[p]))
+                        continue;
+
+                if (asprintf(&t, "%s=%s", names[p], data[p]) < 0) {
+                        strv_free(l);
+                        return -ENOMEM;
+                }
+
+                l[c++] = t;
+        }
+
+        r = bus_property_append_strv(i, property, (void*) l);
+        strv_free(l);
+
+        return r;
+}
+
+static const BusProperty bus_locale_properties[] = {
+        { "Locale",               append_locale,             "as", 0 },
+        { "X11Layout",            bus_property_append_string, "s", offsetof(State, x11_layout),       true },
+        { "X11Model",             bus_property_append_string, "s", offsetof(State, x11_model),        true },
+        { "X11Variant",           bus_property_append_string, "s", offsetof(State, x11_variant),      true },
+        { "X11Options",           bus_property_append_string, "s", offsetof(State, x11_options),      true },
+        { "VConsoleKeymap",       bus_property_append_string, "s", offsetof(State, vc_keymap),        true },
+        { "VConsoleKeymapToggle", bus_property_append_string, "s", offsetof(State, vc_keymap_toggle), true },
+        { NULL, }
+};
+
+static const BusBoundProperties bps[] = {
+        { "org.freedesktop.locale1", bus_locale_properties, &state },
+        { NULL, }
+};
+
+static DBusHandlerResult locale_message_handler(
+                DBusConnection *connection,
+                DBusMessage *message,
+                void *userdata) {
+
+        DBusMessage *reply = NULL, *changed = NULL;
+        DBusError error;
+        int r;
+
+        assert(connection);
+        assert(message);
+
+        dbus_error_init(&error);
+
+        if (dbus_message_is_method_call(message, "org.freedesktop.locale1", "SetLocale")) {
+                char **l = NULL, **i;
+                dbus_bool_t interactive;
+                DBusMessageIter iter;
+                bool modified = false;
+                bool passed[_PROP_MAX];
+                int p;
+
+                if (!dbus_message_iter_init(message, &iter))
+                        return bus_send_error_reply(connection, message, NULL, -EINVAL);
+
+                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) ||
+                    dbus_message_iter_get_arg_type(&iter) != DBUS_TYPE_BOOLEAN)  {
+                        strv_free(l);
+                        return bus_send_error_reply(connection, message, NULL, -EINVAL);
+                }
+
+                dbus_message_iter_get_basic(&iter, &interactive);
+
+                zero(passed);
+
+                /* Check whether a variable changed and if so valid */
+                STRV_FOREACH(i, l) {
+                        bool valid = false;
+
+                        for (p = 0; p < _PROP_MAX; p++) {
+                                size_t k;
+
+                                k = strlen(names[p]);
+                                if (startswith(*i, names[p]) && (*i)[k] == '=') {
+                                        valid = true;
+                                        passed[p] = true;
+
+                                        if (!streq_ptr(*i + k + 1, data[p]))
+                                                modified = true;
+
+                                        break;
+                                }
+                        }
+
+                        if (!valid) {
+                                strv_free(l);
+                                return bus_send_error_reply(connection, message, NULL, -EINVAL);
+                        }
+                }
+
+                /* Check whether a variable is unset */
+                if (!modified)  {
+                        for (p = 0; p < _PROP_MAX; p++)
+                                if (!isempty(data[p]) && !passed[p]) {
+                                        modified = true;
+                                        break;
+                                }
+                }
+
+                if (modified) {
+
+                        r = verify_polkit(connection, message, "org.freedesktop.locale1.set-locale", interactive, NULL, &error);
+                        if (r < 0) {
+                                strv_free(l);
+                                return bus_send_error_reply(connection, message, &error, r);
+                        }
+
+                        STRV_FOREACH(i, l) {
+                                for (p = 0; p < _PROP_MAX; p++) {
+                                        size_t k;
+
+                                        k = strlen(names[p]);
+                                        if (startswith(*i, names[p]) && (*i)[k] == '=') {
+                                                char *t;
+
+                                                t = strdup(*i + k + 1);
+                                                if (!t) {
+                                                        strv_free(l);
+                                                        goto oom;
+                                                }
+
+                                                free(data[p]);
+                                                data[p] = t;
+
+                                                break;
+                                        }
+                                }
+                        }
+
+                        strv_free(l);
+
+                        for (p = 0; p < _PROP_MAX; p++) {
+                                if (passed[p])
+                                        continue;
+
+                                free(data[p]);
+                                data[p] = NULL;
+                        }
+
+                        simplify();
+
+                        r = write_data_locale();
+                        if (r < 0) {
+                                log_error("Failed to set locale: %s", strerror(-r));
+                                return bus_send_error_reply(connection, message, NULL, r);
+                        }
+
+                        push_data(connection);
+
+                        log_info("Changed locale information.");
+
+                        changed = bus_properties_changed_new(
+                                        "/org/freedesktop/locale1",
+                                        "org.freedesktop.locale1",
+                                        "Locale\0");
+                        if (!changed)
+                                goto oom;
+                }
+        } else if (dbus_message_is_method_call(message, "org.freedesktop.locale1", "SetVConsoleKeyboard")) {
+
+                const char *keymap, *keymap_toggle;
+                dbus_bool_t convert, interactive;
+
+                if (!dbus_message_get_args(
+                                    message,
+                                    &error,
+                                    DBUS_TYPE_STRING, &keymap,
+                                    DBUS_TYPE_STRING, &keymap_toggle,
+                                    DBUS_TYPE_BOOLEAN, &convert,
+                                    DBUS_TYPE_BOOLEAN, &interactive,
+                                    DBUS_TYPE_INVALID))
+                        return bus_send_error_reply(connection, message, &error, -EINVAL);
+
+                if (isempty(keymap))
+                        keymap = NULL;
+
+                if (isempty(keymap_toggle))
+                        keymap_toggle = NULL;
+
+                if (!streq_ptr(keymap, state.vc_keymap) ||
+                    !streq_ptr(keymap_toggle, state.vc_keymap_toggle)) {
+
+                        r = verify_polkit(connection, message, "org.freedesktop.locale1.set-keyboard", interactive, NULL, &error);
+                        if (r < 0)
+                                return bus_send_error_reply(connection, message, &error, r);
+
+                        if (free_and_set(&state.vc_keymap, keymap) < 0 ||
+                            free_and_set(&state.vc_keymap_toggle, keymap_toggle) < 0)
+                                goto oom;
+
+                        r = write_data_vconsole();
+                        if (r < 0) {
+                                log_error("Failed to set virtual console keymap: %s", strerror(-r));
+                                return bus_send_error_reply(connection, message, NULL, r);
+                        }
+
+                        log_info("Changed virtual console keymap to '%s'", strempty(state.vc_keymap));
+
+                        r = load_vconsole_keymap(connection, NULL);
+                        if (r < 0)
+                                log_error("Failed to request keymap reload: %s", strerror(-r));
+
+                        changed = bus_properties_changed_new(
+                                        "/org/freedesktop/locale1",
+                                        "org.freedesktop.locale1",
+                                        "VConsoleKeymap\0"
+                                        "VConsoleKeymapToggle\0");
+                        if (!changed)
+                                goto oom;
+
+                        if (convert) {
+                                r = convert_vconsole_to_x11(connection);
+
+                                if (r < 0)
+                                        log_error("Failed to convert keymap data: %s", strerror(-r));
+                        }
+                }
+
+        } else if (dbus_message_is_method_call(message, "org.freedesktop.locale1", "SetX11Keyboard")) {
+
+                const char *layout, *model, *variant, *options;
+                dbus_bool_t convert, interactive;
+
+                if (!dbus_message_get_args(
+                                    message,
+                                    &error,
+                                    DBUS_TYPE_STRING, &layout,
+                                    DBUS_TYPE_STRING, &model,
+                                    DBUS_TYPE_STRING, &variant,
+                                    DBUS_TYPE_STRING, &options,
+                                    DBUS_TYPE_BOOLEAN, &convert,
+                                    DBUS_TYPE_BOOLEAN, &interactive,
+                                    DBUS_TYPE_INVALID))
+                        return bus_send_error_reply(connection, message, &error, -EINVAL);
+
+                if (isempty(layout))
+                        layout = NULL;
+
+                if (isempty(model))
+                        model = NULL;
+
+                if (isempty(variant))
+                        variant = NULL;
+
+                if (isempty(options))
+                        options = NULL;
+
+                if (!streq_ptr(layout, state.x11_layout) ||
+                    !streq_ptr(model, state.x11_model) ||
+                    !streq_ptr(variant, state.x11_variant) ||
+                    !streq_ptr(options, state.x11_options)) {
+
+                        r = verify_polkit(connection, message, "org.freedesktop.locale1.set-keyboard", interactive, NULL, &error);
+                        if (r < 0)
+                                return bus_send_error_reply(connection, message, &error, r);
+
+                        if (free_and_set(&state.x11_layout, layout) < 0 ||
+                            free_and_set(&state.x11_model, model) < 0 ||
+                            free_and_set(&state.x11_variant, variant) < 0 ||
+                            free_and_set(&state.x11_options, options) < 0)
+                                goto oom;
+
+                        r = write_data_x11();
+                        if (r < 0) {
+                                log_error("Failed to set X11 keyboard layout: %s", strerror(-r));
+                                return bus_send_error_reply(connection, message, NULL, r);
+                        }
+
+                        log_info("Changed X11 keyboard layout to '%s'", strempty(state.x11_layout));
+
+                        changed = bus_properties_changed_new(
+                                        "/org/freedesktop/locale1",
+                                        "org.freedesktop.locale1",
+                                        "X11Layout\0"
+                                        "X11Model\0"
+                                        "X11Variant\0"
+                                        "X11Options\0");
+                        if (!changed)
+                                goto oom;
+
+                        if (convert) {
+                                r = convert_x11_to_vconsole(connection);
+
+                                if (r < 0)
+                                        log_error("Failed to convert keymap data: %s", strerror(-r));
+                        }
+                }
+        } else
+                return bus_default_message_handler(connection, message, INTROSPECTION, INTERFACES_LIST, bps);
+
+        if (!(reply = dbus_message_new_method_return(message)))
+                goto oom;
+
+        if (!dbus_connection_send(connection, reply, NULL))
+                goto oom;
+
+        dbus_message_unref(reply);
+        reply = NULL;
+
+        if (changed) {
+
+                if (!dbus_connection_send(connection, changed, NULL))
+                        goto oom;
+
+                dbus_message_unref(changed);
+        }
+
+        return DBUS_HANDLER_RESULT_HANDLED;
+
+oom:
+        if (reply)
+                dbus_message_unref(reply);
+
+        if (changed)
+                dbus_message_unref(changed);
+
+        dbus_error_free(&error);
+
+        return DBUS_HANDLER_RESULT_NEED_MEMORY;
+}
+
+static int connect_bus(DBusConnection **_bus) {
+        static const DBusObjectPathVTable locale_vtable = {
+                .message_function = locale_message_handler
+        };
+        DBusError error;
+        DBusConnection *bus = NULL;
+        int r;
+
+        assert(_bus);
+
+        dbus_error_init(&error);
+
+        bus = dbus_bus_get_private(DBUS_BUS_SYSTEM, &error);
+        if (!bus) {
+                log_error("Failed to get system D-Bus connection: %s", bus_error_message(&error));
+                r = -ECONNREFUSED;
+                goto fail;
+        }
+
+        dbus_connection_set_exit_on_disconnect(bus, FALSE);
+
+        if (!dbus_connection_register_object_path(bus, "/org/freedesktop/locale1", &locale_vtable, NULL) ||
+            !dbus_connection_add_filter(bus, bus_exit_idle_filter, &remain_until, NULL)) {
+                log_error("Not enough memory");
+                r = -ENOMEM;
+                goto fail;
+        }
+
+        r = dbus_bus_request_name(bus, "org.freedesktop.locale1", DBUS_NAME_FLAG_DO_NOT_QUEUE, &error);
+        if (dbus_error_is_set(&error)) {
+                log_error("Failed to register name on bus: %s", bus_error_message(&error));
+                r = -EEXIST;
+                goto fail;
+        }
+
+        if (r != DBUS_REQUEST_NAME_REPLY_PRIMARY_OWNER) {
+                log_error("Failed to acquire name.");
+                r = -EEXIST;
+                goto fail;
+        }
+
+        if (_bus)
+                *_bus = bus;
+
+        return 0;
+
+fail:
+        dbus_connection_close(bus);
+        dbus_connection_unref(bus);
+
+        dbus_error_free(&error);
+
+        return r;
+}
+
+int main(int argc, char *argv[]) {
+        int r;
+        DBusConnection *bus = NULL;
+        bool exiting = false;
+
+        log_set_target(LOG_TARGET_AUTO);
+        log_parse_environment();
+        log_open();
+
+        umask(0022);
+
+        if (argc == 2 && streq(argv[1], "--introspect")) {
+                fputs(DBUS_INTROSPECT_1_0_XML_DOCTYPE_DECL_NODE
+                      "<node>\n", stdout);
+                fputs(locale_interface, stdout);
+                fputs("</node>\n", stdout);
+                return 0;
+        }
+
+        if (argc != 1) {
+                log_error("This program takes no arguments.");
+                r = -EINVAL;
+                goto finish;
+        }
+
+        r = read_data();
+        if (r < 0) {
+                log_error("Failed to read locale data: %s", strerror(-r));
+                goto finish;
+        }
+
+        r = connect_bus(&bus);
+        if (r < 0)
+                goto finish;
+
+        remain_until = now(CLOCK_MONOTONIC) + DEFAULT_EXIT_USEC;
+        for (;;) {
+
+                if (!dbus_connection_read_write_dispatch(bus, exiting ? -1 : (int) (DEFAULT_EXIT_USEC/USEC_PER_MSEC)))
+                        break;
+
+                if (!exiting && remain_until < now(CLOCK_MONOTONIC)) {
+                        exiting = true;
+                        bus_async_unregister_and_exit(bus, "org.freedesktop.locale1");
+                }
+        }
+
+        r = 0;
+
+finish:
+        free_data();
+
+        if (bus) {
+                dbus_connection_flush(bus);
+                dbus_connection_close(bus);
+                dbus_connection_unref(bus);
+        }
+
+        return r < 0 ? EXIT_FAILURE : EXIT_SUCCESS;
+}
diff --git a/src/locale/org.freedesktop.locale1.conf b/src/locale/org.freedesktop.locale1.conf
new file mode 100644 (file)
index 0000000..6827331
--- /dev/null
@@ -0,0 +1,27 @@
+<?xml version="1.0"?> <!--*-nxml-*-->
+<!DOCTYPE busconfig PUBLIC "-//freedesktop//DTD D-BUS Bus Configuration 1.0//EN"
+        "http://www.freedesktop.org/standards/dbus/1.0/busconfig.dtd">
+
+<!--
+  This file is part of systemd.
+
+  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.
+-->
+
+<busconfig>
+
+        <policy user="root">
+                <allow own="org.freedesktop.locale1"/>
+                <allow send_destination="org.freedesktop.locale1"/>
+                <allow receive_sender="org.freedesktop.locale1"/>
+        </policy>
+
+        <policy context="default">
+                <allow send_destination="org.freedesktop.locale1"/>
+                <allow receive_sender="org.freedesktop.locale1"/>
+        </policy>
+
+</busconfig>
diff --git a/src/locale/org.freedesktop.locale1.policy.in b/src/locale/org.freedesktop.locale1.policy.in
new file mode 100644 (file)
index 0000000..1ac50bf
--- /dev/null
@@ -0,0 +1,39 @@
+<?xml version="1.0" encoding="UTF-8"?> <!--*-nxml-*-->
+<!DOCTYPE policyconfig PUBLIC "-//freedesktop//DTD PolicyKit Policy Configuration 1.0//EN"
+        "http://www.freedesktop.org/standards/PolicyKit/1/policyconfig.dtd">
+
+<!--
+  This file is part of systemd.
+
+  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.
+-->
+
+<policyconfig>
+
+        <vendor>The systemd Project</vendor>
+        <vendor_url>http://www.freedesktop.org/wiki/Software/systemd</vendor_url>
+
+        <action id="org.freedesktop.locale1.set-locale">
+                <_description>Set system locale</_description>
+                <_message>Authentication is required to set the system locale.</_message>
+                <defaults>
+                        <allow_any>auth_admin_keep</allow_any>
+                        <allow_inactive>auth_admin_keep</allow_inactive>
+                        <allow_active>auth_admin_keep</allow_active>
+                </defaults>
+        </action>
+
+        <action id="org.freedesktop.locale1.set-keyboard">
+                <_description>Set system keyboard settings</_description>
+                <_message>Authentication is required to set the system keyboard settings.</_message>
+                <defaults>
+                        <allow_any>auth_admin_keep</allow_any>
+                        <allow_inactive>auth_admin_keep</allow_inactive>
+                        <allow_active>auth_admin_keep</allow_active>
+                </defaults>
+        </action>
+
+</policyconfig>
diff --git a/src/locale/org.freedesktop.locale1.service b/src/locale/org.freedesktop.locale1.service
new file mode 100644 (file)
index 0000000..29bd582
--- /dev/null
@@ -0,0 +1,12 @@
+#  This file is part of systemd.
+#
+#  systemd is free software; you can redistribute it and/or modify it
+#  under the terms of the GNU General Public License as published by
+#  the Free Software Foundation; either version 2 of the License, or
+#  (at your option) any later version.
+
+[D-BUS Service]
+Name=org.freedesktop.locale1
+Exec=/bin/false
+User=root
+SystemdService=dbus-org.freedesktop.locale1.service
diff --git a/src/log.c b/src/log.c
new file mode 100644 (file)
index 0000000..9fffc1d
--- /dev/null
+++ b/src/log.c
@@ -0,0 +1,747 @@
+/*-*- 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 <http://www.gnu.org/licenses/>.
+***/
+
+#include <stdarg.h>
+#include <stdio.h>
+#include <errno.h>
+#include <unistd.h>
+#include <fcntl.h>
+#include <sys/socket.h>
+#include <sys/un.h>
+#include <stddef.h>
+
+#include "log.h"
+#include "util.h"
+#include "macro.h"
+#include "socket-util.h"
+
+#define SNDBUF_SIZE (8*1024*1024)
+
+static LogTarget log_target = LOG_TARGET_CONSOLE;
+static int log_max_level = LOG_INFO;
+static int log_facility = LOG_DAEMON;
+
+static int console_fd = STDERR_FILENO;
+static int syslog_fd = -1;
+static int kmsg_fd = -1;
+static int journal_fd = -1;
+
+static bool syslog_is_stream = false;
+
+static bool show_color = false;
+static bool show_location = false;
+
+/* Akin to glibc's __abort_msg; which is private and we hence cannot
+ * use here. */
+static char *log_abort_msg = NULL;
+
+void log_close_console(void) {
+
+        if (console_fd < 0)
+                return;
+
+        if (getpid() == 1) {
+                if (console_fd >= 3)
+                        close_nointr_nofail(console_fd);
+
+                console_fd = -1;
+        }
+}
+
+static int log_open_console(void) {
+
+        if (console_fd >= 0)
+                return 0;
+
+        if (getpid() == 1) {
+
+                console_fd = open_terminal("/dev/console", O_WRONLY|O_NOCTTY|O_CLOEXEC);
+                if (console_fd < 0) {
+                        log_error("Failed to open /dev/console for logging: %s", strerror(-console_fd));
+                        return console_fd;
+                }
+
+                log_debug("Successfully opened /dev/console for logging.");
+        } else
+                console_fd = STDERR_FILENO;
+
+        return 0;
+}
+
+void log_close_kmsg(void) {
+
+        if (kmsg_fd < 0)
+                return;
+
+        close_nointr_nofail(kmsg_fd);
+        kmsg_fd = -1;
+}
+
+static int log_open_kmsg(void) {
+
+        if (kmsg_fd >= 0)
+                return 0;
+
+        kmsg_fd = open("/dev/kmsg", O_WRONLY|O_NOCTTY|O_CLOEXEC);
+        if (kmsg_fd < 0) {
+                log_error("Failed to open /dev/kmsg for logging: %s", strerror(errno));
+                return -errno;
+        }
+
+        log_debug("Successfully opened /dev/kmsg for logging.");
+
+        return 0;
+}
+
+void log_close_syslog(void) {
+
+        if (syslog_fd < 0)
+                return;
+
+        close_nointr_nofail(syslog_fd);
+        syslog_fd = -1;
+}
+
+static int create_log_socket(int type) {
+        int fd;
+
+        /* All output to the syslog/journal fds we do asynchronously,
+         * and if the buffers are full we just drop the messages */
+
+        fd = socket(AF_UNIX, type|SOCK_CLOEXEC|SOCK_NONBLOCK, 0);
+        if (fd < 0)
+                return -errno;
+
+        fd_inc_sndbuf(fd, SNDBUF_SIZE);
+
+        return fd;
+}
+
+static int log_open_syslog(void) {
+        union sockaddr_union sa;
+        int r;
+
+        if (syslog_fd >= 0)
+                return 0;
+
+        zero(sa);
+        sa.un.sun_family = AF_UNIX;
+        strncpy(sa.un.sun_path, "/dev/log", sizeof(sa.un.sun_path));
+
+        syslog_fd = create_log_socket(SOCK_DGRAM);
+        if (syslog_fd < 0) {
+                r = syslog_fd;
+                goto fail;
+        }
+
+        if (connect(syslog_fd, &sa.sa, offsetof(struct sockaddr_un, sun_path) + strlen(sa.un.sun_path)) < 0) {
+                close_nointr_nofail(syslog_fd);
+
+                /* Some legacy syslog systems still use stream
+                 * sockets. They really shouldn't. But what can we
+                 * do... */
+                syslog_fd = create_log_socket(SOCK_STREAM);
+                if (syslog_fd < 0) {
+                        r = syslog_fd;
+                        goto fail;
+                }
+
+                if (connect(syslog_fd, &sa.sa, offsetof(struct sockaddr_un, sun_path) + strlen(sa.un.sun_path)) < 0) {
+                        r = -errno;
+                        goto fail;
+                }
+
+                syslog_is_stream = true;
+        } else
+                syslog_is_stream = false;
+
+        log_debug("Successfully opened syslog for logging.");
+
+        return 0;
+
+fail:
+        log_close_syslog();
+        log_debug("Failed to open syslog for logging: %s", strerror(-r));
+        return r;
+}
+
+void log_close_journal(void) {
+
+        if (journal_fd < 0)
+                return;
+
+        close_nointr_nofail(journal_fd);
+        journal_fd = -1;
+}
+
+static int log_open_journal(void) {
+        union sockaddr_union sa;
+        int r;
+
+        if (journal_fd >= 0)
+                return 0;
+
+        journal_fd = create_log_socket(SOCK_DGRAM);
+        if (journal_fd < 0) {
+                r = journal_fd;
+                goto fail;
+        }
+
+        zero(sa);
+        sa.un.sun_family = AF_UNIX;
+        strncpy(sa.un.sun_path, "/run/systemd/journal/socket", sizeof(sa.un.sun_path));
+
+        if (connect(journal_fd, &sa.sa, offsetof(struct sockaddr_un, sun_path) + strlen(sa.un.sun_path)) < 0) {
+                r = -errno;
+                goto fail;
+        }
+
+        log_debug("Successfully opened journal for logging.");
+
+        return 0;
+
+fail:
+        log_close_journal();
+        log_debug("Failed to open journal for logging: %s", strerror(-r));
+        return r;
+}
+
+int log_open(void) {
+        int r;
+
+        /* If we don't use the console we close it here, to not get
+         * killed by SAK. If we don't use syslog we close it here so
+         * that we are not confused by somebody deleting the socket in
+         * the fs. If we don't use /dev/kmsg we still keep it open,
+         * because there is no reason to close it. */
+
+        if (log_target == LOG_TARGET_NULL) {
+                log_close_journal();
+                log_close_syslog();
+                log_close_console();
+                return 0;
+        }
+
+        if (log_target != LOG_TARGET_AUTO ||
+            getpid() == 1 ||
+            isatty(STDERR_FILENO) <= 0) {
+
+                if (log_target == LOG_TARGET_AUTO ||
+                    log_target == LOG_TARGET_JOURNAL_OR_KMSG ||
+                    log_target == LOG_TARGET_JOURNAL) {
+                        r = log_open_journal();
+                        if (r >= 0) {
+                                log_close_syslog();
+                                log_close_console();
+                                return r;
+                        }
+                }
+
+                if (log_target == LOG_TARGET_SYSLOG_OR_KMSG ||
+                    log_target == LOG_TARGET_SYSLOG) {
+                        r = log_open_syslog();
+                        if (r >= 0) {
+                                log_close_journal();
+                                log_close_console();
+                                return r;
+                        }
+                }
+
+                if (log_target == LOG_TARGET_AUTO ||
+                    log_target == LOG_TARGET_JOURNAL_OR_KMSG ||
+                    log_target == LOG_TARGET_SYSLOG_OR_KMSG ||
+                    log_target == LOG_TARGET_KMSG) {
+                        r = log_open_kmsg();
+                        if (r >= 0) {
+                                log_close_journal();
+                                log_close_syslog();
+                                log_close_console();
+                                return r;
+                        }
+                }
+        }
+
+        log_close_journal();
+        log_close_syslog();
+
+        /* Get the real /dev/console if we are PID=1, hence reopen */
+        log_close_console();
+        return log_open_console();
+}
+
+void log_set_target(LogTarget target) {
+        assert(target >= 0);
+        assert(target < _LOG_TARGET_MAX);
+
+        log_target = target;
+}
+
+void log_close(void) {
+        log_close_journal();
+        log_close_syslog();
+        log_close_kmsg();
+        log_close_console();
+}
+
+void log_forget_fds(void) {
+        console_fd = kmsg_fd = syslog_fd = journal_fd = -1;
+}
+
+void log_set_max_level(int level) {
+        assert((level & LOG_PRIMASK) == level);
+
+        log_max_level = level;
+}
+
+void log_set_facility(int facility) {
+        log_facility = facility;
+}
+
+static int write_to_console(
+                int level,
+                const char*file,
+                int line,
+                const char *func,
+                const char *buffer) {
+
+        char location[64];
+        struct iovec iovec[5];
+        unsigned n = 0;
+        bool highlight;
+
+        if (console_fd < 0)
+                return 0;
+
+        highlight = LOG_PRI(level) <= LOG_ERR && show_color;
+
+        zero(iovec);
+
+        if (show_location) {
+                snprintf(location, sizeof(location), "(%s:%u) ", file, line);
+                char_array_0(location);
+                IOVEC_SET_STRING(iovec[n++], location);
+        }
+
+        if (highlight)
+                IOVEC_SET_STRING(iovec[n++], ANSI_HIGHLIGHT_RED_ON);
+        IOVEC_SET_STRING(iovec[n++], buffer);
+        if (highlight)
+                IOVEC_SET_STRING(iovec[n++], ANSI_HIGHLIGHT_OFF);
+        IOVEC_SET_STRING(iovec[n++], "\n");
+
+        if (writev(console_fd, iovec, n) < 0)
+                return -errno;
+
+        return 1;
+}
+
+static int write_to_syslog(
+        int level,
+        const char*file,
+        int line,
+        const char *func,
+        const char *buffer) {
+
+        char header_priority[16], header_time[64], header_pid[16];
+        struct iovec iovec[5];
+        struct msghdr msghdr;
+        time_t t;
+        struct tm *tm;
+
+        if (syslog_fd < 0)
+                return 0;
+
+        snprintf(header_priority, sizeof(header_priority), "<%i>", level);
+        char_array_0(header_priority);
+
+        t = (time_t) (now(CLOCK_REALTIME) / USEC_PER_SEC);
+        if (!(tm = localtime(&t)))
+                return -EINVAL;
+
+        if (strftime(header_time, sizeof(header_time), "%h %e %T ", tm) <= 0)
+                return -EINVAL;
+
+        snprintf(header_pid, sizeof(header_pid), "[%lu]: ", (unsigned long) getpid());
+        char_array_0(header_pid);
+
+        zero(iovec);
+        IOVEC_SET_STRING(iovec[0], header_priority);
+        IOVEC_SET_STRING(iovec[1], header_time);
+        IOVEC_SET_STRING(iovec[2], program_invocation_short_name);
+        IOVEC_SET_STRING(iovec[3], header_pid);
+        IOVEC_SET_STRING(iovec[4], buffer);
+
+        /* When using syslog via SOCK_STREAM separate the messages by NUL chars */
+        if (syslog_is_stream)
+                iovec[4].iov_len++;
+
+        zero(msghdr);
+        msghdr.msg_iov = iovec;
+        msghdr.msg_iovlen = ELEMENTSOF(iovec);
+
+        for (;;) {
+                ssize_t n;
+
+                n = sendmsg(syslog_fd, &msghdr, MSG_NOSIGNAL);
+                if (n < 0)
+                        return -errno;
+
+                if (!syslog_is_stream ||
+                    (size_t) n >= IOVEC_TOTAL_SIZE(iovec, ELEMENTSOF(iovec)))
+                        break;
+
+                IOVEC_INCREMENT(iovec, ELEMENTSOF(iovec), n);
+        }
+
+        return 1;
+}
+
+static int write_to_kmsg(
+        int level,
+        const char*file,
+        int line,
+        const char *func,
+        const char *buffer) {
+
+        char header_priority[16], header_pid[16];
+        struct iovec iovec[5];
+
+        if (kmsg_fd < 0)
+                return 0;
+
+        snprintf(header_priority, sizeof(header_priority), "<%i>", level);
+        char_array_0(header_priority);
+
+        snprintf(header_pid, sizeof(header_pid), "[%lu]: ", (unsigned long) getpid());
+        char_array_0(header_pid);
+
+        zero(iovec);
+        IOVEC_SET_STRING(iovec[0], header_priority);
+        IOVEC_SET_STRING(iovec[1], program_invocation_short_name);
+        IOVEC_SET_STRING(iovec[2], header_pid);
+        IOVEC_SET_STRING(iovec[3], buffer);
+        IOVEC_SET_STRING(iovec[4], "\n");
+
+        if (writev(kmsg_fd, iovec, ELEMENTSOF(iovec)) < 0)
+                return -errno;
+
+        return 1;
+}
+
+static int write_to_journal(
+        int level,
+        const char*file,
+        int line,
+        const char *func,
+        const char *buffer) {
+
+        char header[LINE_MAX];
+        struct iovec iovec[3];
+        struct msghdr mh;
+
+        if (journal_fd < 0)
+                return 0;
+
+        snprintf(header, sizeof(header),
+                 "PRIORITY=%i\n"
+                 "SYSLOG_FACILITY=%i\n"
+                 "CODE_FILE=%s\n"
+                 "CODE_LINE=%i\n"
+                 "CODE_FUNCTION=%s\n"
+                 "MESSAGE=",
+                 LOG_PRI(level),
+                 LOG_FAC(level),
+                 file,
+                 line,
+                 func);
+
+        char_array_0(header);
+
+        zero(iovec);
+        IOVEC_SET_STRING(iovec[0], header);
+        IOVEC_SET_STRING(iovec[1], buffer);
+        IOVEC_SET_STRING(iovec[2], "\n");
+
+        zero(mh);
+        mh.msg_iov = iovec;
+        mh.msg_iovlen = ELEMENTSOF(iovec);
+
+        if (sendmsg(journal_fd, &mh, MSG_NOSIGNAL) < 0)
+                return -errno;
+
+        return 1;
+}
+
+static int log_dispatch(
+        int level,
+        const char*file,
+        int line,
+        const char *func,
+        char *buffer) {
+
+        int r = 0;
+
+        if (log_target == LOG_TARGET_NULL)
+                return 0;
+
+        /* Patch in LOG_DAEMON facility if necessary */
+        if ((level & LOG_FACMASK) == 0)
+                level = log_facility | LOG_PRI(level);
+
+        do {
+                char *e;
+                int k = 0;
+
+                buffer += strspn(buffer, NEWLINE);
+
+                if (buffer[0] == 0)
+                        break;
+
+                if ((e = strpbrk(buffer, NEWLINE)))
+                        *(e++) = 0;
+
+                if (log_target == LOG_TARGET_AUTO ||
+                    log_target == LOG_TARGET_JOURNAL_OR_KMSG ||
+                    log_target == LOG_TARGET_JOURNAL) {
+
+                        k = write_to_journal(level, file, line, func, buffer);
+                        if (k < 0) {
+                                if (k != -EAGAIN)
+                                        log_close_journal();
+                                log_open_kmsg();
+                        } else if (k > 0)
+                                r++;
+                }
+
+                if (log_target == LOG_TARGET_SYSLOG_OR_KMSG ||
+                    log_target == LOG_TARGET_SYSLOG) {
+
+                        k = write_to_syslog(level, file, line, func, buffer);
+                        if (k < 0) {
+                                if (k != -EAGAIN)
+                                        log_close_syslog();
+                                log_open_kmsg();
+                        } else if (k > 0)
+                                r++;
+                }
+
+                if (k <= 0 &&
+                    (log_target == LOG_TARGET_AUTO ||
+                     log_target == LOG_TARGET_SYSLOG_OR_KMSG ||
+                     log_target == LOG_TARGET_KMSG)) {
+
+                        k = write_to_kmsg(level, file, line, func, buffer);
+                        if (k < 0) {
+                                log_close_kmsg();
+                                log_open_console();
+                        } else if (k > 0)
+                                r++;
+                }
+
+                if (k <= 0) {
+                        k = write_to_console(level, file, line, func, buffer);
+                        if (k < 0)
+                                return k;
+                }
+
+                buffer = e;
+        } while (buffer);
+
+        return r;
+}
+
+int log_dump_internal(
+        int level,
+        const char*file,
+        int line,
+        const char *func,
+        char *buffer) {
+
+        int saved_errno, r;
+
+        /* This modifies the buffer... */
+
+        if (_likely_(LOG_PRI(level) > log_max_level))
+                return 0;
+
+        saved_errno = errno;
+        r = log_dispatch(level, file, line, func, buffer);
+        errno = saved_errno;
+
+        return r;
+}
+
+int log_metav(
+        int level,
+        const char*file,
+        int line,
+        const char *func,
+        const char *format,
+        va_list ap) {
+
+        char buffer[LINE_MAX];
+        int saved_errno, r;
+
+        if (_likely_(LOG_PRI(level) > log_max_level))
+                return 0;
+
+        saved_errno = errno;
+        vsnprintf(buffer, sizeof(buffer), format, ap);
+        char_array_0(buffer);
+
+        r = log_dispatch(level, file, line, func, buffer);
+        errno = saved_errno;
+
+        return r;
+}
+
+int log_meta(
+        int level,
+        const char*file,
+        int line,
+        const char *func,
+        const char *format, ...) {
+
+        int r;
+        va_list ap;
+
+        va_start(ap, format);
+        r = log_metav(level, file, line, func, format, ap);
+        va_end(ap);
+
+        return r;
+}
+
+#pragma GCC diagnostic push
+#pragma GCC diagnostic ignored "-Wformat-nonliteral"
+_noreturn_ static void log_assert(const char *text, const char *file, int line, const char *func, const char *format) {
+        static char buffer[LINE_MAX];
+
+        snprintf(buffer, sizeof(buffer), format, text, file, line, func);
+
+        char_array_0(buffer);
+        log_abort_msg = buffer;
+
+        log_dispatch(LOG_CRIT, file, line, func, buffer);
+        abort();
+}
+#pragma GCC diagnostic pop
+
+_noreturn_ void log_assert_failed(const char *text, const char *file, int line, const char *func) {
+        log_assert(text, file, line, func, "Assertion '%s' failed at %s:%u, function %s(). Aborting.");
+}
+
+_noreturn_ void log_assert_failed_unreachable(const char *text, const char *file, int line, const char *func) {
+        log_assert(text, file, line, func, "Code should not be reached '%s' at %s:%u, function %s(). Aborting.");
+}
+
+int log_set_target_from_string(const char *e) {
+        LogTarget t;
+
+        t = log_target_from_string(e);
+        if (t < 0)
+                return -EINVAL;
+
+        log_set_target(t);
+        return 0;
+}
+
+int log_set_max_level_from_string(const char *e) {
+        int t;
+
+        t = log_level_from_string(e);
+        if (t < 0)
+                return t;
+
+        log_set_max_level(t);
+        return 0;
+}
+
+void log_parse_environment(void) {
+        const char *e;
+
+        if ((e = getenv("SYSTEMD_LOG_TARGET")))
+                if (log_set_target_from_string(e) < 0)
+                        log_warning("Failed to parse log target %s. Ignoring.", e);
+
+        if ((e = getenv("SYSTEMD_LOG_LEVEL")))
+                if (log_set_max_level_from_string(e) < 0)
+                        log_warning("Failed to parse log level %s. Ignoring.", e);
+
+        if ((e = getenv("SYSTEMD_LOG_COLOR")))
+                if (log_show_color_from_string(e) < 0)
+                        log_warning("Failed to parse bool %s. Ignoring.", e);
+
+        if ((e = getenv("SYSTEMD_LOG_LOCATION")))
+                if (log_show_location_from_string(e) < 0)
+                        log_warning("Failed to parse bool %s. Ignoring.", e);
+}
+
+LogTarget log_get_target(void) {
+        return log_target;
+}
+
+int log_get_max_level(void) {
+        return log_max_level;
+}
+
+void log_show_color(bool b) {
+        show_color = b;
+}
+
+void log_show_location(bool b) {
+        show_location = b;
+}
+
+int log_show_color_from_string(const char *e) {
+        int t;
+
+        t = parse_boolean(e);
+        if (t < 0)
+                return t;
+
+        log_show_color(t);
+        return 0;
+}
+
+int log_show_location_from_string(const char *e) {
+        int t;
+
+        t = parse_boolean(e);
+        if (t < 0)
+                return t;
+
+        log_show_location(t);
+        return 0;
+}
+
+static const char *const log_target_table[] = {
+        [LOG_TARGET_CONSOLE] = "console",
+        [LOG_TARGET_KMSG] = "kmsg",
+        [LOG_TARGET_JOURNAL] = "journal",
+        [LOG_TARGET_JOURNAL_OR_KMSG] = "journal-or-kmsg",
+        [LOG_TARGET_SYSLOG] = "syslog",
+        [LOG_TARGET_SYSLOG_OR_KMSG] = "syslog-or-kmsg",
+        [LOG_TARGET_AUTO] = "auto",
+        [LOG_TARGET_NULL] = "null"
+};
+
+DEFINE_STRING_TABLE_LOOKUP(log_target, LogTarget);
diff --git a/src/log.h b/src/log.h
new file mode 100644 (file)
index 0000000..3283808
--- /dev/null
+++ b/src/log.h
@@ -0,0 +1,111 @@
+/*-*- Mode: C; c-basic-offset: 8; indent-tabs-mode: nil -*-*/
+
+#ifndef foologhfoo
+#define foologhfoo
+
+/***
+  This file is part 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 <http://www.gnu.org/licenses/>.
+***/
+
+#include <syslog.h>
+#include <stdbool.h>
+#include <stdarg.h>
+
+#include "macro.h"
+
+typedef enum LogTarget{
+        LOG_TARGET_CONSOLE,
+        LOG_TARGET_KMSG,
+        LOG_TARGET_JOURNAL,
+        LOG_TARGET_JOURNAL_OR_KMSG,
+        LOG_TARGET_SYSLOG,
+        LOG_TARGET_SYSLOG_OR_KMSG,
+        LOG_TARGET_AUTO, /* console if stderr is tty, JOURNAL_OR_KMSG otherwise */
+        LOG_TARGET_NULL,
+        _LOG_TARGET_MAX,
+        _LOG_TARGET_INVALID = -1
+}  LogTarget;
+
+void log_set_target(LogTarget target);
+void log_set_max_level(int level);
+void log_set_facility(int facility);
+
+int log_set_target_from_string(const char *e);
+int log_set_max_level_from_string(const char *e);
+
+void log_show_color(bool b);
+void log_show_location(bool b);
+
+int log_show_color_from_string(const char *e);
+int log_show_location_from_string(const char *e);
+
+LogTarget log_get_target(void);
+int log_get_max_level(void);
+
+int log_open(void);
+void log_close(void);
+void log_forget_fds(void);
+
+void log_close_syslog(void);
+void log_close_journal(void);
+void log_close_kmsg(void);
+void log_close_console(void);
+
+void log_parse_environment(void);
+
+int log_meta(
+        int level,
+        const char*file,
+        int line,
+        const char *func,
+        const char *format, ...) _printf_attr_(5,6);
+
+int log_metav(
+        int level,
+        const char*file,
+        int line,
+        const char *func,
+        const char *format,
+        va_list ap);
+
+_noreturn_ void log_assert_failed(const char *text, const char *file, int line, const char *func);
+_noreturn_ void log_assert_failed_unreachable(const char *text, const char *file, int line, const char *func);
+
+/* This modifies the buffer passed! */
+int log_dump_internal(
+        int level,
+        const char*file,
+        int line,
+        const char *func,
+        char *buffer);
+
+#define log_full(level, ...) log_meta(level,   __FILE__, __LINE__, __func__, __VA_ARGS__)
+
+#define log_debug(...)   log_meta(LOG_DEBUG,   __FILE__, __LINE__, __func__, __VA_ARGS__)
+#define log_info(...)    log_meta(LOG_INFO,    __FILE__, __LINE__, __func__, __VA_ARGS__)
+#define log_notice(...)  log_meta(LOG_NOTICE,  __FILE__, __LINE__, __func__, __VA_ARGS__)
+#define log_warning(...) log_meta(LOG_WARNING, __FILE__, __LINE__, __func__, __VA_ARGS__)
+#define log_error(...)   log_meta(LOG_ERR,     __FILE__, __LINE__, __func__, __VA_ARGS__)
+
+/* This modifies the buffer passed! */
+#define log_dump(level, buffer) log_dump_internal(level, __FILE__, __LINE__, __func__, buffer)
+
+const char *log_target_to_string(LogTarget target);
+LogTarget log_target_from_string(const char *s);
+
+#endif
diff --git a/src/login/.gitignore b/src/login/.gitignore
new file mode 100644 (file)
index 0000000..1c0f399
--- /dev/null
@@ -0,0 +1,3 @@
+logind-gperf.c
+org.freedesktop.login1.policy
+73-seat-late.rules
diff --git a/src/login/70-uaccess.rules b/src/login/70-uaccess.rules
new file mode 100644 (file)
index 0000000..6932492
--- /dev/null
@@ -0,0 +1,72 @@
+#  This file is part of systemd.
+#
+#  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.
+
+ACTION=="remove", GOTO="uaccess_end"
+ENV{MAJOR}=="", GOTO="uaccess_end"
+
+# PTP/MTP protocol devices, cameras, portable media players
+SUBSYSTEM=="usb", ENV{ID_USB_INTERFACES}=="", ENV{DEVTYPE}=="usb_device", IMPORT{program}="usb_id --export %p"
+SUBSYSTEM=="usb", ENV{ID_USB_INTERFACES}=="*:060101:*", TAG+="uaccess"
+
+# Digicams with proprietary protocol
+ENV{ID_GPHOTO2}=="*?", TAG+="uaccess"
+
+# SCSI and USB scanners
+ENV{libsane_matched}=="yes", TAG+="uaccess"
+
+# HPLIP devices (necessary for ink level check and HP tool maintenance)
+ENV{ID_HPLIP}=="1", TAG+="uaccess"
+
+# optical drives
+SUBSYSTEM=="block", ENV{ID_CDROM}=="1", TAG+="uaccess"
+SUBSYSTEM=="scsi_generic", SUBSYSTEMS=="scsi", ATTRS{type}=="4|5", TAG+="uaccess"
+
+# Sound devices
+SUBSYSTEM=="sound", TAG+="uaccess"
+
+# ffado is an userspace driver for firewire sound cards
+SUBSYSTEM=="firewire", ENV{ID_FFADO}=="1", TAG+="uaccess"
+
+# Webcams, frame grabber, TV cards
+SUBSYSTEM=="video4linux", TAG+="uaccess"
+SUBSYSTEM=="dvb", TAG+="uaccess"
+
+# IIDC devices: industrial cameras and some webcams
+SUBSYSTEM=="firewire", ATTR{units}=="*0x00a02d:0x00010*",  TAG+="uaccess"
+SUBSYSTEM=="firewire", ATTR{units}=="*0x00b09d:0x00010*",  TAG+="uaccess"
+# AV/C devices: camcorders, set-top boxes, TV sets, audio devices, and more
+SUBSYSTEM=="firewire", ATTR{units}=="*0x00a02d:0x010001*", TAG+="uaccess"
+SUBSYSTEM=="firewire", ATTR{units}=="*0x00a02d:0x014001*", TAG+="uaccess"
+
+# DRI video devices
+SUBSYSTEM=="drm", KERNEL=="card*", TAG+="uaccess"
+
+# KVM
+SUBSYSTEM=="misc", KERNEL=="kvm", TAG+="uaccess"
+
+# smart-card readers
+ENV{ID_SMARTCARD_READER}=="*?", TAG+="uaccess"
+
+# PDA devices
+ENV{ID_PDA}=="*?", TAG+="uaccess"
+
+# Programmable remote control
+ENV{ID_REMOTE_CONTROL}=="1", TAG+="uaccess"
+
+# joysticks
+SUBSYSTEM=="input", ENV{ID_INPUT_JOYSTICK}=="?*", TAG+="uaccess"
+
+# color measurement devices
+ENV{COLOR_MEASUREMENT_DEVICE}=="*?", TAG+="uaccess"
+
+# DDC/CI device, usually high-end monitors such as the DreamColor
+ENV{DDC_DEVICE}=="*?", TAG+="uaccess"
+
+# media player raw devices (for user-mode drivers, Android SDK, etc.)
+SUBSYSTEM=="usb", ENV{ID_MEDIA_PLAYER}=="?*", TAG+="uaccess"
+
+LABEL="uaccess_end"
diff --git a/src/login/71-seat.rules b/src/login/71-seat.rules
new file mode 100644 (file)
index 0000000..04ccac7
--- /dev/null
@@ -0,0 +1,25 @@
+#  This file is part of systemd.
+#
+#  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.
+
+ACTION=="remove", GOTO="seat_end"
+
+TAG=="uaccess", SUBSYSTEM!="sound", TAG+="seat"
+SUBSYSTEM=="sound", KERNEL=="card*", TAG+="seat"
+SUBSYSTEM=="input", KERNEL=="input*", TAG+="seat"
+SUBSYSTEM=="graphics", KERNEL=="fb[0-9]*", TAG+="seat"
+SUBSYSTEM=="usb", ATTR{bDeviceClass}=="09", TAG+="seat"
+
+# 'Plugable' USB hub, sound, network, graphics adapter
+SUBSYSTEM=="usb", ATTR{idVendor}=="2230", ATTR{idProduct}=="000[13]", ENV{ID_AUTOSEAT}="1"
+
+# Mimo 720, with integrated USB hub, displaylink graphics, and e2i touchscreen
+SUBSYSTEM=="usb", ATTR{idVendor}=="058f", ATTR{idProduct}=="6254", ENV{ID_AUTOSEAT}="1"
+
+TAG=="seat", ENV{ID_PATH}=="", IMPORT{program}="path_id %p"
+TAG=="seat", ENV{ID_FOR_SEAT}=="", ENV{ID_PATH_TAG}!="", ENV{ID_FOR_SEAT}="$env{SUBSYSTEM}-$env{ID_PATH_TAG}"
+
+LABEL="seat_end"
diff --git a/src/login/73-seat-late.rules.in b/src/login/73-seat-late.rules.in
new file mode 100644 (file)
index 0000000..0847932
--- /dev/null
@@ -0,0 +1,17 @@
+#  This file is part of systemd.
+#
+#  systemd is free software; you can redistribute it and/or modify it
+#  under the terms of the GNU General Public License as published by
+#  the Free Software Foundation; either version 2 of the License, or
+#  (at your option) any later version.
+
+ACTION=="remove", GOTO="seat_late_end"
+
+ENV{ID_SEAT}=="", ENV{ID_AUTOSEAT}=="1", ENV{ID_FOR_SEAT}!="", ENV{ID_SEAT}="seat-$env{ID_FOR_SEAT}"
+ENV{ID_SEAT}=="", IMPORT{parent}="ID_SEAT"
+
+ENV{ID_SEAT}!="", TAG+="$env{ID_SEAT}"
+
+TAG=="uaccess", ENV{MAJOR}!="", RUN+="@rootlibexecdir@/systemd-uaccess $env{DEVNAME} $env{ID_SEAT}"
+
+LABEL="seat_late_end"
diff --git a/src/login/Makefile b/src/login/Makefile
new file mode 120000 (symlink)
index 0000000..d0b0e8e
--- /dev/null
@@ -0,0 +1 @@
+../Makefile
\ No newline at end of file
diff --git a/src/login/libsystemd-login.pc.in b/src/login/libsystemd-login.pc.in
new file mode 100644 (file)
index 0000000..cd36a9c
--- /dev/null
@@ -0,0 +1,18 @@
+#  This file is part of systemd.
+#
+#  systemd is free software; you can redistribute it and/or modify it
+#  under the terms of the GNU General Public License as published by
+#  the Free Software Foundation; either version 2 of the License, or
+#  (at your option) any later version.
+
+prefix=@prefix@
+exec_prefix=@exec_prefix@
+libdir=@libdir@
+includedir=@includedir@
+
+Name: systemd
+Description: systemd Login Utility Library
+URL: @PACKAGE_URL@
+Version: @PACKAGE_VERSION@
+Libs: -L${libdir} -lsystemd-login
+Cflags: -I${includedir}
diff --git a/src/login/libsystemd-login.sym b/src/login/libsystemd-login.sym
new file mode 100644 (file)
index 0000000..a5e6c1e
--- /dev/null
@@ -0,0 +1,48 @@
+/***
+  This file is part of systemd.
+
+  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.
+***/
+
+/* Original symbols from systemd v31 */
+
+LIBSYSTEMD_LOGIN_31 {
+global:
+        sd_get_seats;
+        sd_get_sessions;
+        sd_get_uids;
+        sd_login_monitor_flush;
+        sd_login_monitor_get_fd;
+        sd_login_monitor_new;
+        sd_login_monitor_unref;
+        sd_pid_get_owner_uid;
+        sd_pid_get_session;
+        sd_seat_can_multi_session;
+        sd_seat_get_active;
+        sd_seat_get_sessions;
+        sd_session_get_seat;
+        sd_session_get_uid;
+        sd_session_is_active;
+        sd_uid_get_seats;
+        sd_uid_get_sessions;
+        sd_uid_get_state;
+        sd_uid_is_on_seat;
+local:
+        *;
+};
+
+LIBSYSTEMD_LOGIN_38 {
+global:
+        sd_pid_get_unit;
+        sd_session_get_service;
+} LIBSYSTEMD_LOGIN_31;
+
+LIBSYSTEMD_LOGIN_43 {
+global:
+        sd_session_get_type;
+        sd_session_get_class;
+        sd_session_get_display;
+} LIBSYSTEMD_LOGIN_38;
diff --git a/src/login/loginctl.c b/src/login/loginctl.c
new file mode 100644 (file)
index 0000000..30e97e3
--- /dev/null
@@ -0,0 +1,1926 @@
+/*-*- 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 <http://www.gnu.org/licenses/>.
+***/
+
+#include <dbus/dbus.h>
+#include <unistd.h>
+#include <errno.h>
+#include <string.h>
+#include <getopt.h>
+#include <pwd.h>
+
+#include "log.h"
+#include "util.h"
+#include "macro.h"
+#include "pager.h"
+#include "dbus-common.h"
+#include "build.h"
+#include "strv.h"
+#include "cgroup-show.h"
+#include "sysfs-show.h"
+
+static char **arg_property = NULL;
+static bool arg_all = false;
+static bool arg_no_pager = false;
+static const char *arg_kill_who = NULL;
+static int arg_signal = SIGTERM;
+static enum transport {
+        TRANSPORT_NORMAL,
+        TRANSPORT_SSH,
+        TRANSPORT_POLKIT
+} arg_transport = TRANSPORT_NORMAL;
+static const char *arg_host = NULL;
+
+static bool on_tty(void) {
+        static int t = -1;
+
+        /* Note that this is invoked relatively early, before we start
+         * the pager. That means the value we return reflects whether
+         * we originally were started on a tty, not if we currently
+         * are. But this is intended, since we want colour and so on
+         * when run in our own pager. */
+
+        if (_unlikely_(t < 0))
+                t = isatty(STDOUT_FILENO) > 0;
+
+        return t;
+}
+
+static void pager_open_if_enabled(void) {
+
+        /* Cache result before we open the pager */
+        on_tty();
+
+        if (!arg_no_pager)
+                pager_open();
+}
+
+static int list_sessions(DBusConnection *bus, char **args, unsigned n) {
+        DBusMessage *m = NULL, *reply = NULL;
+        DBusError error;
+        int r;
+        DBusMessageIter iter, sub, sub2;
+        unsigned k = 0;
+
+        dbus_error_init(&error);
+
+        assert(bus);
+
+        pager_open_if_enabled();
+
+        m = dbus_message_new_method_call(
+                        "org.freedesktop.login1",
+                        "/org/freedesktop/login1",
+                        "org.freedesktop.login1.Manager",
+                        "ListSessions");
+        if (!m) {
+                log_error("Could not allocate message.");
+                return -ENOMEM;
+        }
+
+        reply = dbus_connection_send_with_reply_and_block(bus, m, -1, &error);
+        if (!reply) {
+                log_error("Failed to issue method call: %s", bus_error_message(&error));
+                r = -EIO;
+                goto finish;
+        }
+
+        if (!dbus_message_iter_init(reply, &iter) ||
+            dbus_message_iter_get_arg_type(&iter) != DBUS_TYPE_ARRAY ||
+            dbus_message_iter_get_element_type(&iter) != DBUS_TYPE_STRUCT)  {
+                log_error("Failed to parse reply.");
+                r = -EIO;
+                goto finish;
+        }
+
+        dbus_message_iter_recurse(&iter, &sub);
+
+        if (on_tty())
+                printf("%10s %10s %-16s %-16s\n", "SESSION", "UID", "USER", "SEAT");
+
+        while (dbus_message_iter_get_arg_type(&sub) != DBUS_TYPE_INVALID) {
+                const char *id, *user, *seat, *object;
+                uint32_t uid;
+
+                if (dbus_message_iter_get_arg_type(&sub) != DBUS_TYPE_STRUCT) {
+                        log_error("Failed to parse reply.");
+                        r = -EIO;
+                        goto finish;
+                }
+
+                dbus_message_iter_recurse(&sub, &sub2);
+
+                if (bus_iter_get_basic_and_next(&sub2, DBUS_TYPE_STRING, &id, true) < 0 ||
+                    bus_iter_get_basic_and_next(&sub2, DBUS_TYPE_UINT32, &uid, true) < 0 ||
+                    bus_iter_get_basic_and_next(&sub2, DBUS_TYPE_STRING, &user, true) < 0 ||
+                    bus_iter_get_basic_and_next(&sub2, DBUS_TYPE_STRING, &seat, true) < 0 ||
+                    bus_iter_get_basic_and_next(&sub2, DBUS_TYPE_OBJECT_PATH, &object, false) < 0) {
+                        log_error("Failed to parse reply.");
+                        r = -EIO;
+                        goto finish;
+                }
+
+                printf("%10s %10u %-16s %-16s\n", id, (unsigned) uid, user, seat);
+
+                k++;
+
+                dbus_message_iter_next(&sub);
+        }
+
+        if (on_tty())
+                printf("\n%u sessions listed.\n", k);
+
+        r = 0;
+
+finish:
+        if (m)
+                dbus_message_unref(m);
+
+        if (reply)
+                dbus_message_unref(reply);
+
+        dbus_error_free(&error);
+
+        return r;
+}
+
+static int list_users(DBusConnection *bus, char **args, unsigned n) {
+        DBusMessage *m = NULL, *reply = NULL;
+        DBusError error;
+        int r;
+        DBusMessageIter iter, sub, sub2;
+        unsigned k = 0;
+
+        dbus_error_init(&error);
+
+        assert(bus);
+
+        pager_open_if_enabled();
+
+        m = dbus_message_new_method_call(
+                        "org.freedesktop.login1",
+                        "/org/freedesktop/login1",
+                        "org.freedesktop.login1.Manager",
+                        "ListUsers");
+        if (!m) {
+                log_error("Could not allocate message.");
+                return -ENOMEM;
+        }
+
+        reply = dbus_connection_send_with_reply_and_block(bus, m, -1, &error);
+        if (!reply) {
+                log_error("Failed to issue method call: %s", bus_error_message(&error));
+                r = -EIO;
+                goto finish;
+        }
+
+        if (!dbus_message_iter_init(reply, &iter) ||
+            dbus_message_iter_get_arg_type(&iter) != DBUS_TYPE_ARRAY ||
+            dbus_message_iter_get_element_type(&iter) != DBUS_TYPE_STRUCT)  {
+                log_error("Failed to parse reply.");
+                r = -EIO;
+                goto finish;
+        }
+
+        dbus_message_iter_recurse(&iter, &sub);
+
+        if (on_tty())
+                printf("%10s %-16s\n", "UID", "USER");
+
+        while (dbus_message_iter_get_arg_type(&sub) != DBUS_TYPE_INVALID) {
+                const char *user, *object;
+                uint32_t uid;
+
+                if (dbus_message_iter_get_arg_type(&sub) != DBUS_TYPE_STRUCT) {
+                        log_error("Failed to parse reply.");
+                        r = -EIO;
+                        goto finish;
+                }
+
+                dbus_message_iter_recurse(&sub, &sub2);
+
+                if (bus_iter_get_basic_and_next(&sub2, DBUS_TYPE_UINT32, &uid, true) < 0 ||
+                    bus_iter_get_basic_and_next(&sub2, DBUS_TYPE_STRING, &user, true) < 0 ||
+                    bus_iter_get_basic_and_next(&sub2, DBUS_TYPE_OBJECT_PATH, &object, false) < 0) {
+                        log_error("Failed to parse reply.");
+                        r = -EIO;
+                        goto finish;
+                }
+
+                printf("%10u %-16s\n", (unsigned) uid, user);
+
+                k++;
+
+                dbus_message_iter_next(&sub);
+        }
+
+        if (on_tty())
+                printf("\n%u users listed.\n", k);
+
+        r = 0;
+
+finish:
+        if (m)
+                dbus_message_unref(m);
+
+        if (reply)
+                dbus_message_unref(reply);
+
+        dbus_error_free(&error);
+
+        return r;
+}
+
+static int list_seats(DBusConnection *bus, char **args, unsigned n) {
+        DBusMessage *m = NULL, *reply = NULL;
+        DBusError error;
+        int r;
+        DBusMessageIter iter, sub, sub2;
+        unsigned k = 0;
+
+        dbus_error_init(&error);
+
+        assert(bus);
+
+        pager_open_if_enabled();
+
+        m = dbus_message_new_method_call(
+                        "org.freedesktop.login1",
+                        "/org/freedesktop/login1",
+                        "org.freedesktop.login1.Manager",
+                        "ListSeats");
+        if (!m) {
+                log_error("Could not allocate message.");
+                return -ENOMEM;
+        }
+
+        reply = dbus_connection_send_with_reply_and_block(bus, m, -1, &error);
+        if (!reply) {
+                log_error("Failed to issue method call: %s", bus_error_message(&error));
+                r = -EIO;
+                goto finish;
+        }
+
+        if (!dbus_message_iter_init(reply, &iter) ||
+            dbus_message_iter_get_arg_type(&iter) != DBUS_TYPE_ARRAY ||
+            dbus_message_iter_get_element_type(&iter) != DBUS_TYPE_STRUCT)  {
+                log_error("Failed to parse reply.");
+                r = -EIO;
+                goto finish;
+        }
+
+        dbus_message_iter_recurse(&iter, &sub);
+
+        if (on_tty())
+                printf("%-16s\n", "SEAT");
+
+        while (dbus_message_iter_get_arg_type(&sub) != DBUS_TYPE_INVALID) {
+                const char *seat, *object;
+
+                if (dbus_message_iter_get_arg_type(&sub) != DBUS_TYPE_STRUCT) {
+                        log_error("Failed to parse reply.");
+                        r = -EIO;
+                        goto finish;
+                }
+
+                dbus_message_iter_recurse(&sub, &sub2);
+
+                if (bus_iter_get_basic_and_next(&sub2, DBUS_TYPE_STRING, &seat, true) < 0 ||
+                    bus_iter_get_basic_and_next(&sub2, DBUS_TYPE_OBJECT_PATH, &object, false) < 0) {
+                        log_error("Failed to parse reply.");
+                        r = -EIO;
+                        goto finish;
+                }
+
+                printf("%-16s\n", seat);
+
+                k++;
+
+                dbus_message_iter_next(&sub);
+        }
+
+        if (on_tty())
+                printf("\n%u seats listed.\n", k);
+
+        r = 0;
+
+finish:
+        if (m)
+                dbus_message_unref(m);
+
+        if (reply)
+                dbus_message_unref(reply);
+
+        dbus_error_free(&error);
+
+        return r;
+}
+
+typedef struct SessionStatusInfo {
+        const char *id;
+        uid_t uid;
+        const char *name;
+        usec_t timestamp;
+        const char *control_group;
+        int vtnr;
+        const char *seat;
+        const char *tty;
+        const char *display;
+        bool remote;
+        const char *remote_host;
+        const char *remote_user;
+        const char *service;
+        pid_t leader;
+        const char *type;
+        const char *class;
+        bool active;
+} SessionStatusInfo;
+
+typedef struct UserStatusInfo {
+        uid_t uid;
+        const char *name;
+        usec_t timestamp;
+        const char *control_group;
+        const char *state;
+        char **sessions;
+        const char *display;
+} UserStatusInfo;
+
+typedef struct SeatStatusInfo {
+        const char *id;
+        const char *active_session;
+        char **sessions;
+} SeatStatusInfo;
+
+static void print_session_status_info(SessionStatusInfo *i) {
+        char since1[FORMAT_TIMESTAMP_PRETTY_MAX], *s1;
+        char since2[FORMAT_TIMESTAMP_MAX], *s2;
+        assert(i);
+
+        printf("%s - ", strna(i->id));
+
+        if (i->name)
+                printf("%s (%u)\n", i->name, (unsigned) i->uid);
+        else
+                printf("%u\n", (unsigned) i->uid);
+
+        s1 = format_timestamp_pretty(since1, sizeof(since1), i->timestamp);
+        s2 = format_timestamp(since2, sizeof(since2), i->timestamp);
+
+        if (s1)
+                printf("\t   Since: %s; %s\n", s2, s1);
+        else if (s2)
+                printf("\t   Since: %s\n", s2);
+
+        if (i->leader > 0) {
+                char *t = NULL;
+
+                printf("\t  Leader: %u", (unsigned) i->leader);
+
+                get_process_comm(i->leader, &t);
+                if (t) {
+                        printf(" (%s)", t);
+                        free(t);
+                }
+
+                printf("\n");
+        }
+
+        if (i->seat) {
+                printf("\t    Seat: %s", i->seat);
+
+                if (i->vtnr > 0)
+                        printf("; vc%i", i->vtnr);
+
+                printf("\n");
+        }
+
+        if (i->tty)
+                printf("\t     TTY: %s\n", i->tty);
+        else if (i->display)
+                printf("\t Display: %s\n", i->display);
+
+        if (i->remote_host && i->remote_user)
+                printf("\t  Remote: %s@%s\n", i->remote_user, i->remote_host);
+        else if (i->remote_host)
+                printf("\t  Remote: %s\n", i->remote_host);
+        else if (i->remote_user)
+                printf("\t  Remote: user %s\n", i->remote_user);
+        else if (i->remote)
+                printf("\t  Remote: Yes\n");
+
+        if (i->service) {
+                printf("\t Service: %s", i->service);
+
+                if (i->type)
+                        printf("; type %s", i->type);
+
+                if (i->class)
+                        printf("; class %s", i->class);
+
+                printf("\n");
+        } else if (i->type) {
+                printf("\t    Type: %s\n", i->type);
+
+                if (i->class)
+                        printf("; class %s", i->class);
+        } else if (i->class)
+                printf("\t   Class: %s\n", i->class);
+
+
+        printf("\t  Active: %s\n", yes_no(i->active));
+
+        if (i->control_group) {
+                unsigned c;
+
+                printf("\t  CGroup: %s\n", i->control_group);
+
+                if (arg_transport != TRANSPORT_SSH) {
+                        c = columns();
+                        if (c > 18)
+                                c -= 18;
+                        else
+                                c = 0;
+
+                        show_cgroup_by_path(i->control_group, "\t\t  ", c, false);
+                }
+        }
+}
+
+static void print_user_status_info(UserStatusInfo *i) {
+        char since1[FORMAT_TIMESTAMP_PRETTY_MAX], *s1;
+        char since2[FORMAT_TIMESTAMP_MAX], *s2;
+        assert(i);
+
+        if (i->name)
+                printf("%s (%u)\n", i->name, (unsigned) i->uid);
+        else
+                printf("%u\n", (unsigned) i->uid);
+
+        s1 = format_timestamp_pretty(since1, sizeof(since1), i->timestamp);
+        s2 = format_timestamp(since2, sizeof(since2), i->timestamp);
+
+        if (s1)
+                printf("\t   Since: %s; %s\n", s2, s1);
+        else if (s2)
+                printf("\t   Since: %s\n", s2);
+
+        if (!isempty(i->state))
+                printf("\t   State: %s\n", i->state);
+
+        if (!strv_isempty(i->sessions)) {
+                char **l;
+                printf("\tSessions:");
+
+                STRV_FOREACH(l, i->sessions) {
+                        if (streq_ptr(*l, i->display))
+                                printf(" *%s", *l);
+                        else
+                                printf(" %s", *l);
+                }
+
+                printf("\n");
+        }
+
+        if (i->control_group) {
+                unsigned c;
+
+                printf("\t  CGroup: %s\n", i->control_group);
+
+                if (arg_transport != TRANSPORT_SSH) {
+                        c = columns();
+                        if (c > 18)
+                                c -= 18;
+                        else
+                                c = 0;
+
+                        show_cgroup_by_path(i->control_group, "\t\t  ", c, false);
+                }
+        }
+}
+
+static void print_seat_status_info(SeatStatusInfo *i) {
+        assert(i);
+
+        printf("%s\n", strna(i->id));
+
+        if (!strv_isempty(i->sessions)) {
+                char **l;
+                printf("\tSessions:");
+
+                STRV_FOREACH(l, i->sessions) {
+                        if (streq_ptr(*l, i->active_session))
+                                printf(" *%s", *l);
+                        else
+                                printf(" %s", *l);
+                }
+
+                printf("\n");
+        }
+
+        if (arg_transport != TRANSPORT_SSH) {
+                unsigned c;
+
+                c = columns();
+                if (c > 21)
+                        c -= 21;
+                else
+                        c = 0;
+
+                printf("\t Devices:\n");
+
+                show_sysfs(i->id, "\t\t  ", c);
+        }
+}
+
+static int status_property_session(const char *name, DBusMessageIter *iter, SessionStatusInfo *i) {
+        assert(name);
+        assert(iter);
+        assert(i);
+
+        switch (dbus_message_iter_get_arg_type(iter)) {
+
+        case DBUS_TYPE_STRING: {
+                const char *s;
+
+                dbus_message_iter_get_basic(iter, &s);
+
+                if (!isempty(s)) {
+                        if (streq(name, "Id"))
+                                i->id = s;
+                        else if (streq(name, "Name"))
+                                i->name = s;
+                        else if (streq(name, "ControlGroupPath"))
+                                i->control_group = s;
+                        else if (streq(name, "TTY"))
+                                i->tty = s;
+                        else if (streq(name, "Display"))
+                                i->display = s;
+                        else if (streq(name, "RemoteHost"))
+                                i->remote_host = s;
+                        else if (streq(name, "RemoteUser"))
+                                i->remote_user = s;
+                        else if (streq(name, "Service"))
+                                i->service = s;
+                        else if (streq(name, "Type"))
+                                i->type = s;
+                        else if (streq(name, "Class"))
+                                i->class = s;
+                }
+                break;
+        }
+
+        case DBUS_TYPE_UINT32: {
+                uint32_t u;
+
+                dbus_message_iter_get_basic(iter, &u);
+
+                if (streq(name, "VTNr"))
+                        i->vtnr = (int) u;
+                else if (streq(name, "Leader"))
+                        i->leader = (pid_t) u;
+
+                break;
+        }
+
+        case DBUS_TYPE_BOOLEAN: {
+                dbus_bool_t b;
+
+                dbus_message_iter_get_basic(iter, &b);
+
+                if (streq(name, "Remote"))
+                        i->remote = b;
+                else if (streq(name, "Active"))
+                        i->active = b;
+
+                break;
+        }
+
+        case DBUS_TYPE_UINT64: {
+                uint64_t u;
+
+                dbus_message_iter_get_basic(iter, &u);
+
+                if (streq(name, "Timestamp"))
+                        i->timestamp = (usec_t) u;
+
+                break;
+        }
+
+        case DBUS_TYPE_STRUCT: {
+                DBusMessageIter sub;
+
+                dbus_message_iter_recurse(iter, &sub);
+
+                if (dbus_message_iter_get_arg_type(&sub) == DBUS_TYPE_UINT32 && streq(name, "User")) {
+                        uint32_t u;
+
+                        dbus_message_iter_get_basic(&sub, &u);
+                        i->uid = (uid_t) u;
+
+                } else if (dbus_message_iter_get_arg_type(&sub) == DBUS_TYPE_STRING && streq(name, "Seat")) {
+                        const char *s;
+
+                        dbus_message_iter_get_basic(&sub, &s);
+
+                        if (!isempty(s))
+                                i->seat = s;
+                }
+
+                break;
+        }
+        }
+
+        return 0;
+}
+
+static int status_property_user(const char *name, DBusMessageIter *iter, UserStatusInfo *i) {
+        assert(name);
+        assert(iter);
+        assert(i);
+
+        switch (dbus_message_iter_get_arg_type(iter)) {
+
+        case DBUS_TYPE_STRING: {
+                const char *s;
+
+                dbus_message_iter_get_basic(iter, &s);
+
+                if (!isempty(s)) {
+                        if (streq(name, "Name"))
+                                i->name = s;
+                        else if (streq(name, "ControlGroupPath"))
+                                i->control_group = s;
+                        else if (streq(name, "State"))
+                                i->state = s;
+                }
+                break;
+        }
+
+        case DBUS_TYPE_UINT32: {
+                uint32_t u;
+
+                dbus_message_iter_get_basic(iter, &u);
+
+                if (streq(name, "UID"))
+                        i->uid = (uid_t) u;
+
+                break;
+        }
+
+        case DBUS_TYPE_UINT64: {
+                uint64_t u;
+
+                dbus_message_iter_get_basic(iter, &u);
+
+                if (streq(name, "Timestamp"))
+                        i->timestamp = (usec_t) u;
+
+                break;
+        }
+
+        case DBUS_TYPE_STRUCT: {
+                DBusMessageIter sub;
+
+                dbus_message_iter_recurse(iter, &sub);
+
+                if (dbus_message_iter_get_arg_type(&sub) == DBUS_TYPE_STRING && streq(name, "Display")) {
+                        const char *s;
+
+                        dbus_message_iter_get_basic(&sub, &s);
+
+                        if (!isempty(s))
+                                i->display = s;
+                }
+
+                break;
+        }
+
+        case DBUS_TYPE_ARRAY: {
+
+                if (dbus_message_iter_get_element_type(iter) == DBUS_TYPE_STRUCT && streq(name, "Sessions")) {
+                        DBusMessageIter sub, sub2;
+
+                        dbus_message_iter_recurse(iter, &sub);
+                        while (dbus_message_iter_get_arg_type(&sub) == DBUS_TYPE_STRUCT) {
+                                const char *id;
+                                const char *path;
+
+                                dbus_message_iter_recurse(&sub, &sub2);
+
+                                if (bus_iter_get_basic_and_next(&sub2, DBUS_TYPE_STRING, &id, true) >= 0 &&
+                                    bus_iter_get_basic_and_next(&sub2, DBUS_TYPE_OBJECT_PATH, &path, false) >= 0) {
+                                        char **l;
+
+                                        l = strv_append(i->sessions, id);
+                                        if (!l)
+                                                return -ENOMEM;
+
+                                        strv_free(i->sessions);
+                                        i->sessions = l;
+                                }
+
+                                dbus_message_iter_next(&sub);
+                        }
+
+                        return 0;
+                }
+        }
+        }
+
+        return 0;
+}
+
+static int status_property_seat(const char *name, DBusMessageIter *iter, SeatStatusInfo *i) {
+        assert(name);
+        assert(iter);
+        assert(i);
+
+        switch (dbus_message_iter_get_arg_type(iter)) {
+
+        case DBUS_TYPE_STRING: {
+                const char *s;
+
+                dbus_message_iter_get_basic(iter, &s);
+
+                if (!isempty(s)) {
+                        if (streq(name, "Id"))
+                                i->id = s;
+                }
+                break;
+        }
+
+        case DBUS_TYPE_STRUCT: {
+                DBusMessageIter sub;
+
+                dbus_message_iter_recurse(iter, &sub);
+
+                if (dbus_message_iter_get_arg_type(&sub) == DBUS_TYPE_STRING && streq(name, "ActiveSession")) {
+                        const char *s;
+
+                        dbus_message_iter_get_basic(&sub, &s);
+
+                        if (!isempty(s))
+                                i->active_session = s;
+                }
+
+                break;
+        }
+
+        case DBUS_TYPE_ARRAY: {
+
+                if (dbus_message_iter_get_element_type(iter) == DBUS_TYPE_STRUCT && streq(name, "Sessions")) {
+                        DBusMessageIter sub, sub2;
+
+                        dbus_message_iter_recurse(iter, &sub);
+                        while (dbus_message_iter_get_arg_type(&sub) == DBUS_TYPE_STRUCT) {
+                                const char *id;
+                                const char *path;
+
+                                dbus_message_iter_recurse(&sub, &sub2);
+
+                                if (bus_iter_get_basic_and_next(&sub2, DBUS_TYPE_STRING, &id, true) >= 0 &&
+                                    bus_iter_get_basic_and_next(&sub2, DBUS_TYPE_OBJECT_PATH, &path, false) >= 0) {
+                                        char **l;
+
+                                        l = strv_append(i->sessions, id);
+                                        if (!l)
+                                                return -ENOMEM;
+
+                                        strv_free(i->sessions);
+                                        i->sessions = l;
+                                }
+
+                                dbus_message_iter_next(&sub);
+                        }
+
+                        return 0;
+                }
+        }
+        }
+
+        return 0;
+}
+
+static int print_property(const char *name, DBusMessageIter *iter) {
+        assert(name);
+        assert(iter);
+
+        if (arg_property && !strv_find(arg_property, name))
+                return 0;
+
+        switch (dbus_message_iter_get_arg_type(iter)) {
+
+        case DBUS_TYPE_STRUCT: {
+                DBusMessageIter sub;
+
+                dbus_message_iter_recurse(iter, &sub);
+
+                if (dbus_message_iter_get_arg_type(&sub) == DBUS_TYPE_STRING &&
+                    (streq(name, "Display") || streq(name, "ActiveSession"))) {
+                        const char *s;
+
+                        dbus_message_iter_get_basic(&sub, &s);
+
+                        if (arg_all || !isempty(s))
+                                printf("%s=%s\n", name, s);
+                        return 0;
+                }
+                break;
+        }
+
+        case DBUS_TYPE_ARRAY:
+
+                if (dbus_message_iter_get_element_type(iter) == DBUS_TYPE_STRUCT && streq(name, "Sessions")) {
+                        DBusMessageIter sub, sub2;
+                        bool found = false;
+
+                        dbus_message_iter_recurse(iter, &sub);
+                        while (dbus_message_iter_get_arg_type(&sub) == DBUS_TYPE_STRUCT) {
+                                const char *id;
+                                const char *path;
+
+                                dbus_message_iter_recurse(&sub, &sub2);
+
+                                if (bus_iter_get_basic_and_next(&sub2, DBUS_TYPE_STRING, &id, true) >= 0 &&
+                                    bus_iter_get_basic_and_next(&sub2, DBUS_TYPE_OBJECT_PATH, &path, false) >= 0) {
+                                        if (found)
+                                                printf(" %s", id);
+                                        else {
+                                                printf("%s=%s", name, id);
+                                                found = true;
+                                        }
+                                }
+
+                                dbus_message_iter_next(&sub);
+                        }
+
+                        if (!found && arg_all)
+                                printf("%s=\n", name);
+                        else if (found)
+                                printf("\n");
+
+                        return 0;
+                }
+
+                break;
+        }
+
+        if (generic_print_property(name, iter, arg_all) > 0)
+                return 0;
+
+        if (arg_all)
+                printf("%s=[unprintable]\n", name);
+
+        return 0;
+}
+
+static int show_one(const char *verb, DBusConnection *bus, const char *path, bool show_properties, bool *new_line) {
+        DBusMessage *m = NULL, *reply = NULL;
+        const char *interface = "";
+        int r;
+        DBusError error;
+        DBusMessageIter iter, sub, sub2, sub3;
+        SessionStatusInfo session_info;
+        UserStatusInfo user_info;
+        SeatStatusInfo seat_info;
+
+        assert(bus);
+        assert(path);
+        assert(new_line);
+
+        zero(session_info);
+        zero(user_info);
+        zero(seat_info);
+
+        dbus_error_init(&error);
+
+        m = dbus_message_new_method_call(
+                        "org.freedesktop.login1",
+                        path,
+                        "org.freedesktop.DBus.Properties",
+                        "GetAll");
+        if (!m) {
+                log_error("Could not allocate message.");
+                r = -ENOMEM;
+                goto finish;
+        }
+
+        if (!dbus_message_append_args(m,
+                                      DBUS_TYPE_STRING, &interface,
+                                      DBUS_TYPE_INVALID)) {
+                log_error("Could not append arguments to message.");
+                r = -ENOMEM;
+                goto finish;
+        }
+
+        reply = dbus_connection_send_with_reply_and_block(bus, m, -1, &error);
+        if (!reply) {
+                log_error("Failed to issue method call: %s", bus_error_message(&error));
+                r = -EIO;
+                goto finish;
+        }
+
+        if (!dbus_message_iter_init(reply, &iter) ||
+            dbus_message_iter_get_arg_type(&iter) != DBUS_TYPE_ARRAY ||
+            dbus_message_iter_get_element_type(&iter) != DBUS_TYPE_DICT_ENTRY)  {
+                log_error("Failed to parse reply.");
+                r = -EIO;
+                goto finish;
+        }
+
+        dbus_message_iter_recurse(&iter, &sub);
+
+        if (*new_line)
+                printf("\n");
+
+        *new_line = true;
+
+        while (dbus_message_iter_get_arg_type(&sub) != DBUS_TYPE_INVALID) {
+                const char *name;
+
+                if (dbus_message_iter_get_arg_type(&sub) != DBUS_TYPE_DICT_ENTRY) {
+                        log_error("Failed to parse reply.");
+                        r = -EIO;
+                        goto finish;
+                }
+
+                dbus_message_iter_recurse(&sub, &sub2);
+
+                if (bus_iter_get_basic_and_next(&sub2, DBUS_TYPE_STRING, &name, true) < 0) {
+                        log_error("Failed to parse reply.");
+                        r = -EIO;
+                        goto finish;
+                }
+
+                if (dbus_message_iter_get_arg_type(&sub2) != DBUS_TYPE_VARIANT)  {
+                        log_error("Failed to parse reply.");
+                        r = -EIO;
+                        goto finish;
+                }
+
+                dbus_message_iter_recurse(&sub2, &sub3);
+
+                if (show_properties)
+                        r = print_property(name, &sub3);
+                else if (strstr(verb, "session"))
+                        r = status_property_session(name, &sub3, &session_info);
+                else if (strstr(verb, "user"))
+                        r = status_property_user(name, &sub3, &user_info);
+                else
+                        r = status_property_seat(name, &sub3, &seat_info);
+
+                if (r < 0) {
+                        log_error("Failed to parse reply.");
+                        r = -EIO;
+                        goto finish;
+                }
+
+                dbus_message_iter_next(&sub);
+        }
+
+        if (!show_properties) {
+                if (strstr(verb, "session"))
+                        print_session_status_info(&session_info);
+                else if (strstr(verb, "user"))
+                        print_user_status_info(&user_info);
+                else
+                        print_seat_status_info(&seat_info);
+        }
+
+        strv_free(seat_info.sessions);
+        strv_free(user_info.sessions);
+
+        r = 0;
+
+finish:
+        if (m)
+                dbus_message_unref(m);
+
+        if (reply)
+                dbus_message_unref(reply);
+
+        dbus_error_free(&error);
+
+        return r;
+}
+
+static int show(DBusConnection *bus, char **args, unsigned n) {
+        DBusMessage *m = NULL, *reply = NULL;
+        int r, ret = 0;
+        DBusError error;
+        unsigned i;
+        bool show_properties, new_line = false;
+
+        assert(bus);
+        assert(args);
+
+        dbus_error_init(&error);
+
+        show_properties = !strstr(args[0], "status");
+
+        if (show_properties)
+                pager_open_if_enabled();
+
+        if (show_properties && n <= 1) {
+                /* If not argument is specified inspect the manager
+                 * itself */
+
+                ret = show_one(args[0], bus, "/org/freedesktop/login1", show_properties, &new_line);
+                goto finish;
+        }
+
+        for (i = 1; i < n; i++) {
+                const char *path = NULL;
+
+                if (strstr(args[0], "session")) {
+
+                        m = dbus_message_new_method_call(
+                                        "org.freedesktop.login1",
+                                        "/org/freedesktop/login1",
+                                        "org.freedesktop.login1.Manager",
+                                        "GetSession");
+                        if (!m) {
+                                log_error("Could not allocate message.");
+                                ret = -ENOMEM;
+                                goto finish;
+                        }
+
+                        if (!dbus_message_append_args(m,
+                                                      DBUS_TYPE_STRING, &args[i],
+                                                      DBUS_TYPE_INVALID)) {
+                                log_error("Could not append arguments to message.");
+                                ret = -ENOMEM;
+                                goto finish;
+                        }
+
+                } else if (strstr(args[0], "user")) {
+                        uid_t uid;
+                        uint32_t u;
+
+                        ret = get_user_creds((const char**) (args+i), &uid, NULL, NULL);
+                        if (ret < 0) {
+                                log_error("User %s unknown.", args[i]);
+                                goto finish;
+                        }
+
+                        m = dbus_message_new_method_call(
+                                        "org.freedesktop.login1",
+                                        "/org/freedesktop/login1",
+                                        "org.freedesktop.login1.Manager",
+                                        "GetUser");
+                        if (!m) {
+                                log_error("Could not allocate message.");
+                                ret = -ENOMEM;
+                                goto finish;
+                        }
+
+                        u = (uint32_t) uid;
+                        if (!dbus_message_append_args(m,
+                                                      DBUS_TYPE_UINT32, &u,
+                                                      DBUS_TYPE_INVALID)) {
+                                log_error("Could not append arguments to message.");
+                                ret = -ENOMEM;
+                                goto finish;
+                        }
+                } else {
+
+                        m = dbus_message_new_method_call(
+                                        "org.freedesktop.login1",
+                                        "/org/freedesktop/login1",
+                                        "org.freedesktop.login1.Manager",
+                                        "GetSeat");
+                        if (!m) {
+                                log_error("Could not allocate message.");
+                                ret = -ENOMEM;
+                                goto finish;
+                        }
+
+                        if (!dbus_message_append_args(m,
+                                                      DBUS_TYPE_STRING, &args[i],
+                                                      DBUS_TYPE_INVALID)) {
+                                log_error("Could not append arguments to message.");
+                                ret = -ENOMEM;
+                                goto finish;
+                        }
+                }
+
+                reply = dbus_connection_send_with_reply_and_block(bus, m, -1, &error);
+                if (!reply) {
+                        log_error("Failed to issue method call: %s", bus_error_message(&error));
+                        ret = -EIO;
+                        goto finish;
+                }
+
+                if (!dbus_message_get_args(reply, &error,
+                                           DBUS_TYPE_OBJECT_PATH, &path,
+                                           DBUS_TYPE_INVALID)) {
+                        log_error("Failed to parse reply: %s", bus_error_message(&error));
+                        ret = -EIO;
+                        goto finish;
+                }
+
+                r = show_one(args[0], bus, path, show_properties, &new_line);
+                if (r != 0)
+                        ret = r;
+
+                dbus_message_unref(m);
+                dbus_message_unref(reply);
+                m = reply = NULL;
+        }
+
+finish:
+        if (m)
+                dbus_message_unref(m);
+
+        if (reply)
+                dbus_message_unref(reply);
+
+        dbus_error_free(&error);
+
+        return ret;
+}
+
+static int activate(DBusConnection *bus, char **args, unsigned n) {
+        DBusMessage *m = NULL;
+        int ret = 0;
+        DBusError error;
+        unsigned i;
+
+        assert(bus);
+        assert(args);
+
+        dbus_error_init(&error);
+
+        for (i = 1; i < n; i++) {
+                DBusMessage *reply;
+
+                m = dbus_message_new_method_call(
+                                "org.freedesktop.login1",
+                                "/org/freedesktop/login1",
+                                "org.freedesktop.login1.Manager",
+                                streq(args[0], "lock-session")      ? "LockSession" :
+                                streq(args[0], "unlock-session")    ? "UnlockSession" :
+                                streq(args[0], "terminate-session") ? "TerminateSession" :
+                                                                      "ActivateSession");
+                if (!m) {
+                        log_error("Could not allocate message.");
+                        ret = -ENOMEM;
+                        goto finish;
+                }
+
+                if (!dbus_message_append_args(m,
+                                              DBUS_TYPE_STRING, &args[i],
+                                              DBUS_TYPE_INVALID)) {
+                        log_error("Could not append arguments to message.");
+                        ret = -ENOMEM;
+                        goto finish;
+                }
+
+                reply = dbus_connection_send_with_reply_and_block(bus, m, -1, &error);
+                if (!reply) {
+                        log_error("Failed to issue method call: %s", bus_error_message(&error));
+                        ret = -EIO;
+                        goto finish;
+                }
+
+                dbus_message_unref(m);
+                dbus_message_unref(reply);
+                m = reply = NULL;
+        }
+
+finish:
+        if (m)
+                dbus_message_unref(m);
+
+        dbus_error_free(&error);
+
+        return ret;
+}
+
+static int kill_session(DBusConnection *bus, char **args, unsigned n) {
+        DBusMessage *m = NULL;
+        int ret = 0;
+        DBusError error;
+        unsigned i;
+
+        assert(bus);
+        assert(args);
+
+        dbus_error_init(&error);
+
+        if (!arg_kill_who)
+                arg_kill_who = "all";
+
+        for (i = 1; i < n; i++) {
+                DBusMessage *reply;
+
+                m = dbus_message_new_method_call(
+                                "org.freedesktop.login1",
+                                "/org/freedesktop/login1",
+                                "org.freedesktop.login1.Manager",
+                                "KillSession");
+                if (!m) {
+                        log_error("Could not allocate message.");
+                        ret = -ENOMEM;
+                        goto finish;
+                }
+
+                if (!dbus_message_append_args(m,
+                                              DBUS_TYPE_STRING, &args[i],
+                                              DBUS_TYPE_STRING, &arg_kill_who,
+                                              DBUS_TYPE_INT32, arg_signal,
+                                              DBUS_TYPE_INVALID)) {
+                        log_error("Could not append arguments to message.");
+                        ret = -ENOMEM;
+                        goto finish;
+                }
+
+                reply = dbus_connection_send_with_reply_and_block(bus, m, -1, &error);
+                if (!reply) {
+                        log_error("Failed to issue method call: %s", bus_error_message(&error));
+                        ret = -EIO;
+                        goto finish;
+                }
+
+                dbus_message_unref(m);
+                dbus_message_unref(reply);
+                m = reply = NULL;
+        }
+
+finish:
+        if (m)
+                dbus_message_unref(m);
+
+        dbus_error_free(&error);
+
+        return ret;
+}
+
+static int enable_linger(DBusConnection *bus, char **args, unsigned n) {
+        DBusMessage *m = NULL;
+        int ret = 0;
+        DBusError error;
+        unsigned i;
+        dbus_bool_t b, interactive = true;
+
+        assert(bus);
+        assert(args);
+
+        dbus_error_init(&error);
+
+        b = streq(args[0], "enable-linger");
+
+        for (i = 1; i < n; i++) {
+                DBusMessage *reply;
+                uint32_t u;
+                uid_t uid;
+
+                m = dbus_message_new_method_call(
+                                "org.freedesktop.login1",
+                                "/org/freedesktop/login1",
+                                "org.freedesktop.login1.Manager",
+                                "SetUserLinger");
+                if (!m) {
+                        log_error("Could not allocate message.");
+                        ret = -ENOMEM;
+                        goto finish;
+                }
+
+                ret = get_user_creds((const char**) (args+i), &uid, NULL, NULL);
+                if (ret < 0) {
+                        log_error("Failed to resolve user %s: %s", args[i], strerror(-ret));
+                        goto finish;
+                }
+
+                u = (uint32_t) uid;
+                if (!dbus_message_append_args(m,
+                                              DBUS_TYPE_UINT32, &u,
+                                              DBUS_TYPE_BOOLEAN, &b,
+                                              DBUS_TYPE_BOOLEAN, &interactive,
+                                              DBUS_TYPE_INVALID)) {
+                        log_error("Could not append arguments to message.");
+                        ret = -ENOMEM;
+                        goto finish;
+                }
+
+                reply = dbus_connection_send_with_reply_and_block(bus, m, -1, &error);
+                if (!reply) {
+                        log_error("Failed to issue method call: %s", bus_error_message(&error));
+                        ret = -EIO;
+                        goto finish;
+                }
+
+                dbus_message_unref(m);
+                dbus_message_unref(reply);
+                m = reply = NULL;
+        }
+
+        ret = 0;
+
+finish:
+        if (m)
+                dbus_message_unref(m);
+
+        dbus_error_free(&error);
+
+        return ret;
+}
+
+static int terminate_user(DBusConnection *bus, char **args, unsigned n) {
+        DBusMessage *m = NULL;
+        int ret = 0;
+        DBusError error;
+        unsigned i;
+
+        assert(bus);
+        assert(args);
+
+        dbus_error_init(&error);
+
+        for (i = 1; i < n; i++) {
+                uint32_t u;
+                uid_t uid;
+                DBusMessage *reply;
+
+                m = dbus_message_new_method_call(
+                                "org.freedesktop.login1",
+                                "/org/freedesktop/login1",
+                                "org.freedesktop.login1.Manager",
+                                "TerminateUser");
+                if (!m) {
+                        log_error("Could not allocate message.");
+                        ret = -ENOMEM;
+                        goto finish;
+                }
+
+                ret = get_user_creds((const char**) (args+i), &uid, NULL, NULL);
+                if (ret < 0) {
+                        log_error("Failed to look up user %s: %s", args[i], strerror(-ret));
+                        goto finish;
+                }
+
+                u = (uint32_t) uid;
+                if (!dbus_message_append_args(m,
+                                              DBUS_TYPE_UINT32, &u,
+                                              DBUS_TYPE_INVALID)) {
+                        log_error("Could not append arguments to message.");
+                        ret = -ENOMEM;
+                        goto finish;
+                }
+
+                reply = dbus_connection_send_with_reply_and_block(bus, m, -1, &error);
+                if (!reply) {
+                        log_error("Failed to issue method call: %s", bus_error_message(&error));
+                        ret = -EIO;
+                        goto finish;
+                }
+
+                dbus_message_unref(m);
+                dbus_message_unref(reply);
+                m = reply = NULL;
+        }
+
+        ret = 0;
+
+finish:
+        if (m)
+                dbus_message_unref(m);
+
+        dbus_error_free(&error);
+
+        return ret;
+}
+
+static int kill_user(DBusConnection *bus, char **args, unsigned n) {
+        DBusMessage *m = NULL;
+        int ret = 0;
+        DBusError error;
+        unsigned i;
+
+        assert(bus);
+        assert(args);
+
+        dbus_error_init(&error);
+
+        if (!arg_kill_who)
+                arg_kill_who = "all";
+
+        for (i = 1; i < n; i++) {
+                DBusMessage *reply;
+                uid_t uid;
+                uint32_t u;
+
+                m = dbus_message_new_method_call(
+                                "org.freedesktop.login1",
+                                "/org/freedesktop/login1",
+                                "org.freedesktop.login1.Manager",
+                                "KillUser");
+                if (!m) {
+                        log_error("Could not allocate message.");
+                        ret = -ENOMEM;
+                        goto finish;
+                }
+
+                ret = get_user_creds((const char**) (args+i), &uid, NULL, NULL);
+                if (ret < 0) {
+                        log_error("Failed to look up user %s: %s", args[i], strerror(-ret));
+                        goto finish;
+                }
+
+                u = (uint32_t) uid;
+                if (!dbus_message_append_args(m,
+                                              DBUS_TYPE_UINT32, &u,
+                                              DBUS_TYPE_INT32, arg_signal,
+                                              DBUS_TYPE_INVALID)) {
+                        log_error("Could not append arguments to message.");
+                        ret = -ENOMEM;
+                        goto finish;
+                }
+
+                reply = dbus_connection_send_with_reply_and_block(bus, m, -1, &error);
+                if (!reply) {
+                        log_error("Failed to issue method call: %s", bus_error_message(&error));
+                        ret = -EIO;
+                        goto finish;
+                }
+
+                dbus_message_unref(m);
+                dbus_message_unref(reply);
+                m = reply = NULL;
+        }
+
+        ret = 0;
+
+finish:
+        if (m)
+                dbus_message_unref(m);
+
+        dbus_error_free(&error);
+
+        return ret;
+}
+
+static int attach(DBusConnection *bus, char **args, unsigned n) {
+        DBusMessage *m = NULL;
+        int ret = 0;
+        DBusError error;
+        unsigned i;
+        dbus_bool_t interactive = true;
+
+        assert(bus);
+        assert(args);
+
+        dbus_error_init(&error);
+
+        for (i = 2; i < n; i++) {
+                DBusMessage *reply;
+
+                m = dbus_message_new_method_call(
+                                "org.freedesktop.login1",
+                                "/org/freedesktop/login1",
+                                "org.freedesktop.login1.Manager",
+                                "AttachDevice");
+                if (!m) {
+                        log_error("Could not allocate message.");
+                        ret = -ENOMEM;
+                        goto finish;
+                }
+
+                if (!dbus_message_append_args(m,
+                                              DBUS_TYPE_STRING, &args[1],
+                                              DBUS_TYPE_STRING, &args[i],
+                                              DBUS_TYPE_BOOLEAN, &interactive,
+                                              DBUS_TYPE_INVALID)) {
+                        log_error("Could not append arguments to message.");
+                        ret = -ENOMEM;
+                        goto finish;
+                }
+
+                reply = dbus_connection_send_with_reply_and_block(bus, m, -1, &error);
+                if (!reply) {
+                        log_error("Failed to issue method call: %s", bus_error_message(&error));
+                        ret = -EIO;
+                        goto finish;
+                }
+
+                dbus_message_unref(m);
+                dbus_message_unref(reply);
+                m = reply = NULL;
+        }
+
+finish:
+        if (m)
+                dbus_message_unref(m);
+
+        dbus_error_free(&error);
+
+        return ret;
+}
+
+static int flush_devices(DBusConnection *bus, char **args, unsigned n) {
+        DBusMessage *m = NULL, *reply = NULL;
+        int ret = 0;
+        DBusError error;
+        dbus_bool_t interactive = true;
+
+        assert(bus);
+        assert(args);
+
+        dbus_error_init(&error);
+
+        m = dbus_message_new_method_call(
+                        "org.freedesktop.login1",
+                        "/org/freedesktop/login1",
+                        "org.freedesktop.login1.Manager",
+                        "FlushDevices");
+        if (!m) {
+                log_error("Could not allocate message.");
+                ret = -ENOMEM;
+                goto finish;
+        }
+
+        if (!dbus_message_append_args(m,
+                                      DBUS_TYPE_BOOLEAN, &interactive,
+                                      DBUS_TYPE_INVALID)) {
+                log_error("Could not append arguments to message.");
+                ret = -ENOMEM;
+                goto finish;
+        }
+
+        reply = dbus_connection_send_with_reply_and_block(bus, m, -1, &error);
+        if (!reply) {
+                log_error("Failed to issue method call: %s", bus_error_message(&error));
+                ret = -EIO;
+                goto finish;
+        }
+
+finish:
+        if (m)
+                dbus_message_unref(m);
+
+        if (reply)
+                dbus_message_unref(reply);
+
+        dbus_error_free(&error);
+
+        return ret;
+}
+
+static int terminate_seat(DBusConnection *bus, char **args, unsigned n) {
+        DBusMessage *m = NULL;
+        int ret = 0;
+        DBusError error;
+        unsigned i;
+
+        assert(bus);
+        assert(args);
+
+        dbus_error_init(&error);
+
+        for (i = 1; i < n; i++) {
+                DBusMessage *reply;
+
+                m = dbus_message_new_method_call(
+                                "org.freedesktop.login1",
+                                "/org/freedesktop/login1",
+                                "org.freedesktop.login1.Manager",
+                                "TerminateSeat");
+                if (!m) {
+                        log_error("Could not allocate message.");
+                        ret = -ENOMEM;
+                        goto finish;
+                }
+
+                if (!dbus_message_append_args(m,
+                                              DBUS_TYPE_STRING, &args[i],
+                                              DBUS_TYPE_INVALID)) {
+                        log_error("Could not append arguments to message.");
+                        ret = -ENOMEM;
+                        goto finish;
+                }
+
+                reply = dbus_connection_send_with_reply_and_block(bus, m, -1, &error);
+                if (!reply) {
+                        log_error("Failed to issue method call: %s", bus_error_message(&error));
+                        ret = -EIO;
+                        goto finish;
+                }
+
+                dbus_message_unref(m);
+                dbus_message_unref(reply);
+                m = reply = NULL;
+        }
+
+finish:
+        if (m)
+                dbus_message_unref(m);
+
+        dbus_error_free(&error);
+
+        return ret;
+}
+
+static int help(void) {
+
+        printf("%s [OPTIONS...] {COMMAND} ...\n\n"
+               "Send control commands to or query the login manager.\n\n"
+               "  -h --help           Show this help\n"
+               "     --version        Show package version\n"
+               "  -p --property=NAME  Show only properties by this name\n"
+               "  -a --all            Show all properties, including empty ones\n"
+               "     --kill-who=WHO   Who to send signal to\n"
+               "  -s --signal=SIGNAL  Which signal to send\n"
+               "  -H --host=[USER@]HOST\n"
+               "                      Show information for remote host\n"
+               "  -P --privileged     Acquire privileges before execution\n"
+               "     --no-pager       Do not pipe output into a pager\n\n"
+               "Commands:\n"
+               "  list-sessions                   List sessions\n"
+               "  session-status [ID...]          Show session status\n"
+               "  show-session [ID...]            Show properties of one or more sessions\n"
+               "  activate [ID]                   Activate a session\n"
+               "  lock-session [ID...]            Screen lock one or more sessions\n"
+               "  unlock-session [ID...]          Screen unlock one or more sessions\n"
+               "  terminate-session [ID...]       Terminate one or more sessions\n"
+               "  kill-session [ID...]            Send signal to processes of a session\n"
+               "  list-users                      List users\n"
+               "  user-status [USER...]           Show user status\n"
+               "  show-user [USER...]             Show properties of one or more users\n"
+               "  enable-linger [USER...]         Enable linger state of one or more users\n"
+               "  disable-linger [USER...]        Disable linger state of one or more users\n"
+               "  terminate-user [USER...]        Terminate all sessions of one or more users\n"
+               "  kill-user [USER...]             Send signal to processes of a user\n"
+               "  list-seats                      List seats\n"
+               "  seat-status [NAME...]           Show seat status\n"
+               "  show-seat [NAME...]             Show properties of one or more seats\n"
+               "  attach [NAME] [DEVICE...]       Attach one or more devices to a seat\n"
+               "  flush-devices                   Flush all device associations\n"
+               "  terminate-seat [NAME...]        Terminate all sessions on one or more seats\n",
+               program_invocation_short_name);
+
+        return 0;
+}
+
+static int parse_argv(int argc, char *argv[]) {
+
+        enum {
+                ARG_VERSION = 0x100,
+                ARG_NO_PAGER,
+                ARG_KILL_WHO
+        };
+
+        static const struct option options[] = {
+                { "help",      no_argument,       NULL, 'h'           },
+                { "version",   no_argument,       NULL, ARG_VERSION   },
+                { "property",  required_argument, NULL, 'p'           },
+                { "all",       no_argument,       NULL, 'a'           },
+                { "no-pager",  no_argument,       NULL, ARG_NO_PAGER  },
+                { "kill-who",  required_argument, NULL, ARG_KILL_WHO  },
+                { "signal",    required_argument, NULL, 's'           },
+                { "host",      required_argument, NULL, 'H'           },
+                { "privileged",no_argument,       NULL, 'P'           },
+                { NULL,        0,                 NULL, 0             }
+        };
+
+        int c;
+
+        assert(argc >= 0);
+        assert(argv);
+
+        while ((c = getopt_long(argc, argv, "hp:as:H:P", options, NULL)) >= 0) {
+
+                switch (c) {
+
+                case 'h':
+                        help();
+                        return 0;
+
+                case ARG_VERSION:
+                        puts(PACKAGE_STRING);
+                        puts(DISTRIBUTION);
+                        puts(SYSTEMD_FEATURES);
+                        return 0;
+
+                case 'p': {
+                        char **l;
+
+                        l = strv_append(arg_property, optarg);
+                        if (!l)
+                                return -ENOMEM;
+
+                        strv_free(arg_property);
+                        arg_property = l;
+
+                        /* If the user asked for a particular
+                         * property, show it to him, even if it is
+                         * empty. */
+                        arg_all = true;
+                        break;
+                }
+
+                case 'a':
+                        arg_all = true;
+                        break;
+
+                case ARG_NO_PAGER:
+                        arg_no_pager = true;
+                        break;
+
+                case ARG_KILL_WHO:
+                        arg_kill_who = optarg;
+                        break;
+
+                case 's':
+                        arg_signal = signal_from_string_try_harder(optarg);
+                        if (arg_signal < 0) {
+                                log_error("Failed to parse signal string %s.", optarg);
+                                return -EINVAL;
+                        }
+                        break;
+
+                case 'P':
+                        arg_transport = TRANSPORT_POLKIT;
+                        break;
+
+                case 'H':
+                        arg_transport = TRANSPORT_SSH;
+                        arg_host = optarg;
+                        break;
+
+                case '?':
+                        return -EINVAL;
+
+                default:
+                        log_error("Unknown option code %c", c);
+                        return -EINVAL;
+                }
+        }
+
+        return 1;
+}
+
+static int loginctl_main(DBusConnection *bus, int argc, char *argv[], DBusError *error) {
+
+        static const struct {
+                const char* verb;
+                const enum {
+                        MORE,
+                        LESS,
+                        EQUAL
+                } argc_cmp;
+                const int argc;
+                int (* const dispatch)(DBusConnection *bus, char **args, unsigned n);
+        } verbs[] = {
+                { "list-sessions",         LESS,   1, list_sessions    },
+                { "session-status",        MORE,   2, show             },
+                { "show-session",          MORE,   1, show             },
+                { "activate",              EQUAL,  2, activate         },
+                { "lock-session",          MORE,   2, activate         },
+                { "unlock-session",        MORE,   2, activate         },
+                { "terminate-session",     MORE,   2, activate         },
+                { "kill-session",          MORE,   2, kill_session     },
+                { "list-users",            EQUAL,  1, list_users       },
+                { "user-status",           MORE,   2, show             },
+                { "show-user",             MORE,   1, show             },
+                { "enable-linger",         MORE,   2, enable_linger    },
+                { "disable-linger",        MORE,   2, enable_linger    },
+                { "terminate-user",        MORE,   2, terminate_user   },
+                { "kill-user",             MORE,   2, kill_user        },
+                { "list-seats",            EQUAL,  1, list_seats       },
+                { "seat-status",           MORE,   2, show             },
+                { "show-seat",             MORE,   1, show             },
+                { "attach",                MORE,   3, attach           },
+                { "flush-devices",         EQUAL,  1, flush_devices    },
+                { "terminate-seat",        MORE,   2, terminate_seat   },
+        };
+
+        int left;
+        unsigned i;
+
+        assert(argc >= 0);
+        assert(argv);
+        assert(error);
+
+        left = argc - optind;
+
+        if (left <= 0)
+                /* Special rule: no arguments means "list-sessions" */
+                i = 0;
+        else {
+                if (streq(argv[optind], "help")) {
+                        help();
+                        return 0;
+                }
+
+                for (i = 0; i < ELEMENTSOF(verbs); i++)
+                        if (streq(argv[optind], verbs[i].verb))
+                                break;
+
+                if (i >= ELEMENTSOF(verbs)) {
+                        log_error("Unknown operation %s", argv[optind]);
+                        return -EINVAL;
+                }
+        }
+
+        switch (verbs[i].argc_cmp) {
+
+        case EQUAL:
+                if (left != verbs[i].argc) {
+                        log_error("Invalid number of arguments.");
+                        return -EINVAL;
+                }
+
+                break;
+
+        case MORE:
+                if (left < verbs[i].argc) {
+                        log_error("Too few arguments.");
+                        return -EINVAL;
+                }
+
+                break;
+
+        case LESS:
+                if (left > verbs[i].argc) {
+                        log_error("Too many arguments.");
+                        return -EINVAL;
+                }
+
+                break;
+
+        default:
+                assert_not_reached("Unknown comparison operator.");
+        }
+
+        if (!bus) {
+                log_error("Failed to get D-Bus connection: %s", error->message);
+                return -EIO;
+        }
+
+        return verbs[i].dispatch(bus, argv + optind, left);
+}
+
+int main(int argc, char*argv[]) {
+        int r, retval = EXIT_FAILURE;
+        DBusConnection *bus = NULL;
+        DBusError error;
+
+        dbus_error_init(&error);
+
+        log_parse_environment();
+        log_open();
+
+        r = parse_argv(argc, argv);
+        if (r < 0)
+                goto finish;
+        else if (r == 0) {
+                retval = EXIT_SUCCESS;
+                goto finish;
+        }
+
+        if (arg_transport == TRANSPORT_NORMAL)
+                bus = dbus_bus_get_private(DBUS_BUS_SYSTEM, &error);
+        else if (arg_transport == TRANSPORT_POLKIT)
+                bus_connect_system_polkit(&bus, &error);
+        else if (arg_transport == TRANSPORT_SSH)
+                bus_connect_system_ssh(NULL, arg_host, &bus, &error);
+        else
+                assert_not_reached("Uh, invalid transport...");
+
+        r = loginctl_main(bus, argc, argv, &error);
+        retval = r < 0 ? EXIT_FAILURE : r;
+
+finish:
+        if (bus) {
+                dbus_connection_flush(bus);
+                dbus_connection_close(bus);
+                dbus_connection_unref(bus);
+        }
+
+        dbus_error_free(&error);
+        dbus_shutdown();
+
+        strv_free(arg_property);
+
+        pager_close();
+
+        return retval;
+}
diff --git a/src/login/logind-acl.c b/src/login/logind-acl.c
new file mode 100644 (file)
index 0000000..eb8a48d
--- /dev/null
@@ -0,0 +1,248 @@
+/*-*- 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 <http://www.gnu.org/licenses/>.
+***/
+
+#include <assert.h>
+#include <sys/acl.h>
+#include <acl/libacl.h>
+#include <errno.h>
+#include <string.h>
+
+#include "logind-acl.h"
+#include "util.h"
+#include "acl-util.h"
+
+static int flush_acl(acl_t acl) {
+        acl_entry_t i;
+        int found;
+        bool changed = false;
+
+        assert(acl);
+
+        for (found = acl_get_entry(acl, ACL_FIRST_ENTRY, &i);
+             found > 0;
+             found = acl_get_entry(acl, ACL_NEXT_ENTRY, &i)) {
+
+                acl_tag_t tag;
+
+                if (acl_get_tag_type(i, &tag) < 0)
+                        return -errno;
+
+                if (tag != ACL_USER)
+                        continue;
+
+                if (acl_delete_entry(acl, i) < 0)
+                        return -errno;
+
+                changed = true;
+        }
+
+        if (found < 0)
+                return -errno;
+
+        return changed;
+}
+
+int devnode_acl(const char *path,
+                bool flush,
+                bool del, uid_t old_uid,
+                bool add, uid_t new_uid) {
+
+        acl_t acl;
+        int r = 0;
+        bool changed = false;
+
+        assert(path);
+
+        acl = acl_get_file(path, ACL_TYPE_ACCESS);
+        if (!acl)
+                return -errno;
+
+        if (flush) {
+
+                r = flush_acl(acl);
+                if (r < 0)
+                        goto finish;
+                if (r > 0)
+                        changed = true;
+
+        } else if (del && old_uid > 0) {
+                acl_entry_t entry;
+
+                r = acl_find_uid(acl, old_uid, &entry);
+                if (r < 0)
+                        goto finish;
+
+                if (r > 0) {
+                        if (acl_delete_entry(acl, entry) < 0) {
+                                r = -errno;
+                                goto finish;
+                        }
+
+                        changed = true;
+                }
+        }
+
+        if (add && new_uid > 0) {
+                acl_entry_t entry;
+                acl_permset_t permset;
+                int rd, wt;
+
+                r = acl_find_uid(acl, new_uid, &entry);
+                if (r < 0)
+                        goto finish;
+
+                if (r == 0) {
+                        if (acl_create_entry(&acl, &entry) < 0) {
+                                r = -errno;
+                                goto finish;
+                        }
+
+                        if (acl_set_tag_type(entry, ACL_USER) < 0 ||
+                            acl_set_qualifier(entry, &new_uid) < 0) {
+                                r = -errno;
+                                goto finish;
+                        }
+                }
+
+                if (acl_get_permset(entry, &permset) < 0) {
+                        r = -errno;
+                        goto finish;
+                }
+
+                rd = acl_get_perm(permset, ACL_READ);
+                if (rd < 0) {
+                        r = -errno;
+                        goto finish;
+                }
+
+                wt = acl_get_perm(permset, ACL_WRITE);
+                if (wt < 0) {
+                        r = -errno;
+                        goto finish;
+                }
+
+                if (!rd || !wt) {
+
+                        if (acl_add_perm(permset, ACL_READ|ACL_WRITE) < 0) {
+                                r = -errno;
+                                goto finish;
+                        }
+
+                        changed = true;
+                }
+        }
+
+        if (!changed)
+                goto finish;
+
+        if (acl_calc_mask(&acl) < 0) {
+                r = -errno;
+                goto finish;
+        }
+
+        if (acl_set_file(path, ACL_TYPE_ACCESS, acl) < 0) {
+                r = -errno;
+                goto finish;
+        }
+
+        r = 0;
+
+finish:
+        acl_free(acl);
+
+        return r;
+}
+
+int devnode_acl_all(struct udev *udev,
+                    const char *seat,
+                    bool flush,
+                    bool del, uid_t old_uid,
+                    bool add, uid_t new_uid) {
+
+        struct udev_list_entry *item = NULL, *first = NULL;
+        struct udev_enumerate *e;
+        int r;
+
+        assert(udev);
+
+        if (isempty(seat))
+                seat = "seat0";
+
+        e = udev_enumerate_new(udev);
+        if (!e)
+                return -ENOMEM;
+
+        /* We can only match by one tag in libudev. We choose
+         * "uaccess" for that. If we could match for two tags here we
+         * could add the seat name as second match tag, but this would
+         * be hardly optimizable in libudev, and hence checking the
+         * second tag manually in our loop is a good solution. */
+
+        r = udev_enumerate_add_match_tag(e, "uaccess");
+        if (r < 0)
+                goto finish;
+
+        r = udev_enumerate_scan_devices(e);
+        if (r < 0)
+                goto finish;
+
+        first = udev_enumerate_get_list_entry(e);
+        udev_list_entry_foreach(item, first) {
+                struct udev_device *d;
+                const char *node, *sn;
+
+                d = udev_device_new_from_syspath(udev, udev_list_entry_get_name(item));
+                if (!d) {
+                        r = -ENOMEM;
+                        goto finish;
+                }
+
+                sn = udev_device_get_property_value(d, "ID_SEAT");
+                if (isempty(sn))
+                        sn = "seat0";
+
+                if (!streq(seat, sn)) {
+                        udev_device_unref(d);
+                        continue;
+                }
+
+                node = udev_device_get_devnode(d);
+                if (!node) {
+                        /* In case people mistag devices with nodes, we need to ignore this */
+                        udev_device_unref(d);
+                        continue;
+                }
+
+                log_debug("Fixing up %s for seat %s...", node, sn);
+
+                r = devnode_acl(node, flush, del, old_uid, add, new_uid);
+                udev_device_unref(d);
+
+                if (r < 0)
+                        goto finish;
+        }
+
+finish:
+        if (e)
+                udev_enumerate_unref(e);
+
+        return r;
+}
diff --git a/src/login/logind-acl.h b/src/login/logind-acl.h
new file mode 100644 (file)
index 0000000..72740f5
--- /dev/null
@@ -0,0 +1,60 @@
+/*-*- Mode: C; c-basic-offset: 8; indent-tabs-mode: nil -*-*/
+
+#ifndef foologindaclhfoo
+#define foologindaclhfoo
+
+/***
+  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 <http://www.gnu.org/licenses/>.
+***/
+
+#include <sys/types.h>
+#include <stdbool.h>
+#include <libudev.h>
+
+#ifdef HAVE_ACL
+
+int devnode_acl(const char *path,
+                bool flush,
+                bool del, uid_t old_uid,
+                bool add, uid_t new_uid);
+
+int devnode_acl_all(struct udev *udev,
+                    const char *seat,
+                    bool flush,
+                    bool del, uid_t old_uid,
+                    bool add, uid_t new_uid);
+#else
+
+static inline int devnode_acl(const char *path,
+                bool flush,
+                bool del, uid_t old_uid,
+                bool add, uid_t new_uid) {
+        return 0;
+}
+
+static inline int devnode_acl_all(struct udev *udev,
+                                  const char *seat,
+                                  bool flush,
+                                  bool del, uid_t old_uid,
+                                  bool add, uid_t new_uid) {
+        return 0;
+}
+
+#endif
+
+#endif
diff --git a/src/login/logind-dbus.c b/src/login/logind-dbus.c
new file mode 100644 (file)
index 0000000..ea6b89f
--- /dev/null
@@ -0,0 +1,1697 @@
+/*-*- 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 <http://www.gnu.org/licenses/>.
+***/
+
+#include <errno.h>
+#include <string.h>
+#include <unistd.h>
+#include <pwd.h>
+
+#include "logind.h"
+#include "dbus-common.h"
+#include "strv.h"
+#include "polkit.h"
+#include "special.h"
+
+#define BUS_MANAGER_INTERFACE                                           \
+        " <interface name=\"org.freedesktop.login1.Manager\">\n"        \
+        "  <method name=\"GetSession\">\n"                              \
+        "   <arg name=\"id\" type=\"s\" direction=\"in\"/>\n"           \
+        "   <arg name=\"session\" type=\"o\" direction=\"out\"/>\n"     \
+        "  </method>\n"                                                 \
+        "  <method name=\"GetSessionByPID\">\n"                         \
+        "   <arg name=\"pid\" type=\"u\" direction=\"in\"/>\n"          \
+        "   <arg name=\"session\" type=\"o\" direction=\"out\"/>\n"     \
+        "  </method>\n"                                                 \
+        "  <method name=\"GetUser\">\n"                                 \
+        "   <arg name=\"uid\" type=\"u\" direction=\"in\"/>\n"          \
+        "   <arg name=\"user\" type=\"o\" direction=\"out\"/>\n"        \
+        "  </method>\n"                                                 \
+        "  <method name=\"GetSeat\">\n"                                 \
+        "   <arg name=\"id\" type=\"s\" direction=\"in\"/>\n"           \
+        "   <arg name=\"seat\" type=\"o\" direction=\"out\"/>\n"        \
+        "  </method>\n"                                                 \
+        "  <method name=\"ListSessions\">\n"                            \
+        "   <arg name=\"sessions\" type=\"a(susso)\" direction=\"out\"/>\n" \
+        "  </method>\n"                                                 \
+        "  <method name=\"ListUsers\">\n"                               \
+        "   <arg name=\"users\" type=\"a(uso)\" direction=\"out\"/>\n"  \
+        "  </method>\n"                                                 \
+        "  <method name=\"ListSeats\">\n"                               \
+        "   <arg name=\"seats\" type=\"a(so)\" direction=\"out\"/>\n"   \
+        "  </method>\n"                                                 \
+        "  <method name=\"CreateSession\">\n"                           \
+        "   <arg name=\"uid\" type=\"u\" direction=\"in\"/>\n"          \
+        "   <arg name=\"leader\" type=\"u\" direction=\"in\"/>\n"       \
+        "   <arg name=\"sevice\" type=\"s\" direction=\"in\"/>\n"       \
+        "   <arg name=\"type\" type=\"s\" direction=\"in\"/>\n"         \
+        "   <arg name=\"class\" type=\"s\" direction=\"in\"/>\n"        \
+        "   <arg name=\"seat\" type=\"s\" direction=\"in\"/>\n"         \
+        "   <arg name=\"vtnr\" type=\"u\" direction=\"in\"/>\n"         \
+        "   <arg name=\"tty\" type=\"s\" direction=\"in\"/>\n"          \
+        "   <arg name=\"display\" type=\"s\" direction=\"in\"/>\n"      \
+        "   <arg name=\"remote\" type=\"b\" direction=\"in\"/>\n"       \
+        "   <arg name=\"remote_user\" type=\"s\" direction=\"in\"/>\n"  \
+        "   <arg name=\"remote_host\" type=\"s\" direction=\"in\"/>\n"  \
+        "   <arg name=\"controllers\" type=\"as\" direction=\"in\"/>\n" \
+        "   <arg name=\"reset_controllers\" type=\"as\" direction=\"in\"/>\n" \
+        "   <arg name=\"kill_processes\" type=\"b\" direction=\"in\"/>\n" \
+        "   <arg name=\"id\" type=\"s\" direction=\"out\"/>\n"          \
+        "   <arg name=\"path\" type=\"o\" direction=\"out\"/>\n"        \
+        "   <arg name=\"runtime_path\" type=\"o\" direction=\"out\"/>\n" \
+        "   <arg name=\"fd\" type=\"h\" direction=\"out\"/>\n"          \
+        "   <arg name=\"seat\" type=\"s\" direction=\"out\"/>\n"        \
+        "   <arg name=\"vtnr\" type=\"u\" direction=\"out\"/>\n"        \
+        "  </method>\n"                                                 \
+        "  <method name=\"ReleaseSession\">\n"                          \
+        "   <arg name=\"id\" type=\"s\" direction=\"in\"/>\n"           \
+        "  </method>\n"                                                 \
+        "  <method name=\"ActivateSession\">\n"                         \
+        "   <arg name=\"id\" type=\"s\" direction=\"in\"/>\n"           \
+        "  </method>\n"                                                 \
+        "  <method name=\"ActivateSessionOnSeat\">\n"                   \
+        "   <arg name=\"id\" type=\"s\" direction=\"in\"/>\n"           \
+        "   <arg name=\"seat\" type=\"s\" direction=\"in\"/>\n"         \
+        "  </method>\n"                                                 \
+        "  <method name=\"LockSession\">\n"                             \
+        "   <arg name=\"id\" type=\"s\" direction=\"in\"/>\n"           \
+        "  </method>\n"                                                 \
+        "  <method name=\"UnlockSession\">\n"                           \
+        "   <arg name=\"id\" type=\"s\" direction=\"in\"/>\n"           \
+        "  </method>\n"                                                 \
+        "  <method name=\"KillSession\">\n"                             \
+        "   <arg name=\"id\" type=\"s\" direction=\"in\"/>\n"           \
+        "   <arg name=\"who\" type=\"s\" direction=\"in\"/>\n"          \
+        "   <arg name=\"signal\" type=\"s\" direction=\"in\"/>\n"       \
+        "  </method>\n"                                                 \
+        "  <method name=\"KillUser\">\n"                                \
+        "   <arg name=\"uid\" type=\"u\" direction=\"in\"/>\n"          \
+        "   <arg name=\"signal\" type=\"s\" direction=\"in\"/>\n"       \
+        "  </method>\n"                                                 \
+        "  <method name=\"TerminateSession\">\n"                        \
+        "   <arg name=\"id\" type=\"s\" direction=\"in\"/>\n"           \
+        "  </method>\n"                                                 \
+        "  <method name=\"TerminateUser\">\n"                           \
+        "   <arg name=\"uid\" type=\"u\" direction=\"in\"/>\n"          \
+        "  </method>\n"                                                 \
+        "  <method name=\"TerminateSeat\">\n"                           \
+        "   <arg name=\"id\" type=\"s\" direction=\"in\"/>\n"           \
+        "  </method>\n"                                                 \
+        "  <method name=\"SetUserLinger\">\n"                           \
+        "   <arg name=\"uid\" type=\"u\" direction=\"in\"/>\n"          \
+        "   <arg name=\"b\" type=\"b\" direction=\"in\"/>\n"            \
+        "   <arg name=\"interactive\" type=\"b\" direction=\"in\"/>\n"  \
+        "  </method>\n"                                                 \
+        "  <method name=\"AttachDevice\">\n"                            \
+        "   <arg name=\"seat\" type=\"s\" direction=\"in\"/>\n"         \
+        "   <arg name=\"sysfs\" type=\"s\" direction=\"in\"/>\n"        \
+        "   <arg name=\"interactive\" type=\"b\" direction=\"in\"/>\n"  \
+        "  </method>\n"                                                 \
+        "  <method name=\"FlushDevices\">\n"                            \
+        "   <arg name=\"interactive\" type=\"b\" direction=\"in\"/>\n"  \
+        "  </method>\n"                                                 \
+        "  <method name=\"PowerOff\">\n"                                \
+        "   <arg name=\"interactive\" type=\"b\" direction=\"in\"/>\n"  \
+        "  </method>\n"                                                 \
+        "  <method name=\"Reboot\">\n"                                  \
+        "   <arg name=\"interactive\" type=\"b\" direction=\"in\"/>\n"  \
+        "  </method>\n"                                                 \
+        "  <method name=\"CanPowerOff\">\n"                             \
+        "   <arg name=\"result\" type=\"s\" direction=\"out\"/>\n"      \
+        "  </method>\n"                                                 \
+        "  <method name=\"CanReboot\">\n"                               \
+        "   <arg name=\"result\" type=\"s\" direction=\"out\"/>\n"      \
+        "  </method>\n"                                                 \
+        "  <signal name=\"SessionNew\">\n"                              \
+        "   <arg name=\"id\" type=\"s\"/>\n"                            \
+        "   <arg name=\"path\" type=\"o\"/>\n"                          \
+        "  </signal>\n"                                                 \
+        "  <signal name=\"SessionRemoved\">\n"                          \
+        "   <arg name=\"id\" type=\"s\"/>\n"                            \
+        "   <arg name=\"path\" type=\"o\"/>\n"                          \
+        "  </signal>\n"                                                 \
+        "  <signal name=\"UserNew\">\n"                                 \
+        "   <arg name=\"uid\" type=\"u\"/>\n"                           \
+        "   <arg name=\"path\" type=\"o\"/>\n"                          \
+        "  </signal>\n"                                                 \
+        "  <signal name=\"UserRemoved\">\n"                             \
+        "   <arg name=\"uid\" type=\"u\"/>\n"                           \
+        "   <arg name=\"path\" type=\"o\"/>\n"                          \
+        "  </signal>\n"                                                 \
+        "  <signal name=\"SeatNew\">\n"                                 \
+        "   <arg name=\"id\" type=\"s\"/>\n"                            \
+        "   <arg name=\"path\" type=\"o\"/>\n"                          \
+        "  </signal>\n"                                                 \
+        "  <signal name=\"SeatRemoved\">\n"                             \
+        "   <arg name=\"id\" type=\"s\"/>\n"                            \
+        "   <arg name=\"path\" type=\"o\"/>\n"                          \
+        "  </signal>\n"                                                 \
+        "  <property name=\"ControlGroupHierarchy\" type=\"s\" access=\"read\"/>\n" \
+        "  <property name=\"Controllers\" type=\"as\" access=\"read\"/>\n" \
+        "  <property name=\"ResetControllers\" type=\"as\" access=\"read\"/>\n" \
+        "  <property name=\"NAutoVTs\" type=\"u\" access=\"read\"/>\n" \
+        "  <property name=\"KillOnlyUsers\" type=\"as\" access=\"read\"/>\n" \
+        "  <property name=\"KillExcludeUsers\" type=\"as\" access=\"read\"/>\n" \
+        "  <property name=\"KillUserProcesses\" type=\"b\" access=\"read\"/>\n" \
+        "  <property name=\"IdleHint\" type=\"b\" access=\"read\"/>\n"  \
+        "  <property name=\"IdleSinceHint\" type=\"t\" access=\"read\"/>\n" \
+        "  <property name=\"IdleSinceHintMonotonic\" type=\"t\" access=\"read\"/>\n" \
+        " </interface>\n"
+
+#define INTROSPECTION_BEGIN                                             \
+        DBUS_INTROSPECT_1_0_XML_DOCTYPE_DECL_NODE                       \
+        "<node>\n"                                                      \
+        BUS_MANAGER_INTERFACE                                           \
+        BUS_PROPERTIES_INTERFACE                                        \
+        BUS_PEER_INTERFACE                                              \
+        BUS_INTROSPECTABLE_INTERFACE
+
+#define INTROSPECTION_END                                               \
+        "</node>\n"
+
+#define INTERFACES_LIST                              \
+        BUS_GENERIC_INTERFACES_LIST                  \
+        "org.freedesktop.login1.Manager\0"
+
+static int bus_manager_append_idle_hint(DBusMessageIter *i, const char *property, void *data) {
+        Manager *m = data;
+        dbus_bool_t b;
+
+        assert(i);
+        assert(property);
+        assert(m);
+
+        b = manager_get_idle_hint(m, NULL) > 0;
+        if (!dbus_message_iter_append_basic(i, DBUS_TYPE_BOOLEAN, &b))
+                return -ENOMEM;
+
+        return 0;
+}
+
+static int bus_manager_append_idle_hint_since(DBusMessageIter *i, const char *property, void *data) {
+        Manager *m = data;
+        dual_timestamp t;
+        uint64_t u;
+
+        assert(i);
+        assert(property);
+        assert(m);
+
+        manager_get_idle_hint(m, &t);
+        u = streq(property, "IdleSinceHint") ? t.realtime : t.monotonic;
+
+        if (!dbus_message_iter_append_basic(i, DBUS_TYPE_UINT64, &u))
+                return -ENOMEM;
+
+        return 0;
+}
+
+static int bus_manager_create_session(Manager *m, DBusMessage *message, DBusMessage **_reply) {
+        Session *session = NULL;
+        User *user = NULL;
+        const char *type, *class, *seat, *tty, *display, *remote_user, *remote_host, *service;
+        uint32_t uid, leader, audit_id = 0;
+        dbus_bool_t remote, kill_processes;
+        char **controllers = NULL, **reset_controllers = NULL;
+        SessionType t;
+        SessionClass c;
+        Seat *s;
+        DBusMessageIter iter;
+        int r;
+        char *id = NULL, *p;
+        uint32_t vtnr = 0;
+        int fifo_fd = -1;
+        DBusMessage *reply = NULL;
+        bool b;
+
+        assert(m);
+        assert(message);
+        assert(_reply);
+
+        if (!dbus_message_iter_init(message, &iter) ||
+            dbus_message_iter_get_arg_type(&iter) != DBUS_TYPE_UINT32)
+                return -EINVAL;
+
+        dbus_message_iter_get_basic(&iter, &uid);
+
+        if (!dbus_message_iter_next(&iter) ||
+            dbus_message_iter_get_arg_type(&iter) != DBUS_TYPE_UINT32)
+                return -EINVAL;
+
+        dbus_message_iter_get_basic(&iter, &leader);
+
+        if (leader <= 0 ||
+            !dbus_message_iter_next(&iter) ||
+            dbus_message_iter_get_arg_type(&iter) != DBUS_TYPE_STRING)
+                return -EINVAL;
+
+        dbus_message_iter_get_basic(&iter, &service);
+
+        if (!dbus_message_iter_next(&iter) ||
+            dbus_message_iter_get_arg_type(&iter) != DBUS_TYPE_STRING)
+                return -EINVAL;
+
+        dbus_message_iter_get_basic(&iter, &type);
+        t = session_type_from_string(type);
+
+        if (t < 0 ||
+            !dbus_message_iter_next(&iter) ||
+            dbus_message_iter_get_arg_type(&iter) != DBUS_TYPE_STRING)
+                return -EINVAL;
+
+        dbus_message_iter_get_basic(&iter, &class);
+        if (isempty(class))
+                c = SESSION_USER;
+        else
+                c = session_class_from_string(class);
+
+        if (c < 0 ||
+            !dbus_message_iter_next(&iter) ||
+            dbus_message_iter_get_arg_type(&iter) != DBUS_TYPE_STRING)
+                return -EINVAL;
+
+        dbus_message_iter_get_basic(&iter, &seat);
+
+        if (isempty(seat))
+                s = NULL;
+        else {
+                s = hashmap_get(m->seats, seat);
+                if (!s)
+                        return -ENOENT;
+        }
+
+        if (!dbus_message_iter_next(&iter) ||
+            dbus_message_iter_get_arg_type(&iter) != DBUS_TYPE_UINT32)
+                return -EINVAL;
+
+        dbus_message_iter_get_basic(&iter, &vtnr);
+
+        if (!dbus_message_iter_next(&iter) ||
+            dbus_message_iter_get_arg_type(&iter) != DBUS_TYPE_STRING)
+                return -EINVAL;
+
+        dbus_message_iter_get_basic(&iter, &tty);
+
+        if (tty_is_vc(tty)) {
+                int v;
+
+                if (!s)
+                        s = m->vtconsole;
+                else if (s != m->vtconsole)
+                        return -EINVAL;
+
+                v = vtnr_from_tty(tty);
+
+                if (v <= 0)
+                        return v < 0 ? v : -EINVAL;
+
+                if (vtnr <= 0)
+                        vtnr = (uint32_t) v;
+                else if (vtnr != (uint32_t) v)
+                        return -EINVAL;
+
+        } else if (!isempty(tty) && s && seat_is_vtconsole(s))
+                return -EINVAL;
+
+        if (s) {
+                if (seat_can_multi_session(s)) {
+                        if (vtnr <= 0 || vtnr > 63)
+                                return -EINVAL;
+                } else {
+                        if (vtnr > 0)
+                                return -EINVAL;
+                }
+        }
+
+        if (!dbus_message_iter_next(&iter) ||
+            dbus_message_iter_get_arg_type(&iter) != DBUS_TYPE_STRING)
+                return -EINVAL;
+
+        dbus_message_iter_get_basic(&iter, &display);
+
+        if (!dbus_message_iter_next(&iter) ||
+            dbus_message_iter_get_arg_type(&iter) != DBUS_TYPE_BOOLEAN)
+                return -EINVAL;
+
+        dbus_message_iter_get_basic(&iter, &remote);
+
+        if (!dbus_message_iter_next(&iter) ||
+            dbus_message_iter_get_arg_type(&iter) != DBUS_TYPE_STRING)
+                return -EINVAL;
+
+        dbus_message_iter_get_basic(&iter, &remote_user);
+
+        if (!dbus_message_iter_next(&iter) ||
+            dbus_message_iter_get_arg_type(&iter) != DBUS_TYPE_STRING)
+                return -EINVAL;
+
+        dbus_message_iter_get_basic(&iter, &remote_host);
+
+        if (!dbus_message_iter_next(&iter) ||
+            dbus_message_iter_get_arg_type(&iter) != DBUS_TYPE_ARRAY ||
+            dbus_message_iter_get_element_type(&iter) != DBUS_TYPE_STRING)
+                return -EINVAL;
+
+        r = bus_parse_strv_iter(&iter, &controllers);
+        if (r < 0)
+                return -EINVAL;
+
+        if (strv_contains(controllers, "systemd") ||
+            !dbus_message_iter_next(&iter) ||
+            dbus_message_iter_get_arg_type(&iter) != DBUS_TYPE_ARRAY ||
+            dbus_message_iter_get_element_type(&iter) != DBUS_TYPE_STRING) {
+                r = -EINVAL;
+                goto fail;
+        }
+
+        r = bus_parse_strv_iter(&iter, &reset_controllers);
+        if (r < 0)
+                goto fail;
+
+        if (strv_contains(reset_controllers, "systemd") ||
+            !dbus_message_iter_next(&iter) ||
+            dbus_message_iter_get_arg_type(&iter) != DBUS_TYPE_BOOLEAN) {
+                r = -EINVAL;
+                goto fail;
+        }
+
+        dbus_message_iter_get_basic(&iter, &kill_processes);
+
+        r = manager_add_user_by_uid(m, uid, &user);
+        if (r < 0)
+                goto fail;
+
+        audit_session_from_pid(leader, &audit_id);
+
+        if (audit_id > 0) {
+                asprintf(&id, "%lu", (unsigned long) audit_id);
+
+                if (!id) {
+                        r = -ENOMEM;
+                        goto fail;
+                }
+
+                session = hashmap_get(m->sessions, id);
+
+                if (session) {
+                        free(id);
+
+                        fifo_fd = session_create_fifo(session);
+                        if (fifo_fd < 0) {
+                                r = fifo_fd;
+                                goto fail;
+                        }
+
+                        /* Session already exists, client is probably
+                         * something like "su" which changes uid but
+                         * is still the same audit session */
+
+                        reply = dbus_message_new_method_return(message);
+                        if (!reply) {
+                                r = -ENOMEM;
+                                goto fail;
+                        }
+
+                        p = session_bus_path(session);
+                        if (!p) {
+                                r = -ENOMEM;
+                                goto fail;
+                        }
+
+                        seat = session->seat ? session->seat->id : "";
+                        vtnr = session->vtnr;
+                        b = dbus_message_append_args(
+                                        reply,
+                                        DBUS_TYPE_STRING, &session->id,
+                                        DBUS_TYPE_OBJECT_PATH, &p,
+                                        DBUS_TYPE_STRING, &session->user->runtime_path,
+                                        DBUS_TYPE_UNIX_FD, &fifo_fd,
+                                        DBUS_TYPE_STRING, &seat,
+                                        DBUS_TYPE_UINT32, &vtnr,
+                                        DBUS_TYPE_INVALID);
+                        free(p);
+
+                        if (!b) {
+                                r = -ENOMEM;
+                                goto fail;
+                        }
+
+                        close_nointr_nofail(fifo_fd);
+                        *_reply = reply;
+
+                        strv_free(controllers);
+                        strv_free(reset_controllers);
+
+                        return 0;
+                }
+
+        } else {
+                do {
+                        free(id);
+                        asprintf(&id, "c%lu", ++m->session_counter);
+
+                        if (!id) {
+                                r = -ENOMEM;
+                                goto fail;
+                        }
+
+                } while (hashmap_get(m->sessions, id));
+        }
+
+        r = manager_add_session(m, user, id, &session);
+        free(id);
+        if (r < 0)
+                goto fail;
+
+        session->leader = leader;
+        session->audit_id = audit_id;
+        session->type = t;
+        session->class = c;
+        session->remote = remote;
+        session->controllers = controllers;
+        session->reset_controllers = reset_controllers;
+        session->kill_processes = kill_processes;
+        session->vtnr = vtnr;
+
+        controllers = reset_controllers = NULL;
+
+        if (!isempty(tty)) {
+                session->tty = strdup(tty);
+                if (!session->tty) {
+                        r = -ENOMEM;
+                        goto fail;
+                }
+        }
+
+        if (!isempty(display)) {
+                session->display = strdup(display);
+                if (!session->display) {
+                        r = -ENOMEM;
+                        goto fail;
+                }
+        }
+
+        if (!isempty(remote_user)) {
+                session->remote_user = strdup(remote_user);
+                if (!session->remote_user) {
+                        r = -ENOMEM;
+                        goto fail;
+                }
+        }
+
+        if (!isempty(remote_host)) {
+                session->remote_host = strdup(remote_host);
+                if (!session->remote_host) {
+                        r = -ENOMEM;
+                        goto fail;
+                }
+        }
+
+        if (!isempty(service)) {
+                session->service = strdup(service);
+                if (!session->service) {
+                        r = -ENOMEM;
+                        goto fail;
+                }
+        }
+
+        fifo_fd = session_create_fifo(session);
+        if (fifo_fd < 0) {
+                r = fifo_fd;
+                goto fail;
+        }
+
+        if (s) {
+                r = seat_attach_session(s, session);
+                if (r < 0)
+                        goto fail;
+        }
+
+        r = session_start(session);
+        if (r < 0)
+                goto fail;
+
+        reply = dbus_message_new_method_return(message);
+        if (!reply) {
+                r = -ENOMEM;
+                goto fail;
+        }
+
+        p = session_bus_path(session);
+        if (!p) {
+                r = -ENOMEM;
+                goto fail;
+        }
+
+        seat = s ? s->id : "";
+        b = dbus_message_append_args(
+                        reply,
+                        DBUS_TYPE_STRING, &session->id,
+                        DBUS_TYPE_OBJECT_PATH, &p,
+                        DBUS_TYPE_STRING, &session->user->runtime_path,
+                        DBUS_TYPE_UNIX_FD, &fifo_fd,
+                        DBUS_TYPE_STRING, &seat,
+                        DBUS_TYPE_UINT32, &vtnr,
+                        DBUS_TYPE_INVALID);
+        free(p);
+
+        if (!b) {
+                r = -ENOMEM;
+                goto fail;
+        }
+
+        close_nointr_nofail(fifo_fd);
+        *_reply = reply;
+
+        return 0;
+
+fail:
+        strv_free(controllers);
+        strv_free(reset_controllers);
+
+        if (session)
+                session_add_to_gc_queue(session);
+
+        if (user)
+                user_add_to_gc_queue(user);
+
+        if (fifo_fd >= 0)
+                close_nointr_nofail(fifo_fd);
+
+        if (reply)
+                dbus_message_unref(reply);
+
+        return r;
+}
+
+static int trigger_device(Manager *m, struct udev_device *d) {
+        struct udev_enumerate *e;
+        struct udev_list_entry *first, *item;
+        int r;
+
+        assert(m);
+
+        e = udev_enumerate_new(m->udev);
+        if (!e) {
+                r = -ENOMEM;
+                goto finish;
+        }
+
+        if (d) {
+                if (udev_enumerate_add_match_parent(e, d) < 0) {
+                        r = -EIO;
+                        goto finish;
+                }
+        }
+
+        if (udev_enumerate_scan_devices(e) < 0) {
+                r = -EIO;
+                goto finish;
+        }
+
+        first = udev_enumerate_get_list_entry(e);
+        udev_list_entry_foreach(item, first) {
+                char *t;
+                const char *p;
+
+                p = udev_list_entry_get_name(item);
+
+                t = strappend(p, "/uevent");
+                if (!t) {
+                        r = -ENOMEM;
+                        goto finish;
+                }
+
+                write_one_line_file(t, "change");
+                free(t);
+        }
+
+        r = 0;
+
+finish:
+        if (e)
+                udev_enumerate_unref(e);
+
+        return r;
+}
+
+static int attach_device(Manager *m, const char *seat, const char *sysfs) {
+        struct udev_device *d;
+        char *rule = NULL, *file = NULL;
+        const char *id_for_seat;
+        int r;
+
+        assert(m);
+        assert(seat);
+        assert(sysfs);
+
+        d = udev_device_new_from_syspath(m->udev, sysfs);
+        if (!d)
+                return -ENODEV;
+
+        if (!udev_device_has_tag(d, "seat")) {
+                r = -ENODEV;
+                goto finish;
+        }
+
+        id_for_seat = udev_device_get_property_value(d, "ID_FOR_SEAT");
+        if (!id_for_seat) {
+                r = -ENODEV;
+                goto finish;
+        }
+
+        if (asprintf(&file, "/etc/udev/rules.d/72-seat-%s.rules", id_for_seat) < 0) {
+                r = -ENOMEM;
+                goto finish;
+        }
+
+        if (asprintf(&rule, "TAG==\"seat\", ENV{ID_FOR_SEAT}==\"%s\", ENV{ID_SEAT}=\"%s\"", id_for_seat, seat) < 0) {
+                r = -ENOMEM;
+                goto finish;
+        }
+
+        mkdir_p("/etc/udev/rules.d", 0755);
+        r = write_one_line_file_atomic(file, rule);
+        if (r < 0)
+                goto finish;
+
+        r = trigger_device(m, d);
+
+finish:
+        free(rule);
+        free(file);
+
+        if (d)
+                udev_device_unref(d);
+
+        return r;
+}
+
+static int flush_devices(Manager *m) {
+        DIR *d;
+
+        assert(m);
+
+        d = opendir("/etc/udev/rules.d");
+        if (!d) {
+                if (errno != ENOENT)
+                        log_warning("Failed to open /etc/udev/rules.d: %m");
+        } else {
+                struct dirent *de;
+
+                while ((de = readdir(d))) {
+
+                        if (!dirent_is_file(de))
+                                continue;
+
+                        if (!startswith(de->d_name, "72-seat-"))
+                                continue;
+
+                        if (!endswith(de->d_name, ".rules"))
+                                continue;
+
+                        if (unlinkat(dirfd(d), de->d_name, 0) < 0)
+                                log_warning("Failed to unlink %s: %m", de->d_name);
+                }
+
+                closedir(d);
+        }
+
+        return trigger_device(m, NULL);
+}
+
+static int have_multiple_sessions(
+                DBusConnection *connection,
+                Manager *m,
+                DBusMessage *message,
+                DBusError *error) {
+
+        Session *s;
+
+        assert(m);
+
+        if (hashmap_size(m->sessions) > 1)
+                return true;
+
+        /* Hmm, there's only one session, but let's make sure it
+         * actually belongs to the user who is asking. If not, better
+         * be safe than sorry. */
+
+        s = hashmap_first(m->sessions);
+        if (s) {
+                unsigned long ul;
+
+                ul = dbus_bus_get_unix_user(connection, dbus_message_get_sender(message), error);
+                if (ul == (unsigned long) -1)
+                        return -EIO;
+
+                return s->user->uid != ul;
+        }
+
+        return false;
+}
+
+static const BusProperty bus_login_manager_properties[] = {
+        { "ControlGroupHierarchy",  bus_property_append_string,         "s",  offsetof(Manager, cgroup_path),        true },
+        { "Controllers",            bus_property_append_strv,           "as", offsetof(Manager, controllers),        true },
+        { "ResetControllers",       bus_property_append_strv,           "as", offsetof(Manager, reset_controllers),  true },
+        { "NAutoVTs",               bus_property_append_unsigned,       "u",  offsetof(Manager, n_autovts)           },
+        { "KillOnlyUsers",          bus_property_append_strv,           "as", offsetof(Manager, kill_only_users),    true },
+        { "KillExcludeUsers",       bus_property_append_strv,           "as", offsetof(Manager, kill_exclude_users), true },
+        { "KillUserProcesses",      bus_property_append_bool,           "b",  offsetof(Manager, kill_user_processes) },
+        { "IdleHint",               bus_manager_append_idle_hint,       "b",  0 },
+        { "IdleSinceHint",          bus_manager_append_idle_hint_since, "t",  0 },
+        { "IdleSinceHintMonotonic", bus_manager_append_idle_hint_since, "t",  0 },
+        { NULL, }
+};
+
+static DBusHandlerResult manager_message_handler(
+                DBusConnection *connection,
+                DBusMessage *message,
+                void *userdata) {
+
+        Manager *m = userdata;
+
+        DBusError error;
+        DBusMessage *reply = NULL;
+        int r;
+
+        assert(connection);
+        assert(message);
+        assert(m);
+
+        dbus_error_init(&error);
+
+        if (dbus_message_is_method_call(message, "org.freedesktop.login1.Manager", "GetSession")) {
+                const char *name;
+                char *p;
+                Session *session;
+                bool b;
+
+                if (!dbus_message_get_args(
+                                    message,
+                                    &error,
+                                    DBUS_TYPE_STRING, &name,
+                                    DBUS_TYPE_INVALID))
+                        return bus_send_error_reply(connection, message, &error, -EINVAL);
+
+                session = hashmap_get(m->sessions, name);
+                if (!session)
+                        return bus_send_error_reply(connection, message, &error, -ENOENT);
+
+                reply = dbus_message_new_method_return(message);
+                if (!reply)
+                        goto oom;
+
+                p = session_bus_path(session);
+                if (!p)
+                        goto oom;
+
+                b = dbus_message_append_args(
+                                reply,
+                                DBUS_TYPE_OBJECT_PATH, &p,
+                                DBUS_TYPE_INVALID);
+                free(p);
+
+                if (!b)
+                        goto oom;
+
+        } else if (dbus_message_is_method_call(message, "org.freedesktop.login1.Manager", "GetSessionByPID")) {
+                uint32_t pid;
+                char *p;
+                Session *session;
+                bool b;
+
+                if (!dbus_message_get_args(
+                                    message,
+                                    &error,
+                                    DBUS_TYPE_UINT32, &pid,
+                                    DBUS_TYPE_INVALID))
+                        return bus_send_error_reply(connection, message, &error, -EINVAL);
+
+                r = manager_get_session_by_pid(m, pid, &session);
+                if (r <= 0)
+                        return bus_send_error_reply(connection, message, NULL, r < 0 ? r : -ENOENT);
+
+                reply = dbus_message_new_method_return(message);
+                if (!reply)
+                        goto oom;
+
+                p = session_bus_path(session);
+                if (!p)
+                        goto oom;
+
+                b = dbus_message_append_args(
+                                reply,
+                                DBUS_TYPE_OBJECT_PATH, &p,
+                                DBUS_TYPE_INVALID);
+                free(p);
+
+                if (!b)
+                        goto oom;
+
+        } else if (dbus_message_is_method_call(message, "org.freedesktop.login1.Manager", "GetUser")) {
+                uint32_t uid;
+                char *p;
+                User *user;
+                bool b;
+
+                if (!dbus_message_get_args(
+                                    message,
+                                    &error,
+                                    DBUS_TYPE_UINT32, &uid,
+                                    DBUS_TYPE_INVALID))
+                        return bus_send_error_reply(connection, message, &error, -EINVAL);
+
+                user = hashmap_get(m->users, ULONG_TO_PTR((unsigned long) uid));
+                if (!user)
+                        return bus_send_error_reply(connection, message, &error, -ENOENT);
+
+                reply = dbus_message_new_method_return(message);
+                if (!reply)
+                        goto oom;
+
+                p = user_bus_path(user);
+                if (!p)
+                        goto oom;
+
+                b = dbus_message_append_args(
+                                reply,
+                                DBUS_TYPE_OBJECT_PATH, &p,
+                                DBUS_TYPE_INVALID);
+                free(p);
+
+                if (!b)
+                        goto oom;
+
+        } else if (dbus_message_is_method_call(message, "org.freedesktop.login1.Manager", "GetSeat")) {
+                const char *name;
+                char *p;
+                Seat *seat;
+                bool b;
+
+                if (!dbus_message_get_args(
+                                    message,
+                                    &error,
+                                    DBUS_TYPE_STRING, &name,
+                                    DBUS_TYPE_INVALID))
+                        return bus_send_error_reply(connection, message, &error, -EINVAL);
+
+                seat = hashmap_get(m->seats, name);
+                if (!seat)
+                        return bus_send_error_reply(connection, message, &error, -ENOENT);
+
+                reply = dbus_message_new_method_return(message);
+                if (!reply)
+                        goto oom;
+
+                p = seat_bus_path(seat);
+                if (!p)
+                        goto oom;
+
+                b = dbus_message_append_args(
+                                reply,
+                                DBUS_TYPE_OBJECT_PATH, &p,
+                                DBUS_TYPE_INVALID);
+                free(p);
+
+                if (!b)
+                        goto oom;
+
+        } else if (dbus_message_is_method_call(message, "org.freedesktop.login1.Manager", "ListSessions")) {
+                char *p;
+                Session *session;
+                Iterator i;
+                DBusMessageIter iter, sub;
+                const char *empty = "";
+
+                reply = dbus_message_new_method_return(message);
+                if (!reply)
+                        goto oom;
+
+                dbus_message_iter_init_append(reply, &iter);
+
+                if (!dbus_message_iter_open_container(&iter, DBUS_TYPE_ARRAY, "(susso)", &sub))
+                        goto oom;
+
+                HASHMAP_FOREACH(session, m->sessions, i) {
+                        DBusMessageIter sub2;
+                        uint32_t uid;
+
+                        if (!dbus_message_iter_open_container(&sub, DBUS_TYPE_STRUCT, NULL, &sub2))
+                                goto oom;
+
+                        uid = session->user->uid;
+
+                        p = session_bus_path(session);
+                        if (!p)
+                                goto oom;
+
+                        if (!dbus_message_iter_append_basic(&sub2, DBUS_TYPE_STRING, &session->id) ||
+                            !dbus_message_iter_append_basic(&sub2, DBUS_TYPE_UINT32, &uid) ||
+                            !dbus_message_iter_append_basic(&sub2, DBUS_TYPE_STRING, &session->user->name) ||
+                            !dbus_message_iter_append_basic(&sub2, DBUS_TYPE_STRING, session->seat ? (const char**) &session->seat->id : &empty) ||
+                            !dbus_message_iter_append_basic(&sub2, DBUS_TYPE_OBJECT_PATH, &p)) {
+                                free(p);
+                                goto oom;
+                        }
+
+                        free(p);
+
+                        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.login1.Manager", "ListUsers")) {
+                char *p;
+                User *user;
+                Iterator i;
+                DBusMessageIter iter, sub;
+
+                reply = dbus_message_new_method_return(message);
+                if (!reply)
+                        goto oom;
+
+                dbus_message_iter_init_append(reply, &iter);
+
+                if (!dbus_message_iter_open_container(&iter, DBUS_TYPE_ARRAY, "(uso)", &sub))
+                        goto oom;
+
+                HASHMAP_FOREACH(user, m->users, i) {
+                        DBusMessageIter sub2;
+                        uint32_t uid;
+
+                        if (!dbus_message_iter_open_container(&sub, DBUS_TYPE_STRUCT, NULL, &sub2))
+                                goto oom;
+
+                        uid = user->uid;
+
+                        p = user_bus_path(user);
+                        if (!p)
+                                goto oom;
+
+                        if (!dbus_message_iter_append_basic(&sub2, DBUS_TYPE_UINT32, &uid) ||
+                            !dbus_message_iter_append_basic(&sub2, DBUS_TYPE_STRING, &user->name) ||
+                            !dbus_message_iter_append_basic(&sub2, DBUS_TYPE_OBJECT_PATH, &p)) {
+                                free(p);
+                                goto oom;
+                        }
+
+                        free(p);
+
+                        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.login1.Manager", "ListSeats")) {
+                char *p;
+                Seat *seat;
+                Iterator i;
+                DBusMessageIter iter, sub;
+
+                reply = dbus_message_new_method_return(message);
+                if (!reply)
+                        goto oom;
+
+                dbus_message_iter_init_append(reply, &iter);
+
+                if (!dbus_message_iter_open_container(&iter, DBUS_TYPE_ARRAY, "(so)", &sub))
+                        goto oom;
+
+                HASHMAP_FOREACH(seat, m->seats, i) {
+                        DBusMessageIter sub2;
+
+                        if (!dbus_message_iter_open_container(&sub, DBUS_TYPE_STRUCT, NULL, &sub2))
+                                goto oom;
+
+                        p = seat_bus_path(seat);
+                        if (!p)
+                                goto oom;
+
+                        if (!dbus_message_iter_append_basic(&sub2, DBUS_TYPE_STRING, &seat->id) ||
+                            !dbus_message_iter_append_basic(&sub2, DBUS_TYPE_OBJECT_PATH, &p)) {
+                                free(p);
+                                goto oom;
+                        }
+
+                        free(p);
+
+                        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.login1.Manager", "CreateSession")) {
+
+                r = bus_manager_create_session(m, message, &reply);
+
+                /* Don't delay the work on OOM here, since it might be
+                 * triggered by a low RLIMIT_NOFILE here (since we
+                 * send a dupped fd to the client), and we'd rather
+                 * see this fail quickly then be retried later */
+
+                if (r < 0)
+                        return bus_send_error_reply(connection, message, &error, r);
+
+        } else if (dbus_message_is_method_call(message, "org.freedesktop.login1.Manager", "ReleaseSession")) {
+                const char *name;
+                Session *session;
+
+                if (!dbus_message_get_args(
+                                    message,
+                                    &error,
+                                    DBUS_TYPE_STRING, &name,
+                                    DBUS_TYPE_INVALID))
+                        return bus_send_error_reply(connection, message, &error, -EINVAL);
+
+                session = hashmap_get(m->sessions, name);
+                if (!session)
+                        return bus_send_error_reply(connection, message, &error, -ENOENT);
+
+                /* We use the FIFO to detect stray sessions where the
+                process invoking PAM dies abnormally. We need to make
+                sure that that process is not killed if at the clean
+                end of the session it closes the FIFO. Hence, with
+                this call explicitly turn off the FIFO logic, so that
+                the PAM code can finish clean up on its own */
+                session_remove_fifo(session);
+
+                reply = dbus_message_new_method_return(message);
+                if (!reply)
+                        goto oom;
+
+        } else if (dbus_message_is_method_call(message, "org.freedesktop.login1.Manager", "ActivateSession")) {
+                const char *name;
+                Session *session;
+
+                if (!dbus_message_get_args(
+                                    message,
+                                    &error,
+                                    DBUS_TYPE_STRING, &name,
+                                    DBUS_TYPE_INVALID))
+                        return bus_send_error_reply(connection, message, &error, -EINVAL);
+
+                session = hashmap_get(m->sessions, name);
+                if (!session)
+                        return bus_send_error_reply(connection, message, &error, -ENOENT);
+
+                r = session_activate(session);
+                if (r < 0)
+                        return bus_send_error_reply(connection, message, NULL, r);
+
+                reply = dbus_message_new_method_return(message);
+                if (!reply)
+                        goto oom;
+
+        } else if (dbus_message_is_method_call(message, "org.freedesktop.login1.Manager", "ActivateSessionOnSeat")) {
+                const char *session_name, *seat_name;
+                Session *session;
+                Seat *seat;
+
+                /* Same as ActivateSession() but refuses to work if
+                 * the seat doesn't match */
+
+                if (!dbus_message_get_args(
+                                    message,
+                                    &error,
+                                    DBUS_TYPE_STRING, &session_name,
+                                    DBUS_TYPE_STRING, &seat_name,
+                                    DBUS_TYPE_INVALID))
+                        return bus_send_error_reply(connection, message, &error, -EINVAL);
+
+                session = hashmap_get(m->sessions, session_name);
+                if (!session)
+                        return bus_send_error_reply(connection, message, &error, -ENOENT);
+
+                seat = hashmap_get(m->seats, seat_name);
+                if (!seat)
+                        return bus_send_error_reply(connection, message, &error, -ENOENT);
+
+                if (session->seat != seat)
+                        return bus_send_error_reply(connection, message, &error, -EINVAL);
+
+                r = session_activate(session);
+                if (r < 0)
+                        return bus_send_error_reply(connection, message, NULL, r);
+
+                reply = dbus_message_new_method_return(message);
+                if (!reply)
+                        goto oom;
+
+        } else if (dbus_message_is_method_call(message, "org.freedesktop.login1.Manager", "LockSession") ||
+                   dbus_message_is_method_call(message, "org.freedesktop.login1.Manager", "UnlockSession")) {
+                const char *name;
+                Session *session;
+
+                if (!dbus_message_get_args(
+                                    message,
+                                    &error,
+                                    DBUS_TYPE_STRING, &name,
+                                    DBUS_TYPE_INVALID))
+                        return bus_send_error_reply(connection, message, &error, -EINVAL);
+
+                session = hashmap_get(m->sessions, name);
+                if (!session)
+                        return bus_send_error_reply(connection, message, &error, -ENOENT);
+
+                if (session_send_lock(session, streq(dbus_message_get_member(message), "LockSession")) < 0)
+                        goto oom;
+
+                reply = dbus_message_new_method_return(message);
+                if (!reply)
+                        goto oom;
+
+        } else if (dbus_message_is_method_call(message, "org.freedesktop.login1.Manager", "KillSession")) {
+                const char *swho;
+                int32_t signo;
+                KillWho who;
+                const char *name;
+                Session *session;
+
+                if (!dbus_message_get_args(
+                                    message,
+                                    &error,
+                                    DBUS_TYPE_STRING, &name,
+                                    DBUS_TYPE_STRING, &swho,
+                                    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 (signo <= 0 || signo >= _NSIG)
+                        return bus_send_error_reply(connection, message, &error, -EINVAL);
+
+                session = hashmap_get(m->sessions, name);
+                if (!session)
+                        return bus_send_error_reply(connection, message, &error, -ENOENT);
+
+                r = session_kill(session, who, signo);
+                if (r < 0)
+                        return bus_send_error_reply(connection, message, NULL, r);
+
+                reply = dbus_message_new_method_return(message);
+                if (!reply)
+                        goto oom;
+
+        } else if (dbus_message_is_method_call(message, "org.freedesktop.login1.Manager", "KillUser")) {
+                uint32_t uid;
+                User *user;
+                int32_t signo;
+
+                if (!dbus_message_get_args(
+                                    message,
+                                    &error,
+                                    DBUS_TYPE_UINT32, &uid,
+                                    DBUS_TYPE_INT32, &signo,
+                                    DBUS_TYPE_INVALID))
+                        return bus_send_error_reply(connection, message, &error, -EINVAL);
+
+                if (signo <= 0 || signo >= _NSIG)
+                        return bus_send_error_reply(connection, message, &error, -EINVAL);
+
+                user = hashmap_get(m->users, ULONG_TO_PTR((unsigned long) uid));
+                if (!user)
+                        return bus_send_error_reply(connection, message, &error, -ENOENT);
+
+                r = user_kill(user, signo);
+                if (r < 0)
+                        return bus_send_error_reply(connection, message, NULL, r);
+
+                reply = dbus_message_new_method_return(message);
+                if (!reply)
+                        goto oom;
+
+        } else if (dbus_message_is_method_call(message, "org.freedesktop.login1.Manager", "TerminateSession")) {
+                const char *name;
+                Session *session;
+
+                if (!dbus_message_get_args(
+                                    message,
+                                    &error,
+                                    DBUS_TYPE_STRING, &name,
+                                    DBUS_TYPE_INVALID))
+                        return bus_send_error_reply(connection, message, &error, -EINVAL);
+
+                session = hashmap_get(m->sessions, name);
+                if (!session)
+                        return bus_send_error_reply(connection, message, &error, -ENOENT);
+
+                r = session_stop(session);
+                if (r < 0)
+                        return bus_send_error_reply(connection, message, NULL, r);
+
+                reply = dbus_message_new_method_return(message);
+                if (!reply)
+                        goto oom;
+
+        } else if (dbus_message_is_method_call(message, "org.freedesktop.login1.Manager", "TerminateUser")) {
+                uint32_t uid;
+                User *user;
+
+                if (!dbus_message_get_args(
+                                    message,
+                                    &error,
+                                    DBUS_TYPE_UINT32, &uid,
+                                    DBUS_TYPE_INVALID))
+                        return bus_send_error_reply(connection, message, &error, -EINVAL);
+
+                user = hashmap_get(m->users, ULONG_TO_PTR((unsigned long) uid));
+                if (!user)
+                        return bus_send_error_reply(connection, message, &error, -ENOENT);
+
+                r = user_stop(user);
+                if (r < 0)
+                        return bus_send_error_reply(connection, message, NULL, r);
+
+                reply = dbus_message_new_method_return(message);
+                if (!reply)
+                        goto oom;
+
+        } else if (dbus_message_is_method_call(message, "org.freedesktop.login1.Manager", "TerminateSeat")) {
+                const char *name;
+                Seat *seat;
+
+                if (!dbus_message_get_args(
+                                    message,
+                                    &error,
+                                    DBUS_TYPE_STRING, &name,
+                                    DBUS_TYPE_INVALID))
+                        return bus_send_error_reply(connection, message, &error, -EINVAL);
+
+                seat = hashmap_get(m->seats, name);
+                if (!seat)
+                        return bus_send_error_reply(connection, message, &error, -ENOENT);
+
+                r = seat_stop_sessions(seat);
+                if (r < 0)
+                        return bus_send_error_reply(connection, message, NULL, r);
+
+                reply = dbus_message_new_method_return(message);
+                if (!reply)
+                        goto oom;
+
+        } else if (dbus_message_is_method_call(message, "org.freedesktop.login1.Manager", "SetUserLinger")) {
+                uint32_t uid;
+                struct passwd *pw;
+                dbus_bool_t b, interactive;
+                char *path;
+
+                if (!dbus_message_get_args(
+                                    message,
+                                    &error,
+                                    DBUS_TYPE_UINT32, &uid,
+                                    DBUS_TYPE_BOOLEAN, &b,
+                                    DBUS_TYPE_BOOLEAN, &interactive,
+                                    DBUS_TYPE_INVALID))
+                        return bus_send_error_reply(connection, message, &error, -EINVAL);
+
+                errno = 0;
+                pw = getpwuid(uid);
+                if (!pw)
+                        return bus_send_error_reply(connection, message, NULL, errno ? -errno : -EINVAL);
+
+                r = verify_polkit(connection, message, "org.freedesktop.login1.set-user-linger", interactive, NULL, &error);
+                if (r < 0)
+                        return bus_send_error_reply(connection, message, &error, r);
+
+                mkdir_p("/var/lib/systemd", 0755);
+
+                r = safe_mkdir("/var/lib/systemd/linger", 0755, 0, 0);
+                if (r < 0)
+                        return bus_send_error_reply(connection, message, &error, r);
+
+                path = strappend("/var/lib/systemd/linger/", pw->pw_name);
+                if (!path)
+                        goto oom;
+
+                if (b) {
+                        User *u;
+
+                        r = touch(path);
+                        free(path);
+
+                        if (r < 0)
+                                return bus_send_error_reply(connection, message, &error, r);
+
+                        if (manager_add_user_by_uid(m, uid, &u) >= 0)
+                                user_start(u);
+
+                } else {
+                        User *u;
+
+                        r = unlink(path);
+                        free(path);
+
+                        if (r < 0 && errno != ENOENT)
+                                return bus_send_error_reply(connection, message, &error, -errno);
+
+                        u = hashmap_get(m->users, ULONG_TO_PTR((unsigned long) uid));
+                        if (u)
+                                user_add_to_gc_queue(u);
+                }
+
+                reply = dbus_message_new_method_return(message);
+                if (!reply)
+                        goto oom;
+
+        } else if (dbus_message_is_method_call(message, "org.freedesktop.login1.Manager", "AttachDevice")) {
+                const char *sysfs, *seat;
+                dbus_bool_t interactive;
+
+                if (!dbus_message_get_args(
+                                    message,
+                                    &error,
+                                    DBUS_TYPE_STRING, &seat,
+                                    DBUS_TYPE_STRING, &sysfs,
+                                    DBUS_TYPE_BOOLEAN, &interactive,
+                                    DBUS_TYPE_INVALID))
+                        return bus_send_error_reply(connection, message, &error, -EINVAL);
+
+                if (!path_startswith(sysfs, "/sys") || !seat_name_is_valid(seat))
+                        return bus_send_error_reply(connection, message, NULL, -EINVAL);
+
+                r = verify_polkit(connection, message, "org.freedesktop.login1.attach-device", interactive, NULL, &error);
+                if (r < 0)
+                        return bus_send_error_reply(connection, message, &error, r);
+
+                r = attach_device(m, seat, sysfs);
+                if (r < 0)
+                        return bus_send_error_reply(connection, message, NULL, -EINVAL);
+
+                reply = dbus_message_new_method_return(message);
+                if (!reply)
+                        goto oom;
+
+
+        } else if (dbus_message_is_method_call(message, "org.freedesktop.login1.Manager", "FlushDevices")) {
+                dbus_bool_t interactive;
+
+                if (!dbus_message_get_args(
+                                    message,
+                                    &error,
+                                    DBUS_TYPE_BOOLEAN, &interactive,
+                                    DBUS_TYPE_INVALID))
+                        return bus_send_error_reply(connection, message, &error, -EINVAL);
+
+                r = verify_polkit(connection, message, "org.freedesktop.login1.flush-devices", interactive, NULL, &error);
+                if (r < 0)
+                        return bus_send_error_reply(connection, message, &error, r);
+
+                r = flush_devices(m);
+                if (r < 0)
+                        return bus_send_error_reply(connection, message, NULL, -EINVAL);
+
+                reply = dbus_message_new_method_return(message);
+                if (!reply)
+                        goto oom;
+
+        } else if (dbus_message_is_method_call(message, "org.freedesktop.login1.Manager", "PowerOff") ||
+                   dbus_message_is_method_call(message, "org.freedesktop.login1.Manager", "Reboot")) {
+                dbus_bool_t interactive;
+                bool multiple_sessions;
+                DBusMessage *forward, *freply;
+                const char *name;
+                const char *mode = "replace";
+                const char *action;
+
+                if (!dbus_message_get_args(
+                                    message,
+                                    &error,
+                                    DBUS_TYPE_BOOLEAN, &interactive,
+                                    DBUS_TYPE_INVALID))
+                        return bus_send_error_reply(connection, message, &error, -EINVAL);
+
+                r = have_multiple_sessions(connection, m, message, &error);
+                if (r < 0)
+                        return bus_send_error_reply(connection, message, &error, r);
+
+                multiple_sessions = r > 0;
+
+                if (streq(dbus_message_get_member(message), "PowerOff")) {
+                        if (multiple_sessions)
+                                action = "org.freedesktop.login1.power-off-multiple-sessions";
+                        else
+                                action = "org.freedesktop.login1.power-off";
+
+                        name = SPECIAL_POWEROFF_TARGET;
+                } else {
+                        if (multiple_sessions)
+                                action = "org.freedesktop.login1.reboot-multiple-sessions";
+                        else
+                                action = "org.freedesktop.login1.reboot";
+
+                        name = SPECIAL_REBOOT_TARGET;
+                }
+
+                r = verify_polkit(connection, message, action, interactive, NULL, &error);
+                if (r < 0)
+                        return bus_send_error_reply(connection, message, &error, r);
+
+                forward = dbus_message_new_method_call(
+                              "org.freedesktop.systemd1",
+                              "/org/freedesktop/systemd1",
+                              "org.freedesktop.systemd1.Manager",
+                              "StartUnit");
+                if (!forward)
+                        return bus_send_error_reply(connection, message, NULL, -ENOMEM);
+
+                if (!dbus_message_append_args(forward,
+                                              DBUS_TYPE_STRING, &name,
+                                              DBUS_TYPE_STRING, &mode,
+                                              DBUS_TYPE_INVALID)) {
+                        dbus_message_unref(forward);
+                        return bus_send_error_reply(connection, message, NULL, -ENOMEM);
+                }
+
+                freply = dbus_connection_send_with_reply_and_block(connection, forward, -1, &error);
+                dbus_message_unref(forward);
+
+                if (!freply)
+                        return bus_send_error_reply(connection, message, &error, -EIO);
+
+                dbus_message_unref(freply);
+
+                reply = dbus_message_new_method_return(message);
+                if (!reply)
+                        goto oom;
+
+        } else if (dbus_message_is_method_call(message, "org.freedesktop.login1.Manager", "CanPowerOff") ||
+                   dbus_message_is_method_call(message, "org.freedesktop.login1.Manager", "CanReboot")) {
+
+                bool multiple_sessions, challenge, b;
+                const char *t, *action;
+
+                r = have_multiple_sessions(connection, m, message, &error);
+                if (r < 0)
+                        return bus_send_error_reply(connection, message, &error, r);
+
+                multiple_sessions = r > 0;
+
+                if (streq(dbus_message_get_member(message), "CanPowerOff")) {
+                        if (multiple_sessions)
+                                action = "org.freedesktop.login1.power-off-multiple-sessions";
+                        else
+                                action = "org.freedesktop.login1.power-off";
+
+                } else {
+                        if (multiple_sessions)
+                                action = "org.freedesktop.login1.reboot-multiple-sessions";
+                        else
+                                action = "org.freedesktop.login1.reboot";
+                }
+
+                r = verify_polkit(connection, message, action, false, &challenge, &error);
+                if (r < 0)
+                        return bus_send_error_reply(connection, message, &error, r);
+
+                reply = dbus_message_new_method_return(message);
+                if (!reply)
+                        goto oom;
+
+                t =     r > 0 ?     "yes" :
+                        challenge ? "challenge" :
+                                    "no";
+
+                b = dbus_message_append_args(
+                                reply,
+                                DBUS_TYPE_STRING, &t,
+                                DBUS_TYPE_INVALID);
+                if (!b)
+                        goto oom;
+
+        } else if (dbus_message_is_method_call(message, "org.freedesktop.DBus.Introspectable", "Introspect")) {
+                char *introspection = NULL;
+                FILE *f;
+                Iterator i;
+                Session *session;
+                Seat *seat;
+                User *user;
+                size_t size;
+                char *p;
+
+                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(seat, m->seats, i) {
+                        p = bus_path_escape(seat->id);
+
+                        if (p) {
+                                fprintf(f, "<node name=\"seat/%s\"/>", p);
+                                free(p);
+                        }
+                }
+
+                HASHMAP_FOREACH(user, m->users, i)
+                        fprintf(f, "<node name=\"user/%llu\"/>", (unsigned long long) user->uid);
+
+                HASHMAP_FOREACH(session, m->sessions, i) {
+                        p = bus_path_escape(session->id);
+
+                        if (p) {
+                                fprintf(f, "<node name=\"session/%s\"/>", p);
+                                free(p);
+                        }
+                }
+
+                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 {
+                const BusBoundProperties bps[] = {
+                        { "org.freedesktop.login1.Manager", bus_login_manager_properties, m },
+                        { NULL, }
+                };
+                return bus_default_message_handler(connection, message, NULL, 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);
+
+        dbus_error_free(&error);
+
+        return DBUS_HANDLER_RESULT_NEED_MEMORY;
+}
+
+const DBusObjectPathVTable bus_manager_vtable = {
+        .message_function = manager_message_handler
+};
+
+DBusHandlerResult bus_message_filter(
+                DBusConnection *connection,
+                DBusMessage *message,
+                void *userdata) {
+
+        Manager *m = userdata;
+        DBusError error;
+
+        assert(m);
+        assert(connection);
+        assert(message);
+
+        dbus_error_init(&error);
+
+        if (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
+                        manager_cgroup_notify_empty(m, cgroup);
+        }
+
+        dbus_error_free(&error);
+
+        return DBUS_HANDLER_RESULT_NOT_YET_HANDLED;
+}
+
+int manager_send_changed(Manager *manager, const char *properties) {
+        DBusMessage *m;
+        int r = -ENOMEM;
+
+        assert(manager);
+
+        m = bus_properties_changed_new("/org/freedesktop/login1", "org.freedesktop.login1.Manager", properties);
+        if (!m)
+                goto finish;
+
+        if (!dbus_connection_send(manager->bus, m, NULL))
+                goto finish;
+
+        r = 0;
+
+finish:
+        if (m)
+                dbus_message_unref(m);
+
+        return r;
+}
diff --git a/src/login/logind-device.c b/src/login/logind-device.c
new file mode 100644 (file)
index 0000000..bbd370f
--- /dev/null
@@ -0,0 +1,86 @@
+/*-*- 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 <http://www.gnu.org/licenses/>.
+***/
+
+#include <assert.h>
+#include <string.h>
+
+#include "logind-device.h"
+#include "util.h"
+
+Device* device_new(Manager *m, const char *sysfs) {
+        Device *d;
+
+        assert(m);
+        assert(sysfs);
+
+        d = new0(Device, 1);
+        if (!d)
+                return NULL;
+
+        d->sysfs = strdup(sysfs);
+        if (!d->sysfs) {
+                free(d);
+                return NULL;
+        }
+
+        if (hashmap_put(m->devices, d->sysfs, d) < 0) {
+                free(d->sysfs);
+                free(d);
+                return NULL;
+        }
+
+        d->manager = m;
+        dual_timestamp_get(&d->timestamp);
+
+        return d;
+}
+
+void device_free(Device *d) {
+        assert(d);
+
+        device_detach(d);
+
+        hashmap_remove(d->manager->devices, d->sysfs);
+
+        free(d->sysfs);
+        free(d);
+}
+
+void device_detach(Device *d) {
+        assert(d);
+
+        if (d->seat)
+                LIST_REMOVE(Device, devices, d->seat->devices, d);
+
+        seat_add_to_gc_queue(d->seat);
+        d->seat = NULL;
+}
+
+void device_attach(Device *d, Seat *s) {
+        assert(d);
+        assert(s);
+
+        if (d->seat)
+                device_detach(d);
+
+        d->seat = s;
+        LIST_PREPEND(Device, devices, s->devices, d);
+}
diff --git a/src/login/logind-device.h b/src/login/logind-device.h
new file mode 100644 (file)
index 0000000..e25a534
--- /dev/null
@@ -0,0 +1,48 @@
+/*-*- Mode: C; c-basic-offset: 8; indent-tabs-mode: nil -*-*/
+
+#ifndef foologinddevicehfoo
+#define foologinddevicehfoo
+
+/***
+  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 <http://www.gnu.org/licenses/>.
+***/
+
+typedef struct Device Device;
+
+#include "list.h"
+#include "util.h"
+#include "logind.h"
+#include "logind-seat.h"
+
+struct Device {
+        Manager *manager;
+
+        char *sysfs;
+        Seat *seat;
+
+        dual_timestamp timestamp;
+
+        LIST_FIELDS(struct Device, devices);
+};
+
+Device* device_new(Manager *m, const char *sysfs);
+void device_free(Device *d);
+void device_attach(Device *d, Seat *s);
+void device_detach(Device *d);
+
+#endif
diff --git a/src/login/logind-gperf.gperf b/src/login/logind-gperf.gperf
new file mode 100644 (file)
index 0000000..940fe10
--- /dev/null
@@ -0,0 +1,22 @@
+%{
+#include <stddef.h>
+#include "conf-parser.h"
+#include "logind.h"
+%}
+struct ConfigPerfItem;
+%null_strings
+%language=ANSI-C
+%define slot-name section_and_lvalue
+%define hash-function-name logind_gperf_hash
+%define lookup-function-name logind_gperf_lookup
+%readonly-tables
+%omit-struct-type
+%struct-type
+%includes
+%%
+Login.NAutoVTs,          config_parse_unsigned, 0, offsetof(Manager, n_autovts)
+Login.KillUserProcesses, config_parse_bool,     0, offsetof(Manager, kill_user_processes)
+Login.KillOnlyUsers,     config_parse_strv,     0, offsetof(Manager, kill_only_users)
+Login.KillExcludeUsers,  config_parse_strv,     0, offsetof(Manager, kill_exclude_users)
+Login.Controllers,       config_parse_strv,     0, offsetof(Manager, controllers)
+Login.ResetControllers,  config_parse_strv,     0, offsetof(Manager, reset_controllers)
diff --git a/src/login/logind-seat-dbus.c b/src/login/logind-seat-dbus.c
new file mode 100644 (file)
index 0000000..95ef5ff
--- /dev/null
@@ -0,0 +1,408 @@
+/*-*- 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 <http://www.gnu.org/licenses/>.
+***/
+
+#include <errno.h>
+#include <string.h>
+
+#include "logind.h"
+#include "logind-seat.h"
+#include "dbus-common.h"
+#include "util.h"
+
+#define BUS_SEAT_INTERFACE \
+        " <interface name=\"org.freedesktop.login1.Seat\">\n"           \
+        "  <method name=\"Terminate\"/>\n"                              \
+        "  <method name=\"ActivateSession\">\n"                         \
+        "   <arg name=\"id\" type=\"s\"/>\n"                            \
+        "  </method>\n"                                                 \
+        "  <property name=\"Id\" type=\"s\" access=\"read\"/>\n"        \
+        "  <property name=\"ActiveSession\" type=\"so\" access=\"read\"/>\n" \
+        "  <property name=\"CanMultiSession\" type=\"b\" access=\"read\"/>\n" \
+        "  <property name=\"Sessions\" type=\"a(so)\" access=\"read\"/>\n" \
+        "  <property name=\"IdleHint\" type=\"b\" access=\"read\"/>\n"  \
+        "  <property name=\"IdleSinceHint\" type=\"t\" access=\"read\"/>\n" \
+        "  <property name=\"IdleSinceHintMonotonic\" type=\"t\" access=\"read\"/>\n" \
+        " </interface>\n"                                               \
+
+#define INTROSPECTION                                                   \
+        DBUS_INTROSPECT_1_0_XML_DOCTYPE_DECL_NODE                       \
+        "<node>\n"                                                      \
+        BUS_SEAT_INTERFACE                                              \
+        BUS_PROPERTIES_INTERFACE                                        \
+        BUS_PEER_INTERFACE                                              \
+        BUS_INTROSPECTABLE_INTERFACE                                    \
+        "</node>\n"
+
+#define INTERFACES_LIST                              \
+        BUS_GENERIC_INTERFACES_LIST                  \
+        "org.freedesktop.login1.Seat\0"
+
+static int bus_seat_append_active(DBusMessageIter *i, const char *property, void *data) {
+        DBusMessageIter sub;
+        Seat *s = data;
+        const char *id, *path;
+        char *p = NULL;
+
+        assert(i);
+        assert(property);
+        assert(s);
+
+        if (!dbus_message_iter_open_container(i, DBUS_TYPE_STRUCT, NULL, &sub))
+                return -ENOMEM;
+
+        if (s->active) {
+                id = s->active->id;
+                path = p = session_bus_path(s->active);
+
+                if (!p)
+                        return -ENOMEM;
+        } else {
+                id = "";
+                path = "/";
+        }
+
+        if (!dbus_message_iter_append_basic(&sub, DBUS_TYPE_STRING, &id) ||
+            !dbus_message_iter_append_basic(&sub, DBUS_TYPE_OBJECT_PATH, &path)) {
+                free(p);
+                return -ENOMEM;
+        }
+
+        free(p);
+
+        if (!dbus_message_iter_close_container(i, &sub))
+                return -ENOMEM;
+
+        return 0;
+}
+
+static int bus_seat_append_sessions(DBusMessageIter *i, const char *property, void *data) {
+        DBusMessageIter sub, sub2;
+        Seat *s = data;
+        Session *session;
+
+        assert(i);
+        assert(property);
+        assert(s);
+
+        if (!dbus_message_iter_open_container(i, DBUS_TYPE_ARRAY, "(so)", &sub))
+                return -ENOMEM;
+
+        LIST_FOREACH(sessions_by_seat, session, s->sessions) {
+                char *p;
+
+                if (!dbus_message_iter_open_container(&sub, DBUS_TYPE_STRUCT, NULL, &sub2))
+                        return -ENOMEM;
+
+                p = session_bus_path(session);
+                if (!p)
+                        return -ENOMEM;
+
+                if (!dbus_message_iter_append_basic(&sub2, DBUS_TYPE_STRING, &session->id) ||
+                    !dbus_message_iter_append_basic(&sub2, DBUS_TYPE_OBJECT_PATH, &p)) {
+                        free(p);
+                        return -ENOMEM;
+                }
+
+                free(p);
+
+                if (!dbus_message_iter_close_container(&sub, &sub2))
+                        return -ENOMEM;
+        }
+
+        if (!dbus_message_iter_close_container(i, &sub))
+                return -ENOMEM;
+
+        return 0;
+}
+
+static int bus_seat_append_multi_session(DBusMessageIter *i, const char *property, void *data) {
+        Seat *s = data;
+        dbus_bool_t b;
+
+        assert(i);
+        assert(property);
+        assert(s);
+
+        b = seat_can_multi_session(s);
+
+        if (!dbus_message_iter_append_basic(i, DBUS_TYPE_BOOLEAN, &b))
+                return -ENOMEM;
+
+        return 0;
+}
+
+static int bus_seat_append_idle_hint(DBusMessageIter *i, const char *property, void *data) {
+        Seat *s = data;
+        dbus_bool_t b;
+
+        assert(i);
+        assert(property);
+        assert(s);
+
+        b = seat_get_idle_hint(s, NULL) > 0;
+        if (!dbus_message_iter_append_basic(i, DBUS_TYPE_BOOLEAN, &b))
+                return -ENOMEM;
+
+        return 0;
+}
+
+static int bus_seat_append_idle_hint_since(DBusMessageIter *i, const char *property, void *data) {
+        Seat *s = data;
+        dual_timestamp t;
+        uint64_t k;
+
+        assert(i);
+        assert(property);
+        assert(s);
+
+        seat_get_idle_hint(s, &t);
+        k = streq(property, "IdleSinceHint") ? t.realtime : t.monotonic;
+
+        if (!dbus_message_iter_append_basic(i, DBUS_TYPE_UINT64, &k))
+                return -ENOMEM;
+
+        return 0;
+}
+
+static int get_seat_for_path(Manager *m, const char *path, Seat **_s) {
+        Seat *s;
+        char *id;
+
+        assert(m);
+        assert(path);
+        assert(_s);
+
+        if (!startswith(path, "/org/freedesktop/login1/seat/"))
+                return -EINVAL;
+
+        id = bus_path_unescape(path + 29);
+        if (!id)
+                return -ENOMEM;
+
+        s = hashmap_get(m->seats, id);
+        free(id);
+
+        if (!s)
+                return -ENOENT;
+
+        *_s = s;
+        return 0;
+}
+
+static const BusProperty bus_login_seat_properties[] = {
+        { "Id",                     bus_property_append_string,      "s", offsetof(Seat, id), true },
+        { "ActiveSession",          bus_seat_append_active,       "(so)", 0 },
+        { "CanMultiSession",        bus_seat_append_multi_session,   "b", 0 },
+        { "Sessions",               bus_seat_append_sessions,    "a(so)", 0 },
+        { "IdleHint",               bus_seat_append_idle_hint,       "b", 0 },
+        { "IdleSinceHint",          bus_seat_append_idle_hint_since, "t", 0 },
+        { "IdleSinceHintMonotonic", bus_seat_append_idle_hint_since, "t", 0 },
+        { NULL, }
+};
+
+static DBusHandlerResult seat_message_dispatch(
+                Seat *s,
+                DBusConnection *connection,
+                DBusMessage *message) {
+
+        DBusError error;
+        DBusMessage *reply = NULL;
+        int r;
+
+        assert(s);
+        assert(connection);
+        assert(message);
+
+        dbus_error_init(&error);
+
+        if (dbus_message_is_method_call(message, "org.freedesktop.login1.Seat", "Terminate")) {
+
+                r = seat_stop_sessions(s);
+                if (r < 0)
+                        return bus_send_error_reply(connection, message, NULL, r);
+
+                reply = dbus_message_new_method_return(message);
+                if (!reply)
+                        goto oom;
+
+        } else if (dbus_message_is_method_call(message, "org.freedesktop.login1.Seat", "ActivateSession")) {
+                const char *name;
+                Session *session;
+
+                if (!dbus_message_get_args(
+                                    message,
+                                    &error,
+                                    DBUS_TYPE_STRING, &name,
+                                    DBUS_TYPE_INVALID))
+                        return bus_send_error_reply(connection, message, &error, -EINVAL);
+
+                session = hashmap_get(s->manager->sessions, name);
+                if (!session || session->seat != s)
+                        return bus_send_error_reply(connection, message, &error, -ENOENT);
+
+                r = session_activate(session);
+                if (r < 0)
+                        return bus_send_error_reply(connection, message, NULL, r);
+
+                reply = dbus_message_new_method_return(message);
+                if (!reply)
+                        goto oom;
+        } else {
+                const BusBoundProperties bps[] = {
+                        { "org.freedesktop.login1.Seat", bus_login_seat_properties, s },
+                        { 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);
+
+        dbus_error_free(&error);
+
+        return DBUS_HANDLER_RESULT_NEED_MEMORY;
+}
+
+static DBusHandlerResult seat_message_handler(
+                DBusConnection *connection,
+                DBusMessage *message,
+                void *userdata) {
+
+        Manager *m = userdata;
+        Seat *s;
+        int r;
+
+        r = get_seat_for_path(m, dbus_message_get_path(message), &s);
+        if (r < 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 seat");
+                        return bus_send_error_reply(connection, message, &e, r);
+                }
+
+                return bus_send_error_reply(connection, message, NULL, r);
+        }
+
+        return seat_message_dispatch(s, connection, message);
+}
+
+const DBusObjectPathVTable bus_seat_vtable = {
+        .message_function = seat_message_handler
+};
+
+char *seat_bus_path(Seat *s) {
+        char *t, *r;
+
+        assert(s);
+
+        t = bus_path_escape(s->id);
+        if (!t)
+                return NULL;
+
+        r = strappend("/org/freedesktop/login1/seat/", t);
+        free(t);
+
+        return r;
+}
+
+int seat_send_signal(Seat *s, bool new_seat) {
+        DBusMessage *m;
+        int r = -ENOMEM;
+        char *p = NULL;
+
+        assert(s);
+
+        m = dbus_message_new_signal("/org/freedesktop/login1",
+                                    "org.freedesktop.login1.Manager",
+                                    new_seat ? "SeatNew" : "SeatRemoved");
+
+        if (!m)
+                return -ENOMEM;
+
+        p = seat_bus_path(s);
+        if (!p)
+                goto finish;
+
+        if (!dbus_message_append_args(
+                            m,
+                            DBUS_TYPE_STRING, &s->id,
+                            DBUS_TYPE_OBJECT_PATH, &p,
+                            DBUS_TYPE_INVALID))
+                goto finish;
+
+        if (!dbus_connection_send(s->manager->bus, m, NULL))
+                goto finish;
+
+        r = 0;
+
+finish:
+        dbus_message_unref(m);
+        free(p);
+
+        return r;
+}
+
+int seat_send_changed(Seat *s, const char *properties) {
+        DBusMessage *m;
+        int r = -ENOMEM;
+        char *p = NULL;
+
+        assert(s);
+
+        if (!s->started)
+                return 0;
+
+        p = seat_bus_path(s);
+        if (!p)
+                return -ENOMEM;
+
+        m = bus_properties_changed_new(p, "org.freedesktop.login1.Seat", properties);
+        if (!m)
+                goto finish;
+
+        if (!dbus_connection_send(s->manager->bus, m, NULL))
+                goto finish;
+
+        r = 0;
+
+finish:
+        if (m)
+                dbus_message_unref(m);
+        free(p);
+
+        return r;
+}
diff --git a/src/login/logind-seat.c b/src/login/logind-seat.c
new file mode 100644 (file)
index 0000000..be37c1c
--- /dev/null
@@ -0,0 +1,514 @@
+/*-*- 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 <http://www.gnu.org/licenses/>.
+***/
+
+#include <assert.h>
+#include <errno.h>
+#include <unistd.h>
+#include <fcntl.h>
+#include <sys/ioctl.h>
+#include <linux/vt.h>
+#include <string.h>
+
+#include "logind-seat.h"
+#include "logind-acl.h"
+#include "util.h"
+
+Seat *seat_new(Manager *m, const char *id) {
+        Seat *s;
+
+        assert(m);
+        assert(id);
+
+        s = new0(Seat, 1);
+        if (!s)
+                return NULL;
+
+        s->state_file = strappend("/run/systemd/seats/", id);
+        if (!s->state_file) {
+                free(s);
+                return NULL;
+        }
+
+        s->id = file_name_from_path(s->state_file);
+        s->manager = m;
+
+        if (hashmap_put(m->seats, s->id, s) < 0) {
+                free(s->state_file);
+                free(s);
+                return NULL;
+        }
+
+        return s;
+}
+
+void seat_free(Seat *s) {
+        assert(s);
+
+        if (s->in_gc_queue)
+                LIST_REMOVE(Seat, gc_queue, s->manager->seat_gc_queue, s);
+
+        while (s->sessions)
+                session_free(s->sessions);
+
+        assert(!s->active);
+
+        while (s->devices)
+                device_free(s->devices);
+
+        hashmap_remove(s->manager->seats, s->id);
+
+        free(s->state_file);
+        free(s);
+}
+
+int seat_save(Seat *s) {
+        int r;
+        FILE *f;
+        char *temp_path;
+
+        assert(s);
+
+        if (!s->started)
+                return 0;
+
+        r = safe_mkdir("/run/systemd/seats", 0755, 0, 0);
+        if (r < 0)
+                goto finish;
+
+        r = fopen_temporary(s->state_file, &f, &temp_path);
+        if (r < 0)
+                goto finish;
+
+        fchmod(fileno(f), 0644);
+
+        fprintf(f,
+                "# This is private data. Do not parse.\n"
+                "IS_VTCONSOLE=%i\n"
+                "CAN_MULTI_SESSION=%i\n",
+                seat_is_vtconsole(s),
+                seat_can_multi_session(s));
+
+        if (s->active) {
+                assert(s->active->user);
+
+                fprintf(f,
+                        "ACTIVE=%s\n"
+                        "ACTIVE_UID=%lu\n",
+                        s->active->id,
+                        (unsigned long) s->active->user->uid);
+        }
+
+        if (s->sessions) {
+                Session *i;
+
+                fputs("SESSIONS=", f);
+                LIST_FOREACH(sessions_by_seat, i, s->sessions) {
+                        fprintf(f,
+                                "%s%c",
+                                i->id,
+                                i->sessions_by_seat_next ? ' ' : '\n');
+                }
+
+                fputs("UIDS=", f);
+                LIST_FOREACH(sessions_by_seat, i, s->sessions)
+                        fprintf(f,
+                                "%lu%c",
+                                (unsigned long) i->user->uid,
+                                i->sessions_by_seat_next ? ' ' : '\n');
+        }
+
+        fflush(f);
+
+        if (ferror(f) || rename(temp_path, s->state_file) < 0) {
+                r = -errno;
+                unlink(s->state_file);
+                unlink(temp_path);
+        }
+
+        fclose(f);
+        free(temp_path);
+
+finish:
+        if (r < 0)
+                log_error("Failed to save seat data for %s: %s", s->id, strerror(-r));
+
+        return r;
+}
+
+int seat_load(Seat *s) {
+        assert(s);
+
+        /* There isn't actually anything to read here ... */
+
+        return 0;
+}
+
+static int vt_allocate(int vtnr) {
+        int fd, r;
+        char *p;
+
+        assert(vtnr >= 1);
+
+        if (asprintf(&p, "/dev/tty%i", vtnr) < 0)
+                return -ENOMEM;
+
+        fd = open_terminal(p, O_RDWR|O_NOCTTY|O_CLOEXEC);
+        free(p);
+
+        r = fd < 0 ? -errno : 0;
+
+        if (fd >= 0)
+                close_nointr_nofail(fd);
+
+        return r;
+}
+
+int seat_preallocate_vts(Seat *s) {
+        int r = 0;
+        unsigned i;
+
+        assert(s);
+        assert(s->manager);
+
+        log_debug("Preallocating VTs...");
+
+        if (s->manager->n_autovts <= 0)
+                return 0;
+
+        if (!seat_can_multi_session(s))
+                return 0;
+
+        for (i = 1; i <= s->manager->n_autovts; i++) {
+                int q;
+
+                q = vt_allocate(i);
+                if (q < 0) {
+                        log_error("Failed to preallocate VT %i: %s", i, strerror(-q));
+                        r = q;
+                }
+        }
+
+        return r;
+}
+
+int seat_apply_acls(Seat *s, Session *old_active) {
+        int r;
+
+        assert(s);
+
+        r = devnode_acl_all(s->manager->udev,
+                            s->id,
+                            false,
+                            !!old_active, old_active ? old_active->user->uid : 0,
+                            !!s->active, s->active ? s->active->user->uid : 0);
+
+        if (r < 0)
+                log_error("Failed to apply ACLs: %s", strerror(-r));
+
+        return r;
+}
+
+int seat_set_active(Seat *s, Session *session) {
+        Session *old_active;
+
+        assert(s);
+        assert(!session || session->seat == s);
+
+        if (session == s->active)
+                return 0;
+
+        old_active = s->active;
+        s->active = session;
+
+        seat_apply_acls(s, old_active);
+
+        if (session && session->started)
+                session_send_changed(session, "Active\0");
+
+        if (!session || session->started)
+                seat_send_changed(s, "ActiveSession\0");
+
+        seat_save(s);
+
+        if (session) {
+                session_save(session);
+                user_save(session->user);
+        }
+
+        if (old_active) {
+                session_save(old_active);
+                user_save(old_active->user);
+        }
+
+        return 0;
+}
+
+int seat_active_vt_changed(Seat *s, int vtnr) {
+        Session *i, *new_active = NULL;
+        int r;
+
+        assert(s);
+        assert(vtnr >= 1);
+
+        if (!seat_can_multi_session(s))
+                return -EINVAL;
+
+        log_debug("VT changed to %i", vtnr);
+
+        LIST_FOREACH(sessions_by_seat, i, s->sessions)
+                if (i->vtnr == vtnr) {
+                        new_active = i;
+                        break;
+                }
+
+        r = seat_set_active(s, new_active);
+        manager_spawn_autovt(s->manager, vtnr);
+
+        return r;
+}
+
+int seat_read_active_vt(Seat *s) {
+        char t[64];
+        ssize_t k;
+        int r, vtnr;
+
+        assert(s);
+
+        if (!seat_can_multi_session(s))
+                return 0;
+
+        lseek(s->manager->console_active_fd, SEEK_SET, 0);
+
+        k = read(s->manager->console_active_fd, t, sizeof(t)-1);
+        if (k <= 0) {
+                log_error("Failed to read current console: %s", k < 0 ? strerror(-errno) : "EOF");
+                return k < 0 ? -errno : -EIO;
+        }
+
+        t[k] = 0;
+        truncate_nl(t);
+
+        if (!startswith(t, "tty")) {
+                log_error("Hm, /sys/class/tty/tty0/active is badly formatted.");
+                return -EIO;
+        }
+
+        r = safe_atoi(t+3, &vtnr);
+        if (r < 0) {
+                log_error("Failed to parse VT number %s", t+3);
+                return r;
+        }
+
+        if (vtnr <= 0) {
+                log_error("VT number invalid: %s", t+3);
+                return -EIO;
+        }
+
+        return seat_active_vt_changed(s, vtnr);
+}
+
+int seat_start(Seat *s) {
+        assert(s);
+
+        if (s->started)
+                return 0;
+
+        log_info("New seat %s.", s->id);
+
+        /* Initialize VT magic stuff */
+        seat_preallocate_vts(s);
+
+        /* Read current VT */
+        seat_read_active_vt(s);
+
+        s->started = true;
+
+        /* Save seat data */
+        seat_save(s);
+
+        seat_send_signal(s, true);
+
+        return 0;
+}
+
+int seat_stop(Seat *s) {
+        int r = 0;
+
+        assert(s);
+
+        if (s->started)
+                log_info("Removed seat %s.", s->id);
+
+        seat_stop_sessions(s);
+
+        unlink(s->state_file);
+        seat_add_to_gc_queue(s);
+
+        if (s->started)
+                seat_send_signal(s, false);
+
+        s->started = false;
+
+        return r;
+}
+
+int seat_stop_sessions(Seat *s) {
+        Session *session;
+        int r = 0, k;
+
+        assert(s);
+
+        LIST_FOREACH(sessions_by_seat, session, s->sessions) {
+                k = session_stop(session);
+                if (k < 0)
+                        r = k;
+        }
+
+        return r;
+}
+
+int seat_attach_session(Seat *s, Session *session) {
+        assert(s);
+        assert(session);
+        assert(!session->seat);
+
+        session->seat = s;
+        LIST_PREPEND(Session, sessions_by_seat, s->sessions, session);
+
+        seat_send_changed(s, "Sessions\0");
+
+        /* Note that even if a seat is not multi-session capable it
+         * still might have multiple sessions on it since old, dead
+         * sessions might continue to be tracked until all their
+         * processes are gone. The most recently added session
+         * (i.e. the first in s->sessions) is the one that matters. */
+
+        if (!seat_can_multi_session(s))
+                seat_set_active(s, session);
+
+        return 0;
+}
+
+bool seat_is_vtconsole(Seat *s) {
+        assert(s);
+
+        return s->manager->vtconsole == s;
+}
+
+bool seat_can_multi_session(Seat *s) {
+        assert(s);
+
+        if (!seat_is_vtconsole(s))
+                return false;
+
+        /* If we can't watch which VT is in the foreground, we don't
+         * support VT switching */
+
+        return s->manager->console_active_fd >= 0;
+}
+
+int seat_get_idle_hint(Seat *s, dual_timestamp *t) {
+        Session *session;
+        bool idle_hint = true;
+        dual_timestamp ts = { 0, 0 };
+
+        assert(s);
+
+        LIST_FOREACH(sessions_by_seat, session, s->sessions) {
+                dual_timestamp k;
+                int ih;
+
+                ih = session_get_idle_hint(session, &k);
+                if (ih < 0)
+                        return ih;
+
+                if (!ih) {
+                        if (!idle_hint) {
+                                if (k.monotonic < ts.monotonic)
+                                        ts = k;
+                        } else {
+                                idle_hint = false;
+                                ts = k;
+                        }
+                } else if (idle_hint) {
+
+                        if (k.monotonic > ts.monotonic)
+                                ts = k;
+                }
+        }
+
+        if (t)
+                *t = ts;
+
+        return idle_hint;
+}
+
+int seat_check_gc(Seat *s, bool drop_not_started) {
+        assert(s);
+
+        if (drop_not_started && !s->started)
+                return 0;
+
+        if (seat_is_vtconsole(s))
+                return 1;
+
+        return !!s->devices;
+}
+
+void seat_add_to_gc_queue(Seat *s) {
+        assert(s);
+
+        if (s->in_gc_queue)
+                return;
+
+        LIST_PREPEND(Seat, gc_queue, s->manager->seat_gc_queue, s);
+        s->in_gc_queue = true;
+}
+
+static bool seat_name_valid_char(char c) {
+        return
+                (c >= 'a' && c <= 'z') ||
+                (c >= 'A' && c <= 'Z') ||
+                (c >= '0' && c <= '9') ||
+                c == '-' ||
+                c == '_';
+}
+
+bool seat_name_is_valid(const char *name) {
+        const char *p;
+
+        assert(name);
+
+        if (!startswith(name, "seat"))
+                return false;
+
+        if (!name[4])
+                return false;
+
+        for (p = name; *p; p++)
+                if (!seat_name_valid_char(*p))
+                        return false;
+
+        if (strlen(name) > 255)
+                return false;
+
+        return true;
+}
diff --git a/src/login/logind-seat.h b/src/login/logind-seat.h
new file mode 100644 (file)
index 0000000..3b2c7f0
--- /dev/null
@@ -0,0 +1,83 @@
+/*-*- Mode: C; c-basic-offset: 8; indent-tabs-mode: nil -*-*/
+
+#ifndef foologindseathfoo
+#define foologindseathfoo
+
+/***
+  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 <http://www.gnu.org/licenses/>.
+***/
+
+typedef struct Seat Seat;
+
+#include "list.h"
+#include "util.h"
+#include "logind.h"
+#include "logind-device.h"
+#include "logind-session.h"
+
+struct Seat {
+        Manager *manager;
+        char *id;
+
+        char *state_file;
+
+        LIST_HEAD(Device, devices);
+
+        Session *active;
+        LIST_HEAD(Session, sessions);
+
+        bool in_gc_queue:1;
+        bool started:1;
+
+        LIST_FIELDS(Seat, gc_queue);
+};
+
+Seat *seat_new(Manager *m, const char *id);
+void seat_free(Seat *s);
+
+int seat_save(Seat *s);
+int seat_load(Seat *s);
+
+int seat_apply_acls(Seat *s, Session *old_active);
+int seat_set_active(Seat *s, Session *session);
+int seat_active_vt_changed(Seat *s, int vtnr);
+int seat_read_active_vt(Seat *s);
+int seat_preallocate_vts(Seat *s);
+
+int seat_attach_session(Seat *s, Session *session);
+
+bool seat_is_vtconsole(Seat *s);
+bool seat_can_multi_session(Seat *s);
+int seat_get_idle_hint(Seat *s, dual_timestamp *t);
+
+int seat_start(Seat *s);
+int seat_stop(Seat *s);
+int seat_stop_sessions(Seat *s);
+
+int seat_check_gc(Seat *s, bool drop_not_started);
+void seat_add_to_gc_queue(Seat *s);
+
+bool seat_name_is_valid(const char *name);
+char *seat_bus_path(Seat *s);
+
+extern const DBusObjectPathVTable bus_seat_vtable;
+
+int seat_send_signal(Seat *s, bool new_seat);
+int seat_send_changed(Seat *s, const char *properties);
+
+#endif
diff --git a/src/login/logind-session-dbus.c b/src/login/logind-session-dbus.c
new file mode 100644 (file)
index 0000000..102f8ac
--- /dev/null
@@ -0,0 +1,528 @@
+/*-*- 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 <http://www.gnu.org/licenses/>.
+***/
+
+#include <errno.h>
+#include <string.h>
+
+#include "logind.h"
+#include "logind-session.h"
+#include "dbus-common.h"
+#include "util.h"
+
+#define BUS_SESSION_INTERFACE \
+        " <interface name=\"org.freedesktop.login1.Session\">\n"        \
+        "  <method name=\"Terminate\"/>\n"                              \
+        "  <method name=\"Activate\"/>\n"                               \
+        "  <method name=\"Lock\"/>\n"                                   \
+        "  <method name=\"Unlock\"/>\n"                                 \
+        "  <method name=\"SetIdleHint\">\n"                             \
+        "   <arg name=\"b\" type=\"b\"/>\n"                             \
+        "  </method>\n"                                                 \
+        "  <method name=\"Kill\">\n"                                    \
+        "   <arg name=\"who\" type=\"s\"/>\n"                           \
+        "   <arg name=\"signal\" type=\"s\"/>\n"                        \
+        "  </method>\n"                                                 \
+        "  <property name=\"Id\" type=\"s\" access=\"read\"/>\n"        \
+        "  <property name=\"User\" type=\"(uo)\" access=\"read\"/>\n"   \
+        "  <property name=\"Name\" type=\"s\" access=\"read\"/>\n"      \
+        "  <property name=\"Timestamp\" type=\"t\" access=\"read\"/>\n" \
+        "  <property name=\"TimestampMonotonic\" type=\"t\" access=\"read\"/>\n" \
+        "  <property name=\"ControlGroupPath\" type=\"s\" access=\"read\"/>\n" \
+        "  <property name=\"VTNr\" type=\"u\" access=\"read\"/>\n"      \
+        "  <property name=\"Seat\" type=\"(so)\" access=\"read\"/>\n"   \
+        "  <property name=\"TTY\" type=\"s\" access=\"read\"/>\n"       \
+        "  <property name=\"Display\" type=\"s\" access=\"read\"/>\n"   \
+        "  <property name=\"Remote\" type=\"b\" access=\"read\"/>\n"    \
+        "  <property name=\"RemoteHost\" type=\"s\" access=\"read\"/>\n" \
+        "  <property name=\"RemoteUser\" type=\"s\" access=\"read\"/>\n" \
+        "  <property name=\"Service\" type=\"s\" access=\"read\"/>\n" \
+        "  <property name=\"Leader\" type=\"u\" access=\"read\"/>\n"    \
+        "  <property name=\"Audit\" type=\"u\" access=\"read\"/>\n"     \
+        "  <property name=\"Type\" type=\"s\" access=\"read\"/>\n"      \
+        "  <property name=\"Class\" type=\"s\" access=\"read\"/>\n"      \
+        "  <property name=\"Active\" type=\"b\" access=\"read\"/>\n"    \
+        "  <property name=\"Controllers\" type=\"as\" access=\"read\"/>\n" \
+        "  <property name=\"ResetControllers\" type=\"as\" access=\"read\"/>\n" \
+        "  <property name=\"KillProcesses\" type=\"b\" access=\"read\"/>\n" \
+        "  <property name=\"IdleHint\" type=\"b\" access=\"read\"/>\n"  \
+        "  <property name=\"IdleSinceHint\" type=\"t\" access=\"read\"/>\n" \
+        "  <property name=\"IdleSinceHintMonotonic\" type=\"t\" access=\"read\"/>\n" \
+        " </interface>\n"
+
+#define INTROSPECTION                                                   \
+        DBUS_INTROSPECT_1_0_XML_DOCTYPE_DECL_NODE                       \
+        "<node>\n"                                                      \
+        BUS_SESSION_INTERFACE                                           \
+        BUS_PROPERTIES_INTERFACE                                        \
+        BUS_PEER_INTERFACE                                              \
+        BUS_INTROSPECTABLE_INTERFACE                                    \
+        "</node>\n"
+
+#define INTERFACES_LIST                              \
+        BUS_GENERIC_INTERFACES_LIST                  \
+        "org.freedesktop.login1.Session\0"
+
+static int bus_session_append_seat(DBusMessageIter *i, const char *property, void *data) {
+        DBusMessageIter sub;
+        Session *s = data;
+        const char *id, *path;
+        char *p = NULL;
+
+        assert(i);
+        assert(property);
+        assert(s);
+
+        if (!dbus_message_iter_open_container(i, DBUS_TYPE_STRUCT, NULL, &sub))
+                return -ENOMEM;
+
+        if (s->seat) {
+                id = s->seat->id;
+                path = p = seat_bus_path(s->seat);
+
+                if (!p)
+                        return -ENOMEM;
+        } else {
+                id = "";
+                path = "/";
+        }
+
+        if (!dbus_message_iter_append_basic(&sub, DBUS_TYPE_STRING, &id) ||
+            !dbus_message_iter_append_basic(&sub, DBUS_TYPE_OBJECT_PATH, &path)) {
+                free(p);
+                return -ENOMEM;
+        }
+
+        free(p);
+
+        if (!dbus_message_iter_close_container(i, &sub))
+                return -ENOMEM;
+
+        return 0;
+}
+
+static int bus_session_append_user(DBusMessageIter *i, const char *property, void *data) {
+        DBusMessageIter sub;
+        User *u = data;
+        char *p = NULL;
+
+        assert(i);
+        assert(property);
+        assert(u);
+
+        if (!dbus_message_iter_open_container(i, DBUS_TYPE_STRUCT, NULL, &sub))
+                return -ENOMEM;
+
+        p = user_bus_path(u);
+        if (!p)
+                return -ENOMEM;
+
+        if (!dbus_message_iter_append_basic(&sub, DBUS_TYPE_UINT32, &u->uid) ||
+            !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_session_append_active(DBusMessageIter *i, const char *property, void *data) {
+        Session *s = data;
+        dbus_bool_t b;
+
+        assert(i);
+        assert(property);
+        assert(s);
+
+        b = session_is_active(s);
+        if (!dbus_message_iter_append_basic(i, DBUS_TYPE_BOOLEAN, &b))
+                return -ENOMEM;
+
+        return 0;
+}
+
+static int bus_session_append_idle_hint(DBusMessageIter *i, const char *property, void *data) {
+        Session *s = data;
+        int b;
+
+        assert(i);
+        assert(property);
+        assert(s);
+
+        b = session_get_idle_hint(s, NULL) > 0;
+        if (!dbus_message_iter_append_basic(i, DBUS_TYPE_BOOLEAN, &b))
+                return -ENOMEM;
+
+        return 0;
+}
+
+static int bus_session_append_idle_hint_since(DBusMessageIter *i, const char *property, void *data) {
+        Session *s = data;
+        dual_timestamp t;
+        uint64_t u;
+
+        assert(i);
+        assert(property);
+        assert(s);
+
+        session_get_idle_hint(s, &t);
+        u = streq(property, "IdleSinceHint") ? t.realtime : t.monotonic;
+
+        if (!dbus_message_iter_append_basic(i, DBUS_TYPE_UINT64, &u))
+                return -ENOMEM;
+
+        return 0;
+}
+
+static DEFINE_BUS_PROPERTY_APPEND_ENUM(bus_session_append_type, session_type, SessionType);
+static DEFINE_BUS_PROPERTY_APPEND_ENUM(bus_session_append_class, session_class, SessionClass);
+
+static int get_session_for_path(Manager *m, const char *path, Session **_s) {
+        Session *s;
+        char *id;
+
+        assert(m);
+        assert(path);
+        assert(_s);
+
+        if (!startswith(path, "/org/freedesktop/login1/session/"))
+                return -EINVAL;
+
+        id = bus_path_unescape(path + 32);
+        if (!id)
+                return -ENOMEM;
+
+        s = hashmap_get(m->sessions, id);
+        free(id);
+
+        if (!s)
+                return -ENOENT;
+
+        *_s = s;
+        return 0;
+}
+
+static const BusProperty bus_login_session_properties[] = {
+        { "Id",                     bus_property_append_string,         "s", offsetof(Session, id),                 true },
+        { "Timestamp",              bus_property_append_usec,           "t", offsetof(Session, timestamp.realtime)  },
+        { "TimestampMonotonic",     bus_property_append_usec,           "t", offsetof(Session, timestamp.monotonic) },
+        { "ControlGroupPath",       bus_property_append_string,         "s", offsetof(Session, cgroup_path),        true },
+        { "VTNr",                   bus_property_append_uint32,         "u", offsetof(Session, vtnr)                },
+        { "Seat",                   bus_session_append_seat,         "(so)", 0 },
+        { "TTY",                    bus_property_append_string,         "s", offsetof(Session, tty),                true },
+        { "Display",                bus_property_append_string,         "s", offsetof(Session, display),            true },
+        { "Remote",                 bus_property_append_bool,           "b", offsetof(Session, remote)              },
+        { "RemoteUser",             bus_property_append_string,         "s", offsetof(Session, remote_user),        true },
+        { "RemoteHost",             bus_property_append_string,         "s", offsetof(Session, remote_host),        true },
+        { "Service",                bus_property_append_string,         "s", offsetof(Session, service),            true },
+        { "Leader",                 bus_property_append_pid,            "u", offsetof(Session, leader)              },
+        { "Audit",                  bus_property_append_uint32,         "u", offsetof(Session, audit_id)            },
+        { "Type",                   bus_session_append_type,            "s", offsetof(Session, type)                },
+        { "Class",                  bus_session_append_class,           "s", offsetof(Session, class)               },
+        { "Active",                 bus_session_append_active,          "b", 0 },
+        { "Controllers",            bus_property_append_strv,          "as", offsetof(Session, controllers),        true },
+        { "ResetControllers",       bus_property_append_strv,          "as", offsetof(Session, reset_controllers),  true },
+        { "KillProcesses",          bus_property_append_bool,           "b", offsetof(Session, kill_processes)      },
+        { "IdleHint",               bus_session_append_idle_hint,       "b", 0 },
+        { "IdleSinceHint",          bus_session_append_idle_hint_since, "t", 0 },
+        { "IdleSinceHintMonotonic", bus_session_append_idle_hint_since, "t", 0 },
+        { NULL, }
+};
+
+static const BusProperty bus_login_session_user_properties[] = {
+        { "User",                   bus_session_append_user,         "(uo)", 0 },
+        { "Name",                   bus_property_append_string,         "s", offsetof(User, name),                  true },
+        { NULL, }
+};
+
+static DBusHandlerResult session_message_dispatch(
+                Session *s,
+                DBusConnection *connection,
+                DBusMessage *message) {
+
+        DBusError error;
+        DBusMessage *reply = NULL;
+        int r;
+
+        assert(s);
+        assert(connection);
+        assert(message);
+
+        dbus_error_init(&error);
+
+        if (dbus_message_is_method_call(message, "org.freedesktop.login1.Session", "Terminate")) {
+
+                r = session_stop(s);
+                if (r < 0)
+                        return bus_send_error_reply(connection, message, NULL, r);
+
+                reply = dbus_message_new_method_return(message);
+                if (!reply)
+                        goto oom;
+
+        } else if (dbus_message_is_method_call(message, "org.freedesktop.login1.Session", "Activate")) {
+
+                r = session_activate(s);
+                if (r < 0)
+                        return bus_send_error_reply(connection, message, NULL, r);
+
+                reply = dbus_message_new_method_return(message);
+                if (!reply)
+                        goto oom;
+
+        } else if (dbus_message_is_method_call(message, "org.freedesktop.login1.Session", "Lock") ||
+                   dbus_message_is_method_call(message, "org.freedesktop.login1.Session", "Unlock")) {
+
+                if (session_send_lock(s, streq(dbus_message_get_member(message), "Lock")) < 0)
+                        goto oom;
+
+                reply = dbus_message_new_method_return(message);
+                if (!reply)
+                        goto oom;
+
+        } else if (dbus_message_is_method_call(message, "org.freedesktop.login1.Session", "SetIdleHint")) {
+                dbus_bool_t b;
+                unsigned long ul;
+
+                if (!dbus_message_get_args(
+                                    message,
+                                    &error,
+                                    DBUS_TYPE_BOOLEAN, &b,
+                                    DBUS_TYPE_INVALID))
+                        return bus_send_error_reply(connection, message, &error, -EINVAL);
+
+                ul = dbus_bus_get_unix_user(connection, dbus_message_get_sender(message), &error);
+                if (ul == (unsigned long) -1)
+                        return bus_send_error_reply(connection, message, &error, -EIO);
+
+                if (ul != 0 && ul != s->user->uid)
+                        return bus_send_error_reply(connection, message, NULL, -EPERM);
+
+                session_set_idle_hint(s, b);
+
+                reply = dbus_message_new_method_return(message);
+                if (!reply)
+                        goto oom;
+
+        } else if (dbus_message_is_method_call(message, "org.freedesktop.login1.Session", "Kill")) {
+                const char *swho;
+                int32_t signo;
+                KillWho who;
+
+                if (!dbus_message_get_args(
+                                    message,
+                                    &error,
+                                    DBUS_TYPE_STRING, &swho,
+                                    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 (signo <= 0 || signo >= _NSIG)
+                        return bus_send_error_reply(connection, message, &error, -EINVAL);
+
+                r = session_kill(s, who, signo);
+                if (r < 0)
+                        return bus_send_error_reply(connection, message, NULL, r);
+
+                reply = dbus_message_new_method_return(message);
+                if (!reply)
+                        goto oom;
+
+        } else {
+                const BusBoundProperties bps[] = {
+                        { "org.freedesktop.login1.Session", bus_login_session_properties,      s       },
+                        { "org.freedesktop.login1.Session", bus_login_session_user_properties, s->user },
+                        { 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);
+
+        dbus_error_free(&error);
+
+        return DBUS_HANDLER_RESULT_NEED_MEMORY;
+}
+
+static DBusHandlerResult session_message_handler(
+                DBusConnection *connection,
+                DBusMessage *message,
+                void *userdata) {
+
+        Manager *m = userdata;
+        Session *s;
+        int r;
+
+        r = get_session_for_path(m, dbus_message_get_path(message), &s);
+        if (r < 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 session");
+                        return bus_send_error_reply(connection, message, &e, r);
+                }
+
+                return bus_send_error_reply(connection, message, NULL, r);
+        }
+
+        return session_message_dispatch(s, connection, message);
+}
+
+const DBusObjectPathVTable bus_session_vtable = {
+        .message_function = session_message_handler
+};
+
+char *session_bus_path(Session *s) {
+        char *t, *r;
+
+        assert(s);
+
+        t = bus_path_escape(s->id);
+        if (!t)
+                return NULL;
+
+        r = strappend("/org/freedesktop/login1/session/", t);
+        free(t);
+
+        return r;
+}
+
+int session_send_signal(Session *s, bool new_session) {
+        DBusMessage *m;
+        int r = -ENOMEM;
+        char *p = NULL;
+
+        assert(s);
+
+        m = dbus_message_new_signal("/org/freedesktop/login1",
+                                    "org.freedesktop.login1.Manager",
+                                    new_session ? "SessionNew" : "SessionRemoved");
+
+        if (!m)
+                return -ENOMEM;
+
+        p = session_bus_path(s);
+        if (!p)
+                goto finish;
+
+        if (!dbus_message_append_args(
+                            m,
+                            DBUS_TYPE_STRING, &s->id,
+                            DBUS_TYPE_OBJECT_PATH, &p,
+                            DBUS_TYPE_INVALID))
+                goto finish;
+
+        if (!dbus_connection_send(s->manager->bus, m, NULL))
+                goto finish;
+
+        r = 0;
+
+finish:
+        dbus_message_unref(m);
+        free(p);
+
+        return r;
+}
+
+int session_send_changed(Session *s, const char *properties) {
+        DBusMessage *m;
+        int r = -ENOMEM;
+        char *p = NULL;
+
+        assert(s);
+
+        if (!s->started)
+                return 0;
+
+        p = session_bus_path(s);
+        if (!p)
+                return -ENOMEM;
+
+        m = bus_properties_changed_new(p, "org.freedesktop.login1.Session", properties);
+        if (!m)
+                goto finish;
+
+        if (!dbus_connection_send(s->manager->bus, m, NULL))
+                goto finish;
+
+        r = 0;
+
+finish:
+        if (m)
+                dbus_message_unref(m);
+        free(p);
+
+        return r;
+}
+
+int session_send_lock(Session *s, bool lock) {
+        DBusMessage *m;
+        bool b;
+        char *p;
+
+        assert(s);
+
+        p = session_bus_path(s);
+        if (!p)
+                return -ENOMEM;
+
+        m = dbus_message_new_signal(p, "org.freedesktop.login1.Session", lock ? "Lock" : "Unlock");
+        free(p);
+
+        if (!m)
+                return -ENOMEM;
+
+        b = dbus_connection_send(s->manager->bus, m, NULL);
+        dbus_message_unref(m);
+
+        if (!b)
+                return -ENOMEM;
+
+        return 0;
+}
diff --git a/src/login/logind-session.c b/src/login/logind-session.c
new file mode 100644 (file)
index 0000000..4e0af86
--- /dev/null
@@ -0,0 +1,982 @@
+/*-*- 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 <http://www.gnu.org/licenses/>.
+***/
+
+#include <errno.h>
+#include <string.h>
+#include <unistd.h>
+#include <sys/epoll.h>
+#include <fcntl.h>
+
+#include "logind-session.h"
+#include "strv.h"
+#include "util.h"
+#include "cgroup-util.h"
+
+#define IDLE_THRESHOLD_USEC (5*USEC_PER_MINUTE)
+
+Session* session_new(Manager *m, User *u, const char *id) {
+        Session *s;
+
+        assert(m);
+        assert(id);
+
+        s = new0(Session, 1);
+        if (!s)
+                return NULL;
+
+        s->state_file = strappend("/run/systemd/sessions/", id);
+        if (!s->state_file) {
+                free(s);
+                return NULL;
+        }
+
+        s->id = file_name_from_path(s->state_file);
+
+        if (hashmap_put(m->sessions, s->id, s) < 0) {
+                free(s->id);
+                free(s);
+                return NULL;
+        }
+
+        s->manager = m;
+        s->fifo_fd = -1;
+        s->user = u;
+
+        LIST_PREPEND(Session, sessions_by_user, u->sessions, s);
+
+        return s;
+}
+
+void session_free(Session *s) {
+        assert(s);
+
+        if (s->in_gc_queue)
+                LIST_REMOVE(Session, gc_queue, s->manager->session_gc_queue, s);
+
+        if (s->user) {
+                LIST_REMOVE(Session, sessions_by_user, s->user->sessions, s);
+
+                if (s->user->display == s)
+                        s->user->display = NULL;
+        }
+
+        if (s->seat) {
+                if (s->seat->active == s)
+                        s->seat->active = NULL;
+
+                LIST_REMOVE(Session, sessions_by_seat, s->seat->sessions, s);
+        }
+
+        if (s->cgroup_path)
+                hashmap_remove(s->manager->cgroups, s->cgroup_path);
+
+        free(s->cgroup_path);
+        strv_free(s->controllers);
+
+        free(s->tty);
+        free(s->display);
+        free(s->remote_host);
+        free(s->remote_user);
+        free(s->service);
+
+        hashmap_remove(s->manager->sessions, s->id);
+
+        session_remove_fifo(s);
+
+        free(s->state_file);
+        free(s);
+}
+
+int session_save(Session *s) {
+        FILE *f;
+        int r = 0;
+        char *temp_path;
+
+        assert(s);
+
+        if (!s->started)
+                return 0;
+
+        r = safe_mkdir("/run/systemd/sessions", 0755, 0, 0);
+        if (r < 0)
+                goto finish;
+
+        r = fopen_temporary(s->state_file, &f, &temp_path);
+        if (r < 0)
+                goto finish;
+
+        assert(s->user);
+
+        fchmod(fileno(f), 0644);
+
+        fprintf(f,
+                "# This is private data. Do not parse.\n"
+                "UID=%lu\n"
+                "USER=%s\n"
+                "ACTIVE=%i\n"
+                "REMOTE=%i\n"
+                "KILL_PROCESSES=%i\n",
+                (unsigned long) s->user->uid,
+                s->user->name,
+                session_is_active(s),
+                s->remote,
+                s->kill_processes);
+
+        if (s->type >= 0)
+                fprintf(f,
+                        "TYPE=%s\n",
+                        session_type_to_string(s->type));
+
+        if (s->class >= 0)
+                fprintf(f,
+                        "CLASS=%s\n",
+                        session_class_to_string(s->class));
+
+        if (s->cgroup_path)
+                fprintf(f,
+                        "CGROUP=%s\n",
+                        s->cgroup_path);
+
+        if (s->fifo_path)
+                fprintf(f,
+                        "FIFO=%s\n",
+                        s->fifo_path);
+
+        if (s->seat)
+                fprintf(f,
+                        "SEAT=%s\n",
+                        s->seat->id);
+
+        if (s->tty)
+                fprintf(f,
+                        "TTY=%s\n",
+                        s->tty);
+
+        if (s->display)
+                fprintf(f,
+                        "DISPLAY=%s\n",
+                        s->display);
+
+        if (s->remote_host)
+                fprintf(f,
+                        "REMOTE_HOST=%s\n",
+                        s->remote_host);
+
+        if (s->remote_user)
+                fprintf(f,
+                        "REMOTE_USER=%s\n",
+                        s->remote_user);
+
+        if (s->service)
+                fprintf(f,
+                        "SERVICE=%s\n",
+                        s->service);
+
+        if (s->seat && seat_can_multi_session(s->seat))
+                fprintf(f,
+                        "VTNR=%i\n",
+                        s->vtnr);
+
+        if (s->leader > 0)
+                fprintf(f,
+                        "LEADER=%lu\n",
+                        (unsigned long) s->leader);
+
+        if (s->audit_id > 0)
+                fprintf(f,
+                        "AUDIT=%llu\n",
+                        (unsigned long long) s->audit_id);
+
+        fflush(f);
+
+        if (ferror(f) || rename(temp_path, s->state_file) < 0) {
+                r = -errno;
+                unlink(s->state_file);
+                unlink(temp_path);
+        }
+
+        fclose(f);
+        free(temp_path);
+
+finish:
+        if (r < 0)
+                log_error("Failed to save session data for %s: %s", s->id, strerror(-r));
+
+        return r;
+}
+
+int session_load(Session *s) {
+        char *remote = NULL,
+                *kill_processes = NULL,
+                *seat = NULL,
+                *vtnr = NULL,
+                *leader = NULL,
+                *audit_id = NULL,
+                *type = NULL,
+                *class = NULL;
+
+        int k, r;
+
+        assert(s);
+
+        r = parse_env_file(s->state_file, NEWLINE,
+                           "REMOTE",         &remote,
+                           "KILL_PROCESSES", &kill_processes,
+                           "CGROUP",         &s->cgroup_path,
+                           "FIFO",           &s->fifo_path,
+                           "SEAT",           &seat,
+                           "TTY",            &s->tty,
+                           "DISPLAY",        &s->display,
+                           "REMOTE_HOST",    &s->remote_host,
+                           "REMOTE_USER",    &s->remote_user,
+                           "SERVICE",        &s->service,
+                           "VTNR",           &vtnr,
+                           "LEADER",         &leader,
+                           "TYPE",           &type,
+                           "CLASS",          &class,
+                           NULL);
+
+        if (r < 0)
+                goto finish;
+
+        if (remote) {
+                k = parse_boolean(remote);
+                if (k >= 0)
+                        s->remote = k;
+        }
+
+        if (kill_processes) {
+                k = parse_boolean(kill_processes);
+                if (k >= 0)
+                        s->kill_processes = k;
+        }
+
+        if (seat && !s->seat) {
+                Seat *o;
+
+                o = hashmap_get(s->manager->seats, seat);
+                if (o)
+                        seat_attach_session(o, s);
+        }
+
+        if (vtnr && s->seat && seat_can_multi_session(s->seat)) {
+                int v;
+
+                k = safe_atoi(vtnr, &v);
+                if (k >= 0 && v >= 1)
+                        s->vtnr = v;
+        }
+
+        if (leader) {
+                pid_t pid;
+
+                k = parse_pid(leader, &pid);
+                if (k >= 0 && pid >= 1) {
+                        s->leader = pid;
+
+                        audit_session_from_pid(pid, &s->audit_id);
+                }
+        }
+
+        if (type) {
+                SessionType t;
+
+                t = session_type_from_string(type);
+                if (t >= 0)
+                        s->type = t;
+        }
+
+        if (class) {
+                SessionClass c;
+
+                c = session_class_from_string(class);
+                if (c >= 0)
+                        s->class = c;
+        }
+
+        if (s->fifo_path) {
+                int fd;
+
+                /* If we open an unopened pipe for reading we will not
+                   get an EOF. to trigger an EOF we hence open it for
+                   reading, but close it right-away which then will
+                   trigger the EOF. */
+
+                fd = session_create_fifo(s);
+                if (fd >= 0)
+                        close_nointr_nofail(fd);
+        }
+
+
+finish:
+        free(remote);
+        free(kill_processes);
+        free(seat);
+        free(vtnr);
+        free(leader);
+        free(audit_id);
+
+        return r;
+}
+
+int session_activate(Session *s) {
+        int r;
+
+        assert(s);
+
+        if (s->vtnr < 0)
+                return -ENOTSUP;
+
+        if (!s->seat)
+                return -ENOTSUP;
+
+        if (s->seat->active == s)
+                return 0;
+
+        assert(seat_is_vtconsole(s->seat));
+
+        r = chvt(s->vtnr);
+        if (r < 0)
+                return r;
+
+        return seat_set_active(s->seat, s);
+}
+
+static int session_link_x11_socket(Session *s) {
+        char *t, *f, *c;
+        size_t k;
+
+        assert(s);
+        assert(s->user);
+        assert(s->user->runtime_path);
+
+        if (s->user->display)
+                return 0;
+
+        if (!s->display || !display_is_local(s->display))
+                return 0;
+
+        k = strspn(s->display+1, "0123456789");
+        f = new(char, sizeof("/tmp/.X11-unix/X") + k);
+        if (!f) {
+                log_error("Out of memory");
+                return -ENOMEM;
+        }
+
+        c = stpcpy(f, "/tmp/.X11-unix/X");
+        memcpy(c, s->display+1, k);
+        c[k] = 0;
+
+        if (access(f, F_OK) < 0) {
+                log_warning("Session %s has display %s with nonexisting socket %s.", s->id, s->display, f);
+                free(f);
+                return -ENOENT;
+        }
+
+        /* Note that this cannot be in a subdir to avoid
+         * vulnerabilities since we are privileged but the runtime
+         * path is owned by the user */
+
+        t = strappend(s->user->runtime_path, "/X11-display");
+        if (!t) {
+                log_error("Out of memory");
+                free(f);
+                return -ENOMEM;
+        }
+
+        if (link(f, t) < 0) {
+                if (errno == EEXIST) {
+                        unlink(t);
+
+                        if (link(f, t) >= 0)
+                                goto done;
+                }
+
+                if (symlink(f, t) < 0) {
+
+                        if (errno == EEXIST) {
+                                unlink(t);
+
+                                if (symlink(f, t) >= 0)
+                                        goto done;
+                        }
+
+                        log_error("Failed to link %s to %s: %m", f, t);
+                        free(f);
+                        free(t);
+                        return -errno;
+                }
+        }
+
+done:
+        log_info("Linked %s to %s.", f, t);
+        free(f);
+        free(t);
+
+        s->user->display = s;
+
+        return 0;
+}
+
+static int session_create_one_group(Session *s, const char *controller, const char *path) {
+        int r;
+
+        assert(s);
+        assert(controller);
+        assert(path);
+
+        if (s->leader > 0) {
+                r = cg_create_and_attach(controller, path, s->leader);
+                if (r < 0)
+                        r = cg_create(controller, path);
+        } else
+                r = cg_create(controller, path);
+
+        if (r < 0)
+                return r;
+
+        r = cg_set_task_access(controller, path, 0644, s->user->uid, s->user->gid, -1);
+        if (r >= 0)
+                r = cg_set_group_access(controller, path, 0755, s->user->uid, s->user->gid);
+
+        return r;
+}
+
+static int session_create_cgroup(Session *s) {
+        char **k;
+        char *p;
+        int r;
+
+        assert(s);
+        assert(s->user);
+        assert(s->user->cgroup_path);
+
+        if (!s->cgroup_path) {
+                if (asprintf(&p, "%s/%s", s->user->cgroup_path, s->id) < 0) {
+                        log_error("Out of memory");
+                        return -ENOMEM;
+                }
+        } else
+                p = s->cgroup_path;
+
+        r = session_create_one_group(s, SYSTEMD_CGROUP_CONTROLLER, p);
+        if (r < 0) {
+                log_error("Failed to create "SYSTEMD_CGROUP_CONTROLLER":%s: %s", p, strerror(-r));
+                free(p);
+                s->cgroup_path = NULL;
+                return r;
+        }
+
+        s->cgroup_path = p;
+
+        STRV_FOREACH(k, s->controllers) {
+
+                if (strv_contains(s->reset_controllers, *k))
+                        continue;
+
+                r = session_create_one_group(s, *k, p);
+                if (r < 0)
+                        log_warning("Failed to create %s:%s: %s", *k, p, strerror(-r));
+        }
+
+        STRV_FOREACH(k, s->manager->controllers) {
+
+                if (strv_contains(s->reset_controllers, *k) ||
+                    strv_contains(s->manager->reset_controllers, *k) ||
+                    strv_contains(s->controllers, *k))
+                        continue;
+
+                r = session_create_one_group(s, *k, p);
+                if (r < 0)
+                        log_warning("Failed to create %s:%s: %s", *k, p, strerror(-r));
+        }
+
+        if (s->leader > 0) {
+
+                STRV_FOREACH(k, s->reset_controllers) {
+                        r = cg_attach(*k, "/", s->leader);
+                        if (r < 0)
+                                log_warning("Failed to reset controller %s: %s", *k, strerror(-r));
+
+                }
+
+                STRV_FOREACH(k, s->manager->reset_controllers) {
+
+                        if (strv_contains(s->reset_controllers, *k) ||
+                            strv_contains(s->controllers, *k))
+                                continue;
+
+                        r = cg_attach(*k, "/", s->leader);
+                        if (r < 0)
+                                log_warning("Failed to reset controller %s: %s", *k, strerror(-r));
+
+                }
+        }
+
+        hashmap_put(s->manager->cgroups, s->cgroup_path, s);
+
+        return 0;
+}
+
+int session_start(Session *s) {
+        int r;
+
+        assert(s);
+        assert(s->user);
+
+        if (s->started)
+                return 0;
+
+        r = user_start(s->user);
+        if (r < 0)
+                return r;
+
+        log_full(s->type == SESSION_TTY || s->type == SESSION_X11 ? LOG_INFO : LOG_DEBUG,
+                 "New session %s of user %s.", s->id, s->user->name);
+
+        /* Create cgroup */
+        r = session_create_cgroup(s);
+        if (r < 0)
+                return r;
+
+        /* Create X11 symlink */
+        session_link_x11_socket(s);
+
+        dual_timestamp_get(&s->timestamp);
+
+        if (s->seat)
+                seat_read_active_vt(s->seat);
+
+        s->started = true;
+
+        /* Save session data */
+        session_save(s);
+        user_save(s->user);
+
+        session_send_signal(s, true);
+
+        if (s->seat) {
+                seat_save(s->seat);
+
+                if (s->seat->active == s)
+                        seat_send_changed(s->seat, "Sessions\0ActiveSession\0");
+                else
+                        seat_send_changed(s->seat, "Sessions\0");
+        }
+
+        user_send_changed(s->user, "Sessions\0");
+
+        return 0;
+}
+
+static bool session_shall_kill(Session *s) {
+        assert(s);
+
+        if (!s->kill_processes)
+                return false;
+
+        if (strv_contains(s->manager->kill_exclude_users, s->user->name))
+                return false;
+
+        if (strv_isempty(s->manager->kill_only_users))
+                return true;
+
+        return strv_contains(s->manager->kill_only_users, s->user->name);
+}
+
+static int session_terminate_cgroup(Session *s) {
+        int r;
+        char **k;
+
+        assert(s);
+
+        if (!s->cgroup_path)
+                return 0;
+
+        cg_trim(SYSTEMD_CGROUP_CONTROLLER, s->cgroup_path, false);
+
+        if (session_shall_kill(s)) {
+
+                r = cg_kill_recursive_and_wait(SYSTEMD_CGROUP_CONTROLLER, s->cgroup_path, true);
+                if (r < 0)
+                        log_error("Failed to kill session cgroup: %s", strerror(-r));
+
+        } else {
+                if (s->leader > 0) {
+                        Session *t;
+
+                        /* We still send a HUP to the leader process,
+                         * even if we are not supposed to kill the
+                         * whole cgroup. But let's first check the
+                         * leader still exists and belongs to our
+                         * session... */
+
+                        r = manager_get_session_by_pid(s->manager, s->leader, &t);
+                        if (r > 0 && t == s) {
+                                kill(s->leader, SIGTERM); /* for normal processes */
+                                kill(s->leader, SIGHUP);  /* for shells */
+                                kill(s->leader, SIGCONT); /* in case they are stopped */
+                        }
+                }
+
+                r = cg_is_empty_recursive(SYSTEMD_CGROUP_CONTROLLER, s->cgroup_path, true);
+                if (r < 0)
+                        log_error("Failed to check session cgroup: %s", strerror(-r));
+                else if (r > 0) {
+                        r = cg_delete(SYSTEMD_CGROUP_CONTROLLER, s->cgroup_path);
+                        if (r < 0)
+                                log_error("Failed to delete session cgroup: %s", strerror(-r));
+                }
+        }
+
+        STRV_FOREACH(k, s->user->manager->controllers)
+                cg_trim(*k, s->cgroup_path, true);
+
+        hashmap_remove(s->manager->cgroups, s->cgroup_path);
+
+        free(s->cgroup_path);
+        s->cgroup_path = NULL;
+
+        return 0;
+}
+
+static int session_unlink_x11_socket(Session *s) {
+        char *t;
+        int r;
+
+        assert(s);
+        assert(s->user);
+
+        if (s->user->display != s)
+                return 0;
+
+        s->user->display = NULL;
+
+        t = strappend(s->user->runtime_path, "/X11-display");
+        if (!t) {
+                log_error("Out of memory");
+                return -ENOMEM;
+        }
+
+        r = unlink(t);
+        free(t);
+
+        return r < 0 ? -errno : 0;
+}
+
+int session_stop(Session *s) {
+        int r = 0, k;
+
+        assert(s);
+
+        if (s->started)
+                log_full(s->type == SESSION_TTY || s->type == SESSION_X11 ? LOG_INFO : LOG_DEBUG,
+                         "Removed session %s.", s->id);
+
+        /* Kill cgroup */
+        k = session_terminate_cgroup(s);
+        if (k < 0)
+                r = k;
+
+        /* Remove X11 symlink */
+        session_unlink_x11_socket(s);
+
+        unlink(s->state_file);
+        session_add_to_gc_queue(s);
+        user_add_to_gc_queue(s->user);
+
+        if (s->started)
+                session_send_signal(s, false);
+
+        if (s->seat) {
+                if (s->seat->active == s)
+                        seat_set_active(s->seat, NULL);
+
+                seat_send_changed(s->seat, "Sessions\0");
+        }
+
+        user_send_changed(s->user, "Sessions\0");
+
+        s->started = false;
+
+        return r;
+}
+
+bool session_is_active(Session *s) {
+        assert(s);
+
+        if (!s->seat)
+                return true;
+
+        return s->seat->active == s;
+}
+
+int session_get_idle_hint(Session *s, dual_timestamp *t) {
+        char *p;
+        struct stat st;
+        usec_t u, n;
+        bool b;
+        int k;
+
+        assert(s);
+
+        if (s->idle_hint) {
+                if (t)
+                        *t = s->idle_hint_timestamp;
+
+                return s->idle_hint;
+        }
+
+        if (isempty(s->tty))
+                goto dont_know;
+
+        if (s->tty[0] != '/') {
+                p = strappend("/dev/", s->tty);
+                if (!p)
+                        return -ENOMEM;
+        } else
+                p = NULL;
+
+        if (!startswith(p ? p : s->tty, "/dev/")) {
+                free(p);
+                goto dont_know;
+        }
+
+        k = lstat(p ? p : s->tty, &st);
+        free(p);
+
+        if (k < 0)
+                goto dont_know;
+
+        u = timespec_load(&st.st_atim);
+        n = now(CLOCK_REALTIME);
+        b = u + IDLE_THRESHOLD_USEC < n;
+
+        if (t)
+                dual_timestamp_from_realtime(t, u + b ? IDLE_THRESHOLD_USEC : 0);
+
+        return b;
+
+dont_know:
+        if (t)
+                *t = s->idle_hint_timestamp;
+
+        return 0;
+}
+
+void session_set_idle_hint(Session *s, bool b) {
+        assert(s);
+
+        if (s->idle_hint == b)
+                return;
+
+        s->idle_hint = b;
+        dual_timestamp_get(&s->idle_hint_timestamp);
+
+        session_send_changed(s,
+                             "IdleHint\0"
+                             "IdleSinceHint\0"
+                             "IdleSinceHintMonotonic\0");
+
+        if (s->seat)
+                seat_send_changed(s->seat,
+                                  "IdleHint\0"
+                                  "IdleSinceHint\0"
+                                  "IdleSinceHintMonotonic\0");
+
+        user_send_changed(s->user,
+                          "IdleHint\0"
+                          "IdleSinceHint\0"
+                          "IdleSinceHintMonotonic\0");
+
+        manager_send_changed(s->manager,
+                             "IdleHint\0"
+                             "IdleSinceHint\0"
+                             "IdleSinceHintMonotonic\0");
+}
+
+int session_create_fifo(Session *s) {
+        int r;
+
+        assert(s);
+
+        /* Create FIFO */
+        if (!s->fifo_path) {
+                r = safe_mkdir("/run/systemd/sessions", 0755, 0, 0);
+                if (r < 0)
+                        return r;
+
+                if (asprintf(&s->fifo_path, "/run/systemd/sessions/%s.ref", s->id) < 0)
+                        return -ENOMEM;
+
+                if (mkfifo(s->fifo_path, 0600) < 0 && errno != EEXIST)
+                        return -errno;
+        }
+
+        /* Open reading side */
+        if (s->fifo_fd < 0) {
+                struct epoll_event ev;
+
+                s->fifo_fd = open(s->fifo_path, O_RDONLY|O_CLOEXEC|O_NDELAY);
+                if (s->fifo_fd < 0)
+                        return -errno;
+
+                r = hashmap_put(s->manager->fifo_fds, INT_TO_PTR(s->fifo_fd + 1), s);
+                if (r < 0)
+                        return r;
+
+                zero(ev);
+                ev.events = 0;
+                ev.data.u32 = FD_FIFO_BASE + s->fifo_fd;
+
+                if (epoll_ctl(s->manager->epoll_fd, EPOLL_CTL_ADD, s->fifo_fd, &ev) < 0)
+                        return -errno;
+        }
+
+        /* Open writing side */
+        r = open(s->fifo_path, O_WRONLY|O_CLOEXEC|O_NDELAY);
+        if (r < 0)
+                return -errno;
+
+        return r;
+}
+
+void session_remove_fifo(Session *s) {
+        assert(s);
+
+        if (s->fifo_fd >= 0) {
+                assert_se(hashmap_remove(s->manager->fifo_fds, INT_TO_PTR(s->fifo_fd + 1)) == s);
+                assert_se(epoll_ctl(s->manager->epoll_fd, EPOLL_CTL_DEL, s->fifo_fd, NULL) == 0);
+                close_nointr_nofail(s->fifo_fd);
+                s->fifo_fd = -1;
+        }
+
+        if (s->fifo_path) {
+                unlink(s->fifo_path);
+                free(s->fifo_path);
+                s->fifo_path = NULL;
+        }
+}
+
+int session_check_gc(Session *s, bool drop_not_started) {
+        int r;
+
+        assert(s);
+
+        if (drop_not_started && !s->started)
+                return 0;
+
+        if (s->fifo_fd >= 0) {
+
+                r = pipe_eof(s->fifo_fd);
+                if (r < 0)
+                        return r;
+
+                if (r == 0)
+                        return 1;
+        }
+
+        if (s->cgroup_path) {
+
+                r = cg_is_empty_recursive(SYSTEMD_CGROUP_CONTROLLER, s->cgroup_path, false);
+                if (r < 0)
+                        return r;
+
+                if (r <= 0)
+                        return 1;
+        }
+
+        return 0;
+}
+
+void session_add_to_gc_queue(Session *s) {
+        assert(s);
+
+        if (s->in_gc_queue)
+                return;
+
+        LIST_PREPEND(Session, gc_queue, s->manager->session_gc_queue, s);
+        s->in_gc_queue = true;
+}
+
+int session_kill(Session *s, KillWho who, int signo) {
+        int r = 0;
+        Set *pid_set = NULL;
+
+        assert(s);
+
+        if (!s->cgroup_path)
+                return -ESRCH;
+
+        if (s->leader <= 0 && who == KILL_LEADER)
+                return -ESRCH;
+
+        if (s->leader > 0)
+                if (kill(s->leader, signo) < 0)
+                        r = -errno;
+
+        if (who == KILL_ALL) {
+                int q;
+
+                pid_set = set_new(trivial_hash_func, trivial_compare_func);
+                if (!pid_set)
+                        return -ENOMEM;
+
+                if (s->leader > 0) {
+                        q = set_put(pid_set, LONG_TO_PTR(s->leader));
+                        if (q < 0)
+                                r = q;
+                }
+
+                q = cg_kill_recursive(SYSTEMD_CGROUP_CONTROLLER, s->cgroup_path, signo, false, true, false, pid_set);
+                if (q < 0)
+                        if (q != -EAGAIN && q != -ESRCH && q != -ENOENT)
+                                r = q;
+        }
+
+        if (pid_set)
+                set_free(pid_set);
+
+        return r;
+}
+
+static const char* const session_type_table[_SESSION_TYPE_MAX] = {
+        [SESSION_TTY] = "tty",
+        [SESSION_X11] = "x11",
+        [SESSION_UNSPECIFIED] = "unspecified"
+};
+
+DEFINE_STRING_TABLE_LOOKUP(session_type, SessionType);
+
+static const char* const session_class_table[_SESSION_CLASS_MAX] = {
+        [SESSION_USER] = "user",
+        [SESSION_GREETER] = "greeter",
+        [SESSION_LOCK_SCREEN] = "lock-screen"
+};
+
+DEFINE_STRING_TABLE_LOOKUP(session_class, SessionClass);
+
+static const char* const kill_who_table[_KILL_WHO_MAX] = {
+        [KILL_LEADER] = "leader",
+        [KILL_ALL] = "all"
+};
+
+DEFINE_STRING_TABLE_LOOKUP(kill_who, KillWho);
diff --git a/src/login/logind-session.h b/src/login/logind-session.h
new file mode 100644 (file)
index 0000000..d0b8c87
--- /dev/null
@@ -0,0 +1,136 @@
+/*-*- Mode: C; c-basic-offset: 8; indent-tabs-mode: nil -*-*/
+
+#ifndef foologindsessionhfoo
+#define foologindsessionhfoo
+
+/***
+  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 <http://www.gnu.org/licenses/>.
+***/
+
+typedef struct Session Session;
+
+#include "list.h"
+#include "util.h"
+#include "logind.h"
+#include "logind-seat.h"
+#include "logind-user.h"
+
+typedef enum SessionType {
+        SESSION_UNSPECIFIED,
+        SESSION_TTY,
+        SESSION_X11,
+        _SESSION_TYPE_MAX,
+        _SESSION_TYPE_INVALID = -1
+} SessionType;
+
+typedef enum SessionClass {
+        SESSION_USER,
+        SESSION_GREETER,
+        SESSION_LOCK_SCREEN,
+        _SESSION_CLASS_MAX,
+        _SESSION_CLASS_INVALID = -1
+} SessionClass;
+
+typedef enum KillWho {
+        KILL_LEADER,
+        KILL_ALL,
+        _KILL_WHO_MAX,
+        _KILL_WHO_INVALID = -1
+} KillWho;
+
+struct Session {
+        Manager *manager;
+
+        char *id;
+        SessionType type;
+        SessionClass class;
+
+        char *state_file;
+
+        User *user;
+
+        dual_timestamp timestamp;
+
+        char *tty;
+        char *display;
+
+        bool remote;
+        char *remote_user;
+        char *remote_host;
+
+        char *service;
+
+        int vtnr;
+        Seat *seat;
+
+        pid_t leader;
+        uint32_t audit_id;
+
+        int fifo_fd;
+        char *fifo_path;
+
+        char *cgroup_path;
+        char **controllers, **reset_controllers;
+
+        bool idle_hint;
+        dual_timestamp idle_hint_timestamp;
+
+        bool kill_processes;
+        bool in_gc_queue:1;
+        bool started:1;
+
+        LIST_FIELDS(Session, sessions_by_user);
+        LIST_FIELDS(Session, sessions_by_seat);
+
+        LIST_FIELDS(Session, gc_queue);
+};
+
+Session *session_new(Manager *m, User *u, const char *id);
+void session_free(Session *s);
+int session_check_gc(Session *s, bool drop_not_started);
+void session_add_to_gc_queue(Session *s);
+int session_activate(Session *s);
+bool session_is_active(Session *s);
+int session_get_idle_hint(Session *s, dual_timestamp *t);
+void session_set_idle_hint(Session *s, bool b);
+int session_create_fifo(Session *s);
+void session_remove_fifo(Session *s);
+int session_start(Session *s);
+int session_stop(Session *s);
+int session_save(Session *s);
+int session_load(Session *s);
+int session_kill(Session *s, KillWho who, int signo);
+
+char *session_bus_path(Session *s);
+
+extern const DBusObjectPathVTable bus_session_vtable;
+
+int session_send_signal(Session *s, bool new_session);
+int session_send_changed(Session *s, const char *properties);
+int session_send_lock(Session *s, bool lock);
+
+const char* session_type_to_string(SessionType t);
+SessionType session_type_from_string(const char *s);
+
+const char* session_class_to_string(SessionClass t);
+SessionClass session_class_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/login/logind-user-dbus.c b/src/login/logind-user-dbus.c
new file mode 100644 (file)
index 0000000..a9d680f
--- /dev/null
@@ -0,0 +1,417 @@
+/*-*- 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 <http://www.gnu.org/licenses/>.
+***/
+
+#include <errno.h>
+#include <string.h>
+
+#include "logind.h"
+#include "logind-user.h"
+#include "dbus-common.h"
+
+#define BUS_USER_INTERFACE \
+        " <interface name=\"org.freedesktop.login1.User\">\n"           \
+        "  <method name=\"Terminate\"/>\n"                              \
+        "  <method name=\"Kill\">\n"                                    \
+        "   <arg name=\"signal\" type=\"s\"/>\n"                        \
+        "  </method>\n"                                                 \
+        "  <property name=\"UID\" type=\"u\" access=\"read\"/>\n"       \
+        "  <property name=\"GID\" type=\"u\" access=\"read\"/>\n"       \
+        "  <property name=\"Name\" type=\"s\" access=\"read\"/>\n"      \
+        "  <property name=\"Timestamp\" type=\"t\" access=\"read\"/>\n" \
+        "  <property name=\"TimestampMonotonic\" type=\"t\" access=\"read\"/>\n" \
+        "  <property name=\"RuntimePath\" type=\"s\" access=\"read\"/>\n" \
+        "  <property name=\"ControlGroupPath\" type=\"s\" access=\"read\"/>\n" \
+        "  <property name=\"Service\" type=\"s\" access=\"read\"/>\n"   \
+        "  <property name=\"Display\" type=\"(so)\" access=\"read\"/>\n" \
+        "  <property name=\"State\" type=\"s\" access=\"read\"/>\n"     \
+        "  <property name=\"Sessions\" type=\"a(so)\" access=\"read\"/>\n" \
+        "  <property name=\"IdleHint\" type=\"b\" access=\"read\"/>\n"  \
+        "  <property name=\"IdleSinceHint\" type=\"t\" access=\"read\"/>\n" \
+        "  <property name=\"IdleSinceHintMonotonic\" type=\"t\" access=\"read\"/>\n" \
+        " </interface>\n"                                               \
+
+#define INTROSPECTION                                                   \
+        DBUS_INTROSPECT_1_0_XML_DOCTYPE_DECL_NODE                       \
+        "<node>\n"                                                      \
+        BUS_USER_INTERFACE                                              \
+        BUS_PROPERTIES_INTERFACE                                        \
+        BUS_PEER_INTERFACE                                              \
+        BUS_INTROSPECTABLE_INTERFACE                                    \
+        "</node>\n"
+
+#define INTERFACES_LIST                              \
+        BUS_GENERIC_INTERFACES_LIST                  \
+        "org.freedesktop.login1.User\0"
+
+static int bus_user_append_display(DBusMessageIter *i, const char *property, void *data) {
+        DBusMessageIter sub;
+        User *u = data;
+        const char *id, *path;
+        char *p = NULL;
+
+        assert(i);
+        assert(property);
+        assert(u);
+
+        if (!dbus_message_iter_open_container(i, DBUS_TYPE_STRUCT, NULL, &sub))
+                return -ENOMEM;
+
+        if (u->display) {
+                id = u->display->id;
+                path = p = session_bus_path(u->display);
+
+                if (!p)
+                        return -ENOMEM;
+        } else {
+                id = "";
+                path = "/";
+        }
+
+        if (!dbus_message_iter_append_basic(&sub, DBUS_TYPE_STRING, &id) ||
+            !dbus_message_iter_append_basic(&sub, DBUS_TYPE_OBJECT_PATH, &path)) {
+                free(p);
+                return -ENOMEM;
+        }
+
+        free(p);
+
+        if (!dbus_message_iter_close_container(i, &sub))
+                return -ENOMEM;
+
+        return 0;
+}
+
+static int bus_user_append_state(DBusMessageIter *i, const char *property, void *data) {
+        User *u = data;
+        const char *state;
+
+        assert(i);
+        assert(property);
+        assert(u);
+
+        state = user_state_to_string(user_get_state(u));
+
+        if (!dbus_message_iter_append_basic(i, DBUS_TYPE_STRING, &state))
+                return -ENOMEM;
+
+        return 0;
+}
+
+static int bus_user_append_sessions(DBusMessageIter *i, const char *property, void *data) {
+        DBusMessageIter sub, sub2;
+        User *u = data;
+        Session *session;
+
+        assert(i);
+        assert(property);
+        assert(u);
+
+        if (!dbus_message_iter_open_container(i, DBUS_TYPE_ARRAY, "(so)", &sub))
+                return -ENOMEM;
+
+        LIST_FOREACH(sessions_by_user, session, u->sessions) {
+                char *p;
+
+                if (!dbus_message_iter_open_container(&sub, DBUS_TYPE_STRUCT, NULL, &sub2))
+                        return -ENOMEM;
+
+                p = session_bus_path(session);
+                if (!p)
+                        return -ENOMEM;
+
+                if (!dbus_message_iter_append_basic(&sub2, DBUS_TYPE_STRING, &session->id) ||
+                    !dbus_message_iter_append_basic(&sub2, DBUS_TYPE_OBJECT_PATH, &p)) {
+                        free(p);
+                        return -ENOMEM;
+                }
+
+                free(p);
+
+                if (!dbus_message_iter_close_container(&sub, &sub2))
+                        return -ENOMEM;
+        }
+
+        if (!dbus_message_iter_close_container(i, &sub))
+                return -ENOMEM;
+
+        return 0;
+}
+
+static int bus_user_append_idle_hint(DBusMessageIter *i, const char *property, void *data) {
+        User *u = data;
+        dbus_bool_t b;
+
+        assert(i);
+        assert(property);
+        assert(u);
+
+        b = user_get_idle_hint(u, NULL) > 0;
+
+        if (!dbus_message_iter_append_basic(i, DBUS_TYPE_BOOLEAN, &b))
+                return -ENOMEM;
+
+        return 0;
+}
+
+static int bus_user_append_idle_hint_since(DBusMessageIter *i, const char *property, void *data) {
+        User *u = data;
+        dual_timestamp t;
+        uint64_t k;
+
+        assert(i);
+        assert(property);
+        assert(u);
+
+        user_get_idle_hint(u, &t);
+        k = streq(property, "IdleSinceHint") ? t.realtime : t.monotonic;
+
+        if (!dbus_message_iter_append_basic(i, DBUS_TYPE_UINT64, &k))
+                return -ENOMEM;
+
+        return 0;
+}
+
+static int get_user_for_path(Manager *m, const char *path, User **_u) {
+        User *u;
+        unsigned long lu;
+        int r;
+
+        assert(m);
+        assert(path);
+        assert(_u);
+
+        if (!startswith(path, "/org/freedesktop/login1/user/"))
+                return -EINVAL;
+
+        r = safe_atolu(path + 29, &lu);
+        if (r < 0)
+                return r;
+
+        u = hashmap_get(m->users, ULONG_TO_PTR(lu));
+        if (!u)
+                return -ENOENT;
+
+        *_u = u;
+        return 0;
+}
+
+static const BusProperty bus_login_user_properties[] = {
+        { "UID",                    bus_property_append_uid,         "u", offsetof(User, uid)                 },
+        { "GID",                    bus_property_append_gid,         "u", offsetof(User, gid)                 },
+        { "Name",                   bus_property_append_string,      "s", offsetof(User, name),               true },
+        { "Timestamp",              bus_property_append_usec,        "t", offsetof(User, timestamp.realtime)  },
+        { "TimestampMonotonic",     bus_property_append_usec,        "t", offsetof(User, timestamp.monotonic) },
+        { "RuntimePath",            bus_property_append_string,      "s", offsetof(User, runtime_path),       true },
+        { "ControlGroupPath",       bus_property_append_string,      "s", offsetof(User, cgroup_path),        true },
+        { "Service",                bus_property_append_string,      "s", offsetof(User, service),            true },
+        { "Display",                bus_user_append_display,      "(so)", 0 },
+        { "State",                  bus_user_append_state,           "s", 0 },
+        { "Sessions",               bus_user_append_sessions,    "a(so)", 0 },
+        { "IdleHint",               bus_user_append_idle_hint,       "b", 0 },
+        { "IdleSinceHint",          bus_user_append_idle_hint_since, "t", 0 },
+        { "IdleSinceHintMonotonic", bus_user_append_idle_hint_since, "t", 0 },
+        { NULL, }
+};
+
+static DBusHandlerResult user_message_dispatch(
+                User *u,
+                DBusConnection *connection,
+                DBusMessage *message) {
+
+        DBusError error;
+        DBusMessage *reply = NULL;
+        int r;
+
+        assert(u);
+        assert(connection);
+        assert(message);
+
+        if (dbus_message_is_method_call(message, "org.freedesktop.login1.User", "Terminate")) {
+
+                r = user_stop(u);
+                if (r < 0)
+                        return bus_send_error_reply(connection, message, NULL, r);
+
+                reply = dbus_message_new_method_return(message);
+                if (!reply)
+                        goto oom;
+        } else if (dbus_message_is_method_call(message, "org.freedesktop.login1.User", "Kill")) {
+                int32_t signo;
+
+                if (!dbus_message_get_args(
+                                    message,
+                                    &error,
+                                    DBUS_TYPE_INT32, &signo,
+                                    DBUS_TYPE_INVALID))
+                        return bus_send_error_reply(connection, message, &error, -EINVAL);
+
+                if (signo <= 0 || signo >= _NSIG)
+                        return bus_send_error_reply(connection, message, &error, -EINVAL);
+
+                r = user_kill(u, signo);
+                if (r < 0)
+                        return bus_send_error_reply(connection, message, NULL, r);
+
+                reply = dbus_message_new_method_return(message);
+                if (!reply)
+                        goto oom;
+
+        } else {
+                const BusBoundProperties bps[] = {
+                        { "org.freedesktop.login1.User", bus_login_user_properties, u },
+                        { 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);
+
+        dbus_error_free(&error);
+
+        return DBUS_HANDLER_RESULT_NEED_MEMORY;
+}
+
+static DBusHandlerResult user_message_handler(
+                DBusConnection *connection,
+                DBusMessage *message,
+                void *userdata) {
+
+        Manager *m = userdata;
+        User *u;
+        int r;
+
+        r = get_user_for_path(m, dbus_message_get_path(message), &u);
+        if (r < 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 user");
+                        return bus_send_error_reply(connection, message, &e, r);
+                }
+
+                return bus_send_error_reply(connection, message, NULL, r);
+        }
+
+        return user_message_dispatch(u, connection, message);
+}
+
+const DBusObjectPathVTable bus_user_vtable = {
+        .message_function = user_message_handler
+};
+
+char *user_bus_path(User *u) {
+        char *s;
+
+        assert(u);
+
+        if (asprintf(&s, "/org/freedesktop/login1/user/%llu", (unsigned long long) u->uid) < 0)
+                return NULL;
+
+        return s;
+}
+
+int user_send_signal(User *u, bool new_user) {
+        DBusMessage *m;
+        int r = -ENOMEM;
+        char *p = NULL;
+        uint32_t uid;
+
+        assert(u);
+
+        m = dbus_message_new_signal("/org/freedesktop/login1",
+                                    "org.freedesktop.login1.Manager",
+                                    new_user ? "UserNew" : "UserRemoved");
+
+        if (!m)
+                return -ENOMEM;
+
+        p = user_bus_path(u);
+        if (!p)
+                goto finish;
+
+        uid = u->uid;
+
+        if (!dbus_message_append_args(
+                            m,
+                            DBUS_TYPE_UINT32, &uid,
+                            DBUS_TYPE_OBJECT_PATH, &p,
+                            DBUS_TYPE_INVALID))
+                goto finish;
+
+        if (!dbus_connection_send(u->manager->bus, m, NULL))
+                goto finish;
+
+        r = 0;
+
+finish:
+        dbus_message_unref(m);
+        free(p);
+
+        return r;
+}
+
+int user_send_changed(User *u, const char *properties) {
+        DBusMessage *m;
+        int r = -ENOMEM;
+        char *p = NULL;
+
+        assert(u);
+
+        if (!u->started)
+                return 0;
+
+        p = user_bus_path(u);
+        if (!p)
+                return -ENOMEM;
+
+        m = bus_properties_changed_new(p, "org.freedesktop.login1.User", properties);
+        if (!m)
+                goto finish;
+
+        if (!dbus_connection_send(u->manager->bus, m, NULL))
+                goto finish;
+
+        r = 0;
+
+finish:
+        if (m)
+                dbus_message_unref(m);
+        free(p);
+
+        return r;
+}
diff --git a/src/login/logind-user.c b/src/login/logind-user.c
new file mode 100644 (file)
index 0000000..717f0e2
--- /dev/null
@@ -0,0 +1,589 @@
+/*-*- 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 <http://www.gnu.org/licenses/>.
+***/
+
+#include <string.h>
+#include <unistd.h>
+#include <errno.h>
+
+#include "logind-user.h"
+#include "util.h"
+#include "cgroup-util.h"
+#include "hashmap.h"
+#include "strv.h"
+
+User* user_new(Manager *m, uid_t uid, gid_t gid, const char *name) {
+        User *u;
+
+        assert(m);
+        assert(name);
+
+        u = new0(User, 1);
+        if (!u)
+                return NULL;
+
+        u->name = strdup(name);
+        if (!u->name) {
+                free(u);
+                return NULL;
+        }
+
+        if (asprintf(&u->state_file, "/run/systemd/users/%lu", (unsigned long) uid) < 0) {
+                free(u->name);
+                free(u);
+                return NULL;
+        }
+
+        if (hashmap_put(m->users, ULONG_TO_PTR((unsigned long) uid), u) < 0) {
+                free(u->state_file);
+                free(u->name);
+                free(u);
+                return NULL;
+        }
+
+        u->manager = m;
+        u->uid = uid;
+        u->gid = gid;
+
+        return u;
+}
+
+void user_free(User *u) {
+        assert(u);
+
+        if (u->in_gc_queue)
+                LIST_REMOVE(User, gc_queue, u->manager->user_gc_queue, u);
+
+        while (u->sessions)
+                session_free(u->sessions);
+
+        free(u->cgroup_path);
+
+        free(u->service);
+        free(u->runtime_path);
+
+        hashmap_remove(u->manager->users, ULONG_TO_PTR((unsigned long) u->uid));
+
+        free(u->name);
+        free(u->state_file);
+        free(u);
+}
+
+int user_save(User *u) {
+        FILE *f;
+        int r;
+        char *temp_path;
+
+        assert(u);
+        assert(u->state_file);
+
+        if (!u->started)
+                return 0;
+
+        r = safe_mkdir("/run/systemd/users", 0755, 0, 0);
+        if (r < 0)
+                goto finish;
+
+        r = fopen_temporary(u->state_file, &f, &temp_path);
+        if (r < 0)
+                goto finish;
+
+        fchmod(fileno(f), 0644);
+
+        fprintf(f,
+                "# This is private data. Do not parse.\n"
+                "NAME=%s\n"
+                "STATE=%s\n",
+                u->name,
+                user_state_to_string(user_get_state(u)));
+
+        if (u->cgroup_path)
+                fprintf(f,
+                        "CGROUP=%s\n",
+                        u->cgroup_path);
+
+        if (u->runtime_path)
+                fprintf(f,
+                        "RUNTIME=%s\n",
+                        u->runtime_path);
+
+        if (u->service)
+                fprintf(f,
+                        "SERVICE=%s\n",
+                        u->service);
+
+        if (u->display)
+                fprintf(f,
+                        "DISPLAY=%s\n",
+                        u->display->id);
+
+        if (u->sessions) {
+                Session *i;
+
+                fputs("SESSIONS=", f);
+                LIST_FOREACH(sessions_by_user, i, u->sessions) {
+                        fprintf(f,
+                                "%s%c",
+                                i->id,
+                                i->sessions_by_user_next ? ' ' : '\n');
+                }
+
+                fputs("SEATS=", f);
+                LIST_FOREACH(sessions_by_user, i, u->sessions) {
+                        if (i->seat)
+                                fprintf(f,
+                                        "%s%c",
+                                        i->seat->id,
+                                        i->sessions_by_user_next ? ' ' : '\n');
+                }
+
+                fputs("ACTIVE_SESSIONS=", f);
+                LIST_FOREACH(sessions_by_user, i, u->sessions)
+                        if (session_is_active(i))
+                                fprintf(f,
+                                        "%lu%c",
+                                        (unsigned long) i->user->uid,
+                                        i->sessions_by_user_next ? ' ' : '\n');
+
+                fputs("ACTIVE_SEATS=", f);
+                LIST_FOREACH(sessions_by_user, i, u->sessions) {
+                        if (session_is_active(i) && i->seat)
+                                fprintf(f,
+                                        "%s%c",
+                                        i->seat->id,
+                                        i->sessions_by_user_next ? ' ' : '\n');
+                }
+        }
+
+        fflush(f);
+
+        if (ferror(f) || rename(temp_path, u->state_file) < 0) {
+                r = -errno;
+                unlink(u->state_file);
+                unlink(temp_path);
+        }
+
+        fclose(f);
+        free(temp_path);
+
+finish:
+        if (r < 0)
+                log_error("Failed to save user data for %s: %s", u->name, strerror(-r));
+
+        return r;
+}
+
+int user_load(User *u) {
+        int r;
+        char *display = NULL;
+        Session *s = NULL;
+
+        assert(u);
+
+        r = parse_env_file(u->state_file, NEWLINE,
+                           "CGROUP", &u->cgroup_path,
+                           "RUNTIME", &u->runtime_path,
+                           "SERVICE", &u->service,
+                           "DISPLAY", &display,
+                           NULL);
+        if (r < 0) {
+                free(display);
+
+                if (r == -ENOENT)
+                        return 0;
+
+                log_error("Failed to read %s: %s", u->state_file, strerror(-r));
+                return r;
+        }
+
+        if (display) {
+                s = hashmap_get(u->manager->sessions, display);
+                free(display);
+        }
+
+        if (s && s->display && display_is_local(s->display))
+                u->display = s;
+
+        return r;
+}
+
+static int user_mkdir_runtime_path(User *u) {
+        char *p;
+        int r;
+
+        assert(u);
+
+        r = safe_mkdir("/run/user", 0755, 0, 0);
+        if (r < 0) {
+                log_error("Failed to create /run/user: %s", strerror(-r));
+                return r;
+        }
+
+        if (!u->runtime_path) {
+                p = strappend("/run/user/", u->name);
+
+                if (!p) {
+                        log_error("Out of memory");
+                        return -ENOMEM;
+                }
+        } else
+                p = u->runtime_path;
+
+        r = safe_mkdir(p, 0700, u->uid, u->gid);
+        if (r < 0) {
+                log_error("Failed to create runtime directory %s: %s", p, strerror(-r));
+                free(p);
+                u->runtime_path = NULL;
+                return r;
+        }
+
+        u->runtime_path = p;
+        return 0;
+}
+
+static int user_create_cgroup(User *u) {
+        char **k;
+        char *p;
+        int r;
+
+        assert(u);
+
+        if (!u->cgroup_path) {
+                if (asprintf(&p, "%s/%s", u->manager->cgroup_path, u->name) < 0) {
+                        log_error("Out of memory");
+                        return -ENOMEM;
+                }
+        } else
+                p = u->cgroup_path;
+
+        r = cg_create(SYSTEMD_CGROUP_CONTROLLER, p);
+        if (r < 0) {
+                log_error("Failed to create cgroup "SYSTEMD_CGROUP_CONTROLLER":%s: %s", p, strerror(-r));
+                free(p);
+                u->cgroup_path = NULL;
+                return r;
+        }
+
+        u->cgroup_path = p;
+
+        STRV_FOREACH(k, u->manager->controllers) {
+
+                if (strv_contains(u->manager->reset_controllers, *k))
+                        continue;
+
+                r = cg_create(*k, p);
+                if (r < 0)
+                        log_warning("Failed to create cgroup %s:%s: %s", *k, p, strerror(-r));
+        }
+
+        return 0;
+}
+
+static int user_start_service(User *u) {
+        assert(u);
+
+        /* FIXME: Fill me in later ... */
+
+        return 0;
+}
+
+int user_start(User *u) {
+        int r;
+
+        assert(u);
+
+        if (u->started)
+                return 0;
+
+        log_debug("New user %s logged in.", u->name);
+
+        /* Make XDG_RUNTIME_DIR */
+        r = user_mkdir_runtime_path(u);
+        if (r < 0)
+                return r;
+
+        /* Create cgroup */
+        r = user_create_cgroup(u);
+        if (r < 0)
+                return r;
+
+        /* Spawn user systemd */
+        r = user_start_service(u);
+        if (r < 0)
+                return r;
+
+        dual_timestamp_get(&u->timestamp);
+
+        u->started = true;
+
+        /* Save new user data */
+        user_save(u);
+
+        user_send_signal(u, true);
+
+        return 0;
+}
+
+static int user_stop_service(User *u) {
+        assert(u);
+
+        if (!u->service)
+                return 0;
+
+        return 0;
+}
+
+static int user_shall_kill(User *u) {
+        assert(u);
+
+        if (!u->manager->kill_user_processes)
+                return false;
+
+        if (strv_contains(u->manager->kill_exclude_users, u->name))
+                return false;
+
+        if (strv_isempty(u->manager->kill_only_users))
+                return true;
+
+        return strv_contains(u->manager->kill_only_users, u->name);
+}
+
+static int user_terminate_cgroup(User *u) {
+        int r;
+        char **k;
+
+        assert(u);
+
+        if (!u->cgroup_path)
+                return 0;
+
+        cg_trim(SYSTEMD_CGROUP_CONTROLLER, u->cgroup_path, false);
+
+        if (user_shall_kill(u)) {
+
+                r = cg_kill_recursive_and_wait(SYSTEMD_CGROUP_CONTROLLER, u->cgroup_path, true);
+                if (r < 0)
+                        log_error("Failed to kill user cgroup: %s", strerror(-r));
+        } else {
+
+                r = cg_is_empty_recursive(SYSTEMD_CGROUP_CONTROLLER, u->cgroup_path, true);
+                if (r < 0)
+                        log_error("Failed to check user cgroup: %s", strerror(-r));
+                else if (r > 0) {
+                        r = cg_delete(SYSTEMD_CGROUP_CONTROLLER, u->cgroup_path);
+                        if (r < 0)
+                                log_error("Failed to delete user cgroup: %s", strerror(-r));
+                } else
+                        r = -EBUSY;
+        }
+
+        STRV_FOREACH(k, u->manager->controllers)
+                cg_trim(*k, u->cgroup_path, true);
+
+        free(u->cgroup_path);
+        u->cgroup_path = NULL;
+
+        return r;
+}
+
+static int user_remove_runtime_path(User *u) {
+        int r;
+
+        assert(u);
+
+        if (!u->runtime_path)
+                return 0;
+
+        r = rm_rf(u->runtime_path, false, true, false);
+        if (r < 0)
+                log_error("Failed to remove runtime directory %s: %s", u->runtime_path, strerror(-r));
+
+        free(u->runtime_path);
+        u->runtime_path = NULL;
+
+        return r;
+}
+
+int user_stop(User *u) {
+        Session *s;
+        int r = 0, k;
+        assert(u);
+
+        if (u->started)
+                log_debug("User %s logged out.", u->name);
+
+        LIST_FOREACH(sessions_by_user, s, u->sessions) {
+                k = session_stop(s);
+                if (k < 0)
+                        r = k;
+        }
+
+        /* Kill systemd */
+        k = user_stop_service(u);
+        if (k < 0)
+                r = k;
+
+        /* Kill cgroup */
+        k = user_terminate_cgroup(u);
+        if (k < 0)
+                r = k;
+
+        /* Kill XDG_RUNTIME_DIR */
+        k = user_remove_runtime_path(u);
+        if (k < 0)
+                r = k;
+
+        unlink(u->state_file);
+        user_add_to_gc_queue(u);
+
+        if (u->started)
+                user_send_signal(u, false);
+
+        u->started = false;
+
+        return r;
+}
+
+int user_get_idle_hint(User *u, dual_timestamp *t) {
+        Session *s;
+        bool idle_hint = true;
+        dual_timestamp ts = { 0, 0 };
+
+        assert(u);
+
+        LIST_FOREACH(sessions_by_user, s, u->sessions) {
+                dual_timestamp k;
+                int ih;
+
+                ih = session_get_idle_hint(s, &k);
+                if (ih < 0)
+                        return ih;
+
+                if (!ih) {
+                        if (!idle_hint) {
+                                if (k.monotonic < ts.monotonic)
+                                        ts = k;
+                        } else {
+                                idle_hint = false;
+                                ts = k;
+                        }
+                } else if (idle_hint) {
+
+                        if (k.monotonic > ts.monotonic)
+                                ts = k;
+                }
+        }
+
+        if (t)
+                *t = ts;
+
+        return idle_hint;
+}
+
+int user_check_gc(User *u, bool drop_not_started) {
+        int r;
+        char *p;
+
+        assert(u);
+
+        if (drop_not_started && !u->started)
+                return 0;
+
+        if (u->sessions)
+                return 1;
+
+        if (asprintf(&p, "/var/lib/systemd/linger/%s", u->name) < 0)
+                return -ENOMEM;
+
+        r = access(p, F_OK) >= 0;
+        free(p);
+
+        if (r > 0)
+                return 1;
+
+        if (u->cgroup_path) {
+                r = cg_is_empty_recursive(SYSTEMD_CGROUP_CONTROLLER, u->cgroup_path, false);
+                if (r < 0)
+                        return r;
+
+                if (r <= 0)
+                        return 1;
+        }
+
+        return 0;
+}
+
+void user_add_to_gc_queue(User *u) {
+        assert(u);
+
+        if (u->in_gc_queue)
+                return;
+
+        LIST_PREPEND(User, gc_queue, u->manager->user_gc_queue, u);
+        u->in_gc_queue = true;
+}
+
+UserState user_get_state(User *u) {
+        Session *i;
+
+        assert(u);
+
+        if (!u->sessions)
+                return USER_LINGERING;
+
+        LIST_FOREACH(sessions_by_user, i, u->sessions)
+                if (session_is_active(i))
+                        return USER_ACTIVE;
+
+        return USER_ONLINE;
+}
+
+int user_kill(User *u, int signo) {
+        int r = 0, q;
+        Set *pid_set = NULL;
+
+        assert(u);
+
+        if (!u->cgroup_path)
+                return -ESRCH;
+
+        pid_set = set_new(trivial_hash_func, trivial_compare_func);
+        if (!pid_set)
+                return -ENOMEM;
+
+        q = cg_kill_recursive(SYSTEMD_CGROUP_CONTROLLER, u->cgroup_path, signo, false, true, false, pid_set);
+        if (q < 0)
+                if (q != -EAGAIN && q != -ESRCH && q != -ENOENT)
+                        r = q;
+
+        if (pid_set)
+                set_free(pid_set);
+
+        return r;
+}
+
+static const char* const user_state_table[_USER_STATE_MAX] = {
+        [USER_OFFLINE] = "offline",
+        [USER_LINGERING] = "lingering",
+        [USER_ONLINE] = "online",
+        [USER_ACTIVE] = "active"
+};
+
+DEFINE_STRING_TABLE_LOOKUP(user_state, UserState);
diff --git a/src/login/logind-user.h b/src/login/logind-user.h
new file mode 100644 (file)
index 0000000..db9a5f6
--- /dev/null
@@ -0,0 +1,86 @@
+/*-*- Mode: C; c-basic-offset: 8; indent-tabs-mode: nil -*-*/
+
+#ifndef foologinduserhfoo
+#define foologinduserhfoo
+
+/***
+  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 <http://www.gnu.org/licenses/>.
+***/
+
+typedef struct User User;
+
+#include "list.h"
+#include "util.h"
+#include "logind.h"
+#include "logind-session.h"
+
+typedef enum UserState {
+        USER_OFFLINE,
+        USER_LINGERING,
+        USER_ONLINE,
+        USER_ACTIVE,
+        _USER_STATE_MAX,
+        _USER_STATE_INVALID = -1
+} UserState;
+
+struct User {
+        Manager *manager;
+
+        uid_t uid;
+        gid_t gid;
+        char *name;
+
+        char *state_file;
+        char *runtime_path;
+        char *service;
+        char *cgroup_path;
+
+        Session *display;
+
+        dual_timestamp timestamp;
+
+        bool in_gc_queue:1;
+        bool started:1;
+
+        LIST_HEAD(Session, sessions);
+        LIST_FIELDS(User, gc_queue);
+};
+
+User* user_new(Manager *m, uid_t uid, gid_t gid, const char *name);
+void user_free(User *u);
+int user_check_gc(User *u, bool drop_not_started);
+void user_add_to_gc_queue(User *u);
+int user_start(User *u);
+int user_stop(User *u);
+UserState user_get_state(User *u);
+int user_get_idle_hint(User *u, dual_timestamp *t);
+int user_save(User *u);
+int user_load(User *u);
+int user_kill(User *u, int signo);
+
+char *user_bus_path(User *s);
+
+extern const DBusObjectPathVTable bus_user_vtable;
+
+int user_send_signal(User *u, bool new_user);
+int user_send_changed(User *u, const char *properties);
+
+const char* user_state_to_string(UserState s);
+UserState user_state_from_string(const char *s);
+
+#endif
diff --git a/src/login/logind.c b/src/login/logind.c
new file mode 100644 (file)
index 0000000..a54195c
--- /dev/null
@@ -0,0 +1,1288 @@
+/*-*- 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 <http://www.gnu.org/licenses/>.
+***/
+
+#include <errno.h>
+#include <pwd.h>
+#include <libudev.h>
+#include <fcntl.h>
+#include <string.h>
+#include <unistd.h>
+#include <sys/epoll.h>
+#include <sys/ioctl.h>
+#include <linux/vt.h>
+
+#include <systemd/sd-daemon.h>
+
+#include "logind.h"
+#include "dbus-common.h"
+#include "dbus-loop.h"
+#include "strv.h"
+#include "conf-parser.h"
+
+Manager *manager_new(void) {
+        Manager *m;
+
+        m = new0(Manager, 1);
+        if (!m)
+                return NULL;
+
+        m->console_active_fd = -1;
+        m->bus_fd = -1;
+        m->udev_seat_fd = -1;
+        m->udev_vcsa_fd = -1;
+        m->epoll_fd = -1;
+        m->n_autovts = 6;
+
+        m->devices = hashmap_new(string_hash_func, string_compare_func);
+        m->seats = hashmap_new(string_hash_func, string_compare_func);
+        m->sessions = hashmap_new(string_hash_func, string_compare_func);
+        m->users = hashmap_new(trivial_hash_func, trivial_compare_func);
+        m->cgroups = hashmap_new(string_hash_func, string_compare_func);
+        m->fifo_fds = hashmap_new(trivial_hash_func, trivial_compare_func);
+
+        if (!m->devices || !m->seats || !m->sessions || !m->users || !m->cgroups || !m->fifo_fds) {
+                manager_free(m);
+                return NULL;
+        }
+
+        m->reset_controllers = strv_new("cpu", NULL);
+        m->kill_exclude_users = strv_new("root", NULL);
+        if (!m->reset_controllers || !m->kill_exclude_users) {
+                manager_free(m);
+                return NULL;
+        }
+
+        m->udev = udev_new();
+        if (!m->udev) {
+                manager_free(m);
+                return NULL;
+        }
+
+        if (cg_get_user_path(&m->cgroup_path) < 0) {
+                manager_free(m);
+                return NULL;
+        }
+
+        return m;
+}
+
+void manager_free(Manager *m) {
+        Session *session;
+        User *u;
+        Device *d;
+        Seat *s;
+
+        assert(m);
+
+        while ((session = hashmap_first(m->sessions)))
+                session_free(session);
+
+        while ((u = hashmap_first(m->users)))
+                user_free(u);
+
+        while ((d = hashmap_first(m->devices)))
+                device_free(d);
+
+        while ((s = hashmap_first(m->seats)))
+                seat_free(s);
+
+        hashmap_free(m->sessions);
+        hashmap_free(m->users);
+        hashmap_free(m->devices);
+        hashmap_free(m->seats);
+        hashmap_free(m->cgroups);
+        hashmap_free(m->fifo_fds);
+
+        if (m->console_active_fd >= 0)
+                close_nointr_nofail(m->console_active_fd);
+
+        if (m->udev_seat_monitor)
+                udev_monitor_unref(m->udev_seat_monitor);
+
+        if (m->udev_vcsa_monitor)
+                udev_monitor_unref(m->udev_vcsa_monitor);
+
+        if (m->udev)
+                udev_unref(m->udev);
+
+        if (m->bus) {
+                dbus_connection_flush(m->bus);
+                dbus_connection_close(m->bus);
+                dbus_connection_unref(m->bus);
+        }
+
+        if (m->bus_fd >= 0)
+                close_nointr_nofail(m->bus_fd);
+
+        if (m->epoll_fd >= 0)
+                close_nointr_nofail(m->epoll_fd);
+
+        strv_free(m->controllers);
+        strv_free(m->reset_controllers);
+        strv_free(m->kill_only_users);
+        strv_free(m->kill_exclude_users);
+
+        free(m->cgroup_path);
+        free(m);
+}
+
+int manager_add_device(Manager *m, const char *sysfs, Device **_device) {
+        Device *d;
+
+        assert(m);
+        assert(sysfs);
+
+        d = hashmap_get(m->devices, sysfs);
+        if (d) {
+                if (_device)
+                        *_device = d;
+
+                return 0;
+        }
+
+        d = device_new(m, sysfs);
+        if (!d)
+                return -ENOMEM;
+
+        if (_device)
+                *_device = d;
+
+        return 0;
+}
+
+int manager_add_seat(Manager *m, const char *id, Seat **_seat) {
+        Seat *s;
+
+        assert(m);
+        assert(id);
+
+        s = hashmap_get(m->seats, id);
+        if (s) {
+                if (_seat)
+                        *_seat = s;
+
+                return 0;
+        }
+
+        s = seat_new(m, id);
+        if (!s)
+                return -ENOMEM;
+
+        if (_seat)
+                *_seat = s;
+
+        return 0;
+}
+
+int manager_add_session(Manager *m, User *u, const char *id, Session **_session) {
+        Session *s;
+
+        assert(m);
+        assert(id);
+
+        s = hashmap_get(m->sessions, id);
+        if (s) {
+                if (_session)
+                        *_session = s;
+
+                return 0;
+        }
+
+        s = session_new(m, u, id);
+        if (!s)
+                return -ENOMEM;
+
+        if (_session)
+                *_session = s;
+
+        return 0;
+}
+
+int manager_add_user(Manager *m, uid_t uid, gid_t gid, const char *name, User **_user) {
+        User *u;
+
+        assert(m);
+        assert(name);
+
+        u = hashmap_get(m->users, ULONG_TO_PTR((unsigned long) uid));
+        if (u) {
+                if (_user)
+                        *_user = u;
+
+                return 0;
+        }
+
+        u = user_new(m, uid, gid, name);
+        if (!u)
+                return -ENOMEM;
+
+        if (_user)
+                *_user = u;
+
+        return 0;
+}
+
+int manager_add_user_by_name(Manager *m, const char *name, User **_user) {
+        uid_t uid;
+        gid_t gid;
+        int r;
+
+        assert(m);
+        assert(name);
+
+        r = get_user_creds(&name, &uid, &gid, NULL);
+        if (r < 0)
+                return r;
+
+        return manager_add_user(m, uid, gid, name, _user);
+}
+
+int manager_add_user_by_uid(Manager *m, uid_t uid, User **_user) {
+        struct passwd *p;
+
+        assert(m);
+
+        errno = 0;
+        p = getpwuid(uid);
+        if (!p)
+                return errno ? -errno : -ENOENT;
+
+        return manager_add_user(m, uid, p->pw_gid, p->pw_name, _user);
+}
+
+int manager_process_seat_device(Manager *m, struct udev_device *d) {
+        Device *device;
+        int r;
+
+        assert(m);
+
+        if (streq_ptr(udev_device_get_action(d), "remove")) {
+
+                device = hashmap_get(m->devices, udev_device_get_syspath(d));
+                if (!device)
+                        return 0;
+
+                seat_add_to_gc_queue(device->seat);
+                device_free(device);
+
+        } else {
+                const char *sn;
+                Seat *seat;
+
+                sn = udev_device_get_property_value(d, "ID_SEAT");
+                if (isempty(sn))
+                        sn = "seat0";
+
+                if (!seat_name_is_valid(sn)) {
+                        log_warning("Device with invalid seat name %s found, ignoring.", sn);
+                        return 0;
+                }
+
+                r = manager_add_device(m, udev_device_get_syspath(d), &device);
+                if (r < 0)
+                        return r;
+
+                r = manager_add_seat(m, sn, &seat);
+                if (r < 0) {
+                        if (!device->seat)
+                                device_free(device);
+
+                        return r;
+                }
+
+                device_attach(device, seat);
+                seat_start(seat);
+        }
+
+        return 0;
+}
+
+int manager_enumerate_devices(Manager *m) {
+        struct udev_list_entry *item = NULL, *first = NULL;
+        struct udev_enumerate *e;
+        int r;
+
+        assert(m);
+
+        /* Loads devices from udev and creates seats for them as
+         * necessary */
+
+        e = udev_enumerate_new(m->udev);
+        if (!e) {
+                r = -ENOMEM;
+                goto finish;
+        }
+
+        r = udev_enumerate_add_match_subsystem(e, "graphics");
+        if (r < 0)
+                goto finish;
+
+        r = udev_enumerate_add_match_tag(e, "seat");
+        if (r < 0)
+                goto finish;
+
+        r = udev_enumerate_scan_devices(e);
+        if (r < 0)
+                goto finish;
+
+        first = udev_enumerate_get_list_entry(e);
+        udev_list_entry_foreach(item, first) {
+                struct udev_device *d;
+                int k;
+
+                d = udev_device_new_from_syspath(m->udev, udev_list_entry_get_name(item));
+                if (!d) {
+                        r = -ENOMEM;
+                        goto finish;
+                }
+
+                k = manager_process_seat_device(m, d);
+                udev_device_unref(d);
+
+                if (k < 0)
+                        r = k;
+        }
+
+finish:
+        if (e)
+                udev_enumerate_unref(e);
+
+        return r;
+}
+
+int manager_enumerate_seats(Manager *m) {
+        DIR *d;
+        struct dirent *de;
+        int r = 0;
+
+        assert(m);
+
+        /* This loads data about seats stored on disk, but does not
+         * actually create any seats. Removes data of seats that no
+         * longer exist. */
+
+        d = opendir("/run/systemd/seats");
+        if (!d) {
+                if (errno == ENOENT)
+                        return 0;
+
+                log_error("Failed to open /run/systemd/seats: %m");
+                return -errno;
+        }
+
+        while ((de = readdir(d))) {
+                Seat *s;
+                int k;
+
+                if (!dirent_is_file(de))
+                        continue;
+
+                s = hashmap_get(m->seats, de->d_name);
+                if (!s) {
+                        unlinkat(dirfd(d), de->d_name, 0);
+                        continue;
+                }
+
+                k = seat_load(s);
+                if (k < 0)
+                        r = k;
+        }
+
+        closedir(d);
+
+        return r;
+}
+
+static int manager_enumerate_users_from_cgroup(Manager *m) {
+        int r = 0;
+        char *name;
+        DIR *d;
+        int k;
+
+        r = cg_enumerate_subgroups(SYSTEMD_CGROUP_CONTROLLER, m->cgroup_path, &d);
+        if (r < 0) {
+                if (r == -ENOENT)
+                        return 0;
+
+                log_error("Failed to open %s: %s", m->cgroup_path, strerror(-r));
+                return r;
+        }
+
+        while ((k = cg_read_subgroup(d, &name)) > 0) {
+                User *user;
+
+                k = manager_add_user_by_name(m, name, &user);
+                if (k < 0) {
+                        free(name);
+                        r = k;
+                        continue;
+                }
+
+                user_add_to_gc_queue(user);
+
+                if (!user->cgroup_path)
+                        if (asprintf(&user->cgroup_path, "%s/%s", m->cgroup_path, name) < 0) {
+                                r = -ENOMEM;
+                                free(name);
+                                break;
+                        }
+
+                free(name);
+        }
+
+        if (r >= 0 && k < 0)
+                r = k;
+
+        closedir(d);
+
+        return r;
+}
+
+static int manager_enumerate_linger_users(Manager *m) {
+        DIR *d;
+        struct dirent *de;
+        int r = 0;
+
+        d = opendir("/var/lib/systemd/linger");
+        if (!d) {
+                if (errno == ENOENT)
+                        return 0;
+
+                log_error("Failed to open /var/lib/systemd/linger/: %m");
+                return -errno;
+        }
+
+        while ((de = readdir(d))) {
+                int k;
+
+                if (!dirent_is_file(de))
+                        continue;
+
+                k = manager_add_user_by_name(m, de->d_name, NULL);
+                if (k < 0) {
+                        log_notice("Couldn't add lingering user %s: %s", de->d_name, strerror(-k));
+                        r = k;
+                }
+        }
+
+        closedir(d);
+
+        return r;
+}
+
+int manager_enumerate_users(Manager *m) {
+        DIR *d;
+        struct dirent *de;
+        int r, k;
+
+        assert(m);
+
+        /* First, enumerate user cgroups */
+        r = manager_enumerate_users_from_cgroup(m);
+
+        /* Second, add lingering users on top */
+        k = manager_enumerate_linger_users(m);
+        if (k < 0)
+                r = k;
+
+        /* Third, read in user data stored on disk */
+        d = opendir("/run/systemd/users");
+        if (!d) {
+                if (errno == ENOENT)
+                        return 0;
+
+                log_error("Failed to open /run/systemd/users: %m");
+                return -errno;
+        }
+
+        while ((de = readdir(d))) {
+                uid_t uid;
+                User *u;
+
+                if (!dirent_is_file(de))
+                        continue;
+
+                k = parse_uid(de->d_name, &uid);
+                if (k < 0) {
+                        log_error("Failed to parse file name %s: %s", de->d_name, strerror(-k));
+                        continue;
+                }
+
+                u = hashmap_get(m->users, ULONG_TO_PTR(uid));
+                if (!u) {
+                        unlinkat(dirfd(d), de->d_name, 0);
+                        continue;
+                }
+
+                k = user_load(u);
+                if (k < 0)
+                        r = k;
+        }
+
+        closedir(d);
+
+        return r;
+}
+
+static int manager_enumerate_sessions_from_cgroup(Manager *m) {
+        User *u;
+        Iterator i;
+        int r = 0;
+
+        HASHMAP_FOREACH(u, m->users, i) {
+                DIR *d;
+                char *name;
+                int k;
+
+                if (!u->cgroup_path)
+                        continue;
+
+                k = cg_enumerate_subgroups(SYSTEMD_CGROUP_CONTROLLER, u->cgroup_path, &d);
+                if (k < 0) {
+                        if (k == -ENOENT)
+                                continue;
+
+                        log_error("Failed to open %s: %s", u->cgroup_path, strerror(-k));
+                        r = k;
+                        continue;
+                }
+
+                while ((k = cg_read_subgroup(d, &name)) > 0) {
+                        Session *session;
+
+                        if (streq(name, "shared"))
+                                continue;
+
+                        k = manager_add_session(m, u, name, &session);
+                        if (k < 0) {
+                                free(name);
+                                break;
+                        }
+
+                        session_add_to_gc_queue(session);
+
+                        if (!session->cgroup_path)
+                                if (asprintf(&session->cgroup_path, "%s/%s", u->cgroup_path, name) < 0) {
+                                        k = -ENOMEM;
+                                        free(name);
+                                        break;
+                                }
+
+                        free(name);
+                }
+
+                closedir(d);
+
+                if (k < 0)
+                        r = k;
+        }
+
+        return r;
+}
+
+int manager_enumerate_sessions(Manager *m) {
+        DIR *d;
+        struct dirent *de;
+        int r = 0;
+
+        assert(m);
+
+        /* First enumerate session cgroups */
+        r = manager_enumerate_sessions_from_cgroup(m);
+
+        /* Second, read in session data stored on disk */
+        d = opendir("/run/systemd/sessions");
+        if (!d) {
+                if (errno == ENOENT)
+                        return 0;
+
+                log_error("Failed to open /run/systemd/sessions: %m");
+                return -errno;
+        }
+
+        while ((de = readdir(d))) {
+                struct Session *s;
+                int k;
+
+                if (!dirent_is_file(de))
+                        continue;
+
+                s = hashmap_get(m->sessions, de->d_name);
+                if (!s) {
+                        unlinkat(dirfd(d), de->d_name, 0);
+                        continue;
+                }
+
+                k = session_load(s);
+                if (k < 0)
+                        r = k;
+        }
+
+        closedir(d);
+
+        return r;
+}
+
+int manager_dispatch_seat_udev(Manager *m) {
+        struct udev_device *d;
+        int r;
+
+        assert(m);
+
+        d = udev_monitor_receive_device(m->udev_seat_monitor);
+        if (!d)
+                return -ENOMEM;
+
+        r = manager_process_seat_device(m, d);
+        udev_device_unref(d);
+
+        return r;
+}
+
+int manager_dispatch_vcsa_udev(Manager *m) {
+        struct udev_device *d;
+        int r = 0;
+        const char *name;
+
+        assert(m);
+
+        d = udev_monitor_receive_device(m->udev_vcsa_monitor);
+        if (!d)
+                return -ENOMEM;
+
+        name = udev_device_get_sysname(d);
+
+        /* Whenever a VCSA device is removed try to reallocate our
+         * VTs, to make sure our auto VTs never go away. */
+
+        if (name && startswith(name, "vcsa") && streq_ptr(udev_device_get_action(d), "remove"))
+                r = seat_preallocate_vts(m->vtconsole);
+
+        udev_device_unref(d);
+
+        return r;
+}
+
+int manager_dispatch_console(Manager *m) {
+        assert(m);
+
+        if (m->vtconsole)
+                seat_read_active_vt(m->vtconsole);
+
+        return 0;
+}
+
+static int vt_is_busy(int vtnr) {
+        struct vt_stat vt_stat;
+        int r = 0, fd;
+
+        assert(vtnr >= 1);
+
+        /* We explicitly open /dev/tty1 here instead of /dev/tty0. If
+         * we'd open the latter we'd open the foreground tty which
+         * hence would be unconditionally busy. By opening /dev/tty1
+         * we avoid this. Since tty1 is special and needs to be an
+         * explicitly loaded getty or DM this is safe. */
+
+        fd = open_terminal("/dev/tty1", O_RDWR|O_NOCTTY|O_CLOEXEC);
+        if (fd < 0)
+                return -errno;
+
+        if (ioctl(fd, VT_GETSTATE, &vt_stat) < 0)
+                r = -errno;
+        else
+                r = !!(vt_stat.v_state & (1 << vtnr));
+
+        close_nointr_nofail(fd);
+
+        return r;
+}
+
+int manager_spawn_autovt(Manager *m, int vtnr) {
+        int r;
+        DBusMessage *message = NULL, *reply = NULL;
+        char *name = NULL;
+        const char *mode = "fail";
+        DBusError error;
+
+        assert(m);
+        assert(vtnr >= 1);
+
+        dbus_error_init(&error);
+
+        if ((unsigned) vtnr > m->n_autovts)
+                return 0;
+
+        r = vt_is_busy(vtnr);
+        if (r < 0)
+                return r;
+        else if (r > 0)
+                return -EBUSY;
+
+        message = dbus_message_new_method_call("org.freedesktop.systemd1", "/org/freedesktop/systemd1", "org.freedesktop.systemd1.Manager", "StartUnit");
+        if (!message) {
+                log_error("Could not allocate message.");
+                r = -ENOMEM;
+                goto finish;
+        }
+
+        if (asprintf(&name, "autovt@tty%i.service", vtnr) < 0) {
+                log_error("Could not allocate service name.");
+                r = -ENOMEM;
+                goto finish;
+        }
+
+        if (!dbus_message_append_args(message,
+                                      DBUS_TYPE_STRING, &name,
+                                      DBUS_TYPE_STRING, &mode,
+                                      DBUS_TYPE_INVALID)) {
+                log_error("Could not attach target and flag information to message.");
+                r = -ENOMEM;
+                goto finish;
+        }
+
+        reply = dbus_connection_send_with_reply_and_block(m->bus, message, -1, &error);
+        if (!reply) {
+                log_error("Failed to start unit: %s", bus_error_message(&error));
+                goto finish;
+        }
+
+        r = 0;
+
+finish:
+        free(name);
+
+        if (message)
+                dbus_message_unref(message);
+
+        if (reply)
+                dbus_message_unref(reply);
+
+        dbus_error_free(&error);
+
+        return r;
+}
+
+int manager_get_session_by_cgroup(Manager *m, const char *cgroup, Session **session) {
+        Session *s;
+        char *p;
+
+        assert(m);
+        assert(cgroup);
+        assert(session);
+
+        s = hashmap_get(m->cgroups, cgroup);
+        if (s) {
+                *session = s;
+                return 1;
+        }
+
+        p = strdup(cgroup);
+        if (!p) {
+                log_error("Out of memory.");
+                return -ENOMEM;
+        }
+
+        for (;;) {
+                char *e;
+
+                e = strrchr(p, '/');
+                if (!e || e == p) {
+                        free(p);
+                        *session = NULL;
+                        return 0;
+                }
+
+                *e = 0;
+
+                s = hashmap_get(m->cgroups, p);
+                if (s) {
+                        free(p);
+                        *session = s;
+                        return 1;
+                }
+        }
+}
+
+int manager_get_session_by_pid(Manager *m, pid_t pid, Session **session) {
+        char *p;
+        int r;
+
+        assert(m);
+        assert(pid >= 1);
+        assert(session);
+
+        r = cg_get_by_pid(SYSTEMD_CGROUP_CONTROLLER, pid, &p);
+        if (r < 0)
+                return r;
+
+        r = manager_get_session_by_cgroup(m, p, session);
+        free(p);
+
+        return r;
+}
+
+void manager_cgroup_notify_empty(Manager *m, const char *cgroup) {
+        Session *s;
+        int r;
+
+        r = manager_get_session_by_cgroup(m, cgroup, &s);
+        if (r <= 0)
+                return;
+
+        session_add_to_gc_queue(s);
+}
+
+static void manager_pipe_notify_eof(Manager *m, int fd) {
+        Session *s;
+
+        assert_se(m);
+        assert_se(fd >= 0);
+
+        assert_se(s = hashmap_get(m->fifo_fds, INT_TO_PTR(fd + 1)));
+        assert(s->fifo_fd == fd);
+        session_remove_fifo(s);
+
+        session_stop(s);
+}
+
+static int manager_connect_bus(Manager *m) {
+        DBusError error;
+        int r;
+        struct epoll_event ev;
+
+        assert(m);
+        assert(!m->bus);
+        assert(m->bus_fd < 0);
+
+        dbus_error_init(&error);
+
+        m->bus = dbus_bus_get_private(DBUS_BUS_SYSTEM, &error);
+        if (!m->bus) {
+                log_error("Failed to get system D-Bus connection: %s", bus_error_message(&error));
+                r = -ECONNREFUSED;
+                goto fail;
+        }
+
+        if (!dbus_connection_register_object_path(m->bus, "/org/freedesktop/login1", &bus_manager_vtable, m) ||
+            !dbus_connection_register_fallback(m->bus, "/org/freedesktop/login1/seat", &bus_seat_vtable, m) ||
+            !dbus_connection_register_fallback(m->bus, "/org/freedesktop/login1/session", &bus_session_vtable, m) ||
+            !dbus_connection_register_fallback(m->bus, "/org/freedesktop/login1/user", &bus_user_vtable, m) ||
+            !dbus_connection_add_filter(m->bus, bus_message_filter, m, NULL)) {
+                log_error("Not enough memory");
+                r = -ENOMEM;
+                goto fail;
+        }
+
+        dbus_bus_add_match(m->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));
+                r = -EIO;
+                goto fail;
+        }
+
+        r = dbus_bus_request_name(m->bus, "org.freedesktop.login1", DBUS_NAME_FLAG_DO_NOT_QUEUE, &error);
+        if (dbus_error_is_set(&error)) {
+                log_error("Failed to register name on bus: %s", bus_error_message(&error));
+                r = -EIO;
+                goto fail;
+        }
+
+        if (r != DBUS_REQUEST_NAME_REPLY_PRIMARY_OWNER)  {
+                log_error("Failed to acquire name.");
+                r = -EEXIST;
+                goto fail;
+        }
+
+        m->bus_fd = bus_loop_open(m->bus);
+        if (m->bus_fd < 0) {
+                r = m->bus_fd;
+                goto fail;
+        }
+
+        zero(ev);
+        ev.events = EPOLLIN;
+        ev.data.u32 = FD_BUS;
+
+        if (epoll_ctl(m->epoll_fd, EPOLL_CTL_ADD, m->bus_fd, &ev) < 0)
+                goto fail;
+
+        return 0;
+
+fail:
+        dbus_error_free(&error);
+
+        return r;
+}
+
+static int manager_connect_console(Manager *m) {
+        struct epoll_event ev;
+
+        assert(m);
+        assert(m->console_active_fd < 0);
+
+        m->console_active_fd = open("/sys/class/tty/tty0/active", O_RDONLY|O_NOCTTY|O_CLOEXEC);
+        if (m->console_active_fd < 0) {
+
+                /* On certain architectures (S390 and Xen), /dev/tty0
+                   does not exist, so don't fail if we can't open it.*/
+                if (errno == ENOENT)
+                        return 0;
+
+                log_error("Failed to open /sys/class/tty/tty0/active: %m");
+                return -errno;
+        }
+
+        zero(ev);
+        ev.events = 0;
+        ev.data.u32 = FD_CONSOLE;
+
+        if (epoll_ctl(m->epoll_fd, EPOLL_CTL_ADD, m->console_active_fd, &ev) < 0)
+                return -errno;
+
+        return 0;
+}
+
+static int manager_connect_udev(Manager *m) {
+        struct epoll_event ev;
+        int r;
+
+        assert(m);
+        assert(!m->udev_seat_monitor);
+        assert(!m->udev_vcsa_monitor);
+
+        m->udev_seat_monitor = udev_monitor_new_from_netlink(m->udev, "udev");
+        if (!m->udev_seat_monitor)
+                return -ENOMEM;
+
+        r = udev_monitor_filter_add_match_tag(m->udev_seat_monitor, "seat");
+        if (r < 0)
+                return r;
+
+        r = udev_monitor_filter_add_match_subsystem_devtype(m->udev_seat_monitor, "graphics", NULL);
+        if (r < 0)
+                return r;
+
+        r = udev_monitor_enable_receiving(m->udev_seat_monitor);
+        if (r < 0)
+                return r;
+
+        m->udev_seat_fd = udev_monitor_get_fd(m->udev_seat_monitor);
+
+        zero(ev);
+        ev.events = EPOLLIN;
+        ev.data.u32 = FD_SEAT_UDEV;
+
+        /* Don't bother watching VCSA devices, if nobody cares */
+        if (m->n_autovts <= 0 || m->console_active_fd < 0)
+                return 0;
+
+        if (epoll_ctl(m->epoll_fd, EPOLL_CTL_ADD, m->udev_seat_fd, &ev) < 0)
+                return -errno;
+
+        m->udev_vcsa_monitor = udev_monitor_new_from_netlink(m->udev, "udev");
+        if (!m->udev_vcsa_monitor)
+                return -ENOMEM;
+
+        r = udev_monitor_filter_add_match_subsystem_devtype(m->udev_vcsa_monitor, "vc", NULL);
+        if (r < 0)
+                return r;
+
+        r = udev_monitor_enable_receiving(m->udev_vcsa_monitor);
+        if (r < 0)
+                return r;
+
+        m->udev_vcsa_fd = udev_monitor_get_fd(m->udev_vcsa_monitor);
+
+        zero(ev);
+        ev.events = EPOLLIN;
+        ev.data.u32 = FD_VCSA_UDEV;
+
+        if (epoll_ctl(m->epoll_fd, EPOLL_CTL_ADD, m->udev_vcsa_fd, &ev) < 0)
+                return -errno;
+
+        return 0;
+}
+
+void manager_gc(Manager *m, bool drop_not_started) {
+        Seat *seat;
+        Session *session;
+        User *user;
+
+        assert(m);
+
+        while ((seat = m->seat_gc_queue)) {
+                LIST_REMOVE(Seat, gc_queue, m->seat_gc_queue, seat);
+                seat->in_gc_queue = false;
+
+                if (seat_check_gc(seat, drop_not_started) == 0) {
+                        seat_stop(seat);
+                        seat_free(seat);
+                }
+        }
+
+        while ((session = m->session_gc_queue)) {
+                LIST_REMOVE(Session, gc_queue, m->session_gc_queue, session);
+                session->in_gc_queue = false;
+
+                if (session_check_gc(session, drop_not_started) == 0) {
+                        session_stop(session);
+                        session_free(session);
+                }
+        }
+
+        while ((user = m->user_gc_queue)) {
+                LIST_REMOVE(User, gc_queue, m->user_gc_queue, user);
+                user->in_gc_queue = false;
+
+                if (user_check_gc(user, drop_not_started) == 0) {
+                        user_stop(user);
+                        user_free(user);
+                }
+        }
+}
+
+int manager_get_idle_hint(Manager *m, dual_timestamp *t) {
+        Session *s;
+        bool idle_hint = true;
+        dual_timestamp ts = { 0, 0 };
+        Iterator i;
+
+        assert(m);
+
+        HASHMAP_FOREACH(s, m->sessions, i) {
+                dual_timestamp k;
+                int ih;
+
+                ih = session_get_idle_hint(s, &k);
+                if (ih < 0)
+                        return ih;
+
+                if (!ih) {
+                        if (!idle_hint) {
+                                if (k.monotonic < ts.monotonic)
+                                        ts = k;
+                        } else {
+                                idle_hint = false;
+                                ts = k;
+                        }
+                } else if (idle_hint) {
+
+                        if (k.monotonic > ts.monotonic)
+                                ts = k;
+                }
+        }
+
+        if (t)
+                *t = ts;
+
+        return idle_hint;
+}
+
+int manager_startup(Manager *m) {
+        int r;
+        Seat *seat;
+        Session *session;
+        User *user;
+        Iterator i;
+
+        assert(m);
+        assert(m->epoll_fd <= 0);
+
+        m->epoll_fd = epoll_create1(EPOLL_CLOEXEC);
+        if (m->epoll_fd < 0)
+                return -errno;
+
+        /* Connect to console */
+        r = manager_connect_console(m);
+        if (r < 0)
+                return r;
+
+        /* Connect to udev */
+        r = manager_connect_udev(m);
+        if (r < 0)
+                return r;
+
+        /* Connect to the bus */
+        r = manager_connect_bus(m);
+        if (r < 0)
+                return r;
+
+        /* Instantiate magic seat 0 */
+        r = manager_add_seat(m, "seat0", &m->vtconsole);
+        if (r < 0)
+                return r;
+
+        /* Deserialize state */
+        manager_enumerate_devices(m);
+        manager_enumerate_seats(m);
+        manager_enumerate_users(m);
+        manager_enumerate_sessions(m);
+
+        /* Remove stale objects before we start them */
+        manager_gc(m, false);
+
+        /* And start everything */
+        HASHMAP_FOREACH(seat, m->seats, i)
+                seat_start(seat);
+
+        HASHMAP_FOREACH(user, m->users, i)
+                user_start(user);
+
+        HASHMAP_FOREACH(session, m->sessions, i)
+                session_start(session);
+
+        return 0;
+}
+
+int manager_run(Manager *m) {
+        assert(m);
+
+        for (;;) {
+                struct epoll_event event;
+                int n;
+
+                manager_gc(m, true);
+
+                if (dbus_connection_dispatch(m->bus) != DBUS_DISPATCH_COMPLETE)
+                        continue;
+
+                manager_gc(m, true);
+
+                n = epoll_wait(m->epoll_fd, &event, 1, -1);
+                if (n < 0) {
+                        if (errno == EINTR || errno == EAGAIN)
+                                continue;
+
+                        log_error("epoll() failed: %m");
+                        return -errno;
+                }
+
+                switch (event.data.u32) {
+
+                case FD_SEAT_UDEV:
+                        manager_dispatch_seat_udev(m);
+                        break;
+
+                case FD_VCSA_UDEV:
+                        manager_dispatch_vcsa_udev(m);
+                        break;
+
+                case FD_CONSOLE:
+                        manager_dispatch_console(m);
+                        break;
+
+                case FD_BUS:
+                        bus_loop_dispatch(m->bus_fd);
+                        break;
+
+                default:
+                        if (event.data.u32 >= FD_FIFO_BASE)
+                                manager_pipe_notify_eof(m, event.data.u32 - FD_FIFO_BASE);
+                }
+        }
+
+        return 0;
+}
+
+static int manager_parse_config_file(Manager *m) {
+        FILE *f;
+        const char *fn;
+        int r;
+
+        assert(m);
+
+        fn = "/etc/systemd/logind.conf";
+        f = fopen(fn, "re");
+        if (!f) {
+                if (errno == ENOENT)
+                        return 0;
+
+                log_warning("Failed to open configuration file %s: %m", fn);
+                return -errno;
+        }
+
+        r = config_parse(fn, f, "Login\0", config_item_perf_lookup, (void*) logind_gperf_lookup, false, m);
+        if (r < 0)
+                log_warning("Failed to parse configuration file: %s", strerror(-r));
+
+        fclose(f);
+
+        return r;
+}
+
+int main(int argc, char *argv[]) {
+        Manager *m = NULL;
+        int r;
+
+        log_set_target(LOG_TARGET_AUTO);
+        log_set_facility(LOG_AUTH);
+        log_parse_environment();
+        log_open();
+
+        umask(0022);
+
+        if (argc != 1) {
+                log_error("This program takes no arguments.");
+                r = -EINVAL;
+                goto finish;
+        }
+
+        m = manager_new();
+        if (!m) {
+                log_error("Out of memory");
+                r = -ENOMEM;
+                goto finish;
+        }
+
+        manager_parse_config_file(m);
+
+        r = manager_startup(m);
+        if (r < 0) {
+                log_error("Failed to fully start up daemon: %s", strerror(-r));
+                goto finish;
+        }
+
+        log_debug("systemd-logind running as pid %lu", (unsigned long) getpid());
+
+        sd_notify(false,
+                  "READY=1\n"
+                  "STATUS=Processing requests...");
+
+        r = manager_run(m);
+
+        log_debug("systemd-logind stopped as pid %lu", (unsigned long) getpid());
+
+finish:
+        sd_notify(false,
+                  "STATUS=Shutting down...");
+
+        if (m)
+                manager_free(m);
+
+        return r < 0 ? EXIT_FAILURE : EXIT_SUCCESS;
+}
diff --git a/src/login/logind.conf b/src/login/logind.conf
new file mode 100644 (file)
index 0000000..24b9d77
--- /dev/null
@@ -0,0 +1,16 @@
+#  This file is part of systemd.
+#
+#  systemd is free software; you can redistribute it and/or modify it
+#  under the terms of the GNU General Public License as published by
+#  the Free Software Foundation; either version 2 of the License, or
+#  (at your option) any later version.
+#
+# See logind.conf(5) for details
+
+[Login]
+#NAutoVTs=6
+#KillUserProcesses=no
+#KillOnlyUsers=
+#KillExcludeUsers=root
+#Controllers=
+#ResetControllers=cpu
diff --git a/src/login/logind.h b/src/login/logind.h
new file mode 100644 (file)
index 0000000..a4b4602
--- /dev/null
@@ -0,0 +1,131 @@
+/*-*- Mode: C; c-basic-offset: 8; indent-tabs-mode: nil -*-*/
+
+#ifndef foologindhfoo
+#define foologindhfoo
+
+/***
+  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 <http://www.gnu.org/licenses/>.
+***/
+
+#include <stdbool.h>
+#include <inttypes.h>
+#include <dbus/dbus.h>
+#include <libudev.h>
+
+#include "util.h"
+#include "list.h"
+#include "hashmap.h"
+#include "cgroup-util.h"
+
+typedef struct Manager Manager;
+
+#include "logind-device.h"
+#include "logind-seat.h"
+#include "logind-session.h"
+#include "logind-user.h"
+
+struct Manager {
+        DBusConnection *bus;
+
+        Hashmap *devices;
+        Hashmap *seats;
+        Hashmap *sessions;
+        Hashmap *users;
+
+        LIST_HEAD(Seat, seat_gc_queue);
+        LIST_HEAD(Session, session_gc_queue);
+        LIST_HEAD(User, user_gc_queue);
+
+        struct udev *udev;
+        struct udev_monitor *udev_seat_monitor, *udev_vcsa_monitor;
+
+        int udev_seat_fd;
+        int udev_vcsa_fd;
+
+        int console_active_fd;
+        int bus_fd;
+        int epoll_fd;
+
+        unsigned n_autovts;
+
+        Seat *vtconsole;
+
+        char *cgroup_path;
+        char **controllers, **reset_controllers;
+
+        char **kill_only_users, **kill_exclude_users;
+
+        bool kill_user_processes;
+
+        unsigned long session_counter;
+
+        Hashmap *cgroups;
+        Hashmap *fifo_fds;
+};
+
+enum {
+        FD_SEAT_UDEV,
+        FD_VCSA_UDEV,
+        FD_CONSOLE,
+        FD_BUS,
+        FD_FIFO_BASE
+};
+
+Manager *manager_new(void);
+void manager_free(Manager *m);
+
+int manager_add_device(Manager *m, const char *sysfs, Device **_device);
+int manager_add_seat(Manager *m, const char *id, Seat **_seat);
+int manager_add_session(Manager *m, User *u, const char *id, Session **_session);
+int manager_add_user(Manager *m, uid_t uid, gid_t gid, const char *name, User **_user);
+int manager_add_user_by_name(Manager *m, const char *name, User **_user);
+int manager_add_user_by_uid(Manager *m, uid_t uid, User **_user);
+
+int manager_process_seat_device(Manager *m, struct udev_device *d);
+int manager_dispatch_seat_udev(Manager *m);
+int manager_dispatch_vcsa_udev(Manager *m);
+int manager_dispatch_console(Manager *m);
+
+int manager_enumerate_devices(Manager *m);
+int manager_enumerate_seats(Manager *m);
+int manager_enumerate_sessions(Manager *m);
+int manager_enumerate_users(Manager *m);
+
+int manager_startup(Manager *m);
+int manager_run(Manager *m);
+int manager_spawn_autovt(Manager *m, int vtnr);
+
+void manager_cgroup_notify_empty(Manager *m, const char *cgroup);
+
+void manager_gc(Manager *m, bool drop_not_started);
+
+int manager_get_idle_hint(Manager *m, dual_timestamp *t);
+
+int manager_get_session_by_cgroup(Manager *m, const char *cgroup, Session **session);
+int manager_get_session_by_pid(Manager *m, pid_t pid, Session **session);
+
+extern const DBusObjectPathVTable bus_manager_vtable;
+
+DBusHandlerResult bus_message_filter(DBusConnection *c, DBusMessage *message, void *userdata);
+
+int manager_send_changed(Manager *manager, const char *properties);
+
+/* gperf lookup function */
+const struct ConfigPerfItem* logind_gperf_lookup(const char *key, unsigned length);
+
+#endif
diff --git a/src/login/multi-seat-x.c b/src/login/multi-seat-x.c
new file mode 100644 (file)
index 0000000..7133e02
--- /dev/null
@@ -0,0 +1,195 @@
+/*-*- 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 <http://www.gnu.org/licenses/>.
+***/
+
+#include <string.h>
+#include <unistd.h>
+
+#include <libudev.h>
+
+#include "util.h"
+
+int main(int argc, char *argv[]) {
+
+        struct udev *udev = NULL;
+        struct udev_enumerate *enumerator = NULL;
+        struct udev_list_entry *first, *item;
+        int i;
+        const char *seat = NULL;
+        char **new_argv;
+        char *path = NULL, *device_node = NULL;
+        int r;
+        FILE *f = NULL;
+
+        /* This binary will go away as soon as X natively supports
+         * display enumeration with udev in a way that covers both PCI
+         * and USB. */
+
+        /* This will simply determine the fb device id of the graphics
+         * device assigned to a seat and write a configuration file
+         * from it and then spawn the real X server. */
+
+        /* If this file is removed, don't forget to remove the code
+         * that invokes this in gdm and other display managers. */
+
+        for (i = 1; i < argc; i++)
+                if (streq(argv[i], "-seat"))
+                        seat = argv[i+1];
+
+        if (isempty(seat) || streq(seat, "seat0")) {
+                argv[0] = (char*) X_SERVER;
+                execv(X_SERVER, argv);
+                log_error("Failed to execute real X server: %m");
+                goto fail;
+        }
+
+        udev = udev_new();
+        if (!udev) {
+                log_error("Failed to allocate udev environment.");
+                goto fail;
+        }
+
+        enumerator = udev_enumerate_new(udev);
+        if (!enumerator) {
+                log_error("Failed to allocate udev enumerator.");
+                goto fail;
+        }
+
+        udev_enumerate_add_match_subsystem(enumerator, "graphics");
+        udev_enumerate_add_match_tag(enumerator, seat);
+
+        r = udev_enumerate_scan_devices(enumerator);
+        if (r < 0) {
+                log_error("Failed to enumerate devices.");
+                goto fail;
+        }
+
+        first = udev_enumerate_get_list_entry(enumerator);
+        udev_list_entry_foreach(item, first) {
+                struct udev_device *d;
+                const char *dn;
+
+                d = udev_device_new_from_syspath(udev, udev_list_entry_get_name(item));
+                if (!d)
+                        continue;
+
+                dn = udev_device_get_devnode(d);
+
+                if (dn) {
+                        device_node = strdup(dn);
+                        if (!device_node) {
+                                udev_device_unref(d);
+                                log_error("Out of memory.");
+                                goto fail;
+                        }
+                }
+
+                udev_device_unref(d);
+
+                if (device_node)
+                        break;
+        }
+
+        if (!device_node) {
+                log_error("Failed to find device node for seat %s.", seat);
+                goto fail;
+        }
+
+        r = safe_mkdir("/run/systemd/multi-session-x", 0755, 0, 0);
+        if (r < 0) {
+                log_error("Failed to create directory: %s", strerror(-r));
+                goto fail;
+        }
+
+        path = strappend("/run/systemd/multi-session-x/", seat);
+        if (!path) {
+                log_error("Out of memory");
+                goto fail;
+        }
+
+        f = fopen(path, "we");
+        if (!f) {
+                log_error("Failed to write configuration file: %m");
+                goto fail;
+        }
+
+        fprintf(f,
+                "Section \"Device\"\n"
+                "        Identifier \"udev\"\n"
+                "        Driver \"fbdev\"\n"
+                "        Option \"fbdev\" \"%s\"\n"
+                "EndSection\n"
+                "Section \"ServerFlags\"\n"
+                "        Option \"AutoAddDevices\" \"True\"\n"
+                "        Option \"AllowEmptyInput\" \"True\"\n"
+                "        Option \"DontVTSwitch\" \"True\"\n"
+                "EndSection\n"
+                "Section \"InputClass\"\n"
+                "        Identifier \"Force Input Devices to Seat\"\n"
+                "        Option \"GrabDevice\" \"True\"\n"
+                "EndSection\n",
+                device_node);
+
+        fflush(f);
+
+        if (ferror(f)) {
+                log_error("Failed to write configuration file: %m");
+                goto fail;
+        }
+
+        fclose(f);
+        f = NULL;
+
+        new_argv = alloca(sizeof(char*) * (argc + 3 + 1));
+        memcpy(new_argv, argv, sizeof(char*) * (argc + 2 + 1));
+
+        new_argv[0] = (char*) X_SERVER;
+        new_argv[argc+0] = (char*) "-config";
+        new_argv[argc+1] = path;
+        new_argv[argc+2] = (char*) "-sharevts";
+        new_argv[argc+3] = NULL;
+
+        udev_enumerate_unref(enumerator);
+        enumerator = NULL;
+
+        udev_unref(udev);
+        udev = NULL;
+
+        free(device_node);
+        device_node = NULL;
+
+        execv(X_SERVER, new_argv);
+        log_error("Failed to execute real X server: %m");
+
+fail:
+        if (enumerator)
+                udev_enumerate_unref(enumerator);
+
+        if (udev)
+                udev_unref(udev);
+
+        free(path);
+        free(device_node);
+
+        if (f)
+                fclose(f);
+
+        return EXIT_FAILURE;
+}
diff --git a/src/login/org.freedesktop.login1.conf b/src/login/org.freedesktop.login1.conf
new file mode 100644 (file)
index 0000000..9ef852b
--- /dev/null
@@ -0,0 +1,118 @@
+<?xml version="1.0"?> <!--*-nxml-*-->
+<!DOCTYPE busconfig PUBLIC "-//freedesktop//DTD D-BUS Bus Configuration 1.0//EN"
+        "http://www.freedesktop.org/standards/dbus/1.0/busconfig.dtd">
+
+<!--
+  This file is part of systemd.
+
+  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.
+-->
+
+<busconfig>
+
+        <policy user="root">
+                <allow own="org.freedesktop.login1"/>
+                <allow send_destination="org.freedesktop.login1"/>
+                <allow receive_sender="org.freedesktop.login1"/>
+        </policy>
+
+        <policy context="default">
+                <deny send_destination="org.freedesktop.login1"/>
+
+                <allow send_destination="org.freedesktop.login1"
+                       send_interface="org.freedesktop.DBus.Introspectable"/>
+
+                <allow send_destination="org.freedesktop.login1"
+                       send_interface="org.freedesktop.DBus.Peer"/>
+
+                <allow send_destination="org.freedesktop.login1"
+                       send_interface="org.freedesktop.DBus.Properties"
+                       send_member="Get"/>
+
+                <allow send_destination="org.freedesktop.login1"
+                       send_interface="org.freedesktop.DBus.Properties"
+                       send_member="GetAll"/>
+
+                <allow send_destination="org.freedesktop.login1"
+                       send_interface="org.freedesktop.login1.Manager"
+                       send_member="GetSession"/>
+
+                <allow send_destination="org.freedesktop.login1"
+                       send_interface="org.freedesktop.login1.Manager"
+                       send_member="GetSessionByPID"/>
+
+                <allow send_destination="org.freedesktop.login1"
+                       send_interface="org.freedesktop.login1.Manager"
+                       send_member="GetUser"/>
+
+                <allow send_destination="org.freedesktop.login1"
+                       send_interface="org.freedesktop.login1.Manager"
+                       send_member="GetSeat"/>
+
+                <allow send_destination="org.freedesktop.login1"
+                       send_interface="org.freedesktop.login1.Manager"
+                       send_member="ListSessions"/>
+
+                <allow send_destination="org.freedesktop.login1"
+                       send_interface="org.freedesktop.login1.Manager"
+                       send_member="ListUsers"/>
+
+                <allow send_destination="org.freedesktop.login1"
+                       send_interface="org.freedesktop.login1.Manager"
+                       send_member="ListSeats"/>
+
+                <allow send_destination="org.freedesktop.login1"
+                       send_interface="org.freedesktop.login1.Manager"
+                       send_member="SetUserLinger"/>
+
+                <allow send_destination="org.freedesktop.login1"
+                       send_interface="org.freedesktop.login1.Manager"
+                       send_member="ActivateSession"/>
+
+                <allow send_destination="org.freedesktop.login1"
+                       send_interface="org.freedesktop.login1.Manager"
+                       send_member="ActivateSessionOnSeat"/>
+
+                <allow send_destination="org.freedesktop.login1"
+                       send_interface="org.freedesktop.login1.Manager"
+                       send_member="PowerOff"/>
+
+                <allow send_destination="org.freedesktop.login1"
+                       send_interface="org.freedesktop.login1.Manager"
+                       send_member="Reboot"/>
+
+                <allow send_destination="org.freedesktop.login1"
+                       send_interface="org.freedesktop.login1.Manager"
+                       send_member="CanPowerOff"/>
+
+                <allow send_destination="org.freedesktop.login1"
+                       send_interface="org.freedesktop.login1.Manager"
+                       send_member="CanReboot"/>
+
+                <allow send_destination="org.freedesktop.login1"
+                       send_interface="org.freedesktop.login1.Manager"
+                       send_member="AttachDevice"/>
+
+                <allow send_destination="org.freedesktop.login1"
+                       send_interface="org.freedesktop.login1.Manager"
+                       send_member="FlushDevices"/>
+
+                <allow send_destination="org.freedesktop.login1"
+                       send_interface="org.freedesktop.login1.Seat"
+                       send_member="ActivateSession"/>
+
+                <allow send_destination="org.freedesktop.login1"
+                       send_interface="org.freedesktop.login1.Session"
+                       send_member="Activate"/>
+
+                <allow send_destination="org.freedesktop.login1"
+                       send_interface="org.freedesktop.login1.Session"
+                       send_member="SetIdleHint"/>
+
+                <allow receive_sender="org.freedesktop.login1"/>
+        </policy>
+
+</busconfig>
diff --git a/src/login/org.freedesktop.login1.policy.in b/src/login/org.freedesktop.login1.policy.in
new file mode 100644 (file)
index 0000000..adc9048
--- /dev/null
@@ -0,0 +1,89 @@
+<?xml version="1.0" encoding="UTF-8"?> <!--*-nxml-*-->
+<!DOCTYPE policyconfig PUBLIC "-//freedesktop//DTD PolicyKit Policy Configuration 1.0//EN"
+        "http://www.freedesktop.org/standards/PolicyKit/1/policyconfig.dtd">
+
+<!--
+  This file is part of systemd.
+
+  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.
+-->
+
+<policyconfig>
+
+        <vendor>The systemd Project</vendor>
+        <vendor_url>http://www.freedesktop.org/wiki/Software/systemd</vendor_url>
+
+        <action id="org.freedesktop.login1.set-user-linger">
+                <_description>Allow non-logged-in users to run programs</_description>
+                <_message>Authentication is required to allow a non-logged-in user to run programs</_message>
+                <defaults>
+                        <allow_any>auth_admin_keep</allow_any>
+                        <allow_inactive>auth_admin_keep</allow_inactive>
+                        <allow_active>auth_admin_keep</allow_active>
+                </defaults>
+        </action>
+
+        <action id="org.freedesktop.login1.attach-device">
+                <_description>Allow attaching devices to seats</_description>
+                <_message>Authentication is required to allow attaching a device to a seat</_message>
+                <defaults>
+                        <allow_any>auth_admin_keep</allow_any>
+                        <allow_inactive>auth_admin_keep</allow_inactive>
+                        <allow_active>auth_admin_keep</allow_active>
+                </defaults>
+        </action>
+
+        <action id="org.freedesktop.login1.flush-devices">
+                <_description>Flush device to seat attachments</_description>
+                <_message>Authentication is required to allow resetting how devices are attached to seats</_message>
+                <defaults>
+                        <allow_any>auth_admin_keep</allow_any>
+                        <allow_inactive>auth_admin_keep</allow_inactive>
+                        <allow_active>auth_admin_keep</allow_active>
+                </defaults>
+        </action>
+
+        <action id="org.freedesktop.login1.power-off">
+                <_description>Power off the system</_description>
+                <_message>Authentication is required to allow powering off the system</_message>
+                <defaults>
+                        <allow_any>auth_admin_keep</allow_any>
+                        <allow_inactive>auth_admin_keep</allow_inactive>
+                        <allow_active>yes</allow_active>
+                </defaults>
+        </action>
+
+        <action id="org.freedesktop.login1.power-off-multiple-sessions">
+                <_description>Power off the system when other users are logged in</_description>
+                <_message>Authentication is required to allow powering off the system while other users are logged in</_message>
+                <defaults>
+                        <allow_any>auth_admin_keep</allow_any>
+                        <allow_inactive>auth_admin_keep</allow_inactive>
+                        <allow_active>auth_admin_keep</allow_active>
+                </defaults>
+        </action>
+
+        <action id="org.freedesktop.login1.reboot">
+                <_description>Reboot the system</_description>
+                <_message>Authentication is required to allow rebooting the system</_message>
+                <defaults>
+                        <allow_any>auth_admin_keep</allow_any>
+                        <allow_inactive>auth_admin_keep</allow_inactive>
+                        <allow_active>yes</allow_active>
+                </defaults>
+        </action>
+
+        <action id="org.freedesktop.login1.reboot-multiple-sessions">
+                <_description>Reboot the system when other users are logged in</_description>
+                <_message>Authentication is required to allow rebooting the system while other users are logged in</_message>
+                <defaults>
+                        <allow_any>auth_admin_keep</allow_any>
+                        <allow_inactive>auth_admin_keep</allow_inactive>
+                        <allow_active>auth_admin_keep</allow_active>
+                </defaults>
+        </action>
+
+</policyconfig>
diff --git a/src/login/org.freedesktop.login1.service b/src/login/org.freedesktop.login1.service
new file mode 100644 (file)
index 0000000..4a64177
--- /dev/null
@@ -0,0 +1,12 @@
+#  This file is part of systemd.
+#
+#  systemd is free software; you can redistribute it and/or modify it
+#  under the terms of the GNU General Public License as published by
+#  the Free Software Foundation; either version 2 of the License, or
+#  (at your option) any later version.
+
+[D-BUS Service]
+Name=org.freedesktop.login1
+Exec=/bin/false
+User=root
+SystemdService=dbus-org.freedesktop.login1.service
diff --git a/src/login/pam-module.c b/src/login/pam-module.c
new file mode 100644 (file)
index 0000000..4106d2b
--- /dev/null
@@ -0,0 +1,695 @@
+/*-*- 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 <http://www.gnu.org/licenses/>.
+***/
+
+#include <errno.h>
+#include <fcntl.h>
+#include <sys/file.h>
+#include <pwd.h>
+#include <endian.h>
+#include <sys/capability.h>
+
+#include <security/pam_modules.h>
+#include <security/_pam_macros.h>
+#include <security/pam_modutil.h>
+#include <security/pam_ext.h>
+#include <security/pam_misc.h>
+
+#include <systemd/sd-daemon.h>
+
+#include "util.h"
+#include "macro.h"
+#include "strv.h"
+#include "dbus-common.h"
+#include "def.h"
+#include "socket-util.h"
+
+static int parse_argv(pam_handle_t *handle,
+                      int argc, const char **argv,
+                      char ***controllers,
+                      char ***reset_controllers,
+                      bool *kill_processes,
+                      char ***kill_only_users,
+                      char ***kill_exclude_users,
+                      bool *debug) {
+
+        unsigned i;
+
+        assert(argc >= 0);
+        assert(argc == 0 || argv);
+
+        for (i = 0; i < (unsigned) argc; i++) {
+                int k;
+
+                if (startswith(argv[i], "kill-session-processes=")) {
+                        if ((k = parse_boolean(argv[i] + 23)) < 0) {
+                                pam_syslog(handle, LOG_ERR, "Failed to parse kill-session-processes= argument.");
+                                return k;
+                        }
+
+                        if (kill_processes)
+                                *kill_processes = k;
+
+                } else if (startswith(argv[i], "kill-session=")) {
+                        /* As compatibility for old versions */
+
+                        if ((k = parse_boolean(argv[i] + 13)) < 0) {
+                                pam_syslog(handle, LOG_ERR, "Failed to parse kill-session= argument.");
+                                return k;
+                        }
+
+                        if (kill_processes)
+                                *kill_processes = k;
+
+                } else if (startswith(argv[i], "controllers=")) {
+
+                        if (controllers) {
+                                char **l;
+
+                                if (!(l = strv_split(argv[i] + 12, ","))) {
+                                        pam_syslog(handle, LOG_ERR, "Out of memory.");
+                                        return -ENOMEM;
+                                }
+
+                                strv_free(*controllers);
+                                *controllers = l;
+                        }
+
+                } else if (startswith(argv[i], "reset-controllers=")) {
+
+                        if (reset_controllers) {
+                                char **l;
+
+                                if (!(l = strv_split(argv[i] + 18, ","))) {
+                                        pam_syslog(handle, LOG_ERR, "Out of memory.");
+                                        return -ENOMEM;
+                                }
+
+                                strv_free(*reset_controllers);
+                                *reset_controllers = l;
+                        }
+
+                } else if (startswith(argv[i], "kill-only-users=")) {
+
+                        if (kill_only_users) {
+                                char **l;
+
+                                if (!(l = strv_split(argv[i] + 16, ","))) {
+                                        pam_syslog(handle, LOG_ERR, "Out of memory.");
+                                        return -ENOMEM;
+                                }
+
+                                strv_free(*kill_only_users);
+                                *kill_only_users = l;
+                        }
+
+                } else if (startswith(argv[i], "kill-exclude-users=")) {
+
+                        if (kill_exclude_users) {
+                                char **l;
+
+                                if (!(l = strv_split(argv[i] + 19, ","))) {
+                                        pam_syslog(handle, LOG_ERR, "Out of memory.");
+                                        return -ENOMEM;
+                                }
+
+                                strv_free(*kill_exclude_users);
+                                *kill_exclude_users = l;
+                        }
+
+                } else if (startswith(argv[i], "debug=")) {
+                        if ((k = parse_boolean(argv[i] + 6)) < 0) {
+                                pam_syslog(handle, LOG_ERR, "Failed to parse debug= argument.");
+                                return k;
+                        }
+
+                        if (debug)
+                                *debug = k;
+
+                } else if (startswith(argv[i], "create-session=") ||
+                           startswith(argv[i], "kill-user=")) {
+
+                        pam_syslog(handle, LOG_WARNING, "Option %s not supported anymore, ignoring.", argv[i]);
+
+                } else {
+                        pam_syslog(handle, LOG_ERR, "Unknown parameter '%s'.", argv[i]);
+                        return -EINVAL;
+                }
+        }
+
+        return 0;
+}
+
+static int get_user_data(
+                pam_handle_t *handle,
+                const char **ret_username,
+                struct passwd **ret_pw) {
+
+        const char *username = NULL;
+        struct passwd *pw = NULL;
+        uid_t uid;
+        int r;
+
+        assert(handle);
+        assert(ret_username);
+        assert(ret_pw);
+
+        r = audit_loginuid_from_pid(0, &uid);
+        if (r >= 0)
+                pw = pam_modutil_getpwuid(handle, uid);
+        else {
+                r = pam_get_user(handle, &username, NULL);
+                if (r != PAM_SUCCESS) {
+                        pam_syslog(handle, LOG_ERR, "Failed to get user name.");
+                        return r;
+                }
+
+                if (isempty(username)) {
+                        pam_syslog(handle, LOG_ERR, "User name not valid.");
+                        return PAM_AUTH_ERR;
+                }
+
+                pw = pam_modutil_getpwnam(handle, username);
+        }
+
+        if (!pw) {
+                pam_syslog(handle, LOG_ERR, "Failed to get user data.");
+                return PAM_USER_UNKNOWN;
+        }
+
+        *ret_pw = pw;
+        *ret_username = username ? username : pw->pw_name;
+
+        return PAM_SUCCESS;
+}
+
+static bool check_user_lists(
+                pam_handle_t *handle,
+                uid_t uid,
+                char **kill_only_users,
+                char **kill_exclude_users) {
+
+        const char *name = NULL;
+        char **l;
+
+        assert(handle);
+
+        if (uid == 0)
+                name = "root"; /* Avoid obvious NSS requests, to suppress network traffic */
+        else {
+                struct passwd *pw;
+
+                pw = pam_modutil_getpwuid(handle, uid);
+                if (pw)
+                        name = pw->pw_name;
+        }
+
+        STRV_FOREACH(l, kill_exclude_users) {
+                uid_t u;
+
+                if (parse_uid(*l, &u) >= 0)
+                        if (u == uid)
+                                return false;
+
+                if (name && streq(name, *l))
+                        return false;
+        }
+
+        if (strv_isempty(kill_only_users))
+                return true;
+
+        STRV_FOREACH(l, kill_only_users) {
+                uid_t u;
+
+                if (parse_uid(*l, &u) >= 0)
+                        if (u == uid)
+                                return true;
+
+                if (name && streq(name, *l))
+                        return true;
+        }
+
+        return false;
+}
+
+static int get_seat_from_display(const char *display, const char **seat, uint32_t *vtnr) {
+        char *p = NULL;
+        int r;
+        int fd;
+        union sockaddr_union sa;
+        struct ucred ucred;
+        socklen_t l;
+        char *tty;
+        int v;
+
+        assert(display);
+        assert(vtnr);
+
+        /* We deduce the X11 socket from the display name, then use
+         * SO_PEERCRED to determine the X11 server process, ask for
+         * the controlling tty of that and if it's a VC then we know
+         * the seat and the virtual terminal. Sounds ugly, is only
+         * semi-ugly. */
+
+        r = socket_from_display(display, &p);
+        if (r < 0)
+                return r;
+
+        fd = socket(AF_UNIX, SOCK_STREAM|SOCK_CLOEXEC, 0);
+        if (fd < 0) {
+                free(p);
+                return -errno;
+        }
+
+        zero(sa);
+        sa.un.sun_family = AF_UNIX;
+        strncpy(sa.un.sun_path, p, sizeof(sa.un.sun_path)-1);
+        free(p);
+
+        if (connect(fd, &sa.sa, offsetof(struct sockaddr_un, sun_path) + strlen(sa.un.sun_path)) < 0) {
+                close_nointr_nofail(fd);
+                return -errno;
+        }
+
+        l = sizeof(ucred);
+        r = getsockopt(fd, SOL_SOCKET, SO_PEERCRED, &ucred, &l);
+        close_nointr_nofail(fd);
+
+        if (r < 0)
+                return -errno;
+
+        r = get_ctty(ucred.pid, NULL, &tty);
+        if (r < 0)
+                return r;
+
+        v = vtnr_from_tty(tty);
+        free(tty);
+
+        if (v < 0)
+                return v;
+        else if (v == 0)
+                return -ENOENT;
+
+        if (seat)
+                *seat = "seat0";
+        *vtnr = (uint32_t) v;
+
+        return 0;
+}
+
+_public_ PAM_EXTERN int pam_sm_open_session(
+                pam_handle_t *handle,
+                int flags,
+                int argc, const char **argv) {
+
+        struct passwd *pw;
+        bool kill_processes = false, debug = false;
+        const char *username, *id, *object_path, *runtime_path, *service = NULL, *tty = NULL, *display = NULL, *remote_user = NULL, *remote_host = NULL, *seat = NULL, *type, *class, *cvtnr = NULL;
+        char **controllers = NULL, **reset_controllers = NULL, **kill_only_users = NULL, **kill_exclude_users = NULL;
+        DBusError error;
+        uint32_t uid, pid;
+        DBusMessageIter iter;
+        dbus_bool_t kp;
+        int session_fd = -1;
+        DBusConnection *bus = NULL;
+        DBusMessage *m = NULL, *reply = NULL;
+        dbus_bool_t remote;
+        int r;
+        uint32_t vtnr = 0;
+
+        assert(handle);
+
+        dbus_error_init(&error);
+
+        /* pam_syslog(handle, LOG_INFO, "pam-systemd initializing"); */
+
+        /* Make this a NOP on non-systemd systems */
+        if (sd_booted() <= 0)
+                return PAM_SUCCESS;
+
+        if (parse_argv(handle,
+                       argc, argv,
+                       &controllers, &reset_controllers,
+                       &kill_processes, &kill_only_users, &kill_exclude_users,
+                       &debug) < 0) {
+                r = PAM_SESSION_ERR;
+                goto finish;
+        }
+
+        r = get_user_data(handle, &username, &pw);
+        if (r != PAM_SUCCESS)
+                goto finish;
+
+        /* Make sure we don't enter a loop by talking to
+         * systemd-logind when it is actually waiting for the
+         * background to finish start-up. If the service is
+         * "systemd-shared" we simply set XDG_RUNTIME_DIR and
+         * leave. */
+
+        pam_get_item(handle, PAM_SERVICE, (const void**) &service);
+        if (streq_ptr(service, "systemd-shared")) {
+                char *p, *rt = NULL;
+
+                if (asprintf(&p, "/run/systemd/users/%lu", (unsigned long) pw->pw_uid) < 0) {
+                        r = PAM_BUF_ERR;
+                        goto finish;
+                }
+
+                r = parse_env_file(p, NEWLINE,
+                                   "RUNTIME", &rt,
+                                   NULL);
+                free(p);
+
+                if (r < 0 && r != -ENOENT) {
+                        r = PAM_SESSION_ERR;
+                        free(rt);
+                        goto finish;
+                }
+
+                if (rt)  {
+                        r = pam_misc_setenv(handle, "XDG_RUNTIME_DIR", rt, 0);
+                        free(rt);
+
+                        if (r != PAM_SUCCESS) {
+                                pam_syslog(handle, LOG_ERR, "Failed to set runtime dir.");
+                                goto finish;
+                        }
+                }
+
+                r = PAM_SUCCESS;
+                goto finish;
+        }
+
+        if (kill_processes)
+                kill_processes = check_user_lists(handle, pw->pw_uid, kill_only_users, kill_exclude_users);
+
+        dbus_connection_set_change_sigpipe(FALSE);
+
+        bus = dbus_bus_get_private(DBUS_BUS_SYSTEM, &error);
+        if (!bus) {
+                pam_syslog(handle, LOG_ERR, "Failed to connect to system bus: %s", bus_error_message(&error));
+                r = PAM_SESSION_ERR;
+                goto finish;
+        }
+
+        m = dbus_message_new_method_call(
+                        "org.freedesktop.login1",
+                        "/org/freedesktop/login1",
+                        "org.freedesktop.login1.Manager",
+                        "CreateSession");
+        if (!m) {
+                pam_syslog(handle, LOG_ERR, "Could not allocate create session message.");
+                r = PAM_BUF_ERR;
+                goto finish;
+        }
+
+        uid = pw->pw_uid;
+        pid = getpid();
+
+        pam_get_item(handle, PAM_XDISPLAY, (const void**) &display);
+        pam_get_item(handle, PAM_TTY, (const void**) &tty);
+        pam_get_item(handle, PAM_RUSER, (const void**) &remote_user);
+        pam_get_item(handle, PAM_RHOST, (const void**) &remote_host);
+        seat = pam_getenv(handle, "XDG_SEAT");
+        cvtnr = pam_getenv(handle, "XDG_VTNR");
+
+        service = strempty(service);
+        tty = strempty(tty);
+        display = strempty(display);
+        remote_user = strempty(remote_user);
+        remote_host = strempty(remote_host);
+        seat = strempty(seat);
+
+        if (strchr(tty, ':')) {
+                /* A tty with a colon is usually an X11 display, place
+                 * there to show up in utmp. We rearrange things and
+                 * don't pretend that an X display was a tty */
+
+                if (isempty(display))
+                        display = tty;
+                tty = "";
+        } else if (streq(tty, "cron")) {
+                /* cron has been setting PAM_TTY to "cron" for a very long time
+                 * and it cannot stop doing that for compatibility reasons. */
+                tty = "";
+        }
+
+        if (!isempty(cvtnr))
+                safe_atou32(cvtnr, &vtnr);
+
+        if (!isempty(display) && vtnr <= 0) {
+                if (isempty(seat))
+                        get_seat_from_display(display, &seat, &vtnr);
+                else if (streq(seat, "seat0"))
+                        get_seat_from_display(display, NULL, &vtnr);
+        }
+
+        type = !isempty(display) ? "x11" :
+                   !isempty(tty) ? "tty" : "unspecified";
+
+        class = pam_getenv(handle, "XDG_SESSION_CLASS");
+        if (isempty(class))
+                class = "user";
+
+        remote = !isempty(remote_host) &&
+                !streq(remote_host, "localhost") &&
+                !streq(remote_host, "localhost.localdomain");
+
+        if (!dbus_message_append_args(m,
+                                      DBUS_TYPE_UINT32, &uid,
+                                      DBUS_TYPE_UINT32, &pid,
+                                      DBUS_TYPE_STRING, &service,
+                                      DBUS_TYPE_STRING, &type,
+                                      DBUS_TYPE_STRING, &class,
+                                      DBUS_TYPE_STRING, &seat,
+                                      DBUS_TYPE_UINT32, &vtnr,
+                                      DBUS_TYPE_STRING, &tty,
+                                      DBUS_TYPE_STRING, &display,
+                                      DBUS_TYPE_BOOLEAN, &remote,
+                                      DBUS_TYPE_STRING, &remote_user,
+                                      DBUS_TYPE_STRING, &remote_host,
+                                      DBUS_TYPE_INVALID)) {
+                pam_syslog(handle, LOG_ERR, "Could not attach parameters to message.");
+                r = PAM_BUF_ERR;
+                goto finish;
+        }
+
+        dbus_message_iter_init_append(m, &iter);
+
+        r = bus_append_strv_iter(&iter, controllers);
+        if (r < 0) {
+                pam_syslog(handle, LOG_ERR, "Could not attach parameter to message.");
+                r = PAM_BUF_ERR;
+                goto finish;
+        }
+
+        r = bus_append_strv_iter(&iter, reset_controllers);
+        if (r < 0) {
+                pam_syslog(handle, LOG_ERR, "Could not attach parameter to message.");
+                r = PAM_BUF_ERR;
+                goto finish;
+        }
+
+        kp = kill_processes;
+        if (!dbus_message_iter_append_basic(&iter, DBUS_TYPE_BOOLEAN, &kp)) {
+                pam_syslog(handle, LOG_ERR, "Could not attach parameter to message.");
+                r = PAM_BUF_ERR;
+                goto finish;
+        }
+
+        if (debug)
+                pam_syslog(handle, LOG_DEBUG, "Asking logind to create session: "
+                           "uid=%u pid=%u service=%s type=%s seat=%s vtnr=%u tty=%s display=%s remote=%s remote_user=%s remote_host=%s",
+                           uid, pid, service, type, seat, vtnr, tty, display, yes_no(remote), remote_user, remote_host);
+
+        reply = dbus_connection_send_with_reply_and_block(bus, m, -1, &error);
+        if (!reply) {
+                pam_syslog(handle, LOG_ERR, "Failed to create session: %s", bus_error_message(&error));
+                r = PAM_SESSION_ERR;
+                goto finish;
+        }
+
+        if (!dbus_message_get_args(reply, &error,
+                                   DBUS_TYPE_STRING, &id,
+                                   DBUS_TYPE_OBJECT_PATH, &object_path,
+                                   DBUS_TYPE_STRING, &runtime_path,
+                                   DBUS_TYPE_UNIX_FD, &session_fd,
+                                   DBUS_TYPE_STRING, &seat,
+                                   DBUS_TYPE_UINT32, &vtnr,
+                                   DBUS_TYPE_INVALID)) {
+                pam_syslog(handle, LOG_ERR, "Failed to parse message: %s", bus_error_message(&error));
+                r = PAM_SESSION_ERR;
+                goto finish;
+        }
+
+        if (debug)
+                pam_syslog(handle, LOG_DEBUG, "Reply from logind: "
+                           "id=%s object_path=%s runtime_path=%s session_fd=%d seat=%s vtnr=%u",
+                           id, object_path, runtime_path, session_fd, seat, vtnr);
+
+        r = pam_misc_setenv(handle, "XDG_SESSION_ID", id, 0);
+        if (r != PAM_SUCCESS) {
+                pam_syslog(handle, LOG_ERR, "Failed to set session id.");
+                goto finish;
+        }
+
+        r = pam_misc_setenv(handle, "XDG_RUNTIME_DIR", runtime_path, 0);
+        if (r != PAM_SUCCESS) {
+                pam_syslog(handle, LOG_ERR, "Failed to set runtime dir.");
+                goto finish;
+        }
+
+        if (!isempty(seat)) {
+                r = pam_misc_setenv(handle, "XDG_SEAT", seat, 0);
+                if (r != PAM_SUCCESS) {
+                        pam_syslog(handle, LOG_ERR, "Failed to set seat.");
+                        goto finish;
+                }
+        }
+
+        if (vtnr > 0) {
+                char buf[11];
+                snprintf(buf, sizeof(buf), "%u", vtnr);
+                char_array_0(buf);
+
+                r = pam_misc_setenv(handle, "XDG_VTNR", buf, 0);
+                if (r != PAM_SUCCESS) {
+                        pam_syslog(handle, LOG_ERR, "Failed to set virtual terminal number.");
+                        goto finish;
+                }
+        }
+
+        if (session_fd >= 0) {
+                r = pam_set_data(handle, "systemd.session-fd", INT_TO_PTR(session_fd+1), NULL);
+                if (r != PAM_SUCCESS) {
+                        pam_syslog(handle, LOG_ERR, "Failed to install session fd.");
+                        return r;
+                }
+        }
+
+        session_fd = -1;
+
+        r = PAM_SUCCESS;
+
+finish:
+        strv_free(controllers);
+        strv_free(reset_controllers);
+        strv_free(kill_only_users);
+        strv_free(kill_exclude_users);
+
+        dbus_error_free(&error);
+
+        if (bus) {
+                dbus_connection_close(bus);
+                dbus_connection_unref(bus);
+        }
+
+        if (m)
+                dbus_message_unref(m);
+
+        if (reply)
+                dbus_message_unref(reply);
+
+        if (session_fd >= 0)
+                close_nointr_nofail(session_fd);
+
+        return r;
+}
+
+_public_ PAM_EXTERN int pam_sm_close_session(
+                pam_handle_t *handle,
+                int flags,
+                int argc, const char **argv) {
+
+        const void *p = NULL;
+        const char *id;
+        DBusConnection *bus = NULL;
+        DBusMessage *m = NULL, *reply = NULL;
+        DBusError error;
+        int r;
+
+        assert(handle);
+
+        dbus_error_init(&error);
+
+        id = pam_getenv(handle, "XDG_SESSION_ID");
+        if (id) {
+
+                /* Before we go and close the FIFO we need to tell
+                 * logind that this is a clean session shutdown, so
+                 * that it doesn't just go and slaughter us
+                 * immediately after closing the fd */
+
+                bus = dbus_bus_get_private(DBUS_BUS_SYSTEM, &error);
+                if (!bus) {
+                        pam_syslog(handle, LOG_ERR, "Failed to connect to system bus: %s", bus_error_message(&error));
+                        r = PAM_SESSION_ERR;
+                        goto finish;
+                }
+
+                m = dbus_message_new_method_call(
+                                "org.freedesktop.login1",
+                                "/org/freedesktop/login1",
+                                "org.freedesktop.login1.Manager",
+                                "ReleaseSession");
+                if (!m) {
+                        pam_syslog(handle, LOG_ERR, "Could not allocate release session message.");
+                        r = PAM_BUF_ERR;
+                        goto finish;
+                }
+
+                if (!dbus_message_append_args(m,
+                                              DBUS_TYPE_STRING, &id,
+                                              DBUS_TYPE_INVALID)) {
+                        pam_syslog(handle, LOG_ERR, "Could not attach parameters to message.");
+                        r = PAM_BUF_ERR;
+                        goto finish;
+                }
+
+                reply = dbus_connection_send_with_reply_and_block(bus, m, -1, &error);
+                if (!reply) {
+                        pam_syslog(handle, LOG_ERR, "Failed to release session: %s", bus_error_message(&error));
+                        r = PAM_SESSION_ERR;
+                        goto finish;
+                }
+        }
+
+        r = PAM_SUCCESS;
+
+finish:
+        pam_get_data(handle, "systemd.session-fd", &p);
+        if (p)
+                close_nointr(PTR_TO_INT(p) - 1);
+
+        dbus_error_free(&error);
+
+        if (bus) {
+                dbus_connection_close(bus);
+                dbus_connection_unref(bus);
+        }
+
+        if (m)
+                dbus_message_unref(m);
+
+        if (reply)
+                dbus_message_unref(reply);
+
+        return r;
+}
diff --git a/src/login/sd-login.c b/src/login/sd-login.c
new file mode 100644 (file)
index 0000000..887c421
--- /dev/null
@@ -0,0 +1,835 @@
+/*-*- 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 <http://www.gnu.org/licenses/>.
+***/
+
+#include <unistd.h>
+#include <string.h>
+#include <errno.h>
+#include <sys/inotify.h>
+
+#include "util.h"
+#include "cgroup-util.h"
+#include "macro.h"
+#include "sd-login.h"
+#include "strv.h"
+
+static int pid_get_cgroup(pid_t pid, char **root, char **cgroup) {
+        char *cg_process, *cg_init, *p;
+        int r;
+
+        if (pid == 0)
+                pid = getpid();
+
+        if (pid <= 0)
+                return -EINVAL;
+
+        r = cg_get_by_pid(SYSTEMD_CGROUP_CONTROLLER, pid, &cg_process);
+        if (r < 0)
+                return r;
+
+        r = cg_get_by_pid(SYSTEMD_CGROUP_CONTROLLER, 1, &cg_init);
+        if (r < 0) {
+                free(cg_process);
+                return r;
+        }
+
+        if (endswith(cg_init, "/system"))
+                cg_init[strlen(cg_init)-7] = 0;
+        else if (streq(cg_init, "/"))
+                cg_init[0] = 0;
+
+        if (startswith(cg_process, cg_init))
+                p = cg_process + strlen(cg_init);
+        else
+                p = cg_process;
+
+        free(cg_init);
+
+        if (cgroup) {
+                char* c;
+
+                c = strdup(p);
+                if (!c) {
+                        free(cg_process);
+                        return -ENOMEM;
+                }
+
+                *cgroup = c;
+        }
+
+        if (root) {
+                cg_process[p-cg_process] = 0;
+                *root = cg_process;
+        } else
+                free(cg_process);
+
+        return 0;
+}
+
+_public_ int sd_pid_get_session(pid_t pid, char **session) {
+        int r;
+        char *cgroup, *p;
+
+        if (!session)
+                return -EINVAL;
+
+        r = pid_get_cgroup(pid, NULL, &cgroup);
+        if (r < 0)
+                return r;
+
+        if (!startswith(cgroup, "/user/")) {
+                free(cgroup);
+                return -ENOENT;
+        }
+
+        p = strchr(cgroup + 6, '/');
+        if (!p) {
+                free(cgroup);
+                return -ENOENT;
+        }
+
+        p++;
+        if (startswith(p, "shared/") || streq(p, "shared")) {
+                free(cgroup);
+                return -ENOENT;
+        }
+
+        p = strndup(p, strcspn(p, "/"));
+        free(cgroup);
+
+        if (!p)
+                return -ENOMEM;
+
+        *session = p;
+        return 0;
+}
+
+_public_ int sd_pid_get_unit(pid_t pid, char **unit) {
+        int r;
+        char *cgroup, *p, *at, *b;
+        size_t k;
+
+        if (!unit)
+                return -EINVAL;
+
+        r = pid_get_cgroup(pid, NULL, &cgroup);
+        if (r < 0)
+                return r;
+
+        if (!startswith(cgroup, "/system/")) {
+                free(cgroup);
+                return -ENOENT;
+        }
+
+        p = cgroup + 8;
+        k = strcspn(p, "/");
+
+        at = memchr(p, '@', k);
+        if (at && at[1] == '.') {
+                size_t j;
+
+                /* This is a templated service */
+                if (p[k] != '/') {
+                        free(cgroup);
+                        return -EIO;
+                }
+
+                j = strcspn(p+k+1, "/");
+
+                b = malloc(k + j + 1);
+
+                if (b) {
+                        memcpy(b, p, at - p + 1);
+                        memcpy(b + (at - p) + 1, p + k + 1, j);
+                        memcpy(b + (at - p) + 1 + j, at + 1, k - (at - p) - 1);
+                        b[k+j] = 0;
+                }
+        } else
+                  b = strndup(p, k);
+
+        free(cgroup);
+
+        if (!b)
+                return -ENOMEM;
+
+        *unit = b;
+        return 0;
+}
+
+_public_ int sd_pid_get_owner_uid(pid_t pid, uid_t *uid) {
+        int r;
+        char *root, *cgroup, *p, *cc;
+        struct stat st;
+
+        if (!uid)
+                return -EINVAL;
+
+        r = pid_get_cgroup(pid, &root, &cgroup);
+        if (r < 0)
+                return r;
+
+        if (!startswith(cgroup, "/user/")) {
+                free(cgroup);
+                free(root);
+                return -ENOENT;
+        }
+
+        p = strchr(cgroup + 6, '/');
+        if (!p) {
+                free(cgroup);
+                return -ENOENT;
+        }
+
+        p++;
+        p += strcspn(p, "/");
+        *p = 0;
+
+        r = cg_get_path(SYSTEMD_CGROUP_CONTROLLER, root, cgroup, &cc);
+        free(root);
+        free(cgroup);
+
+        if (r < 0)
+                return -ENOMEM;
+
+        r = lstat(cc, &st);
+        free(cc);
+
+        if (r < 0)
+                return -errno;
+
+        if (!S_ISDIR(st.st_mode))
+                return -ENOTDIR;
+
+        *uid = st.st_uid;
+        return 0;
+}
+
+_public_ int sd_uid_get_state(uid_t uid, char**state) {
+        char *p, *s = NULL;
+        int r;
+
+        if (!state)
+                return -EINVAL;
+
+        if (asprintf(&p, "/run/systemd/users/%lu", (unsigned long) uid) < 0)
+                return -ENOMEM;
+
+        r = parse_env_file(p, NEWLINE, "STATE", &s, NULL);
+        free(p);
+
+        if (r == -ENOENT) {
+                free(s);
+                s = strdup("offline");
+                if (!s)
+                        return -ENOMEM;
+
+                *state = s;
+                return 0;
+        } else if (r < 0) {
+                free(s);
+                return r;
+        } else if (!s)
+                return -EIO;
+
+        *state = s;
+        return 0;
+}
+
+_public_ int sd_uid_is_on_seat(uid_t uid, int require_active, const char *seat) {
+        char *p, *w, *t, *state, *s = NULL;
+        size_t l;
+        int r;
+        const char *variable;
+
+        if (!seat)
+                return -EINVAL;
+
+        variable = require_active ? "ACTIVE_UID" : "UIDS";
+
+        p = strappend("/run/systemd/seats/", seat);
+        if (!p)
+                return -ENOMEM;
+
+        r = parse_env_file(p, NEWLINE, variable, &s, NULL);
+        free(p);
+
+        if (r < 0) {
+                free(s);
+                return r;
+        }
+
+        if (!s)
+                return -EIO;
+
+        if (asprintf(&t, "%lu", (unsigned long) uid) < 0) {
+                free(s);
+                return -ENOMEM;
+        }
+
+        FOREACH_WORD(w, l, s, state) {
+                if (strncmp(t, w, l) == 0) {
+                        free(s);
+                        free(t);
+
+                        return 1;
+                }
+        }
+
+        free(s);
+        free(t);
+
+        return 0;
+}
+
+static int uid_get_array(uid_t uid, const char *variable, char ***array) {
+        char *p, *s = NULL;
+        char **a;
+        int r;
+
+        if (asprintf(&p, "/run/systemd/users/%lu", (unsigned long) uid) < 0)
+                return -ENOMEM;
+
+        r = parse_env_file(p, NEWLINE,
+                           variable, &s,
+                           NULL);
+        free(p);
+
+        if (r < 0) {
+                free(s);
+
+                if (r == -ENOENT) {
+                        if (array)
+                                *array = NULL;
+                        return 0;
+                }
+
+                return r;
+        }
+
+        if (!s) {
+                if (array)
+                        *array = NULL;
+                return 0;
+        }
+
+        a = strv_split(s, " ");
+        free(s);
+
+        if (!a)
+                return -ENOMEM;
+
+        strv_uniq(a);
+        r = strv_length(a);
+
+        if (array)
+                *array = a;
+        else
+                strv_free(a);
+
+        return r;
+}
+
+_public_ int sd_uid_get_sessions(uid_t uid, int require_active, char ***sessions) {
+        return uid_get_array(uid, require_active ? "ACTIVE_SESSIONS" : "SESSIONS", sessions);
+}
+
+_public_ int sd_uid_get_seats(uid_t uid, int require_active, char ***seats) {
+        return uid_get_array(uid, require_active ? "ACTIVE_SEATS" : "SEATS", seats);
+}
+
+static int file_of_session(const char *session, char **_p) {
+        char *p;
+        int r;
+
+        assert(_p);
+
+        if (session)
+                p = strappend("/run/systemd/sessions/", session);
+        else {
+                char *buf;
+
+                r = sd_pid_get_session(0, &buf);
+                if (r < 0)
+                        return r;
+
+                p = strappend("/run/systemd/sessions/", buf);
+                free(buf);
+        }
+
+        if (!p)
+                return -ENOMEM;
+
+        *_p = p;
+        return 0;
+}
+
+_public_ int sd_session_is_active(const char *session) {
+        int r;
+        char *p, *s = NULL;
+
+        r = file_of_session(session, &p);
+        if (r < 0)
+                return r;
+
+        r = parse_env_file(p, NEWLINE, "ACTIVE", &s, NULL);
+        free(p);
+
+        if (r < 0) {
+                free(s);
+                return r;
+        }
+
+        if (!s)
+                return -EIO;
+
+        r = parse_boolean(s);
+        free(s);
+
+        return r;
+}
+
+_public_ int sd_session_get_uid(const char *session, uid_t *uid) {
+        int r;
+        char *p, *s = NULL;
+
+        if (!uid)
+                return -EINVAL;
+
+        r = file_of_session(session, &p);
+        if (r < 0)
+                return r;
+
+        r = parse_env_file(p, NEWLINE, "UID", &s, NULL);
+        free(p);
+
+        if (r < 0) {
+                free(s);
+                return r;
+        }
+
+        if (!s)
+                return -EIO;
+
+        r = parse_uid(s, uid);
+        free(s);
+
+        return r;
+}
+
+static int session_get_string(const char *session, const char *field, char **value) {
+        char *p, *s = NULL;
+        int r;
+
+        if (!value)
+                return -EINVAL;
+
+        r = file_of_session(session, &p);
+        if (r < 0)
+                return r;
+
+        r = parse_env_file(p, NEWLINE, field, &s, NULL);
+        free(p);
+
+        if (r < 0) {
+                free(s);
+                return r;
+        }
+
+        if (isempty(s))
+                return -ENOENT;
+
+        *value = s;
+        return 0;
+}
+
+_public_ int sd_session_get_seat(const char *session, char **seat) {
+        return session_get_string(session, "SEAT", seat);
+}
+
+_public_ int sd_session_get_service(const char *session, char **service) {
+        return session_get_string(session, "SERVICE", service);
+}
+
+_public_ int sd_session_get_type(const char *session, char **type) {
+        return session_get_string(session, "TYPE", type);
+}
+
+_public_ int sd_session_get_class(const char *session, char **class) {
+        return session_get_string(session, "CLASS", class);
+}
+
+_public_ int sd_session_get_display(const char *session, char **display) {
+        return session_get_string(session, "DISPLAY", display);
+}
+
+static int file_of_seat(const char *seat, char **_p) {
+        char *p;
+        int r;
+
+        assert(_p);
+
+        if (seat)
+                p = strappend("/run/systemd/seats/", seat);
+        else {
+                char *buf;
+
+                r = sd_session_get_seat(NULL, &buf);
+                if (r < 0)
+                        return r;
+
+                p = strappend("/run/systemd/seats/", buf);
+                free(buf);
+        }
+
+        if (!p)
+                return -ENOMEM;
+
+        *_p = p;
+        return 0;
+}
+
+_public_ int sd_seat_get_active(const char *seat, char **session, uid_t *uid) {
+        char *p, *s = NULL, *t = NULL;
+        int r;
+
+        if (!session && !uid)
+                return -EINVAL;
+
+        r = file_of_seat(seat, &p);
+        if (r < 0)
+                return r;
+
+        r = parse_env_file(p, NEWLINE,
+                           "ACTIVE", &s,
+                           "ACTIVE_UID", &t,
+                           NULL);
+        free(p);
+
+        if (r < 0) {
+                free(s);
+                free(t);
+                return r;
+        }
+
+        if (session && !s)  {
+                free(t);
+                return -ENOENT;
+        }
+
+        if (uid && !t) {
+                free(s);
+                return -ENOENT;
+        }
+
+        if (uid && t) {
+                r = parse_uid(t, uid);
+                if (r < 0) {
+                        free(t);
+                        free(s);
+                        return r;
+                }
+        }
+
+        free(t);
+
+        if (session && s)
+                *session = s;
+        else
+                free(s);
+
+        return 0;
+}
+
+_public_ int sd_seat_get_sessions(const char *seat, char ***sessions, uid_t **uids, unsigned *n_uids) {
+        char *p, *s = NULL, *t = NULL, **a = NULL;
+        uid_t *b = NULL;
+        unsigned n = 0;
+        int r;
+
+        r = file_of_seat(seat, &p);
+        if (r < 0)
+                return r;
+
+        r = parse_env_file(p, NEWLINE,
+                           "SESSIONS", &s,
+                           "ACTIVE_SESSIONS", &t,
+                           NULL);
+        free(p);
+
+        if (r < 0) {
+                free(s);
+                free(t);
+                return r;
+        }
+
+        if (s) {
+                a = strv_split(s, " ");
+                if (!a) {
+                        free(s);
+                        free(t);
+                        return -ENOMEM;
+                }
+        }
+
+        free(s);
+
+        if (uids && t) {
+                char *w, *state;
+                size_t l;
+
+                FOREACH_WORD(w, l, t, state)
+                        n++;
+
+                if (n == 0)
+                        b = NULL;
+                else {
+                        unsigned i = 0;
+
+                        b = new(uid_t, n);
+                        if (!b) {
+                                strv_free(a);
+                                return -ENOMEM;
+                        }
+
+                        FOREACH_WORD(w, l, t, state) {
+                                char *k;
+
+                                k = strndup(w, l);
+                                if (!k) {
+                                        free(t);
+                                        free(b);
+                                        strv_free(a);
+                                        return -ENOMEM;
+                                }
+
+                                r = parse_uid(k, b + i);
+                                free(k);
+                                if (r < 0)
+                                        continue;
+
+                                i++;
+                        }
+                }
+        }
+
+        free(t);
+
+        r = strv_length(a);
+
+        if (sessions)
+                *sessions = a;
+        else
+                strv_free(a);
+
+        if (uids)
+                *uids = b;
+
+        if (n_uids)
+                *n_uids = n;
+
+        return r;
+}
+
+_public_ int sd_seat_can_multi_session(const char *seat) {
+        char *p, *s = NULL;
+        int r;
+
+        r = file_of_seat(seat, &p);
+        if (r < 0)
+                return r;
+
+        r = parse_env_file(p, NEWLINE,
+                           "CAN_MULTI_SESSION", &s,
+                           NULL);
+        free(p);
+
+        if (r < 0) {
+                free(s);
+                return r;
+        }
+
+        if (s) {
+                r = parse_boolean(s);
+                free(s);
+        } else
+                r = 0;
+
+        return r;
+}
+
+_public_ int sd_get_seats(char ***seats) {
+        return get_files_in_directory("/run/systemd/seats/", seats);
+}
+
+_public_ int sd_get_sessions(char ***sessions) {
+        return get_files_in_directory("/run/systemd/sessions/", sessions);
+}
+
+_public_ int sd_get_uids(uid_t **users) {
+        DIR *d;
+        int r = 0;
+        unsigned n = 0;
+        uid_t *l = NULL;
+
+        d = opendir("/run/systemd/users/");
+        if (!d)
+                return -errno;
+
+        for (;;) {
+                struct dirent buffer, *de;
+                int k;
+                uid_t uid;
+
+                k = readdir_r(d, &buffer, &de);
+                if (k != 0) {
+                        r = -k;
+                        goto finish;
+                }
+
+                if (!de)
+                        break;
+
+                dirent_ensure_type(d, de);
+
+                if (!dirent_is_file(de))
+                        continue;
+
+                k = parse_uid(de->d_name, &uid);
+                if (k < 0)
+                        continue;
+
+                if (users) {
+                        if ((unsigned) r >= n) {
+                                uid_t *t;
+
+                                n = MAX(16, 2*r);
+                                t = realloc(l, sizeof(uid_t) * n);
+                                if (!t) {
+                                        r = -ENOMEM;
+                                        goto finish;
+                                }
+
+                                l = t;
+                        }
+
+                        assert((unsigned) r < n);
+                        l[r++] = uid;
+                } else
+                        r++;
+        }
+
+finish:
+        if (d)
+                closedir(d);
+
+        if (r >= 0) {
+                if (users)
+                        *users = l;
+        } else
+                free(l);
+
+        return r;
+}
+
+static inline int MONITOR_TO_FD(sd_login_monitor *m) {
+        return (int) (unsigned long) m - 1;
+}
+
+static inline sd_login_monitor* FD_TO_MONITOR(int fd) {
+        return (sd_login_monitor*) (unsigned long) (fd + 1);
+}
+
+_public_ int sd_login_monitor_new(const char *category, sd_login_monitor **m) {
+        int fd, k;
+        bool good = false;
+
+        if (!m)
+                return -EINVAL;
+
+        fd = inotify_init1(IN_NONBLOCK|IN_CLOEXEC);
+        if (fd < 0)
+                return errno;
+
+        if (!category || streq(category, "seat")) {
+                k = inotify_add_watch(fd, "/run/systemd/seats/", IN_MOVED_TO|IN_DELETE);
+                if (k < 0) {
+                        close_nointr_nofail(fd);
+                        return -errno;
+                }
+
+                good = true;
+        }
+
+        if (!category || streq(category, "session")) {
+                k = inotify_add_watch(fd, "/run/systemd/sessions/", IN_MOVED_TO|IN_DELETE);
+                if (k < 0) {
+                        close_nointr_nofail(fd);
+                        return -errno;
+                }
+
+                good = true;
+        }
+
+        if (!category || streq(category, "uid")) {
+                k = inotify_add_watch(fd, "/run/systemd/users/", IN_MOVED_TO|IN_DELETE);
+                if (k < 0) {
+                        close_nointr_nofail(fd);
+                        return -errno;
+                }
+
+                good = true;
+        }
+
+        if (!good) {
+                close_nointr(fd);
+                return -EINVAL;
+        }
+
+        *m = FD_TO_MONITOR(fd);
+        return 0;
+}
+
+_public_ sd_login_monitor* sd_login_monitor_unref(sd_login_monitor *m) {
+        int fd;
+
+        if (!m)
+                return NULL;
+
+        fd = MONITOR_TO_FD(m);
+        close_nointr(fd);
+
+        return NULL;
+}
+
+_public_ int sd_login_monitor_flush(sd_login_monitor *m) {
+
+        if (!m)
+                return -EINVAL;
+
+        return flush_fd(MONITOR_TO_FD(m));
+}
+
+_public_ int sd_login_monitor_get_fd(sd_login_monitor *m) {
+
+        if (!m)
+                return -EINVAL;
+
+        return MONITOR_TO_FD(m);
+}
diff --git a/src/login/sysfs-show.c b/src/login/sysfs-show.c
new file mode 100644 (file)
index 0000000..b8b356d
--- /dev/null
@@ -0,0 +1,187 @@
+/*-*- 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 <http://www.gnu.org/licenses/>.
+***/
+
+#include <errno.h>
+#include <string.h>
+#include <libudev.h>
+
+#include "util.h"
+#include "sysfs-show.h"
+
+static int show_sysfs_one(
+                struct udev *udev,
+                const char *seat,
+                struct udev_list_entry **item,
+                const char *sub,
+                const char *prefix,
+                unsigned n_columns) {
+
+        assert(udev);
+        assert(seat);
+        assert(item);
+        assert(prefix);
+
+        while (*item) {
+                struct udev_list_entry *next, *lookahead;
+                struct udev_device *d;
+                const char *sn, *name, *sysfs, *subsystem, *sysname;
+                char *l, *k;
+
+                sysfs = udev_list_entry_get_name(*item);
+                if (!path_startswith(sysfs, sub))
+                        return 0;
+
+                d = udev_device_new_from_syspath(udev, sysfs);
+                if (!d) {
+                        *item = udev_list_entry_get_next(*item);
+                        continue;
+                }
+
+                sn = udev_device_get_property_value(d, "ID_SEAT");
+                if (isempty(sn))
+                        sn = "seat0";
+
+                /* fixme, also check for tag 'seat' here */
+                if (!streq(seat, sn) || !udev_device_has_tag(d, "seat")) {
+                        udev_device_unref(d);
+                        *item = udev_list_entry_get_next(*item);
+                        continue;
+                }
+
+                name = udev_device_get_sysattr_value(d, "name");
+                if (!name)
+                        name = udev_device_get_sysattr_value(d, "id");
+                subsystem = udev_device_get_subsystem(d);
+                sysname = udev_device_get_sysname(d);
+
+                /* Look if there's more coming after this */
+                lookahead = next = udev_list_entry_get_next(*item);
+                while (lookahead) {
+                        const char *lookahead_sysfs;
+
+                        lookahead_sysfs = udev_list_entry_get_name(lookahead);
+
+                        if (path_startswith(lookahead_sysfs, sub) &&
+                            !path_startswith(lookahead_sysfs, sysfs)) {
+                                struct udev_device *lookahead_d;
+
+                                lookahead_d = udev_device_new_from_syspath(udev, lookahead_sysfs);
+                                if (lookahead_d) {
+                                        const char *lookahead_sn;
+                                        bool found;
+
+                                        lookahead_sn = udev_device_get_property_value(d, "ID_SEAT");
+                                        if (isempty(lookahead_sn))
+                                                lookahead_sn = "seat0";
+
+                                        found = streq(seat, lookahead_sn) && udev_device_has_tag(lookahead_d, "seat");
+                                        udev_device_unref(lookahead_d);
+
+                                        if (found)
+                                                break;
+                                }
+                        }
+
+                        lookahead = udev_list_entry_get_next(lookahead);
+                }
+
+                k = ellipsize(sysfs, n_columns, 20);
+                printf("%s%s %s\n", prefix, lookahead ? "\342\224\234" : "\342\224\224", k ? k : sysfs);
+                free(k);
+
+                if (asprintf(&l,
+                             "(%s:%s)%s%s%s",
+                             subsystem, sysname,
+                             name ? " \"" : "", name ? name : "", name ? "\"" : "") < 0) {
+                        udev_device_unref(d);
+                        return -ENOMEM;
+                }
+
+                k = ellipsize(l, n_columns, 70);
+                printf("%s%s %s\n", prefix, lookahead ? "\342\224\202" : " ", k ? k : l);
+                free(k);
+                free(l);
+
+                *item = next;
+                if (*item) {
+                        char *p;
+
+                        p = strappend(prefix, lookahead ? "\342\224\202 " : "  ");
+                        show_sysfs_one(udev, seat, item, sysfs, p ? p : prefix, n_columns - 2);
+                        free(p);
+                }
+
+                udev_device_unref(d);
+        }
+
+        return 0;
+}
+
+int show_sysfs(const char *seat, const char *prefix, unsigned n_columns) {
+        struct udev *udev;
+        struct udev_list_entry *first = NULL;
+        struct udev_enumerate *e;
+        int r;
+
+        if (n_columns <= 0)
+                n_columns = columns();
+
+        if (!prefix)
+                prefix = "";
+
+        if (isempty(seat))
+                seat = "seat0";
+
+        udev = udev_new();
+        if (!udev)
+                return -ENOMEM;
+
+        e = udev_enumerate_new(udev);
+        if (!e) {
+                r = -ENOMEM;
+                goto finish;
+        }
+
+        if (!streq(seat, "seat0"))
+                r = udev_enumerate_add_match_tag(e, seat);
+        else
+                r = udev_enumerate_add_match_tag(e, "seat");
+
+        if (r < 0)
+                goto finish;
+
+        r = udev_enumerate_scan_devices(e);
+        if (r < 0)
+                goto finish;
+
+        first = udev_enumerate_get_list_entry(e);
+        if (first)
+                show_sysfs_one(udev, seat, &first, "/", prefix, n_columns);
+
+finish:
+        if (e)
+                udev_enumerate_unref(e);
+
+        if (udev)
+                udev_unref(udev);
+
+        return r;
+}
diff --git a/src/login/test-login.c b/src/login/test-login.c
new file mode 100644 (file)
index 0000000..dd84042
--- /dev/null
@@ -0,0 +1,188 @@
+/*-*- 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 <http://www.gnu.org/licenses/>.
+***/
+
+#include <sys/poll.h>
+#include <string.h>
+
+#include <systemd/sd-login.h>
+
+#include "util.h"
+#include "strv.h"
+
+int main(int argc, char* argv[]) {
+        int r, k;
+        uid_t u, u2;
+        char *seat, *type, *class, *display;
+        char *session;
+        char *state;
+        char *session2;
+        char *t;
+        char **seats, **sessions;
+        uid_t *uids;
+        unsigned n;
+        struct pollfd pollfd;
+        sd_login_monitor *m;
+
+        assert_se(sd_pid_get_session(0, &session) == 0);
+        printf("session = %s\n", session);
+
+        assert_se(sd_pid_get_owner_uid(0, &u2) == 0);
+        printf("user = %lu\n", (unsigned long) u2);
+
+        r = sd_uid_get_sessions(u2, false, &sessions);
+        assert_se(r >= 0);
+        assert_se(r == (int) strv_length(sessions));
+        assert_se(t = strv_join(sessions, ", "));
+        strv_free(sessions);
+        printf("sessions = %s\n", t);
+        free(t);
+
+        assert_se(r == sd_uid_get_sessions(u2, false, NULL));
+
+        r = sd_uid_get_seats(u2, false, &seats);
+        assert_se(r >= 0);
+        assert_se(r == (int) strv_length(seats));
+        assert_se(t = strv_join(seats, ", "));
+        strv_free(seats);
+        printf("seats = %s\n", t);
+        free(t);
+
+        assert_se(r == sd_uid_get_seats(u2, false, NULL));
+
+        r = sd_session_is_active(session);
+        assert_se(r >= 0);
+        printf("active = %s\n", yes_no(r));
+
+        assert_se(sd_session_get_uid(session, &u) >= 0);
+        printf("uid = %lu\n", (unsigned long) u);
+        assert_se(u == u2);
+
+        assert_se(sd_session_get_type(session, &type) >= 0);
+        printf("type = %s\n", type);
+        free(type);
+
+        assert_se(sd_session_get_class(session, &class) >= 0);
+        printf("class = %s\n", class);
+        free(class);
+
+        assert_se(sd_session_get_display(session, &display) >= 0);
+        printf("display = %s\n", display);
+        free(display);
+
+        assert_se(sd_session_get_seat(session, &seat) >= 0);
+        printf("seat = %s\n", seat);
+
+        r = sd_seat_can_multi_session(seat);
+        assert_se(r >= 0);
+        printf("can do multi session = %s\n", yes_no(r));
+
+        assert_se(sd_uid_get_state(u, &state) >= 0);
+        printf("state = %s\n", state);
+
+        assert_se(sd_uid_is_on_seat(u, 0, seat) > 0);
+
+        k = sd_uid_is_on_seat(u, 1, seat);
+        assert_se(k >= 0);
+        assert_se(!!r == !!r);
+
+        assert_se(sd_seat_get_active(seat, &session2, &u2) >= 0);
+        printf("session2 = %s\n", session2);
+        printf("uid2 = %lu\n", (unsigned long) u2);
+
+        r = sd_seat_get_sessions(seat, &sessions, &uids, &n);
+        assert_se(r >= 0);
+        printf("n_sessions = %i\n", r);
+        assert_se(r == (int) strv_length(sessions));
+        assert_se(t = strv_join(sessions, ", "));
+        strv_free(sessions);
+        printf("sessions = %s\n", t);
+        free(t);
+        printf("uids =");
+        for (k = 0; k < (int) n; k++)
+                printf(" %lu", (unsigned long) uids[k]);
+        printf("\n");
+        free(uids);
+
+        assert_se(sd_seat_get_sessions(seat, NULL, NULL, NULL) == r);
+
+        free(session);
+        free(state);
+        free(session2);
+        free(seat);
+
+        r = sd_get_seats(&seats);
+        assert_se(r >= 0);
+        assert_se(r == (int) strv_length(seats));
+        assert_se(t = strv_join(seats, ", "));
+        strv_free(seats);
+        printf("n_seats = %i\n", r);
+        printf("seats = %s\n", t);
+        free(t);
+
+        assert_se(sd_get_seats(NULL) == r);
+
+        r = sd_seat_get_active(NULL, &t, NULL);
+        assert_se(r >= 0);
+        printf("active session on current seat = %s\n", t);
+        free(t);
+
+        r = sd_get_sessions(&sessions);
+        assert_se(r >= 0);
+        assert_se(r == (int) strv_length(sessions));
+        assert_se(t = strv_join(sessions, ", "));
+        strv_free(sessions);
+        printf("n_sessions = %i\n", r);
+        printf("sessions = %s\n", t);
+        free(t);
+
+        assert_se(sd_get_sessions(NULL) == r);
+
+        r = sd_get_uids(&uids);
+        assert_se(r >= 0);
+
+        printf("uids =");
+        for (k = 0; k < r; k++)
+                printf(" %lu", (unsigned long) uids[k]);
+        printf("\n");
+        free(uids);
+
+        printf("n_uids = %i\n", r);
+        assert_se(sd_get_uids(NULL) == r);
+
+        r = sd_login_monitor_new("session", &m);
+        assert_se(r >= 0);
+
+        zero(pollfd);
+        pollfd.fd = sd_login_monitor_get_fd(m);
+        pollfd.events = POLLIN;
+
+        for (n = 0; n < 5; n++) {
+                r = poll(&pollfd, 1, -1);
+                assert_se(r >= 0);
+
+                sd_login_monitor_flush(m);
+                printf("Wake!\n");
+        }
+
+        sd_login_monitor_unref(m);
+
+        return 0;
+}
diff --git a/src/login/uaccess.c b/src/login/uaccess.c
new file mode 100644 (file)
index 0000000..e1af5bf
--- /dev/null
@@ -0,0 +1,91 @@
+/*-*- 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 <http://www.gnu.org/licenses/>.
+***/
+
+#include <errno.h>
+#include <string.h>
+
+#include <systemd/sd-daemon.h>
+#include <systemd/sd-login.h>
+
+#include "logind-acl.h"
+#include "util.h"
+#include "log.h"
+
+int main(int argc, char *argv[]) {
+        int r;
+        const char *path = NULL, *seat;
+        bool changed_acl = false;
+        uid_t uid;
+
+        log_set_target(LOG_TARGET_AUTO);
+        log_parse_environment();
+        log_open();
+
+        umask(0022);
+
+        if (argc < 2 || argc > 3) {
+                log_error("This program expects one or two arguments.");
+                r = -EINVAL;
+                goto finish;
+        }
+
+        /* Make sure we don't muck around with ACLs the system is not
+         * running systemd. */
+        if (!sd_booted())
+                return 0;
+
+        path = argv[1];
+        seat = argc < 3 || isempty(argv[2]) ? "seat0" : argv[2];
+
+        r = sd_seat_get_active(seat, NULL, &uid);
+        if (r == -ENOENT) {
+                /* No active session on this seat */
+                r = 0;
+                goto finish;
+        } else if (r < 0) {
+                log_error("Failed to determine active user on seat %s.", seat);
+                goto finish;
+        }
+
+        r = devnode_acl(path, true, false, 0, true, uid);
+        if (r < 0) {
+                log_error("Failed to apply ACL on %s: %s", path, strerror(-r));
+                goto finish;
+        }
+
+        changed_acl = true;
+        r = 0;
+
+finish:
+        if (path && !changed_acl) {
+                int k;
+                /* Better be safe that sorry and reset ACL */
+
+                k = devnode_acl(path, true, false, 0, false, 0);
+                if (k < 0) {
+                        log_error("Failed to apply ACL on %s: %s", path, strerror(-k));
+                        if (r >= 0)
+                                r = k;
+                }
+        }
+
+        return r < 0 ? EXIT_FAILURE : EXIT_SUCCESS;
+}
diff --git a/src/login/user-sessions.c b/src/login/user-sessions.c
new file mode 100644 (file)
index 0000000..64aa3bb
--- /dev/null
@@ -0,0 +1,100 @@
+/*-*- 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 <http://www.gnu.org/licenses/>.
+***/
+
+#include <string.h>
+#include <unistd.h>
+#include <errno.h>
+
+#include "log.h"
+#include "util.h"
+#include "cgroup-util.h"
+
+int main(int argc, char*argv[]) {
+        int ret = EXIT_FAILURE;
+
+        if (argc != 2) {
+                log_error("This program requires one argument.");
+                return EXIT_FAILURE;
+        }
+
+        log_set_target(LOG_TARGET_AUTO);
+        log_parse_environment();
+        log_open();
+
+        umask(0022);
+
+        if (streq(argv[1], "start")) {
+                int q = 0, r = 0;
+
+                if (unlink("/run/nologin") < 0 && errno != ENOENT) {
+                        log_error("Failed to remove /run/nologin file: %m");
+                        r = -errno;
+                }
+
+                if (unlink("/etc/nologin") < 0 && errno != ENOENT) {
+
+                        /* If the file doesn't exist and /etc simply
+                         * was read-only (in which case unlink()
+                         * returns EROFS even if the file doesn't
+                         * exist), don't complain */
+
+                        if (errno != EROFS || access("/etc/nologin", F_OK) >= 0) {
+                                log_error("Failed to remove /etc/nologin file: %m");
+                                q = -errno;
+                        }
+                }
+
+                if (r < 0 || q < 0)
+                        goto finish;
+
+        } else if (streq(argv[1], "stop")) {
+                int r, q;
+                char *cgroup_user_tree = NULL;
+
+                if ((r = write_one_line_file_atomic("/run/nologin", "System is going down.")) < 0)
+                        log_error("Failed to create /run/nologin: %s", strerror(-r));
+
+                if ((q = cg_get_user_path(&cgroup_user_tree)) < 0) {
+                        log_error("Failed to determine use path: %s", strerror(-q));
+                        goto finish;
+                }
+
+                q = cg_kill_recursive_and_wait(SYSTEMD_CGROUP_CONTROLLER, cgroup_user_tree, true);
+                free(cgroup_user_tree);
+
+                if (q < 0) {
+                        log_error("Failed to kill sessions: %s", strerror(-q));
+                        goto finish;
+                }
+
+                if (r < 0)
+                        goto finish;
+
+        } else {
+                log_error("Unknown verb %s.", argv[1]);
+                goto finish;
+        }
+
+        ret = EXIT_SUCCESS;
+
+finish:
+        return ret;
+}
diff --git a/src/logs-show.c b/src/logs-show.c
new file mode 100644 (file)
index 0000000..0a07a77
--- /dev/null
@@ -0,0 +1,664 @@
+/*-*- Mode: C; c-basic-offset: 8; indent-tabs-mode: nil -*-*/
+
+/***
+  This file is part of systemd.
+
+  Copyright 2012 Lennart Poettering
+
+  systemd is free software; you can redistribute it and/or modify it
+  under the terms of the GNU 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 <http://www.gnu.org/licenses/>.
+***/
+
+#include <time.h>
+#include <assert.h>
+#include <errno.h>
+#include <sys/poll.h>
+#include <string.h>
+
+#include "logs-show.h"
+#include "log.h"
+#include "util.h"
+
+#define PRINT_THRESHOLD 128
+
+static bool contains_unprintable(const void *p, size_t l) {
+        const char *j;
+
+        for (j = p; j < (const char *) p + l; j++)
+                if (*j < ' ' || *j >= 127)
+                        return true;
+
+        return false;
+}
+
+static int parse_field(const void *data, size_t length, const char *field, char **target, size_t *target_size) {
+        size_t fl, nl;
+        void *buf;
+
+        assert(data);
+        assert(field);
+        assert(target);
+        assert(target_size);
+
+        fl = strlen(field);
+        if (length < fl)
+                return 0;
+
+        if (memcmp(data, field, fl))
+                return 0;
+
+        nl = length - fl;
+        buf = malloc(nl+1);
+        memcpy(buf, (const char*) data + fl, nl);
+        ((char*)buf)[nl] = 0;
+        if (!buf) {
+                log_error("Out of memory");
+                return -ENOMEM;
+        }
+
+        free(*target);
+        *target = buf;
+        *target_size = nl;
+
+        return 1;
+}
+
+static bool shall_print(bool show_all, char *p, size_t l) {
+        if (show_all)
+                return true;
+
+        if (l > PRINT_THRESHOLD)
+                return false;
+
+        if (contains_unprintable(p, l))
+                return false;
+
+        return true;
+}
+
+static int output_short(sd_journal *j, unsigned line, unsigned n_columns, bool show_all, bool monotonic_mode) {
+        int r;
+        const void *data;
+        size_t length;
+        size_t n = 0;
+        char *hostname = NULL, *identifier = NULL, *comm = NULL, *pid = NULL, *fake_pid = NULL, *message = NULL, *realtime = NULL, *monotonic = NULL;
+        size_t hostname_len = 0, identifier_len = 0, comm_len = 0, pid_len = 0, fake_pid_len = 0, message_len = 0, realtime_len = 0, monotonic_len = 0;
+
+        assert(j);
+
+        SD_JOURNAL_FOREACH_DATA(j, data, length) {
+
+                r = parse_field(data, length, "_HOSTNAME=", &hostname, &hostname_len);
+                if (r < 0)
+                        goto finish;
+                else if (r > 0)
+                        continue;
+
+                r = parse_field(data, length, "SYSLOG_IDENTIFIER=", &identifier, &identifier_len);
+                if (r < 0)
+                        goto finish;
+                else if (r > 0)
+                        continue;
+
+                r = parse_field(data, length, "_COMM=", &comm, &comm_len);
+                if (r < 0)
+                        goto finish;
+                else if (r > 0)
+                        continue;
+
+                r = parse_field(data, length, "_PID=", &pid, &pid_len);
+                if (r < 0)
+                        goto finish;
+                else if (r > 0)
+                        continue;
+
+                r = parse_field(data, length, "SYSLOG_PID=", &fake_pid, &fake_pid_len);
+                if (r < 0)
+                        goto finish;
+                else if (r > 0)
+                        continue;
+
+                r = parse_field(data, length, "_SOURCE_REALTIME_TIMESTAMP=", &realtime, &realtime_len);
+                if (r < 0)
+                        goto finish;
+                else if (r > 0)
+                        continue;
+
+                r = parse_field(data, length, "_SOURCE_MONOTONIC_TIMESTAMP=", &monotonic, &monotonic_len);
+                if (r < 0)
+                        goto finish;
+                else if (r > 0)
+                        continue;
+
+                r = parse_field(data, length, "MESSAGE=", &message, &message_len);
+                if (r < 0)
+                        goto finish;
+        }
+
+        if (!message) {
+                r = 0;
+                goto finish;
+        }
+
+        if (monotonic_mode) {
+                uint64_t t;
+                sd_id128_t boot_id;
+
+                r = -ENOENT;
+
+                if (monotonic)
+                        r = safe_atou64(monotonic, &t);
+
+                if (r < 0)
+                        r = sd_journal_get_monotonic_usec(j, &t, &boot_id);
+
+                if (r < 0) {
+                        log_error("Failed to get monotonic: %s", strerror(-r));
+                        goto finish;
+                }
+
+                printf("[%5llu.%06llu]",
+                       (unsigned long long) (t / USEC_PER_SEC),
+                       (unsigned long long) (t % USEC_PER_SEC));
+
+                n += 1 + 5 + 1 + 6 + 1;
+
+        } else {
+                char buf[64];
+                uint64_t x;
+                time_t t;
+                struct tm tm;
+
+                r = -ENOENT;
+
+                if (realtime)
+                        r = safe_atou64(realtime, &x);
+
+                if (r < 0)
+                        r = sd_journal_get_realtime_usec(j, &x);
+
+                if (r < 0) {
+                        log_error("Failed to get realtime: %s", strerror(-r));
+                        goto finish;
+                }
+
+                t = (time_t) (x / USEC_PER_SEC);
+                if (strftime(buf, sizeof(buf), "%b %d %H:%M:%S", localtime_r(&t, &tm)) <= 0) {
+                        log_error("Failed to format time.");
+                        goto finish;
+                }
+
+                fputs(buf, stdout);
+                n += strlen(buf);
+        }
+
+        if (hostname && shall_print(show_all, hostname, hostname_len)) {
+                printf(" %.*s", (int) hostname_len, hostname);
+                n += hostname_len + 1;
+        }
+
+        if (identifier && shall_print(show_all, identifier, identifier_len)) {
+                printf(" %.*s", (int) identifier_len, identifier);
+                n += identifier_len + 1;
+        } else if (comm && shall_print(show_all, comm, comm_len)) {
+                printf(" %.*s", (int) comm_len, comm);
+                n += comm_len + 1;
+        }
+
+        if (pid && shall_print(show_all, pid, pid_len)) {
+                printf("[%.*s]", (int) pid_len, pid);
+                n += pid_len + 2;
+        } else if (fake_pid && shall_print(show_all, fake_pid, fake_pid_len)) {
+                printf("[%.*s]", (int) fake_pid_len, fake_pid);
+                n += fake_pid_len + 2;
+        }
+
+        if (show_all)
+                printf(": %.*s\n", (int) message_len, message);
+        else if (contains_unprintable(message, message_len)) {
+                char bytes[FORMAT_BYTES_MAX];
+                printf(": [%s blob data]\n", format_bytes(bytes, sizeof(bytes), message_len));
+        } else if (message_len + n < n_columns)
+                printf(": %.*s\n", (int) message_len, message);
+        else if (n < n_columns) {
+                char *e;
+
+                e = ellipsize_mem(message, message_len, n_columns - n - 2, 90);
+
+                if (!e)
+                        printf(": %.*s\n", (int) message_len, message);
+                else
+                        printf(": %s\n", e);
+
+                free(e);
+        } else
+                fputs("\n", stdout);
+
+        r = 0;
+
+finish:
+        free(hostname);
+        free(identifier);
+        free(comm);
+        free(pid);
+        free(fake_pid);
+        free(message);
+        free(monotonic);
+        free(realtime);
+
+        return r;
+}
+
+static int output_short_realtime(sd_journal *j, unsigned line, unsigned n_columns, bool show_all) {
+        return output_short(j, line, n_columns, show_all, false);
+}
+
+static int output_short_monotonic(sd_journal *j, unsigned line, unsigned n_columns, bool show_all) {
+        return output_short(j, line, n_columns, show_all, true);
+}
+
+static int output_verbose(sd_journal *j, unsigned line, unsigned n_columns, bool show_all) {
+        const void *data;
+        size_t length;
+        char *cursor;
+        uint64_t realtime;
+        char ts[FORMAT_TIMESTAMP_MAX];
+        int r;
+
+        assert(j);
+
+        r = sd_journal_get_realtime_usec(j, &realtime);
+        if (r < 0) {
+                log_error("Failed to get realtime timestamp: %s", strerror(-r));
+                return r;
+        }
+
+        r = sd_journal_get_cursor(j, &cursor);
+        if (r < 0) {
+                log_error("Failed to get cursor: %s", strerror(-r));
+                return r;
+        }
+
+        printf("%s [%s]\n",
+               format_timestamp(ts, sizeof(ts), realtime),
+               cursor);
+
+        free(cursor);
+
+        SD_JOURNAL_FOREACH_DATA(j, data, length) {
+                if (!show_all && (length > PRINT_THRESHOLD ||
+                                  contains_unprintable(data, length))) {
+                        const char *c;
+                        char bytes[FORMAT_BYTES_MAX];
+
+                        c = memchr(data, '=', length);
+                        if (!c) {
+                                log_error("Invalid field.");
+                                return -EINVAL;
+                        }
+
+                        printf("\t%.*s=[%s blob data]\n",
+                               (int) (c - (const char*) data),
+                               (const char*) data,
+                               format_bytes(bytes, sizeof(bytes), length - (c - (const char *) data) - 1));
+                } else
+                        printf("\t%.*s\n", (int) length, (const char*) data);
+        }
+
+        return 0;
+}
+
+static int output_export(sd_journal *j, unsigned line, unsigned n_columns, bool show_all) {
+        sd_id128_t boot_id;
+        char sid[33];
+        int r;
+        usec_t realtime, monotonic;
+        char *cursor;
+        const void *data;
+        size_t length;
+
+        assert(j);
+
+        r = sd_journal_get_realtime_usec(j, &realtime);
+        if (r < 0) {
+                log_error("Failed to get realtime timestamp: %s", strerror(-r));
+                return r;
+        }
+
+        r = sd_journal_get_monotonic_usec(j, &monotonic, &boot_id);
+        if (r < 0) {
+                log_error("Failed to get monotonic timestamp: %s", strerror(-r));
+                return r;
+        }
+
+        r = sd_journal_get_cursor(j, &cursor);
+        if (r < 0) {
+                log_error("Failed to get cursor: %s", strerror(-r));
+                return r;
+        }
+
+        printf("__CURSOR=%s\n"
+               "__REALTIME=%llu\n"
+               "__MONOTONIC=%llu\n"
+               "__BOOT_ID=%s\n",
+               cursor,
+               (unsigned long long) realtime,
+               (unsigned long long) monotonic,
+               sd_id128_to_string(boot_id, sid));
+
+        free(cursor);
+
+        SD_JOURNAL_FOREACH_DATA(j, data, length) {
+
+                if (contains_unprintable(data, length)) {
+                        const char *c;
+                        uint64_t le64;
+
+                        c = memchr(data, '=', length);
+                        if (!c) {
+                                log_error("Invalid field.");
+                                return -EINVAL;
+                        }
+
+                        fwrite(data, c - (const char*) data, 1, stdout);
+                        fputc('\n', stdout);
+                        le64 = htole64(length - (c - (const char*) data) - 1);
+                        fwrite(&le64, sizeof(le64), 1, stdout);
+                        fwrite(c + 1, length - (c - (const char*) data) - 1, 1, stdout);
+                } else
+                        fwrite(data, length, 1, stdout);
+
+                fputc('\n', stdout);
+        }
+
+        fputc('\n', stdout);
+
+        return 0;
+}
+
+static void json_escape(const char* p, size_t l) {
+
+        if (contains_unprintable(p, l)) {
+                bool not_first = false;
+
+                fputs("[ ", stdout);
+
+                while (l > 0) {
+                        if (not_first)
+                                printf(", %u", (uint8_t) *p);
+                        else {
+                                not_first = true;
+                                printf("%u", (uint8_t) *p);
+                        }
+
+                        p++;
+                        l--;
+                }
+
+                fputs(" ]", stdout);
+        } else {
+                fputc('\"', stdout);
+
+                while (l > 0) {
+                        if (*p == '"' || *p == '\\') {
+                                fputc('\\', stdout);
+                                fputc(*p, stdout);
+                        } else
+                                fputc(*p, stdout);
+
+                        p++;
+                        l--;
+                }
+
+                fputc('\"', stdout);
+        }
+}
+
+static int output_json(sd_journal *j, unsigned line, unsigned n_columns, bool show_all) {
+        uint64_t realtime, monotonic;
+        char *cursor;
+        const void *data;
+        size_t length;
+        sd_id128_t boot_id;
+        char sid[33];
+        int r;
+
+        assert(j);
+
+        r = sd_journal_get_realtime_usec(j, &realtime);
+        if (r < 0) {
+                log_error("Failed to get realtime timestamp: %s", strerror(-r));
+                return r;
+        }
+
+        r = sd_journal_get_monotonic_usec(j, &monotonic, &boot_id);
+        if (r < 0) {
+                log_error("Failed to get monotonic timestamp: %s", strerror(-r));
+                return r;
+        }
+
+        r = sd_journal_get_cursor(j, &cursor);
+        if (r < 0) {
+                log_error("Failed to get cursor: %s", strerror(-r));
+                return r;
+        }
+
+        if (line == 1)
+                fputc('\n', stdout);
+        else
+                fputs(",\n", stdout);
+
+        printf("{\n"
+               "\t\"__CURSOR\" : \"%s\",\n"
+               "\t\"__REALTIME\" : \"%llu\",\n"
+               "\t\"__MONOTONIC\" : \"%llu\",\n"
+               "\t\"__BOOT_ID\" : \"%s\"",
+               cursor,
+               (unsigned long long) realtime,
+               (unsigned long long) monotonic,
+               sd_id128_to_string(boot_id, sid));
+
+        free(cursor);
+
+        SD_JOURNAL_FOREACH_DATA(j, data, length) {
+                const char *c;
+
+                c = memchr(data, '=', length);
+                if (!c) {
+                        log_error("Invalid field.");
+                        return -EINVAL;
+                }
+
+                fputs(",\n\t", stdout);
+                json_escape(data, c - (const char*) data);
+                fputs(" : ", stdout);
+                json_escape(c + 1, length - (c - (const char*) data) - 1);
+        }
+
+        fputs("\n}", stdout);
+        fflush(stdout);
+
+        return 0;
+}
+
+static int output_cat(sd_journal *j, unsigned line, unsigned n_columns, bool show_all) {
+        const void *data;
+        size_t l;
+        int r;
+
+        assert(j);
+
+        r = sd_journal_get_data(j, "MESSAGE", &data, &l);
+        if (r < 0) {
+                log_error("Failed to get data: %s", strerror(-r));
+                return r;
+        }
+
+        assert(l >= 8);
+
+        fwrite((const char*) data + 8, 1, l - 8, stdout);
+        putchar('\n');
+
+        return 0;
+}
+
+static int (*output_funcs[_OUTPUT_MODE_MAX])(sd_journal*j, unsigned line, unsigned n_columns, bool show_all) = {
+        [OUTPUT_SHORT] = output_short_realtime,
+        [OUTPUT_SHORT_MONOTONIC] = output_short_monotonic,
+        [OUTPUT_VERBOSE] = output_verbose,
+        [OUTPUT_EXPORT] = output_export,
+        [OUTPUT_JSON] = output_json,
+        [OUTPUT_CAT] = output_cat
+};
+
+int output_journal(sd_journal *j, OutputMode mode, unsigned line, unsigned n_columns, bool show_all) {
+        assert(mode >= 0);
+        assert(mode < _OUTPUT_MODE_MAX);
+
+        if (n_columns <= 0)
+                n_columns = columns();
+
+        return output_funcs[mode](j, line, n_columns, show_all);
+}
+
+int show_journal_by_unit(
+                const char *unit,
+                OutputMode mode,
+                unsigned n_columns,
+                usec_t not_before,
+                unsigned how_many,
+                bool show_all,
+                bool follow) {
+
+        char *m = NULL;
+        sd_journal *j;
+        int r;
+        int fd;
+        unsigned line = 0;
+        bool need_seek = false;
+
+        assert(mode >= 0);
+        assert(mode < _OUTPUT_MODE_MAX);
+        assert(unit);
+
+        if (!endswith(unit, ".service") &&
+            !endswith(unit, ".socket") &&
+            !endswith(unit, ".mount") &&
+            !endswith(unit, ".swap"))
+                return 0;
+
+        if (how_many <= 0)
+                return 0;
+
+        if (asprintf(&m, "_SYSTEMD_UNIT=%s", unit) < 0) {
+                r = -ENOMEM;
+                goto finish;
+        }
+
+        r = sd_journal_open(&j, SD_JOURNAL_LOCAL_ONLY|SD_JOURNAL_SYSTEM_ONLY);
+        if (r < 0)
+                goto finish;
+
+        fd = sd_journal_get_fd(j);
+        if (fd < 0)
+                goto finish;
+
+        r = sd_journal_add_match(j, m, strlen(m));
+        if (r < 0)
+                goto finish;
+
+        r = sd_journal_seek_tail(j);
+        if (r < 0)
+                goto finish;
+
+        r = sd_journal_previous_skip(j, how_many);
+        if (r < 0)
+                goto finish;
+
+        if (mode == OUTPUT_JSON) {
+                fputc('[', stdout);
+                fflush(stdout);
+        }
+
+        for (;;) {
+                for (;;) {
+                        usec_t usec;
+
+                        if (need_seek) {
+                                r = sd_journal_next(j);
+                                if (r < 0)
+                                        goto finish;
+                        }
+
+                        if (r == 0)
+                                break;
+
+                        need_seek = true;
+
+                        if (not_before > 0) {
+                                r = sd_journal_get_monotonic_usec(j, &usec, NULL);
+
+                                /* -ESTALE is returned if the
+                                   timestamp is not from this boot */
+                                if (r == -ESTALE)
+                                        continue;
+                                else if (r < 0)
+                                        goto finish;
+
+                                if (usec < not_before)
+                                        continue;
+                        }
+
+                        line ++;
+
+                        r = output_journal(j, mode, line, n_columns, show_all);
+                        if (r < 0)
+                                goto finish;
+                }
+
+                if (!follow)
+                        break;
+
+                r = fd_wait_for_event(fd, POLLIN, (usec_t) -1);
+                if (r < 0)
+                        goto finish;
+
+                r = sd_journal_process(j);
+                if (r < 0)
+                        goto finish;
+
+        }
+
+        if (mode == OUTPUT_JSON)
+                fputs("\n]\n", stdout);
+
+finish:
+        if (m)
+                free(m);
+
+        if (j)
+                sd_journal_close(j);
+
+        return r;
+}
+
+static const char *const output_mode_table[_OUTPUT_MODE_MAX] = {
+        [OUTPUT_SHORT] = "short",
+        [OUTPUT_SHORT_MONOTONIC] = "short-monotonic",
+        [OUTPUT_VERBOSE] = "verbose",
+        [OUTPUT_EXPORT] = "export",
+        [OUTPUT_JSON] = "json",
+        [OUTPUT_CAT] = "cat"
+};
+
+DEFINE_STRING_TABLE_LOOKUP(output_mode, OutputMode);
diff --git a/src/logs-show.h b/src/logs-show.h
new file mode 100644 (file)
index 0000000..db9c7e3
--- /dev/null
@@ -0,0 +1,56 @@
+/*-*- Mode: C; c-basic-offset: 8; indent-tabs-mode: nil -*-*/
+
+#ifndef foologsshowhfoo
+#define foologsshowhfoo
+
+/***
+  This file is part of systemd.
+
+  Copyright 2012 Lennart Poettering
+
+  systemd is free software; you can redistribute it and/or modify it
+  under the terms of the GNU 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 <http://www.gnu.org/licenses/>.
+***/
+
+#include <stdbool.h>
+
+#include <systemd/sd-journal.h>
+
+#include "util.h"
+
+typedef enum OutputMode {
+        OUTPUT_SHORT,
+        OUTPUT_SHORT_MONOTONIC,
+        OUTPUT_VERBOSE,
+        OUTPUT_EXPORT,
+        OUTPUT_JSON,
+        OUTPUT_CAT,
+        _OUTPUT_MODE_MAX,
+        _OUTPUT_MODE_INVALID = -1
+} OutputMode;
+
+int output_journal(sd_journal *j, OutputMode mode, unsigned line, unsigned n_columns, bool show_all);
+
+int show_journal_by_unit(
+                const char *unit,
+                OutputMode mode,
+                unsigned n_columns,
+                usec_t not_before,
+                unsigned how_many,
+                bool show_all,
+                bool follow);
+
+const char* output_mode_to_string(OutputMode m);
+OutputMode output_mode_from_string(const char *s);
+
+#endif
diff --git a/src/loopback-setup.c b/src/loopback-setup.c
new file mode 100644 (file)
index 0000000..b6359de
--- /dev/null
@@ -0,0 +1,274 @@
+/*-*- 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 <http://www.gnu.org/licenses/>.
+***/
+
+#include <errno.h>
+#include <sys/socket.h>
+#include <net/if.h>
+#include <asm/types.h>
+#include <netinet/in.h>
+#include <string.h>
+#include <stdlib.h>
+#include <unistd.h>
+#include <linux/netlink.h>
+#include <linux/rtnetlink.h>
+
+#include "util.h"
+#include "macro.h"
+#include "loopback-setup.h"
+#include "socket-util.h"
+
+#define NLMSG_TAIL(nmsg)                                                \
+        ((struct rtattr *) (((uint8_t*) (nmsg)) + NLMSG_ALIGN((nmsg)->nlmsg_len)))
+
+static int add_rtattr(struct nlmsghdr *n, size_t max_length, int type, const void *data, size_t data_length) {
+        size_t length;
+        struct rtattr *rta;
+
+        length = RTA_LENGTH(data_length);
+
+        if (NLMSG_ALIGN(n->nlmsg_len) + RTA_ALIGN(length) > max_length)
+                return -E2BIG;
+
+        rta = NLMSG_TAIL(n);
+        rta->rta_type = type;
+        rta->rta_len = length;
+        memcpy(RTA_DATA(rta), data, data_length);
+        n->nlmsg_len = NLMSG_ALIGN(n->nlmsg_len) + RTA_ALIGN(length);
+
+        return 0;
+}
+
+static ssize_t sendto_loop(int fd, const void *buf, size_t buf_len, int flags, const struct sockaddr *sa, socklen_t sa_len) {
+
+        for (;;) {
+                ssize_t l;
+
+                if ((l = sendto(fd, buf, buf_len, flags, sa, sa_len)) >= 0)
+                        return l;
+
+                if (errno != EINTR)
+                        return -errno;
+        }
+}
+
+static ssize_t recvfrom_loop(int fd, void *buf, size_t buf_len, int flags, struct sockaddr *sa, socklen_t *sa_len) {
+
+        for (;;) {
+                ssize_t l;
+
+                if ((l = recvfrom(fd, buf, buf_len, flags, sa, sa_len)) >= 0)
+                        return l;
+
+                if (errno != EINTR)
+                        return -errno;
+        }
+}
+
+static int add_adresses(int fd, int if_loopback, unsigned *requests) {
+        union {
+                struct sockaddr sa;
+                struct sockaddr_nl nl;
+        } sa;
+        union {
+                struct nlmsghdr header;
+                uint8_t buf[NLMSG_ALIGN(sizeof(struct nlmsghdr)) +
+                            NLMSG_ALIGN(sizeof(struct ifaddrmsg)) +
+                            RTA_LENGTH(sizeof(struct in6_addr))];
+        } request;
+
+        struct ifaddrmsg *ifaddrmsg;
+        uint32_t ipv4_address = htonl(INADDR_LOOPBACK);
+        int r;
+
+        zero(request);
+
+        request.header.nlmsg_len = NLMSG_LENGTH(sizeof(struct ifaddrmsg));
+        request.header.nlmsg_type = RTM_NEWADDR;
+        request.header.nlmsg_flags = NLM_F_REQUEST|NLM_F_CREATE|NLM_F_ACK;
+        request.header.nlmsg_seq = *requests + 1;
+
+        ifaddrmsg = NLMSG_DATA(&request.header);
+        ifaddrmsg->ifa_family = AF_INET;
+        ifaddrmsg->ifa_prefixlen = 8;
+        ifaddrmsg->ifa_flags = IFA_F_PERMANENT;
+        ifaddrmsg->ifa_scope = RT_SCOPE_HOST;
+        ifaddrmsg->ifa_index = if_loopback;
+
+        if ((r = add_rtattr(&request.header, sizeof(request), IFA_LOCAL, &ipv4_address, sizeof(ipv4_address))) < 0)
+                return r;
+
+        zero(sa);
+        sa.nl.nl_family = AF_NETLINK;
+
+        if (sendto_loop(fd, &request, request.header.nlmsg_len, 0, &sa.sa, sizeof(sa)) < 0)
+                return -errno;
+        (*requests)++;
+
+        if (!socket_ipv6_is_supported())
+                return 0;
+
+        request.header.nlmsg_len = NLMSG_LENGTH(sizeof(struct ifaddrmsg));
+        request.header.nlmsg_seq = *requests + 1;
+
+        ifaddrmsg->ifa_family = AF_INET6;
+        ifaddrmsg->ifa_prefixlen = 128;
+
+        if ((r = add_rtattr(&request.header, sizeof(request), IFA_LOCAL, &in6addr_loopback, sizeof(in6addr_loopback))) < 0)
+                return r;
+
+        if (sendto_loop(fd, &request, request.header.nlmsg_len, 0, &sa.sa, sizeof(sa)) < 0)
+                return -errno;
+        (*requests)++;
+
+        return 0;
+}
+
+static int start_interface(int fd, int if_loopback, unsigned *requests) {
+        union {
+                struct sockaddr sa;
+                struct sockaddr_nl nl;
+        } sa;
+        union {
+                struct nlmsghdr header;
+                uint8_t buf[NLMSG_ALIGN(sizeof(struct nlmsghdr)) +
+                            NLMSG_ALIGN(sizeof(struct ifinfomsg))];
+        } request;
+
+        struct ifinfomsg *ifinfomsg;
+
+        zero(request);
+
+        request.header.nlmsg_len = NLMSG_LENGTH(sizeof(struct ifinfomsg));
+        request.header.nlmsg_type = RTM_NEWLINK;
+        request.header.nlmsg_flags = NLM_F_REQUEST|NLM_F_ACK;
+        request.header.nlmsg_seq = *requests + 1;
+
+        ifinfomsg = NLMSG_DATA(&request.header);
+        ifinfomsg->ifi_family = AF_UNSPEC;
+        ifinfomsg->ifi_index = if_loopback;
+        ifinfomsg->ifi_flags = IFF_UP;
+        ifinfomsg->ifi_change = IFF_UP;
+
+        zero(sa);
+        sa.nl.nl_family = AF_NETLINK;
+
+        if (sendto_loop(fd, &request, request.header.nlmsg_len, 0, &sa.sa, sizeof(sa)) < 0)
+                return -errno;
+
+        (*requests)++;
+
+        return 0;
+}
+
+static int read_response(int fd, unsigned requests_max) {
+        union {
+                struct sockaddr sa;
+                struct sockaddr_nl nl;
+        } sa;
+        union {
+                struct nlmsghdr header;
+                uint8_t buf[NLMSG_ALIGN(sizeof(struct nlmsghdr)) +
+                            NLMSG_ALIGN(sizeof(struct nlmsgerr))];
+        } response;
+
+        ssize_t l;
+        socklen_t sa_len = sizeof(sa);
+        struct nlmsgerr *nlmsgerr;
+
+        if ((l = recvfrom_loop(fd, &response, sizeof(response), 0, &sa.sa, &sa_len)) < 0)
+                return -errno;
+
+        if (sa_len != sizeof(sa.nl) ||
+            sa.nl.nl_family != AF_NETLINK)
+                return -EIO;
+
+        if (sa.nl.nl_pid != 0)
+                return 0;
+
+        if ((size_t) l < sizeof(struct nlmsghdr))
+                return -EIO;
+
+        if (response.header.nlmsg_type != NLMSG_ERROR ||
+            (pid_t) response.header.nlmsg_pid != getpid() ||
+            response.header.nlmsg_seq >= requests_max)
+                return 0;
+
+        if ((size_t) l < NLMSG_LENGTH(sizeof(struct nlmsgerr)) ||
+            response.header.nlmsg_len < NLMSG_LENGTH(sizeof(struct nlmsgerr)))
+                return -EIO;
+
+        nlmsgerr = NLMSG_DATA(&response.header);
+
+        if (nlmsgerr->error < 0 && nlmsgerr->error != -EEXIST) {
+                log_warning("Netlink failure for request %i: %s", response.header.nlmsg_seq, strerror(-nlmsgerr->error));
+                return nlmsgerr->error;
+        }
+
+        return response.header.nlmsg_seq;
+}
+
+int loopback_setup(void) {
+        int r, if_loopback;
+        union {
+                struct sockaddr sa;
+                struct sockaddr_nl nl;
+                struct sockaddr_storage storage;
+        } sa;
+        unsigned requests = 0, i;
+        int fd;
+
+        errno = 0;
+        if ((if_loopback = (int) if_nametoindex("lo")) <= 0)
+                return errno ? -errno : -ENODEV;
+
+        if ((fd = socket(PF_NETLINK, SOCK_RAW, NETLINK_ROUTE)) < 0)
+                return -errno;
+
+        zero(sa);
+        sa.nl.nl_family = AF_NETLINK;
+
+        if (bind(fd, &sa.sa, sizeof(sa)) < 0) {
+                r = -errno;
+                goto finish;
+        }
+
+        if ((r = add_adresses(fd, if_loopback, &requests)) < 0)
+                goto finish;
+
+        if ((r = start_interface(fd, if_loopback, &requests)) < 0)
+                goto finish;
+
+        for (i = 0; i < requests; i++) {
+                if ((r = read_response(fd, requests)) < 0)
+                        goto finish;
+        }
+
+        r = 0;
+
+finish:
+        if (r < 0)
+                log_warning("Failed to configure loopback device: %s", strerror(-r));
+
+        if (fd >= 0)
+                close_nointr_nofail(fd);
+
+        return r;
+}
diff --git a/src/loopback-setup.h b/src/loopback-setup.h
new file mode 100644 (file)
index 0000000..81f4529
--- /dev/null
@@ -0,0 +1,27 @@
+/*-*- Mode: C; c-basic-offset: 8; indent-tabs-mode: nil -*-*/
+
+#ifndef fooloopbacksetuphfoo
+#define fooloopbacksetuphfoo
+
+/***
+  This file is part 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 <http://www.gnu.org/licenses/>.
+***/
+
+int loopback_setup(void);
+
+#endif
diff --git a/src/machine-id-main.c b/src/machine-id-main.c
new file mode 100644 (file)
index 0000000..03970a2
--- /dev/null
@@ -0,0 +1,35 @@
+/*-*- 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 <http://www.gnu.org/licenses/>.
+***/
+
+#include <unistd.h>
+#include <stdlib.h>
+
+#include "machine-id-setup.h"
+#include "log.h"
+
+int main(int argc, char *argv[]) {
+
+        log_set_target(LOG_TARGET_AUTO);
+        log_parse_environment();
+        log_open();
+
+        return machine_id_setup() < 0 ? EXIT_FAILURE : EXIT_SUCCESS;
+}
diff --git a/src/machine-id-setup.c b/src/machine-id-setup.c
new file mode 100644 (file)
index 0000000..0f97433
--- /dev/null
@@ -0,0 +1,267 @@
+/*-*- 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 <http://www.gnu.org/licenses/>.
+***/
+
+#include <unistd.h>
+#include <stdio.h>
+#include <errno.h>
+#include <string.h>
+#include <stdlib.h>
+#include <fcntl.h>
+#include <sys/mount.h>
+
+#include <systemd/sd-id128.h>
+
+#include "machine-id-setup.h"
+#include "macro.h"
+#include "util.h"
+#include "log.h"
+#include "virt.h"
+
+static int shorten_uuid(char destination[36], const char *source) {
+        unsigned i, j;
+
+        for (i = 0, j = 0; i < 36 && j < 32; i++) {
+                int t;
+
+                t = unhexchar(source[i]);
+                if (t < 0)
+                        continue;
+
+                destination[j++] = hexchar(t);
+        }
+
+        if (i == 36 && j == 32) {
+                destination[32] = '\n';
+                destination[33] = 0;
+                return 0;
+        }
+
+        return -EINVAL;
+}
+
+static int generate(char id[34]) {
+        int fd, r;
+        unsigned char *p;
+        sd_id128_t buf;
+        char *q;
+        ssize_t k;
+        const char *vm_id;
+
+        assert(id);
+
+        /* First, try reading the D-Bus machine id, unless it is a symlink */
+        fd = open("/var/lib/dbus/machine-id", O_RDONLY|O_CLOEXEC|O_NOCTTY|O_NOFOLLOW);
+        if (fd >= 0) {
+
+                k = loop_read(fd, id, 32, false);
+                close_nointr_nofail(fd);
+
+                if (k >= 32) {
+                        id[32] = '\n';
+                        id[33] = 0;
+
+                        log_info("Initializing machine ID from D-Bus machine ID.");
+                        return 0;
+                }
+        }
+
+        /* If that didn't work, see if we are running in qemu/kvm and a
+         * machine ID was passed in via -uuid on the qemu/kvm command
+         * line */
+
+        r = detect_vm(&vm_id);
+        if (r > 0 && streq(vm_id, "kvm")) {
+                char uuid[37];
+
+                fd = open("/sys/class/dmi/id/product_uuid", O_RDONLY|O_CLOEXEC|O_NOCTTY|O_NOFOLLOW);
+                if (fd >= 0) {
+                        k = loop_read(fd, uuid, 36, false);
+                        close_nointr_nofail(fd);
+
+                        if (k >= 36) {
+                                r = shorten_uuid(id, uuid);
+                                if (r >= 0) {
+                                        log_info("Initializing machine ID from KVM UUID");
+                                        return 0;
+                                }
+                        }
+                }
+        }
+
+        /* If that didn't work either, see if we are running in a
+         * container, and a machine ID was passed in via
+         * $container_uuid the way libvirt/LXC does it */
+
+        r = detect_container(NULL);
+        if (r > 0) {
+                FILE *f;
+
+                f = fopen("/proc/1/environ", "re");
+                if (f) {
+                        bool done = false;
+
+                        do {
+                                char line[LINE_MAX];
+                                unsigned i;
+
+                                for (i = 0; i < sizeof(line)-1; i++) {
+                                        int c;
+
+                                        c = getc(f);
+                                        if (_unlikely_(c == EOF)) {
+                                                done = true;
+                                                break;
+                                        } else if (c == 0)
+                                                break;
+
+                                        line[i] = c;
+                                }
+                                line[i] = 0;
+
+                                if (startswith(line, "container_uuid=") &&
+                                    strlen(line + 15) >= 36) {
+                                        r = shorten_uuid(id, line + 15);
+                                        if (r >= 0) {
+                                                log_info("Initializing machine ID from container UUID");
+                                                return 0;
+                                        }
+                                }
+
+                        } while (!done);
+
+                        fclose(f);
+                }
+        }
+
+        /* If that didn't work, generate a random machine id */
+        r = sd_id128_randomize(&buf);
+        if (r < 0) {
+                log_error("Failed to open /dev/urandom: %s", strerror(-r));
+                return r;
+        }
+
+        for (p = buf.bytes, q = id; p < buf.bytes + sizeof(buf); p++, q += 2) {
+                q[0] = hexchar(*p >> 4);
+                q[1] = hexchar(*p & 15);
+        }
+
+        id[32] = '\n';
+        id[33] = 0;
+
+        log_info("Initializing machine ID from random generator.");
+
+        return 0;
+}
+
+int machine_id_setup(void) {
+        int fd, r;
+        bool writable;
+        struct stat st;
+        char id[34]; /* 32 + \n + \0 */
+        mode_t m;
+
+        m = umask(0000);
+
+        /* We create this 0444, to indicate that this isn't really
+         * something you should ever modify. Of course, since the file
+         * will be owned by root it doesn't matter much, but maybe
+         * people look. */
+
+        fd = open("/etc/machine-id", O_RDWR|O_CREAT|O_CLOEXEC|O_NOCTTY, 0444);
+        if (fd >= 0)
+                writable = true;
+        else {
+                fd = open("/etc/machine-id", O_RDONLY|O_CLOEXEC|O_NOCTTY);
+                if (fd < 0) {
+                        umask(m);
+                        log_error("Cannot open /etc/machine-id: %m");
+                        return -errno;
+                }
+
+                writable = false;
+        }
+
+        umask(m);
+
+        if (fstat(fd, &st) < 0) {
+                log_error("fstat() failed: %m");
+                r = -errno;
+                goto finish;
+        }
+
+        if (S_ISREG(st.st_mode)) {
+                if (loop_read(fd, id, 32, false) >= 32) {
+                        r = 0;
+                        goto finish;
+                }
+        }
+
+        /* Hmm, so, the id currently stored is not useful, then let's
+         * generate one */
+
+        r = generate(id);
+        if (r < 0)
+                goto finish;
+
+        if (S_ISREG(st.st_mode) && writable) {
+                lseek(fd, 0, SEEK_SET);
+
+                if (loop_write(fd, id, 33, false) == 33) {
+                        r = 0;
+                        goto finish;
+                }
+        }
+
+        close_nointr_nofail(fd);
+        fd = -1;
+
+        /* Hmm, we couldn't write it? So let's write it to
+         * /run/systemd/machine-id as a replacement */
+
+        mkdir_p("/run/systemd", 0755);
+
+        m = umask(0022);
+        r = write_one_line_file("/run/systemd/machine-id", id);
+        umask(m);
+
+        if (r < 0) {
+                log_error("Cannot write /run/systemd/machine-id: %s", strerror(-r));
+
+                unlink("/run/systemd/machine-id");
+                goto finish;
+        }
+
+        /* And now, let's mount it over */
+        r = mount("/run/systemd/machine-id", "/etc/machine-id", "bind", MS_BIND|MS_RDONLY, NULL) < 0 ? -errno : 0;
+        unlink("/run/systemd/machine-id");
+
+        if (r < 0)
+                log_error("Failed to mount /etc/machine-id: %s", strerror(-r));
+        else
+                log_info("Installed transient /etc/machine-id file.");
+
+finish:
+
+        if (fd >= 0)
+                close_nointr_nofail(fd);
+
+        return r;
+}
diff --git a/src/machine-id-setup.h b/src/machine-id-setup.h
new file mode 100644 (file)
index 0000000..4d0a9cf
--- /dev/null
@@ -0,0 +1,27 @@
+/*-*- Mode: C; c-basic-offset: 8; indent-tabs-mode: nil -*-*/
+
+#ifndef foomachineidsetuphfoo
+#define foomachineidsetuphfoo
+
+/***
+  This file is part 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 <http://www.gnu.org/licenses/>.
+***/
+
+int machine_id_setup(void);
+
+#endif
diff --git a/src/macro.h b/src/macro.h
new file mode 100644 (file)
index 0000000..19f259e
--- /dev/null
@@ -0,0 +1,181 @@
+/*-*- Mode: C; c-basic-offset: 8; indent-tabs-mode: nil -*-*/
+
+#ifndef foomacrohfoo
+#define foomacrohfoo
+
+/***
+  This file is part 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 <http://www.gnu.org/licenses/>.
+***/
+
+#include <assert.h>
+#include <sys/param.h>
+#include <sys/types.h>
+#include <sys/uio.h>
+#include <inttypes.h>
+
+#define _printf_attr_(a,b) __attribute__ ((format (printf, a, b)))
+#define _sentinel_ __attribute__ ((sentinel))
+#define _noreturn_ __attribute__((noreturn))
+#define _unused_ __attribute__ ((unused))
+#define _destructor_ __attribute__ ((destructor))
+#define _pure_ __attribute__ ((pure))
+#define _const_ __attribute__ ((const))
+#define _deprecated_ __attribute__ ((deprecated))
+#define _packed_ __attribute__ ((packed))
+#define _malloc_ __attribute__ ((malloc))
+#define _weak_ __attribute__ ((weak))
+#define _likely_(x) (__builtin_expect(!!(x),1))
+#define _unlikely_(x) (__builtin_expect(!!(x),0))
+#define _public_ __attribute__ ((visibility("default")))
+#define _hidden_ __attribute__ ((visibility("hidden")))
+#define _weakref_(x) __attribute__((weakref(#x)))
+#define _introspect_(x) __attribute__((section("introspect." x)))
+
+#define XSTRINGIFY(x) #x
+#define STRINGIFY(x) XSTRINGIFY(x)
+
+/* Rounds up */
+#define ALIGN(l) ALIGN_TO((l), sizeof(void*))
+static inline size_t ALIGN_TO(size_t l, size_t ali) {
+        return ((l + ali - 1) & ~(ali - 1));
+}
+
+#define ELEMENTSOF(x) (sizeof(x)/sizeof((x)[0]))
+
+#ifndef MAX
+#define MAX(a,b)                                \
+        __extension__ ({                        \
+                        typeof(a) _a = (a);     \
+                        typeof(b) _b = (b);     \
+                        _a > _b ? _a : _b;      \
+                })
+#endif
+
+#define MAX3(a,b,c)                             \
+        MAX(MAX(a,b),c)
+
+#ifndef MIN
+#define MIN(a,b)                                \
+        __extension__ ({                        \
+                        typeof(a) _a = (a);     \
+                        typeof(b) _b = (b);     \
+                        _a < _b ? _a : _b;      \
+                })
+#endif
+
+#define MIN3(a,b,c)                             \
+        MIN(MIN(a,b),c)
+
+#define CLAMP(x, low, high)                                             \
+        __extension__ ({                                                \
+                        typeof(x) _x = (x);                             \
+                        typeof(low) _low = (low);                       \
+                        typeof(high) _high = (high);                    \
+                        ((_x > _high) ? _high : ((_x < _low) ? _low : _x)); \
+                })
+
+#define assert_se(expr)                                                 \
+        do {                                                            \
+                if (_unlikely_(!(expr)))                                \
+                        log_assert_failed(#expr, __FILE__, __LINE__, __PRETTY_FUNCTION__); \
+        } while (false)                                                 \
+
+/* We override the glibc assert() here. */
+#undef assert
+#ifdef NDEBUG
+#define assert(expr) do {} while(false)
+#else
+#define assert(expr) assert_se(expr)
+#endif
+
+#define assert_not_reached(t)                                           \
+        do {                                                            \
+                log_assert_failed_unreachable(t, __FILE__, __LINE__, __PRETTY_FUNCTION__); \
+        } while (false)
+
+#define assert_cc(expr)                            \
+        do {                                       \
+                switch (0) {                       \
+                        case 0:                    \
+                        case !!(expr):             \
+                                ;                  \
+                }                                  \
+        } while (false)
+
+#define PTR_TO_UINT(p) ((unsigned int) ((uintptr_t) (p)))
+#define UINT_TO_PTR(u) ((void*) ((uintptr_t) (u)))
+
+#define PTR_TO_UINT32(p) ((uint32_t) ((uintptr_t) (p)))
+#define UINT32_TO_PTR(u) ((void*) ((uintptr_t) (u)))
+
+#define PTR_TO_ULONG(p) ((unsigned long) ((uintptr_t) (p)))
+#define ULONG_TO_PTR(u) ((void*) ((uintptr_t) (u)))
+
+#define PTR_TO_INT(p) ((int) ((intptr_t) (p)))
+#define INT_TO_PTR(u) ((void*) ((intptr_t) (u)))
+
+#define TO_INT32(p) ((int32_t) ((intptr_t) (p)))
+#define INT32_TO_PTR(u) ((void*) ((intptr_t) (u)))
+
+#define PTR_TO_LONG(p) ((long) ((intptr_t) (p)))
+#define LONG_TO_PTR(u) ((void*) ((intptr_t) (u)))
+
+#define memzero(x,l) (memset((x), 0, (l)))
+#define zero(x) (memzero(&(x), sizeof(x)))
+
+#define char_array_0(x) x[sizeof(x)-1] = 0;
+
+#define IOVEC_SET_STRING(i, s)                  \
+        do {                                    \
+                struct iovec *_i = &(i);        \
+                char *_s = (char *)(s);         \
+                _i->iov_base = _s;              \
+                _i->iov_len = strlen(_s);       \
+        } while(false)
+
+static inline size_t IOVEC_TOTAL_SIZE(const struct iovec *i, unsigned n) {
+        unsigned j;
+        size_t r = 0;
+
+        for (j = 0; j < n; j++)
+                r += i[j].iov_len;
+
+        return r;
+}
+
+static inline size_t IOVEC_INCREMENT(struct iovec *i, unsigned n, size_t k) {
+        unsigned j;
+
+        for (j = 0; j < n; j++) {
+                size_t sub;
+
+                if (_unlikely_(k <= 0))
+                        break;
+
+                sub = MIN(i[j].iov_len, k);
+                i[j].iov_len -= sub;
+                i[j].iov_base = (uint8_t*) i[j].iov_base + sub;
+                k -= sub;
+        }
+
+        return k;
+}
+
+#include "log.h"
+
+#endif
diff --git a/src/main.c b/src/main.c
new file mode 100644 (file)
index 0000000..7ae8841
--- /dev/null
@@ -0,0 +1,1632 @@
+/*-*- 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 <http://www.gnu.org/licenses/>.
+***/
+
+#include <dbus/dbus.h>
+
+#include <stdio.h>
+#include <errno.h>
+#include <string.h>
+#include <unistd.h>
+#include <sys/types.h>
+#include <sys/stat.h>
+#include <getopt.h>
+#include <signal.h>
+#include <sys/wait.h>
+#include <fcntl.h>
+#include <sys/prctl.h>
+
+#include "manager.h"
+#include "log.h"
+#include "mount-setup.h"
+#include "hostname-setup.h"
+#include "loopback-setup.h"
+#include "kmod-setup.h"
+#include "locale-setup.h"
+#include "selinux-setup.h"
+#include "ima-setup.h"
+#include "machine-id-setup.h"
+#include "load-fragment.h"
+#include "fdset.h"
+#include "special.h"
+#include "conf-parser.h"
+#include "bus-errors.h"
+#include "missing.h"
+#include "label.h"
+#include "build.h"
+#include "strv.h"
+#include "def.h"
+#include "virt.h"
+
+static enum {
+        ACTION_RUN,
+        ACTION_HELP,
+        ACTION_TEST,
+        ACTION_DUMP_CONFIGURATION_ITEMS,
+        ACTION_DONE
+} arg_action = ACTION_RUN;
+
+static char *arg_default_unit = NULL;
+static ManagerRunningAs arg_running_as = _MANAGER_RUNNING_AS_INVALID;
+
+static bool arg_dump_core = true;
+static bool arg_crash_shell = false;
+static int arg_crash_chvt = -1;
+static bool arg_confirm_spawn = false;
+static bool arg_show_status = true;
+#ifdef HAVE_SYSV_COMPAT
+static bool arg_sysv_console = true;
+#endif
+static bool arg_mount_auto = true;
+static bool arg_swap_auto = true;
+static char **arg_default_controllers = NULL;
+static char ***arg_join_controllers = NULL;
+static ExecOutput arg_default_std_output = EXEC_OUTPUT_JOURNAL;
+static ExecOutput arg_default_std_error = EXEC_OUTPUT_INHERIT;
+
+static FILE* serialization = NULL;
+
+static void nop_handler(int sig) {
+}
+
+_noreturn_ static void crash(int sig) {
+
+        if (!arg_dump_core)
+                log_error("Caught <%s>, not dumping core.", signal_to_string(sig));
+        else {
+                struct sigaction sa;
+                pid_t pid;
+
+                /* We want to wait for the core process, hence let's enable SIGCHLD */
+                zero(sa);
+                sa.sa_handler = nop_handler;
+                sa.sa_flags = SA_NOCLDSTOP|SA_RESTART;
+                assert_se(sigaction(SIGCHLD, &sa, NULL) == 0);
+
+                if ((pid = fork()) < 0)
+                        log_error("Caught <%s>, cannot fork for core dump: %s", signal_to_string(sig), strerror(errno));
+
+                else if (pid == 0) {
+                        struct rlimit rl;
+
+                        /* Enable default signal handler for core dump */
+                        zero(sa);
+                        sa.sa_handler = SIG_DFL;
+                        assert_se(sigaction(sig, &sa, NULL) == 0);
+
+                        /* Don't limit the core dump size */
+                        zero(rl);
+                        rl.rlim_cur = RLIM_INFINITY;
+                        rl.rlim_max = RLIM_INFINITY;
+                        setrlimit(RLIMIT_CORE, &rl);
+
+                        /* Just to be sure... */
+                        assert_se(chdir("/") == 0);
+
+                        /* Raise the signal again */
+                        raise(sig);
+
+                        assert_not_reached("We shouldn't be here...");
+                        _exit(1);
+
+                } else {
+                        siginfo_t status;
+                        int r;
+
+                        /* Order things nicely. */
+                        if ((r = wait_for_terminate(pid, &status)) < 0)
+                                log_error("Caught <%s>, waitpid() failed: %s", signal_to_string(sig), strerror(-r));
+                        else if (status.si_code != CLD_DUMPED)
+                                log_error("Caught <%s>, core dump failed.", signal_to_string(sig));
+                        else
+                                log_error("Caught <%s>, dumped core as pid %lu.", signal_to_string(sig), (unsigned long) pid);
+                }
+        }
+
+        if (arg_crash_chvt)
+                chvt(arg_crash_chvt);
+
+        if (arg_crash_shell) {
+                struct sigaction sa;
+                pid_t pid;
+
+                log_info("Executing crash shell in 10s...");
+                sleep(10);
+
+                /* Let the kernel reap children for us */
+                zero(sa);
+                sa.sa_handler = SIG_IGN;
+                sa.sa_flags = SA_NOCLDSTOP|SA_NOCLDWAIT|SA_RESTART;
+                assert_se(sigaction(SIGCHLD, &sa, NULL) == 0);
+
+                if ((pid = fork()) < 0)
+                        log_error("Failed to fork off crash shell: %s", strerror(errno));
+                else if (pid == 0) {
+                        int fd, r;
+
+                        if ((fd = acquire_terminal("/dev/console", false, true, true)) < 0)
+                                log_error("Failed to acquire terminal: %s", strerror(-fd));
+                        else if ((r = make_stdio(fd)) < 0)
+                                log_error("Failed to duplicate terminal fd: %s", strerror(-r));
+
+                        execl("/bin/sh", "/bin/sh", NULL);
+
+                        log_error("execl() failed: %s", strerror(errno));
+                        _exit(1);
+                }
+
+                log_info("Successfully spawned crash shell as pid %lu.", (unsigned long) pid);
+        }
+
+        log_info("Freezing execution.");
+        freeze();
+}
+
+static void install_crash_handler(void) {
+        struct sigaction sa;
+
+        zero(sa);
+
+        sa.sa_handler = crash;
+        sa.sa_flags = SA_NODEFER;
+
+        sigaction_many(&sa, SIGNALS_CRASH_HANDLER, -1);
+}
+
+static int console_setup(bool do_reset) {
+        int tty_fd, r;
+
+        /* If we are init, we connect stdin/stdout/stderr to /dev/null
+         * and make sure we don't have a controlling tty. */
+
+        release_terminal();
+
+        if (!do_reset)
+                return 0;
+
+        tty_fd = open_terminal("/dev/console", O_WRONLY|O_NOCTTY|O_CLOEXEC);
+        if (tty_fd < 0) {
+                log_error("Failed to open /dev/console: %s", strerror(-tty_fd));
+                return -tty_fd;
+        }
+
+        /* We don't want to force text mode.
+         * plymouth may be showing pictures already from initrd. */
+        r = reset_terminal_fd(tty_fd, false);
+        if (r < 0)
+                log_error("Failed to reset /dev/console: %s", strerror(-r));
+
+        close_nointr_nofail(tty_fd);
+        return r;
+}
+
+static int set_default_unit(const char *u) {
+        char *c;
+
+        assert(u);
+
+        if (!(c = strdup(u)))
+                return -ENOMEM;
+
+        free(arg_default_unit);
+        arg_default_unit = c;
+        return 0;
+}
+
+static int parse_proc_cmdline_word(const char *word) {
+
+        static const char * const rlmap[] = {
+                "emergency", SPECIAL_EMERGENCY_TARGET,
+                "-b",        SPECIAL_EMERGENCY_TARGET,
+                "single",    SPECIAL_RESCUE_TARGET,
+                "-s",        SPECIAL_RESCUE_TARGET,
+                "s",         SPECIAL_RESCUE_TARGET,
+                "S",         SPECIAL_RESCUE_TARGET,
+                "1",         SPECIAL_RESCUE_TARGET,
+                "2",         SPECIAL_RUNLEVEL2_TARGET,
+                "3",         SPECIAL_RUNLEVEL3_TARGET,
+                "4",         SPECIAL_RUNLEVEL4_TARGET,
+                "5",         SPECIAL_RUNLEVEL5_TARGET,
+        };
+
+        assert(word);
+
+        if (startswith(word, "systemd.unit="))
+                return set_default_unit(word + 13);
+
+        else if (startswith(word, "systemd.log_target=")) {
+
+                if (log_set_target_from_string(word + 19) < 0)
+                        log_warning("Failed to parse log target %s. Ignoring.", word + 19);
+
+        } else if (startswith(word, "systemd.log_level=")) {
+
+                if (log_set_max_level_from_string(word + 18) < 0)
+                        log_warning("Failed to parse log level %s. Ignoring.", word + 18);
+
+        } else if (startswith(word, "systemd.log_color=")) {
+
+                if (log_show_color_from_string(word + 18) < 0)
+                        log_warning("Failed to parse log color setting %s. Ignoring.", word + 18);
+
+        } else if (startswith(word, "systemd.log_location=")) {
+
+                if (log_show_location_from_string(word + 21) < 0)
+                        log_warning("Failed to parse log location setting %s. Ignoring.", word + 21);
+
+        } else if (startswith(word, "systemd.dump_core=")) {
+                int r;
+
+                if ((r = parse_boolean(word + 18)) < 0)
+                        log_warning("Failed to parse dump core switch %s. Ignoring.", word + 18);
+                else
+                        arg_dump_core = r;
+
+        } else if (startswith(word, "systemd.crash_shell=")) {
+                int r;
+
+                if ((r = parse_boolean(word + 20)) < 0)
+                        log_warning("Failed to parse crash shell switch %s. Ignoring.", word + 20);
+                else
+                        arg_crash_shell = r;
+
+        } else if (startswith(word, "systemd.confirm_spawn=")) {
+                int r;
+
+                if ((r = parse_boolean(word + 22)) < 0)
+                        log_warning("Failed to parse confirm spawn switch %s. Ignoring.", word + 22);
+                else
+                        arg_confirm_spawn = r;
+
+        } else if (startswith(word, "systemd.crash_chvt=")) {
+                int k;
+
+                if (safe_atoi(word + 19, &k) < 0)
+                        log_warning("Failed to parse crash chvt switch %s. Ignoring.", word + 19);
+                else
+                        arg_crash_chvt = k;
+
+        } else if (startswith(word, "systemd.show_status=")) {
+                int r;
+
+                if ((r = parse_boolean(word + 20)) < 0)
+                        log_warning("Failed to parse show status switch %s. Ignoring.", word + 20);
+                else
+                        arg_show_status = r;
+        } else if (startswith(word, "systemd.default_standard_output=")) {
+                int r;
+
+                if ((r = exec_output_from_string(word + 32)) < 0)
+                        log_warning("Failed to parse default standard output switch %s. Ignoring.", word + 32);
+                else
+                        arg_default_std_output = r;
+        } else if (startswith(word, "systemd.default_standard_error=")) {
+                int r;
+
+                if ((r = exec_output_from_string(word + 31)) < 0)
+                        log_warning("Failed to parse default standard error switch %s. Ignoring.", word + 31);
+                else
+                        arg_default_std_error = r;
+        } else if (startswith(word, "systemd.setenv=")) {
+                char *cenv, *eq;
+                int r;
+
+                cenv = strdup(word + 15);
+                if (!cenv)
+                        return -ENOMEM;
+
+                eq = strchr(cenv, '=');
+                if (!eq) {
+                        r = unsetenv(cenv);
+                        if (r < 0)
+                                log_warning("unsetenv failed %s. Ignoring.", strerror(errno));
+                } else {
+                        *eq = 0;
+                        r = setenv(cenv, eq + 1, 1);
+                        if (r < 0)
+                                log_warning("setenv failed %s. Ignoring.", strerror(errno));
+                }
+                free(cenv);
+#ifdef HAVE_SYSV_COMPAT
+        } else if (startswith(word, "systemd.sysv_console=")) {
+                int r;
+
+                if ((r = parse_boolean(word + 21)) < 0)
+                        log_warning("Failed to parse SysV console switch %s. Ignoring.", word + 20);
+                else
+                        arg_sysv_console = r;
+#endif
+
+        } else if (startswith(word, "systemd.")) {
+
+                log_warning("Unknown kernel switch %s. Ignoring.", word);
+
+                log_info("Supported kernel switches:\n"
+                         "systemd.unit=UNIT                        Default unit to start\n"
+                         "systemd.dump_core=0|1                    Dump core on crash\n"
+                         "systemd.crash_shell=0|1                  Run shell on crash\n"
+                         "systemd.crash_chvt=N                     Change to VT #N on crash\n"
+                         "systemd.confirm_spawn=0|1                Confirm every process spawn\n"
+                         "systemd.show_status=0|1                  Show status updates on the console during bootup\n"
+#ifdef HAVE_SYSV_COMPAT
+                         "systemd.sysv_console=0|1                 Connect output of SysV scripts to console\n"
+#endif
+                         "systemd.log_target=console|kmsg|journal|journal-or-kmsg|syslog|syslog-or-kmsg|null\n"
+                         "                                         Log target\n"
+                         "systemd.log_level=LEVEL                  Log level\n"
+                         "systemd.log_color=0|1                    Highlight important log messages\n"
+                         "systemd.log_location=0|1                 Include code location in log messages\n"
+                         "systemd.default_standard_output=null|tty|syslog|syslog+console|kmsg|kmsg+console|journal|journal+console\n"
+                         "                                         Set default log output for services\n"
+                         "systemd.default_standard_error=null|tty|syslog|syslog+console|kmsg|kmsg+console|journal|journal+console\n"
+                         "                                         Set default log error output for services\n");
+
+        } else if (streq(word, "quiet")) {
+                arg_show_status = false;
+#ifdef HAVE_SYSV_COMPAT
+                arg_sysv_console = false;
+#endif
+        } else {
+                unsigned i;
+
+                /* SysV compatibility */
+                for (i = 0; i < ELEMENTSOF(rlmap); i += 2)
+                        if (streq(word, rlmap[i]))
+                                return set_default_unit(rlmap[i+1]);
+        }
+
+        return 0;
+}
+
+static int config_parse_level2(
+                const char *filename,
+                unsigned line,
+                const char *section,
+                const char *lvalue,
+                int ltype,
+                const char *rvalue,
+                void *data,
+                void *userdata) {
+
+        assert(filename);
+        assert(lvalue);
+        assert(rvalue);
+
+        log_set_max_level_from_string(rvalue);
+        return 0;
+}
+
+static int config_parse_target(
+                const char *filename,
+                unsigned line,
+                const char *section,
+                const char *lvalue,
+                int ltype,
+                const char *rvalue,
+                void *data,
+                void *userdata) {
+
+        assert(filename);
+        assert(lvalue);
+        assert(rvalue);
+
+        log_set_target_from_string(rvalue);
+        return 0;
+}
+
+static int config_parse_color(
+                const char *filename,
+                unsigned line,
+                const char *section,
+                const char *lvalue,
+                int ltype,
+                const char *rvalue,
+                void *data,
+                void *userdata) {
+
+        assert(filename);
+        assert(lvalue);
+        assert(rvalue);
+
+        log_show_color_from_string(rvalue);
+        return 0;
+}
+
+static int config_parse_location(
+                const char *filename,
+                unsigned line,
+                const char *section,
+                const char *lvalue,
+                int ltype,
+                const char *rvalue,
+                void *data,
+                void *userdata) {
+
+        assert(filename);
+        assert(lvalue);
+        assert(rvalue);
+
+        log_show_location_from_string(rvalue);
+        return 0;
+}
+
+static int config_parse_cpu_affinity2(
+                const char *filename,
+                unsigned line,
+                const char *section,
+                const char *lvalue,
+                int ltype,
+                const char *rvalue,
+                void *data,
+                void *userdata) {
+
+        char *w;
+        size_t l;
+        char *state;
+        cpu_set_t *c = NULL;
+        unsigned ncpus = 0;
+
+        assert(filename);
+        assert(lvalue);
+        assert(rvalue);
+
+        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)
+                        if (!(c = cpu_set_malloc(&ncpus)))
+                                return -ENOMEM;
+
+                if (r < 0 || cpu >= ncpus) {
+                        log_error("[%s:%u] Failed to parse CPU affinity: %s", filename, line, rvalue);
+                        CPU_FREE(c);
+                        return -EBADMSG;
+                }
+
+                CPU_SET_S(cpu, CPU_ALLOC_SIZE(ncpus), c);
+        }
+
+        if (c) {
+                if (sched_setaffinity(0, CPU_ALLOC_SIZE(ncpus), c) < 0)
+                        log_warning("Failed to set CPU affinity: %m");
+
+                CPU_FREE(c);
+        }
+
+        return 0;
+}
+
+static void strv_free_free(char ***l) {
+        char ***i;
+
+        if (!l)
+                return;
+
+        for (i = l; *i; i++)
+                strv_free(*i);
+
+        free(l);
+}
+
+static void free_join_controllers(void) {
+        if (!arg_join_controllers)
+                return;
+
+        strv_free_free(arg_join_controllers);
+        arg_join_controllers = NULL;
+}
+
+static int config_parse_join_controllers(
+                const char *filename,
+                unsigned line,
+                const char *section,
+                const char *lvalue,
+                int ltype,
+                const char *rvalue,
+                void *data,
+                void *userdata) {
+
+        unsigned n = 0;
+        char *state, *w;
+        size_t length;
+
+        assert(filename);
+        assert(lvalue);
+        assert(rvalue);
+
+        free_join_controllers();
+
+        FOREACH_WORD_QUOTED(w, length, rvalue, state) {
+                char *s, **l;
+
+                s = strndup(w, length);
+                if (!s)
+                        return -ENOMEM;
+
+                l = strv_split(s, ",");
+                free(s);
+
+                strv_uniq(l);
+
+                if (strv_length(l) <= 1) {
+                        strv_free(l);
+                        continue;
+                }
+
+                if (!arg_join_controllers) {
+                        arg_join_controllers = new(char**, 2);
+                        if (!arg_join_controllers) {
+                                strv_free(l);
+                                return -ENOMEM;
+                        }
+
+                        arg_join_controllers[0] = l;
+                        arg_join_controllers[1] = NULL;
+
+                        n = 1;
+                } else {
+                        char ***a;
+                        char ***t;
+
+                        t = new0(char**, n+2);
+                        if (!t) {
+                                strv_free(l);
+                                return -ENOMEM;
+                        }
+
+                        n = 0;
+
+                        for (a = arg_join_controllers; *a; a++) {
+
+                                if (strv_overlap(*a, l)) {
+                                        char **c;
+
+                                        c = strv_merge(*a, l);
+                                        if (!c) {
+                                                strv_free(l);
+                                                strv_free_free(t);
+                                                return -ENOMEM;
+                                        }
+
+                                        strv_free(l);
+                                        l = c;
+                                } else {
+                                        char **c;
+
+                                        c = strv_copy(*a);
+                                        if (!c) {
+                                                strv_free(l);
+                                                strv_free_free(t);
+                                                return -ENOMEM;
+                                        }
+
+                                        t[n++] = c;
+                                }
+                        }
+
+                        t[n++] = strv_uniq(l);
+
+                        strv_free_free(arg_join_controllers);
+                        arg_join_controllers = t;
+                }
+        }
+
+        return 0;
+}
+
+static int parse_config_file(void) {
+
+        const ConfigTableItem items[] = {
+                { "Manager", "LogLevel",              config_parse_level2,       0, NULL                     },
+                { "Manager", "LogTarget",             config_parse_target,       0, NULL                     },
+                { "Manager", "LogColor",              config_parse_color,        0, NULL                     },
+                { "Manager", "LogLocation",           config_parse_location,     0, NULL                     },
+                { "Manager", "DumpCore",              config_parse_bool,         0, &arg_dump_core           },
+                { "Manager", "CrashShell",            config_parse_bool,         0, &arg_crash_shell         },
+                { "Manager", "ShowStatus",            config_parse_bool,         0, &arg_show_status         },
+#ifdef HAVE_SYSV_COMPAT
+                { "Manager", "SysVConsole",           config_parse_bool,         0, &arg_sysv_console        },
+#endif
+                { "Manager", "CrashChVT",             config_parse_int,          0, &arg_crash_chvt          },
+                { "Manager", "CPUAffinity",           config_parse_cpu_affinity2, 0, NULL                    },
+                { "Manager", "MountAuto",             config_parse_bool,         0, &arg_mount_auto          },
+                { "Manager", "SwapAuto",              config_parse_bool,         0, &arg_swap_auto           },
+                { "Manager", "DefaultControllers",    config_parse_strv,         0, &arg_default_controllers },
+                { "Manager", "DefaultStandardOutput", config_parse_output,       0, &arg_default_std_output  },
+                { "Manager", "DefaultStandardError",  config_parse_output,       0, &arg_default_std_error   },
+                { "Manager", "JoinControllers",       config_parse_join_controllers, 0, &arg_join_controllers },
+                { NULL, NULL, NULL, 0, NULL }
+        };
+
+        FILE *f;
+        const char *fn;
+        int r;
+
+        fn = arg_running_as == MANAGER_SYSTEM ? SYSTEM_CONFIG_FILE : USER_CONFIG_FILE;
+        f = fopen(fn, "re");
+        if (!f) {
+                if (errno == ENOENT)
+                        return 0;
+
+                log_warning("Failed to open configuration file '%s': %m", fn);
+                return 0;
+        }
+
+        r = config_parse(fn, f, "Manager\0", config_item_table_lookup, (void*) items, false, NULL);
+        if (r < 0)
+                log_warning("Failed to parse configuration file: %s", strerror(-r));
+
+        fclose(f);
+
+        return 0;
+}
+
+static int parse_proc_cmdline(void) {
+        char *line, *w, *state;
+        int r;
+        size_t l;
+
+        /* Don't read /proc/cmdline if we are in a container, since
+         * that is only relevant for the host system */
+        if (detect_container(NULL) > 0)
+                return 0;
+
+        if ((r = read_one_line_file("/proc/cmdline", &line)) < 0) {
+                log_warning("Failed to read /proc/cmdline, ignoring: %s", strerror(-r));
+                return 0;
+        }
+
+        FOREACH_WORD_QUOTED(w, l, line, state) {
+                char *word;
+
+                if (!(word = strndup(w, l))) {
+                        r = -ENOMEM;
+                        goto finish;
+                }
+
+                r = parse_proc_cmdline_word(word);
+                free(word);
+
+                if (r < 0)
+                        goto finish;
+        }
+
+        r = 0;
+
+finish:
+        free(line);
+        return r;
+}
+
+static int parse_argv(int argc, char *argv[]) {
+
+        enum {
+                ARG_LOG_LEVEL = 0x100,
+                ARG_LOG_TARGET,
+                ARG_LOG_COLOR,
+                ARG_LOG_LOCATION,
+                ARG_UNIT,
+                ARG_SYSTEM,
+                ARG_USER,
+                ARG_TEST,
+                ARG_DUMP_CONFIGURATION_ITEMS,
+                ARG_DUMP_CORE,
+                ARG_CRASH_SHELL,
+                ARG_CONFIRM_SPAWN,
+                ARG_SHOW_STATUS,
+                ARG_SYSV_CONSOLE,
+                ARG_DESERIALIZE,
+                ARG_INTROSPECT,
+                ARG_DEFAULT_STD_OUTPUT,
+                ARG_DEFAULT_STD_ERROR
+        };
+
+        static const struct option options[] = {
+                { "log-level",                required_argument, NULL, ARG_LOG_LEVEL                },
+                { "log-target",               required_argument, NULL, ARG_LOG_TARGET               },
+                { "log-color",                optional_argument, NULL, ARG_LOG_COLOR                },
+                { "log-location",             optional_argument, NULL, ARG_LOG_LOCATION             },
+                { "unit",                     required_argument, NULL, ARG_UNIT                     },
+                { "system",                   no_argument,       NULL, ARG_SYSTEM                   },
+                { "user",                     no_argument,       NULL, ARG_USER                     },
+                { "test",                     no_argument,       NULL, ARG_TEST                     },
+                { "help",                     no_argument,       NULL, 'h'                          },
+                { "dump-configuration-items", no_argument,       NULL, ARG_DUMP_CONFIGURATION_ITEMS },
+                { "dump-core",                no_argument,       NULL, ARG_DUMP_CORE                },
+                { "crash-shell",              no_argument,       NULL, ARG_CRASH_SHELL              },
+                { "confirm-spawn",            no_argument,       NULL, ARG_CONFIRM_SPAWN            },
+                { "show-status",              optional_argument, NULL, ARG_SHOW_STATUS              },
+#ifdef HAVE_SYSV_COMPAT
+                { "sysv-console",             optional_argument, NULL, ARG_SYSV_CONSOLE             },
+#endif
+                { "deserialize",              required_argument, NULL, ARG_DESERIALIZE              },
+                { "introspect",               optional_argument, NULL, ARG_INTROSPECT               },
+                { "default-standard-output",  required_argument, NULL, ARG_DEFAULT_STD_OUTPUT,      },
+                { "default-standard-error",   required_argument, NULL, ARG_DEFAULT_STD_ERROR,       },
+                { NULL,                       0,                 NULL, 0                            }
+        };
+
+        int c, r;
+
+        assert(argc >= 1);
+        assert(argv);
+
+        if (getpid() == 1)
+                opterr = 0;
+
+        while ((c = getopt_long(argc, argv, "hDbsz:", options, NULL)) >= 0)
+
+                switch (c) {
+
+                case ARG_LOG_LEVEL:
+                        if ((r = log_set_max_level_from_string(optarg)) < 0) {
+                                log_error("Failed to parse log level %s.", optarg);
+                                return r;
+                        }
+
+                        break;
+
+                case ARG_LOG_TARGET:
+
+                        if ((r = log_set_target_from_string(optarg)) < 0) {
+                                log_error("Failed to parse log target %s.", optarg);
+                                return r;
+                        }
+
+                        break;
+
+                case ARG_LOG_COLOR:
+
+                        if (optarg) {
+                                if ((r = log_show_color_from_string(optarg)) < 0) {
+                                        log_error("Failed to parse log color setting %s.", optarg);
+                                        return r;
+                                }
+                        } else
+                                log_show_color(true);
+
+                        break;
+
+                case ARG_LOG_LOCATION:
+
+                        if (optarg) {
+                                if ((r = log_show_location_from_string(optarg)) < 0) {
+                                        log_error("Failed to parse log location setting %s.", optarg);
+                                        return r;
+                                }
+                        } else
+                                log_show_location(true);
+
+                        break;
+
+                case ARG_DEFAULT_STD_OUTPUT:
+
+                        if ((r = exec_output_from_string(optarg)) < 0) {
+                                log_error("Failed to parse default standard output setting %s.", optarg);
+                                return r;
+                        } else
+                                arg_default_std_output = r;
+                        break;
+
+                case ARG_DEFAULT_STD_ERROR:
+
+                        if ((r = exec_output_from_string(optarg)) < 0) {
+                                log_error("Failed to parse default standard error output setting %s.", optarg);
+                                return r;
+                        } else
+                                arg_default_std_error = r;
+                        break;
+
+                case ARG_UNIT:
+
+                        if ((r = set_default_unit(optarg)) < 0) {
+                                log_error("Failed to set default unit %s: %s", optarg, strerror(-r));
+                                return r;
+                        }
+
+                        break;
+
+                case ARG_SYSTEM:
+                        arg_running_as = MANAGER_SYSTEM;
+                        break;
+
+                case ARG_USER:
+                        arg_running_as = MANAGER_USER;
+                        break;
+
+                case ARG_TEST:
+                        arg_action = ACTION_TEST;
+                        break;
+
+                case ARG_DUMP_CONFIGURATION_ITEMS:
+                        arg_action = ACTION_DUMP_CONFIGURATION_ITEMS;
+                        break;
+
+                case ARG_DUMP_CORE:
+                        arg_dump_core = true;
+                        break;
+
+                case ARG_CRASH_SHELL:
+                        arg_crash_shell = true;
+                        break;
+
+                case ARG_CONFIRM_SPAWN:
+                        arg_confirm_spawn = true;
+                        break;
+
+                case ARG_SHOW_STATUS:
+
+                        if (optarg) {
+                                if ((r = parse_boolean(optarg)) < 0) {
+                                        log_error("Failed to show status boolean %s.", optarg);
+                                        return r;
+                                }
+                                arg_show_status = r;
+                        } else
+                                arg_show_status = true;
+                        break;
+#ifdef HAVE_SYSV_COMPAT
+                case ARG_SYSV_CONSOLE:
+
+                        if (optarg) {
+                                if ((r = parse_boolean(optarg)) < 0) {
+                                        log_error("Failed to SysV console boolean %s.", optarg);
+                                        return r;
+                                }
+                                arg_sysv_console = r;
+                        } else
+                                arg_sysv_console = true;
+                        break;
+#endif
+
+                case ARG_DESERIALIZE: {
+                        int fd;
+                        FILE *f;
+
+                        if ((r = safe_atoi(optarg, &fd)) < 0 || fd < 0) {
+                                log_error("Failed to parse deserialize option %s.", optarg);
+                                return r;
+                        }
+
+                        if (!(f = fdopen(fd, "r"))) {
+                                log_error("Failed to open serialization fd: %m");
+                                return r;
+                        }
+
+                        if (serialization)
+                                fclose(serialization);
+
+                        serialization = f;
+
+                        break;
+                }
+
+                case ARG_INTROSPECT: {
+                        const char * const * i = NULL;
+
+                        for (i = bus_interface_table; *i; i += 2)
+                                if (!optarg || streq(i[0], optarg)) {
+                                        fputs(DBUS_INTROSPECT_1_0_XML_DOCTYPE_DECL_NODE
+                                              "<node>\n", stdout);
+                                        fputs(i[1], stdout);
+                                        fputs("</node>\n", stdout);
+
+                                        if (optarg)
+                                                break;
+                                }
+
+                        if (!i[0] && optarg)
+                                log_error("Unknown interface %s.", optarg);
+
+                        arg_action = ACTION_DONE;
+                        break;
+                }
+
+                case 'h':
+                        arg_action = ACTION_HELP;
+                        break;
+
+                case 'D':
+                        log_set_max_level(LOG_DEBUG);
+                        break;
+
+                case 'b':
+                case 's':
+                case 'z':
+                        /* Just to eat away the sysvinit kernel
+                         * cmdline args without getopt() error
+                         * messages that we'll parse in
+                         * parse_proc_cmdline_word() or ignore. */
+
+                case '?':
+                default:
+                        if (getpid() != 1) {
+                                log_error("Unknown option code %c", c);
+                                return -EINVAL;
+                        }
+
+                        break;
+                }
+
+        if (optind < argc && getpid() != 1) {
+                /* Hmm, when we aren't run as init system
+                 * let's complain about excess arguments */
+
+                log_error("Excess arguments.");
+                return -EINVAL;
+        }
+
+        if (detect_container(NULL) > 0) {
+                char **a;
+
+                /* All /proc/cmdline arguments the kernel didn't
+                 * understand it passed to us. We're not really
+                 * interested in that usually since /proc/cmdline is
+                 * more interesting and complete. With one exception:
+                 * if we are run in a container /proc/cmdline is not
+                 * relevant for the container, hence we rely on argv[]
+                 * instead. */
+
+                for (a = argv; a < argv + argc; a++)
+                        if ((r = parse_proc_cmdline_word(*a)) < 0)
+                                return r;
+        }
+
+        return 0;
+}
+
+static int help(void) {
+
+        printf("%s [OPTIONS...]\n\n"
+               "Starts up and maintains the system or user services.\n\n"
+               "  -h --help                      Show this help\n"
+               "     --test                      Determine startup sequence, dump it and exit\n"
+               "     --dump-configuration-items  Dump understood unit configuration items\n"
+               "     --introspect[=INTERFACE]    Extract D-Bus interface data\n"
+               "     --unit=UNIT                 Set default unit\n"
+               "     --system                    Run a system instance, even if PID != 1\n"
+               "     --user                      Run a user instance\n"
+               "     --dump-core                 Dump core on crash\n"
+               "     --crash-shell               Run shell on crash\n"
+               "     --confirm-spawn             Ask for confirmation when spawning processes\n"
+               "     --show-status[=0|1]         Show status updates on the console during bootup\n"
+#ifdef HAVE_SYSV_COMPAT
+               "     --sysv-console[=0|1]        Connect output of SysV scripts to console\n"
+#endif
+               "     --log-target=TARGET         Set log target (console, journal, syslog, kmsg, journal-or-kmsg, syslog-or-kmsg, null)\n"
+               "     --log-level=LEVEL           Set log level (debug, info, notice, warning, err, crit, alert, emerg)\n"
+               "     --log-color[=0|1]           Highlight important log messages\n"
+               "     --log-location[=0|1]        Include code location in log messages\n"
+               "     --default-standard-output=  Set default standard output for services\n"
+               "     --default-standard-error=   Set default standard error output for services\n",
+               program_invocation_short_name);
+
+        return 0;
+}
+
+static int prepare_reexecute(Manager *m, FILE **_f, FDSet **_fds) {
+        FILE *f = NULL;
+        FDSet *fds = NULL;
+        int r;
+
+        assert(m);
+        assert(_f);
+        assert(_fds);
+
+        /* Make sure nothing is really destructed when we shut down */
+        m->n_reloading ++;
+
+        if ((r = manager_open_serialization(m, &f)) < 0) {
+                log_error("Failed to create serialization file: %s", strerror(-r));
+                goto fail;
+        }
+
+        if (!(fds = fdset_new())) {
+                r = -ENOMEM;
+                log_error("Failed to allocate fd set: %s", strerror(-r));
+                goto fail;
+        }
+
+        if ((r = manager_serialize(m, f, fds)) < 0) {
+                log_error("Failed to serialize state: %s", strerror(-r));
+                goto fail;
+        }
+
+        if (fseeko(f, 0, SEEK_SET) < 0) {
+                log_error("Failed to rewind serialization fd: %m");
+                goto fail;
+        }
+
+        if ((r = fd_cloexec(fileno(f), false)) < 0) {
+                log_error("Failed to disable O_CLOEXEC for serialization: %s", strerror(-r));
+                goto fail;
+        }
+
+        if ((r = fdset_cloexec(fds, false)) < 0) {
+                log_error("Failed to disable O_CLOEXEC for serialization fds: %s", strerror(-r));
+                goto fail;
+        }
+
+        *_f = f;
+        *_fds = fds;
+
+        return 0;
+
+fail:
+        fdset_free(fds);
+
+        if (f)
+                fclose(f);
+
+        return r;
+}
+
+static struct dual_timestamp* parse_initrd_timestamp(struct dual_timestamp *t) {
+        const char *e;
+        unsigned long long a, b;
+
+        assert(t);
+
+        if (!(e = getenv("RD_TIMESTAMP")))
+                return NULL;
+
+        if (sscanf(e, "%llu %llu", &a, &b) != 2)
+                return NULL;
+
+        t->realtime = (usec_t) a;
+        t->monotonic = (usec_t) b;
+
+        return t;
+}
+
+static void test_mtab(void) {
+        char *p;
+
+        /* Check that /etc/mtab is a symlink */
+
+        if (readlink_malloc("/etc/mtab", &p) >= 0) {
+                bool b;
+
+                b = streq(p, "/proc/self/mounts") || streq(p, "/proc/mounts");
+                free(p);
+
+                if (b)
+                        return;
+        }
+
+        log_warning("/etc/mtab is not a symlink or not pointing to /proc/self/mounts. "
+                    "This is not supported anymore. "
+                    "Please make sure to replace this file by a symlink to avoid incorrect or misleading mount(8) output.");
+}
+
+static void test_usr(void) {
+
+        /* Check that /usr is not a separate fs */
+
+        if (dir_is_empty("/usr") <= 0)
+                return;
+
+        log_warning("/usr appears to be on its own filesytem and is not already mounted. This is not a supported setup. "
+                    "Some things will probably break (sometimes even silently) in mysterious ways. "
+                    "Consult http://freedesktop.org/wiki/Software/systemd/separate-usr-is-broken for more information.");
+}
+
+static void test_cgroups(void) {
+
+        if (access("/proc/cgroups", F_OK) >= 0)
+                return;
+
+        log_warning("CONFIG_CGROUPS was not set when your kernel was compiled. "
+                    "Systems without control groups are not supported. "
+                    "We will now sleep for 10s, and then continue boot-up. "
+                    "Expect breakage and please do not file bugs. "
+                    "Instead fix your kernel and enable CONFIG_CGROUPS." );
+
+        sleep(10);
+}
+
+int main(int argc, char *argv[]) {
+        Manager *m = NULL;
+        int r, retval = EXIT_FAILURE;
+        usec_t before_startup, after_startup;
+        char timespan[FORMAT_TIMESPAN_MAX];
+        FDSet *fds = NULL;
+        bool reexecute = false;
+        const char *shutdown_verb = NULL;
+        dual_timestamp initrd_timestamp = { 0ULL, 0ULL };
+        static char systemd[] = "systemd";
+        bool is_reexec = false;
+        int j;
+        bool loaded_policy = false;
+
+#ifdef HAVE_SYSV_COMPAT
+        if (getpid() != 1 && strstr(program_invocation_short_name, "init")) {
+                /* This is compatibility support for SysV, where
+                 * calling init as a user is identical to telinit. */
+
+                errno = -ENOENT;
+                execv(SYSTEMCTL_BINARY_PATH, argv);
+                log_error("Failed to exec " SYSTEMCTL_BINARY_PATH ": %m");
+                return 1;
+        }
+#endif
+
+        /* Determine if this is a reexecution or normal bootup. We do
+         * the full command line parsing much later, so let's just
+         * have a quick peek here. */
+
+        for (j = 1; j < argc; j++)
+                if (streq(argv[j], "--deserialize")) {
+                        is_reexec = true;
+                        break;
+                }
+
+        /* If we get started via the /sbin/init symlink then we are
+           called 'init'. After a subsequent reexecution we are then
+           called 'systemd'. That is confusing, hence let's call us
+           systemd right-away. */
+        program_invocation_short_name = systemd;
+        prctl(PR_SET_NAME, systemd);
+
+        saved_argv = argv;
+        saved_argc = argc;
+
+        log_show_color(isatty(STDERR_FILENO) > 0);
+        log_show_location(false);
+        log_set_max_level(LOG_INFO);
+
+        if (getpid() == 1) {
+                arg_running_as = MANAGER_SYSTEM;
+                log_set_target(detect_container(NULL) > 0 ? LOG_TARGET_CONSOLE : LOG_TARGET_JOURNAL_OR_KMSG);
+
+                if (!is_reexec) {
+                        if (selinux_setup(&loaded_policy) < 0)
+                                goto finish;
+                        if (ima_setup() < 0)
+                                goto finish;
+                }
+
+                log_open();
+
+                if (label_init() < 0)
+                        goto finish;
+
+                if (!is_reexec)
+                        if (hwclock_is_localtime() > 0) {
+                                int min;
+
+                                r = hwclock_apply_localtime_delta(&min);
+                                if (r < 0)
+                                        log_error("Failed to apply local time delta, ignoring: %s", strerror(-r));
+                                else
+                                        log_info("RTC configured in localtime, applying delta of %i minutes to system time.", min);
+                        }
+
+        } else {
+                arg_running_as = MANAGER_USER;
+                log_set_target(LOG_TARGET_AUTO);
+                log_open();
+        }
+
+        /* Initialize default unit */
+        if (set_default_unit(SPECIAL_DEFAULT_TARGET) < 0)
+                goto finish;
+
+        /* By default, mount "cpu" and "cpuacct" together */
+        arg_join_controllers = new(char**, 2);
+        if (!arg_join_controllers)
+                goto finish;
+
+        arg_join_controllers[0] = strv_new("cpu", "cpuacct", NULL);
+        arg_join_controllers[1] = NULL;
+
+        if (!arg_join_controllers[0])
+                goto finish;
+
+        /* Mount /proc, /sys and friends, so that /proc/cmdline and
+         * /proc/$PID/fd is available. */
+        if (geteuid() == 0 && !getenv("SYSTEMD_SKIP_API_MOUNTS")) {
+                r = mount_setup(loaded_policy);
+                if (r < 0)
+                        goto finish;
+        }
+
+        /* Reset all signal handlers. */
+        assert_se(reset_all_signal_handlers() == 0);
+
+        /* If we are init, we can block sigkill. Yay. */
+        ignore_signals(SIGNALS_IGNORE, -1);
+
+        if (parse_config_file() < 0)
+                goto finish;
+
+        if (arg_running_as == MANAGER_SYSTEM)
+                if (parse_proc_cmdline() < 0)
+                        goto finish;
+
+        log_parse_environment();
+
+        if (parse_argv(argc, argv) < 0)
+                goto finish;
+
+        if (arg_action == ACTION_TEST && geteuid() == 0) {
+                log_error("Don't run test mode as root.");
+                goto finish;
+        }
+
+        if (arg_running_as == MANAGER_SYSTEM &&
+            arg_action == ACTION_RUN &&
+            running_in_chroot() > 0) {
+                log_error("Cannot be run in a chroot() environment.");
+                goto finish;
+        }
+
+        if (arg_action == ACTION_HELP) {
+                retval = help();
+                goto finish;
+        } else if (arg_action == ACTION_DUMP_CONFIGURATION_ITEMS) {
+                unit_dump_config_items(stdout);
+                retval = EXIT_SUCCESS;
+                goto finish;
+        } else if (arg_action == ACTION_DONE) {
+                retval = EXIT_SUCCESS;
+                goto finish;
+        }
+
+        assert_se(arg_action == ACTION_RUN || arg_action == ACTION_TEST);
+
+        /* Close logging fds, in order not to confuse fdset below */
+        log_close();
+
+        /* Remember open file descriptors for later deserialization */
+        if (serialization) {
+                if ((r = fdset_new_fill(&fds)) < 0) {
+                        log_error("Failed to allocate fd set: %s", strerror(-r));
+                        goto finish;
+                }
+
+                assert_se(fdset_remove(fds, fileno(serialization)) >= 0);
+        } else
+                close_all_fds(NULL, 0);
+
+        /* Set up PATH unless it is already set */
+        setenv("PATH",
+#ifdef HAVE_SPLIT_USR
+               "/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin",
+#else
+               "/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin",
+#endif
+               arg_running_as == MANAGER_SYSTEM);
+
+        if (arg_running_as == MANAGER_SYSTEM) {
+                /* Parse the data passed to us by the initrd and unset it */
+                parse_initrd_timestamp(&initrd_timestamp);
+                filter_environ("RD_");
+
+                /* Unset some environment variables passed in from the
+                 * kernel that don't really make sense for us. */
+                unsetenv("HOME");
+                unsetenv("TERM");
+
+                /* All other variables are left as is, so that clients
+                 * can still read them via /proc/1/environ */
+        }
+
+        /* Move out of the way, so that we won't block unmounts */
+        assert_se(chdir("/")  == 0);
+
+        if (arg_running_as == MANAGER_SYSTEM) {
+                /* Become a session leader if we aren't one yet. */
+                setsid();
+
+                /* Disable the umask logic */
+                umask(0);
+        }
+
+        /* Make sure D-Bus doesn't fiddle with the SIGPIPE handlers */
+        dbus_connection_set_change_sigpipe(FALSE);
+
+        /* Reset the console, but only if this is really init and we
+         * are freshly booted */
+        if (arg_running_as == MANAGER_SYSTEM && arg_action == ACTION_RUN) {
+                console_setup(getpid() == 1 && !is_reexec);
+                make_null_stdio();
+        }
+
+        /* Open the logging devices, if possible and necessary */
+        log_open();
+
+        /* Make sure we leave a core dump without panicing the
+         * kernel. */
+        if (getpid() == 1)
+                install_crash_handler();
+
+        if (geteuid() == 0 && !getenv("SYSTEMD_SKIP_API_MOUNTS")) {
+                r = mount_cgroup_controllers(arg_join_controllers);
+                if (r < 0)
+                        goto finish;
+        }
+
+        log_full(arg_running_as == MANAGER_SYSTEM ? LOG_INFO : LOG_DEBUG,
+                 PACKAGE_STRING " running in %s mode. (" SYSTEMD_FEATURES "; " DISTRIBUTION ")", manager_running_as_to_string(arg_running_as));
+
+        if (arg_running_as == MANAGER_SYSTEM && !is_reexec) {
+                locale_setup();
+
+                if (arg_show_status || plymouth_running())
+                        status_welcome();
+
+                kmod_setup();
+                hostname_setup();
+                machine_id_setup();
+                loopback_setup();
+
+                test_mtab();
+                test_usr();
+                test_cgroups();
+        }
+
+        if ((r = manager_new(arg_running_as, &m)) < 0) {
+                log_error("Failed to allocate manager object: %s", strerror(-r));
+                goto finish;
+        }
+
+        m->confirm_spawn = arg_confirm_spawn;
+#ifdef HAVE_SYSV_COMPAT
+        m->sysv_console = arg_sysv_console;
+#endif
+        m->mount_auto = arg_mount_auto;
+        m->swap_auto = arg_swap_auto;
+        m->default_std_output = arg_default_std_output;
+        m->default_std_error = arg_default_std_error;
+
+        if (dual_timestamp_is_set(&initrd_timestamp))
+                m->initrd_timestamp = initrd_timestamp;
+
+        if (arg_default_controllers)
+                manager_set_default_controllers(m, arg_default_controllers);
+
+        manager_set_show_status(m, arg_show_status);
+
+        before_startup = now(CLOCK_MONOTONIC);
+
+        if ((r = manager_startup(m, serialization, fds)) < 0)
+                log_error("Failed to fully start up daemon: %s", strerror(-r));
+
+        if (fds) {
+                /* This will close all file descriptors that were opened, but
+                 * not claimed by any unit. */
+
+                fdset_free(fds);
+                fds = NULL;
+        }
+
+        if (serialization) {
+                fclose(serialization);
+                serialization = NULL;
+        } else {
+                DBusError error;
+                Unit *target = NULL;
+                Job *default_unit_job;
+
+                dbus_error_init(&error);
+
+                log_debug("Activating default unit: %s", arg_default_unit);
+
+                if ((r = manager_load_unit(m, arg_default_unit, NULL, &error, &target)) < 0) {
+                        log_error("Failed to load default target: %s", bus_error(&error, r));
+                        dbus_error_free(&error);
+                } else if (target->load_state == UNIT_ERROR)
+                        log_error("Failed to load default target: %s", strerror(-target->load_error));
+                else if (target->load_state == UNIT_MASKED)
+                        log_error("Default target masked.");
+
+                if (!target || target->load_state != UNIT_LOADED) {
+                        log_info("Trying to load rescue target...");
+
+                        if ((r = manager_load_unit(m, SPECIAL_RESCUE_TARGET, NULL, &error, &target)) < 0) {
+                                log_error("Failed to load rescue target: %s", bus_error(&error, r));
+                                dbus_error_free(&error);
+                                goto finish;
+                        } else if (target->load_state == UNIT_ERROR) {
+                                log_error("Failed to load rescue target: %s", strerror(-target->load_error));
+                                goto finish;
+                        } else if (target->load_state == UNIT_MASKED) {
+                                log_error("Rescue target masked.");
+                                goto finish;
+                        }
+                }
+
+                assert(target->load_state == UNIT_LOADED);
+
+                if (arg_action == ACTION_TEST) {
+                        printf("-> By units:\n");
+                        manager_dump_units(m, stdout, "\t");
+                }
+
+                r = manager_add_job(m, JOB_START, target, JOB_REPLACE, false, &error, &default_unit_job);
+                if (r < 0) {
+                        log_error("Failed to start default target: %s", bus_error(&error, r));
+                        dbus_error_free(&error);
+                        goto finish;
+                }
+                m->default_unit_job_id = default_unit_job->id;
+
+                after_startup = now(CLOCK_MONOTONIC);
+                log_full(arg_action == ACTION_TEST ? LOG_INFO : LOG_DEBUG,
+                         "Loaded units and determined initial transaction in %s.",
+                          format_timespan(timespan, sizeof(timespan), after_startup - before_startup));
+
+                if (arg_action == ACTION_TEST) {
+                        printf("-> By jobs:\n");
+                        manager_dump_jobs(m, stdout, "\t");
+                        retval = EXIT_SUCCESS;
+                        goto finish;
+                }
+        }
+
+        for (;;) {
+                if ((r = manager_loop(m)) < 0) {
+                        log_error("Failed to run mainloop: %s", strerror(-r));
+                        goto finish;
+                }
+
+                switch (m->exit_code) {
+
+                case MANAGER_EXIT:
+                        retval = EXIT_SUCCESS;
+                        log_debug("Exit.");
+                        goto finish;
+
+                case MANAGER_RELOAD:
+                        log_info("Reloading.");
+                        if ((r = manager_reload(m)) < 0)
+                                log_error("Failed to reload: %s", strerror(-r));
+                        break;
+
+                case MANAGER_REEXECUTE:
+                        if (prepare_reexecute(m, &serialization, &fds) < 0)
+                                goto finish;
+
+                        reexecute = true;
+                        log_notice("Reexecuting.");
+                        goto finish;
+
+                case MANAGER_REBOOT:
+                case MANAGER_POWEROFF:
+                case MANAGER_HALT:
+                case MANAGER_KEXEC: {
+                        static const char * const table[_MANAGER_EXIT_CODE_MAX] = {
+                                [MANAGER_REBOOT] = "reboot",
+                                [MANAGER_POWEROFF] = "poweroff",
+                                [MANAGER_HALT] = "halt",
+                                [MANAGER_KEXEC] = "kexec"
+                        };
+
+                        assert_se(shutdown_verb = table[m->exit_code]);
+
+                        log_notice("Shutting down.");
+                        goto finish;
+                }
+
+                default:
+                        assert_not_reached("Unknown exit code.");
+                }
+        }
+
+finish:
+        if (m)
+                manager_free(m);
+
+        free(arg_default_unit);
+        strv_free(arg_default_controllers);
+        free_join_controllers();
+
+        dbus_shutdown();
+
+        label_finish();
+
+        if (reexecute) {
+                const char *args[15];
+                unsigned i = 0;
+                char sfd[16];
+
+                assert(serialization);
+                assert(fds);
+
+                args[i++] = SYSTEMD_BINARY_PATH;
+
+                args[i++] = "--log-level";
+                args[i++] = log_level_to_string(log_get_max_level());
+
+                args[i++] = "--log-target";
+                args[i++] = log_target_to_string(log_get_target());
+
+                if (arg_running_as == MANAGER_SYSTEM)
+                        args[i++] = "--system";
+                else
+                        args[i++] = "--user";
+
+                if (arg_dump_core)
+                        args[i++] = "--dump-core";
+
+                if (arg_crash_shell)
+                        args[i++] = "--crash-shell";
+
+                if (arg_confirm_spawn)
+                        args[i++] = "--confirm-spawn";
+
+                if (arg_show_status)
+                        args[i++] = "--show-status=1";
+                else
+                        args[i++] = "--show-status=0";
+
+#ifdef HAVE_SYSV_COMPAT
+                if (arg_sysv_console)
+                        args[i++] = "--sysv-console=1";
+                else
+                        args[i++] = "--sysv-console=0";
+#endif
+
+                snprintf(sfd, sizeof(sfd), "%i", fileno(serialization));
+                char_array_0(sfd);
+
+                args[i++] = "--deserialize";
+                args[i++] = sfd;
+
+                args[i++] = NULL;
+
+                assert(i <= ELEMENTSOF(args));
+
+                execv(args[0], (char* const*) args);
+
+                log_error("Failed to reexecute: %m");
+        }
+
+        if (serialization)
+                fclose(serialization);
+
+        if (fds)
+                fdset_free(fds);
+
+        if (shutdown_verb) {
+                const char * command_line[] = {
+                        SYSTEMD_SHUTDOWN_BINARY_PATH,
+                        shutdown_verb,
+                        NULL
+                };
+
+                execv(SYSTEMD_SHUTDOWN_BINARY_PATH, (char **) command_line);
+                log_error("Failed to execute shutdown binary, freezing: %m");
+        }
+
+        if (getpid() == 1)
+                freeze();
+
+        return retval;
+}
diff --git a/src/manager.c b/src/manager.c
new file mode 100644 (file)
index 0000000..74bd740
--- /dev/null
@@ -0,0 +1,3199 @@
+/*-*- 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 <http://www.gnu.org/licenses/>.
+***/
+
+#include <assert.h>
+#include <errno.h>
+#include <string.h>
+#include <sys/epoll.h>
+#include <signal.h>
+#include <sys/signalfd.h>
+#include <sys/wait.h>
+#include <unistd.h>
+#include <sys/poll.h>
+#include <sys/reboot.h>
+#include <sys/ioctl.h>
+#include <linux/kd.h>
+#include <termios.h>
+#include <fcntl.h>
+#include <sys/types.h>
+#include <sys/stat.h>
+#include <dirent.h>
+
+#ifdef HAVE_AUDIT
+#include <libaudit.h>
+#endif
+
+#include <systemd/sd-daemon.h>
+
+#include "manager.h"
+#include "hashmap.h"
+#include "macro.h"
+#include "strv.h"
+#include "log.h"
+#include "util.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"
+
+/* 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;
+}
+
+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 */
+
+        if (!(m->environment = strv_copy(environ)))
+                goto fail;
+
+        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;
+
+        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 */
+        if ((r = manager_dispatch_sigchld(m)) < 0)
+                return r;
+
+        while (m->exit_code == MANAGER_RUNNING) {
+                struct epoll_event event;
+                int n;
+
+                if (!ratelimit_test(&rl)) {
+                        /* Yay, something is going seriously wrong, pause a little */
+                        log_warning("Looping too fast. Throttling execution a little.");
+                        sleep(1);
+                }
+
+                if (manager_dispatch_load_queue(m) > 0)
+                        continue;
+
+                if (manager_dispatch_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;
+
+                if ((n = epoll_wait(m->epoll_fd, &event, 1, -1)) < 0) {
+
+                        if (errno == EINTR)
+                                continue;
+
+                        return -errno;
+                }
+
+                assert(n == 1);
+
+                if ((r = process_event(m, &event)) < 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
new file mode 100644 (file)
index 0000000..a9d08f0
--- /dev/null
@@ -0,0 +1,301 @@
+/*-*- 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 <http://www.gnu.org/licenses/>.
+***/
+
+#include <stdbool.h>
+#include <inttypes.h>
+#include <stdio.h>
+#include <dbus/dbus.h>
+
+#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;
+
+        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/missing.h b/src/missing.h
new file mode 100644 (file)
index 0000000..095bf1f
--- /dev/null
@@ -0,0 +1,187 @@
+/*-*- Mode: C; c-basic-offset: 8; indent-tabs-mode: nil -*-*/
+
+#ifndef foomissinghfoo
+#define foomissinghfoo
+
+/***
+  This file is part 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 <http://www.gnu.org/licenses/>.
+***/
+
+/* Missing glibc definitions to access certain kernel APIs */
+
+#include <sys/resource.h>
+#include <sys/syscall.h>
+#include <fcntl.h>
+#include <unistd.h>
+#include <linux/oom.h>
+
+#ifdef HAVE_AUDIT
+#include <libaudit.h>
+#endif
+
+#include "macro.h"
+
+#ifdef ARCH_MIPS
+#include <asm/sgidefs.h>
+#endif
+
+#ifndef RLIMIT_RTTIME
+#define RLIMIT_RTTIME 15
+#endif
+
+#ifndef F_LINUX_SPECIFIC_BASE
+#define F_LINUX_SPECIFIC_BASE 1024
+#endif
+
+#ifndef F_SETPIPE_SZ
+#define F_SETPIPE_SZ (F_LINUX_SPECIFIC_BASE + 7)
+#endif
+
+#ifndef F_GETPIPE_SZ
+#define F_GETPIPE_SZ (F_LINUX_SPECIFIC_BASE + 8)
+#endif
+
+#ifndef IP_FREEBIND
+#define IP_FREEBIND 15
+#endif
+
+#ifndef OOM_SCORE_ADJ_MIN
+#define OOM_SCORE_ADJ_MIN (-1000)
+#endif
+
+#ifndef OOM_SCORE_ADJ_MAX
+#define OOM_SCORE_ADJ_MAX 1000
+#endif
+
+#ifndef AUDIT_SERVICE_START
+#define AUDIT_SERVICE_START 1130 /* Service (daemon) start */
+#endif
+
+#ifndef AUDIT_SERVICE_STOP
+#define AUDIT_SERVICE_STOP 1131 /* Service (daemon) stop */
+#endif
+
+#ifndef TIOCVHANGUP
+#define TIOCVHANGUP 0x5437
+#endif
+
+#ifndef IP_TRANSPARENT
+#define IP_TRANSPARENT 19
+#endif
+
+static inline int pivot_root(const char *new_root, const char *put_old) {
+        return syscall(SYS_pivot_root, new_root, put_old);
+}
+
+#ifdef __x86_64__
+#  ifndef __NR_fanotify_init
+#    define __NR_fanotify_init 300
+#  endif
+#  ifndef __NR_fanotify_mark
+#    define __NR_fanotify_mark 301
+#  endif
+#elif defined _MIPS_SIM
+#  if _MIPS_SIM == _MIPS_SIM_ABI32
+#    ifndef __NR_fanotify_init
+#      define __NR_fanotify_init 4336
+#    endif
+#    ifndef __NR_fanotify_mark
+#      define __NR_fanotify_mark 4337
+#    endif
+#  elif _MIPS_SIM == _MIPS_SIM_NABI32
+#    ifndef __NR_fanotify_init
+#      define __NR_fanotify_init 6300
+#    endif
+#    ifndef __NR_fanotify_mark
+#      define __NR_fanotify_mark 6301
+#    endif
+#  elif _MIPS_SIM == _MIPS_SIM_ABI64
+#    ifndef __NR_fanotify_init
+#      define __NR_fanotify_init 5295
+#    endif
+#    ifndef __NR_fanotify_mark
+#      define __NR_fanotify_mark 5296
+#    endif
+#  endif
+#else
+#  ifndef __NR_fanotify_init
+#    define __NR_fanotify_init 338
+#  endif
+#  ifndef __NR_fanotify_mark
+#    define __NR_fanotify_mark 339
+#  endif
+#endif
+
+static inline int fanotify_init(unsigned int flags, unsigned int event_f_flags) {
+        return syscall(__NR_fanotify_init, flags, event_f_flags);
+}
+
+static inline int fanotify_mark(int fanotify_fd, unsigned int flags, uint64_t mask,
+                                int dfd, const char *pathname) {
+#if defined _MIPS_SIM && _MIPS_SIM == _MIPS_SIM_ABI32
+        union {
+                uint64_t _64;
+                uint32_t _32[2];
+        } _mask;
+        _mask._64 = mask;
+
+        return syscall(__NR_fanotify_mark, fanotify_fd, flags,
+                       _mask._32[0], _mask._32[1], dfd, pathname);
+#else
+        return syscall(__NR_fanotify_mark, fanotify_fd, flags, mask, dfd, pathname);
+#endif
+}
+
+#ifndef BTRFS_IOCTL_MAGIC
+#define BTRFS_IOCTL_MAGIC 0x94
+#endif
+
+#ifndef BTRFS_PATH_NAME_MAX
+#define BTRFS_PATH_NAME_MAX 4087
+#endif
+
+struct btrfs_ioctl_vol_args {
+        int64_t fd;
+        char name[BTRFS_PATH_NAME_MAX + 1];
+};
+
+#ifndef BTRFS_IOC_DEFRAG
+#define BTRFS_IOC_DEFRAG _IOW(BTRFS_IOCTL_MAGIC, 2, struct btrfs_ioctl_vol_args)
+#endif
+
+#ifndef BTRFS_SUPER_MAGIC
+#define BTRFS_SUPER_MAGIC 0x9123683E
+#endif
+
+#ifndef MS_MOVE
+#define MS_MOVE 8192
+#endif
+
+#ifndef MS_PRIVATE
+#define MS_PRIVATE  (1 << 18)
+#endif
+
+static inline pid_t gettid(void) {
+        return (pid_t) syscall(SYS_gettid);
+}
+
+#ifndef SCM_SECURITY
+#define SCM_SECURITY 0x03
+#endif
+
+#endif
diff --git a/src/modules-load.c b/src/modules-load.c
new file mode 100644 (file)
index 0000000..ff1f690
--- /dev/null
@@ -0,0 +1,155 @@
+/*-*- 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 <http://www.gnu.org/licenses/>.
+***/
+
+#include <unistd.h>
+#include <fcntl.h>
+#include <errno.h>
+#include <string.h>
+#include <sys/stat.h>
+#include <limits.h>
+#include <dirent.h>
+#include <libkmod.h>
+
+#include "log.h"
+#include "util.h"
+#include "strv.h"
+
+#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 main(int argc, char *argv[]) {
+        int r = EXIT_FAILURE;
+        char **files, **fn;
+        struct kmod_ctx *ctx;
+        const int probe_flags = KMOD_PROBE_APPLY_BLACKLIST|KMOD_PROBE_IGNORE_LOADED;
+
+        if (argc > 1) {
+                log_error("This program takes no argument.");
+                return EXIT_FAILURE;
+        }
+
+        log_set_target(LOG_TARGET_AUTO);
+        log_parse_environment();
+        log_open();
+
+        umask(0022);
+
+        ctx = kmod_new(NULL, NULL);
+        if (!ctx) {
+                log_error("Failed to allocate memory for kmod.");
+                goto finish;
+        }
+
+        kmod_load_resources(ctx);
+
+        kmod_set_log_fn(ctx, systemd_kmod_log, NULL);
+
+        if (conf_files_list(&files, ".conf",
+                            "/etc/modules-load.d",
+                            "/run/modules-load.d",
+                            "/usr/local/lib/modules-load.d",
+                            "/usr/lib/modules-load.d",
+#ifdef HAVE_SPLIT_USR
+                            "/lib/modules-load.d",
+#endif
+                            NULL) < 0) {
+                log_error("Failed to enumerate modules-load.d files: %s", strerror(-r));
+                goto finish;
+        }
+
+        r = EXIT_SUCCESS;
+
+        STRV_FOREACH(fn, files) {
+                FILE *f;
+
+                f = fopen(*fn, "re");
+                if (!f) {
+                        if (errno == ENOENT)
+                                continue;
+
+                        log_error("Failed to open %s: %m", *fn);
+                        r = EXIT_FAILURE;
+                        continue;
+                }
+
+                log_debug("apply: %s\n", *fn);
+                for (;;) {
+                        char line[LINE_MAX], *l;
+                        struct kmod_list *itr, *modlist = NULL;
+                        int err;
+
+                        if (!fgets(line, sizeof(line), f))
+                                break;
+
+                        l = strstrip(line);
+                        if (*l == '#' || *l == 0)
+                                continue;
+
+                        err = kmod_module_new_from_lookup(ctx, l, &modlist);
+                        if (err < 0) {
+                                log_error("Failed to lookup alias '%s'", l);
+                                r = EXIT_FAILURE;
+                                continue;
+                        }
+
+                        kmod_list_foreach(itr, modlist) {
+                                struct kmod_module *mod;
+
+                                mod = kmod_module_get_module(itr);
+                                err = kmod_module_probe_insert_module(mod, probe_flags,
+                                                                      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': %s", kmod_module_get_name(mod),
+                                                        strerror(-err));
+                                        r = EXIT_FAILURE;
+                                }
+
+                                kmod_module_unref(mod);
+                        }
+
+                        kmod_module_unref_list(modlist);
+                }
+
+                if (ferror(f)) {
+                        log_error("Failed to read from file: %m");
+                        r = EXIT_FAILURE;
+                }
+
+                fclose(f);
+        }
+
+finish:
+        strv_free(files);
+        kmod_unref(ctx);
+
+        return r;
+}
diff --git a/src/mount-setup.c b/src/mount-setup.c
new file mode 100644 (file)
index 0000000..aaffb65
--- /dev/null
@@ -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 <http://www.gnu.org/licenses/>.
+***/
+
+#include <sys/mount.h>
+#include <errno.h>
+#include <sys/stat.h>
+#include <stdlib.h>
+#include <string.h>
+#include <libgen.h>
+#include <assert.h>
+#include <unistd.h>
+#include <ftw.h>
+
+#include "mount-setup.h"
+#include "log.h"
+#include "macro.h"
+#include "util.h"
+#include "label.h"
+#include "set.h"
+#include "strv.h"
+
+#ifndef TTY_GID
+#define TTY_GID 5
+#endif
+
+typedef struct MountPoint {
+        const char *what;
+        const char *where;
+        const char *type;
+        const char *options;
+        unsigned long flags;
+        bool fatal;
+} MountPoint;
+
+/* The first three entries we might need before SELinux is up. The
+ * fourth (securityfs) is needed by IMA to load a custom policy. The
+ * other ones we can delay until SELinux and IMA are loaded. */
+#define N_EARLY_MOUNT 4
+
+static const MountPoint mount_table[] = {
+        { "proc",     "/proc",                  "proc",     NULL,                MS_NOSUID|MS_NOEXEC|MS_NODEV, true },
+        { "sysfs",    "/sys",                   "sysfs",    NULL,                MS_NOSUID|MS_NOEXEC|MS_NODEV, true },
+        { "devtmpfs", "/dev",                   "devtmpfs", "mode=755",          MS_NOSUID,                    true },
+        { "securityfs", "/sys/kernel/security", "securityfs", NULL,              MS_NOSUID|MS_NOEXEC|MS_NODEV, false },
+        { "tmpfs",    "/dev/shm",               "tmpfs",    "mode=1777",         MS_NOSUID|MS_NODEV,           true },
+        { "devpts",   "/dev/pts",               "devpts",   "mode=620,gid=" STRINGIFY(TTY_GID), MS_NOSUID|MS_NOEXEC, false },
+        { "tmpfs",    "/run",                   "tmpfs",    "mode=755",          MS_NOSUID|MS_NODEV, true },
+        { "tmpfs",    "/sys/fs/cgroup",         "tmpfs",    "mode=755",          MS_NOSUID|MS_NOEXEC|MS_NODEV, false },
+        { "cgroup",   "/sys/fs/cgroup/systemd", "cgroup",   "none,name=systemd", MS_NOSUID|MS_NOEXEC|MS_NODEV, false },
+};
+
+/* These are API file systems that might be mounted by other software,
+ * we just list them here so that we know that we should ignore them */
+
+static const char * const ignore_paths[] = {
+        "/sys/fs/selinux",
+        "/selinux",
+        "/proc/bus/usb"
+};
+
+bool mount_point_is_api(const char *path) {
+        unsigned i;
+
+        /* Checks if this mount point is considered "API", and hence
+         * should be ignored */
+
+        for (i = 0; i < ELEMENTSOF(mount_table); i ++)
+                if (path_equal(path, mount_table[i].where))
+                        return true;
+
+        return path_startswith(path, "/sys/fs/cgroup/");
+}
+
+bool mount_point_ignore(const char *path) {
+        unsigned i;
+
+        for (i = 0; i < ELEMENTSOF(ignore_paths); i++)
+                if (path_equal(path, ignore_paths[i]))
+                        return true;
+
+        return false;
+}
+
+static int mount_one(const MountPoint *p, bool relabel) {
+        int r;
+
+        assert(p);
+
+        /* Relabel first, just in case */
+        if (relabel)
+                label_fix(p->where, true);
+
+        if ((r = path_is_mount_point(p->where, true)) < 0)
+                return r;
+
+        if (r > 0)
+                return 0;
+
+        /* The access mode here doesn't really matter too much, since
+         * the mounted file system will take precedence anyway. */
+        mkdir_p(p->where, 0755);
+
+        log_debug("Mounting %s to %s of type %s with options %s.",
+                  p->what,
+                  p->where,
+                  p->type,
+                  strna(p->options));
+
+        if (mount(p->what,
+                  p->where,
+                  p->type,
+                  p->flags,
+                  p->options) < 0) {
+                log_error("Failed to mount %s: %s", p->where, strerror(errno));
+                return p->fatal ? -errno : 0;
+        }
+
+        /* Relabel again, since we now mounted something fresh here */
+        if (relabel)
+                label_fix(p->where, false);
+
+        return 1;
+}
+
+int mount_setup_early(void) {
+        unsigned i;
+        int r = 0;
+
+        assert_cc(N_EARLY_MOUNT <= ELEMENTSOF(mount_table));
+
+        /* Do a minimal mount of /proc and friends to enable the most
+         * basic stuff, such as SELinux */
+        for (i = 0; i < N_EARLY_MOUNT; i ++)  {
+                int j;
+
+                j = mount_one(mount_table + i, false);
+                if (r == 0)
+                        r = j;
+        }
+
+        return r;
+}
+
+int mount_cgroup_controllers(char ***join_controllers) {
+        int r;
+        FILE *f;
+        char buf[LINE_MAX];
+        Set *controllers;
+
+        /* Mount all available cgroup controllers that are built into the kernel. */
+
+        f = fopen("/proc/cgroups", "re");
+        if (!f) {
+                log_error("Failed to enumerate cgroup controllers: %m");
+                return 0;
+        }
+
+        controllers = set_new(string_hash_func, string_compare_func);
+        if (!controllers) {
+                r = -ENOMEM;
+                log_error("Failed to allocate controller set.");
+                goto finish;
+        }
+
+        /* Ignore the header line */
+        (void) fgets(buf, sizeof(buf), f);
+
+        for (;;) {
+                char *controller;
+                int enabled = 0;
+
+                if (fscanf(f, "%ms %*i %*i %i", &controller, &enabled) != 2) {
+
+                        if (feof(f))
+                                break;
+
+                        log_error("Failed to parse /proc/cgroups.");
+                        r = -EIO;
+                        goto finish;
+                }
+
+                if (!enabled) {
+                        free(controller);
+                        continue;
+                }
+
+                r = set_put(controllers, controller);
+                if (r < 0) {
+                        log_error("Failed to add controller to set.");
+                        free(controller);
+                        goto finish;
+                }
+        }
+
+        for (;;) {
+                MountPoint p;
+                char *controller, *where, *options;
+                char ***k = NULL;
+
+                controller = set_steal_first(controllers);
+                if (!controller)
+                        break;
+
+                if (join_controllers)
+                        for (k = join_controllers; *k; k++)
+                                if (strv_find(*k, controller))
+                                        break;
+
+                if (k && *k) {
+                        char **i, **j;
+
+                        for (i = *k, j = *k; *i; i++) {
+
+                                if (!streq(*i, controller)) {
+                                        char *t;
+
+                                        t = set_remove(controllers, *i);
+                                        if (!t) {
+                                                free(*i);
+                                                continue;
+                                        }
+                                        free(t);
+                                }
+
+                                *(j++) = *i;
+                        }
+
+                        *j = NULL;
+
+                        options = strv_join(*k, ",");
+                        if (!options) {
+                                log_error("Failed to join options");
+                                free(controller);
+                                r = -ENOMEM;
+                                goto finish;
+                        }
+
+                } else {
+                        options = controller;
+                        controller = NULL;
+                }
+
+                where = strappend("/sys/fs/cgroup/", options);
+                if (!where) {
+                        log_error("Failed to build path");
+                        free(options);
+                        r = -ENOMEM;
+                        goto finish;
+                }
+
+                zero(p);
+                p.what = "cgroup";
+                p.where = where;
+                p.type = "cgroup";
+                p.options = options;
+                p.flags = MS_NOSUID|MS_NOEXEC|MS_NODEV;
+                p.fatal = false;
+
+                r = mount_one(&p, true);
+                free(controller);
+                free(where);
+
+                if (r < 0) {
+                        free(options);
+                        goto finish;
+                }
+
+                if (r > 0 && k && *k) {
+                        char **i;
+
+                        for (i = *k; *i; i++) {
+                                char *t;
+
+                                t = strappend("/sys/fs/cgroup/", *i);
+                                if (!t) {
+                                        log_error("Failed to build path");
+                                        r = -ENOMEM;
+                                        free(options);
+                                        goto finish;
+                                }
+
+                                r = symlink(options, t);
+                                free(t);
+
+                                if (r < 0 && errno != EEXIST) {
+                                        log_error("Failed to create symlink: %m");
+                                        r = -errno;
+                                        free(options);
+                                        goto finish;
+                                }
+                        }
+                }
+
+                free(options);
+        }
+
+        r = 0;
+
+finish:
+        set_free_free(controllers);
+
+        fclose(f);
+
+        return r;
+}
+
+static int symlink_and_label(const char *old_path, const char *new_path) {
+        int r;
+
+        assert(old_path);
+        assert(new_path);
+
+        if ((r = label_symlinkfile_set(new_path)) < 0)
+                return r;
+
+        if (symlink(old_path, new_path) < 0)
+                r = -errno;
+
+        label_file_clear();
+
+        return r;
+}
+
+static int nftw_cb(
+                const char *fpath,
+                const struct stat *sb,
+                int tflag,
+                struct FTW *ftwbuf) {
+
+        /* No need to label /dev twice in a row... */
+        if (_unlikely_(ftwbuf->level == 0))
+                return FTW_CONTINUE;
+
+        label_fix(fpath, true);
+
+        /* /run/initramfs is static data and big, no need to
+         * dynamically relabel its contents at boot... */
+        if (_unlikely_(ftwbuf->level == 1 &&
+                      tflag == FTW_D &&
+                      streq(fpath, "/run/initramfs")))
+                return FTW_SKIP_SUBTREE;
+
+        return FTW_CONTINUE;
+};
+
+int mount_setup(bool loaded_policy) {
+
+        static const char symlinks[] =
+                "/proc/kcore\0"      "/dev/core\0"
+                "/proc/self/fd\0"    "/dev/fd\0"
+                "/proc/self/fd/0\0"  "/dev/stdin\0"
+                "/proc/self/fd/1\0"  "/dev/stdout\0"
+                "/proc/self/fd/2\0"  "/dev/stderr\0";
+
+        static const char relabel[] =
+                "/run/initramfs/root-fsck\0"
+                "/run/initramfs/shutdown\0";
+
+        int r;
+        unsigned i;
+        const char *j, *k;
+
+        for (i = 0; i < ELEMENTSOF(mount_table); i ++) {
+                r = mount_one(mount_table + i, true);
+
+                if (r < 0)
+                        return r;
+        }
+
+        /* Nodes in devtmpfs and /run need to be manually updated for
+         * the appropriate labels, after mounting. The other virtual
+         * API file systems like /sys and /proc do not need that, they
+         * use the same label for all their files. */
+        if (loaded_policy) {
+                usec_t before_relabel, after_relabel;
+                char timespan[FORMAT_TIMESPAN_MAX];
+
+                before_relabel = now(CLOCK_MONOTONIC);
+
+                nftw("/dev", nftw_cb, 64, FTW_MOUNT|FTW_PHYS|FTW_ACTIONRETVAL);
+                nftw("/run", nftw_cb, 64, FTW_MOUNT|FTW_PHYS|FTW_ACTIONRETVAL);
+
+                /* Explicitly relabel these */
+                NULSTR_FOREACH(j, relabel)
+                        label_fix(j, true);
+
+                after_relabel = now(CLOCK_MONOTONIC);
+
+                log_info("Relabelled /dev and /run in %s.",
+                         format_timespan(timespan, sizeof(timespan), after_relabel - before_relabel));
+        }
+
+        /* Create a few default symlinks, which are normally created
+         * by udevd, but some scripts might need them before we start
+         * udevd. */
+        NULSTR_FOREACH_PAIR(j, k, symlinks)
+                symlink_and_label(j, k);
+
+        /* Create a few directories we always want around */
+        label_mkdir("/run/systemd", 0755);
+        label_mkdir("/run/systemd/system", 0755);
+
+        return 0;
+}
diff --git a/src/mount-setup.h b/src/mount-setup.h
new file mode 100644 (file)
index 0000000..c1a27ba
--- /dev/null
@@ -0,0 +1,36 @@
+/*-*- Mode: C; c-basic-offset: 8; indent-tabs-mode: nil -*-*/
+
+#ifndef foomountsetuphfoo
+#define foomountsetuphfoo
+
+/***
+  This file is part 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 <http://www.gnu.org/licenses/>.
+***/
+
+#include <stdbool.h>
+
+int mount_setup_early(void);
+
+int mount_setup(bool loaded_policy);
+
+int mount_cgroup_controllers(char ***join_controllers);
+
+bool mount_point_is_api(const char *path);
+bool mount_point_ignore(const char *path);
+
+#endif
diff --git a/src/mount.c b/src/mount.c
new file mode 100644 (file)
index 0000000..ed0f819
--- /dev/null
@@ -0,0 +1,1929 @@
+/*-*- 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 <http://www.gnu.org/licenses/>.
+***/
+
+#include <errno.h>
+#include <stdio.h>
+#include <mntent.h>
+#include <sys/epoll.h>
+#include <signal.h>
+
+#include "unit.h"
+#include "mount.h"
+#include "load-fragment.h"
+#include "load-dropin.h"
+#include "log.h"
+#include "strv.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
new file mode 100644 (file)
index 0000000..9318444
--- /dev/null
@@ -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 <http://www.gnu.org/licenses/>.
+***/
+
+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
new file mode 100644 (file)
index 0000000..09bc829
--- /dev/null
@@ -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 <http://www.gnu.org/licenses/>.
+***/
+
+#include <errno.h>
+#include <sys/mount.h>
+#include <string.h>
+#include <stdio.h>
+#include <unistd.h>
+#include <sys/stat.h>
+#include <sys/types.h>
+#include <sched.h>
+#include <sys/syscall.h>
+#include <limits.h>
+#include <linux/fs.h>
+
+#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
new file mode 100644 (file)
index 0000000..7cf1ded
--- /dev/null
@@ -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 <http://www.gnu.org/licenses/>.
+***/
+
+#include <stdbool.h>
+
+int setup_namespace(
+                char **writable,
+                char **readable,
+                char **inaccessible,
+                bool private_tmp,
+                unsigned long flags);
+
+#endif
diff --git a/src/notify.c b/src/notify.c
new file mode 100644 (file)
index 0000000..943808e
--- /dev/null
@@ -0,0 +1,228 @@
+/*-*- 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 <http://www.gnu.org/licenses/>.
+***/
+
+#include <stdio.h>
+#include <getopt.h>
+#include <error.h>
+#include <errno.h>
+#include <unistd.h>
+#include <stdlib.h>
+#include <string.h>
+
+#include <systemd/sd-daemon.h>
+
+#include "strv.h"
+#include "util.h"
+#include "log.h"
+#include "sd-readahead.h"
+#include "build.h"
+
+static bool arg_ready = false;
+static pid_t arg_pid = 0;
+static const char *arg_status = NULL;
+static bool arg_booted = false;
+static const char *arg_readahead = NULL;
+
+static int help(void) {
+
+        printf("%s [OPTIONS...] [VARIABLE=VALUE...]\n\n"
+               "Notify the init system about service status updates.\n\n"
+               "  -h --help             Show this help\n"
+               "     --version          Show package version\n"
+               "     --ready            Inform the init system about service start-up completion\n"
+               "     --pid[=PID]        Set main pid of daemon\n"
+               "     --status=TEXT      Set status text\n"
+               "     --booted           Returns 0 if the system was booted up with systemd, non-zero otherwise\n"
+               "     --readahead=ACTION Controls read-ahead operations\n",
+               program_invocation_short_name);
+
+        return 0;
+}
+
+static int parse_argv(int argc, char *argv[]) {
+
+        enum {
+                ARG_READY = 0x100,
+                ARG_VERSION,
+                ARG_PID,
+                ARG_STATUS,
+                ARG_BOOTED,
+                ARG_READAHEAD
+        };
+
+        static const struct option options[] = {
+                { "help",      no_argument,       NULL, 'h'           },
+                { "version",   no_argument,       NULL, ARG_VERSION   },
+                { "ready",     no_argument,       NULL, ARG_READY     },
+                { "pid",       optional_argument, NULL, ARG_PID       },
+                { "status",    required_argument, NULL, ARG_STATUS    },
+                { "booted",    no_argument,       NULL, ARG_BOOTED    },
+                { "readahead", required_argument, NULL, ARG_READAHEAD },
+                { NULL,        0,                 NULL, 0             }
+        };
+
+        int c;
+
+        assert(argc >= 0);
+        assert(argv);
+
+        while ((c = getopt_long(argc, argv, "h", options, NULL)) >= 0) {
+
+                switch (c) {
+
+                case 'h':
+                        help();
+                        return 0;
+
+                case ARG_VERSION:
+                        puts(PACKAGE_STRING);
+                        puts(DISTRIBUTION);
+                        puts(SYSTEMD_FEATURES);
+                        return 0;
+
+                case ARG_READY:
+                        arg_ready = true;
+                        break;
+
+                case ARG_PID:
+
+                        if (optarg) {
+                                if (parse_pid(optarg, &arg_pid) < 0) {
+                                        log_error("Failed to parse PID %s.", optarg);
+                                        return -EINVAL;
+                                }
+                        } else
+                                arg_pid = getppid();
+
+                        break;
+
+                case ARG_STATUS:
+                        arg_status = optarg;
+                        break;
+
+                case ARG_BOOTED:
+                        arg_booted = true;
+                        break;
+
+                case ARG_READAHEAD:
+                        arg_readahead = optarg;
+                        break;
+
+                case '?':
+                        return -EINVAL;
+
+                default:
+                        log_error("Unknown option code %c", c);
+                        return -EINVAL;
+                }
+        }
+
+        if (optind >= argc &&
+            !arg_ready &&
+            !arg_status &&
+            !arg_pid &&
+            !arg_booted &&
+            !arg_readahead) {
+                help();
+                return -EINVAL;
+        }
+
+        return 1;
+}
+
+int main(int argc, char* argv[]) {
+        char* our_env[4], **final_env = NULL;
+        unsigned i = 0;
+        char *status = NULL, *cpid = NULL, *n = NULL;
+        int r, retval = EXIT_FAILURE;
+
+        log_parse_environment();
+        log_open();
+
+        if ((r = parse_argv(argc, argv)) <= 0) {
+                retval = r < 0 ? EXIT_FAILURE : EXIT_SUCCESS;
+                goto finish;
+        }
+
+        if (arg_booted)
+                return sd_booted() <= 0;
+
+        if (arg_readahead) {
+                if ((r = sd_readahead(arg_readahead)) < 0) {
+                        log_error("Failed to issue read-ahead control command: %s", strerror(-r));
+                        goto finish;
+                }
+        }
+
+        if (arg_ready)
+                our_env[i++] = (char*) "READY=1";
+
+        if (arg_status) {
+                if (!(status = strappend("STATUS=", arg_status))) {
+                        log_error("Failed to allocate STATUS string.");
+                        goto finish;
+                }
+
+                our_env[i++] = status;
+        }
+
+        if (arg_pid > 0) {
+                if (asprintf(&cpid, "MAINPID=%lu", (unsigned long) arg_pid) < 0) {
+                        log_error("Failed to allocate MAINPID string.");
+                        goto finish;
+                }
+
+                our_env[i++] = cpid;
+        }
+
+        our_env[i++] = NULL;
+
+        if (!(final_env = strv_env_merge(2, our_env, argv + optind))) {
+                log_error("Failed to merge string sets.");
+                goto finish;
+        }
+
+        if (strv_length(final_env) <= 0) {
+                retval = EXIT_SUCCESS;
+                goto finish;
+        }
+
+        if (!(n = strv_join(final_env, "\n"))) {
+                log_error("Failed to concatenate strings.");
+                goto finish;
+        }
+
+        if ((r = sd_notify(false, n)) < 0) {
+                log_error("Failed to notify init system: %s", strerror(-r));
+                goto finish;
+        }
+
+        retval = r <= 0 ? EXIT_FAILURE : EXIT_SUCCESS;
+
+finish:
+        free(status);
+        free(cpid);
+        free(n);
+
+        strv_free(final_env);
+
+        return retval;
+}
diff --git a/src/nspawn.c b/src/nspawn.c
new file mode 100644 (file)
index 0000000..6f5a9d9
--- /dev/null
@@ -0,0 +1,894 @@
+/*-*- 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 <http://www.gnu.org/licenses/>.
+***/
+
+#include <signal.h>
+#include <sched.h>
+#include <unistd.h>
+#include <sys/types.h>
+#include <sys/syscall.h>
+#include <sys/mount.h>
+#include <sys/wait.h>
+#include <stdlib.h>
+#include <string.h>
+#include <stdio.h>
+#include <errno.h>
+#include <sys/prctl.h>
+#include <sys/capability.h>
+#include <getopt.h>
+#include <sys/epoll.h>
+#include <termios.h>
+#include <sys/signalfd.h>
+#include <grp.h>
+#include <linux/fs.h>
+
+#include <systemd/sd-daemon.h>
+
+#include "log.h"
+#include "util.h"
+#include "missing.h"
+#include "cgroup-util.h"
+#include "strv.h"
+#include "loopback-setup.h"
+
+static char *arg_directory = NULL;
+static char *arg_user = NULL;
+static bool arg_private_network = false;
+
+static int help(void) {
+
+        printf("%s [OPTIONS...] [PATH] [ARGUMENTS...]\n\n"
+               "Spawn a minimal namespace container for debugging, testing and building.\n\n"
+               "  -h --help            Show this help\n"
+               "  -D --directory=NAME  Root directory for the container\n"
+               "  -u --user=USER       Run the command under specified user or uid\n"
+               "     --private-network Disable network in container\n",
+               program_invocation_short_name);
+
+        return 0;
+}
+
+static int parse_argv(int argc, char *argv[]) {
+
+        enum {
+                ARG_PRIVATE_NETWORK = 0x100
+        };
+
+        static const struct option options[] = {
+                { "help",            no_argument,       NULL, 'h'                 },
+                { "directory",       required_argument, NULL, 'D'                 },
+                { "user",            required_argument, NULL, 'u'                 },
+                { "private-network", no_argument,       NULL, ARG_PRIVATE_NETWORK },
+                { NULL,              0,                 NULL, 0                   }
+        };
+
+        int c;
+
+        assert(argc >= 0);
+        assert(argv);
+
+        while ((c = getopt_long(argc, argv, "+hD:u:", options, NULL)) >= 0) {
+
+                switch (c) {
+
+                case 'h':
+                        help();
+                        return 0;
+
+                case 'D':
+                        free(arg_directory);
+                        if (!(arg_directory = strdup(optarg))) {
+                                log_error("Failed to duplicate root directory.");
+                                return -ENOMEM;
+                        }
+
+                        break;
+
+                case 'u':
+                        free(arg_user);
+                        if (!(arg_user = strdup(optarg))) {
+                                log_error("Failed to duplicate user name.");
+                                return -ENOMEM;
+                        }
+
+                        break;
+
+                case ARG_PRIVATE_NETWORK:
+                        arg_private_network = true;
+                        break;
+
+                case '?':
+                        return -EINVAL;
+
+                default:
+                        log_error("Unknown option code %c", c);
+                        return -EINVAL;
+                }
+        }
+
+        return 1;
+}
+
+static int mount_all(const char *dest) {
+
+        typedef struct MountPoint {
+                const char *what;
+                const char *where;
+                const char *type;
+                const char *options;
+                unsigned long flags;
+                bool fatal;
+        } MountPoint;
+
+        static const MountPoint mount_table[] = {
+                { "proc",      "/proc",     "proc",  NULL,       MS_NOSUID|MS_NOEXEC|MS_NODEV, true  },
+                { "/proc/sys", "/proc/sys", "bind",  NULL,       MS_BIND, true                       },   /* Bind mount first */
+                { "/proc/sys", "/proc/sys", "bind",  NULL,       MS_BIND|MS_RDONLY|MS_REMOUNT, true  },   /* Then, make it r/o */
+                { "/sys",      "/sys",      "bind",  NULL,       MS_BIND,                      true  },   /* Bind mount first */
+                { "/sys",      "/sys",      "bind",  NULL,       MS_BIND|MS_RDONLY|MS_REMOUNT, true  },   /* Then, make it r/o */
+                { "tmpfs",     "/dev",      "tmpfs", "mode=755", MS_NOSUID,                    true  },
+                { "/dev/pts",  "/dev/pts",  "bind",  NULL,       MS_BIND,                      true  },
+                { "tmpfs",     "/run",      "tmpfs", "mode=755", MS_NOSUID|MS_NODEV,           true  },
+#ifdef HAVE_SELINUX
+                { "/sys/fs/selinux", "/sys/fs/selinux", "bind", NULL, MS_BIND,                      false },  /* Bind mount first */
+                { "/sys/fs/selinux", "/sys/fs/selinux", "bind", NULL, MS_BIND|MS_RDONLY|MS_REMOUNT, false },  /* Then, make it r/o */
+#endif
+        };
+
+        unsigned k;
+        int r = 0;
+        char *where;
+
+        for (k = 0; k < ELEMENTSOF(mount_table); k++) {
+                int t;
+
+                if (asprintf(&where, "%s/%s", dest, mount_table[k].where) < 0) {
+                        log_error("Out of memory");
+
+                        if (r == 0)
+                                r = -ENOMEM;
+
+                        break;
+                }
+
+                if ((t = path_is_mount_point(where, false)) < 0) {
+                        log_error("Failed to detect whether %s is a mount point: %s", where, strerror(-t));
+                        free(where);
+
+                        if (r == 0)
+                                r = t;
+
+                        continue;
+                }
+
+                mkdir_p(where, 0755);
+
+                if (mount(mount_table[k].what,
+                          where,
+                          mount_table[k].type,
+                          mount_table[k].flags,
+                          mount_table[k].options) < 0 &&
+                    mount_table[k].fatal) {
+
+                        log_error("mount(%s) failed: %m", where);
+
+                        if (r == 0)
+                                r = -errno;
+                }
+
+                free(where);
+        }
+
+        /* Fix the timezone, if possible */
+        if (asprintf(&where, "%s/etc/localtime", dest) >= 0) {
+
+                if (mount("/etc/localtime", where, "bind", MS_BIND, NULL) >= 0)
+                        mount("/etc/localtime", where, "bind", MS_BIND|MS_REMOUNT|MS_RDONLY, NULL);
+
+                free(where);
+        }
+
+        if (asprintf(&where, "%s/etc/timezone", dest) >= 0) {
+
+                if (mount("/etc/timezone", where, "bind", MS_BIND, NULL) >= 0)
+                        mount("/etc/timezone", where, "bind", MS_BIND|MS_REMOUNT|MS_RDONLY, NULL);
+
+                free(where);
+        }
+
+        return r;
+}
+
+static int copy_devnodes(const char *dest, const char *console) {
+
+        static const char devnodes[] =
+                "null\0"
+                "zero\0"
+                "full\0"
+                "random\0"
+                "urandom\0"
+                "tty\0"
+                "ptmx\0"
+                "kmsg\0"
+                "rtc0\0";
+
+        const char *d;
+        int r = 0, k;
+        mode_t u;
+        struct stat st;
+        char *from = NULL, *to = NULL;
+
+        assert(dest);
+        assert(console);
+
+        u = umask(0000);
+
+        NULSTR_FOREACH(d, devnodes) {
+                from = to = NULL;
+
+                asprintf(&from, "/dev/%s", d);
+                asprintf(&to, "%s/dev/%s", dest, d);
+
+                if (!from || !to) {
+                        log_error("Failed to allocate devnode path");
+
+                        free(from);
+                        free(to);
+
+                        from = to = NULL;
+
+                        if (r == 0)
+                                r = -ENOMEM;
+
+                        break;
+                }
+
+                if (stat(from, &st) < 0) {
+
+                        if (errno != ENOENT) {
+                                log_error("Failed to stat %s: %m", from);
+                                if (r == 0)
+                                        r = -errno;
+                        }
+
+                } else if (!S_ISCHR(st.st_mode) && !S_ISBLK(st.st_mode)) {
+
+                        log_error("%s is not a char or block device, cannot copy.", from);
+                        if (r == 0)
+                                r = -EIO;
+
+                } else if (mknod(to, st.st_mode, st.st_rdev) < 0) {
+
+                        log_error("mknod(%s) failed: %m", dest);
+                        if (r == 0)
+                                r = -errno;
+                }
+
+                free(from);
+                free(to);
+        }
+
+        if (stat(console, &st) < 0) {
+
+                log_error("Failed to stat %s: %m", console);
+                if (r == 0)
+                        r = -errno;
+
+                goto finish;
+
+        } else if (!S_ISCHR(st.st_mode)) {
+
+                log_error("/dev/console is not a char device.");
+                if (r == 0)
+                        r = -EIO;
+
+                goto finish;
+        }
+
+        if (asprintf(&to, "%s/dev/console", dest) < 0) {
+
+                log_error("Out of memory");
+                if (r == 0)
+                        r = -ENOMEM;
+
+                 goto finish;
+        }
+
+        /* We need to bind mount the right tty to /dev/console since
+         * ptys can only exist on pts file systems. To have something
+         * to bind mount things on we create a device node first, that
+         * has the right major/minor (note that the major minor
+         * doesn't actually matter here, since we mount it over
+         * anyway). */
+
+        if (mknod(to, (st.st_mode & ~07777) | 0600, st.st_rdev) < 0)
+                log_error("mknod for /dev/console failed: %m");
+
+        if (mount(console, to, "bind", MS_BIND, NULL) < 0) {
+                log_error("bind mount for /dev/console failed: %m");
+
+                if (r == 0)
+                        r = -errno;
+        }
+
+        free(to);
+
+        if ((k = chmod_and_chown(console, 0600, 0, 0)) < 0) {
+                log_error("Failed to correct access mode for TTY: %s", strerror(-k));
+
+                if (r == 0)
+                        r = k;
+        }
+
+finish:
+        umask(u);
+
+        return r;
+}
+
+static int drop_capabilities(void) {
+        static const unsigned long retain[] = {
+                CAP_CHOWN,
+                CAP_DAC_OVERRIDE,
+                CAP_DAC_READ_SEARCH,
+                CAP_FOWNER,
+                CAP_FSETID,
+                CAP_IPC_OWNER,
+                CAP_KILL,
+                CAP_LEASE,
+                CAP_LINUX_IMMUTABLE,
+                CAP_NET_BIND_SERVICE,
+                CAP_NET_BROADCAST,
+                CAP_NET_RAW,
+                CAP_SETGID,
+                CAP_SETFCAP,
+                CAP_SETPCAP,
+                CAP_SETUID,
+                CAP_SYS_ADMIN,
+                CAP_SYS_CHROOT,
+                CAP_SYS_NICE,
+                CAP_SYS_PTRACE,
+                CAP_SYS_TTY_CONFIG
+        };
+
+        unsigned long l;
+
+        for (l = 0; l <= cap_last_cap(); l++) {
+                unsigned i;
+
+                for (i = 0; i < ELEMENTSOF(retain); i++)
+                        if (retain[i] == l)
+                                break;
+
+                if (i < ELEMENTSOF(retain))
+                        continue;
+
+                if (prctl(PR_CAPBSET_DROP, l) < 0) {
+                        log_error("PR_CAPBSET_DROP failed: %m");
+                        return -errno;
+                }
+        }
+
+        return 0;
+}
+
+static int is_os_tree(const char *path) {
+        int r;
+        char *p;
+        /* We use /bin/sh as flag file if something is an OS */
+
+        if (asprintf(&p, "%s/bin/sh", path) < 0)
+                return -ENOMEM;
+
+        r = access(p, F_OK);
+        free(p);
+
+        return r < 0 ? 0 : 1;
+}
+
+static int process_pty(int master, sigset_t *mask) {
+
+        char in_buffer[LINE_MAX], out_buffer[LINE_MAX];
+        size_t in_buffer_full = 0, out_buffer_full = 0;
+        struct epoll_event stdin_ev, stdout_ev, master_ev, signal_ev;
+        bool stdin_readable = false, stdout_writable = false, master_readable = false, master_writable = false;
+        int ep = -1, signal_fd = -1, r;
+
+        fd_nonblock(STDIN_FILENO, 1);
+        fd_nonblock(STDOUT_FILENO, 1);
+        fd_nonblock(master, 1);
+
+        if ((signal_fd = signalfd(-1, mask, SFD_NONBLOCK|SFD_CLOEXEC)) < 0) {
+                log_error("signalfd(): %m");
+                r = -errno;
+                goto finish;
+        }
+
+        if ((ep = epoll_create1(EPOLL_CLOEXEC)) < 0) {
+                log_error("Failed to create epoll: %m");
+                r = -errno;
+                goto finish;
+        }
+
+        zero(stdin_ev);
+        stdin_ev.events = EPOLLIN|EPOLLET;
+        stdin_ev.data.fd = STDIN_FILENO;
+
+        zero(stdout_ev);
+        stdout_ev.events = EPOLLOUT|EPOLLET;
+        stdout_ev.data.fd = STDOUT_FILENO;
+
+        zero(master_ev);
+        master_ev.events = EPOLLIN|EPOLLOUT|EPOLLET;
+        master_ev.data.fd = master;
+
+        zero(signal_ev);
+        signal_ev.events = EPOLLIN;
+        signal_ev.data.fd = signal_fd;
+
+        if (epoll_ctl(ep, EPOLL_CTL_ADD, STDIN_FILENO, &stdin_ev) < 0 ||
+            epoll_ctl(ep, EPOLL_CTL_ADD, STDOUT_FILENO, &stdout_ev) < 0 ||
+            epoll_ctl(ep, EPOLL_CTL_ADD, master, &master_ev) < 0 ||
+            epoll_ctl(ep, EPOLL_CTL_ADD, signal_fd, &signal_ev) < 0) {
+                log_error("Failed to regiser fds in epoll: %m");
+                r = -errno;
+                goto finish;
+        }
+
+        for (;;) {
+                struct epoll_event ev[16];
+                ssize_t k;
+                int i, nfds;
+
+                if ((nfds = epoll_wait(ep, ev, ELEMENTSOF(ev), -1)) < 0) {
+
+                        if (errno == EINTR || errno == EAGAIN)
+                                continue;
+
+                        log_error("epoll_wait(): %m");
+                        r = -errno;
+                        goto finish;
+                }
+
+                assert(nfds >= 1);
+
+                for (i = 0; i < nfds; i++) {
+                        if (ev[i].data.fd == STDIN_FILENO) {
+
+                                if (ev[i].events & (EPOLLIN|EPOLLHUP))
+                                        stdin_readable = true;
+
+                        } else if (ev[i].data.fd == STDOUT_FILENO) {
+
+                                if (ev[i].events & (EPOLLOUT|EPOLLHUP))
+                                        stdout_writable = true;
+
+                        } else if (ev[i].data.fd == master) {
+
+                                if (ev[i].events & (EPOLLIN|EPOLLHUP))
+                                        master_readable = true;
+
+                                if (ev[i].events & (EPOLLOUT|EPOLLHUP))
+                                        master_writable = true;
+
+                        } else if (ev[i].data.fd == signal_fd) {
+                                struct signalfd_siginfo sfsi;
+                                ssize_t n;
+
+                                if ((n = read(signal_fd, &sfsi, sizeof(sfsi))) != sizeof(sfsi)) {
+
+                                        if (n >= 0) {
+                                                log_error("Failed to read from signalfd: invalid block size");
+                                                r = -EIO;
+                                                goto finish;
+                                        }
+
+                                        if (errno != EINTR && errno != EAGAIN) {
+                                                log_error("Failed to read from signalfd: %m");
+                                                r = -errno;
+                                                goto finish;
+                                        }
+                                } else {
+
+                                        if (sfsi.ssi_signo == SIGWINCH) {
+                                                struct winsize ws;
+
+                                                /* The window size changed, let's forward that. */
+                                                if (ioctl(STDIN_FILENO, TIOCGWINSZ, &ws) >= 0)
+                                                        ioctl(master, TIOCSWINSZ, &ws);
+                                        } else {
+                                                r = 0;
+                                                goto finish;
+                                        }
+                                }
+                        }
+                }
+
+                while ((stdin_readable && in_buffer_full <= 0) ||
+                       (master_writable && in_buffer_full > 0) ||
+                       (master_readable && out_buffer_full <= 0) ||
+                       (stdout_writable && out_buffer_full > 0)) {
+
+                        if (stdin_readable && in_buffer_full < LINE_MAX) {
+
+                                if ((k = read(STDIN_FILENO, in_buffer + in_buffer_full, LINE_MAX - in_buffer_full)) < 0) {
+
+                                        if (errno == EAGAIN || errno == EPIPE || errno == ECONNRESET || errno == EIO)
+                                                stdin_readable = false;
+                                        else {
+                                                log_error("read(): %m");
+                                                r = -errno;
+                                                goto finish;
+                                        }
+                                } else
+                                        in_buffer_full += (size_t) k;
+                        }
+
+                        if (master_writable && in_buffer_full > 0) {
+
+                                if ((k = write(master, in_buffer, in_buffer_full)) < 0) {
+
+                                        if (errno == EAGAIN || errno == EPIPE || errno == ECONNRESET || errno == EIO)
+                                                master_writable = false;
+                                        else {
+                                                log_error("write(): %m");
+                                                r = -errno;
+                                                goto finish;
+                                        }
+
+                                } else {
+                                        assert(in_buffer_full >= (size_t) k);
+                                        memmove(in_buffer, in_buffer + k, in_buffer_full - k);
+                                        in_buffer_full -= k;
+                                }
+                        }
+
+                        if (master_readable && out_buffer_full < LINE_MAX) {
+
+                                if ((k = read(master, out_buffer + out_buffer_full, LINE_MAX - out_buffer_full)) < 0) {
+
+                                        if (errno == EAGAIN || errno == EPIPE || errno == ECONNRESET || errno == EIO)
+                                                master_readable = false;
+                                        else {
+                                                log_error("read(): %m");
+                                                r = -errno;
+                                                goto finish;
+                                        }
+                                }  else
+                                        out_buffer_full += (size_t) k;
+                        }
+
+                        if (stdout_writable && out_buffer_full > 0) {
+
+                                if ((k = write(STDOUT_FILENO, out_buffer, out_buffer_full)) < 0) {
+
+                                        if (errno == EAGAIN || errno == EPIPE || errno == ECONNRESET || errno == EIO)
+                                                stdout_writable = false;
+                                        else {
+                                                log_error("write(): %m");
+                                                r = -errno;
+                                                goto finish;
+                                        }
+
+                                } else {
+                                        assert(out_buffer_full >= (size_t) k);
+                                        memmove(out_buffer, out_buffer + k, out_buffer_full - k);
+                                        out_buffer_full -= k;
+                                }
+                        }
+                }
+        }
+
+finish:
+        if (ep >= 0)
+                close_nointr_nofail(ep);
+
+        if (signal_fd >= 0)
+                close_nointr_nofail(signal_fd);
+
+        return r;
+}
+
+int main(int argc, char *argv[]) {
+        pid_t pid = 0;
+        int r = EXIT_FAILURE, k;
+        char *oldcg = NULL, *newcg = NULL;
+        int master = -1;
+        const char *console = NULL;
+        struct termios saved_attr, raw_attr;
+        sigset_t mask;
+        bool saved_attr_valid = false;
+        struct winsize ws;
+
+        log_parse_environment();
+        log_open();
+
+        if ((r = parse_argv(argc, argv)) <= 0)
+                goto finish;
+
+        if (arg_directory) {
+                char *p;
+
+                p = path_make_absolute_cwd(arg_directory);
+                free(arg_directory);
+                arg_directory = p;
+        } else
+                arg_directory = get_current_dir_name();
+
+        if (!arg_directory) {
+                log_error("Failed to determine path");
+                goto finish;
+        }
+
+        path_kill_slashes(arg_directory);
+
+        if (geteuid() != 0) {
+                log_error("Need to be root.");
+                goto finish;
+        }
+
+        if (sd_booted() <= 0) {
+                log_error("Not running on a systemd system.");
+                goto finish;
+        }
+
+        if (path_equal(arg_directory, "/")) {
+                log_error("Spawning container on root directory not supported.");
+                goto finish;
+        }
+
+        if (is_os_tree(arg_directory) <= 0) {
+                log_error("Directory %s doesn't look like an OS root directory. Refusing.", arg_directory);
+                goto finish;
+        }
+
+        if ((k = cg_get_by_pid(SYSTEMD_CGROUP_CONTROLLER, 0, &oldcg)) < 0) {
+                log_error("Failed to determine current cgroup: %s", strerror(-k));
+                goto finish;
+        }
+
+        if (asprintf(&newcg, "%s/nspawn-%lu", oldcg, (unsigned long) getpid()) < 0) {
+                log_error("Failed to allocate cgroup path.");
+                goto finish;
+        }
+
+        if ((k = cg_create_and_attach(SYSTEMD_CGROUP_CONTROLLER, newcg, 0)) < 0)  {
+                log_error("Failed to create cgroup: %s", strerror(-k));
+                goto finish;
+        }
+
+        if ((master = posix_openpt(O_RDWR|O_NOCTTY|O_CLOEXEC|O_NDELAY)) < 0) {
+                log_error("Failed to acquire pseudo tty: %m");
+                goto finish;
+        }
+
+        if (!(console = ptsname(master))) {
+                log_error("Failed to determine tty name: %m");
+                goto finish;
+        }
+
+        log_info("Spawning namespace container on %s (console is %s).", arg_directory, console);
+
+        if (ioctl(STDIN_FILENO, TIOCGWINSZ, &ws) >= 0)
+                ioctl(master, TIOCSWINSZ, &ws);
+
+        if (unlockpt(master) < 0) {
+                log_error("Failed to unlock tty: %m");
+                goto finish;
+        }
+
+        if (tcgetattr(STDIN_FILENO, &saved_attr) < 0) {
+                log_error("Failed to get terminal attributes: %m");
+                goto finish;
+        }
+
+        saved_attr_valid = true;
+
+        raw_attr = saved_attr;
+        cfmakeraw(&raw_attr);
+        raw_attr.c_lflag &= ~ECHO;
+
+        if (tcsetattr(STDIN_FILENO, TCSANOW, &raw_attr) < 0) {
+                log_error("Failed to set terminal attributes: %m");
+                goto finish;
+        }
+
+        assert_se(sigemptyset(&mask) == 0);
+        sigset_add_many(&mask, SIGCHLD, SIGWINCH, SIGTERM, SIGINT, -1);
+        assert_se(sigprocmask(SIG_BLOCK, &mask, NULL) == 0);
+
+        pid = syscall(__NR_clone, SIGCHLD|CLONE_NEWIPC|CLONE_NEWNS|CLONE_NEWPID|CLONE_NEWUTS|(arg_private_network ? CLONE_NEWNET : 0), NULL);
+        if (pid < 0) {
+                if (errno == EINVAL)
+                        log_error("clone() failed, do you have namespace support enabled in your kernel? (You need UTS, IPC, PID and NET namespacing built in): %m");
+                else
+                        log_error("clone() failed: %m");
+
+                goto finish;
+        }
+
+        if (pid == 0) {
+                /* child */
+
+                const char *hn;
+                const char *home = NULL;
+                uid_t uid = (uid_t) -1;
+                gid_t gid = (gid_t) -1;
+                const char *envp[] = {
+                        "PATH=/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin",
+                        "container=systemd-nspawn", /* LXC sets container=lxc, so follow the scheme here */
+                        NULL, /* TERM */
+                        NULL, /* HOME */
+                        NULL, /* USER */
+                        NULL, /* LOGNAME */
+                        NULL
+                };
+
+                envp[2] = strv_find_prefix(environ, "TERM=");
+
+                close_nointr_nofail(master);
+
+                close_nointr(STDIN_FILENO);
+                close_nointr(STDOUT_FILENO);
+                close_nointr(STDERR_FILENO);
+
+                close_all_fds(NULL, 0);
+
+                reset_all_signal_handlers();
+
+                assert_se(sigemptyset(&mask) == 0);
+                assert_se(sigprocmask(SIG_SETMASK, &mask, NULL) == 0);
+
+                if (setsid() < 0)
+                        goto child_fail;
+
+                if (prctl(PR_SET_PDEATHSIG, SIGKILL) < 0)
+                        goto child_fail;
+
+                /* Mark / as private, in case somebody marked it shared */
+                if (mount(NULL, "/", NULL, MS_PRIVATE|MS_REC, NULL) < 0)
+                        goto child_fail;
+
+                if (mount_all(arg_directory) < 0)
+                        goto child_fail;
+
+                if (copy_devnodes(arg_directory, console) < 0)
+                        goto child_fail;
+
+                if (chdir(arg_directory) < 0) {
+                        log_error("chdir(%s) failed: %m", arg_directory);
+                        goto child_fail;
+                }
+
+                if (open_terminal("dev/console", O_RDWR) != STDIN_FILENO ||
+                    dup2(STDIN_FILENO, STDOUT_FILENO) != STDOUT_FILENO ||
+                    dup2(STDIN_FILENO, STDERR_FILENO) != STDERR_FILENO)
+                        goto child_fail;
+
+                if (mount(arg_directory, "/", "bind", MS_BIND|MS_MOVE, NULL) < 0) {
+                        log_error("mount(MS_MOVE) failed: %m");
+                        goto child_fail;
+                }
+
+                if (chroot(".") < 0) {
+                        log_error("chroot() failed: %m");
+                        goto child_fail;
+                }
+
+                if (chdir("/") < 0) {
+                        log_error("chdir() failed: %m");
+                        goto child_fail;
+                }
+
+                umask(0022);
+
+                loopback_setup();
+
+                if (drop_capabilities() < 0)
+                        goto child_fail;
+
+                if (arg_user) {
+
+                        if (get_user_creds((const char**)&arg_user, &uid, &gid, &home) < 0) {
+                                log_error("get_user_creds() failed: %m");
+                                goto child_fail;
+                        }
+
+                        if (mkdir_parents(home, 0775) < 0) {
+                                log_error("mkdir_parents() failed: %m");
+                                goto child_fail;
+                        }
+
+                        if (safe_mkdir(home, 0775, uid, gid) < 0) {
+                                log_error("safe_mkdir() failed: %m");
+                                goto child_fail;
+                        }
+
+                        if (initgroups((const char*)arg_user, gid) < 0) {
+                                log_error("initgroups() failed: %m");
+                                goto child_fail;
+                        }
+
+                        if (setresgid(gid, gid, gid) < 0) {
+                                log_error("setregid() failed: %m");
+                                goto child_fail;
+                        }
+
+                        if (setresuid(uid, uid, uid) < 0) {
+                                log_error("setreuid() failed: %m");
+                                goto child_fail;
+                        }
+                }
+
+                if ((asprintf((char**)(envp + 3), "HOME=%s", home? home: "/root") < 0) ||
+                    (asprintf((char**)(envp + 4), "USER=%s", arg_user? arg_user : "root") < 0) ||
+                    (asprintf((char**)(envp + 5), "LOGNAME=%s", arg_user? arg_user : "root") < 0)) {
+                    log_error("Out of memory");
+                    goto child_fail;
+                }
+
+                if ((hn = file_name_from_path(arg_directory)))
+                        sethostname(hn, strlen(hn));
+
+                if (argc > optind)
+                        execvpe(argv[optind], argv + optind, (char**) envp);
+                else {
+                        chdir(home ? home : "/root");
+                        execle("/bin/bash", "-bash", NULL, (char**) envp);
+                }
+
+                log_error("execv() failed: %m");
+
+        child_fail:
+                _exit(EXIT_FAILURE);
+        }
+
+        if (process_pty(master, &mask) < 0)
+                goto finish;
+
+        if (saved_attr_valid) {
+                tcsetattr(STDIN_FILENO, TCSANOW, &saved_attr);
+                saved_attr_valid = false;
+        }
+
+        r = wait_for_terminate_and_warn(argc > optind ? argv[optind] : "bash", pid);
+
+        if (r < 0)
+                r = EXIT_FAILURE;
+
+finish:
+        if (saved_attr_valid)
+                tcsetattr(STDIN_FILENO, TCSANOW, &saved_attr);
+
+        if (master >= 0)
+                close_nointr_nofail(master);
+
+        if (oldcg)
+                cg_attach(SYSTEMD_CGROUP_CONTROLLER, oldcg, 0);
+
+        if (newcg)
+                cg_kill_recursive_and_wait(SYSTEMD_CGROUP_CONTROLLER, newcg, true);
+
+        free(arg_directory);
+        free(oldcg);
+        free(newcg);
+
+        return r;
+}
diff --git a/src/org.freedesktop.systemd1.conf b/src/org.freedesktop.systemd1.conf
new file mode 100644 (file)
index 0000000..201afe6
--- /dev/null
@@ -0,0 +1,92 @@
+<?xml version="1.0"?> <!--*-nxml-*-->
+<!DOCTYPE busconfig PUBLIC "-//freedesktop//DTD D-BUS Bus Configuration 1.0//EN"
+        "http://www.freedesktop.org/standards/dbus/1.0/busconfig.dtd">
+
+<!--
+  This file is part of systemd.
+
+  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.
+-->
+
+<busconfig>
+
+        <policy user="root">
+                <allow own="org.freedesktop.systemd1"/>
+
+                <!-- Root clients can do everything -->
+                <allow send_destination="org.freedesktop.systemd1"/>
+                <allow receive_sender="org.freedesktop.systemd1"/>
+
+                <!-- systemd may receive activator requests -->
+                <allow receive_interface="org.freedesktop.systemd1.Activator"
+                       receive_member="ActivationRequest"/>
+        </policy>
+
+        <policy context="default">
+                <deny send_destination="org.freedesktop.systemd1"/>
+
+                <allow send_destination="org.freedesktop.systemd1"
+                       send_interface="org.freedesktop.DBus.Introspectable"/>
+
+                <allow send_destination="org.freedesktop.systemd1"
+                       send_interface="org.freedesktop.DBus.Peer"/>
+
+                <allow send_destination="org.freedesktop.systemd1"
+                       send_interface="org.freedesktop.DBus.Properties"
+                       send_member="Get"/>
+
+                <allow send_destination="org.freedesktop.systemd1"
+                       send_interface="org.freedesktop.DBus.Properties"
+                       send_member="GetAll"/>
+
+                <allow send_destination="org.freedesktop.systemd1"
+                       send_interface="org.freedesktop.systemd1.Manager"
+                       send_member="GetUnit"/>
+
+                <allow send_destination="org.freedesktop.systemd1"
+                       send_interface="org.freedesktop.systemd1.Manager"
+                       send_member="GetUnitByPID"/>
+
+                <allow send_destination="org.freedesktop.systemd1"
+                       send_interface="org.freedesktop.systemd1.Manager"
+                       send_member="LoadUnit"/>
+
+                <allow send_destination="org.freedesktop.systemd1"
+                       send_interface="org.freedesktop.systemd1.Manager"
+                       send_member="GetJob"/>
+
+                <allow send_destination="org.freedesktop.systemd1"
+                       send_interface="org.freedesktop.systemd1.Manager"
+                       send_member="ListUnits"/>
+
+                <allow send_destination="org.freedesktop.systemd1"
+                       send_interface="org.freedesktop.systemd1.Manager"
+                       send_member="ListUnitFiles"/>
+
+                <allow send_destination="org.freedesktop.systemd1"
+                       send_interface="org.freedesktop.systemd1.Manager"
+                       send_member="GetUnitFileState"/>
+
+                <allow send_destination="org.freedesktop.systemd1"
+                       send_interface="org.freedesktop.systemd1.Manager"
+                       send_member="ListJobs"/>
+
+                <allow send_destination="org.freedesktop.systemd1"
+                       send_interface="org.freedesktop.systemd1.Manager"
+                       send_member="Subscribe"/>
+
+                <allow send_destination="org.freedesktop.systemd1"
+                       send_interface="org.freedesktop.systemd1.Manager"
+                       send_member="Unsubscribe"/>
+
+                <allow send_destination="org.freedesktop.systemd1"
+                       send_interface="org.freedesktop.systemd1.Manager"
+                       send_member="Dump"/>
+
+                <allow receive_sender="org.freedesktop.systemd1"/>
+        </policy>
+
+</busconfig>
diff --git a/src/org.freedesktop.systemd1.policy.in.in b/src/org.freedesktop.systemd1.policy.in.in
new file mode 100644 (file)
index 0000000..1771314
--- /dev/null
@@ -0,0 +1,41 @@
+<?xml version="1.0" encoding="UTF-8"?> <!--*-nxml-*-->
+<!DOCTYPE policyconfig PUBLIC "-//freedesktop//DTD PolicyKit Policy Configuration 1.0//EN"
+        "http://www.freedesktop.org/standards/PolicyKit/1/policyconfig.dtd">
+
+<!--
+  This file is part of systemd.
+
+  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.
+-->
+
+<policyconfig>
+
+        <vendor>The systemd Project</vendor>
+        <vendor_url>http://www.freedesktop.org/wiki/Software/systemd</vendor_url>
+
+        <action id="org.freedesktop.systemd1.reply-password">
+                <_description>Send passphrase back to system</_description>
+                <_message>Authentication is required to send the entered passphrase back to the system.</_message>
+                <defaults>
+                        <allow_any>no</allow_any>
+                        <allow_inactive>no</allow_inactive>
+                        <allow_active>auth_admin_keep</allow_active>
+                </defaults>
+                <annotate key="org.freedesktop.policykit.exec.path">@rootlibexecdir@/systemd-reply-password</annotate>
+        </action>
+
+        <action id="org.freedesktop.systemd1.bus-access">
+                <_description>Privileged system and service manager access</_description>
+                <_message>Authentication is required to access the system and service manager.</_message>
+                <defaults>
+                        <allow_any>no</allow_any>
+                        <allow_inactive>no</allow_inactive>
+                        <allow_active>auth_admin_keep</allow_active>
+                </defaults>
+                <annotate key="org.freedesktop.policykit.exec.path">@bindir@/systemd-stdio-bridge</annotate>
+        </action>
+
+</policyconfig>
diff --git a/src/org.freedesktop.systemd1.service b/src/org.freedesktop.systemd1.service
new file mode 100644 (file)
index 0000000..7e1dfd4
--- /dev/null
@@ -0,0 +1,11 @@
+#  This file is part of systemd.
+#
+#  systemd is free software; you can redistribute it and/or modify it
+#  under the terms of the GNU General Public License as published by
+#  the Free Software Foundation; either version 2 of the License, or
+#  (at your option) any later version.
+
+[D-BUS Service]
+Name=org.freedesktop.systemd1
+Exec=/bin/false
+User=root
diff --git a/src/pager.c b/src/pager.c
new file mode 100644 (file)
index 0000000..3fc8182
--- /dev/null
@@ -0,0 +1,134 @@
+/*-*- 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 <http://www.gnu.org/licenses/>.
+***/
+
+#include <sys/types.h>
+#include <fcntl.h>
+#include <stdlib.h>
+#include <unistd.h>
+#include <string.h>
+#include <sys/prctl.h>
+
+#include "pager.h"
+#include "util.h"
+#include "macro.h"
+
+static pid_t pager_pid = 0;
+
+_noreturn_ static void pager_fallback(void) {
+        ssize_t n;
+        do {
+                n = splice(STDIN_FILENO, NULL, STDOUT_FILENO, NULL, 64*1024, 0);
+        } while (n > 0);
+        if (n < 0) {
+                log_error("Internal pager failed: %m");
+                _exit(EXIT_FAILURE);
+        }
+        _exit(EXIT_SUCCESS);
+}
+
+void pager_open(void) {
+        int fd[2];
+        const char *pager;
+        pid_t parent_pid;
+
+        if (pager_pid > 0)
+                return;
+
+        if ((pager = getenv("SYSTEMD_PAGER")) || (pager = getenv("PAGER")))
+                if (!*pager || streq(pager, "cat"))
+                        return;
+
+        if (isatty(STDOUT_FILENO) <= 0)
+                return;
+
+        /* Determine and cache number of columns before we spawn the
+         * pager so that we get the value from the actual tty */
+        columns();
+
+        if (pipe(fd) < 0) {
+                log_error("Failed to create pager pipe: %m");
+                return;
+        }
+
+        parent_pid = getpid();
+
+        pager_pid = fork();
+        if (pager_pid < 0) {
+                log_error("Failed to fork pager: %m");
+                close_pipe(fd);
+                return;
+        }
+
+        /* In the child start the pager */
+        if (pager_pid == 0) {
+
+                dup2(fd[0], STDIN_FILENO);
+                close_pipe(fd);
+
+                setenv("LESS", "FRSX", 0);
+
+                /* Make sure the pager goes away when the parent dies */
+                if (prctl(PR_SET_PDEATHSIG, SIGTERM) < 0)
+                        _exit(EXIT_FAILURE);
+
+                /* Check whether our parent died before we were able
+                 * to set the death signal */
+                if (getppid() != parent_pid)
+                        _exit(EXIT_SUCCESS);
+
+                if (pager) {
+                        execlp(pager, pager, NULL);
+                        execl("/bin/sh", "sh", "-c", pager, NULL);
+                }
+
+                /* Debian's alternatives command for pagers is
+                 * called 'pager'. Note that we do not call
+                 * sensible-pagers here, since that is just a
+                 * shell script that implements a logic that
+                 * is similar to this one anyway, but is
+                 * Debian-specific. */
+                execlp("pager", "pager", NULL);
+
+                execlp("less", "less", NULL);
+                execlp("more", "more", NULL);
+
+                pager_fallback();
+                /* not reached */
+        }
+
+        /* Return in the parent */
+        if (dup2(fd[1], STDOUT_FILENO) < 0)
+                log_error("Failed to duplicate pager pipe: %m");
+
+        close_pipe(fd);
+}
+
+void pager_close(void) {
+
+        if (pager_pid <= 0)
+                return;
+
+        /* Inform pager that we are done */
+        fclose(stdout);
+        kill(pager_pid, SIGCONT);
+        wait_for_terminate(pager_pid, NULL);
+        pager_pid = 0;
+}
diff --git a/src/pager.h b/src/pager.h
new file mode 100644 (file)
index 0000000..b5b4998
--- /dev/null
@@ -0,0 +1,28 @@
+/*-*- Mode: C; c-basic-offset: 8; indent-tabs-mode: nil -*-*/
+
+#ifndef foopagerhfoo
+#define foopagerhfoo
+
+/***
+  This file is part 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 <http://www.gnu.org/licenses/>.
+***/
+
+void pager_open(void);
+void pager_close(void);
+
+#endif
diff --git a/src/path-lookup.c b/src/path-lookup.c
new file mode 100644 (file)
index 0000000..5464ced
--- /dev/null
@@ -0,0 +1,347 @@
+/*-*- 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 <http://www.gnu.org/licenses/>.
+***/
+
+#include <assert.h>
+#include <stdlib.h>
+#include <stdio.h>
+#include <unistd.h>
+#include <errno.h>
+
+#include "util.h"
+#include "strv.h"
+
+#include "path-lookup.h"
+
+int user_config_home(char **config_home) {
+        const char *e;
+
+        if ((e = getenv("XDG_CONFIG_HOME"))) {
+                if (asprintf(config_home, "%s/systemd/user", e) < 0)
+                        return -ENOMEM;
+
+                return 1;
+        } else {
+                const char *home;
+
+                if ((home = getenv("HOME"))) {
+                        if (asprintf(config_home, "%s/.config/systemd/user", home) < 0)
+                                return -ENOMEM;
+
+                        return 1;
+                }
+        }
+
+        return 0;
+}
+
+static char** user_dirs(void) {
+        const char * const config_unit_paths[] = {
+                USER_CONFIG_UNIT_PATH,
+                "/etc/systemd/user",
+                "/run/systemd/user",
+                NULL
+        };
+
+        const char * const data_unit_paths[] = {
+                "/usr/local/lib/systemd/user",
+                "/usr/local/share/systemd/user",
+                USER_DATA_UNIT_PATH,
+                "/usr/lib/systemd/user",
+                "/usr/share/systemd/user",
+                NULL
+        };
+
+        const char *home, *e;
+        char *config_home = NULL, *data_home = NULL;
+        char **config_dirs = NULL, **data_dirs = NULL;
+        char **r = NULL, **t;
+
+        /* Implement the mechanisms defined in
+         *
+         * http://standards.freedesktop.org/basedir-spec/basedir-spec-0.6.html
+         *
+         * We look in both the config and the data dirs because we
+         * want to encourage that distributors ship their unit files
+         * as data, and allow overriding as configuration.
+         */
+
+        if (user_config_home(&config_home) < 0)
+                goto fail;
+
+        home = getenv("HOME");
+
+        if ((e = getenv("XDG_CONFIG_DIRS")))
+                if (!(config_dirs = strv_split(e, ":")))
+                        goto fail;
+
+        /* We don't treat /etc/xdg/systemd here as the spec
+         * suggests because we assume that that is a link to
+         * /etc/systemd/ anyway. */
+
+        if ((e = getenv("XDG_DATA_HOME"))) {
+                if (asprintf(&data_home, "%s/systemd/user", e) < 0)
+                        goto fail;
+
+        } else if (home) {
+                if (asprintf(&data_home, "%s/.local/share/systemd/user", home) < 0)
+                        goto fail;
+
+                /* There is really no need for two unit dirs in $HOME,
+                 * except to be fully compliant with the XDG spec. We
+                 * now try to link the two dirs, so that we can
+                 * minimize disk seeks a little. Further down we'll
+                 * then filter out this link, if it is actually is
+                 * one. */
+
+                mkdir_parents(data_home, 0777);
+                (void) symlink("../../../.config/systemd/user", data_home);
+        }
+
+        if ((e = getenv("XDG_DATA_DIRS")))
+                data_dirs = strv_split(e, ":");
+        else
+                data_dirs = strv_new("/usr/local/share",
+                                     "/usr/share",
+                                     NULL);
+
+        if (!data_dirs)
+                goto fail;
+
+        /* Now merge everything we found. */
+        if (config_home) {
+                if (!(t = strv_append(r, config_home)))
+                        goto fail;
+                strv_free(r);
+                r = t;
+        }
+
+        if (!strv_isempty(config_dirs)) {
+                if (!(t = strv_merge_concat(r, config_dirs, "/systemd/user")))
+                        goto finish;
+                strv_free(r);
+                r = t;
+        }
+
+        if (!(t = strv_merge(r, (char**) config_unit_paths)))
+                goto fail;
+        strv_free(r);
+        r = t;
+
+        if (data_home) {
+                if (!(t = strv_append(r, data_home)))
+                        goto fail;
+                strv_free(r);
+                r = t;
+        }
+
+        if (!strv_isempty(data_dirs)) {
+                if (!(t = strv_merge_concat(r, data_dirs, "/systemd/user")))
+                        goto fail;
+                strv_free(r);
+                r = t;
+        }
+
+        if (!(t = strv_merge(r, (char**) data_unit_paths)))
+                goto fail;
+        strv_free(r);
+        r = t;
+
+        if (!strv_path_make_absolute_cwd(r))
+            goto fail;
+
+finish:
+        free(config_home);
+        strv_free(config_dirs);
+        free(data_home);
+        strv_free(data_dirs);
+
+        return r;
+
+fail:
+        strv_free(r);
+        r = NULL;
+        goto finish;
+}
+
+int lookup_paths_init(LookupPaths *p, ManagerRunningAs running_as, bool personal) {
+        const char *e;
+        char *t;
+
+        assert(p);
+
+        /* First priority is whatever has been passed to us via env
+         * vars */
+        if ((e = getenv("SYSTEMD_UNIT_PATH")))
+                if (!(p->unit_path = split_path_and_make_absolute(e)))
+                        return -ENOMEM;
+
+        if (strv_isempty(p->unit_path)) {
+
+                /* Nothing is set, so let's figure something out. */
+                strv_free(p->unit_path);
+
+                if (running_as == MANAGER_USER) {
+
+                        if (personal)
+                                p->unit_path = user_dirs();
+                        else
+                                p->unit_path = strv_new(
+                                                /* If you modify this you also want to modify
+                                                 * systemduserunitpath= in systemd.pc.in, and
+                                                 * the arrays in user_dirs() above! */
+                                                USER_CONFIG_UNIT_PATH,
+                                                "/etc/systemd/user",
+                                                "/run/systemd/user",
+                                                "/usr/local/lib/systemd/user",
+                                                "/usr/local/share/systemd/user",
+                                                USER_DATA_UNIT_PATH,
+                                                "/usr/lib/systemd/user",
+                                                "/usr/share/systemd/user",
+                                                NULL);
+
+                        if (!p->unit_path)
+                                return -ENOMEM;
+
+                } else
+                        if (!(p->unit_path = strv_new(
+                                              /* If you modify this you also want to modify
+                                               * systemdsystemunitpath= in systemd.pc.in! */
+                                              SYSTEM_CONFIG_UNIT_PATH,
+                                              "/etc/systemd/system",
+                                              "/run/systemd/system",
+                                              "/usr/local/lib/systemd/system",
+                                              SYSTEM_DATA_UNIT_PATH,
+                                              "/usr/lib/systemd/system",
+#ifdef HAVE_SPLIT_USR
+                                              "/lib/systemd/system",
+#endif
+                                              NULL)))
+                                return -ENOMEM;
+        }
+
+        if (p->unit_path)
+                if (!strv_path_canonicalize(p->unit_path))
+                        return -ENOMEM;
+
+        strv_uniq(p->unit_path);
+        strv_path_remove_empty(p->unit_path);
+
+        if (!strv_isempty(p->unit_path)) {
+
+                if (!(t = strv_join(p->unit_path, "\n\t")))
+                        return -ENOMEM;
+                log_debug("Looking for unit files in:\n\t%s", t);
+                free(t);
+        } else {
+                log_debug("Ignoring unit files.");
+                strv_free(p->unit_path);
+                p->unit_path = NULL;
+        }
+
+        if (running_as == MANAGER_SYSTEM) {
+#ifdef HAVE_SYSV_COMPAT
+                /* /etc/init.d/ compatibility does not matter to users */
+
+                if ((e = getenv("SYSTEMD_SYSVINIT_PATH")))
+                        if (!(p->sysvinit_path = split_path_and_make_absolute(e)))
+                                return -ENOMEM;
+
+                if (strv_isempty(p->sysvinit_path)) {
+                        strv_free(p->sysvinit_path);
+
+                        if (!(p->sysvinit_path = strv_new(
+                                              SYSTEM_SYSVINIT_PATH,     /* /etc/init.d/ */
+                                              NULL)))
+                                return -ENOMEM;
+                }
+
+                if ((e = getenv("SYSTEMD_SYSVRCND_PATH")))
+                        if (!(p->sysvrcnd_path = split_path_and_make_absolute(e)))
+                                return -ENOMEM;
+
+                if (strv_isempty(p->sysvrcnd_path)) {
+                        strv_free(p->sysvrcnd_path);
+
+                        if (!(p->sysvrcnd_path = strv_new(
+                                              SYSTEM_SYSVRCND_PATH,     /* /etc/rcN.d/ */
+                                              NULL)))
+                                return -ENOMEM;
+                }
+
+                if (p->sysvinit_path)
+                        if (!strv_path_canonicalize(p->sysvinit_path))
+                                return -ENOMEM;
+
+                if (p->sysvrcnd_path)
+                        if (!strv_path_canonicalize(p->sysvrcnd_path))
+                                return -ENOMEM;
+
+                strv_uniq(p->sysvinit_path);
+                strv_uniq(p->sysvrcnd_path);
+
+                strv_path_remove_empty(p->sysvinit_path);
+                strv_path_remove_empty(p->sysvrcnd_path);
+
+                if (!strv_isempty(p->sysvinit_path)) {
+
+                        if (!(t = strv_join(p->sysvinit_path, "\n\t")))
+                                return -ENOMEM;
+
+                        log_debug("Looking for SysV init scripts in:\n\t%s", t);
+                        free(t);
+                } else {
+                        log_debug("Ignoring SysV init scripts.");
+                        strv_free(p->sysvinit_path);
+                        p->sysvinit_path = NULL;
+                }
+
+                if (!strv_isempty(p->sysvrcnd_path)) {
+
+                        if (!(t = strv_join(p->sysvrcnd_path, "\n\t")))
+                                return -ENOMEM;
+
+                        log_debug("Looking for SysV rcN.d links in:\n\t%s", t);
+                        free(t);
+                } else {
+                        log_debug("Ignoring SysV rcN.d links.");
+                        strv_free(p->sysvrcnd_path);
+                        p->sysvrcnd_path = NULL;
+                }
+#else
+                log_debug("Disabled SysV init scripts and rcN.d links support");
+#endif
+        }
+
+        return 0;
+}
+
+void lookup_paths_free(LookupPaths *p) {
+        assert(p);
+
+        strv_free(p->unit_path);
+        p->unit_path = NULL;
+
+#ifdef HAVE_SYSV_COMPAT
+        strv_free(p->sysvinit_path);
+        strv_free(p->sysvrcnd_path);
+        p->sysvinit_path = p->sysvrcnd_path = NULL;
+#endif
+}
diff --git a/src/path-lookup.h b/src/path-lookup.h
new file mode 100644 (file)
index 0000000..fc2887d
--- /dev/null
@@ -0,0 +1,40 @@
+/*-*- Mode: C; c-basic-offset: 8; indent-tabs-mode: nil -*-*/
+
+#ifndef foopathlookuphfoo
+#define foopathlookuphfoo
+
+/***
+  This file is part 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 <http://www.gnu.org/licenses/>.
+***/
+
+typedef struct LookupPaths {
+        char **unit_path;
+#ifdef HAVE_SYSV_COMPAT
+        char **sysvinit_path;
+        char **sysvrcnd_path;
+#endif
+} LookupPaths;
+
+#include "manager.h"
+
+int user_config_home(char **config_home);
+
+int lookup_paths_init(LookupPaths *p, ManagerRunningAs running_as, bool personal);
+void lookup_paths_free(LookupPaths *p);
+
+#endif
diff --git a/src/path.c b/src/path.c
new file mode 100644 (file)
index 0000000..e97cd09
--- /dev/null
@@ -0,0 +1,770 @@
+/*-*- 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 <http://www.gnu.org/licenses/>.
+***/
+
+#include <sys/inotify.h>
+#include <sys/epoll.h>
+#include <sys/ioctl.h>
+#include <errno.h>
+#include <unistd.h>
+
+#include "unit.h"
+#include "unit-name.h"
+#include "path.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
new file mode 100644 (file)
index 0000000..efb6b5e
--- /dev/null
@@ -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 <http://www.gnu.org/licenses/>.
+***/
+
+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.c b/src/polkit.c
new file mode 100644 (file)
index 0000000..3acbdc6
--- /dev/null
@@ -0,0 +1,205 @@
+/*-*- 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 <http://www.gnu.org/licenses/>.
+***/
+
+#include <sys/types.h>
+
+#include <errno.h>
+
+#include "util.h"
+#include "dbus-common.h"
+#include "polkit.h"
+
+/* This mimics dbus_bus_get_unix_user() */
+static pid_t get_unix_process_id(
+                DBusConnection *connection,
+                const char *name,
+                DBusError *error) {
+
+        DBusMessage *m = NULL, *reply = NULL;
+        uint32_t pid = 0;
+
+        m = dbus_message_new_method_call(
+                        DBUS_SERVICE_DBUS,
+                        DBUS_PATH_DBUS,
+                        DBUS_INTERFACE_DBUS,
+                        "GetConnectionUnixProcessID");
+        if (!m) {
+                dbus_set_error_const(error, DBUS_ERROR_NO_MEMORY, NULL);
+                goto finish;
+        }
+
+        if (!dbus_message_append_args(
+                            m,
+                            DBUS_TYPE_STRING, &name,
+                            DBUS_TYPE_INVALID)) {
+                dbus_set_error_const(error, DBUS_ERROR_NO_MEMORY, NULL);
+                goto finish;
+        }
+
+        reply = dbus_connection_send_with_reply_and_block(connection, m, -1, error);
+        if (!reply)
+                goto finish;
+
+        if (dbus_set_error_from_message(error, reply))
+                goto finish;
+
+        if (!dbus_message_get_args(
+                            reply, error,
+                            DBUS_TYPE_UINT32, &pid,
+                            DBUS_TYPE_INVALID))
+                goto finish;
+
+finish:
+        if (m)
+                dbus_message_unref(m);
+
+        if (reply)
+                dbus_message_unref(reply);
+
+        return (pid_t) pid;
+}
+
+int verify_polkit(
+                DBusConnection *c,
+                DBusMessage *request,
+                const char *action,
+                bool interactive,
+                bool *_challenge,
+                DBusError *error) {
+
+        DBusMessage *m = NULL, *reply = NULL;
+        const char *unix_process = "unix-process", *pid = "pid", *starttime = "start-time", *cancel_id = "";
+        const char *sender;
+        uint32_t flags = interactive ? 1 : 0;
+        pid_t pid_raw;
+        uint32_t pid_u32;
+        unsigned long long starttime_raw;
+        uint64_t starttime_u64;
+        DBusMessageIter iter_msg, iter_struct, iter_array, iter_dict, iter_variant;
+        int r;
+        dbus_bool_t authorized = FALSE, challenge = FALSE;
+
+        assert(c);
+        assert(request);
+
+        sender = dbus_message_get_sender(request);
+        if (!sender)
+                return -EINVAL;
+
+        pid_raw = get_unix_process_id(c, sender, error);
+        if (pid_raw == 0)
+                return -EINVAL;
+
+        r = get_starttime_of_pid(pid_raw, &starttime_raw);
+        if (r < 0)
+                return r;
+
+        m = dbus_message_new_method_call(
+                        "org.freedesktop.PolicyKit1",
+                        "/org/freedesktop/PolicyKit1/Authority",
+                        "org.freedesktop.PolicyKit1.Authority",
+                        "CheckAuthorization");
+        if (!m)
+                return -ENOMEM;
+
+        dbus_message_iter_init_append(m, &iter_msg);
+
+        pid_u32 = (uint32_t) pid_raw;
+        starttime_u64 = (uint64_t) starttime_raw;
+
+        if (!dbus_message_iter_open_container(&iter_msg, DBUS_TYPE_STRUCT, NULL, &iter_struct) ||
+            !dbus_message_iter_append_basic(&iter_struct, DBUS_TYPE_STRING, &unix_process) ||
+            !dbus_message_iter_open_container(&iter_struct, DBUS_TYPE_ARRAY, "{sv}", &iter_array) ||
+            !dbus_message_iter_open_container(&iter_array, DBUS_TYPE_DICT_ENTRY, NULL, &iter_dict) ||
+            !dbus_message_iter_append_basic(&iter_dict, DBUS_TYPE_STRING, &pid) ||
+            !dbus_message_iter_open_container(&iter_dict, DBUS_TYPE_VARIANT, "u", &iter_variant) ||
+            !dbus_message_iter_append_basic(&iter_variant, DBUS_TYPE_UINT32, &pid_u32) ||
+            !dbus_message_iter_close_container(&iter_dict, &iter_variant) ||
+            !dbus_message_iter_close_container(&iter_array, &iter_dict) ||
+            !dbus_message_iter_open_container(&iter_array, DBUS_TYPE_DICT_ENTRY, NULL, &iter_dict) ||
+            !dbus_message_iter_append_basic(&iter_dict, DBUS_TYPE_STRING, &starttime) ||
+            !dbus_message_iter_open_container(&iter_dict, DBUS_TYPE_VARIANT, "t", &iter_variant) ||
+            !dbus_message_iter_append_basic(&iter_variant, DBUS_TYPE_UINT64, &starttime_u64) ||
+            !dbus_message_iter_close_container(&iter_dict, &iter_variant) ||
+            !dbus_message_iter_close_container(&iter_array, &iter_dict) ||
+            !dbus_message_iter_close_container(&iter_struct, &iter_array) ||
+            !dbus_message_iter_close_container(&iter_msg, &iter_struct) ||
+            !dbus_message_iter_append_basic(&iter_msg, DBUS_TYPE_STRING, &action) ||
+            !dbus_message_iter_open_container(&iter_msg, DBUS_TYPE_ARRAY, "{ss}", &iter_array) ||
+            !dbus_message_iter_close_container(&iter_msg, &iter_array) ||
+            !dbus_message_iter_append_basic(&iter_msg, DBUS_TYPE_UINT32, &flags) ||
+            !dbus_message_iter_append_basic(&iter_msg, DBUS_TYPE_STRING, &cancel_id)) {
+                r = -ENOMEM;
+                goto finish;
+        }
+
+        reply = dbus_connection_send_with_reply_and_block(c, m, -1, error);
+        if (!reply) {
+                r = -EIO;
+                goto finish;
+        }
+
+        if (dbus_set_error_from_message(error, reply)) {
+                r = -EIO;
+                goto finish;
+        }
+
+        if (!dbus_message_iter_init(reply, &iter_msg) ||
+            dbus_message_iter_get_arg_type(&iter_msg) != DBUS_TYPE_STRUCT) {
+                r = -EIO;
+                goto finish;
+        }
+
+        dbus_message_iter_recurse(&iter_msg, &iter_struct);
+
+        if (dbus_message_iter_get_arg_type(&iter_struct) != DBUS_TYPE_BOOLEAN) {
+                r = -EIO;
+                goto finish;
+        }
+
+        dbus_message_iter_get_basic(&iter_struct, &authorized);
+
+        if (!dbus_message_iter_next(&iter_struct) ||
+            dbus_message_iter_get_arg_type(&iter_struct) != DBUS_TYPE_BOOLEAN) {
+                r = -EIO;
+                goto finish;
+        }
+
+        dbus_message_iter_get_basic(&iter_struct, &challenge);
+
+        if (authorized)
+                r = 1;
+        else if (_challenge) {
+                *_challenge = !!challenge;
+                r = 0;
+        } else
+                r = -EPERM;
+
+finish:
+
+        if (m)
+                dbus_message_unref(m);
+
+        if (reply)
+                dbus_message_unref(reply);
+
+        return r;
+}
diff --git a/src/polkit.h b/src/polkit.h
new file mode 100644 (file)
index 0000000..0d08194
--- /dev/null
@@ -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 <http://www.gnu.org/licenses/>.
+***/
+
+#include <stdbool.h>
+#include <dbus/dbus.h>
+
+int verify_polkit(
+                DBusConnection *c,
+                DBusMessage *request,
+                const char *action,
+                bool interactive,
+                bool *challenge,
+                DBusError *error);
+
+#endif
diff --git a/src/quotacheck.c b/src/quotacheck.c
new file mode 100644 (file)
index 0000000..b6648b8
--- /dev/null
@@ -0,0 +1,120 @@
+/*-*- 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 <http://www.gnu.org/licenses/>.
+***/
+
+#include <stdio.h>
+#include <stdbool.h>
+#include <string.h>
+#include <errno.h>
+#include <unistd.h>
+
+#include "util.h"
+#include "virt.h"
+
+static bool arg_skip = false;
+static bool arg_force = false;
+
+static int parse_proc_cmdline(void) {
+        char *line, *w, *state;
+        int r;
+        size_t l;
+
+        if (detect_container(NULL) > 0)
+                return 0;
+
+        if ((r = read_one_line_file("/proc/cmdline", &line)) < 0) {
+                log_warning("Failed to read /proc/cmdline, ignoring: %s", strerror(-r));
+                return 0;
+        }
+
+        FOREACH_WORD_QUOTED(w, l, line, state) {
+
+                if (strneq(w, "quotacheck.mode=auto", l))
+                        arg_force = arg_skip = false;
+                else if (strneq(w, "quotacheck.mode=force", l))
+                        arg_force = true;
+                else if (strneq(w, "quotacheck.mode=skip", l))
+                        arg_skip = true;
+                else if (startswith(w, "quotacheck.mode"))
+                        log_warning("Invalid quotacheck.mode= parameter. Ignoring.");
+#if defined(TARGET_FEDORA) || defined(TARGET_MANDRIVA) || defined(TARGET_MAGEIA)
+                else if (strneq(w, "forcequotacheck", l))
+                        arg_force = true;
+#endif
+        }
+
+        free(line);
+        return 0;
+}
+
+static void test_files(void) {
+#if defined(TARGET_FEDORA) || defined(TARGET_MANDRIVA) || defined(TARGET_MAGEIA)
+        /* This exists only on Fedora, Mandriva or Mageia */
+        if (access("/forcequotacheck", F_OK) >= 0)
+                arg_force = true;
+#endif
+}
+
+int main(int argc, char *argv[]) {
+        static const char * const cmdline[] = {
+                "/sbin/quotacheck",
+                "-anug",
+                NULL
+        };
+
+        int r = EXIT_FAILURE;
+        pid_t pid;
+
+        if (argc > 1) {
+                log_error("This program takes no arguments.");
+                return EXIT_FAILURE;
+        }
+
+        log_set_target(LOG_TARGET_AUTO);
+        log_parse_environment();
+        log_open();
+
+        umask(0022);
+
+        parse_proc_cmdline();
+        test_files();
+
+        if (!arg_force) {
+                if (arg_skip)
+                        return 0;
+
+                if (access("/run/systemd/quotacheck", F_OK) < 0)
+                        return 0;
+        }
+
+        if ((pid = fork()) < 0) {
+                log_error("fork(): %m");
+                goto finish;
+        } else if (pid == 0) {
+                /* Child */
+                execv(cmdline[0], (char**) cmdline);
+                _exit(1); /* Operational error */
+        }
+
+        r = wait_for_terminate_and_warn("quotacheck", pid) == 0 ? EXIT_SUCCESS : EXIT_FAILURE;
+
+finish:
+        return r;
+}
diff --git a/src/random-seed.c b/src/random-seed.c
new file mode 100644 (file)
index 0000000..8b43bac
--- /dev/null
@@ -0,0 +1,147 @@
+/*-*- 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 <http://www.gnu.org/licenses/>.
+***/
+
+#include <unistd.h>
+#include <fcntl.h>
+#include <errno.h>
+#include <string.h>
+#include <sys/stat.h>
+
+#include "log.h"
+#include "util.h"
+
+#define POOL_SIZE_MIN 512
+
+int main(int argc, char *argv[]) {
+        int seed_fd = -1, random_fd = -1;
+        int ret = EXIT_FAILURE;
+        void* buf;
+        size_t buf_size = 0;
+        ssize_t r;
+        FILE *f;
+
+        if (argc != 2) {
+                log_error("This program requires one argument.");
+                return EXIT_FAILURE;
+        }
+
+        log_set_target(LOG_TARGET_AUTO);
+        log_parse_environment();
+        log_open();
+
+        umask(0022);
+
+        /* Read pool size, if possible */
+        if ((f = fopen("/proc/sys/kernel/random/poolsize", "re"))) {
+                if (fscanf(f, "%zu", &buf_size) > 0) {
+                        /* poolsize is in bits on 2.6, but we want bytes */
+                        buf_size /= 8;
+                }
+
+                fclose(f);
+        }
+
+        if (buf_size <= POOL_SIZE_MIN)
+                buf_size = POOL_SIZE_MIN;
+
+        if (!(buf = malloc(buf_size))) {
+                log_error("Failed to allocate buffer.");
+                goto finish;
+        }
+
+        if (mkdir_parents(RANDOM_SEED, 0755) < 0) {
+                log_error("Failed to create directories parents of %s: %m", RANDOM_SEED);
+                goto finish;
+        }
+
+        /* When we load the seed we read it and write it to the device
+         * and then immediately update the saved seed with new data,
+         * to make sure the next boot gets seeded differently. */
+
+        if (streq(argv[1], "load")) {
+
+                if ((seed_fd = open(RANDOM_SEED, O_RDWR|O_CLOEXEC|O_NOCTTY|O_CREAT, 0600)) < 0) {
+                        if ((seed_fd = open(RANDOM_SEED, O_RDONLY|O_CLOEXEC|O_NOCTTY)) < 0) {
+                                log_error("Failed to open random seed: %m");
+                                goto finish;
+                        }
+                }
+
+                if ((random_fd = open("/dev/urandom", O_RDWR|O_CLOEXEC|O_NOCTTY, 0600)) < 0) {
+                        if ((random_fd = open("/dev/urandom", O_WRONLY|O_CLOEXEC|O_NOCTTY, 0600)) < 0) {
+                                log_error("Failed to open /dev/urandom: %m");
+                                goto finish;
+                        }
+                }
+
+                if ((r = loop_read(seed_fd, buf, buf_size, false)) <= 0) {
+
+                        if (r != 0)
+                                log_error("Failed to read seed file: %m");
+                } else {
+                        lseek(seed_fd, 0, SEEK_SET);
+
+                        if ((r = loop_write(random_fd, buf, (size_t) r, false)) <= 0)
+                                log_error("Failed to write seed to /dev/random: %s", r < 0 ? strerror(errno) : "short write");
+                }
+
+        } else if (streq(argv[1], "save")) {
+
+                if ((seed_fd = open(RANDOM_SEED, O_WRONLY|O_CLOEXEC|O_NOCTTY|O_CREAT, 0600)) < 0) {
+                        log_error("Failed to open random seed: %m");
+                        goto finish;
+                }
+
+                if ((random_fd = open("/dev/urandom", O_RDONLY|O_CLOEXEC|O_NOCTTY)) < 0) {
+                        log_error("Failed to open /dev/urandom: %m");
+                        goto finish;
+                }
+        } else {
+                log_error("Unknown verb %s.", argv[1]);
+                goto finish;
+        }
+
+        /* This is just a safety measure. Given that we are root and
+         * most likely created the file ourselves the mode and owner
+         * should be correct anyway. */
+        fchmod(seed_fd, 0600);
+        fchown(seed_fd, 0, 0);
+
+        if ((r = loop_read(random_fd, buf, buf_size, false)) <= 0)
+                log_error("Failed to read new seed from /dev/urandom: %s", r < 0 ? strerror(errno) : "EOF");
+        else {
+                if ((r = loop_write(seed_fd, buf, (size_t) r, false)) <= 0)
+                        log_error("Failed to write new random seed file: %s", r < 0 ? strerror(errno) : "short write");
+        }
+
+        ret = EXIT_SUCCESS;
+
+finish:
+        if (random_fd >= 0)
+                close_nointr_nofail(random_fd);
+
+        if (seed_fd >= 0)
+                close_nointr_nofail(seed_fd);
+
+        free(buf);
+
+        return ret;
+}
diff --git a/src/ratelimit.c b/src/ratelimit.c
new file mode 100644 (file)
index 0000000..93157c7
--- /dev/null
@@ -0,0 +1,57 @@
+/*-*- 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 <http://www.gnu.org/licenses/>.
+***/
+
+#include <assert.h>
+
+#include "ratelimit.h"
+#include "log.h"
+
+/* Modelled after Linux' lib/ratelimit.c by Dave Young
+ * <hidave.darkstar@gmail.com>, which is licensed GPLv2. */
+
+bool ratelimit_test(RateLimit *r) {
+        usec_t ts;
+
+        assert(r);
+
+        if (r->interval <= 0 || r->burst <= 0)
+                return true;
+
+        ts = now(CLOCK_MONOTONIC);
+
+        if (r->begin <= 0 ||
+            r->begin + r->interval < ts) {
+                r->begin = ts;
+
+                /* Reset counter */
+                r->num = 0;
+                goto good;
+        }
+
+        if (r->num <= r->burst)
+                goto good;
+
+        return false;
+
+good:
+        r->num++;
+        return true;
+}
diff --git a/src/ratelimit.h b/src/ratelimit.h
new file mode 100644 (file)
index 0000000..a6443e7
--- /dev/null
@@ -0,0 +1,53 @@
+/*-*- Mode: C; c-basic-offset: 8; indent-tabs-mode: nil -*-*/
+
+#ifndef fooratelimithfoo
+#define fooratelimithfoo
+
+/***
+  This file is part 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 <http://www.gnu.org/licenses/>.
+***/
+
+#include "util.h"
+
+typedef struct RateLimit {
+        usec_t interval;
+        usec_t begin;
+        unsigned burst;
+        unsigned num;
+} RateLimit;
+
+#define RATELIMIT_DEFINE(_name, _interval, _burst)       \
+        RateLimit _name = {                              \
+                .interval = (_interval),                 \
+                .burst = (_burst),                       \
+                .num = 0,                                \
+                .begin = 0                               \
+        }
+
+#define RATELIMIT_INIT(v, _interval, _burst)             \
+        do {                                             \
+                RateLimit *_r = &(v);                    \
+                _r->interval = (_interval);              \
+                _r->burst = (_burst);                    \
+                _r->num = 0;                             \
+                _r->begin = 0;                           \
+        } while (false)
+
+bool ratelimit_test(RateLimit *r);
+
+#endif
diff --git a/src/rc-local-generator.c b/src/rc-local-generator.c
new file mode 100644 (file)
index 0000000..56785cf
--- /dev/null
@@ -0,0 +1,107 @@
+/*-*- Mode: C; c-basic-offset: 8; indent-tabs-mode: nil -*-*/
+
+/***
+  This file is part of systemd.
+
+  Copyright 2010 Lennart Poettering
+  Copyright 2011 Michal Schmidt
+
+  systemd is free software; you can redistribute it and/or modify it
+  under the terms of the GNU 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 <http://www.gnu.org/licenses/>.
+***/
+
+#include <errno.h>
+#include <stdio.h>
+#include <unistd.h>
+
+#include "log.h"
+#include "util.h"
+
+#if defined(TARGET_FEDORA) || defined(TARGET_MANDRIVA) || defined(TARGET_MAGEIA)
+#define SCRIPT_PATH "/etc/rc.d/rc.local"
+#elif defined(TARGET_SUSE)
+#define SCRIPT_PATH "/etc/init.d/boot.local"
+#endif
+
+const char *arg_dest = "/tmp";
+
+static int add_symlink(const char *service) {
+        char *from = NULL, *to = NULL;
+        int r;
+
+        assert(service);
+
+        asprintf(&from, SYSTEM_DATA_UNIT_PATH "/%s", service);
+        asprintf(&to, "%s/multi-user.target.wants/%s", arg_dest, service);
+
+        if (!from || !to) {
+                log_error("Out of memory");
+                r = -ENOMEM;
+                goto finish;
+        }
+
+        mkdir_parents(to, 0755);
+
+        r = symlink(from, to);
+        if (r < 0) {
+                if (errno == EEXIST)
+                        r = 0;
+                else {
+                        log_error("Failed to create symlink from %s to %s: %m", from, to);
+                        r = -errno;
+                }
+        }
+
+finish:
+
+        free(from);
+        free(to);
+
+        return r;
+}
+
+static bool file_is_executable(const char *f) {
+        struct stat st;
+
+        if (stat(f, &st) < 0)
+                return false;
+
+        return S_ISREG(st.st_mode) && (st.st_mode & 0111);
+}
+
+int main(int argc, char *argv[]) {
+
+        int r = EXIT_SUCCESS;
+
+        if (argc > 2) {
+                log_error("This program takes one or no arguments.");
+                return EXIT_FAILURE;
+        }
+
+        log_set_target(LOG_TARGET_AUTO);
+        log_parse_environment();
+        log_open();
+
+        if (argc > 1)
+                arg_dest = argv[1];
+
+        if (file_is_executable(SCRIPT_PATH)) {
+                log_debug("Automatically adding rc-local.service.");
+
+                if (add_symlink("rc-local.service") < 0)
+                        r = EXIT_FAILURE;
+
+        }
+
+        return r;
+}
diff --git a/src/readahead/Makefile b/src/readahead/Makefile
new file mode 120000 (symlink)
index 0000000..d0b0e8e
--- /dev/null
@@ -0,0 +1 @@
+../Makefile
\ No newline at end of file
diff --git a/src/readahead/readahead-collect.c b/src/readahead/readahead-collect.c
new file mode 100644 (file)
index 0000000..7e6c243
--- /dev/null
@@ -0,0 +1,693 @@
+/*-*- 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 <http://www.gnu.org/licenses/>.
+***/
+
+#include <errno.h>
+#include <inttypes.h>
+#include <fcntl.h>
+#include <linux/limits.h>
+#include <stdbool.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <sys/select.h>
+#include <sys/time.h>
+#include <sys/types.h>
+#include <sys/stat.h>
+#include <unistd.h>
+#include <linux/fanotify.h>
+#include <sys/signalfd.h>
+#include <sys/poll.h>
+#include <sys/mman.h>
+#include <linux/fs.h>
+#include <linux/fiemap.h>
+#include <sys/ioctl.h>
+#include <sys/vfs.h>
+#include <getopt.h>
+#include <sys/inotify.h>
+
+#include <systemd/sd-daemon.h>
+
+#include "missing.h"
+#include "util.h"
+#include "set.h"
+#include "ioprio.h"
+#include "readahead-common.h"
+#include "virt.h"
+
+/* fixme:
+ *
+ * - detect ssd on btrfs/lvm...
+ * - read ahead directories
+ * - gzip?
+ * - remount rw?
+ * - handle files where nothing is in mincore
+ * - does ioprio_set work with fadvise()?
+ */
+
+static unsigned arg_files_max = 16*1024;
+static off_t arg_file_size_max = READAHEAD_FILE_SIZE_MAX;
+static usec_t arg_timeout = 2*USEC_PER_MINUTE;
+
+static ReadaheadShared *shared = NULL;
+
+/* Avoid collisions with the NULL pointer */
+#define SECTOR_TO_PTR(s) ULONG_TO_PTR((s)+1)
+#define PTR_TO_SECTOR(p) (PTR_TO_ULONG(p)-1)
+
+static int btrfs_defrag(int fd) {
+        struct btrfs_ioctl_vol_args data;
+
+        zero(data);
+        data.fd = fd;
+
+        return ioctl(fd, BTRFS_IOC_DEFRAG, &data);
+}
+
+static int pack_file(FILE *pack, const char *fn, bool on_btrfs) {
+        struct stat st;
+        void *start = MAP_FAILED;
+        uint8_t *vec;
+        uint32_t b, c;
+        size_t l, pages;
+        bool mapped;
+        int r = 0, fd = -1, k;
+
+        assert(pack);
+        assert(fn);
+
+        if ((fd = open(fn, O_RDONLY|O_CLOEXEC|O_NOATIME|O_NOCTTY|O_NOFOLLOW)) < 0) {
+
+                if (errno == ENOENT)
+                        return 0;
+
+                if (errno == EPERM || errno == EACCES)
+                        return 0;
+
+                log_warning("open(%s) failed: %m", fn);
+                r = -errno;
+                goto finish;
+        }
+
+        if ((k = file_verify(fd, fn, arg_file_size_max, &st)) <= 0) {
+                r = k;
+                goto finish;
+        }
+
+        if (on_btrfs)
+                btrfs_defrag(fd);
+
+        l = PAGE_ALIGN(st.st_size);
+        if ((start = mmap(NULL, l, PROT_READ, MAP_SHARED, fd, 0)) == MAP_FAILED) {
+                log_warning("mmap(%s) failed: %m", fn);
+                r = -errno;
+                goto finish;
+        }
+
+        pages = l / page_size();
+
+        vec = alloca(pages);
+        memset(vec, 0, pages);
+        if (mincore(start, l, vec) < 0) {
+                log_warning("mincore(%s) failed: %m", fn);
+                r = -errno;
+                goto finish;
+        }
+
+        fputs(fn, pack);
+        fputc('\n', pack);
+
+        mapped = false;
+        for (c = 0; c < pages; c++) {
+                bool new_mapped = !!(vec[c] & 1);
+
+                if (!mapped && new_mapped)
+                        b = c;
+                else if (mapped && !new_mapped) {
+                        fwrite(&b, sizeof(b), 1, pack);
+                        fwrite(&c, sizeof(c), 1, pack);
+
+                        log_debug("%s: page %u to %u", fn, b, c);
+                }
+
+                mapped = new_mapped;
+        }
+
+        /* We don't write any range data if we should read the entire file */
+        if (mapped && b > 0) {
+                fwrite(&b, sizeof(b), 1, pack);
+                fwrite(&c, sizeof(c), 1, pack);
+
+                log_debug("%s: page %u to %u", fn, b, c);
+        }
+
+        /* End marker */
+        b = 0;
+        fwrite(&b, sizeof(b), 1, pack);
+        fwrite(&b, sizeof(b), 1, pack);
+
+finish:
+        if (start != MAP_FAILED)
+                munmap(start, l);
+
+        if (fd >= 0)
+                close_nointr_nofail(fd);
+
+        return r;
+}
+
+static unsigned long fd_first_block(int fd) {
+        struct {
+                struct fiemap fiemap;
+                struct fiemap_extent extent;
+        } data;
+
+        zero(data);
+        data.fiemap.fm_length = ~0ULL;
+        data.fiemap.fm_extent_count = 1;
+
+        if (ioctl(fd, FS_IOC_FIEMAP, &data) < 0)
+                return 0;
+
+        if (data.fiemap.fm_mapped_extents <= 0)
+                return 0;
+
+        if (data.fiemap.fm_extents[0].fe_flags & FIEMAP_EXTENT_UNKNOWN)
+                return 0;
+
+        return (unsigned long) data.fiemap.fm_extents[0].fe_physical;
+}
+
+struct item {
+        const char *path;
+        unsigned long block;
+};
+
+static int qsort_compare(const void *a, const void *b) {
+        const struct item *i, *j;
+
+        i = a;
+        j = b;
+
+        if (i->block < j->block)
+                return -1;
+        if (i->block > j->block)
+                return 1;
+
+        return strcmp(i->path, j->path);
+}
+
+static int collect(const char *root) {
+        enum {
+                FD_FANOTIFY,  /* Get the actual fs events */
+                FD_SIGNAL,
+                FD_INOTIFY,   /* We get notifications to quit early via this fd */
+                _FD_MAX
+        };
+        struct pollfd pollfd[_FD_MAX];
+        int fanotify_fd = -1, signal_fd = -1, inotify_fd = -1, r = 0;
+        pid_t my_pid;
+        Hashmap *files = NULL;
+        Iterator i;
+        char *p, *q;
+        sigset_t mask;
+        FILE *pack = NULL;
+        char *pack_fn_new = NULL, *pack_fn = NULL;
+        bool on_ssd, on_btrfs;
+        struct statfs sfs;
+        usec_t not_after;
+
+        assert(root);
+
+        write_one_line_file("/proc/self/oom_score_adj", "1000");
+
+        if (ioprio_set(IOPRIO_WHO_PROCESS, getpid(), IOPRIO_PRIO_VALUE(IOPRIO_CLASS_IDLE, 0)) < 0)
+                log_warning("Failed to set IDLE IO priority class: %m");
+
+        assert_se(sigemptyset(&mask) == 0);
+        sigset_add_many(&mask, SIGINT, SIGTERM, -1);
+        assert_se(sigprocmask(SIG_SETMASK, &mask, NULL) == 0);
+
+        if ((signal_fd = signalfd(-1, &mask, SFD_NONBLOCK|SFD_CLOEXEC)) < 0) {
+                log_error("signalfd(): %m");
+                r = -errno;
+                goto finish;
+        }
+
+        if (!(files = hashmap_new(string_hash_func, string_compare_func))) {
+                log_error("Failed to allocate set.");
+                r = -ENOMEM;
+                goto finish;
+        }
+
+        if ((fanotify_fd = fanotify_init(FAN_CLOEXEC|FAN_NONBLOCK, O_RDONLY|O_LARGEFILE|O_CLOEXEC|O_NOATIME)) < 0)  {
+                log_error("Failed to create fanotify object: %m");
+                r = -errno;
+                goto finish;
+        }
+
+        if (fanotify_mark(fanotify_fd, FAN_MARK_ADD|FAN_MARK_MOUNT, FAN_OPEN, AT_FDCWD, root) < 0) {
+                log_error("Failed to mark %s: %m", root);
+                r = -errno;
+                goto finish;
+        }
+
+        if ((inotify_fd = open_inotify()) < 0) {
+                r = inotify_fd;
+                goto finish;
+        }
+
+        not_after = now(CLOCK_MONOTONIC) + arg_timeout;
+
+        my_pid = getpid();
+
+        zero(pollfd);
+        pollfd[FD_FANOTIFY].fd = fanotify_fd;
+        pollfd[FD_FANOTIFY].events = POLLIN;
+        pollfd[FD_SIGNAL].fd = signal_fd;
+        pollfd[FD_SIGNAL].events = POLLIN;
+        pollfd[FD_INOTIFY].fd = inotify_fd;
+        pollfd[FD_INOTIFY].events = POLLIN;
+
+        sd_notify(0,
+                  "READY=1\n"
+                  "STATUS=Collecting readahead data");
+
+        log_debug("Collecting...");
+
+        if (access("/run/systemd/readahead/cancel", F_OK) >= 0) {
+                log_debug("Collection canceled");
+                r = -ECANCELED;
+                goto finish;
+        }
+
+        if (access("/run/systemd/readahead/done", F_OK) >= 0) {
+                log_debug("Got termination request");
+                goto done;
+        }
+
+        for (;;) {
+                union {
+                        struct fanotify_event_metadata metadata;
+                        char buffer[4096];
+                } data;
+                ssize_t n;
+                struct fanotify_event_metadata *m;
+                usec_t t;
+                int h;
+
+                if (hashmap_size(files) > arg_files_max) {
+                        log_debug("Reached maximum number of read ahead files, ending collection.");
+                        break;
+                }
+
+                t = now(CLOCK_MONOTONIC);
+                if (t >= not_after) {
+                        log_debug("Reached maximum collection time, ending collection.");
+                        break;
+                }
+
+                if ((h = poll(pollfd, _FD_MAX, (int) ((not_after - t) / USEC_PER_MSEC))) < 0) {
+
+                        if (errno == EINTR)
+                                continue;
+
+                        log_error("poll(): %m");
+                        r = -errno;
+                        goto finish;
+                }
+
+                if (h == 0) {
+                        log_debug("Reached maximum collection time, ending collection.");
+                        break;
+                }
+
+                if (pollfd[FD_SIGNAL].revents) {
+                        log_debug("Got signal.");
+                        break;
+                }
+
+                if (pollfd[FD_INOTIFY].revents) {
+                        uint8_t inotify_buffer[sizeof(struct inotify_event) + FILENAME_MAX];
+                        struct inotify_event *e;
+
+                        if ((n = read(inotify_fd, &inotify_buffer, sizeof(inotify_buffer))) < 0) {
+                                if (errno == EINTR || errno == EAGAIN)
+                                        continue;
+
+                                log_error("Failed to read inotify event: %m");
+                                r = -errno;
+                                goto finish;
+                        }
+
+                        e = (struct inotify_event*) inotify_buffer;
+                        while (n > 0) {
+                                size_t step;
+
+                                if ((e->mask & IN_CREATE) && streq(e->name, "cancel")) {
+                                        log_debug("Collection canceled");
+                                        r = -ECANCELED;
+                                        goto finish;
+                                }
+
+                                if ((e->mask & IN_CREATE) && streq(e->name, "done")) {
+                                        log_debug("Got termination request");
+                                        goto done;
+                                }
+
+                                step = sizeof(struct inotify_event) + e->len;
+                                assert(step <= (size_t) n);
+
+                                e = (struct inotify_event*) ((uint8_t*) e + step);
+                                n -= step;
+                        }
+                }
+
+                if ((n = read(fanotify_fd, &data, sizeof(data))) < 0) {
+
+                        if (errno == EINTR || errno == EAGAIN)
+                                continue;
+
+                        /* fanotify sometimes returns EACCES on read()
+                         * where it shouldn't. For now let's just
+                         * ignore it here (which is safe), but
+                         * eventually this should be
+                         * dropped when the kernel is fixed.
+                         *
+                         * https://bugzilla.redhat.com/show_bug.cgi?id=707577 */
+                        if (errno == EACCES)
+                                continue;
+
+                        log_error("Failed to read event: %m");
+                        r = -errno;
+                        goto finish;
+                }
+
+                for (m = &data.metadata; FAN_EVENT_OK(m, n); m = FAN_EVENT_NEXT(m, n)) {
+                        char fn[PATH_MAX];
+                        int k;
+
+                        if (m->fd < 0)
+                                goto next_iteration;
+
+                        if (m->pid == my_pid)
+                                goto next_iteration;
+
+                        __sync_synchronize();
+                        if (m->pid == shared->replay)
+                                goto next_iteration;
+
+                        snprintf(fn, sizeof(fn), "/proc/self/fd/%i", m->fd);
+                        char_array_0(fn);
+
+                        if ((k = readlink_malloc(fn, &p)) >= 0) {
+                                if (startswith(p, "/tmp") ||
+                                    endswith(p, " (deleted)") ||
+                                    hashmap_get(files, p))
+                                        /* Not interesting, or
+                                         * already read */
+                                        free(p);
+                                else {
+                                        unsigned long ul;
+
+                                        ul = fd_first_block(m->fd);
+
+                                        if ((k = hashmap_put(files, p, SECTOR_TO_PTR(ul))) < 0) {
+                                                log_warning("set_put() failed: %s", strerror(-k));
+                                                free(p);
+                                        }
+                                }
+
+                        } else
+                                log_warning("readlink(%s) failed: %s", fn, strerror(-k));
+
+                next_iteration:
+                        if (m->fd)
+                                close_nointr_nofail(m->fd);
+                }
+        }
+
+done:
+        if (fanotify_fd >= 0) {
+                close_nointr_nofail(fanotify_fd);
+                fanotify_fd = -1;
+        }
+
+        log_debug("Writing Pack File...");
+
+        on_ssd = fs_on_ssd(root) > 0;
+        log_debug("On SSD: %s", yes_no(on_ssd));
+
+        on_btrfs = statfs(root, &sfs) >= 0 && (long) sfs.f_type == (long) BTRFS_SUPER_MAGIC;
+        log_debug("On btrfs: %s", yes_no(on_btrfs));
+
+        asprintf(&pack_fn, "%s/.readahead", root);
+        asprintf(&pack_fn_new, "%s/.readahead.new", root);
+
+        if (!pack_fn || !pack_fn_new) {
+                log_error("Out of memory");
+                r = -ENOMEM;
+                goto finish;
+        }
+
+        if (!(pack = fopen(pack_fn_new, "we"))) {
+                log_error("Failed to open pack file: %m");
+                r = -errno;
+                goto finish;
+        }
+
+        fputs(CANONICAL_HOST "\n", pack);
+        putc(on_ssd ? 'S' : 'R', pack);
+
+        if (on_ssd || on_btrfs) {
+
+                /* On SSD or on btrfs, just write things out in the
+                 * order the files were accessed. */
+
+                HASHMAP_FOREACH_KEY(q, p, files, i)
+                        pack_file(pack, p, on_btrfs);
+        } else {
+                struct item *ordered, *j;
+                unsigned k, n;
+
+                /* On rotating media, order things by the block
+                 * numbers */
+
+                log_debug("Ordering...");
+
+                n = hashmap_size(files);
+                if (!(ordered = new(struct item, n))) {
+                        log_error("Out of memory");
+                        r = -ENOMEM;
+                        goto finish;
+                }
+
+                j = ordered;
+                HASHMAP_FOREACH_KEY(q, p, files, i) {
+                        j->path = p;
+                        j->block = PTR_TO_SECTOR(q);
+                        j++;
+                }
+
+                assert(ordered + n == j);
+
+                qsort(ordered, n, sizeof(struct item), qsort_compare);
+
+                for (k = 0; k < n; k++)
+                        pack_file(pack, ordered[k].path, on_btrfs);
+
+                free(ordered);
+        }
+
+        log_debug("Finalizing...");
+
+        fflush(pack);
+
+        if (ferror(pack)) {
+                log_error("Failed to write pack file.");
+                r = -EIO;
+                goto finish;
+        }
+
+        if (rename(pack_fn_new, pack_fn) < 0) {
+                log_error("Failed to rename readahead file: %m");
+                r = -errno;
+                goto finish;
+        }
+
+        fclose(pack);
+        pack = NULL;
+
+        log_debug("Done.");
+
+finish:
+        if (fanotify_fd >= 0)
+                close_nointr_nofail(fanotify_fd);
+
+        if (signal_fd >= 0)
+                close_nointr_nofail(signal_fd);
+
+        if (inotify_fd >= 0)
+                close_nointr_nofail(inotify_fd);
+
+        if (pack) {
+                fclose(pack);
+                unlink(pack_fn_new);
+        }
+
+        free(pack_fn_new);
+        free(pack_fn);
+
+        while ((p = hashmap_steal_first_key(files)))
+                free(p);
+
+        hashmap_free(files);
+
+        return r;
+}
+
+static int help(void) {
+
+        printf("%s [OPTIONS...] [DIRECTORY]\n\n"
+               "Collect read-ahead data on early boot.\n\n"
+               "  -h --help                 Show this help\n"
+               "     --max-files=INT        Maximum number of files to read ahead\n"
+               "     --max-file-size=BYTES  Maximum size of files to read ahead\n"
+               "     --timeout=USEC         Maximum time to spend collecting data\n",
+               program_invocation_short_name);
+
+        return 0;
+}
+
+static int parse_argv(int argc, char *argv[]) {
+
+        enum {
+                ARG_FILES_MAX = 0x100,
+                ARG_FILE_SIZE_MAX,
+                ARG_TIMEOUT
+        };
+
+        static const struct option options[] = {
+                { "help",          no_argument,       NULL, 'h'                },
+                { "files-max",     required_argument, NULL, ARG_FILES_MAX      },
+                { "file-size-max", required_argument, NULL, ARG_FILE_SIZE_MAX  },
+                { "timeout",       required_argument, NULL, ARG_TIMEOUT        },
+                { NULL,            0,                 NULL, 0                  }
+        };
+
+        int c;
+
+        assert(argc >= 0);
+        assert(argv);
+
+        while ((c = getopt_long(argc, argv, "h", options, NULL)) >= 0) {
+
+                switch (c) {
+
+                case 'h':
+                        help();
+                        return 0;
+
+                case ARG_FILES_MAX:
+                        if (safe_atou(optarg, &arg_files_max) < 0 || arg_files_max <= 0) {
+                                log_error("Failed to parse maximum number of files %s.", optarg);
+                                return -EINVAL;
+                        }
+                        break;
+
+                case ARG_FILE_SIZE_MAX: {
+                        unsigned long long ull;
+
+                        if (safe_atollu(optarg, &ull) < 0 || ull <= 0) {
+                                log_error("Failed to parse maximum file size %s.", optarg);
+                                return -EINVAL;
+                        }
+
+                        arg_file_size_max = (off_t) ull;
+                        break;
+                }
+
+                case ARG_TIMEOUT:
+                        if (parse_usec(optarg, &arg_timeout) < 0 || arg_timeout <= 0) {
+                                log_error("Failed to parse timeout %s.", optarg);
+                                return -EINVAL;
+                        }
+
+                        break;
+
+                case '?':
+                        return -EINVAL;
+
+                default:
+                        log_error("Unknown option code %c", c);
+                        return -EINVAL;
+                }
+        }
+
+        if (optind != argc &&
+            optind != argc-1) {
+                help();
+                return -EINVAL;
+        }
+
+        return 1;
+}
+
+int main(int argc, char *argv[]) {
+        int r;
+        const char *root;
+
+        log_set_target(LOG_TARGET_AUTO);
+        log_parse_environment();
+        log_open();
+
+        umask(0022);
+
+        if ((r = parse_argv(argc, argv)) <= 0)
+                return r < 0 ? EXIT_FAILURE : EXIT_SUCCESS;
+
+        root = optind < argc ? argv[optind] : "/";
+
+        if (fs_on_read_only(root) > 0) {
+                log_info("Disabling readahead collector due to read-only media.");
+                return 0;
+        }
+
+        if (!enough_ram()) {
+                log_info("Disabling readahead collector due to low memory.");
+                return 0;
+        }
+
+        if (detect_virtualization(NULL) > 0) {
+                log_info("Disabling readahead collector due to execution in virtualized environment.");
+                return 0;
+        }
+
+        if (!(shared = shared_get()))
+                return 1;
+
+        shared->collect = getpid();
+        __sync_synchronize();
+
+        if (collect(root) < 0)
+                return 1;
+
+        return 0;
+}
diff --git a/src/readahead/readahead-common.c b/src/readahead/readahead-common.c
new file mode 100644 (file)
index 0000000..67214ec
--- /dev/null
@@ -0,0 +1,269 @@
+/*-*- 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 <http://www.gnu.org/licenses/>.
+***/
+
+#include <errno.h>
+#include <stdlib.h>
+#include <string.h>
+#include <sys/sysinfo.h>
+#include <sys/inotify.h>
+#include <fcntl.h>
+#include <sys/mman.h>
+#include <unistd.h>
+#include <libudev.h>
+
+#include "log.h"
+#include "readahead-common.h"
+#include "util.h"
+
+int file_verify(int fd, const char *fn, off_t file_size_max, struct stat *st) {
+        assert(fd >= 0);
+        assert(fn);
+        assert(st);
+
+        if (fstat(fd, st) < 0) {
+                log_warning("fstat(%s) failed: %m", fn);
+                return -errno;
+        }
+
+        if (!S_ISREG(st->st_mode)) {
+                log_debug("Not preloading special file %s", fn);
+                return 0;
+        }
+
+        if (st->st_size <= 0 || st->st_size > file_size_max) {
+                log_debug("Not preloading file %s with size out of bounds %llu", fn, (unsigned long long) st->st_size);
+                return 0;
+        }
+
+        return 1;
+}
+
+int fs_on_ssd(const char *p) {
+        struct stat st;
+        struct udev *udev = NULL;
+        struct udev_device *udev_device = NULL, *look_at = NULL;
+        bool b = false;
+        const char *devtype, *rotational, *model, *id;
+
+        assert(p);
+
+        if (stat(p, &st) < 0)
+                return -errno;
+
+        if (major(st.st_dev) == 0)
+                return false;
+
+        if (!(udev = udev_new()))
+                return -ENOMEM;
+
+        if (!(udev_device = udev_device_new_from_devnum(udev, 'b', st.st_dev)))
+                goto finish;
+
+        if ((devtype = udev_device_get_property_value(udev_device, "DEVTYPE")) &&
+            streq(devtype, "partition"))
+                look_at = udev_device_get_parent(udev_device);
+        else
+                look_at = udev_device;
+
+        if (!look_at)
+                goto finish;
+
+        /* First, try high-level property */
+        if ((id = udev_device_get_property_value(look_at, "ID_SSD"))) {
+                b = streq(id, "1");
+                goto finish;
+        }
+
+        /* Second, try kernel attribute */
+        if ((rotational = udev_device_get_sysattr_value(look_at, "queue/rotational")))
+                if ((b = streq(rotational, "0")))
+                        goto finish;
+
+        /* Finally, fallback to heuristics */
+        if (!(look_at = udev_device_get_parent(look_at)))
+                goto finish;
+
+        if ((model = udev_device_get_sysattr_value(look_at, "model")))
+                b = !!strstr(model, "SSD");
+
+finish:
+        if (udev_device)
+                udev_device_unref(udev_device);
+
+        if (udev)
+                udev_unref(udev);
+
+        return b;
+}
+
+int fs_on_read_only(const char *p) {
+        struct stat st;
+        struct udev *udev = NULL;
+        struct udev_device *udev_device = NULL;
+        bool b = false;
+        const char *read_only;
+
+        assert(p);
+
+        if (stat(p, &st) < 0)
+                return -errno;
+
+        if (major(st.st_dev) == 0)
+                return false;
+
+        if (!(udev = udev_new()))
+                return -ENOMEM;
+
+        if (!(udev_device = udev_device_new_from_devnum(udev, 'b', st.st_dev)))
+                goto finish;
+
+        if ((read_only = udev_device_get_sysattr_value(udev_device, "ro")))
+                if ((b = streq(read_only, "1")))
+                        goto finish;
+
+finish:
+        if (udev_device)
+                udev_device_unref(udev_device);
+
+        if (udev)
+                udev_unref(udev);
+
+        return b;
+}
+
+bool enough_ram(void) {
+        struct sysinfo si;
+
+        assert_se(sysinfo(&si) >= 0);
+
+        /* Enable readahead only with at least 128MB memory */
+        return si.totalram > 127 * 1024*1024 / si.mem_unit;
+}
+
+int open_inotify(void) {
+        int fd;
+
+        if ((fd = inotify_init1(IN_CLOEXEC|IN_NONBLOCK)) < 0) {
+                log_error("Failed to create inotify handle: %m");
+                return -errno;
+        }
+
+        mkdir("/run/systemd", 0755);
+        mkdir("/run/systemd/readahead", 0755);
+
+        if (inotify_add_watch(fd, "/run/systemd/readahead", IN_CREATE) < 0) {
+                log_error("Failed to watch /run/systemd/readahead: %m");
+                close_nointr_nofail(fd);
+                return -errno;
+        }
+
+        return fd;
+}
+
+ReadaheadShared *shared_get(void) {
+        int fd;
+        ReadaheadShared *m = NULL;
+
+        mkdir("/run/systemd", 0755);
+        mkdir("/run/systemd/readahead", 0755);
+
+        if ((fd = open("/run/systemd/readahead/shared", O_CREAT|O_RDWR|O_CLOEXEC, 0644)) < 0) {
+                log_error("Failed to create shared memory segment: %m");
+                goto finish;
+        }
+
+        if (ftruncate(fd, sizeof(ReadaheadShared)) < 0) {
+                log_error("Failed to truncate shared memory segment: %m");
+                goto finish;
+        }
+
+        if ((m = mmap(NULL, sizeof(ReadaheadShared), PROT_WRITE|PROT_READ, MAP_SHARED, fd, 0)) == MAP_FAILED) {
+                log_error("Failed to mmap shared memory segment: %m");
+                m = NULL;
+                goto finish;
+        }
+
+finish:
+        if (fd >= 0)
+                close_nointr_nofail(fd);
+
+        return m;
+}
+
+#define BUMP_REQUEST_NR (16*1024)
+
+int bump_request_nr(const char *p) {
+        struct stat st;
+        uint64_t u;
+        char *ap = NULL, *line = NULL;
+        int r;
+        dev_t d;
+
+        assert(p);
+
+        if (stat(p, &st) < 0)
+                return -errno;
+
+        if (major(st.st_dev) == 0)
+                return 0;
+
+        d = st.st_dev;
+        block_get_whole_disk(d, &d);
+
+        if (asprintf(&ap, "/sys/dev/block/%u:%u/queue/nr_requests", major(d), minor(d)) < 0) {
+                r= -ENOMEM;
+                goto finish;
+        }
+
+        r = read_one_line_file(ap, &line);
+        if (r < 0) {
+                if (r == -ENOENT)
+                        r = 0;
+                goto finish;
+        }
+
+        r = safe_atou64(line, &u);
+        if (r >= 0 && u >= BUMP_REQUEST_NR) {
+                r = 0;
+                goto finish;
+        }
+
+        free(line);
+        line = NULL;
+
+        if (asprintf(&line, "%lu", (unsigned long) BUMP_REQUEST_NR) < 0) {
+                r = -ENOMEM;
+                goto finish;
+        }
+
+        r = write_one_line_file(ap, line);
+        if (r < 0)
+                goto finish;
+
+        log_info("Bumped block_nr parameter of %u:%u to %lu. This is a temporary hack and should be removed one day.", major(d), minor(d), (unsigned long) BUMP_REQUEST_NR);
+        r = 1;
+
+finish:
+        free(ap);
+        free(line);
+
+        return r;
+}
diff --git a/src/readahead/readahead-common.h b/src/readahead/readahead-common.h
new file mode 100644 (file)
index 0000000..9547ad2
--- /dev/null
@@ -0,0 +1,50 @@
+/*-*- Mode: C; c-basic-offset: 8; indent-tabs-mode: nil -*-*/
+
+#ifndef fooreadaheadcommonhfoo
+#define fooreadaheadcommonhfoo
+
+/***
+  This file is part 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 <http://www.gnu.org/licenses/>.
+***/
+
+#include <sys/stat.h>
+#include <sys/types.h>
+
+#include "macro.h"
+
+#define READAHEAD_FILE_SIZE_MAX (10*1024*1024)
+
+int file_verify(int fd, const char *fn, off_t file_size_max, struct stat *st);
+
+int fs_on_ssd(const char *p);
+int fs_on_read_only(const char *p);
+
+bool enough_ram(void);
+
+int open_inotify(void);
+
+typedef struct ReadaheadShared {
+        pid_t collect;
+        pid_t replay;
+} _packed_ ReadaheadShared;
+
+ReadaheadShared *shared_get(void);
+
+int bump_request_nr(const char *p);
+
+#endif
diff --git a/src/readahead/readahead-replay.c b/src/readahead/readahead-replay.c
new file mode 100644 (file)
index 0000000..0c739c8
--- /dev/null
@@ -0,0 +1,378 @@
+/*-*- 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 <http://www.gnu.org/licenses/>.
+***/
+
+#include <errno.h>
+#include <inttypes.h>
+#include <fcntl.h>
+#include <linux/limits.h>
+#include <stdbool.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <sys/select.h>
+#include <sys/time.h>
+#include <sys/types.h>
+#include <sys/stat.h>
+#include <unistd.h>
+#include <getopt.h>
+#include <sys/inotify.h>
+
+#include <systemd/sd-daemon.h>
+
+#include "missing.h"
+#include "util.h"
+#include "set.h"
+#include "ioprio.h"
+#include "readahead-common.h"
+#include "virt.h"
+
+static off_t arg_file_size_max = READAHEAD_FILE_SIZE_MAX;
+
+static ReadaheadShared *shared = NULL;
+
+static int unpack_file(FILE *pack) {
+        char fn[PATH_MAX];
+        int r = 0, fd = -1;
+        bool any = false;
+        struct stat st;
+
+        assert(pack);
+
+        if (!fgets(fn, sizeof(fn), pack))
+                return 0;
+
+        char_array_0(fn);
+        truncate_nl(fn);
+
+        if ((fd = open(fn, O_RDONLY|O_CLOEXEC|O_NOATIME|O_NOCTTY|O_NOFOLLOW)) < 0) {
+
+                if (errno != ENOENT && errno != EPERM && errno != EACCES)
+                        log_warning("open(%s) failed: %m", fn);
+
+        } else if (file_verify(fd, fn, arg_file_size_max, &st) <= 0) {
+                close_nointr_nofail(fd);
+                fd = -1;
+        }
+
+        for (;;) {
+                uint32_t b, c;
+
+                if (fread(&b, sizeof(b), 1, pack) != 1 ||
+                    fread(&c, sizeof(c), 1, pack) != 1) {
+                        log_error("Premature end of pack file.");
+                        r = -EIO;
+                        goto finish;
+                }
+
+                if (b == 0 && c == 0)
+                        break;
+
+                if (c <= b) {
+                        log_error("Invalid pack file.");
+                        r = -EIO;
+                        goto finish;
+                }
+
+                log_debug("%s: page %u to %u", fn, b, c);
+
+                any = true;
+
+                if (fd >= 0)
+                        if (posix_fadvise(fd, b * page_size(), (c - b) * page_size(), POSIX_FADV_WILLNEED) < 0) {
+                                log_warning("posix_fadvise() failed: %m");
+                                goto finish;
+                        }
+        }
+
+        if (!any && fd >= 0) {
+                /* if no range is encoded in the pack file this is
+                 * intended to mean that the whole file shall be
+                 * read */
+
+                if (posix_fadvise(fd, 0, st.st_size, POSIX_FADV_WILLNEED) < 0) {
+                        log_warning("posix_fadvise() failed: %m");
+                        goto finish;
+                }
+        }
+
+finish:
+        if (fd >= 0)
+                close_nointr_nofail(fd);
+
+        return r;
+}
+
+static int replay(const char *root) {
+        FILE *pack = NULL;
+        char line[LINE_MAX];
+        int r = 0;
+        char *pack_fn = NULL;
+        int c;
+        bool on_ssd, ready = false;
+        int prio;
+        int inotify_fd = -1;
+
+        assert(root);
+
+        write_one_line_file("/proc/self/oom_score_adj", "1000");
+        bump_request_nr(root);
+
+        if (asprintf(&pack_fn, "%s/.readahead", root) < 0) {
+                log_error("Out of memory");
+                r = -ENOMEM;
+                goto finish;
+        }
+
+        if ((!(pack = fopen(pack_fn, "re")))) {
+                if (errno == ENOENT)
+                        log_debug("No pack file found.");
+                else {
+                        log_error("Failed to open pack file: %m");
+                        r = -errno;
+                }
+
+                goto finish;
+        }
+
+        posix_fadvise(fileno(pack), 0, 0, POSIX_FADV_WILLNEED);
+
+        if ((inotify_fd = open_inotify()) < 0) {
+                r = inotify_fd;
+                goto finish;
+        }
+
+        if (!(fgets(line, sizeof(line), pack))) {
+                log_error("Premature end of pack file.");
+                r = -EIO;
+                goto finish;
+        }
+
+        char_array_0(line);
+
+        if (!streq(line, CANONICAL_HOST "\n")) {
+                log_debug("Pack file host type mismatch.");
+                goto finish;
+        }
+
+        if ((c = getc(pack)) == EOF) {
+                log_debug("Premature end of pack file.");
+                r = -EIO;
+                goto finish;
+        }
+
+        /* We do not retest SSD here, so that we can start replaying
+         * before udev is up.*/
+        on_ssd = c == 'S';
+        log_debug("On SSD: %s", yes_no(on_ssd));
+
+        if (on_ssd)
+                prio = IOPRIO_PRIO_VALUE(IOPRIO_CLASS_IDLE, 0);
+        else
+                /* We are not using RT here, since we'd starve IO that
+                we didn't record (which is for example blkid, since
+                its disk accesses go directly to the block device and
+                are thus not visible in fallocate) to death. However,
+                we do ask for an IO prio that is slightly higher than
+                the default (which is BE. 4) */
+                prio = IOPRIO_PRIO_VALUE(IOPRIO_CLASS_BE, 2);
+
+        if (ioprio_set(IOPRIO_WHO_PROCESS, getpid(), prio) < 0)
+                log_warning("Failed to set IDLE IO priority class: %m");
+
+        sd_notify(0, "STATUS=Replaying readahead data");
+
+        log_debug("Replaying...");
+
+        if (access("/run/systemd/readahead/noreplay", F_OK) >= 0) {
+                log_debug("Got termination request");
+                goto done;
+        }
+
+        while (!feof(pack) && !ferror(pack)) {
+                uint8_t inotify_buffer[sizeof(struct inotify_event) + FILENAME_MAX];
+                int k;
+                ssize_t n;
+
+                if ((n = read(inotify_fd, &inotify_buffer, sizeof(inotify_buffer))) < 0) {
+                        if (errno != EINTR && errno != EAGAIN) {
+                                log_error("Failed to read inotify event: %m");
+                                r = -errno;
+                                goto finish;
+                        }
+                } else {
+                        struct inotify_event *e = (struct inotify_event*) inotify_buffer;
+
+                        while (n > 0) {
+                                size_t step;
+
+                                if ((e->mask & IN_CREATE) && streq(e->name, "noreplay")) {
+                                        log_debug("Got termination request");
+                                        goto done;
+                                }
+
+                                step = sizeof(struct inotify_event) + e->len;
+                                assert(step <= (size_t) n);
+
+                                e = (struct inotify_event*) ((uint8_t*) e + step);
+                                n -= step;
+                        }
+                }
+
+                if ((k = unpack_file(pack)) < 0) {
+                        r = k;
+                        goto finish;
+                }
+
+                if (!ready) {
+                        /* We delay the ready notification until we
+                         * queued at least one read */
+                        sd_notify(0, "READY=1");
+                        ready = true;
+                }
+        }
+
+done:
+        if (!ready)
+                sd_notify(0, "READY=1");
+
+        if (ferror(pack)) {
+                log_error("Failed to read pack file.");
+                r = -EIO;
+                goto finish;
+        }
+
+        log_debug("Done.");
+
+finish:
+        if (pack)
+                fclose(pack);
+
+        if (inotify_fd >= 0)
+                close_nointr_nofail(inotify_fd);
+
+        free(pack_fn);
+
+        return r;
+}
+
+
+static int help(void) {
+
+        printf("%s [OPTIONS...] [DIRECTORY]\n\n"
+               "Replay collected read-ahead data on early boot.\n\n"
+               "  -h --help                 Show this help\n"
+               "     --max-file-size=BYTES  Maximum size of files to read ahead\n",
+               program_invocation_short_name);
+
+        return 0;
+}
+
+static int parse_argv(int argc, char *argv[]) {
+
+        enum {
+                ARG_FILE_SIZE_MAX
+        };
+
+        static const struct option options[] = {
+                { "help",          no_argument,       NULL, 'h'                },
+                { "file-size-max", required_argument, NULL, ARG_FILE_SIZE_MAX  },
+                { NULL,            0,                 NULL, 0                  }
+        };
+
+        int c;
+
+        assert(argc >= 0);
+        assert(argv);
+
+        while ((c = getopt_long(argc, argv, "h", options, NULL)) >= 0) {
+
+                switch (c) {
+
+                case 'h':
+                        help();
+                        return 0;
+
+                case ARG_FILE_SIZE_MAX: {
+                        unsigned long long ull;
+
+                        if (safe_atollu(optarg, &ull) < 0 || ull <= 0) {
+                                log_error("Failed to parse maximum file size %s.", optarg);
+                                return -EINVAL;
+                        }
+
+                        arg_file_size_max = (off_t) ull;
+                        break;
+                }
+
+                case '?':
+                        return -EINVAL;
+
+                default:
+                        log_error("Unknown option code %c", c);
+                        return -EINVAL;
+                }
+        }
+
+        if (optind != argc &&
+            optind != argc-1) {
+                help();
+                return -EINVAL;
+        }
+
+        return 1;
+}
+
+int main(int argc, char*argv[]) {
+        int r;
+        const char *root;
+
+        log_set_target(LOG_TARGET_AUTO);
+        log_parse_environment();
+        log_open();
+
+        umask(0022);
+
+        if ((r = parse_argv(argc, argv)) <= 0)
+                return r < 0 ? EXIT_FAILURE : EXIT_SUCCESS;
+
+        root = optind < argc ? argv[optind] : "/";
+
+        if (!enough_ram()) {
+                log_info("Disabling readahead replay due to low memory.");
+                return 0;
+        }
+
+        if (detect_virtualization(NULL) > 0) {
+                log_info("Disabling readahead replay due to execution in virtualized environment.");
+                return 0;
+        }
+
+        if (!(shared = shared_get()))
+                return 1;
+
+        shared->replay = getpid();
+        __sync_synchronize();
+
+        if (replay(root) < 0)
+                return 1;
+
+        return 0;
+}
diff --git a/src/readahead/sd-readahead.c b/src/readahead/sd-readahead.c
new file mode 100644 (file)
index 0000000..a334066
--- /dev/null
@@ -0,0 +1,88 @@
+/*-*- Mode: C; c-basic-offset: 8; indent-tabs-mode: nil -*-*/
+
+/***
+  Copyright 2010 Lennart Poettering
+
+  Permission is hereby granted, free of charge, to any person
+  obtaining a copy of this software and associated documentation files
+  (the "Software"), to deal in the Software without restriction,
+  including without limitation the rights to use, copy, modify, merge,
+  publish, distribute, sublicense, and/or sell copies of the Software,
+  and to permit persons to whom the Software is furnished to do so,
+  subject to the following conditions:
+
+  The above copyright notice and this permission notice shall be
+  included in all copies or substantial portions of the Software.
+
+  THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+  EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
+  MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
+  NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS
+  BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN
+  ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
+  CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+  SOFTWARE.
+***/
+
+#ifndef _GNU_SOURCE
+#define _GNU_SOURCE
+#endif
+
+#include <unistd.h>
+#include <errno.h>
+#include <sys/stat.h>
+#include <sys/types.h>
+#include <fcntl.h>
+#include <string.h>
+
+#include "sd-readahead.h"
+
+#if (__GNUC__ >= 4)
+#ifdef SD_EXPORT_SYMBOLS
+/* Export symbols */
+#define _sd_export_ __attribute__ ((visibility("default")))
+#else
+/* Don't export the symbols */
+#define _sd_export_ __attribute__ ((visibility("hidden")))
+#endif
+#else
+#define _sd_export_
+#endif
+
+static int touch(const char *path) {
+
+#if !defined(DISABLE_SYSTEMD) && defined(__linux__)
+        int fd;
+
+        mkdir("/run/systemd", 0755);
+        mkdir("/run/systemd/readahead", 0755);
+
+        if ((fd = open(path, O_WRONLY|O_CREAT|O_CLOEXEC|O_NOCTTY, 0666)) < 0)
+                return -errno;
+
+        for (;;) {
+                if (close(fd) >= 0)
+                        break;
+
+                if (errno != -EINTR)
+                        return -errno;
+        }
+
+#endif
+        return 0;
+}
+
+_sd_export_ int sd_readahead(const char *action) {
+
+        if (!action)
+                return -EINVAL;
+
+        if (strcmp(action, "cancel") == 0)
+                return touch("/run/systemd/readahead/cancel");
+        else if (strcmp(action, "done") == 0)
+                return touch("/run/systemd/readahead/done");
+        else if (strcmp(action, "noreplay") == 0)
+                return touch("/run/systemd/readahead/noreplay");
+
+        return -EINVAL;
+}
diff --git a/src/remount-api-vfs.c b/src/remount-api-vfs.c
new file mode 100644 (file)
index 0000000..3e146eb
--- /dev/null
@@ -0,0 +1,161 @@
+/*-*- 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 <http://www.gnu.org/licenses/>.
+***/
+
+#include <unistd.h>
+#include <fcntl.h>
+#include <errno.h>
+#include <string.h>
+#include <sys/stat.h>
+#include <sys/wait.h>
+#include <mntent.h>
+
+#include "log.h"
+#include "util.h"
+#include "set.h"
+#include "mount-setup.h"
+#include "exit-status.h"
+
+/* Goes through /etc/fstab and remounts all API file systems, applying
+ * options that are in /etc/fstab that systemd might not have
+ * respected */
+
+int main(int argc, char *argv[]) {
+        int ret = EXIT_FAILURE;
+        FILE *f = NULL;
+        struct mntent* me;
+        Hashmap *pids = NULL;
+
+        if (argc > 1) {
+                log_error("This program takes no argument.");
+                return EXIT_FAILURE;
+        }
+
+        log_set_target(LOG_TARGET_AUTO);
+        log_parse_environment();
+        log_open();
+
+        umask(0022);
+
+        f = setmntent("/etc/fstab", "r");
+        if (!f) {
+                log_error("Failed to open /etc/fstab: %m");
+                goto finish;
+        }
+
+        pids = hashmap_new(trivial_hash_func, trivial_compare_func);
+        if (!pids) {
+                log_error("Failed to allocate set");
+                goto finish;
+        }
+
+        ret = EXIT_SUCCESS;
+
+        while ((me = getmntent(f))) {
+                pid_t pid;
+                int k;
+                char *s;
+
+                if (!mount_point_is_api(me->mnt_dir))
+                        continue;
+
+                log_debug("Remounting %s", me->mnt_dir);
+
+                pid = fork();
+                if (pid < 0) {
+                        log_error("Failed to fork: %m");
+                        ret = EXIT_FAILURE;
+                        continue;
+                }
+
+                if (pid == 0) {
+                        const char *arguments[5];
+                        /* Child */
+
+                        arguments[0] = "/bin/mount";
+                        arguments[1] = me->mnt_dir;
+                        arguments[2] = "-o";
+                        arguments[3] = "remount";
+                        arguments[4] = NULL;
+
+                        execv("/bin/mount", (char **) arguments);
+
+                        log_error("Failed to execute /bin/mount: %m");
+                        _exit(EXIT_FAILURE);
+                }
+
+                /* Parent */
+
+                s = strdup(me->mnt_dir);
+                if (!s) {
+                        log_error("Out of memory.");
+                        ret = EXIT_FAILURE;
+                        continue;
+                }
+
+
+                k = hashmap_put(pids, UINT_TO_PTR(pid), s);
+                if (k < 0) {
+                        log_error("Failed to add PID to set: %s", strerror(-k));
+                        ret = EXIT_FAILURE;
+                        continue;
+                }
+        }
+
+        while (!hashmap_isempty(pids)) {
+                siginfo_t si;
+                char *s;
+
+                zero(si);
+                if (waitid(P_ALL, 0, &si, WEXITED) < 0) {
+
+                        if (errno == EINTR)
+                                continue;
+
+                        log_error("waitid() failed: %m");
+                        ret = EXIT_FAILURE;
+                        break;
+                }
+
+                s = hashmap_remove(pids, UINT_TO_PTR(si.si_pid));
+                if (s) {
+                        if (!is_clean_exit(si.si_code, si.si_status)) {
+                                if (si.si_code == CLD_EXITED)
+                                        log_error("/bin/mount for %s exited with exit status %i.", s, si.si_status);
+                                else
+                                        log_error("/bin/mount for %s terminated by signal %s.", s, signal_to_string(si.si_status));
+
+                                ret = EXIT_FAILURE;
+                        }
+
+                        free(s);
+                }
+        }
+
+finish:
+
+        if (pids)
+                hashmap_free_free(pids);
+
+        if (f)
+                endmntent(f);
+
+        return ret;
+}
diff --git a/src/reply-password.c b/src/reply-password.c
new file mode 100644 (file)
index 0000000..3a96049
--- /dev/null
@@ -0,0 +1,109 @@
+/*-*- 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 <http://www.gnu.org/licenses/>.
+***/
+
+#include <sys/socket.h>
+#include <sys/poll.h>
+#include <sys/types.h>
+#include <assert.h>
+#include <string.h>
+#include <errno.h>
+#include <unistd.h>
+#include <fcntl.h>
+#include <sys/un.h>
+#include <sys/stat.h>
+#include <sys/signalfd.h>
+#include <getopt.h>
+#include <stddef.h>
+
+#include "log.h"
+#include "macro.h"
+#include "util.h"
+
+static int send_on_socket(int fd, const char *socket_name, const void *packet, size_t size) {
+        union {
+                struct sockaddr sa;
+                struct sockaddr_un un;
+        } sa;
+
+        assert(fd >= 0);
+        assert(socket_name);
+        assert(packet);
+
+        zero(sa);
+        sa.un.sun_family = AF_UNIX;
+        strncpy(sa.un.sun_path, socket_name, sizeof(sa.un.sun_path));
+
+        if (sendto(fd, packet, size, MSG_NOSIGNAL, &sa.sa, offsetof(struct sockaddr_un, sun_path) + strlen(socket_name)) < 0) {
+                log_error("Failed to send: %m");
+                return -1;
+        }
+
+        return 0;
+}
+
+int main(int argc, char *argv[]) {
+        int fd = -1, r = EXIT_FAILURE;
+        char packet[LINE_MAX];
+        size_t length;
+
+        log_set_target(LOG_TARGET_AUTO);
+        log_parse_environment();
+        log_open();
+
+        if (argc != 3) {
+                log_error("Wrong number of arguments.");
+                goto finish;
+        }
+
+        if (streq(argv[1], "1")) {
+
+                packet[0] = '+';
+                if (!fgets(packet+1, sizeof(packet)-1, stdin)) {
+                        log_error("Failed to read password: %m");
+                        goto finish;
+                }
+
+                truncate_nl(packet+1);
+                length = 1 + strlen(packet+1) + 1;
+        } else if (streq(argv[1], "0")) {
+                packet[0] = '-';
+                length = 1;
+        } else {
+                log_error("Invalid first argument %s", argv[1]);
+                goto finish;
+        }
+
+        if ((fd = socket(AF_UNIX, SOCK_DGRAM|SOCK_CLOEXEC|SOCK_NONBLOCK, 0)) < 0) {
+                log_error("socket() failed: %m");
+                goto finish;
+        }
+
+        if (send_on_socket(fd, argv[2], packet, length) < 0)
+                goto finish;
+
+        r = EXIT_SUCCESS;
+
+finish:
+        if (fd >= 0)
+                close_nointr_nofail(fd);
+
+        return r;
+}
diff --git a/src/sd-id128.c b/src/sd-id128.c
new file mode 100644 (file)
index 0000000..b4184e1
--- /dev/null
@@ -0,0 +1,221 @@
+/*-*- 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 <http://www.gnu.org/licenses/>.
+***/
+
+#include <errno.h>
+#include <fcntl.h>
+#include <unistd.h>
+
+#include "sd-id128.h"
+
+#include "util.h"
+#include "macro.h"
+
+_public_ char *sd_id128_to_string(sd_id128_t id, char s[33]) {
+        unsigned n;
+
+        if (!s)
+                return NULL;
+
+        for (n = 0; n < 16; n++) {
+                s[n*2] = hexchar(id.bytes[n] >> 4);
+                s[n*2+1] = hexchar(id.bytes[n] & 0xF);
+        }
+
+        s[32] = 0;
+
+        return s;
+}
+
+_public_ int sd_id128_from_string(const char s[33], sd_id128_t *ret) {
+        unsigned n;
+        sd_id128_t t;
+
+        if (!s)
+                return -EINVAL;
+        if (!ret)
+                return -EINVAL;
+
+        for (n = 0; n < 16; n++) {
+                int a, b;
+
+                a = unhexchar(s[n*2]);
+                if (a < 0)
+                        return -EINVAL;
+
+                b = unhexchar(s[n*2+1]);
+                if (b < 0)
+                        return -EINVAL;
+
+                t.bytes[n] = (a << 4) | b;
+        }
+
+        if (s[32] != 0)
+                return -EINVAL;
+
+        *ret = t;
+        return 0;
+}
+
+static sd_id128_t make_v4_uuid(sd_id128_t id) {
+        /* Stolen from generate_random_uuid() of drivers/char/random.c
+         * in the kernel sources */
+
+        /* Set UUID version to 4 --- truly random generation */
+        id.bytes[6] = (id.bytes[6] & 0x0F) | 0x40;
+
+        /* Set the UUID variant to DCE */
+        id.bytes[8] = (id.bytes[8] & 0x3F) | 0x80;
+
+        return id;
+}
+
+_public_ int sd_id128_get_machine(sd_id128_t *ret) {
+        static __thread sd_id128_t saved_machine_id;
+        static __thread bool saved_machine_id_valid = false;
+        int fd;
+        char buf[32];
+        ssize_t k;
+        unsigned j;
+        sd_id128_t t;
+
+        if (!ret)
+                return -EINVAL;
+
+        if (saved_machine_id_valid) {
+                *ret = saved_machine_id;
+                return 0;
+        }
+
+        fd = open("/etc/machine-id", O_RDONLY|O_CLOEXEC|O_NOCTTY);
+        if (fd < 0)
+                return -errno;
+
+        k = loop_read(fd, buf, 32, false);
+        close_nointr_nofail(fd);
+
+        if (k < 0)
+                return (int) k;
+
+        if (k < 32)
+                return -EIO;
+
+        for (j = 0; j < 16; j++) {
+                int a, b;
+
+                a = unhexchar(buf[j*2]);
+                b = unhexchar(buf[j*2+1]);
+
+                if (a < 0 || b < 0)
+                        return -EIO;
+
+                t.bytes[j] = a << 4 | b;
+        }
+
+        saved_machine_id = t;
+        saved_machine_id_valid = true;
+
+        *ret = t;
+        return 0;
+}
+
+_public_ int sd_id128_get_boot(sd_id128_t *ret) {
+        static __thread sd_id128_t saved_boot_id;
+        static __thread bool saved_boot_id_valid = false;
+        int fd;
+        char buf[36];
+        ssize_t k;
+        unsigned j;
+        sd_id128_t t;
+        char *p;
+
+        if (!ret)
+                return -EINVAL;
+
+        if (saved_boot_id_valid) {
+                *ret = saved_boot_id;
+                return 0;
+        }
+
+        fd = open("/proc/sys/kernel/random/boot_id", O_RDONLY|O_CLOEXEC|O_NOCTTY);
+        if (fd < 0)
+                return -errno;
+
+        k = loop_read(fd, buf, 36, false);
+        close_nointr_nofail(fd);
+
+        if (k < 0)
+                return (int) k;
+
+        if (k < 36)
+                return -EIO;
+
+        for (j = 0, p = buf; j < 16; j++) {
+                int a, b;
+
+                if (*p == '-')
+                        p++;
+
+                a = unhexchar(p[0]);
+                b = unhexchar(p[1]);
+
+                if (a < 0 || b < 0)
+                        return -EIO;
+
+                t.bytes[j] = a << 4 | b;
+
+                p += 2;
+        }
+
+        saved_boot_id = t;
+        saved_boot_id_valid = true;
+
+        *ret = t;
+        return 0;
+}
+
+_public_ int sd_id128_randomize(sd_id128_t *ret) {
+        int fd;
+        ssize_t k;
+        sd_id128_t t;
+
+        if (!ret)
+                return -EINVAL;
+
+        fd = open("/dev/urandom", O_RDONLY|O_CLOEXEC|O_NOCTTY);
+        if (fd < 0)
+                return -errno;
+
+        k = loop_read(fd, &t, 16, false);
+        close_nointr_nofail(fd);
+
+        if (k < 0)
+                return (int) k;
+
+        if (k < 16)
+                return -EIO;
+
+        /* Turn this into a valid v4 UUID, to be nice. Note that we
+         * only guarantee this for newly generated UUIDs, not for
+         * pre-existing ones.*/
+
+        *ret = make_v4_uuid(t);
+        return 0;
+}
diff --git a/src/securebits.h b/src/securebits.h
new file mode 100644 (file)
index 0000000..ba0bba5
--- /dev/null
@@ -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/selinux-setup.c b/src/selinux-setup.c
new file mode 100644 (file)
index 0000000..a7e1fa4
--- /dev/null
@@ -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 <http://www.gnu.org/licenses/>.
+***/
+
+#include <unistd.h>
+#include <stdio.h>
+#include <errno.h>
+#include <string.h>
+#include <stdlib.h>
+
+#ifdef HAVE_SELINUX
+#include <selinux/selinux.h>
+#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
new file mode 100644 (file)
index 0000000..6b8fe00
--- /dev/null
@@ -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 <http://www.gnu.org/licenses/>.
+***/
+
+#include <stdbool.h>
+
+int selinux_setup(bool *loaded_policy);
+
+#endif
diff --git a/src/service.c b/src/service.c
new file mode 100644 (file)
index 0000000..bf2e0a9
--- /dev/null
@@ -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 <http://www.gnu.org/licenses/>.
+***/
+
+#include <errno.h>
+#include <signal.h>
+#include <dirent.h>
+#include <unistd.h>
+#include <sys/reboot.h>
+
+#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 <interactive>, not used, equivalent to X-Interactive */
+                if (parsed && !startswith_no_case (parsed[0], "<interactive>")) {
+                        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
new file mode 100644 (file)
index 0000000..60b1051
--- /dev/null
@@ -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 <http://www.gnu.org/licenses/>.
+***/
+
+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/set.c b/src/set.c
new file mode 100644 (file)
index 0000000..097b9d3
--- /dev/null
+++ b/src/set.c
@@ -0,0 +1,118 @@
+/*-*- 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 <http://www.gnu.org/licenses/>.
+***/
+
+#include <stdlib.h>
+
+#include "set.h"
+#include "hashmap.h"
+
+#define MAKE_SET(h) ((Set*) (h))
+#define MAKE_HASHMAP(s) ((Hashmap*) (s))
+
+/* For now this is not much more than a wrapper around a hashmap */
+
+Set *set_new(hash_func_t hash_func, compare_func_t compare_func) {
+        return MAKE_SET(hashmap_new(hash_func, compare_func));
+}
+
+void set_free(Set* s) {
+        hashmap_free(MAKE_HASHMAP(s));
+}
+
+void set_free_free(Set *s) {
+        hashmap_free_free(MAKE_HASHMAP(s));
+}
+
+int set_ensure_allocated(Set **s, hash_func_t hash_func, compare_func_t compare_func) {
+        return hashmap_ensure_allocated((Hashmap**) s, hash_func, compare_func);
+}
+
+int set_put(Set *s, void *value) {
+        return hashmap_put(MAKE_HASHMAP(s), value, value);
+}
+
+int set_replace(Set *s, void *value) {
+        return hashmap_replace(MAKE_HASHMAP(s), value, value);
+}
+
+void *set_get(Set *s, void *value) {
+        return hashmap_get(MAKE_HASHMAP(s), value);
+}
+
+void *set_remove(Set *s, void *value) {
+        return hashmap_remove(MAKE_HASHMAP(s), value);
+}
+
+int set_remove_and_put(Set *s, void *old_value, void *new_value) {
+        return hashmap_remove_and_put(MAKE_HASHMAP(s), old_value, new_value, new_value);
+}
+
+unsigned set_size(Set *s) {
+        return hashmap_size(MAKE_HASHMAP(s));
+}
+
+bool set_isempty(Set *s) {
+        return hashmap_isempty(MAKE_HASHMAP(s));
+}
+
+void *set_iterate(Set *s, Iterator *i) {
+        return hashmap_iterate(MAKE_HASHMAP(s), i, NULL);
+}
+
+void *set_iterate_backwards(Set *s, Iterator *i) {
+        return hashmap_iterate_backwards(MAKE_HASHMAP(s), i, NULL);
+}
+
+void *set_iterate_skip(Set *s, void *value, Iterator *i) {
+        return hashmap_iterate_skip(MAKE_HASHMAP(s), value, i);
+}
+
+void *set_steal_first(Set *s) {
+        return hashmap_steal_first(MAKE_HASHMAP(s));
+}
+
+void* set_first(Set *s) {
+        return hashmap_first(MAKE_HASHMAP(s));
+}
+
+void* set_last(Set *s) {
+        return hashmap_last(MAKE_HASHMAP(s));
+}
+
+int set_merge(Set *s, Set *other) {
+        return hashmap_merge(MAKE_HASHMAP(s), MAKE_HASHMAP(other));
+}
+
+void set_move(Set *s, Set *other) {
+        return hashmap_move(MAKE_HASHMAP(s), MAKE_HASHMAP(other));
+}
+
+int set_move_one(Set *s, Set *other, void *value) {
+        return hashmap_move_one(MAKE_HASHMAP(s), MAKE_HASHMAP(other), value);
+}
+
+Set* set_copy(Set *s) {
+        return MAKE_SET(hashmap_copy(MAKE_HASHMAP(s)));
+}
+
+void set_clear(Set *s) {
+        hashmap_clear(MAKE_HASHMAP(s));
+}
diff --git a/src/set.h b/src/set.h
new file mode 100644 (file)
index 0000000..885780c
--- /dev/null
+++ b/src/set.h
@@ -0,0 +1,69 @@
+/*-*- Mode: C; c-basic-offset: 8; indent-tabs-mode: nil -*-*/
+
+#ifndef foosethfoo
+#define foosethfoo
+
+/***
+  This file is part 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 <http://www.gnu.org/licenses/>.
+***/
+
+/* Pretty straightforward set implementation. Internally based on the
+ * hashmap. That means that as a minor optimization a NULL set
+ * object will be treated as empty set for all read
+ * operations. That way it is not necessary to instantiate an object
+ * for each set use. */
+
+#include "hashmap.h"
+
+typedef struct Set Set;
+
+Set *set_new(hash_func_t hash_func, compare_func_t compare_func);
+void set_free(Set* s);
+void set_free_free(Set *s);
+Set* set_copy(Set *s);
+int set_ensure_allocated(Set **s, hash_func_t hash_func, compare_func_t compare_func);
+
+int set_put(Set *s, void *value);
+int set_replace(Set *s, void *value);
+void *set_get(Set *s, void *value);
+void *set_remove(Set *s, void *value);
+int set_remove_and_put(Set *s, void *old_value, void *new_value);
+
+int set_merge(Set *s, Set *other);
+void set_move(Set *s, Set *other);
+int set_move_one(Set *s, Set *other, void *value);
+
+unsigned set_size(Set *s);
+bool set_isempty(Set *s);
+
+void *set_iterate(Set *s, Iterator *i);
+void *set_iterate_backwards(Set *s, Iterator *i);
+void *set_iterate_skip(Set *s, void *value, Iterator *i);
+
+void set_clear(Set *s);
+void *set_steal_first(Set *s);
+void* set_first(Set *s);
+void* set_last(Set *s);
+
+#define SET_FOREACH(e, s, i) \
+        for ((i) = ITERATOR_FIRST, (e) = set_iterate((s), &(i)); (e); (e) = set_iterate((s), &(i)))
+
+#define SET_FOREACH_BACKWARDS(e, s, i) \
+        for ((i) = ITERATOR_LAST, (e) = set_iterate_backwards((s), &(i)); (e); (e) = set_iterate_backwards((s), &(i)))
+
+#endif
diff --git a/src/shutdown.c b/src/shutdown.c
new file mode 100644 (file)
index 0000000..d157e0f
--- /dev/null
@@ -0,0 +1,485 @@
+/*-*- Mode: C; c-basic-offset: 8; indent-tabs-mode: nil -*-*/
+
+/***
+  This file is part of systemd.
+
+  Copyright 2010 ProFUSION embedded systems
+
+  systemd is free software; you can redistribute it and/or modify it
+  under the terms of the GNU 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 <http://www.gnu.org/licenses/>.
+***/
+
+#include <sys/mman.h>
+#include <sys/types.h>
+#include <sys/reboot.h>
+#include <linux/reboot.h>
+#include <sys/wait.h>
+#include <sys/types.h>
+#include <sys/stat.h>
+#include <sys/mount.h>
+#include <sys/syscall.h>
+#include <fcntl.h>
+#include <dirent.h>
+#include <errno.h>
+#include <unistd.h>
+#include <signal.h>
+#include <stdbool.h>
+#include <stdlib.h>
+#include <string.h>
+
+#include "missing.h"
+#include "log.h"
+#include "umount.h"
+#include "util.h"
+#include "virt.h"
+
+#define TIMEOUT_USEC (5 * USEC_PER_SEC)
+#define FINALIZE_ATTEMPTS 50
+
+static bool ignore_proc(pid_t pid) {
+        char buf[PATH_MAX];
+        FILE *f;
+        char c;
+        size_t count;
+        uid_t uid;
+        int r;
+
+        /* We are PID 1, let's not commit suicide */
+        if (pid == 1)
+                return true;
+
+        r = get_process_uid(pid, &uid);
+        if (r < 0)
+                return true; /* not really, but better safe than sorry */
+
+        /* Non-root processes otherwise are always subject to be killed */
+        if (uid != 0)
+                return false;
+
+        snprintf(buf, sizeof(buf), "/proc/%lu/cmdline", (unsigned long) pid);
+        char_array_0(buf);
+
+        f = fopen(buf, "re");
+        if (!f)
+                return true; /* not really, but has the desired effect */
+
+        count = fread(&c, 1, 1, f);
+        fclose(f);
+
+        /* Kernel threads have an empty cmdline */
+        if (count <= 0)
+                return true;
+
+        /* Processes with argv[0][0] = '@' we ignore from the killing
+         * spree.
+         *
+         * http://www.freedesktop.org/wiki/Software/systemd/RootStorageDaemons */
+        if (count == 1 && c == '@')
+                return true;
+
+        return false;
+}
+
+static int killall(int sign) {
+        DIR *dir;
+        struct dirent *d;
+        unsigned int n_processes = 0;
+
+        dir = opendir("/proc");
+        if (!dir)
+                return -errno;
+
+        while ((d = readdir(dir))) {
+                pid_t pid;
+
+                if (parse_pid(d->d_name, &pid) < 0)
+                        continue;
+
+                if (ignore_proc(pid))
+                        continue;
+
+                if (kill(pid, sign) == 0)
+                        n_processes++;
+                else
+                        log_warning("Could not kill %d: %m", pid);
+        }
+
+        closedir(dir);
+
+        return n_processes;
+}
+
+static void wait_for_children(int n_processes, sigset_t *mask) {
+        usec_t until;
+
+        assert(mask);
+
+        until = now(CLOCK_MONOTONIC) + TIMEOUT_USEC;
+        for (;;) {
+                struct timespec ts;
+                int k;
+                usec_t n;
+
+                for (;;) {
+                        pid_t pid = waitpid(-1, NULL, WNOHANG);
+
+                        if (pid == 0)
+                                break;
+
+                        if (pid < 0 && errno == ECHILD)
+                                return;
+
+                        if (n_processes > 0)
+                                if (--n_processes == 0)
+                                        return;
+                }
+
+                n = now(CLOCK_MONOTONIC);
+                if (n >= until)
+                        return;
+
+                timespec_store(&ts, until - n);
+
+                if ((k = sigtimedwait(mask, NULL, &ts)) != SIGCHLD) {
+
+                        if (k < 0 && errno != EAGAIN) {
+                                log_error("sigtimedwait() failed: %m");
+                                return;
+                        }
+
+                        if (k >= 0)
+                                log_warning("sigtimedwait() returned unexpected signal.");
+                }
+        }
+}
+
+static void send_signal(int sign) {
+        sigset_t mask, oldmask;
+        int n_processes;
+
+        assert_se(sigemptyset(&mask) == 0);
+        assert_se(sigaddset(&mask, SIGCHLD) == 0);
+        assert_se(sigprocmask(SIG_BLOCK, &mask, &oldmask) == 0);
+
+        if (kill(-1, SIGSTOP) < 0 && errno != ESRCH)
+                log_warning("kill(-1, SIGSTOP) failed: %m");
+
+        n_processes = killall(sign);
+
+        if (kill(-1, SIGCONT) < 0 && errno != ESRCH)
+                log_warning("kill(-1, SIGCONT) failed: %m");
+
+        if (n_processes <= 0)
+                goto finish;
+
+        wait_for_children(n_processes, &mask);
+
+finish:
+        sigprocmask(SIG_SETMASK, &oldmask, NULL);
+}
+
+static void ultimate_send_signal(int sign) {
+        sigset_t mask, oldmask;
+        int r;
+
+        assert_se(sigemptyset(&mask) == 0);
+        assert_se(sigaddset(&mask, SIGCHLD) == 0);
+        assert_se(sigprocmask(SIG_BLOCK, &mask, &oldmask) == 0);
+
+        if (kill(-1, SIGSTOP) < 0 && errno != ESRCH)
+                log_warning("kill(-1, SIGSTOP) failed: %m");
+
+        r = kill(-1, sign);
+        if (r < 0 && errno != ESRCH)
+                log_warning("kill(-1, %s) failed: %m", signal_to_string(sign));
+
+        if (kill(-1, SIGCONT) < 0 && errno != ESRCH)
+                log_warning("kill(-1, SIGCONT) failed: %m");
+
+        if (r < 0)
+                goto finish;
+
+        wait_for_children(0, &mask);
+
+finish:
+        sigprocmask(SIG_SETMASK, &oldmask, NULL);
+}
+
+static int prepare_new_root(void) {
+        static const char dirs[] =
+                "/run/initramfs/oldroot\0"
+                "/run/initramfs/proc\0"
+                "/run/initramfs/sys\0"
+                "/run/initramfs/dev\0"
+                "/run/initramfs/run\0";
+
+        const char *dir;
+
+        if (mount("/run/initramfs", "/run/initramfs", NULL, MS_BIND, NULL) < 0) {
+                log_error("Failed to mount bind /run/initramfs on /run/initramfs: %m");
+                return -errno;
+        }
+
+        if (mount(NULL, "/run/initramfs", NULL, MS_PRIVATE, NULL) < 0) {
+                log_error("Failed to make /run/initramfs private mount: %m");
+                return -errno;
+        }
+
+        NULSTR_FOREACH(dir, dirs)
+                if (mkdir_p(dir, 0755) < 0 && errno != EEXIST) {
+                        log_error("Failed to mkdir %s: %m", dir);
+                        return -errno;
+                }
+
+        if (mount("/sys", "/run/initramfs/sys", NULL, MS_BIND, NULL) < 0) {
+                log_error("Failed to mount bind /sys on /run/initramfs/sys: %m");
+                return -errno;
+        }
+
+        if (mount("/proc", "/run/initramfs/proc", NULL, MS_BIND, NULL) < 0) {
+                log_error("Failed to mount bind /proc on /run/initramfs/proc: %m");
+                return -errno;
+        }
+
+        if (mount("/dev", "/run/initramfs/dev", NULL, MS_BIND, NULL) < 0) {
+                log_error("Failed to mount bind /dev on /run/initramfs/dev: %m");
+                return -errno;
+        }
+
+        if (mount("/run", "/run/initramfs/run", NULL, MS_BIND, NULL) < 0) {
+                log_error("Failed to mount bind /run on /run/initramfs/run: %m");
+                return -errno;
+        }
+
+        return 0;
+}
+
+static int pivot_to_new_root(void) {
+        int fd;
+
+        chdir("/run/initramfs");
+
+        /*
+          In case some evil process made "/" MS_SHARED
+          It works for pivot_root, but the ref count for the root device
+          is not decreasing :-/
+        */
+        if (mount(NULL, "/", NULL, MS_PRIVATE, NULL) < 0) {
+                log_error("Failed to make \"/\" private mount %m");
+                return -errno;
+        }
+
+        if (pivot_root(".", "oldroot") < 0) {
+                log_error("pivot failed: %m");
+                /* only chroot if pivot root succeded */
+                return -errno;
+        }
+
+        chroot(".");
+        log_info("Successfully changed into root pivot.");
+
+        fd = open("/dev/console", O_RDWR);
+        if (fd < 0)
+                log_error("Failed to open /dev/console: %m");
+        else {
+                make_stdio(fd);
+
+                /* Initialize the controlling terminal */
+                setsid();
+                ioctl(STDIN_FILENO, TIOCSCTTY, NULL);
+        }
+
+        return 0;
+}
+
+int main(int argc, char *argv[]) {
+        int cmd, r;
+        unsigned retries;
+        bool need_umount = true, need_swapoff = true, need_loop_detach = true, need_dm_detach = true;
+        bool killed_everbody = false, in_container;
+
+        log_parse_environment();
+        log_set_target(LOG_TARGET_CONSOLE); /* syslog will die if not gone yet */
+        log_open();
+
+        umask(0022);
+
+        if (getpid() != 1) {
+                log_error("Not executed by init (pid 1).");
+                r = -EPERM;
+                goto error;
+        }
+
+        if (argc != 2) {
+                log_error("Invalid number of arguments.");
+                r = -EINVAL;
+                goto error;
+        }
+
+        in_container = detect_container(NULL) > 0;
+
+        if (streq(argv[1], "reboot"))
+                cmd = RB_AUTOBOOT;
+        else if (streq(argv[1], "poweroff"))
+                cmd = RB_POWER_OFF;
+        else if (streq(argv[1], "halt"))
+                cmd = RB_HALT_SYSTEM;
+        else if (streq(argv[1], "kexec"))
+                cmd = LINUX_REBOOT_CMD_KEXEC;
+        else {
+                log_error("Unknown action '%s'.", argv[1]);
+                r = -EINVAL;
+                goto error;
+        }
+
+        /* lock us into memory */
+        if (mlockall(MCL_CURRENT|MCL_FUTURE) != 0)
+                log_warning("Cannot lock process memory: %m");
+
+        log_info("Sending SIGTERM to remaining processes...");
+        send_signal(SIGTERM);
+
+        log_info("Sending SIGKILL to remaining processes...");
+        send_signal(SIGKILL);
+
+        if (in_container)
+                need_swapoff = false;
+
+        /* Unmount all mountpoints, swaps, and loopback devices */
+        for (retries = 0; retries < FINALIZE_ATTEMPTS; retries++) {
+                bool changed = false;
+
+                if (need_umount) {
+                        log_info("Unmounting file systems.");
+                        r = umount_all(&changed);
+                        if (r == 0)
+                                need_umount = false;
+                        else if (r > 0)
+                                log_info("Not all file systems unmounted, %d left.", r);
+                        else
+                                log_error("Failed to unmount file systems: %s", strerror(-r));
+                }
+
+                if (need_swapoff) {
+                        log_info("Disabling swaps.");
+                        r = swapoff_all(&changed);
+                        if (r == 0)
+                                need_swapoff = false;
+                        else if (r > 0)
+                                log_info("Not all swaps are turned off, %d left.", r);
+                        else
+                                log_error("Failed to turn off swaps: %s", strerror(-r));
+                }
+
+                if (need_loop_detach) {
+                        log_info("Detaching loop devices.");
+                        r = loopback_detach_all(&changed);
+                        if (r == 0)
+                                need_loop_detach = false;
+                        else if (r > 0)
+                                log_info("Not all loop devices detached, %d left.", r);
+                        else
+                                log_error("Failed to detach loop devices: %s", strerror(-r));
+                }
+
+                if (need_dm_detach) {
+                        log_info("Detaching DM devices.");
+                        r = dm_detach_all(&changed);
+                        if (r == 0)
+                                need_dm_detach = false;
+                        else if (r > 0)
+                                log_warning("Not all DM devices detached, %d left.", r);
+                        else
+                                log_error("Failed to detach DM devices: %s", strerror(-r));
+                }
+
+                if (!need_umount && !need_swapoff && !need_loop_detach && !need_dm_detach) {
+                        if (retries > 0)
+                                log_info("All filesystems, swaps, loop devices, DM devices detached.");
+                        /* Yay, done */
+                        break;
+                }
+
+                /* If in this iteration we didn't manage to
+                 * unmount/deactivate anything, we either kill more
+                 * processes, or simply give up */
+                if (!changed) {
+
+                        if (killed_everbody) {
+                                /* Hmm, we already killed everybody,
+                                 * let's just give up */
+                                log_error("Cannot finalize remaining file systems and devices, giving up.");
+                                break;
+                        }
+
+                        log_warning("Cannot finalize remaining file systems and devices, trying to kill remaining processes.");
+                        ultimate_send_signal(SIGTERM);
+                        ultimate_send_signal(SIGKILL);
+                        killed_everbody = true;
+                }
+
+                log_debug("Couldn't finalize remaining file systems and devices after %u retries, trying again.", retries+1);
+        }
+
+        if (retries >= FINALIZE_ATTEMPTS)
+                log_error("Too many iterations, giving up.");
+
+        execute_directory(SYSTEM_SHUTDOWN_PATH, NULL, NULL);
+
+        /* If we are in a container, just exit, this will kill our
+         * container for good. */
+        if (in_container) {
+                log_error("Exiting container.");
+                exit(0);
+        }
+
+        if (access("/run/initramfs/shutdown", X_OK) == 0) {
+
+                if (prepare_new_root() >= 0 &&
+                    pivot_to_new_root() >= 0) {
+                        execv("/shutdown", argv);
+                        log_error("Failed to execute shutdown binary: %m");
+                }
+        }
+
+        sync();
+
+        if (cmd == LINUX_REBOOT_CMD_KEXEC) {
+                /* We cheat and exec kexec to avoid doing all its work */
+                pid_t pid = fork();
+
+                if (pid < 0)
+                        log_error("Could not fork: %m. Falling back to normal reboot.");
+                else if (pid > 0) {
+                        wait_for_terminate_and_warn("kexec", pid);
+                        log_warning("kexec failed. Falling back to normal reboot.");
+                } else {
+                        /* Child */
+                        const char *args[3] = { "/sbin/kexec", "-e", NULL };
+                        execv(args[0], (char * const *) args);
+                        return EXIT_FAILURE;
+                }
+
+                cmd = RB_AUTOBOOT;
+        }
+
+        reboot(cmd);
+        log_error("Failed to invoke reboot(): %m");
+        r = -errno;
+
+  error:
+        log_error("Critical error while doing system shutdown: %s", strerror(-r));
+
+        freeze();
+        return EXIT_FAILURE;
+}
diff --git a/src/shutdownd.c b/src/shutdownd.c
new file mode 100644 (file)
index 0000000..b4052d4
--- /dev/null
@@ -0,0 +1,366 @@
+/*-*- 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 <http://www.gnu.org/licenses/>.
+***/
+
+#include <sys/socket.h>
+#include <sys/poll.h>
+#include <sys/types.h>
+#include <sys/timerfd.h>
+#include <assert.h>
+#include <string.h>
+#include <errno.h>
+#include <unistd.h>
+#include <fcntl.h>
+
+#include <systemd/sd-daemon.h>
+
+#include "shutdownd.h"
+#include "log.h"
+#include "macro.h"
+#include "util.h"
+#include "utmp-wtmp.h"
+
+static int read_packet(int fd, struct shutdownd_command *_c) {
+        struct msghdr msghdr;
+        struct iovec iovec;
+        struct ucred *ucred;
+        union {
+                struct cmsghdr cmsghdr;
+                uint8_t buf[CMSG_SPACE(sizeof(struct ucred))];
+        } control;
+        struct shutdownd_command c;
+        ssize_t n;
+
+        assert(fd >= 0);
+        assert(_c);
+
+        zero(iovec);
+        iovec.iov_base = &c;
+        iovec.iov_len = sizeof(c);
+
+        zero(control);
+        zero(msghdr);
+        msghdr.msg_iov = &iovec;
+        msghdr.msg_iovlen = 1;
+        msghdr.msg_control = &control;
+        msghdr.msg_controllen = sizeof(control);
+
+        if ((n = recvmsg(fd, &msghdr, MSG_DONTWAIT)) <= 0) {
+                if (n >= 0) {
+                        log_error("Short read");
+                        return -EIO;
+                }
+
+                if (errno == EAGAIN || errno == EINTR)
+                        return 0;
+
+                log_error("recvmsg(): %m");
+                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 message without credentials. Ignoring.");
+                return 0;
+        }
+
+        ucred = (struct ucred*) CMSG_DATA(&control.cmsghdr);
+        if (ucred->uid != 0) {
+                log_warning("Got request from unprivileged user. Ignoring.");
+                return 0;
+        }
+
+        if (n != sizeof(c)) {
+                log_warning("Message has invalid size. Ignoring");
+                return 0;
+        }
+
+        char_array_0(c.wall_message);
+
+        *_c = c;
+        return 1;
+}
+
+static void warn_wall(usec_t n, struct shutdownd_command *c) {
+        char date[FORMAT_TIMESTAMP_MAX];
+        const char *prefix;
+        char *l = NULL;
+
+        assert(c);
+        assert(c->warn_wall);
+
+        if (n >= c->elapse)
+                return;
+
+        if (c->mode == 'H')
+                prefix = "The system is going down for system halt at ";
+        else if (c->mode == 'P')
+                prefix = "The system is going down for power-off at ";
+        else if (c->mode == 'r')
+                prefix = "The system is going down for reboot at ";
+        else
+                assert_not_reached("Unknown mode!");
+
+        if (asprintf(&l, "%s%s%s%s!", c->wall_message, c->wall_message[0] ? "\n" : "",
+                     prefix, format_timestamp(date, sizeof(date), c->elapse)) < 0)
+                log_error("Failed to allocate wall message");
+        else {
+                utmp_wall(l, NULL);
+                free(l);
+        }
+}
+
+static usec_t when_wall(usec_t n, usec_t elapse) {
+
+        static const struct {
+                usec_t delay;
+                usec_t interval;
+        } table[] = {
+                { 10 * USEC_PER_MINUTE, USEC_PER_MINUTE      },
+                { USEC_PER_HOUR,        15 * USEC_PER_MINUTE },
+                { 3 * USEC_PER_HOUR,    30 * USEC_PER_MINUTE }
+        };
+
+        usec_t left, sub;
+        unsigned i;
+
+        /* If the time is already passed, then don't announce */
+        if (n >= elapse)
+                return 0;
+
+        left = elapse - n;
+        for (i = 0; i < ELEMENTSOF(table); i++)
+                if (n + table[i].delay >= elapse) {
+                        sub = ((left / table[i].interval) * table[i].interval);
+                        break;
+                }
+
+        if (i >= ELEMENTSOF(table))
+                sub = ((left / USEC_PER_HOUR) * USEC_PER_HOUR);
+
+        return elapse > sub ? elapse - sub : 1;
+}
+
+static usec_t when_nologin(usec_t elapse) {
+        return elapse > 5*USEC_PER_MINUTE ? elapse - 5*USEC_PER_MINUTE : 1;
+}
+
+int main(int argc, char *argv[]) {
+        enum {
+                FD_SOCKET,
+                FD_WALL_TIMER,
+                FD_NOLOGIN_TIMER,
+                FD_SHUTDOWN_TIMER,
+                _FD_MAX
+        };
+
+        int r = EXIT_FAILURE, n_fds;
+        struct shutdownd_command c;
+        struct pollfd pollfd[_FD_MAX];
+        bool exec_shutdown = false, unlink_nologin = false, failed = false;
+        unsigned i;
+
+        if (getppid() != 1) {
+                log_error("This program should be invoked by init only.");
+                return EXIT_FAILURE;
+        }
+
+        if (argc > 1) {
+                log_error("This program does not take arguments.");
+                return EXIT_FAILURE;
+        }
+
+        log_set_target(LOG_TARGET_AUTO);
+        log_parse_environment();
+        log_open();
+
+        umask(0022);
+
+        if ((n_fds = sd_listen_fds(true)) < 0) {
+                log_error("Failed to read listening file descriptors from environment: %s", strerror(-r));
+                return EXIT_FAILURE;
+        }
+
+        if (n_fds != 1) {
+                log_error("Need exactly one file descriptor.");
+                return EXIT_FAILURE;
+        }
+
+        zero(c);
+        zero(pollfd);
+
+        pollfd[FD_SOCKET].fd = SD_LISTEN_FDS_START;
+        pollfd[FD_SOCKET].events = POLLIN;
+
+        for (i = 0; i < _FD_MAX; i++) {
+
+                if (i == FD_SOCKET)
+                        continue;
+
+                pollfd[i].events = POLLIN;
+
+                if ((pollfd[i].fd = timerfd_create(CLOCK_REALTIME, TFD_NONBLOCK|TFD_CLOEXEC)) < 0) {
+                        log_error("timerfd_create(): %m");
+                        failed = true;
+                }
+        }
+
+        if (failed)
+                goto finish;
+
+        log_debug("systemd-shutdownd running as pid %lu", (unsigned long) getpid());
+
+        sd_notify(false,
+                  "READY=1\n"
+                  "STATUS=Processing requests...");
+
+        do {
+                int k;
+                usec_t n;
+
+                if (poll(pollfd, _FD_MAX, -1) < 0) {
+
+                        if (errno == EAGAIN || errno == EINTR)
+                                continue;
+
+                        log_error("poll(): %m");
+                        goto finish;
+                }
+
+                n = now(CLOCK_REALTIME);
+
+                if (pollfd[FD_SOCKET].revents) {
+
+                        if ((k = read_packet(pollfd[FD_SOCKET].fd, &c)) < 0)
+                                goto finish;
+                        else if (k > 0 && c.elapse > 0) {
+                                struct itimerspec its;
+                                char date[FORMAT_TIMESTAMP_MAX];
+
+                                if (c.warn_wall) {
+                                        /* Send wall messages every so often */
+                                        zero(its);
+                                        timespec_store(&its.it_value, when_wall(n, c.elapse));
+                                        if (timerfd_settime(pollfd[FD_WALL_TIMER].fd, TFD_TIMER_ABSTIME, &its, NULL) < 0) {
+                                                log_error("timerfd_settime(): %m");
+                                                goto finish;
+                                        }
+
+                                        /* Warn immediately if less than 15 minutes are left */
+                                        if (n < c.elapse &&
+                                            n + 15*USEC_PER_MINUTE >= c.elapse)
+                                                warn_wall(n, &c);
+                                }
+
+                                /* Disallow logins 5 minutes prior to shutdown */
+                                zero(its);
+                                timespec_store(&its.it_value, when_nologin(c.elapse));
+                                if (timerfd_settime(pollfd[FD_NOLOGIN_TIMER].fd, TFD_TIMER_ABSTIME, &its, NULL) < 0) {
+                                        log_error("timerfd_settime(): %m");
+                                        goto finish;
+                                }
+
+                                /* Shutdown after the specified time is reached */
+                                zero(its);
+                                timespec_store(&its.it_value, c.elapse);
+                                if (timerfd_settime(pollfd[FD_SHUTDOWN_TIMER].fd, TFD_TIMER_ABSTIME, &its, NULL) < 0) {
+                                        log_error("timerfd_settime(): %m");
+                                        goto finish;
+                                }
+
+                                sd_notifyf(false,
+                                           "STATUS=Shutting down at %s...",
+                                           format_timestamp(date, sizeof(date), c.elapse));
+                        }
+                }
+
+                if (pollfd[FD_WALL_TIMER].revents) {
+                        struct itimerspec its;
+
+                        warn_wall(n, &c);
+                        flush_fd(pollfd[FD_WALL_TIMER].fd);
+
+                        /* Restart timer */
+                        zero(its);
+                        timespec_store(&its.it_value, when_wall(n, c.elapse));
+                        if (timerfd_settime(pollfd[FD_WALL_TIMER].fd, TFD_TIMER_ABSTIME, &its, NULL) < 0) {
+                                log_error("timerfd_settime(): %m");
+                                goto finish;
+                        }
+                }
+
+                if (pollfd[FD_NOLOGIN_TIMER].revents) {
+                        int e;
+
+                        log_info("Creating /run/nologin, blocking further logins...");
+
+                        if ((e = write_one_line_file_atomic("/run/nologin", "System is going down.")) < 0)
+                                log_error("Failed to create /run/nologin: %s", strerror(-e));
+                        else
+                                unlink_nologin = true;
+
+                        flush_fd(pollfd[FD_NOLOGIN_TIMER].fd);
+                }
+
+                if (pollfd[FD_SHUTDOWN_TIMER].revents) {
+                        exec_shutdown = true;
+                        goto finish;
+                }
+
+        } while (c.elapse > 0);
+
+        r = EXIT_SUCCESS;
+
+        log_debug("systemd-shutdownd stopped as pid %lu", (unsigned long) getpid());
+
+finish:
+
+        for (i = 0; i < _FD_MAX; i++)
+                if (pollfd[i].fd >= 0)
+                        close_nointr_nofail(pollfd[i].fd);
+
+        if (unlink_nologin)
+                unlink("/run/nologin");
+
+        if (exec_shutdown && !c.dry_run) {
+                char sw[3];
+
+                sw[0] = '-';
+                sw[1] = c.mode;
+                sw[2] = 0;
+
+                execl(SYSTEMCTL_BINARY_PATH,
+                      "shutdown",
+                      sw,
+                      "now",
+                      (c.warn_wall && c.wall_message[0]) ? c.wall_message :
+                      (c.warn_wall ? NULL : "--no-wall"),
+                      NULL);
+
+                log_error("Failed to execute /sbin/shutdown: %m");
+        }
+
+        sd_notify(false,
+                  "STATUS=Exiting...");
+
+        return r;
+}
diff --git a/src/shutdownd.h b/src/shutdownd.h
new file mode 100644 (file)
index 0000000..4581649
--- /dev/null
@@ -0,0 +1,46 @@
+/*-*- Mode: C; c-basic-offset: 8; indent-tabs-mode: nil -*-*/
+
+#ifndef fooshutdowndhfoo
+#define fooshutdowndhfoo
+
+/***
+  This file is part 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 <http://www.gnu.org/licenses/>.
+***/
+
+#include "util.h"
+#include "macro.h"
+
+/* This is a private message, we don't care much about ABI
+ * stability. */
+
+_packed_ struct shutdownd_command {
+        usec_t elapse;
+        char mode; /* H, P, r, i.e. the switches usually passed to
+                    * shutdown to select whether to halt, power-off or
+                    * reboot the machine */
+        bool dry_run;
+        bool warn_wall;
+
+        /* Yepp, sometimes we are lazy and use fixed-size strings like
+         * this one. Shame on us. But then again, we'd have to
+         * pre-allocate the receive buffer anyway, so there's nothing
+         * too bad here. */
+        char wall_message[4096];
+};
+
+#endif
diff --git a/src/snapshot.c b/src/snapshot.c
new file mode 100644 (file)
index 0000000..82ec510
--- /dev/null
@@ -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 <http://www.gnu.org/licenses/>.
+***/
+
+#include <errno.h>
+
+#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
new file mode 100644 (file)
index 0000000..bf92e99
--- /dev/null
@@ -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 <http://www.gnu.org/licenses/>.
+***/
+
+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-util.c b/src/socket-util.c
new file mode 100644 (file)
index 0000000..acc4d33
--- /dev/null
@@ -0,0 +1,652 @@
+/*-*- 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 <http://www.gnu.org/licenses/>.
+***/
+
+#include <assert.h>
+#include <string.h>
+#include <unistd.h>
+#include <errno.h>
+#include <stdlib.h>
+#include <arpa/inet.h>
+#include <stdio.h>
+#include <net/if.h>
+#include <sys/types.h>
+#include <sys/stat.h>
+#include <stddef.h>
+#include <sys/ioctl.h>
+
+#include "macro.h"
+#include "util.h"
+#include "socket-util.h"
+#include "missing.h"
+#include "label.h"
+
+int socket_address_parse(SocketAddress *a, const char *s) {
+        int r;
+        char *e, *n;
+        unsigned u;
+
+        assert(a);
+        assert(s);
+
+        zero(*a);
+        a->type = SOCK_STREAM;
+
+        if (*s == '[') {
+                /* IPv6 in [x:.....:z]:p notation */
+
+                if (!socket_ipv6_is_supported()) {
+                        log_warning("Binding to IPv6 address not available since kernel does not support IPv6.");
+                        return -EAFNOSUPPORT;
+                }
+
+                if (!(e = strchr(s+1, ']')))
+                        return -EINVAL;
+
+                if (!(n = strndup(s+1, e-s-1)))
+                        return -ENOMEM;
+
+                errno = 0;
+                if (inet_pton(AF_INET6, n, &a->sockaddr.in6.sin6_addr) <= 0) {
+                        free(n);
+                        return errno != 0 ? -errno : -EINVAL;
+                }
+
+                free(n);
+
+                e++;
+                if (*e != ':')
+                        return -EINVAL;
+
+                e++;
+                if ((r = safe_atou(e, &u)) < 0)
+                        return r;
+
+                if (u <= 0 || u > 0xFFFF)
+                        return -EINVAL;
+
+                a->sockaddr.in6.sin6_family = AF_INET6;
+                a->sockaddr.in6.sin6_port = htons((uint16_t) u);
+                a->size = sizeof(struct sockaddr_in6);
+
+        } else if (*s == '/') {
+                /* AF_UNIX socket */
+
+                size_t l;
+
+                l = strlen(s);
+                if (l >= sizeof(a->sockaddr.un.sun_path))
+                        return -EINVAL;
+
+                a->sockaddr.un.sun_family = AF_UNIX;
+                memcpy(a->sockaddr.un.sun_path, s, l);
+                a->size = offsetof(struct sockaddr_un, sun_path) + l + 1;
+
+        } else if (*s == '@') {
+                /* Abstract AF_UNIX socket */
+                size_t l;
+
+                l = strlen(s+1);
+                if (l >= sizeof(a->sockaddr.un.sun_path) - 1)
+                        return -EINVAL;
+
+                a->sockaddr.un.sun_family = AF_UNIX;
+                memcpy(a->sockaddr.un.sun_path+1, s+1, l);
+                a->size = offsetof(struct sockaddr_un, sun_path) + 1 + l;
+
+        } else {
+
+                if ((e = strchr(s, ':'))) {
+
+                        if ((r = safe_atou(e+1, &u)) < 0)
+                                return r;
+
+                        if (u <= 0 || u > 0xFFFF)
+                                return -EINVAL;
+
+                        if (!(n = strndup(s, e-s)))
+                                return -ENOMEM;
+
+                        /* IPv4 in w.x.y.z:p notation? */
+                        if ((r = inet_pton(AF_INET, n, &a->sockaddr.in4.sin_addr)) < 0) {
+                                free(n);
+                                return -errno;
+                        }
+
+                        if (r > 0) {
+                                /* Gotcha, it's a traditional IPv4 address */
+                                free(n);
+
+                                a->sockaddr.in4.sin_family = AF_INET;
+                                a->sockaddr.in4.sin_port = htons((uint16_t) u);
+                                a->size = sizeof(struct sockaddr_in);
+                        } else {
+                                unsigned idx;
+
+                                if (strlen(n) > IF_NAMESIZE-1) {
+                                        free(n);
+                                        return -EINVAL;
+                                }
+
+                                /* Uh, our last resort, an interface name */
+                                idx = if_nametoindex(n);
+                                free(n);
+
+                                if (idx == 0)
+                                        return -EINVAL;
+
+                                if (!socket_ipv6_is_supported()) {
+                                        log_warning("Binding to interface is not available since kernel does not support IPv6.");
+                                        return -EAFNOSUPPORT;
+                                }
+
+                                a->sockaddr.in6.sin6_family = AF_INET6;
+                                a->sockaddr.in6.sin6_port = htons((uint16_t) u);
+                                a->sockaddr.in6.sin6_scope_id = idx;
+                                a->sockaddr.in6.sin6_addr = in6addr_any;
+                                a->size = sizeof(struct sockaddr_in6);
+                        }
+                } else {
+
+                        /* Just a port */
+                        if ((r = safe_atou(s, &u)) < 0)
+                                return r;
+
+                        if (u <= 0 || u > 0xFFFF)
+                                return -EINVAL;
+
+                        if (socket_ipv6_is_supported()) {
+                                a->sockaddr.in6.sin6_family = AF_INET6;
+                                a->sockaddr.in6.sin6_port = htons((uint16_t) u);
+                                a->sockaddr.in6.sin6_addr = in6addr_any;
+                                a->size = sizeof(struct sockaddr_in6);
+                        } else {
+                                a->sockaddr.in4.sin_family = AF_INET;
+                                a->sockaddr.in4.sin_port = htons((uint16_t) u);
+                                a->sockaddr.in4.sin_addr.s_addr = INADDR_ANY;
+                                a->size = sizeof(struct sockaddr_in);
+                        }
+                }
+        }
+
+        return 0;
+}
+
+int socket_address_parse_netlink(SocketAddress *a, const char *s) {
+        int family;
+        unsigned group = 0;
+        char* sfamily = NULL;
+        assert(a);
+        assert(s);
+
+        zero(*a);
+        a->type = SOCK_RAW;
+
+        errno = 0;
+        if (sscanf(s, "%ms %u", &sfamily, &group) < 1)
+                return errno ? -errno : -EINVAL;
+
+        if ((family = netlink_family_from_string(sfamily)) < 0)
+                if (safe_atoi(sfamily, &family) < 0) {
+                        free(sfamily);
+                        return -EINVAL;
+                }
+
+        free(sfamily);
+
+        a->sockaddr.nl.nl_family = AF_NETLINK;
+        a->sockaddr.nl.nl_groups = group;
+
+        a->type = SOCK_RAW;
+        a->size = sizeof(struct sockaddr_nl);
+        a->protocol = family;
+
+        return 0;
+}
+
+int socket_address_verify(const SocketAddress *a) {
+        assert(a);
+
+        switch (socket_address_family(a)) {
+
+        case AF_INET:
+                if (a->size != sizeof(struct sockaddr_in))
+                        return -EINVAL;
+
+                if (a->sockaddr.in4.sin_port == 0)
+                        return -EINVAL;
+
+                if (a->type != SOCK_STREAM && a->type != SOCK_DGRAM)
+                        return -EINVAL;
+
+                return 0;
+
+        case AF_INET6:
+                if (a->size != sizeof(struct sockaddr_in6))
+                        return -EINVAL;
+
+                if (a->sockaddr.in6.sin6_port == 0)
+                        return -EINVAL;
+
+                if (a->type != SOCK_STREAM && a->type != SOCK_DGRAM)
+                        return -EINVAL;
+
+                return 0;
+
+        case AF_UNIX:
+                if (a->size < offsetof(struct sockaddr_un, sun_path))
+                        return -EINVAL;
+
+                if (a->size > offsetof(struct sockaddr_un, sun_path)) {
+
+                        if (a->sockaddr.un.sun_path[0] != 0) {
+                                char *e;
+
+                                /* path */
+                                if (!(e = memchr(a->sockaddr.un.sun_path, 0, sizeof(a->sockaddr.un.sun_path))))
+                                        return -EINVAL;
+
+                                if (a->size != offsetof(struct sockaddr_un, sun_path) + (e - a->sockaddr.un.sun_path) + 1)
+                                        return -EINVAL;
+                        }
+                }
+
+                if (a->type != SOCK_STREAM && a->type != SOCK_DGRAM && a->type != SOCK_SEQPACKET)
+                        return -EINVAL;
+
+                return 0;
+
+        case AF_NETLINK:
+
+                if (a->size != sizeof(struct sockaddr_nl))
+                        return -EINVAL;
+
+                if (a->type != SOCK_RAW && a->type != SOCK_DGRAM)
+                        return -EINVAL;
+
+                return 0;
+
+        default:
+                return -EAFNOSUPPORT;
+        }
+}
+
+int socket_address_print(const SocketAddress *a, char **p) {
+        int r;
+        assert(a);
+        assert(p);
+
+        if ((r = socket_address_verify(a)) < 0)
+                return r;
+
+        switch (socket_address_family(a)) {
+
+        case AF_INET: {
+                char *ret;
+
+                if (!(ret = new(char, INET_ADDRSTRLEN+1+5+1)))
+                        return -ENOMEM;
+
+                if (!inet_ntop(AF_INET, &a->sockaddr.in4.sin_addr, ret, INET_ADDRSTRLEN)) {
+                        free(ret);
+                        return -errno;
+                }
+
+                sprintf(strchr(ret, 0), ":%u", ntohs(a->sockaddr.in4.sin_port));
+                *p = ret;
+                return 0;
+        }
+
+        case AF_INET6: {
+                char *ret;
+
+                if (!(ret = new(char, 1+INET6_ADDRSTRLEN+2+5+1)))
+                        return -ENOMEM;
+
+                ret[0] = '[';
+                if (!inet_ntop(AF_INET6, &a->sockaddr.in6.sin6_addr, ret+1, INET6_ADDRSTRLEN)) {
+                        free(ret);
+                        return -errno;
+                }
+
+                sprintf(strchr(ret, 0), "]:%u", ntohs(a->sockaddr.in6.sin6_port));
+                *p = ret;
+                return 0;
+        }
+
+        case AF_UNIX: {
+                char *ret;
+
+                if (a->size <= offsetof(struct sockaddr_un, sun_path)) {
+
+                        if (!(ret = strdup("<unnamed>")))
+                                return -ENOMEM;
+
+                } else if (a->sockaddr.un.sun_path[0] == 0) {
+                        /* abstract */
+
+                        /* FIXME: We assume we can print the
+                         * socket path here and that it hasn't
+                         * more than one NUL byte. That is
+                         * actually an invalid assumption */
+
+                        if (!(ret = new(char, sizeof(a->sockaddr.un.sun_path)+1)))
+                                return -ENOMEM;
+
+                        ret[0] = '@';
+                        memcpy(ret+1, a->sockaddr.un.sun_path+1, sizeof(a->sockaddr.un.sun_path)-1);
+                        ret[sizeof(a->sockaddr.un.sun_path)] = 0;
+
+                } else {
+
+                        if (!(ret = strdup(a->sockaddr.un.sun_path)))
+                                return -ENOMEM;
+                }
+
+                *p = ret;
+                return 0;
+        }
+
+        case AF_NETLINK: {
+                const char *sfamily;
+
+                if ((sfamily = netlink_family_to_string(a->protocol)))
+                        r = asprintf(p, "%s %u", sfamily, a->sockaddr.nl.nl_groups);
+                else
+                        r = asprintf(p, "%i %u", a->protocol, a->sockaddr.nl.nl_groups);
+
+                if (r < 0)
+                        return -ENOMEM;
+
+                return 0;
+        }
+
+        default:
+                return -EINVAL;
+        }
+}
+
+int socket_address_listen(
+                const SocketAddress *a,
+                int backlog,
+                SocketAddressBindIPv6Only only,
+                const char *bind_to_device,
+                bool free_bind,
+                bool transparent,
+                mode_t directory_mode,
+                mode_t socket_mode,
+                const char *label,
+                int *ret) {
+
+        int r, fd, one;
+        assert(a);
+        assert(ret);
+
+        if ((r = socket_address_verify(a)) < 0)
+                return r;
+
+        if (socket_address_family(a) == AF_INET6 && !socket_ipv6_is_supported())
+                return -EAFNOSUPPORT;
+
+        r = label_socket_set(label);
+        if (r < 0)
+                return r;
+
+        fd = socket(socket_address_family(a), a->type | SOCK_NONBLOCK | SOCK_CLOEXEC, a->protocol);
+        r = fd < 0 ? -errno : 0;
+
+        label_socket_clear();
+
+        if (r < 0)
+                return r;
+
+        if (socket_address_family(a) == AF_INET6 && only != SOCKET_ADDRESS_DEFAULT) {
+                int flag = only == SOCKET_ADDRESS_IPV6_ONLY;
+
+                if (setsockopt(fd, IPPROTO_IPV6, IPV6_V6ONLY, &flag, sizeof(flag)) < 0)
+                        goto fail;
+        }
+
+        if (socket_address_family(a) == AF_INET || socket_address_family(a) == AF_INET6) {
+                if (bind_to_device)
+                        if (setsockopt(fd, SOL_SOCKET, SO_BINDTODEVICE, bind_to_device, strlen(bind_to_device)+1) < 0)
+                                goto fail;
+
+                if (free_bind) {
+                        one = 1;
+                        if (setsockopt(fd, IPPROTO_IP, IP_FREEBIND, &one, sizeof(one)) < 0)
+                                log_warning("IP_FREEBIND failed: %m");
+                }
+
+                if (transparent) {
+                        one = 1;
+                        if (setsockopt(fd, IPPROTO_IP, IP_TRANSPARENT, &one, sizeof(one)) < 0)
+                                log_warning("IP_TRANSPARENT failed: %m");
+                }
+        }
+
+        one = 1;
+        if (setsockopt(fd, SOL_SOCKET, SO_REUSEADDR, &one, sizeof(one)) < 0)
+                goto fail;
+
+        if (socket_address_family(a) == AF_UNIX && a->sockaddr.un.sun_path[0] != 0) {
+                mode_t old_mask;
+
+                /* Create parents */
+                mkdir_parents(a->sockaddr.un.sun_path, directory_mode);
+
+                /* Enforce the right access mode for the socket*/
+                old_mask = umask(~ socket_mode);
+
+                /* Include the original umask in our mask */
+                umask(~socket_mode | old_mask);
+
+                r = label_bind(fd, &a->sockaddr.sa, a->size);
+
+                if (r < 0 && errno == EADDRINUSE) {
+                        /* Unlink and try again */
+                        unlink(a->sockaddr.un.sun_path);
+                        r = bind(fd, &a->sockaddr.sa, a->size);
+                }
+
+                umask(old_mask);
+        } else
+                r = bind(fd, &a->sockaddr.sa, a->size);
+
+        if (r < 0)
+                goto fail;
+
+        if (socket_address_can_accept(a))
+                if (listen(fd, backlog) < 0)
+                        goto fail;
+
+        *ret = fd;
+        return 0;
+
+fail:
+        r = -errno;
+        close_nointr_nofail(fd);
+        return r;
+}
+
+bool socket_address_can_accept(const SocketAddress *a) {
+        assert(a);
+
+        return
+                a->type == SOCK_STREAM ||
+                a->type == SOCK_SEQPACKET;
+}
+
+bool socket_address_equal(const SocketAddress *a, const SocketAddress *b) {
+        assert(a);
+        assert(b);
+
+        /* Invalid addresses are unequal to all */
+        if (socket_address_verify(a) < 0 ||
+            socket_address_verify(b) < 0)
+                return false;
+
+        if (a->type != b->type)
+                return false;
+
+        if (a->size != b->size)
+                return false;
+
+        if (socket_address_family(a) != socket_address_family(b))
+                return false;
+
+        switch (socket_address_family(a)) {
+
+        case AF_INET:
+                if (a->sockaddr.in4.sin_addr.s_addr != b->sockaddr.in4.sin_addr.s_addr)
+                        return false;
+
+                if (a->sockaddr.in4.sin_port != b->sockaddr.in4.sin_port)
+                        return false;
+
+                break;
+
+        case AF_INET6:
+                if (memcmp(&a->sockaddr.in6.sin6_addr, &b->sockaddr.in6.sin6_addr, sizeof(a->sockaddr.in6.sin6_addr)) != 0)
+                        return false;
+
+                if (a->sockaddr.in6.sin6_port != b->sockaddr.in6.sin6_port)
+                        return false;
+
+                break;
+
+        case AF_UNIX:
+
+                if ((a->sockaddr.un.sun_path[0] == 0) != (b->sockaddr.un.sun_path[0] == 0))
+                        return false;
+
+                if (a->sockaddr.un.sun_path[0]) {
+                        if (strncmp(a->sockaddr.un.sun_path, b->sockaddr.un.sun_path, sizeof(a->sockaddr.un.sun_path)) != 0)
+                                return false;
+                } else {
+                        if (memcmp(a->sockaddr.un.sun_path, b->sockaddr.un.sun_path, a->size) != 0)
+                                return false;
+                }
+
+                break;
+
+        case AF_NETLINK:
+
+                if (a->protocol != b->protocol)
+                        return false;
+
+                if (a->sockaddr.nl.nl_groups != b->sockaddr.nl.nl_groups)
+                        return false;
+
+                break;
+
+        default:
+                /* Cannot compare, so we assume the addresses are different */
+                return false;
+        }
+
+        return true;
+}
+
+bool socket_address_is(const SocketAddress *a, const char *s, int type) {
+        struct SocketAddress b;
+
+        assert(a);
+        assert(s);
+
+        if (socket_address_parse(&b, s) < 0)
+                return false;
+
+        b.type = type;
+
+        return socket_address_equal(a, &b);
+}
+
+bool socket_address_is_netlink(const SocketAddress *a, const char *s) {
+        struct SocketAddress b;
+
+        assert(a);
+        assert(s);
+
+        if (socket_address_parse_netlink(&b, s) < 0)
+                return false;
+
+        return socket_address_equal(a, &b);
+}
+
+bool socket_address_needs_mount(const SocketAddress *a, const char *prefix) {
+        assert(a);
+
+        if (socket_address_family(a) != AF_UNIX)
+                return false;
+
+        if (a->sockaddr.un.sun_path[0] == 0)
+                return false;
+
+        return path_startswith(a->sockaddr.un.sun_path, prefix);
+}
+
+bool socket_ipv6_is_supported(void) {
+        char *l = 0;
+        bool enabled;
+
+        if (access("/sys/module/ipv6", F_OK) != 0)
+                return 0;
+
+        /* If we can't check "disable" parameter, assume enabled */
+        if (read_one_line_file("/sys/module/ipv6/parameters/disable", &l) < 0)
+                return 1;
+
+        /* If module was loaded with disable=1 no IPv6 available */
+        enabled = l[0] == '0';
+        free(l);
+
+        return enabled;
+}
+
+static const char* const netlink_family_table[] = {
+        [NETLINK_ROUTE] = "route",
+        [NETLINK_FIREWALL] = "firewall",
+        [NETLINK_INET_DIAG] = "inet-diag",
+        [NETLINK_NFLOG] = "nflog",
+        [NETLINK_XFRM] = "xfrm",
+        [NETLINK_SELINUX] = "selinux",
+        [NETLINK_ISCSI] = "iscsi",
+        [NETLINK_AUDIT] = "audit",
+        [NETLINK_FIB_LOOKUP] = "fib-lookup",
+        [NETLINK_CONNECTOR] = "connector",
+        [NETLINK_NETFILTER] = "netfilter",
+        [NETLINK_IP6_FW] = "ip6-fw",
+        [NETLINK_DNRTMSG] = "dnrtmsg",
+        [NETLINK_KOBJECT_UEVENT] = "kobject-uevent",
+        [NETLINK_GENERIC] = "generic",
+        [NETLINK_SCSITRANSPORT] = "scsitransport",
+        [NETLINK_ECRYPTFS] = "ecryptfs"
+};
+
+DEFINE_STRING_TABLE_LOOKUP(netlink_family, int);
+
+static const char* const socket_address_bind_ipv6_only_table[_SOCKET_ADDRESS_BIND_IPV6_ONLY_MAX] = {
+        [SOCKET_ADDRESS_DEFAULT] = "default",
+        [SOCKET_ADDRESS_BOTH] = "both",
+        [SOCKET_ADDRESS_IPV6_ONLY] = "ipv6-only"
+};
+
+DEFINE_STRING_TABLE_LOOKUP(socket_address_bind_ipv6_only, SocketAddressBindIPv6Only);
diff --git a/src/socket-util.h b/src/socket-util.h
new file mode 100644 (file)
index 0000000..8ccbd37
--- /dev/null
@@ -0,0 +1,102 @@
+/*-*- Mode: C; c-basic-offset: 8; indent-tabs-mode: nil -*-*/
+
+#ifndef foosocketutilhfoo
+#define foosocketutilhfoo
+
+/***
+  This file is part 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 <http://www.gnu.org/licenses/>.
+***/
+
+#include <sys/socket.h>
+#include <netinet/in.h>
+#include <sys/un.h>
+#include <net/if.h>
+#include <asm/types.h>
+#include <linux/netlink.h>
+
+#include "macro.h"
+#include "util.h"
+
+union sockaddr_union {
+        struct sockaddr sa;
+        struct sockaddr_in in4;
+        struct sockaddr_in6 in6;
+        struct sockaddr_un un;
+        struct sockaddr_nl nl;
+        struct sockaddr_storage storage;
+};
+
+typedef struct SocketAddress {
+        union sockaddr_union sockaddr;
+
+        /* We store the size here explicitly due to the weird
+         * sockaddr_un semantics for abstract sockets */
+        socklen_t size;
+
+        /* Socket type, i.e. SOCK_STREAM, SOCK_DGRAM, ... */
+        int type;
+
+        /* Socket protocol, IPPROTO_xxx, usually 0, except for netlink */
+        int protocol;
+} SocketAddress;
+
+typedef enum SocketAddressBindIPv6Only {
+        SOCKET_ADDRESS_DEFAULT,
+        SOCKET_ADDRESS_BOTH,
+        SOCKET_ADDRESS_IPV6_ONLY,
+        _SOCKET_ADDRESS_BIND_IPV6_ONLY_MAX,
+        _SOCKET_ADDRESS_BIND_IPV6_ONLY_INVALID = -1
+} SocketAddressBindIPv6Only;
+
+#define socket_address_family(a) ((a)->sockaddr.sa.sa_family)
+
+int socket_address_parse(SocketAddress *a, const char *s);
+int socket_address_parse_netlink(SocketAddress *a, const char *s);
+int socket_address_print(const SocketAddress *a, char **p);
+int socket_address_verify(const SocketAddress *a);
+
+bool socket_address_can_accept(const SocketAddress *a);
+
+int socket_address_listen(
+                const SocketAddress *a,
+                int backlog,
+                SocketAddressBindIPv6Only only,
+                const char *bind_to_device,
+                bool free_bind,
+                bool transparent,
+                mode_t directory_mode,
+                mode_t socket_mode,
+                const char *label,
+                int *ret);
+
+bool socket_address_is(const SocketAddress *a, const char *s, int type);
+bool socket_address_is_netlink(const SocketAddress *a, const char *s);
+
+bool socket_address_equal(const SocketAddress *a, const SocketAddress *b);
+
+bool socket_address_needs_mount(const SocketAddress *a, const char *prefix);
+
+const char* socket_address_bind_ipv6_only_to_string(SocketAddressBindIPv6Only b);
+SocketAddressBindIPv6Only socket_address_bind_ipv6_only_from_string(const char *s);
+
+const char* netlink_family_to_string(int b);
+int netlink_family_from_string(const char *s);
+
+bool socket_ipv6_is_supported(void);
+
+#endif
diff --git a/src/socket.c b/src/socket.c
new file mode 100644 (file)
index 0000000..bb75d96
--- /dev/null
@@ -0,0 +1,2217 @@
+/*-*- 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 <http://www.gnu.org/licenses/>.
+***/
+
+#include <sys/types.h>
+#include <sys/stat.h>
+#include <unistd.h>
+#include <errno.h>
+#include <fcntl.h>
+#include <sys/epoll.h>
+#include <signal.h>
+#include <arpa/inet.h>
+#include <mqueue.h>
+
+#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 "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
new file mode 100644 (file)
index 0000000..6470d8b
--- /dev/null
@@ -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 <http://www.gnu.org/licenses/>.
+***/
+
+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.c b/src/spawn-agent.c
new file mode 100644 (file)
index 0000000..2de2530
--- /dev/null
@@ -0,0 +1,120 @@
+/*-*- 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 <http://www.gnu.org/licenses/>.
+***/
+
+#include <sys/types.h>
+#include <stdlib.h>
+#include <unistd.h>
+#include <string.h>
+#include <sys/prctl.h>
+#include <signal.h>
+#include <fcntl.h>
+
+#include "log.h"
+#include "util.h"
+#include "spawn-agent.h"
+
+static pid_t agent_pid = 0;
+
+void agent_open(void) {
+        pid_t parent_pid;
+
+        if (agent_pid > 0)
+                return;
+
+        /* We check STDIN here, not STDOUT, since this is about input,
+         * not output */
+        if (!isatty(STDIN_FILENO))
+                return;
+
+        parent_pid = getpid();
+
+        /* Spawns a temporary TTY agent, making sure it goes away when
+         * we go away */
+
+        agent_pid = fork();
+        if (agent_pid < 0) {
+                log_error("Failed to fork agent: %m");
+                return;
+        }
+
+        if (agent_pid == 0) {
+                /* In the child */
+
+                int fd;
+                bool stdout_is_tty, stderr_is_tty;
+
+                /* Make sure the agent goes away when the parent dies */
+                if (prctl(PR_SET_PDEATHSIG, SIGTERM) < 0)
+                        _exit(EXIT_FAILURE);
+
+                /* Check whether our parent died before we were able
+                 * to set the death signal */
+                if (getppid() != parent_pid)
+                        _exit(EXIT_SUCCESS);
+
+                /* Don't leak fds to the agent */
+                close_all_fds(NULL, 0);
+
+                stdout_is_tty = isatty(STDOUT_FILENO);
+                stderr_is_tty = isatty(STDERR_FILENO);
+
+                if (!stdout_is_tty || !stderr_is_tty) {
+                        /* Detach from stdout/stderr. and reopen
+                         * /dev/tty for them. This is important to
+                         * ensure that when systemctl is started via
+                         * popen() or a similar call that expects to
+                         * read EOF we actually do generate EOF and
+                         * not delay this indefinitely by because we
+                         * keep an unused copy of stdin around. */
+                        fd = open("/dev/tty", O_WRONLY);
+                        if (fd < 0) {
+                                log_error("Failed to open /dev/tty: %m");
+                                _exit(EXIT_FAILURE);
+                        }
+
+                        if (!stdout_is_tty)
+                                dup2(fd, STDOUT_FILENO);
+
+                        if (!stderr_is_tty)
+                                dup2(fd, STDERR_FILENO);
+
+                        if (fd > 2)
+                                close(fd);
+                }
+
+                execl(SYSTEMD_TTY_ASK_PASSWORD_AGENT_BINARY_PATH, SYSTEMD_TTY_ASK_PASSWORD_AGENT_BINARY_PATH, "--watch", NULL);
+
+                log_error("Unable to execute agent: %m");
+                _exit(EXIT_FAILURE);
+        }
+}
+
+void agent_close(void) {
+
+        if (agent_pid <= 0)
+                return;
+
+        /* Inform agent that we are done */
+        kill(agent_pid, SIGTERM);
+        kill(agent_pid, SIGCONT);
+        wait_for_terminate(agent_pid, NULL);
+        agent_pid = 0;
+}
diff --git a/src/spawn-agent.h b/src/spawn-agent.h
new file mode 100644 (file)
index 0000000..fd0a910
--- /dev/null
@@ -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 <http://www.gnu.org/licenses/>.
+***/
+
+void agent_open(void);
+void agent_close(void);
+
+#endif
diff --git a/src/special.h b/src/special.h
new file mode 100644 (file)
index 0000000..8185eaf
--- /dev/null
@@ -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 <http://www.gnu.org/licenses/>.
+***/
+
+#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; Should pull in syslog.socket or syslog.service */
+#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/specifier.c b/src/specifier.c
new file mode 100644 (file)
index 0000000..a9fff88
--- /dev/null
@@ -0,0 +1,108 @@
+/*-*- 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 <http://www.gnu.org/licenses/>.
+***/
+
+#include <string.h>
+
+#include "macro.h"
+#include "util.h"
+#include "specifier.h"
+
+/*
+ * Generic infrastructure for replacing %x style specifiers in
+ * strings. Will call a callback for each replacement.
+ *
+ */
+
+char *specifier_printf(const char *text, const Specifier table[], void *userdata) {
+        char *r, *t;
+        const char *f;
+        bool percent = false;
+        size_t l;
+
+        assert(text);
+        assert(table);
+
+        l = strlen(text);
+        if (!(r = new(char, l+1)))
+                return NULL;
+
+        t = r;
+
+        for (f = text; *f; f++, l--) {
+
+                if (percent) {
+                        if (*f == '%')
+                                *(t++) = '%';
+                        else {
+                                const Specifier *i;
+
+                                for (i = table; i->specifier; i++)
+                                        if (i->specifier == *f)
+                                                break;
+
+                                if (i->lookup) {
+                                        char *n, *w;
+                                        size_t k, j;
+
+                                        if (!(w = i->lookup(i->specifier, i->data, userdata))) {
+                                                free(r);
+                                                return NULL;
+                                        }
+
+                                        j = t - r;
+                                        k = strlen(w);
+
+                                        if (!(n = new(char, j + k + l + 1))) {
+                                                free(r);
+                                                free(w);
+                                                return NULL;
+                                        }
+
+                                        memcpy(n, r, j);
+                                        memcpy(n + j, w, k);
+
+                                        free(r);
+                                        free(w);
+
+                                        r = n;
+                                        t = n + j + k;
+                                } else {
+                                        *(t++) = '%';
+                                        *(t++) = *f;
+                                }
+                        }
+
+                        percent = false;
+                } else if (*f == '%')
+                        percent = true;
+                else
+                        *(t++) = *f;
+        }
+
+        *t = 0;
+        return r;
+}
+
+/* Generic handler for simple string replacements */
+
+char* specifier_string(char specifier, void *data, void *userdata) {
+        return strdup(strempty(data));
+}
diff --git a/src/specifier.h b/src/specifier.h
new file mode 100644 (file)
index 0000000..041166c
--- /dev/null
@@ -0,0 +1,37 @@
+/*-*- Mode: C; c-basic-offset: 8; indent-tabs-mode: nil -*-*/
+
+#ifndef foospecifierhfoo
+#define foospecifierhfoo
+
+/***
+  This file is part 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 <http://www.gnu.org/licenses/>.
+***/
+
+typedef char* (*SpecifierCallback)(char specifier, void *data, void *userdata);
+
+typedef struct Specifier {
+        const char specifier;
+        const SpecifierCallback lookup;
+        void *data;
+} Specifier;
+
+char *specifier_printf(const char *text, const Specifier table[], void *userdata);
+
+char* specifier_string(char specifier, void *data, void *userdata);
+
+#endif
diff --git a/src/strv.c b/src/strv.c
new file mode 100644 (file)
index 0000000..bb309d9
--- /dev/null
@@ -0,0 +1,690 @@
+/*-*- 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 <http://www.gnu.org/licenses/>.
+***/
+
+#include <assert.h>
+#include <stdlib.h>
+#include <stdarg.h>
+#include <string.h>
+#include <errno.h>
+
+#include "util.h"
+#include "strv.h"
+
+char *strv_find(char **l, const char *name) {
+        char **i;
+
+        assert(name);
+
+        STRV_FOREACH(i, l)
+                if (streq(*i, name))
+                        return *i;
+
+        return NULL;
+}
+
+char *strv_find_prefix(char **l, const char *name) {
+        char **i;
+
+        assert(name);
+
+        STRV_FOREACH(i, l)
+                if (startswith(*i, name))
+                        return *i;
+
+        return NULL;
+}
+
+void strv_free(char **l) {
+        char **k;
+
+        if (!l)
+                return;
+
+        for (k = l; *k; k++)
+                free(*k);
+
+        free(l);
+}
+
+char **strv_copy(char **l) {
+        char **r, **k;
+
+        k = r = new(char*, strv_length(l)+1);
+        if (!k)
+                return NULL;
+
+        if (l)
+                for (; *l; k++, l++)
+                        if (!(*k = strdup(*l)))
+                                goto fail;
+
+        *k = NULL;
+        return r;
+
+fail:
+        for (k--; k >= r; k--)
+                free(*k);
+
+        free(r);
+
+        return NULL;
+}
+
+unsigned strv_length(char **l) {
+        unsigned n = 0;
+
+        if (!l)
+                return 0;
+
+        for (; *l; l++)
+                n++;
+
+        return n;
+}
+
+char **strv_new_ap(const char *x, va_list ap) {
+        const char *s;
+        char **a;
+        unsigned n = 0, i = 0;
+        va_list aq;
+
+        if (x) {
+                n = 1;
+
+                va_copy(aq, ap);
+                while (va_arg(aq, const char*))
+                        n++;
+                va_end(aq);
+        }
+
+        if (!(a = new(char*, n+1)))
+                return NULL;
+
+        if (x) {
+                if (!(a[i] = strdup(x))) {
+                        free(a);
+                        return NULL;
+                }
+
+                i++;
+
+                while ((s = va_arg(ap, const char*))) {
+                        if (!(a[i] = strdup(s)))
+                                goto fail;
+
+                        i++;
+                }
+        }
+
+        a[i] = NULL;
+
+        return a;
+
+fail:
+
+        for (; i > 0; i--)
+                if (a[i-1])
+                        free(a[i-1]);
+
+        free(a);
+
+        return NULL;
+}
+
+char **strv_new(const char *x, ...) {
+        char **r;
+        va_list ap;
+
+        va_start(ap, x);
+        r = strv_new_ap(x, ap);
+        va_end(ap);
+
+        return r;
+}
+
+char **strv_merge(char **a, char **b) {
+        char **r, **k;
+
+        if (!a)
+                return strv_copy(b);
+
+        if (!b)
+                return strv_copy(a);
+
+        if (!(r = new(char*, strv_length(a)+strv_length(b)+1)))
+                return NULL;
+
+        for (k = r; *a; k++, a++)
+                if (!(*k = strdup(*a)))
+                        goto fail;
+        for (; *b; k++, b++)
+                if (!(*k = strdup(*b)))
+                        goto fail;
+
+        *k = NULL;
+        return r;
+
+fail:
+        for (k--; k >= r; k--)
+                free(*k);
+
+        free(r);
+
+        return NULL;
+}
+
+char **strv_merge_concat(char **a, char **b, const char *suffix) {
+        char **r, **k;
+
+        /* Like strv_merge(), but appends suffix to all strings in b, before adding */
+
+        if (!b)
+                return strv_copy(a);
+
+        r = new(char*, strv_length(a) + strv_length(b) + 1);
+        if (!r)
+                return NULL;
+
+        k = r;
+        if (a)
+                for (; *a; k++, a++) {
+                        *k = strdup(*a);
+                        if (!*k)
+                                goto fail;
+                }
+
+        for (; *b; k++, b++) {
+                *k = strappend(*b, suffix);
+                if (!*k)
+                        goto fail;
+        }
+
+        *k = NULL;
+        return r;
+
+fail:
+        for (k--; k >= r; k--)
+                free(*k);
+
+        free(r);
+
+        return NULL;
+
+}
+
+char **strv_split(const char *s, const char *separator) {
+        char *state;
+        char *w;
+        size_t l;
+        unsigned n, i;
+        char **r;
+
+        assert(s);
+
+        n = 0;
+        FOREACH_WORD_SEPARATOR(w, l, s, separator, state)
+                n++;
+
+        if (!(r = new(char*, n+1)))
+                return NULL;
+
+        i = 0;
+        FOREACH_WORD_SEPARATOR(w, l, s, separator, state)
+                if (!(r[i++] = strndup(w, l))) {
+                        strv_free(r);
+                        return NULL;
+                }
+
+        r[i] = NULL;
+        return r;
+}
+
+char **strv_split_quoted(const char *s) {
+        char *state;
+        char *w;
+        size_t l;
+        unsigned n, i;
+        char **r;
+
+        assert(s);
+
+        n = 0;
+        FOREACH_WORD_QUOTED(w, l, s, state)
+                n++;
+
+        if (!(r = new(char*, n+1)))
+                return NULL;
+
+        i = 0;
+        FOREACH_WORD_QUOTED(w, l, s, state)
+                if (!(r[i++] = cunescape_length(w, l))) {
+                        strv_free(r);
+                        return NULL;
+                }
+
+        r[i] = NULL;
+        return r;
+}
+
+char *strv_join(char **l, const char *separator) {
+        char *r, *e;
+        char **s;
+        size_t n, k;
+
+        if (!separator)
+                separator = " ";
+
+        k = strlen(separator);
+
+        n = 0;
+        STRV_FOREACH(s, l) {
+                if (n != 0)
+                        n += k;
+                n += strlen(*s);
+        }
+
+        if (!(r = new(char, n+1)))
+                return NULL;
+
+        e = r;
+        STRV_FOREACH(s, l) {
+                if (e != r)
+                        e = stpcpy(e, separator);
+
+                e = stpcpy(e, *s);
+        }
+
+        *e = 0;
+
+        return r;
+}
+
+char **strv_append(char **l, const char *s) {
+        char **r, **k;
+
+        if (!l)
+                return strv_new(s, NULL);
+
+        if (!s)
+                return strv_copy(l);
+
+        r = new(char*, strv_length(l)+2);
+        if (!r)
+                return NULL;
+
+        for (k = r; *l; k++, l++)
+                if (!(*k = strdup(*l)))
+                        goto fail;
+
+        if (!(*(k++) = strdup(s)))
+                goto fail;
+
+        *k = NULL;
+        return r;
+
+fail:
+        for (k--; k >= r; k--)
+                free(*k);
+
+        free(r);
+
+        return NULL;
+}
+
+char **strv_uniq(char **l) {
+        char **i;
+
+        /* Drops duplicate entries. The first identical string will be
+         * kept, the others dropped */
+
+        STRV_FOREACH(i, l)
+                strv_remove(i+1, *i);
+
+        return l;
+}
+
+char **strv_remove(char **l, const char *s) {
+        char **f, **t;
+
+        if (!l)
+                return NULL;
+
+        assert(s);
+
+        /* Drops every occurrence of s in the string list, edits
+         * in-place. */
+
+        for (f = t = l; *f; f++) {
+
+                if (streq(*f, s)) {
+                        free(*f);
+                        continue;
+                }
+
+                *(t++) = *f;
+        }
+
+        *t = NULL;
+        return l;
+}
+
+static int env_append(char **r, char ***k, char **a) {
+        assert(r);
+        assert(k);
+
+        if (!a)
+                return 0;
+
+        /* Add the entries of a to *k unless they already exist in *r
+         * in which case they are overridden instead. This assumes
+         * there is enough space in the r array. */
+
+        for (; *a; a++) {
+                char **j;
+                size_t n;
+
+                n = strcspn(*a, "=");
+
+                if ((*a)[n] == '=')
+                        n++;
+
+                for (j = r; j < *k; j++)
+                        if (strncmp(*j, *a, n) == 0)
+                                break;
+
+                if (j >= *k)
+                        (*k)++;
+                else
+                        free(*j);
+
+                if (!(*j = strdup(*a)))
+                        return -ENOMEM;
+        }
+
+        return 0;
+}
+
+char **strv_env_merge(unsigned n_lists, ...) {
+        size_t n = 0;
+        char **l, **k, **r;
+        va_list ap;
+        unsigned i;
+
+        /* Merges an arbitrary number of environment sets */
+
+        va_start(ap, n_lists);
+        for (i = 0; i < n_lists; i++) {
+                l = va_arg(ap, char**);
+                n += strv_length(l);
+        }
+        va_end(ap);
+
+        if (!(r = new(char*, n+1)))
+                return NULL;
+
+        k = r;
+
+        va_start(ap, n_lists);
+        for (i = 0; i < n_lists; i++) {
+                l = va_arg(ap, char**);
+                if (env_append(r, &k, l) < 0)
+                        goto fail;
+        }
+        va_end(ap);
+
+        *k = NULL;
+
+        return r;
+
+fail:
+        va_end(ap);
+
+        for (k--; k >= r; k--)
+                free(*k);
+
+        free(r);
+
+        return NULL;
+}
+
+static bool env_match(const char *t, const char *pattern) {
+        assert(t);
+        assert(pattern);
+
+        /* pattern a matches string a
+         *         a matches a=
+         *         a matches a=b
+         *         a= matches a=
+         *         a=b matches a=b
+         *         a= does not match a
+         *         a=b does not match a=
+         *         a=b does not match a
+         *         a=b does not match a=c */
+
+        if (streq(t, pattern))
+                return true;
+
+        if (!strchr(pattern, '=')) {
+                size_t l = strlen(pattern);
+
+                return strncmp(t, pattern, l) == 0 && t[l] == '=';
+        }
+
+        return false;
+}
+
+char **strv_env_delete(char **x, unsigned n_lists, ...) {
+        size_t n, i = 0;
+        char **k, **r;
+        va_list ap;
+
+        /* Deletes every entry from x that is mentioned in the other
+         * string lists */
+
+        n = strv_length(x);
+
+        r = new(char*, n+1);
+        if (!r)
+                return NULL;
+
+        STRV_FOREACH(k, x) {
+                unsigned v;
+
+                va_start(ap, n_lists);
+                for (v = 0; v < n_lists; v++) {
+                        char **l, **j;
+
+                        l = va_arg(ap, char**);
+                        STRV_FOREACH(j, l)
+                                if (env_match(*k, *j))
+                                        goto skip;
+                }
+                va_end(ap);
+
+                r[i] = strdup(*k);
+                if (!r[i]) {
+                        strv_free(r);
+                        return NULL;
+                }
+
+                i++;
+                continue;
+
+        skip:
+                va_end(ap);
+        }
+
+        r[i] = NULL;
+
+        assert(i <= n);
+
+        return r;
+}
+
+char **strv_env_unset(char **l, const char *p) {
+
+        char **f, **t;
+
+        if (!l)
+                return NULL;
+
+        assert(p);
+
+        /* Drops every occurrence of the env var setting p in the
+         * string list. edits in-place. */
+
+        for (f = t = l; *f; f++) {
+
+                if (env_match(*f, p)) {
+                        free(*f);
+                        continue;
+                }
+
+                *(t++) = *f;
+        }
+
+        *t = NULL;
+        return l;
+}
+
+char **strv_env_set(char **x, const char *p) {
+
+        char **k, **r;
+        char* m[2] = { (char*) p, NULL };
+
+        /* Overrides the env var setting of p, returns a new copy */
+
+        if (!(r = new(char*, strv_length(x)+2)))
+                return NULL;
+
+        k = r;
+        if (env_append(r, &k, x) < 0)
+                goto fail;
+
+        if (env_append(r, &k, m) < 0)
+                goto fail;
+
+        *k = NULL;
+
+        return r;
+
+fail:
+        for (k--; k >= r; k--)
+                free(*k);
+
+        free(r);
+
+        return NULL;
+
+}
+
+char *strv_env_get_with_length(char **l, const char *name, size_t k) {
+        char **i;
+
+        assert(name);
+
+        STRV_FOREACH(i, l)
+                if (strncmp(*i, name, k) == 0 &&
+                    (*i)[k] == '=')
+                        return *i + k + 1;
+
+        return NULL;
+}
+
+char *strv_env_get(char **l, const char *name) {
+        return strv_env_get_with_length(l, name, strlen(name));
+}
+
+char **strv_env_clean(char **l) {
+        char **r, **ret;
+
+        for (r = ret = l; *l; l++) {
+                const char *equal;
+
+                equal = strchr(*l, '=');
+
+                if (equal && equal[1] == 0) {
+                        free(*l);
+                        continue;
+                }
+
+                *(r++) = *l;
+        }
+
+        *r = NULL;
+
+        return ret;
+}
+
+char **strv_parse_nulstr(const char *s, size_t l) {
+        const char *p;
+        unsigned c = 0, i = 0;
+        char **v;
+
+        assert(s || l <= 0);
+
+        if (l <= 0)
+                return strv_new(NULL, NULL);
+
+        for (p = s; p < s + l; p++)
+                if (*p == 0)
+                        c++;
+
+        if (s[l-1] != 0)
+                c++;
+
+        if (!(v = new0(char*, c+1)))
+                return NULL;
+
+        p = s;
+        while (p < s + l) {
+                const char *e;
+
+                e = memchr(p, 0, s + l - p);
+
+                if (!(v[i++] = strndup(p, e ? e - p : s + l - p))) {
+                        strv_free(v);
+                        return NULL;
+                }
+
+                if (!e)
+                        break;
+
+                p = e + 1;
+        }
+
+        assert(i == c);
+
+        return v;
+}
+
+bool strv_overlap(char **a, char **b) {
+        char **i, **j;
+
+        STRV_FOREACH(i, a) {
+                STRV_FOREACH(j, b) {
+                        if (streq(*i, *j))
+                                return true;
+                }
+        }
+
+        return false;
+}
diff --git a/src/strv.h b/src/strv.h
new file mode 100644 (file)
index 0000000..d038c9f
--- /dev/null
@@ -0,0 +1,79 @@
+/*-*- Mode: C; c-basic-offset: 8; indent-tabs-mode: nil -*-*/
+
+#ifndef foostrvhfoo
+#define foostrvhfoo
+
+/***
+  This file is part 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 <http://www.gnu.org/licenses/>.
+***/
+
+#include <stdarg.h>
+#include <stdbool.h>
+
+#include "macro.h"
+
+char *strv_find(char **l, const char *name);
+char *strv_find_prefix(char **l, const char *name);
+
+void strv_free(char **l);
+char **strv_copy(char **l) _malloc_;
+unsigned strv_length(char **l);
+
+char **strv_merge(char **a, char **b);
+char **strv_merge_concat(char **a, char **b, const char *suffix);
+char **strv_append(char **l, const char *s);
+
+char **strv_remove(char **l, const char *s);
+char **strv_uniq(char **l);
+
+#define strv_contains(l, s) (!!strv_find((l), (s)))
+
+char **strv_new(const char *x, ...) _sentinel_ _malloc_;
+char **strv_new_ap(const char *x, va_list ap) _malloc_;
+
+static inline bool strv_isempty(char **l) {
+        return !l || !*l;
+}
+
+char **strv_split(const char *s, const char *separator) _malloc_;
+char **strv_split_quoted(const char *s) _malloc_;
+
+char *strv_join(char **l, const char *separator) _malloc_;
+
+char **strv_env_merge(unsigned n_lists, ...);
+char **strv_env_delete(char **x, unsigned n_lists, ...);
+
+char **strv_env_set(char **x, const char *p);
+char **strv_env_unset(char **l, const char *p);
+
+char *strv_env_get_with_length(char **l, const char *name, size_t k);
+char *strv_env_get(char **x, const char *n);
+
+char **strv_env_clean(char **l);
+
+char **strv_parse_nulstr(const char *s, size_t l);
+
+bool strv_overlap(char **a, char **b);
+
+#define STRV_FOREACH(s, l)                      \
+        for ((s) = (l); (s) && *(s); (s)++)
+
+#define STRV_FOREACH_BACKWARDS(s, l)            \
+        for (; (l) && ((s) >= (l)); (s)--)
+
+#endif
diff --git a/src/swap.c b/src/swap.c
new file mode 100644 (file)
index 0000000..9c72732
--- /dev/null
@@ -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 <http://www.gnu.org/licenses/>.
+***/
+
+#include <errno.h>
+#include <limits.h>
+#include <unistd.h>
+#include <fcntl.h>
+#include <sys/epoll.h>
+#include <sys/stat.h>
+#include <sys/swap.h>
+#include <libudev.h>
+
+#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
new file mode 100644 (file)
index 0000000..62d08da
--- /dev/null
@@ -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 <http://www.gnu.org/licenses/>.
+***/
+
+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/sysctl.c b/src/sysctl.c
new file mode 100644 (file)
index 0000000..17c6719
--- /dev/null
@@ -0,0 +1,272 @@
+/*-*- 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 <http://www.gnu.org/licenses/>.
+***/
+
+#include <stdlib.h>
+#include <stdbool.h>
+#include <errno.h>
+#include <string.h>
+#include <stdio.h>
+#include <limits.h>
+#include <getopt.h>
+
+#include "log.h"
+#include "strv.h"
+#include "util.h"
+#include "strv.h"
+
+#define PROC_SYS_PREFIX "/proc/sys/"
+
+static char **arg_prefixes = NULL;
+
+static int apply_sysctl(const char *property, const char *value) {
+        char *p, *n;
+        int r = 0, k;
+
+        log_debug("Setting '%s' to '%s'", property, value);
+
+        p = new(char, sizeof(PROC_SYS_PREFIX) + strlen(property));
+        if (!p) {
+                log_error("Out of memory");
+                return -ENOMEM;
+        }
+
+        n = stpcpy(p, PROC_SYS_PREFIX);
+        strcpy(n, property);
+
+        for (; *n; n++)
+                if (*n == '.')
+                        *n = '/';
+
+        if (!strv_isempty(arg_prefixes)) {
+                char **i;
+                bool good = false;
+
+                STRV_FOREACH(i, arg_prefixes)
+                        if (path_startswith(p, *i)) {
+                                good = true;
+                                break;
+                        }
+
+                if (!good) {
+                        log_debug("Skipping %s", p);
+                        free(p);
+                        return 0;
+                }
+        }
+
+        k = write_one_line_file(p, value);
+        if (k < 0) {
+
+                log_full(k == -ENOENT ? LOG_DEBUG : LOG_WARNING,
+                         "Failed to write '%s' to '%s': %s", value, p, strerror(-k));
+
+                if (k != -ENOENT && r == 0)
+                        r = k;
+        }
+
+        free(p);
+
+        return r;
+}
+
+static int apply_file(const char *path, bool ignore_enoent) {
+        FILE *f;
+        int r = 0;
+
+        assert(path);
+
+        if (!(f = fopen(path, "re"))) {
+                if (ignore_enoent && errno == ENOENT)
+                        return 0;
+
+                log_error("Failed to open file '%s', ignoring: %m", path);
+                return -errno;
+        }
+
+        log_debug("apply: %s\n", path);
+        while (!feof(f)) {
+                char l[LINE_MAX], *p, *value;
+                int k;
+
+                if (!fgets(l, sizeof(l), f)) {
+                        if (feof(f))
+                                break;
+
+                        log_error("Failed to read file '%s', ignoring: %m", path);
+                        r = -errno;
+                        goto finish;
+                }
+
+                p = strstrip(l);
+
+                if (!*p)
+                        continue;
+
+                if (strchr(COMMENTS, *p))
+                        continue;
+
+                if (!(value = strchr(p, '='))) {
+                        log_error("Line is not an assignment in file '%s': %s", path, value);
+
+                        if (r == 0)
+                                r = -EINVAL;
+                        continue;
+                }
+
+                *value = 0;
+                value++;
+
+                if ((k = apply_sysctl(strstrip(p), strstrip(value))) < 0 && r == 0)
+                        r = k;
+        }
+
+finish:
+        fclose(f);
+
+        return r;
+}
+
+static int help(void) {
+
+        printf("%s [OPTIONS...] [CONFIGURATION FILE...]\n\n"
+               "Applies kernel sysctl settings.\n\n"
+               "  -h --help             Show this help\n"
+               "     --prefix=PATH      Only apply rules that apply to paths with the specified prefix\n",
+               program_invocation_short_name);
+
+        return 0;
+}
+
+static int parse_argv(int argc, char *argv[]) {
+
+        enum {
+                ARG_PREFIX
+        };
+
+        static const struct option options[] = {
+                { "help",      no_argument,       NULL, 'h'           },
+                { "prefix",    required_argument, NULL, ARG_PREFIX    },
+                { NULL,        0,                 NULL, 0             }
+        };
+
+        int c;
+
+        assert(argc >= 0);
+        assert(argv);
+
+        while ((c = getopt_long(argc, argv, "h", options, NULL)) >= 0) {
+
+                switch (c) {
+
+                case 'h':
+                        help();
+                        return 0;
+
+                case ARG_PREFIX: {
+                        char *p;
+                        char **l;
+
+                        for (p = optarg; *p; p++)
+                                if (*p == '.')
+                                        *p = '/';
+
+                        l = strv_append(arg_prefixes, optarg);
+                        if (!l) {
+                                log_error("Out of memory");
+                                return -ENOMEM;
+                        }
+
+                        strv_free(arg_prefixes);
+                        arg_prefixes = l;
+
+                        break;
+                }
+
+                case '?':
+                        return -EINVAL;
+
+                default:
+                        log_error("Unknown option code %c", c);
+                        return -EINVAL;
+                }
+        }
+
+        return 1;
+}
+
+int main(int argc, char *argv[]) {
+        int r = 0;
+
+        r = parse_argv(argc, argv);
+        if (r <= 0)
+                return r < 0 ? EXIT_FAILURE : EXIT_SUCCESS;
+
+        log_set_target(LOG_TARGET_AUTO);
+        log_parse_environment();
+        log_open();
+
+        umask(0022);
+
+        if (argc > optind) {
+                int i;
+
+                for (i = optind; i < argc; i++) {
+                        int k;
+
+                        k = apply_file(argv[i], false);
+                        if (k < 0 && r == 0)
+                                r = k;
+                }
+        } else {
+                char **files, **f;
+                int k;
+
+                r = conf_files_list(&files, ".conf",
+                                    "/etc/sysctl.d",
+                                    "/run/sysctl.d",
+                                    "/usr/local/lib/sysctl.d",
+                                    "/usr/lib/sysctl.d",
+#ifdef HAVE_SPLIT_USR
+                                    "/lib/sysctl.d",
+#endif
+                                    NULL);
+                if (r < 0) {
+                        log_error("Failed to enumerate sysctl.d files: %s", strerror(-r));
+                        goto finish;
+                }
+
+                STRV_FOREACH(f, files) {
+                        k = apply_file(*f, true);
+                        if (k < 0 && r == 0)
+                                r = k;
+                }
+
+                k = apply_file("/etc/sysctl.conf", true);
+                if (k < 0 && r == 0)
+                        r = k;
+
+                strv_free(files);
+        }
+finish:
+        strv_free(arg_prefixes);
+
+        return r < 0 ? EXIT_FAILURE : EXIT_SUCCESS;
+}
diff --git a/src/sysfs-show.h b/src/sysfs-show.h
new file mode 100644 (file)
index 0000000..9939e8b
--- /dev/null
@@ -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 <http://www.gnu.org/licenses/>.
+***/
+
+int show_sysfs(const char *seat, const char *prefix, unsigned columns);
+
+#endif
diff --git a/src/system.conf b/src/system.conf
new file mode 100644 (file)
index 0000000..33d09bc
--- /dev/null
@@ -0,0 +1,26 @@
+#  This file is part of systemd.
+#
+#  systemd is free software; you can redistribute it and/or modify it
+#  under the terms of the GNU General Public License as published by
+#  the Free Software Foundation; either version 2 of the License, or
+#  (at your option) any later version.
+#
+# See systemd.conf(5) for details
+
+[Manager]
+#LogLevel=info
+#LogTarget=journal-or-kmsg
+#LogColor=yes
+#LogLocation=no
+#DumpCore=yes
+#CrashShell=no
+#ShowStatus=yes
+#SysVConsole=yes
+#CrashChVT=1
+#CPUAffinity=1 2
+#MountAuto=yes
+#SwapAuto=yes
+#DefaultControllers=cpu
+#DefaultStandardOutput=journal
+#DefaultStandardError=inherit
+#JoinControllers=cpu,cpuacct
diff --git a/src/systemctl.c b/src/systemctl.c
new file mode 100644 (file)
index 0000000..4b27b69
--- /dev/null
@@ -0,0 +1,5489 @@
+/*-*- 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 <http://www.gnu.org/licenses/>.
+***/
+
+#include <sys/reboot.h>
+#include <stdio.h>
+#include <getopt.h>
+#include <stdbool.h>
+#include <string.h>
+#include <errno.h>
+#include <sys/ioctl.h>
+#include <termios.h>
+#include <unistd.h>
+#include <fcntl.h>
+#include <sys/socket.h>
+#include <sys/stat.h>
+#include <stddef.h>
+#include <sys/prctl.h>
+#include <dbus/dbus.h>
+
+#include <systemd/sd-daemon.h>
+
+#include "log.h"
+#include "util.h"
+#include "macro.h"
+#include "set.h"
+#include "utmp-wtmp.h"
+#include "special.h"
+#include "initreq.h"
+#include "strv.h"
+#include "dbus-common.h"
+#include "cgroup-show.h"
+#include "cgroup-util.h"
+#include "list.h"
+#include "path-lookup.h"
+#include "conf-parser.h"
+#include "shutdownd.h"
+#include "exit-status.h"
+#include "bus-errors.h"
+#include "build.h"
+#include "unit-name.h"
+#include "pager.h"
+#include "spawn-agent.h"
+#include "install.h"
+#include "logs-show.h"
+
+static const char *arg_type = NULL;
+static char **arg_property = NULL;
+static bool arg_all = false;
+static const char *arg_job_mode = "replace";
+static UnitFileScope arg_scope = UNIT_FILE_SYSTEM;
+static bool arg_immediate = false;
+static bool arg_no_block = false;
+static bool arg_no_legend = false;
+static bool arg_no_pager = false;
+static bool arg_no_wtmp = false;
+static bool arg_no_sync = false;
+static bool arg_no_wall = false;
+static bool arg_no_reload = false;
+static bool arg_dry = false;
+static bool arg_quiet = false;
+static bool arg_full = false;
+static int arg_force = 0;
+static bool arg_ask_password = false;
+static bool arg_failed = false;
+static bool arg_runtime = false;
+static char **arg_wall = NULL;
+static const char *arg_kill_who = NULL;
+static const char *arg_kill_mode = NULL;
+static int arg_signal = SIGTERM;
+static const char *arg_root = NULL;
+static usec_t arg_when = 0;
+static enum action {
+        ACTION_INVALID,
+        ACTION_SYSTEMCTL,
+        ACTION_HALT,
+        ACTION_POWEROFF,
+        ACTION_REBOOT,
+        ACTION_KEXEC,
+        ACTION_EXIT,
+        ACTION_RUNLEVEL2,
+        ACTION_RUNLEVEL3,
+        ACTION_RUNLEVEL4,
+        ACTION_RUNLEVEL5,
+        ACTION_RESCUE,
+        ACTION_EMERGENCY,
+        ACTION_DEFAULT,
+        ACTION_RELOAD,
+        ACTION_REEXEC,
+        ACTION_RUNLEVEL,
+        ACTION_CANCEL_SHUTDOWN,
+        _ACTION_MAX
+} arg_action = ACTION_SYSTEMCTL;
+static enum dot {
+        DOT_ALL,
+        DOT_ORDER,
+        DOT_REQUIRE
+} arg_dot = DOT_ALL;
+static enum transport {
+        TRANSPORT_NORMAL,
+        TRANSPORT_SSH,
+        TRANSPORT_POLKIT
+} arg_transport = TRANSPORT_NORMAL;
+static const char *arg_host = NULL;
+static bool arg_follow = false;
+static unsigned arg_lines = 10;
+static OutputMode arg_output = OUTPUT_SHORT;
+
+static bool private_bus = false;
+
+static int daemon_reload(DBusConnection *bus, char **args);
+static void halt_now(enum action a);
+
+static bool on_tty(void) {
+        static int t = -1;
+
+        /* Note that this is invoked relatively early, before we start
+         * the pager. That means the value we return reflects whether
+         * we originally were started on a tty, not if we currently
+         * are. But this is intended, since we want colour and so on
+         * when run in our own pager. */
+
+        if (_unlikely_(t < 0))
+                t = isatty(STDOUT_FILENO) > 0;
+
+        return t;
+}
+
+static void pager_open_if_enabled(void) {
+
+        /* Cache result before we open the pager */
+        on_tty();
+
+        if (arg_no_pager)
+                return;
+
+        pager_open();
+}
+
+static void agent_open_if_enabled(void) {
+
+        /* Open the password agent as a child process if necessary */
+
+        if (!arg_ask_password)
+                return;
+
+        if (arg_scope != UNIT_FILE_SYSTEM)
+                return;
+
+        agent_open();
+}
+
+static const char *ansi_highlight_red(bool b) {
+
+        if (!on_tty())
+                return "";
+
+        return b ? ANSI_HIGHLIGHT_RED_ON : ANSI_HIGHLIGHT_OFF;
+}
+
+static const char *ansi_highlight_green(bool b) {
+
+        if (!on_tty())
+                return "";
+
+        return b ? ANSI_HIGHLIGHT_GREEN_ON : ANSI_HIGHLIGHT_OFF;
+}
+
+static bool error_is_no_service(const DBusError *error) {
+        assert(error);
+
+        if (!dbus_error_is_set(error))
+                return false;
+
+        if (dbus_error_has_name(error, DBUS_ERROR_NAME_HAS_NO_OWNER))
+                return true;
+
+        if (dbus_error_has_name(error, DBUS_ERROR_SERVICE_UNKNOWN))
+                return true;
+
+        return startswith(error->name, "org.freedesktop.DBus.Error.Spawn.");
+}
+
+static int translate_bus_error_to_exit_status(int r, const DBusError *error) {
+        assert(error);
+
+        if (!dbus_error_is_set(error))
+                return r;
+
+        if (dbus_error_has_name(error, DBUS_ERROR_ACCESS_DENIED) ||
+            dbus_error_has_name(error, BUS_ERROR_ONLY_BY_DEPENDENCY) ||
+            dbus_error_has_name(error, BUS_ERROR_NO_ISOLATION) ||
+            dbus_error_has_name(error, BUS_ERROR_TRANSACTION_IS_DESTRUCTIVE))
+                return EXIT_NOPERMISSION;
+
+        if (dbus_error_has_name(error, BUS_ERROR_NO_SUCH_UNIT))
+                return EXIT_NOTINSTALLED;
+
+        if (dbus_error_has_name(error, BUS_ERROR_JOB_TYPE_NOT_APPLICABLE) ||
+            dbus_error_has_name(error, BUS_ERROR_NOT_SUPPORTED))
+                return EXIT_NOTIMPLEMENTED;
+
+        if (dbus_error_has_name(error, BUS_ERROR_LOAD_FAILED))
+                return EXIT_NOTCONFIGURED;
+
+        if (r != 0)
+                return r;
+
+        return EXIT_FAILURE;
+}
+
+static void warn_wall(enum action a) {
+        static const char *table[_ACTION_MAX] = {
+                [ACTION_HALT]      = "The system is going down for system halt NOW!",
+                [ACTION_REBOOT]    = "The system is going down for reboot NOW!",
+                [ACTION_POWEROFF]  = "The system is going down for power-off NOW!",
+                [ACTION_KEXEC]     = "The system is going down for kexec reboot NOW!",
+                [ACTION_RESCUE]    = "The system is going down to rescue mode NOW!",
+                [ACTION_EMERGENCY] = "The system is going down to emergency mode NOW!"
+        };
+
+        if (arg_no_wall)
+                return;
+
+        if (arg_wall) {
+                char *p;
+
+                if (!(p = strv_join(arg_wall, " "))) {
+                        log_error("Failed to join strings.");
+                        return;
+                }
+
+                if (*p) {
+                        utmp_wall(p, NULL);
+                        free(p);
+                        return;
+                }
+
+                free(p);
+        }
+
+        if (!table[a])
+                return;
+
+        utmp_wall(table[a], NULL);
+}
+
+static bool avoid_bus(void) {
+
+        if (running_in_chroot() > 0)
+                return true;
+
+        if (sd_booted() <= 0)
+                return true;
+
+        if (!isempty(arg_root))
+                return true;
+
+        if (arg_scope == UNIT_FILE_GLOBAL)
+                return true;
+
+        return false;
+}
+
+struct unit_info {
+        const char *id;
+        const char *description;
+        const char *load_state;
+        const char *active_state;
+        const char *sub_state;
+        const char *following;
+        const char *unit_path;
+        uint32_t job_id;
+        const char *job_type;
+        const char *job_path;
+};
+
+static int compare_unit_info(const void *a, const void *b) {
+        const char *d1, *d2;
+        const struct unit_info *u = a, *v = b;
+
+        d1 = strrchr(u->id, '.');
+        d2 = strrchr(v->id, '.');
+
+        if (d1 && d2) {
+                int r;
+
+                if ((r = strcasecmp(d1, d2)) != 0)
+                        return r;
+        }
+
+        return strcasecmp(u->id, v->id);
+}
+
+static bool output_show_unit(const struct unit_info *u) {
+        const char *dot;
+
+        if (arg_failed)
+                return streq(u->active_state, "failed");
+
+        return (!arg_type || ((dot = strrchr(u->id, '.')) &&
+                              streq(dot+1, arg_type))) &&
+                (arg_all || !(streq(u->active_state, "inactive") || u->following[0]) || u->job_id > 0);
+}
+
+static void output_units_list(const struct unit_info *unit_infos, unsigned c) {
+        unsigned id_len, max_id_len, active_len, sub_len, job_len, desc_len, n_shown = 0;
+        const struct unit_info *u;
+
+        max_id_len = sizeof("UNIT")-1;
+        active_len = sizeof("ACTIVE")-1;
+        sub_len = sizeof("SUB")-1;
+        job_len = sizeof("JOB")-1;
+        desc_len = 0;
+
+        for (u = unit_infos; u < unit_infos + c; u++) {
+                if (!output_show_unit(u))
+                        continue;
+
+                max_id_len = MAX(max_id_len, strlen(u->id));
+                active_len = MAX(active_len, strlen(u->active_state));
+                sub_len = MAX(sub_len, strlen(u->sub_state));
+                if (u->job_id != 0)
+                        job_len = MAX(job_len, strlen(u->job_type));
+        }
+
+        if (!arg_full) {
+                unsigned basic_len;
+                id_len = MIN(max_id_len, 25);
+                basic_len = 5 + id_len + 6 + active_len + sub_len + job_len;
+                if (basic_len < (unsigned) columns()) {
+                        unsigned extra_len, incr;
+                        extra_len = columns() - basic_len;
+                        /* Either UNIT already got 25, or is fully satisfied.
+                         * Grant up to 25 to DESC now. */
+                        incr = MIN(extra_len, 25);
+                        desc_len += incr;
+                        extra_len -= incr;
+                        /* split the remaining space between UNIT and DESC,
+                         * but do not give UNIT more than it needs. */
+                        if (extra_len > 0) {
+                                incr = MIN(extra_len / 2, max_id_len - id_len);
+                                id_len += incr;
+                                desc_len += extra_len - incr;
+                        }
+                }
+        } else
+                id_len = max_id_len;
+
+        if (!arg_no_legend) {
+                printf("%-*s %-6s %-*s %-*s %-*s ", id_len, "UNIT", "LOAD",
+                       active_len, "ACTIVE", sub_len, "SUB", job_len, "JOB");
+                if (!arg_full && arg_no_pager)
+                        printf("%.*s\n", desc_len, "DESCRIPTION");
+                else
+                        printf("%s\n", "DESCRIPTION");
+        }
+
+        for (u = unit_infos; u < unit_infos + c; u++) {
+                char *e;
+                const char *on_loaded, *off_loaded;
+                const char *on_active, *off_active;
+
+                if (!output_show_unit(u))
+                        continue;
+
+                n_shown++;
+
+                if (streq(u->load_state, "error")) {
+                        on_loaded = ansi_highlight_red(true);
+                        off_loaded = ansi_highlight_red(false);
+                } else
+                        on_loaded = off_loaded = "";
+
+                if (streq(u->active_state, "failed")) {
+                        on_active = ansi_highlight_red(true);
+                        off_active = ansi_highlight_red(false);
+                } else
+                        on_active = off_active = "";
+
+                e = arg_full ? NULL : ellipsize(u->id, id_len, 33);
+
+                printf("%-*s %s%-6s%s %s%-*s %-*s%s %-*s ",
+                       id_len, e ? e : u->id,
+                       on_loaded, u->load_state, off_loaded,
+                       on_active, active_len, u->active_state,
+                       sub_len, u->sub_state, off_active,
+                       job_len, u->job_id ? u->job_type : "");
+                if (!arg_full && arg_no_pager)
+                        printf("%.*s\n", desc_len, u->description);
+                else
+                        printf("%s\n", u->description);
+
+                free(e);
+        }
+
+        if (!arg_no_legend) {
+                printf("\nLOAD   = Reflects whether the unit definition was properly loaded.\n"
+                       "ACTIVE = The high-level unit activation state, i.e. generalization of SUB.\n"
+                       "SUB    = The low-level unit activation state, values depend on unit type.\n"
+                       "JOB    = Pending job for the unit.\n");
+
+                if (arg_all)
+                        printf("\n%u units listed.\n", n_shown);
+                else
+                        printf("\n%u units listed. Pass --all to see inactive units, too.\n", n_shown);
+        }
+}
+
+static int list_units(DBusConnection *bus, char **args) {
+        DBusMessage *m = NULL, *reply = NULL;
+        DBusError error;
+        int r;
+        DBusMessageIter iter, sub, sub2;
+        unsigned c = 0, n_units = 0;
+        struct unit_info *unit_infos = NULL;
+
+        dbus_error_init(&error);
+
+        assert(bus);
+
+        pager_open_if_enabled();
+
+        if (!(m = dbus_message_new_method_call(
+                              "org.freedesktop.systemd1",
+                              "/org/freedesktop/systemd1",
+                              "org.freedesktop.systemd1.Manager",
+                              "ListUnits"))) {
+                log_error("Could not allocate message.");
+                return -ENOMEM;
+        }
+
+        if (!(reply = dbus_connection_send_with_reply_and_block(bus, m, -1, &error))) {
+                log_error("Failed to issue method call: %s", bus_error_message(&error));
+                r = -EIO;
+                goto finish;
+        }
+
+        if (!dbus_message_iter_init(reply, &iter) ||
+            dbus_message_iter_get_arg_type(&iter) != DBUS_TYPE_ARRAY ||
+            dbus_message_iter_get_element_type(&iter) != DBUS_TYPE_STRUCT)  {
+                log_error("Failed to parse reply.");
+                r = -EIO;
+                goto finish;
+        }
+
+        dbus_message_iter_recurse(&iter, &sub);
+
+        while (dbus_message_iter_get_arg_type(&sub) != DBUS_TYPE_INVALID) {
+                struct unit_info *u;
+
+                if (dbus_message_iter_get_arg_type(&sub) != DBUS_TYPE_STRUCT) {
+                        log_error("Failed to parse reply.");
+                        r = -EIO;
+                        goto finish;
+                }
+
+                if (c >= n_units) {
+                        struct unit_info *w;
+
+                        n_units = MAX(2*c, 16);
+                        w = realloc(unit_infos, sizeof(struct unit_info) * n_units);
+
+                        if (!w) {
+                                log_error("Failed to allocate unit array.");
+                                r = -ENOMEM;
+                                goto finish;
+                        }
+
+                        unit_infos = w;
+                }
+
+                u = unit_infos+c;
+
+                dbus_message_iter_recurse(&sub, &sub2);
+
+                if (bus_iter_get_basic_and_next(&sub2, DBUS_TYPE_STRING, &u->id, true) < 0 ||
+                    bus_iter_get_basic_and_next(&sub2, DBUS_TYPE_STRING, &u->description, true) < 0 ||
+                    bus_iter_get_basic_and_next(&sub2, DBUS_TYPE_STRING, &u->load_state, true) < 0 ||
+                    bus_iter_get_basic_and_next(&sub2, DBUS_TYPE_STRING, &u->active_state, true) < 0 ||
+                    bus_iter_get_basic_and_next(&sub2, DBUS_TYPE_STRING, &u->sub_state, true) < 0 ||
+                    bus_iter_get_basic_and_next(&sub2, DBUS_TYPE_STRING, &u->following, true) < 0 ||
+                    bus_iter_get_basic_and_next(&sub2, DBUS_TYPE_OBJECT_PATH, &u->unit_path, true) < 0 ||
+                    bus_iter_get_basic_and_next(&sub2, DBUS_TYPE_UINT32, &u->job_id, true) < 0 ||
+                    bus_iter_get_basic_and_next(&sub2, DBUS_TYPE_STRING, &u->job_type, true) < 0 ||
+                    bus_iter_get_basic_and_next(&sub2, DBUS_TYPE_OBJECT_PATH, &u->job_path, false) < 0) {
+                        log_error("Failed to parse reply.");
+                        r = -EIO;
+                        goto finish;
+                }
+
+                dbus_message_iter_next(&sub);
+                c++;
+        }
+
+        if (c > 0) {
+                qsort(unit_infos, c, sizeof(struct unit_info), compare_unit_info);
+                output_units_list(unit_infos, c);
+        }
+
+        r = 0;
+
+finish:
+        if (m)
+                dbus_message_unref(m);
+
+        if (reply)
+                dbus_message_unref(reply);
+
+        free(unit_infos);
+
+        dbus_error_free(&error);
+
+        return r;
+}
+
+static int compare_unit_file_list(const void *a, const void *b) {
+        const char *d1, *d2;
+        const UnitFileList *u = a, *v = b;
+
+        d1 = strrchr(u->path, '.');
+        d2 = strrchr(v->path, '.');
+
+        if (d1 && d2) {
+                int r;
+
+                r = strcasecmp(d1, d2);
+                if (r != 0)
+                        return r;
+        }
+
+        return strcasecmp(file_name_from_path(u->path), file_name_from_path(v->path));
+}
+
+static bool output_show_unit_file(const UnitFileList *u) {
+        const char *dot;
+
+        return !arg_type || ((dot = strrchr(u->path, '.')) && streq(dot+1, arg_type));
+}
+
+static void output_unit_file_list(const UnitFileList *units, unsigned c) {
+        unsigned max_id_len, id_cols, state_cols, n_shown = 0;
+        const UnitFileList *u;
+
+        max_id_len = sizeof("UNIT FILE")-1;
+        state_cols = sizeof("STATE")-1;
+        for (u = units; u < units + c; u++) {
+                if (!output_show_unit_file(u))
+                        continue;
+
+                max_id_len = MAX(max_id_len, strlen(file_name_from_path(u->path)));
+                state_cols = MAX(state_cols, strlen(unit_file_state_to_string(u->state)));
+        }
+
+        if (!arg_full) {
+                unsigned basic_cols;
+                id_cols = MIN(max_id_len, 25);
+                basic_cols = 1 + id_cols + state_cols;
+                if (basic_cols < (unsigned) columns())
+                        id_cols += MIN(columns() - basic_cols, max_id_len - id_cols);
+        } else
+                id_cols = max_id_len;
+
+        if (!arg_no_legend)
+                printf("%-*s %-*s\n", id_cols, "UNIT FILE", state_cols, "STATE");
+
+        for (u = units; u < units + c; u++) {
+                char *e;
+                const char *on, *off;
+                const char *id;
+
+                if (!output_show_unit_file(u))
+                        continue;
+
+                n_shown++;
+
+                if (u->state == UNIT_FILE_MASKED ||
+                    u->state == UNIT_FILE_MASKED_RUNTIME ||
+                    u->state == UNIT_FILE_DISABLED) {
+                        on  = ansi_highlight_red(true);
+                        off = ansi_highlight_red(false);
+                } else if (u->state == UNIT_FILE_ENABLED) {
+                        on  = ansi_highlight_green(true);
+                        off = ansi_highlight_green(false);
+                } else
+                        on = off = "";
+
+                id = file_name_from_path(u->path);
+
+                e = arg_full ? NULL : ellipsize(id, id_cols, 33);
+
+                printf("%-*s %s%-*s%s\n",
+                       id_cols, e ? e : id,
+                       on, state_cols, unit_file_state_to_string(u->state), off);
+
+                free(e);
+        }
+
+        if (!arg_no_legend)
+                printf("\n%u unit files listed.\n", n_shown);
+}
+
+static int list_unit_files(DBusConnection *bus, char **args) {
+        DBusMessage *m = NULL, *reply = NULL;
+        DBusError error;
+        int r;
+        DBusMessageIter iter, sub, sub2;
+        unsigned c = 0, n_units = 0;
+        UnitFileList *units = NULL;
+
+        dbus_error_init(&error);
+
+        pager_open_if_enabled();
+
+        if (avoid_bus()) {
+                Hashmap *h;
+                UnitFileList *u;
+                Iterator i;
+
+                h = hashmap_new(string_hash_func, string_compare_func);
+                if (!h) {
+                        log_error("Out of memory");
+                        return -ENOMEM;
+                }
+
+                r = unit_file_get_list(arg_scope, arg_root, h);
+                if (r < 0) {
+                        unit_file_list_free(h);
+                        log_error("Failed to get unit file list: %s", strerror(-r));
+                        return r;
+                }
+
+                n_units = hashmap_size(h);
+                units = new(UnitFileList, n_units);
+                if (!units) {
+                        unit_file_list_free(h);
+                        log_error("Out of memory");
+                        return -ENOMEM;
+                }
+
+                HASHMAP_FOREACH(u, h, i) {
+                        memcpy(units + c++, u, sizeof(UnitFileList));
+                        free(u);
+                }
+
+                hashmap_free(h);
+        } else {
+                assert(bus);
+
+                m = dbus_message_new_method_call(
+                                "org.freedesktop.systemd1",
+                                "/org/freedesktop/systemd1",
+                                "org.freedesktop.systemd1.Manager",
+                                "ListUnitFiles");
+                if (!m) {
+                        log_error("Could not allocate message.");
+                        return -ENOMEM;
+                }
+
+                reply = dbus_connection_send_with_reply_and_block(bus, m, -1, &error);
+                if (!reply) {
+                        log_error("Failed to issue method call: %s", bus_error_message(&error));
+                        r = -EIO;
+                        goto finish;
+                }
+
+                if (!dbus_message_iter_init(reply, &iter) ||
+                    dbus_message_iter_get_arg_type(&iter) != DBUS_TYPE_ARRAY ||
+                    dbus_message_iter_get_element_type(&iter) != DBUS_TYPE_STRUCT)  {
+                        log_error("Failed to parse reply.");
+                        r = -EIO;
+                        goto finish;
+                }
+
+                dbus_message_iter_recurse(&iter, &sub);
+
+                while (dbus_message_iter_get_arg_type(&sub) != DBUS_TYPE_INVALID) {
+                        UnitFileList *u;
+                        const char *state;
+
+                        if (dbus_message_iter_get_arg_type(&sub) != DBUS_TYPE_STRUCT) {
+                                log_error("Failed to parse reply.");
+                                r = -EIO;
+                                goto finish;
+                        }
+
+                        if (c >= n_units) {
+                                UnitFileList *w;
+
+                                n_units = MAX(2*c, 16);
+                                w = realloc(units, sizeof(struct UnitFileList) * n_units);
+
+                                if (!w) {
+                                        log_error("Failed to allocate unit array.");
+                                        r = -ENOMEM;
+                                        goto finish;
+                                }
+
+                                units = w;
+                        }
+
+                        u = units+c;
+
+                        dbus_message_iter_recurse(&sub, &sub2);
+
+                        if (bus_iter_get_basic_and_next(&sub2, DBUS_TYPE_STRING, &u->path, true) < 0 ||
+                            bus_iter_get_basic_and_next(&sub2, DBUS_TYPE_STRING, &state, false) < 0) {
+                                log_error("Failed to parse reply.");
+                                r = -EIO;
+                                goto finish;
+                        }
+
+                        u->state = unit_file_state_from_string(state);
+
+                        dbus_message_iter_next(&sub);
+                        c++;
+                }
+        }
+
+        if (c > 0) {
+                qsort(units, c, sizeof(UnitFileList), compare_unit_file_list);
+                output_unit_file_list(units, c);
+        }
+
+        r = 0;
+
+finish:
+        if (m)
+                dbus_message_unref(m);
+
+        if (reply)
+                dbus_message_unref(reply);
+
+        free(units);
+
+        dbus_error_free(&error);
+
+        return r;
+}
+
+static int dot_one_property(const char *name, const char *prop, DBusMessageIter *iter) {
+        static const char * const colors[] = {
+                "Requires",              "[color=\"black\"]",
+                "RequiresOverridable",   "[color=\"black\"]",
+                "Requisite",             "[color=\"darkblue\"]",
+                "RequisiteOverridable",  "[color=\"darkblue\"]",
+                "Wants",                 "[color=\"darkgrey\"]",
+                "Conflicts",             "[color=\"red\"]",
+                "ConflictedBy",          "[color=\"red\"]",
+                "After",                 "[color=\"green\"]"
+        };
+
+        const char *c = NULL;
+        unsigned i;
+
+        assert(name);
+        assert(prop);
+        assert(iter);
+
+        for (i = 0; i < ELEMENTSOF(colors); i += 2)
+                if (streq(colors[i], prop)) {
+                        c = colors[i+1];
+                        break;
+                }
+
+        if (!c)
+                return 0;
+
+        if (arg_dot != DOT_ALL)
+                if ((arg_dot == DOT_ORDER) != streq(prop, "After"))
+                        return 0;
+
+        switch (dbus_message_iter_get_arg_type(iter)) {
+
+        case DBUS_TYPE_ARRAY:
+
+                if (dbus_message_iter_get_element_type(iter) == DBUS_TYPE_STRING) {
+                        DBusMessageIter sub;
+
+                        dbus_message_iter_recurse(iter, &sub);
+
+                        while (dbus_message_iter_get_arg_type(&sub) != DBUS_TYPE_INVALID) {
+                                const char *s;
+
+                                assert(dbus_message_iter_get_arg_type(&sub) == DBUS_TYPE_STRING);
+                                dbus_message_iter_get_basic(&sub, &s);
+                                printf("\t\"%s\"->\"%s\" %s;\n", name, s, c);
+
+                                dbus_message_iter_next(&sub);
+                        }
+
+                        return 0;
+                }
+        }
+
+        return 0;
+}
+
+static int dot_one(DBusConnection *bus, const char *name, const char *path) {
+        DBusMessage *m = NULL, *reply = NULL;
+        const char *interface = "org.freedesktop.systemd1.Unit";
+        int r;
+        DBusError error;
+        DBusMessageIter iter, sub, sub2, sub3;
+
+        assert(bus);
+        assert(path);
+
+        dbus_error_init(&error);
+
+        if (!(m = dbus_message_new_method_call(
+                              "org.freedesktop.systemd1",
+                              path,
+                              "org.freedesktop.DBus.Properties",
+                              "GetAll"))) {
+                log_error("Could not allocate message.");
+                r = -ENOMEM;
+                goto finish;
+        }
+
+        if (!dbus_message_append_args(m,
+                                      DBUS_TYPE_STRING, &interface,
+                                      DBUS_TYPE_INVALID)) {
+                log_error("Could not append arguments to message.");
+                r = -ENOMEM;
+                goto finish;
+        }
+
+        if (!(reply = dbus_connection_send_with_reply_and_block(bus, m, -1, &error))) {
+                log_error("Failed to issue method call: %s", bus_error_message(&error));
+                r = -EIO;
+                goto finish;
+        }
+
+        if (!dbus_message_iter_init(reply, &iter) ||
+            dbus_message_iter_get_arg_type(&iter) != DBUS_TYPE_ARRAY ||
+            dbus_message_iter_get_element_type(&iter) != DBUS_TYPE_DICT_ENTRY)  {
+                log_error("Failed to parse reply.");
+                r = -EIO;
+                goto finish;
+        }
+
+        dbus_message_iter_recurse(&iter, &sub);
+
+        while (dbus_message_iter_get_arg_type(&sub) != DBUS_TYPE_INVALID) {
+                const char *prop;
+
+                if (dbus_message_iter_get_arg_type(&sub) != DBUS_TYPE_DICT_ENTRY) {
+                        log_error("Failed to parse reply.");
+                        r = -EIO;
+                        goto finish;
+                }
+
+                dbus_message_iter_recurse(&sub, &sub2);
+
+                if (bus_iter_get_basic_and_next(&sub2, DBUS_TYPE_STRING, &prop, true) < 0) {
+                        log_error("Failed to parse reply.");
+                        r = -EIO;
+                        goto finish;
+                }
+
+                if (dbus_message_iter_get_arg_type(&sub2) != DBUS_TYPE_VARIANT)  {
+                        log_error("Failed to parse reply.");
+                        r = -EIO;
+                        goto finish;
+                }
+
+                dbus_message_iter_recurse(&sub2, &sub3);
+
+                if (dot_one_property(name, prop, &sub3)) {
+                        log_error("Failed to parse reply.");
+                        r = -EIO;
+                        goto finish;
+                }
+
+                dbus_message_iter_next(&sub);
+        }
+
+        r = 0;
+
+finish:
+        if (m)
+                dbus_message_unref(m);
+
+        if (reply)
+                dbus_message_unref(reply);
+
+        dbus_error_free(&error);
+
+        return r;
+}
+
+static int dot(DBusConnection *bus, char **args) {
+        DBusMessage *m = NULL, *reply = NULL;
+        DBusError error;
+        int r;
+        DBusMessageIter iter, sub, sub2;
+
+        dbus_error_init(&error);
+
+        assert(bus);
+
+        if (!(m = dbus_message_new_method_call(
+                              "org.freedesktop.systemd1",
+                              "/org/freedesktop/systemd1",
+                              "org.freedesktop.systemd1.Manager",
+                              "ListUnits"))) {
+                log_error("Could not allocate message.");
+                return -ENOMEM;
+        }
+
+        if (!(reply = dbus_connection_send_with_reply_and_block(bus, m, -1, &error))) {
+                log_error("Failed to issue method call: %s", bus_error_message(&error));
+                r = -EIO;
+                goto finish;
+        }
+
+        if (!dbus_message_iter_init(reply, &iter) ||
+            dbus_message_iter_get_arg_type(&iter) != DBUS_TYPE_ARRAY ||
+            dbus_message_iter_get_element_type(&iter) != DBUS_TYPE_STRUCT)  {
+                log_error("Failed to parse reply.");
+                r = -EIO;
+                goto finish;
+        }
+
+        printf("digraph systemd {\n");
+
+        dbus_message_iter_recurse(&iter, &sub);
+        while (dbus_message_iter_get_arg_type(&sub) != DBUS_TYPE_INVALID) {
+                const char *id, *description, *load_state, *active_state, *sub_state, *following, *unit_path;
+
+                if (dbus_message_iter_get_arg_type(&sub) != DBUS_TYPE_STRUCT) {
+                        log_error("Failed to parse reply.");
+                        r = -EIO;
+                        goto finish;
+                }
+
+                dbus_message_iter_recurse(&sub, &sub2);
+
+                if (bus_iter_get_basic_and_next(&sub2, DBUS_TYPE_STRING, &id, true) < 0 ||
+                    bus_iter_get_basic_and_next(&sub2, DBUS_TYPE_STRING, &description, true) < 0 ||
+                    bus_iter_get_basic_and_next(&sub2, DBUS_TYPE_STRING, &load_state, true) < 0 ||
+                    bus_iter_get_basic_and_next(&sub2, DBUS_TYPE_STRING, &active_state, true) < 0 ||
+                    bus_iter_get_basic_and_next(&sub2, DBUS_TYPE_STRING, &sub_state, true) < 0 ||
+                    bus_iter_get_basic_and_next(&sub2, DBUS_TYPE_STRING, &following, true) < 0 ||
+                    bus_iter_get_basic_and_next(&sub2, DBUS_TYPE_OBJECT_PATH, &unit_path, true) < 0) {
+                        log_error("Failed to parse reply.");
+                        r = -EIO;
+                        goto finish;
+                }
+
+                if ((r = dot_one(bus, id, unit_path)) < 0)
+                        goto finish;
+
+                /* printf("\t\"%s\";\n", id); */
+                dbus_message_iter_next(&sub);
+        }
+
+        printf("}\n");
+
+        log_info("   Color legend: black     = Requires\n"
+                 "                 dark blue = Requisite\n"
+                 "                 dark grey = Wants\n"
+                 "                 red       = Conflicts\n"
+                 "                 green     = After\n");
+
+        if (on_tty())
+                log_notice("-- You probably want to process this output with graphviz' dot tool.\n"
+                           "-- Try a shell pipeline like 'systemctl dot | dot -Tsvg > systemd.svg'!\n");
+
+        r = 0;
+
+finish:
+        if (m)
+                dbus_message_unref(m);
+
+        if (reply)
+                dbus_message_unref(reply);
+
+        dbus_error_free(&error);
+
+        return r;
+}
+
+static int list_jobs(DBusConnection *bus, char **args) {
+        DBusMessage *m = NULL, *reply = NULL;
+        DBusError error;
+        int r;
+        DBusMessageIter iter, sub, sub2;
+        unsigned k = 0;
+
+        dbus_error_init(&error);
+
+        assert(bus);
+
+        pager_open_if_enabled();
+
+        if (!(m = dbus_message_new_method_call(
+                              "org.freedesktop.systemd1",
+                              "/org/freedesktop/systemd1",
+                              "org.freedesktop.systemd1.Manager",
+                              "ListJobs"))) {
+                log_error("Could not allocate message.");
+                return -ENOMEM;
+        }
+
+        if (!(reply = dbus_connection_send_with_reply_and_block(bus, m, -1, &error))) {
+                log_error("Failed to issue method call: %s", bus_error_message(&error));
+                r = -EIO;
+                goto finish;
+        }
+
+        if (!dbus_message_iter_init(reply, &iter) ||
+            dbus_message_iter_get_arg_type(&iter) != DBUS_TYPE_ARRAY ||
+            dbus_message_iter_get_element_type(&iter) != DBUS_TYPE_STRUCT)  {
+                log_error("Failed to parse reply.");
+                r = -EIO;
+                goto finish;
+        }
+
+        dbus_message_iter_recurse(&iter, &sub);
+
+        if (on_tty())
+                printf("%4s %-25s %-15s %-7s\n", "JOB", "UNIT", "TYPE", "STATE");
+
+        while (dbus_message_iter_get_arg_type(&sub) != DBUS_TYPE_INVALID) {
+                const char *name, *type, *state, *job_path, *unit_path;
+                uint32_t id;
+                char *e;
+
+                if (dbus_message_iter_get_arg_type(&sub) != DBUS_TYPE_STRUCT) {
+                        log_error("Failed to parse reply.");
+                        r = -EIO;
+                        goto finish;
+                }
+
+                dbus_message_iter_recurse(&sub, &sub2);
+
+                if (bus_iter_get_basic_and_next(&sub2, DBUS_TYPE_UINT32, &id, true) < 0 ||
+                    bus_iter_get_basic_and_next(&sub2, DBUS_TYPE_STRING, &name, true) < 0 ||
+                    bus_iter_get_basic_and_next(&sub2, DBUS_TYPE_STRING, &type, true) < 0 ||
+                    bus_iter_get_basic_and_next(&sub2, DBUS_TYPE_STRING, &state, true) < 0 ||
+                    bus_iter_get_basic_and_next(&sub2, DBUS_TYPE_OBJECT_PATH, &job_path, true) < 0 ||
+                    bus_iter_get_basic_and_next(&sub2, DBUS_TYPE_OBJECT_PATH, &unit_path, false) < 0) {
+                        log_error("Failed to parse reply.");
+                        r = -EIO;
+                        goto finish;
+                }
+
+                e = arg_full ? NULL : ellipsize(name, 25, 33);
+                printf("%4u %-25s %-15s %-7s\n", id, e ? e : name, type, state);
+                free(e);
+
+                k++;
+
+                dbus_message_iter_next(&sub);
+        }
+
+        if (on_tty())
+                printf("\n%u jobs listed.\n", k);
+
+        r = 0;
+
+finish:
+        if (m)
+                dbus_message_unref(m);
+
+        if (reply)
+                dbus_message_unref(reply);
+
+        dbus_error_free(&error);
+
+        return r;
+}
+
+static int load_unit(DBusConnection *bus, char **args) {
+        DBusMessage *m = NULL;
+        DBusError error;
+        int r;
+        char **name;
+
+        dbus_error_init(&error);
+
+        assert(bus);
+        assert(args);
+
+        STRV_FOREACH(name, args+1) {
+                DBusMessage *reply;
+
+                if (!(m = dbus_message_new_method_call(
+                                      "org.freedesktop.systemd1",
+                                      "/org/freedesktop/systemd1",
+                                      "org.freedesktop.systemd1.Manager",
+                                      "LoadUnit"))) {
+                        log_error("Could not allocate message.");
+                        r = -ENOMEM;
+                        goto finish;
+                }
+
+                if (!dbus_message_append_args(m,
+                                              DBUS_TYPE_STRING, name,
+                                              DBUS_TYPE_INVALID)) {
+                        log_error("Could not append arguments to message.");
+                        r = -ENOMEM;
+                        goto finish;
+                }
+
+                if (!(reply = dbus_connection_send_with_reply_and_block(bus, m, -1, &error))) {
+                        log_error("Failed to issue method call: %s", bus_error_message(&error));
+                        r = -EIO;
+                        goto finish;
+                }
+
+                dbus_message_unref(m);
+                dbus_message_unref(reply);
+
+                m = reply = NULL;
+        }
+
+        r = 0;
+
+finish:
+        if (m)
+                dbus_message_unref(m);
+
+        dbus_error_free(&error);
+
+        return r;
+}
+
+static int cancel_job(DBusConnection *bus, char **args) {
+        DBusMessage *m = NULL, *reply = NULL;
+        DBusError error;
+        int r;
+        char **name;
+
+        dbus_error_init(&error);
+
+        assert(bus);
+        assert(args);
+
+        if (strv_length(args) <= 1)
+                return daemon_reload(bus, args);
+
+        STRV_FOREACH(name, args+1) {
+                unsigned id;
+                const char *path;
+
+                if (!(m = dbus_message_new_method_call(
+                                      "org.freedesktop.systemd1",
+                                      "/org/freedesktop/systemd1",
+                                      "org.freedesktop.systemd1.Manager",
+                                      "GetJob"))) {
+                        log_error("Could not allocate message.");
+                        r = -ENOMEM;
+                        goto finish;
+                }
+
+                if ((r = safe_atou(*name, &id)) < 0) {
+                        log_error("Failed to parse job id: %s", strerror(-r));
+                        goto finish;
+                }
+
+                assert_cc(sizeof(uint32_t) == sizeof(id));
+                if (!dbus_message_append_args(m,
+                                              DBUS_TYPE_UINT32, &id,
+                                              DBUS_TYPE_INVALID)) {
+                        log_error("Could not append arguments to message.");
+                        r = -ENOMEM;
+                        goto finish;
+                }
+
+                if (!(reply = dbus_connection_send_with_reply_and_block(bus, m, -1, &error))) {
+                        log_error("Failed to issue method call: %s", bus_error_message(&error));
+                        r = -EIO;
+                        goto finish;
+                }
+
+                if (!dbus_message_get_args(reply, &error,
+                                           DBUS_TYPE_OBJECT_PATH, &path,
+                                           DBUS_TYPE_INVALID)) {
+                        log_error("Failed to parse reply: %s", bus_error_message(&error));
+                        r = -EIO;
+                        goto finish;
+                }
+
+                dbus_message_unref(m);
+                if (!(m = dbus_message_new_method_call(
+                                      "org.freedesktop.systemd1",
+                                      path,
+                                      "org.freedesktop.systemd1.Job",
+                                      "Cancel"))) {
+                        log_error("Could not allocate message.");
+                        r = -ENOMEM;
+                        goto finish;
+                }
+
+                dbus_message_unref(reply);
+                if (!(reply = dbus_connection_send_with_reply_and_block(bus, m, -1, &error))) {
+                        log_error("Failed to issue method call: %s", bus_error_message(&error));
+                        r = -EIO;
+                        goto finish;
+                }
+
+                dbus_message_unref(m);
+                dbus_message_unref(reply);
+                m = reply = NULL;
+        }
+
+        r = 0;
+
+finish:
+        if (m)
+                dbus_message_unref(m);
+
+        if (reply)
+                dbus_message_unref(reply);
+
+        dbus_error_free(&error);
+
+        return r;
+}
+
+static bool need_daemon_reload(DBusConnection *bus, const char *unit) {
+        DBusMessage *m = NULL, *reply = NULL;
+        dbus_bool_t b = FALSE;
+        DBusMessageIter iter, sub;
+        const char
+                *interface = "org.freedesktop.systemd1.Unit",
+                *property = "NeedDaemonReload",
+                *path;
+
+        /* We ignore all errors here, since this is used to show a warning only */
+
+        if (!(m = dbus_message_new_method_call(
+                              "org.freedesktop.systemd1",
+                              "/org/freedesktop/systemd1",
+                              "org.freedesktop.systemd1.Manager",
+                              "GetUnit")))
+                goto finish;
+
+        if (!dbus_message_append_args(m,
+                                      DBUS_TYPE_STRING, &unit,
+                                      DBUS_TYPE_INVALID))
+                goto finish;
+
+        if (!(reply = dbus_connection_send_with_reply_and_block(bus, m, -1, NULL)))
+                goto finish;
+
+        if (!dbus_message_get_args(reply, NULL,
+                                   DBUS_TYPE_OBJECT_PATH, &path,
+                                   DBUS_TYPE_INVALID))
+                goto finish;
+
+        dbus_message_unref(m);
+        if (!(m = dbus_message_new_method_call(
+                              "org.freedesktop.systemd1",
+                              path,
+                              "org.freedesktop.DBus.Properties",
+                              "Get")))
+                goto finish;
+
+        if (!dbus_message_append_args(m,
+                                      DBUS_TYPE_STRING, &interface,
+                                      DBUS_TYPE_STRING, &property,
+                                      DBUS_TYPE_INVALID)) {
+                goto finish;
+        }
+
+        dbus_message_unref(reply);
+        if (!(reply = dbus_connection_send_with_reply_and_block(bus, m, -1, NULL)))
+                goto finish;
+
+        if (!dbus_message_iter_init(reply, &iter) ||
+            dbus_message_iter_get_arg_type(&iter) != DBUS_TYPE_VARIANT)
+                goto finish;
+
+        dbus_message_iter_recurse(&iter, &sub);
+
+        if (dbus_message_iter_get_arg_type(&sub) != DBUS_TYPE_BOOLEAN)
+                goto finish;
+
+        dbus_message_iter_get_basic(&sub, &b);
+
+finish:
+        if (m)
+                dbus_message_unref(m);
+
+        if (reply)
+                dbus_message_unref(reply);
+
+        return b;
+}
+
+typedef struct WaitData {
+        Set *set;
+        char *result;
+} WaitData;
+
+static DBusHandlerResult wait_filter(DBusConnection *connection, DBusMessage *message, void *data) {
+        DBusError error;
+        WaitData *d = data;
+
+        assert(connection);
+        assert(message);
+        assert(d);
+
+        dbus_error_init(&error);
+
+        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_error("Warning! D-Bus connection terminated.");
+                dbus_connection_close(connection);
+
+        } else if (dbus_message_is_signal(message, "org.freedesktop.systemd1.Manager", "JobRemoved")) {
+                uint32_t id;
+                const char *path, *result;
+                dbus_bool_t success = true;
+
+                if (dbus_message_get_args(message, &error,
+                                          DBUS_TYPE_UINT32, &id,
+                                          DBUS_TYPE_OBJECT_PATH, &path,
+                                          DBUS_TYPE_STRING, &result,
+                                          DBUS_TYPE_INVALID)) {
+                        char *p;
+
+                        if ((p = set_remove(d->set, (char*) path)))
+                                free(p);
+
+                        if (*result)
+                                d->result = strdup(result);
+
+                        goto finish;
+                }
+#ifndef LEGACY
+                dbus_error_free(&error);
+
+                if (dbus_message_get_args(message, &error,
+                                          DBUS_TYPE_UINT32, &id,
+                                          DBUS_TYPE_OBJECT_PATH, &path,
+                                          DBUS_TYPE_BOOLEAN, &success,
+                                          DBUS_TYPE_INVALID)) {
+                        char *p;
+
+                        /* Compatibility with older systemd versions <
+                         * 19 during upgrades. This should be dropped
+                         * one day */
+
+                        if ((p = set_remove(d->set, (char*) path)))
+                                free(p);
+
+                        if (!success)
+                                d->result = strdup("failed");
+
+                        goto finish;
+                }
+#endif
+
+                log_error("Failed to parse message: %s", bus_error_message(&error));
+        }
+
+finish:
+        dbus_error_free(&error);
+        return DBUS_HANDLER_RESULT_NOT_YET_HANDLED;
+}
+
+static int enable_wait_for_jobs(DBusConnection *bus) {
+        DBusError error;
+
+        assert(bus);
+
+        if (private_bus)
+                return 0;
+
+        dbus_error_init(&error);
+        dbus_bus_add_match(bus,
+                           "type='signal',"
+                           "sender='org.freedesktop.systemd1',"
+                           "interface='org.freedesktop.systemd1.Manager',"
+                           "member='JobRemoved',"
+                           "path='/org/freedesktop/systemd1'",
+                           &error);
+
+        if (dbus_error_is_set(&error)) {
+                log_error("Failed to add match: %s", bus_error_message(&error));
+                dbus_error_free(&error);
+                return -EIO;
+        }
+
+        /* This is slightly dirty, since we don't undo the match registrations. */
+        return 0;
+}
+
+static int wait_for_jobs(DBusConnection *bus, Set *s) {
+        int r;
+        WaitData d;
+
+        assert(bus);
+        assert(s);
+
+        zero(d);
+        d.set = s;
+
+        if (!dbus_connection_add_filter(bus, wait_filter, &d, NULL)) {
+                log_error("Failed to add filter.");
+                r = -ENOMEM;
+                goto finish;
+        }
+
+        while (!set_isempty(s) &&
+               dbus_connection_read_write_dispatch(bus, -1))
+                ;
+
+        if (!arg_quiet && d.result) {
+                if (streq(d.result, "timeout"))
+                        log_error("Job timed out.");
+                else if (streq(d.result, "canceled"))
+                        log_error("Job canceled.");
+                else if (streq(d.result, "dependency"))
+                        log_error("A dependency job failed. See system journal for details.");
+                else if (!streq(d.result, "done") && !streq(d.result, "skipped"))
+                        log_error("Job failed. See system journal and 'systemctl status' for details.");
+        }
+
+        if (streq_ptr(d.result, "timeout"))
+                r = -ETIME;
+        else if (streq_ptr(d.result, "canceled"))
+                r = -ECANCELED;
+        else if (!streq_ptr(d.result, "done") && !streq_ptr(d.result, "skipped"))
+                r = -EIO;
+        else
+                r = 0;
+
+        free(d.result);
+
+finish:
+        /* This is slightly dirty, since we don't undo the filter registration. */
+
+        return r;
+}
+
+static int start_unit_one(
+                DBusConnection *bus,
+                const char *method,
+                const char *name,
+                const char *mode,
+                DBusError *error,
+                Set *s) {
+
+        DBusMessage *m = NULL, *reply = NULL;
+        const char *path;
+        int r;
+
+        assert(bus);
+        assert(method);
+        assert(name);
+        assert(mode);
+        assert(error);
+        assert(arg_no_block || s);
+
+        if (!(m = dbus_message_new_method_call(
+                              "org.freedesktop.systemd1",
+                              "/org/freedesktop/systemd1",
+                              "org.freedesktop.systemd1.Manager",
+                              method))) {
+                log_error("Could not allocate message.");
+                r = -ENOMEM;
+                goto finish;
+        }
+
+        if (!dbus_message_append_args(m,
+                                      DBUS_TYPE_STRING, &name,
+                                      DBUS_TYPE_STRING, &mode,
+                                      DBUS_TYPE_INVALID)) {
+                log_error("Could not append arguments to message.");
+                r = -ENOMEM;
+                goto finish;
+        }
+
+        if (!(reply = dbus_connection_send_with_reply_and_block(bus, m, -1, error))) {
+
+                if (arg_action != ACTION_SYSTEMCTL && error_is_no_service(error)) {
+                        /* There's always a fallback possible for
+                         * legacy actions. */
+                        r = -EADDRNOTAVAIL;
+                        goto finish;
+                }
+
+                log_error("Failed to issue method call: %s", bus_error_message(error));
+                r = -EIO;
+                goto finish;
+        }
+
+        if (!dbus_message_get_args(reply, error,
+                                   DBUS_TYPE_OBJECT_PATH, &path,
+                                   DBUS_TYPE_INVALID)) {
+                log_error("Failed to parse reply: %s", bus_error_message(error));
+                r = -EIO;
+                goto finish;
+        }
+
+        if (need_daemon_reload(bus, name))
+                log_warning("Warning: Unit file of created job changed on disk, 'systemctl %s daemon-reload' recommended.",
+                            arg_scope == UNIT_FILE_SYSTEM ? "--system" : "--user");
+
+        if (!arg_no_block) {
+                char *p;
+
+                if (!(p = strdup(path))) {
+                        log_error("Failed to duplicate path.");
+                        r = -ENOMEM;
+                        goto finish;
+                }
+
+                if ((r = set_put(s, p)) < 0) {
+                        free(p);
+                        log_error("Failed to add path to set.");
+                        goto finish;
+                }
+        }
+
+        r = 0;
+
+finish:
+        if (m)
+                dbus_message_unref(m);
+
+        if (reply)
+                dbus_message_unref(reply);
+
+        return r;
+}
+
+static enum action verb_to_action(const char *verb) {
+        if (streq(verb, "halt"))
+                return ACTION_HALT;
+        else if (streq(verb, "poweroff"))
+                return ACTION_POWEROFF;
+        else if (streq(verb, "reboot"))
+                return ACTION_REBOOT;
+        else if (streq(verb, "kexec"))
+                return ACTION_KEXEC;
+        else if (streq(verb, "rescue"))
+                return ACTION_RESCUE;
+        else if (streq(verb, "emergency"))
+                return ACTION_EMERGENCY;
+        else if (streq(verb, "default"))
+                return ACTION_DEFAULT;
+        else if (streq(verb, "exit"))
+                return ACTION_EXIT;
+        else
+                return ACTION_INVALID;
+}
+
+static int start_unit(DBusConnection *bus, char **args) {
+
+        static const char * const table[_ACTION_MAX] = {
+                [ACTION_HALT] = SPECIAL_HALT_TARGET,
+                [ACTION_POWEROFF] = SPECIAL_POWEROFF_TARGET,
+                [ACTION_REBOOT] = SPECIAL_REBOOT_TARGET,
+                [ACTION_KEXEC] = SPECIAL_KEXEC_TARGET,
+                [ACTION_RUNLEVEL2] = SPECIAL_RUNLEVEL2_TARGET,
+                [ACTION_RUNLEVEL3] = SPECIAL_RUNLEVEL3_TARGET,
+                [ACTION_RUNLEVEL4] = SPECIAL_RUNLEVEL4_TARGET,
+                [ACTION_RUNLEVEL5] = SPECIAL_RUNLEVEL5_TARGET,
+                [ACTION_RESCUE] = SPECIAL_RESCUE_TARGET,
+                [ACTION_EMERGENCY] = SPECIAL_EMERGENCY_TARGET,
+                [ACTION_DEFAULT] = SPECIAL_DEFAULT_TARGET,
+                [ACTION_EXIT] = SPECIAL_EXIT_TARGET
+        };
+
+        int r, ret = 0;
+        const char *method, *mode, *one_name;
+        Set *s = NULL;
+        DBusError error;
+        char **name;
+
+        dbus_error_init(&error);
+
+        assert(bus);
+
+        agent_open_if_enabled();
+
+        if (arg_action == ACTION_SYSTEMCTL) {
+                method =
+                        streq(args[0], "stop") ||
+                        streq(args[0], "condstop")              ? "StopUnit" :
+                        streq(args[0], "reload")                ? "ReloadUnit" :
+                        streq(args[0], "restart")               ? "RestartUnit" :
+
+                        streq(args[0], "try-restart")           ||
+                        streq(args[0], "condrestart")           ? "TryRestartUnit" :
+
+                        streq(args[0], "reload-or-restart")     ? "ReloadOrRestartUnit" :
+
+                        streq(args[0], "reload-or-try-restart") ||
+                        streq(args[0], "condreload") ||
+
+                        streq(args[0], "force-reload")          ? "ReloadOrTryRestartUnit" :
+                                                                  "StartUnit";
+
+                mode =
+                        (streq(args[0], "isolate") ||
+                         streq(args[0], "rescue")  ||
+                         streq(args[0], "emergency")) ? "isolate" : arg_job_mode;
+
+                one_name = table[verb_to_action(args[0])];
+
+        } else {
+                assert(arg_action < ELEMENTSOF(table));
+                assert(table[arg_action]);
+
+                method = "StartUnit";
+
+                mode = (arg_action == ACTION_EMERGENCY ||
+                        arg_action == ACTION_RESCUE ||
+                        arg_action == ACTION_RUNLEVEL2 ||
+                        arg_action == ACTION_RUNLEVEL3 ||
+                        arg_action == ACTION_RUNLEVEL4 ||
+                        arg_action == ACTION_RUNLEVEL5) ? "isolate" : "replace";
+
+                one_name = table[arg_action];
+        }
+
+        if (!arg_no_block) {
+                if ((ret = enable_wait_for_jobs(bus)) < 0) {
+                        log_error("Could not watch jobs: %s", strerror(-ret));
+                        goto finish;
+                }
+
+                if (!(s = set_new(string_hash_func, string_compare_func))) {
+                        log_error("Failed to allocate set.");
+                        ret = -ENOMEM;
+                        goto finish;
+                }
+        }
+
+        if (one_name) {
+                if ((ret = start_unit_one(bus, method, one_name, mode, &error, s)) <= 0)
+                        goto finish;
+        } else {
+                STRV_FOREACH(name, args+1)
+                        if ((r = start_unit_one(bus, method, *name, mode, &error, s)) != 0) {
+                                ret = translate_bus_error_to_exit_status(r, &error);
+                                dbus_error_free(&error);
+                        }
+        }
+
+        if (!arg_no_block)
+                if ((r = wait_for_jobs(bus, s)) < 0) {
+                        ret = r;
+                        goto finish;
+                }
+
+finish:
+        if (s)
+                set_free_free(s);
+
+        dbus_error_free(&error);
+
+        return ret;
+}
+
+/* ask systemd-logind, which might grant access to unprivileged users through polkit */
+static int reboot_with_logind(DBusConnection *bus, enum action a) {
+#ifdef HAVE_LOGIND
+        const char *method;
+        DBusMessage *m = NULL, *reply = NULL;
+        DBusError error;
+        dbus_bool_t interactive = true;
+        int r;
+
+        dbus_error_init(&error);
+
+        switch (a) {
+
+        case ACTION_REBOOT:
+                method = "Reboot";
+                break;
+
+        case ACTION_POWEROFF:
+                method = "PowerOff";
+                break;
+
+        default:
+                return -EINVAL;
+        }
+
+        m = dbus_message_new_method_call(
+                                "org.freedesktop.login1",
+                                "/org/freedesktop/login1",
+                                "org.freedesktop.login1.Manager",
+                                method);
+        if (!m) {
+                log_error("Could not allocate message.");
+                r = -ENOMEM;
+                goto finish;
+        }
+
+        if (!dbus_message_append_args(m,
+                                      DBUS_TYPE_BOOLEAN, &interactive,
+                                      DBUS_TYPE_INVALID)) {
+                log_error("Could not append arguments to message.");
+                r = -ENOMEM;
+                goto finish;
+        }
+
+        reply = dbus_connection_send_with_reply_and_block(bus, m, -1, &error);
+        if (!reply) {
+                if (error_is_no_service(&error)) {
+                        log_debug("Failed to issue method call: %s", bus_error_message(&error));
+                        r = -ENOENT;
+                        goto finish;
+                }
+
+                if (dbus_error_has_name(&error, DBUS_ERROR_ACCESS_DENIED)) {
+                        log_debug("Failed to issue method call: %s", bus_error_message(&error));
+                        r = -EACCES;
+                        goto finish;
+                }
+
+                log_info("Failed to issue method call: %s", bus_error_message(&error));
+                r = -EIO;
+                goto finish;
+        }
+
+        r = 0;
+
+finish:
+        if (m)
+                dbus_message_unref(m);
+
+        if (reply)
+                dbus_message_unref(reply);
+
+        dbus_error_free(&error);
+
+        return r;
+#else
+        return -ENOSYS;
+#endif
+}
+
+static int start_special(DBusConnection *bus, char **args) {
+        enum action a;
+        int r;
+
+        assert(bus);
+        assert(args);
+
+        a = verb_to_action(args[0]);
+
+        if (arg_force >= 2 && a == ACTION_HALT)
+                halt_now(ACTION_HALT);
+
+        if (arg_force >= 2 && a == ACTION_POWEROFF)
+                halt_now(ACTION_POWEROFF);
+
+        if (arg_force >= 2 && a == ACTION_REBOOT)
+                halt_now(ACTION_REBOOT);
+
+        if (arg_force &&
+            (a == ACTION_HALT ||
+             a == ACTION_POWEROFF ||
+             a == ACTION_REBOOT ||
+             a == ACTION_KEXEC ||
+             a == ACTION_EXIT))
+                return daemon_reload(bus, args);
+
+        if (geteuid() != 0) {
+                /* first try logind, to allow authentication with polkit */
+                if (a == ACTION_POWEROFF ||
+                    a == ACTION_REBOOT) {
+                        r = reboot_with_logind(bus, a);
+                        if (r >= 0)
+                                return r;
+                }
+        }
+
+        r = start_unit(bus, args);
+        if (r >= 0)
+                warn_wall(a);
+
+        return r;
+}
+
+static int check_unit(DBusConnection *bus, char **args) {
+        DBusMessage *m = NULL, *reply = NULL;
+        const char
+                *interface = "org.freedesktop.systemd1.Unit",
+                *property = "ActiveState";
+        int r = 3; /* According to LSB: "program is not running" */
+        DBusError error;
+        char **name;
+
+        assert(bus);
+        assert(args);
+
+        dbus_error_init(&error);
+
+        STRV_FOREACH(name, args+1) {
+                const char *path = NULL;
+                const char *state;
+                DBusMessageIter iter, sub;
+
+                if (!(m = dbus_message_new_method_call(
+                                      "org.freedesktop.systemd1",
+                                      "/org/freedesktop/systemd1",
+                                      "org.freedesktop.systemd1.Manager",
+                                      "GetUnit"))) {
+                        log_error("Could not allocate message.");
+                        r = -ENOMEM;
+                        goto finish;
+                }
+
+                if (!dbus_message_append_args(m,
+                                              DBUS_TYPE_STRING, name,
+                                              DBUS_TYPE_INVALID)) {
+                        log_error("Could not append arguments to message.");
+                        r = -ENOMEM;
+                        goto finish;
+                }
+
+                if (!(reply = dbus_connection_send_with_reply_and_block(bus, m, -1, &error))) {
+
+                        /* Hmm, cannot figure out anything about this unit... */
+                        if (!arg_quiet)
+                                puts("unknown");
+
+                        dbus_error_free(&error);
+                        dbus_message_unref(m);
+                        m = NULL;
+                        continue;
+                }
+
+                if (!dbus_message_get_args(reply, &error,
+                                           DBUS_TYPE_OBJECT_PATH, &path,
+                                           DBUS_TYPE_INVALID)) {
+                        log_error("Failed to parse reply: %s", bus_error_message(&error));
+                        r = -EIO;
+                        goto finish;
+                }
+
+                dbus_message_unref(m);
+                if (!(m = dbus_message_new_method_call(
+                                      "org.freedesktop.systemd1",
+                                      path,
+                                      "org.freedesktop.DBus.Properties",
+                                      "Get"))) {
+                        log_error("Could not allocate message.");
+                        r = -ENOMEM;
+                        goto finish;
+                }
+
+                if (!dbus_message_append_args(m,
+                                              DBUS_TYPE_STRING, &interface,
+                                              DBUS_TYPE_STRING, &property,
+                                              DBUS_TYPE_INVALID)) {
+                        log_error("Could not append arguments to message.");
+                        r = -ENOMEM;
+                        goto finish;
+                }
+
+                dbus_message_unref(reply);
+                if (!(reply = dbus_connection_send_with_reply_and_block(bus, m, -1, &error))) {
+                        log_error("Failed to issue method call: %s", bus_error_message(&error));
+                        r = -EIO;
+                        goto finish;
+                }
+
+                if (!dbus_message_iter_init(reply, &iter) ||
+                    dbus_message_iter_get_arg_type(&iter) != DBUS_TYPE_VARIANT)  {
+                        log_error("Failed to parse reply.");
+                        r = -EIO;
+                        goto finish;
+                }
+
+                dbus_message_iter_recurse(&iter, &sub);
+
+                if (dbus_message_iter_get_arg_type(&sub) != DBUS_TYPE_STRING)  {
+                        log_error("Failed to parse reply.");
+                        r = -EIO;
+                        goto finish;
+                }
+
+                dbus_message_iter_get_basic(&sub, &state);
+
+                if (!arg_quiet)
+                        puts(state);
+
+                if (streq(state, "active") || streq(state, "reloading"))
+                        r = 0;
+
+                dbus_message_unref(m);
+                dbus_message_unref(reply);
+                m = reply = NULL;
+        }
+
+finish:
+        if (m)
+                dbus_message_unref(m);
+
+        if (reply)
+                dbus_message_unref(reply);
+
+        dbus_error_free(&error);
+
+        return r;
+}
+
+static int kill_unit(DBusConnection *bus, char **args) {
+        DBusMessage *m = NULL;
+        int r = 0;
+        DBusError error;
+        char **name;
+
+        assert(bus);
+        assert(args);
+
+        dbus_error_init(&error);
+
+        if (!arg_kill_who)
+                arg_kill_who = "all";
+
+        if (!arg_kill_mode)
+                arg_kill_mode = streq(arg_kill_who, "all") ? "control-group" : "process";
+
+        STRV_FOREACH(name, args+1) {
+                DBusMessage *reply;
+
+                if (!(m = dbus_message_new_method_call(
+                                      "org.freedesktop.systemd1",
+                                      "/org/freedesktop/systemd1",
+                                      "org.freedesktop.systemd1.Manager",
+                                      "KillUnit"))) {
+                        log_error("Could not allocate message.");
+                        r = -ENOMEM;
+                        goto finish;
+                }
+
+                if (!dbus_message_append_args(m,
+                                              DBUS_TYPE_STRING, name,
+                                              DBUS_TYPE_STRING, &arg_kill_who,
+                                              DBUS_TYPE_STRING, &arg_kill_mode,
+                                              DBUS_TYPE_INT32, &arg_signal,
+                                              DBUS_TYPE_INVALID)) {
+                        log_error("Could not append arguments to message.");
+                        r = -ENOMEM;
+                        goto finish;
+                }
+
+                if (!(reply = dbus_connection_send_with_reply_and_block(bus, m, -1, &error))) {
+                        log_error("Failed to issue method call: %s", bus_error_message(&error));
+                        dbus_error_free(&error);
+                        r = -EIO;
+                }
+
+                dbus_message_unref(m);
+
+                if (reply)
+                        dbus_message_unref(reply);
+                m = reply = NULL;
+        }
+
+finish:
+        if (m)
+                dbus_message_unref(m);
+
+        dbus_error_free(&error);
+
+        return r;
+}
+
+typedef struct ExecStatusInfo {
+        char *name;
+
+        char *path;
+        char **argv;
+
+        bool ignore;
+
+        usec_t start_timestamp;
+        usec_t exit_timestamp;
+        pid_t pid;
+        int code;
+        int status;
+
+        LIST_FIELDS(struct ExecStatusInfo, exec);
+} ExecStatusInfo;
+
+static void exec_status_info_free(ExecStatusInfo *i) {
+        assert(i);
+
+        free(i->name);
+        free(i->path);
+        strv_free(i->argv);
+        free(i);
+}
+
+static int exec_status_info_deserialize(DBusMessageIter *sub, ExecStatusInfo *i) {
+        uint64_t start_timestamp, exit_timestamp, start_timestamp_monotonic, exit_timestamp_monotonic;
+        DBusMessageIter sub2, sub3;
+        const char*path;
+        unsigned n;
+        uint32_t pid;
+        int32_t code, status;
+        dbus_bool_t ignore;
+
+        assert(i);
+        assert(i);
+
+        if (dbus_message_iter_get_arg_type(sub) != DBUS_TYPE_STRUCT)
+                return -EIO;
+
+        dbus_message_iter_recurse(sub, &sub2);
+
+        if (bus_iter_get_basic_and_next(&sub2, DBUS_TYPE_STRING, &path, true) < 0)
+                return -EIO;
+
+        if (!(i->path = strdup(path)))
+                return -ENOMEM;
+
+        if (dbus_message_iter_get_arg_type(&sub2) != DBUS_TYPE_ARRAY ||
+            dbus_message_iter_get_element_type(&sub2) != DBUS_TYPE_STRING)
+                return -EIO;
+
+        n = 0;
+        dbus_message_iter_recurse(&sub2, &sub3);
+        while (dbus_message_iter_get_arg_type(&sub3) != DBUS_TYPE_INVALID) {
+                assert(dbus_message_iter_get_arg_type(&sub3) == DBUS_TYPE_STRING);
+                dbus_message_iter_next(&sub3);
+                n++;
+        }
+
+
+        if (!(i->argv = new0(char*, n+1)))
+                return -ENOMEM;
+
+        n = 0;
+        dbus_message_iter_recurse(&sub2, &sub3);
+        while (dbus_message_iter_get_arg_type(&sub3) != DBUS_TYPE_INVALID) {
+                const char *s;
+
+                assert(dbus_message_iter_get_arg_type(&sub3) == DBUS_TYPE_STRING);
+                dbus_message_iter_get_basic(&sub3, &s);
+                dbus_message_iter_next(&sub3);
+
+                if (!(i->argv[n++] = strdup(s)))
+                        return -ENOMEM;
+        }
+
+        if (!dbus_message_iter_next(&sub2) ||
+            bus_iter_get_basic_and_next(&sub2, DBUS_TYPE_BOOLEAN, &ignore, true) < 0 ||
+            bus_iter_get_basic_and_next(&sub2, DBUS_TYPE_UINT64, &start_timestamp, true) < 0 ||
+            bus_iter_get_basic_and_next(&sub2, DBUS_TYPE_UINT64, &start_timestamp_monotonic, true) < 0 ||
+            bus_iter_get_basic_and_next(&sub2, DBUS_TYPE_UINT64, &exit_timestamp, true) < 0 ||
+            bus_iter_get_basic_and_next(&sub2, DBUS_TYPE_UINT64, &exit_timestamp_monotonic, true) < 0 ||
+            bus_iter_get_basic_and_next(&sub2, DBUS_TYPE_UINT32, &pid, true) < 0 ||
+            bus_iter_get_basic_and_next(&sub2, DBUS_TYPE_INT32, &code, true) < 0 ||
+            bus_iter_get_basic_and_next(&sub2, DBUS_TYPE_INT32, &status, false) < 0)
+                return -EIO;
+
+        i->ignore = ignore;
+        i->start_timestamp = (usec_t) start_timestamp;
+        i->exit_timestamp = (usec_t) exit_timestamp;
+        i->pid = (pid_t) pid;
+        i->code = code;
+        i->status = status;
+
+        return 0;
+}
+
+typedef struct UnitStatusInfo {
+        const char *id;
+        const char *load_state;
+        const char *active_state;
+        const char *sub_state;
+        const char *unit_file_state;
+
+        const char *description;
+        const char *following;
+
+        const char *path;
+        const char *default_control_group;
+
+        const char *load_error;
+        const char *result;
+
+        usec_t inactive_exit_timestamp;
+        usec_t inactive_exit_timestamp_monotonic;
+        usec_t active_enter_timestamp;
+        usec_t active_exit_timestamp;
+        usec_t inactive_enter_timestamp;
+
+        bool need_daemon_reload;
+
+        /* Service */
+        pid_t main_pid;
+        pid_t control_pid;
+        const char *status_text;
+        bool running:1;
+#ifdef HAVE_SYSV_COMPAT
+        bool is_sysv:1;
+#endif
+
+        usec_t start_timestamp;
+        usec_t exit_timestamp;
+
+        int exit_code, exit_status;
+
+        usec_t condition_timestamp;
+        bool condition_result;
+
+        /* Socket */
+        unsigned n_accepted;
+        unsigned n_connections;
+        bool accept;
+
+        /* Device */
+        const char *sysfs_path;
+
+        /* Mount, Automount */
+        const char *where;
+
+        /* Swap */
+        const char *what;
+
+        LIST_HEAD(ExecStatusInfo, exec);
+} UnitStatusInfo;
+
+static void print_status_info(UnitStatusInfo *i) {
+        ExecStatusInfo *p;
+        const char *on, *off, *ss;
+        usec_t timestamp;
+        char since1[FORMAT_TIMESTAMP_PRETTY_MAX], *s1;
+        char since2[FORMAT_TIMESTAMP_MAX], *s2;
+
+        assert(i);
+
+        /* This shows pretty information about a unit. See
+         * print_property() for a low-level property printer */
+
+        printf("%s", strna(i->id));
+
+        if (i->description && !streq_ptr(i->id, i->description))
+                printf(" - %s", i->description);
+
+        printf("\n");
+
+        if (i->following)
+                printf("\t  Follow: unit currently follows state of %s\n", i->following);
+
+        if (streq_ptr(i->load_state, "error")) {
+                on = ansi_highlight_red(true);
+                off = ansi_highlight_red(false);
+        } else
+                on = off = "";
+
+        if (i->load_error)
+                printf("\t  Loaded: %s%s%s (Reason: %s)\n", on, strna(i->load_state), off, i->load_error);
+        else if (i->path && i->unit_file_state)
+                printf("\t  Loaded: %s%s%s (%s; %s)\n", on, strna(i->load_state), off, i->path, i->unit_file_state);
+        else if (i->path)
+                printf("\t  Loaded: %s%s%s (%s)\n", on, strna(i->load_state), off, i->path);
+        else
+                printf("\t  Loaded: %s%s%s\n", on, strna(i->load_state), off);
+
+        ss = streq_ptr(i->active_state, i->sub_state) ? NULL : i->sub_state;
+
+        if (streq_ptr(i->active_state, "failed")) {
+                on = ansi_highlight_red(true);
+                off = ansi_highlight_red(false);
+        } else if (streq_ptr(i->active_state, "active") || streq_ptr(i->active_state, "reloading")) {
+                on = ansi_highlight_green(true);
+                off = ansi_highlight_green(false);
+        } else
+                on = off = "";
+
+        if (ss)
+                printf("\t  Active: %s%s (%s)%s",
+                       on,
+                       strna(i->active_state),
+                       ss,
+                       off);
+        else
+                printf("\t  Active: %s%s%s",
+                       on,
+                       strna(i->active_state),
+                       off);
+
+        if (!isempty(i->result) && !streq(i->result, "success"))
+                printf(" (Result: %s)", i->result);
+
+        timestamp = (streq_ptr(i->active_state, "active")      ||
+                     streq_ptr(i->active_state, "reloading"))   ? i->active_enter_timestamp :
+                    (streq_ptr(i->active_state, "inactive")    ||
+                     streq_ptr(i->active_state, "failed"))      ? i->inactive_enter_timestamp :
+                    streq_ptr(i->active_state, "activating")    ? i->inactive_exit_timestamp :
+                                                                  i->active_exit_timestamp;
+
+        s1 = format_timestamp_pretty(since1, sizeof(since1), timestamp);
+        s2 = format_timestamp(since2, sizeof(since2), timestamp);
+
+        if (s1)
+                printf(" since %s; %s\n", s2, s1);
+        else if (s2)
+                printf(" since %s\n", s2);
+        else
+                printf("\n");
+
+        if (!i->condition_result && i->condition_timestamp > 0) {
+                s1 = format_timestamp_pretty(since1, sizeof(since1), i->condition_timestamp);
+                s2 = format_timestamp(since2, sizeof(since2), i->condition_timestamp);
+
+                if (s1)
+                        printf("\t          start condition failed at %s; %s\n", s2, s1);
+                else if (s2)
+                        printf("\t          start condition failed at %s\n", s2);
+        }
+
+        if (i->sysfs_path)
+                printf("\t  Device: %s\n", i->sysfs_path);
+        if (i->where)
+                printf("\t   Where: %s\n", i->where);
+        if (i->what)
+                printf("\t    What: %s\n", i->what);
+
+        if (i->accept)
+                printf("\tAccepted: %u; Connected: %u\n", i->n_accepted, i->n_connections);
+
+        LIST_FOREACH(exec, p, i->exec) {
+                char *t;
+                bool good;
+
+                /* Only show exited processes here */
+                if (p->code == 0)
+                        continue;
+
+                t = strv_join(p->argv, " ");
+                printf("\t Process: %u %s=%s ", p->pid, p->name, strna(t));
+                free(t);
+
+#ifdef HAVE_SYSV_COMPAT
+                if (i->is_sysv)
+                        good = is_clean_exit_lsb(p->code, p->status);
+                else
+#endif
+                        good = is_clean_exit(p->code, p->status);
+
+                if (!good) {
+                        on = ansi_highlight_red(true);
+                        off = ansi_highlight_red(false);
+                } else
+                        on = off = "";
+
+                printf("%s(code=%s, ", on, sigchld_code_to_string(p->code));
+
+                if (p->code == CLD_EXITED) {
+                        const char *c;
+
+                        printf("status=%i", p->status);
+
+#ifdef HAVE_SYSV_COMPAT
+                        if ((c = exit_status_to_string(p->status, i->is_sysv ? EXIT_STATUS_LSB : EXIT_STATUS_SYSTEMD)))
+#else
+                        if ((c = exit_status_to_string(p->status, EXIT_STATUS_SYSTEMD)))
+#endif
+                                printf("/%s", c);
+
+                } else
+                        printf("signal=%s", signal_to_string(p->status));
+
+                printf(")%s\n", off);
+
+                if (i->main_pid == p->pid &&
+                    i->start_timestamp == p->start_timestamp &&
+                    i->exit_timestamp == p->start_timestamp)
+                        /* Let's not show this twice */
+                        i->main_pid = 0;
+
+                if (p->pid == i->control_pid)
+                        i->control_pid = 0;
+        }
+
+        if (i->main_pid > 0 || i->control_pid > 0) {
+                printf("\t");
+
+                if (i->main_pid > 0) {
+                        printf("Main PID: %u", (unsigned) i->main_pid);
+
+                        if (i->running) {
+                                char *t = NULL;
+                                get_process_comm(i->main_pid, &t);
+                                if (t) {
+                                        printf(" (%s)", t);
+                                        free(t);
+                                }
+                        } else if (i->exit_code > 0) {
+                                printf(" (code=%s, ", sigchld_code_to_string(i->exit_code));
+
+                                if (i->exit_code == CLD_EXITED) {
+                                        const char *c;
+
+                                        printf("status=%i", i->exit_status);
+
+#ifdef HAVE_SYSV_COMPAT
+                                        if ((c = exit_status_to_string(i->exit_status, i->is_sysv ? EXIT_STATUS_LSB : EXIT_STATUS_SYSTEMD)))
+#else
+                                        if ((c = exit_status_to_string(i->exit_status, EXIT_STATUS_SYSTEMD)))
+#endif
+                                                printf("/%s", c);
+
+                                } else
+                                        printf("signal=%s", signal_to_string(i->exit_status));
+                                printf(")");
+                        }
+                }
+
+                if (i->main_pid > 0 && i->control_pid > 0)
+                        printf(";");
+
+                if (i->control_pid > 0) {
+                        char *t = NULL;
+
+                        printf(" Control: %u", (unsigned) i->control_pid);
+
+                        get_process_comm(i->control_pid, &t);
+                        if (t) {
+                                printf(" (%s)", t);
+                                free(t);
+                        }
+                }
+
+                printf("\n");
+        }
+
+        if (i->status_text)
+                printf("\t  Status: \"%s\"\n", i->status_text);
+
+        if (i->default_control_group) {
+                unsigned c;
+
+                printf("\t  CGroup: %s\n", i->default_control_group);
+
+                if (arg_transport != TRANSPORT_SSH) {
+                        if ((c = columns()) > 18)
+                                c -= 18;
+                        else
+                                c = 0;
+
+                        show_cgroup_by_path(i->default_control_group, "\t\t  ", c, false);
+                }
+        }
+
+        if (i->id && arg_transport != TRANSPORT_SSH) {
+                printf("\n");
+                show_journal_by_unit(i->id, arg_output, 0, i->inactive_exit_timestamp_monotonic, arg_lines, arg_all, arg_follow);
+        }
+
+        if (i->need_daemon_reload)
+                printf("\n%sWarning:%s Unit file changed on disk, 'systemctl %s daemon-reload' recommended.\n",
+                       ansi_highlight_red(true),
+                       ansi_highlight_red(false),
+                       arg_scope == UNIT_FILE_SYSTEM ? "--system" : "--user");
+}
+
+static int status_property(const char *name, DBusMessageIter *iter, UnitStatusInfo *i) {
+
+        assert(name);
+        assert(iter);
+        assert(i);
+
+        switch (dbus_message_iter_get_arg_type(iter)) {
+
+        case DBUS_TYPE_STRING: {
+                const char *s;
+
+                dbus_message_iter_get_basic(iter, &s);
+
+                if (!isempty(s)) {
+                        if (streq(name, "Id"))
+                                i->id = s;
+                        else if (streq(name, "LoadState"))
+                                i->load_state = s;
+                        else if (streq(name, "ActiveState"))
+                                i->active_state = s;
+                        else if (streq(name, "SubState"))
+                                i->sub_state = s;
+                        else if (streq(name, "Description"))
+                                i->description = s;
+                        else if (streq(name, "FragmentPath"))
+                                i->path = s;
+#ifdef HAVE_SYSV_COMPAT
+                        else if (streq(name, "SysVPath")) {
+                                i->is_sysv = true;
+                                i->path = s;
+                        }
+#endif
+                        else if (streq(name, "DefaultControlGroup"))
+                                i->default_control_group = s;
+                        else if (streq(name, "StatusText"))
+                                i->status_text = s;
+                        else if (streq(name, "SysFSPath"))
+                                i->sysfs_path = s;
+                        else if (streq(name, "Where"))
+                                i->where = s;
+                        else if (streq(name, "What"))
+                                i->what = s;
+                        else if (streq(name, "Following"))
+                                i->following = s;
+                        else if (streq(name, "UnitFileState"))
+                                i->unit_file_state = s;
+                        else if (streq(name, "Result"))
+                                i->result = s;
+                }
+
+                break;
+        }
+
+        case DBUS_TYPE_BOOLEAN: {
+                dbus_bool_t b;
+
+                dbus_message_iter_get_basic(iter, &b);
+
+                if (streq(name, "Accept"))
+                        i->accept = b;
+                else if (streq(name, "NeedDaemonReload"))
+                        i->need_daemon_reload = b;
+                else if (streq(name, "ConditionResult"))
+                        i->condition_result = b;
+
+                break;
+        }
+
+        case DBUS_TYPE_UINT32: {
+                uint32_t u;
+
+                dbus_message_iter_get_basic(iter, &u);
+
+                if (streq(name, "MainPID")) {
+                        if (u > 0) {
+                                i->main_pid = (pid_t) u;
+                                i->running = true;
+                        }
+                } else if (streq(name, "ControlPID"))
+                        i->control_pid = (pid_t) u;
+                else if (streq(name, "ExecMainPID")) {
+                        if (u > 0)
+                                i->main_pid = (pid_t) u;
+                } else if (streq(name, "NAccepted"))
+                        i->n_accepted = u;
+                else if (streq(name, "NConnections"))
+                        i->n_connections = u;
+
+                break;
+        }
+
+        case DBUS_TYPE_INT32: {
+                int32_t j;
+
+                dbus_message_iter_get_basic(iter, &j);
+
+                if (streq(name, "ExecMainCode"))
+                        i->exit_code = (int) j;
+                else if (streq(name, "ExecMainStatus"))
+                        i->exit_status = (int) j;
+
+                break;
+        }
+
+        case DBUS_TYPE_UINT64: {
+                uint64_t u;
+
+                dbus_message_iter_get_basic(iter, &u);
+
+                if (streq(name, "ExecMainStartTimestamp"))
+                        i->start_timestamp = (usec_t) u;
+                else if (streq(name, "ExecMainExitTimestamp"))
+                        i->exit_timestamp = (usec_t) u;
+                else if (streq(name, "ActiveEnterTimestamp"))
+                        i->active_enter_timestamp = (usec_t) u;
+                else if (streq(name, "InactiveEnterTimestamp"))
+                        i->inactive_enter_timestamp = (usec_t) u;
+                else if (streq(name, "InactiveExitTimestamp"))
+                        i->inactive_exit_timestamp = (usec_t) u;
+                else if (streq(name, "InactiveExitTimestampMonotonic"))
+                        i->inactive_exit_timestamp_monotonic = (usec_t) u;
+                else if (streq(name, "ActiveExitTimestamp"))
+                        i->active_exit_timestamp = (usec_t) u;
+                else if (streq(name, "ConditionTimestamp"))
+                        i->condition_timestamp = (usec_t) u;
+
+                break;
+        }
+
+        case DBUS_TYPE_ARRAY: {
+
+                if (dbus_message_iter_get_element_type(iter) == DBUS_TYPE_STRUCT &&
+                    startswith(name, "Exec")) {
+                        DBusMessageIter sub;
+
+                        dbus_message_iter_recurse(iter, &sub);
+                        while (dbus_message_iter_get_arg_type(&sub) == DBUS_TYPE_STRUCT) {
+                                ExecStatusInfo *info;
+                                int r;
+
+                                if (!(info = new0(ExecStatusInfo, 1)))
+                                        return -ENOMEM;
+
+                                if (!(info->name = strdup(name))) {
+                                        free(info);
+                                        return -ENOMEM;
+                                }
+
+                                if ((r = exec_status_info_deserialize(&sub, info)) < 0) {
+                                        free(info);
+                                        return r;
+                                }
+
+                                LIST_PREPEND(ExecStatusInfo, exec, i->exec, info);
+
+                                dbus_message_iter_next(&sub);
+                        }
+                }
+
+                break;
+        }
+
+        case DBUS_TYPE_STRUCT: {
+
+                if (streq(name, "LoadError")) {
+                        DBusMessageIter sub;
+                        const char *n, *message;
+                        int r;
+
+                        dbus_message_iter_recurse(iter, &sub);
+
+                        r = bus_iter_get_basic_and_next(&sub, DBUS_TYPE_STRING, &n, true);
+                        if (r < 0)
+                                return r;
+
+                        r = bus_iter_get_basic_and_next(&sub, DBUS_TYPE_STRING, &message, false);
+                        if (r < 0)
+                                return r;
+
+                        if (!isempty(message))
+                                i->load_error = message;
+                }
+
+                break;
+        }
+        }
+
+        return 0;
+}
+
+static int print_property(const char *name, DBusMessageIter *iter) {
+        assert(name);
+        assert(iter);
+
+        /* This is a low-level property printer, see
+         * print_status_info() for the nicer output */
+
+        if (arg_property && !strv_find(arg_property, name))
+                return 0;
+
+        switch (dbus_message_iter_get_arg_type(iter)) {
+
+        case DBUS_TYPE_STRUCT: {
+                DBusMessageIter sub;
+                dbus_message_iter_recurse(iter, &sub);
+
+                if (dbus_message_iter_get_arg_type(&sub) == DBUS_TYPE_UINT32 && streq(name, "Job")) {
+                        uint32_t u;
+
+                        dbus_message_iter_get_basic(&sub, &u);
+
+                        if (u)
+                                printf("%s=%u\n", name, (unsigned) u);
+                        else if (arg_all)
+                                printf("%s=\n", name);
+
+                        return 0;
+                } else if (dbus_message_iter_get_arg_type(&sub) == DBUS_TYPE_STRING && streq(name, "Unit")) {
+                        const char *s;
+
+                        dbus_message_iter_get_basic(&sub, &s);
+
+                        if (arg_all || s[0])
+                                printf("%s=%s\n", name, s);
+
+                        return 0;
+                } else if (dbus_message_iter_get_arg_type(&sub) == DBUS_TYPE_STRING && streq(name, "LoadError")) {
+                        const char *a = NULL, *b = NULL;
+
+                        if (bus_iter_get_basic_and_next(&sub, DBUS_TYPE_STRING, &a, true) >= 0)
+                                bus_iter_get_basic_and_next(&sub, DBUS_TYPE_STRING, &b, false);
+
+                        if (arg_all || !isempty(a) || !isempty(b))
+                                printf("%s=%s \"%s\"\n", name, strempty(a), strempty(b));
+
+                        return 0;
+                }
+
+                break;
+        }
+
+        case DBUS_TYPE_ARRAY:
+
+                if (dbus_message_iter_get_element_type(iter) == DBUS_TYPE_STRUCT && streq(name, "EnvironmentFiles")) {
+                        DBusMessageIter sub, sub2;
+
+                        dbus_message_iter_recurse(iter, &sub);
+                        while (dbus_message_iter_get_arg_type(&sub) == DBUS_TYPE_STRUCT) {
+                                const char *path;
+                                dbus_bool_t ignore;
+
+                                dbus_message_iter_recurse(&sub, &sub2);
+
+                                if (bus_iter_get_basic_and_next(&sub2, DBUS_TYPE_STRING, &path, true) >= 0 &&
+                                    bus_iter_get_basic_and_next(&sub2, DBUS_TYPE_BOOLEAN, &ignore, false) >= 0)
+                                        printf("EnvironmentFile=%s (ignore_errors=%s)\n", path, yes_no(ignore));
+
+                                dbus_message_iter_next(&sub);
+                        }
+
+                        return 0;
+
+                } else if (dbus_message_iter_get_element_type(iter) == DBUS_TYPE_STRUCT && streq(name, "Paths")) {
+                        DBusMessageIter sub, sub2;
+
+                        dbus_message_iter_recurse(iter, &sub);
+                        while (dbus_message_iter_get_arg_type(&sub) == DBUS_TYPE_STRUCT) {
+                                const char *type, *path;
+
+                                dbus_message_iter_recurse(&sub, &sub2);
+
+                                if (bus_iter_get_basic_and_next(&sub2, DBUS_TYPE_STRING, &type, true) >= 0 &&
+                                    bus_iter_get_basic_and_next(&sub2, DBUS_TYPE_STRING, &path, false) >= 0)
+                                        printf("%s=%s\n", type, path);
+
+                                dbus_message_iter_next(&sub);
+                        }
+
+                        return 0;
+
+                } else if (dbus_message_iter_get_element_type(iter) == DBUS_TYPE_STRUCT && streq(name, "Timers")) {
+                        DBusMessageIter sub, sub2;
+
+                        dbus_message_iter_recurse(iter, &sub);
+                        while (dbus_message_iter_get_arg_type(&sub) == DBUS_TYPE_STRUCT) {
+                                const char *base;
+                                uint64_t value, next_elapse;
+
+                                dbus_message_iter_recurse(&sub, &sub2);
+
+                                if (bus_iter_get_basic_and_next(&sub2, DBUS_TYPE_STRING, &base, true) >= 0 &&
+                                    bus_iter_get_basic_and_next(&sub2, DBUS_TYPE_UINT64, &value, true) >= 0 &&
+                                    bus_iter_get_basic_and_next(&sub2, DBUS_TYPE_UINT64, &next_elapse, false) >= 0) {
+                                        char timespan1[FORMAT_TIMESPAN_MAX], timespan2[FORMAT_TIMESPAN_MAX];
+
+                                        printf("%s={ value=%s ; next_elapse=%s }\n",
+                                               base,
+                                               format_timespan(timespan1, sizeof(timespan1), value),
+                                               format_timespan(timespan2, sizeof(timespan2), next_elapse));
+                                }
+
+                                dbus_message_iter_next(&sub);
+                        }
+
+                        return 0;
+
+                } else if (dbus_message_iter_get_element_type(iter) == DBUS_TYPE_STRUCT && streq(name, "ControlGroupAttributes")) {
+                        DBusMessageIter sub, sub2;
+
+                        dbus_message_iter_recurse(iter, &sub);
+                        while (dbus_message_iter_get_arg_type(&sub) == DBUS_TYPE_STRUCT) {
+                                const char *controller, *attr, *value;
+
+                                dbus_message_iter_recurse(&sub, &sub2);
+
+                                if (bus_iter_get_basic_and_next(&sub2, DBUS_TYPE_STRING, &controller, true) >= 0 &&
+                                    bus_iter_get_basic_and_next(&sub2, DBUS_TYPE_STRING, &attr, true) >= 0 &&
+                                    bus_iter_get_basic_and_next(&sub2, DBUS_TYPE_STRING, &value, false) >= 0) {
+
+                                        printf("ControlGroupAttribute={ controller=%s ; attribute=%s ; value=\"%s\" }\n",
+                                               controller,
+                                               attr,
+                                               value);
+                                }
+
+                                dbus_message_iter_next(&sub);
+                        }
+
+                        return 0;
+
+                } else if (dbus_message_iter_get_element_type(iter) == DBUS_TYPE_STRUCT && startswith(name, "Exec")) {
+                        DBusMessageIter sub;
+
+                        dbus_message_iter_recurse(iter, &sub);
+                        while (dbus_message_iter_get_arg_type(&sub) == DBUS_TYPE_STRUCT) {
+                                ExecStatusInfo info;
+
+                                zero(info);
+                                if (exec_status_info_deserialize(&sub, &info) >= 0) {
+                                        char timestamp1[FORMAT_TIMESTAMP_MAX], timestamp2[FORMAT_TIMESTAMP_MAX];
+                                        char *t;
+
+                                        t = strv_join(info.argv, " ");
+
+                                        printf("%s={ path=%s ; argv[]=%s ; ignore_errors=%s ; start_time=[%s] ; stop_time=[%s] ; pid=%u ; code=%s ; status=%i%s%s }\n",
+                                               name,
+                                               strna(info.path),
+                                               strna(t),
+                                               yes_no(info.ignore),
+                                               strna(format_timestamp(timestamp1, sizeof(timestamp1), info.start_timestamp)),
+                                               strna(format_timestamp(timestamp2, sizeof(timestamp2), info.exit_timestamp)),
+                                               (unsigned) info. pid,
+                                               sigchld_code_to_string(info.code),
+                                               info.status,
+                                               info.code == CLD_EXITED ? "" : "/",
+                                               strempty(info.code == CLD_EXITED ? NULL : signal_to_string(info.status)));
+
+                                        free(t);
+                                }
+
+                                free(info.path);
+                                strv_free(info.argv);
+
+                                dbus_message_iter_next(&sub);
+                        }
+
+                        return 0;
+                }
+
+                break;
+        }
+
+        if (generic_print_property(name, iter, arg_all) > 0)
+                return 0;
+
+        if (arg_all)
+                printf("%s=[unprintable]\n", name);
+
+        return 0;
+}
+
+static int show_one(const char *verb, DBusConnection *bus, const char *path, bool show_properties, bool *new_line) {
+        DBusMessage *m = NULL, *reply = NULL;
+        const char *interface = "";
+        int r;
+        DBusError error;
+        DBusMessageIter iter, sub, sub2, sub3;
+        UnitStatusInfo info;
+        ExecStatusInfo *p;
+
+        assert(bus);
+        assert(path);
+        assert(new_line);
+
+        zero(info);
+        dbus_error_init(&error);
+
+        if (!(m = dbus_message_new_method_call(
+                              "org.freedesktop.systemd1",
+                              path,
+                              "org.freedesktop.DBus.Properties",
+                              "GetAll"))) {
+                log_error("Could not allocate message.");
+                r = -ENOMEM;
+                goto finish;
+        }
+
+        if (!dbus_message_append_args(m,
+                                      DBUS_TYPE_STRING, &interface,
+                                      DBUS_TYPE_INVALID)) {
+                log_error("Could not append arguments to message.");
+                r = -ENOMEM;
+                goto finish;
+        }
+
+        if (!(reply = dbus_connection_send_with_reply_and_block(bus, m, -1, &error))) {
+                log_error("Failed to issue method call: %s", bus_error_message(&error));
+                r = -EIO;
+                goto finish;
+        }
+
+        if (!dbus_message_iter_init(reply, &iter) ||
+            dbus_message_iter_get_arg_type(&iter) != DBUS_TYPE_ARRAY ||
+            dbus_message_iter_get_element_type(&iter) != DBUS_TYPE_DICT_ENTRY)  {
+                log_error("Failed to parse reply.");
+                r = -EIO;
+                goto finish;
+        }
+
+        dbus_message_iter_recurse(&iter, &sub);
+
+        if (*new_line)
+                printf("\n");
+
+        *new_line = true;
+
+        while (dbus_message_iter_get_arg_type(&sub) != DBUS_TYPE_INVALID) {
+                const char *name;
+
+                if (dbus_message_iter_get_arg_type(&sub) != DBUS_TYPE_DICT_ENTRY) {
+                        log_error("Failed to parse reply.");
+                        r = -EIO;
+                        goto finish;
+                }
+
+                dbus_message_iter_recurse(&sub, &sub2);
+
+                if (bus_iter_get_basic_and_next(&sub2, DBUS_TYPE_STRING, &name, true) < 0) {
+                        log_error("Failed to parse reply.");
+                        r = -EIO;
+                        goto finish;
+                }
+
+                if (dbus_message_iter_get_arg_type(&sub2) != DBUS_TYPE_VARIANT)  {
+                        log_error("Failed to parse reply.");
+                        r = -EIO;
+                        goto finish;
+                }
+
+                dbus_message_iter_recurse(&sub2, &sub3);
+
+                if (show_properties)
+                        r = print_property(name, &sub3);
+                else
+                        r = status_property(name, &sub3, &info);
+
+                if (r < 0) {
+                        log_error("Failed to parse reply.");
+                        r = -EIO;
+                        goto finish;
+                }
+
+                dbus_message_iter_next(&sub);
+        }
+
+        r = 0;
+
+        if (!show_properties)
+                print_status_info(&info);
+
+        if (!streq_ptr(info.active_state, "active") &&
+            !streq_ptr(info.active_state, "reloading") &&
+            streq(verb, "status"))
+                /* According to LSB: "program not running" */
+                r = 3;
+
+        while ((p = info.exec)) {
+                LIST_REMOVE(ExecStatusInfo, exec, info.exec, p);
+                exec_status_info_free(p);
+        }
+
+finish:
+        if (m)
+                dbus_message_unref(m);
+
+        if (reply)
+                dbus_message_unref(reply);
+
+        dbus_error_free(&error);
+
+        return r;
+}
+
+static int show(DBusConnection *bus, char **args) {
+        DBusMessage *m = NULL, *reply = NULL;
+        int r, ret = 0;
+        DBusError error;
+        bool show_properties, new_line = false;
+        char **name;
+
+        assert(bus);
+        assert(args);
+
+        dbus_error_init(&error);
+
+        show_properties = !streq(args[0], "status");
+
+        if (show_properties)
+                pager_open_if_enabled();
+
+        if (show_properties && strv_length(args) <= 1) {
+                /* If not argument is specified inspect the manager
+                 * itself */
+
+                ret = show_one(args[0], bus, "/org/freedesktop/systemd1", show_properties, &new_line);
+                goto finish;
+        }
+
+        STRV_FOREACH(name, args+1) {
+                const char *path = NULL;
+                uint32_t id;
+
+                if (safe_atou32(*name, &id) < 0) {
+
+                        /* Interpret as unit name */
+
+                        if (!(m = dbus_message_new_method_call(
+                                              "org.freedesktop.systemd1",
+                                              "/org/freedesktop/systemd1",
+                                              "org.freedesktop.systemd1.Manager",
+                                              "LoadUnit"))) {
+                                log_error("Could not allocate message.");
+                                ret = -ENOMEM;
+                                goto finish;
+                        }
+
+                        if (!dbus_message_append_args(m,
+                                                      DBUS_TYPE_STRING, name,
+                                                      DBUS_TYPE_INVALID)) {
+                                log_error("Could not append arguments to message.");
+                                ret = -ENOMEM;
+                                goto finish;
+                        }
+
+                        if (!(reply = dbus_connection_send_with_reply_and_block(bus, m, -1, &error))) {
+
+                                if (!dbus_error_has_name(&error, DBUS_ERROR_ACCESS_DENIED)) {
+                                        log_error("Failed to issue method call: %s", bus_error_message(&error));
+                                        ret = -EIO;
+                                        goto finish;
+                                }
+
+                                dbus_error_free(&error);
+
+                                dbus_message_unref(m);
+                                if (!(m = dbus_message_new_method_call(
+                                                      "org.freedesktop.systemd1",
+                                                      "/org/freedesktop/systemd1",
+                                                      "org.freedesktop.systemd1.Manager",
+                                                      "GetUnit"))) {
+                                        log_error("Could not allocate message.");
+                                        ret = -ENOMEM;
+                                        goto finish;
+                                }
+
+                                if (!dbus_message_append_args(m,
+                                                              DBUS_TYPE_STRING, name,
+                                                              DBUS_TYPE_INVALID)) {
+                                        log_error("Could not append arguments to message.");
+                                        ret = -ENOMEM;
+                                        goto finish;
+                                }
+
+                                if (!(reply = dbus_connection_send_with_reply_and_block(bus, m, -1, &error))) {
+                                        log_error("Failed to issue method call: %s", bus_error_message(&error));
+
+                                        if (dbus_error_has_name(&error, BUS_ERROR_NO_SUCH_UNIT))
+                                                ret = 4; /* According to LSB: "program or service status is unknown" */
+                                        else
+                                                ret = -EIO;
+                                        goto finish;
+                                }
+                        }
+
+                } else if (show_properties) {
+
+                        /* Interpret as job id */
+
+                        if (!(m = dbus_message_new_method_call(
+                                              "org.freedesktop.systemd1",
+                                              "/org/freedesktop/systemd1",
+                                              "org.freedesktop.systemd1.Manager",
+                                              "GetJob"))) {
+                                log_error("Could not allocate message.");
+                                ret = -ENOMEM;
+                                goto finish;
+                        }
+
+                        if (!dbus_message_append_args(m,
+                                                      DBUS_TYPE_UINT32, &id,
+                                                      DBUS_TYPE_INVALID)) {
+                                log_error("Could not append arguments to message.");
+                                ret = -ENOMEM;
+                                goto finish;
+                        }
+
+                        if (!(reply = dbus_connection_send_with_reply_and_block(bus, m, -1, &error))) {
+                                log_error("Failed to issue method call: %s", bus_error_message(&error));
+                                ret = -EIO;
+                                goto finish;
+                        }
+                } else {
+
+                        /* Interpret as PID */
+
+                        if (!(m = dbus_message_new_method_call(
+                                              "org.freedesktop.systemd1",
+                                              "/org/freedesktop/systemd1",
+                                              "org.freedesktop.systemd1.Manager",
+                                              "GetUnitByPID"))) {
+                                log_error("Could not allocate message.");
+                                ret = -ENOMEM;
+                                goto finish;
+                        }
+
+                        if (!dbus_message_append_args(m,
+                                                      DBUS_TYPE_UINT32, &id,
+                                                      DBUS_TYPE_INVALID)) {
+                                log_error("Could not append arguments to message.");
+                                ret = -ENOMEM;
+                                goto finish;
+                        }
+
+                        if (!(reply = dbus_connection_send_with_reply_and_block(bus, m, -1, &error))) {
+                                log_error("Failed to issue method call: %s", bus_error_message(&error));
+                                ret = -EIO;
+                                goto finish;
+                        }
+                }
+
+                if (!dbus_message_get_args(reply, &error,
+                                           DBUS_TYPE_OBJECT_PATH, &path,
+                                           DBUS_TYPE_INVALID)) {
+                        log_error("Failed to parse reply: %s", bus_error_message(&error));
+                        ret = -EIO;
+                        goto finish;
+                }
+
+                if ((r = show_one(args[0], bus, path, show_properties, &new_line)) != 0)
+                        ret = r;
+
+                dbus_message_unref(m);
+                dbus_message_unref(reply);
+                m = reply = NULL;
+        }
+
+finish:
+        if (m)
+                dbus_message_unref(m);
+
+        if (reply)
+                dbus_message_unref(reply);
+
+        dbus_error_free(&error);
+
+        return ret;
+}
+
+static int dump(DBusConnection *bus, char **args) {
+        DBusMessage *m = NULL, *reply = NULL;
+        DBusError error;
+        int r;
+        const char *text;
+
+        dbus_error_init(&error);
+
+        pager_open_if_enabled();
+
+        if (!(m = dbus_message_new_method_call(
+                              "org.freedesktop.systemd1",
+                              "/org/freedesktop/systemd1",
+                              "org.freedesktop.systemd1.Manager",
+                              "Dump"))) {
+                log_error("Could not allocate message.");
+                return -ENOMEM;
+        }
+
+        if (!(reply = dbus_connection_send_with_reply_and_block(bus, m, -1, &error))) {
+                log_error("Failed to issue method call: %s", bus_error_message(&error));
+                r = -EIO;
+                goto finish;
+        }
+
+        if (!dbus_message_get_args(reply, &error,
+                                   DBUS_TYPE_STRING, &text,
+                                   DBUS_TYPE_INVALID)) {
+                log_error("Failed to parse reply: %s", bus_error_message(&error));
+                r = -EIO;
+                goto finish;
+        }
+
+        fputs(text, stdout);
+
+        r = 0;
+
+finish:
+        if (m)
+                dbus_message_unref(m);
+
+        if (reply)
+                dbus_message_unref(reply);
+
+        dbus_error_free(&error);
+
+        return r;
+}
+
+static int snapshot(DBusConnection *bus, char **args) {
+        DBusMessage *m = NULL, *reply = NULL;
+        DBusError error;
+        int r;
+        const char *name = "", *path, *id;
+        dbus_bool_t cleanup = FALSE;
+        DBusMessageIter iter, sub;
+        const char
+                *interface = "org.freedesktop.systemd1.Unit",
+                *property = "Id";
+
+        dbus_error_init(&error);
+
+        if (!(m = dbus_message_new_method_call(
+                              "org.freedesktop.systemd1",
+                              "/org/freedesktop/systemd1",
+                              "org.freedesktop.systemd1.Manager",
+                              "CreateSnapshot"))) {
+                log_error("Could not allocate message.");
+                return -ENOMEM;
+        }
+
+        if (strv_length(args) > 1)
+                name = args[1];
+
+        if (!dbus_message_append_args(m,
+                                      DBUS_TYPE_STRING, &name,
+                                      DBUS_TYPE_BOOLEAN, &cleanup,
+                                      DBUS_TYPE_INVALID)) {
+                log_error("Could not append arguments to message.");
+                r = -ENOMEM;
+                goto finish;
+        }
+
+        if (!(reply = dbus_connection_send_with_reply_and_block(bus, m, -1, &error))) {
+                log_error("Failed to issue method call: %s", bus_error_message(&error));
+                r = -EIO;
+                goto finish;
+        }
+
+        if (!dbus_message_get_args(reply, &error,
+                                   DBUS_TYPE_OBJECT_PATH, &path,
+                                   DBUS_TYPE_INVALID)) {
+                log_error("Failed to parse reply: %s", bus_error_message(&error));
+                r = -EIO;
+                goto finish;
+        }
+
+        dbus_message_unref(m);
+        if (!(m = dbus_message_new_method_call(
+                              "org.freedesktop.systemd1",
+                              path,
+                              "org.freedesktop.DBus.Properties",
+                              "Get"))) {
+                log_error("Could not allocate message.");
+                return -ENOMEM;
+        }
+
+        if (!dbus_message_append_args(m,
+                                      DBUS_TYPE_STRING, &interface,
+                                      DBUS_TYPE_STRING, &property,
+                                      DBUS_TYPE_INVALID)) {
+                log_error("Could not append arguments to message.");
+                r = -ENOMEM;
+                goto finish;
+        }
+
+        dbus_message_unref(reply);
+        if (!(reply = dbus_connection_send_with_reply_and_block(bus, m, -1, &error))) {
+                log_error("Failed to issue method call: %s", bus_error_message(&error));
+                r = -EIO;
+                goto finish;
+        }
+
+        if (!dbus_message_iter_init(reply, &iter) ||
+            dbus_message_iter_get_arg_type(&iter) != DBUS_TYPE_VARIANT)  {
+                log_error("Failed to parse reply.");
+                r = -EIO;
+                goto finish;
+        }
+
+        dbus_message_iter_recurse(&iter, &sub);
+
+        if (dbus_message_iter_get_arg_type(&sub) != DBUS_TYPE_STRING)  {
+                log_error("Failed to parse reply.");
+                r = -EIO;
+                goto finish;
+        }
+
+        dbus_message_iter_get_basic(&sub, &id);
+
+        if (!arg_quiet)
+                puts(id);
+        r = 0;
+
+finish:
+        if (m)
+                dbus_message_unref(m);
+
+        if (reply)
+                dbus_message_unref(reply);
+
+        dbus_error_free(&error);
+
+        return r;
+}
+
+static int delete_snapshot(DBusConnection *bus, char **args) {
+        DBusMessage *m = NULL, *reply = NULL;
+        int r;
+        DBusError error;
+        char **name;
+
+        assert(bus);
+        assert(args);
+
+        dbus_error_init(&error);
+
+        STRV_FOREACH(name, args+1) {
+                const char *path = NULL;
+
+                if (!(m = dbus_message_new_method_call(
+                                      "org.freedesktop.systemd1",
+                                      "/org/freedesktop/systemd1",
+                                      "org.freedesktop.systemd1.Manager",
+                                      "GetUnit"))) {
+                        log_error("Could not allocate message.");
+                        r = -ENOMEM;
+                        goto finish;
+                }
+
+                if (!dbus_message_append_args(m,
+                                              DBUS_TYPE_STRING, name,
+                                              DBUS_TYPE_INVALID)) {
+                        log_error("Could not append arguments to message.");
+                        r = -ENOMEM;
+                        goto finish;
+                }
+
+                if (!(reply = dbus_connection_send_with_reply_and_block(bus, m, -1, &error))) {
+                        log_error("Failed to issue method call: %s", bus_error_message(&error));
+                        r = -EIO;
+                        goto finish;
+                }
+
+                if (!dbus_message_get_args(reply, &error,
+                                           DBUS_TYPE_OBJECT_PATH, &path,
+                                           DBUS_TYPE_INVALID)) {
+                        log_error("Failed to parse reply: %s", bus_error_message(&error));
+                        r = -EIO;
+                        goto finish;
+                }
+
+                dbus_message_unref(m);
+                if (!(m = dbus_message_new_method_call(
+                                      "org.freedesktop.systemd1",
+                                      path,
+                                      "org.freedesktop.systemd1.Snapshot",
+                                      "Remove"))) {
+                        log_error("Could not allocate message.");
+                        r = -ENOMEM;
+                        goto finish;
+                }
+
+                dbus_message_unref(reply);
+                if (!(reply = dbus_connection_send_with_reply_and_block(bus, m, -1, &error))) {
+                        log_error("Failed to issue method call: %s", bus_error_message(&error));
+                        r = -EIO;
+                        goto finish;
+                }
+
+                dbus_message_unref(m);
+                dbus_message_unref(reply);
+                m = reply = NULL;
+        }
+
+        r = 0;
+
+finish:
+        if (m)
+                dbus_message_unref(m);
+
+        if (reply)
+                dbus_message_unref(reply);
+
+        dbus_error_free(&error);
+
+        return r;
+}
+
+static int daemon_reload(DBusConnection *bus, char **args) {
+        DBusMessage *m = NULL, *reply = NULL;
+        DBusError error;
+        int r;
+        const char *method;
+
+        dbus_error_init(&error);
+
+        if (arg_action == ACTION_RELOAD)
+                method = "Reload";
+        else if (arg_action == ACTION_REEXEC)
+                method = "Reexecute";
+        else {
+                assert(arg_action == ACTION_SYSTEMCTL);
+
+                method =
+                        streq(args[0], "clear-jobs")    ||
+                        streq(args[0], "cancel")        ? "ClearJobs" :
+                        streq(args[0], "daemon-reexec") ? "Reexecute" :
+                        streq(args[0], "reset-failed")  ? "ResetFailed" :
+                        streq(args[0], "halt")          ? "Halt" :
+                        streq(args[0], "poweroff")      ? "PowerOff" :
+                        streq(args[0], "reboot")        ? "Reboot" :
+                        streq(args[0], "kexec")         ? "KExec" :
+                        streq(args[0], "exit")          ? "Exit" :
+                                    /* "daemon-reload" */ "Reload";
+        }
+
+        if (!(m = dbus_message_new_method_call(
+                              "org.freedesktop.systemd1",
+                              "/org/freedesktop/systemd1",
+                              "org.freedesktop.systemd1.Manager",
+                              method))) {
+                log_error("Could not allocate message.");
+                return -ENOMEM;
+        }
+
+        if (!(reply = dbus_connection_send_with_reply_and_block(bus, m, -1, &error))) {
+
+                if (arg_action != ACTION_SYSTEMCTL && error_is_no_service(&error)) {
+                        /* There's always a fallback possible for
+                         * legacy actions. */
+                        r = -EADDRNOTAVAIL;
+                        goto finish;
+                }
+
+                if (streq(method, "Reexecute") && dbus_error_has_name(&error, DBUS_ERROR_NO_REPLY)) {
+                        /* On reexecution, we expect a disconnect, not
+                         * a reply */
+                        r = 0;
+                        goto finish;
+                }
+
+                log_error("Failed to issue method call: %s", bus_error_message(&error));
+                r = -EIO;
+                goto finish;
+        }
+
+        r = 0;
+
+finish:
+        if (m)
+                dbus_message_unref(m);
+
+        if (reply)
+                dbus_message_unref(reply);
+
+        dbus_error_free(&error);
+
+        return r;
+}
+
+static int reset_failed(DBusConnection *bus, char **args) {
+        DBusMessage *m = NULL;
+        int r;
+        DBusError error;
+        char **name;
+
+        assert(bus);
+        dbus_error_init(&error);
+
+        if (strv_length(args) <= 1)
+                return daemon_reload(bus, args);
+
+        STRV_FOREACH(name, args+1) {
+                DBusMessage *reply;
+
+                if (!(m = dbus_message_new_method_call(
+                                      "org.freedesktop.systemd1",
+                                      "/org/freedesktop/systemd1",
+                                      "org.freedesktop.systemd1.Manager",
+                                      "ResetFailedUnit"))) {
+                        log_error("Could not allocate message.");
+                        r = -ENOMEM;
+                        goto finish;
+                }
+
+                if (!dbus_message_append_args(m,
+                                              DBUS_TYPE_STRING, name,
+                                              DBUS_TYPE_INVALID)) {
+                        log_error("Could not append arguments to message.");
+                        r = -ENOMEM;
+                        goto finish;
+                }
+
+                if (!(reply = dbus_connection_send_with_reply_and_block(bus, m, -1, &error))) {
+                        log_error("Failed to issue method call: %s", bus_error_message(&error));
+                        r = -EIO;
+                        goto finish;
+                }
+
+                dbus_message_unref(m);
+                dbus_message_unref(reply);
+                m = reply = NULL;
+        }
+
+        r = 0;
+
+finish:
+        if (m)
+                dbus_message_unref(m);
+
+        dbus_error_free(&error);
+
+        return r;
+}
+
+static int show_enviroment(DBusConnection *bus, char **args) {
+        DBusMessage *m = NULL, *reply = NULL;
+        DBusError error;
+        DBusMessageIter iter, sub, sub2;
+        int r;
+        const char
+                *interface = "org.freedesktop.systemd1.Manager",
+                *property = "Environment";
+
+        dbus_error_init(&error);
+
+        pager_open_if_enabled();
+
+        if (!(m = dbus_message_new_method_call(
+                              "org.freedesktop.systemd1",
+                              "/org/freedesktop/systemd1",
+                              "org.freedesktop.DBus.Properties",
+                              "Get"))) {
+                log_error("Could not allocate message.");
+                return -ENOMEM;
+        }
+
+        if (!dbus_message_append_args(m,
+                                      DBUS_TYPE_STRING, &interface,
+                                      DBUS_TYPE_STRING, &property,
+                                      DBUS_TYPE_INVALID)) {
+                log_error("Could not append arguments to message.");
+                r = -ENOMEM;
+                goto finish;
+        }
+
+        if (!(reply = dbus_connection_send_with_reply_and_block(bus, m, -1, &error))) {
+                log_error("Failed to issue method call: %s", bus_error_message(&error));
+                r = -EIO;
+                goto finish;
+        }
+
+        if (!dbus_message_iter_init(reply, &iter) ||
+            dbus_message_iter_get_arg_type(&iter) != DBUS_TYPE_VARIANT)  {
+                log_error("Failed to parse reply.");
+                r = -EIO;
+                goto finish;
+        }
+
+        dbus_message_iter_recurse(&iter, &sub);
+
+        if (dbus_message_iter_get_arg_type(&sub) != DBUS_TYPE_ARRAY ||
+            dbus_message_iter_get_element_type(&sub) != DBUS_TYPE_STRING)  {
+                log_error("Failed to parse reply.");
+                r = -EIO;
+                goto finish;
+        }
+
+        dbus_message_iter_recurse(&sub, &sub2);
+
+        while (dbus_message_iter_get_arg_type(&sub2) != DBUS_TYPE_INVALID) {
+                const char *text;
+
+                if (dbus_message_iter_get_arg_type(&sub2) != DBUS_TYPE_STRING) {
+                        log_error("Failed to parse reply.");
+                        r = -EIO;
+                        goto finish;
+                }
+
+                dbus_message_iter_get_basic(&sub2, &text);
+                printf("%s\n", text);
+
+                dbus_message_iter_next(&sub2);
+        }
+
+        r = 0;
+
+finish:
+        if (m)
+                dbus_message_unref(m);
+
+        if (reply)
+                dbus_message_unref(reply);
+
+        dbus_error_free(&error);
+
+        return r;
+}
+
+static int set_environment(DBusConnection *bus, char **args) {
+        DBusMessage *m = NULL, *reply = NULL;
+        DBusError error;
+        int r;
+        const char *method;
+        DBusMessageIter iter, sub;
+        char **name;
+
+        dbus_error_init(&error);
+
+        method = streq(args[0], "set-environment")
+                ? "SetEnvironment"
+                : "UnsetEnvironment";
+
+        if (!(m = dbus_message_new_method_call(
+                              "org.freedesktop.systemd1",
+                              "/org/freedesktop/systemd1",
+                              "org.freedesktop.systemd1.Manager",
+                              method))) {
+
+                log_error("Could not allocate message.");
+                return -ENOMEM;
+        }
+
+        dbus_message_iter_init_append(m, &iter);
+
+        if (!dbus_message_iter_open_container(&iter, DBUS_TYPE_ARRAY, "s", &sub)) {
+                log_error("Could not append arguments to message.");
+                r = -ENOMEM;
+                goto finish;
+        }
+
+        STRV_FOREACH(name, args+1)
+                if (!dbus_message_iter_append_basic(&sub, DBUS_TYPE_STRING, name)) {
+                        log_error("Could not append arguments to message.");
+                        r = -ENOMEM;
+                        goto finish;
+                }
+
+        if (!dbus_message_iter_close_container(&iter, &sub)) {
+                log_error("Could not append arguments to message.");
+                r = -ENOMEM;
+                goto finish;
+        }
+
+        if (!(reply = dbus_connection_send_with_reply_and_block(bus, m, -1, &error))) {
+                log_error("Failed to issue method call: %s", bus_error_message(&error));
+                r = -EIO;
+                goto finish;
+        }
+
+        r = 0;
+
+finish:
+        if (m)
+                dbus_message_unref(m);
+
+        if (reply)
+                dbus_message_unref(reply);
+
+        dbus_error_free(&error);
+
+        return r;
+}
+
+static int enable_sysv_units(char **args) {
+        int r = 0;
+
+#if defined (HAVE_SYSV_COMPAT) && (defined(TARGET_FEDORA) || defined(TARGET_MANDRIVA) || defined(TARGET_SUSE) || defined(TARGET_MEEGO) || defined(TARGET_ALTLINUX) || defined(TARGET_MAGEIA))
+        const char *verb = args[0];
+        unsigned f = 1, t = 1;
+        LookupPaths paths;
+
+        if (arg_scope != UNIT_FILE_SYSTEM)
+                return 0;
+
+        if (!streq(verb, "enable") &&
+            !streq(verb, "disable") &&
+            !streq(verb, "is-enabled"))
+                return 0;
+
+        /* Processes all SysV units, and reshuffles the array so that
+         * afterwards only the native units remain */
+
+        zero(paths);
+        r = lookup_paths_init(&paths, MANAGER_SYSTEM, false);
+        if (r < 0)
+                return r;
+
+        r = 0;
+
+        for (f = 1; args[f]; f++) {
+                const char *name;
+                char *p;
+                bool found_native = false, found_sysv;
+                unsigned c = 1;
+                const char *argv[6] = { "/sbin/chkconfig", NULL, NULL, NULL, NULL };
+                char **k, *l, *q = NULL;
+                int j;
+                pid_t pid;
+                siginfo_t status;
+
+                name = args[f];
+
+                if (!endswith(name, ".service"))
+                        continue;
+
+                if (path_is_absolute(name))
+                        continue;
+
+                STRV_FOREACH(k, paths.unit_path) {
+                        p = NULL;
+
+                        if (!isempty(arg_root))
+                                asprintf(&p, "%s/%s/%s", arg_root, *k, name);
+                        else
+                                asprintf(&p, "%s/%s", *k, name);
+
+                        if (!p) {
+                                log_error("No memory");
+                                r = -ENOMEM;
+                                goto finish;
+                        }
+
+                        found_native = access(p, F_OK) >= 0;
+                        free(p);
+
+                        if (found_native)
+                                break;
+                }
+
+                if (found_native)
+                        continue;
+
+                p = NULL;
+                if (!isempty(arg_root))
+                        asprintf(&p, "%s/" SYSTEM_SYSVINIT_PATH "/%s", arg_root, name);
+                else
+                        asprintf(&p, SYSTEM_SYSVINIT_PATH "/%s", name);
+                if (!p) {
+                        log_error("No memory");
+                        r = -ENOMEM;
+                        goto finish;
+                }
+
+                p[strlen(p) - sizeof(".service") + 1] = 0;
+                found_sysv = access(p, F_OK) >= 0;
+
+                if (!found_sysv) {
+                        free(p);
+                        continue;
+                }
+
+                /* Mark this entry, so that we don't try enabling it as native unit */
+                args[f] = (char*) "";
+
+                log_info("%s is not a native service, redirecting to /sbin/chkconfig.", name);
+
+                if (!isempty(arg_root))
+                        argv[c++] = q = strappend("--root=", arg_root);
+
+                argv[c++] = file_name_from_path(p);
+                argv[c++] =
+                        streq(verb, "enable") ? "on" :
+                        streq(verb, "disable") ? "off" : "--level=5";
+                argv[c] = NULL;
+
+                l = strv_join((char**)argv, " ");
+                if (!l) {
+                        log_error("No memory.");
+                        free(q);
+                        free(p);
+                        r = -ENOMEM;
+                        goto finish;
+                }
+
+                log_info("Executing %s", l);
+                free(l);
+
+                pid = fork();
+                if (pid < 0) {
+                        log_error("Failed to fork: %m");
+                        free(p);
+                        free(q);
+                        r = -errno;
+                        goto finish;
+                } else if (pid == 0) {
+                        /* Child */
+
+                        execv(argv[0], (char**) argv);
+                        _exit(EXIT_FAILURE);
+                }
+
+                free(p);
+                free(q);
+
+                j = wait_for_terminate(pid, &status);
+                if (j < 0) {
+                        log_error("Failed to wait for child: %s", strerror(-r));
+                        r = j;
+                        goto finish;
+                }
+
+                if (status.si_code == CLD_EXITED) {
+                        if (streq(verb, "is-enabled")) {
+                                if (status.si_status == 0) {
+                                        if (!arg_quiet)
+                                                puts("enabled");
+                                        r = 1;
+                                } else {
+                                        if (!arg_quiet)
+                                                puts("disabled");
+                                }
+
+                        } else if (status.si_status != 0) {
+                                r = -EINVAL;
+                                goto finish;
+                        }
+                } else {
+                        r = -EPROTO;
+                        goto finish;
+                }
+        }
+
+finish:
+        lookup_paths_free(&paths);
+
+        /* Drop all SysV units */
+        for (f = 1, t = 1; args[f]; f++) {
+
+                if (isempty(args[f]))
+                        continue;
+
+                args[t++] = args[f];
+        }
+
+        args[t] = NULL;
+
+#endif
+        return r;
+}
+
+static int enable_unit(DBusConnection *bus, char **args) {
+        const char *verb = args[0];
+        UnitFileChange *changes = NULL;
+        unsigned n_changes = 0, i;
+        int carries_install_info = -1;
+        DBusMessage *m = NULL, *reply = NULL;
+        int r;
+        DBusError error;
+
+        r = enable_sysv_units(args);
+        if (r < 0)
+                return r;
+
+        if (!args[1])
+                return 0;
+
+        dbus_error_init(&error);
+
+        if (!bus || avoid_bus()) {
+                if (streq(verb, "enable")) {
+                        r = unit_file_enable(arg_scope, arg_runtime, arg_root, args+1, arg_force, &changes, &n_changes);
+                        carries_install_info = r;
+                } else if (streq(verb, "disable"))
+                        r = unit_file_disable(arg_scope, arg_runtime, arg_root, args+1, &changes, &n_changes);
+                else if (streq(verb, "reenable")) {
+                        r = unit_file_reenable(arg_scope, arg_runtime, arg_root, args+1, arg_force, &changes, &n_changes);
+                        carries_install_info = r;
+                } else if (streq(verb, "link"))
+                        r = unit_file_link(arg_scope, arg_runtime, arg_root, args+1, arg_force, &changes, &n_changes);
+                else if (streq(verb, "preset")) {
+                        r = unit_file_preset(arg_scope, arg_runtime, arg_root, args+1, arg_force, &changes, &n_changes);
+                        carries_install_info = r;
+                } else if (streq(verb, "mask"))
+                        r = unit_file_mask(arg_scope, arg_runtime, arg_root, args+1, arg_force, &changes, &n_changes);
+                else if (streq(verb, "unmask"))
+                        r = unit_file_unmask(arg_scope, arg_runtime, arg_root, args+1, &changes, &n_changes);
+                else
+                        assert_not_reached("Unknown verb");
+
+                if (r < 0) {
+                        log_error("Operation failed: %s", strerror(-r));
+                        goto finish;
+                }
+
+                if (!arg_quiet) {
+                        for (i = 0; i < n_changes; i++) {
+                                if (changes[i].type == UNIT_FILE_SYMLINK)
+                                        log_info("ln -s '%s' '%s'", changes[i].source, changes[i].path);
+                                else
+                                        log_info("rm '%s'", changes[i].path);
+                        }
+                }
+
+        } else {
+                const char *method;
+                bool send_force = true, expect_carries_install_info = false;
+                dbus_bool_t a, b;
+                DBusMessageIter iter, sub, sub2;
+
+                if (streq(verb, "enable")) {
+                        method = "EnableUnitFiles";
+                        expect_carries_install_info = true;
+                } else if (streq(verb, "disable")) {
+                        method = "DisableUnitFiles";
+                        send_force = false;
+                } else if (streq(verb, "reenable")) {
+                        method = "ReenableUnitFiles";
+                        expect_carries_install_info = true;
+                } else if (streq(verb, "link"))
+                        method = "LinkUnitFiles";
+                else if (streq(verb, "preset")) {
+                        method = "PresetUnitFiles";
+                        expect_carries_install_info = true;
+                } else if (streq(verb, "mask"))
+                        method = "MaskUnitFiles";
+                else if (streq(verb, "unmask")) {
+                        method = "UnmaskUnitFiles";
+                        send_force = false;
+                } else
+                        assert_not_reached("Unknown verb");
+
+                m = dbus_message_new_method_call(
+                                "org.freedesktop.systemd1",
+                                "/org/freedesktop/systemd1",
+                                "org.freedesktop.systemd1.Manager",
+                                method);
+                if (!m) {
+                        log_error("Out of memory");
+                        r = -ENOMEM;
+                        goto finish;
+                }
+
+                dbus_message_iter_init_append(m, &iter);
+
+                r = bus_append_strv_iter(&iter, args+1);
+                if (r < 0) {
+                        log_error("Failed to append unit files.");
+                        goto finish;
+                }
+
+                a = arg_runtime;
+                if (!dbus_message_iter_append_basic(&iter, DBUS_TYPE_BOOLEAN, &a)) {
+                        log_error("Failed to append runtime boolean.");
+                        r = -ENOMEM;
+                        goto finish;
+                }
+
+                if (send_force) {
+                        b = arg_force;
+
+                        if (!dbus_message_iter_append_basic(&iter, DBUS_TYPE_BOOLEAN, &b)) {
+                                log_error("Failed to append force boolean.");
+                                r = -ENOMEM;
+                                goto finish;
+                        }
+                }
+
+                reply = dbus_connection_send_with_reply_and_block(bus, m, -1, &error);
+                if (!reply) {
+                        log_error("Failed to issue method call: %s", bus_error_message(&error));
+                        r = -EIO;
+                        goto finish;
+                }
+
+                if (!dbus_message_iter_init(reply, &iter)) {
+                        log_error("Failed to initialize iterator.");
+                        goto finish;
+                }
+
+                if (expect_carries_install_info) {
+                        r = bus_iter_get_basic_and_next(&iter, DBUS_TYPE_BOOLEAN, &b, true);
+                        if (r < 0) {
+                                log_error("Failed to parse reply.");
+                                goto finish;
+                        }
+
+                        carries_install_info = b;
+                }
+
+                if (dbus_message_iter_get_arg_type(&iter) != DBUS_TYPE_ARRAY ||
+                    dbus_message_iter_get_element_type(&iter) != DBUS_TYPE_STRUCT)  {
+                        log_error("Failed to parse reply.");
+                        r = -EIO;
+                        goto finish;
+                }
+
+                dbus_message_iter_recurse(&iter, &sub);
+                while (dbus_message_iter_get_arg_type(&sub) != DBUS_TYPE_INVALID) {
+                        const char *type, *path, *source;
+
+                        if (dbus_message_iter_get_arg_type(&sub) != DBUS_TYPE_STRUCT) {
+                                log_error("Failed to parse reply.");
+                                r = -EIO;
+                                goto finish;
+                        }
+
+                        dbus_message_iter_recurse(&sub, &sub2);
+
+                        if (bus_iter_get_basic_and_next(&sub2, DBUS_TYPE_STRING, &type, true) < 0 ||
+                            bus_iter_get_basic_and_next(&sub2, DBUS_TYPE_STRING, &path, true) < 0 ||
+                            bus_iter_get_basic_and_next(&sub2, DBUS_TYPE_STRING, &source, false) < 0) {
+                                log_error("Failed to parse reply.");
+                                r = -EIO;
+                                goto finish;
+                        }
+
+                        if (!arg_quiet) {
+                                if (streq(type, "symlink"))
+                                        log_info("ln -s '%s' '%s'", source, path);
+                                else
+                                        log_info("rm '%s'", path);
+                        }
+
+                        dbus_message_iter_next(&sub);
+                }
+
+                /* Try to reload if enabeld */
+                if (!arg_no_reload)
+                        r = daemon_reload(bus, args);
+        }
+
+        if (carries_install_info == 0)
+                log_warning("Warning: unit files do not carry install information. No operation executed.");
+
+finish:
+        if (m)
+                dbus_message_unref(m);
+
+        if (reply)
+                dbus_message_unref(reply);
+
+        unit_file_changes_free(changes, n_changes);
+
+        dbus_error_free(&error);
+        return r;
+}
+
+static int unit_is_enabled(DBusConnection *bus, char **args) {
+        DBusError error;
+        int r;
+        DBusMessage *m = NULL, *reply = NULL;
+        bool enabled;
+        char **name;
+
+        dbus_error_init(&error);
+
+        r = enable_sysv_units(args);
+        if (r < 0)
+                return r;
+
+        enabled = r > 0;
+
+        if (!bus || avoid_bus()) {
+
+                STRV_FOREACH(name, args+1) {
+                        UnitFileState state;
+
+                        state = unit_file_get_state(arg_scope, arg_root, *name);
+                        if (state < 0) {
+                                r = state;
+                                goto finish;
+                        }
+
+                        if (state == UNIT_FILE_ENABLED ||
+                            state == UNIT_FILE_ENABLED_RUNTIME ||
+                            state == UNIT_FILE_STATIC)
+                                enabled = true;
+
+                        if (!arg_quiet)
+                                puts(unit_file_state_to_string(state));
+                }
+
+        } else {
+                STRV_FOREACH(name, args+1) {
+                        const char *s;
+
+                        m = dbus_message_new_method_call(
+                                        "org.freedesktop.systemd1",
+                                        "/org/freedesktop/systemd1",
+                                        "org.freedesktop.systemd1.Manager",
+                                        "GetUnitFileState");
+                        if (!m) {
+                                log_error("Out of memory");
+                                r = -ENOMEM;
+                                goto finish;
+                        }
+
+                        if (!dbus_message_append_args(m,
+                                                      DBUS_TYPE_STRING, name,
+                                                      DBUS_TYPE_INVALID)) {
+                                log_error("Could not append arguments to message.");
+                                r = -ENOMEM;
+                                goto finish;
+                        }
+
+                        reply = dbus_connection_send_with_reply_and_block(bus, m, -1, &error);
+                        if (!reply) {
+                                log_error("Failed to issue method call: %s", bus_error_message(&error));
+                                r = -EIO;
+                                goto finish;
+                        }
+
+                        if (!dbus_message_get_args(reply, &error,
+                                                   DBUS_TYPE_STRING, &s,
+                                                   DBUS_TYPE_INVALID)) {
+                                log_error("Failed to parse reply: %s", bus_error_message(&error));
+                                r = -EIO;
+                                goto finish;
+                        }
+
+                        dbus_message_unref(m);
+                        dbus_message_unref(reply);
+                        m = reply = NULL;
+
+                        if (streq(s, "enabled") ||
+                            streq(s, "enabled-runtime") ||
+                            streq(s, "static"))
+                                enabled = true;
+
+                        if (!arg_quiet)
+                                puts(s);
+                }
+        }
+
+        r = enabled ? 0 : 1;
+
+finish:
+        if (m)
+                dbus_message_unref(m);
+
+        if (reply)
+                dbus_message_unref(reply);
+
+        dbus_error_free(&error);
+        return r;
+}
+
+static int systemctl_help(void) {
+
+        pager_open_if_enabled();
+
+        printf("%s [OPTIONS...] {COMMAND} ...\n\n"
+               "Query or send control commands to the systemd manager.\n\n"
+               "  -h --help           Show this help\n"
+               "     --version        Show package version\n"
+               "  -t --type=TYPE      List only units of a particular type\n"
+               "  -p --property=NAME  Show only properties by this name\n"
+               "  -a --all            Show all units/properties, including dead/empty ones\n"
+               "     --failed         Show only failed units\n"
+               "     --full           Don't ellipsize unit names on output\n"
+               "     --fail           When queueing a new job, fail if conflicting jobs are\n"
+               "                      pending\n"
+               "     --ignore-dependencies\n"
+               "                      When queueing a new job, ignore all its dependencies\n"
+               "     --kill-who=WHO   Who to send signal to\n"
+               "  -s --signal=SIGNAL  Which signal to send\n"
+               "  -H --host=[USER@]HOST\n"
+               "                      Show information for remote host\n"
+               "  -P --privileged     Acquire privileges before execution\n"
+               "  -q --quiet          Suppress output\n"
+               "     --no-block       Do not wait until operation finished\n"
+               "     --no-wall        Don't send wall message before halt/power-off/reboot\n"
+               "     --no-reload      When enabling/disabling unit files, don't reload daemon\n"
+               "                      configuration\n"
+               "     --no-legend      Do not print a legend (column headers and hints)\n"
+               "     --no-pager       Do not pipe output into a pager\n"
+               "     --no-ask-password\n"
+               "                      Do not ask for system passwords\n"
+               "     --order          When generating graph for dot, show only order\n"
+               "     --require        When generating graph for dot, show only requirement\n"
+               "     --system         Connect to system manager\n"
+               "     --user           Connect to user service manager\n"
+               "     --global         Enable/disable unit files globally\n"
+               "  -f --force          When enabling unit files, override existing symlinks\n"
+               "                      When shutting down, execute action immediately\n"
+               "     --root=PATH      Enable unit files in the specified root directory\n"
+               "     --runtime        Enable unit files only temporarily until next reboot\n"
+               "  -n --lines=INTEGER  Journal entries to show\n"
+               "     --follow         Follow journal\n"
+               "  -o --output=STRING  Change journal output mode (short, short-monotonic,\n"
+               "                      verbose, export, json, cat)\n\n"
+               "Unit Commands:\n"
+               "  list-units                      List loaded units\n"
+               "  start [NAME...]                 Start (activate) one or more units\n"
+               "  stop [NAME...]                  Stop (deactivate) one or more units\n"
+               "  reload [NAME...]                Reload one or more units\n"
+               "  restart [NAME...]               Start or restart one or more units\n"
+               "  try-restart [NAME...]           Restart one or more units if active\n"
+               "  reload-or-restart [NAME...]     Reload one or more units is possible,\n"
+               "                                  otherwise start or restart\n"
+               "  reload-or-try-restart [NAME...] Reload one or more units is possible,\n"
+               "                                  otherwise restart if active\n"
+               "  isolate [NAME]                  Start one unit and stop all others\n"
+               "  kill [NAME...]                  Send signal to processes of a unit\n"
+               "  is-active [NAME...]             Check whether units are active\n"
+               "  status [NAME...|PID...]         Show runtime status of one or more units\n"
+               "  show [NAME...|JOB...]           Show properties of one or more\n"
+               "                                  units/jobs or the manager\n"
+               "  reset-failed [NAME...]          Reset failed state for all, one, or more\n"
+               "                                  units\n"
+               "  load [NAME...]                  Load one or more units\n\n"
+               "Unit File Commands:\n"
+               "  list-unit-files                 List installed unit files\n"
+               "  enable [NAME...]                Enable one or more unit files\n"
+               "  disable [NAME...]               Disable one or more unit files\n"
+               "  reenable [NAME...]              Reenable one or more unit files\n"
+               "  preset [NAME...]                Enable/disable one or more unit files\n"
+               "                                  based on preset configuration\n"
+               "  mask [NAME...]                  Mask one or more units\n"
+               "  unmask [NAME...]                Unmask one or more units\n"
+               "  link [PATH...]                  Link one or more units files into\n"
+               "                                  the search path\n"
+               "  is-enabled [NAME...]            Check whether unit files are enabled\n\n"
+               "Job Commands:\n"
+               "  list-jobs                       List jobs\n"
+               "  cancel [JOB...]                 Cancel all, one, or more jobs\n\n"
+               "Status Commands:\n"
+               "  dump                            Dump server status\n"
+               "  dot                             Dump dependency graph for dot(1)\n\n"
+               "Snapshot Commands:\n"
+               "  snapshot [NAME]                 Create a snapshot\n"
+               "  delete [NAME...]                Remove one or more snapshots\n\n"
+               "Environment Commands:\n"
+               "  show-environment                Dump environment\n"
+               "  set-environment [NAME=VALUE...] Set one or more environment variables\n"
+               "  unset-environment [NAME...]     Unset one or more environment variables\n\n"
+               "Manager Lifecycle Commands:\n"
+               "  daemon-reload                   Reload systemd manager configuration\n"
+               "  daemon-reexec                   Reexecute systemd manager\n\n"
+               "System Commands:\n"
+               "  default                         Enter system default mode\n"
+               "  rescue                          Enter system rescue mode\n"
+               "  emergency                       Enter system emergency mode\n"
+               "  halt                            Shut down and halt the system\n"
+               "  poweroff                        Shut down and power-off the system\n"
+               "  reboot                          Shut down and reboot the system\n"
+               "  kexec                           Shut down and reboot the system with kexec\n"
+               "  exit                            Ask for user instance termination\n",
+               program_invocation_short_name);
+
+        return 0;
+}
+
+static int halt_help(void) {
+
+        printf("%s [OPTIONS...]\n\n"
+               "%s the system.\n\n"
+               "     --help      Show this help\n"
+               "     --halt      Halt the machine\n"
+               "  -p --poweroff  Switch off the machine\n"
+               "     --reboot    Reboot the machine\n"
+               "  -f --force     Force immediate halt/power-off/reboot\n"
+               "  -w --wtmp-only Don't halt/power-off/reboot, just write wtmp record\n"
+               "  -d --no-wtmp   Don't write wtmp record\n"
+               "  -n --no-sync   Don't sync before halt/power-off/reboot\n"
+               "     --no-wall   Don't send wall message before halt/power-off/reboot\n",
+               program_invocation_short_name,
+               arg_action == ACTION_REBOOT   ? "Reboot" :
+               arg_action == ACTION_POWEROFF ? "Power off" :
+                                               "Halt");
+
+        return 0;
+}
+
+static int shutdown_help(void) {
+
+        printf("%s [OPTIONS...] [TIME] [WALL...]\n\n"
+               "Shut down the system.\n\n"
+               "     --help      Show this help\n"
+               "  -H --halt      Halt the machine\n"
+               "  -P --poweroff  Power-off the machine\n"
+               "  -r --reboot    Reboot the machine\n"
+               "  -h             Equivalent to --poweroff, overriden by --halt\n"
+               "  -k             Don't halt/power-off/reboot, just send warnings\n"
+               "     --no-wall   Don't send wall message before halt/power-off/reboot\n"
+               "  -c             Cancel a pending shutdown\n",
+               program_invocation_short_name);
+
+        return 0;
+}
+
+static int telinit_help(void) {
+
+        printf("%s [OPTIONS...] {COMMAND}\n\n"
+               "Send control commands to the init daemon.\n\n"
+               "     --help      Show this help\n"
+               "     --no-wall   Don't send wall message before halt/power-off/reboot\n\n"
+               "Commands:\n"
+               "  0              Power-off the machine\n"
+               "  6              Reboot the machine\n"
+               "  2, 3, 4, 5     Start runlevelX.target unit\n"
+               "  1, s, S        Enter rescue mode\n"
+               "  q, Q           Reload init daemon configuration\n"
+               "  u, U           Reexecute init daemon\n",
+               program_invocation_short_name);
+
+        return 0;
+}
+
+static int runlevel_help(void) {
+
+        printf("%s [OPTIONS...]\n\n"
+               "Prints the previous and current runlevel of the init system.\n\n"
+               "     --help      Show this help\n",
+               program_invocation_short_name);
+
+        return 0;
+}
+
+static int systemctl_parse_argv(int argc, char *argv[]) {
+
+        enum {
+                ARG_FAIL = 0x100,
+                ARG_IGNORE_DEPENDENCIES,
+                ARG_VERSION,
+                ARG_USER,
+                ARG_SYSTEM,
+                ARG_GLOBAL,
+                ARG_NO_BLOCK,
+                ARG_NO_LEGEND,
+                ARG_NO_PAGER,
+                ARG_NO_WALL,
+                ARG_ORDER,
+                ARG_REQUIRE,
+                ARG_ROOT,
+                ARG_FULL,
+                ARG_NO_RELOAD,
+                ARG_KILL_MODE,
+                ARG_KILL_WHO,
+                ARG_NO_ASK_PASSWORD,
+                ARG_FAILED,
+                ARG_RUNTIME,
+                ARG_FOLLOW,
+                ARG_FORCE
+        };
+
+        static const struct option options[] = {
+                { "help",      no_argument,       NULL, 'h'           },
+                { "version",   no_argument,       NULL, ARG_VERSION   },
+                { "type",      required_argument, NULL, 't'           },
+                { "property",  required_argument, NULL, 'p'           },
+                { "all",       no_argument,       NULL, 'a'           },
+                { "failed",    no_argument,       NULL, ARG_FAILED    },
+                { "full",      no_argument,       NULL, ARG_FULL      },
+                { "fail",      no_argument,       NULL, ARG_FAIL      },
+                { "ignore-dependencies", no_argument, NULL, ARG_IGNORE_DEPENDENCIES },
+                { "user",      no_argument,       NULL, ARG_USER      },
+                { "system",    no_argument,       NULL, ARG_SYSTEM    },
+                { "global",    no_argument,       NULL, ARG_GLOBAL    },
+                { "no-block",  no_argument,       NULL, ARG_NO_BLOCK  },
+                { "no-legend", no_argument,       NULL, ARG_NO_LEGEND },
+                { "no-pager",  no_argument,       NULL, ARG_NO_PAGER  },
+                { "no-wall",   no_argument,       NULL, ARG_NO_WALL   },
+                { "quiet",     no_argument,       NULL, 'q'           },
+                { "order",     no_argument,       NULL, ARG_ORDER     },
+                { "require",   no_argument,       NULL, ARG_REQUIRE   },
+                { "root",      required_argument, NULL, ARG_ROOT      },
+                { "force",     no_argument,       NULL, ARG_FORCE     },
+                { "no-reload", no_argument,       NULL, ARG_NO_RELOAD },
+                { "kill-mode", required_argument, NULL, ARG_KILL_MODE }, /* undocumented on purpose */
+                { "kill-who",  required_argument, NULL, ARG_KILL_WHO  },
+                { "signal",    required_argument, NULL, 's'           },
+                { "no-ask-password", no_argument, NULL, ARG_NO_ASK_PASSWORD },
+                { "host",      required_argument, NULL, 'H'           },
+                { "privileged",no_argument,       NULL, 'P'           },
+                { "runtime",   no_argument,       NULL, ARG_RUNTIME   },
+                { "lines",     required_argument, NULL, 'n'           },
+                { "follow",    no_argument,       NULL, ARG_FOLLOW    },
+                { "output",    required_argument, NULL, 'o'           },
+                { NULL,        0,                 NULL, 0             }
+        };
+
+        int c;
+
+        assert(argc >= 0);
+        assert(argv);
+
+        /* Only when running as systemctl we ask for passwords */
+        arg_ask_password = true;
+
+        while ((c = getopt_long(argc, argv, "ht:p:aqfs:H:Pn:o:", options, NULL)) >= 0) {
+
+                switch (c) {
+
+                case 'h':
+                        systemctl_help();
+                        return 0;
+
+                case ARG_VERSION:
+                        puts(PACKAGE_STRING);
+                        puts(DISTRIBUTION);
+                        puts(SYSTEMD_FEATURES);
+                        return 0;
+
+                case 't':
+                        arg_type = optarg;
+                        break;
+
+                case 'p': {
+                        char **l;
+
+                        if (!(l = strv_append(arg_property, optarg)))
+                                return -ENOMEM;
+
+                        strv_free(arg_property);
+                        arg_property = l;
+
+                        /* If the user asked for a particular
+                         * property, show it to him, even if it is
+                         * empty. */
+                        arg_all = true;
+                        break;
+                }
+
+                case 'a':
+                        arg_all = true;
+                        break;
+
+                case ARG_FAIL:
+                        arg_job_mode = "fail";
+                        break;
+
+                case ARG_IGNORE_DEPENDENCIES:
+                        arg_job_mode = "ignore-dependencies";
+                        break;
+
+                case ARG_USER:
+                        arg_scope = UNIT_FILE_USER;
+                        break;
+
+                case ARG_SYSTEM:
+                        arg_scope = UNIT_FILE_SYSTEM;
+                        break;
+
+                case ARG_GLOBAL:
+                        arg_scope = UNIT_FILE_GLOBAL;
+                        break;
+
+                case ARG_NO_BLOCK:
+                        arg_no_block = true;
+                        break;
+
+                case ARG_NO_LEGEND:
+                        arg_no_legend = true;
+                        break;
+
+                case ARG_NO_PAGER:
+                        arg_no_pager = true;
+                        break;
+
+                case ARG_NO_WALL:
+                        arg_no_wall = true;
+                        break;
+
+                case ARG_ORDER:
+                        arg_dot = DOT_ORDER;
+                        break;
+
+                case ARG_REQUIRE:
+                        arg_dot = DOT_REQUIRE;
+                        break;
+
+                case ARG_ROOT:
+                        arg_root = optarg;
+                        break;
+
+                case ARG_FULL:
+                        arg_full = true;
+                        break;
+
+                case ARG_FAILED:
+                        arg_failed = true;
+                        break;
+
+                case 'q':
+                        arg_quiet = true;
+                        break;
+
+                case ARG_FORCE:
+                        arg_force ++;
+                        break;
+
+                case ARG_FOLLOW:
+                        arg_follow = true;
+                        break;
+
+                case 'f':
+                        /* -f is short for both --follow and --force! */
+                        arg_force ++;
+                        arg_follow = true;
+                        break;
+
+                case ARG_NO_RELOAD:
+                        arg_no_reload = true;
+                        break;
+
+                case ARG_KILL_WHO:
+                        arg_kill_who = optarg;
+                        break;
+
+                case ARG_KILL_MODE:
+                        arg_kill_mode = optarg;
+                        break;
+
+                case 's':
+                        if ((arg_signal = signal_from_string_try_harder(optarg)) < 0) {
+                                log_error("Failed to parse signal string %s.", optarg);
+                                return -EINVAL;
+                        }
+                        break;
+
+                case ARG_NO_ASK_PASSWORD:
+                        arg_ask_password = false;
+                        break;
+
+                case 'P':
+                        arg_transport = TRANSPORT_POLKIT;
+                        break;
+
+                case 'H':
+                        arg_transport = TRANSPORT_SSH;
+                        arg_host = optarg;
+                        break;
+
+                case ARG_RUNTIME:
+                        arg_runtime = true;
+                        break;
+
+                case 'n':
+                        if (safe_atou(optarg, &arg_lines) < 0) {
+                                log_error("Failed to parse lines '%s'", optarg);
+                                return -EINVAL;
+                        }
+                        break;
+
+                case 'o':
+                        arg_output = output_mode_from_string(optarg);
+                        if (arg_output < 0) {
+                                log_error("Unknown output '%s'.", optarg);
+                                return -EINVAL;
+                        }
+                        break;
+
+                case '?':
+                        return -EINVAL;
+
+                default:
+                        log_error("Unknown option code %c", c);
+                        return -EINVAL;
+                }
+        }
+
+        if (arg_transport != TRANSPORT_NORMAL && arg_scope != UNIT_FILE_SYSTEM) {
+                log_error("Cannot access user instance remotely.");
+                return -EINVAL;
+        }
+
+        return 1;
+}
+
+static int halt_parse_argv(int argc, char *argv[]) {
+
+        enum {
+                ARG_HELP = 0x100,
+                ARG_HALT,
+                ARG_REBOOT,
+                ARG_NO_WALL
+        };
+
+        static const struct option options[] = {
+                { "help",      no_argument,       NULL, ARG_HELP    },
+                { "halt",      no_argument,       NULL, ARG_HALT    },
+                { "poweroff",  no_argument,       NULL, 'p'         },
+                { "reboot",    no_argument,       NULL, ARG_REBOOT  },
+                { "force",     no_argument,       NULL, 'f'         },
+                { "wtmp-only", no_argument,       NULL, 'w'         },
+                { "no-wtmp",   no_argument,       NULL, 'd'         },
+                { "no-sync",   no_argument,       NULL, 'n'         },
+                { "no-wall",   no_argument,       NULL, ARG_NO_WALL },
+                { NULL,        0,                 NULL, 0           }
+        };
+
+        int c, runlevel;
+
+        assert(argc >= 0);
+        assert(argv);
+
+        if (utmp_get_runlevel(&runlevel, NULL) >= 0)
+                if (runlevel == '0' || runlevel == '6')
+                        arg_immediate = true;
+
+        while ((c = getopt_long(argc, argv, "pfwdnih", options, NULL)) >= 0) {
+                switch (c) {
+
+                case ARG_HELP:
+                        halt_help();
+                        return 0;
+
+                case ARG_HALT:
+                        arg_action = ACTION_HALT;
+                        break;
+
+                case 'p':
+                        if (arg_action != ACTION_REBOOT)
+                                arg_action = ACTION_POWEROFF;
+                        break;
+
+                case ARG_REBOOT:
+                        arg_action = ACTION_REBOOT;
+                        break;
+
+                case 'f':
+                        arg_immediate = true;
+                        break;
+
+                case 'w':
+                        arg_dry = true;
+                        break;
+
+                case 'd':
+                        arg_no_wtmp = true;
+                        break;
+
+                case 'n':
+                        arg_no_sync = true;
+                        break;
+
+                case ARG_NO_WALL:
+                        arg_no_wall = true;
+                        break;
+
+                case 'i':
+                case 'h':
+                        /* Compatibility nops */
+                        break;
+
+                case '?':
+                        return -EINVAL;
+
+                default:
+                        log_error("Unknown option code %c", c);
+                        return -EINVAL;
+                }
+        }
+
+        if (optind < argc) {
+                log_error("Too many arguments.");
+                return -EINVAL;
+        }
+
+        return 1;
+}
+
+static int parse_time_spec(const char *t, usec_t *_u) {
+        assert(t);
+        assert(_u);
+
+        if (streq(t, "now"))
+                *_u = 0;
+        else if (!strchr(t, ':')) {
+                uint64_t u;
+
+                if (safe_atou64(t, &u) < 0)
+                        return -EINVAL;
+
+                *_u = now(CLOCK_REALTIME) + USEC_PER_MINUTE * u;
+        } else {
+                char *e = NULL;
+                long hour, minute;
+                struct tm tm;
+                time_t s;
+                usec_t n;
+
+                errno = 0;
+                hour = strtol(t, &e, 10);
+                if (errno != 0 || *e != ':' || hour < 0 || hour > 23)
+                        return -EINVAL;
+
+                minute = strtol(e+1, &e, 10);
+                if (errno != 0 || *e != 0 || minute < 0 || minute > 59)
+                        return -EINVAL;
+
+                n = now(CLOCK_REALTIME);
+                s = (time_t) (n / USEC_PER_SEC);
+
+                zero(tm);
+                assert_se(localtime_r(&s, &tm));
+
+                tm.tm_hour = (int) hour;
+                tm.tm_min = (int) minute;
+                tm.tm_sec = 0;
+
+                assert_se(s = mktime(&tm));
+
+                *_u = (usec_t) s * USEC_PER_SEC;
+
+                while (*_u <= n)
+                        *_u += USEC_PER_DAY;
+        }
+
+        return 0;
+}
+
+static int shutdown_parse_argv(int argc, char *argv[]) {
+
+        enum {
+                ARG_HELP = 0x100,
+                ARG_NO_WALL
+        };
+
+        static const struct option options[] = {
+                { "help",      no_argument,       NULL, ARG_HELP    },
+                { "halt",      no_argument,       NULL, 'H'         },
+                { "poweroff",  no_argument,       NULL, 'P'         },
+                { "reboot",    no_argument,       NULL, 'r'         },
+                { "no-wall",   no_argument,       NULL, ARG_NO_WALL },
+                { NULL,        0,                 NULL, 0           }
+        };
+
+        int c, r;
+
+        assert(argc >= 0);
+        assert(argv);
+
+        while ((c = getopt_long(argc, argv, "HPrhkt:afFc", options, NULL)) >= 0) {
+                switch (c) {
+
+                case ARG_HELP:
+                        shutdown_help();
+                        return 0;
+
+                case 'H':
+                        arg_action = ACTION_HALT;
+                        break;
+
+                case 'P':
+                        arg_action = ACTION_POWEROFF;
+                        break;
+
+                case 'r':
+                        if (kexec_loaded())
+                                arg_action = ACTION_KEXEC;
+                        else
+                                arg_action = ACTION_REBOOT;
+                        break;
+
+                case 'h':
+                        if (arg_action != ACTION_HALT)
+                                arg_action = ACTION_POWEROFF;
+                        break;
+
+                case 'k':
+                        arg_dry = true;
+                        break;
+
+                case ARG_NO_WALL:
+                        arg_no_wall = true;
+                        break;
+
+                case 't':
+                case 'a':
+                        /* Compatibility nops */
+                        break;
+
+                case 'c':
+                        arg_action = ACTION_CANCEL_SHUTDOWN;
+                        break;
+
+                case '?':
+                        return -EINVAL;
+
+                default:
+                        log_error("Unknown option code %c", c);
+                        return -EINVAL;
+                }
+        }
+
+        if (argc > optind) {
+                if ((r = parse_time_spec(argv[optind], &arg_when)) < 0) {
+                        log_error("Failed to parse time specification: %s", argv[optind]);
+                        return r;
+                }
+        } else
+                arg_when = now(CLOCK_REALTIME) + USEC_PER_MINUTE;
+
+        /* We skip the time argument */
+        if (argc > optind + 1)
+                arg_wall = argv + optind + 1;
+
+        optind = argc;
+
+        return 1;
+}
+
+static int telinit_parse_argv(int argc, char *argv[]) {
+
+        enum {
+                ARG_HELP = 0x100,
+                ARG_NO_WALL
+        };
+
+        static const struct option options[] = {
+                { "help",      no_argument,       NULL, ARG_HELP    },
+                { "no-wall",   no_argument,       NULL, ARG_NO_WALL },
+                { NULL,        0,                 NULL, 0           }
+        };
+
+        static const struct {
+                char from;
+                enum action to;
+        } table[] = {
+                { '0', ACTION_POWEROFF },
+                { '6', ACTION_REBOOT },
+                { '1', ACTION_RESCUE },
+                { '2', ACTION_RUNLEVEL2 },
+                { '3', ACTION_RUNLEVEL3 },
+                { '4', ACTION_RUNLEVEL4 },
+                { '5', ACTION_RUNLEVEL5 },
+                { 's', ACTION_RESCUE },
+                { 'S', ACTION_RESCUE },
+                { 'q', ACTION_RELOAD },
+                { 'Q', ACTION_RELOAD },
+                { 'u', ACTION_REEXEC },
+                { 'U', ACTION_REEXEC }
+        };
+
+        unsigned i;
+        int c;
+
+        assert(argc >= 0);
+        assert(argv);
+
+        while ((c = getopt_long(argc, argv, "", options, NULL)) >= 0) {
+                switch (c) {
+
+                case ARG_HELP:
+                        telinit_help();
+                        return 0;
+
+                case ARG_NO_WALL:
+                        arg_no_wall = true;
+                        break;
+
+                case '?':
+                        return -EINVAL;
+
+                default:
+                        log_error("Unknown option code %c", c);
+                        return -EINVAL;
+                }
+        }
+
+        if (optind >= argc) {
+                telinit_help();
+                return -EINVAL;
+        }
+
+        if (optind + 1 < argc) {
+                log_error("Too many arguments.");
+                return -EINVAL;
+        }
+
+        if (strlen(argv[optind]) != 1) {
+                log_error("Expected single character argument.");
+                return -EINVAL;
+        }
+
+        for (i = 0; i < ELEMENTSOF(table); i++)
+                if (table[i].from == argv[optind][0])
+                        break;
+
+        if (i >= ELEMENTSOF(table)) {
+                log_error("Unknown command %s.", argv[optind]);
+                return -EINVAL;
+        }
+
+        arg_action = table[i].to;
+
+        optind ++;
+
+        return 1;
+}
+
+static int runlevel_parse_argv(int argc, char *argv[]) {
+
+        enum {
+                ARG_HELP = 0x100,
+        };
+
+        static const struct option options[] = {
+                { "help",      no_argument,       NULL, ARG_HELP    },
+                { NULL,        0,                 NULL, 0           }
+        };
+
+        int c;
+
+        assert(argc >= 0);
+        assert(argv);
+
+        while ((c = getopt_long(argc, argv, "", options, NULL)) >= 0) {
+                switch (c) {
+
+                case ARG_HELP:
+                        runlevel_help();
+                        return 0;
+
+                case '?':
+                        return -EINVAL;
+
+                default:
+                        log_error("Unknown option code %c", c);
+                        return -EINVAL;
+                }
+        }
+
+        if (optind < argc) {
+                log_error("Too many arguments.");
+                return -EINVAL;
+        }
+
+        return 1;
+}
+
+static int parse_argv(int argc, char *argv[]) {
+        assert(argc >= 0);
+        assert(argv);
+
+        if (program_invocation_short_name) {
+
+                if (strstr(program_invocation_short_name, "halt")) {
+                        arg_action = ACTION_HALT;
+                        return halt_parse_argv(argc, argv);
+                } else if (strstr(program_invocation_short_name, "poweroff")) {
+                        arg_action = ACTION_POWEROFF;
+                        return halt_parse_argv(argc, argv);
+                } else if (strstr(program_invocation_short_name, "reboot")) {
+                        if (kexec_loaded())
+                                arg_action = ACTION_KEXEC;
+                        else
+                                arg_action = ACTION_REBOOT;
+                        return halt_parse_argv(argc, argv);
+                } else if (strstr(program_invocation_short_name, "shutdown")) {
+                        arg_action = ACTION_POWEROFF;
+                        return shutdown_parse_argv(argc, argv);
+                } else if (strstr(program_invocation_short_name, "init")) {
+
+                        if (sd_booted() > 0) {
+                                arg_action = ACTION_INVALID;
+                                return telinit_parse_argv(argc, argv);
+                        } else {
+                                /* Hmm, so some other init system is
+                                 * running, we need to forward this
+                                 * request to it. For now we simply
+                                 * guess that it is Upstart. */
+
+                                execv("/lib/upstart/telinit", argv);
+
+                                log_error("Couldn't find an alternative telinit implementation to spawn.");
+                                return -EIO;
+                        }
+
+                } else if (strstr(program_invocation_short_name, "runlevel")) {
+                        arg_action = ACTION_RUNLEVEL;
+                        return runlevel_parse_argv(argc, argv);
+                }
+        }
+
+        arg_action = ACTION_SYSTEMCTL;
+        return systemctl_parse_argv(argc, argv);
+}
+
+static int action_to_runlevel(void) {
+
+        static const char table[_ACTION_MAX] = {
+                [ACTION_HALT] =      '0',
+                [ACTION_POWEROFF] =  '0',
+                [ACTION_REBOOT] =    '6',
+                [ACTION_RUNLEVEL2] = '2',
+                [ACTION_RUNLEVEL3] = '3',
+                [ACTION_RUNLEVEL4] = '4',
+                [ACTION_RUNLEVEL5] = '5',
+                [ACTION_RESCUE] =    '1'
+        };
+
+        assert(arg_action < _ACTION_MAX);
+
+        return table[arg_action];
+}
+
+static int talk_upstart(void) {
+        DBusMessage *m = NULL, *reply = NULL;
+        DBusError error;
+        int previous, rl, r;
+        char
+                env1_buf[] = "RUNLEVEL=X",
+                env2_buf[] = "PREVLEVEL=X";
+        char *env1 = env1_buf, *env2 = env2_buf;
+        const char *emit = "runlevel";
+        dbus_bool_t b_false = FALSE;
+        DBusMessageIter iter, sub;
+        DBusConnection *bus;
+
+        dbus_error_init(&error);
+
+        if (!(rl = action_to_runlevel()))
+                return 0;
+
+        if (utmp_get_runlevel(&previous, NULL) < 0)
+                previous = 'N';
+
+        if (!(bus = dbus_connection_open_private("unix:abstract=/com/ubuntu/upstart", &error))) {
+                if (dbus_error_has_name(&error, DBUS_ERROR_NO_SERVER)) {
+                        r = 0;
+                        goto finish;
+                }
+
+                log_error("Failed to connect to Upstart bus: %s", bus_error_message(&error));
+                r = -EIO;
+                goto finish;
+        }
+
+        if ((r = bus_check_peercred(bus)) < 0) {
+                log_error("Failed to verify owner of bus.");
+                goto finish;
+        }
+
+        if (!(m = dbus_message_new_method_call(
+                              "com.ubuntu.Upstart",
+                              "/com/ubuntu/Upstart",
+                              "com.ubuntu.Upstart0_6",
+                              "EmitEvent"))) {
+
+                log_error("Could not allocate message.");
+                r = -ENOMEM;
+                goto finish;
+        }
+
+        dbus_message_iter_init_append(m, &iter);
+
+        env1_buf[sizeof(env1_buf)-2] = rl;
+        env2_buf[sizeof(env2_buf)-2] = previous;
+
+        if (!dbus_message_iter_append_basic(&iter, DBUS_TYPE_STRING, &emit) ||
+            !dbus_message_iter_open_container(&iter, DBUS_TYPE_ARRAY, "s", &sub) ||
+            !dbus_message_iter_append_basic(&sub, DBUS_TYPE_STRING, &env1) ||
+            !dbus_message_iter_append_basic(&sub, DBUS_TYPE_STRING, &env2) ||
+            !dbus_message_iter_close_container(&iter, &sub) ||
+            !dbus_message_iter_append_basic(&iter, DBUS_TYPE_BOOLEAN, &b_false)) {
+                log_error("Could not append arguments to message.");
+                r = -ENOMEM;
+                goto finish;
+        }
+
+        if (!(reply = dbus_connection_send_with_reply_and_block(bus, m, -1, &error))) {
+
+                if (error_is_no_service(&error)) {
+                        r = -EADDRNOTAVAIL;
+                        goto finish;
+                }
+
+                log_error("Failed to issue method call: %s", bus_error_message(&error));
+                r = -EIO;
+                goto finish;
+        }
+
+        r = 1;
+
+finish:
+        if (m)
+                dbus_message_unref(m);
+
+        if (reply)
+                dbus_message_unref(reply);
+
+        if (bus) {
+                dbus_connection_flush(bus);
+                dbus_connection_close(bus);
+                dbus_connection_unref(bus);
+        }
+
+        dbus_error_free(&error);
+
+        return r;
+}
+
+static int talk_initctl(void) {
+        struct init_request request;
+        int r, fd;
+        char rl;
+
+        if (!(rl = action_to_runlevel()))
+                return 0;
+
+        zero(request);
+        request.magic = INIT_MAGIC;
+        request.sleeptime = 0;
+        request.cmd = INIT_CMD_RUNLVL;
+        request.runlevel = rl;
+
+        if ((fd = open(INIT_FIFO, O_WRONLY|O_NDELAY|O_CLOEXEC|O_NOCTTY)) < 0) {
+
+                if (errno == ENOENT)
+                        return 0;
+
+                log_error("Failed to open "INIT_FIFO": %m");
+                return -errno;
+        }
+
+        errno = 0;
+        r = loop_write(fd, &request, sizeof(request), false) != sizeof(request);
+        close_nointr_nofail(fd);
+
+        if (r < 0) {
+                log_error("Failed to write to "INIT_FIFO": %m");
+                return errno ? -errno : -EIO;
+        }
+
+        return 1;
+}
+
+static int systemctl_main(DBusConnection *bus, int argc, char *argv[], DBusError *error) {
+
+        static const struct {
+                const char* verb;
+                const enum {
+                        MORE,
+                        LESS,
+                        EQUAL
+                } argc_cmp;
+                const int argc;
+                int (* const dispatch)(DBusConnection *bus, char **args);
+        } verbs[] = {
+                { "list-units",            LESS,  1, list_units        },
+                { "list-unit-files",       EQUAL, 1, list_unit_files   },
+                { "list-jobs",             EQUAL, 1, list_jobs         },
+                { "clear-jobs",            EQUAL, 1, daemon_reload     },
+                { "load",                  MORE,  2, load_unit         },
+                { "cancel",                MORE,  2, cancel_job        },
+                { "start",                 MORE,  2, start_unit        },
+                { "stop",                  MORE,  2, start_unit        },
+                { "condstop",              MORE,  2, start_unit        }, /* For compatibility with ALTLinux */
+                { "reload",                MORE,  2, start_unit        },
+                { "restart",               MORE,  2, start_unit        },
+                { "try-restart",           MORE,  2, start_unit        },
+                { "reload-or-restart",     MORE,  2, start_unit        },
+                { "reload-or-try-restart", MORE,  2, start_unit        },
+                { "force-reload",          MORE,  2, start_unit        }, /* For compatibility with SysV */
+                { "condreload",            MORE,  2, start_unit        }, /* For compatibility with ALTLinux */
+                { "condrestart",           MORE,  2, start_unit        }, /* For compatibility with RH */
+                { "isolate",               EQUAL, 2, start_unit        },
+                { "kill",                  MORE,  2, kill_unit         },
+                { "is-active",             MORE,  2, check_unit        },
+                { "check",                 MORE,  2, check_unit        },
+                { "show",                  MORE,  1, show              },
+                { "status",                MORE,  2, show              },
+                { "dump",                  EQUAL, 1, dump              },
+                { "dot",                   EQUAL, 1, dot               },
+                { "snapshot",              LESS,  2, snapshot          },
+                { "delete",                MORE,  2, delete_snapshot   },
+                { "daemon-reload",         EQUAL, 1, daemon_reload     },
+                { "daemon-reexec",         EQUAL, 1, daemon_reload     },
+                { "show-environment",      EQUAL, 1, show_enviroment   },
+                { "set-environment",       MORE,  2, set_environment   },
+                { "unset-environment",     MORE,  2, set_environment   },
+                { "halt",                  EQUAL, 1, start_special     },
+                { "poweroff",              EQUAL, 1, start_special     },
+                { "reboot",                EQUAL, 1, start_special     },
+                { "kexec",                 EQUAL, 1, start_special     },
+                { "default",               EQUAL, 1, start_special     },
+                { "rescue",                EQUAL, 1, start_special     },
+                { "emergency",             EQUAL, 1, start_special     },
+                { "exit",                  EQUAL, 1, start_special     },
+                { "reset-failed",          MORE,  1, reset_failed      },
+                { "enable",                MORE,  2, enable_unit       },
+                { "disable",               MORE,  2, enable_unit       },
+                { "is-enabled",            MORE,  2, unit_is_enabled   },
+                { "reenable",              MORE,  2, enable_unit       },
+                { "preset",                MORE,  2, enable_unit       },
+                { "mask",                  MORE,  2, enable_unit       },
+                { "unmask",                MORE,  2, enable_unit       },
+                { "link",                  MORE,  2, enable_unit       }
+        };
+
+        int left;
+        unsigned i;
+
+        assert(argc >= 0);
+        assert(argv);
+        assert(error);
+
+        left = argc - optind;
+
+        if (left <= 0)
+                /* Special rule: no arguments means "list-units" */
+                i = 0;
+        else {
+                if (streq(argv[optind], "help")) {
+                        systemctl_help();
+                        return 0;
+                }
+
+                for (i = 0; i < ELEMENTSOF(verbs); i++)
+                        if (streq(argv[optind], verbs[i].verb))
+                                break;
+
+                if (i >= ELEMENTSOF(verbs)) {
+                        log_error("Unknown operation %s", argv[optind]);
+                        return -EINVAL;
+                }
+        }
+
+        switch (verbs[i].argc_cmp) {
+
+        case EQUAL:
+                if (left != verbs[i].argc) {
+                        log_error("Invalid number of arguments.");
+                        return -EINVAL;
+                }
+
+                break;
+
+        case MORE:
+                if (left < verbs[i].argc) {
+                        log_error("Too few arguments.");
+                        return -EINVAL;
+                }
+
+                break;
+
+        case LESS:
+                if (left > verbs[i].argc) {
+                        log_error("Too many arguments.");
+                        return -EINVAL;
+                }
+
+                break;
+
+        default:
+                assert_not_reached("Unknown comparison operator.");
+        }
+
+        /* Require a bus connection for all operations but
+         * enable/disable */
+        if (!streq(verbs[i].verb, "enable") &&
+            !streq(verbs[i].verb, "disable") &&
+            !streq(verbs[i].verb, "is-enabled") &&
+            !streq(verbs[i].verb, "list-unit-files") &&
+            !streq(verbs[i].verb, "reenable") &&
+            !streq(verbs[i].verb, "preset") &&
+            !streq(verbs[i].verb, "mask") &&
+            !streq(verbs[i].verb, "unmask") &&
+            !streq(verbs[i].verb, "link")) {
+
+                if (running_in_chroot() > 0) {
+                        log_info("Running in chroot, ignoring request.");
+                        return 0;
+                }
+
+                if (!bus) {
+                        log_error("Failed to get D-Bus connection: %s",
+                                  dbus_error_is_set(error) ? error->message : "No connection to service manager.");
+                        return -EIO;
+                }
+
+        } else {
+
+                if (!bus && !avoid_bus()) {
+                        log_error("Failed to get D-Bus connection: %s",
+                                  dbus_error_is_set(error) ? error->message : "No connection to service manager.");
+                        return -EIO;
+                }
+        }
+
+        return verbs[i].dispatch(bus, argv + optind);
+}
+
+static int send_shutdownd(usec_t t, char mode, bool dry_run, bool warn, const char *message) {
+        int fd = -1;
+        struct msghdr msghdr;
+        struct iovec iovec;
+        union sockaddr_union sockaddr;
+        struct shutdownd_command c;
+
+        zero(c);
+        c.elapse = t;
+        c.mode = mode;
+        c.dry_run = dry_run;
+        c.warn_wall = warn;
+
+        if (message)
+                strncpy(c.wall_message, message, sizeof(c.wall_message));
+
+        if ((fd = socket(AF_UNIX, SOCK_DGRAM|SOCK_CLOEXEC, 0)) < 0)
+                return -errno;
+
+        zero(sockaddr);
+        sockaddr.sa.sa_family = AF_UNIX;
+        sockaddr.un.sun_path[0] = 0;
+        strncpy(sockaddr.un.sun_path, "/run/systemd/shutdownd", sizeof(sockaddr.un.sun_path));
+
+        zero(iovec);
+        iovec.iov_base = (char*) &c;
+        iovec.iov_len = sizeof(c);
+
+        zero(msghdr);
+        msghdr.msg_name = &sockaddr;
+        msghdr.msg_namelen = offsetof(struct sockaddr_un, sun_path) + sizeof("/run/systemd/shutdownd") - 1;
+
+        msghdr.msg_iov = &iovec;
+        msghdr.msg_iovlen = 1;
+
+        if (sendmsg(fd, &msghdr, MSG_NOSIGNAL) < 0) {
+                close_nointr_nofail(fd);
+                return -errno;
+        }
+
+        close_nointr_nofail(fd);
+        return 0;
+}
+
+static int reload_with_fallback(DBusConnection *bus) {
+
+        if (bus) {
+                /* First, try systemd via D-Bus. */
+                if (daemon_reload(bus, NULL) >= 0)
+                        return 0;
+        }
+
+        /* Nothing else worked, so let's try signals */
+        assert(arg_action == ACTION_RELOAD || arg_action == ACTION_REEXEC);
+
+        if (kill(1, arg_action == ACTION_RELOAD ? SIGHUP : SIGTERM) < 0) {
+                log_error("kill() failed: %m");
+                return -errno;
+        }
+
+        return 0;
+}
+
+static int start_with_fallback(DBusConnection *bus) {
+
+        if (bus) {
+                /* First, try systemd via D-Bus. */
+                if (start_unit(bus, NULL) >= 0)
+                        goto done;
+        }
+
+        /* Hmm, talking to systemd via D-Bus didn't work. Then
+         * let's try to talk to Upstart via D-Bus. */
+        if (talk_upstart() > 0)
+                goto done;
+
+        /* Nothing else worked, so let's try
+         * /dev/initctl */
+        if (talk_initctl() > 0)
+                goto done;
+
+        log_error("Failed to talk to init daemon.");
+        return -EIO;
+
+done:
+        warn_wall(arg_action);
+        return 0;
+}
+
+static void halt_now(enum action a) {
+
+       /* Make sure C-A-D is handled by the kernel from this
+         * point on... */
+        reboot(RB_ENABLE_CAD);
+
+        switch (a) {
+
+        case ACTION_HALT:
+                log_info("Halting.");
+                reboot(RB_HALT_SYSTEM);
+                break;
+
+        case ACTION_POWEROFF:
+                log_info("Powering off.");
+                reboot(RB_POWER_OFF);
+                break;
+
+        case ACTION_REBOOT:
+                log_info("Rebooting.");
+                reboot(RB_AUTOBOOT);
+                break;
+
+        default:
+                assert_not_reached("Unknown halt action.");
+        }
+
+        assert_not_reached("Uh? This shouldn't happen.");
+}
+
+static int halt_main(DBusConnection *bus) {
+        int r;
+
+        if (geteuid() != 0) {
+                if (arg_action == ACTION_POWEROFF ||
+                    arg_action == ACTION_REBOOT) {
+                        r = reboot_with_logind(bus, arg_action);
+                        if (r >= 0)
+                                return r;
+                }
+
+                log_error("Must be root.");
+                return -EPERM;
+        }
+
+        if (arg_when > 0) {
+                char *m;
+                char date[FORMAT_TIMESTAMP_MAX];
+
+                m = strv_join(arg_wall, " ");
+                r = send_shutdownd(arg_when,
+                                   arg_action == ACTION_HALT     ? 'H' :
+                                   arg_action == ACTION_POWEROFF ? 'P' :
+                                                                   'r',
+                                   arg_dry,
+                                   !arg_no_wall,
+                                   m);
+                free(m);
+
+                if (r < 0)
+                        log_warning("Failed to talk to shutdownd, proceeding with immediate shutdown: %s", strerror(-r));
+                else {
+                        log_info("Shutdown scheduled for %s, use 'shutdown -c' to cancel.",
+                                 format_timestamp(date, sizeof(date), arg_when));
+                        return 0;
+                }
+        }
+
+        if (!arg_dry && !arg_immediate)
+                return start_with_fallback(bus);
+
+        if (!arg_no_wtmp) {
+                if (sd_booted() > 0)
+                        log_debug("Not writing utmp record, assuming that systemd-update-utmp is used.");
+                else if ((r = utmp_put_shutdown()) < 0)
+                        log_warning("Failed to write utmp record: %s", strerror(-r));
+        }
+
+        if (!arg_no_sync)
+                sync();
+
+        if (arg_dry)
+                return 0;
+
+        halt_now(arg_action);
+        /* We should never reach this. */
+        return -ENOSYS;
+}
+
+static int runlevel_main(void) {
+        int r, runlevel, previous;
+
+        r = utmp_get_runlevel(&runlevel, &previous);
+        if (r < 0) {
+                puts("unknown");
+                return r;
+        }
+
+        printf("%c %c\n",
+               previous <= 0 ? 'N' : previous,
+               runlevel <= 0 ? 'N' : runlevel);
+
+        return 0;
+}
+
+int main(int argc, char*argv[]) {
+        int r, retval = EXIT_FAILURE;
+        DBusConnection *bus = NULL;
+        DBusError error;
+
+        dbus_error_init(&error);
+
+        log_parse_environment();
+        log_open();
+
+        if ((r = parse_argv(argc, argv)) < 0)
+                goto finish;
+        else if (r == 0) {
+                retval = EXIT_SUCCESS;
+                goto finish;
+        }
+
+        /* /sbin/runlevel doesn't need to communicate via D-Bus, so
+         * let's shortcut this */
+        if (arg_action == ACTION_RUNLEVEL) {
+                r = runlevel_main();
+                retval = r < 0 ? EXIT_FAILURE : r;
+                goto finish;
+        }
+
+        if (running_in_chroot() > 0 && arg_action != ACTION_SYSTEMCTL) {
+                log_info("Running in chroot, ignoring request.");
+                retval = 0;
+                goto finish;
+        }
+
+        if (!avoid_bus()) {
+                if (arg_transport == TRANSPORT_NORMAL)
+                        bus_connect(arg_scope == UNIT_FILE_SYSTEM ? DBUS_BUS_SYSTEM : DBUS_BUS_SESSION, &bus, &private_bus, &error);
+                else if (arg_transport == TRANSPORT_POLKIT) {
+                        bus_connect_system_polkit(&bus, &error);
+                        private_bus = false;
+                } else if (arg_transport == TRANSPORT_SSH) {
+                        bus_connect_system_ssh(NULL, arg_host, &bus, &error);
+                        private_bus = false;
+                } else
+                        assert_not_reached("Uh, invalid transport...");
+        }
+
+        switch (arg_action) {
+
+        case ACTION_SYSTEMCTL:
+                r = systemctl_main(bus, argc, argv, &error);
+                break;
+
+        case ACTION_HALT:
+        case ACTION_POWEROFF:
+        case ACTION_REBOOT:
+        case ACTION_KEXEC:
+                r = halt_main(bus);
+                break;
+
+        case ACTION_RUNLEVEL2:
+        case ACTION_RUNLEVEL3:
+        case ACTION_RUNLEVEL4:
+        case ACTION_RUNLEVEL5:
+        case ACTION_RESCUE:
+        case ACTION_EMERGENCY:
+        case ACTION_DEFAULT:
+                r = start_with_fallback(bus);
+                break;
+
+        case ACTION_RELOAD:
+        case ACTION_REEXEC:
+                r = reload_with_fallback(bus);
+                break;
+
+        case ACTION_CANCEL_SHUTDOWN:
+                r = send_shutdownd(0, 0, false, false, NULL);
+                break;
+
+        case ACTION_INVALID:
+        case ACTION_RUNLEVEL:
+        default:
+                assert_not_reached("Unknown action");
+        }
+
+        retval = r < 0 ? EXIT_FAILURE : r;
+
+finish:
+        if (bus) {
+                dbus_connection_flush(bus);
+                dbus_connection_close(bus);
+                dbus_connection_unref(bus);
+        }
+
+        dbus_error_free(&error);
+
+        dbus_shutdown();
+
+        strv_free(arg_property);
+
+        pager_close();
+        agent_close();
+
+        return retval;
+}
diff --git a/src/systemd-analyze b/src/systemd-analyze
new file mode 100755 (executable)
index 0000000..a49fbb7
--- /dev/null
@@ -0,0 +1,276 @@
+#!/usr/bin/python
+
+import dbus, sys
+
+def acquire_time_data():
+
+        manager = dbus.Interface(bus.get_object('org.freedesktop.systemd1', '/org/freedesktop/systemd1'), 'org.freedesktop.systemd1.Manager')
+        units = manager.ListUnits()
+
+        l = []
+
+        for i in units:
+                if i[5] != "":
+                        continue
+
+                properties = dbus.Interface(bus.get_object('org.freedesktop.systemd1', i[6]), 'org.freedesktop.DBus.Properties')
+
+                ixt = int(properties.Get('org.freedesktop.systemd1.Unit', 'InactiveExitTimestampMonotonic'))
+                aet = int(properties.Get('org.freedesktop.systemd1.Unit', 'ActiveEnterTimestampMonotonic'))
+                axt = int(properties.Get('org.freedesktop.systemd1.Unit', 'ActiveExitTimestampMonotonic'))
+                iet = int(properties.Get('org.freedesktop.systemd1.Unit', 'InactiveEnterTimestampMonotonic'))
+
+                l.append((str(i[0]), ixt, aet, axt, iet))
+
+        return l
+
+def acquire_start_time():
+        properties = dbus.Interface(bus.get_object('org.freedesktop.systemd1', '/org/freedesktop/systemd1'), 'org.freedesktop.DBus.Properties')
+
+        initrd_time = int(properties.Get('org.freedesktop.systemd1.Manager', 'InitRDTimestampMonotonic'))
+        startup_time = int(properties.Get('org.freedesktop.systemd1.Manager', 'StartupTimestampMonotonic'))
+        finish_time = int(properties.Get('org.freedesktop.systemd1.Manager', 'FinishTimestampMonotonic'))
+
+        if finish_time == 0:
+                sys.stderr.write("Bootup is not yet finished. Please try again later.\n")
+                sys.exit(1)
+
+        assert initrd_time <= startup_time
+        assert startup_time <= finish_time
+
+        return initrd_time, startup_time, finish_time
+
+def draw_box(context, j, k, l, m, r = 0, g = 0, b = 0):
+        context.save()
+        context.set_source_rgb(r, g, b)
+        context.rectangle(j, k, l, m)
+        context.fill()
+        context.restore()
+
+def draw_text(context, x, y, text, size = 12, r = 0, g = 0, b = 0, vcenter = 0.5, hcenter = 0.5):
+        context.save()
+
+        context.set_source_rgb(r, g, b)
+        context.select_font_face("Sans", cairo.FONT_SLANT_NORMAL, cairo.FONT_WEIGHT_NORMAL)
+        context.set_font_size(size)
+
+        if vcenter or hcenter:
+                x_bearing, y_bearing, width, height = context.text_extents(text)[:4]
+
+                if hcenter:
+                        x = x - width*hcenter - x_bearing
+
+                if vcenter:
+                        y = y - height*vcenter - y_bearing
+
+        context.move_to(x, y)
+        context.show_text(text)
+
+        context.restore()
+
+def help():
+        sys.stdout.write("""systemd-analyze time
+systemd-analyze blame
+systemd-analyze plot
+
+Process systemd profiling information
+
+  -h --help         Show this help
+""")
+
+
+bus = dbus.SystemBus()
+
+if len(sys.argv) <= 1 or sys.argv[1] == 'time':
+
+        initrd_time, start_time, finish_time = acquire_start_time()
+
+        if initrd_time > 0:
+                print "Startup finished in %lums (kernel) + %lums (initramfs) + %lums (userspace) = %lums" % ( \
+                        initrd_time/1000, \
+                        (start_time - initrd_time)/1000, \
+                        (finish_time - start_time)/1000, \
+                        finish_time/1000)
+        else:
+                print "Startup finished in %lums (kernel) + %lums (userspace) = %lums" % ( \
+                        start_time/1000, \
+                        (finish_time - start_time)/1000, \
+                        finish_time/1000)
+
+
+elif sys.argv[1] == 'blame':
+
+        data = acquire_time_data()
+        s = sorted(data, key = lambda i: i[2] - i[1], reverse = True)
+
+        for name, ixt, aet, axt, iet in s:
+
+                if ixt <= 0 or aet <= 0:
+                        continue
+
+                if aet <= ixt:
+                        continue
+
+                sys.stdout.write("%6lums %s\n" % ((aet - ixt) / 1000, name))
+
+elif sys.argv[1] == 'plot':
+        import cairo, os
+
+        initrd_time, start_time, finish_time = acquire_start_time()
+        data = acquire_time_data()
+        s = sorted(data, key = lambda i: i[1])
+
+        # Account for kernel and initramfs bars if they exist
+        if initrd_time > 0:
+                count = 3
+        else:
+                count = 2
+
+        for name, ixt, aet, axt, iet in s:
+
+                if (ixt >= start_time and ixt <= finish_time) or \
+                                (aet >= start_time and aet <= finish_time) or \
+                                (axt >= start_time and axt <= finish_time):
+                        count += 1
+
+        border = 100
+        bar_height = 20
+        bar_space = bar_height * 0.1
+
+        # 1000px = 10s, 1px = 10ms
+        width = finish_time/10000 + border*2
+        height = count * (bar_height + bar_space) + border * 2
+
+        if width < 1000:
+                width = 1000
+
+        surface = cairo.SVGSurface(sys.stdout, width, height)
+        context = cairo.Context(surface)
+
+        draw_box(context, 0, 0, width, height, 1, 1, 1)
+
+        context.translate(border + 0.5, border + 0.5)
+
+        context.save()
+        context.set_line_width(1)
+        context.set_source_rgb(0.7, 0.7, 0.7)
+
+        for x in range(0, finish_time/10000 + 100, 100):
+                context.move_to(x, 0)
+                context.line_to(x, height-border*2)
+
+        context.move_to(0, 0)
+        context.line_to(width-border*2, 0)
+
+        context.move_to(0, height-border*2)
+        context.line_to(width-border*2, height-border*2)
+
+        context.stroke()
+        context.restore()
+
+        osrel = "Linux"
+        if os.path.exists("/etc/os-release"):
+                for line in open("/etc/os-release"):
+                        if line.startswith('PRETTY_NAME='):
+                                osrel = line[12:]
+                                osrel = osrel.strip('\"\n')
+                                break
+
+        banner = "{} {} ({} {}) {}".format(osrel, *(os.uname()[1:5]))
+        draw_text(context, 0, -15, banner, hcenter = 0, vcenter = 1)
+
+        for x in range(0, finish_time/10000 + 100, 100):
+                draw_text(context, x, -5, "%lus" % (x/100), vcenter = 0, hcenter = 0)
+
+        y = 0
+
+        # draw boxes for kernel and initramfs boot time
+        if initrd_time > 0:
+                draw_box(context, 0, y, initrd_time/10000, bar_height, 0.7, 0.7, 0.7)
+                draw_text(context, 10, y + bar_height/2, "kernel", hcenter = 0)
+                y += bar_height + bar_space
+
+                draw_box(context, initrd_time/10000, y, start_time/10000-initrd_time/10000, bar_height, 0.7, 0.7, 0.7)
+                draw_text(context, initrd_time/10000 + 10, y + bar_height/2, "initramfs", hcenter = 0)
+                y += bar_height + bar_space
+
+        else:
+                draw_box(context, 0, y, start_time/10000, bar_height, 0.6, 0.6, 0.6)
+                draw_text(context, 10, y + bar_height/2, "kernel", hcenter = 0)
+                y += bar_height + bar_space
+
+        draw_box(context, start_time/10000, y, finish_time/10000-start_time/10000, bar_height, 0.7, 0.7, 0.7)
+        draw_text(context, start_time/10000 + 10, y + bar_height/2, "userspace", hcenter = 0)
+        y += bar_height + bar_space
+
+        for name, ixt, aet, axt, iet in s:
+
+                drawn = False
+                left = -1
+
+                if ixt >= start_time and ixt <= finish_time:
+
+                        # Activating
+                        a = ixt
+                        b = min(filter(lambda x: x >= ixt, (aet, axt, iet, finish_time))) - ixt
+
+                        draw_box(context, a/10000, y, b/10000, bar_height, 1, 0, 0)
+                        drawn = True
+
+                        if left < 0:
+                                left = a
+
+                if aet >= start_time and aet <= finish_time:
+
+                        # Active
+                        a = aet
+                        b = min(filter(lambda x: x >= aet, (axt, iet, finish_time))) - aet
+
+                        draw_box(context, a/10000, y, b/10000, bar_height, .8, .6, .6)
+                        drawn = True
+
+                        if left < 0:
+                                left = a
+
+                if axt >= start_time and axt <= finish_time:
+
+                        # Deactivating
+                        a = axt
+                        b = min(filter(lambda x: x >= axt, (iet, finish_time))) - axt
+
+                        draw_box(context, a/10000, y, b/10000, bar_height, .6, .4, .4)
+                        drawn = True
+
+                        if left < 0:
+                                left = a
+
+                if drawn:
+                        x = left/10000
+
+                        if x < width/2-border:
+                                draw_text(context, x + 10, y + bar_height/2, name, hcenter = 0)
+                        else:
+                                draw_text(context, x - 10, y + bar_height/2, name, hcenter = 1)
+
+                        y += bar_height + bar_space
+
+        draw_text(context, 0, height-border*2, "Legend: Red = Activating; Pink = Active; Dark Pink = Deactivating", hcenter = 0, vcenter = -1)
+
+        if initrd_time > 0:
+                draw_text(context, 0, height-border*2 + bar_height, "Startup finished in %lums (kernel) + %lums (initramfs) + %lums (userspace) = %lums" % ( \
+                        initrd_time/1000, \
+                        (start_time - initrd_time)/1000, \
+                        (finish_time - start_time)/1000, \
+                        finish_time/1000), hcenter = 0, vcenter = -1)
+        else:
+                draw_text(context, 0, height-border*2 + bar_height, "Startup finished in %lums (kernel) + %lums (userspace) = %lums" % ( \
+                        start_time/1000, \
+                        (finish_time - start_time)/1000, \
+                        finish_time/1000), hcenter = 0, vcenter = -1)
+
+        surface.finish()
+elif sys.argv[1] in ("help", "--help", "-h"):
+        help()
+else:
+        sys.stderr.write("Unknown verb '%s'.\n" % sys.argv[1])
+        sys.exit(1)
diff --git a/src/systemd-bash-completion.sh b/src/systemd-bash-completion.sh
new file mode 100644 (file)
index 0000000..62601de
--- /dev/null
@@ -0,0 +1,281 @@
+# This file is part of systemd.
+#
+# Copyright 2010 Ran Benita
+#
+# 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 <http://www.gnu.org/licenses/>.
+
+__systemctl() {
+        systemctl --full --no-legend "$@"
+}
+
+__contains_word () {
+        local word=$1; shift
+        for w in $*; do [[ $w = $word ]] && return 0; done
+        return 1
+}
+
+__filter_units_by_property () {
+        local property=$1 value=$2 ; shift 2
+        local units=("$@")
+        local props
+        IFS=$'\n' read -rd '' -a props < \
+            <(__systemctl show --property "$property" -- "${units[@]}")
+        for ((i=0; $i < ${#units[*]}; i++)); do
+                if [[ "${props[i]}" = "$property=$value" ]]; then
+                        echo "${units[i]}"
+                fi
+        done
+}
+
+__get_all_units      () { __systemctl list-units --all \
+        | { while read a b; do echo "$a"; done; }; }
+__get_active_units   () { __systemctl list-units       \
+        | { while read a b; do echo "$a"; done; }; }
+__get_inactive_units () { __systemctl list-units --all \
+        | { while read a b c d; do [[ $c == "inactive" ]] && echo "$a"; done; }; }
+__get_failed_units   () { __systemctl list-units       \
+        | { while read a b c d; do [[ $c == "failed"   ]] && echo "$a"; done; }; }
+__get_enabled_units  () { __systemctl list-unit-files  \
+        | { while read a b c  ; do [[ $b == "enabled"  ]] && echo "$a"; done; }; }
+__get_disabled_units () { __systemctl list-unit-files  \
+        | { while read a b c  ; do [[ $b == "disabled" ]] && echo "$a"; done; }; }
+__get_masked_units   () { __systemctl list-unit-files  \
+        | { while read a b c  ; do [[ $b == "masked"   ]] && echo "$a"; done; }; }
+
+_systemctl () {
+        local cur=${COMP_WORDS[COMP_CWORD]} prev=${COMP_WORDS[COMP_CWORD-1]}
+        local verb comps
+
+        local -A OPTS=(
+               [STANDALONE]='--all -a --defaults --fail --ignore-dependencies --failed --force -f --full --global
+                             --help -h --no-ask-password --no-block --no-legend --no-pager --no-reload --no-wall
+                             --order --require --quiet -q --privileged -P --system --user --version --runtime'
+                      [ARG]='--host -H --kill-mode --kill-who --property -p --signal -s --type -t --root'
+        )
+
+        if __contains_word "$prev" ${OPTS[ARG]}; then
+                case $prev in
+                        --signal|-s)
+                                comps=$(compgen -A signal)
+                        ;;
+                        --type|-t)
+                                comps='automount device mount path service snapshot socket swap target timer'
+                        ;;
+                        --kill-who)
+                                comps='all control main'
+                        ;;
+                        --kill-mode)
+                                comps='control-group process'
+                        ;;
+                        --root)
+                                comps=$(compgen -A directory -- "$cur" )
+                                compopt -o filenames
+                        ;;
+                        --host|-H)
+                                comps=$(compgen -A hostname)
+                        ;;
+                        --property|-p)
+                                comps=''
+                        ;;
+                esac
+                COMPREPLY=( $(compgen -W "$comps" -- "$cur") )
+                return 0
+        fi
+
+
+        if [[ "$cur" = -* ]]; then
+                COMPREPLY=( $(compgen -W "${OPTS[*]}" -- "$cur") )
+                return 0
+        fi
+
+        local -A VERBS=(
+                [ALL_UNITS]='is-active is-enabled status show mask preset'
+            [ENABLED_UNITS]='disable reenable'
+           [DISABLED_UNITS]='enable'
+             [FAILED_UNITS]='reset-failed'
+          [STARTABLE_UNITS]='start'
+          [STOPPABLE_UNITS]='stop condstop kill try-restart condrestart'
+         [ISOLATABLE_UNITS]='isolate'
+         [RELOADABLE_UNITS]='reload condreload reload-or-try-restart force-reload'
+        [RESTARTABLE_UNITS]='restart reload-or-restart'
+             [MASKED_UNITS]='unmask'
+                     [JOBS]='cancel'
+                [SNAPSHOTS]='delete'
+                     [ENVS]='set-environment unset-environment'
+               [STANDALONE]='daemon-reexec daemon-reload default dot dump
+                             emergency exit halt kexec list-jobs list-units
+                             list-unit-files poweroff reboot rescue show-environment'
+                     [NAME]='snapshot load'
+                     [FILE]='link'
+        )
+
+        for ((i=0; $i <= $COMP_CWORD; i++)); do
+                if __contains_word "${COMP_WORDS[i]}" ${VERBS[*]} &&
+                 ! __contains_word "${COMP_WORDS[i-1]}" ${OPTS[ARG}]}; then
+                        verb=${COMP_WORDS[i]}
+                        break
+                fi
+        done
+
+        if   [[ -z $verb ]]; then
+                comps="${VERBS[*]}"
+
+        elif __contains_word "$verb" ${VERBS[ALL_UNITS]}; then
+                comps=$( __get_all_units )
+
+        elif __contains_word "$verb" ${VERBS[ENABLED_UNITS]}; then
+                comps=$( __get_enabled_units )
+
+        elif __contains_word "$verb" ${VERBS[DISABLED_UNITS]}; then
+                comps=$( __get_disabled_units )
+
+        elif __contains_word "$verb" ${VERBS[STARTABLE_UNITS]}; then
+                comps=$( __filter_units_by_property CanStart yes \
+                      $( __get_inactive_units \
+                       | while read line; do \
+                               [[ "$line" =~ \.(device|snapshot)$ ]] || echo "$line"; \
+                       done ))
+
+        elif __contains_word "$verb" ${VERBS[RESTARTABLE_UNITS]}; then
+                comps=$( __filter_units_by_property CanStart yes \
+                      $( __get_all_units \
+                       | while read line; do \
+                               [[ "$line" =~ \.(device|snapshot|socket|timer)$ ]] || echo "$line"; \
+                       done ))
+
+        elif __contains_word "$verb" ${VERBS[STOPPABLE_UNITS]}; then
+                comps=$( __filter_units_by_property CanStop yes \
+                      $( __get_active_units ) )
+
+        elif __contains_word "$verb" ${VERBS[RELOADABLE_UNITS]}; then
+                comps=$( __filter_units_by_property CanReload yes \
+                      $( __get_active_units ) )
+
+        elif __contains_word "$verb" ${VERBS[ISOLATABLE_UNITS]}; then
+                comps=$( __filter_units_by_property AllowIsolate yes \
+                      $( __get_all_units ) )
+
+        elif __contains_word "$verb" ${VERBS[FAILED_UNITS]}; then
+                comps=$( __get_failed_units )
+
+        elif __contains_word "$verb" ${VERBS[MASKED_UNITS]}; then
+                comps=$( __get_masked_units )
+
+        elif __contains_word "$verb" ${VERBS[STANDALONE]} ${VERBS[NAME]}; then
+                comps=''
+
+        elif __contains_word "$verb" ${VERBS[JOBS]}; then
+                comps=$( __systemctl list-jobs | { while read a b; do echo "$a"; done; } )
+
+        elif __contains_word "$verb" ${VERBS[SNAPSHOTS]}; then
+                comps=$( __systemctl list-units --type snapshot --full --all \
+                        | { while read a b; do echo "$a"; done; } )
+
+        elif __contains_word "$verb" ${VERBS[ENVS]}; then
+                comps=$( __systemctl show-environment \
+                    | while read line; do echo "${line%%=*}=";done )
+                compopt -o nospace
+
+        elif __contains_word "$verb" ${VERBS[FILE]}; then
+                comps=$( compgen -A file -- "$cur" )
+                compopt -o filenames
+        fi
+
+        COMPREPLY=( $(compgen -W "$comps" -- "$cur") )
+        return 0
+}
+complete -F _systemctl systemctl
+
+__get_all_sessions () { systemd-loginctl list-sessions | { while read a b; do echo "$a"; done; } ; }
+__get_all_users    () { systemd-loginctl list-users    | { while read a b; do echo "$b"; done; } ; }
+__get_all_seats    () { systemd-loginctl list-seats    | { while read a b; do echo "$a"; done; } ; }
+
+_loginctl () {
+        local cur=${COMP_WORDS[COMP_CWORD]} prev=${COMP_WORDS[COMP_CWORD-1]}
+        local verb comps
+
+        local -A OPTS=(
+               [STANDALONE]='--all -a --help -h --no-pager --privileged -P --version'
+                      [ARG]='--host -H --kill-who --property -p --signal -s'
+        )
+
+        if __contains_word "$prev" ${OPTS[ARG]}; then
+                case $prev in
+                        --signal|-s)
+                                comps=$(compgen -A signal)
+                        ;;
+                        --kill-who)
+                                comps='all leader'
+                        ;;
+                        --host|-H)
+                                comps=$(compgen -A hostname)
+                        ;;
+                        --property|-p)
+                                comps=''
+                        ;;
+                esac
+                COMPREPLY=( $(compgen -W "$comps" -- "$cur") )
+                return 0
+        fi
+
+
+        if [[ "$cur" = -* ]]; then
+                COMPREPLY=( $(compgen -W "${OPTS[*]}" -- "$cur") )
+                return 0
+        fi
+
+        local -A VERBS=(
+                [SESSIONS]='session-status show-session activate lock-session unlock-session terminate-session kill-session'
+                [USERS]='user-status show-user enable-linger disable-linger terminate-user kill-user'
+                [SEATS]='seat-status show-seat terminate-seat'
+                [STANDALONE]='list-sessions list-users list-seats flush-devices'
+                [ATTACH]='attach'
+        )
+
+        for ((i=0; $i <= $COMP_CWORD; i++)); do
+                if __contains_word "${COMP_WORDS[i]}" ${VERBS[*]} &&
+                 ! __contains_word "${COMP_WORDS[i-1]}" ${OPTS[ARG}]}; then
+                        verb=${COMP_WORDS[i]}
+                        break
+                fi
+        done
+
+        if   [[ -z $verb ]]; then
+                comps="${VERBS[*]}"
+
+        elif __contains_word "$verb" ${VERBS[SESSIONS]}; then
+                comps=$( __get_all_sessions )
+
+        elif __contains_word "$verb" ${VERBS[USERS]}; then
+                comps=$( __get_all_users )
+
+        elif __contains_word "$verb" ${VERBS[SEATS]}; then
+                comps=$( __get_all_seats )
+
+        elif __contains_word "$verb" ${VERBS[STANDALONE]}; then
+                comps=''
+
+        elif __contains_word "$verb" ${VERBS[ATTACH]}; then
+                if [[ $prev = $verb ]]; then
+                        comps=$( __get_all_seats )
+                else
+                        comps=$(compgen -A file -- "$cur" )
+                        compopt -o filenames
+                fi
+        fi
+
+        COMPREPLY=( $(compgen -W "$comps" -- "$cur") )
+        return 0
+}
+complete -F _loginctl loginctl
diff --git a/src/systemd.pc.in b/src/systemd.pc.in
new file mode 100644 (file)
index 0000000..79470f4
--- /dev/null
@@ -0,0 +1,21 @@
+#  This file is part of systemd.
+#
+#  systemd is free software; you can redistribute it and/or modify it
+#  under the terms of the GNU General Public License as published by
+#  the Free Software Foundation; either version 2 of the License, or
+#  (at your option) any later version.
+
+prefix=@prefix@
+exec_prefix=@exec_prefix@
+systemdutildir=@rootlibexecdir@
+systemdsystemunitdir=@systemunitdir@
+systemduserunitdir=@userunitdir@
+systemdsystemconfdir=@pkgsysconfdir@/system
+systemduserconfdir=@pkgsysconfdir@/user
+systemdsystemunitpath=${systemdsystemconfdir}:/etc/systemd/system:/run/systemd/system:/usr/local/lib/systemd/system:${systemdsystemunitdir}:/usr/lib/systemd/system:/lib/systemd/system
+systemduserunitpath=${systemduserconfdir}:/etc/systemd/user:/run/systemd/user:/usr/local/lib/systemd/user:/usr/local/share/systemd/user:${systemduserunitdir}:/usr/lib/systemd/user:/usr/share/systemd/user
+
+Name: systemd
+Description: systemd System and Service Manager
+URL: @PACKAGE_URL@
+Version: @PACKAGE_VERSION@
diff --git a/src/systemd/Makefile b/src/systemd/Makefile
new file mode 120000 (symlink)
index 0000000..d0b0e8e
--- /dev/null
@@ -0,0 +1 @@
+../Makefile
\ No newline at end of file
similarity index 100%
rename from src/sd-daemon.h
rename to src/systemd/sd-daemon.h
diff --git a/src/systemd/sd-id128.h b/src/systemd/sd-id128.h
new file mode 100644 (file)
index 0000000..af2841e
--- /dev/null
@@ -0,0 +1,69 @@
+/*-*- Mode: C; c-basic-offset: 8; indent-tabs-mode: nil -*-*/
+
+#ifndef fooid128hfoo
+#define fooid128hfoo
+
+/***
+  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 <http://www.gnu.org/licenses/>.
+***/
+
+#include <inttypes.h>
+#include <stdbool.h>
+#include <string.h>
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+typedef union sd_id128 sd_id128_t;
+
+union sd_id128 {
+        uint8_t bytes[16];
+        uint64_t qwords[2];
+};
+
+char *sd_id128_to_string(sd_id128_t id, char s[33]);
+
+int sd_id128_from_string(const char s[33], sd_id128_t *ret);
+
+int sd_id128_randomize(sd_id128_t *ret);
+
+int sd_id128_get_machine(sd_id128_t *ret);
+
+int sd_id128_get_boot(sd_id128_t *ret);
+
+#define SD_ID128_MAKE(v0, v1, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12, v13, v14, v15) \
+        ((sd_id128_t) { .bytes = { 0x##v0, 0x##v1, 0x##v2, 0x##v3, 0x##v4, 0x##v5, 0x##v6, 0x##v7, \
+                                   0x##v8, 0x##v9, 0x##v10, 0x##v11, 0x##v12, 0x##v13, 0x##v14, 0x##v15 }})
+
+/* Note that SD_ID128_FORMAT_VAL will evaluate the passed argument 16
+ * times. It is hence not a good idea to call this macro with an
+ * expensive function as paramater or an expression with side
+ * effects */
+#define SD_ID128_FORMAT_STR "%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x"
+#define SD_ID128_FORMAT_VAL(x) (x).bytes[0], (x).bytes[1], (x).bytes[2], (x).bytes[3], (x).bytes[4], (x).bytes[5], (x).bytes[6], (x).bytes[7], (x).bytes[8], (x).bytes[9], (x).bytes[10], (x).bytes[11], (x).bytes[12], (x).bytes[13], (x).bytes[14], (x).bytes[15]
+
+static inline bool sd_id128_equal(sd_id128_t a, sd_id128_t b) {
+        return memcmp(&a, &b, 16) == 0;
+}
+
+#ifdef __cplusplus
+}
+#endif
+
+#endif
diff --git a/src/systemd/sd-journal.h b/src/systemd/sd-journal.h
new file mode 100644 (file)
index 0000000..f27e461
--- /dev/null
@@ -0,0 +1,132 @@
+/*-*- Mode: C; c-basic-offset: 8; indent-tabs-mode: nil -*-*/
+
+#ifndef foojournalhfoo
+#define foojournalhfoo
+
+/***
+  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 <http://www.gnu.org/licenses/>.
+***/
+
+#include <inttypes.h>
+#include <sys/types.h>
+#include <stdarg.h>
+#include <sys/uio.h>
+#include <syslog.h>
+
+#include <systemd/sd-id128.h>
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+/* Write to daemon */
+int sd_journal_print(int piority, const char *format, ...) __attribute__ ((format (printf, 2, 3)));
+int sd_journal_printv(int priority, const char *format, va_list ap);
+int sd_journal_send(const char *format, ...) __attribute__((sentinel));
+int sd_journal_sendv(const struct iovec *iov, int n);
+
+/* Used by the macros below */
+int sd_journal_print_with_location(int priority, const char *file, const char *line, const char *func, const char *format, ...) __attribute__ ((format (printf, 5, 6)));
+int sd_journal_printv_with_location(int priority, const char *file, const char *line, const char *func, const char *format, va_list ap);
+int sd_journal_send_with_location(const char *file, const char *line, const char *func, const char *format, ...) __attribute__((sentinel));
+int sd_journal_sendv_with_location(const char *file, const char *line, const char *func, const struct iovec *iov, int n);
+
+/* implicitly add code location to messages sent, if this is enabled */
+#ifndef SD_JOURNAL_SUPPRESS_LOCATION
+
+#define _sd_XSTRINGIFY(x) #x
+#define _sd_STRINGIFY(x) _sd_XSTRINGIFY(x)
+
+#define sd_journal_print(priority, ...) sd_journal_print_with_location(priority, "CODE_FILE=" __FILE__, "CODE_LINE=" _sd_STRINGIFY(__LINE__), __func__, __VA_ARGS__)
+#define sd_journal_printv(priority, format, ap) sd_journal_printv_with_location(priority, "CODE_FILE=" __FILE__, "CODE_LINE=" _sd_STRINGIFY(__LINE__), __func__, format, ap)
+#define sd_journal_send(...) sd_journal_send_with_location("CODE_FILE=" __FILE__, "CODE_LINE=" _sd_STRINGIFY(__LINE__), __func__, __VA_ARGS__)
+#define sd_journal_sendv(iovec, n) sd_journal_sendv_with_location("CODE_FILE=" __FILE__, "CODE_LINE=" _sd_STRINGIFY(__LINE__), __func__, iovec, n)
+
+#endif
+
+int sd_journal_stream_fd(const char *identifier, int priority, int level_prefix);
+
+/* Browse journal stream */
+
+typedef struct sd_journal sd_journal;
+
+enum {
+        SD_JOURNAL_LOCAL_ONLY = 1,
+        SD_JOURNAL_RUNTIME_ONLY = 2,
+        SD_JOURNAL_SYSTEM_ONLY = 4
+};
+
+int sd_journal_open(sd_journal **ret, int flags);
+void sd_journal_close(sd_journal *j);
+
+int sd_journal_previous(sd_journal *j);
+int sd_journal_next(sd_journal *j);
+
+int sd_journal_previous_skip(sd_journal *j, uint64_t skip);
+int sd_journal_next_skip(sd_journal *j, uint64_t skip);
+
+int sd_journal_get_realtime_usec(sd_journal *j, uint64_t *ret);
+int sd_journal_get_monotonic_usec(sd_journal *j, uint64_t *ret, sd_id128_t *ret_boot_id);
+int sd_journal_get_data(sd_journal *j, const char *field, const void **data, size_t *l);
+int sd_journal_enumerate_data(sd_journal *j, const void **data, size_t *l);
+void sd_journal_restart_data(sd_journal *j);
+
+int sd_journal_add_match(sd_journal *j, const void *data, size_t size);
+void sd_journal_flush_matches(sd_journal *j);
+
+int sd_journal_seek_head(sd_journal *j);
+int sd_journal_seek_tail(sd_journal *j);
+int sd_journal_seek_monotonic_usec(sd_journal *j, sd_id128_t boot_id, uint64_t usec);
+int sd_journal_seek_realtime_usec(sd_journal *j, uint64_t usec);
+int sd_journal_seek_cursor(sd_journal *j, const char *cursor);
+
+int sd_journal_get_cursor(sd_journal *j, char **cursor);
+
+/* int sd_journal_query_unique(sd_journal *j, const char *field);      /\* missing *\/ */
+/* int sd_journal_enumerate_unique(sd_journal *j, const void **data, size_t *l); /\* missing *\/ */
+/* void sd_journal_restart_unique(sd_journal *j);                      /\* missing *\/ */
+
+enum {
+        SD_JOURNAL_NOP,
+        SD_JOURNAL_APPEND,
+        SD_JOURNAL_INVALIDATE_ADD,
+        SD_JOURNAL_INVALIDATE_REMOVE
+};
+
+int sd_journal_get_fd(sd_journal *j);
+int sd_journal_process(sd_journal *j);
+
+#define SD_JOURNAL_FOREACH(j)                                           \
+        if (sd_journal_seek_head(j) >= 0)                               \
+                while (sd_journal_next(j) > 0)
+
+#define SD_JOURNAL_FOREACH_BACKWARDS(j)                                 \
+        if (sd_journal_seek_tail(j) >= 0)                               \
+                while (sd_journal_previous(j) > 0)
+
+#define SD_JOURNAL_FOREACH_DATA(j, data, l)                             \
+        for (sd_journal_restart_data(j); sd_journal_enumerate_data((j), &(data), &(l)) > 0; )
+
+#define SD_JOURNAL_FOREACH_UNIQUE(j, data, l)                           \
+        for (sd_journal_restart_unique(j); sd_journal_enumerate_data((j), &(data), &(l)) > 0; )
+
+#ifdef __cplusplus
+}
+#endif
+
+#endif
diff --git a/src/systemd/sd-login.h b/src/systemd/sd-login.h
new file mode 100644 (file)
index 0000000..6e99cfc
--- /dev/null
@@ -0,0 +1,145 @@
+/*-*- Mode: C; c-basic-offset: 8; indent-tabs-mode: nil -*-*/
+
+#ifndef foosdloginhfoo
+#define foosdloginhfoo
+
+/***
+  This file is part of systemd.
+
+  Copyright 2011 Lennart Poettering
+
+  systemd is free software; you can redistribute it and/or modify it
+  under the terms of the GNU 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 <http://www.gnu.org/licenses/>.
+***/
+
+#include <sys/types.h>
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+/*
+ * A few points:
+ *
+ * Instead of returning an empty string array or empty uid array, we
+ * may return NULL.
+ *
+ * Free the data we return with libc free().
+ *
+ * We return error codes as negative errno, kernel-style. 0 or
+ * positive on success.
+ *
+ * These functions access data in /proc, /sys/fs/cgroup and /run. All
+ * of these are virtual file systems, hence the accesses are
+ * relatively cheap.
+ */
+
+/* Get session from PID. Note that 'shared' processes of a user are
+ * not attached to a session, but only attached to a user. This will
+ * return an error for system processes and 'shared' processes of a
+ * user. */
+int sd_pid_get_session(pid_t pid, char **session);
+
+/* Get UID of the owner of the session of the PID (or in case the
+ * process is a 'shared' user process the UID of that user is
+ * returned). This will not return the UID of the process, but rather
+ * the UID of the owner of the cgroup the process is in. This will
+ * return an error for system processes. */
+int sd_pid_get_owner_uid(pid_t pid, uid_t *uid);
+
+/* Get systemd unit (i.e. service) name from PID. This will return an
+ * error for non-service processes. */
+int sd_pid_get_unit(pid_t, char **unit);
+
+/* Get state from uid. Possible states: offline, lingering, online, active */
+int sd_uid_get_state(uid_t uid, char**state);
+
+/* Return 1 if uid has session on seat. If require_active is true will
+ * look for active sessions only. */
+int sd_uid_is_on_seat(uid_t uid, int require_active, const char *seat);
+
+/* Return sessions of user. If require_active is true will look for
+ * active sessions only. Returns number of sessions as return
+ * value. If sessions is NULL will just return number of sessions. */
+int sd_uid_get_sessions(uid_t uid, int require_active, char ***sessions);
+
+/* Return seats of user is on. If require_active is true will look for
+ * active seats only.  Returns number of seats. If seats is NULL will
+ * just return number of seats.*/
+int sd_uid_get_seats(uid_t uid, int require_active, char ***seats);
+
+/* Return 1 if the session is a active */
+int sd_session_is_active(const char *session);
+
+/* Determine user id of session */
+int sd_session_get_uid(const char *session, uid_t *uid);
+
+/* Determine seat of session */
+int sd_session_get_seat(const char *session, char **seat);
+
+/* Determine the (PAM) service name this session was registered by. */
+int sd_session_get_service(const char *session, char **service);
+
+/* Determine the type of this session, i.e. one of "tty", "x11" or "unspecified". */
+int sd_session_get_type(const char *session, char **type);
+
+/* Determine the class of this session, i.e. one of "user", "greeter" or "lock-screen". */
+int sd_session_get_class(const char *session, char **clazz);
+
+/* Determine the X11 display of this session. */
+int sd_session_get_display(const char *session, char **display);
+
+/* Return active session and user of seat */
+int sd_seat_get_active(const char *seat, char **session, uid_t *uid);
+
+/* Return sessions and users on seat. Returns number of sessions as
+ * return value. If sessions is NULL returns only the number of
+ * sessions. */
+int sd_seat_get_sessions(const char *seat, char ***sessions, uid_t **uid, unsigned *n_uids);
+
+/* Return whether the seat is multi-session capable */
+int sd_seat_can_multi_session(const char *seat);
+
+/* Get all seats, store in *seats. Returns the number of seats. If
+ * seats is NULL only returns number of seats. */
+int sd_get_seats(char ***seats);
+
+/* Get all sessions, store in *sessions. Returns the number of
+ * sessions. If sessions is NULL only returns number of sessions. */
+int sd_get_sessions(char ***sessions);
+
+/* Get all logged in users, store in *users. Returns the number of
+ * users. If users is NULL only returns the number of users. */
+int sd_get_uids(uid_t **users);
+
+/* Monitor object */
+typedef struct sd_login_monitor sd_login_monitor;
+
+/* Create a new monitor. Category must be NULL, "seat", "session",
+ * "uid" to get monitor events for the specific category (or all). */
+int sd_login_monitor_new(const char *category, sd_login_monitor** ret);
+
+/* Destroys the passed monitor. Returns NULL. */
+sd_login_monitor* sd_login_monitor_unref(sd_login_monitor *m);
+
+/* Flushes the monitor */
+int sd_login_monitor_flush(sd_login_monitor *m);
+
+/* Get FD from monitor */
+int sd_login_monitor_get_fd(sd_login_monitor *m);
+
+#ifdef __cplusplus
+}
+#endif
+
+#endif
diff --git a/src/systemd/sd-messages.h b/src/systemd/sd-messages.h
new file mode 100644 (file)
index 0000000..c5ac3ab
--- /dev/null
@@ -0,0 +1,41 @@
+/*-*- Mode: C; c-basic-offset: 8; indent-tabs-mode: nil -*-*/
+
+#ifndef foosdmessageshfoo
+#define foosdmessageshfoo
+
+/***
+  This file is part of systemd.
+
+  Copyright 2012 Lennart Poettering
+
+  systemd is free software; you can redistribute it and/or modify it
+  under the terms of the GNU 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 <http://www.gnu.org/licenses/>.
+***/
+
+#include <systemd/sd-id128.h>
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+#define SD_MESSAGE_JOURNAL_START   SD_ID128_MAKE(f7,73,79,a8,49,0b,40,8b,be,5f,69,40,50,5a,77,7b)
+#define SD_MESSAGE_JOURNAL_STOP    SD_ID128_MAKE(d9,3f,b3,c9,c2,4d,45,1a,97,ce,a6,15,ce,59,c0,0b)
+#define SD_MESSAGE_JOURNAL_DROPPED SD_ID128_MAKE(a5,96,d6,fe,7b,fa,49,94,82,8e,72,30,9e,95,d6,1e)
+
+#define SD_MESSAGE_COREDUMP        SD_ID128_MAKE(fc,2e,22,bc,6e,e6,47,b6,b9,07,29,ab,34,a2,50,b1)
+
+#ifdef __cplusplus
+}
+#endif
+
+#endif
diff --git a/src/systemd/sd-readahead.h b/src/systemd/sd-readahead.h
new file mode 100644 (file)
index 0000000..1f8c5a0
--- /dev/null
@@ -0,0 +1,73 @@
+/*-*- Mode: C; c-basic-offset: 8; indent-tabs-mode: nil -*-*/
+
+#ifndef foosdreadaheadhfoo
+#define foosdreadaheadhfoo
+
+/***
+  Copyright 2010 Lennart Poettering
+
+  Permission is hereby granted, free of charge, to any person
+  obtaining a copy of this software and associated documentation files
+  (the "Software"), to deal in the Software without restriction,
+  including without limitation the rights to use, copy, modify, merge,
+  publish, distribute, sublicense, and/or sell copies of the Software,
+  and to permit persons to whom the Software is furnished to do so,
+  subject to the following conditions:
+
+  The above copyright notice and this permission notice shall be
+  included in all copies or substantial portions of the Software.
+
+  THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+  EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
+  MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
+  NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS
+  BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN
+  ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
+  CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+  SOFTWARE.
+***/
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+/*
+  Reference implementation of a few boot readahead related
+  interfaces. These interfaces are trivial to implement. To simplify
+  porting we provide this reference implementation.  Applications are
+  welcome to reimplement the algorithms described here if they do not
+  want to include these two source files.
+
+  You may compile this with -DDISABLE_SYSTEMD to disable systemd
+  support. This makes all calls NOPs.
+
+  Since this is drop-in code we don't want any of our symbols to be
+  exported in any case. Hence we declare hidden visibility for all of
+  them.
+
+  You may find an up-to-date version of these source files online:
+
+  http://cgit.freedesktop.org/systemd/systemd/plain/src/systemd/sd-readahead.h
+  http://cgit.freedesktop.org/systemd/systemd/plain/src/readahead/sd-readahead.c
+
+  This should compile on non-Linux systems, too, but all functions
+  will become NOPs.
+
+  See sd-readahead(7) for more information.
+*/
+
+/*
+  Controls ongoing disk read-ahead operations during boot-up. The argument
+  must be a string, and either "cancel", "done" or "noreplay".
+
+  cancel = terminate read-ahead data collection, drop collected information
+  done = terminate read-ahead data collection, keep collected information
+  noreplay = terminate read-ahead replay
+*/
+int sd_readahead(const char *action);
+
+#ifdef __cplusplus
+}
+#endif
+
+#endif
diff --git a/src/target.c b/src/target.c
new file mode 100644 (file)
index 0000000..6c1e0c3
--- /dev/null
@@ -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 <http://www.gnu.org/licenses/>.
+***/
+
+#include <errno.h>
+#include <signal.h>
+#include <unistd.h>
+
+#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
new file mode 100644 (file)
index 0000000..5b97c86
--- /dev/null
@@ -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 <http://www.gnu.org/licenses/>.
+***/
+
+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
new file mode 100644 (file)
index 0000000..0aab142
--- /dev/null
@@ -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 <http://www.gnu.org/licenses/>.
+***/
+
+#include <sys/socket.h>
+#include <sys/un.h>
+#include <arpa/inet.h>
+#include <netinet/in.h>
+
+#ifdef HAVE_LIBWRAP
+#include <tcpd.h>
+#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
new file mode 100644 (file)
index 0000000..4d4553e
--- /dev/null
@@ -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 <http://www.gnu.org/licenses/>.
+***/
+
+#include <stdbool.h>
+
+bool socket_tcpwrap(int fd, const char *name);
+
+#endif
diff --git a/src/test-cgroup.c b/src/test-cgroup.c
new file mode 100644 (file)
index 0000000..eb18937
--- /dev/null
@@ -0,0 +1,104 @@
+/*-*- 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 <http://www.gnu.org/licenses/>.
+***/
+
+#include <unistd.h>
+#include <string.h>
+
+#include "cgroup-util.h"
+#include "util.h"
+#include "log.h"
+
+int main(int argc, char*argv[]) {
+        char *path;
+        char *c, *p;
+
+        assert_se(cg_create(SYSTEMD_CGROUP_CONTROLLER, "/test-a") == 0);
+        assert_se(cg_create(SYSTEMD_CGROUP_CONTROLLER, "/test-a") == 0);
+        assert_se(cg_create(SYSTEMD_CGROUP_CONTROLLER, "/test-b") == 0);
+        assert_se(cg_create(SYSTEMD_CGROUP_CONTROLLER, "/test-b/test-c") == 0);
+        assert_se(cg_create_and_attach(SYSTEMD_CGROUP_CONTROLLER, "/test-b", 0) == 0);
+
+        assert_se(cg_get_by_pid(SYSTEMD_CGROUP_CONTROLLER, getpid(), &path) == 0);
+        assert_se(streq(path, "/test-b"));
+        free(path);
+
+        assert_se(cg_attach(SYSTEMD_CGROUP_CONTROLLER, "/test-a", 0) == 0);
+
+        assert_se(cg_get_by_pid(SYSTEMD_CGROUP_CONTROLLER, getpid(), &path) == 0);
+        assert_se(path_equal(path, "/test-a"));
+        free(path);
+
+        assert_se(cg_create_and_attach(SYSTEMD_CGROUP_CONTROLLER, "/test-b/test-d", 0) == 0);
+
+        assert_se(cg_get_by_pid(SYSTEMD_CGROUP_CONTROLLER, getpid(), &path) == 0);
+        assert_se(path_equal(path, "/test-b/test-d"));
+        free(path);
+
+        assert_se(cg_get_path(SYSTEMD_CGROUP_CONTROLLER, "/test-b/test-d", NULL, &path) == 0);
+        assert_se(path_equal(path, "/sys/fs/cgroup/systemd/test-b/test-d"));
+        free(path);
+
+        assert_se(cg_is_empty(SYSTEMD_CGROUP_CONTROLLER, "/test-a", false) > 0);
+        assert_se(cg_is_empty(SYSTEMD_CGROUP_CONTROLLER, "/test-b", false) > 0);
+        assert_se(cg_is_empty_recursive(SYSTEMD_CGROUP_CONTROLLER, "/test-a", false) > 0);
+        assert_se(cg_is_empty_recursive(SYSTEMD_CGROUP_CONTROLLER, "/test-b", false) == 0);
+
+        assert_se(cg_kill_recursive(SYSTEMD_CGROUP_CONTROLLER, "/test-a", 0, false, false, false, NULL) == 0);
+        assert_se(cg_kill_recursive(SYSTEMD_CGROUP_CONTROLLER, "/test-b", 0, false, false, false, NULL) > 0);
+
+        assert_se(cg_migrate_recursive(SYSTEMD_CGROUP_CONTROLLER, "/test-b", "/test-a", false, false) > 0);
+
+        assert_se(cg_is_empty_recursive(SYSTEMD_CGROUP_CONTROLLER, "/test-a", false) == 0);
+        assert_se(cg_is_empty_recursive(SYSTEMD_CGROUP_CONTROLLER, "/test-b", false) > 0);
+
+        assert_se(cg_kill_recursive(SYSTEMD_CGROUP_CONTROLLER, "/test-a", 0, false, false, false, NULL) > 0);
+        assert_se(cg_kill_recursive(SYSTEMD_CGROUP_CONTROLLER, "/test-b", 0, false, false, false, NULL) == 0);
+
+        cg_trim(SYSTEMD_CGROUP_CONTROLLER, "/", false);
+
+        assert_se(cg_delete(SYSTEMD_CGROUP_CONTROLLER, "/test-b") < 0);
+        assert_se(cg_delete(SYSTEMD_CGROUP_CONTROLLER, "/test-a") >= 0);
+
+        assert_se(cg_split_spec("foobar:/", &c, &p) == 0);
+        assert(streq(c, "foobar"));
+        assert(streq(p, "/"));
+        free(c);
+        free(p);
+
+        assert_se(cg_split_spec("foobar:", &c, &p) < 0);
+        assert_se(cg_split_spec("foobar:asdfd", &c, &p) < 0);
+        assert_se(cg_split_spec(":///", &c, &p) < 0);
+        assert_se(cg_split_spec(":", &c, &p) < 0);
+        assert_se(cg_split_spec("", &c, &p) < 0);
+        assert_se(cg_split_spec("fo/obar:/", &c, &p) < 0);
+
+        assert_se(cg_split_spec("/", &c, &p) >= 0);
+        assert(c == NULL);
+        assert(streq(p, "/"));
+        free(p);
+
+        assert_se(cg_split_spec("foo", &c, &p) >= 0);
+        assert(streq(c, "foo"));
+        assert(p == NULL);
+        free(c);
+
+        return 0;
+}
diff --git a/src/test-daemon.c b/src/test-daemon.c
new file mode 100644 (file)
index 0000000..20c5d15
--- /dev/null
@@ -0,0 +1,37 @@
+/*-*- 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 <http://www.gnu.org/licenses/>.
+***/
+
+#include <unistd.h>
+
+#include <systemd/sd-daemon.h>
+
+int main(int argc, char*argv[]) {
+
+        sd_notify(0, "STATUS=Starting up");
+        sleep(5);
+        sd_notify(0,
+                  "STATUS=Running\n"
+                  "READY=1");
+        sleep(10);
+        sd_notify(0, "STATUS=Quitting");
+
+        return 0;
+}
diff --git a/src/test-engine.c b/src/test-engine.c
new file mode 100644 (file)
index 0000000..46a2d2c
--- /dev/null
@@ -0,0 +1,99 @@
+/*-*- 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 <http://www.gnu.org/licenses/>.
+***/
+
+#include <stdio.h>
+#include <errno.h>
+#include <string.h>
+#include <unistd.h>
+
+#include "manager.h"
+
+int main(int argc, char *argv[]) {
+        Manager *m = NULL;
+        Unit *a = NULL, *b = NULL, *c = NULL, *d = NULL, *e = NULL, *g = NULL, *h = NULL;
+        Job *j;
+
+        assert_se(set_unit_path("test") >= 0);
+
+        assert_se(manager_new(MANAGER_SYSTEM, &m) >= 0);
+
+        printf("Load1:\n");
+        assert_se(manager_load_unit(m, "a.service", NULL, NULL, &a) >= 0);
+        assert_se(manager_load_unit(m, "b.service", NULL, NULL, &b) >= 0);
+        assert_se(manager_load_unit(m, "c.service", NULL, NULL, &c) >= 0);
+        manager_dump_units(m, stdout, "\t");
+
+        printf("Test1: (Trivial)\n");
+        assert_se(manager_add_job(m, JOB_START, c, JOB_REPLACE, false, NULL, &j) == 0);
+        manager_dump_jobs(m, stdout, "\t");
+
+        printf("Load2:\n");
+        manager_clear_jobs(m);
+        assert_se(manager_load_unit(m, "d.service", NULL, NULL, &d) >= 0);
+        assert_se(manager_load_unit(m, "e.service", NULL, NULL, &e) >= 0);
+        manager_dump_units(m, stdout, "\t");
+
+        printf("Test2: (Cyclic Order, Unfixable)\n");
+        assert_se(manager_add_job(m, JOB_START, d, JOB_REPLACE, false, NULL, &j) == -ENOEXEC);
+        manager_dump_jobs(m, stdout, "\t");
+
+        printf("Test3: (Cyclic Order, Fixable, Garbage Collector)\n");
+        assert_se(manager_add_job(m, JOB_START, e, JOB_REPLACE, false, NULL, &j) == 0);
+        manager_dump_jobs(m, stdout, "\t");
+
+        printf("Test4: (Identical transaction)\n");
+        assert_se(manager_add_job(m, JOB_START, e, JOB_FAIL, false, NULL, &j) == 0);
+        manager_dump_jobs(m, stdout, "\t");
+
+        printf("Load3:\n");
+        assert_se(manager_load_unit(m, "g.service", NULL, NULL, &g) >= 0);
+        manager_dump_units(m, stdout, "\t");
+
+        printf("Test5: (Colliding transaction, fail)\n");
+        assert_se(manager_add_job(m, JOB_START, g, JOB_FAIL, false, NULL, &j) == -EEXIST);
+
+        printf("Test6: (Colliding transaction, replace)\n");
+        assert_se(manager_add_job(m, JOB_START, g, JOB_REPLACE, false, NULL, &j) == 0);
+        manager_dump_jobs(m, stdout, "\t");
+
+        printf("Test7: (Unmergeable job type, fail)\n");
+        assert_se(manager_add_job(m, JOB_STOP, g, JOB_FAIL, false, NULL, &j) == -EEXIST);
+
+        printf("Test8: (Mergeable job type, fail)\n");
+        assert_se(manager_add_job(m, JOB_RESTART, g, JOB_FAIL, false, NULL, &j) == 0);
+        manager_dump_jobs(m, stdout, "\t");
+
+        printf("Test9: (Unmergeable job type, replace)\n");
+        assert_se(manager_add_job(m, JOB_STOP, g, JOB_REPLACE, false, NULL, &j) == 0);
+        manager_dump_jobs(m, stdout, "\t");
+
+        printf("Load4:\n");
+        assert_se(manager_load_unit(m, "h.service", NULL, NULL, &h) >= 0);
+        manager_dump_units(m, stdout, "\t");
+
+        printf("Test10: (Unmergeable job type of auxiliary job, fail)\n");
+        assert_se(manager_add_job(m, JOB_START, h, JOB_FAIL, false, NULL, &j) == 0);
+        manager_dump_jobs(m, stdout, "\t");
+
+        manager_free(m);
+
+        return 0;
+}
diff --git a/src/test-env-replace.c b/src/test-env-replace.c
new file mode 100644 (file)
index 0000000..05dbacd
--- /dev/null
@@ -0,0 +1,127 @@
+/*-*- 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 <http://www.gnu.org/licenses/>.
+***/
+
+#include <unistd.h>
+#include <string.h>
+
+#include "util.h"
+#include "log.h"
+#include "strv.h"
+
+int main(int argc, char *argv[]) {
+
+        const char *env[] = {
+                "FOO=BAR BAR",
+                "BAR=waldo",
+                NULL
+        };
+
+        const char *line[] = {
+                "FOO$FOO",
+                "FOO$FOOFOO",
+                "FOO${FOO}$FOO",
+                "FOO${FOO}",
+                "${FOO}",
+                "$FOO",
+                "$FOO$FOO",
+                "${FOO}${BAR}",
+                "${FOO",
+                NULL
+        };
+
+        char **i, **r, *t, **a, **b;
+        const char nulstr[] = "fuck\0fuck2\0fuck3\0\0fuck5\0\0xxx";
+
+        a = strv_parse_nulstr(nulstr, sizeof(nulstr)-1);
+
+        STRV_FOREACH(i, a)
+                printf("nulstr--%s\n", *i);
+
+        strv_free(a);
+
+        r = replace_env_argv((char**) line, (char**) env);
+
+        STRV_FOREACH(i, r)
+                printf("%s\n", *i);
+
+        strv_free(r);
+
+        t = normalize_env_assignment("foo=bar");
+        printf("%s\n", t);
+        free(t);
+
+        t = normalize_env_assignment("=bar");
+        printf("%s\n", t);
+        free(t);
+
+        t = normalize_env_assignment("foo=");
+        printf("%s\n", t);
+        free(t);
+
+        t = normalize_env_assignment("=");
+        printf("%s\n", t);
+        free(t);
+
+        t = normalize_env_assignment("");
+        printf("%s\n", t);
+        free(t);
+
+        t = normalize_env_assignment("a=\"waldo\"");
+        printf("%s\n", t);
+        free(t);
+
+        t = normalize_env_assignment("a=\"waldo");
+        printf("%s\n", t);
+        free(t);
+
+        t = normalize_env_assignment("a=waldo\"");
+        printf("%s\n", t);
+        free(t);
+
+        t = normalize_env_assignment("a=\'");
+        printf("%s\n", t);
+        free(t);
+
+        t = normalize_env_assignment("a=\'\'");
+        printf("%s\n", t);
+        free(t);
+
+        a = strv_new("FOO=BAR", "WALDO=WALDO", "WALDO=", "PIEP", "SCHLUMPF=SMURF", NULL);
+        b = strv_new("FOO=KKK", "FOO=", "PIEP=", "SCHLUMPF=SMURFF", "NANANANA=YES", NULL);
+
+        r = strv_env_merge(2, a, b);
+        strv_free(a);
+        strv_free(b);
+
+        STRV_FOREACH(i, r)
+                printf("%s\n", *i);
+
+        printf("CLEANED UP:\n");
+
+        r = strv_env_clean(r);
+
+        STRV_FOREACH(i, r)
+                printf("%s\n", *i);
+
+        strv_free(r);
+
+        return 0;
+}
diff --git a/src/test-hostname.c b/src/test-hostname.c
new file mode 100644 (file)
index 0000000..0a08416
--- /dev/null
@@ -0,0 +1,37 @@
+/*-*- 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 <http://www.gnu.org/licenses/>.
+***/
+
+#include <errno.h>
+#include <string.h>
+#include <stdio.h>
+#include <fcntl.h>
+
+#include "hostname-setup.h"
+#include "util.h"
+
+int main(int argc, char* argv[]) {
+        int r;
+
+        if ((r = hostname_setup()) < 0)
+                fprintf(stderr, "hostname: %s\n", strerror(-r));
+
+        return 0;
+}
diff --git a/src/test-id128.c b/src/test-id128.c
new file mode 100644 (file)
index 0000000..617c955
--- /dev/null
@@ -0,0 +1,52 @@
+/*-*- 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 <http://www.gnu.org/licenses/>.
+***/
+
+#include <string.h>
+
+#include <systemd/sd-id128.h>
+
+#include "util.h"
+#include "macro.h"
+
+#define ID128_WALDI SD_ID128_MAKE(01, 02, 03, 04, 05, 06, 07, 08, 09, 0a, 0b, 0c, 0d, 0e, 0f, 10)
+
+int main(int argc, char *argv[]) {
+        sd_id128_t id, id2;
+        char t[33];
+
+        assert_se(sd_id128_randomize(&id) == 0);
+        printf("random: %s\n", sd_id128_to_string(id, t));
+
+        assert_se(sd_id128_from_string(t, &id2) == 0);
+        assert_se(sd_id128_equal(id, id2));
+
+        assert_se(sd_id128_get_machine(&id) == 0);
+        printf("machine: %s\n", sd_id128_to_string(id, t));
+
+        assert_se(sd_id128_get_boot(&id) == 0);
+        printf("boot: %s\n", sd_id128_to_string(id, t));
+
+        printf("waldi: %s\n", sd_id128_to_string(ID128_WALDI, t));
+
+        printf("waldi2: " SD_ID128_FORMAT_STR "\n", SD_ID128_FORMAT_VAL(ID128_WALDI));
+
+        return 0;
+}
diff --git a/src/test-install.c b/src/test-install.c
new file mode 100644 (file)
index 0000000..f8e87e0
--- /dev/null
@@ -0,0 +1,264 @@
+/*-*- 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 <http://www.gnu.org/licenses/>.
+***/
+
+#include <errno.h>
+#include <string.h>
+#include <stdio.h>
+#include <fcntl.h>
+
+#include "util.h"
+#include "install.h"
+
+static void dump_changes(UnitFileChange *c, unsigned n) {
+        unsigned i;
+
+        assert(n == 0 || c);
+
+        for (i = 0; i < n; i++) {
+                if (c[i].type == UNIT_FILE_UNLINK)
+                        printf("rm '%s'\n", c[i].path);
+                else if (c[i].type == UNIT_FILE_SYMLINK)
+                        printf("ln -s '%s' '%s'\n", c[i].source, c[i].path);
+        }
+}
+
+int main(int argc, char* argv[]) {
+        Hashmap *h;
+        UnitFileList *p;
+        Iterator i;
+        int r;
+        const char *const files[] = { "avahi-daemon.service", NULL };
+        const char *const files2[] = { "/home/lennart/test.service", NULL };
+        UnitFileChange *changes = NULL;
+        unsigned n_changes = 0;
+
+        h = hashmap_new(string_hash_func, string_compare_func);
+        r = unit_file_get_list(UNIT_FILE_SYSTEM, NULL, h);
+        assert_se(r == 0);
+
+        HASHMAP_FOREACH(p, h, i) {
+                UnitFileState s;
+
+                s = unit_file_get_state(UNIT_FILE_SYSTEM, NULL, file_name_from_path(p->path));
+
+                assert_se(p->state == s);
+
+                fprintf(stderr, "%s (%s)\n",
+                        p->path,
+                        unit_file_state_to_string(p->state));
+        }
+
+        unit_file_list_free(h);
+
+        log_error("enable");
+
+        r = unit_file_enable(UNIT_FILE_SYSTEM, false, NULL, (char**) files, false, &changes, &n_changes);
+        assert_se(r >= 0);
+
+        log_error("enable2");
+
+        r = unit_file_enable(UNIT_FILE_SYSTEM, false, NULL, (char**) files, false, &changes, &n_changes);
+        assert_se(r >= 0);
+
+        dump_changes(changes, n_changes);
+        unit_file_changes_free(changes, n_changes);
+
+        assert_se(unit_file_get_state(UNIT_FILE_SYSTEM, NULL, files[0]) == UNIT_FILE_ENABLED);
+
+        log_error("disable");
+
+        changes = NULL;
+        n_changes = 0;
+
+        r = unit_file_disable(UNIT_FILE_SYSTEM, false, NULL, (char**) files, &changes, &n_changes);
+        assert_se(r >= 0);
+
+        dump_changes(changes, n_changes);
+        unit_file_changes_free(changes, n_changes);
+
+        assert_se(unit_file_get_state(UNIT_FILE_SYSTEM, NULL, files[0]) == UNIT_FILE_DISABLED);
+
+        log_error("mask");
+        changes = NULL;
+        n_changes = 0;
+
+        r = unit_file_mask(UNIT_FILE_SYSTEM, false, NULL, (char**) files, false, &changes, &n_changes);
+        assert_se(r >= 0);
+        log_error("mask2");
+        r = unit_file_mask(UNIT_FILE_SYSTEM, false, NULL, (char**) files, false, &changes, &n_changes);
+        assert_se(r >= 0);
+
+        dump_changes(changes, n_changes);
+        unit_file_changes_free(changes, n_changes);
+
+        assert_se(unit_file_get_state(UNIT_FILE_SYSTEM, NULL, files[0]) == UNIT_FILE_MASKED);
+
+        log_error("unmask");
+        changes = NULL;
+        n_changes = 0;
+
+        r = unit_file_unmask(UNIT_FILE_SYSTEM, false, NULL, (char**) files, &changes, &n_changes);
+        assert_se(r >= 0);
+        log_error("unmask2");
+        r = unit_file_unmask(UNIT_FILE_SYSTEM, false, NULL, (char**) files, &changes, &n_changes);
+        assert_se(r >= 0);
+
+        dump_changes(changes, n_changes);
+        unit_file_changes_free(changes, n_changes);
+
+        assert_se(unit_file_get_state(UNIT_FILE_SYSTEM, NULL, files[0]) == UNIT_FILE_DISABLED);
+
+        log_error("mask");
+        changes = NULL;
+        n_changes = 0;
+
+        r = unit_file_mask(UNIT_FILE_SYSTEM, false, NULL, (char**) files, false, &changes, &n_changes);
+        assert_se(r >= 0);
+
+        dump_changes(changes, n_changes);
+        unit_file_changes_free(changes, n_changes);
+
+        assert_se(unit_file_get_state(UNIT_FILE_SYSTEM, NULL, files[0]) == UNIT_FILE_MASKED);
+
+        log_error("disable");
+        changes = NULL;
+        n_changes = 0;
+
+        r = unit_file_disable(UNIT_FILE_SYSTEM, false, NULL, (char**) files, &changes, &n_changes);
+        assert_se(r >= 0);
+        log_error("disable2");
+        r = unit_file_disable(UNIT_FILE_SYSTEM, false, NULL, (char**) files, &changes, &n_changes);
+        assert_se(r >= 0);
+
+        dump_changes(changes, n_changes);
+        unit_file_changes_free(changes, n_changes);
+
+        assert_se(unit_file_get_state(UNIT_FILE_SYSTEM, NULL, files[0]) == UNIT_FILE_MASKED);
+
+        log_error("umask");
+        changes = NULL;
+        n_changes = 0;
+
+        r = unit_file_unmask(UNIT_FILE_SYSTEM, false, NULL, (char**) files, &changes, &n_changes);
+        assert_se(r >= 0);
+
+        dump_changes(changes, n_changes);
+        unit_file_changes_free(changes, n_changes);
+
+        assert_se(unit_file_get_state(UNIT_FILE_SYSTEM, NULL, files[0]) == UNIT_FILE_DISABLED);
+
+        log_error("enable files2");
+        changes = NULL;
+        n_changes = 0;
+
+        r = unit_file_enable(UNIT_FILE_SYSTEM, false, NULL, (char**) files2, false, &changes, &n_changes);
+        assert_se(r >= 0);
+
+        dump_changes(changes, n_changes);
+        unit_file_changes_free(changes, n_changes);
+
+        assert_se(unit_file_get_state(UNIT_FILE_SYSTEM, NULL, file_name_from_path(files2[0])) == UNIT_FILE_ENABLED);
+
+        log_error("disable files2");
+        changes = NULL;
+        n_changes = 0;
+
+        r = unit_file_disable(UNIT_FILE_SYSTEM, false, NULL, (char**) files2, &changes, &n_changes);
+        assert_se(r >= 0);
+
+        dump_changes(changes, n_changes);
+        unit_file_changes_free(changes, n_changes);
+
+        assert_se(unit_file_get_state(UNIT_FILE_SYSTEM, NULL, file_name_from_path(files2[0])) == _UNIT_FILE_STATE_INVALID);
+
+        log_error("link files2");
+        changes = NULL;
+        n_changes = 0;
+
+        r = unit_file_link(UNIT_FILE_SYSTEM, false, NULL, (char**) files2, false, &changes, &n_changes);
+        assert_se(r >= 0);
+
+        dump_changes(changes, n_changes);
+        unit_file_changes_free(changes, n_changes);
+
+        assert_se(unit_file_get_state(UNIT_FILE_SYSTEM, NULL, file_name_from_path(files2[0])) == UNIT_FILE_LINKED);
+
+        log_error("disable files2");
+        changes = NULL;
+        n_changes = 0;
+
+        r = unit_file_disable(UNIT_FILE_SYSTEM, false, NULL, (char**) files2, &changes, &n_changes);
+        assert_se(r >= 0);
+
+        dump_changes(changes, n_changes);
+        unit_file_changes_free(changes, n_changes);
+
+        assert_se(unit_file_get_state(UNIT_FILE_SYSTEM, NULL, file_name_from_path(files2[0])) == _UNIT_FILE_STATE_INVALID);
+
+        log_error("link files2");
+        changes = NULL;
+        n_changes = 0;
+
+        r = unit_file_link(UNIT_FILE_SYSTEM, false, NULL, (char**) files2, false, &changes, &n_changes);
+        assert_se(r >= 0);
+
+        dump_changes(changes, n_changes);
+        unit_file_changes_free(changes, n_changes);
+
+        assert_se(unit_file_get_state(UNIT_FILE_SYSTEM, NULL, file_name_from_path(files2[0])) == UNIT_FILE_LINKED);
+
+        log_error("reenable files2");
+        changes = NULL;
+        n_changes = 0;
+
+        r = unit_file_reenable(UNIT_FILE_SYSTEM, false, NULL, (char**) files2, false, &changes, &n_changes);
+        assert_se(r >= 0);
+
+        dump_changes(changes, n_changes);
+        unit_file_changes_free(changes, n_changes);
+
+        assert_se(unit_file_get_state(UNIT_FILE_SYSTEM, NULL, file_name_from_path(files2[0])) == UNIT_FILE_ENABLED);
+
+        log_error("disable files2");
+        changes = NULL;
+        n_changes = 0;
+
+        r = unit_file_disable(UNIT_FILE_SYSTEM, false, NULL, (char**) files2, &changes, &n_changes);
+        assert_se(r >= 0);
+
+        dump_changes(changes, n_changes);
+        unit_file_changes_free(changes, n_changes);
+
+        assert_se(unit_file_get_state(UNIT_FILE_SYSTEM, NULL, file_name_from_path(files2[0])) == _UNIT_FILE_STATE_INVALID);
+        log_error("preset files");
+        changes = NULL;
+        n_changes = 0;
+
+        r = unit_file_preset(UNIT_FILE_SYSTEM, false, NULL, (char**) files, false, &changes, &n_changes);
+        assert_se(r >= 0);
+
+        dump_changes(changes, n_changes);
+        unit_file_changes_free(changes, n_changes);
+
+        assert_se(unit_file_get_state(UNIT_FILE_SYSTEM, NULL, file_name_from_path(files[0])) == UNIT_FILE_ENABLED);
+
+        return 0;
+}
diff --git a/src/test-job-type.c b/src/test-job-type.c
new file mode 100644 (file)
index 0000000..9de21e1
--- /dev/null
@@ -0,0 +1,84 @@
+/*-*- 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 <http://www.gnu.org/licenses/>.
+***/
+
+#include <stdio.h>
+#include <errno.h>
+#include <string.h>
+#include <unistd.h>
+
+#include "job.h"
+
+int main(int argc, char*argv[]) {
+        JobType a, b, c, d, e, f, g;
+
+        for (a = 0; a < _JOB_TYPE_MAX; a++)
+                for (b = 0; b < _JOB_TYPE_MAX; b++) {
+
+                        if (!job_type_is_mergeable(a, b))
+                                printf("Not mergeable: %s + %s\n", job_type_to_string(a), job_type_to_string(b));
+
+                        for (c = 0; c < _JOB_TYPE_MAX; c++) {
+
+                                /* Verify transitivity of mergeability
+                                 * of job types */
+                                assert(!job_type_is_mergeable(a, b) ||
+                                       !job_type_is_mergeable(b, c) ||
+                                       job_type_is_mergeable(a, c));
+
+                                d = a;
+                                if (job_type_merge(&d, b) >= 0) {
+
+                                        printf("%s + %s = %s\n", job_type_to_string(a), job_type_to_string(b), job_type_to_string(d));
+
+                                        /* Verify that merged entries can be
+                                         * merged with the same entries they
+                                         * can be merged with separately */
+                                        assert(!job_type_is_mergeable(a, c) || job_type_is_mergeable(d, c));
+                                        assert(!job_type_is_mergeable(b, c) || job_type_is_mergeable(d, c));
+
+                                        /* Verify that if a merged
+                                         * with b is not mergeable with
+                                         * c then either a or b is not
+                                         * mergeable with c either. */
+                                        assert(job_type_is_mergeable(d, c) || !job_type_is_mergeable(a, c) || !job_type_is_mergeable(b, c));
+
+                                        e = b;
+                                        if (job_type_merge(&e, c) >= 0) {
+
+                                                /* Verify associativity */
+
+                                                f = d;
+                                                assert(job_type_merge(&f, c) == 0);
+
+                                                g = e;
+                                                assert(job_type_merge(&g, a) == 0);
+
+                                                assert(f == g);
+
+                                                printf("%s + %s + %s = %s\n", job_type_to_string(a), job_type_to_string(b), job_type_to_string(c), job_type_to_string(d));
+                                        }
+                                }
+                        }
+                }
+
+
+        return 0;
+}
diff --git a/src/test-loopback.c b/src/test-loopback.c
new file mode 100644 (file)
index 0000000..9784aaf
--- /dev/null
@@ -0,0 +1,37 @@
+/*-*- 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 <http://www.gnu.org/licenses/>.
+***/
+
+#include <errno.h>
+#include <string.h>
+#include <stdio.h>
+#include <fcntl.h>
+
+#include "loopback-setup.h"
+#include "util.h"
+
+int main(int argc, char* argv[]) {
+        int r;
+
+        if ((r = loopback_setup()) < 0)
+                fprintf(stderr, "loopback: %s\n", strerror(-r));
+
+        return 0;
+}
diff --git a/src/test-ns.c b/src/test-ns.c
new file mode 100644 (file)
index 0000000..e2bdfc5
--- /dev/null
@@ -0,0 +1,60 @@
+/*-*- 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 <http://www.gnu.org/licenses/>.
+***/
+
+#include <stdlib.h>
+#include <string.h>
+#include <unistd.h>
+#include <sys/mount.h>
+#include <linux/fs.h>
+
+#include "namespace.h"
+#include "log.h"
+
+int main(int argc, char *argv[]) {
+        const char * const writable[] = {
+                "/home",
+                NULL
+        };
+
+        const char * const readable[] = {
+                "/",
+                "/usr",
+                "/boot",
+                NULL
+        };
+
+        const char * const inaccessible[] = {
+                "/home/lennart/projects",
+                NULL
+        };
+
+        int r;
+
+        if ((r = setup_namespace((char**) writable, (char**) readable, (char**) inaccessible, true, MS_SHARED)) < 0) {
+                log_error("Failed to setup namespace: %s", strerror(-r));
+                return 1;
+        }
+
+        execl("/bin/sh", "/bin/sh", NULL);
+        log_error("execl(): %m");
+
+        return 1;
+}
diff --git a/src/test-strv.c b/src/test-strv.c
new file mode 100644 (file)
index 0000000..1d577df
--- /dev/null
@@ -0,0 +1,66 @@
+/*-*- 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 <http://www.gnu.org/licenses/>.
+***/
+
+#include <string.h>
+
+#include "util.h"
+#include "specifier.h"
+
+int main(int argc, char *argv[]) {
+        const Specifier table[] = {
+                { 'a', specifier_string, (char*) "AAAA" },
+                { 'b', specifier_string, (char*) "BBBB" },
+                { 0, NULL, NULL }
+        };
+
+        char *w, *state;
+        size_t l;
+        const char test[] = "test a b c 'd' e '' '' hhh '' ''";
+
+        printf("<%s>\n", test);
+
+        FOREACH_WORD_QUOTED(w, l, test, state) {
+                char *t;
+
+                assert_se(t = strndup(w, l));
+                printf("<%s>\n", t);
+                free(t);
+        }
+
+        printf("%s\n", default_term_for_tty("/dev/tty23"));
+        printf("%s\n", default_term_for_tty("/dev/ttyS23"));
+        printf("%s\n", default_term_for_tty("/dev/tty0"));
+        printf("%s\n", default_term_for_tty("/dev/pty0"));
+        printf("%s\n", default_term_for_tty("/dev/pts/0"));
+        printf("%s\n", default_term_for_tty("/dev/console"));
+        printf("%s\n", default_term_for_tty("tty23"));
+        printf("%s\n", default_term_for_tty("ttyS23"));
+        printf("%s\n", default_term_for_tty("tty0"));
+        printf("%s\n", default_term_for_tty("pty0"));
+        printf("%s\n", default_term_for_tty("pts/0"));
+        printf("%s\n", default_term_for_tty("console"));
+
+        w = specifier_printf("xxx a=%a b=%b yyy", table, NULL);
+        printf("<%s>\n", w);
+        free(w);
+
+        return 0;
+}
diff --git a/src/timedate/.gitignore b/src/timedate/.gitignore
new file mode 100644 (file)
index 0000000..48757f0
--- /dev/null
@@ -0,0 +1 @@
+org.freedesktop.timedate1.policy
diff --git a/src/timedate/Makefile b/src/timedate/Makefile
new file mode 120000 (symlink)
index 0000000..d0b0e8e
--- /dev/null
@@ -0,0 +1 @@
+../Makefile
\ No newline at end of file
diff --git a/src/timedate/org.freedesktop.timedate1.conf b/src/timedate/org.freedesktop.timedate1.conf
new file mode 100644 (file)
index 0000000..c9c221b
--- /dev/null
@@ -0,0 +1,27 @@
+<?xml version="1.0"?> <!--*-nxml-*-->
+<!DOCTYPE busconfig PUBLIC "-//freedesktop//DTD D-BUS Bus Configuration 1.0//EN"
+        "http://www.freedesktop.org/standards/dbus/1.0/busconfig.dtd">
+
+<!--
+  This file is part of systemd.
+
+  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.
+-->
+
+<busconfig>
+
+        <policy user="root">
+                <allow own="org.freedesktop.timedate1"/>
+                <allow send_destination="org.freedesktop.timedate1"/>
+                <allow receive_sender="org.freedesktop.timedate1"/>
+        </policy>
+
+        <policy context="default">
+                <allow send_destination="org.freedesktop.timedate1"/>
+                <allow receive_sender="org.freedesktop.timedate1"/>
+        </policy>
+
+</busconfig>
diff --git a/src/timedate/org.freedesktop.timedate1.policy.in b/src/timedate/org.freedesktop.timedate1.policy.in
new file mode 100644 (file)
index 0000000..3d9c208
--- /dev/null
@@ -0,0 +1,61 @@
+<?xml version="1.0" encoding="UTF-8"?> <!--*-nxml-*-->
+<!DOCTYPE policyconfig PUBLIC "-//freedesktop//DTD PolicyKit Policy Configuration 1.0//EN"
+        "http://www.freedesktop.org/standards/PolicyKit/1/policyconfig.dtd">
+
+<!--
+  This file is part of systemd.
+
+  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.
+-->
+
+<policyconfig>
+
+        <vendor>The systemd Project</vendor>
+        <vendor_url>http://www.freedesktop.org/wiki/Software/systemd</vendor_url>
+
+        <action id="org.freedesktop.timedate1.set-time">
+                <_description>Set system time</_description>
+                <_message>Authentication is required to set the system time.</_message>
+                <defaults>
+                        <allow_any>auth_admin_keep</allow_any>
+                        <allow_inactive>auth_admin_keep</allow_inactive>
+                        <allow_active>auth_admin_keep</allow_active>
+                </defaults>
+        </action>
+
+        <action id="org.freedesktop.timedate1.set-timezone">
+                <_description>Set system timezone</_description>
+                <_message>Authentication is required to set the system timezone.</_message>
+                <defaults>
+                        <allow_any>auth_admin_keep</allow_any>
+                        <allow_inactive>auth_admin_keep</allow_inactive>
+                        <allow_active>auth_admin_keep</allow_active>
+                </defaults>
+        </action>
+
+        <action id="org.freedesktop.timedate1.set-local-rtc">
+                <_description>Set RTC to local timezone or UTC</_description>
+                <_message>Authentication is required to control whether
+                the RTC stores the local or UTC time.</_message>
+                <defaults>
+                        <allow_any>auth_admin_keep</allow_any>
+                        <allow_inactive>auth_admin_keep</allow_inactive>
+                        <allow_active>auth_admin_keep</allow_active>
+                </defaults>
+        </action>
+
+        <action id="org.freedesktop.timedate1.set-ntp">
+                <_description>Turn network time synchronization on or off</_description>
+                <_message>Authentication is required to control whether
+                network time synchronization shall be enabled.</_message>
+                <defaults>
+                        <allow_any>auth_admin_keep</allow_any>
+                        <allow_inactive>auth_admin_keep</allow_inactive>
+                        <allow_active>auth_admin_keep</allow_active>
+                </defaults>
+        </action>
+
+</policyconfig>
diff --git a/src/timedate/org.freedesktop.timedate1.service b/src/timedate/org.freedesktop.timedate1.service
new file mode 100644 (file)
index 0000000..c3120b6
--- /dev/null
@@ -0,0 +1,12 @@
+#  This file is part of systemd.
+#
+#  systemd is free software; you can redistribute it and/or modify it
+#  under the terms of the GNU General Public License as published by
+#  the Free Software Foundation; either version 2 of the License, or
+#  (at your option) any later version.
+
+[D-BUS Service]
+Name=org.freedesktop.timedate1
+Exec=/bin/false
+User=root
+SystemdService=dbus-org.freedesktop.timedate1.service
diff --git a/src/timedate/timedated.c b/src/timedate/timedated.c
new file mode 100644 (file)
index 0000000..6a7d980
--- /dev/null
@@ -0,0 +1,930 @@
+/*-*- 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 <http://www.gnu.org/licenses/>.
+***/
+
+#include <dbus/dbus.h>
+
+#include <errno.h>
+#include <string.h>
+#include <unistd.h>
+
+#include "util.h"
+#include "strv.h"
+#include "dbus-common.h"
+#include "polkit.h"
+#include "def.h"
+
+#define NULL_ADJTIME_UTC "0.0 0 0\n0\nUTC\n"
+#define NULL_ADJTIME_LOCAL "0.0 0 0\n0\nLOCAL\n"
+
+#define INTERFACE                                                       \
+        " <interface name=\"org.freedesktop.timedate1\">\n"             \
+        "  <property name=\"Timezone\" type=\"s\" access=\"read\"/>\n"  \
+        "  <property name=\"LocalRTC\" type=\"b\" access=\"read\"/>\n"  \
+        "  <property name=\"NTP\" type=\"b\" access=\"read\"/>\n"       \
+        "  <method name=\"SetTime\">\n"                                 \
+        "   <arg name=\"usec_utc\" type=\"x\" direction=\"in\"/>\n"     \
+        "   <arg name=\"relative\" type=\"b\" direction=\"in\"/>\n"     \
+        "   <arg name=\"user_interaction\" type=\"b\" direction=\"in\"/>\n" \
+        "  </method>\n"                                                 \
+        "  <method name=\"SetTimezone\">\n"                             \
+        "   <arg name=\"timezone\" type=\"s\" direction=\"in\"/>\n"     \
+        "   <arg name=\"user_interaction\" type=\"b\" direction=\"in\"/>\n" \
+        "  </method>\n"                                                 \
+        "  <method name=\"SetLocalRTC\">\n"                             \
+        "   <arg name=\"local_rtc\" type=\"b\" direction=\"in\"/>\n"    \
+        "   <arg name=\"fix_system\" type=\"b\" direction=\"in\"/>\n"   \
+        "   <arg name=\"user_interaction\" type=\"b\" direction=\"in\"/>\n" \
+        "  </method>\n"                                                 \
+        "  <method name=\"SetNTP\">\n"                                  \
+        "   <arg name=\"use_ntp\" type=\"b\" direction=\"in\"/>\n"      \
+        "   <arg name=\"user_interaction\" type=\"b\" direction=\"in\"/>\n" \
+        "  </method>\n"                                                 \
+        " </interface>\n"
+
+#define INTROSPECTION                                                   \
+        DBUS_INTROSPECT_1_0_XML_DOCTYPE_DECL_NODE                       \
+        "<node>\n"                                                      \
+        INTERFACE                                                       \
+        BUS_PROPERTIES_INTERFACE                                        \
+        BUS_INTROSPECTABLE_INTERFACE                                    \
+        BUS_PEER_INTERFACE                                              \
+        "</node>\n"
+
+#define INTERFACES_LIST                         \
+        BUS_GENERIC_INTERFACES_LIST             \
+        "org.freedesktop.timedate1\0"
+
+const char timedate_interface[] _introspect_("timedate1") = INTERFACE;
+
+typedef struct TZ {
+        char *zone;
+        bool local_rtc;
+        int use_ntp;
+} TZ;
+
+static TZ tz = {
+        .use_ntp = -1,
+};
+
+static usec_t remain_until;
+
+static void free_data(void) {
+        free(tz.zone);
+        tz.zone = NULL;
+
+        tz.local_rtc = false;
+}
+
+static bool valid_timezone(const char *name) {
+        const char *p;
+        char *t;
+        bool slash = false;
+        int r;
+        struct stat st;
+
+        assert(name);
+
+        if (*name == '/' || *name == 0)
+                return false;
+
+        for (p = name; *p; p++) {
+                if (!(*p >= '0' && *p <= '9') &&
+                    !(*p >= 'a' && *p <= 'z') &&
+                    !(*p >= 'A' && *p <= 'Z') &&
+                    !(*p == '-' || *p == '_' || *p == '+' || *p == '/'))
+                        return false;
+
+                if (*p == '/') {
+
+                        if (slash)
+                                return false;
+
+                        slash = true;
+                } else
+                        slash = false;
+        }
+
+        if (slash)
+                return false;
+
+        t = strappend("/usr/share/zoneinfo/", name);
+        if (!t)
+                return false;
+
+        r = stat(t, &st);
+        free(t);
+
+        if (r < 0)
+                return false;
+
+        if (!S_ISREG(st.st_mode))
+                return false;
+
+        return true;
+}
+
+static void verify_timezone(void) {
+        char *p, *a = NULL, *b = NULL;
+        size_t l, q;
+        int j, k;
+
+        if (!tz.zone)
+                return;
+
+        p = strappend("/usr/share/zoneinfo/", tz.zone);
+        if (!p) {
+                log_error("Out of memory");
+                return;
+        }
+
+        j = read_full_file("/etc/localtime", &a, &l);
+        k = read_full_file(p, &b, &q);
+
+        free(p);
+
+        if (j < 0 || k < 0 || l != q || memcmp(a, b, l)) {
+                log_warning("/etc/localtime and /etc/timezone out of sync.");
+                free(tz.zone);
+                tz.zone = NULL;
+        }
+
+        free(a);
+        free(b);
+}
+
+static int read_data(void) {
+        int r;
+
+        free_data();
+
+        r = read_one_line_file("/etc/timezone", &tz.zone);
+        if (r < 0) {
+                if (r != -ENOENT)
+                        log_warning("Failed to read /etc/timezone: %s", strerror(-r));
+
+#ifdef TARGET_FEDORA
+                r = parse_env_file("/etc/sysconfig/clock", NEWLINE,
+                                   "ZONE", &tz.zone,
+                                   NULL);
+
+                if (r < 0 && r != -ENOENT)
+                        log_warning("Failed to read /etc/sysconfig/clock: %s", strerror(-r));
+#endif
+        }
+
+        if (isempty(tz.zone)) {
+                free(tz.zone);
+                tz.zone = NULL;
+        }
+
+        verify_timezone();
+
+        tz.local_rtc = hwclock_is_localtime() > 0;
+
+        return 0;
+}
+
+static int write_data_timezone(void) {
+        int r = 0;
+        char *p;
+
+        if (!tz.zone) {
+                if (unlink("/etc/timezone") < 0 && errno != ENOENT)
+                        r = -errno;
+
+                if (unlink("/etc/localtime") < 0 && errno != ENOENT)
+                        r = -errno;
+
+                return r;
+        }
+
+        p = strappend("/usr/share/zoneinfo/", tz.zone);
+        if (!p) {
+                log_error("Out of memory");
+                return -ENOMEM;
+        }
+
+        r = symlink_or_copy_atomic(p, "/etc/localtime");
+        free(p);
+
+        if (r < 0)
+                return r;
+
+        r = write_one_line_file_atomic("/etc/timezone", tz.zone);
+        if (r < 0)
+                return r;
+
+        return 0;
+}
+
+static int write_data_local_rtc(void) {
+        int r;
+        char *s, *w;
+
+        r = read_full_file("/etc/adjtime", &s, NULL);
+        if (r < 0) {
+                if (r != -ENOENT)
+                        return r;
+
+                if (!tz.local_rtc)
+                        return 0;
+
+                w = strdup(NULL_ADJTIME_LOCAL);
+                if (!w)
+                        return -ENOMEM;
+        } else {
+                char *p, *e;
+                size_t a, b;
+
+                p = strchr(s, '\n');
+                if (!p) {
+                        free(s);
+                        return -EIO;
+                }
+
+                p = strchr(p+1, '\n');
+                if (!p) {
+                        free(s);
+                        return -EIO;
+                }
+
+                p++;
+                e = strchr(p, '\n');
+                if (!e) {
+                        free(s);
+                        return -EIO;
+                }
+
+                a = p - s;
+                b = strlen(e);
+
+                w = new(char, a + (tz.local_rtc ? 5 : 3) + b + 1);
+                if (!w) {
+                        free(s);
+                        return -ENOMEM;
+                }
+
+                *(char*) mempcpy(stpcpy(mempcpy(w, s, a), tz.local_rtc ? "LOCAL" : "UTC"), e, b) = 0;
+
+                if (streq(w, NULL_ADJTIME_UTC)) {
+                        free(w);
+
+                        if (unlink("/etc/adjtime") < 0) {
+                                if (errno != ENOENT)
+                                        return -errno;
+                        }
+
+                        return 0;
+                }
+        }
+
+        r = write_one_line_file_atomic("/etc/adjtime", w);
+        free(w);
+
+        return r;
+}
+
+static int read_ntp(DBusConnection *bus) {
+        DBusMessage *m = NULL, *reply = NULL;
+        const char *name = "ntpd.service", *s;
+        DBusError error;
+        int r;
+
+        assert(bus);
+
+        dbus_error_init(&error);
+
+        m = dbus_message_new_method_call(
+                        "org.freedesktop.systemd1",
+                        "/org/freedesktop/systemd1",
+                        "org.freedesktop.systemd1.Manager",
+                        "GetUnitFileState");
+
+        if (!m) {
+                log_error("Out of memory");
+                r = -ENOMEM;
+                goto finish;
+        }
+
+        if (!dbus_message_append_args(m,
+                                      DBUS_TYPE_STRING, &name,
+                                      DBUS_TYPE_INVALID)) {
+                log_error("Could not append arguments to message.");
+                r = -ENOMEM;
+                goto finish;
+        }
+
+        reply = dbus_connection_send_with_reply_and_block(bus, m, -1, &error);
+        if (!reply) {
+
+                if (streq(error.name, "org.freedesktop.DBus.Error.FileNotFound")) {
+                        /* NTP is not installed. */
+                        tz.use_ntp = false;
+                        r = 0;
+                        goto finish;
+                }
+
+                log_error("Failed to issue method call: %s", bus_error_message(&error));
+                r = -EIO;
+                goto finish;
+        }
+
+        if (!dbus_message_get_args(reply, &error,
+                                   DBUS_TYPE_STRING, &s,
+                                   DBUS_TYPE_INVALID)) {
+                log_error("Failed to parse reply: %s", bus_error_message(&error));
+                r = -EIO;
+                goto finish;
+        }
+
+        tz.use_ntp =
+                streq(s, "enabled") ||
+                streq(s, "enabled-runtime");
+        r = 0;
+
+finish:
+        if (m)
+                dbus_message_unref(m);
+
+        if (reply)
+                dbus_message_unref(reply);
+
+        dbus_error_free(&error);
+
+        return r;
+}
+
+static int start_ntp(DBusConnection *bus, DBusError *error) {
+        DBusMessage *m = NULL, *reply = NULL;
+        const char *name = "ntpd.service", *mode = "replace";
+        int r;
+
+        assert(bus);
+        assert(error);
+
+        m = dbus_message_new_method_call(
+                        "org.freedesktop.systemd1",
+                        "/org/freedesktop/systemd1",
+                        "org.freedesktop.systemd1.Manager",
+                        tz.use_ntp ? "StartUnit" : "StopUnit");
+        if (!m) {
+                log_error("Could not allocate message.");
+                r = -ENOMEM;
+                goto finish;
+        }
+
+        if (!dbus_message_append_args(m,
+                                      DBUS_TYPE_STRING, &name,
+                                      DBUS_TYPE_STRING, &mode,
+                                      DBUS_TYPE_INVALID)) {
+                log_error("Could not append arguments to message.");
+                r = -ENOMEM;
+                goto finish;
+        }
+
+        reply = dbus_connection_send_with_reply_and_block(bus, m, -1, error);
+        if (!reply) {
+                log_error("Failed to issue method call: %s", bus_error_message(error));
+                r = -EIO;
+                goto finish;
+        }
+
+        r = 0;
+
+finish:
+        if (m)
+                dbus_message_unref(m);
+
+        if (reply)
+                dbus_message_unref(reply);
+
+        return r;
+}
+
+static int enable_ntp(DBusConnection *bus, DBusError *error) {
+        DBusMessage *m = NULL, *reply = NULL;
+        const char * const names[] = { "ntpd.service", NULL };
+        int r;
+        DBusMessageIter iter;
+        dbus_bool_t f = FALSE, t = TRUE;
+
+        assert(bus);
+        assert(error);
+
+        m = dbus_message_new_method_call(
+                        "org.freedesktop.systemd1",
+                        "/org/freedesktop/systemd1",
+                        "org.freedesktop.systemd1.Manager",
+                        tz.use_ntp ? "EnableUnitFiles" : "DisableUnitFiles");
+
+        if (!m) {
+                log_error("Could not allocate message.");
+                r = -ENOMEM;
+                goto finish;
+        }
+
+        dbus_message_iter_init_append(m, &iter);
+
+        r = bus_append_strv_iter(&iter, (char**) names);
+        if (r < 0) {
+                log_error("Failed to append unit files.");
+                goto finish;
+        }
+        /* send runtime bool */
+        if (!dbus_message_iter_append_basic(&iter, DBUS_TYPE_BOOLEAN, &f)) {
+                log_error("Failed to append runtime boolean.");
+                r = -ENOMEM;
+                goto finish;
+        }
+
+        if (tz.use_ntp) {
+                /* send force bool */
+                if (!dbus_message_iter_append_basic(&iter, DBUS_TYPE_BOOLEAN, &t)) {
+                        log_error("Failed to append force boolean.");
+                        r = -ENOMEM;
+                        goto finish;
+                }
+        }
+
+        reply = dbus_connection_send_with_reply_and_block(bus, m, -1, error);
+        if (!reply) {
+                log_error("Failed to issue method call: %s", bus_error_message(error));
+                r = -EIO;
+                goto finish;
+        }
+
+        dbus_message_unref(m);
+        m = dbus_message_new_method_call(
+                        "org.freedesktop.systemd1",
+                        "/org/freedesktop/systemd1",
+                        "org.freedesktop.systemd1.Manager",
+                        "Reload");
+        if (!m) {
+                log_error("Could not allocate message.");
+                r = -ENOMEM;
+                goto finish;
+        }
+
+        dbus_message_unref(reply);
+        reply = dbus_connection_send_with_reply_and_block(bus, m, -1, error);
+        if (!reply) {
+                log_error("Failed to issue method call: %s", bus_error_message(error));
+                r = -EIO;
+                goto finish;
+        }
+
+        r = 0;
+
+finish:
+        if (m)
+                dbus_message_unref(m);
+
+        if (reply)
+                dbus_message_unref(reply);
+
+        return r;
+}
+
+static int property_append_ntp(DBusMessageIter *i, const char *property, void *data) {
+        dbus_bool_t db;
+
+        assert(i);
+        assert(property);
+
+        db = tz.use_ntp > 0;
+
+        if (!dbus_message_iter_append_basic(i, DBUS_TYPE_BOOLEAN, &db))
+                return -ENOMEM;
+
+        return 0;
+}
+
+static const BusProperty bus_timedate_properties[] = {
+        { "Timezone", bus_property_append_string, "s", offsetof(TZ, zone),     true },
+        { "LocalRTC", bus_property_append_bool,   "b", offsetof(TZ, local_rtc) },
+        { "NTP",      property_append_ntp,        "b", offsetof(TZ, use_ntp)   },
+        { NULL, }
+};
+
+static const BusBoundProperties bps[] = {
+        { "org.freedesktop.timedate1", bus_timedate_properties, &tz },
+        { NULL, }
+};
+
+static DBusHandlerResult timedate_message_handler(
+                DBusConnection *connection,
+                DBusMessage *message,
+                void *userdata) {
+
+        DBusMessage *reply = NULL, *changed = NULL;
+        DBusError error;
+        int r;
+
+        assert(connection);
+        assert(message);
+
+        dbus_error_init(&error);
+
+        if (dbus_message_is_method_call(message, "org.freedesktop.timedate1", "SetTimezone")) {
+                const char *z;
+                dbus_bool_t interactive;
+
+                if (!dbus_message_get_args(
+                                    message,
+                                    &error,
+                                    DBUS_TYPE_STRING, &z,
+                                    DBUS_TYPE_BOOLEAN, &interactive,
+                                    DBUS_TYPE_INVALID))
+                        return bus_send_error_reply(connection, message, &error, -EINVAL);
+
+                if (!valid_timezone(z))
+                        return bus_send_error_reply(connection, message, NULL, -EINVAL);
+
+                if (!streq_ptr(z, tz.zone)) {
+                        char *t;
+
+                        r = verify_polkit(connection, message, "org.freedesktop.timedate1.set-timezone", interactive, NULL, &error);
+                        if (r < 0)
+                                return bus_send_error_reply(connection, message, &error, r);
+
+                        t = strdup(z);
+                        if (!t)
+                                goto oom;
+
+                        free(tz.zone);
+                        tz.zone = t;
+
+                        /* 1. Write new configuration file */
+                        r = write_data_timezone();
+                        if (r < 0) {
+                                log_error("Failed to set timezone: %s", strerror(-r));
+                                return bus_send_error_reply(connection, message, NULL, r);
+                        }
+
+                        if (tz.local_rtc) {
+                                struct timespec ts;
+                                struct tm *tm;
+
+                                /* 2. Teach kernel new timezone */
+                                hwclock_apply_localtime_delta(NULL);
+
+                                /* 3. Sync RTC from system clock, with the new delta */
+                                assert_se(clock_gettime(CLOCK_REALTIME, &ts) == 0);
+                                assert_se(tm = localtime(&ts.tv_sec));
+                                hwclock_set_time(tm);
+                        }
+
+                        log_info("Changed timezone to '%s'.", tz.zone);
+
+                        changed = bus_properties_changed_new(
+                                        "/org/freedesktop/timedate1",
+                                        "org.freedesktop.timedate1",
+                                        "Timezone\0");
+                        if (!changed)
+                                goto oom;
+                }
+
+        } else if (dbus_message_is_method_call(message, "org.freedesktop.timedate1", "SetLocalRTC")) {
+                dbus_bool_t lrtc;
+                dbus_bool_t fix_system;
+                dbus_bool_t interactive;
+
+                if (!dbus_message_get_args(
+                                    message,
+                                    &error,
+                                    DBUS_TYPE_BOOLEAN, &lrtc,
+                                    DBUS_TYPE_BOOLEAN, &fix_system,
+                                    DBUS_TYPE_BOOLEAN, &interactive,
+                                    DBUS_TYPE_INVALID))
+                        return bus_send_error_reply(connection, message, &error, -EINVAL);
+
+                if (lrtc != tz.local_rtc) {
+                        struct timespec ts;
+
+                        r = verify_polkit(connection, message, "org.freedesktop.timedate1.set-local-rtc", interactive, NULL, &error);
+                        if (r < 0)
+                                return bus_send_error_reply(connection, message, &error, r);
+
+                        tz.local_rtc = lrtc;
+
+                        /* 1. Write new configuration file */
+                        r = write_data_local_rtc();
+                        if (r < 0) {
+                                log_error("Failed to set RTC to local/UTC: %s", strerror(-r));
+                                return bus_send_error_reply(connection, message, NULL, r);
+                        }
+
+                        /* 2. Teach kernel new timezone */
+                        if (tz.local_rtc)
+                                hwclock_apply_localtime_delta(NULL);
+                        else
+                                hwclock_reset_localtime_delta();
+
+                        /* 3. Synchronize clocks */
+                        assert_se(clock_gettime(CLOCK_REALTIME, &ts) == 0);
+
+                        if (fix_system) {
+                                struct tm tm;
+
+                                /* Sync system clock from RTC; first,
+                                 * initialize the timezone fields of
+                                 * struct tm. */
+                                if (tz.local_rtc)
+                                        tm = *localtime(&ts.tv_sec);
+                                else
+                                        tm = *gmtime(&ts.tv_sec);
+
+                                /* Override the main fields of
+                                 * struct tm, but not the timezone
+                                 * fields */
+                                if (hwclock_get_time(&tm) >= 0) {
+
+                                        /* And set the system clock
+                                         * with this */
+                                        if (tz.local_rtc)
+                                                ts.tv_sec = mktime(&tm);
+                                        else
+                                                ts.tv_sec = timegm(&tm);
+
+                                        clock_settime(CLOCK_REALTIME, &ts);
+                                }
+
+                        } else {
+                                struct tm *tm;
+
+                                /* Sync RTC from system clock */
+                                if (tz.local_rtc)
+                                        tm = localtime(&ts.tv_sec);
+                                else
+                                        tm = gmtime(&ts.tv_sec);
+
+                                hwclock_set_time(tm);
+                        }
+
+                        log_info("RTC configured to %s time.", tz.local_rtc ? "local" : "UTC");
+
+                        changed = bus_properties_changed_new(
+                                        "/org/freedesktop/timedate1",
+                                        "org.freedesktop.timedate1",
+                                        "LocalRTC\0");
+                        if (!changed)
+                                goto oom;
+                }
+
+        } else if (dbus_message_is_method_call(message, "org.freedesktop.timedate1", "SetTime")) {
+                int64_t utc;
+                dbus_bool_t relative;
+                dbus_bool_t interactive;
+
+                if (!dbus_message_get_args(
+                                    message,
+                                    &error,
+                                    DBUS_TYPE_INT64, &utc,
+                                    DBUS_TYPE_BOOLEAN, &relative,
+                                    DBUS_TYPE_BOOLEAN, &interactive,
+                                    DBUS_TYPE_INVALID))
+                        return bus_send_error_reply(connection, message, &error, -EINVAL);
+
+                if (!relative && utc <= 0)
+                        return bus_send_error_reply(connection, message, NULL, -EINVAL);
+
+                if (!relative || utc != 0) {
+                        struct timespec ts;
+                        struct tm* tm;
+
+                        r = verify_polkit(connection, message, "org.freedesktop.timedate1.set-time", interactive, NULL, &error);
+                        if (r < 0)
+                                return bus_send_error_reply(connection, message, &error, r);
+
+                        if (relative)
+                                timespec_store(&ts, now(CLOCK_REALTIME) + utc);
+                        else
+                                timespec_store(&ts, utc);
+
+                        /* Set system clock */
+                        if (clock_settime(CLOCK_REALTIME, &ts) < 0) {
+                                log_error("Failed to set local time: %m");
+                                return bus_send_error_reply(connection, message, NULL, -errno);
+                        }
+
+                        /* Sync down to RTC */
+                        if (tz.local_rtc)
+                                tm = localtime(&ts.tv_sec);
+                        else
+                                tm = gmtime(&ts.tv_sec);
+
+                        hwclock_set_time(tm);
+
+                        log_info("Changed local time to %s", ctime(&ts.tv_sec));
+                }
+        } else if (dbus_message_is_method_call(message, "org.freedesktop.timedate1", "SetNTP")) {
+                dbus_bool_t ntp;
+                dbus_bool_t interactive;
+
+                if (!dbus_message_get_args(
+                                    message,
+                                    &error,
+                                    DBUS_TYPE_BOOLEAN, &ntp,
+                                    DBUS_TYPE_BOOLEAN, &interactive,
+                                    DBUS_TYPE_INVALID))
+                        return bus_send_error_reply(connection, message, &error, -EINVAL);
+
+                if (ntp != !!tz.use_ntp) {
+
+                        r = verify_polkit(connection, message, "org.freedesktop.timedate1.set-ntp", interactive, NULL, &error);
+                        if (r < 0)
+                                return bus_send_error_reply(connection, message, &error, r);
+
+                        tz.use_ntp = !!ntp;
+
+                        r = enable_ntp(connection, &error);
+                        if (r < 0)
+                                return bus_send_error_reply(connection, message, &error, r);
+
+                        r = start_ntp(connection, &error);
+                        if (r < 0)
+                                return bus_send_error_reply(connection, message, &error, r);
+
+                        log_info("Set NTP to %s", tz.use_ntp ? "enabled" : "disabled");
+
+                        changed = bus_properties_changed_new(
+                                        "/org/freedesktop/timedate1",
+                                        "org.freedesktop.timedate1",
+                                        "NTP\0");
+                        if (!changed)
+                                goto oom;
+                }
+
+        } else
+                return bus_default_message_handler(connection, message, INTROSPECTION, INTERFACES_LIST, bps);
+
+        if (!(reply = dbus_message_new_method_return(message)))
+                goto oom;
+
+        if (!dbus_connection_send(connection, reply, NULL))
+                goto oom;
+
+        dbus_message_unref(reply);
+        reply = NULL;
+
+        if (changed) {
+
+                if (!dbus_connection_send(connection, changed, NULL))
+                        goto oom;
+
+                dbus_message_unref(changed);
+        }
+
+        return DBUS_HANDLER_RESULT_HANDLED;
+
+oom:
+        if (reply)
+                dbus_message_unref(reply);
+
+        if (changed)
+                dbus_message_unref(changed);
+
+        dbus_error_free(&error);
+
+        return DBUS_HANDLER_RESULT_NEED_MEMORY;
+}
+
+static int connect_bus(DBusConnection **_bus) {
+        static const DBusObjectPathVTable timedate_vtable = {
+                .message_function = timedate_message_handler
+        };
+        DBusError error;
+        DBusConnection *bus = NULL;
+        int r;
+
+        assert(_bus);
+
+        dbus_error_init(&error);
+
+        bus = dbus_bus_get_private(DBUS_BUS_SYSTEM, &error);
+        if (!bus) {
+                log_error("Failed to get system D-Bus connection: %s", bus_error_message(&error));
+                r = -ECONNREFUSED;
+                goto fail;
+        }
+
+        dbus_connection_set_exit_on_disconnect(bus, FALSE);
+
+        if (!dbus_connection_register_object_path(bus, "/org/freedesktop/timedate1", &timedate_vtable, NULL) ||
+            !dbus_connection_add_filter(bus, bus_exit_idle_filter, &remain_until, NULL)) {
+                log_error("Not enough memory");
+                r = -ENOMEM;
+                goto fail;
+        }
+
+        r = dbus_bus_request_name(bus, "org.freedesktop.timedate1", DBUS_NAME_FLAG_DO_NOT_QUEUE, &error);
+        if (dbus_error_is_set(&error)) {
+                log_error("Failed to register name on bus: %s", bus_error_message(&error));
+                r = -EEXIST;
+                goto fail;
+        }
+
+        if (r != DBUS_REQUEST_NAME_REPLY_PRIMARY_OWNER)  {
+                log_error("Failed to acquire name.");
+                r = -EEXIST;
+                goto fail;
+        }
+
+        if (_bus)
+                *_bus = bus;
+
+        return 0;
+
+fail:
+        dbus_connection_close(bus);
+        dbus_connection_unref(bus);
+
+        dbus_error_free(&error);
+
+        return r;
+}
+
+int main(int argc, char *argv[]) {
+        int r;
+        DBusConnection *bus = NULL;
+        bool exiting = false;
+
+        log_set_target(LOG_TARGET_AUTO);
+        log_parse_environment();
+        log_open();
+
+        umask(0022);
+
+        if (argc == 2 && streq(argv[1], "--introspect")) {
+                fputs(DBUS_INTROSPECT_1_0_XML_DOCTYPE_DECL_NODE
+                      "<node>\n", stdout);
+                fputs(timedate_interface, stdout);
+                fputs("</node>\n", stdout);
+                return 0;
+        }
+
+        if (argc != 1) {
+                log_error("This program takes no arguments.");
+                r = -EINVAL;
+                goto finish;
+        }
+
+        r = read_data();
+        if (r < 0) {
+                log_error("Failed to read timezone data: %s", strerror(-r));
+                goto finish;
+        }
+
+        r = connect_bus(&bus);
+        if (r < 0)
+                goto finish;
+
+        r = read_ntp(bus);
+        if (r < 0) {
+                log_error("Failed to determine whether NTP is enabled: %s", strerror(-r));
+                goto finish;
+        }
+
+        remain_until = now(CLOCK_MONOTONIC) + DEFAULT_EXIT_USEC;
+        for (;;) {
+
+                if (!dbus_connection_read_write_dispatch(bus, exiting ? -1 : (int) (DEFAULT_EXIT_USEC/USEC_PER_MSEC)))
+                        break;
+
+                if (!exiting && remain_until < now(CLOCK_MONOTONIC)) {
+                        exiting = true;
+                        bus_async_unregister_and_exit(bus, "org.freedesktop.hostname1");
+                }
+        }
+
+        r = 0;
+
+finish:
+        free_data();
+
+        if (bus) {
+                dbus_connection_flush(bus);
+                dbus_connection_close(bus);
+                dbus_connection_unref(bus);
+        }
+
+        return r < 0 ? EXIT_FAILURE : EXIT_SUCCESS;
+}
diff --git a/src/timer.c b/src/timer.c
new file mode 100644 (file)
index 0000000..e318fee
--- /dev/null
@@ -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 <http://www.gnu.org/licenses/>.
+***/
+
+#include <errno.h>
+
+#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
new file mode 100644 (file)
index 0000000..f5c5c64
--- /dev/null
@@ -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 <http://www.gnu.org/licenses/>.
+***/
+
+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/timestamp.c b/src/timestamp.c
new file mode 100644 (file)
index 0000000..ce51429
--- /dev/null
@@ -0,0 +1,39 @@
+/*-*- 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 <http://www.gnu.org/licenses/>.
+***/
+
+#include <stdio.h>
+
+#include "util.h"
+
+int main(int argc, char *argv[]) {
+        struct dual_timestamp t;
+
+        /* This is mostly useful for stuff like init ram disk scripts
+         * which want to take a proper timestamp to do minimal bootup
+         * profiling. */
+
+        dual_timestamp_get(&t);
+        printf("%llu %llu\n",
+               (unsigned long long) t.realtime,
+               (unsigned long long) t.monotonic);
+
+        return 0;
+}
diff --git a/src/tmpfiles.c b/src/tmpfiles.c
new file mode 100644 (file)
index 0000000..873bf23
--- /dev/null
@@ -0,0 +1,1314 @@
+/*-*- Mode: C; c-basic-offset: 8; indent-tabs-mode: nil -*-*/
+
+/***
+  This file is part of systemd.
+
+  Copyright 2010 Lennart Poettering, Kay Sievers
+
+  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 <http://www.gnu.org/licenses/>.
+***/
+
+#include <unistd.h>
+#include <fcntl.h>
+#include <errno.h>
+#include <string.h>
+#include <sys/stat.h>
+#include <limits.h>
+#include <dirent.h>
+#include <grp.h>
+#include <pwd.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <stddef.h>
+#include <getopt.h>
+#include <stdbool.h>
+#include <time.h>
+#include <sys/types.h>
+#include <sys/param.h>
+#include <glob.h>
+#include <fnmatch.h>
+
+#include "log.h"
+#include "util.h"
+#include "strv.h"
+#include "label.h"
+#include "set.h"
+
+/* This reads all files listed in /etc/tmpfiles.d/?*.conf and creates
+ * them in the file system. This is intended to be used to create
+ * properly owned directories beneath /tmp, /var/tmp, /run, which are
+ * volatile and hence need to be recreated on bootup. */
+
+typedef enum ItemType {
+        /* These ones take file names */
+        CREATE_FILE = 'f',
+        TRUNCATE_FILE = 'F',
+        WRITE_FILE = 'w',
+        CREATE_DIRECTORY = 'd',
+        TRUNCATE_DIRECTORY = 'D',
+        CREATE_FIFO = 'p',
+        CREATE_SYMLINK = 'L',
+        CREATE_CHAR_DEVICE = 'c',
+        CREATE_BLOCK_DEVICE = 'b',
+
+        /* These ones take globs */
+        IGNORE_PATH = 'x',
+        REMOVE_PATH = 'r',
+        RECURSIVE_REMOVE_PATH = 'R',
+        RELABEL_PATH = 'z',
+        RECURSIVE_RELABEL_PATH = 'Z'
+} ItemType;
+
+typedef struct Item {
+        ItemType type;
+
+        char *path;
+        char *argument;
+        uid_t uid;
+        gid_t gid;
+        mode_t mode;
+        usec_t age;
+
+        dev_t major_minor;
+
+        bool uid_set:1;
+        bool gid_set:1;
+        bool mode_set:1;
+        bool age_set:1;
+} Item;
+
+static Hashmap *items = NULL, *globs = NULL;
+static Set *unix_sockets = NULL;
+
+static bool arg_create = false;
+static bool arg_clean = false;
+static bool arg_remove = false;
+
+static const char *arg_prefix = NULL;
+
+#define MAX_DEPTH 256
+
+static bool needs_glob(ItemType t) {
+        return t == IGNORE_PATH || t == REMOVE_PATH || t == RECURSIVE_REMOVE_PATH || t == RELABEL_PATH || t == RECURSIVE_RELABEL_PATH;
+}
+
+static struct Item* find_glob(Hashmap *h, const char *match) {
+        Item *j;
+        Iterator i;
+
+        HASHMAP_FOREACH(j, h, i)
+                if (fnmatch(j->path, match, FNM_PATHNAME|FNM_PERIOD) == 0)
+                        return j;
+
+        return NULL;
+}
+
+static void load_unix_sockets(void) {
+        FILE *f = NULL;
+        char line[LINE_MAX];
+
+        if (unix_sockets)
+                return;
+
+        /* We maintain a cache of the sockets we found in
+         * /proc/net/unix to speed things up a little. */
+
+        unix_sockets = set_new(string_hash_func, string_compare_func);
+        if (!unix_sockets)
+                return;
+
+        f = fopen("/proc/net/unix", "re");
+        if (!f)
+                return;
+
+        /* Skip header */
+        if (!fgets(line, sizeof(line), f))
+                goto fail;
+
+        for (;;) {
+                char *p, *s;
+                int k;
+
+                if (!fgets(line, sizeof(line), f))
+                        break;
+
+                truncate_nl(line);
+
+                p = strchr(line, ':');
+                if (!p)
+                        continue;
+
+                if (strlen(p) < 37)
+                        continue;
+
+                p += 37;
+                p += strspn(p, WHITESPACE);
+                p += strcspn(p, WHITESPACE); /* skip one more word */
+                p += strspn(p, WHITESPACE);
+
+                if (*p != '/')
+                        continue;
+
+                s = strdup(p);
+                if (!s)
+                        goto fail;
+
+                path_kill_slashes(s);
+
+                k = set_put(unix_sockets, s);
+                if (k < 0) {
+                        free(s);
+
+                        if (k != -EEXIST)
+                                goto fail;
+                }
+        }
+
+        fclose(f);
+        return;
+
+fail:
+        set_free_free(unix_sockets);
+        unix_sockets = NULL;
+
+        if (f)
+                fclose(f);
+}
+
+static bool unix_socket_alive(const char *fn) {
+        assert(fn);
+
+        load_unix_sockets();
+
+        if (unix_sockets)
+                return !!set_get(unix_sockets, (char*) fn);
+
+        /* We don't know, so assume yes */
+        return true;
+}
+
+static int dir_cleanup(
+                const char *p,
+                DIR *d,
+                const struct stat *ds,
+                usec_t cutoff,
+                dev_t rootdev,
+                bool mountpoint,
+                int maxdepth)
+{
+        struct dirent *dent;
+        struct timespec times[2];
+        bool deleted = false;
+        char *sub_path = NULL;
+        int r = 0;
+
+        while ((dent = readdir(d))) {
+                struct stat s;
+                usec_t age;
+
+                if (streq(dent->d_name, ".") ||
+                    streq(dent->d_name, ".."))
+                        continue;
+
+                if (fstatat(dirfd(d), dent->d_name, &s, AT_SYMLINK_NOFOLLOW) < 0) {
+
+                        if (errno != ENOENT) {
+                                log_error("stat(%s/%s) failed: %m", p, dent->d_name);
+                                r = -errno;
+                        }
+
+                        continue;
+                }
+
+                /* Stay on the same filesystem */
+                if (s.st_dev != rootdev)
+                        continue;
+
+                /* Do not delete read-only files owned by root */
+                if (s.st_uid == 0 && !(s.st_mode & S_IWUSR))
+                        continue;
+
+                free(sub_path);
+                sub_path = NULL;
+
+                if (asprintf(&sub_path, "%s/%s", p, dent->d_name) < 0) {
+                        log_error("Out of memory");
+                        r = -ENOMEM;
+                        goto finish;
+                }
+
+                /* Is there an item configured for this path? */
+                if (hashmap_get(items, sub_path))
+                        continue;
+
+                if (find_glob(globs, sub_path))
+                        continue;
+
+                if (S_ISDIR(s.st_mode)) {
+
+                        if (mountpoint &&
+                            streq(dent->d_name, "lost+found") &&
+                            s.st_uid == 0)
+                                continue;
+
+                        if (maxdepth <= 0)
+                                log_warning("Reached max depth on %s.", sub_path);
+                        else {
+                                DIR *sub_dir;
+                                int q;
+
+                                sub_dir = xopendirat(dirfd(d), dent->d_name, O_NOFOLLOW);
+                                if (sub_dir == NULL) {
+                                        if (errno != ENOENT) {
+                                                log_error("opendir(%s/%s) failed: %m", p, dent->d_name);
+                                                r = -errno;
+                                        }
+
+                                        continue;
+                                }
+
+                                q = dir_cleanup(sub_path, sub_dir, &s, cutoff, rootdev, false, maxdepth-1);
+                                closedir(sub_dir);
+
+                                if (q < 0)
+                                        r = q;
+                        }
+
+                        /* Ignore ctime, we change it when deleting */
+                        age = MAX(timespec_load(&s.st_mtim),
+                                  timespec_load(&s.st_atim));
+                        if (age >= cutoff)
+                                continue;
+
+                        log_debug("rmdir '%s'\n", sub_path);
+
+                        if (unlinkat(dirfd(d), dent->d_name, AT_REMOVEDIR) < 0) {
+                                if (errno != ENOENT && errno != ENOTEMPTY) {
+                                        log_error("rmdir(%s): %m", sub_path);
+                                        r = -errno;
+                                }
+                        }
+
+                } else {
+                        /* Skip files for which the sticky bit is
+                         * set. These are semantics we define, and are
+                         * unknown elsewhere. See XDG_RUNTIME_DIR
+                         * specification for details. */
+                        if (s.st_mode & S_ISVTX)
+                                continue;
+
+                        if (mountpoint && S_ISREG(s.st_mode)) {
+                                if (streq(dent->d_name, ".journal") &&
+                                    s.st_uid == 0)
+                                        continue;
+
+                                if (streq(dent->d_name, "aquota.user") ||
+                                    streq(dent->d_name, "aquota.group"))
+                                        continue;
+                        }
+
+                        /* Ignore sockets that are listed in /proc/net/unix */
+                        if (S_ISSOCK(s.st_mode) && unix_socket_alive(sub_path))
+                                continue;
+
+                        /* Ignore device nodes */
+                        if (S_ISCHR(s.st_mode) || S_ISBLK(s.st_mode))
+                                continue;
+
+                        age = MAX3(timespec_load(&s.st_mtim),
+                                   timespec_load(&s.st_atim),
+                                   timespec_load(&s.st_ctim));
+
+                        if (age >= cutoff)
+                                continue;
+
+                        log_debug("unlink '%s'\n", sub_path);
+
+                        if (unlinkat(dirfd(d), dent->d_name, 0) < 0) {
+                                if (errno != ENOENT) {
+                                        log_error("unlink(%s): %m", sub_path);
+                                        r = -errno;
+                                }
+                        }
+
+                        deleted = true;
+                }
+        }
+
+finish:
+        if (deleted) {
+                /* Restore original directory timestamps */
+                times[0] = ds->st_atim;
+                times[1] = ds->st_mtim;
+
+                if (futimens(dirfd(d), times) < 0)
+                        log_error("utimensat(%s): %m", p);
+        }
+
+        free(sub_path);
+
+        return r;
+}
+
+static int clean_item(Item *i) {
+        DIR *d;
+        struct stat s, ps;
+        bool mountpoint;
+        int r;
+        usec_t cutoff, n;
+
+        assert(i);
+
+        if (i->type != CREATE_DIRECTORY &&
+            i->type != TRUNCATE_DIRECTORY &&
+            i->type != IGNORE_PATH)
+                return 0;
+
+        if (!i->age_set || i->age <= 0)
+                return 0;
+
+        n = now(CLOCK_REALTIME);
+        if (n < i->age)
+                return 0;
+
+        cutoff = n - i->age;
+
+        d = opendir(i->path);
+        if (!d) {
+                if (errno == ENOENT)
+                        return 0;
+
+                log_error("Failed to open directory %s: %m", i->path);
+                return -errno;
+        }
+
+        if (fstat(dirfd(d), &s) < 0) {
+                log_error("stat(%s) failed: %m", i->path);
+                r = -errno;
+                goto finish;
+        }
+
+        if (!S_ISDIR(s.st_mode)) {
+                log_error("%s is not a directory.", i->path);
+                r = -ENOTDIR;
+                goto finish;
+        }
+
+        if (fstatat(dirfd(d), "..", &ps, AT_SYMLINK_NOFOLLOW) != 0) {
+                log_error("stat(%s/..) failed: %m", i->path);
+                r = -errno;
+                goto finish;
+        }
+
+        mountpoint = s.st_dev != ps.st_dev ||
+                     (s.st_dev == ps.st_dev && s.st_ino == ps.st_ino);
+
+        r = dir_cleanup(i->path, d, &s, cutoff, s.st_dev, mountpoint, MAX_DEPTH);
+
+finish:
+        if (d)
+                closedir(d);
+
+        return r;
+}
+
+static int item_set_perms(Item *i, const char *path) {
+        /* not using i->path directly because it may be a glob */
+        if (i->mode_set)
+                if (chmod(path, i->mode) < 0) {
+                        log_error("chmod(%s) failed: %m", path);
+                        return -errno;
+                }
+
+        if (i->uid_set || i->gid_set)
+                if (chown(path,
+                          i->uid_set ? i->uid : (uid_t) -1,
+                          i->gid_set ? i->gid : (gid_t) -1) < 0) {
+
+                        log_error("chown(%s) failed: %m", path);
+                        return -errno;
+                }
+
+        return label_fix(path, false);
+}
+
+static int recursive_relabel_children(Item *i, const char *path) {
+        DIR *d;
+        int ret = 0;
+
+        /* This returns the first error we run into, but nevertheless
+         * tries to go on */
+
+        d = opendir(path);
+        if (!d)
+                return errno == ENOENT ? 0 : -errno;
+
+        for (;;) {
+                struct dirent buf, *de;
+                bool is_dir;
+                int r;
+                char *entry_path;
+
+                r = readdir_r(d, &buf, &de);
+                if (r != 0) {
+                        if (ret == 0)
+                                ret = -r;
+                        break;
+                }
+
+                if (!de)
+                        break;
+
+                if (streq(de->d_name, ".") || streq(de->d_name, ".."))
+                        continue;
+
+                if (asprintf(&entry_path, "%s/%s", path, de->d_name) < 0) {
+                        if (ret == 0)
+                                ret = -ENOMEM;
+                        continue;
+                }
+
+                if (de->d_type == DT_UNKNOWN) {
+                        struct stat st;
+
+                        if (lstat(entry_path, &st) < 0) {
+                                if (ret == 0 && errno != ENOENT)
+                                        ret = -errno;
+                                free(entry_path);
+                                continue;
+                        }
+
+                        is_dir = S_ISDIR(st.st_mode);
+
+                } else
+                        is_dir = de->d_type == DT_DIR;
+
+                r = item_set_perms(i, entry_path);
+                if (r < 0) {
+                        if (ret == 0 && r != -ENOENT)
+                                ret = r;
+                        free(entry_path);
+                        continue;
+                }
+
+                if (is_dir) {
+                        r = recursive_relabel_children(i, entry_path);
+                        if (r < 0 && ret == 0)
+                                ret = r;
+                }
+
+                free(entry_path);
+        }
+
+        closedir(d);
+
+        return ret;
+}
+
+static int recursive_relabel(Item *i, const char *path) {
+        int r;
+        struct stat st;
+
+        r = item_set_perms(i, path);
+        if (r < 0)
+                return r;
+
+        if (lstat(path, &st) < 0)
+                return -errno;
+
+        if (S_ISDIR(st.st_mode))
+                r = recursive_relabel_children(i, path);
+
+        return r;
+}
+
+static int glob_item(Item *i, int (*action)(Item *, const char *)) {
+        int r = 0, k;
+        glob_t g;
+        char **fn;
+
+        zero(g);
+
+        errno = 0;
+        if ((k = glob(i->path, GLOB_NOSORT|GLOB_BRACE, NULL, &g)) != 0) {
+
+                if (k != GLOB_NOMATCH) {
+                        if (errno != 0)
+                                errno = EIO;
+
+                        log_error("glob(%s) failed: %m", i->path);
+                        return -errno;
+                }
+        }
+
+        STRV_FOREACH(fn, g.gl_pathv)
+                if ((k = action(i, *fn)) < 0)
+                        r = k;
+
+        globfree(&g);
+        return r;
+}
+
+static int create_item(Item *i) {
+        int r;
+        mode_t u;
+        struct stat st;
+
+        assert(i);
+
+        switch (i->type) {
+
+        case IGNORE_PATH:
+        case REMOVE_PATH:
+        case RECURSIVE_REMOVE_PATH:
+                return 0;
+
+        case CREATE_FILE:
+        case TRUNCATE_FILE:
+        case WRITE_FILE: {
+                int fd, flags;
+
+                flags = i->type == CREATE_FILE ? O_CREAT|O_APPEND :
+                        i->type == TRUNCATE_FILE ? O_CREAT|O_TRUNC : 0;
+
+                u = umask(0);
+                fd = open(i->path, flags|O_NDELAY|O_CLOEXEC|O_WRONLY|O_NOCTTY|O_NOFOLLOW, i->mode);
+                umask(u);
+
+                if (fd < 0) {
+                        if (i->type == WRITE_FILE && errno == ENOENT)
+                                break;
+
+                        log_error("Failed to create file %s: %m", i->path);
+                        return -errno;
+                }
+
+                if (i->argument) {
+                        ssize_t n;
+                        size_t l;
+                        struct iovec iovec[2];
+                        static const char new_line = '\n';
+
+                        l = strlen(i->argument);
+
+                        zero(iovec);
+                        iovec[0].iov_base = i->argument;
+                        iovec[0].iov_len = l;
+
+                        iovec[1].iov_base = (void*) &new_line;
+                        iovec[1].iov_len = 1;
+
+                        n = writev(fd, iovec, 2);
+                        if (n < 0 || (size_t) n != l+1) {
+                                log_error("Failed to write file %s: %s", i->path, n < 0 ? strerror(-n) : "Short");
+                                close_nointr_nofail(fd);
+                                return n < 0 ? n : -EIO;
+                        }
+                }
+
+                close_nointr_nofail(fd);
+
+                if (stat(i->path, &st) < 0) {
+                        log_error("stat(%s) failed: %m", i->path);
+                        return -errno;
+                }
+
+                if (!S_ISREG(st.st_mode)) {
+                        log_error("%s is not a file.", i->path);
+                        return -EEXIST;
+                }
+
+                r = item_set_perms(i, i->path);
+                if (r < 0)
+                        return r;
+
+                break;
+        }
+
+        case TRUNCATE_DIRECTORY:
+        case CREATE_DIRECTORY:
+
+                u = umask(0);
+                mkdir_parents(i->path, 0755);
+                r = mkdir(i->path, i->mode);
+                umask(u);
+
+                if (r < 0 && errno != EEXIST) {
+                        log_error("Failed to create directory %s: %m", i->path);
+                        return -errno;
+                }
+
+                if (stat(i->path, &st) < 0) {
+                        log_error("stat(%s) failed: %m", i->path);
+                        return -errno;
+                }
+
+                if (!S_ISDIR(st.st_mode)) {
+                        log_error("%s is not a directory.", i->path);
+                        return -EEXIST;
+                }
+
+                r = item_set_perms(i, i->path);
+                if (r < 0)
+                        return r;
+
+                break;
+
+        case CREATE_FIFO:
+
+                u = umask(0);
+                r = mkfifo(i->path, i->mode);
+                umask(u);
+
+                if (r < 0 && errno != EEXIST) {
+                        log_error("Failed to create fifo %s: %m", i->path);
+                        return -errno;
+                }
+
+                if (stat(i->path, &st) < 0) {
+                        log_error("stat(%s) failed: %m", i->path);
+                        return -errno;
+                }
+
+                if (!S_ISFIFO(st.st_mode)) {
+                        log_error("%s is not a fifo.", i->path);
+                        return -EEXIST;
+                }
+
+                r = item_set_perms(i, i->path);
+                if (r < 0)
+                        return r;
+
+                break;
+
+        case CREATE_SYMLINK: {
+                char *x;
+
+                r = symlink(i->argument, i->path);
+                if (r < 0 && errno != EEXIST) {
+                        log_error("symlink(%s, %s) failed: %m", i->argument, i->path);
+                        return -errno;
+                }
+
+                r = readlink_malloc(i->path, &x);
+                if (r < 0) {
+                        log_error("readlink(%s) failed: %s", i->path, strerror(-r));
+                        return -errno;
+                }
+
+                if (!streq(i->argument, x)) {
+                        free(x);
+                        log_error("%s is not the right symlinks.", i->path);
+                        return -EEXIST;
+                }
+
+                free(x);
+                break;
+        }
+
+        case CREATE_BLOCK_DEVICE:
+        case CREATE_CHAR_DEVICE: {
+
+                u = umask(0);
+                r = mknod(i->path, i->mode | (i->type == CREATE_BLOCK_DEVICE ? S_IFBLK : S_IFCHR), i->major_minor);
+                umask(u);
+
+                if (r < 0 && errno != EEXIST) {
+                        log_error("Failed to create device node %s: %m", i->path);
+                        return -errno;
+                }
+
+                if (stat(i->path, &st) < 0) {
+                        log_error("stat(%s) failed: %m", i->path);
+                        return -errno;
+                }
+
+                if (i->type == CREATE_BLOCK_DEVICE ? !S_ISBLK(st.st_mode) : !S_ISCHR(st.st_mode)) {
+                        log_error("%s is not a device node.", i->path);
+                        return -EEXIST;
+                }
+
+                r = item_set_perms(i, i->path);
+                if (r < 0)
+                        return r;
+
+                break;
+        }
+
+        case RELABEL_PATH:
+
+                r = glob_item(i, item_set_perms);
+                if (r < 0)
+                        return 0;
+                break;
+
+        case RECURSIVE_RELABEL_PATH:
+
+                r = glob_item(i, recursive_relabel);
+                if (r < 0)
+                        return r;
+        }
+
+        log_debug("%s created successfully.", i->path);
+
+        return 0;
+}
+
+static int remove_item_instance(Item *i, const char *instance) {
+        int r;
+
+        assert(i);
+
+        switch (i->type) {
+
+        case CREATE_FILE:
+        case TRUNCATE_FILE:
+        case CREATE_DIRECTORY:
+        case CREATE_FIFO:
+        case CREATE_SYMLINK:
+        case CREATE_BLOCK_DEVICE:
+        case CREATE_CHAR_DEVICE:
+        case IGNORE_PATH:
+        case RELABEL_PATH:
+        case RECURSIVE_RELABEL_PATH:
+        case WRITE_FILE:
+                break;
+
+        case REMOVE_PATH:
+                if (remove(instance) < 0 && errno != ENOENT) {
+                        log_error("remove(%s): %m", instance);
+                        return -errno;
+                }
+
+                break;
+
+        case TRUNCATE_DIRECTORY:
+        case RECURSIVE_REMOVE_PATH:
+                r = rm_rf(instance, false, i->type == RECURSIVE_REMOVE_PATH, false);
+                if (r < 0 && r != -ENOENT) {
+                        log_error("rm_rf(%s): %s", instance, strerror(-r));
+                        return r;
+                }
+
+                break;
+        }
+
+        return 0;
+}
+
+static int remove_item(Item *i) {
+        int r = 0;
+
+        assert(i);
+
+        switch (i->type) {
+
+        case CREATE_FILE:
+        case TRUNCATE_FILE:
+        case CREATE_DIRECTORY:
+        case CREATE_FIFO:
+        case CREATE_SYMLINK:
+        case CREATE_CHAR_DEVICE:
+        case CREATE_BLOCK_DEVICE:
+        case IGNORE_PATH:
+        case RELABEL_PATH:
+        case RECURSIVE_RELABEL_PATH:
+        case WRITE_FILE:
+                break;
+
+        case REMOVE_PATH:
+        case TRUNCATE_DIRECTORY:
+        case RECURSIVE_REMOVE_PATH:
+                r = glob_item(i, remove_item_instance);
+                break;
+        }
+
+        return r;
+}
+
+static int process_item(Item *i) {
+        int r, q, p;
+
+        assert(i);
+
+        r = arg_create ? create_item(i) : 0;
+        q = arg_remove ? remove_item(i) : 0;
+        p = arg_clean ? clean_item(i) : 0;
+
+        if (r < 0)
+                return r;
+
+        if (q < 0)
+                return q;
+
+        return p;
+}
+
+static void item_free(Item *i) {
+        assert(i);
+
+        free(i->path);
+        free(i->argument);
+        free(i);
+}
+
+static bool item_equal(Item *a, Item *b) {
+        assert(a);
+        assert(b);
+
+        if (!streq_ptr(a->path, b->path))
+                return false;
+
+        if (a->type != b->type)
+                return false;
+
+        if (a->uid_set != b->uid_set ||
+            (a->uid_set && a->uid != b->uid))
+            return false;
+
+        if (a->gid_set != b->gid_set ||
+            (a->gid_set && a->gid != b->gid))
+            return false;
+
+        if (a->mode_set != b->mode_set ||
+            (a->mode_set && a->mode != b->mode))
+            return false;
+
+        if (a->age_set != b->age_set ||
+            (a->age_set && a->age != b->age))
+            return false;
+
+        if ((a->type == CREATE_FILE ||
+             a->type == TRUNCATE_FILE ||
+             a->type == WRITE_FILE ||
+             a->type == CREATE_SYMLINK) &&
+            !streq_ptr(a->argument, b->argument))
+                return false;
+
+        if ((a->type == CREATE_CHAR_DEVICE ||
+             a->type == CREATE_BLOCK_DEVICE) &&
+            a->major_minor != b->major_minor)
+                return false;
+
+        return true;
+}
+
+static int parse_line(const char *fname, unsigned line, const char *buffer) {
+        Item *i, *existing;
+        char *mode = NULL, *user = NULL, *group = NULL, *age = NULL;
+        char type;
+        Hashmap *h;
+        int r, n = -1;
+
+        assert(fname);
+        assert(line >= 1);
+        assert(buffer);
+
+        i = new0(Item, 1);
+        if (!i) {
+                log_error("Out of memory");
+                return -ENOMEM;
+        }
+
+        if (sscanf(buffer,
+                   "%c "
+                   "%ms "
+                   "%ms "
+                   "%ms "
+                   "%ms "
+                   "%ms "
+                   "%n",
+                   &type,
+                   &i->path,
+                   &mode,
+                   &user,
+                   &group,
+                   &age,
+                   &n) < 2) {
+                log_error("[%s:%u] Syntax error.", fname, line);
+                r = -EIO;
+                goto finish;
+        }
+
+        if (n >= 0)  {
+                n += strspn(buffer+n, WHITESPACE);
+                if (buffer[n] != 0 && (buffer[n] != '-' || buffer[n+1] != 0)) {
+                        i->argument = unquote(buffer+n, "\"");
+                        if (!i->argument) {
+                                log_error("Out of memory");
+                                return -ENOMEM;
+                        }
+                }
+        }
+
+        switch(type) {
+
+        case CREATE_FILE:
+        case TRUNCATE_FILE:
+        case CREATE_DIRECTORY:
+        case TRUNCATE_DIRECTORY:
+        case CREATE_FIFO:
+        case IGNORE_PATH:
+        case REMOVE_PATH:
+        case RECURSIVE_REMOVE_PATH:
+        case RELABEL_PATH:
+        case RECURSIVE_RELABEL_PATH:
+                break;
+
+        case CREATE_SYMLINK:
+                if (!i->argument) {
+                        log_error("[%s:%u] Symlink file requires argument.", fname, line);
+                        r = -EBADMSG;
+                        goto finish;
+                }
+                break;
+
+        case WRITE_FILE:
+                if (!i->argument) {
+                        log_error("[%s:%u] Write file requires argument.", fname, line);
+                        r = -EBADMSG;
+                        goto finish;
+                }
+                break;
+
+        case CREATE_CHAR_DEVICE:
+        case CREATE_BLOCK_DEVICE: {
+                unsigned major, minor;
+
+                if (!i->argument) {
+                        log_error("[%s:%u] Device file requires argument.", fname, line);
+                        r = -EBADMSG;
+                        goto finish;
+                }
+
+                if (sscanf(i->argument, "%u:%u", &major, &minor) != 2) {
+                        log_error("[%s:%u] Can't parse device file major/minor '%s'.", fname, line, i->argument);
+                        r = -EBADMSG;
+                        goto finish;
+                }
+
+                i->major_minor = makedev(major, minor);
+                break;
+        }
+
+        default:
+                log_error("[%s:%u] Unknown file type '%c'.", fname, line, type);
+                r = -EBADMSG;
+                goto finish;
+        }
+
+        i->type = type;
+
+        if (!path_is_absolute(i->path)) {
+                log_error("[%s:%u] Path '%s' not absolute.", fname, line, i->path);
+                r = -EBADMSG;
+                goto finish;
+        }
+
+        path_kill_slashes(i->path);
+
+        if (arg_prefix && !path_startswith(i->path, arg_prefix)) {
+                r = 0;
+                goto finish;
+        }
+
+        if (user && !streq(user, "-")) {
+                const char *u = user;
+
+                r = get_user_creds(&u, &i->uid, NULL, NULL);
+                if (r < 0) {
+                        log_error("[%s:%u] Unknown user '%s'.", fname, line, user);
+                        goto finish;
+                }
+
+                i->uid_set = true;
+        }
+
+        if (group && !streq(group, "-")) {
+                const char *g = group;
+
+                r = get_group_creds(&g, &i->gid);
+                if (r < 0) {
+                        log_error("[%s:%u] Unknown group '%s'.", fname, line, group);
+                        goto finish;
+                }
+
+                i->gid_set = true;
+        }
+
+        if (mode && !streq(mode, "-")) {
+                unsigned m;
+
+                if (sscanf(mode, "%o", &m) != 1) {
+                        log_error("[%s:%u] Invalid mode '%s'.", fname, line, mode);
+                        r = -ENOENT;
+                        goto finish;
+                }
+
+                i->mode = m;
+                i->mode_set = true;
+        } else
+                i->mode =
+                        i->type == CREATE_DIRECTORY ||
+                        i->type == TRUNCATE_DIRECTORY ? 0755 : 0644;
+
+        if (age && !streq(age, "-")) {
+                if (parse_usec(age, &i->age) < 0) {
+                        log_error("[%s:%u] Invalid age '%s'.", fname, line, age);
+                        r = -EBADMSG;
+                        goto finish;
+                }
+
+                i->age_set = true;
+        }
+
+        h = needs_glob(i->type) ? globs : items;
+
+        existing = hashmap_get(h, i->path);
+        if (existing) {
+
+                /* Two identical items are fine */
+                if (!item_equal(existing, i))
+                        log_warning("Two or more conflicting lines for %s configured, ignoring.", i->path);
+
+                r = 0;
+                goto finish;
+        }
+
+        r = hashmap_put(h, i->path, i);
+        if (r < 0) {
+                log_error("Failed to insert item %s: %s", i->path, strerror(-r));
+                goto finish;
+        }
+
+        i = NULL;
+        r = 0;
+
+finish:
+        free(user);
+        free(group);
+        free(mode);
+        free(age);
+
+        if (i)
+                item_free(i);
+
+        return r;
+}
+
+static int help(void) {
+
+        printf("%s [OPTIONS...] [CONFIGURATION FILE...]\n\n"
+               "Creates, deletes and cleans up volatile and temporary files and directories.\n\n"
+               "  -h --help             Show this help\n"
+               "     --create           Create marked files/directories\n"
+               "     --clean            Clean up marked directories\n"
+               "     --remove           Remove marked files/directories\n"
+               "     --prefix=PATH      Only apply rules that apply to paths with the specified prefix\n",
+               program_invocation_short_name);
+
+        return 0;
+}
+
+static int parse_argv(int argc, char *argv[]) {
+
+        enum {
+                ARG_CREATE,
+                ARG_CLEAN,
+                ARG_REMOVE,
+                ARG_PREFIX
+        };
+
+        static const struct option options[] = {
+                { "help",      no_argument,       NULL, 'h'           },
+                { "create",    no_argument,       NULL, ARG_CREATE    },
+                { "clean",     no_argument,       NULL, ARG_CLEAN     },
+                { "remove",    no_argument,       NULL, ARG_REMOVE    },
+                { "prefix",    required_argument, NULL, ARG_PREFIX    },
+                { NULL,        0,                 NULL, 0             }
+        };
+
+        int c;
+
+        assert(argc >= 0);
+        assert(argv);
+
+        while ((c = getopt_long(argc, argv, "h", options, NULL)) >= 0) {
+
+                switch (c) {
+
+                case 'h':
+                        help();
+                        return 0;
+
+                case ARG_CREATE:
+                        arg_create = true;
+                        break;
+
+                case ARG_CLEAN:
+                        arg_clean = true;
+                        break;
+
+                case ARG_REMOVE:
+                        arg_remove = true;
+                        break;
+
+                case ARG_PREFIX:
+                        arg_prefix = optarg;
+                        break;
+
+                case '?':
+                        return -EINVAL;
+
+                default:
+                        log_error("Unknown option code %c", c);
+                        return -EINVAL;
+                }
+        }
+
+        if (!arg_clean && !arg_create && !arg_remove) {
+                log_error("You need to specify at least one of --clean, --create or --remove.");
+                return -EINVAL;
+        }
+
+        return 1;
+}
+
+static int read_config_file(const char *fn, bool ignore_enoent) {
+        FILE *f;
+        unsigned v = 0;
+        int r = 0;
+
+        assert(fn);
+
+        f = fopen(fn, "re");
+        if (!f) {
+
+                if (ignore_enoent && errno == ENOENT)
+                        return 0;
+
+                log_error("Failed to open %s: %m", fn);
+                return -errno;
+        }
+
+        log_debug("apply: %s\n", fn);
+        for (;;) {
+                char line[LINE_MAX], *l;
+                int k;
+
+                if (!(fgets(line, sizeof(line), f)))
+                        break;
+
+                v++;
+
+                l = strstrip(line);
+                if (*l == '#' || *l == 0)
+                        continue;
+
+                if ((k = parse_line(fn, v, l)) < 0)
+                        if (r == 0)
+                                r = k;
+        }
+
+        if (ferror(f)) {
+                log_error("Failed to read from file %s: %m", fn);
+                if (r == 0)
+                        r = -EIO;
+        }
+
+        fclose(f);
+
+        return r;
+}
+
+int main(int argc, char *argv[]) {
+        int r;
+        Item *i;
+        Iterator iterator;
+
+        r = parse_argv(argc, argv);
+        if (r <= 0)
+                return r < 0 ? EXIT_FAILURE : EXIT_SUCCESS;
+
+        log_set_target(LOG_TARGET_AUTO);
+        log_parse_environment();
+        log_open();
+
+        umask(0022);
+
+        label_init();
+
+        items = hashmap_new(string_hash_func, string_compare_func);
+        globs = hashmap_new(string_hash_func, string_compare_func);
+
+        if (!items || !globs) {
+                log_error("Out of memory");
+                r = EXIT_FAILURE;
+                goto finish;
+        }
+
+        r = EXIT_SUCCESS;
+
+        if (optind < argc) {
+                int j;
+
+                for (j = optind; j < argc; j++)
+                        if (read_config_file(argv[j], false) < 0)
+                                r = EXIT_FAILURE;
+
+        } else {
+                char **files, **f;
+
+                r = conf_files_list(&files, ".conf",
+                                    "/etc/tmpfiles.d",
+                                    "/run/tmpfiles.d",
+                                    "/usr/local/lib/tmpfiles.d",
+                                    "/usr/lib/tmpfiles.d",
+                                    NULL);
+                if (r < 0) {
+                        r = EXIT_FAILURE;
+                        log_error("Failed to enumerate tmpfiles.d files: %s", strerror(-r));
+                        goto finish;
+                }
+
+                STRV_FOREACH(f, files) {
+                        if (read_config_file(*f, true) < 0)
+                                r = EXIT_FAILURE;
+                }
+
+                strv_free(files);
+        }
+
+        HASHMAP_FOREACH(i, globs, iterator)
+                process_item(i);
+
+        HASHMAP_FOREACH(i, items, iterator)
+                process_item(i);
+
+finish:
+        while ((i = hashmap_steal_first(items)))
+                item_free(i);
+
+        while ((i = hashmap_steal_first(globs)))
+                item_free(i);
+
+        hashmap_free(items);
+        hashmap_free(globs);
+
+        set_free_free(unix_sockets);
+
+        label_finish();
+
+        return r;
+}
diff --git a/src/tty-ask-password-agent.c b/src/tty-ask-password-agent.c
new file mode 100644 (file)
index 0000000..13481b2
--- /dev/null
@@ -0,0 +1,753 @@
+/*-*- 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 <http://www.gnu.org/licenses/>.
+***/
+
+#include <stdbool.h>
+#include <errno.h>
+#include <string.h>
+#include <sys/socket.h>
+#include <sys/un.h>
+#include <stddef.h>
+#include <sys/poll.h>
+#include <sys/inotify.h>
+#include <unistd.h>
+#include <getopt.h>
+#include <sys/signalfd.h>
+#include <fcntl.h>
+
+#include "util.h"
+#include "conf-parser.h"
+#include "utmp-wtmp.h"
+#include "socket-util.h"
+#include "ask-password-api.h"
+#include "strv.h"
+
+static enum {
+        ACTION_LIST,
+        ACTION_QUERY,
+        ACTION_WATCH,
+        ACTION_WALL
+} arg_action = ACTION_QUERY;
+
+static bool arg_plymouth = false;
+static bool arg_console = false;
+
+static int ask_password_plymouth(
+                const char *message,
+                usec_t until,
+                const char *flag_file,
+                bool accept_cached,
+                char ***_passphrases) {
+
+        int fd = -1, notify = -1;
+        union sockaddr_union sa;
+        char *packet = NULL;
+        ssize_t k;
+        int r, n;
+        struct pollfd pollfd[2];
+        char buffer[LINE_MAX];
+        size_t p = 0;
+        enum {
+                POLL_SOCKET,
+                POLL_INOTIFY
+        };
+
+        assert(_passphrases);
+
+        if (flag_file) {
+                if ((notify = inotify_init1(IN_CLOEXEC|IN_NONBLOCK)) < 0) {
+                        r = -errno;
+                        goto finish;
+                }
+
+                if (inotify_add_watch(notify, flag_file, IN_ATTRIB /* for the link count */) < 0) {
+                        r = -errno;
+                        goto finish;
+                }
+        }
+
+        if ((fd = socket(AF_UNIX, SOCK_STREAM|SOCK_CLOEXEC|SOCK_NONBLOCK, 0)) < 0) {
+                r = -errno;
+                goto finish;
+        }
+
+        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) {
+                log_error("Failed to connect to Plymouth: %m");
+                r = -errno;
+                goto finish;
+        }
+
+        if (accept_cached) {
+                packet = strdup("c");
+                n = 1;
+        } else
+                asprintf(&packet, "*\002%c%s%n", (int) (strlen(message) + 1), message, &n);
+
+        if (!packet) {
+                r = -ENOMEM;
+                goto finish;
+        }
+
+        if ((k = loop_write(fd, packet, n+1, true)) != n+1) {
+                r = k < 0 ? (int) k : -EIO;
+                goto finish;
+        }
+
+        zero(pollfd);
+        pollfd[POLL_SOCKET].fd = fd;
+        pollfd[POLL_SOCKET].events = POLLIN;
+        pollfd[POLL_INOTIFY].fd = notify;
+        pollfd[POLL_INOTIFY].events = POLLIN;
+
+        for (;;) {
+                int sleep_for = -1, j;
+
+                if (until > 0) {
+                        usec_t y;
+
+                        y = now(CLOCK_MONOTONIC);
+
+                        if (y > until) {
+                                r = -ETIME;
+                                goto finish;
+                        }
+
+                        sleep_for = (int) ((until - y) / USEC_PER_MSEC);
+                }
+
+                if (flag_file)
+                        if (access(flag_file, F_OK) < 0) {
+                                r = -errno;
+                                goto finish;
+                        }
+
+                if ((j = poll(pollfd, notify > 0 ? 2 : 1, sleep_for)) < 0) {
+
+                        if (errno == EINTR)
+                                continue;
+
+                        r = -errno;
+                        goto finish;
+                } else if (j == 0) {
+                        r = -ETIME;
+                        goto finish;
+                }
+
+                if (notify > 0 && pollfd[POLL_INOTIFY].revents != 0)
+                        flush_fd(notify);
+
+                if (pollfd[POLL_SOCKET].revents == 0)
+                        continue;
+
+                if ((k = read(fd, buffer + p, sizeof(buffer) - p)) <= 0) {
+                        r = k < 0 ? -errno : -EIO;
+                        goto finish;
+                }
+
+                p += k;
+
+                if (p < 1)
+                        continue;
+
+                if (buffer[0] == 5) {
+
+                        if (accept_cached) {
+                                /* Hmm, first try with cached
+                                 * passwords failed, so let's retry
+                                 * with a normal password request */
+                                free(packet);
+                                packet = NULL;
+
+                                if (asprintf(&packet, "*\002%c%s%n", (int) (strlen(message) + 1), message, &n) < 0) {
+                                        r = -ENOMEM;
+                                        goto finish;
+                                }
+
+                                if ((k = loop_write(fd, packet, n+1, true)) != n+1) {
+                                        r = k < 0 ? (int) k : -EIO;
+                                        goto finish;
+                                }
+
+                                accept_cached = false;
+                                p = 0;
+                                continue;
+                        }
+
+                        /* No password, because UI not shown */
+                        r = -ENOENT;
+                        goto finish;
+
+                } else if (buffer[0] == 2 || buffer[0] == 9) {
+                        uint32_t size;
+                        char **l;
+
+                        /* One ore more answers */
+                        if (p < 5)
+                                continue;
+
+                        memcpy(&size, buffer+1, sizeof(size));
+                        size = le32toh(size);
+                        if (size+5 > sizeof(buffer)) {
+                                r = -EIO;
+                                goto finish;
+                        }
+
+                        if (p-5 < size)
+                                continue;
+
+                        if (!(l = strv_parse_nulstr(buffer + 5, size))) {
+                                r = -ENOMEM;
+                                goto finish;
+                        }
+
+                        *_passphrases = l;
+                        break;
+
+                } else {
+                        /* Unknown packet */
+                        r = -EIO;
+                        goto finish;
+                }
+        }
+
+        r = 0;
+
+finish:
+        if (notify >= 0)
+                close_nointr_nofail(notify);
+
+        if (fd >= 0)
+                close_nointr_nofail(fd);
+
+        free(packet);
+
+        return r;
+}
+
+static int parse_password(const char *filename, char **wall) {
+        char *socket_name = NULL, *message = NULL, *packet = NULL;
+        uint64_t not_after = 0;
+        unsigned pid = 0;
+        int socket_fd = -1;
+        bool accept_cached = false;
+
+        const ConfigTableItem items[] = {
+                { "Ask", "Socket",       config_parse_string,   0, &socket_name   },
+                { "Ask", "NotAfter",     config_parse_uint64,   0, &not_after     },
+                { "Ask", "Message",      config_parse_string,   0, &message       },
+                { "Ask", "PID",          config_parse_unsigned, 0, &pid           },
+                { "Ask", "AcceptCached", config_parse_bool,     0, &accept_cached },
+                { NULL, NULL, NULL, 0, NULL }
+        };
+
+        FILE *f;
+        int r;
+
+        assert(filename);
+
+        f = fopen(filename, "re");
+        if (!f) {
+                if (errno == ENOENT)
+                        return 0;
+
+                log_error("open(%s): %m", filename);
+                return -errno;
+        }
+
+        r = config_parse(filename, f, NULL, config_item_table_lookup, (void*) items, true, NULL);
+        if (r < 0) {
+                log_error("Failed to parse password file %s: %s", filename, strerror(-r));
+                goto finish;
+        }
+
+        if (!socket_name) {
+                log_error("Invalid password file %s", filename);
+                r = -EBADMSG;
+                goto finish;
+        }
+
+        if (not_after > 0) {
+                if (now(CLOCK_MONOTONIC) > not_after) {
+                        r = 0;
+                        goto finish;
+                }
+        }
+
+        if (pid > 0 &&
+            kill(pid, 0) < 0 &&
+            errno == ESRCH) {
+                r = 0;
+                goto finish;
+        }
+
+        if (arg_action == ACTION_LIST)
+                printf("'%s' (PID %u)\n", message, pid);
+        else if (arg_action == ACTION_WALL) {
+                char *_wall;
+
+                if (asprintf(&_wall,
+                             "%s%sPassword entry required for \'%s\' (PID %u).\r\n"
+                             "Please enter password with the systemd-tty-ask-password-agent tool!",
+                             *wall ? *wall : "",
+                             *wall ? "\r\n\r\n" : "",
+                             message,
+                             pid) < 0) {
+                        log_error("Out of memory");
+                        r = -ENOMEM;
+                        goto finish;
+                }
+
+                free(*wall);
+                *wall = _wall;
+        } else {
+                union {
+                        struct sockaddr sa;
+                        struct sockaddr_un un;
+                } sa;
+                size_t packet_length = 0;
+
+                assert(arg_action == ACTION_QUERY ||
+                       arg_action == ACTION_WATCH);
+
+                if (access(socket_name, W_OK) < 0) {
+
+                        if (arg_action == ACTION_QUERY)
+                                log_info("Not querying '%s' (PID %u), lacking privileges.", message, pid);
+
+                        r = 0;
+                        goto finish;
+                }
+
+                if (arg_plymouth) {
+                        char **passwords = NULL;
+
+                        if ((r = ask_password_plymouth(message, not_after, filename, accept_cached, &passwords)) >= 0) {
+                                char **p;
+
+                                packet_length = 1;
+                                STRV_FOREACH(p, passwords)
+                                        packet_length += strlen(*p) + 1;
+
+                                if (!(packet = new(char, packet_length)))
+                                        r = -ENOMEM;
+                                else {
+                                        char *d;
+
+                                        packet[0] = '+';
+                                        d = packet+1;
+
+                                        STRV_FOREACH(p, passwords)
+                                                d = stpcpy(d, *p) + 1;
+                                }
+                        }
+
+                } else {
+                        int tty_fd = -1;
+                        char *password;
+
+                        if (arg_console)
+                                if ((tty_fd = acquire_terminal("/dev/console", false, false, false)) < 0) {
+                                        r = tty_fd;
+                                        goto finish;
+                                }
+
+                        r = ask_password_tty(message, not_after, filename, &password);
+
+                        if (arg_console) {
+                                close_nointr_nofail(tty_fd);
+                                release_terminal();
+                        }
+
+                        if (r >= 0) {
+                                packet_length = 1+strlen(password)+1;
+                                if (!(packet = new(char, packet_length)))
+                                        r = -ENOMEM;
+                                else {
+                                        packet[0] = '+';
+                                        strcpy(packet+1, password);
+                                }
+
+                                free(password);
+                        }
+                }
+
+                if (r == -ETIME || r == -ENOENT) {
+                        /* If the query went away, that's OK */
+                        r = 0;
+                        goto finish;
+                }
+
+                if (r < 0) {
+                        log_error("Failed to query password: %s", strerror(-r));
+                        goto finish;
+                }
+
+                if ((socket_fd = socket(AF_UNIX, SOCK_DGRAM|SOCK_CLOEXEC, 0)) < 0) {
+                        log_error("socket(): %m");
+                        r = -errno;
+                        goto finish;
+                }
+
+                zero(sa);
+                sa.un.sun_family = AF_UNIX;
+                strncpy(sa.un.sun_path, socket_name, sizeof(sa.un.sun_path));
+
+                if (sendto(socket_fd, packet, packet_length, MSG_NOSIGNAL, &sa.sa, offsetof(struct sockaddr_un, sun_path) + strlen(socket_name)) < 0) {
+                        log_error("Failed to send: %m");
+                        r = -errno;
+                        goto finish;
+                }
+        }
+
+finish:
+        fclose(f);
+
+        if (socket_fd >= 0)
+                close_nointr_nofail(socket_fd);
+
+        free(packet);
+        free(socket_name);
+        free(message);
+
+        return r;
+}
+
+static int wall_tty_block(void) {
+        char *p;
+        int fd, r;
+        dev_t devnr;
+
+        r = get_ctty_devnr(0, &devnr);
+        if (r < 0)
+                return -r;
+
+        if (asprintf(&p, "/run/systemd/ask-password-block/%u:%u", major(devnr), minor(devnr)) < 0)
+                return -ENOMEM;
+
+        mkdir_parents(p, 0700);
+        mkfifo(p, 0600);
+
+        fd = open(p, O_RDONLY|O_CLOEXEC|O_NONBLOCK|O_NOCTTY);
+        free(p);
+
+        if (fd < 0)
+                return -errno;
+
+        return fd;
+}
+
+static bool wall_tty_match(const char *path) {
+        int fd, k;
+        char *p;
+        struct stat st;
+
+        if (path_is_absolute(path))
+                k = lstat(path, &st);
+        else {
+                if (asprintf(&p, "/dev/%s", path) < 0)
+                        return true;
+
+                k = lstat(p, &st);
+                free(p);
+        }
+
+        if (k < 0)
+                return true;
+
+        if (!S_ISCHR(st.st_mode))
+                return true;
+
+        /* We use named pipes to ensure that wall messages suggesting
+         * password entry are not printed over password prompts
+         * already shown. We use the fact here that opening a pipe in
+         * non-blocking mode for write-only will succeed only if
+         * there's some writer behind it. Using pipes has the
+         * advantage that the block will automatically go away if the
+         * process dies. */
+
+        if (asprintf(&p, "/run/systemd/ask-password-block/%u:%u", major(st.st_rdev), minor(st.st_rdev)) < 0)
+                return true;
+
+        fd = open(p, O_WRONLY|O_CLOEXEC|O_NONBLOCK|O_NOCTTY);
+        free(p);
+
+        if (fd < 0)
+                return true;
+
+        /* What, we managed to open the pipe? Then this tty is filtered. */
+        close_nointr_nofail(fd);
+        return false;
+}
+
+static int show_passwords(void) {
+        DIR *d;
+        struct dirent *de;
+        int r = 0;
+
+        if (!(d = opendir("/run/systemd/ask-password"))) {
+                if (errno == ENOENT)
+                        return 0;
+
+                log_error("opendir(): %m");
+                return -errno;
+        }
+
+        while ((de = readdir(d))) {
+                char *p;
+                int q;
+                char *wall;
+
+                /* We only support /dev on tmpfs, hence we can rely on
+                 * d_type to be reliable */
+
+                if (de->d_type != DT_REG)
+                        continue;
+
+                if (ignore_file(de->d_name))
+                        continue;
+
+                if (!startswith(de->d_name, "ask."))
+                        continue;
+
+                if (!(p = strappend("/run/systemd/ask-password/", de->d_name))) {
+                        log_error("Out of memory");
+                        r = -ENOMEM;
+                        goto finish;
+                }
+
+                wall = NULL;
+                if ((q = parse_password(p, &wall)) < 0)
+                        r = q;
+
+                free(p);
+
+                if (wall) {
+                        utmp_wall(wall, wall_tty_match);
+                        free(wall);
+                }
+        }
+
+finish:
+        if (d)
+                closedir(d);
+
+        return r;
+}
+
+static int watch_passwords(void) {
+        enum {
+                FD_INOTIFY,
+                FD_SIGNAL,
+                _FD_MAX
+        };
+
+        int notify = -1, signal_fd = -1, tty_block_fd = -1;
+        struct pollfd pollfd[_FD_MAX];
+        sigset_t mask;
+        int r;
+
+        tty_block_fd = wall_tty_block();
+
+        mkdir_p("/run/systemd/ask-password", 0755);
+
+        if ((notify = inotify_init1(IN_CLOEXEC)) < 0) {
+                r = -errno;
+                goto finish;
+        }
+
+        if (inotify_add_watch(notify, "/run/systemd/ask-password", IN_CLOSE_WRITE|IN_MOVED_TO) < 0) {
+                r = -errno;
+                goto finish;
+        }
+
+        assert_se(sigemptyset(&mask) == 0);
+        sigset_add_many(&mask, SIGINT, SIGTERM, -1);
+        assert_se(sigprocmask(SIG_SETMASK, &mask, NULL) == 0);
+
+        if ((signal_fd = signalfd(-1, &mask, SFD_NONBLOCK|SFD_CLOEXEC)) < 0) {
+                log_error("signalfd(): %m");
+                r = -errno;
+                goto finish;
+        }
+
+        zero(pollfd);
+        pollfd[FD_INOTIFY].fd = notify;
+        pollfd[FD_INOTIFY].events = POLLIN;
+        pollfd[FD_SIGNAL].fd = signal_fd;
+        pollfd[FD_SIGNAL].events = POLLIN;
+
+        for (;;) {
+                if ((r = show_passwords()) < 0)
+                        log_error("Failed to show password: %s", strerror(-r));
+
+                if (poll(pollfd, _FD_MAX, -1) < 0) {
+
+                        if (errno == EINTR)
+                                continue;
+
+                        r = -errno;
+                        goto finish;
+                }
+
+                if (pollfd[FD_INOTIFY].revents != 0)
+                        flush_fd(notify);
+
+                if (pollfd[FD_SIGNAL].revents != 0)
+                        break;
+        }
+
+        r = 0;
+
+finish:
+        if (notify >= 0)
+                close_nointr_nofail(notify);
+
+        if (signal_fd >= 0)
+                close_nointr_nofail(signal_fd);
+
+        if (tty_block_fd >= 0)
+                close_nointr_nofail(tty_block_fd);
+
+        return r;
+}
+
+static int help(void) {
+
+        printf("%s [OPTIONS...]\n\n"
+               "Process system password requests.\n\n"
+               "  -h --help     Show this help\n"
+               "     --list     Show pending password requests\n"
+               "     --query    Process pending password requests\n"
+               "     --watch    Continuously process password requests\n"
+               "     --wall     Continuously forward password requests to wall\n"
+               "     --plymouth Ask question with Plymouth instead of on TTY\n"
+               "     --console  Ask question on /dev/console instead of current TTY\n",
+               program_invocation_short_name);
+
+        return 0;
+}
+
+static int parse_argv(int argc, char *argv[]) {
+
+        enum {
+                ARG_LIST = 0x100,
+                ARG_QUERY,
+                ARG_WATCH,
+                ARG_WALL,
+                ARG_PLYMOUTH,
+                ARG_CONSOLE
+        };
+
+        static const struct option options[] = {
+                { "help",     no_argument, NULL, 'h'          },
+                { "list",     no_argument, NULL, ARG_LIST     },
+                { "query",    no_argument, NULL, ARG_QUERY    },
+                { "watch",    no_argument, NULL, ARG_WATCH    },
+                { "wall",     no_argument, NULL, ARG_WALL     },
+                { "plymouth", no_argument, NULL, ARG_PLYMOUTH },
+                { "console",  no_argument, NULL, ARG_CONSOLE  },
+                { NULL,    0,           NULL, 0               }
+        };
+
+        int c;
+
+        assert(argc >= 0);
+        assert(argv);
+
+        while ((c = getopt_long(argc, argv, "h", options, NULL)) >= 0) {
+
+                switch (c) {
+
+                case 'h':
+                        help();
+                        return 0;
+
+                case ARG_LIST:
+                        arg_action = ACTION_LIST;
+                        break;
+
+                case ARG_QUERY:
+                        arg_action = ACTION_QUERY;
+                        break;
+
+                case ARG_WATCH:
+                        arg_action = ACTION_WATCH;
+                        break;
+
+                case ARG_WALL:
+                        arg_action = ACTION_WALL;
+                        break;
+
+                case ARG_PLYMOUTH:
+                        arg_plymouth = true;
+                        break;
+
+                case ARG_CONSOLE:
+                        arg_console = true;
+                        break;
+
+                case '?':
+                        return -EINVAL;
+
+                default:
+                        log_error("Unknown option code %c", c);
+                        return -EINVAL;
+                }
+        }
+
+        if (optind != argc) {
+                help();
+                return -EINVAL;
+        }
+
+        return 1;
+}
+
+int main(int argc, char *argv[]) {
+        int r;
+
+        log_parse_environment();
+        log_open();
+
+        umask(0022);
+
+        if ((r = parse_argv(argc, argv)) <= 0)
+                goto finish;
+
+        if (arg_console) {
+                setsid();
+                release_terminal();
+        }
+
+        if (arg_action == ACTION_WATCH ||
+            arg_action == ACTION_WALL)
+                r = watch_passwords();
+        else
+                r = show_passwords();
+
+        if (r < 0)
+                log_error("Error: %s", strerror(-r));
+
+finish:
+        return r < 0 ? EXIT_FAILURE : EXIT_SUCCESS;
+}
diff --git a/src/udev/.gitignore b/src/udev/.gitignore
new file mode 100644 (file)
index 0000000..fa3500b
--- /dev/null
@@ -0,0 +1,40 @@
+*~
+*.o
+*.a
+*.lo
+*.la
+.libs
+.deps
+.dirstamp
+Makefile
+Makefile.in
+/aclocal.m4
+/autom4te.cache
+/config.h
+/config.h.in
+/config.log
+/config.status
+/config.guess
+/config.sub
+/libtool
+/ltmain.sh
+/install-sh
+/missing
+/configure
+/stamp-h1
+/depcomp
+/gtk-doc.make
+/build-aux
+/udev-test-install
+/udevd
+/udevadm
+/test-udev
+/test-libudev
+/accelerometer
+/ata_id
+/cdrom_id
+/collect
+/mtd_probe
+/v4l_id
+/keymap
+/scsi_id
diff --git a/src/udev/.vimrc b/src/udev/.vimrc
new file mode 100644 (file)
index 0000000..366fbdc
--- /dev/null
@@ -0,0 +1,4 @@
+" 'set exrc' in ~/.vimrc will read .vimrc from the current directory
+set tabstop=8
+set shiftwidth=8
+set expandtab
similarity index 100%
rename from COPYING
rename to src/udev/COPYING
similarity index 100%
rename from ChangeLog
rename to src/udev/ChangeLog
similarity index 100%
rename from INSTALL
rename to src/udev/INSTALL
diff --git a/src/udev/Makefile.am b/src/udev/Makefile.am
new file mode 100644 (file)
index 0000000..1c7f86b
--- /dev/null
@@ -0,0 +1,712 @@
+# Copyright (C) 2008-2012 Kay Sievers <kay.sievers@vrfy.org>
+# Copyright (C) 2009 Diego Elio 'Flameeyes' Pettenò <flameeyes@gmail.com>
+
+SUBDIRS = .
+
+ACLOCAL_AMFLAGS = -I m4 ${ACLOCAL_FLAGS}
+
+AM_MAKEFLAGS = --no-print-directory
+
+LIBUDEV_CURRENT=13
+LIBUDEV_REVISION=2
+LIBUDEV_AGE=13
+
+LIBGUDEV_CURRENT=1
+LIBGUDEV_REVISION=1
+LIBGUDEV_AGE=1
+
+AM_CPPFLAGS = \
+       -include $(top_builddir)/config.h \
+       -I$(top_srcdir)/src \
+       -DSYSCONFDIR=\""$(sysconfdir)"\" \
+       -DPKGLIBEXECDIR=\""$(libexecdir)/udev"\"
+
+AM_CFLAGS = \
+       ${my_CFLAGS} \
+       -fvisibility=hidden \
+       -ffunction-sections \
+       -fdata-sections
+
+AM_LDFLAGS = \
+       -Wl,--gc-sections \
+       -Wl,--as-needed
+
+DISTCHECK_CONFIGURE_FLAGS = \
+       --enable-debug \
+       --enable-rule_generator \
+       --enable-floppy \
+       --with-selinux \
+       --enable-gtk-doc \
+       --with-systemdsystemunitdir=$$dc_install_base/$(systemdsystemunitdir)
+
+BUILT_SOURCES =
+EXTRA_DIST =
+CLEANFILES =
+INSTALL_EXEC_HOOKS =
+INSTALL_DATA_HOOKS =
+UNINSTALL_EXEC_HOOKS =
+DISTCHECK_HOOKS =
+DISTCLEAN_LOCAL_HOOKS =
+
+udevhomedir = $(libexecdir)/udev
+udevhome_SCRIPTS =
+dist_udevhome_SCRIPTS =
+dist_udevhome_DATA =
+dist_man_MANS =
+
+SED_PROCESS = \
+       $(AM_V_GEN)$(MKDIR_P) $(dir $@) && $(SED) \
+       -e 's,@VERSION\@,$(VERSION),g' \
+       -e 's,@prefix\@,$(prefix),g' \
+       -e 's,@rootprefix\@,$(rootprefix),g' \
+       -e 's,@exec_prefix\@,$(exec_prefix),g' \
+       -e 's,@libdir\@,$(libdir),g' \
+       -e 's,@includedir\@,$(includedir),g' \
+       -e 's,@bindir\@,$(bindir),g' \
+       -e 's,@pkglibexecdir\@,$(libexecdir)/udev,g' \
+       < $< > $@ || rm $@
+
+%.pc: %.pc.in Makefile
+       $(SED_PROCESS)
+
+%.rules: %.rules.in Makefile
+       $(SED_PROCESS)
+
+%.service: %.service.in Makefile
+       $(SED_PROCESS)
+
+%.sh: %.sh.in Makefile
+       $(SED_PROCESS)
+       $(AM_V_GEN)chmod +x $@
+
+%.pl: %.pl.in Makefile
+       $(SED_PROCESS)
+       $(AM_V_GEN)chmod +x $@
+
+# ------------------------------------------------------------------------------
+SUBDIRS += src/docs
+
+include_HEADERS = src/libudev.h
+lib_LTLIBRARIES = libudev.la
+noinst_LTLIBRARIES = libudev-private.la
+
+libudev_la_SOURCES =\
+       src/libudev-private.h \
+       src/libudev.c \
+       src/libudev-list.c \
+       src/libudev-util.c \
+       src/libudev-device.c \
+       src/libudev-enumerate.c \
+       src/libudev-monitor.c \
+       src/libudev-queue.c
+
+libudev_la_LDFLAGS = \
+       $(AM_LDFLAGS) \
+       -version-info $(LIBUDEV_CURRENT):$(LIBUDEV_REVISION):$(LIBUDEV_AGE)
+
+libudev_private_la_SOURCES =\
+       $(libudev_la_SOURCES) \
+       src/libudev-util-private.c \
+       src/libudev-device-private.c \
+       src/libudev-queue-private.c
+
+if WITH_SELINUX
+libudev_private_la_SOURCES += src/libudev-selinux-private.c
+libudev_private_la_LIBADD = $(SELINUX_LIBS)
+endif
+
+pkgconfigdir = $(libdir)/pkgconfig
+pkgconfig_DATA = src/libudev.pc
+EXTRA_DIST += src/libudev.pc.in
+CLEANFILES += src/libudev.pc
+
+EXTRA_DIST += src/COPYING
+# move lib from $(libdir) to $(rootlib_execdir) and update devel link, if needed
+libudev-install-move-hook:
+       if test "$(libdir)" != "$(rootlib_execdir)"; then \
+               mkdir -p $(DESTDIR)$(rootlib_execdir) && \
+               so_img_name=$$(readlink $(DESTDIR)$(libdir)/libudev.so) && \
+               so_img_rel_target_prefix=$$(echo $(libdir) | sed 's,\(^/\|\)[^/][^/]*,..,g') && \
+               ln -sf $$so_img_rel_target_prefix$(rootlib_execdir)/$$so_img_name $(DESTDIR)$(libdir)/libudev.so && \
+               mv $(DESTDIR)$(libdir)/libudev.so.* $(DESTDIR)$(rootlib_execdir); \
+       fi
+
+libudev-uninstall-move-hook:
+       rm -f $(DESTDIR)$(rootlib_execdir)/libudev.so*
+
+INSTALL_EXEC_HOOKS += libudev-install-move-hook
+UNINSTALL_EXEC_HOOKS += libudev-uninstall-move-hook
+
+# ------------------------------------------------------------------------------
+udev-confdirs:
+       -mkdir -p $(DESTDIR)$(sysconfdir)/udev/rules.d
+       -mkdir -p $(DESTDIR)$(libexecdir)/udev/devices
+
+INSTALL_DATA_HOOKS += udev-confdirs
+
+udevrulesdir = $(libexecdir)/udev/rules.d
+dist_udevrules_DATA = \
+       rules/42-usb-hid-pm.rules \
+       rules/50-udev-default.rules \
+       rules/60-persistent-storage-tape.rules \
+       rules/60-persistent-serial.rules \
+       rules/60-persistent-input.rules \
+       rules/60-persistent-alsa.rules \
+       rules/60-persistent-storage.rules \
+       rules/75-net-description.rules \
+       rules/75-tty-description.rules \
+       rules/78-sound-card.rules \
+       rules/80-drivers.rules \
+       rules/95-udev-late.rules
+
+udevconfdir = $(sysconfdir)/udev
+dist_udevconf_DATA = src/udev.conf
+
+sharepkgconfigdir = $(datadir)/pkgconfig
+sharepkgconfig_DATA = src/udev.pc
+EXTRA_DIST += src/udev.pc.in
+CLEANFILES += src/udev.pc
+
+if WITH_SYSTEMD
+dist_systemdsystemunit_DATA = \
+       src/udev-control.socket \
+       src/udev-kernel.socket
+
+systemdsystemunit_DATA = \
+       src/udev.service \
+       src/udev-trigger.service \
+       src/udev-settle.service
+
+EXTRA_DIST += \
+       src/udev.service.in \
+       src/udev-trigger.service.in \
+       src/udev-settle.service.in
+
+CLEANFILES += \
+       src/udev.service \
+       src/udev-trigger.service \
+       src/udev-settle.service
+
+systemd-install-hook:
+       mkdir -p $(DESTDIR)$(systemdsystemunitdir)/sockets.target.wants
+       ln -sf ../udev-control.socket $(DESTDIR)$(systemdsystemunitdir)/sockets.target.wants/udev-control.socket
+       ln -sf ../udev-kernel.socket $(DESTDIR)$(systemdsystemunitdir)/sockets.target.wants/udev-kernel.socket
+       mkdir -p $(DESTDIR)$(systemdsystemunitdir)/basic.target.wants
+       ln -sf ../udev.service $(DESTDIR)$(systemdsystemunitdir)/basic.target.wants/udev.service
+       ln -sf ../udev-trigger.service $(DESTDIR)$(systemdsystemunitdir)/basic.target.wants/udev-trigger.service
+
+INSTALL_DATA_HOOKS += systemd-install-hook
+endif
+
+bin_PROGRAMS = \
+       udevadm
+
+pkglibexec_PROGRAMS = \
+       udevd
+
+udev_common_sources = \
+       src/udev.h \
+       src/udev-event.c \
+       src/udev-watch.c \
+       src/udev-node.c \
+       src/udev-rules.c \
+       src/udev-ctrl.c \
+       src/udev-builtin.c \
+       src/udev-builtin-blkid.c \
+       src/udev-builtin-firmware.c \
+       src/udev-builtin-hwdb.c \
+       src/udev-builtin-input_id.c \
+       src/udev-builtin-kmod.c \
+       src/udev-builtin-path_id.c \
+       src/udev-builtin-usb_id.c
+
+udev_common_CFLAGS = \
+       $(BLKID_CFLAGS) \
+       $(KMOD_CFLAGS)
+
+udev_common_LDADD = \
+       libudev-private.la \
+       $(BLKID_LIBS) \
+       $(KMOD_LIBS)
+
+udev_common_CPPFLAGS = \
+       $(AM_CPPFLAGS) \
+       -DFIRMWARE_PATH="$(FIRMWARE_PATH)" \
+       -DUSB_DATABASE=\"$(USB_DATABASE)\" -DPCI_DATABASE=\"$(PCI_DATABASE)\"
+
+udevd_SOURCES = \
+       $(udev_common_sources) \
+       src/udevd.c \
+       src/sd-daemon.h \
+       src/sd-daemon.c
+udevd_CFLAGS = $(udev_common_CFLAGS)
+udevd_LDADD = $(udev_common_LDADD)
+udevd_CPPFLAGS = $(udev_common_CPPFLAGS)
+
+udevadm_SOURCES = \
+       $(udev_common_sources) \
+       src/udevadm.c \
+       src/udevadm-info.c \
+       src/udevadm-control.c \
+       src/udevadm-monitor.c \
+       src/udevadm-settle.c \
+       src/udevadm-trigger.c \
+       src/udevadm-test.c \
+       src/udevadm-test-builtin.c
+udevadm_CFLAGS = $(udev_common_CFLAGS)
+udevadm_LDADD = $(udev_common_LDADD)
+udevadm_CPPFLAGS = $(udev_common_CPPFLAGS)
+
+# ------------------------------------------------------------------------------
+if ENABLE_MANPAGES
+dist_man_MANS += \
+       src/udev.7 \
+       src/udevadm.8 \
+       src/udevd.8
+endif
+
+EXTRA_DIST += \
+       src/udev.xml \
+       src/udevadm.xml \
+       src/udevd.xml
+
+if HAVE_XSLTPROC
+dist_noinst_DATA = \
+       src/udev.html \
+       src/udevadm.html \
+       src/udevd.html
+
+src/%.7 src/%.8 : src/%.xml
+       $(AM_V_GEN)$(XSLTPROC) -o $@ -nonet http://docbook.sourceforge.net/release/xsl/current/manpages/docbook.xsl $<
+
+src/%.html : src/%.xml
+       $(AM_V_GEN)$(XSLTPROC) -o $@ -nonet http://docbook.sourceforge.net/release/xsl/current/xhtml-1_1/docbook.xsl $<
+endif
+
+# ------------------------------------------------------------------------------
+TESTS = \
+       test/udev-test.pl \
+       test/rules-test.sh
+
+check_PROGRAMS = \
+       test-libudev \
+       test-udev
+
+test_libudev_SOURCES = src/test-libudev.c
+test_libudev_LDADD = libudev.la
+
+test_udev_SOURCES = \
+       $(udev_common_sources) \
+       src/test-udev.c
+test_udev_CFLAGS = $(udev_common_CFLAGS)
+test_udev_LDADD = $(udev_common_LDADD)
+test_udev_CPPFLAGS = $(udev_common_CPPFLAGS)
+test_udev_DEPENDENCIES = test/sys
+
+# packed sysfs test tree
+test/sys:
+       $(AM_V_GEN)mkdir -p test && tar -C test/ -xJf $(top_srcdir)/test/sys.tar.xz
+
+test-sys-distclean:
+       -rm -rf test/sys
+DISTCLEAN_LOCAL_HOOKS += test-sys-distclean
+
+EXTRA_DIST += test/sys.tar.xz
+
+# ------------------------------------------------------------------------------
+ata_id_SOURCES = src/ata_id/ata_id.c
+ata_id_LDADD = libudev-private.la
+pkglibexec_PROGRAMS += ata_id
+
+# ------------------------------------------------------------------------------
+cdrom_id_SOURCES = src/cdrom_id/cdrom_id.c
+cdrom_id_LDADD = libudev-private.la
+pkglibexec_PROGRAMS += cdrom_id
+dist_udevrules_DATA += src/cdrom_id/60-cdrom_id.rules
+
+# ------------------------------------------------------------------------------
+collect_SOURCES = src/collect/collect.c
+collect_LDADD = libudev-private.la
+pkglibexec_PROGRAMS += collect
+
+# ------------------------------------------------------------------------------
+scsi_id_SOURCES =\
+       src/scsi_id/scsi_id.c \
+       src/scsi_id/scsi_serial.c \
+       src/scsi_id/scsi.h \
+       src/scsi_id/scsi_id.h
+scsi_id_LDADD = libudev-private.la
+pkglibexec_PROGRAMS += scsi_id
+dist_man_MANS += src/scsi_id/scsi_id.8
+EXTRA_DIST += src/scsi_id/README
+
+# ------------------------------------------------------------------------------
+v4l_id_SOURCES = src/v4l_id/v4l_id.c
+v4l_id_LDADD = libudev-private.la
+pkglibexec_PROGRAMS += v4l_id
+dist_udevrules_DATA += src/v4l_id/60-persistent-v4l.rules
+
+# ------------------------------------------------------------------------------
+accelerometer_SOURCES = src/accelerometer/accelerometer.c
+accelerometer_LDADD = libudev-private.la -lm
+pkglibexec_PROGRAMS += accelerometer
+dist_udevrules_DATA += src/accelerometer/61-accelerometer.rules
+
+# ------------------------------------------------------------------------------
+if ENABLE_GUDEV
+SUBDIRS += src/gudev/docs
+
+libgudev_includedir=$(includedir)/gudev-1.0/gudev
+libgudev_include_HEADERS = \
+       src/gudev/gudev.h \
+       src/gudev/gudevenums.h \
+       src/gudev/gudevenumtypes.h \
+       src/gudev/gudevtypes.h \
+       src/gudev/gudevclient.h \
+       src/gudev/gudevdevice.h \
+       src/gudev/gudevenumerator.h
+
+lib_LTLIBRARIES += libgudev-1.0.la
+
+pkgconfig_DATA += src/gudev/gudev-1.0.pc
+EXTRA_DIST += src/gudev/gudev-1.0.pc.in
+CLEANFILES += src/gudev/gudev-1.0.pc
+
+libgudev_1_0_la_SOURCES = \
+       src/gudev/gudevenums.h \
+       src/gudev/gudevenumtypes.h \
+       src/gudev/gudevenumtypes.h\
+       src/gudev/gudevtypes.h \
+       src/gudev/gudevclient.h \
+       src/gudev/gudevclient.c \
+       src/gudev/gudevdevice.h \
+       src/gudev/gudevdevice.c \
+       src/gudev/gudevenumerator.h \
+       src/gudev/gudevenumerator.c \
+       src/gudev/gudevprivate.h
+
+nodist_libgudev_1_0_la_SOURCES = \
+       src/gudev/gudevmarshal.h \
+       src/gudev/gudevmarshal.c \
+       src/gudev/gudevenumtypes.h \
+       src/gudev/gudevenumtypes.c
+BUILT_SOURCES += $(nodist_libgudev_1_0_la_SOURCES)
+
+libgudev_1_0_la_CPPFLAGS = \
+       $(AM_CPPFLAGS) \
+       -I$(top_builddir)/src\
+       -I$(top_srcdir)/src\
+       -I$(top_builddir)/src/gudev \
+       -I$(top_srcdir)/src/gudev \
+       -D_POSIX_PTHREAD_SEMANTICS -D_REENTRANT \
+       -D_GUDEV_COMPILATION \
+       -DG_LOG_DOMAIN=\"GUdev\"
+
+libgudev_1_0_la_CFLAGS = \
+       -fvisibility=default \
+       $(GLIB_CFLAGS)
+
+libgudev_1_0_la_LIBADD = libudev.la $(GLIB_LIBS)
+
+libgudev_1_0_la_LDFLAGS = \
+       -version-info $(LIBGUDEV_CURRENT):$(LIBGUDEV_REVISION):$(LIBGUDEV_AGE) \
+       -export-dynamic -no-undefined \
+       -export-symbols-regex '^g_udev_.*'
+
+EXTRA_DIST += \
+       src/gudev/COPYING \
+       src/gudev/gudevmarshal.list \
+       src/gudev/gudevenumtypes.h.template \
+       src/gudev/gudevenumtypes.c.template \
+       src/gudev/gjs-example.js \
+       src/gudev/seed-example-enum.js \
+       src/gudev/seed-example.js
+
+src/gudev/gudevmarshal.h: src/gudev/gudevmarshal.list
+       $(AM_V_GEN)glib-genmarshal $< --prefix=g_udev_marshal --header > $@
+
+src/gudev/gudevmarshal.c: src/gudev/gudevmarshal.list
+       $(AM_V_GEN)echo "#include \"gudevmarshal.h\"" > $@ && \
+       glib-genmarshal $< --prefix=g_udev_marshal --body >> $@
+
+src/gudev/gudevenumtypes.h: src/gudev/gudevenumtypes.h.template src/gudev/gudevenums.h
+       $(AM_V_GEN)glib-mkenums --template $^ > \
+           $@.tmp && mv $@.tmp $@
+
+src/gudev/gudevenumtypes.c: src/gudev/gudevenumtypes.c.template src/gudev/gudevenums.h
+       $(AM_V_GEN)glib-mkenums --template $^ > \
+           $@.tmp && mv $@.tmp $@
+
+if ENABLE_INTROSPECTION
+src/gudev/GUdev-1.0.gir: libgudev-1.0.la $(G_IR_SCANNER)
+       $(AM_V_GEN)$(G_IR_SCANNER) -v \
+               --warn-all \
+               --namespace GUdev \
+               --nsversion=1.0 \
+               --include=GObject-2.0 \
+               --library=gudev-1.0 \
+               --library-path=$(top_builddir)/src \
+               --library-path=$(top_builddir)/src/gudev \
+               --output $@ \
+               --pkg=glib-2.0 \
+               --pkg=gobject-2.0 \
+               --pkg-export=gudev-1.0 \
+               --c-include=gudev/gudev.h \
+               -I$(top_srcdir)/src/\
+               -I$(top_builddir)/src/\
+               -D_GUDEV_COMPILATION \
+               -D_GUDEV_WORK_AROUND_DEV_T_BUG \
+               $(top_srcdir)/src/gudev/gudev.h \
+               $(top_srcdir)/src/gudev/gudevtypes.h \
+               $(top_srcdir)/src/gudev/gudevenums.h \
+               $(or $(wildcard $(top_builddir)/src/gudev/gudevenumtypes.h),$(top_srcdir)/src/gudev/gudevenumtypes.h) \
+               $(top_srcdir)/src/gudev/gudevclient.h \
+               $(top_srcdir)/src/gudev/gudevdevice.h \
+               $(top_srcdir)/src/gudev/gudevenumerator.h \
+               $(top_srcdir)/src/gudev/gudevclient.c \
+               $(top_srcdir)/src/gudev/gudevdevice.c \
+               $(top_srcdir)/src/gudev/gudevenumerator.c
+
+src/gudev/GUdev-1.0.typelib: src/gudev/GUdev-1.0.gir $(G_IR_COMPILER)
+       $(AM_V_GEN)g-ir-compiler $< -o $@
+
+girdir = $(GIRDIR)
+gir_DATA = src/gudev/GUdev-1.0.gir
+
+typelibsdir = $(GIRTYPELIBDIR)
+typelibs_DATA = src/gudev/GUdev-1.0.typelib
+
+CLEANFILES += $(gir_DATA) $(typelibs_DATA)
+endif # ENABLE_INTROSPECTION
+
+# move lib from $(libdir) to $(rootlib_execdir) and update devel link, if needed
+libgudev-install-move-hook:
+       if test "$(libdir)" != "$(rootlib_execdir)"; then \
+               mkdir -p $(DESTDIR)$(rootlib_execdir) && \
+               so_img_name=$$(readlink $(DESTDIR)$(libdir)/libgudev-1.0.so) && \
+               so_img_rel_target_prefix=$$(echo $(libdir) | sed 's,\(^/\|\)[^/][^/]*,..,g') && \
+               ln -sf $$so_img_rel_target_prefix$(rootlib_execdir)/$$so_img_name $(DESTDIR)$(libdir)/libgudev-1.0.so && \
+               mv $(DESTDIR)$(libdir)/libgudev-1.0.so.* $(DESTDIR)$(rootlib_execdir); \
+       fi
+
+libgudev-uninstall-move-hook:
+       rm -f $(DESTDIR)$(rootlib_execdir)/libgudev-1.0.so*
+
+INSTALL_EXEC_HOOKS += libgudev-install-move-hook
+UNINSTALL_EXEC_HOOKS += libgudev-uninstall-move-hook
+endif
+
+# ------------------------------------------------------------------------------
+if ENABLE_KEYMAP
+keymap_SOURCES = src/keymap/keymap.c
+keymap_CPPFLAGS = $(AM_CPPFLAGS) -I src/keymap
+nodist_keymap_SOURCES = \
+       src/keymap/keys-from-name.h \
+       src/keymap/keys-to-name.h
+BUILT_SOURCES += $(nodist_keymap_SOURCES)
+
+pkglibexec_PROGRAMS += keymap
+dist_doc_DATA = src/keymap/README.keymap.txt
+
+dist_udevrules_DATA += \
+       src/keymap/95-keymap.rules \
+       src/keymap/95-keyboard-force-release.rules
+
+dist_udevhome_SCRIPTS += src/keymap/findkeyboards
+udevhome_SCRIPTS += src/keymap/keyboard-force-release.sh
+
+EXTRA_DIST += \
+       src/keymap/check-keymaps.sh \
+       src/keymap/keyboard-force-release.sh.in
+
+CLEANFILES += \
+       src/keymap/keys.txt \
+       src/keymap/keys-from-name.gperf \
+       src/keymap/keyboard-force-release.sh
+
+udevkeymapdir = $(libexecdir)/udev/keymaps
+dist_udevkeymap_DATA = \
+       src/keymap/keymaps/acer \
+       src/keymap/keymaps/acer-aspire_5720 \
+       src/keymap/keymaps/acer-aspire_8930 \
+       src/keymap/keymaps/acer-aspire_5920g \
+       src/keymap/keymaps/acer-aspire_6920 \
+       src/keymap/keymaps/acer-travelmate_c300 \
+       src/keymap/keymaps/asus \
+       src/keymap/keymaps/compaq-e_evo \
+       src/keymap/keymaps/dell \
+       src/keymap/keymaps/dell-latitude-xt2 \
+       src/keymap/keymaps/everex-xt5000 \
+       src/keymap/keymaps/fujitsu-amilo_li_2732 \
+       src/keymap/keymaps/fujitsu-amilo_pa_2548 \
+       src/keymap/keymaps/fujitsu-amilo_pro_edition_v3505 \
+       src/keymap/keymaps/fujitsu-amilo_pro_v3205 \
+       src/keymap/keymaps/fujitsu-amilo_si_1520 \
+       src/keymap/keymaps/fujitsu-esprimo_mobile_v5 \
+       src/keymap/keymaps/fujitsu-esprimo_mobile_v6 \
+       src/keymap/keymaps/genius-slimstar-320 \
+       src/keymap/keymaps/hewlett-packard \
+       src/keymap/keymaps/hewlett-packard-2510p_2530p \
+       src/keymap/keymaps/hewlett-packard-compaq_elitebook \
+       src/keymap/keymaps/hewlett-packard-pavilion \
+       src/keymap/keymaps/hewlett-packard-presario-2100 \
+       src/keymap/keymaps/hewlett-packard-tablet \
+       src/keymap/keymaps/hewlett-packard-tx2 \
+       src/keymap/keymaps/ibm-thinkpad-usb-keyboard-trackpoint \
+       src/keymap/keymaps/inventec-symphony_6.0_7.0 \
+       src/keymap/keymaps/lenovo-3000 \
+       src/keymap/keymaps/lenovo-ideapad \
+       src/keymap/keymaps/lenovo-thinkpad-usb-keyboard-trackpoint \
+       src/keymap/keymaps/lenovo-thinkpad_x6_tablet \
+       src/keymap/keymaps/lenovo-thinkpad_x200_tablet \
+       src/keymap/keymaps/lg-x110 \
+       src/keymap/keymaps/logitech-wave \
+       src/keymap/keymaps/logitech-wave-cordless \
+       src/keymap/keymaps/logitech-wave-pro-cordless \
+       src/keymap/keymaps/maxdata-pro_7000 \
+       src/keymap/keymaps/medion-fid2060 \
+       src/keymap/keymaps/medionnb-a555 \
+       src/keymap/keymaps/micro-star \
+       src/keymap/keymaps/module-asus-w3j \
+       src/keymap/keymaps/module-ibm \
+       src/keymap/keymaps/module-lenovo \
+       src/keymap/keymaps/module-sony \
+       src/keymap/keymaps/module-sony-old \
+       src/keymap/keymaps/module-sony-vgn \
+       src/keymap/keymaps/olpc-xo \
+       src/keymap/keymaps/onkyo \
+       src/keymap/keymaps/oqo-model2 \
+       src/keymap/keymaps/samsung-other \
+       src/keymap/keymaps/samsung-90x3a \
+       src/keymap/keymaps/samsung-sq1us \
+       src/keymap/keymaps/samsung-sx20s \
+       src/keymap/keymaps/toshiba-satellite_a100 \
+       src/keymap/keymaps/toshiba-satellite_a110 \
+       src/keymap/keymaps/toshiba-satellite_m30x \
+       src/keymap/keymaps/zepto-znote
+
+udevkeymapforcereldir = $(libexecdir)/udev/keymaps/force-release
+dist_udevkeymapforcerel_DATA = \
+       src/keymap/force-release-maps/dell-touchpad \
+       src/keymap/force-release-maps/hp-other \
+       src/keymap/force-release-maps/samsung-other \
+       src/keymap/force-release-maps/samsung-90x3a \
+       src/keymap/force-release-maps/common-volume-keys
+
+src/keymap/keys.txt: $(INCLUDE_PREFIX)/linux/input.h
+       $(AM_V_at)mkdir -p src/keymap
+       $(AM_V_GEN)$(AWK) '/^#define.*KEY_[^ ]+[ \t]+[0-9]/ { if ($$2 != "KEY_MAX") { print $$2 } }' < $< | sed 's/^KEY_COFFEE$$/KEY_SCREENLOCK/' > $@
+
+src/keymap/keys-from-name.gperf: src/keymap/keys.txt
+       $(AM_V_GEN)$(AWK) 'BEGIN{ print "struct key { const char* name; unsigned short id; };"; print "%null-strings"; print "%%";} { print $$1 ", " $$1 }' < $< > $@
+
+src/keymap/keys-from-name.h: src/keymap/keys-from-name.gperf Makefile
+       $(AM_V_GEN)$(GPERF) -L ANSI-C -t --ignore-case -N lookup_key -H hash_key_name -p -C < $< > $@
+
+src/keymap/keys-to-name.h: src/keymap/keys.txt Makefile
+       $(AM_V_GEN)$(AWK) 'BEGIN{ print "const char* const key_names[KEY_CNT] = { "} { print "[" $$1 "] = \"" $$1 "\"," } END{print "};"}' < $< > $@
+
+keymaps-distcheck-hook: src/keymap/keys.txt
+       $(top_srcdir)/src/keymap/check-keymaps.sh $(top_srcdir) $^
+DISTCHECK_HOOKS += keymaps-distcheck-hook
+endif
+
+if ENABLE_MTD_PROBE
+# ------------------------------------------------------------------------------
+mtd_probe_SOURCES =  \
+       src/mtd_probe/mtd_probe.c \
+       src/mtd_probe/mtd_probe.h \
+       src/mtd_probe/probe_smartmedia.c
+mtd_probe_CPPFLAGS = $(AM_CPPFLAGS)
+dist_udevrules_DATA += src/mtd_probe/75-probe_mtd.rules
+pkglibexec_PROGRAMS += mtd_probe
+endif
+
+# ------------------------------------------------------------------------------
+if ENABLE_RULE_GENERATOR
+dist_udevhome_SCRIPTS += \
+       src/rule_generator/write_cd_rules \
+       src/rule_generator/write_net_rules
+
+dist_udevhome_DATA += \
+       src/rule_generator/rule_generator.functions
+
+dist_udevrules_DATA += \
+       src/rule_generator/75-cd-aliases-generator.rules \
+       src/rule_generator/75-persistent-net-generator.rules
+endif
+
+# ------------------------------------------------------------------------------
+if ENABLE_FLOPPY
+create_floppy_devices_SOURCES = src/floppy/create_floppy_devices.c
+create_floppy_devices_LDADD = libudev-private.la
+pkglibexec_PROGRAMS += create_floppy_devices
+dist_udevrules_DATA += src/floppy/60-floppy.rules
+endif
+
+# ------------------------------------------------------------------------------
+clean-local:
+       rm -rf udev-test-install
+
+distclean-local:
+       rm -rf autom4te.cache
+
+EXTRA_DIST += \
+       $(TESTS) \
+       test/rule-syntax-check.py
+
+CLEANFILES += \
+       $(BUILT_SOURCES)
+
+install-exec-hook: $(INSTALL_EXEC_HOOKS)
+
+install-data-hook: $(INSTALL_DATA_HOOKS)
+
+uninstall-hook: $(UNINSTALL_EXEC_HOOKS)
+
+distcheck-hook: $(DISTCHECK_HOOKS)
+
+distclean-local: $(DISTCLEAN_LOCAL_HOOKS)
+
+# ------------------------------------------------------------------------------
+PREVIOUS_VERSION = `expr $(VERSION) - 1`
+changelog:
+       @ head -1 ChangeLog | grep -q "to v$(PREVIOUS_VERSION)"
+       @ mv ChangeLog ChangeLog.tmp
+       @ echo "Summary of changes from v$(PREVIOUS_VERSION) to v$(VERSION)" >> ChangeLog
+       @ echo "============================================" >> ChangeLog
+       @ echo >> ChangeLog
+       @ git log --pretty=short $(PREVIOUS_VERSION)..HEAD | git shortlog  >> ChangeLog
+       @ echo >> ChangeLog
+       @ cat ChangeLog
+       @ cat ChangeLog.tmp >> ChangeLog
+       @ rm ChangeLog.tmp
+
+test-install:
+       rm -rf $(PWD)/udev-test-install/
+       make DESTDIR=$(PWD)/udev-test-install install
+       tree $(PWD)/udev-test-install/
+
+git-release:
+       head -1 ChangeLog | grep -q "to v$(VERSION)"
+       head -1 NEWS | grep -q "udev $(VERSION)"
+       git commit -a -m "release $(VERSION)"
+       git tag -m "udev $(VERSION)" -s $(VERSION)
+       git gc --prune=0
+
+git-sync:
+       git push
+       git push --tags
+
+tar-sync:
+       rm -f udev-$(VERSION).tar.sign
+       xz -d -c udev-$(VERSION).tar.xz | gpg --armor --detach-sign --output udev-$(VERSION).tar.sign
+       kup put udev-$(VERSION).tar.xz  udev-$(VERSION).tar.sign /pub/linux/utils/kernel/hotplug/
+
+doc-sync:
+       for i in src/*.html; do rm -f $$i.sign; gpg --armor --detach-sign --output=$$i.sign $$i; done
+       for i in src/*.html; do echo $$i; kup put $$i $$i.sign /pub/linux/utils/kernel/hotplug/udev/; done
+       for i in src/docs/html/*.{html,css,png}; do rm -f $$i.sign; gpg --armor --detach-sign --output=$$i.sign $$i; done
+       for i in src/docs/html/*.{html,css,png}; do echo $$i; kup put $$i $$i.sign /pub/linux/utils/kernel/hotplug/libudev/; done
+       for i in src/gudev/docs/html/*.{html,css,png}; do rm -f $$i.sign; gpg --armor --detach-sign --output=$$i.sign $$i; done
+       for i in src/gudev/docs/html/*.{html,css,png}; do echo $$i; kup put $$i $$i.sign /pub/linux/utils/kernel/hotplug/gudev/; done
diff --git a/src/udev/NEWS b/src/udev/NEWS
new file mode 100644 (file)
index 0000000..f4f6f4e
--- /dev/null
@@ -0,0 +1,1735 @@
+udev 182
+========
+Rules files in /etc/udev/rules.s/ with the same name as rules files in
+/run/udev/rules.d/ now always have precedence. The stack of files is now:
+/usr/lib (package), /run (runtime, auto-generated), /etc (admin), while
+the later ones override the earlier ones. In other words: the admin has
+always the last say.
+
+USB auto-suspend is now enabled by default for some built-in USB HID
+devices.
+
+/dev/disk/by-path/ links are no longer created for ATA devices behind
+an 'ATA transport class', the logic to extract predictable numbers does
+not exist in the kernel at this moment.
+
+/dev/disk/by-id/scsi-* compatibility links are no longer created for
+ATA devices, they have their own ata-* prefix.
+
+The s390 rule to set mode == 0666 for /dev/z90crypt is is removed from
+the udev tree and will be part of s390utils (or alternatively could be
+done by the kernel driver itself).
+
+The udev-acl tool is no longer provided, it will be part of a future
+ConsoleKit release. On systemd systems, advanced ConsoleKit and udev-acl
+functionality are provided by systemd.
+
+udev 181
+========
+Require kmod version 5.
+
+Provide /dev/cdrom symlink for /dev/sr0.
+
+udev 180
+========
+Fix for ID_PART_ENTRY_* property names, added by the blkid built-in. The
+fix is needed for udisk2 to operate properly.
+
+Fix for skipped rule execution when the kernel has removed the device
+node in /dev again, before the event was even started. The fix is needed
+to run device-mapper/LVM events properly.
+
+Fix for the man page installation, which was skipped when xsltproc was not
+installed.
+
+udev 179
+========
+Bugfix for $name resolution, which broke at least some keymap handling.
+
+udev 178
+========
+Bugfix for the firmware loading behavior with kernel modules which
+try to load firmware in the module_init() path. The blocked event
+runs into a timout now, which should allow the firmware to be loaded.
+
+Bugfix for a wrong DEVNAME= export, which breaks at least the udev-acl
+tool.
+
+Bugfix for missing ID_ properties for GPT partitions.
+
+The RUN+="socket:.." option is deprecated and should not be used. A warning
+during rules parsing is printed now. Services which listen to udev events,
+need to subscribe to the netlink messages with libudev and not let udev block
+in the rules execution until the message is delivered.
+
+udev 177
+========
+Bugfix for rule_generator instalation.
+
+udev 176
+========
+The 'devtmpfs' filesystem is required now, udev will not create or delete
+device nodes anymore, it only adjusts permissions and ownership of device
+nodes and maintains additional symlinks.
+
+A writable /run directory (ususally tmpfs) is required now for a fully
+functional udev, there is no longer a fallback to /dev/.udev.
+
+The default 'configure' install locations have changed. Packages for systems
+with the historic / vs. /usr split need to be adapted, otherwise udev will
+be installed in /usr and not work properly. Example configuration options
+to install things the traditional way are in INSTALL.
+
+The default install location of the 'udevadm' tool moved from 'sbin'
+to /usr/bin. Some tools expect udevadm in 'sbin', a symlink to udevadm
+needs to be manually created if needed, or --bindir=/sbin be specified.
+
+The expected value of '--libexecdir=' has changed and must no longer contain
+the 'udev' directory.
+
+Kernel modules are now loaded directly by linking udev to 'libkmod'. The
+'modprobe' tool is no longer executed by udev.
+
+The 'blkid' tool is no longer executed from udev rules. Udev links
+directly to libblkid now.
+
+Firmware is loaded natively by udev now, the external 'firmware' binary
+is no longer used.
+
+All built-in tools can be listed and tested with 'udevadm test-builtin'.
+
+The 'udevadm control --reload-rules' option has been renamed to '--reload'.
+It now also reloads the kernel module configuration.
+
+The systemd socket files use PassCredentials=yes, which is available in
+systemd version 38.
+
+The udev build system only creates a .xz tarball now.
+
+All tabs in the source code used for indentation are replaced by spaces now. :)
+
+udev 175
+========
+Bugfixes.
+
+udev 174
+========
+Bugfixes.
+
+The udev daemon moved to /lib/udev/udevd. Non-systemd init systems
+and non-dracut initramfs image generators need to change the init
+scripts. Alternatively the udev build needs to move udevd back to
+/sbin or create a symlink in /sbin, which is not done by default.
+
+The path_id, usb_id, input_id tools are built-in commands now and
+the stand-alone tools do not exist anymore. Static lists of file in
+initramfs generators need to be updated. For testing, the commands
+can still be executed standalone with 'udevadm test-builtin <cmd>'.
+
+The fusectl filesystem is no longer mounted directly from udev.
+Systemd systems will take care of mounting fusectl and configfs
+now. Non-systemd systems need to ship their own rule if they
+need these filesystems auto-mounted.
+
+The long deprecated keys: SYSFS=, ID=, BUS= have been removed.
+
+The support for 'udevadm trigger --type=failed, and the
+RUN{fail_event_on_error} attribute was removed.
+
+The udev control socket is now created in /run/udev/control
+and no longer as an abstract namespace one.
+
+The rules to create persistent network interface and cdrom link
+rules automatically in /etc/udev/rules.d/ have been disabled by
+default. Explicit configuration will be required for these use
+cases, udev will no longer try to write any persistent system
+configuration from a device hotplug path.
+
+udev 173
+========
+Bugfixes.
+
+The udev-acl extra is no longer enabled by default now. To enable it,
+--enable-udev_acl needs to be given at ./configure time. On systemd
+systems, the udev-acl rules prevent it from running as the functionality
+has moved to systemd.
+
+udev 172
+========
+Bugfixes.
+
+Udev now enables kernel media-presence polling if available. Part
+of udisks optical drive tray-handling moved to cdrom_id: The tray
+is locked as soon as a media is detected to enable the receiving
+of media-eject-request events. Media-eject-request events will
+eject the media.
+
+Libudev enumerate is now able to enumerate a subtree of a given
+device.
+
+The mobile-action-modeswitch modeswitch tool was deleted. The
+functionality is provided by usb_modeswitch now.
+
+udev 171
+========
+Bugfixes.
+
+The systemd service files require systemd version 28. The systemd
+socket activation make it possible now to start 'udevd' and 'udevadm
+trigger' in parallel.
+
+udev 170
+========
+Fix bug in control message handling, which can lead to a failing
+udevadm control --exit. Thanks to Jürg Billeter for help tracking
+it down.
+
+udev 169
+========
+Bugfixes.
+
+We require at least Linux kernel 2.6.32 now. Some platforms might
+require a later kernel that supports accept4() and similar, or
+need to backport the trivial syscall wiring to the older kernels.
+
+The hid2hci tool moved to the bluez package and was removed.
+
+Many of the extras can be --enable/--disabled at ./configure
+time. The --disable-extras option was removed. Some extras have
+been disabled by default. The current options and their defaults
+can be checked with './configure --help'.
+
+udev 168
+========
+Bugfixes.
+
+Udev logs a warning now if /run is not writable at udevd
+startup. It will still fall back to /dev/.udev, but this is
+now considered a bug.
+
+The running udev daemon can now cleanly shut down with:
+  udevadm control --exit
+
+Udev in initramfs should clean the state of the udev database
+with: udevadm info --cleanup-db which will remove all state left
+behind from events/rules in initramfs. If initramfs uses
+--cleanup-db and device-mapper/LVM, the rules in initramfs need
+to add OPTIONS+="db_persist" for all dm devices. This will
+prevent removal of the udev database for these devices.
+
+Spawned programs by PROGRAM/IMPORT/RUN now have a hard timeout of
+120 seconds per process. If that timeout is reached the spawned
+process will be killed. The event timeout can be overwritten with
+udev rules.
+
+If systemd is used, udev gets now activated by netlink data.
+Systemd will bind the netlink socket which will buffer all data.
+If needed, such setup allows a seemless update of the udev daemon,
+where no event can be lost during a udevd update/restart.
+Packages need to make sure to: systemctl stop udev.socket udev.service
+or 'mask' udev.service during the upgrade to prevent any unwanted
+auto-spawning of udevd.
+This version of udev conflicts with systemd version below 25. The
+unchanged service files will not wirk correctly.
+
+udev 167
+========
+Bugfixes.
+
+The udev runtime data moved from /dev/.udev/ to /run/udev/. The
+/run mountpoint is supposed to be a tmpfs mounted during early boot,
+available and writable to for all tools at any time during bootup,
+it replaces /var/run/, which should become a symlink some day.
+
+If /run does not exist, or is not writable, udev will fall back using
+/dev/.udev/.
+
+On systemd systems with initramfs and LVM used, packagers must
+make sure, that the systemd and initramfs versions match. The initramfs
+needs to create the /run mountpoint for udev to store the data, and
+mount this tmpfs to /run in the rootfs, so the that the udev database
+is preserved for the udev version started in the rootfs.
+
+The command 'udevadm info --convert-db' is gone. The udev daemon
+itself, at startup, converts any old database version if necessary.
+
+The systemd services files have been reorganized. The udev control
+socket is bound by systemd and passed to the started udev daemon.
+The udev-settle.service is no longer active by default. Services which
+can not handle hotplug setups properly need to actively pull it in, to
+act like a barrier. Alternatively the settle service can be unconditionally
+'systemctl'enabled, and act like a barrier for basic.target.
+
+The fstab_import callout is no longer built or installed. Udev
+should not be used to mount, does not watch changes to fstab, and
+should not mirror fstab values in the udev database.
+
+udev 166
+========
+Bugfixes.
+
+New and updated keymaps.
+
+udev 165
+========
+Bugfixes.
+
+The udev database has changed, After installation of a new udev
+version, 'udevadm info --convert-db' should be called, to let the new
+udev/libudev version read the already stored data.
+
+udevadm now supports quoting of property values, and prefixing of
+key names:
+  $ udevadm info --export --export-prefix=MY_ --query=property -n sda
+  MY_MAJOR='259'
+  MY_MINOR='0'
+  MY_DEVNAME='/dev/sda'
+  MY_DEVTYPE='disk'
+  ...
+
+libudev now supports:
+  udev_device_get_is_initialized()
+  udev_enumerate_add_match_is_initialized()
+to be able to skip devices the kernel has created , but udev has
+not already handled.
+
+libudev now supports:
+  udev_device_get_usec_since_initialized()
+to retrieve the "age" of a udev device record.
+
+GUdev supports a more generic GUdevEnumerator class, udev TAG
+handling, device initialization and timestamp now.
+
+The counterpart of /sys/dev/{char,block}/$major:$minor,
+/dev/{char,block}/$major:$minor symlinks are now unconditionally
+created, even when no rule files exist.
+
+New and updated keymaps.
+
+udev 164
+========
+Bugfixes.
+
+GUdev moved from /usr to /.
+
+udev 163
+========
+Bugfixes.
+
+udev 162
+========
+Bugfixes.
+
+Persistent network naming rules are disabled inside of Qemu/KVM now.
+
+New and updated keymaps.
+
+Udev gets unconditionally enabled on systemd installations now. There
+is no longer the need to to run 'systemctl enable udev.service'.
+
+udev 161
+========
+Bugfixes.
+
+udev 160
+========
+Bugfixes.
+
+udev 159
+========
+Bugfixes.
+
+New and fixed keymaps.
+
+Install systemd service files if applicable.
+
+udev 158
+========
+Bugfixes.
+
+All distribution specific rules are removed from the udev source tree,
+most of them are no longer needed. The Gentoo rules which allow to support
+older kernel versions, which are not covered by the default rules anymore
+has moved to rules/misc/30-kernel-compat.rules.
+
+udev 157
+========
+Bugfixes.
+
+The option --debug-trace and the environemnt variable UDEVD_MAX_CHILDS=
+was removed from udevd.
+
+Udevd now checks the kernel commandline for the following variables:
+  udev.log-priority=<syslog priority>
+  udev.children-max=<maximum number of workers>
+  udev.exec-delay=<seconds to delay the execution of RUN=>
+to help debuging coldplug setups where the loading of a kernel
+module crashes the system.
+
+The subdirectory in the source tree rules/packages has been renamed to
+rules/arch, anc contains only architecture specific rules now.
+
+udev 156
+========
+Bugfixes.
+
+udev 155
+========
+Bugfixes.
+
+Now the udev daemon itself, does on startup:
+  - copy the content of /lib/udev/devices to /dev
+  - create the standard symlinks like /dev/std{in,out,err},
+    /dev/core, /dev/fd, ...
+  - use static node information provided by kernel modules
+    and creates these nodes to allow module on-demand loading
+  - possibly apply permissions to all ststic nodes from udev
+    rules which are annotated to match a static node
+
+The default mode for a device node is 0600 now to match the kernel
+created devtmpfs defaults. If GROUP= is specified and no MODE= is
+given the default will be 0660.
+
+udev 154
+========
+Bugfixes.
+
+Udev now gradually starts to pass control over the primary device nodes
+and their names to the kernel, and will in the end only manage the
+permissions of the node, and possibly create additional symlinks.
+As a first step NAME="" will be ignored, and NAME= setings with names
+other than the kernel provided name will result in a logged warning.
+Kernels that don't provide device names, or devtmpfs is not used, will
+still work as they did before, but it is strongly recommended to use
+only the same names for the primary device node as the recent kernel
+provides for all devices.
+
+udev 153
+========
+Fix broken firmware loader search path.
+
+udev 152
+========
+Bugfixes.
+
+"udevadm trigger" defaults to "change" events now instead of "add"
+events. The "udev boot script" might need to add "--action=add" to
+the trigger command if not already there, in case the initial coldplug
+events are expected as "add" events.
+
+The option "all_partitons" was removed from udev. This should not be
+needed for usual hardware. Udev can not safely make assumptions
+about non-existing partition major/minor numbers, and therefore no
+longer provide this unreliable and unsafe option.
+
+The option "ignore_remove" was removed from udev. With devtmpfs
+udev passed control over device nodes to the kernel. This option
+should not be needed, or can not work as advertised. Neither
+udev nor the kernel will remove device nodes which are copied from
+the /lib/udev/devices/ directory.
+
+All "add|change" matches are replaced by "!remove" in the rules and
+in the udev logic. All types of events will update possible symlinks
+and permissions, only "remove" is handled special now.
+
+The modem modeswitch extra was removed and the external usb_modeswitch
+program should be used instead.
+
+New and fixed keymaps.
+
+udev 151
+========
+Bugfixes.
+
+udev 150
+========
+Bugfixes.
+
+Kernels with SYSFS_DEPRECATED=y are not supported since a while. Many users
+depend on the current sysfs layout and the information not available in the
+deprecated layout. All remaining support for the deprecated sysfs layout is
+removed now.
+
+udev 149
+========
+Fix for a possible endless loop in the new input_id program.
+
+udev 148
+========
+Bugfixes.
+
+The option "ignore_device" does no longer exist. There is no way to
+ignore an event, as libudev events can not be suppressed by rules.
+It only prevented RUN keys from being executed, which results in an
+inconsistent behavior in current setups.
+
+BUS=, SYSFS{}=, ID= are long deprecated and should be SUBSYSTEM(S)=,
+ATTR(S){}=, KERNEL(S)=. It will cause a warning once for every rule
+file from now on.
+
+The support for the deprecated IDE devices has been removed from the
+default set of rules. Distros who still care about non-libata drivers
+need to add the rules to the compat rules file.
+
+The ID_CLASS property on input devices has been replaced by the more accurate
+set of flags ID_INPUT_{KEYBOARD,KEY,MOUSE,TOUCHPAD,TABLET,JOYSTICK}. These are
+determined by the new "input_id" prober now. Some devices, such as touchpads,
+can have several classes. So if you previously had custom udev rules which e. g.
+checked for ENV{ID_CLASS}=="kbd", you need to replace this with
+ENV{ID_INPUT_KEYBOARD}=="?*".
+
+udev 147
+========
+Bugfixes.
+
+To support DEVPATH strings larger than the maximum file name length, the
+private udev database format has changed. If some software still reads the
+private files in /dev/.udev/, which it shouldn't, now it's time to fix it.
+Please do not port anything to the new format again, everything in /dev/.udev
+is and always was private to udev, and may and will change any time without
+prior notice.
+
+Multiple devices claiming the same names in /dev are limited to symlinks
+only now. Mixing identical symlink names and node names is not supported.
+This reduces the amount of data in the database significantly.
+
+NAME="%k" causes a warning now. It's is and always was completely superfluous.
+It will break kernel supplied DEVNAMEs and therefore it needs to be removed
+from all rules.
+
+Most NAME= instructions got removed. Kernel 2.6.31 supplies the needed names
+if they are not the default. To support older kernels, the NAME= rules need to
+be added to the compat rules file.
+
+Symlinks to udevadm with the old command names are no longer resolved to
+the udevadm commands.
+
+The udev-acl tool got adopted to changes in ConsoleKit. Version 0.4.1 is
+required now.
+
+The option "last_rule" does no longer exist. Its use breaks too many
+things which expect to be run from independent later rules, and is an idication
+that something needs to be fixed properly instead.
+
+The gudev API is no longer marked as experimental,
+G_UDEV_API_IS_SUBJECT_TO_CHANGE is no longer needed. The gudev introspection
+is enabled by default now. Various projects already depend on introspection
+information to bind dynamic languages to the gudev interfaces.
+
+udev 146
+========
+Bugfixes.
+
+The udevadm trigger "--retry-failed" option, which is replaced since quite
+a while by "--type=failed" is removed.
+
+The failed tracking was not working at all for a few releases. The RUN
+option "ignore_error" is replaced by a "fail_event_on_error" option, and the
+default is not to track any failing RUN executions.
+
+New keymaps, new modem, hid2hci updated.
+
+udev 145
+========
+Fix possible crash in udevd when worker processes are busy, rules are
+changed at the same time, and workers get killed to reload the rules.
+
+udev 144
+========
+Bugfixes.
+
+Properties set with ENV{.FOO}="bar" are marked private by starting the
+name with a '.'. They will not be stored in the database, and not be
+exported with the event.
+
+Firmware files are looked up in:
+  /lib/firmware/updates/$(uname -r)
+  /lib/firmware/updates
+  /lib/firmware/$(uname -r)
+  /lib/firmware"
+now.
+
+ATA devices switched the property from ID_BUS=scsi to ID_BUS=ata.
+ata_id, instead of scsi_id, is the default tool now for ATA devices.
+
+udev 143
+========
+Bugfixes.
+
+The configure options have changed because another library needs to be
+installed in a different location. Instead of exec_prefix and udev_prefix,
+libdir, rootlibdir and libexecdir are used. The Details are explained in
+the README file.
+
+Event processes now get re-used after they handled an event. This reduces
+the number of forks and the pressure on the CPU significantly, because
+cloned event processes no longer cause page faults in the main daemon.
+After the events have settled, a few worker processes stay around for
+future events, all others get cleaned up.
+
+To be able to use signalfd(), udev depends on kernel version 2.6.25 now.
+Also inotify support is mandatory now to run udev.
+
+The format of the queue exported by the udev damon has changed. There is
+no longer a /dev/.udev/queue/ directory. The current event queue can be
+accessed with udevadm settle and libudedv.
+
+Libudev does not have the unstable API header anymore. From now on,
+incompatible changes will be handled by bumping the library major version.
+
+To build udev from the git tree gtk-doc is needed now. The tarballs will
+build without it and contain the pre-built documentation. An online copy
+is available here:
+  http://www.kernel.org/pub/linux/utils/kernel/hotplug/libudev/
+
+The tools from the udev-extras repository have been merged into the main
+udev repository. Some of the extras have larger external dependencies, and
+they can be disabled with the configure switch --disable-extras.
+
+udev 142
+========
+Bugfixes.
+
+The program vol_id and the library libvolume_id are removed from the
+repository. Libvolume_id is merged with libblkid from the util-linux-ng
+package. Persistent disk links for label and uuid depend on the
+util-linux-ng version (2.15) of blkid now. Older versions of blkid
+can not be used with udev.
+
+Libudev allows to subscribe to udev events. To prevent unwanted messages
+to be delivered, and waking up the subscribing process, a filter can be
+installed, to drop messages inside a kernel socket filter. The filters
+match on the <subsytem>:<devtype> properties of the device.
+    This is part of the ongoing effort to replace HAL, and switch current
+users over to directly use libudev.
+    Libudev is still marked as experimental, and its interface might
+eventually change if needed, but no major changes of the currently exported
+interface are expected anymore, and a first stable release should happen
+soon.
+
+A too old kernel (2.6.21) or a kernel with CONFIG_SYSFS_DEPRECATED
+is not supported since while and udevd will log an error message at
+startup. It should still be able to boot-up, but advanced rules and system
+services which depend on the information not available in the old sysfs
+format will fail to work correctly.
+
+DVB device naming is supplied by the kernel now. In case older kernels
+need to be supported, the old shell script should be added to a compat
+rules file.
+
+udev 141
+========
+Bugfixes.
+
+The processed udev events get send back to the netlink socket. Libudev
+provides access to these events. This is work-in-progress, to replace
+the DeviceKit daemon functionality directly with libudev. There are
+upcoming kernel changes to allow non-root users to subcribe to these
+events.
+
+udev 140
+========
+Bugfixes.
+
+"udevadm settle" now optionally accepts a range of events to wait for,
+instead of waiting for "all" events.
+
+udev 139
+========
+Bugfixes.
+
+The installed watch for block device metadata changes is now removed
+during event hadling, because some (broken) tools may be called from udev
+rules and (wrongly) open the device with write access. After the finished
+event handling the watch is restored.
+
+udev 138
+========
+Bugfixes.
+
+Device nodes can be watched for changes with inotify with OPTIONS="watch".
+If closed after being opened for writing, a "change" uevent will occur.
+/dev/disk/by-{label,uuid}/* symlinks will be automatically updated.
+
+udev 137
+========
+Bugfixes.
+
+The udevadm test command has no longer a --force option, nodes and symlinks
+are always updated with a test run now.
+
+The udevd daemon can be started with --resolve-names=never to avoid all user
+and group lookups (e.g. in cut-down systems) or --resolve-names=late to
+lookup user and groups every time events are handled.
+
+udev 136
+========
+Bugfixes.
+
+We are currently merging the Ubuntu rules in the udev default rules,
+and get one step closer to provide a common Linux /dev setup, regarding
+device names, symlinks, and default device permissions. On udev startup,
+we now expect the following groups to be resolvable to their ids with
+glibc's getgrnam():
+  disk, cdrom, floppy, tape, audio, video, lp, tty, dialout, kmem.
+LDAP setups need to make sure, that these groups are always resolvable at
+bootup, with only the rootfs mounted, and without network access available.
+
+Some systems may need to add some new, currently not used groups, or need
+to add some users to new groups, but the cost of this change is minimal,
+compared to the pain the current, rather random, differences between the
+various distributions cause for upstream projects and third-party vendors.
+
+In general, "normal" users who log into a machine should never be a member
+of any such group, but the device-access should be managed by dynamic ACLs,
+which get added and removed for the specific users on login/logout and
+session activity/inactivity. These groups are only provided for custom setups,
+and mainly system services, to allow proper privilege separation.
+A video-streaming daemon uid would be a member of "audio" and "video", to get
+access to the sound and video devices, but no "normal" user should ever belong
+to the "audio" group, because he could listen to the built-in microphone with
+any ssh-session established from the other side of the world.
+
+/dev/serial/by-{id,path}/ now contains links for ttyUSB devices,
+which do not depend on the kernel device name. As usual, unique
+devices - only a single one per product connected, or a real
+USB serial number in the device - are always found with the same
+name in the by-id/ directory.
+Completely identical devices may overwrite their names in by-id/
+and can only be found reliably in the by-path/ directory. Devices
+specified by by-path/ must not change their connection, like the
+USB port number they are plugged in, to keep their name.
+
+To support some advanced features, Linux 2.6.22 is the oldest supported
+version now. The kernel config with enabled SYSFS_DEPRECATED is no longer
+supported. Older kernels should still work, and devices nodes should be
+reliably created, but some rules and libudev will not work correctly because
+the old kernels do not provide the expected information or interfaces.
+
+udev 135
+========
+Bugfixes.
+
+Fix for a possible segfault while swapping network interface names in udev
+versions 131-134.
+
+udev 134
+========
+Bugfixes.
+
+The group "video" is part of the default rules now.
+
+udev 133
+========
+Bugfix for kernels using SYSFS_DEPRECATED* option and finding parent
+block devices in some cases. No common distro uses this option anymore,
+and we do not get enough testing for this and recent udev versions. If
+this option is not needed to run some old distro with a new kernel,
+it should be disabled in the kernel config.
+
+Bugfix for the $links substitution variable, which may crash if no links
+are created. This should not happen in usual setups because we always
+create /dev/{block,char}/ links.
+
+The strings of the parsed rules, which are kept in memory, no longer
+contain duplicate entries, or duplicate tails of strings. This, and the
+new rules parsing/matching code reduces the total in-memory size of
+a huge distro rule sets to 0.08 MB, compared to the 1.2MB of udev
+version 130.
+
+The export of DEVTYPE=disk/partition got removed from the default
+rules. This value is available from the kernel. The pnp shell script
+modprobe hack is removed from the default rules. ACPI devices have _proper_
+modalias support and take care of the same functionality.
+Installations which support old kernels, but install current default
+udev rules may want to add that to the compat rules file.
+
+Libvolume_id now always probes for all known filesystems, and does not
+stop at the first match. Some filesystems are marked as "exclusive probe",
+and if any other filesytem type matches at the same time, libvolume_id
+will, by default, not return any probing result. This is intended to prevent
+mis-detection with conflicting left-over signatures found from earlier
+file system formats. That way, we no longer depend on the probe-order
+in case of multiple competing signatures. In some setups the kernel allows
+to mount a volume with just the old filesystem signature still in place.
+This may damage the new filesystem and cause data-loss, just by mounting
+it. Because volume_id can not decide which one the correct signature is,
+the wrong signatures need to be removed manually from the volume, or the
+volume needs to be reformatted, to enable filesystem detection and possible
+auto-mounting.
+
+udev 132
+========
+Fix segfault if compiled without optimization and dbg() does not get
+compiled out and uses variables which are not available.
+
+udev 131
+========
+Bugfixes. (And maybe new bugs. :))
+
+The rule matching engine got converted from a rule list to a token
+array which reduced the in-memory rules representation of a full
+featured distros with thousends of udev rules from 1.2MB to 0.12 MB.
+Limits like 5 ENV and ATTR matches, and one single instance for most
+other keys per rule are gone.
+
+The NAME assignment is no longer special cased. If later rules assign
+a NAME value again, the former value will be overwritten. As usual
+for most other keys, the NAME value can be protected by doing a final
+assignment with NAME:="<value>".
+
+All udev code now uses libudev, which is also exported. The library
+is still under development, marked as experimental, and its interface
+may change as long as the DeviceKit integration is not finished.
+
+Many thanks to Alan Jenkins for his continuous help, and finding and
+optimizing some of the computing expensive parts.
+
+udev 130
+========
+Bugfixes.
+
+Kernel devices and device nodes are connected now by reverse indizes in
+/sys and /dev. A device number retrieved by a stat() or similar, the
+kernel device directory can be found by looking up:
+  /sys/dev/{block,char}/<maj>:<min>
+and the device node of the same device by looking up:
+  /dev/{block,char}/<maj>:<min>
+
+udev 129
+========
+Fix recently introduced bug, which caused a compilation without large
+file support, where vol_id does not recognize raid signatures at the end
+of a volume.
+
+Firewire disks now create both, by-id/scsi-* and by-id/ieee-* links.
+Seems some kernel versions prevent the creation of the ieee-* links,
+so people used the scsi-* link which disappeared now.
+
+More libudev work. Almost all udevadm functionality comes from libudev
+now.
+
+udevadm trigger has a new option --type, which allows to trigger events
+for "devices", for "subsystems", or "failed" devices. The old option
+--retry-failed" still works, but is no longer mentioned in the man page.
+
+udev 128
+========
+Bugfixes.
+
+The udevadm info --device-id-of-file= output has changed to use
+the obvious format. Possible current users should use the --export
+option which is not affected.
+
+The old udev commands symlinks to udevadm are not installed, if
+these symlinks are used, a warning is printed.
+
+udev 127
+========
+Bugfixes.
+
+Optical drive's media is no longer probed for raid signatures,
+reading the end of the device causes some devices to malfunction.
+Also the offset of the last session found is used now to probe
+for the filesystem.
+
+The volume_id library got a major version number update to 1,
+some deprecated functions are removed.
+
+A shared library "libudev" gets installed now to provide access
+to udev device information. DeviceKit, the successor of HAL, will
+need this library to access the udev database and search sysfs for
+devices.
+The library is currently in an experimental state, also the API is
+expected to change, as long as the DeviceKit integration is not
+finished.
+
+udev 126
+========
+We use ./configure now. See INSTALL for details. Current
+options are:
+    --prefix=
+        "/usr" - prefix for man pages, include files
+    --exec-prefix=
+        "" - the root filesystem, prefix for libs and binaries
+    --sysconfdir=
+        "/etc"
+    --with-libdir-name=
+        "lib" - directory name for libraries, not a path name
+        multilib 64bit systems may use "lib64" instead of "lib"
+    --enable-debug
+        compile-in verbose debug messages
+    --disable-logging
+        disable all logging and compile-out all log strings
+    --with-selinux
+        link against SELInux libraries, to set the expected context
+        for created files
+
+In the default rules, the group "disk" gets permissions 0660 instead
+of 0640. One small step closer to unify distro rules. Some day, all
+distros hopefully end up with the same set of rules.
+
+No symlinks to udevadm are installed anymore, if they are still needed,
+they should be provided by the package.
+
+udev 125
+========
+Bugfixes.
+
+Default udev rules, which are not supposed to be edited by the user, should
+be placed in /lib/udev/rules.d/ now, to make it clear that they are private to
+the udev package and will be replaced with an update. Udev will pick up rule
+files from:
+  /lib/udev/rules.d/  - default installed rules
+  /etc/udev/rules.d/  - user rules + on-the-fly generated rules
+  /dev/.udev/rules.d/ - temporary non-persistent rules created after bootup
+It does not matter in which directory a rule file lives, all files are sorted
+in lexical order.
+
+To help creating /dev/root, we have now:
+  $ udevadm info --export --export-prefix="ROOT_" --device-id-of-file=/
+  ROOT_MAJOR=8
+  ROOT_MINOR=5
+In case the current --device-id-of-file is already used, please switch to
+the --export format version, it saves the output parsing and the old
+format will be changed to use ':' as a separator, like the format in the
+sysfs 'dev' file.
+
+udev 124
+========
+Fix cdrom_id to properly recognize blank media.
+
+udev 123
+========
+Bugfixes.
+
+Tape drive id-data is queried from /dev/bsg/* instead of the tape
+nodes. This avoids rewinding tapes on open().
+
+udev 122
+========
+Bugfixes.
+
+The symlinks udevcontrol and udevtrigger are no longer installed by
+the Makefile.
+
+The scsi_id program does not depend on sysfs anymore. It can speak
+SGv4 now, so /dev/bsg/* device nodes can be used, to query SCSI device
+data, which should solve some old problems with tape devices, where
+we better do not open all tape device nodes to identify the device.
+
+udev 121
+========
+Many bugfixes.
+
+The cdrom_id program is replaced by an advanced version, which can
+detect most common device types, and also properties of the inserted
+media. This is part of moving some basic functionality from HAL into
+udev (and the kernel).
+
+udev 120
+========
+Bugfixes.
+
+The last WAIT_FOR_SYSFS rule is removed from the default rules.
+
+The symlinks to udevadm for the debugging tools: udevmonitor and
+udevtest are no longer created.
+
+The symlinks to the udevadm man page for the old tool names are
+no longer created.
+
+Abstract namespace sockets paths in RUN+="socket:@<path>" rules,
+should be prefixed with '@' to indicate that the path is not a
+real file.
+
+udev 119
+========
+Bugfixes.
+
+udev 118
+========
+Bugfixes.
+
+Udevstart is removed from the tree, it did not get installed for
+a long time now, and is long replaced by trigger and settle.
+
+udev 117
+========
+Bugfixes.
+
+All udev tools are merged into a single binary called udevadm.
+The old names of the tools are built-in commands in udevadm now.
+Symlinks to udevadm, with the names of the old tools, provide
+the same functionality as the standalone tools. There is also
+only a single udevadm.8 man page left for all tools.
+
+Tools like mkinitramfs should be checked, if they need to include
+udevadm in the list of files.
+
+udev 116
+========
+Bugfixes.
+
+udev 115
+========
+Bugfixes.
+
+The etc/udev/rules.d/ directory now contains a default set of basic
+udev rules. This initial version is the result of a rules file merge
+of Fedora and openSUSE. For these both distros only a few specific
+rules are left in their own file, named after the distro. Rules which
+are optionally installed, because they are only valid for a specific
+architecture, or rules for subsystems which are not always used are
+in etc/udev/packages/.
+
+udev 114
+========
+Bugfixes.
+
+Dynamic rules can be created in /dev/.udev/rules.d/ to trigger
+actions by dynamically created rules.
+
+SYMLINK=="<value>" matches agains the entries in the list of
+currently defined symlinks. The links are not created in the
+filesystem at that point in time, but the values can be matched.
+
+RUN{ignore_error}+="<program>" will ignore any exit code from the
+program and not record as a failed event.
+
+udev 113
+========
+Bugfixes.
+
+Final merge of patches/features from the Ubuntu package.
+
+udev 112
+========
+Bugfixes.
+
+Control characters in filesystem label strings are no longer silenty
+removed, but hex-encoded, to be able to uniquely identify the device
+by its symlink in /dev/disk/by-label/.
+If libvolume_id is used by mount(8), LABEL= will work as expected,
+if slashes or other characters are used in the label string.
+
+To test the existence of a file, TEST=="<file>" and TEST!="<file>"
+can be specified now. The TEST key accepts an optional mode mask
+TEST{0100}=="<is executable file>".
+
+Scsi_id now supports a mode without expecting scsi-specific sysfs
+entries to allow the extraction of cciss-device persistent properties.
+
+udev 111
+========
+Bugfixes.
+
+In the future, we may see uuid's which are just simple character
+strings (see the DDF Raid Specification). For that reason vol_id now
+exports ID_FS_UUID_SAFE, just like ID_FS_LABEL_SAFE. For things like
+the creation of symlinks, the *_SAFE values ensure, that no control
+or whitespace characters are used in the filename.
+
+Possible users of libvolume_id, please use the volume_id_get_* functions.
+The public struct will go away in a future release of the library.
+
+udev 110
+========
+Bugfixes.
+
+Removal of useless extras/eventrecorder.sh.
+
+udev 109
+========
+Bugfixes.
+
+udev 108
+========
+Bugfixes.
+
+The directory multiplexer for dev.d/ and hotplug.d are finally removed
+from the udev package.
+
+udev 107
+========
+Bugfixes.
+
+Symlinks can have priorities now, the priority is assigned to the device
+and specified with OPTIONS="link_priority=100". Devices with higher
+priorities overwrite the symlinks of devices with lower priorities.
+If the device that currently owns the link, goes away, the symlink
+will be removed, and recreated, pointing to the next device with the
+highest actual priority. This should make /dev/disk/by-{label,uuid,id}
+more reliable, if multiple devices contain the same metadata and overwrite
+these symlinks.
+
+The dasd_id program is removed from the udev tree, and dasdinfo, with the
+needed rules, are part of the s390-tools now.
+
+Please add KERNEL=="[0-9]*:[0-9]*" to the scsi wait-for-sysfs rule,
+we may get the scsi sysfs mess fixed some day, and this will only catch
+the devices we are looking for.
+
+USB serial numbers for storage devices have the target:lun now appended,
+to make it possibble to distinguish broken multi-lun devices with all
+the same SCSI identifiers.
+
+Note: The extra "run_directory" which searches and executes stuff in
+/etc/hotplug.d/ and /etc/dev.d/ is long deprecated, and will be removed
+with the next release. Make sure, that you don't use it anymore, or
+provides your own implementation of that inefficient stuff.
+We are tired of reports about a "slow udev", because these directories
+contain stuff, that runs with _every_ event, instead of using rules,
+that run programs only for the matching events.
+
+udev 106
+========
+Bugfixes.
+
+udev 105
+========
+Bugfixes.
+
+DRIVER== will match only for devices that actually have a real
+driver. DRIVERS== must be used, if parent devices should be
+included in the match.
+
+Libvolume_id's "linux_raid" detection needed another fix.
+
+udev 104
+========
+Bugfixes.
+
+udev 103
+========
+Add additional check to volume_id detection of via_raid, cause
+some company decided to put a matching pattern all over the empty
+storage area of their music players.
+
+udev 102
+========
+Fix path_id for SAS devices.
+
+udev 101
+========
+The udev daemon can be started with --debug-trace now, which will
+execute all events serialized to get a chance to catch a possible
+action that crashes the box.
+
+A warning is logged, if PHYSDEV* keys, the "device" link, or a parent
+device attribute like $attr{../file} is used, only WAIT_FOR_SYSFS rules
+are excluded from the warning. Referencing parent attributes directly
+may break when something in the kernel driver model changes. Udev will
+just find the attribute by walking up the parent chain.
+
+Udevtrigger now sorts the list of devices depending on the device
+dependency, so a "usb" device is triggered after the parent "pci"
+device.
+
+udev 100
+========
+Revert persistent-storage ata-serial '_' '-' replacement.
+
+udev 099
+========
+Bugfixes.
+
+Udevtrigger can now filter the list of devices to be triggered. Matches
+for subsystems or sysfs attributes can be specified.
+
+The entries in /dev/.udev/queue and /dev/.udev/failed have changed to
+zero-sized files to avoid pointing to /sys and confuse broken tools which
+scan the /dev directory. To retry failed events, udevtrigger --retry-failed
+should be used now.
+
+The rules and scripts to create udev rules for persistent network
+devices and optical drives are in the extras/rules_generator directory
+now. If you use something similar, please consider replacing your own
+version with this, to share the support effort. The rule_generator
+installs its own rules into /etc/udev/rules.d.
+
+The cdrom_id tool installs its own rule now in /etc/udev/rules.d, cause
+the rule_generator depends on cdrom_id to be called in an earlier rule.
+
+udev 098
+========
+Bugfixes.
+
+Renaming of some key names (the old names still work):
+BUS -> SUBSYSTEMS, ID -> KERNELS, SYSFS -> ATTRS, DRIVER -> DRIVERS.
+(The behavior of the key DRIVER will change soon in one of the next
+releases, to match only the event device, please switch to DRIVERS
+instead. If DRIVER is used, it will behave like DRIVERS, but an error
+is logged.
+With the new key names, we have a more consistent and simpler scheme.
+We can match the properties of the event device only, with: KERNEL,
+SUBSYSTEM, ATTR, DRIVER. Or include all the parent devices in the match,
+with: KERNELS, SUBSYSTEMS, ATTRS, DRIVERS. ID, BUS, SYSFS, DRIVER are no
+longer mentioned in the man page and should be switched in the rule
+files.
+
+ATTR{file}="value" can be used now, to write to a sysfs file of the
+event device. Instead of:
+  ..., SYSFS{type}=="0|7|14", RUN+="/bin/sh -c 'echo 60 > /sys$$DEVPATH/timeout'"
+we now can do:
+  ..., ATTR{type}=="0|7|14", ATTR{timeout}="60"
+
+All the PHYSDEV* keys are deprecated and will be removed from a
+future kernel:
+  PHYDEVPATH -    is the path of a parent device and should not be
+                  needed at all.
+  PHYSDEVBUS -    is just a SUBSYSTEM value of a parent, and can be
+                  matched with SUBSYSTEMS==
+  PHYSDEVDRIVER - for bus devices it is available as ENV{DRIVER}.
+                  Newer kernels will have DRIVER in the environment,
+                  for older kernels udev puts in. Class device will
+                  no longer carry this property of a parent and
+                  DRIVERS== can be used to match such a parent value.
+Note that ENV{DRIVER} is only available for a few bus devices, where
+the driver is already bound at device event time. On coldplug, the
+events for a lot devices are already bound to a driver, and they will have
+that value set. But on hotplug, at the time the kernel creates the device,
+it can't know what driver may claim the device after that, therefore
+in most cases it will be empty.
+
+Failed events should now be re-triggered with:
+   udevtrigger --retry-failed.
+Please switch to this command, so we keep the details of the /dev/.udev/failed/
+files private to the udev tools. We may need to switch the current symlink
+target, cause some obviously broken tools try to scan all files in /dev
+including /dev/.udev/, find the links to /sys and end up stat()'ing sysfs files
+million times. This takes ages on slow boxes.
+
+The udevinfo attribute walk (-a) now works with giving a device node
+name (-n) instead of a devpath (-p). The query now always works, also when
+no database file was created by udev.
+
+The built-in /etc/passwd /etc/group parser is removed, we always depend on
+getpwnam() and getgrnam() now. One of the next releases will depend on
+fnmatch() and may use getopt_long().
+
+udev 097
+========
+Bugfixes and small improvements.
+
+udev 096
+========
+Fix path_id for recent kernels.
+
+udev 095
+========
+%e is finally gone.
+
+Added support for swapping network interface names, by temporarily
+renaming the device and wait for the target name to become free.
+
+udev 094
+========
+The built-in MODALIAS key and substitution is removed.
+
+udev 093
+========
+The binary firmware helper is replaced by the usual simple
+shell script. Udevsend is removed from the tree.
+
+udev 092
+========
+Bugfix release.
+
+udev 091
+========
+Some more keys require the correct use of '==' and '=' depending
+on the kind of operation beeing an assignment or a match. Rules
+with invalid operations are skipped and logged to syslog. Please
+test with udevtest if the parsing of your rules throws errors and
+fix possibly broken rules.
+
+udev 090
+========
+Provide "udevsettle" to wait for all current udev events to finish.
+It also watches the current kernel netlink queue by comparing the
+even sequence number to make sure that there are no current pending
+events that have not already arrived in the daemon.
+
+udev 089
+========
+Fix rule to skip persistent rules for removable IDE devices, which
+also skipped optical IDE drives.
+
+All *_id program are installed in /lib/udev/ by default now.
+
+No binary is stripped anymore as this should be done in the
+packaging process and not at build time.
+
+libvolume_id is provided as a shared library now and vol_id is
+linked against it. Also one of the next HAL versions will require
+this library, and the HAL build process will also require the
+header file to be installed. The copy of the same code in HAL will
+be removed to have only a single copy left on the system.
+
+udev 088
+========
+Add persistent links for SCSI tapes. The rules file is renamed
+to 60-persistent-storage.rules.
+
+Create persistent path for usb devices. Can be used for all sorts
+of devices that can't be distinguished by other properties like
+multiple identical keyboards and mice connected to the same box.
+
+Provide "udevtrigger" program to request events on coldplug. The
+shell script is much too slow with thousends of devices.
+
+udev 087
+========
+Fix persistent disk rules to exclude removable IDE drives.
+
+Warn if %e, $modalias or MODALIAS is used.
+
+udev 086
+========
+Fix queue export, which wasn't correct for subsequent add/remove
+events for the same device.
+
+udev 085
+========
+Fix cramfs detection on big endian.
+
+Make WAIT_FOR_SYSFS usable in "normal" rules and silent if the whole
+device goes away.
+
+udev 084
+========
+If BUS== and SYSFS{}== have been used in the same rule, the sysfs
+attributes were only checked at the parent device that matched the
+by BUS requested subsystem. Fix it to also look at the device we
+received the event for.
+
+Build variable CROSS has changed to CROSS_COMPILE to match the kernel
+build name.
+
+udev 083
+========
+Fix a bug where NAME="" would prevent RUN from beeing executed.
+
+RUN="/bin/program" does not longer automatically add the subsystem
+as the first parameter. This is from the days of /sbin/hotplug
+which is dead now and it's just confusing to need to add a space at
+the end of the program name to prevent this.
+If you use rules that need the subsystem as the first parameter,
+like the old "udev_run_hotlugd" and "udev_run_devd", add the subsystem
+to the key like RUN+="/bin/program $env{SUBSYSTEM}".
+
+udev 082
+========
+The udev man page has moved to udev(7) as it does not describe a command
+anymore. The programs udev, udevstart and udevsend are no longer installed
+by default and must be copied manually, if they should be installed or
+included in a package.
+
+Fix a bug where "ignore_device" could run earlier collected RUN keys before
+the ignore rule was applied.
+
+More preparation for future sysfs changes. usb_id and scsi_id no longer
+depend on a magic order of devices in the /devices chain. Specific devices
+should be requested by their subsytem.
+
+This will always find the scsi parent device without depending on a specific
+path position:
+  dev = sysfs_device_get(devpath);
+  dev_usb = sysfs_device_get_parent_with_subsystem(dev, "scsi");
+
+The "device" link in the current sysfs layout will be automatically
+_resolved_ as a parent and in the new sysfs layout it will just _be_ the
+parent in the devpath. If a device is requested by it's symlink, like all
+class devices in the new sysfs layout will look like, it gets automatically
+resolved and substituted with the real devpath and not the symlink path.
+
+Note:
+A similar logic must be applied to _all_ sysfs users, including
+scripts, that search along parent devices in sysfs. The explicit use of
+the "device" link must be avoided. With the future sysfs layout all
+DEVPATH's will start with /devices/ and have a "subsystem" symlink poiting
+back to the "class" or the "bus". The layout of the parent devices in
+/devices is not necessarily expected to be stable across kernel releases and
+searching for parents by their subsystem should make sysfs users tolerant
+for changed parent chains.
+
+udev 081
+========
+Prepare udev to work with the experimental kernel patch, that moves
+/sys/class devices to /sys/devices and /sys/block to /sys/class/block.
+
+Clarify BUS, ID, $id usage and fix $id behavior. This prepares for
+moving the class devices to /sys/devices.
+
+Thanks again to Marco for help finding a hopefully nice compromise
+to make %b simpler and working again.
+
+udev 080
+========
+Complete removal of libsysfs, replaced by simple helper functions
+which are much simpler and a bit faster. The udev daemon operatesentirely
+on event parameters and does not use sysfs for simple rules anymore.
+Please report any new bugs/problems, that may be caused by this big
+change. They will be fixed immediately.
+
+The enumeration format character '%e' is deprecated and will be
+removed sometimes from a future udev version. It never worked correctly
+outside of udevstart, so we can't use it with the new parallel
+coldplug. A simple enumeration is as useless as the devfs naming
+scheme, just get rid of both if you still use it.
+
+MODALIAS and $modalias is not needed and will be removed from one of
+the next udev versions, replace it in all rules with ENV{MODALIAS} or
+the sysfs "modalias" value.
+
+Thanks a lot to Marco for all his help on finding and fixing bugs.
+
+udev 079
+========
+Let scsi_id request libata drive serial numbers from page 0x80.
+
+Renamed etc/udev/persistent.rules to persistent-disk.rules and
+added /dev/disk/by-name/* for device mapper device names.
+
+Removed %e from the man page. It never worked reliably outside
+of udevstart and udevstart is no longer recommended to use.
+
+udev 078
+========
+Symlinks are now exported to the event environment. Hopefully it's no
+longer needed to run udevinfo from an event process, like it was
+mentioned on the hotplug list:
+  UDEV  [1134776873.702967] add@/block/sdb
+  ...
+  DEVNAME=/dev/sdb
+  DEVLINKS=/dev/disk/by-id/usb-IBM_Memory_Key_0218B301030027E8 /dev/disk/by-path/usb-0218B301030027E8:0:0:0
+
+udev 077
+========
+Fix a problem if udevsend is used as the hotplug handler and tries to use
+syslog, which causes a "vc" event loop. 2.6.15 will make udevsend obsolete
+and this kind of problems will hopefully go away soon.
+
+udev 076
+========
+All built-in logic to work around bad sysfs timing is removed with this
+version. The need to wait for sysfs files is almost fixed with a kernel
+version that doesn't work with this udev version anyway. Until we fix
+the timing of the "bus" link creation, the former integrated logic should
+be emulated by a rule placed before all other rules:
+  ACTION=="add", DEVPATH=="/devices/*", ENV{PHYSDEVBUS}=="?*", WAIT_FOR_SYSFS="bus"
+
+The option "udev_db" does no longer exist. All udev state will be in
+/$udev_root/.udev/ now, there is no longer an option to set this
+to anything else.
+If the init script or something else used this value, just depend on
+this hardcoded path. But remember _all_content_ of this directory is
+still private to udev and can change at any time.
+
+Default location for rule sripts and helper programs is now: /lib/udev/.
+Everything that is not useful on the commandline should go into this
+directory. Some of the helpers in the extras folder are installed there
+now. The rules need to be changed, to find the helpers there.
+
+Also /lib/udev/devices is recommended as a directory where packages or
+the user can place real device nodes, which get copied over to /dev at
+every boot. This should replace the various solutions with custom config
+files.
+
+Udevsend does no longer start the udev daemon. This must be done with
+the init script that prepares /dev on tmpfs and creates the initial nodes,
+before starting the daemon.
+
+udev 075
+========
+Silent a too verbose error logging for the old hotplug.d/ dev.d/
+emulation.
+
+The copy of klibc is removed. A systemwide installed version of klibc
+should be used to build a klibc udev now.
+
+udev 074
+========
+NAME="" will not create any nodes, but execute RUN keys. To completely
+ignore an event the OPTION "ignore_device" should be used.
+
+After removal of the reorder queue, events with a TIMEOUT can be executed
+without any queuing now.
+
+udev 073
+========
+Fixed bug in udevd, if inotify is not available. We depend on netlink
+uevents now, kernels without that event source will not work with that
+version of udev anymore.
+
+udev 072
+========
+The rule parsing happens now in the daemon once at startup, all udev
+event processes inherit the already parsed rules from the daemon.
+It is shipped with SUSE10.0 and reduces heavily the system load at
+startup. The option to save precompiled rules and let the udev process
+pick the them up is removed, as it's no longer needed.
+
+Kernel 2.6.15 will have symlinks at /class/input pointing to the real
+device. Libsysfs is changed to "translate" the requested link into the
+real device path, as it would happen with the hotplug event. Otherwise
+device removal and the udev database will not work.
+
+Using 'make STRIPCMD=' will leave the binaries unstripped for debugging
+and packaging.
+
+A few improvements for vol_id, the filesytem probing code.
+
+udev 071
+========
+Fix a stupid typo in extras/run_directory for "make install".
+
+scsi_id creates the temporary devnode now in /dev for usage with a
+non-writable /tmp directory.
+
+The uevent kernel socket buffer can carry app. 50.000 events now,
+let's see who can break this again. :)
+
+The upcoming kernel will have a new input driver core integration.
+Some class devices are now symlinks to the real device. libsysfs
+needs a fix for this to work correctly. Udevstart of older udev
+versions will _not_ create these devices!
+
+udev 070
+========
+Fix a 'install' target in the Makefile, that prevents EXTRAS from
+beeing installed.
+
+udev 069
+========
+A bunch of mostly trivial bugfixes. From now on no node name or
+symlink name can contain any character than plain whitelisted ascii
+characters or validated utf8 byte-streams. This is needed for the
+/dev/disk/by-label/* links, because we import untrusted data and
+export it to the filesystem.
+
+udev 068
+========
+More bugfixes. If udevd was started from the kernel, we don't
+have stdin/stdout/stderr, which broke the forked tools in some
+situations.
+
+udev 067
+========
+Bugfix. udevstart event ordering was broken for a long time.
+The new run_program() uncovered it, because /dev/null was not
+available while we try to run external programs.
+Now udevstart should create it before we run anything.
+
+udev 066
+========
+Minor bugfixes and some distro rules updates. If you don't have the
+persistent disk rules in /dev/disk/by-*/* on your distro, just
+grab it from here. :)
+
+udev 065
+========
+We can use socket communication now to pass events from udev to
+other programs:
+  RUN+="socket:/org/freedesktop/hal/udev_event"
+will pass the whole udev event to the HAL daemon without the need
+for a forked helper. (See ChangeLog for udevmonitor, as an example)
+
+udev 064
+========
+Mostly bugfixes and see ChangeLog.
+
+The test for the existence of an environment value should be
+switched from:
+  ENV{KEY}=="*" to ENV{KEY}=="?*"
+because "*" will not fail anymore, if the key does not exist or
+is empty.
+
+udev 063
+========
+Bugfixes and a few tweaks described in the ChangeLog.
+
+udev 062
+========
+Mostly a Bugfix release.
+
+Added WAIT_FOR_SYSFS="<attribute>" to be able to fight against the sysfs
+timing with custom rules.
+
+udev 061
+========
+We changed the  internal rule storage format. Our large rule files took
+2 MB of RAM, with the change we are down to 99kB.
+
+If the device-node has been created with default name and no symlink or
+options are to remenber, it is not longer stored in the udevdb. HAL will
+need to be updated to work correctly with that change.
+
+To overrride optimization flags, OPTFLAGS may be used now.
+
+udev 060
+========
+Bugfix release.
+
+udev 059
+========
+Major changes happened with this release. The goal is to take over the
+complete kernel-event handling and provide a more efficient way to dispatch
+kernel events. Replacing most of the current shell script logic and the
+kernel forked helper with a netlink-daemon and a rule-based event handling.
+
+o udevd listens to netlink events now. The first valid netlink event
+  will make udevd ignore any message from udevsend that contains a
+  SEQNUM, to avoid duplicate events. The forked events can be disabled
+  with:
+    echo "" > /proc/sys/kernel/hotplug
+  For full support, the broken input-subsytem needs to be fixed, not to
+  bypass the driver core.
+
+o /etc/dev.d/ + /etc/hotplug.d/ directory multiplexing is completely
+  removed from udev itself and must be emulated by calling small
+  helper binaries provided in the extras folder:
+    make EXTRAS=extras/run_directory/
+  will build udev_run_devd and udev_run_hotplugd, which can be called
+  from a rule if needed:
+    RUN+="/sbin/udev_run_hotplugd"
+  The recommended way to handle this is to convert all the calls from
+  the directories to explicit udev rules and get completely rid of the
+  multiplexing. (To catch a ttyUSB event, you now no longer need to
+  fork and exit 300 tty script instances you are not interested in, it
+  is just one rule that matches exactly the device.)
+
+o udev handles now _all_ events not just events for class and block
+  devices, this way it is possible to control the complete event
+  behavior with udev rules. Especially useful for rules like:
+    ACTION="add", DEVPATH="/devices/*", MODALIAS=="?*", RUN+="/sbin/modprobe $modalias"
+
+o As used in the modalias rule, udev supports now textual
+  substitution placeholder along with the usual format chars. This
+  needs to be documented, for now it's only visible in udev_rules_parse.c.
+
+o The rule keys support now more operations. This is documented in the
+  man page. It is possible to add values to list-keys like the SYMLINK
+  and RUN list with KEY+="value" and to clear the list by assigning KEY="".
+  Also "final"-assignments are supported by using KEY:="value", which will
+  prevent changing the key by any later rule.
+
+o kernel 2.6.12 has the "detached_state" attribute removed from
+  sysfs, which was used to recognize sysfs population. We switched that
+  to wait for the "bus" link, which is only available in kernels after 2.6.11.
+  Running this udev version on older kernels may cause a short delay for
+  some events.
+
+o To provide infrastructure for persistent device naming, the id programs:
+  scsi_id, vol_id (former udev_volume_id), and ata_id (new) are able now
+  to export the probed data in environment key format:
+    pim:~ # /sbin/ata_id --export /dev/hda
+    ID_MODEL=HTS726060M9AT00
+    ID_SERIAL=MRH401M4G6UM9B
+    ID_REVISION=MH4OA6BA
+
+  The following rules:
+    KERNEL="hd*[!0-9]", IMPORT="/sbin/ata_id --export $tempnode"
+    KERNEL="hd*[!0-9]", ENV{ID_SERIAL}=="?*", SYMLINK+="disk/by-id/$env{ID_MODEL}_$env{ID_SERIAL}"
+
+  Will create:
+    kay@pim:~> tree /dev/disk
+    /dev/disk
+    |-- by-id
+    |   |-- HTS726060M9AT00_MRH401M4G6UM9B -> ../../hda
+    |   `-- IBM-Memory_Key -> ../../sda
+    |-- by-label
+    |   |-- swap -> ../../hda1
+    |   |-- date -> ../../sda1
+    |   `-- home -> ../../hda3
+    `-- by-uuid
+        |-- 2E08712B0870F2E7 -> ../../hda3
+        |-- 9352cfef-7687-47bc-a2a3-34cf136f72e1 -> ../../hda1
+        |-- E845-7A89 -> ../../sda1
+        `-- b2a61681-3812-4f13-a4ff-920d70604299 -> ../../hda2
+
+  The IMPORT= operation will import these keys in the environment and make
+  it available for later PROGRAM= and RUN= executed programs. The keys are
+  also stored in the udevdb and can be queried from there with one of the
+  next udev versions.
+
+o A few binaries are silently added to the repository, which can be used
+  to replay kernel events from initramfs instead of using coldplug. udevd
+  can be instructed now to queue-up events while the stored events from
+  initramfs are filled into the udevd-queue. This code is still under
+  development and there is no documentation now besides the code itself.
+  The additional binaries get compiled, but are not installed by default.
+
+o There is also a temporary fix for a performance problem where too many
+  events happen in parallel and every event needs to parse the rules.
+  udev can now read precompiled rules stored on disk. This is likely to be
+  replaced by a more elegant solution in a future udev version.
+
+udev 058
+========
+With kernel version 2.6.12, the sysfs file "detached_state" was removed.
+Fix for libsysfs not to expect this file was added.
+
+udev 057
+========
+All rules are applied now, but only the first matching rule with a NAME-key
+will be applied. All later rules with NAME-key are completely ignored. This
+way system supplied symlinks or permissions gets applied to user-defined
+naming rules.
+
+Note:
+Please check your rules setup, if you may need to add OPTIONS="last_rule"
+to some rules, to keep the old behavior.
+
+The rules are read on "remove"-events too. That makes is possible to match
+with keys that are available on remove (KERNEL, SUBSYSTEM, ID, ENV, ...) to
+instruct udev to ignore an event (OPTIONS="ignore_device").
+The new ACTION-key may be used to let a rule act only at a "remove"-event.
+
+The new RUN-key supports rule-based execution of programs after device-node
+handling. This is meant as a general replacement for the dev.d/-directories
+to give fine grained control over the execution of programs.
+
+The %s{}-sysfs format char replacement values are searched at any of the
+devices in the device chain now, not only at the class-device.
+
+We support log priority levels now. The value udev_log in udev.conf is used
+to determine what is printed to syslog. This makes it possible to
+run a version with compiled-in debug messages in a production environment
+which is sometimes needed to find a bug.
+It is still possible to supress the inclusion of _any_ syslog usage with
+USE_LOG=false to create the smallest possible binaries if needed.
+The configured udev_log value can be overridden with the environment variable
+UDEV_LOG.
+
+udev 056
+========
+Possible use of a system-wide klibc:
+  make USE_KLIBC=true KLCC=/usr/bin/klcc all
+will link against an external klibc and our own version will be ignored.
+
+udev 055
+========
+We support an unlimited count of symlinks now.
+
+If USE_STATIC=true is passed to a glibc build, we link statically and use
+a built-in userdb parser to resolve user and group names.
+
+The PLACE= key is gone. It can be replaced by an ID= for a long time, because
+we walk up the chain of physical devices to find a match.
+
+The KEY="<value>" format supports '=', '==', '!=,' , '+=' now. This makes it
+easy to skip certain attribute matches without composing rules with weird
+character class negations like:
+  KERNEL="[!s][!c][!d]*"
+this can now be replaced with:
+  KERNEL!="scd*"
+The current simple '=' is still supported, and should work as it does today,
+but existing rules should be converted if possible, to be better readable.
+
+We have new ENV{}== key now, to match against a maximum of 5 environment
+variables.
+
+udevstart is its own binary again, because we don't need co carry this araound
+with every forked event.
diff --git a/src/udev/README b/src/udev/README
new file mode 100644 (file)
index 0000000..38459c6
--- /dev/null
@@ -0,0 +1,101 @@
+udev - Linux userspace device management
+
+Integrating udev in the system has complex dependencies and may differ from
+distribution to distribution. A system may not be able to boot up or work
+reliably without a properly installed udev version. The upstream udev project
+does not recommend replacing a distro's udev installation with the upstream
+version.
+
+The upstream udev project's set of default rules may require a most recent
+kernel release to work properly.
+
+Tools and rules shipped by udev are not public API and may change at any time.
+Never call any private tool in /usr/lib/udev from any external application; it
+might just go away in the next release. Access to udev information is only offered
+by udevadm and libudev. Tools and rules in /usr/lib/udev and the entire contents
+of the /run/udev directory are private to udev and do change whenever needed.
+
+Requirements:
+  - Version 2.6.34 of the Linux kernel with sysfs, procfs, signalfd, inotify,
+    unix domain sockets, networking and hotplug enabled
+
+  - Some architectures might need a later kernel, that supports accept4(),
+    or need to backport the accept4() syscall wiring in the kernel.
+
+  - These options are required:
+      CONFIG_DEVTMPFS=y
+      CONFIG_HOTPLUG=y
+      CONFIG_INOTIFY_USER=y
+      CONFIG_NET=y
+      CONFIG_PROC_FS=y
+      CONFIG_SIGNALFD=y
+      CONFIG_SYSFS=y
+      CONFIG_SYSFS_DEPRECATED*=n
+      CONFIG_UEVENT_HELPER_PATH=""
+
+  - These options might be needed:
+      CONFIG_BLK_DEV_BSG=y (SCSI devices)
+      CONFIG_TMPFS_POSIX_ACL=y (user ACLs for device nodes)
+
+  - The /dev directory needs the 'devtmpfs' filesystem mounted.
+    Udev only manages the permissions and ownership of the
+    kernel-provided device nodes, and possibly creates additional symlinks.
+
+  - Udev requires /run to be writable, which is usually done by mounting a
+    'tmpfs' filesystem.
+
+  - This version of udev does not work properly with the CONFIG_SYSFS_DEPRECATED*
+    option enabled.
+
+  - The deprecated hotplug helper /sbin/hotplug should be disabled in the
+    kernel configuration, it is not needed today, and may render the system
+    unusable because the kernel may create too many processes in parallel
+    so that the system runs out-of-memory.
+
+  - The proc filesystem must be mounted on /proc, and the sysfs filesystem must
+    be mounted at /sys. No other locations are supported by a standard
+    udev installation.
+
+  - The default rule sset requires the following group names resolvable at udev startup:
+      disk, cdrom, floppy, tape, audio, video, lp, tty, dialout, and kmem.
+    Especially in LDAP setups, it is required that getgrnam() be able to resolve
+    these group names with only the rootfs mounted and while no network is
+    available.
+
+  - Some udev extras have external dependencies like:
+      libglib2, usbutils, pciutils, and gperf.
+    All these extras can be disabled with configure options.
+
+Setup:
+  - The udev daemon should be started to handle device events sent by the kernel.
+    During bootup, the events for already existing devices can be replayed, so
+    that they are configured by udev. The systemd service files contain the
+    needed commands to start the udev daemon and the coldplug sequence.
+
+  - Restarting the daemon never applies any rules to existing devices.
+
+  - New/changed rule files are picked up automatically; there is usually no
+    daemon restart or signal needed.
+
+Operation:
+  - Based on events the kernel sends out on device creation/removal, udev
+    creates/removes device nodes and symlinks in the /dev directory.
+
+  - All kernel events are matched against a set of specified rules, which
+    possibly hook into the event processing and load required kernel
+    modules to set up devices. For all devices, the kernel exports a major/minor
+    number; if needed, udev creates a device node with the default kernel
+    device name. If specified, udev applies permissions/ownership to the device
+    node, creates additional symlinks pointing to the node, and executes
+    programs to handle the device.
+
+  - The events udev handles, and the information udev merges into its device
+    database, can be accessed with libudev:
+      http://www.kernel.org/pub/linux/utils/kernel/hotplug/libudev/
+      http://www.kernel.org/pub/linux/utils/kernel/hotplug/gudev/
+
+For more details about udev and udev rules, see the udev man pages:
+      http://www.kernel.org/pub/linux/utils/kernel/hotplug/udev/
+
+Please direct any comment/question to the linux-hotplug mailing list at:
+  linux-hotplug@vger.kernel.org
diff --git a/src/udev/TODO b/src/udev/TODO
new file mode 100644 (file)
index 0000000..8b8b9c8
--- /dev/null
@@ -0,0 +1,22 @@
+ - find a way to tell udev to not cancel firmware
+   requests in initramfs
+
+ - scsi_id -> sg3_utils?
+
+ - make gtk-doc optional like kmod
+
+ - move /usr/lib/udev/devices/ to tmpfiles
+
+ - trigger --subsystem-match=usb/usb_device
+
+ - kill rules_generator
+
+ - have a $attrs{} ?
+
+ - remove RUN+="socket:"
+
+ - libudev.so.1
+     - symbol versioning
+     - return object with *_unref()
+     - udev_monitor_from_socket()
+     - udev_queue_get_failed_list_entry()
diff --git a/src/udev/autogen.sh b/src/udev/autogen.sh
new file mode 100755 (executable)
index 0000000..55ee03a
--- /dev/null
@@ -0,0 +1,44 @@
+#!/bin/sh -e
+
+if [ -f .git/hooks/pre-commit.sample -a ! -f .git/hooks/pre-commit ] ; then
+        cp -p .git/hooks/pre-commit.sample .git/hooks/pre-commit && \
+        chmod +x .git/hooks/pre-commit && \
+        echo "Activated pre-commit hook."
+fi
+
+gtkdocize
+autoreconf --install --symlink
+
+libdir() {
+        echo $(cd $1/$(gcc -print-multi-os-directory); pwd)
+}
+
+args="$args \
+--prefix=/usr \
+--sysconfdir=/etc \
+--libdir=$(libdir /usr/lib) \
+--with-selinux \
+--enable-gtk-doc"
+
+if [ -L /bin ]; then
+args="$args \
+--libexecdir=/usr/lib \
+--with-systemdsystemunitdir=/usr/lib/systemd/system \
+"
+else
+args="$args \
+--with-rootprefix= \
+---with-rootlibdir=$(libdir /lib) \
+--bindir=/sbin \
+--libexecdir=/lib \
+--with-systemdsystemunitdir=/lib/systemd/system \
+"
+fi
+
+echo
+echo "----------------------------------------------------------------"
+echo "Initialized build system. For a common configuration please run:"
+echo "----------------------------------------------------------------"
+echo
+echo "./configure CFLAGS='-g -O1' $args"
+echo
diff --git a/src/udev/configure.ac b/src/udev/configure.ac
new file mode 100644 (file)
index 0000000..b31b62f
--- /dev/null
@@ -0,0 +1,242 @@
+AC_PREREQ(2.60)
+AC_INIT([udev],
+       [182],
+       [linux-hotplug@vger.kernel.org],
+       [udev],
+       [http://www.kernel.org/pub/linux/utils/kernel/hotplug/udev.html])
+AC_CONFIG_SRCDIR([src/udevd.c])
+AC_CONFIG_AUX_DIR([build-aux])
+AM_INIT_AUTOMAKE([check-news foreign 1.11 -Wall -Wno-portability silent-rules tar-pax no-dist-gzip dist-xz subdir-objects])
+AC_USE_SYSTEM_EXTENSIONS
+AC_SYS_LARGEFILE
+AC_CONFIG_MACRO_DIR([m4])
+AM_SILENT_RULES([yes])
+LT_INIT([disable-static])
+AC_PROG_AWK
+AC_PROG_SED
+AC_PROG_MKDIR_P
+GTK_DOC_CHECK(1.10)
+AC_PREFIX_DEFAULT([/usr])
+
+AC_PATH_PROG([XSLTPROC], [xsltproc])
+AM_CONDITIONAL(HAVE_XSLTPROC, test x"$XSLTPROC" != x)
+
+AC_SEARCH_LIBS([clock_gettime], [rt], [], [AC_MSG_ERROR([POSIX RT library not found])])
+
+PKG_CHECK_MODULES(BLKID, blkid >= 2.20)
+
+PKG_CHECK_MODULES(KMOD, libkmod >= 5)
+
+AC_ARG_WITH([rootprefix],
+       AS_HELP_STRING([--with-rootprefix=DIR], [rootfs directory prefix for config files and kernel modules]),
+       [], [with_rootprefix=${ac_default_prefix}])
+AC_SUBST([rootprefix], [$with_rootprefix])
+
+AC_ARG_WITH([rootlibdir],
+       AS_HELP_STRING([--with-rootlibdir=DIR], [rootfs directory to install shared libraries]),
+       [], [with_rootlibdir=$libdir])
+AC_SUBST([rootlib_execdir], [$with_rootlibdir])
+
+AC_ARG_WITH([selinux],
+       AS_HELP_STRING([--with-selinux], [enable SELinux support]),
+       [], [with_selinux=no])
+AS_IF([test "x$with_selinux" = "xyes"], [
+       LIBS_save=$LIBS
+       AC_CHECK_LIB(selinux, getprevcon,
+              [],
+              AC_MSG_ERROR([SELinux selected but libselinux not found]))
+       LIBS=$LIBS_save
+       SELINUX_LIBS="-lselinux -lsepol"
+       AC_DEFINE(WITH_SELINUX, [1] ,[SELinux support.])
+])
+AC_SUBST([SELINUX_LIBS])
+AM_CONDITIONAL(WITH_SELINUX, [test "x$with_selinux" = "xyes"])
+
+AC_ARG_ENABLE([debug],
+       AS_HELP_STRING([--enable-debug], [enable debug messages @<:@default=disabled@:>@]),
+       [], [enable_debug=no])
+AS_IF([test "x$enable_debug" = "xyes"], [ AC_DEFINE(ENABLE_DEBUG, [1], [Debug messages.]) ])
+
+AC_ARG_ENABLE([logging],
+       AS_HELP_STRING([--disable-logging], [disable system logging @<:@default=enabled@:>@]),
+       [], enable_logging=yes)
+AS_IF([test "x$enable_logging" = "xyes"], [ AC_DEFINE(ENABLE_LOGGING, [1], [System logging.]) ])
+
+AC_ARG_ENABLE([manpages],
+        AS_HELP_STRING([--disable-manpages], [disable man pages @<:@default=enabled@:>@]),
+        [], enable_manpages=yes)
+AM_CONDITIONAL([ENABLE_MANPAGES], [test "x$enable_manpages" = "xyes"])
+
+if test "x$cross_compiling" = "xno" ; then
+       AC_CHECK_FILES([/usr/share/pci.ids], [pciids=/usr/share/pci.ids])
+       AC_CHECK_FILES([/usr/share/hwdata/pci.ids], [pciids=/usr/share/hwdata/pci.ids])
+       AC_CHECK_FILES([/usr/share/misc/pci.ids], [pciids=/usr/share/misc/pci.ids])
+fi
+
+AC_ARG_WITH(usb-ids-path,
+       [AS_HELP_STRING([--with-usb-ids-path=DIR], [Path to usb.ids file])],
+       [USB_DATABASE=${withval}],
+       [if test -n "$usbids" ; then
+              USB_DATABASE="$usbids"
+       else
+              PKG_CHECK_MODULES(USBUTILS, usbutils >= 0.82)
+              AC_SUBST([USB_DATABASE], [$($PKG_CONFIG --variable=usbids usbutils)])
+       fi])
+AC_MSG_CHECKING([for USB database location])
+AC_MSG_RESULT([$USB_DATABASE])
+AC_SUBST(USB_DATABASE)
+
+AC_ARG_WITH(pci-ids-path,
+       [AS_HELP_STRING([--with-pci-ids-path=DIR], [Path to pci.ids file])],
+       [PCI_DATABASE=${withval}],
+       [if test -n "$pciids" ; then
+              PCI_DATABASE="$pciids"
+       else
+              AC_MSG_ERROR([pci.ids not found, try --with-pci-ids-path=])
+       fi])
+AC_MSG_CHECKING([for PCI database location])
+AC_MSG_RESULT([$PCI_DATABASE])
+AC_SUBST(PCI_DATABASE)
+
+AC_ARG_WITH(firmware-path,
+       AS_HELP_STRING([--with-firmware-path=DIR[[[:DIR[...]]]]],
+          [Firmware search path (default=ROOTPREFIX/lib/firmware/updates:ROOTPREFIX/lib/firmware)]),
+       [], [with_firmware_path="$rootprefix/lib/firmware/updates:$rootprefix/lib/firmware"])
+OLD_IFS=$IFS
+IFS=:
+for i in $with_firmware_path; do
+       if test "x${FIRMWARE_PATH}" = "x"; then
+              FIRMWARE_PATH="\\\"${i}/\\\""
+       else
+              FIRMWARE_PATH="${FIRMWARE_PATH}, \\\"${i}/\\\""
+       fi
+done
+IFS=$OLD_IFS
+AC_SUBST([FIRMWARE_PATH], [$FIRMWARE_PATH])
+
+AC_ARG_WITH([systemdsystemunitdir],
+       AS_HELP_STRING([--with-systemdsystemunitdir=DIR], [Directory for systemd service files]),
+       [], [with_systemdsystemunitdir=$($PKG_CONFIG --variable=systemdsystemunitdir systemd)])
+AS_IF([test "x$with_systemdsystemunitdir" != "xno"], [ AC_SUBST([systemdsystemunitdir], [$with_systemdsystemunitdir]) ])
+AM_CONDITIONAL(WITH_SYSTEMD, [test -n "$with_systemdsystemunitdir" -a "x$with_systemdsystemunitdir" != "xno" ])
+
+# ------------------------------------------------------------------------------
+# GUdev - libudev gobject interface
+# ------------------------------------------------------------------------------
+AC_ARG_ENABLE([gudev],
+       AS_HELP_STRING([--disable-gudev], [disable Gobject libudev support @<:@default=enabled@:>@]),
+       [], [enable_gudev=yes])
+AS_IF([test "x$enable_gudev" = "xyes"], [ PKG_CHECK_MODULES([GLIB], [glib-2.0 >= 2.22.0 gobject-2.0 >= 2.22.0]) ])
+
+AC_ARG_ENABLE([introspection],
+       AS_HELP_STRING([--disable-introspection], [disable GObject introspection @<:@default=enabled@:>@]),
+       [], [enable_introspection=yes])
+AS_IF([test "x$enable_introspection" = "xyes"], [
+       PKG_CHECK_MODULES([INTROSPECTION], [gobject-introspection-1.0 >= 0.6.2])
+       AC_DEFINE([ENABLE_INTROSPECTION], [1], [enable GObject introspection support])
+       AC_SUBST([G_IR_SCANNER], [$($PKG_CONFIG --variable=g_ir_scanner gobject-introspection-1.0)])
+       AC_SUBST([G_IR_COMPILER], [$($PKG_CONFIG --variable=g_ir_compiler gobject-introspection-1.0)])
+       AC_SUBST([G_IR_GENERATE], [$($PKG_CONFIG --variable=g_ir_generate gobject-introspection-1.0)])
+       AC_SUBST([GIRDIR], [$($PKG_CONFIG --define-variable=datadir=${datadir} --variable=girdir gobject-introspection-1.0)])
+       AC_SUBST([GIRTYPELIBDIR], [$($PKG_CONFIG --define-variable=libdir=${libdir} --variable=typelibdir gobject-introspection-1.0)])
+])
+AM_CONDITIONAL([ENABLE_INTROSPECTION], [test "x$enable_introspection" = "xyes"])
+AM_CONDITIONAL([ENABLE_GUDEV], [test "x$enable_gudev" = "xyes"])
+
+# ------------------------------------------------------------------------------
+# keymap - map custom hardware's multimedia keys
+# ------------------------------------------------------------------------------
+AC_ARG_ENABLE([keymap],
+       AS_HELP_STRING([--disable-keymap], [disable keymap fixup support @<:@default=enabled@:>@]),
+       [], [enable_keymap=yes])
+AS_IF([test "x$enable_keymap" = "xyes"], [
+       AC_PATH_PROG([GPERF], [gperf])
+       if test -z "$GPERF"; then
+              AC_MSG_ERROR([gperf is needed])
+       fi
+
+       AC_CHECK_HEADER([linux/input.h], [:], AC_MSG_ERROR([kernel headers not found]))
+       AC_SUBST([INCLUDE_PREFIX], [$(echo '#include <linux/input.h>' | eval $ac_cpp -E - | sed -n '/linux\/input.h/ {s:.*"\(.*\)/linux/input.h".*:\1:; p; q}')])
+])
+AM_CONDITIONAL([ENABLE_KEYMAP], [test "x$enable_keymap" = "xyes"])
+
+# ------------------------------------------------------------------------------
+# mtd_probe - autoloads FTL module for mtd devices
+# ------------------------------------------------------------------------------
+AC_ARG_ENABLE([mtd_probe],
+       AS_HELP_STRING([--disable-mtd_probe], [disable MTD support @<:@default=enabled@:>@]),
+       [], [enable_mtd_probe=yes])
+AM_CONDITIONAL([ENABLE_MTD_PROBE], [test "x$enable_mtd_probe" = "xyes"])
+
+# ------------------------------------------------------------------------------
+# rule_generator - persistent network and optical device rule generator
+# ------------------------------------------------------------------------------
+AC_ARG_ENABLE([rule_generator],
+       AS_HELP_STRING([--enable-rule_generator], [enable persistent network + cdrom links support @<:@default=disabled@:>@]),
+       [], [enable_rule_generator=no])
+AM_CONDITIONAL([ENABLE_RULE_GENERATOR], [test "x$enable_rule_generator" = "xyes"])
+
+# ------------------------------------------------------------------------------
+# create_floppy_devices - historical floppy kernel device nodes (/dev/fd0h1440, ...)
+# ------------------------------------------------------------------------------
+AC_ARG_ENABLE([floppy],
+       AS_HELP_STRING([--enable-floppy], [enable legacy floppy support @<:@default=disabled@:>@]),
+       [], [enable_floppy=no])
+AM_CONDITIONAL([ENABLE_FLOPPY], [test "x$enable_floppy" = "xyes"])
+
+my_CFLAGS="-Wall \
+-Wmissing-declarations -Wmissing-prototypes \
+-Wnested-externs -Wpointer-arith \
+-Wpointer-arith -Wsign-compare -Wchar-subscripts \
+-Wstrict-prototypes -Wshadow \
+-Wformat-security -Wtype-limits"
+AC_SUBST([my_CFLAGS])
+
+AC_CONFIG_HEADERS(config.h)
+AC_CONFIG_FILES([
+       Makefile
+       src/docs/Makefile
+       src/docs/version.xml
+       src/gudev/docs/Makefile
+       src/gudev/docs/version.xml
+])
+
+AC_OUTPUT
+AC_MSG_RESULT([
+        $PACKAGE $VERSION
+        ========
+
+        prefix:                  ${prefix}
+        rootprefix:              ${rootprefix}
+        sysconfdir:              ${sysconfdir}
+        bindir:                  ${bindir}
+        libdir:                  ${libdir}
+        rootlibdir:              ${rootlib_execdir}
+        libexecdir:              ${libexecdir}
+        datarootdir:             ${datarootdir}
+        mandir:                  ${mandir}
+        includedir:              ${includedir}
+        include_prefix:          ${INCLUDE_PREFIX}
+        systemdsystemunitdir:    ${systemdsystemunitdir}
+        firmware path:           ${FIRMWARE_PATH}
+        usb.ids:                 ${USB_DATABASE}
+        pci.ids:                 ${PCI_DATABASE}
+
+        compiler:                ${CC}
+        cflags:                  ${CFLAGS}
+        ldflags:                 ${LDFLAGS}
+        xsltproc:                ${XSLTPROC}
+        gperf:                   ${GPERF}
+
+        logging:                 ${enable_logging}
+        debug:                   ${enable_debug}
+        selinux:                 ${with_selinux}
+
+        man pages                ${enable_manpages}
+        gudev:                   ${enable_gudev}
+        gintrospection:          ${enable_introspection}
+        keymap:                  ${enable_keymap}
+        mtd_probe:               ${enable_mtd_probe}
+        rule_generator:          ${enable_rule_generator}
+        floppy:                  ${enable_floppy}
+])
diff --git a/src/udev/m4/.gitignore b/src/udev/m4/.gitignore
new file mode 100644 (file)
index 0000000..0ca2c03
--- /dev/null
@@ -0,0 +1,4 @@
+libtool.m4
+lt*m4
+gtk-doc.m4
+
diff --git a/src/udev/src/.gitignore b/src/udev/src/.gitignore
new file mode 100644 (file)
index 0000000..beb8604
--- /dev/null
@@ -0,0 +1,5 @@
+*.[78]
+*.html
+udev.pc
+libudev.pc
+udev*.service
similarity index 100%
rename from src/COPYING
rename to src/udev/src/COPYING
similarity index 100%
rename from src/gudev/COPYING
rename to src/udev/src/gudev/COPYING
similarity index 100%
rename from src/gudev/gudev.h
rename to src/udev/src/gudev/gudev.h
similarity index 100%
rename from src/libudev.c
rename to src/udev/src/libudev.c
similarity index 100%
rename from src/libudev.h
rename to src/udev/src/libudev.h
similarity index 100%
rename from src/libudev.pc.in
rename to src/udev/src/libudev.pc.in
diff --git a/src/udev/src/sd-daemon.c b/src/udev/src/sd-daemon.c
new file mode 100644 (file)
index 0000000..763e079
--- /dev/null
@@ -0,0 +1,530 @@
+/*-*- Mode: C; c-basic-offset: 8; indent-tabs-mode: nil -*-*/
+
+/***
+  Copyright 2010 Lennart Poettering
+
+  Permission is hereby granted, free of charge, to any person
+  obtaining a copy of this software and associated documentation files
+  (the "Software"), to deal in the Software without restriction,
+  including without limitation the rights to use, copy, modify, merge,
+  publish, distribute, sublicense, and/or sell copies of the Software,
+  and to permit persons to whom the Software is furnished to do so,
+  subject to the following conditions:
+
+  The above copyright notice and this permission notice shall be
+  included in all copies or substantial portions of the Software.
+
+  THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+  EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
+  MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
+  NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS
+  BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN
+  ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
+  CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+  SOFTWARE.
+***/
+
+#ifndef _GNU_SOURCE
+#define _GNU_SOURCE
+#endif
+
+#include <sys/types.h>
+#include <sys/stat.h>
+#include <sys/socket.h>
+#include <sys/un.h>
+#ifdef __BIONIC__
+#include <linux/fcntl.h>
+#else
+#include <sys/fcntl.h>
+#endif
+#include <netinet/in.h>
+#include <stdlib.h>
+#include <errno.h>
+#include <unistd.h>
+#include <string.h>
+#include <stdarg.h>
+#include <stdio.h>
+#include <stddef.h>
+#include <limits.h>
+
+#if defined(__linux__)
+#include <mqueue.h>
+#endif
+
+#include "sd-daemon.h"
+
+#if (__GNUC__ >= 4)
+#ifdef SD_EXPORT_SYMBOLS
+/* Export symbols */
+#define _sd_export_ __attribute__ ((visibility("default")))
+#else
+/* Don't export the symbols */
+#define _sd_export_ __attribute__ ((visibility("hidden")))
+#endif
+#else
+#define _sd_export_
+#endif
+
+_sd_export_ int sd_listen_fds(int unset_environment) {
+
+#if defined(DISABLE_SYSTEMD) || !defined(__linux__)
+        return 0;
+#else
+        int r, fd;
+        const char *e;
+        char *p = NULL;
+        unsigned long l;
+
+        if (!(e = getenv("LISTEN_PID"))) {
+                r = 0;
+                goto finish;
+        }
+
+        errno = 0;
+        l = strtoul(e, &p, 10);
+
+        if (errno != 0) {
+                r = -errno;
+                goto finish;
+        }
+
+        if (!p || *p || l <= 0) {
+                r = -EINVAL;
+                goto finish;
+        }
+
+        /* Is this for us? */
+        if (getpid() != (pid_t) l) {
+                r = 0;
+                goto finish;
+        }
+
+        if (!(e = getenv("LISTEN_FDS"))) {
+                r = 0;
+                goto finish;
+        }
+
+        errno = 0;
+        l = strtoul(e, &p, 10);
+
+        if (errno != 0) {
+                r = -errno;
+                goto finish;
+        }
+
+        if (!p || *p) {
+                r = -EINVAL;
+                goto finish;
+        }
+
+        for (fd = SD_LISTEN_FDS_START; fd < SD_LISTEN_FDS_START + (int) l; fd ++) {
+                int flags;
+
+                if ((flags = fcntl(fd, F_GETFD)) < 0) {
+                        r = -errno;
+                        goto finish;
+                }
+
+                if (flags & FD_CLOEXEC)
+                        continue;
+
+                if (fcntl(fd, F_SETFD, flags | FD_CLOEXEC) < 0) {
+                        r = -errno;
+                        goto finish;
+                }
+        }
+
+        r = (int) l;
+
+finish:
+        if (unset_environment) {
+                unsetenv("LISTEN_PID");
+                unsetenv("LISTEN_FDS");
+        }
+
+        return r;
+#endif
+}
+
+_sd_export_ int sd_is_fifo(int fd, const char *path) {
+        struct stat st_fd;
+
+        if (fd < 0)
+                return -EINVAL;
+
+        memset(&st_fd, 0, sizeof(st_fd));
+        if (fstat(fd, &st_fd) < 0)
+                return -errno;
+
+        if (!S_ISFIFO(st_fd.st_mode))
+                return 0;
+
+        if (path) {
+                struct stat st_path;
+
+                memset(&st_path, 0, sizeof(st_path));
+                if (stat(path, &st_path) < 0) {
+
+                        if (errno == ENOENT || errno == ENOTDIR)
+                                return 0;
+
+                        return -errno;
+                }
+
+                return
+                        st_path.st_dev == st_fd.st_dev &&
+                        st_path.st_ino == st_fd.st_ino;
+        }
+
+        return 1;
+}
+
+_sd_export_ int sd_is_special(int fd, const char *path) {
+        struct stat st_fd;
+
+        if (fd < 0)
+                return -EINVAL;
+
+        if (fstat(fd, &st_fd) < 0)
+                return -errno;
+
+        if (!S_ISREG(st_fd.st_mode) && !S_ISCHR(st_fd.st_mode))
+                return 0;
+
+        if (path) {
+                struct stat st_path;
+
+                if (stat(path, &st_path) < 0) {
+
+                        if (errno == ENOENT || errno == ENOTDIR)
+                                return 0;
+
+                        return -errno;
+                }
+
+                if (S_ISREG(st_fd.st_mode) && S_ISREG(st_path.st_mode))
+                        return
+                                st_path.st_dev == st_fd.st_dev &&
+                                st_path.st_ino == st_fd.st_ino;
+                else if (S_ISCHR(st_fd.st_mode) && S_ISCHR(st_path.st_mode))
+                        return st_path.st_rdev == st_fd.st_rdev;
+                else
+                        return 0;
+        }
+
+        return 1;
+}
+
+static int sd_is_socket_internal(int fd, int type, int listening) {
+        struct stat st_fd;
+
+        if (fd < 0 || type < 0)
+                return -EINVAL;
+
+        if (fstat(fd, &st_fd) < 0)
+                return -errno;
+
+        if (!S_ISSOCK(st_fd.st_mode))
+                return 0;
+
+        if (type != 0) {
+                int other_type = 0;
+                socklen_t l = sizeof(other_type);
+
+                if (getsockopt(fd, SOL_SOCKET, SO_TYPE, &other_type, &l) < 0)
+                        return -errno;
+
+                if (l != sizeof(other_type))
+                        return -EINVAL;
+
+                if (other_type != type)
+                        return 0;
+        }
+
+        if (listening >= 0) {
+                int accepting = 0;
+                socklen_t l = sizeof(accepting);
+
+                if (getsockopt(fd, SOL_SOCKET, SO_ACCEPTCONN, &accepting, &l) < 0)
+                        return -errno;
+
+                if (l != sizeof(accepting))
+                        return -EINVAL;
+
+                if (!accepting != !listening)
+                        return 0;
+        }
+
+        return 1;
+}
+
+union sockaddr_union {
+        struct sockaddr sa;
+        struct sockaddr_in in4;
+        struct sockaddr_in6 in6;
+        struct sockaddr_un un;
+        struct sockaddr_storage storage;
+};
+
+_sd_export_ int sd_is_socket(int fd, int family, int type, int listening) {
+        int r;
+
+        if (family < 0)
+                return -EINVAL;
+
+        if ((r = sd_is_socket_internal(fd, type, listening)) <= 0)
+                return r;
+
+        if (family > 0) {
+                union sockaddr_union sockaddr;
+                socklen_t l;
+
+                memset(&sockaddr, 0, sizeof(sockaddr));
+                l = sizeof(sockaddr);
+
+                if (getsockname(fd, &sockaddr.sa, &l) < 0)
+                        return -errno;
+
+                if (l < sizeof(sa_family_t))
+                        return -EINVAL;
+
+                return sockaddr.sa.sa_family == family;
+        }
+
+        return 1;
+}
+
+_sd_export_ int sd_is_socket_inet(int fd, int family, int type, int listening, uint16_t port) {
+        union sockaddr_union sockaddr;
+        socklen_t l;
+        int r;
+
+        if (family != 0 && family != AF_INET && family != AF_INET6)
+                return -EINVAL;
+
+        if ((r = sd_is_socket_internal(fd, type, listening)) <= 0)
+                return r;
+
+        memset(&sockaddr, 0, sizeof(sockaddr));
+        l = sizeof(sockaddr);
+
+        if (getsockname(fd, &sockaddr.sa, &l) < 0)
+                return -errno;
+
+        if (l < sizeof(sa_family_t))
+                return -EINVAL;
+
+        if (sockaddr.sa.sa_family != AF_INET &&
+            sockaddr.sa.sa_family != AF_INET6)
+                return 0;
+
+        if (family > 0)
+                if (sockaddr.sa.sa_family != family)
+                        return 0;
+
+        if (port > 0) {
+                if (sockaddr.sa.sa_family == AF_INET) {
+                        if (l < sizeof(struct sockaddr_in))
+                                return -EINVAL;
+
+                        return htons(port) == sockaddr.in4.sin_port;
+                } else {
+                        if (l < sizeof(struct sockaddr_in6))
+                                return -EINVAL;
+
+                        return htons(port) == sockaddr.in6.sin6_port;
+                }
+        }
+
+        return 1;
+}
+
+_sd_export_ int sd_is_socket_unix(int fd, int type, int listening, const char *path, size_t length) {
+        union sockaddr_union sockaddr;
+        socklen_t l;
+        int r;
+
+        if ((r = sd_is_socket_internal(fd, type, listening)) <= 0)
+                return r;
+
+        memset(&sockaddr, 0, sizeof(sockaddr));
+        l = sizeof(sockaddr);
+
+        if (getsockname(fd, &sockaddr.sa, &l) < 0)
+                return -errno;
+
+        if (l < sizeof(sa_family_t))
+                return -EINVAL;
+
+        if (sockaddr.sa.sa_family != AF_UNIX)
+                return 0;
+
+        if (path) {
+                if (length <= 0)
+                        length = strlen(path);
+
+                if (length <= 0)
+                        /* Unnamed socket */
+                        return l == offsetof(struct sockaddr_un, sun_path);
+
+                if (path[0])
+                        /* Normal path socket */
+                        return
+                                (l >= offsetof(struct sockaddr_un, sun_path) + length + 1) &&
+                                memcmp(path, sockaddr.un.sun_path, length+1) == 0;
+                else
+                        /* Abstract namespace socket */
+                        return
+                                (l == offsetof(struct sockaddr_un, sun_path) + length) &&
+                                memcmp(path, sockaddr.un.sun_path, length) == 0;
+        }
+
+        return 1;
+}
+
+_sd_export_ int sd_is_mq(int fd, const char *path) {
+#if !defined(__linux__)
+        return 0;
+#else
+        struct mq_attr attr;
+
+        if (fd < 0)
+                return -EINVAL;
+
+        if (mq_getattr(fd, &attr) < 0)
+                return -errno;
+
+        if (path) {
+                char fpath[PATH_MAX];
+                struct stat a, b;
+
+                if (path[0] != '/')
+                        return -EINVAL;
+
+                if (fstat(fd, &a) < 0)
+                        return -errno;
+
+                strncpy(stpcpy(fpath, "/dev/mqueue"), path, sizeof(fpath) - 12);
+                fpath[sizeof(fpath)-1] = 0;
+
+                if (stat(fpath, &b) < 0)
+                        return -errno;
+
+                if (a.st_dev != b.st_dev ||
+                    a.st_ino != b.st_ino)
+                        return 0;
+        }
+
+        return 1;
+#endif
+}
+
+_sd_export_ int sd_notify(int unset_environment, const char *state) {
+#if defined(DISABLE_SYSTEMD) || !defined(__linux__) || !defined(SOCK_CLOEXEC)
+        return 0;
+#else
+        int fd = -1, r;
+        struct msghdr msghdr;
+        struct iovec iovec;
+        union sockaddr_union sockaddr;
+        const char *e;
+
+        if (!state) {
+                r = -EINVAL;
+                goto finish;
+        }
+
+        if (!(e = getenv("NOTIFY_SOCKET")))
+                return 0;
+
+        /* Must be an abstract socket, or an absolute path */
+        if ((e[0] != '@' && e[0] != '/') || e[1] == 0) {
+                r = -EINVAL;
+                goto finish;
+        }
+
+        if ((fd = socket(AF_UNIX, SOCK_DGRAM|SOCK_CLOEXEC, 0)) < 0) {
+                r = -errno;
+                goto finish;
+        }
+
+        memset(&sockaddr, 0, sizeof(sockaddr));
+        sockaddr.sa.sa_family = AF_UNIX;
+        strncpy(sockaddr.un.sun_path, e, sizeof(sockaddr.un.sun_path));
+
+        if (sockaddr.un.sun_path[0] == '@')
+                sockaddr.un.sun_path[0] = 0;
+
+        memset(&iovec, 0, sizeof(iovec));
+        iovec.iov_base = (char*) state;
+        iovec.iov_len = strlen(state);
+
+        memset(&msghdr, 0, sizeof(msghdr));
+        msghdr.msg_name = &sockaddr;
+        msghdr.msg_namelen = offsetof(struct sockaddr_un, sun_path) + strlen(e);
+
+        if (msghdr.msg_namelen > sizeof(struct sockaddr_un))
+                msghdr.msg_namelen = sizeof(struct sockaddr_un);
+
+        msghdr.msg_iov = &iovec;
+        msghdr.msg_iovlen = 1;
+
+        if (sendmsg(fd, &msghdr, MSG_NOSIGNAL) < 0) {
+                r = -errno;
+                goto finish;
+        }
+
+        r = 1;
+
+finish:
+        if (unset_environment)
+                unsetenv("NOTIFY_SOCKET");
+
+        if (fd >= 0)
+                close(fd);
+
+        return r;
+#endif
+}
+
+_sd_export_ int sd_notifyf(int unset_environment, const char *format, ...) {
+#if defined(DISABLE_SYSTEMD) || !defined(__linux__)
+        return 0;
+#else
+        va_list ap;
+        char *p = NULL;
+        int r;
+
+        va_start(ap, format);
+        r = vasprintf(&p, format, ap);
+        va_end(ap);
+
+        if (r < 0 || !p)
+                return -ENOMEM;
+
+        r = sd_notify(unset_environment, p);
+        free(p);
+
+        return r;
+#endif
+}
+
+_sd_export_ int sd_booted(void) {
+#if defined(DISABLE_SYSTEMD) || !defined(__linux__)
+        return 0;
+#else
+
+        struct stat a, b;
+
+        /* We simply test whether the systemd cgroup hierarchy is
+         * mounted */
+
+        if (lstat("/sys/fs/cgroup", &a) < 0)
+                return 0;
+
+        if (lstat("/sys/fs/cgroup/systemd", &b) < 0)
+                return 0;
+
+        return a.st_dev != b.st_dev;
+#endif
+}
diff --git a/src/udev/src/sd-daemon.h b/src/udev/src/sd-daemon.h
new file mode 100644 (file)
index 0000000..fe51159
--- /dev/null
@@ -0,0 +1,282 @@
+/*-*- Mode: C; c-basic-offset: 8; indent-tabs-mode: nil -*-*/
+
+#ifndef foosddaemonhfoo
+#define foosddaemonhfoo
+
+/***
+  Copyright 2010 Lennart Poettering
+
+  Permission is hereby granted, free of charge, to any person
+  obtaining a copy of this software and associated documentation files
+  (the "Software"), to deal in the Software without restriction,
+  including without limitation the rights to use, copy, modify, merge,
+  publish, distribute, sublicense, and/or sell copies of the Software,
+  and to permit persons to whom the Software is furnished to do so,
+  subject to the following conditions:
+
+  The above copyright notice and this permission notice shall be
+  included in all copies or substantial portions of the Software.
+
+  THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+  EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
+  MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
+  NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS
+  BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN
+  ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
+  CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+  SOFTWARE.
+***/
+
+#include <sys/types.h>
+#include <inttypes.h>
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+/*
+  Reference implementation of a few systemd related interfaces for
+  writing daemons. These interfaces are trivial to implement. To
+  simplify porting we provide this reference implementation.
+  Applications are welcome to reimplement the algorithms described
+  here if they do not want to include these two source files.
+
+  The following functionality is provided:
+
+  - Support for logging with log levels on stderr
+  - File descriptor passing for socket-based activation
+  - Daemon startup and status notification
+  - Detection of systemd boots
+
+  You may compile this with -DDISABLE_SYSTEMD to disable systemd
+  support. This makes all those calls NOPs that are directly related to
+  systemd (i.e. only sd_is_xxx() will stay useful).
+
+  Since this is drop-in code we don't want any of our symbols to be
+  exported in any case. Hence we declare hidden visibility for all of
+  them.
+
+  You may find an up-to-date version of these source files online:
+
+  http://cgit.freedesktop.org/systemd/systemd/plain/src/systemd/sd-daemon.h
+  http://cgit.freedesktop.org/systemd/systemd/plain/src/sd-daemon.c
+
+  This should compile on non-Linux systems, too, but with the
+  exception of the sd_is_xxx() calls all functions will become NOPs.
+
+  See sd-daemon(7) for more information.
+*/
+
+#ifndef _sd_printf_attr_
+#if __GNUC__ >= 4
+#define _sd_printf_attr_(a,b) __attribute__ ((format (printf, a, b)))
+#else
+#define _sd_printf_attr_(a,b)
+#endif
+#endif
+
+/*
+  Log levels for usage on stderr:
+
+          fprintf(stderr, SD_NOTICE "Hello World!\n");
+
+  This is similar to printk() usage in the kernel.
+*/
+#define SD_EMERG   "<0>"  /* system is unusable */
+#define SD_ALERT   "<1>"  /* action must be taken immediately */
+#define SD_CRIT    "<2>"  /* critical conditions */
+#define SD_ERR     "<3>"  /* error conditions */
+#define SD_WARNING "<4>"  /* warning conditions */
+#define SD_NOTICE  "<5>"  /* normal but significant condition */
+#define SD_INFO    "<6>"  /* informational */
+#define SD_DEBUG   "<7>"  /* debug-level messages */
+
+/* The first passed file descriptor is fd 3 */
+#define SD_LISTEN_FDS_START 3
+
+/*
+  Returns how many file descriptors have been passed, or a negative
+  errno code on failure. Optionally, removes the $LISTEN_FDS and
+  $LISTEN_PID file descriptors from the environment (recommended, but
+  problematic in threaded environments). If r is the return value of
+  this function you'll find the file descriptors passed as fds
+  SD_LISTEN_FDS_START to SD_LISTEN_FDS_START+r-1. Returns a negative
+  errno style error code on failure. This function call ensures that
+  the FD_CLOEXEC flag is set for the passed file descriptors, to make
+  sure they are not passed on to child processes. If FD_CLOEXEC shall
+  not be set, the caller needs to unset it after this call for all file
+  descriptors that are used.
+
+  See sd_listen_fds(3) for more information.
+*/
+int sd_listen_fds(int unset_environment);
+
+/*
+  Helper call for identifying a passed file descriptor. Returns 1 if
+  the file descriptor is a FIFO in the file system stored under the
+  specified path, 0 otherwise. If path is NULL a path name check will
+  not be done and the call only verifies if the file descriptor
+  refers to a FIFO. Returns a negative errno style error code on
+  failure.
+
+  See sd_is_fifo(3) for more information.
+*/
+int sd_is_fifo(int fd, const char *path);
+
+/*
+  Helper call for identifying a passed file descriptor. Returns 1 if
+  the file descriptor is a special character device on the file
+  system stored under the specified path, 0 otherwise.
+  If path is NULL a path name check will not be done and the call
+  only verifies if the file descriptor refers to a special character.
+  Returns a negative errno style error code on failure.
+
+  See sd_is_special(3) for more information.
+*/
+int sd_is_special(int fd, const char *path);
+
+/*
+  Helper call for identifying a passed file descriptor. Returns 1 if
+  the file descriptor is a socket of the specified family (AF_INET,
+  ...) and type (SOCK_DGRAM, SOCK_STREAM, ...), 0 otherwise. If
+  family is 0 a socket family check will not be done. If type is 0 a
+  socket type check will not be done and the call only verifies if
+  the file descriptor refers to a socket. If listening is > 0 it is
+  verified that the socket is in listening mode. (i.e. listen() has
+  been called) If listening is == 0 it is verified that the socket is
+  not in listening mode. If listening is < 0 no listening mode check
+  is done. Returns a negative errno style error code on failure.
+
+  See sd_is_socket(3) for more information.
+*/
+int sd_is_socket(int fd, int family, int type, int listening);
+
+/*
+  Helper call for identifying a passed file descriptor. Returns 1 if
+  the file descriptor is an Internet socket, of the specified family
+  (either AF_INET or AF_INET6) and the specified type (SOCK_DGRAM,
+  SOCK_STREAM, ...), 0 otherwise. If version is 0 a protocol version
+  check is not done. If type is 0 a socket type check will not be
+  done. If port is 0 a socket port check will not be done. The
+  listening flag is used the same way as in sd_is_socket(). Returns a
+  negative errno style error code on failure.
+
+  See sd_is_socket_inet(3) for more information.
+*/
+int sd_is_socket_inet(int fd, int family, int type, int listening, uint16_t port);
+
+/*
+  Helper call for identifying a passed file descriptor. Returns 1 if
+  the file descriptor is an AF_UNIX socket of the specified type
+  (SOCK_DGRAM, SOCK_STREAM, ...) and path, 0 otherwise. If type is 0
+  a socket type check will not be done. If path is NULL a socket path
+  check will not be done. For normal AF_UNIX sockets set length to
+  0. For abstract namespace sockets set length to the length of the
+  socket name (including the initial 0 byte), and pass the full
+  socket path in path (including the initial 0 byte). The listening
+  flag is used the same way as in sd_is_socket(). Returns a negative
+  errno style error code on failure.
+
+  See sd_is_socket_unix(3) for more information.
+*/
+int sd_is_socket_unix(int fd, int type, int listening, const char *path, size_t length);
+
+/*
+  Helper call for identifying a passed file descriptor. Returns 1 if
+  the file descriptor is a POSIX Message Queue of the specified name,
+  0 otherwise. If path is NULL a message queue name check is not
+  done. Returns a negative errno style error code on failure.
+*/
+int sd_is_mq(int fd, const char *path);
+
+/*
+  Informs systemd about changed daemon state. This takes a number of
+  newline separated environment-style variable assignments in a
+  string. The following variables are known:
+
+     READY=1      Tells systemd that daemon startup is finished (only
+                  relevant for services of Type=notify). The passed
+                  argument is a boolean "1" or "0". Since there is
+                  little value in signaling non-readiness the only
+                  value daemons should send is "READY=1".
+
+     STATUS=...   Passes a single-line status string back to systemd
+                  that describes the daemon state. This is free-from
+                  and can be used for various purposes: general state
+                  feedback, fsck-like programs could pass completion
+                  percentages and failing programs could pass a human
+                  readable error message. Example: "STATUS=Completed
+                  66% of file system check..."
+
+     ERRNO=...    If a daemon fails, the errno-style error code,
+                  formatted as string. Example: "ERRNO=2" for ENOENT.
+
+     BUSERROR=... If a daemon fails, the D-Bus error-style error
+                  code. Example: "BUSERROR=org.freedesktop.DBus.Error.TimedOut"
+
+     MAINPID=...  The main pid of a daemon, in case systemd did not
+                  fork off the process itself. Example: "MAINPID=4711"
+
+     WATCHDOG=1   Tells systemd to update the watchdog timestamp.
+                  Services using this feature should do this in
+                  regular intervals. A watchdog framework can use the
+                  timestamps to detect failed services.
+
+  Daemons can choose to send additional variables. However, it is
+  recommended to prefix variable names not listed above with X_.
+
+  Returns a negative errno-style error code on failure. Returns > 0
+  if systemd could be notified, 0 if it couldn't possibly because
+  systemd is not running.
+
+  Example: When a daemon finished starting up, it could issue this
+  call to notify systemd about it:
+
+     sd_notify(0, "READY=1");
+
+  See sd_notifyf() for more complete examples.
+
+  See sd_notify(3) for more information.
+*/
+int sd_notify(int unset_environment, const char *state);
+
+/*
+  Similar to sd_notify() but takes a format string.
+
+  Example 1: A daemon could send the following after initialization:
+
+     sd_notifyf(0, "READY=1\n"
+                   "STATUS=Processing requests...\n"
+                   "MAINPID=%lu",
+                   (unsigned long) getpid());
+
+  Example 2: A daemon could send the following shortly before
+  exiting, on failure:
+
+     sd_notifyf(0, "STATUS=Failed to start up: %s\n"
+                   "ERRNO=%i",
+                   strerror(errno),
+                   errno);
+
+  See sd_notifyf(3) for more information.
+*/
+int sd_notifyf(int unset_environment, const char *format, ...) _sd_printf_attr_(2,3);
+
+/*
+  Returns > 0 if the system was booted with systemd. Returns < 0 on
+  error. Returns 0 if the system was not booted with systemd. Note
+  that all of the functions above handle non-systemd boots just
+  fine. You should NOT protect them with a call to this function. Also
+  note that this function checks whether the system, not the user
+  session is controlled by systemd. However the functions above work
+  for both user and system services.
+
+  See sd_booted(3) for more information.
+*/
+int sd_booted(void);
+
+#ifdef __cplusplus
+}
+#endif
+
+#endif
similarity index 100%
rename from src/test-udev.c
rename to src/udev/src/test-udev.c
similarity index 100%
rename from src/udev-ctrl.c
rename to src/udev/src/udev-ctrl.c
similarity index 100%
rename from src/udev-event.c
rename to src/udev/src/udev-event.c
similarity index 100%
rename from src/udev-node.c
rename to src/udev/src/udev-node.c
similarity index 100%
rename from src/udev-rules.c
rename to src/udev/src/udev-rules.c
similarity index 100%
rename from src/udev-watch.c
rename to src/udev/src/udev-watch.c
similarity index 100%
rename from src/udev.conf
rename to src/udev/src/udev.conf
similarity index 100%
rename from src/udev.h
rename to src/udev/src/udev.h
similarity index 100%
rename from src/udev.pc.in
rename to src/udev/src/udev.pc.in
similarity index 100%
rename from src/udev.xml
rename to src/udev/src/udev.xml
similarity index 100%
rename from src/udevadm.c
rename to src/udev/src/udevadm.c
similarity index 100%
rename from src/udevadm.xml
rename to src/udev/src/udevadm.xml
similarity index 100%
rename from src/udevd.c
rename to src/udev/src/udevd.c
similarity index 100%
rename from src/udevd.xml
rename to src/udev/src/udevd.xml
similarity index 100%
rename from test/.gitignore
rename to src/udev/test/.gitignore
similarity index 100%
rename from test/sys.tar.xz
rename to src/udev/test/sys.tar.xz
similarity index 100%
rename from test/udev-test.pl
rename to src/udev/test/udev-test.pl
diff --git a/src/umount.c b/src/umount.c
new file mode 100644 (file)
index 0000000..0a63d23
--- /dev/null
@@ -0,0 +1,644 @@
+/*-*- Mode: C; c-basic-offset: 8; indent-tabs-mode: nil -*-*/
+
+/***
+  This file is part of systemd.
+
+  Copyright 2010 ProFUSION embedded systems
+
+  systemd is free software; you can redistribute it and/or modify it
+  under the terms of the GNU 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 <http://www.gnu.org/licenses/>.
+***/
+
+#include <errno.h>
+#include <fcntl.h>
+#include <string.h>
+#include <sys/mount.h>
+#include <sys/swap.h>
+#include <unistd.h>
+#include <linux/loop.h>
+#include <linux/dm-ioctl.h>
+#include <libudev.h>
+
+#include "list.h"
+#include "mount-setup.h"
+#include "umount.h"
+#include "util.h"
+
+typedef struct MountPoint {
+        char *path;
+        dev_t devnum;
+        bool skip_ro;
+        LIST_FIELDS (struct MountPoint, mount_point);
+} MountPoint;
+
+static void mount_point_free(MountPoint **head, MountPoint *m) {
+        assert(head);
+        assert(m);
+
+        LIST_REMOVE(MountPoint, mount_point, *head, m);
+
+        free(m->path);
+        free(m);
+}
+
+static void mount_points_list_free(MountPoint **head) {
+        assert(head);
+
+        while (*head)
+                mount_point_free(head, *head);
+}
+
+static int mount_points_list_get(MountPoint **head) {
+        FILE *proc_self_mountinfo;
+        char *path, *p;
+        unsigned int i;
+        int r;
+
+        assert(head);
+
+        if (!(proc_self_mountinfo = fopen("/proc/self/mountinfo", "re")))
+                return -errno;
+
+        for (i = 1;; i++) {
+                int k;
+                MountPoint *m;
+                char *root;
+                bool skip_ro;
+
+                path = p = NULL;
+
+                if ((k = fscanf(proc_self_mountinfo,
+                                "%*s "       /* (1) mount id */
+                                "%*s "       /* (2) parent id */
+                                "%*s "       /* (3) major:minor */
+                                "%ms "       /* (4) root */
+                                "%ms "       /* (5) mount point */
+                                "%*s"        /* (6) mount options */
+                                "%*[^-]"     /* (7) optional fields */
+                                "- "         /* (8) separator */
+                                "%*s "       /* (9) file system type */
+                                "%*s"        /* (10) mount source */
+                                "%*s"        /* (11) mount options 2 */
+                                "%*[^\n]",   /* some rubbish at the end */
+                                &root,
+                                &path)) != 2) {
+                        if (k == EOF)
+                                break;
+
+                        log_warning("Failed to parse /proc/self/mountinfo:%u.", i);
+
+                        free(path);
+                        continue;
+                }
+
+                /* If we encounter a bind mount, don't try to remount
+                 * the source dir too early */
+                skip_ro = !streq(root, "/");
+                free(root);
+
+                p = cunescape(path);
+                free(path);
+
+                if (!p) {
+                        r = -ENOMEM;
+                        goto finish;
+                }
+
+                if (mount_point_is_api(p) || mount_point_ignore(p)) {
+                        free(p);
+                        continue;
+                }
+
+                if (!(m = new0(MountPoint, 1))) {
+                        free(p);
+                        r = -ENOMEM;
+                        goto finish;
+                }
+
+                m->path = p;
+                m->skip_ro = skip_ro;
+                LIST_PREPEND(MountPoint, mount_point, *head, m);
+        }
+
+        r = 0;
+
+finish:
+        fclose(proc_self_mountinfo);
+
+        return r;
+}
+
+static int swap_list_get(MountPoint **head) {
+        FILE *proc_swaps;
+        unsigned int i;
+        int r;
+
+        assert(head);
+
+        if (!(proc_swaps = fopen("/proc/swaps", "re")))
+                return (errno == ENOENT) ? 0 : -errno;
+
+        (void) fscanf(proc_swaps, "%*s %*s %*s %*s %*s\n");
+
+        for (i = 2;; i++) {
+                MountPoint *swap;
+                char *dev = NULL, *d;
+                int k;
+
+                if ((k = fscanf(proc_swaps,
+                                "%ms " /* device/file */
+                                "%*s " /* type of swap */
+                                "%*s " /* swap size */
+                                "%*s " /* used */
+                                "%*s\n", /* priority */
+                                &dev)) != 1) {
+
+                        if (k == EOF)
+                                break;
+
+                        log_warning("Failed to parse /proc/swaps:%u.", i);
+
+                        free(dev);
+                        continue;
+                }
+
+                if (endswith(dev, "(deleted)")) {
+                        free(dev);
+                        continue;
+                }
+
+                d = cunescape(dev);
+                free(dev);
+
+                if (!d) {
+                        r = -ENOMEM;
+                        goto finish;
+                }
+
+                if (!(swap = new0(MountPoint, 1))) {
+                        free(d);
+                        r = -ENOMEM;
+                        goto finish;
+                }
+
+                swap->path = d;
+                LIST_PREPEND(MountPoint, mount_point, *head, swap);
+        }
+
+        r = 0;
+
+finish:
+        fclose(proc_swaps);
+
+        return r;
+}
+
+static int loopback_list_get(MountPoint **head) {
+        int r;
+        struct udev *udev;
+        struct udev_enumerate *e = NULL;
+        struct udev_list_entry *item = NULL, *first = NULL;
+
+        assert(head);
+
+        if (!(udev = udev_new())) {
+                r = -ENOMEM;
+                goto finish;
+        }
+
+        if (!(e = udev_enumerate_new(udev))) {
+                r = -ENOMEM;
+                goto finish;
+        }
+
+        if (udev_enumerate_add_match_subsystem(e, "block") < 0 ||
+            udev_enumerate_add_match_sysname(e, "loop*") < 0) {
+                r = -EIO;
+                goto finish;
+        }
+
+        if (udev_enumerate_scan_devices(e) < 0) {
+                r = -EIO;
+                goto finish;
+        }
+
+        first = udev_enumerate_get_list_entry(e);
+        udev_list_entry_foreach(item, first) {
+                MountPoint *lb;
+                struct udev_device *d;
+                char *loop;
+                const char *dn;
+
+                if (!(d = udev_device_new_from_syspath(udev, udev_list_entry_get_name(item)))) {
+                        r = -ENOMEM;
+                        goto finish;
+                }
+
+                if (!(dn = udev_device_get_devnode(d))) {
+                        udev_device_unref(d);
+                        continue;
+                }
+
+                loop = strdup(dn);
+                udev_device_unref(d);
+
+                if (!loop) {
+                        r = -ENOMEM;
+                        goto finish;
+                }
+
+                if (!(lb = new0(MountPoint, 1))) {
+                        free(loop);
+                        r = -ENOMEM;
+                        goto finish;
+                }
+
+                lb->path = loop;
+                LIST_PREPEND(MountPoint, mount_point, *head, lb);
+        }
+
+        r = 0;
+
+finish:
+        if (e)
+                udev_enumerate_unref(e);
+
+        if (udev)
+                udev_unref(udev);
+
+        return r;
+}
+
+static int dm_list_get(MountPoint **head) {
+        int r;
+        struct udev *udev;
+        struct udev_enumerate *e = NULL;
+        struct udev_list_entry *item = NULL, *first = NULL;
+
+        assert(head);
+
+        if (!(udev = udev_new())) {
+                r = -ENOMEM;
+                goto finish;
+        }
+
+        if (!(e = udev_enumerate_new(udev))) {
+                r = -ENOMEM;
+                goto finish;
+        }
+
+        if (udev_enumerate_add_match_subsystem(e, "block") < 0 ||
+            udev_enumerate_add_match_sysname(e, "dm-*") < 0) {
+                r = -EIO;
+                goto finish;
+        }
+
+        if (udev_enumerate_scan_devices(e) < 0) {
+                r = -EIO;
+                goto finish;
+        }
+
+        first = udev_enumerate_get_list_entry(e);
+
+        udev_list_entry_foreach(item, first) {
+                MountPoint *m;
+                struct udev_device *d;
+                dev_t devnum;
+                char *node;
+                const char *dn;
+
+                if (!(d = udev_device_new_from_syspath(udev, udev_list_entry_get_name(item)))) {
+                        r = -ENOMEM;
+                        goto finish;
+                }
+
+                devnum = udev_device_get_devnum(d);
+                dn = udev_device_get_devnode(d);
+
+                if (major(devnum) == 0 || !dn) {
+                        udev_device_unref(d);
+                        continue;
+                }
+
+                node = strdup(dn);
+                udev_device_unref(d);
+
+                if (!node) {
+                        r = -ENOMEM;
+                        goto finish;
+                }
+
+                if (!(m = new(MountPoint, 1))) {
+                        free(node);
+                        r = -ENOMEM;
+                        goto finish;
+                }
+
+                m->path = node;
+                m->devnum = devnum;
+                LIST_PREPEND(MountPoint, mount_point, *head, m);
+        }
+
+        r = 0;
+
+finish:
+        if (e)
+                udev_enumerate_unref(e);
+
+        if (udev)
+                udev_unref(udev);
+
+        return r;
+}
+
+static int delete_loopback(const char *device) {
+        int fd, r;
+
+        if ((fd = open(device, O_RDONLY|O_CLOEXEC)) < 0)
+                return errno == ENOENT ? 0 : -errno;
+
+        r = ioctl(fd, LOOP_CLR_FD, 0);
+        close_nointr_nofail(fd);
+
+        if (r >= 0)
+                return 1;
+
+        /* ENXIO: not bound, so no error */
+        if (errno == ENXIO)
+                return 0;
+
+        return -errno;
+}
+
+static int delete_dm(dev_t devnum) {
+        int fd, r;
+        struct dm_ioctl dm;
+
+        assert(major(devnum) != 0);
+
+        if ((fd = open("/dev/mapper/control", O_RDWR|O_CLOEXEC)) < 0)
+                return -errno;
+
+        zero(dm);
+        dm.version[0] = DM_VERSION_MAJOR;
+        dm.version[1] = DM_VERSION_MINOR;
+        dm.version[2] = DM_VERSION_PATCHLEVEL;
+
+        dm.data_size = sizeof(dm);
+        dm.dev = devnum;
+
+        r = ioctl(fd, DM_DEV_REMOVE, &dm);
+        close_nointr_nofail(fd);
+
+        return r >= 0 ? 0 : -errno;
+}
+
+static int mount_points_list_umount(MountPoint **head, bool *changed, bool log_error) {
+        MountPoint *m, *n;
+        int n_failed = 0;
+
+        assert(head);
+
+        LIST_FOREACH_SAFE(mount_point, m, n, *head) {
+                if (path_equal(m->path, "/")
+#ifndef HAVE_SPLIT_USR
+                    || path_equal(m->path, "/usr")
+#endif
+                ) {
+                        n_failed++;
+                        continue;
+                }
+
+                /* Trying to umount. Forcing to umount if busy (only for NFS mounts) */
+                if (umount2(m->path, MNT_FORCE) == 0) {
+                        log_info("Unmounted %s.", m->path);
+                        if (changed)
+                                *changed = true;
+
+                        mount_point_free(head, m);
+                } else if (log_error) {
+                        log_warning("Could not unmount %s: %m", m->path);
+                        n_failed++;
+                }
+        }
+
+        return n_failed;
+}
+
+static int mount_points_list_remount_read_only(MountPoint **head, bool *changed) {
+        MountPoint *m, *n;
+        int n_failed = 0;
+
+        assert(head);
+
+        LIST_FOREACH_SAFE(mount_point, m, n, *head) {
+
+                if (m->skip_ro) {
+                        n_failed++;
+                        continue;
+                }
+
+                /* Trying to remount read-only */
+                if (mount(NULL, m->path, NULL, MS_MGC_VAL|MS_REMOUNT|MS_RDONLY, NULL) == 0) {
+                        if (changed)
+                                *changed = true;
+
+                        mount_point_free(head, m);
+                } else {
+                        log_warning("Could not remount as read-only %s: %m", m->path);
+                        n_failed++;
+                }
+        }
+
+        return n_failed;
+}
+
+static int swap_points_list_off(MountPoint **head, bool *changed) {
+        MountPoint *m, *n;
+        int n_failed = 0;
+
+        assert(head);
+
+        LIST_FOREACH_SAFE(mount_point, m, n, *head) {
+                if (swapoff(m->path) == 0) {
+                        if (changed)
+                                *changed = true;
+
+                        mount_point_free(head, m);
+                } else {
+                        log_warning("Could not deactivate swap %s: %m", m->path);
+                        n_failed++;
+                }
+        }
+
+        return n_failed;
+}
+
+static int loopback_points_list_detach(MountPoint **head, bool *changed) {
+        MountPoint *m, *n;
+        int n_failed = 0, k;
+        struct stat root_st;
+
+        assert(head);
+
+        k = lstat("/", &root_st);
+
+        LIST_FOREACH_SAFE(mount_point, m, n, *head) {
+                int r;
+                struct stat loopback_st;
+
+                if (k >= 0 &&
+                    major(root_st.st_dev) != 0 &&
+                    lstat(m->path, &loopback_st) >= 0 &&
+                    root_st.st_dev == loopback_st.st_rdev) {
+                        n_failed ++;
+                        continue;
+                }
+
+                if ((r = delete_loopback(m->path)) >= 0) {
+
+                        if (r > 0 && changed)
+                                *changed = true;
+
+                        mount_point_free(head, m);
+                } else {
+                        log_warning("Could not delete loopback %s: %m", m->path);
+                        n_failed++;
+                }
+        }
+
+        return n_failed;
+}
+
+static int dm_points_list_detach(MountPoint **head, bool *changed) {
+        MountPoint *m, *n;
+        int n_failed = 0, k;
+        struct stat root_st;
+
+        assert(head);
+
+        k = lstat("/", &root_st);
+
+        LIST_FOREACH_SAFE(mount_point, m, n, *head) {
+                int r;
+
+                if (k >= 0 &&
+                    major(root_st.st_dev) != 0 &&
+                    root_st.st_dev == m->devnum) {
+                        n_failed ++;
+                        continue;
+                }
+
+                if ((r = delete_dm(m->devnum)) >= 0) {
+
+                        if (r > 0 && changed)
+                                *changed = true;
+
+                        mount_point_free(head, m);
+                } else {
+                        log_warning("Could not delete dm %s: %m", m->path);
+                        n_failed++;
+                }
+        }
+
+        return n_failed;
+}
+
+int umount_all(bool *changed) {
+        int r;
+        bool umount_changed;
+
+        LIST_HEAD(MountPoint, mp_list_head);
+
+        LIST_HEAD_INIT(MountPoint, mp_list_head);
+
+        r = mount_points_list_get(&mp_list_head);
+        if (r < 0)
+                goto end;
+
+        /* retry umount, until nothing can be umounted anymore */
+        do {
+                umount_changed = false;
+
+                mount_points_list_umount(&mp_list_head, &umount_changed, false);
+                if (umount_changed)
+                        *changed = true;
+
+        } while (umount_changed);
+
+        /* umount one more time with logging enabled */
+        r = mount_points_list_umount(&mp_list_head, &umount_changed, true);
+        if (r <= 0)
+                goto end;
+
+        r = mount_points_list_remount_read_only(&mp_list_head, changed);
+
+  end:
+        mount_points_list_free(&mp_list_head);
+
+        return r;
+}
+
+int swapoff_all(bool *changed) {
+        int r;
+        LIST_HEAD(MountPoint, swap_list_head);
+
+        LIST_HEAD_INIT(MountPoint, swap_list_head);
+
+        r = swap_list_get(&swap_list_head);
+        if (r < 0)
+                goto end;
+
+        r = swap_points_list_off(&swap_list_head, changed);
+
+  end:
+        mount_points_list_free(&swap_list_head);
+
+        return r;
+}
+
+int loopback_detach_all(bool *changed) {
+        int r;
+        LIST_HEAD(MountPoint, loopback_list_head);
+
+        LIST_HEAD_INIT(MountPoint, loopback_list_head);
+
+        r = loopback_list_get(&loopback_list_head);
+        if (r < 0)
+                goto end;
+
+        r = loopback_points_list_detach(&loopback_list_head, changed);
+
+  end:
+        mount_points_list_free(&loopback_list_head);
+
+        return r;
+}
+
+int dm_detach_all(bool *changed) {
+        int r;
+        LIST_HEAD(MountPoint, dm_list_head);
+
+        LIST_HEAD_INIT(MountPoint, dm_list_head);
+
+        r = dm_list_get(&dm_list_head);
+        if (r < 0)
+                goto end;
+
+        r = dm_points_list_detach(&dm_list_head, changed);
+
+  end:
+        mount_points_list_free(&dm_list_head);
+
+        return r;
+}
diff --git a/src/umount.h b/src/umount.h
new file mode 100644 (file)
index 0000000..acdf09a
--- /dev/null
@@ -0,0 +1,33 @@
+/*-*- Mode: C; c-basic-offset: 8; indent-tabs-mode: nil -*-*/
+
+#ifndef fooumounthfoo
+#define fooumounthfoo
+
+/***
+  This file is part of systemd.
+
+  Copyright 2010 ProFUSION embedded systems
+
+  systemd is free software; you can redistribute it and/or modify it
+  under the terms of the GNU 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 <http://www.gnu.org/licenses/>.
+***/
+
+int umount_all(bool *changed);
+
+int swapoff_all(bool *changed);
+
+int loopback_detach_all(bool *changed);
+
+int dm_detach_all(bool *changed);
+
+#endif
diff --git a/src/unit-name.c b/src/unit-name.c
new file mode 100644 (file)
index 0000000..1cbb804
--- /dev/null
@@ -0,0 +1,448 @@
+/*-*- 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 <http://www.gnu.org/licenses/>.
+***/
+
+#include <errno.h>
+#include <string.h>
+#include <assert.h>
+
+#include "util.h"
+#include "unit-name.h"
+
+#define VALID_CHARS                             \
+        "0123456789"                            \
+        "abcdefghijklmnopqrstuvwxyz"            \
+        "ABCDEFGHIJKLMNOPQRSTUVWXYZ"            \
+        ":-_.\\"
+
+bool unit_name_is_valid_no_type(const char *n, bool template_ok) {
+        const char *e, *i, *at;
+
+        /* Valid formats:
+         *
+         *         string@instance.suffix
+         *         string.suffix
+         */
+
+        assert(n);
+
+        if (strlen(n) >= UNIT_NAME_MAX)
+                return false;
+
+        e = strrchr(n, '.');
+        if (!e || e == n)
+                return false;
+
+        for (i = n, at = NULL; i < e; i++) {
+
+                if (*i == '@' && !at)
+                        at = i;
+
+                if (!strchr("@" VALID_CHARS, *i))
+                        return false;
+        }
+
+        if (at) {
+                if (at == n)
+                        return false;
+
+                if (!template_ok && at+1 == e)
+                        return false;
+        }
+
+        return true;
+}
+
+bool unit_instance_is_valid(const char *i) {
+        assert(i);
+
+        /* The max length depends on the length of the string, so we
+         * don't really check this here. */
+
+        if (i[0] == 0)
+                return false;
+
+        /* We allow additional @ in the instance string, we do not
+         * allow them in the prefix! */
+
+        for (; *i; i++)
+                if (!strchr("@" VALID_CHARS, *i))
+                        return false;
+
+        return true;
+}
+
+bool unit_prefix_is_valid(const char *p) {
+
+        /* We don't allow additional @ in the instance string */
+
+        if (p[0] == 0)
+                return false;
+
+        for (; *p; p++)
+                if (!strchr(VALID_CHARS, *p))
+                        return false;
+
+        return true;
+}
+
+int unit_name_to_instance(const char *n, char **instance) {
+        const char *p, *d;
+        char *i;
+
+        assert(n);
+        assert(instance);
+
+        /* Everything past the first @ and before the last . is the instance */
+        if (!(p = strchr(n, '@'))) {
+                *instance = NULL;
+                return 0;
+        }
+
+        assert_se(d = strrchr(n, '.'));
+        assert(p < d);
+
+        if (!(i = strndup(p+1, d-p-1)))
+                return -ENOMEM;
+
+        *instance = i;
+        return 0;
+}
+
+char *unit_name_to_prefix_and_instance(const char *n) {
+        const char *d;
+
+        assert(n);
+
+        assert_se(d = strrchr(n, '.'));
+
+        return strndup(n, d - n);
+}
+
+char *unit_name_to_prefix(const char *n) {
+        const char *p;
+
+        if ((p = strchr(n, '@')))
+                return strndup(n, p - n);
+
+        return unit_name_to_prefix_and_instance(n);
+}
+
+char *unit_name_change_suffix(const char *n, const char *suffix) {
+        char *e, *r;
+        size_t a, b;
+
+        assert(n);
+        assert(unit_name_is_valid_no_type(n, true));
+        assert(suffix);
+
+        assert_se(e = strrchr(n, '.'));
+        a = e - n;
+        b = strlen(suffix);
+
+        if (!(r = new(char, a + b + 1)))
+                return NULL;
+
+        memcpy(r, n, a);
+        memcpy(r+a, suffix, b+1);
+
+        return r;
+}
+
+char *unit_name_build(const char *prefix, const char *instance, const char *suffix) {
+        assert(prefix);
+        assert(unit_prefix_is_valid(prefix));
+        assert(!instance || unit_instance_is_valid(instance));
+        assert(suffix);
+
+        if (!instance)
+                return strappend(prefix, suffix);
+
+        return join(prefix, "@", instance, suffix, NULL);
+}
+
+static char* do_escape(const char *f, char *t) {
+        assert(f);
+        assert(t);
+
+        for (; *f; f++) {
+                if (*f == '/')
+                        *(t++) = '-';
+                else if (*f == '-' || *f == '\\' || !strchr(VALID_CHARS, *f)) {
+                        *(t++) = '\\';
+                        *(t++) = 'x';
+                        *(t++) = hexchar(*f >> 4);
+                        *(t++) = hexchar(*f);
+                } else
+                        *(t++) = *f;
+        }
+
+        return t;
+}
+
+char *unit_name_build_escape(const char *prefix, const char *instance, const char *suffix) {
+        char *r, *t;
+        size_t a, b, c;
+
+        assert(prefix);
+        assert(suffix);
+
+        /* Takes a arbitrary string for prefix and instance plus a
+         * suffix and makes a nice string suitable as unit name of it,
+         * escaping all weird chars on the way.
+         *
+         * / becomes ., and all chars not allowed in a unit name get
+         * escaped as \xFF, including \ and ., of course. This
+         * escaping is hence reversible.
+         *
+         * This is primarily useful to make nice unit names from
+         * strings, but is actually useful for any kind of string.
+         */
+
+        a = strlen(prefix);
+        c = strlen(suffix);
+
+        if (instance) {
+                b = strlen(instance);
+
+                if (!(r = new(char, a*4 + 1 + b*4 + c + 1)))
+                        return NULL;
+
+                t = do_escape(prefix, r);
+                *(t++) = '@';
+                t = do_escape(instance, t);
+        } else {
+
+                if (!(r = new(char, a*4 + c + 1)))
+                        return NULL;
+
+                t = do_escape(prefix, r);
+        }
+
+        strcpy(t, suffix);
+        return r;
+}
+
+char *unit_name_escape(const char *f) {
+        char *r, *t;
+
+        if (!(r = new(char, strlen(f)*4+1)))
+                return NULL;
+
+        t = do_escape(f, r);
+        *t = 0;
+
+        return r;
+
+}
+
+char *unit_name_unescape(const char *f) {
+        char *r, *t;
+
+        assert(f);
+
+        if (!(r = strdup(f)))
+                return NULL;
+
+        for (t = r; *f; f++) {
+                if (*f == '-')
+                        *(t++) = '/';
+                else if (*f == '\\') {
+                        int a, b;
+
+                        if (f[1] != 'x' ||
+                            (a = unhexchar(f[2])) < 0 ||
+                            (b = unhexchar(f[3])) < 0) {
+                                /* Invalid escape code, let's take it literal then */
+                                *(t++) = '\\';
+                        } else {
+                                *(t++) = (char) ((a << 4) | b);
+                                f += 3;
+                        }
+                } else
+                        *(t++) = *f;
+        }
+
+        *t = 0;
+
+        return r;
+}
+
+bool unit_name_is_template(const char *n) {
+        const char *p;
+
+        assert(n);
+
+        if (!(p = strchr(n, '@')))
+                return false;
+
+        return p[1] == '.';
+}
+
+char *unit_name_replace_instance(const char *f, const char *i) {
+        const char *p, *e;
+        char *r, *k;
+        size_t a;
+
+        assert(f);
+
+        p = strchr(f, '@');
+        assert_se(e = strrchr(f, '.'));
+
+        a = p - f;
+
+        if (p) {
+                size_t b;
+
+                b = strlen(i);
+
+                if (!(r = new(char, a + 1 + b + strlen(e) + 1)))
+                        return NULL;
+
+                k = mempcpy(r, f, a + 1);
+                k = mempcpy(k, i, b);
+        } else {
+
+                if (!(r = new(char, a + strlen(e) + 1)))
+                        return NULL;
+
+                k = mempcpy(r, f, a);
+        }
+
+        strcpy(k, e);
+        return r;
+}
+
+char *unit_name_template(const char *f) {
+        const char *p, *e;
+        char *r;
+        size_t a;
+
+        if (!(p = strchr(f, '@')))
+                return strdup(f);
+
+        assert_se(e = strrchr(f, '.'));
+        a = p - f + 1;
+
+        if (!(r = new(char, a + strlen(e) + 1)))
+                return NULL;
+
+        strcpy(mempcpy(r, f, a), e);
+        return r;
+
+}
+
+char *unit_name_from_path(const char *path, const char *suffix) {
+        char *p, *r;
+
+        assert(path);
+        assert(suffix);
+
+        if (!(p = strdup(path)))
+                return NULL;
+
+        path_kill_slashes(p);
+
+        path = p[0] == '/' ? p + 1 : p;
+
+        if (path[0] == 0) {
+                free(p);
+                return strappend("-", suffix);
+        }
+
+        r = unit_name_build_escape(path, NULL, suffix);
+        free(p);
+
+        return r;
+}
+
+char *unit_name_from_path_instance(const char *prefix, const char *path, const char *suffix) {
+        char *p, *r;
+
+        assert(path);
+        assert(suffix);
+
+        if (!(p = strdup(path)))
+                return NULL;
+
+        path_kill_slashes(p);
+
+        path = p[0] == '/' ? p + 1 : p;
+
+        if (path[0] == 0) {
+                free(p);
+                return unit_name_build_escape(prefix, "-", suffix);
+        }
+
+        r = unit_name_build_escape(prefix, path, suffix);
+        free(p);
+
+        return r;
+}
+
+char *unit_name_to_path(const char *name) {
+        char *w, *e;
+
+        assert(name);
+
+        if (!(w = unit_name_to_prefix(name)))
+                return NULL;
+
+        e = unit_name_unescape(w);
+        free(w);
+
+        if (!e)
+                return NULL;
+
+        if (e[0] != '/') {
+                w = strappend("/", e);
+                free(e);
+
+                if (!w)
+                        return NULL;
+
+                e = w;
+        }
+
+        return e;
+}
+
+char *unit_name_path_unescape(const char *f) {
+        char *e;
+
+        assert(f);
+
+        if (!(e = unit_name_unescape(f)))
+                return NULL;
+
+        if (e[0] != '/') {
+                char *w;
+
+                w = strappend("/", e);
+                free(e);
+
+                if (!w)
+                        return NULL;
+
+                e = w;
+        }
+
+        return e;
+}
diff --git a/src/unit-name.h b/src/unit-name.h
new file mode 100644 (file)
index 0000000..e369910
--- /dev/null
@@ -0,0 +1,57 @@
+/*-*- Mode: C; c-basic-offset: 8; indent-tabs-mode: nil -*-*/
+
+#ifndef foounitnamehfoo
+#define foounitnamehfoo
+
+/***
+  This file is part 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 <http://www.gnu.org/licenses/>.
+***/
+
+#include <stdbool.h>
+
+#define UNIT_NAME_MAX 256
+
+int unit_name_to_instance(const char *n, char **instance);
+char* unit_name_to_prefix(const char *n);
+char* unit_name_to_prefix_and_instance(const char *n);
+
+bool unit_name_is_valid_no_type(const char *n, bool template_ok);
+bool unit_prefix_is_valid(const char *p);
+bool unit_instance_is_valid(const char *i);
+
+char *unit_name_change_suffix(const char *n, const char *suffix);
+
+char *unit_name_build(const char *prefix, const char *instance, const char *suffix);
+char *unit_name_build_escape(const char *prefix, const char *instance, const char *suffix);
+
+char *unit_name_escape(const char *f);
+char *unit_name_unescape(const char *f);
+
+char *unit_name_path_unescape(const char *f);
+
+bool unit_name_is_template(const char *n);
+
+char *unit_name_replace_instance(const char *f, const char *i);
+
+char *unit_name_template(const char *f);
+
+char *unit_name_from_path(const char *path, const char *suffix);
+char *unit_name_from_path_instance(const char *prefix, const char *path, const char *suffix);
+char *unit_name_to_path(const char *name);
+
+#endif
diff --git a/src/unit.c b/src/unit.c
new file mode 100644 (file)
index 0000000..9e33701
--- /dev/null
@@ -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 <http://www.gnu.org/licenses/>.
+***/
+
+#include <assert.h>
+#include <errno.h>
+#include <string.h>
+#include <sys/epoll.h>
+#include <sys/timerfd.h>
+#include <sys/poll.h>
+#include <stdlib.h>
+#include <unistd.h>
+#include <sys/stat.h>
+
+#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
new file mode 100644 (file)
index 0000000..756f465
--- /dev/null
@@ -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 <http://www.gnu.org/licenses/>.
+***/
+
+#include <stdbool.h>
+#include <stdlib.h>
+
+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/update-utmp.c b/src/update-utmp.c
new file mode 100644 (file)
index 0000000..0d177d6
--- /dev/null
@@ -0,0 +1,423 @@
+/*-*- 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 <http://www.gnu.org/licenses/>.
+***/
+
+#include <assert.h>
+#include <errno.h>
+#include <string.h>
+#include <sys/types.h>
+#include <unistd.h>
+
+#include <dbus/dbus.h>
+
+#ifdef HAVE_AUDIT
+#include <libaudit.h>
+#endif
+
+#include "log.h"
+#include "macro.h"
+#include "util.h"
+#include "special.h"
+#include "utmp-wtmp.h"
+#include "dbus-common.h"
+
+typedef struct Context {
+        DBusConnection *bus;
+#ifdef HAVE_AUDIT
+        int audit_fd;
+#endif
+} Context;
+
+static usec_t get_startup_time(Context *c) {
+        const char
+                *interface = "org.freedesktop.systemd1.Manager",
+                *property = "StartupTimestamp";
+
+        DBusError error;
+        usec_t t = 0;
+        DBusMessage *m = NULL, *reply = NULL;
+        DBusMessageIter iter, sub;
+
+        dbus_error_init(&error);
+
+        assert(c);
+
+        if (!(m = dbus_message_new_method_call(
+                              "org.freedesktop.systemd1",
+                              "/org/freedesktop/systemd1",
+                              "org.freedesktop.DBus.Properties",
+                              "Get"))) {
+                log_error("Could not allocate message.");
+                goto finish;
+        }
+
+        if (!dbus_message_append_args(m,
+                                      DBUS_TYPE_STRING, &interface,
+                                      DBUS_TYPE_STRING, &property,
+                                      DBUS_TYPE_INVALID)) {
+                log_error("Could not append arguments to message.");
+                goto finish;
+        }
+
+        if (!(reply = dbus_connection_send_with_reply_and_block(c->bus, m, -1, &error))) {
+                log_error("Failed to send command: %s", bus_error_message(&error));
+                goto finish;
+        }
+
+        if (!dbus_message_iter_init(reply, &iter) ||
+            dbus_message_iter_get_arg_type(&iter) != DBUS_TYPE_VARIANT)  {
+                log_error("Failed to parse reply.");
+                goto finish;
+        }
+
+        dbus_message_iter_recurse(&iter, &sub);
+
+        if (dbus_message_iter_get_arg_type(&sub) != DBUS_TYPE_UINT64)  {
+                log_error("Failed to parse reply.");
+                goto finish;
+        }
+
+        dbus_message_iter_get_basic(&sub, &t);
+
+finish:
+        if (m)
+                dbus_message_unref(m);
+
+        if (reply)
+                dbus_message_unref(reply);
+
+        dbus_error_free(&error);
+
+        return t;
+}
+
+static int get_current_runlevel(Context *c) {
+        static const struct {
+                const int runlevel;
+                const char *special;
+        } table[] = {
+                /* The first target of this list that is active or has
+                 * a job scheduled wins. We prefer runlevels 5 and 3
+                 * here over the others, since these are the main
+                 * runlevels used on Fedora. It might make sense to
+                 * change the order on some distributions. */
+                { '5', SPECIAL_RUNLEVEL5_TARGET },
+                { '3', SPECIAL_RUNLEVEL3_TARGET },
+                { '4', SPECIAL_RUNLEVEL4_TARGET },
+                { '2', SPECIAL_RUNLEVEL2_TARGET },
+                { 'S', SPECIAL_RESCUE_TARGET },
+        };
+        const char
+                *interface = "org.freedesktop.systemd1.Unit",
+                *property = "ActiveState";
+
+        DBusMessage *m = NULL, *reply = NULL;
+        int r = 0;
+        unsigned i;
+        DBusError error;
+
+        assert(c);
+
+        dbus_error_init(&error);
+
+        for (i = 0; i < ELEMENTSOF(table); i++) {
+                const char *path = NULL, *state;
+                DBusMessageIter iter, sub;
+
+                if (!(m = dbus_message_new_method_call(
+                                      "org.freedesktop.systemd1",
+                                      "/org/freedesktop/systemd1",
+                                      "org.freedesktop.systemd1.Manager",
+                                      "GetUnit"))) {
+                        log_error("Could not allocate message.");
+                        r = -ENOMEM;
+                        goto finish;
+                }
+
+                if (!dbus_message_append_args(m,
+                                              DBUS_TYPE_STRING, &table[i].special,
+                                              DBUS_TYPE_INVALID)) {
+                        log_error("Could not append arguments to message.");
+                        r = -ENOMEM;
+                        goto finish;
+                }
+
+                if (!(reply = dbus_connection_send_with_reply_and_block(c->bus, m, -1, &error))) {
+                        dbus_error_free(&error);
+                        continue;
+                }
+
+                if (!dbus_message_get_args(reply, &error,
+                                           DBUS_TYPE_OBJECT_PATH, &path,
+                                           DBUS_TYPE_INVALID)) {
+                        log_error("Failed to parse reply: %s", bus_error_message(&error));
+                        r = -EIO;
+                        goto finish;
+                }
+
+                dbus_message_unref(m);
+                if (!(m = dbus_message_new_method_call(
+                                      "org.freedesktop.systemd1",
+                                      path,
+                                      "org.freedesktop.DBus.Properties",
+                                      "Get"))) {
+                        log_error("Could not allocate message.");
+                        r = -ENOMEM;
+                        goto finish;
+                }
+
+                if (!dbus_message_append_args(m,
+                                              DBUS_TYPE_STRING, &interface,
+                                              DBUS_TYPE_STRING, &property,
+                                              DBUS_TYPE_INVALID)) {
+                        log_error("Could not append arguments to message.");
+                        r = -ENOMEM;
+                        goto finish;
+                }
+
+                dbus_message_unref(reply);
+                if (!(reply = dbus_connection_send_with_reply_and_block(c->bus, m, -1, &error))) {
+                        log_error("Failed to send command: %s", bus_error_message(&error));
+                        r = -EIO;
+                        goto finish;
+                }
+
+                if (!dbus_message_iter_init(reply, &iter) ||
+                    dbus_message_iter_get_arg_type(&iter) != DBUS_TYPE_VARIANT)  {
+                        log_error("Failed to parse reply.");
+                        r = -EIO;
+                        goto finish;
+                }
+
+                dbus_message_iter_recurse(&iter, &sub);
+
+                if (dbus_message_iter_get_arg_type(&sub) != DBUS_TYPE_STRING)  {
+                        log_error("Failed to parse reply.");
+                        r = -EIO;
+                        goto finish;
+                }
+
+                dbus_message_iter_get_basic(&sub, &state);
+
+                if (streq(state, "active") || streq(state, "reloading"))
+                        r = table[i].runlevel;
+
+                dbus_message_unref(m);
+                dbus_message_unref(reply);
+                m = reply = NULL;
+
+                if (r)
+                        break;
+        }
+
+finish:
+        if (m)
+                dbus_message_unref(m);
+
+        if (reply)
+                dbus_message_unref(reply);
+
+        dbus_error_free(&error);
+
+        return r;
+}
+
+static int on_reboot(Context *c) {
+        int r = 0, q;
+        usec_t t;
+
+        assert(c);
+
+        /* We finished start-up, so let's write the utmp
+         * record and send the audit msg */
+
+#ifdef HAVE_AUDIT
+        if (c->audit_fd >= 0)
+                if (audit_log_user_message(c->audit_fd, AUDIT_SYSTEM_BOOT, "init", NULL, NULL, NULL, 1) < 0) {
+                        log_error("Failed to send audit message: %m");
+                        r = -errno;
+                }
+#endif
+
+        /* If this call fails it will return 0, which
+         * utmp_put_reboot() will then fix to the current time */
+        t = get_startup_time(c);
+
+        if ((q = utmp_put_reboot(t)) < 0) {
+                log_error("Failed to write utmp record: %s", strerror(-q));
+                r = q;
+        }
+
+        return r;
+}
+
+static int on_shutdown(Context *c) {
+        int r = 0, q;
+
+        assert(c);
+
+        /* We started shut-down, so let's write the utmp
+         * record and send the audit msg */
+
+#ifdef HAVE_AUDIT
+        if (c->audit_fd >= 0)
+                if (audit_log_user_message(c->audit_fd, AUDIT_SYSTEM_SHUTDOWN, "init", NULL, NULL, NULL, 1) < 0) {
+                        log_error("Failed to send audit message: %m");
+                        r = -errno;
+                }
+#endif
+
+        if ((q = utmp_put_shutdown()) < 0) {
+                log_error("Failed to write utmp record: %s", strerror(-q));
+                r = q;
+        }
+
+        return r;
+}
+
+static int on_runlevel(Context *c) {
+        int r = 0, q, previous, runlevel;
+
+        assert(c);
+
+        /* We finished changing runlevel, so let's write the
+         * utmp record and send the audit msg */
+
+        /* First, get last runlevel */
+        if ((q = utmp_get_runlevel(&previous, NULL)) < 0) {
+
+                if (q != -ESRCH && q != -ENOENT) {
+                        log_error("Failed to get current runlevel: %s", strerror(-q));
+                        return q;
+                }
+
+                /* Hmm, we didn't find any runlevel, that means we
+                 * have been rebooted */
+                r = on_reboot(c);
+                previous = 0;
+        }
+
+        /* Secondly, get new runlevel */
+        if ((runlevel = get_current_runlevel(c)) < 0)
+                return runlevel;
+
+        if (previous == runlevel)
+                return 0;
+
+#ifdef HAVE_AUDIT
+        if (c->audit_fd >= 0) {
+                char *s = NULL;
+
+                if (asprintf(&s, "old-level=%c new-level=%c",
+                             previous > 0 ? previous : 'N',
+                             runlevel > 0 ? runlevel : 'N') < 0)
+                        return -ENOMEM;
+
+                if (audit_log_user_message(c->audit_fd, AUDIT_SYSTEM_RUNLEVEL, s, NULL, NULL, NULL, 1) < 0) {
+                        log_error("Failed to send audit message: %m");
+                        r = -errno;
+                }
+
+                free(s);
+        }
+#endif
+
+        if ((q = utmp_put_runlevel(runlevel, previous)) < 0) {
+                log_error("Failed to write utmp record: %s", strerror(-q));
+                r = q;
+        }
+
+        return r;
+}
+
+int main(int argc, char *argv[]) {
+        int r;
+        DBusError error;
+        Context c;
+
+        dbus_error_init(&error);
+
+        zero(c);
+#ifdef HAVE_AUDIT
+        c.audit_fd = -1;
+#endif
+
+        if (getppid() != 1) {
+                log_error("This program should be invoked by init only.");
+                return EXIT_FAILURE;
+        }
+
+        if (argc != 2) {
+                log_error("This program requires one argument.");
+                return EXIT_FAILURE;
+        }
+
+        log_set_target(LOG_TARGET_AUTO);
+        log_parse_environment();
+        log_open();
+
+        umask(0022);
+
+#ifdef HAVE_AUDIT
+        if ((c.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
+
+        if (bus_connect(DBUS_BUS_SYSTEM, &c.bus, NULL, &error) < 0) {
+                log_error("Failed to get D-Bus connection: %s", bus_error_message(&error));
+                r = -EIO;
+                goto finish;
+        }
+
+        log_debug("systemd-update-utmp running as pid %lu", (unsigned long) getpid());
+
+        if (streq(argv[1], "reboot"))
+                r = on_reboot(&c);
+        else if (streq(argv[1], "shutdown"))
+                r = on_shutdown(&c);
+        else if (streq(argv[1], "runlevel"))
+                r = on_runlevel(&c);
+        else {
+                log_error("Unknown command %s", argv[1]);
+                r = -EINVAL;
+        }
+
+        log_debug("systemd-update-utmp stopped as pid %lu", (unsigned long) getpid());
+
+finish:
+#ifdef HAVE_AUDIT
+        if (c.audit_fd >= 0)
+                audit_close(c.audit_fd);
+#endif
+
+        if (c.bus) {
+                dbus_connection_flush(c.bus);
+                dbus_connection_close(c.bus);
+                dbus_connection_unref(c.bus);
+        }
+
+        dbus_error_free(&error);
+        dbus_shutdown();
+
+        return r < 0 ? EXIT_FAILURE : EXIT_SUCCESS;
+}
diff --git a/src/user.conf b/src/user.conf
new file mode 100644 (file)
index 0000000..9508a02
--- /dev/null
@@ -0,0 +1,17 @@
+#  This file is part of systemd.
+#
+#  systemd is free software; you can redistribute it and/or modify it
+#  under the terms of the GNU General Public License as published by
+#  the Free Software Foundation; either version 2 of the License, or
+#  (at your option) any later version.
+#
+# See systemd.conf(5) for details
+
+[Manager]
+#LogLevel=info
+#LogTarget=console
+#LogColor=yes
+#LogLocation=no
+#DefaultControllers=cpu
+#DefaultStandardOutput=inherit
+#DefaultStandardError=inherit
diff --git a/src/utf8.c b/src/utf8.c
new file mode 100644 (file)
index 0000000..11619dc
--- /dev/null
@@ -0,0 +1,214 @@
+/*-*- Mode: C; c-basic-offset: 8; indent-tabs-mode: nil -*-*/
+
+/***
+  This file is part of systemd.
+
+  Copyright 2012 Lennart Poettering
+
+  systemd is free software; you can redistribute it and/or modify it
+  under the terms of the GNU 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 <http://www.gnu.org/licenses/>.
+***/
+
+/* This file is based on the GLIB utf8 validation functions. The
+ * original license text follows. */
+
+/* gutf8.c - Operations on UTF-8 strings.
+ *
+ * Copyright (C) 1999 Tom Tromey
+ * Copyright (C) 2000 Red Hat, Inc.
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2 of the License, or (at your option) any later version.
+ *
+ * This library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.         See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this library; if not, write to the
+ * Free Software Foundation, Inc., 59 Temple Place - Suite 330,
+ * Boston, MA 02111-1307, USA.
+ */
+
+#include <errno.h>
+#include <stdlib.h>
+#include <inttypes.h>
+#include <string.h>
+#include <stdbool.h>
+
+#include "utf8.h"
+
+#define FILTER_CHAR '_'
+
+static inline bool is_unicode_valid(uint32_t ch) {
+
+        if (ch >= 0x110000) /* End of unicode space */
+                return false;
+        if ((ch & 0xFFFFF800) == 0xD800) /* Reserved area for UTF-16 */
+                return false;
+        if ((ch >= 0xFDD0) && (ch <= 0xFDEF)) /* Reserved */
+                return false;
+        if ((ch & 0xFFFE) == 0xFFFE) /* BOM (Byte Order Mark) */
+                return false;
+
+        return true;
+}
+
+static inline bool is_continuation_char(uint8_t ch) {
+        if ((ch & 0xc0) != 0x80) /* 10xxxxxx */
+                return false;
+        return true;
+}
+
+static inline void merge_continuation_char(uint32_t *u_ch, uint8_t ch) {
+        *u_ch <<= 6;
+        *u_ch |= ch & 0x3f;
+}
+
+static char* utf8_validate(const char *str, char *output) {
+        uint32_t val = 0;
+        uint32_t min = 0;
+        const uint8_t *p, *last;
+        int size;
+        uint8_t *o;
+
+        assert(str);
+
+        o = (uint8_t*) output;
+        for (p = (const uint8_t*) str; *p; p++) {
+                if (*p < 128) {
+                        if (o)
+                                *o = *p;
+                } else {
+                        last = p;
+
+                        if ((*p & 0xe0) == 0xc0) { /* 110xxxxx two-char seq. */
+                                size = 2;
+                                min = 128;
+                                val = (uint32_t) (*p & 0x1e);
+                                goto ONE_REMAINING;
+                        } else if ((*p & 0xf0) == 0xe0) { /* 1110xxxx three-char seq.*/
+                                size = 3;
+                                min = (1 << 11);
+                                val = (uint32_t) (*p & 0x0f);
+                                goto TWO_REMAINING;
+                        } else if ((*p & 0xf8) == 0xf0) { /* 11110xxx four-char seq */
+                                size = 4;
+                                min = (1 << 16);
+                                val = (uint32_t) (*p & 0x07);
+                        } else
+                                goto error;
+
+                        p++;
+                        if (!is_continuation_char(*p))
+                                goto error;
+                        merge_continuation_char(&val, *p);
+
+                TWO_REMAINING:
+                        p++;
+                        if (!is_continuation_char(*p))
+                                goto error;
+                        merge_continuation_char(&val, *p);
+
+                ONE_REMAINING:
+                        p++;
+                        if (!is_continuation_char(*p))
+                                goto error;
+                        merge_continuation_char(&val, *p);
+
+                        if (val < min)
+                                goto error;
+
+                        if (!is_unicode_valid(val))
+                                goto error;
+
+                        if (o) {
+                                memcpy(o, last, (size_t) size);
+                                o += size;
+                        }
+
+                        continue;
+
+                error:
+                        if (o) {
+                                *o = FILTER_CHAR;
+                                p = last; /* We retry at the next character */
+                        } else
+                                goto failure;
+                }
+
+                if (o)
+                        o++;
+        }
+
+        if (o) {
+                *o = '\0';
+                return output;
+        }
+
+        return (char*) str;
+
+failure:
+        return NULL;
+}
+
+char* utf8_is_valid (const char *str) {
+        return utf8_validate(str, NULL);
+}
+
+char* utf8_filter (const char *str) {
+        char *new_str;
+
+        assert(str);
+
+        new_str = malloc(strlen(str) + 1);
+        if (!new_str)
+                return NULL;
+
+        return utf8_validate(str, new_str);
+}
+
+char *ascii_is_valid(const char *str) {
+        const char *p;
+
+        assert(str);
+
+        for (p = str; *p; p++)
+                if ((unsigned char) *p >= 128)
+                        return NULL;
+
+        return (char*) str;
+}
+
+char *ascii_filter(const char *str) {
+        char *r, *s, *d;
+        size_t l;
+
+        assert(str);
+
+        l = strlen(str);
+        r = malloc(l + 1);
+        if (!r)
+                return NULL;
+
+        for (s = r, d = r; *s; s++)
+                if ((unsigned char) *s < 128)
+                        *(d++) = *s;
+
+        *d = 0;
+
+        return r;
+}
diff --git a/src/utf8.h b/src/utf8.h
new file mode 100644 (file)
index 0000000..9a72bec
--- /dev/null
@@ -0,0 +1,33 @@
+/*-*- Mode: C; c-basic-offset: 8; indent-tabs-mode: nil -*-*/
+
+#ifndef fooutf8hfoo
+#define fooutf8hfoo
+
+/***
+  This file is part of systemd.
+
+  Copyright 2012 Lennart Poettering
+
+  systemd is free software; you can redistribute it and/or modify it
+  under the terms of the GNU 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 <http://www.gnu.org/licenses/>.
+***/
+
+#include "macro.h"
+
+char *utf8_is_valid(const char *s) _pure_;
+char *ascii_is_valid(const char *s) _pure_;
+
+char *utf8_filter(const char *s);
+char *ascii_filter(const char *s);
+
+#endif
diff --git a/src/util.c b/src/util.c
new file mode 100644 (file)
index 0000000..dfc1dc6
--- /dev/null
@@ -0,0 +1,6256 @@
+/*-*- 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 <http://www.gnu.org/licenses/>.
+***/
+
+#include <assert.h>
+#include <string.h>
+#include <unistd.h>
+#include <errno.h>
+#include <stdlib.h>
+#include <signal.h>
+#include <stdio.h>
+#include <syslog.h>
+#include <sched.h>
+#include <sys/resource.h>
+#include <linux/sched.h>
+#include <sys/types.h>
+#include <sys/stat.h>
+#include <fcntl.h>
+#include <dirent.h>
+#include <sys/ioctl.h>
+#include <linux/vt.h>
+#include <linux/tiocl.h>
+#include <termios.h>
+#include <stdarg.h>
+#include <sys/inotify.h>
+#include <sys/poll.h>
+#include <libgen.h>
+#include <ctype.h>
+#include <sys/prctl.h>
+#include <sys/utsname.h>
+#include <pwd.h>
+#include <netinet/ip.h>
+#include <linux/kd.h>
+#include <dlfcn.h>
+#include <sys/wait.h>
+#include <sys/capability.h>
+#include <sys/time.h>
+#include <linux/rtc.h>
+#include <glob.h>
+#include <grp.h>
+#include <sys/mman.h>
+
+#include "macro.h"
+#include "util.h"
+#include "ioprio.h"
+#include "missing.h"
+#include "log.h"
+#include "strv.h"
+#include "label.h"
+#include "exit-status.h"
+#include "hashmap.h"
+
+int saved_argc = 0;
+char **saved_argv = NULL;
+
+size_t page_size(void) {
+        static __thread size_t pgsz = 0;
+        long r;
+
+        if (_likely_(pgsz > 0))
+                return pgsz;
+
+        assert_se((r = sysconf(_SC_PAGESIZE)) > 0);
+
+        pgsz = (size_t) r;
+
+        return pgsz;
+}
+
+bool streq_ptr(const char *a, const char *b) {
+
+        /* Like streq(), but tries to make sense of NULL pointers */
+
+        if (a && b)
+                return streq(a, b);
+
+        if (!a && !b)
+                return true;
+
+        return false;
+}
+
+usec_t now(clockid_t clock_id) {
+        struct timespec ts;
+
+        assert_se(clock_gettime(clock_id, &ts) == 0);
+
+        return timespec_load(&ts);
+}
+
+dual_timestamp* dual_timestamp_get(dual_timestamp *ts) {
+        assert(ts);
+
+        ts->realtime = now(CLOCK_REALTIME);
+        ts->monotonic = now(CLOCK_MONOTONIC);
+
+        return ts;
+}
+
+dual_timestamp* dual_timestamp_from_realtime(dual_timestamp *ts, usec_t u) {
+        int64_t delta;
+        assert(ts);
+
+        ts->realtime = u;
+
+        if (u == 0)
+                ts->monotonic = 0;
+        else {
+                delta = (int64_t) now(CLOCK_REALTIME) - (int64_t) u;
+
+                ts->monotonic = now(CLOCK_MONOTONIC);
+
+                if ((int64_t) ts->monotonic > delta)
+                        ts->monotonic -= delta;
+                else
+                        ts->monotonic = 0;
+        }
+
+        return ts;
+}
+
+usec_t timespec_load(const struct timespec *ts) {
+        assert(ts);
+
+        return
+                (usec_t) ts->tv_sec * USEC_PER_SEC +
+                (usec_t) ts->tv_nsec / NSEC_PER_USEC;
+}
+
+struct timespec *timespec_store(struct timespec *ts, usec_t u)  {
+        assert(ts);
+
+        ts->tv_sec = (time_t) (u / USEC_PER_SEC);
+        ts->tv_nsec = (long int) ((u % USEC_PER_SEC) * NSEC_PER_USEC);
+
+        return ts;
+}
+
+usec_t timeval_load(const struct timeval *tv) {
+        assert(tv);
+
+        return
+                (usec_t) tv->tv_sec * USEC_PER_SEC +
+                (usec_t) tv->tv_usec;
+}
+
+struct timeval *timeval_store(struct timeval *tv, usec_t u) {
+        assert(tv);
+
+        tv->tv_sec = (time_t) (u / USEC_PER_SEC);
+        tv->tv_usec = (suseconds_t) (u % USEC_PER_SEC);
+
+        return tv;
+}
+
+bool endswith(const char *s, const char *postfix) {
+        size_t sl, pl;
+
+        assert(s);
+        assert(postfix);
+
+        sl = strlen(s);
+        pl = strlen(postfix);
+
+        if (pl == 0)
+                return true;
+
+        if (sl < pl)
+                return false;
+
+        return memcmp(s + sl - pl, postfix, pl) == 0;
+}
+
+bool startswith(const char *s, const char *prefix) {
+        size_t sl, pl;
+
+        assert(s);
+        assert(prefix);
+
+        sl = strlen(s);
+        pl = strlen(prefix);
+
+        if (pl == 0)
+                return true;
+
+        if (sl < pl)
+                return false;
+
+        return memcmp(s, prefix, pl) == 0;
+}
+
+bool startswith_no_case(const char *s, const char *prefix) {
+        size_t sl, pl;
+        unsigned i;
+
+        assert(s);
+        assert(prefix);
+
+        sl = strlen(s);
+        pl = strlen(prefix);
+
+        if (pl == 0)
+                return true;
+
+        if (sl < pl)
+                return false;
+
+        for(i = 0; i < pl; ++i) {
+                if (tolower(s[i]) != tolower(prefix[i]))
+                        return false;
+        }
+
+        return true;
+}
+
+bool first_word(const char *s, const char *word) {
+        size_t sl, wl;
+
+        assert(s);
+        assert(word);
+
+        sl = strlen(s);
+        wl = strlen(word);
+
+        if (sl < wl)
+                return false;
+
+        if (wl == 0)
+                return true;
+
+        if (memcmp(s, word, wl) != 0)
+                return false;
+
+        return s[wl] == 0 ||
+                strchr(WHITESPACE, s[wl]);
+}
+
+int close_nointr(int fd) {
+        assert(fd >= 0);
+
+        for (;;) {
+                int r;
+
+                r = close(fd);
+                if (r >= 0)
+                        return r;
+
+                if (errno != EINTR)
+                        return -errno;
+        }
+}
+
+void close_nointr_nofail(int fd) {
+        int saved_errno = errno;
+
+        /* like close_nointr() but cannot fail, and guarantees errno
+         * is unchanged */
+
+        assert_se(close_nointr(fd) == 0);
+
+        errno = saved_errno;
+}
+
+void close_many(const int fds[], unsigned n_fd) {
+        unsigned i;
+
+        for (i = 0; i < n_fd; i++)
+                close_nointr_nofail(fds[i]);
+}
+
+int parse_boolean(const char *v) {
+        assert(v);
+
+        if (streq(v, "1") || v[0] == 'y' || v[0] == 'Y' || v[0] == 't' || v[0] == 'T' || !strcasecmp(v, "on"))
+                return 1;
+        else if (streq(v, "0") || v[0] == 'n' || v[0] == 'N' || v[0] == 'f' || v[0] == 'F' || !strcasecmp(v, "off"))
+                return 0;
+
+        return -EINVAL;
+}
+
+int parse_pid(const char *s, pid_t* ret_pid) {
+        unsigned long ul = 0;
+        pid_t pid;
+        int r;
+
+        assert(s);
+        assert(ret_pid);
+
+        if ((r = safe_atolu(s, &ul)) < 0)
+                return r;
+
+        pid = (pid_t) ul;
+
+        if ((unsigned long) pid != ul)
+                return -ERANGE;
+
+        if (pid <= 0)
+                return -ERANGE;
+
+        *ret_pid = pid;
+        return 0;
+}
+
+int parse_uid(const char *s, uid_t* ret_uid) {
+        unsigned long ul = 0;
+        uid_t uid;
+        int r;
+
+        assert(s);
+        assert(ret_uid);
+
+        if ((r = safe_atolu(s, &ul)) < 0)
+                return r;
+
+        uid = (uid_t) ul;
+
+        if ((unsigned long) uid != ul)
+                return -ERANGE;
+
+        *ret_uid = uid;
+        return 0;
+}
+
+int safe_atou(const char *s, unsigned *ret_u) {
+        char *x = NULL;
+        unsigned long l;
+
+        assert(s);
+        assert(ret_u);
+
+        errno = 0;
+        l = strtoul(s, &x, 0);
+
+        if (!x || *x || errno)
+                return errno ? -errno : -EINVAL;
+
+        if ((unsigned long) (unsigned) l != l)
+                return -ERANGE;
+
+        *ret_u = (unsigned) l;
+        return 0;
+}
+
+int safe_atoi(const char *s, int *ret_i) {
+        char *x = NULL;
+        long l;
+
+        assert(s);
+        assert(ret_i);
+
+        errno = 0;
+        l = strtol(s, &x, 0);
+
+        if (!x || *x || errno)
+                return errno ? -errno : -EINVAL;
+
+        if ((long) (int) l != l)
+                return -ERANGE;
+
+        *ret_i = (int) l;
+        return 0;
+}
+
+int safe_atollu(const char *s, long long unsigned *ret_llu) {
+        char *x = NULL;
+        unsigned long long l;
+
+        assert(s);
+        assert(ret_llu);
+
+        errno = 0;
+        l = strtoull(s, &x, 0);
+
+        if (!x || *x || errno)
+                return errno ? -errno : -EINVAL;
+
+        *ret_llu = l;
+        return 0;
+}
+
+int safe_atolli(const char *s, long long int *ret_lli) {
+        char *x = NULL;
+        long long l;
+
+        assert(s);
+        assert(ret_lli);
+
+        errno = 0;
+        l = strtoll(s, &x, 0);
+
+        if (!x || *x || errno)
+                return errno ? -errno : -EINVAL;
+
+        *ret_lli = l;
+        return 0;
+}
+
+/* Split a string into words. */
+char *split(const char *c, size_t *l, const char *separator, char **state) {
+        char *current;
+
+        current = *state ? *state : (char*) c;
+
+        if (!*current || *c == 0)
+                return NULL;
+
+        current += strspn(current, separator);
+        *l = strcspn(current, separator);
+        *state = current+*l;
+
+        return (char*) current;
+}
+
+/* Split a string into words, but consider strings enclosed in '' and
+ * "" as words even if they include spaces. */
+char *split_quoted(const char *c, size_t *l, char **state) {
+        char *current, *e;
+        bool escaped = false;
+
+        current = *state ? *state : (char*) c;
+
+        if (!*current || *c == 0)
+                return NULL;
+
+        current += strspn(current, WHITESPACE);
+
+        if (*current == '\'') {
+                current ++;
+
+                for (e = current; *e; e++) {
+                        if (escaped)
+                                escaped = false;
+                        else if (*e == '\\')
+                                escaped = true;
+                        else if (*e == '\'')
+                                break;
+                }
+
+                *l = e-current;
+                *state = *e == 0 ? e : e+1;
+        } else if (*current == '\"') {
+                current ++;
+
+                for (e = current; *e; e++) {
+                        if (escaped)
+                                escaped = false;
+                        else if (*e == '\\')
+                                escaped = true;
+                        else if (*e == '\"')
+                                break;
+                }
+
+                *l = e-current;
+                *state = *e == 0 ? e : e+1;
+        } else {
+                for (e = current; *e; e++) {
+                        if (escaped)
+                                escaped = false;
+                        else if (*e == '\\')
+                                escaped = true;
+                        else if (strchr(WHITESPACE, *e))
+                                break;
+                }
+                *l = e-current;
+                *state = e;
+        }
+
+        return (char*) current;
+}
+
+char **split_path_and_make_absolute(const char *p) {
+        char **l;
+        assert(p);
+
+        if (!(l = strv_split(p, ":")))
+                return NULL;
+
+        if (!strv_path_make_absolute_cwd(l)) {
+                strv_free(l);
+                return NULL;
+        }
+
+        return l;
+}
+
+int get_parent_of_pid(pid_t pid, pid_t *_ppid) {
+        int r;
+        FILE *f;
+        char fn[PATH_MAX], line[LINE_MAX], *p;
+        long unsigned ppid;
+
+        assert(pid > 0);
+        assert(_ppid);
+
+        assert_se(snprintf(fn, sizeof(fn)-1, "/proc/%lu/stat", (unsigned long) pid) < (int) (sizeof(fn)-1));
+        char_array_0(fn);
+
+        if (!(f = fopen(fn, "re")))
+                return -errno;
+
+        if (!(fgets(line, sizeof(line), f))) {
+                r = feof(f) ? -EIO : -errno;
+                fclose(f);
+                return r;
+        }
+
+        fclose(f);
+
+        /* Let's skip the pid and comm fields. The latter is enclosed
+         * in () but does not escape any () in its value, so let's
+         * skip over it manually */
+
+        if (!(p = strrchr(line, ')')))
+                return -EIO;
+
+        p++;
+
+        if (sscanf(p, " "
+                   "%*c "  /* state */
+                   "%lu ", /* ppid */
+                   &ppid) != 1)
+                return -EIO;
+
+        if ((long unsigned) (pid_t) ppid != ppid)
+                return -ERANGE;
+
+        *_ppid = (pid_t) ppid;
+
+        return 0;
+}
+
+int get_starttime_of_pid(pid_t pid, unsigned long long *st) {
+        int r;
+        FILE *f;
+        char fn[PATH_MAX], line[LINE_MAX], *p;
+
+        assert(pid > 0);
+        assert(st);
+
+        assert_se(snprintf(fn, sizeof(fn)-1, "/proc/%lu/stat", (unsigned long) pid) < (int) (sizeof(fn)-1));
+        char_array_0(fn);
+
+        if (!(f = fopen(fn, "re")))
+                return -errno;
+
+        if (!(fgets(line, sizeof(line), f))) {
+                r = feof(f) ? -EIO : -errno;
+                fclose(f);
+                return r;
+        }
+
+        fclose(f);
+
+        /* Let's skip the pid and comm fields. The latter is enclosed
+         * in () but does not escape any () in its value, so let's
+         * skip over it manually */
+
+        if (!(p = strrchr(line, ')')))
+                return -EIO;
+
+        p++;
+
+        if (sscanf(p, " "
+                   "%*c "  /* state */
+                   "%*d "  /* ppid */
+                   "%*d "  /* pgrp */
+                   "%*d "  /* session */
+                   "%*d "  /* tty_nr */
+                   "%*d "  /* tpgid */
+                   "%*u "  /* flags */
+                   "%*u "  /* minflt */
+                   "%*u "  /* cminflt */
+                   "%*u "  /* majflt */
+                   "%*u "  /* cmajflt */
+                   "%*u "  /* utime */
+                   "%*u "  /* stime */
+                   "%*d "  /* cutime */
+                   "%*d "  /* cstime */
+                   "%*d "  /* priority */
+                   "%*d "  /* nice */
+                   "%*d "  /* num_threads */
+                   "%*d "  /* itrealvalue */
+                   "%llu "  /* starttime */,
+                   st) != 1)
+                return -EIO;
+
+        return 0;
+}
+
+int write_one_line_file(const char *fn, const char *line) {
+        FILE *f;
+        int r;
+
+        assert(fn);
+        assert(line);
+
+        if (!(f = fopen(fn, "we")))
+                return -errno;
+
+        errno = 0;
+        if (fputs(line, f) < 0) {
+                r = -errno;
+                goto finish;
+        }
+
+        if (!endswith(line, "\n"))
+                fputc('\n', f);
+
+        fflush(f);
+
+        if (ferror(f)) {
+                if (errno != 0)
+                        r = -errno;
+                else
+                        r = -EIO;
+        } else
+                r = 0;
+
+finish:
+        fclose(f);
+        return r;
+}
+
+int fchmod_umask(int fd, mode_t m) {
+        mode_t u;
+        int r;
+
+        u = umask(0777);
+        r = fchmod(fd, m & (~u)) < 0 ? -errno : 0;
+        umask(u);
+
+        return r;
+}
+
+int write_one_line_file_atomic(const char *fn, const char *line) {
+        FILE *f;
+        int r;
+        char *p;
+
+        assert(fn);
+        assert(line);
+
+        r = fopen_temporary(fn, &f, &p);
+        if (r < 0)
+                return r;
+
+        fchmod_umask(fileno(f), 0644);
+
+        errno = 0;
+        if (fputs(line, f) < 0) {
+                r = -errno;
+                goto finish;
+        }
+
+        if (!endswith(line, "\n"))
+                fputc('\n', f);
+
+        fflush(f);
+
+        if (ferror(f)) {
+                if (errno != 0)
+                        r = -errno;
+                else
+                        r = -EIO;
+        } else {
+                if (rename(p, fn) < 0)
+                        r = -errno;
+                else
+                        r = 0;
+        }
+
+finish:
+        if (r < 0)
+                unlink(p);
+
+        fclose(f);
+        free(p);
+
+        return r;
+}
+
+int read_one_line_file(const char *fn, char **line) {
+        FILE *f;
+        int r;
+        char t[LINE_MAX], *c;
+
+        assert(fn);
+        assert(line);
+
+        f = fopen(fn, "re");
+        if (!f)
+                return -errno;
+
+        if (!fgets(t, sizeof(t), f)) {
+
+                if (ferror(f)) {
+                        r = -errno;
+                        goto finish;
+                }
+
+                t[0] = 0;
+        }
+
+        c = strdup(t);
+        if (!c) {
+                r = -ENOMEM;
+                goto finish;
+        }
+
+        truncate_nl(c);
+
+        *line = c;
+        r = 0;
+
+finish:
+        fclose(f);
+        return r;
+}
+
+int read_full_file(const char *fn, char **contents, size_t *size) {
+        FILE *f;
+        int r;
+        size_t n, l;
+        char *buf = NULL;
+        struct stat st;
+
+        if (!(f = fopen(fn, "re")))
+                return -errno;
+
+        if (fstat(fileno(f), &st) < 0) {
+                r = -errno;
+                goto finish;
+        }
+
+        /* Safety check */
+        if (st.st_size > 4*1024*1024) {
+                r = -E2BIG;
+                goto finish;
+        }
+
+        n = st.st_size > 0 ? st.st_size : LINE_MAX;
+        l = 0;
+
+        for (;;) {
+                char *t;
+                size_t k;
+
+                if (!(t = realloc(buf, n+1))) {
+                        r = -ENOMEM;
+                        goto finish;
+                }
+
+                buf = t;
+                k = fread(buf + l, 1, n - l, f);
+
+                if (k <= 0) {
+                        if (ferror(f)) {
+                                r = -errno;
+                                goto finish;
+                        }
+
+                        break;
+                }
+
+                l += k;
+                n *= 2;
+
+                /* Safety check */
+                if (n > 4*1024*1024) {
+                        r = -E2BIG;
+                        goto finish;
+                }
+        }
+
+        buf[l] = 0;
+        *contents = buf;
+        buf = NULL;
+
+        if (size)
+                *size = l;
+
+        r = 0;
+
+finish:
+        fclose(f);
+        free(buf);
+
+        return r;
+}
+
+int parse_env_file(
+                const char *fname,
+                const char *separator, ...) {
+
+        int r = 0;
+        char *contents = NULL, *p;
+
+        assert(fname);
+        assert(separator);
+
+        if ((r = read_full_file(fname, &contents, NULL)) < 0)
+                return r;
+
+        p = contents;
+        for (;;) {
+                const char *key = NULL;
+
+                p += strspn(p, separator);
+                p += strspn(p, WHITESPACE);
+
+                if (!*p)
+                        break;
+
+                if (!strchr(COMMENTS, *p)) {
+                        va_list ap;
+                        char **value;
+
+                        va_start(ap, separator);
+                        while ((key = va_arg(ap, char *))) {
+                                size_t n;
+                                char *v;
+
+                                value = va_arg(ap, char **);
+
+                                n = strlen(key);
+                                if (strncmp(p, key, n) != 0 ||
+                                    p[n] != '=')
+                                        continue;
+
+                                p += n + 1;
+                                n = strcspn(p, separator);
+
+                                if (n >= 2 &&
+                                    strchr(QUOTES, p[0]) &&
+                                    p[n-1] == p[0])
+                                        v = strndup(p+1, n-2);
+                                else
+                                        v = strndup(p, n);
+
+                                if (!v) {
+                                        r = -ENOMEM;
+                                        va_end(ap);
+                                        goto fail;
+                                }
+
+                                if (v[0] == '\0') {
+                                        /* return empty value strings as NULL */
+                                        free(v);
+                                        v = NULL;
+                                }
+
+                                free(*value);
+                                *value = v;
+
+                                p += n;
+
+                                r ++;
+                                break;
+                        }
+                        va_end(ap);
+                }
+
+                if (!key)
+                        p += strcspn(p, separator);
+        }
+
+fail:
+        free(contents);
+        return r;
+}
+
+int load_env_file(
+                const char *fname,
+                char ***rl) {
+
+        FILE *f;
+        char **m = NULL;
+        int r;
+
+        assert(fname);
+        assert(rl);
+
+        if (!(f = fopen(fname, "re")))
+                return -errno;
+
+        while (!feof(f)) {
+                char l[LINE_MAX], *p, *u;
+                char **t;
+
+                if (!fgets(l, sizeof(l), f)) {
+                        if (feof(f))
+                                break;
+
+                        r = -errno;
+                        goto finish;
+                }
+
+                p = strstrip(l);
+
+                if (!*p)
+                        continue;
+
+                if (strchr(COMMENTS, *p))
+                        continue;
+
+                if (!(u = normalize_env_assignment(p))) {
+                        log_error("Out of memory");
+                        r = -ENOMEM;
+                        goto finish;
+                }
+
+                t = strv_append(m, u);
+                free(u);
+
+                if (!t) {
+                        log_error("Out of memory");
+                        r = -ENOMEM;
+                        goto finish;
+                }
+
+                strv_free(m);
+                m = t;
+        }
+
+        r = 0;
+
+        *rl = m;
+        m = NULL;
+
+finish:
+        if (f)
+                fclose(f);
+
+        strv_free(m);
+
+        return r;
+}
+
+int write_env_file(const char *fname, char **l) {
+        char **i, *p;
+        FILE *f;
+        int r;
+
+        r = fopen_temporary(fname, &f, &p);
+        if (r < 0)
+                return r;
+
+        fchmod_umask(fileno(f), 0644);
+
+        errno = 0;
+        STRV_FOREACH(i, l) {
+                fputs(*i, f);
+                fputc('\n', f);
+        }
+
+        fflush(f);
+
+        if (ferror(f)) {
+                if (errno != 0)
+                        r = -errno;
+                else
+                        r = -EIO;
+        } else {
+                if (rename(p, fname) < 0)
+                        r = -errno;
+                else
+                        r = 0;
+        }
+
+        if (r < 0)
+                unlink(p);
+
+        fclose(f);
+        free(p);
+
+        return r;
+}
+
+char *truncate_nl(char *s) {
+        assert(s);
+
+        s[strcspn(s, NEWLINE)] = 0;
+        return s;
+}
+
+int get_process_comm(pid_t pid, char **name) {
+        int r;
+
+        assert(name);
+
+        if (pid == 0)
+                r = read_one_line_file("/proc/self/comm", name);
+        else {
+                char *p;
+                if (asprintf(&p, "/proc/%lu/comm", (unsigned long) pid) < 0)
+                        return -ENOMEM;
+
+                r = read_one_line_file(p, name);
+                free(p);
+        }
+
+        return r;
+}
+
+int get_process_cmdline(pid_t pid, size_t max_length, bool comm_fallback, char **line) {
+        char *r, *k;
+        int c;
+        bool space = false;
+        size_t left;
+        FILE *f;
+
+        assert(max_length > 0);
+        assert(line);
+
+        if (pid == 0)
+                f = fopen("/proc/self/cmdline", "re");
+        else {
+                char *p;
+                if (asprintf(&p, "/proc/%lu/cmdline", (unsigned long) pid) < 0)
+                        return -ENOMEM;
+
+                f = fopen(p, "re");
+                free(p);
+        }
+
+        if (!f)
+                return -errno;
+
+        r = new(char, max_length);
+        if (!r) {
+                fclose(f);
+                return -ENOMEM;
+        }
+
+        k = r;
+        left = max_length;
+        while ((c = getc(f)) != EOF) {
+
+                if (isprint(c)) {
+                        if (space) {
+                                if (left <= 4)
+                                        break;
+
+                                *(k++) = ' ';
+                                left--;
+                                space = false;
+                        }
+
+                        if (left <= 4)
+                                break;
+
+                        *(k++) = (char) c;
+                        left--;
+                }  else
+                        space = true;
+        }
+
+        if (left <= 4) {
+                size_t n = MIN(left-1, 3U);
+                memcpy(k, "...", n);
+                k[n] = 0;
+        } else
+                *k = 0;
+
+        fclose(f);
+
+        /* Kernel threads have no argv[] */
+        if (r[0] == 0) {
+                char *t;
+                int h;
+
+                free(r);
+
+                if (!comm_fallback)
+                        return -ENOENT;
+
+                h = get_process_comm(pid, &t);
+                if (h < 0)
+                        return h;
+
+                r = join("[", t, "]", NULL);
+                free(t);
+
+                if (!r)
+                        return -ENOMEM;
+        }
+
+        *line = r;
+        return 0;
+}
+
+int is_kernel_thread(pid_t pid) {
+        char *p;
+        size_t count;
+        char c;
+        bool eof;
+        FILE *f;
+
+        if (pid == 0)
+                return 0;
+
+        if (asprintf(&p, "/proc/%lu/cmdline", (unsigned long) pid) < 0)
+                return -ENOMEM;
+
+        f = fopen(p, "re");
+        free(p);
+
+        if (!f)
+                return -errno;
+
+        count = fread(&c, 1, 1, f);
+        eof = feof(f);
+        fclose(f);
+
+        /* Kernel threads have an empty cmdline */
+
+        if (count <= 0)
+                return eof ? 1 : -errno;
+
+        return 0;
+}
+
+int get_process_exe(pid_t pid, char **name) {
+        int r;
+
+        assert(name);
+
+        if (pid == 0)
+                r = readlink_malloc("/proc/self/exe", name);
+        else {
+                char *p;
+                if (asprintf(&p, "/proc/%lu/exe", (unsigned long) pid) < 0)
+                        return -ENOMEM;
+
+                r = readlink_malloc(p, name);
+                free(p);
+        }
+
+        return r;
+}
+
+int get_process_uid(pid_t pid, uid_t *uid) {
+        char *p;
+        FILE *f;
+        int r;
+
+        assert(uid);
+
+        if (pid == 0)
+                return getuid();
+
+        if (asprintf(&p, "/proc/%lu/status", (unsigned long) pid) < 0)
+                return -ENOMEM;
+
+        f = fopen(p, "re");
+        free(p);
+
+        if (!f)
+                return -errno;
+
+        while (!feof(f)) {
+                char line[LINE_MAX], *l;
+
+                if (!fgets(line, sizeof(line), f)) {
+                        if (feof(f))
+                                break;
+
+                        r = -errno;
+                        goto finish;
+                }
+
+                l = strstrip(line);
+
+                if (startswith(l, "Uid:")) {
+                        l += 4;
+                        l += strspn(l, WHITESPACE);
+
+                        l[strcspn(l, WHITESPACE)] = 0;
+
+                        r = parse_uid(l, uid);
+                        goto finish;
+                }
+        }
+
+        r = -EIO;
+
+finish:
+        fclose(f);
+
+        return r;
+}
+
+char *strnappend(const char *s, const char *suffix, size_t b) {
+        size_t a;
+        char *r;
+
+        if (!s && !suffix)
+                return strdup("");
+
+        if (!s)
+                return strndup(suffix, b);
+
+        if (!suffix)
+                return strdup(s);
+
+        assert(s);
+        assert(suffix);
+
+        a = strlen(s);
+
+        if (!(r = new(char, a+b+1)))
+                return NULL;
+
+        memcpy(r, s, a);
+        memcpy(r+a, suffix, b);
+        r[a+b] = 0;
+
+        return r;
+}
+
+char *strappend(const char *s, const char *suffix) {
+        return strnappend(s, suffix, suffix ? strlen(suffix) : 0);
+}
+
+int readlink_malloc(const char *p, char **r) {
+        size_t l = 100;
+
+        assert(p);
+        assert(r);
+
+        for (;;) {
+                char *c;
+                ssize_t n;
+
+                if (!(c = new(char, l)))
+                        return -ENOMEM;
+
+                if ((n = readlink(p, c, l-1)) < 0) {
+                        int ret = -errno;
+                        free(c);
+                        return ret;
+                }
+
+                if ((size_t) n < l-1) {
+                        c[n] = 0;
+                        *r = c;
+                        return 0;
+                }
+
+                free(c);
+                l *= 2;
+        }
+}
+
+int readlink_and_make_absolute(const char *p, char **r) {
+        char *target, *k;
+        int j;
+
+        assert(p);
+        assert(r);
+
+        if ((j = readlink_malloc(p, &target)) < 0)
+                return j;
+
+        k = file_in_same_dir(p, target);
+        free(target);
+
+        if (!k)
+                return -ENOMEM;
+
+        *r = k;
+        return 0;
+}
+
+int readlink_and_canonicalize(const char *p, char **r) {
+        char *t, *s;
+        int j;
+
+        assert(p);
+        assert(r);
+
+        j = readlink_and_make_absolute(p, &t);
+        if (j < 0)
+                return j;
+
+        s = canonicalize_file_name(t);
+        if (s) {
+                free(t);
+                *r = s;
+        } else
+                *r = t;
+
+        path_kill_slashes(*r);
+
+        return 0;
+}
+
+int parent_of_path(const char *path, char **_r) {
+        const char *e, *a = NULL, *b = NULL, *p;
+        char *r;
+        bool slash = false;
+
+        assert(path);
+        assert(_r);
+
+        if (!*path)
+                return -EINVAL;
+
+        for (e = path; *e; e++) {
+
+                if (!slash && *e == '/') {
+                        a = b;
+                        b = e;
+                        slash = true;
+                } else if (slash && *e != '/')
+                        slash = false;
+        }
+
+        if (*(e-1) == '/')
+                p = a;
+        else
+                p = b;
+
+        if (!p)
+                return -EINVAL;
+
+        if (p == path)
+                r = strdup("/");
+        else
+                r = strndup(path, p-path);
+
+        if (!r)
+                return -ENOMEM;
+
+        *_r = r;
+        return 0;
+}
+
+
+char *file_name_from_path(const char *p) {
+        char *r;
+
+        assert(p);
+
+        if ((r = strrchr(p, '/')))
+                return r + 1;
+
+        return (char*) p;
+}
+
+bool path_is_absolute(const char *p) {
+        assert(p);
+
+        return p[0] == '/';
+}
+
+bool is_path(const char *p) {
+
+        return !!strchr(p, '/');
+}
+
+char *path_make_absolute(const char *p, const char *prefix) {
+        assert(p);
+
+        /* Makes every item in the list an absolute path by prepending
+         * the prefix, if specified and necessary */
+
+        if (path_is_absolute(p) || !prefix)
+                return strdup(p);
+
+        return join(prefix, "/", p, NULL);
+}
+
+char *path_make_absolute_cwd(const char *p) {
+        char *cwd, *r;
+
+        assert(p);
+
+        /* Similar to path_make_absolute(), but prefixes with the
+         * current working directory. */
+
+        if (path_is_absolute(p))
+                return strdup(p);
+
+        if (!(cwd = get_current_dir_name()))
+                return NULL;
+
+        r = path_make_absolute(p, cwd);
+        free(cwd);
+
+        return r;
+}
+
+char **strv_path_make_absolute_cwd(char **l) {
+        char **s;
+
+        /* Goes through every item in the string list and makes it
+         * absolute. This works in place and won't rollback any
+         * changes on failure. */
+
+        STRV_FOREACH(s, l) {
+                char *t;
+
+                if (!(t = path_make_absolute_cwd(*s)))
+                        return NULL;
+
+                free(*s);
+                *s = t;
+        }
+
+        return l;
+}
+
+char **strv_path_canonicalize(char **l) {
+        char **s;
+        unsigned k = 0;
+        bool enomem = false;
+
+        if (strv_isempty(l))
+                return l;
+
+        /* Goes through every item in the string list and canonicalize
+         * the path. This works in place and won't rollback any
+         * changes on failure. */
+
+        STRV_FOREACH(s, l) {
+                char *t, *u;
+
+                t = path_make_absolute_cwd(*s);
+                free(*s);
+
+                if (!t) {
+                        enomem = true;
+                        continue;
+                }
+
+                errno = 0;
+                u = canonicalize_file_name(t);
+                free(t);
+
+                if (!u) {
+                        if (errno == ENOMEM || !errno)
+                                enomem = true;
+
+                        continue;
+                }
+
+                l[k++] = u;
+        }
+
+        l[k] = NULL;
+
+        if (enomem)
+                return NULL;
+
+        return l;
+}
+
+char **strv_path_remove_empty(char **l) {
+        char **f, **t;
+
+        if (!l)
+                return NULL;
+
+        for (f = t = l; *f; f++) {
+
+                if (dir_is_empty(*f) > 0) {
+                        free(*f);
+                        continue;
+                }
+
+                *(t++) = *f;
+        }
+
+        *t = NULL;
+        return l;
+}
+
+int reset_all_signal_handlers(void) {
+        int sig;
+
+        for (sig = 1; sig < _NSIG; sig++) {
+                struct sigaction sa;
+
+                if (sig == SIGKILL || sig == SIGSTOP)
+                        continue;
+
+                zero(sa);
+                sa.sa_handler = SIG_DFL;
+                sa.sa_flags = SA_RESTART;
+
+                /* On Linux the first two RT signals are reserved by
+                 * glibc, and sigaction() will return EINVAL for them. */
+                if ((sigaction(sig, &sa, NULL) < 0))
+                        if (errno != EINVAL)
+                                return -errno;
+        }
+
+        return 0;
+}
+
+char *strstrip(char *s) {
+        char *e;
+
+        /* Drops trailing whitespace. Modifies the string in
+         * place. Returns pointer to first non-space character */
+
+        s += strspn(s, WHITESPACE);
+
+        for (e = strchr(s, 0); e > s; e --)
+                if (!strchr(WHITESPACE, e[-1]))
+                        break;
+
+        *e = 0;
+
+        return s;
+}
+
+char *delete_chars(char *s, const char *bad) {
+        char *f, *t;
+
+        /* Drops all whitespace, regardless where in the string */
+
+        for (f = s, t = s; *f; f++) {
+                if (strchr(bad, *f))
+                        continue;
+
+                *(t++) = *f;
+        }
+
+        *t = 0;
+
+        return s;
+}
+
+bool in_charset(const char *s, const char* charset) {
+        const char *i;
+
+        assert(s);
+        assert(charset);
+
+        for (i = s; *i; i++)
+                if (!strchr(charset, *i))
+                        return false;
+
+        return true;
+}
+
+char *file_in_same_dir(const char *path, const char *filename) {
+        char *e, *r;
+        size_t k;
+
+        assert(path);
+        assert(filename);
+
+        /* This removes the last component of path and appends
+         * filename, unless the latter is absolute anyway or the
+         * former isn't */
+
+        if (path_is_absolute(filename))
+                return strdup(filename);
+
+        if (!(e = strrchr(path, '/')))
+                return strdup(filename);
+
+        k = strlen(filename);
+        if (!(r = new(char, e-path+1+k+1)))
+                return NULL;
+
+        memcpy(r, path, e-path+1);
+        memcpy(r+(e-path)+1, filename, k+1);
+
+        return r;
+}
+
+int safe_mkdir(const char *path, mode_t mode, uid_t uid, gid_t gid) {
+        struct stat st;
+
+        if (label_mkdir(path, mode) >= 0)
+                if (chmod_and_chown(path, mode, uid, gid) < 0)
+                        return -errno;
+
+        if (lstat(path, &st) < 0)
+                return -errno;
+
+        if ((st.st_mode & 0777) != mode ||
+            st.st_uid != uid ||
+            st.st_gid != gid ||
+            !S_ISDIR(st.st_mode)) {
+                errno = EEXIST;
+                return -errno;
+        }
+
+        return 0;
+}
+
+
+int mkdir_parents(const char *path, mode_t mode) {
+        const char *p, *e;
+
+        assert(path);
+
+        /* Creates every parent directory in the path except the last
+         * component. */
+
+        p = path + strspn(path, "/");
+        for (;;) {
+                int r;
+                char *t;
+
+                e = p + strcspn(p, "/");
+                p = e + strspn(e, "/");
+
+                /* Is this the last component? If so, then we're
+                 * done */
+                if (*p == 0)
+                        return 0;
+
+                if (!(t = strndup(path, e - path)))
+                        return -ENOMEM;
+
+                r = label_mkdir(t, mode);
+                free(t);
+
+                if (r < 0 && errno != EEXIST)
+                        return -errno;
+        }
+}
+
+int mkdir_p(const char *path, mode_t mode) {
+        int r;
+
+        /* Like mkdir -p */
+
+        if ((r = mkdir_parents(path, mode)) < 0)
+                return r;
+
+        if (label_mkdir(path, mode) < 0 && errno != EEXIST)
+                return -errno;
+
+        return 0;
+}
+
+int rmdir_parents(const char *path, const char *stop) {
+        size_t l;
+        int r = 0;
+
+        assert(path);
+        assert(stop);
+
+        l = strlen(path);
+
+        /* Skip trailing slashes */
+        while (l > 0 && path[l-1] == '/')
+                l--;
+
+        while (l > 0) {
+                char *t;
+
+                /* Skip last component */
+                while (l > 0 && path[l-1] != '/')
+                        l--;
+
+                /* Skip trailing slashes */
+                while (l > 0 && path[l-1] == '/')
+                        l--;
+
+                if (l <= 0)
+                        break;
+
+                if (!(t = strndup(path, l)))
+                        return -ENOMEM;
+
+                if (path_startswith(stop, t)) {
+                        free(t);
+                        return 0;
+                }
+
+                r = rmdir(t);
+                free(t);
+
+                if (r < 0)
+                        if (errno != ENOENT)
+                                return -errno;
+        }
+
+        return 0;
+}
+
+
+char hexchar(int x) {
+        static const char table[16] = "0123456789abcdef";
+
+        return table[x & 15];
+}
+
+int unhexchar(char c) {
+
+        if (c >= '0' && c <= '9')
+                return c - '0';
+
+        if (c >= 'a' && c <= 'f')
+                return c - 'a' + 10;
+
+        if (c >= 'A' && c <= 'F')
+                return c - 'A' + 10;
+
+        return -1;
+}
+
+char octchar(int x) {
+        return '0' + (x & 7);
+}
+
+int unoctchar(char c) {
+
+        if (c >= '0' && c <= '7')
+                return c - '0';
+
+        return -1;
+}
+
+char decchar(int x) {
+        return '0' + (x % 10);
+}
+
+int undecchar(char c) {
+
+        if (c >= '0' && c <= '9')
+                return c - '0';
+
+        return -1;
+}
+
+char *cescape(const char *s) {
+        char *r, *t;
+        const char *f;
+
+        assert(s);
+
+        /* Does C style string escaping. */
+
+        if (!(r = new(char, strlen(s)*4 + 1)))
+                return NULL;
+
+        for (f = s, t = r; *f; f++)
+
+                switch (*f) {
+
+                case '\a':
+                        *(t++) = '\\';
+                        *(t++) = 'a';
+                        break;
+                case '\b':
+                        *(t++) = '\\';
+                        *(t++) = 'b';
+                        break;
+                case '\f':
+                        *(t++) = '\\';
+                        *(t++) = 'f';
+                        break;
+                case '\n':
+                        *(t++) = '\\';
+                        *(t++) = 'n';
+                        break;
+                case '\r':
+                        *(t++) = '\\';
+                        *(t++) = 'r';
+                        break;
+                case '\t':
+                        *(t++) = '\\';
+                        *(t++) = 't';
+                        break;
+                case '\v':
+                        *(t++) = '\\';
+                        *(t++) = 'v';
+                        break;
+                case '\\':
+                        *(t++) = '\\';
+                        *(t++) = '\\';
+                        break;
+                case '"':
+                        *(t++) = '\\';
+                        *(t++) = '"';
+                        break;
+                case '\'':
+                        *(t++) = '\\';
+                        *(t++) = '\'';
+                        break;
+
+                default:
+                        /* For special chars we prefer octal over
+                         * hexadecimal encoding, simply because glib's
+                         * g_strescape() does the same */
+                        if ((*f < ' ') || (*f >= 127)) {
+                                *(t++) = '\\';
+                                *(t++) = octchar((unsigned char) *f >> 6);
+                                *(t++) = octchar((unsigned char) *f >> 3);
+                                *(t++) = octchar((unsigned char) *f);
+                        } else
+                                *(t++) = *f;
+                        break;
+                }
+
+        *t = 0;
+
+        return r;
+}
+
+char *cunescape_length(const char *s, size_t length) {
+        char *r, *t;
+        const char *f;
+
+        assert(s);
+
+        /* Undoes C style string escaping */
+
+        r = new(char, length+1);
+        if (!r)
+                return r;
+
+        for (f = s, t = r; f < s + length; f++) {
+
+                if (*f != '\\') {
+                        *(t++) = *f;
+                        continue;
+                }
+
+                f++;
+
+                switch (*f) {
+
+                case 'a':
+                        *(t++) = '\a';
+                        break;
+                case 'b':
+                        *(t++) = '\b';
+                        break;
+                case 'f':
+                        *(t++) = '\f';
+                        break;
+                case 'n':
+                        *(t++) = '\n';
+                        break;
+                case 'r':
+                        *(t++) = '\r';
+                        break;
+                case 't':
+                        *(t++) = '\t';
+                        break;
+                case 'v':
+                        *(t++) = '\v';
+                        break;
+                case '\\':
+                        *(t++) = '\\';
+                        break;
+                case '"':
+                        *(t++) = '"';
+                        break;
+                case '\'':
+                        *(t++) = '\'';
+                        break;
+
+                case 's':
+                        /* This is an extension of the XDG syntax files */
+                        *(t++) = ' ';
+                        break;
+
+                case 'x': {
+                        /* hexadecimal encoding */
+                        int a, b;
+
+                        a = unhexchar(f[1]);
+                        b = unhexchar(f[2]);
+
+                        if (a < 0 || b < 0) {
+                                /* Invalid escape code, let's take it literal then */
+                                *(t++) = '\\';
+                                *(t++) = 'x';
+                        } else {
+                                *(t++) = (char) ((a << 4) | b);
+                                f += 2;
+                        }
+
+                        break;
+                }
+
+                case '0':
+                case '1':
+                case '2':
+                case '3':
+                case '4':
+                case '5':
+                case '6':
+                case '7': {
+                        /* octal encoding */
+                        int a, b, c;
+
+                        a = unoctchar(f[0]);
+                        b = unoctchar(f[1]);
+                        c = unoctchar(f[2]);
+
+                        if (a < 0 || b < 0 || c < 0) {
+                                /* Invalid escape code, let's take it literal then */
+                                *(t++) = '\\';
+                                *(t++) = f[0];
+                        } else {
+                                *(t++) = (char) ((a << 6) | (b << 3) | c);
+                                f += 2;
+                        }
+
+                        break;
+                }
+
+                case 0:
+                        /* premature end of string.*/
+                        *(t++) = '\\';
+                        goto finish;
+
+                default:
+                        /* Invalid escape code, let's take it literal then */
+                        *(t++) = '\\';
+                        *(t++) = *f;
+                        break;
+                }
+        }
+
+finish:
+        *t = 0;
+        return r;
+}
+
+char *cunescape(const char *s) {
+        return cunescape_length(s, strlen(s));
+}
+
+char *xescape(const char *s, const char *bad) {
+        char *r, *t;
+        const char *f;
+
+        /* Escapes all chars in bad, in addition to \ and all special
+         * chars, in \xFF style escaping. May be reversed with
+         * cunescape. */
+
+        if (!(r = new(char, strlen(s)*4+1)))
+                return NULL;
+
+        for (f = s, t = r; *f; f++) {
+
+                if ((*f < ' ') || (*f >= 127) ||
+                    (*f == '\\') || strchr(bad, *f)) {
+                        *(t++) = '\\';
+                        *(t++) = 'x';
+                        *(t++) = hexchar(*f >> 4);
+                        *(t++) = hexchar(*f);
+                } else
+                        *(t++) = *f;
+        }
+
+        *t = 0;
+
+        return r;
+}
+
+char *bus_path_escape(const char *s) {
+        char *r, *t;
+        const char *f;
+
+        assert(s);
+
+        /* Escapes all chars that D-Bus' object path cannot deal
+         * with. Can be reverse with bus_path_unescape() */
+
+        if (!(r = new(char, strlen(s)*3+1)))
+                return NULL;
+
+        for (f = s, t = r; *f; f++) {
+
+                if (!(*f >= 'A' && *f <= 'Z') &&
+                    !(*f >= 'a' && *f <= 'z') &&
+                    !(*f >= '0' && *f <= '9')) {
+                        *(t++) = '_';
+                        *(t++) = hexchar(*f >> 4);
+                        *(t++) = hexchar(*f);
+                } else
+                        *(t++) = *f;
+        }
+
+        *t = 0;
+
+        return r;
+}
+
+char *bus_path_unescape(const char *f) {
+        char *r, *t;
+
+        assert(f);
+
+        if (!(r = strdup(f)))
+                return NULL;
+
+        for (t = r; *f; f++) {
+
+                if (*f == '_') {
+                        int a, b;
+
+                        if ((a = unhexchar(f[1])) < 0 ||
+                            (b = unhexchar(f[2])) < 0) {
+                                /* Invalid escape code, let's take it literal then */
+                                *(t++) = '_';
+                        } else {
+                                *(t++) = (char) ((a << 4) | b);
+                                f += 2;
+                        }
+                } else
+                        *(t++) = *f;
+        }
+
+        *t = 0;
+
+        return r;
+}
+
+char *path_kill_slashes(char *path) {
+        char *f, *t;
+        bool slash = false;
+
+        /* Removes redundant inner and trailing slashes. Modifies the
+         * passed string in-place.
+         *
+         * ///foo///bar/ becomes /foo/bar
+         */
+
+        for (f = path, t = path; *f; f++) {
+
+                if (*f == '/') {
+                        slash = true;
+                        continue;
+                }
+
+                if (slash) {
+                        slash = false;
+                        *(t++) = '/';
+                }
+
+                *(t++) = *f;
+        }
+
+        /* Special rule, if we are talking of the root directory, a
+        trailing slash is good */
+
+        if (t == path && slash)
+                *(t++) = '/';
+
+        *t = 0;
+        return path;
+}
+
+bool path_startswith(const char *path, const char *prefix) {
+        assert(path);
+        assert(prefix);
+
+        if ((path[0] == '/') != (prefix[0] == '/'))
+                return false;
+
+        for (;;) {
+                size_t a, b;
+
+                path += strspn(path, "/");
+                prefix += strspn(prefix, "/");
+
+                if (*prefix == 0)
+                        return true;
+
+                if (*path == 0)
+                        return false;
+
+                a = strcspn(path, "/");
+                b = strcspn(prefix, "/");
+
+                if (a != b)
+                        return false;
+
+                if (memcmp(path, prefix, a) != 0)
+                        return false;
+
+                path += a;
+                prefix += b;
+        }
+}
+
+bool path_equal(const char *a, const char *b) {
+        assert(a);
+        assert(b);
+
+        if ((a[0] == '/') != (b[0] == '/'))
+                return false;
+
+        for (;;) {
+                size_t j, k;
+
+                a += strspn(a, "/");
+                b += strspn(b, "/");
+
+                if (*a == 0 && *b == 0)
+                        return true;
+
+                if (*a == 0 || *b == 0)
+                        return false;
+
+                j = strcspn(a, "/");
+                k = strcspn(b, "/");
+
+                if (j != k)
+                        return false;
+
+                if (memcmp(a, b, j) != 0)
+                        return false;
+
+                a += j;
+                b += k;
+        }
+}
+
+char *ascii_strlower(char *t) {
+        char *p;
+
+        assert(t);
+
+        for (p = t; *p; p++)
+                if (*p >= 'A' && *p <= 'Z')
+                        *p = *p - 'A' + 'a';
+
+        return t;
+}
+
+bool ignore_file(const char *filename) {
+        assert(filename);
+
+        return
+                filename[0] == '.' ||
+                streq(filename, "lost+found") ||
+                streq(filename, "aquota.user") ||
+                streq(filename, "aquota.group") ||
+                endswith(filename, "~") ||
+                endswith(filename, ".rpmnew") ||
+                endswith(filename, ".rpmsave") ||
+                endswith(filename, ".rpmorig") ||
+                endswith(filename, ".dpkg-old") ||
+                endswith(filename, ".dpkg-new") ||
+                endswith(filename, ".swp");
+}
+
+int fd_nonblock(int fd, bool nonblock) {
+        int flags;
+
+        assert(fd >= 0);
+
+        if ((flags = fcntl(fd, F_GETFL, 0)) < 0)
+                return -errno;
+
+        if (nonblock)
+                flags |= O_NONBLOCK;
+        else
+                flags &= ~O_NONBLOCK;
+
+        if (fcntl(fd, F_SETFL, flags) < 0)
+                return -errno;
+
+        return 0;
+}
+
+int fd_cloexec(int fd, bool cloexec) {
+        int flags;
+
+        assert(fd >= 0);
+
+        if ((flags = fcntl(fd, F_GETFD, 0)) < 0)
+                return -errno;
+
+        if (cloexec)
+                flags |= FD_CLOEXEC;
+        else
+                flags &= ~FD_CLOEXEC;
+
+        if (fcntl(fd, F_SETFD, flags) < 0)
+                return -errno;
+
+        return 0;
+}
+
+static bool fd_in_set(int fd, const int fdset[], unsigned n_fdset) {
+        unsigned i;
+
+        assert(n_fdset == 0 || fdset);
+
+        for (i = 0; i < n_fdset; i++)
+                if (fdset[i] == fd)
+                        return true;
+
+        return false;
+}
+
+int close_all_fds(const int except[], unsigned n_except) {
+        DIR *d;
+        struct dirent *de;
+        int r = 0;
+
+        assert(n_except == 0 || except);
+
+        d = opendir("/proc/self/fd");
+        if (!d) {
+                int fd;
+                struct rlimit rl;
+
+                /* When /proc isn't available (for example in chroots)
+                 * the fallback is brute forcing through the fd
+                 * table */
+
+                assert_se(getrlimit(RLIMIT_NOFILE, &rl) >= 0);
+                for (fd = 3; fd < (int) rl.rlim_max; fd ++) {
+
+                        if (fd_in_set(fd, except, n_except))
+                                continue;
+
+                        if (close_nointr(fd) < 0)
+                                if (errno != EBADF && r == 0)
+                                        r = -errno;
+                }
+
+                return r;
+        }
+
+        while ((de = readdir(d))) {
+                int fd = -1;
+
+                if (ignore_file(de->d_name))
+                        continue;
+
+                if (safe_atoi(de->d_name, &fd) < 0)
+                        /* Let's better ignore this, just in case */
+                        continue;
+
+                if (fd < 3)
+                        continue;
+
+                if (fd == dirfd(d))
+                        continue;
+
+                if (fd_in_set(fd, except, n_except))
+                        continue;
+
+                if (close_nointr(fd) < 0) {
+                        /* Valgrind has its own FD and doesn't want to have it closed */
+                        if (errno != EBADF && r == 0)
+                                r = -errno;
+                }
+        }
+
+        closedir(d);
+        return r;
+}
+
+bool chars_intersect(const char *a, const char *b) {
+        const char *p;
+
+        /* Returns true if any of the chars in a are in b. */
+        for (p = a; *p; p++)
+                if (strchr(b, *p))
+                        return true;
+
+        return false;
+}
+
+char *format_timestamp(char *buf, size_t l, usec_t t) {
+        struct tm tm;
+        time_t sec;
+
+        assert(buf);
+        assert(l > 0);
+
+        if (t <= 0)
+                return NULL;
+
+        sec = (time_t) (t / USEC_PER_SEC);
+
+        if (strftime(buf, l, "%a, %d %b %Y %H:%M:%S %z", localtime_r(&sec, &tm)) <= 0)
+                return NULL;
+
+        return buf;
+}
+
+char *format_timestamp_pretty(char *buf, size_t l, usec_t t) {
+        usec_t n, d;
+
+        n = now(CLOCK_REALTIME);
+
+        if (t <= 0 || t > n || t + USEC_PER_DAY*7 <= t)
+                return NULL;
+
+        d = n - t;
+
+        if (d >= USEC_PER_YEAR)
+                snprintf(buf, l, "%llu years and %llu months ago",
+                         (unsigned long long) (d / USEC_PER_YEAR),
+                         (unsigned long long) ((d % USEC_PER_YEAR) / USEC_PER_MONTH));
+        else if (d >= USEC_PER_MONTH)
+                snprintf(buf, l, "%llu months and %llu days ago",
+                         (unsigned long long) (d / USEC_PER_MONTH),
+                         (unsigned long long) ((d % USEC_PER_MONTH) / USEC_PER_DAY));
+        else if (d >= USEC_PER_WEEK)
+                snprintf(buf, l, "%llu weeks and %llu days ago",
+                         (unsigned long long) (d / USEC_PER_WEEK),
+                         (unsigned long long) ((d % USEC_PER_WEEK) / USEC_PER_DAY));
+        else if (d >= 2*USEC_PER_DAY)
+                snprintf(buf, l, "%llu days ago", (unsigned long long) (d / USEC_PER_DAY));
+        else if (d >= 25*USEC_PER_HOUR)
+                snprintf(buf, l, "1 day and %lluh ago",
+                         (unsigned long long) ((d - USEC_PER_DAY) / USEC_PER_HOUR));
+        else if (d >= 6*USEC_PER_HOUR)
+                snprintf(buf, l, "%lluh ago",
+                         (unsigned long long) (d / USEC_PER_HOUR));
+        else if (d >= USEC_PER_HOUR)
+                snprintf(buf, l, "%lluh %llumin ago",
+                         (unsigned long long) (d / USEC_PER_HOUR),
+                         (unsigned long long) ((d % USEC_PER_HOUR) / USEC_PER_MINUTE));
+        else if (d >= 5*USEC_PER_MINUTE)
+                snprintf(buf, l, "%llumin ago",
+                         (unsigned long long) (d / USEC_PER_MINUTE));
+        else if (d >= USEC_PER_MINUTE)
+                snprintf(buf, l, "%llumin %llus ago",
+                         (unsigned long long) (d / USEC_PER_MINUTE),
+                         (unsigned long long) ((d % USEC_PER_MINUTE) / USEC_PER_SEC));
+        else if (d >= USEC_PER_SEC)
+                snprintf(buf, l, "%llus ago",
+                         (unsigned long long) (d / USEC_PER_SEC));
+        else if (d >= USEC_PER_MSEC)
+                snprintf(buf, l, "%llums ago",
+                         (unsigned long long) (d / USEC_PER_MSEC));
+        else if (d > 0)
+                snprintf(buf, l, "%lluus ago",
+                         (unsigned long long) d);
+        else
+                snprintf(buf, l, "now");
+
+        buf[l-1] = 0;
+        return buf;
+}
+
+char *format_timespan(char *buf, size_t l, usec_t t) {
+        static const struct {
+                const char *suffix;
+                usec_t usec;
+        } table[] = {
+                { "w", USEC_PER_WEEK },
+                { "d", USEC_PER_DAY },
+                { "h", USEC_PER_HOUR },
+                { "min", USEC_PER_MINUTE },
+                { "s", USEC_PER_SEC },
+                { "ms", USEC_PER_MSEC },
+                { "us", 1 },
+        };
+
+        unsigned i;
+        char *p = buf;
+
+        assert(buf);
+        assert(l > 0);
+
+        if (t == (usec_t) -1)
+                return NULL;
+
+        if (t == 0) {
+                snprintf(p, l, "0");
+                p[l-1] = 0;
+                return p;
+        }
+
+        /* The result of this function can be parsed with parse_usec */
+
+        for (i = 0; i < ELEMENTSOF(table); i++) {
+                int k;
+                size_t n;
+
+                if (t < table[i].usec)
+                        continue;
+
+                if (l <= 1)
+                        break;
+
+                k = snprintf(p, l, "%s%llu%s", p > buf ? " " : "", (unsigned long long) (t / table[i].usec), table[i].suffix);
+                n = MIN((size_t) k, l);
+
+                l -= n;
+                p += n;
+
+                t %= table[i].usec;
+        }
+
+        *p = 0;
+
+        return buf;
+}
+
+bool fstype_is_network(const char *fstype) {
+        static const char * const table[] = {
+                "cifs",
+                "smbfs",
+                "ncpfs",
+                "nfs",
+                "nfs4",
+                "gfs",
+                "gfs2"
+        };
+
+        unsigned i;
+
+        for (i = 0; i < ELEMENTSOF(table); i++)
+                if (streq(table[i], fstype))
+                        return true;
+
+        return false;
+}
+
+int chvt(int vt) {
+        int fd, r = 0;
+
+        if ((fd = open_terminal("/dev/tty0", O_RDWR|O_NOCTTY|O_CLOEXEC)) < 0)
+                return -errno;
+
+        if (vt < 0) {
+                int tiocl[2] = {
+                        TIOCL_GETKMSGREDIRECT,
+                        0
+                };
+
+                if (ioctl(fd, TIOCLINUX, tiocl) < 0) {
+                        r = -errno;
+                        goto fail;
+                }
+
+                vt = tiocl[0] <= 0 ? 1 : tiocl[0];
+        }
+
+        if (ioctl(fd, VT_ACTIVATE, vt) < 0)
+                r = -errno;
+
+fail:
+        close_nointr_nofail(fd);
+        return r;
+}
+
+int read_one_char(FILE *f, char *ret, usec_t t, bool *need_nl) {
+        struct termios old_termios, new_termios;
+        char c;
+        char line[LINE_MAX];
+
+        assert(f);
+        assert(ret);
+
+        if (tcgetattr(fileno(f), &old_termios) >= 0) {
+                new_termios = old_termios;
+
+                new_termios.c_lflag &= ~ICANON;
+                new_termios.c_cc[VMIN] = 1;
+                new_termios.c_cc[VTIME] = 0;
+
+                if (tcsetattr(fileno(f), TCSADRAIN, &new_termios) >= 0) {
+                        size_t k;
+
+                        if (t != (usec_t) -1) {
+                                if (fd_wait_for_event(fileno(f), POLLIN, t) <= 0) {
+                                        tcsetattr(fileno(f), TCSADRAIN, &old_termios);
+                                        return -ETIMEDOUT;
+                                }
+                        }
+
+                        k = fread(&c, 1, 1, f);
+
+                        tcsetattr(fileno(f), TCSADRAIN, &old_termios);
+
+                        if (k <= 0)
+                                return -EIO;
+
+                        if (need_nl)
+                                *need_nl = c != '\n';
+
+                        *ret = c;
+                        return 0;
+                }
+        }
+
+        if (t != (usec_t) -1)
+                if (fd_wait_for_event(fileno(f), POLLIN, t) <= 0)
+                        return -ETIMEDOUT;
+
+        if (!fgets(line, sizeof(line), f))
+                return -EIO;
+
+        truncate_nl(line);
+
+        if (strlen(line) != 1)
+                return -EBADMSG;
+
+        if (need_nl)
+                *need_nl = false;
+
+        *ret = line[0];
+        return 0;
+}
+
+int ask(char *ret, const char *replies, const char *text, ...) {
+        bool on_tty;
+
+        assert(ret);
+        assert(replies);
+        assert(text);
+
+        on_tty = isatty(STDOUT_FILENO);
+
+        for (;;) {
+                va_list ap;
+                char c;
+                int r;
+                bool need_nl = true;
+
+                if (on_tty)
+                        fputs(ANSI_HIGHLIGHT_ON, stdout);
+
+                va_start(ap, text);
+                vprintf(text, ap);
+                va_end(ap);
+
+                if (on_tty)
+                        fputs(ANSI_HIGHLIGHT_OFF, stdout);
+
+                fflush(stdout);
+
+                r = read_one_char(stdin, &c, (usec_t) -1, &need_nl);
+                if (r < 0) {
+
+                        if (r == -EBADMSG) {
+                                puts("Bad input, please try again.");
+                                continue;
+                        }
+
+                        putchar('\n');
+                        return r;
+                }
+
+                if (need_nl)
+                        putchar('\n');
+
+                if (strchr(replies, c)) {
+                        *ret = c;
+                        return 0;
+                }
+
+                puts("Read unexpected character, please try again.");
+        }
+}
+
+int reset_terminal_fd(int fd, bool switch_to_text) {
+        struct termios termios;
+        int r = 0;
+
+        /* Set terminal to some sane defaults */
+
+        assert(fd >= 0);
+
+        /* We leave locked terminal attributes untouched, so that
+         * Plymouth may set whatever it wants to set, and we don't
+         * interfere with that. */
+
+        /* Disable exclusive mode, just in case */
+        ioctl(fd, TIOCNXCL);
+
+        /* Switch to text mode */
+        if (switch_to_text)
+                ioctl(fd, KDSETMODE, KD_TEXT);
+
+        /* Enable console unicode mode */
+        ioctl(fd, KDSKBMODE, K_UNICODE);
+
+        if (tcgetattr(fd, &termios) < 0) {
+                r = -errno;
+                goto finish;
+        }
+
+        /* We only reset the stuff that matters to the software. How
+         * hardware is set up we don't touch assuming that somebody
+         * else will do that for us */
+
+        termios.c_iflag &= ~(IGNBRK | BRKINT | ISTRIP | INLCR | IGNCR | IUCLC);
+        termios.c_iflag |= ICRNL | IMAXBEL | IUTF8;
+        termios.c_oflag |= ONLCR;
+        termios.c_cflag |= CREAD;
+        termios.c_lflag = ISIG | ICANON | IEXTEN | ECHO | ECHOE | ECHOK | ECHOCTL | ECHOPRT | ECHOKE;
+
+        termios.c_cc[VINTR]    =   03;  /* ^C */
+        termios.c_cc[VQUIT]    =  034;  /* ^\ */
+        termios.c_cc[VERASE]   = 0177;
+        termios.c_cc[VKILL]    =  025;  /* ^X */
+        termios.c_cc[VEOF]     =   04;  /* ^D */
+        termios.c_cc[VSTART]   =  021;  /* ^Q */
+        termios.c_cc[VSTOP]    =  023;  /* ^S */
+        termios.c_cc[VSUSP]    =  032;  /* ^Z */
+        termios.c_cc[VLNEXT]   =  026;  /* ^V */
+        termios.c_cc[VWERASE]  =  027;  /* ^W */
+        termios.c_cc[VREPRINT] =  022;  /* ^R */
+        termios.c_cc[VEOL]     =    0;
+        termios.c_cc[VEOL2]    =    0;
+
+        termios.c_cc[VTIME]  = 0;
+        termios.c_cc[VMIN]   = 1;
+
+        if (tcsetattr(fd, TCSANOW, &termios) < 0)
+                r = -errno;
+
+finish:
+        /* Just in case, flush all crap out */
+        tcflush(fd, TCIOFLUSH);
+
+        return r;
+}
+
+int reset_terminal(const char *name) {
+        int fd, r;
+
+        fd = open_terminal(name, O_RDWR|O_NOCTTY|O_CLOEXEC);
+        if (fd < 0)
+                return fd;
+
+        r = reset_terminal_fd(fd, true);
+        close_nointr_nofail(fd);
+
+        return r;
+}
+
+int open_terminal(const char *name, int mode) {
+        int fd, r;
+        unsigned c = 0;
+
+        /*
+         * If a TTY is in the process of being closed opening it might
+         * cause EIO. This is horribly awful, but unlikely to be
+         * changed in the kernel. Hence we work around this problem by
+         * retrying a couple of times.
+         *
+         * https://bugs.launchpad.net/ubuntu/+source/linux/+bug/554172/comments/245
+         */
+
+        for (;;) {
+                if ((fd = open(name, mode)) >= 0)
+                        break;
+
+                if (errno != EIO)
+                        return -errno;
+
+                if (c >= 20)
+                        return -errno;
+
+                usleep(50 * USEC_PER_MSEC);
+                c++;
+        }
+
+        if (fd < 0)
+                return -errno;
+
+        if ((r = isatty(fd)) < 0) {
+                close_nointr_nofail(fd);
+                return -errno;
+        }
+
+        if (!r) {
+                close_nointr_nofail(fd);
+                return -ENOTTY;
+        }
+
+        return fd;
+}
+
+int flush_fd(int fd) {
+        struct pollfd pollfd;
+
+        zero(pollfd);
+        pollfd.fd = fd;
+        pollfd.events = POLLIN;
+
+        for (;;) {
+                char buf[LINE_MAX];
+                ssize_t l;
+                int r;
+
+                if ((r = poll(&pollfd, 1, 0)) < 0) {
+
+                        if (errno == EINTR)
+                                continue;
+
+                        return -errno;
+                }
+
+                if (r == 0)
+                        return 0;
+
+                if ((l = read(fd, buf, sizeof(buf))) < 0) {
+
+                        if (errno == EINTR)
+                                continue;
+
+                        if (errno == EAGAIN)
+                                return 0;
+
+                        return -errno;
+                }
+
+                if (l <= 0)
+                        return 0;
+        }
+}
+
+int acquire_terminal(const char *name, bool fail, bool force, bool ignore_tiocstty_eperm) {
+        int fd = -1, notify = -1, r, wd = -1;
+
+        assert(name);
+
+        /* We use inotify to be notified when the tty is closed. We
+         * create the watch before checking if we can actually acquire
+         * it, so that we don't lose any event.
+         *
+         * Note: strictly speaking this actually watches for the
+         * device being closed, it does *not* really watch whether a
+         * tty loses its controlling process. However, unless some
+         * rogue process uses TIOCNOTTY on /dev/tty *after* closing
+         * its tty otherwise this will not become a problem. As long
+         * as the administrator makes sure not configure any service
+         * on the same tty as an untrusted user this should not be a
+         * problem. (Which he probably should not do anyway.) */
+
+        if (!fail && !force) {
+                if ((notify = inotify_init1(IN_CLOEXEC)) < 0) {
+                        r = -errno;
+                        goto fail;
+                }
+
+                if ((wd = inotify_add_watch(notify, name, IN_CLOSE)) < 0) {
+                        r = -errno;
+                        goto fail;
+                }
+        }
+
+        for (;;) {
+                if (notify >= 0)
+                        if ((r = flush_fd(notify)) < 0)
+                                goto fail;
+
+                /* We pass here O_NOCTTY only so that we can check the return
+                 * value TIOCSCTTY and have a reliable way to figure out if we
+                 * successfully became the controlling process of the tty */
+                if ((fd = open_terminal(name, O_RDWR|O_NOCTTY|O_CLOEXEC)) < 0)
+                        return fd;
+
+                /* First, try to get the tty */
+                r = ioctl(fd, TIOCSCTTY, force);
+
+                /* Sometimes it makes sense to ignore TIOCSCTTY
+                 * returning EPERM, i.e. when very likely we already
+                 * are have this controlling terminal. */
+                if (r < 0 && errno == EPERM && ignore_tiocstty_eperm)
+                        r = 0;
+
+                if (r < 0 && (force || fail || errno != EPERM)) {
+                        r = -errno;
+                        goto fail;
+                }
+
+                if (r >= 0)
+                        break;
+
+                assert(!fail);
+                assert(!force);
+                assert(notify >= 0);
+
+                for (;;) {
+                        uint8_t inotify_buffer[sizeof(struct inotify_event) + FILENAME_MAX];
+                        ssize_t l;
+                        struct inotify_event *e;
+
+                        if ((l = read(notify, inotify_buffer, sizeof(inotify_buffer))) < 0) {
+
+                                if (errno == EINTR)
+                                        continue;
+
+                                r = -errno;
+                                goto fail;
+                        }
+
+                        e = (struct inotify_event*) inotify_buffer;
+
+                        while (l > 0) {
+                                size_t step;
+
+                                if (e->wd != wd || !(e->mask & IN_CLOSE)) {
+                                        r = -EIO;
+                                        goto fail;
+                                }
+
+                                step = sizeof(struct inotify_event) + e->len;
+                                assert(step <= (size_t) l);
+
+                                e = (struct inotify_event*) ((uint8_t*) e + step);
+                                l -= step;
+                        }
+
+                        break;
+                }
+
+                /* We close the tty fd here since if the old session
+                 * ended our handle will be dead. It's important that
+                 * we do this after sleeping, so that we don't enter
+                 * an endless loop. */
+                close_nointr_nofail(fd);
+        }
+
+        if (notify >= 0)
+                close_nointr_nofail(notify);
+
+        r = reset_terminal_fd(fd, true);
+        if (r < 0)
+                log_warning("Failed to reset terminal: %s", strerror(-r));
+
+        return fd;
+
+fail:
+        if (fd >= 0)
+                close_nointr_nofail(fd);
+
+        if (notify >= 0)
+                close_nointr_nofail(notify);
+
+        return r;
+}
+
+int release_terminal(void) {
+        int r = 0, fd;
+        struct sigaction sa_old, sa_new;
+
+        if ((fd = open("/dev/tty", O_RDWR|O_NOCTTY|O_NDELAY|O_CLOEXEC)) < 0)
+                return -errno;
+
+        /* Temporarily ignore SIGHUP, so that we don't get SIGHUP'ed
+         * by our own TIOCNOTTY */
+
+        zero(sa_new);
+        sa_new.sa_handler = SIG_IGN;
+        sa_new.sa_flags = SA_RESTART;
+        assert_se(sigaction(SIGHUP, &sa_new, &sa_old) == 0);
+
+        if (ioctl(fd, TIOCNOTTY) < 0)
+                r = -errno;
+
+        assert_se(sigaction(SIGHUP, &sa_old, NULL) == 0);
+
+        close_nointr_nofail(fd);
+        return r;
+}
+
+int sigaction_many(const struct sigaction *sa, ...) {
+        va_list ap;
+        int r = 0, sig;
+
+        va_start(ap, sa);
+        while ((sig = va_arg(ap, int)) > 0)
+                if (sigaction(sig, sa, NULL) < 0)
+                        r = -errno;
+        va_end(ap);
+
+        return r;
+}
+
+int ignore_signals(int sig, ...) {
+        struct sigaction sa;
+        va_list ap;
+        int r = 0;
+
+        zero(sa);
+        sa.sa_handler = SIG_IGN;
+        sa.sa_flags = SA_RESTART;
+
+        if (sigaction(sig, &sa, NULL) < 0)
+                r = -errno;
+
+        va_start(ap, sig);
+        while ((sig = va_arg(ap, int)) > 0)
+                if (sigaction(sig, &sa, NULL) < 0)
+                        r = -errno;
+        va_end(ap);
+
+        return r;
+}
+
+int default_signals(int sig, ...) {
+        struct sigaction sa;
+        va_list ap;
+        int r = 0;
+
+        zero(sa);
+        sa.sa_handler = SIG_DFL;
+        sa.sa_flags = SA_RESTART;
+
+        if (sigaction(sig, &sa, NULL) < 0)
+                r = -errno;
+
+        va_start(ap, sig);
+        while ((sig = va_arg(ap, int)) > 0)
+                if (sigaction(sig, &sa, NULL) < 0)
+                        r = -errno;
+        va_end(ap);
+
+        return r;
+}
+
+int close_pipe(int p[]) {
+        int a = 0, b = 0;
+
+        assert(p);
+
+        if (p[0] >= 0) {
+                a = close_nointr(p[0]);
+                p[0] = -1;
+        }
+
+        if (p[1] >= 0) {
+                b = close_nointr(p[1]);
+                p[1] = -1;
+        }
+
+        return a < 0 ? a : b;
+}
+
+ssize_t loop_read(int fd, void *buf, size_t nbytes, bool do_poll) {
+        uint8_t *p;
+        ssize_t n = 0;
+
+        assert(fd >= 0);
+        assert(buf);
+
+        p = buf;
+
+        while (nbytes > 0) {
+                ssize_t k;
+
+                if ((k = read(fd, p, nbytes)) <= 0) {
+
+                        if (k < 0 && errno == EINTR)
+                                continue;
+
+                        if (k < 0 && errno == EAGAIN && do_poll) {
+                                struct pollfd pollfd;
+
+                                zero(pollfd);
+                                pollfd.fd = fd;
+                                pollfd.events = POLLIN;
+
+                                if (poll(&pollfd, 1, -1) < 0) {
+                                        if (errno == EINTR)
+                                                continue;
+
+                                        return n > 0 ? n : -errno;
+                                }
+
+                                if (pollfd.revents != POLLIN)
+                                        return n > 0 ? n : -EIO;
+
+                                continue;
+                        }
+
+                        return n > 0 ? n : (k < 0 ? -errno : 0);
+                }
+
+                p += k;
+                nbytes -= k;
+                n += k;
+        }
+
+        return n;
+}
+
+ssize_t loop_write(int fd, const void *buf, size_t nbytes, bool do_poll) {
+        const uint8_t *p;
+        ssize_t n = 0;
+
+        assert(fd >= 0);
+        assert(buf);
+
+        p = buf;
+
+        while (nbytes > 0) {
+                ssize_t k;
+
+                k = write(fd, p, nbytes);
+                if (k <= 0) {
+
+                        if (k < 0 && errno == EINTR)
+                                continue;
+
+                        if (k < 0 && errno == EAGAIN && do_poll) {
+                                struct pollfd pollfd;
+
+                                zero(pollfd);
+                                pollfd.fd = fd;
+                                pollfd.events = POLLOUT;
+
+                                if (poll(&pollfd, 1, -1) < 0) {
+                                        if (errno == EINTR)
+                                                continue;
+
+                                        return n > 0 ? n : -errno;
+                                }
+
+                                if (pollfd.revents != POLLOUT)
+                                        return n > 0 ? n : -EIO;
+
+                                continue;
+                        }
+
+                        return n > 0 ? n : (k < 0 ? -errno : 0);
+                }
+
+                p += k;
+                nbytes -= k;
+                n += k;
+        }
+
+        return n;
+}
+
+int path_is_mount_point(const char *t, bool allow_symlink) {
+        struct stat a, b;
+        char *parent;
+        int r;
+
+        if (allow_symlink)
+                r = stat(t, &a);
+        else
+                r = lstat(t, &a);
+
+        if (r < 0) {
+                if (errno == ENOENT)
+                        return 0;
+
+                return -errno;
+        }
+
+        r = parent_of_path(t, &parent);
+        if (r < 0)
+                return r;
+
+        r = lstat(parent, &b);
+        free(parent);
+
+        if (r < 0)
+                return -errno;
+
+        return a.st_dev != b.st_dev;
+}
+
+int parse_usec(const char *t, usec_t *usec) {
+        static const struct {
+                const char *suffix;
+                usec_t usec;
+        } table[] = {
+                { "sec", USEC_PER_SEC },
+                { "s", USEC_PER_SEC },
+                { "min", USEC_PER_MINUTE },
+                { "hr", USEC_PER_HOUR },
+                { "h", USEC_PER_HOUR },
+                { "d", USEC_PER_DAY },
+                { "w", USEC_PER_WEEK },
+                { "msec", USEC_PER_MSEC },
+                { "ms", USEC_PER_MSEC },
+                { "m", USEC_PER_MINUTE },
+                { "usec", 1ULL },
+                { "us", 1ULL },
+                { "", USEC_PER_SEC },
+        };
+
+        const char *p;
+        usec_t r = 0;
+
+        assert(t);
+        assert(usec);
+
+        p = t;
+        do {
+                long long l;
+                char *e;
+                unsigned i;
+
+                errno = 0;
+                l = strtoll(p, &e, 10);
+
+                if (errno != 0)
+                        return -errno;
+
+                if (l < 0)
+                        return -ERANGE;
+
+                if (e == p)
+                        return -EINVAL;
+
+                e += strspn(e, WHITESPACE);
+
+                for (i = 0; i < ELEMENTSOF(table); i++)
+                        if (startswith(e, table[i].suffix)) {
+                                r += (usec_t) l * table[i].usec;
+                                p = e + strlen(table[i].suffix);
+                                break;
+                        }
+
+                if (i >= ELEMENTSOF(table))
+                        return -EINVAL;
+
+        } while (*p != 0);
+
+        *usec = r;
+
+        return 0;
+}
+
+int parse_bytes(const char *t, off_t *bytes) {
+        static const struct {
+                const char *suffix;
+                off_t factor;
+        } table[] = {
+                { "B", 1 },
+                { "K", 1024ULL },
+                { "M", 1024ULL*1024ULL },
+                { "G", 1024ULL*1024ULL*1024ULL },
+                { "T", 1024ULL*1024ULL*1024ULL*1024ULL },
+                { "P", 1024ULL*1024ULL*1024ULL*1024ULL*1024ULL },
+                { "E", 1024ULL*1024ULL*1024ULL*1024ULL*1024ULL*1024ULL },
+                { "", 1 },
+        };
+
+        const char *p;
+        off_t r = 0;
+
+        assert(t);
+        assert(bytes);
+
+        p = t;
+        do {
+                long long l;
+                char *e;
+                unsigned i;
+
+                errno = 0;
+                l = strtoll(p, &e, 10);
+
+                if (errno != 0)
+                        return -errno;
+
+                if (l < 0)
+                        return -ERANGE;
+
+                if (e == p)
+                        return -EINVAL;
+
+                e += strspn(e, WHITESPACE);
+
+                for (i = 0; i < ELEMENTSOF(table); i++)
+                        if (startswith(e, table[i].suffix)) {
+                                r += (off_t) l * table[i].factor;
+                                p = e + strlen(table[i].suffix);
+                                break;
+                        }
+
+                if (i >= ELEMENTSOF(table))
+                        return -EINVAL;
+
+        } while (*p != 0);
+
+        *bytes = r;
+
+        return 0;
+}
+
+int make_stdio(int fd) {
+        int r, s, t;
+
+        assert(fd >= 0);
+
+        r = dup2(fd, STDIN_FILENO);
+        s = dup2(fd, STDOUT_FILENO);
+        t = dup2(fd, STDERR_FILENO);
+
+        if (fd >= 3)
+                close_nointr_nofail(fd);
+
+        if (r < 0 || s < 0 || t < 0)
+                return -errno;
+
+        fd_cloexec(STDIN_FILENO, false);
+        fd_cloexec(STDOUT_FILENO, false);
+        fd_cloexec(STDERR_FILENO, false);
+
+        return 0;
+}
+
+int make_null_stdio(void) {
+        int null_fd;
+
+        if ((null_fd = open("/dev/null", O_RDWR|O_NOCTTY)) < 0)
+                return -errno;
+
+        return make_stdio(null_fd);
+}
+
+bool is_device_path(const char *path) {
+
+        /* Returns true on paths that refer to a device, either in
+         * sysfs or in /dev */
+
+        return
+                path_startswith(path, "/dev/") ||
+                path_startswith(path, "/sys/");
+}
+
+int dir_is_empty(const char *path) {
+        DIR *d;
+        int r;
+        struct dirent buf, *de;
+
+        if (!(d = opendir(path)))
+                return -errno;
+
+        for (;;) {
+                if ((r = readdir_r(d, &buf, &de)) > 0) {
+                        r = -r;
+                        break;
+                }
+
+                if (!de) {
+                        r = 1;
+                        break;
+                }
+
+                if (!ignore_file(de->d_name)) {
+                        r = 0;
+                        break;
+                }
+        }
+
+        closedir(d);
+        return r;
+}
+
+unsigned long long random_ull(void) {
+        int fd;
+        uint64_t ull;
+        ssize_t r;
+
+        if ((fd = open("/dev/urandom", O_RDONLY|O_CLOEXEC|O_NOCTTY)) < 0)
+                goto fallback;
+
+        r = loop_read(fd, &ull, sizeof(ull), true);
+        close_nointr_nofail(fd);
+
+        if (r != sizeof(ull))
+                goto fallback;
+
+        return ull;
+
+fallback:
+        return random() * RAND_MAX + random();
+}
+
+void rename_process(const char name[8]) {
+        assert(name);
+
+        /* This is a like a poor man's setproctitle(). It changes the
+         * comm field, argv[0], and also the glibc's internally used
+         * name of the process. For the first one a limit of 16 chars
+         * applies, to the second one usually one of 10 (i.e. length
+         * of "/sbin/init"), to the third one one of 7 (i.e. length of
+         * "systemd"). If you pass a longer string it will be
+         * truncated */
+
+        prctl(PR_SET_NAME, name);
+
+        if (program_invocation_name)
+                strncpy(program_invocation_name, name, strlen(program_invocation_name));
+
+        if (saved_argc > 0) {
+                int i;
+
+                if (saved_argv[0])
+                        strncpy(saved_argv[0], name, strlen(saved_argv[0]));
+
+                for (i = 1; i < saved_argc; i++) {
+                        if (!saved_argv[i])
+                                break;
+
+                        memset(saved_argv[i], 0, strlen(saved_argv[i]));
+                }
+        }
+}
+
+void sigset_add_many(sigset_t *ss, ...) {
+        va_list ap;
+        int sig;
+
+        assert(ss);
+
+        va_start(ap, ss);
+        while ((sig = va_arg(ap, int)) > 0)
+                assert_se(sigaddset(ss, sig) == 0);
+        va_end(ap);
+}
+
+char* gethostname_malloc(void) {
+        struct utsname u;
+
+        assert_se(uname(&u) >= 0);
+
+        if (u.nodename[0])
+                return strdup(u.nodename);
+
+        return strdup(u.sysname);
+}
+
+char* getlogname_malloc(void) {
+        uid_t uid;
+        long bufsize;
+        char *buf, *name;
+        struct passwd pwbuf, *pw = NULL;
+        struct stat st;
+
+        if (isatty(STDIN_FILENO) && fstat(STDIN_FILENO, &st) >= 0)
+                uid = st.st_uid;
+        else
+                uid = getuid();
+
+        /* Shortcut things to avoid NSS lookups */
+        if (uid == 0)
+                return strdup("root");
+
+        if ((bufsize = sysconf(_SC_GETPW_R_SIZE_MAX)) <= 0)
+                bufsize = 4096;
+
+        if (!(buf = malloc(bufsize)))
+                return NULL;
+
+        if (getpwuid_r(uid, &pwbuf, buf, bufsize, &pw) == 0 && pw) {
+                name = strdup(pw->pw_name);
+                free(buf);
+                return name;
+        }
+
+        free(buf);
+
+        if (asprintf(&name, "%lu", (unsigned long) uid) < 0)
+                return NULL;
+
+        return name;
+}
+
+int getttyname_malloc(int fd, char **r) {
+        char path[PATH_MAX], *c;
+        int k;
+
+        assert(r);
+
+        if ((k = ttyname_r(fd, path, sizeof(path))) != 0)
+                return -k;
+
+        char_array_0(path);
+
+        if (!(c = strdup(startswith(path, "/dev/") ? path + 5 : path)))
+                return -ENOMEM;
+
+        *r = c;
+        return 0;
+}
+
+int getttyname_harder(int fd, char **r) {
+        int k;
+        char *s;
+
+        if ((k = getttyname_malloc(fd, &s)) < 0)
+                return k;
+
+        if (streq(s, "tty")) {
+                free(s);
+                return get_ctty(0, NULL, r);
+        }
+
+        *r = s;
+        return 0;
+}
+
+int get_ctty_devnr(pid_t pid, dev_t *d) {
+        int k;
+        char line[LINE_MAX], *p, *fn;
+        unsigned long ttynr;
+        FILE *f;
+
+        if (asprintf(&fn, "/proc/%lu/stat", (unsigned long) (pid <= 0 ? getpid() : pid)) < 0)
+                return -ENOMEM;
+
+        f = fopen(fn, "re");
+        free(fn);
+        if (!f)
+                return -errno;
+
+        if (!fgets(line, sizeof(line), f)) {
+                k = feof(f) ? -EIO : -errno;
+                fclose(f);
+                return k;
+        }
+
+        fclose(f);
+
+        p = strrchr(line, ')');
+        if (!p)
+                return -EIO;
+
+        p++;
+
+        if (sscanf(p, " "
+                   "%*c "  /* state */
+                   "%*d "  /* ppid */
+                   "%*d "  /* pgrp */
+                   "%*d "  /* session */
+                   "%lu ", /* ttynr */
+                   &ttynr) != 1)
+                return -EIO;
+
+        *d = (dev_t) ttynr;
+        return 0;
+}
+
+int get_ctty(pid_t pid, dev_t *_devnr, char **r) {
+        int k;
+        char fn[PATH_MAX], *s, *b, *p;
+        dev_t devnr;
+
+        assert(r);
+
+        k = get_ctty_devnr(pid, &devnr);
+        if (k < 0)
+                return k;
+
+        snprintf(fn, sizeof(fn), "/dev/char/%u:%u", major(devnr), minor(devnr));
+        char_array_0(fn);
+
+        if ((k = readlink_malloc(fn, &s)) < 0) {
+
+                if (k != -ENOENT)
+                        return k;
+
+                /* This is an ugly hack */
+                if (major(devnr) == 136) {
+                        if (asprintf(&b, "pts/%lu", (unsigned long) minor(devnr)) < 0)
+                                return -ENOMEM;
+
+                        *r = b;
+                        if (_devnr)
+                                *_devnr = devnr;
+
+                        return 0;
+                }
+
+                /* Probably something like the ptys which have no
+                 * symlink in /dev/char. Let's return something
+                 * vaguely useful. */
+
+                if (!(b = strdup(fn + 5)))
+                        return -ENOMEM;
+
+                *r = b;
+                if (_devnr)
+                        *_devnr = devnr;
+
+                return 0;
+        }
+
+        if (startswith(s, "/dev/"))
+                p = s + 5;
+        else if (startswith(s, "../"))
+                p = s + 3;
+        else
+                p = s;
+
+        b = strdup(p);
+        free(s);
+
+        if (!b)
+                return -ENOMEM;
+
+        *r = b;
+        if (_devnr)
+                *_devnr = devnr;
+
+        return 0;
+}
+
+static int rm_rf_children(int fd, bool only_dirs, bool honour_sticky) {
+        DIR *d;
+        int ret = 0;
+
+        assert(fd >= 0);
+
+        /* This returns the first error we run into, but nevertheless
+         * tries to go on */
+
+        if (!(d = fdopendir(fd))) {
+                close_nointr_nofail(fd);
+
+                return errno == ENOENT ? 0 : -errno;
+        }
+
+        for (;;) {
+                struct dirent buf, *de;
+                bool is_dir, keep_around = false;
+                int r;
+
+                if ((r = readdir_r(d, &buf, &de)) != 0) {
+                        if (ret == 0)
+                                ret = -r;
+                        break;
+                }
+
+                if (!de)
+                        break;
+
+                if (streq(de->d_name, ".") || streq(de->d_name, ".."))
+                        continue;
+
+                if (de->d_type == DT_UNKNOWN) {
+                        struct stat st;
+
+                        if (fstatat(fd, de->d_name, &st, AT_SYMLINK_NOFOLLOW) < 0) {
+                                if (ret == 0 && errno != ENOENT)
+                                        ret = -errno;
+                                continue;
+                        }
+
+                        if (honour_sticky)
+                                keep_around =
+                                        (st.st_uid == 0 || st.st_uid == getuid()) &&
+                                        (st.st_mode & S_ISVTX);
+
+                        is_dir = S_ISDIR(st.st_mode);
+
+                } else {
+                        if (honour_sticky) {
+                                struct stat st;
+
+                                if (fstatat(fd, de->d_name, &st, AT_SYMLINK_NOFOLLOW) < 0) {
+                                        if (ret == 0 && errno != ENOENT)
+                                                ret = -errno;
+                                        continue;
+                                }
+
+                                keep_around =
+                                        (st.st_uid == 0 || st.st_uid == getuid()) &&
+                                        (st.st_mode & S_ISVTX);
+                        }
+
+                        is_dir = de->d_type == DT_DIR;
+                }
+
+                if (is_dir) {
+                        int subdir_fd;
+
+                        subdir_fd = openat(fd, de->d_name, O_RDONLY|O_NONBLOCK|O_DIRECTORY|O_CLOEXEC|O_NOFOLLOW);
+                        if (subdir_fd < 0) {
+                                if (ret == 0 && errno != ENOENT)
+                                        ret = -errno;
+                                continue;
+                        }
+
+                        if ((r = rm_rf_children(subdir_fd, only_dirs, honour_sticky)) < 0) {
+                                if (ret == 0)
+                                        ret = r;
+                        }
+
+                        if (!keep_around)
+                                if (unlinkat(fd, de->d_name, AT_REMOVEDIR) < 0) {
+                                        if (ret == 0 && errno != ENOENT)
+                                                ret = -errno;
+                                }
+
+                } else if (!only_dirs && !keep_around) {
+
+                        if (unlinkat(fd, de->d_name, 0) < 0) {
+                                if (ret == 0 && errno != ENOENT)
+                                        ret = -errno;
+                        }
+                }
+        }
+
+        closedir(d);
+
+        return ret;
+}
+
+int rm_rf(const char *path, bool only_dirs, bool delete_root, bool honour_sticky) {
+        int fd;
+        int r;
+
+        assert(path);
+
+        if ((fd = open(path, O_RDONLY|O_NONBLOCK|O_DIRECTORY|O_CLOEXEC)) < 0) {
+
+                if (errno != ENOTDIR)
+                        return -errno;
+
+                if (delete_root && !only_dirs)
+                        if (unlink(path) < 0)
+                                return -errno;
+
+                return 0;
+        }
+
+        r = rm_rf_children(fd, only_dirs, honour_sticky);
+
+        if (delete_root) {
+
+                if (honour_sticky && file_is_priv_sticky(path) > 0)
+                        return r;
+
+                if (rmdir(path) < 0 && errno != ENOENT) {
+                        if (r == 0)
+                                r = -errno;
+                }
+        }
+
+        return r;
+}
+
+int chmod_and_chown(const char *path, mode_t mode, uid_t uid, gid_t gid) {
+        assert(path);
+
+        /* Under the assumption that we are running privileged we
+         * first change the access mode and only then hand out
+         * ownership to avoid a window where access is too open. */
+
+        if (mode != (mode_t) -1)
+                if (chmod(path, mode) < 0)
+                        return -errno;
+
+        if (uid != (uid_t) -1 || gid != (gid_t) -1)
+                if (chown(path, uid, gid) < 0)
+                        return -errno;
+
+        return 0;
+}
+
+int fchmod_and_fchown(int fd, mode_t mode, uid_t uid, gid_t gid) {
+        assert(fd >= 0);
+
+        /* Under the assumption that we are running privileged we
+         * first change the access mode and only then hand out
+         * ownership to avoid a window where access is too open. */
+
+        if (fchmod(fd, mode) < 0)
+                return -errno;
+
+        if (fchown(fd, uid, gid) < 0)
+                return -errno;
+
+        return 0;
+}
+
+cpu_set_t* cpu_set_malloc(unsigned *ncpus) {
+        cpu_set_t *r;
+        unsigned n = 1024;
+
+        /* Allocates the cpuset in the right size */
+
+        for (;;) {
+                if (!(r = CPU_ALLOC(n)))
+                        return NULL;
+
+                if (sched_getaffinity(0, CPU_ALLOC_SIZE(n), r) >= 0) {
+                        CPU_ZERO_S(CPU_ALLOC_SIZE(n), r);
+
+                        if (ncpus)
+                                *ncpus = n;
+
+                        return r;
+                }
+
+                CPU_FREE(r);
+
+                if (errno != EINVAL)
+                        return NULL;
+
+                n *= 2;
+        }
+}
+
+void status_vprintf(const char *status, bool ellipse, const char *format, va_list ap) {
+        char *s = NULL, *spaces = NULL, *e;
+        int fd = -1, c;
+        size_t emax, sl, left;
+        struct iovec iovec[5];
+        int n = 0;
+
+        assert(format);
+
+        /* This independent of logging, as status messages are
+         * optional and go exclusively to the console. */
+
+        if (vasprintf(&s, format, ap) < 0)
+                goto finish;
+
+        fd = open_terminal("/dev/console", O_WRONLY|O_NOCTTY|O_CLOEXEC);
+        if (fd < 0)
+                goto finish;
+
+        if (ellipse) {
+                c = fd_columns(fd);
+                if (c <= 0)
+                        c = 80;
+
+                if (status) {
+                        sl = 2 + 6 + 1; /* " [" status "]" */
+                        emax = (size_t) c > sl ? c - sl - 1 : 0;
+                } else
+                        emax = c - 1;
+
+                e = ellipsize(s, emax, 75);
+                if (e) {
+                        free(s);
+                        s = e;
+                }
+        }
+
+        zero(iovec);
+        IOVEC_SET_STRING(iovec[n++], s);
+
+        if (ellipse) {
+                sl = strlen(s);
+                left = emax > sl ? emax - sl : 0;
+                if (left > 0) {
+                        spaces = malloc(left);
+                        if (spaces) {
+                                memset(spaces, ' ', left);
+                                iovec[n].iov_base = spaces;
+                                iovec[n].iov_len = left;
+                                n++;
+                        }
+                }
+        }
+
+        if (status) {
+                IOVEC_SET_STRING(iovec[n++], " [");
+                IOVEC_SET_STRING(iovec[n++], status);
+                IOVEC_SET_STRING(iovec[n++], "]\n");
+        } else
+                IOVEC_SET_STRING(iovec[n++], "\n");
+
+        writev(fd, iovec, n);
+
+finish:
+        free(s);
+        free(spaces);
+
+        if (fd >= 0)
+                close_nointr_nofail(fd);
+}
+
+void status_printf(const char *status, bool ellipse, const char *format, ...) {
+        va_list ap;
+
+        assert(format);
+
+        va_start(ap, format);
+        status_vprintf(status, ellipse, format, ap);
+        va_end(ap);
+}
+
+void status_welcome(void) {
+        char *pretty_name = NULL, *ansi_color = NULL;
+        const char *const_pretty = NULL, *const_color = NULL;
+        int r;
+
+        if ((r = parse_env_file("/etc/os-release", NEWLINE,
+                                "PRETTY_NAME", &pretty_name,
+                                "ANSI_COLOR", &ansi_color,
+                                NULL)) < 0) {
+
+                if (r != -ENOENT)
+                        log_warning("Failed to read /etc/os-release: %s", strerror(-r));
+        }
+
+        if (!pretty_name && !const_pretty)
+                const_pretty = "Linux";
+
+        if (!ansi_color && !const_color)
+                const_color = "1";
+
+        status_printf(NULL,
+                      false,
+                      "\nWelcome to \x1B[%sm%s\x1B[0m!\n",
+                      const_color ? const_color : ansi_color,
+                      const_pretty ? const_pretty : pretty_name);
+
+        free(ansi_color);
+        free(pretty_name);
+}
+
+char *replace_env(const char *format, char **env) {
+        enum {
+                WORD,
+                CURLY,
+                VARIABLE
+        } state = WORD;
+
+        const char *e, *word = format;
+        char *r = NULL, *k;
+
+        assert(format);
+
+        for (e = format; *e; e ++) {
+
+                switch (state) {
+
+                case WORD:
+                        if (*e == '$')
+                                state = CURLY;
+                        break;
+
+                case CURLY:
+                        if (*e == '{') {
+                                if (!(k = strnappend(r, word, e-word-1)))
+                                        goto fail;
+
+                                free(r);
+                                r = k;
+
+                                word = e-1;
+                                state = VARIABLE;
+
+                        } else if (*e == '$') {
+                                if (!(k = strnappend(r, word, e-word)))
+                                        goto fail;
+
+                                free(r);
+                                r = k;
+
+                                word = e+1;
+                                state = WORD;
+                        } else
+                                state = WORD;
+                        break;
+
+                case VARIABLE:
+                        if (*e == '}') {
+                                const char *t;
+
+                                if (!(t = strv_env_get_with_length(env, word+2, e-word-2)))
+                                        t = "";
+
+                                if (!(k = strappend(r, t)))
+                                        goto fail;
+
+                                free(r);
+                                r = k;
+
+                                word = e+1;
+                                state = WORD;
+                        }
+                        break;
+                }
+        }
+
+        if (!(k = strnappend(r, word, e-word)))
+                goto fail;
+
+        free(r);
+        return k;
+
+fail:
+        free(r);
+        return NULL;
+}
+
+char **replace_env_argv(char **argv, char **env) {
+        char **r, **i;
+        unsigned k = 0, l = 0;
+
+        l = strv_length(argv);
+
+        if (!(r = new(char*, l+1)))
+                return NULL;
+
+        STRV_FOREACH(i, argv) {
+
+                /* If $FOO appears as single word, replace it by the split up variable */
+                if ((*i)[0] == '$' && (*i)[1] != '{') {
+                        char *e;
+                        char **w, **m;
+                        unsigned q;
+
+                        if ((e = strv_env_get(env, *i+1))) {
+
+                                if (!(m = strv_split_quoted(e))) {
+                                        r[k] = NULL;
+                                        strv_free(r);
+                                        return NULL;
+                                }
+                        } else
+                                m = NULL;
+
+                        q = strv_length(m);
+                        l = l + q - 1;
+
+                        if (!(w = realloc(r, sizeof(char*) * (l+1)))) {
+                                r[k] = NULL;
+                                strv_free(r);
+                                strv_free(m);
+                                return NULL;
+                        }
+
+                        r = w;
+                        if (m) {
+                                memcpy(r + k, m, q * sizeof(char*));
+                                free(m);
+                        }
+
+                        k += q;
+                        continue;
+                }
+
+                /* If ${FOO} appears as part of a word, replace it by the variable as-is */
+                if (!(r[k++] = replace_env(*i, env))) {
+                        strv_free(r);
+                        return NULL;
+                }
+        }
+
+        r[k] = NULL;
+        return r;
+}
+
+int fd_columns(int fd) {
+        struct winsize ws;
+        zero(ws);
+
+        if (ioctl(fd, TIOCGWINSZ, &ws) < 0)
+                return -errno;
+
+        if (ws.ws_col <= 0)
+                return -EIO;
+
+        return ws.ws_col;
+}
+
+unsigned columns(void) {
+        static __thread int parsed_columns = 0;
+        const char *e;
+
+        if (_likely_(parsed_columns > 0))
+                return parsed_columns;
+
+        e = getenv("COLUMNS");
+        if (e)
+                parsed_columns = atoi(e);
+
+        if (parsed_columns <= 0)
+                parsed_columns = fd_columns(STDOUT_FILENO);
+
+        if (parsed_columns <= 0)
+                parsed_columns = 80;
+
+        return parsed_columns;
+}
+
+int fd_lines(int fd) {
+        struct winsize ws;
+        zero(ws);
+
+        if (ioctl(fd, TIOCGWINSZ, &ws) < 0)
+                return -errno;
+
+        if (ws.ws_row <= 0)
+                return -EIO;
+
+        return ws.ws_row;
+}
+
+unsigned lines(void) {
+        static __thread int parsed_lines = 0;
+        const char *e;
+
+        if (_likely_(parsed_lines > 0))
+                return parsed_lines;
+
+        e = getenv("LINES");
+        if (e)
+                parsed_lines = atoi(e);
+
+        if (parsed_lines <= 0)
+                parsed_lines = fd_lines(STDOUT_FILENO);
+
+        if (parsed_lines <= 0)
+                parsed_lines = 25;
+
+        return parsed_lines;
+}
+
+int running_in_chroot(void) {
+        struct stat a, b;
+
+        zero(a);
+        zero(b);
+
+        /* Only works as root */
+
+        if (stat("/proc/1/root", &a) < 0)
+                return -errno;
+
+        if (stat("/", &b) < 0)
+                return -errno;
+
+        return
+                a.st_dev != b.st_dev ||
+                a.st_ino != b.st_ino;
+}
+
+char *ellipsize_mem(const char *s, size_t old_length, size_t new_length, unsigned percent) {
+        size_t x;
+        char *r;
+
+        assert(s);
+        assert(percent <= 100);
+        assert(new_length >= 3);
+
+        if (old_length <= 3 || old_length <= new_length)
+                return strndup(s, old_length);
+
+        r = new0(char, new_length+1);
+        if (!r)
+                return r;
+
+        x = (new_length * percent) / 100;
+
+        if (x > new_length - 3)
+                x = new_length - 3;
+
+        memcpy(r, s, x);
+        r[x] = '.';
+        r[x+1] = '.';
+        r[x+2] = '.';
+        memcpy(r + x + 3,
+               s + old_length - (new_length - x - 3),
+               new_length - x - 3);
+
+        return r;
+}
+
+char *ellipsize(const char *s, size_t length, unsigned percent) {
+        return ellipsize_mem(s, strlen(s), length, percent);
+}
+
+int touch(const char *path) {
+        int fd;
+
+        assert(path);
+
+        if ((fd = open(path, O_WRONLY|O_CREAT|O_CLOEXEC|O_NOCTTY, 0644)) < 0)
+                return -errno;
+
+        close_nointr_nofail(fd);
+        return 0;
+}
+
+char *unquote(const char *s, const char* quotes) {
+        size_t l;
+        assert(s);
+
+        l = strlen(s);
+        if (l < 2)
+                return strdup(s);
+
+        if (strchr(quotes, s[0]) && s[l-1] == s[0])
+                return strndup(s+1, l-2);
+
+        return strdup(s);
+}
+
+char *normalize_env_assignment(const char *s) {
+        char *name, *value, *p, *r;
+
+        p = strchr(s, '=');
+
+        if (!p) {
+                if (!(r = strdup(s)))
+                        return NULL;
+
+                return strstrip(r);
+        }
+
+        if (!(name = strndup(s, p - s)))
+                return NULL;
+
+        if (!(p = strdup(p+1))) {
+                free(name);
+                return NULL;
+        }
+
+        value = unquote(strstrip(p), QUOTES);
+        free(p);
+
+        if (!value) {
+                free(name);
+                return NULL;
+        }
+
+        if (asprintf(&r, "%s=%s", name, value) < 0)
+                r = NULL;
+
+        free(value);
+        free(name);
+
+        return r;
+}
+
+int wait_for_terminate(pid_t pid, siginfo_t *status) {
+        siginfo_t dummy;
+
+        assert(pid >= 1);
+
+        if (!status)
+                status = &dummy;
+
+        for (;;) {
+                zero(*status);
+
+                if (waitid(P_PID, pid, status, WEXITED) < 0) {
+
+                        if (errno == EINTR)
+                                continue;
+
+                        return -errno;
+                }
+
+                return 0;
+        }
+}
+
+int wait_for_terminate_and_warn(const char *name, pid_t pid) {
+        int r;
+        siginfo_t status;
+
+        assert(name);
+        assert(pid > 1);
+
+        if ((r = wait_for_terminate(pid, &status)) < 0) {
+                log_warning("Failed to wait for %s: %s", name, strerror(-r));
+                return r;
+        }
+
+        if (status.si_code == CLD_EXITED) {
+                if (status.si_status != 0) {
+                        log_warning("%s failed with error code %i.", name, status.si_status);
+                        return status.si_status;
+                }
+
+                log_debug("%s succeeded.", name);
+                return 0;
+
+        } else if (status.si_code == CLD_KILLED ||
+                   status.si_code == CLD_DUMPED) {
+
+                log_warning("%s terminated by signal %s.", name, signal_to_string(status.si_status));
+                return -EPROTO;
+        }
+
+        log_warning("%s failed due to unknown reason.", name);
+        return -EPROTO;
+
+}
+
+_noreturn_ void freeze(void) {
+
+        /* Make sure nobody waits for us on a socket anymore */
+        close_all_fds(NULL, 0);
+
+        sync();
+
+        for (;;)
+                pause();
+}
+
+bool null_or_empty(struct stat *st) {
+        assert(st);
+
+        if (S_ISREG(st->st_mode) && st->st_size <= 0)
+                return true;
+
+        if (S_ISCHR(st->st_mode) || S_ISBLK(st->st_mode))
+                return true;
+
+        return false;
+}
+
+int null_or_empty_path(const char *fn) {
+        struct stat st;
+
+        assert(fn);
+
+        if (stat(fn, &st) < 0)
+                return -errno;
+
+        return null_or_empty(&st);
+}
+
+DIR *xopendirat(int fd, const char *name, int flags) {
+        int nfd;
+        DIR *d;
+
+        if ((nfd = openat(fd, name, O_RDONLY|O_NONBLOCK|O_DIRECTORY|O_CLOEXEC|flags)) < 0)
+                return NULL;
+
+        if (!(d = fdopendir(nfd))) {
+                close_nointr_nofail(nfd);
+                return NULL;
+        }
+
+        return d;
+}
+
+int signal_from_string_try_harder(const char *s) {
+        int signo;
+        assert(s);
+
+        if ((signo = signal_from_string(s)) <= 0)
+                if (startswith(s, "SIG"))
+                        return signal_from_string(s+3);
+
+        return signo;
+}
+
+void dual_timestamp_serialize(FILE *f, const char *name, dual_timestamp *t) {
+
+        assert(f);
+        assert(name);
+        assert(t);
+
+        if (!dual_timestamp_is_set(t))
+                return;
+
+        fprintf(f, "%s=%llu %llu\n",
+                name,
+                (unsigned long long) t->realtime,
+                (unsigned long long) t->monotonic);
+}
+
+void dual_timestamp_deserialize(const char *value, dual_timestamp *t) {
+        unsigned long long a, b;
+
+        assert(value);
+        assert(t);
+
+        if (sscanf(value, "%lli %llu", &a, &b) != 2)
+                log_debug("Failed to parse finish timestamp value %s", value);
+        else {
+                t->realtime = a;
+                t->monotonic = b;
+        }
+}
+
+char *fstab_node_to_udev_node(const char *p) {
+        char *dn, *t, *u;
+        int r;
+
+        /* FIXME: to follow udev's logic 100% we need to leave valid
+         * UTF8 chars unescaped */
+
+        if (startswith(p, "LABEL=")) {
+
+                if (!(u = unquote(p+6, "\"\'")))
+                        return NULL;
+
+                t = xescape(u, "/ ");
+                free(u);
+
+                if (!t)
+                        return NULL;
+
+                r = asprintf(&dn, "/dev/disk/by-label/%s", t);
+                free(t);
+
+                if (r < 0)
+                        return NULL;
+
+                return dn;
+        }
+
+        if (startswith(p, "UUID=")) {
+
+                if (!(u = unquote(p+5, "\"\'")))
+                        return NULL;
+
+                t = xescape(u, "/ ");
+                free(u);
+
+                if (!t)
+                        return NULL;
+
+                r = asprintf(&dn, "/dev/disk/by-uuid/%s", t);
+                free(t);
+
+                if (r < 0)
+                        return NULL;
+
+                return dn;
+        }
+
+        return strdup(p);
+}
+
+void filter_environ(const char *prefix) {
+        int i, j;
+        assert(prefix);
+
+        if (!environ)
+                return;
+
+        for (i = 0, j = 0; environ[i]; i++) {
+
+                if (startswith(environ[i], prefix))
+                        continue;
+
+                environ[j++] = environ[i];
+        }
+
+        environ[j] = NULL;
+}
+
+bool tty_is_vc(const char *tty) {
+        assert(tty);
+
+        if (startswith(tty, "/dev/"))
+                tty += 5;
+
+        return vtnr_from_tty(tty) >= 0;
+}
+
+int vtnr_from_tty(const char *tty) {
+        int i, r;
+
+        assert(tty);
+
+        if (startswith(tty, "/dev/"))
+                tty += 5;
+
+        if (!startswith(tty, "tty") )
+                return -EINVAL;
+
+        if (tty[3] < '0' || tty[3] > '9')
+                return -EINVAL;
+
+        r = safe_atoi(tty+3, &i);
+        if (r < 0)
+                return r;
+
+        if (i < 0 || i > 63)
+                return -EINVAL;
+
+        return i;
+}
+
+bool tty_is_vc_resolve(const char *tty) {
+        char *active = NULL;
+        bool b;
+
+        assert(tty);
+
+        if (startswith(tty, "/dev/"))
+                tty += 5;
+
+        /* Resolve where /dev/console is pointing to */
+        if (streq(tty, "console"))
+                if (read_one_line_file("/sys/class/tty/console/active", &active) >= 0) {
+                        /* If multiple log outputs are configured the
+                         * last one is what /dev/console points to */
+                        tty = strrchr(active, ' ');
+                        if (tty)
+                                tty++;
+                        else
+                                tty = active;
+                }
+
+        b = tty_is_vc(tty);
+        free(active);
+
+        return b;
+}
+
+const char *default_term_for_tty(const char *tty) {
+        assert(tty);
+
+        return tty_is_vc_resolve(tty) ? "TERM=linux" : "TERM=vt100";
+}
+
+bool dirent_is_file(const struct dirent *de) {
+        assert(de);
+
+        if (ignore_file(de->d_name))
+                return false;
+
+        if (de->d_type != DT_REG &&
+            de->d_type != DT_LNK &&
+            de->d_type != DT_UNKNOWN)
+                return false;
+
+        return true;
+}
+
+bool dirent_is_file_with_suffix(const struct dirent *de, const char *suffix) {
+        assert(de);
+
+        if (!dirent_is_file(de))
+                return false;
+
+        return endswith(de->d_name, suffix);
+}
+
+void execute_directory(const char *directory, DIR *d, char *argv[]) {
+        DIR *_d = NULL;
+        struct dirent *de;
+        Hashmap *pids = NULL;
+
+        assert(directory);
+
+        /* Executes all binaries in a directory in parallel and waits
+         * until all they all finished. */
+
+        if (!d) {
+                if (!(_d = opendir(directory))) {
+
+                        if (errno == ENOENT)
+                                return;
+
+                        log_error("Failed to enumerate directory %s: %m", directory);
+                        return;
+                }
+
+                d = _d;
+        }
+
+        if (!(pids = hashmap_new(trivial_hash_func, trivial_compare_func))) {
+                log_error("Failed to allocate set.");
+                goto finish;
+        }
+
+        while ((de = readdir(d))) {
+                char *path;
+                pid_t pid;
+                int k;
+
+                if (!dirent_is_file(de))
+                        continue;
+
+                if (asprintf(&path, "%s/%s", directory, de->d_name) < 0) {
+                        log_error("Out of memory");
+                        continue;
+                }
+
+                if ((pid = fork()) < 0) {
+                        log_error("Failed to fork: %m");
+                        free(path);
+                        continue;
+                }
+
+                if (pid == 0) {
+                        char *_argv[2];
+                        /* Child */
+
+                        if (!argv) {
+                                _argv[0] = path;
+                                _argv[1] = NULL;
+                                argv = _argv;
+                        } else
+                                if (!argv[0])
+                                        argv[0] = path;
+
+                        execv(path, argv);
+
+                        log_error("Failed to execute %s: %m", path);
+                        _exit(EXIT_FAILURE);
+                }
+
+                log_debug("Spawned %s as %lu", path, (unsigned long) pid);
+
+                if ((k = hashmap_put(pids, UINT_TO_PTR(pid), path)) < 0) {
+                        log_error("Failed to add PID to set: %s", strerror(-k));
+                        free(path);
+                }
+        }
+
+        while (!hashmap_isempty(pids)) {
+                pid_t pid = PTR_TO_UINT(hashmap_first_key(pids));
+                siginfo_t si;
+                char *path;
+
+                zero(si);
+                if (waitid(P_PID, pid, &si, WEXITED) < 0) {
+
+                        if (errno == EINTR)
+                                continue;
+
+                        log_error("waitid() failed: %m");
+                        goto finish;
+                }
+
+                if ((path = hashmap_remove(pids, UINT_TO_PTR(si.si_pid)))) {
+                        if (!is_clean_exit(si.si_code, si.si_status)) {
+                                if (si.si_code == CLD_EXITED)
+                                        log_error("%s exited with exit status %i.", path, si.si_status);
+                                else
+                                        log_error("%s terminated by signal %s.", path, signal_to_string(si.si_status));
+                        } else
+                                log_debug("%s exited successfully.", path);
+
+                        free(path);
+                }
+        }
+
+finish:
+        if (_d)
+                closedir(_d);
+
+        if (pids)
+                hashmap_free_free(pids);
+}
+
+int kill_and_sigcont(pid_t pid, int sig) {
+        int r;
+
+        r = kill(pid, sig) < 0 ? -errno : 0;
+
+        if (r >= 0)
+                kill(pid, SIGCONT);
+
+        return r;
+}
+
+bool nulstr_contains(const char*nulstr, const char *needle) {
+        const char *i;
+
+        if (!nulstr)
+                return false;
+
+        NULSTR_FOREACH(i, nulstr)
+                if (streq(i, needle))
+                        return true;
+
+        return false;
+}
+
+bool plymouth_running(void) {
+        return access("/run/plymouth/pid", F_OK) >= 0;
+}
+
+void parse_syslog_priority(char **p, int *priority) {
+        int a = 0, b = 0, c = 0;
+        int k;
+
+        assert(p);
+        assert(*p);
+        assert(priority);
+
+        if ((*p)[0] != '<')
+                return;
+
+        if (!strchr(*p, '>'))
+                return;
+
+        if ((*p)[2] == '>') {
+                c = undecchar((*p)[1]);
+                k = 3;
+        } else if ((*p)[3] == '>') {
+                b = undecchar((*p)[1]);
+                c = undecchar((*p)[2]);
+                k = 4;
+        } else if ((*p)[4] == '>') {
+                a = undecchar((*p)[1]);
+                b = undecchar((*p)[2]);
+                c = undecchar((*p)[3]);
+                k = 5;
+        } else
+                return;
+
+        if (a < 0 || b < 0 || c < 0)
+                return;
+
+        *priority = a*100+b*10+c;
+        *p += k;
+}
+
+void skip_syslog_pid(char **buf) {
+        char *p;
+
+        assert(buf);
+        assert(*buf);
+
+        p = *buf;
+
+        if (*p != '[')
+                return;
+
+        p++;
+        p += strspn(p, "0123456789");
+
+        if (*p != ']')
+                return;
+
+        p++;
+
+        *buf = p;
+}
+
+void skip_syslog_date(char **buf) {
+        enum {
+                LETTER,
+                SPACE,
+                NUMBER,
+                SPACE_OR_NUMBER,
+                COLON
+        } sequence[] = {
+                LETTER, LETTER, LETTER,
+                SPACE,
+                SPACE_OR_NUMBER, NUMBER,
+                SPACE,
+                SPACE_OR_NUMBER, NUMBER,
+                COLON,
+                SPACE_OR_NUMBER, NUMBER,
+                COLON,
+                SPACE_OR_NUMBER, NUMBER,
+                SPACE
+        };
+
+        char *p;
+        unsigned i;
+
+        assert(buf);
+        assert(*buf);
+
+        p = *buf;
+
+        for (i = 0; i < ELEMENTSOF(sequence); i++, p++) {
+
+                if (!*p)
+                        return;
+
+                switch (sequence[i]) {
+
+                case SPACE:
+                        if (*p != ' ')
+                                return;
+                        break;
+
+                case SPACE_OR_NUMBER:
+                        if (*p == ' ')
+                                break;
+
+                        /* fall through */
+
+                case NUMBER:
+                        if (*p < '0' || *p > '9')
+                                return;
+
+                        break;
+
+                case LETTER:
+                        if (!(*p >= 'A' && *p <= 'Z') &&
+                            !(*p >= 'a' && *p <= 'z'))
+                                return;
+
+                        break;
+
+                case COLON:
+                        if (*p != ':')
+                                return;
+                        break;
+
+                }
+        }
+
+        *buf = p;
+}
+
+int have_effective_cap(int value) {
+        cap_t cap;
+        cap_flag_value_t fv;
+        int r;
+
+        if (!(cap = cap_get_proc()))
+                return -errno;
+
+        if (cap_get_flag(cap, value, CAP_EFFECTIVE, &fv) < 0)
+                r = -errno;
+        else
+                r = fv == CAP_SET;
+
+        cap_free(cap);
+        return r;
+}
+
+char* strshorten(char *s, size_t l) {
+        assert(s);
+
+        if (l < strlen(s))
+                s[l] = 0;
+
+        return s;
+}
+
+static bool hostname_valid_char(char c) {
+        return
+                (c >= 'a' && c <= 'z') ||
+                (c >= 'A' && c <= 'Z') ||
+                (c >= '0' && c <= '9') ||
+                c == '-' ||
+                c == '_' ||
+                c == '.';
+}
+
+bool hostname_is_valid(const char *s) {
+        const char *p;
+
+        if (isempty(s))
+                return false;
+
+        for (p = s; *p; p++)
+                if (!hostname_valid_char(*p))
+                        return false;
+
+        if (p-s > HOST_NAME_MAX)
+                return false;
+
+        return true;
+}
+
+char* hostname_cleanup(char *s) {
+        char *p, *d;
+
+        for (p = s, d = s; *p; p++)
+                if ((*p >= 'a' && *p <= 'z') ||
+                    (*p >= 'A' && *p <= 'Z') ||
+                    (*p >= '0' && *p <= '9') ||
+                    *p == '-' ||
+                    *p == '_' ||
+                    *p == '.')
+                        *(d++) = *p;
+
+        *d = 0;
+
+        strshorten(s, HOST_NAME_MAX);
+        return s;
+}
+
+int pipe_eof(int fd) {
+        struct pollfd pollfd;
+        int r;
+
+        zero(pollfd);
+        pollfd.fd = fd;
+        pollfd.events = POLLIN|POLLHUP;
+
+        r = poll(&pollfd, 1, 0);
+        if (r < 0)
+                return -errno;
+
+        if (r == 0)
+                return 0;
+
+        return pollfd.revents & POLLHUP;
+}
+
+int fd_wait_for_event(int fd, int event, usec_t t) {
+        struct pollfd pollfd;
+        int r;
+
+        zero(pollfd);
+        pollfd.fd = fd;
+        pollfd.events = event;
+
+        r = poll(&pollfd, 1, t == (usec_t) -1 ? -1 : (int) (t / USEC_PER_MSEC));
+        if (r < 0)
+                return -errno;
+
+        if (r == 0)
+                return 0;
+
+        return pollfd.revents;
+}
+
+int fopen_temporary(const char *path, FILE **_f, char **_temp_path) {
+        FILE *f;
+        char *t;
+        const char *fn;
+        size_t k;
+        int fd;
+
+        assert(path);
+        assert(_f);
+        assert(_temp_path);
+
+        t = new(char, strlen(path) + 1 + 6 + 1);
+        if (!t)
+                return -ENOMEM;
+
+        fn = file_name_from_path(path);
+        k = fn-path;
+        memcpy(t, path, k);
+        t[k] = '.';
+        stpcpy(stpcpy(t+k+1, fn), "XXXXXX");
+
+        fd = mkostemp(t, O_WRONLY|O_CLOEXEC);
+        if (fd < 0) {
+                free(t);
+                return -errno;
+        }
+
+        f = fdopen(fd, "we");
+        if (!f) {
+                unlink(t);
+                free(t);
+                return -errno;
+        }
+
+        *_f = f;
+        *_temp_path = t;
+
+        return 0;
+}
+
+int terminal_vhangup_fd(int fd) {
+        assert(fd >= 0);
+
+        if (ioctl(fd, TIOCVHANGUP) < 0)
+                return -errno;
+
+        return 0;
+}
+
+int terminal_vhangup(const char *name) {
+        int fd, r;
+
+        fd = open_terminal(name, O_RDWR|O_NOCTTY|O_CLOEXEC);
+        if (fd < 0)
+                return fd;
+
+        r = terminal_vhangup_fd(fd);
+        close_nointr_nofail(fd);
+
+        return r;
+}
+
+int vt_disallocate(const char *name) {
+        int fd, r;
+        unsigned u;
+
+        /* Deallocate the VT if possible. If not possible
+         * (i.e. because it is the active one), at least clear it
+         * entirely (including the scrollback buffer) */
+
+        if (!startswith(name, "/dev/"))
+                return -EINVAL;
+
+        if (!tty_is_vc(name)) {
+                /* So this is not a VT. I guess we cannot deallocate
+                 * it then. But let's at least clear the screen */
+
+                fd = open_terminal(name, O_RDWR|O_NOCTTY|O_CLOEXEC);
+                if (fd < 0)
+                        return fd;
+
+                loop_write(fd,
+                           "\033[r"    /* clear scrolling region */
+                           "\033[H"    /* move home */
+                           "\033[2J",  /* clear screen */
+                           10, false);
+                close_nointr_nofail(fd);
+
+                return 0;
+        }
+
+        if (!startswith(name, "/dev/tty"))
+                return -EINVAL;
+
+        r = safe_atou(name+8, &u);
+        if (r < 0)
+                return r;
+
+        if (u <= 0)
+                return -EINVAL;
+
+        /* Try to deallocate */
+        fd = open_terminal("/dev/tty0", O_RDWR|O_NOCTTY|O_CLOEXEC);
+        if (fd < 0)
+                return fd;
+
+        r = ioctl(fd, VT_DISALLOCATE, u);
+        close_nointr_nofail(fd);
+
+        if (r >= 0)
+                return 0;
+
+        if (errno != EBUSY)
+                return -errno;
+
+        /* Couldn't deallocate, so let's clear it fully with
+         * scrollback */
+        fd = open_terminal(name, O_RDWR|O_NOCTTY|O_CLOEXEC);
+        if (fd < 0)
+                return fd;
+
+        loop_write(fd,
+                   "\033[r"   /* clear scrolling region */
+                   "\033[H"   /* move home */
+                   "\033[3J", /* clear screen including scrollback, requires Linux 2.6.40 */
+                   10, false);
+        close_nointr_nofail(fd);
+
+        return 0;
+}
+
+static int files_add(Hashmap *h, const char *path, const char *suffix) {
+        DIR *dir;
+        struct dirent buffer, *de;
+        int r = 0;
+
+        dir = opendir(path);
+        if (!dir) {
+                if (errno == ENOENT)
+                        return 0;
+                return -errno;
+        }
+
+        for (;;) {
+                int k;
+                char *p, *f;
+
+                k = readdir_r(dir, &buffer, &de);
+                if (k != 0) {
+                        r = -k;
+                        goto finish;
+                }
+
+                if (!de)
+                        break;
+
+                if (!dirent_is_file_with_suffix(de, suffix))
+                        continue;
+
+                if (asprintf(&p, "%s/%s", path, de->d_name) < 0) {
+                        r = -ENOMEM;
+                        goto finish;
+                }
+
+                f = canonicalize_file_name(p);
+                if (!f) {
+                        log_error("Failed to canonicalize file name '%s': %m", p);
+                        free(p);
+                        continue;
+                }
+                free(p);
+
+                log_debug("found: %s\n", f);
+                if (hashmap_put(h, file_name_from_path(f), f) <= 0)
+                        free(f);
+        }
+
+finish:
+        closedir(dir);
+        return r;
+}
+
+static int base_cmp(const void *a, const void *b) {
+        const char *s1, *s2;
+
+        s1 = *(char * const *)a;
+        s2 = *(char * const *)b;
+        return strcmp(file_name_from_path(s1), file_name_from_path(s2));
+}
+
+int conf_files_list(char ***strv, const char *suffix, const char *dir, ...) {
+        Hashmap *fh = NULL;
+        char **dirs = NULL;
+        char **files = NULL;
+        char **p;
+        va_list ap;
+        int r = 0;
+
+        va_start(ap, dir);
+        dirs = strv_new_ap(dir, ap);
+        va_end(ap);
+        if (!dirs) {
+                r = -ENOMEM;
+                goto finish;
+        }
+        if (!strv_path_canonicalize(dirs)) {
+                r = -ENOMEM;
+                goto finish;
+        }
+        if (!strv_uniq(dirs)) {
+                r = -ENOMEM;
+                goto finish;
+        }
+
+        fh = hashmap_new(string_hash_func, string_compare_func);
+        if (!fh) {
+                r = -ENOMEM;
+                goto finish;
+        }
+
+        STRV_FOREACH(p, dirs) {
+                if (files_add(fh, *p, suffix) < 0) {
+                        log_error("Failed to search for files.");
+                        r = -EINVAL;
+                        goto finish;
+                }
+        }
+
+        files = hashmap_get_strv(fh);
+        if (files == NULL) {
+                log_error("Failed to compose list of files.");
+                r = -ENOMEM;
+                goto finish;
+        }
+
+        qsort(files, hashmap_size(fh), sizeof(char *), base_cmp);
+
+finish:
+        strv_free(dirs);
+        hashmap_free(fh);
+        *strv = files;
+        return r;
+}
+
+int hwclock_is_localtime(void) {
+        FILE *f;
+        bool local = false;
+
+        /*
+         * The third line of adjtime is "UTC" or "LOCAL" or nothing.
+         *   # /etc/adjtime
+         *   0.0 0 0
+         *   0
+         *   UTC
+         */
+        f = fopen("/etc/adjtime", "re");
+        if (f) {
+                char line[LINE_MAX];
+                bool b;
+
+                b = fgets(line, sizeof(line), f) &&
+                        fgets(line, sizeof(line), f) &&
+                        fgets(line, sizeof(line), f);
+
+                fclose(f);
+
+                if (!b)
+                        return -EIO;
+
+
+                truncate_nl(line);
+                local = streq(line, "LOCAL");
+
+        } else if (errno != -ENOENT)
+                return -errno;
+
+        return local;
+}
+
+int hwclock_apply_localtime_delta(int *min) {
+        const struct timeval *tv_null = NULL;
+        struct timespec ts;
+        struct tm *tm;
+        int minuteswest;
+        struct timezone tz;
+
+        assert_se(clock_gettime(CLOCK_REALTIME, &ts) == 0);
+        assert_se(tm = localtime(&ts.tv_sec));
+        minuteswest = tm->tm_gmtoff / 60;
+
+        tz.tz_minuteswest = -minuteswest;
+        tz.tz_dsttime = 0; /* DST_NONE*/
+
+        /*
+         * If the hardware clock does not run in UTC, but in local time:
+         * The very first time we set the kernel's timezone, it will warp
+         * the clock so that it runs in UTC instead of local time.
+         */
+        if (settimeofday(tv_null, &tz) < 0)
+                return -errno;
+        if (min)
+                *min = minuteswest;
+        return 0;
+}
+
+int hwclock_reset_localtime_delta(void) {
+        const struct timeval *tv_null = NULL;
+        struct timezone tz;
+
+        tz.tz_minuteswest = 0;
+        tz.tz_dsttime = 0; /* DST_NONE*/
+
+        if (settimeofday(tv_null, &tz) < 0)
+                return -errno;
+
+        return 0;
+}
+
+int rtc_open(int flags) {
+        int fd;
+        DIR *d;
+
+        /* First, we try to make use of the /dev/rtc symlink. If that
+         * doesn't exist, we open the first RTC which has hctosys=1
+         * set. If we don't find any we just take the first RTC that
+         * exists at all. */
+
+        fd = open("/dev/rtc", flags);
+        if (fd >= 0)
+                return fd;
+
+        d = opendir("/sys/class/rtc");
+        if (!d)
+                goto fallback;
+
+        for (;;) {
+                char *p, *v;
+                struct dirent buf, *de;
+                int r;
+
+                r = readdir_r(d, &buf, &de);
+                if (r != 0)
+                        goto fallback;
+
+                if (!de)
+                        goto fallback;
+
+                if (ignore_file(de->d_name))
+                        continue;
+
+                p = join("/sys/class/rtc/", de->d_name, "/hctosys", NULL);
+                if (!p) {
+                        closedir(d);
+                        return -ENOMEM;
+                }
+
+                r = read_one_line_file(p, &v);
+                free(p);
+
+                if (r < 0)
+                        continue;
+
+                r = parse_boolean(v);
+                free(v);
+
+                if (r <= 0)
+                        continue;
+
+                p = strappend("/dev/", de->d_name);
+                fd = open(p, flags);
+                free(p);
+
+                if (fd >= 0) {
+                        closedir(d);
+                        return fd;
+                }
+        }
+
+fallback:
+        if (d)
+                closedir(d);
+
+        fd = open("/dev/rtc0", flags);
+        if (fd < 0)
+                return -errno;
+
+        return fd;
+}
+
+int hwclock_get_time(struct tm *tm) {
+        int fd;
+        int err = 0;
+
+        assert(tm);
+
+        fd = rtc_open(O_RDONLY|O_CLOEXEC);
+        if (fd < 0)
+                return -errno;
+
+        /* This leaves the timezone fields of struct tm
+         * uninitialized! */
+        if (ioctl(fd, RTC_RD_TIME, tm) < 0)
+                err = -errno;
+
+        /* We don't now daylight saving, so we reset this in order not
+         * to confused mktime(). */
+        tm->tm_isdst = -1;
+
+        close_nointr_nofail(fd);
+
+        return err;
+}
+
+int hwclock_set_time(const struct tm *tm) {
+        int fd;
+        int err = 0;
+
+        assert(tm);
+
+        fd = rtc_open(O_RDONLY|O_CLOEXEC);
+        if (fd < 0)
+                return -errno;
+
+        if (ioctl(fd, RTC_SET_TIME, tm) < 0)
+                err = -errno;
+
+        close_nointr_nofail(fd);
+
+        return err;
+}
+
+int copy_file(const char *from, const char *to) {
+        int r, fdf, fdt;
+
+        assert(from);
+        assert(to);
+
+        fdf = open(from, O_RDONLY|O_CLOEXEC|O_NOCTTY);
+        if (fdf < 0)
+                return -errno;
+
+        fdt = open(to, O_WRONLY|O_CREAT|O_EXCL|O_CLOEXEC|O_NOCTTY, 0644);
+        if (fdt < 0) {
+                close_nointr_nofail(fdf);
+                return -errno;
+        }
+
+        for (;;) {
+                char buf[PIPE_BUF];
+                ssize_t n, k;
+
+                n = read(fdf, buf, sizeof(buf));
+                if (n < 0) {
+                        r = -errno;
+
+                        close_nointr_nofail(fdf);
+                        close_nointr(fdt);
+                        unlink(to);
+
+                        return r;
+                }
+
+                if (n == 0)
+                        break;
+
+                errno = 0;
+                k = loop_write(fdt, buf, n, false);
+                if (n != k) {
+                        r = k < 0 ? k : (errno ? -errno : -EIO);
+
+                        close_nointr_nofail(fdf);
+                        close_nointr(fdt);
+
+                        unlink(to);
+                        return r;
+                }
+        }
+
+        close_nointr_nofail(fdf);
+        r = close_nointr(fdt);
+
+        if (r < 0) {
+                unlink(to);
+                return r;
+        }
+
+        return 0;
+}
+
+int symlink_or_copy(const char *from, const char *to) {
+        char *pf = NULL, *pt = NULL;
+        struct stat a, b;
+        int r;
+
+        assert(from);
+        assert(to);
+
+        if (parent_of_path(from, &pf) < 0 ||
+            parent_of_path(to, &pt) < 0) {
+                r = -ENOMEM;
+                goto finish;
+        }
+
+        if (stat(pf, &a) < 0 ||
+            stat(pt, &b) < 0) {
+                r = -errno;
+                goto finish;
+        }
+
+        if (a.st_dev != b.st_dev) {
+                free(pf);
+                free(pt);
+
+                return copy_file(from, to);
+        }
+
+        if (symlink(from, to) < 0) {
+                r = -errno;
+                goto finish;
+        }
+
+        r = 0;
+
+finish:
+        free(pf);
+        free(pt);
+
+        return r;
+}
+
+int symlink_or_copy_atomic(const char *from, const char *to) {
+        char *t, *x;
+        const char *fn;
+        size_t k;
+        unsigned long long ull;
+        unsigned i;
+        int r;
+
+        assert(from);
+        assert(to);
+
+        t = new(char, strlen(to) + 1 + 16 + 1);
+        if (!t)
+                return -ENOMEM;
+
+        fn = file_name_from_path(to);
+        k = fn-to;
+        memcpy(t, to, k);
+        t[k] = '.';
+        x = stpcpy(t+k+1, fn);
+
+        ull = random_ull();
+        for (i = 0; i < 16; i++) {
+                *(x++) = hexchar(ull & 0xF);
+                ull >>= 4;
+        }
+
+        *x = 0;
+
+        r = symlink_or_copy(from, t);
+        if (r < 0) {
+                unlink(t);
+                free(t);
+                return r;
+        }
+
+        if (rename(t, to) < 0) {
+                r = -errno;
+                unlink(t);
+                free(t);
+                return r;
+        }
+
+        free(t);
+        return r;
+}
+
+int audit_session_from_pid(pid_t pid, uint32_t *id) {
+        char *s;
+        uint32_t u;
+        int r;
+
+        assert(id);
+
+        if (have_effective_cap(CAP_AUDIT_CONTROL) <= 0)
+                return -ENOENT;
+
+        if (pid == 0)
+                r = read_one_line_file("/proc/self/sessionid", &s);
+        else {
+                char *p;
+
+                if (asprintf(&p, "/proc/%lu/sessionid", (unsigned long) pid) < 0)
+                        return -ENOMEM;
+
+                r = read_one_line_file(p, &s);
+                free(p);
+        }
+
+        if (r < 0)
+                return r;
+
+        r = safe_atou32(s, &u);
+        free(s);
+
+        if (r < 0)
+                return r;
+
+        if (u == (uint32_t) -1 || u <= 0)
+                return -ENOENT;
+
+        *id = u;
+        return 0;
+}
+
+int audit_loginuid_from_pid(pid_t pid, uid_t *uid) {
+        char *s;
+        uid_t u;
+        int r;
+
+        assert(uid);
+
+        /* Only use audit login uid if we are executed with sufficient
+         * capabilities so that pam_loginuid could do its job. If we
+         * are lacking the CAP_AUDIT_CONTROL capabality we most likely
+         * are being run in a container and /proc/self/loginuid is
+         * useless since it probably contains a uid of the host
+         * system. */
+
+        if (have_effective_cap(CAP_AUDIT_CONTROL) <= 0)
+                return -ENOENT;
+
+        if (pid == 0)
+                r = read_one_line_file("/proc/self/loginuid", &s);
+        else {
+                char *p;
+
+                if (asprintf(&p, "/proc/%lu/loginuid", (unsigned long) pid) < 0)
+                        return -ENOMEM;
+
+                r = read_one_line_file(p, &s);
+                free(p);
+        }
+
+        if (r < 0)
+                return r;
+
+        r = parse_uid(s, &u);
+        free(s);
+
+        if (r < 0)
+                return r;
+
+        if (u == (uid_t) -1)
+                return -ENOENT;
+
+        *uid = (uid_t) u;
+        return 0;
+}
+
+bool display_is_local(const char *display) {
+        assert(display);
+
+        return
+                display[0] == ':' &&
+                display[1] >= '0' &&
+                display[1] <= '9';
+}
+
+int socket_from_display(const char *display, char **path) {
+        size_t k;
+        char *f, *c;
+
+        assert(display);
+        assert(path);
+
+        if (!display_is_local(display))
+                return -EINVAL;
+
+        k = strspn(display+1, "0123456789");
+
+        f = new(char, sizeof("/tmp/.X11-unix/X") + k);
+        if (!f)
+                return -ENOMEM;
+
+        c = stpcpy(f, "/tmp/.X11-unix/X");
+        memcpy(c, display+1, k);
+        c[k] = 0;
+
+        *path = f;
+
+        return 0;
+}
+
+int get_user_creds(const char **username, uid_t *uid, gid_t *gid, const char **home) {
+        struct passwd *p;
+        uid_t u;
+
+        assert(username);
+        assert(*username);
+
+        /* We enforce some special rules for uid=0: in order to avoid
+         * NSS lookups for root we hardcode its data. */
+
+        if (streq(*username, "root") || streq(*username, "0")) {
+                *username = "root";
+
+                if (uid)
+                        *uid = 0;
+
+                if (gid)
+                        *gid = 0;
+
+                if (home)
+                        *home = "/root";
+                return 0;
+        }
+
+        if (parse_uid(*username, &u) >= 0) {
+                errno = 0;
+                p = getpwuid(u);
+
+                /* If there are multiple users with the same id, make
+                 * sure to leave $USER to the configured value instead
+                 * of the first occurrence in the database. However if
+                 * the uid was configured by a numeric uid, then let's
+                 * pick the real username from /etc/passwd. */
+                if (p)
+                        *username = p->pw_name;
+        } else {
+                errno = 0;
+                p = getpwnam(*username);
+        }
+
+        if (!p)
+                return errno != 0 ? -errno : -ESRCH;
+
+        if (uid)
+                *uid = p->pw_uid;
+
+        if (gid)
+                *gid = p->pw_gid;
+
+        if (home)
+                *home = p->pw_dir;
+
+        return 0;
+}
+
+int get_group_creds(const char **groupname, gid_t *gid) {
+        struct group *g;
+        gid_t id;
+
+        assert(groupname);
+
+        /* We enforce some special rules for gid=0: in order to avoid
+         * NSS lookups for root we hardcode its data. */
+
+        if (streq(*groupname, "root") || streq(*groupname, "0")) {
+                *groupname = "root";
+
+                if (gid)
+                        *gid = 0;
+
+                return 0;
+        }
+
+        if (parse_gid(*groupname, &id) >= 0) {
+                errno = 0;
+                g = getgrgid(id);
+
+                if (g)
+                        *groupname = g->gr_name;
+        } else {
+                errno = 0;
+                g = getgrnam(*groupname);
+        }
+
+        if (!g)
+                return errno != 0 ? -errno : -ESRCH;
+
+        if (gid)
+                *gid = g->gr_gid;
+
+        return 0;
+}
+
+int in_group(const char *name) {
+        gid_t gid, *gids;
+        int ngroups_max, r, i;
+
+        r = get_group_creds(&name, &gid);
+        if (r < 0)
+                return r;
+
+        if (getgid() == gid)
+                return 1;
+
+        if (getegid() == gid)
+                return 1;
+
+        ngroups_max = sysconf(_SC_NGROUPS_MAX);
+        assert(ngroups_max > 0);
+
+        gids = alloca(sizeof(gid_t) * ngroups_max);
+
+        r = getgroups(ngroups_max, gids);
+        if (r < 0)
+                return -errno;
+
+        for (i = 0; i < r; i++)
+                if (gids[i] == gid)
+                        return 1;
+
+        return 0;
+}
+
+int glob_exists(const char *path) {
+        glob_t g;
+        int r, k;
+
+        assert(path);
+
+        zero(g);
+        errno = 0;
+        k = glob(path, GLOB_NOSORT|GLOB_BRACE, NULL, &g);
+
+        if (k == GLOB_NOMATCH)
+                r = 0;
+        else if (k == GLOB_NOSPACE)
+                r = -ENOMEM;
+        else if (k == 0)
+                r = !strv_isempty(g.gl_pathv);
+        else
+                r = errno ? -errno : -EIO;
+
+        globfree(&g);
+
+        return r;
+}
+
+int dirent_ensure_type(DIR *d, struct dirent *de) {
+        struct stat st;
+
+        assert(d);
+        assert(de);
+
+        if (de->d_type != DT_UNKNOWN)
+                return 0;
+
+        if (fstatat(dirfd(d), de->d_name, &st, AT_SYMLINK_NOFOLLOW) < 0)
+                return -errno;
+
+        de->d_type =
+                S_ISREG(st.st_mode)  ? DT_REG  :
+                S_ISDIR(st.st_mode)  ? DT_DIR  :
+                S_ISLNK(st.st_mode)  ? DT_LNK  :
+                S_ISFIFO(st.st_mode) ? DT_FIFO :
+                S_ISSOCK(st.st_mode) ? DT_SOCK :
+                S_ISCHR(st.st_mode)  ? DT_CHR  :
+                S_ISBLK(st.st_mode)  ? DT_BLK  :
+                                       DT_UNKNOWN;
+
+        return 0;
+}
+
+int in_search_path(const char *path, char **search) {
+        char **i, *parent;
+        int r;
+
+        r = parent_of_path(path, &parent);
+        if (r < 0)
+                return r;
+
+        r = 0;
+
+        STRV_FOREACH(i, search) {
+                if (path_equal(parent, *i)) {
+                        r = 1;
+                        break;
+                }
+        }
+
+        free(parent);
+
+        return r;
+}
+
+int get_files_in_directory(const char *path, char ***list) {
+        DIR *d;
+        int r = 0;
+        unsigned n = 0;
+        char **l = NULL;
+
+        assert(path);
+
+        /* Returns all files in a directory in *list, and the number
+         * of files as return value. If list is NULL returns only the
+         * number */
+
+        d = opendir(path);
+        if (!d)
+                return -errno;
+
+        for (;;) {
+                struct dirent buffer, *de;
+                int k;
+
+                k = readdir_r(d, &buffer, &de);
+                if (k != 0) {
+                        r = -k;
+                        goto finish;
+                }
+
+                if (!de)
+                        break;
+
+                dirent_ensure_type(d, de);
+
+                if (!dirent_is_file(de))
+                        continue;
+
+                if (list) {
+                        if ((unsigned) r >= n) {
+                                char **t;
+
+                                n = MAX(16, 2*r);
+                                t = realloc(l, sizeof(char*) * n);
+                                if (!t) {
+                                        r = -ENOMEM;
+                                        goto finish;
+                                }
+
+                                l = t;
+                        }
+
+                        assert((unsigned) r < n);
+
+                        l[r] = strdup(de->d_name);
+                        if (!l[r]) {
+                                r = -ENOMEM;
+                                goto finish;
+                        }
+
+                        l[++r] = NULL;
+                } else
+                        r++;
+        }
+
+finish:
+        if (d)
+                closedir(d);
+
+        if (r >= 0) {
+                if (list)
+                        *list = l;
+        } else
+                strv_free(l);
+
+        return r;
+}
+
+char *join(const char *x, ...) {
+        va_list ap;
+        size_t l;
+        char *r, *p;
+
+        va_start(ap, x);
+
+        if (x) {
+                l = strlen(x);
+
+                for (;;) {
+                        const char *t;
+
+                        t = va_arg(ap, const char *);
+                        if (!t)
+                                break;
+
+                        l += strlen(t);
+                }
+        } else
+                l = 0;
+
+        va_end(ap);
+
+        r = new(char, l+1);
+        if (!r)
+                return NULL;
+
+        if (x) {
+                p = stpcpy(r, x);
+
+                va_start(ap, x);
+
+                for (;;) {
+                        const char *t;
+
+                        t = va_arg(ap, const char *);
+                        if (!t)
+                                break;
+
+                        p = stpcpy(p, t);
+                }
+
+                va_end(ap);
+        } else
+                r[0] = 0;
+
+        return r;
+}
+
+bool is_main_thread(void) {
+        static __thread int cached = 0;
+
+        if (_unlikely_(cached == 0))
+                cached = getpid() == gettid() ? 1 : -1;
+
+        return cached > 0;
+}
+
+int block_get_whole_disk(dev_t d, dev_t *ret) {
+        char *p, *s;
+        int r;
+        unsigned n, m;
+
+        assert(ret);
+
+        /* If it has a queue this is good enough for us */
+        if (asprintf(&p, "/sys/dev/block/%u:%u/queue", major(d), minor(d)) < 0)
+                return -ENOMEM;
+
+        r = access(p, F_OK);
+        free(p);
+
+        if (r >= 0) {
+                *ret = d;
+                return 0;
+        }
+
+        /* If it is a partition find the originating device */
+        if (asprintf(&p, "/sys/dev/block/%u:%u/partition", major(d), minor(d)) < 0)
+                return -ENOMEM;
+
+        r = access(p, F_OK);
+        free(p);
+
+        if (r < 0)
+                return -ENOENT;
+
+        /* Get parent dev_t */
+        if (asprintf(&p, "/sys/dev/block/%u:%u/../dev", major(d), minor(d)) < 0)
+                return -ENOMEM;
+
+        r = read_one_line_file(p, &s);
+        free(p);
+
+        if (r < 0)
+                return r;
+
+        r = sscanf(s, "%u:%u", &m, &n);
+        free(s);
+
+        if (r != 2)
+                return -EINVAL;
+
+        /* Only return this if it is really good enough for us. */
+        if (asprintf(&p, "/sys/dev/block/%u:%u/queue", m, n) < 0)
+                return -ENOMEM;
+
+        r = access(p, F_OK);
+        free(p);
+
+        if (r >= 0) {
+                *ret = makedev(m, n);
+                return 0;
+        }
+
+        return -ENOENT;
+}
+
+int file_is_priv_sticky(const char *p) {
+        struct stat st;
+
+        assert(p);
+
+        if (lstat(p, &st) < 0)
+                return -errno;
+
+        return
+                (st.st_uid == 0 || st.st_uid == getuid()) &&
+                (st.st_mode & S_ISVTX);
+}
+
+static const char *const ioprio_class_table[] = {
+        [IOPRIO_CLASS_NONE] = "none",
+        [IOPRIO_CLASS_RT] = "realtime",
+        [IOPRIO_CLASS_BE] = "best-effort",
+        [IOPRIO_CLASS_IDLE] = "idle"
+};
+
+DEFINE_STRING_TABLE_LOOKUP(ioprio_class, int);
+
+static const char *const sigchld_code_table[] = {
+        [CLD_EXITED] = "exited",
+        [CLD_KILLED] = "killed",
+        [CLD_DUMPED] = "dumped",
+        [CLD_TRAPPED] = "trapped",
+        [CLD_STOPPED] = "stopped",
+        [CLD_CONTINUED] = "continued",
+};
+
+DEFINE_STRING_TABLE_LOOKUP(sigchld_code, int);
+
+static const char *const log_facility_unshifted_table[LOG_NFACILITIES] = {
+        [LOG_FAC(LOG_KERN)] = "kern",
+        [LOG_FAC(LOG_USER)] = "user",
+        [LOG_FAC(LOG_MAIL)] = "mail",
+        [LOG_FAC(LOG_DAEMON)] = "daemon",
+        [LOG_FAC(LOG_AUTH)] = "auth",
+        [LOG_FAC(LOG_SYSLOG)] = "syslog",
+        [LOG_FAC(LOG_LPR)] = "lpr",
+        [LOG_FAC(LOG_NEWS)] = "news",
+        [LOG_FAC(LOG_UUCP)] = "uucp",
+        [LOG_FAC(LOG_CRON)] = "cron",
+        [LOG_FAC(LOG_AUTHPRIV)] = "authpriv",
+        [LOG_FAC(LOG_FTP)] = "ftp",
+        [LOG_FAC(LOG_LOCAL0)] = "local0",
+        [LOG_FAC(LOG_LOCAL1)] = "local1",
+        [LOG_FAC(LOG_LOCAL2)] = "local2",
+        [LOG_FAC(LOG_LOCAL3)] = "local3",
+        [LOG_FAC(LOG_LOCAL4)] = "local4",
+        [LOG_FAC(LOG_LOCAL5)] = "local5",
+        [LOG_FAC(LOG_LOCAL6)] = "local6",
+        [LOG_FAC(LOG_LOCAL7)] = "local7"
+};
+
+DEFINE_STRING_TABLE_LOOKUP(log_facility_unshifted, int);
+
+static const char *const log_level_table[] = {
+        [LOG_EMERG] = "emerg",
+        [LOG_ALERT] = "alert",
+        [LOG_CRIT] = "crit",
+        [LOG_ERR] = "err",
+        [LOG_WARNING] = "warning",
+        [LOG_NOTICE] = "notice",
+        [LOG_INFO] = "info",
+        [LOG_DEBUG] = "debug"
+};
+
+DEFINE_STRING_TABLE_LOOKUP(log_level, int);
+
+static const char* const sched_policy_table[] = {
+        [SCHED_OTHER] = "other",
+        [SCHED_BATCH] = "batch",
+        [SCHED_IDLE] = "idle",
+        [SCHED_FIFO] = "fifo",
+        [SCHED_RR] = "rr"
+};
+
+DEFINE_STRING_TABLE_LOOKUP(sched_policy, int);
+
+static const char* const rlimit_table[] = {
+        [RLIMIT_CPU] = "LimitCPU",
+        [RLIMIT_FSIZE] = "LimitFSIZE",
+        [RLIMIT_DATA] = "LimitDATA",
+        [RLIMIT_STACK] = "LimitSTACK",
+        [RLIMIT_CORE] = "LimitCORE",
+        [RLIMIT_RSS] = "LimitRSS",
+        [RLIMIT_NOFILE] = "LimitNOFILE",
+        [RLIMIT_AS] = "LimitAS",
+        [RLIMIT_NPROC] = "LimitNPROC",
+        [RLIMIT_MEMLOCK] = "LimitMEMLOCK",
+        [RLIMIT_LOCKS] = "LimitLOCKS",
+        [RLIMIT_SIGPENDING] = "LimitSIGPENDING",
+        [RLIMIT_MSGQUEUE] = "LimitMSGQUEUE",
+        [RLIMIT_NICE] = "LimitNICE",
+        [RLIMIT_RTPRIO] = "LimitRTPRIO",
+        [RLIMIT_RTTIME] = "LimitRTTIME"
+};
+
+DEFINE_STRING_TABLE_LOOKUP(rlimit, int);
+
+static const char* const ip_tos_table[] = {
+        [IPTOS_LOWDELAY] = "low-delay",
+        [IPTOS_THROUGHPUT] = "throughput",
+        [IPTOS_RELIABILITY] = "reliability",
+        [IPTOS_LOWCOST] = "low-cost",
+};
+
+DEFINE_STRING_TABLE_LOOKUP(ip_tos, int);
+
+static const char *const __signal_table[] = {
+        [SIGHUP] = "HUP",
+        [SIGINT] = "INT",
+        [SIGQUIT] = "QUIT",
+        [SIGILL] = "ILL",
+        [SIGTRAP] = "TRAP",
+        [SIGABRT] = "ABRT",
+        [SIGBUS] = "BUS",
+        [SIGFPE] = "FPE",
+        [SIGKILL] = "KILL",
+        [SIGUSR1] = "USR1",
+        [SIGSEGV] = "SEGV",
+        [SIGUSR2] = "USR2",
+        [SIGPIPE] = "PIPE",
+        [SIGALRM] = "ALRM",
+        [SIGTERM] = "TERM",
+#ifdef SIGSTKFLT
+        [SIGSTKFLT] = "STKFLT",  /* Linux on SPARC doesn't know SIGSTKFLT */
+#endif
+        [SIGCHLD] = "CHLD",
+        [SIGCONT] = "CONT",
+        [SIGSTOP] = "STOP",
+        [SIGTSTP] = "TSTP",
+        [SIGTTIN] = "TTIN",
+        [SIGTTOU] = "TTOU",
+        [SIGURG] = "URG",
+        [SIGXCPU] = "XCPU",
+        [SIGXFSZ] = "XFSZ",
+        [SIGVTALRM] = "VTALRM",
+        [SIGPROF] = "PROF",
+        [SIGWINCH] = "WINCH",
+        [SIGIO] = "IO",
+        [SIGPWR] = "PWR",
+        [SIGSYS] = "SYS"
+};
+
+DEFINE_PRIVATE_STRING_TABLE_LOOKUP(__signal, int);
+
+const char *signal_to_string(int signo) {
+        static __thread char buf[12];
+        const char *name;
+
+        name = __signal_to_string(signo);
+        if (name)
+                return name;
+
+        if (signo >= SIGRTMIN && signo <= SIGRTMAX)
+                snprintf(buf, sizeof(buf) - 1, "RTMIN+%d", signo - SIGRTMIN);
+        else
+                snprintf(buf, sizeof(buf) - 1, "%d", signo);
+        char_array_0(buf);
+        return buf;
+}
+
+int signal_from_string(const char *s) {
+        int signo;
+        int offset = 0;
+        unsigned u;
+
+        signo =__signal_from_string(s);
+        if (signo > 0)
+                return signo;
+
+        if (startswith(s, "RTMIN+")) {
+                s += 6;
+                offset = SIGRTMIN;
+        }
+        if (safe_atou(s, &u) >= 0) {
+                signo = (int) u + offset;
+                if (signo > 0 && signo < _NSIG)
+                        return signo;
+        }
+        return -1;
+}
+
+bool kexec_loaded(void) {
+       bool loaded = false;
+       char *s;
+
+       if (read_one_line_file("/sys/kernel/kexec_loaded", &s) >= 0) {
+               if (s[0] == '1')
+                       loaded = true;
+               free(s);
+       }
+       return loaded;
+}
+
+int strdup_or_null(const char *a, char **b) {
+        char *c;
+
+        assert(b);
+
+        if (!a) {
+                *b = NULL;
+                return 0;
+        }
+
+        c = strdup(a);
+        if (!c)
+                return -ENOMEM;
+
+        *b = c;
+        return 0;
+}
+
+int prot_from_flags(int flags) {
+
+        switch (flags & O_ACCMODE) {
+
+        case O_RDONLY:
+                return PROT_READ;
+
+        case O_WRONLY:
+                return PROT_WRITE;
+
+        case O_RDWR:
+                return PROT_READ|PROT_WRITE;
+
+        default:
+                return -EINVAL;
+        }
+}
+
+unsigned long cap_last_cap(void) {
+        static __thread unsigned long saved;
+        static __thread bool valid = false;
+        unsigned long p;
+
+        if (valid)
+                return saved;
+
+        p = (unsigned long) CAP_LAST_CAP;
+
+        if (prctl(PR_CAPBSET_READ, p) < 0) {
+
+                /* Hmm, look downwards, until we find one that
+                 * works */
+                for (p--; p > 0; p --)
+                        if (prctl(PR_CAPBSET_READ, p) >= 0)
+                                break;
+
+        } else {
+
+                /* Hmm, look upwards, until we find one that doesn't
+                 * work */
+                for (;; p++)
+                        if (prctl(PR_CAPBSET_READ, p+1) < 0)
+                                break;
+        }
+
+        saved = p;
+        valid = true;
+
+        return p;
+}
+
+char *format_bytes(char *buf, size_t l, off_t t) {
+        unsigned i;
+
+        static const struct {
+                const char *suffix;
+                off_t factor;
+        } table[] = {
+                { "E", 1024ULL*1024ULL*1024ULL*1024ULL*1024ULL*1024ULL },
+                { "P", 1024ULL*1024ULL*1024ULL*1024ULL*1024ULL },
+                { "T", 1024ULL*1024ULL*1024ULL*1024ULL },
+                { "G", 1024ULL*1024ULL*1024ULL },
+                { "M", 1024ULL*1024ULL },
+                { "K", 1024ULL },
+        };
+
+        for (i = 0; i < ELEMENTSOF(table); i++) {
+
+                if (t >= table[i].factor) {
+                        snprintf(buf, l,
+                                 "%llu.%llu%s",
+                                 (unsigned long long) (t / table[i].factor),
+                                 (unsigned long long) (((t*10ULL) / table[i].factor) % 10ULL),
+                                 table[i].suffix);
+
+                        goto finish;
+                }
+        }
+
+        snprintf(buf, l, "%lluB", (unsigned long long) t);
+
+finish:
+        buf[l-1] = 0;
+        return buf;
+
+}
+
+void* memdup(const void *p, size_t l) {
+        void *r;
+
+        assert(p);
+
+        r = malloc(l);
+        if (!r)
+                return NULL;
+
+        memcpy(r, p, l);
+        return r;
+}
+
+int fd_inc_sndbuf(int fd, size_t n) {
+        int r, value;
+        socklen_t l = sizeof(value);
+
+        r = getsockopt(fd, SOL_SOCKET, SO_SNDBUF, &value, &l);
+        if (r >= 0 &&
+            l == sizeof(value) &&
+            (size_t) value >= n*2)
+                return 0;
+
+        value = (int) n;
+        r = setsockopt(fd, SOL_SOCKET, SO_SNDBUF, &value, sizeof(value));
+        if (r < 0)
+                return -errno;
+
+        return 1;
+}
+
+int fd_inc_rcvbuf(int fd, size_t n) {
+        int r, value;
+        socklen_t l = sizeof(value);
+
+        r = getsockopt(fd, SOL_SOCKET, SO_RCVBUF, &value, &l);
+        if (r >= 0 &&
+            l == sizeof(value) &&
+            (size_t) value >= n*2)
+                return 0;
+
+        value = (int) n;
+        r = setsockopt(fd, SOL_SOCKET, SO_RCVBUF, &value, sizeof(value));
+        if (r < 0)
+                return -errno;
+
+        return 1;
+}
diff --git a/src/util.h b/src/util.h
new file mode 100644 (file)
index 0000000..b1af6db
--- /dev/null
@@ -0,0 +1,544 @@
+/*-*- Mode: C; c-basic-offset: 8; indent-tabs-mode: nil -*-*/
+
+#ifndef fooutilhfoo
+#define fooutilhfoo
+
+/***
+  This file is part 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 <http://www.gnu.org/licenses/>.
+***/
+
+#include <inttypes.h>
+#include <time.h>
+#include <sys/time.h>
+#include <stdarg.h>
+#include <stdbool.h>
+#include <stdlib.h>
+#include <stdio.h>
+#include <signal.h>
+#include <sched.h>
+#include <limits.h>
+#include <sys/stat.h>
+#include <dirent.h>
+
+#include "macro.h"
+
+typedef uint64_t usec_t;
+
+typedef struct dual_timestamp {
+        usec_t realtime;
+        usec_t monotonic;
+} dual_timestamp;
+
+#define MSEC_PER_SEC  1000ULL
+#define USEC_PER_SEC  1000000ULL
+#define USEC_PER_MSEC 1000ULL
+#define NSEC_PER_SEC  1000000000ULL
+#define NSEC_PER_MSEC 1000000ULL
+#define NSEC_PER_USEC 1000ULL
+
+#define USEC_PER_MINUTE (60ULL*USEC_PER_SEC)
+#define USEC_PER_HOUR (60ULL*USEC_PER_MINUTE)
+#define USEC_PER_DAY (24ULL*USEC_PER_HOUR)
+#define USEC_PER_WEEK (7ULL*USEC_PER_DAY)
+#define USEC_PER_MONTH (2629800ULL*USEC_PER_SEC)
+#define USEC_PER_YEAR (31557600ULL*USEC_PER_SEC)
+
+/* What is interpreted as whitespace? */
+#define WHITESPACE " \t\n\r"
+#define NEWLINE "\n\r"
+#define QUOTES "\"\'"
+#define COMMENTS "#;\n"
+
+#define FORMAT_TIMESTAMP_MAX 64
+#define FORMAT_TIMESTAMP_PRETTY_MAX 256
+#define FORMAT_TIMESPAN_MAX 64
+#define FORMAT_BYTES_MAX 8
+
+#define ANSI_HIGHLIGHT_ON "\x1B[1;39m"
+#define ANSI_HIGHLIGHT_RED_ON "\x1B[1;31m"
+#define ANSI_HIGHLIGHT_GREEN_ON "\x1B[1;32m"
+#define ANSI_HIGHLIGHT_OFF "\x1B[0m"
+
+usec_t now(clockid_t clock);
+
+dual_timestamp* dual_timestamp_get(dual_timestamp *ts);
+dual_timestamp* dual_timestamp_from_realtime(dual_timestamp *ts, usec_t u);
+
+#define dual_timestamp_is_set(ts) ((ts)->realtime > 0)
+
+usec_t timespec_load(const struct timespec *ts);
+struct timespec *timespec_store(struct timespec *ts, usec_t u);
+
+usec_t timeval_load(const struct timeval *tv);
+struct timeval *timeval_store(struct timeval *tv, usec_t u);
+
+size_t page_size(void);
+#define PAGE_ALIGN(l) ALIGN_TO((l), page_size())
+
+#define streq(a,b) (strcmp((a),(b)) == 0)
+#define strneq(a, b, n) (strncmp((a), (b), (n)) == 0)
+
+bool streq_ptr(const char *a, const char *b);
+
+#define new(t, n) ((t*) malloc(sizeof(t)*(n)))
+
+#define new0(t, n) ((t*) calloc((n), sizeof(t)))
+
+#define malloc0(n) (calloc((n), 1))
+
+static inline const char* yes_no(bool b) {
+        return b ? "yes" : "no";
+}
+
+static inline const char* strempty(const char *s) {
+        return s ? s : "";
+}
+
+static inline const char* strnull(const char *s) {
+        return s ? s : "(null)";
+}
+
+static inline const char *strna(const char *s) {
+        return s ? s : "n/a";
+}
+
+static inline bool is_path_absolute(const char *p) {
+        return *p == '/';
+}
+
+static inline bool isempty(const char *p) {
+        return !p || !p[0];
+}
+
+bool endswith(const char *s, const char *postfix);
+bool startswith(const char *s, const char *prefix);
+bool startswith_no_case(const char *s, const char *prefix);
+
+bool first_word(const char *s, const char *word);
+
+int close_nointr(int fd);
+void close_nointr_nofail(int fd);
+void close_many(const int fds[], unsigned n_fd);
+
+int parse_boolean(const char *v);
+int parse_usec(const char *t, usec_t *usec);
+int parse_bytes(const char *t, off_t *bytes);
+int parse_pid(const char *s, pid_t* ret_pid);
+int parse_uid(const char *s, uid_t* ret_uid);
+#define parse_gid(s, ret_uid) parse_uid(s, ret_uid)
+
+int safe_atou(const char *s, unsigned *ret_u);
+int safe_atoi(const char *s, int *ret_i);
+
+int safe_atollu(const char *s, unsigned long long *ret_u);
+int safe_atolli(const char *s, long long int *ret_i);
+
+#if __WORDSIZE == 32
+static inline int safe_atolu(const char *s, unsigned long *ret_u) {
+        assert_cc(sizeof(unsigned long) == sizeof(unsigned));
+        return safe_atou(s, (unsigned*) ret_u);
+}
+static inline int safe_atoli(const char *s, long int *ret_u) {
+        assert_cc(sizeof(long int) == sizeof(int));
+        return safe_atoi(s, (int*) ret_u);
+}
+#else
+static inline int safe_atolu(const char *s, unsigned long *ret_u) {
+        assert_cc(sizeof(unsigned long) == sizeof(unsigned long long));
+        return safe_atollu(s, (unsigned long long*) ret_u);
+}
+static inline int safe_atoli(const char *s, long int *ret_u) {
+        assert_cc(sizeof(long int) == sizeof(long long int));
+        return safe_atolli(s, (long long int*) ret_u);
+}
+#endif
+
+static inline int safe_atou32(const char *s, uint32_t *ret_u) {
+        assert_cc(sizeof(uint32_t) == sizeof(unsigned));
+        return safe_atou(s, (unsigned*) ret_u);
+}
+
+static inline int safe_atoi32(const char *s, int32_t *ret_i) {
+        assert_cc(sizeof(int32_t) == sizeof(int));
+        return safe_atoi(s, (int*) ret_i);
+}
+
+static inline int safe_atou64(const char *s, uint64_t *ret_u) {
+        assert_cc(sizeof(uint64_t) == sizeof(unsigned long long));
+        return safe_atollu(s, (unsigned long long*) ret_u);
+}
+
+static inline int safe_atoi64(const char *s, int64_t *ret_i) {
+        assert_cc(sizeof(int64_t) == sizeof(long long int));
+        return safe_atolli(s, (long long int*) ret_i);
+}
+
+char *split(const char *c, size_t *l, const char *separator, char **state);
+char *split_quoted(const char *c, size_t *l, char **state);
+
+#define FOREACH_WORD(word, length, s, state)                            \
+        for ((state) = NULL, (word) = split((s), &(length), WHITESPACE, &(state)); (word); (word) = split((s), &(length), WHITESPACE, &(state)))
+
+#define FOREACH_WORD_SEPARATOR(word, length, s, separator, state)       \
+        for ((state) = NULL, (word) = split((s), &(length), (separator), &(state)); (word); (word) = split((s), &(length), (separator), &(state)))
+
+#define FOREACH_WORD_QUOTED(word, length, s, state)                     \
+        for ((state) = NULL, (word) = split_quoted((s), &(length), &(state)); (word); (word) = split_quoted((s), &(length), &(state)))
+
+char **split_path_and_make_absolute(const char *p);
+
+pid_t get_parent_of_pid(pid_t pid, pid_t *ppid);
+int get_starttime_of_pid(pid_t pid, unsigned long long *st);
+
+int write_one_line_file(const char *fn, const char *line);
+int write_one_line_file_atomic(const char *fn, const char *line);
+int read_one_line_file(const char *fn, char **line);
+int read_full_file(const char *fn, char **contents, size_t *size);
+
+int parse_env_file(const char *fname, const char *separator, ...) _sentinel_;
+int load_env_file(const char *fname, char ***l);
+int write_env_file(const char *fname, char **l);
+
+char *strappend(const char *s, const char *suffix);
+char *strnappend(const char *s, const char *suffix, size_t length);
+
+char *replace_env(const char *format, char **env);
+char **replace_env_argv(char **argv, char **env);
+
+int readlink_malloc(const char *p, char **r);
+int readlink_and_make_absolute(const char *p, char **r);
+int readlink_and_canonicalize(const char *p, char **r);
+
+char *file_name_from_path(const char *p);
+bool is_path(const char *p);
+
+bool path_is_absolute(const char *p);
+char *path_make_absolute(const char *p, const char *prefix);
+char *path_make_absolute_cwd(const char *p);
+
+char **strv_path_make_absolute_cwd(char **l);
+char **strv_path_canonicalize(char **l);
+char **strv_path_remove_empty(char **l);
+
+int reset_all_signal_handlers(void);
+
+char *strstrip(char *s);
+char *delete_chars(char *s, const char *bad);
+char *truncate_nl(char *s);
+
+char *file_in_same_dir(const char *path, const char *filename);
+int safe_mkdir(const char *path, mode_t mode, uid_t uid, gid_t gid);
+int mkdir_parents(const char *path, mode_t mode);
+int mkdir_p(const char *path, mode_t mode);
+
+int parent_of_path(const char *path, char **parent);
+
+int rmdir_parents(const char *path, const char *stop);
+
+int get_process_comm(pid_t pid, char **name);
+int get_process_cmdline(pid_t pid, size_t max_length, bool comm_fallback, char **line);
+int get_process_exe(pid_t pid, char **name);
+int get_process_uid(pid_t pid, uid_t *uid);
+
+char hexchar(int x);
+int unhexchar(char c);
+char octchar(int x);
+int unoctchar(char c);
+char decchar(int x);
+int undecchar(char c);
+
+char *cescape(const char *s);
+char *cunescape(const char *s);
+char *cunescape_length(const char *s, size_t length);
+
+char *xescape(const char *s, const char *bad);
+
+char *bus_path_escape(const char *s);
+char *bus_path_unescape(const char *s);
+
+char *path_kill_slashes(char *path);
+
+bool path_startswith(const char *path, const char *prefix);
+bool path_equal(const char *a, const char *b);
+
+char *ascii_strlower(char *path);
+
+bool dirent_is_file(const struct dirent *de);
+bool dirent_is_file_with_suffix(const struct dirent *de, const char *suffix);
+
+bool ignore_file(const char *filename);
+
+bool chars_intersect(const char *a, const char *b);
+
+char *format_timestamp(char *buf, size_t l, usec_t t);
+char *format_timestamp_pretty(char *buf, size_t l, usec_t t);
+char *format_timespan(char *buf, size_t l, usec_t t);
+
+int make_stdio(int fd);
+int make_null_stdio(void);
+
+unsigned long long random_ull(void);
+
+#define __DEFINE_STRING_TABLE_LOOKUP(name,type,scope)                   \
+        scope const char *name##_to_string(type i) {                    \
+                if (i < 0 || i >= (type) ELEMENTSOF(name##_table))      \
+                        return NULL;                                    \
+                return name##_table[i];                                 \
+        }                                                               \
+        scope type name##_from_string(const char *s) {                  \
+                type i;                                                 \
+                unsigned u = 0;                                         \
+                assert(s);                                              \
+                for (i = 0; i < (type)ELEMENTSOF(name##_table); i++)    \
+                        if (name##_table[i] &&                          \
+                            streq(name##_table[i], s))                  \
+                                return i;                               \
+                if (safe_atou(s, &u) >= 0 &&                            \
+                    u < ELEMENTSOF(name##_table))                       \
+                        return (type) u;                                \
+                return (type) -1;                                       \
+        }                                                               \
+        struct __useless_struct_to_allow_trailing_semicolon__
+
+#define DEFINE_STRING_TABLE_LOOKUP(name,type) __DEFINE_STRING_TABLE_LOOKUP(name,type,)
+#define DEFINE_PRIVATE_STRING_TABLE_LOOKUP(name,type) __DEFINE_STRING_TABLE_LOOKUP(name,type,static)
+
+int fd_nonblock(int fd, bool nonblock);
+int fd_cloexec(int fd, bool cloexec);
+
+int close_all_fds(const int except[], unsigned n_except);
+
+bool fstype_is_network(const char *fstype);
+
+int chvt(int vt);
+
+int read_one_char(FILE *f, char *ret, usec_t timeout, bool *need_nl);
+int ask(char *ret, const char *replies, const char *text, ...);
+
+int reset_terminal_fd(int fd, bool switch_to_text);
+int reset_terminal(const char *name);
+
+int open_terminal(const char *name, int mode);
+int acquire_terminal(const char *name, bool fail, bool force, bool ignore_tiocstty_eperm);
+int release_terminal(void);
+
+int flush_fd(int fd);
+
+int ignore_signals(int sig, ...);
+int default_signals(int sig, ...);
+int sigaction_many(const struct sigaction *sa, ...);
+
+int close_pipe(int p[]);
+int fopen_temporary(const char *path, FILE **_f, char **_temp_path);
+
+ssize_t loop_read(int fd, void *buf, size_t nbytes, bool do_poll);
+ssize_t loop_write(int fd, const void *buf, size_t nbytes, bool do_poll);
+
+int path_is_mount_point(const char *path, bool allow_symlink);
+
+bool is_device_path(const char *path);
+
+int dir_is_empty(const char *path);
+
+void rename_process(const char name[8]);
+
+void sigset_add_many(sigset_t *ss, ...);
+
+char* gethostname_malloc(void);
+char* getlogname_malloc(void);
+
+int getttyname_malloc(int fd, char **r);
+int getttyname_harder(int fd, char **r);
+
+int get_ctty_devnr(pid_t pid, dev_t *d);
+int get_ctty(pid_t, dev_t *_devnr, char **r);
+
+int chmod_and_chown(const char *path, mode_t mode, uid_t uid, gid_t gid);
+int fchmod_and_fchown(int fd, mode_t mode, uid_t uid, gid_t gid);
+
+int rm_rf(const char *path, bool only_dirs, bool delete_root, bool honour_sticky);
+
+int pipe_eof(int fd);
+
+cpu_set_t* cpu_set_malloc(unsigned *ncpus);
+
+void status_vprintf(const char *status, bool ellipse, const char *format, va_list ap);
+void status_printf(const char *status, bool ellipse, const char *format, ...);
+void status_welcome(void);
+
+int fd_columns(int fd);
+unsigned columns(void);
+
+int fd_lines(int fd);
+unsigned lines(void);
+
+int running_in_chroot(void);
+
+char *ellipsize(const char *s, size_t length, unsigned percent);
+char *ellipsize_mem(const char *s, size_t old_length, size_t new_length, unsigned percent);
+
+int touch(const char *path);
+
+char *unquote(const char *s, const char *quotes);
+char *normalize_env_assignment(const char *s);
+
+int wait_for_terminate(pid_t pid, siginfo_t *status);
+int wait_for_terminate_and_warn(const char *name, pid_t pid);
+
+_noreturn_ void freeze(void);
+
+bool null_or_empty(struct stat *st);
+int null_or_empty_path(const char *fn);
+
+DIR *xopendirat(int dirfd, const char *name, int flags);
+
+void dual_timestamp_serialize(FILE *f, const char *name, dual_timestamp *t);
+void dual_timestamp_deserialize(const char *value, dual_timestamp *t);
+
+char *fstab_node_to_udev_node(const char *p);
+
+void filter_environ(const char *prefix);
+
+bool tty_is_vc(const char *tty);
+bool tty_is_vc_resolve(const char *tty);
+int vtnr_from_tty(const char *tty);
+const char *default_term_for_tty(const char *tty);
+
+void execute_directory(const char *directory, DIR *_d, char *argv[]);
+
+int kill_and_sigcont(pid_t pid, int sig);
+
+bool nulstr_contains(const char*nulstr, const char *needle);
+
+bool plymouth_running(void);
+
+void parse_syslog_priority(char **p, int *priority);
+void skip_syslog_pid(char **buf);
+void skip_syslog_date(char **buf);
+
+int have_effective_cap(int value);
+
+bool hostname_is_valid(const char *s);
+char* hostname_cleanup(char *s);
+
+char* strshorten(char *s, size_t l);
+
+int terminal_vhangup_fd(int fd);
+int terminal_vhangup(const char *name);
+
+int vt_disallocate(const char *name);
+
+int copy_file(const char *from, const char *to);
+int symlink_or_copy(const char *from, const char *to);
+int symlink_or_copy_atomic(const char *from, const char *to);
+
+int fchmod_umask(int fd, mode_t mode);
+
+int conf_files_list(char ***strv, const char *suffix, const char *dir, ...);
+
+int hwclock_is_localtime(void);
+int hwclock_apply_localtime_delta(int *min);
+int hwclock_reset_localtime_delta(void);
+int hwclock_get_time(struct tm *tm);
+int hwclock_set_time(const struct tm *tm);
+
+int audit_session_from_pid(pid_t pid, uint32_t *id);
+int audit_loginuid_from_pid(pid_t pid, uid_t *uid);
+
+bool display_is_local(const char *display);
+int socket_from_display(const char *display, char **path);
+
+int get_user_creds(const char **username, uid_t *uid, gid_t *gid, const char **home);
+int get_group_creds(const char **groupname, gid_t *gid);
+
+int in_group(const char *name);
+
+int glob_exists(const char *path);
+
+int dirent_ensure_type(DIR *d, struct dirent *de);
+
+int in_search_path(const char *path, char **search);
+int get_files_in_directory(const char *path, char ***list);
+
+char *join(const char *x, ...) _sentinel_;
+
+bool is_main_thread(void);
+
+bool in_charset(const char *s, const char* charset);
+
+int block_get_whole_disk(dev_t d, dev_t *ret);
+
+int file_is_priv_sticky(const char *p);
+
+int strdup_or_null(const char *a, char **b);
+
+#define NULSTR_FOREACH(i, l)                                    \
+        for ((i) = (l); (i) && *(i); (i) = strchr((i), 0)+1)
+
+#define NULSTR_FOREACH_PAIR(i, j, l)                             \
+        for ((i) = (l), (j) = strchr((i), 0)+1; (i) && *(i); (i) = strchr((j), 0)+1, (j) = *(i) ? strchr((i), 0)+1 : (i))
+
+const char *ioprio_class_to_string(int i);
+int ioprio_class_from_string(const char *s);
+
+const char *sigchld_code_to_string(int i);
+int sigchld_code_from_string(const char *s);
+
+const char *log_facility_unshifted_to_string(int i);
+int log_facility_unshifted_from_string(const char *s);
+
+const char *log_level_to_string(int i);
+int log_level_from_string(const char *s);
+
+const char *sched_policy_to_string(int i);
+int sched_policy_from_string(const char *s);
+
+const char *rlimit_to_string(int i);
+int rlimit_from_string(const char *s);
+
+const char *ip_tos_to_string(int i);
+int ip_tos_from_string(const char *s);
+
+const char *signal_to_string(int i);
+int signal_from_string(const char *s);
+
+int signal_from_string_try_harder(const char *s);
+
+extern int saved_argc;
+extern char **saved_argv;
+
+bool kexec_loaded(void);
+
+int prot_from_flags(int flags);
+
+unsigned long cap_last_cap(void);
+
+char *format_bytes(char *buf, size_t l, off_t t);
+
+int fd_wait_for_event(int fd, int event, usec_t timeout);
+
+void* memdup(const void *p, size_t l);
+
+int rtc_open(int flags);
+
+int is_kernel_thread(pid_t pid);
+
+int fd_inc_sndbuf(int fd, size_t n);
+int fd_inc_rcvbuf(int fd, size_t n);
+
+#endif
diff --git a/src/utmp-wtmp.c b/src/utmp-wtmp.c
new file mode 100644 (file)
index 0000000..217ae1e
--- /dev/null
@@ -0,0 +1,430 @@
+/*-*- 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 <http://www.gnu.org/licenses/>.
+***/
+
+#include <utmpx.h>
+#include <errno.h>
+#include <assert.h>
+#include <string.h>
+#include <sys/utsname.h>
+#include <fcntl.h>
+#include <unistd.h>
+#include <sys/poll.h>
+
+#include "macro.h"
+#include "utmp-wtmp.h"
+
+int utmp_get_runlevel(int *runlevel, int *previous) {
+        struct utmpx lookup, *found;
+        int r;
+        const char *e;
+
+        assert(runlevel);
+
+        /* If these values are set in the environment this takes
+         * precedence. Presumably, sysvinit does this to work around a
+         * race condition that would otherwise exist where we'd always
+         * go to disk and hence might read runlevel data that might be
+         * very new and does not apply to the current script being
+         * executed. */
+
+        if ((e = getenv("RUNLEVEL")) && e[0] > 0) {
+                *runlevel = e[0];
+
+                if (previous) {
+                        /* $PREVLEVEL seems to be an Upstart thing */
+
+                        if ((e = getenv("PREVLEVEL")) && e[0] > 0)
+                                *previous = e[0];
+                        else
+                                *previous = 0;
+                }
+
+                return 0;
+        }
+
+        if (utmpxname(_PATH_UTMPX) < 0)
+                return -errno;
+
+        setutxent();
+
+        zero(lookup);
+        lookup.ut_type = RUN_LVL;
+
+        if (!(found = getutxid(&lookup)))
+                r = -errno;
+        else {
+                int a, b;
+
+                a = found->ut_pid & 0xFF;
+                b = (found->ut_pid >> 8) & 0xFF;
+
+                if (a < 0 || b < 0)
+                        r = -EIO;
+                else {
+                        *runlevel = a;
+
+                        if (previous)
+                                *previous = b;
+                        r = 0;
+                }
+        }
+
+        endutxent();
+
+        return r;
+}
+
+static void init_timestamp(struct utmpx *store, usec_t t) {
+        assert(store);
+
+        zero(*store);
+
+        if (t <= 0)
+                t = now(CLOCK_REALTIME);
+
+        store->ut_tv.tv_sec = t / USEC_PER_SEC;
+        store->ut_tv.tv_usec = t % USEC_PER_SEC;
+}
+
+static void init_entry(struct utmpx *store, usec_t t) {
+        struct utsname uts;
+
+        assert(store);
+
+        init_timestamp(store, t);
+
+        zero(uts);
+
+        if (uname(&uts) >= 0)
+                strncpy(store->ut_host, uts.release, sizeof(store->ut_host));
+
+        strncpy(store->ut_line, "~", sizeof(store->ut_line));  /* or ~~ ? */
+        strncpy(store->ut_id, "~~", sizeof(store->ut_id));
+}
+
+static int write_entry_utmp(const struct utmpx *store) {
+        int r;
+
+        assert(store);
+
+        /* utmp is similar to wtmp, but there is only one entry for
+         * each entry type resp. user; i.e. basically a key/value
+         * table. */
+
+        if (utmpxname(_PATH_UTMPX) < 0)
+                return -errno;
+
+        setutxent();
+
+        if (!pututxline(store))
+                r = -errno;
+        else
+                r = 0;
+
+        endutxent();
+
+        return r;
+}
+
+static int write_entry_wtmp(const struct utmpx *store) {
+        assert(store);
+
+        /* wtmp is a simple append-only file where each entry is
+        simply appended to * the end; i.e. basically a log. */
+
+        errno = 0;
+        updwtmpx(_PATH_WTMPX, store);
+        return -errno;
+}
+
+static int write_utmp_wtmp(const struct utmpx *store_utmp, const struct utmpx *store_wtmp) {
+        int r, s;
+
+        r = write_entry_utmp(store_utmp);
+        s = write_entry_wtmp(store_wtmp);
+
+        if (r >= 0)
+                r = s;
+
+        /* If utmp/wtmp have been disabled, that's a good thing, hence
+         * ignore the errors */
+        if (r == -ENOENT)
+                r = 0;
+
+        return r;
+}
+
+static int write_entry_both(const struct utmpx *store) {
+        return write_utmp_wtmp(store, store);
+}
+
+int utmp_put_shutdown(void) {
+        struct utmpx store;
+
+        init_entry(&store, 0);
+
+        store.ut_type = RUN_LVL;
+        strncpy(store.ut_user, "shutdown", sizeof(store.ut_user));
+
+        return write_entry_both(&store);
+}
+
+int utmp_put_reboot(usec_t t) {
+        struct utmpx store;
+
+        init_entry(&store, t);
+
+        store.ut_type = BOOT_TIME;
+        strncpy(store.ut_user, "reboot", sizeof(store.ut_user));
+
+        return write_entry_both(&store);
+}
+
+static const char *sanitize_id(const char *id) {
+        size_t l;
+
+        assert(id);
+        l = strlen(id);
+
+        if (l <= sizeof(((struct utmpx*) NULL)->ut_id))
+                return id;
+
+        return id + l - sizeof(((struct utmpx*) NULL)->ut_id);
+}
+
+int utmp_put_init_process(const char *id, pid_t pid, pid_t sid, const char *line) {
+        struct utmpx store;
+
+        assert(id);
+
+        init_timestamp(&store, 0);
+
+        store.ut_type = INIT_PROCESS;
+        store.ut_pid = pid;
+        store.ut_session = sid;
+
+        strncpy(store.ut_id, sanitize_id(id), sizeof(store.ut_id));
+
+        if (line)
+                strncpy(store.ut_line, file_name_from_path(line), sizeof(store.ut_line));
+
+        return write_entry_both(&store);
+}
+
+int utmp_put_dead_process(const char *id, pid_t pid, int code, int status) {
+        struct utmpx lookup, store, store_wtmp, *found;
+
+        assert(id);
+
+        setutxent();
+
+        zero(lookup);
+        lookup.ut_type = INIT_PROCESS; /* looks for DEAD_PROCESS, LOGIN_PROCESS, USER_PROCESS, too */
+        strncpy(lookup.ut_id, sanitize_id(id), sizeof(lookup.ut_id));
+
+        if (!(found = getutxid(&lookup)))
+                return 0;
+
+        if (found->ut_pid != pid)
+                return 0;
+
+        memcpy(&store, found, sizeof(store));
+        store.ut_type = DEAD_PROCESS;
+        store.ut_exit.e_termination = code;
+        store.ut_exit.e_exit = status;
+
+        zero(store.ut_user);
+        zero(store.ut_host);
+        zero(store.ut_tv);
+
+        memcpy(&store_wtmp, &store, sizeof(store_wtmp));
+        /* wtmp wants the current time */
+        init_timestamp(&store_wtmp, 0);
+
+        return write_utmp_wtmp(&store, &store_wtmp);
+}
+
+
+int utmp_put_runlevel(int runlevel, int previous) {
+        struct utmpx store;
+        int r;
+
+        assert(runlevel > 0);
+
+        if (previous <= 0) {
+                /* Find the old runlevel automatically */
+
+                if ((r = utmp_get_runlevel(&previous, NULL)) < 0) {
+                        if (r != -ESRCH)
+                                return r;
+
+                        previous = 0;
+                }
+        }
+
+        if (previous == runlevel)
+                return 0;
+
+        init_entry(&store, 0);
+
+        store.ut_type = RUN_LVL;
+        store.ut_pid = (runlevel & 0xFF) | ((previous & 0xFF) << 8);
+        strncpy(store.ut_user, "runlevel", sizeof(store.ut_user));
+
+        return write_entry_both(&store);
+}
+
+#define TIMEOUT_MSEC 50
+
+static int write_to_terminal(const char *tty, const char *message) {
+        int fd, r;
+        const char *p;
+        size_t left;
+        usec_t end;
+
+        assert(tty);
+        assert(message);
+
+        if ((fd = open(tty, O_WRONLY|O_NDELAY|O_NOCTTY|O_CLOEXEC)) < 0)
+                return -errno;
+
+        if (!isatty(fd)) {
+                r = -errno;
+                goto finish;
+        }
+
+        p = message;
+        left = strlen(message);
+
+        end = now(CLOCK_MONOTONIC) + TIMEOUT_MSEC*USEC_PER_MSEC;
+
+        while (left > 0) {
+                ssize_t n;
+                struct pollfd pollfd;
+                usec_t t;
+                int k;
+
+                t = now(CLOCK_MONOTONIC);
+
+                if (t >= end) {
+                        r = -ETIME;
+                        goto finish;
+                }
+
+                zero(pollfd);
+                pollfd.fd = fd;
+                pollfd.events = POLLOUT;
+
+                if ((k = poll(&pollfd, 1, (end - t) / USEC_PER_MSEC)) < 0)
+                        return -errno;
+
+                if (k <= 0) {
+                        r = -ETIME;
+                        goto finish;
+                }
+
+                if ((n = write(fd, p, left)) < 0) {
+
+                        if (errno == EAGAIN)
+                                continue;
+
+                        r = -errno;
+                        goto finish;
+                }
+
+                assert((size_t) n <= left);
+
+                p += n;
+                left -= n;
+        }
+
+        r = 0;
+
+finish:
+        close_nointr_nofail(fd);
+
+        return r;
+}
+
+int utmp_wall(const char *message, bool (*match_tty)(const char *tty)) {
+        struct utmpx *u;
+        char date[FORMAT_TIMESTAMP_MAX];
+        char *text = NULL, *hn = NULL, *un = NULL, *tty = NULL;
+        int r;
+
+        if (!(hn = gethostname_malloc()) ||
+            !(un = getlogname_malloc())) {
+                r = -ENOMEM;
+                goto finish;
+        }
+
+        getttyname_harder(STDIN_FILENO, &tty);
+
+        if (asprintf(&text,
+                     "\a\r\n"
+                     "Broadcast message from %s@%s%s%s (%s):\r\n\r\n"
+                     "%s\r\n\r\n",
+                     un, hn,
+                     tty ? " on " : "", strempty(tty),
+                     format_timestamp(date, sizeof(date), now(CLOCK_REALTIME)),
+                     message) < 0) {
+                r = -ENOMEM;
+                goto finish;
+        }
+
+        setutxent();
+
+        r = 0;
+
+        while ((u = getutxent())) {
+                int q;
+                const char *path;
+                char *buf = NULL;
+
+                if (u->ut_type != USER_PROCESS || u->ut_user[0] == 0)
+                        continue;
+
+                if (path_startswith(u->ut_line, "/dev/"))
+                        path = u->ut_line;
+                else {
+                        if (asprintf(&buf, "/dev/%s", u->ut_line) < 0) {
+                                r = -ENOMEM;
+                                goto finish;
+                        }
+
+                        path = buf;
+                }
+
+                if (!match_tty || match_tty(path))
+                        if ((q = write_to_terminal(path, text)) < 0)
+                                r = q;
+
+                free(buf);
+        }
+
+finish:
+        free(hn);
+        free(un);
+        free(tty);
+        free(text);
+
+        return r;
+}
diff --git a/src/utmp-wtmp.h b/src/utmp-wtmp.h
new file mode 100644 (file)
index 0000000..a5998eb
--- /dev/null
@@ -0,0 +1,38 @@
+/*-*- Mode: C; c-basic-offset: 8; indent-tabs-mode: nil -*-*/
+
+#ifndef fooutmpwtmphfoo
+#define fooutmpwtmphfoo
+
+/***
+  This file is part 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 <http://www.gnu.org/licenses/>.
+***/
+
+#include "util.h"
+
+int utmp_get_runlevel(int *runlevel, int *previous);
+
+int utmp_put_shutdown(void);
+int utmp_put_reboot(usec_t timestamp);
+int utmp_put_runlevel(int runlevel, int previous);
+
+int utmp_put_dead_process(const char *id, pid_t pid, int code, int status);
+int utmp_put_init_process(const char *id, pid_t pid, pid_t sid, const char *line);
+
+int utmp_wall(const char *message, bool (*match_tty)(const char *tty));
+
+#endif
diff --git a/src/vconsole/Makefile b/src/vconsole/Makefile
new file mode 120000 (symlink)
index 0000000..d0b0e8e
--- /dev/null
@@ -0,0 +1 @@
+../Makefile
\ No newline at end of file
diff --git a/src/vconsole/vconsole-setup.c b/src/vconsole/vconsole-setup.c
new file mode 100644 (file)
index 0000000..9196789
--- /dev/null
@@ -0,0 +1,459 @@
+/*-*- Mode: C; c-basic-offset: 8; indent-tabs-mode: nil -*-*/
+
+/***
+  This file is part of systemd.
+
+  Copyright 2010 Kay Sievers
+
+  systemd is free software; you can redistribute it and/or modify it
+  under the terms of the GNU 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 <http://www.gnu.org/licenses/>.
+***/
+
+#include <stdio.h>
+#include <unistd.h>
+#include <stdlib.h>
+#include <errno.h>
+#include <string.h>
+#include <fcntl.h>
+#include <ctype.h>
+#include <stdbool.h>
+#include <stdarg.h>
+#include <limits.h>
+#include <locale.h>
+#include <langinfo.h>
+#include <sys/ioctl.h>
+#include <sys/wait.h>
+#include <linux/tiocl.h>
+#include <linux/kd.h>
+
+#include "util.h"
+#include "log.h"
+#include "macro.h"
+#include "virt.h"
+
+static bool is_vconsole(int fd) {
+        unsigned char data[1];
+
+        data[0] = TIOCL_GETFGCONSOLE;
+        return ioctl(fd, TIOCLINUX, data) >= 0;
+}
+
+static bool is_locale_utf8(void) {
+        const char *set;
+
+        if (!setlocale(LC_ALL, ""))
+                return true;
+
+        set = nl_langinfo(CODESET);
+        if (!set)
+                return true;
+
+        return streq(set, "UTF-8");
+}
+
+static int disable_utf8(int fd) {
+        int r = 0, k;
+
+        if (ioctl(fd, KDSKBMODE, K_XLATE) < 0)
+                r = -errno;
+
+        if (loop_write(fd, "\033%@", 3, false) < 0)
+                r = -errno;
+
+        if ((k = write_one_line_file("/sys/module/vt/parameters/default_utf8", "0")) < 0)
+                r = k;
+
+        if (r < 0)
+                log_warning("Failed to disable UTF-8: %s", strerror(errno));
+
+        return r;
+}
+
+static int load_keymap(const char *vc, const char *map, const char *map_toggle, bool utf8, pid_t *_pid) {
+        const char *args[8];
+        int i = 0;
+        pid_t pid;
+
+        if (isempty(map)) {
+                /* An empty map means kernel map */
+                *_pid = 0;
+                return 0;
+        }
+
+        args[i++] = KBD_LOADKEYS;
+        args[i++] = "-q";
+        args[i++] = "-C";
+        args[i++] = vc;
+        if (utf8)
+                args[i++] = "-u";
+        args[i++] = map;
+        if (map_toggle)
+                args[i++] = map_toggle;
+        args[i++] = NULL;
+
+        if ((pid = fork()) < 0) {
+                log_error("Failed to fork: %m");
+                return -errno;
+        } else if (pid == 0) {
+                execv(args[0], (char **) args);
+                _exit(EXIT_FAILURE);
+        }
+
+        *_pid = pid;
+        return 0;
+}
+
+static int load_font(const char *vc, const char *font, const char *map, const char *unimap, pid_t *_pid) {
+        const char *args[9];
+        int i = 0;
+        pid_t pid;
+
+        if (isempty(font)) {
+                /* An empty font means kernel font */
+                *_pid = 0;
+                return 0;
+        }
+
+        args[i++] = KBD_SETFONT;
+        args[i++] = "-C";
+        args[i++] = vc;
+        args[i++] = font;
+        if (map) {
+                args[i++] = "-m";
+                args[i++] = map;
+        }
+        if (unimap) {
+                args[i++] = "-u";
+                args[i++] = unimap;
+        }
+        args[i++] = NULL;
+
+        if ((pid = fork()) < 0) {
+                log_error("Failed to fork: %m");
+                return -errno;
+        } else if (pid == 0) {
+                execv(args[0], (char **) args);
+                _exit(EXIT_FAILURE);
+        }
+
+        *_pid = pid;
+        return 0;
+}
+
+int main(int argc, char **argv) {
+        const char *vc;
+        char *vc_keymap = NULL;
+        char *vc_keymap_toggle = NULL;
+        char *vc_font = NULL;
+        char *vc_font_map = NULL;
+        char *vc_font_unimap = NULL;
+#ifdef TARGET_GENTOO
+        char *vc_unicode = NULL;
+#endif
+#if defined(TARGET_MANDRIVA) || defined(TARGET_MAGEIA)
+        char *vc_keytable = NULL;
+#endif
+        int fd = -1;
+        bool utf8;
+        int r = EXIT_FAILURE;
+        pid_t font_pid = 0, keymap_pid = 0;
+
+        log_set_target(LOG_TARGET_AUTO);
+        log_parse_environment();
+        log_open();
+
+        umask(0022);
+
+        if (argv[1])
+                vc = argv[1];
+        else
+                vc = "/dev/tty0";
+
+        if ((fd = open_terminal(vc, O_RDWR|O_CLOEXEC)) < 0) {
+                log_error("Failed to open %s: %m", vc);
+                goto finish;
+        }
+
+        if (!is_vconsole(fd)) {
+                log_error("Device %s is not a virtual console.", vc);
+                goto finish;
+        }
+
+        utf8 = is_locale_utf8();
+
+        vc_keymap = strdup("us");
+        vc_font = strdup(DEFAULT_FONT);
+
+        if (!vc_keymap || !vc_font) {
+                log_error("Failed to allocate strings.");
+                goto finish;
+        }
+
+        r = 0;
+
+        if (detect_container(NULL) <= 0)
+                if ((r = parse_env_file("/proc/cmdline", WHITESPACE,
+                                        "vconsole.keymap", &vc_keymap,
+                                        "vconsole.keymap.toggle", &vc_keymap_toggle,
+                                        "vconsole.font", &vc_font,
+                                        "vconsole.font.map", &vc_font_map,
+                                        "vconsole.font.unimap", &vc_font_unimap,
+                                        NULL)) < 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/vconsole.conf */
+        if (r <= 0 &&
+            (r = parse_env_file("/etc/vconsole.conf", NEWLINE,
+                                "KEYMAP", &vc_keymap,
+                                "KEYMAP_TOGGLE", &vc_keymap_toggle,
+                                "FONT", &vc_font,
+                                "FONT_MAP", &vc_font_map,
+                                "FONT_UNIMAP", &vc_font_unimap,
+                                NULL)) < 0) {
+
+                if (r != -ENOENT)
+                        log_warning("Failed to read /etc/vconsole.conf: %s", strerror(-r));
+        }
+
+        if (r <= 0) {
+#if defined(TARGET_FEDORA) || defined(TARGET_MEEGO)
+                if ((r = parse_env_file("/etc/sysconfig/i18n", NEWLINE,
+                                        "SYSFONT", &vc_font,
+                                        "SYSFONTACM", &vc_font_map,
+                                        "UNIMAP", &vc_font_unimap,
+                                        NULL)) < 0) {
+
+                        if (r != -ENOENT)
+                                log_warning("Failed to read /etc/sysconfig/i18n: %s", strerror(-r));
+                }
+
+                if ((r = parse_env_file("/etc/sysconfig/keyboard", NEWLINE,
+                                        "KEYTABLE", &vc_keymap,
+                                        "KEYMAP", &vc_keymap,
+                                        NULL)) < 0) {
+
+                        if (r != -ENOENT)
+                                log_warning("Failed to read /etc/sysconfig/i18n: %s", strerror(-r));
+                }
+
+                if (access("/etc/sysconfig/console/default.kmap", F_OK) >= 0) {
+                        char *t;
+
+                        if (!(t = strdup("/etc/sysconfig/console/default.kmap"))) {
+                                log_error("Out of memory.");
+                                goto finish;
+                        }
+
+                        free(vc_keymap);
+                        vc_keymap = t;
+                }
+
+#elif defined(TARGET_SUSE)
+                if ((r = parse_env_file("/etc/sysconfig/keyboard", NEWLINE,
+                                        "KEYTABLE", &vc_keymap,
+                                        NULL)) < 0) {
+
+                        if (r != -ENOENT)
+                                log_warning("Failed to read /etc/sysconfig/keyboard: %s", strerror(-r));
+                }
+
+                if ((r = parse_env_file("/etc/sysconfig/console", NEWLINE,
+                                        "CONSOLE_FONT", &vc_font,
+                                        "CONSOLE_SCREENMAP", &vc_font_map,
+                                        "CONSOLE_UNICODEMAP", &vc_font_unimap,
+                                        NULL)) < 0) {
+
+                        if (r != -ENOENT)
+                                log_warning("Failed to read /etc/sysconfig/console: %s", strerror(-r));
+                }
+
+#elif defined(TARGET_ARCH)
+                if ((r = parse_env_file("/etc/rc.conf", NEWLINE,
+                                        "KEYMAP", &vc_keymap,
+                                        "CONSOLEFONT", &vc_font,
+                                        "CONSOLEMAP", &vc_font_map,
+                                        NULL)) < 0) {
+
+                        if (r != -ENOENT)
+                                log_warning("Failed to read /etc/rc.conf: %s", strerror(-r));
+                }
+
+#elif defined(TARGET_FRUGALWARE)
+                if ((r = parse_env_file("/etc/sysconfig/keymap", NEWLINE,
+                                        "keymap", &vc_keymap,
+                                        NULL)) < 0) {
+                        if (r != -ENOENT)
+                                log_warning("Failed to read /etc/sysconfig/keymap: %s", strerror(-r));
+                }
+                if ((r = parse_env_file("/etc/sysconfig/font", NEWLINE,
+                                        "font", &vc_font,
+                                        NULL)) < 0) {
+                        if (r != -ENOENT)
+                                log_warning("Failed to read /etc/sysconfig/font: %s", strerror(-r));
+                }
+
+#elif defined(TARGET_ALTLINUX)
+                if ((r = parse_env_file("/etc/sysconfig/keyboard", NEWLINE,
+                                        "KEYTABLE", &vc_keymap,
+                                        NULL)) < 0) {
+
+                        if (r != -ENOENT)
+                                log_warning("Failed to read /etc/sysconfig/keyboard: %s", strerror(-r));
+                }
+
+                if ((r = parse_env_file("/etc/sysconfig/consolefont", NEWLINE,
+                                        "SYSFONT", &vc_font,
+                                        NULL)) < 0) {
+
+                        if (r != -ENOENT)
+                                log_warning("Failed to read /etc/sysconfig/console: %s", strerror(-r));
+                }
+
+#elif defined(TARGET_GENTOO)
+                if ((r = parse_env_file("/etc/rc.conf", NEWLINE,
+                                        "unicode", &vc_unicode,
+                                        NULL)) < 0) {
+                        if (r != -ENOENT)
+                                log_warning("Failed to read /etc/rc.conf: %s", strerror(-r));
+                }
+
+                if (vc_unicode) {
+                        int rc_unicode;
+
+                        if ((rc_unicode = parse_boolean(vc_unicode)) < 0)
+                                log_error("Unknown value for /etc/rc.conf unicode=%s", vc_unicode);
+                        else {
+                                if (rc_unicode && !utf8)
+                                        log_warning("/etc/rc.conf wants unicode, but current locale is not UTF-8 capable!");
+                                else if (!rc_unicode && utf8) {
+                                        log_debug("/etc/rc.conf does not want unicode, leave it on in kernel but does not apply to vconsole.");
+                                        utf8 = false;
+                                }
+                        }
+                }
+
+                /* /etc/conf.d/consolefont comments and gentoo
+                 * documentation mention uppercase, but the actual
+                 * contents are lowercase.  the existing
+                 * /etc/init.d/consolefont tries both
+                 */
+                if ((r = parse_env_file("/etc/conf.d/consolefont", NEWLINE,
+                                        "CONSOLEFONT", &vc_font,
+                                        "consolefont", &vc_font,
+                                        "consoletranslation", &vc_font_map,
+                                        "CONSOLETRANSLATION", &vc_font_map,
+                                        "unicodemap", &vc_font_unimap,
+                                        "UNICODEMAP", &vc_font_unimap,
+                                        NULL)) < 0) {
+                        if (r != -ENOENT)
+                                log_warning("Failed to read /etc/conf.d/consolefont: %s", strerror(-r));
+                }
+
+                if ((r = parse_env_file("/etc/conf.d/keymaps", NEWLINE,
+                                        "keymap", &vc_keymap,
+                                        "KEYMAP", &vc_keymap,
+                                        NULL)) < 0) {
+                        if (r != -ENOENT)
+                                log_warning("Failed to read /etc/conf.d/keymaps: %s", strerror(-r));
+                }
+
+#elif defined(TARGET_MANDRIVA) || defined (TARGET_MAGEIA)
+
+                if ((r = parse_env_file("/etc/sysconfig/i18n", NEWLINE,
+                                        "SYSFONT", &vc_font,
+                                        "SYSFONTACM", &vc_font_map,
+                                        "UNIMAP", &vc_font_unimap,
+                                        NULL)) < 0) {
+
+                        if (r != -ENOENT)
+                                log_warning("Failed to read /etc/sysconfig/i18n: %s", strerror(-r));
+                }
+
+                if ((r = parse_env_file("/etc/sysconfig/keyboard", NEWLINE,
+                                        "KEYTABLE", &vc_keytable,
+                                        "KEYMAP", &vc_keymap,
+                                        "UNIKEYTABLE", &vc_keymap,
+                                        "GRP_TOGGLE", &vc_keymap_toggle,
+                                        NULL)) < 0) {
+
+                        if (r != -ENOENT)
+                                log_warning("Failed to read /etc/sysconfig/i18n: %s", strerror(-r));
+                }
+
+                if (vc_keytable) {
+                        if (vc_keymap)
+                                free(vc_keymap);
+                        if (utf8) {
+                                if (endswith(vc_keytable, ".uni") || strstr(vc_keytable, ".uni."))
+                                        vc_keymap = strdup(vc_keytable);
+                                else {
+                                        char *s;
+                                        if ((s = strstr(vc_keytable, ".map")))
+                                                vc_keytable[s-vc_keytable+1] = '\0';
+                                        vc_keymap = strappend(vc_keytable, ".uni");
+                                }
+                        } else
+                                vc_keymap = strdup(vc_keytable);
+
+                        free(vc_keytable);
+
+                        if (!vc_keymap) {
+                                log_error("Out of memory.");
+                                goto finish;
+                        }
+                }
+
+                if (access("/etc/sysconfig/console/default.kmap", F_OK) >= 0) {
+                        char *t;
+
+                        if (!(t = strdup("/etc/sysconfig/console/default.kmap"))) {
+                                log_error("Out of memory.");
+                                goto finish;
+                        }
+
+                        free(vc_keymap);
+                        vc_keymap = t;
+                }
+#endif
+        }
+
+        r = EXIT_FAILURE;
+
+        if (!utf8)
+                disable_utf8(fd);
+
+        if (load_keymap(vc, vc_keymap, vc_keymap_toggle, utf8, &keymap_pid) >= 0 &&
+            load_font(vc, vc_font, vc_font_map, vc_font_unimap, &font_pid) >= 0)
+                r = EXIT_SUCCESS;
+
+finish:
+        if (keymap_pid > 0)
+                wait_for_terminate_and_warn(KBD_LOADKEYS, keymap_pid);
+
+        if (font_pid > 0)
+                wait_for_terminate_and_warn(KBD_SETFONT, font_pid);
+
+        free(vc_keymap);
+        free(vc_font);
+        free(vc_font_map);
+        free(vc_font_unimap);
+
+        if (fd >= 0)
+                close_nointr_nofail(fd);
+
+        return r;
+}
diff --git a/src/virt.c b/src/virt.c
new file mode 100644 (file)
index 0000000..4c526ff
--- /dev/null
@@ -0,0 +1,292 @@
+/*-*- 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 <http://www.gnu.org/licenses/>.
+***/
+
+#include <string.h>
+#include <errno.h>
+#include <unistd.h>
+
+#include "util.h"
+#include "virt.h"
+
+/* Returns a short identifier for the various VM implementations */
+int detect_vm(const char **id) {
+
+#if defined(__i386__) || defined(__x86_64__)
+
+        /* Both CPUID and DMI are x86 specific interfaces... */
+
+        static const char *const dmi_vendors[] = {
+                "/sys/class/dmi/id/sys_vendor",
+                "/sys/class/dmi/id/board_vendor",
+                "/sys/class/dmi/id/bios_vendor"
+        };
+
+        static const char dmi_vendor_table[] =
+                "QEMU\0"                  "qemu\0"
+                /* http://kb.vmware.com/selfservice/microsites/search.do?language=en_US&cmd=displayKC&externalId=1009458 */
+                "VMware\0"                "vmware\0"
+                "VMW\0"                   "vmware\0"
+                "Microsoft Corporation\0" "microsoft\0"
+                "innotek GmbH\0"          "oracle\0"
+                "Xen\0"                   "xen\0"
+                "Bochs\0"                 "bochs\0";
+
+        static const char cpuid_vendor_table[] =
+                "XenVMMXenVMM\0"          "xen\0"
+                "KVMKVMKVM\0"             "kvm\0"
+                /* http://kb.vmware.com/selfservice/microsites/search.do?language=en_US&cmd=displayKC&externalId=1009458 */
+                "VMwareVMware\0"          "vmware\0"
+                /* http://msdn.microsoft.com/en-us/library/ff542428.aspx */
+                "Microsoft Hv\0"          "microsoft\0";
+
+        uint32_t eax, ecx;
+        union {
+                uint32_t sig32[3];
+                char text[13];
+        } sig;
+        unsigned i;
+        const char *j, *k;
+        bool hypervisor;
+
+        /* http://lwn.net/Articles/301888/ */
+        zero(sig);
+
+#if defined (__i386__)
+#define REG_a "eax"
+#define REG_b "ebx"
+#elif defined (__amd64__)
+#define REG_a "rax"
+#define REG_b "rbx"
+#endif
+
+        /* First detect whether there is a hypervisor */
+        eax = 1;
+        __asm__ __volatile__ (
+                /* ebx/rbx is being used for PIC! */
+                "  push %%"REG_b"         \n\t"
+                "  cpuid                  \n\t"
+                "  pop %%"REG_b"          \n\t"
+
+                : "=a" (eax), "=c" (ecx)
+                : "0" (eax)
+        );
+
+        hypervisor = !!(ecx & 0x80000000U);
+
+        if (hypervisor) {
+
+                /* There is a hypervisor, see what it is */
+                eax = 0x40000000U;
+                __asm__ __volatile__ (
+                        /* ebx/rbx is being used for PIC! */
+                        "  push %%"REG_b"         \n\t"
+                        "  cpuid                  \n\t"
+                        "  mov %%ebx, %1          \n\t"
+                        "  pop %%"REG_b"          \n\t"
+
+                        : "=a" (eax), "=r" (sig.sig32[0]), "=c" (sig.sig32[1]), "=d" (sig.sig32[2])
+                        : "0" (eax)
+                );
+
+                NULSTR_FOREACH_PAIR(j, k, cpuid_vendor_table)
+                        if (streq(sig.text, j)) {
+
+                                if (id)
+                                        *id = k;
+
+                                return 1;
+                        }
+        }
+
+        for (i = 0; i < ELEMENTSOF(dmi_vendors); i++) {
+                char *s;
+                int r;
+                const char *found = NULL;
+
+                if ((r = read_one_line_file(dmi_vendors[i], &s)) < 0) {
+                        if (r != -ENOENT)
+                                return r;
+
+                        continue;
+                }
+
+                NULSTR_FOREACH_PAIR(j, k, dmi_vendor_table)
+                        if (startswith(s, j))
+                                found = k;
+                free(s);
+
+                if (found) {
+                        if (id)
+                                *id = found;
+
+                        return 1;
+                }
+        }
+
+        if (hypervisor) {
+                if (id)
+                        *id = "other";
+
+                return 1;
+        }
+
+#endif
+        return 0;
+}
+
+int detect_container(const char **id) {
+        FILE *f;
+
+        /* Unfortunately many of these operations require root access
+         * in one way or another */
+
+        if (geteuid() != 0)
+                return -EPERM;
+
+        if (running_in_chroot() > 0) {
+
+                if (id)
+                        *id = "chroot";
+
+                return 1;
+        }
+
+        /* /proc/vz exists in container and outside of the container,
+         * /proc/bc only outside of the container. */
+        if (access("/proc/vz", F_OK) >= 0 &&
+            access("/proc/bc", F_OK) < 0) {
+
+                if (id)
+                        *id = "openvz";
+
+                return 1;
+        }
+
+        f = fopen("/proc/1/environ", "re");
+        if (f) {
+                bool done = false;
+
+                do {
+                        char line[LINE_MAX];
+                        unsigned i;
+
+                        for (i = 0; i < sizeof(line)-1; i++) {
+                                int c;
+
+                                c = getc(f);
+                                if (_unlikely_(c == EOF)) {
+                                        done = true;
+                                        break;
+                                } else if (c == 0)
+                                        break;
+
+                                line[i] = c;
+                        }
+                        line[i] = 0;
+
+                        if (streq(line, "container=lxc")) {
+                                fclose(f);
+
+                                if (id)
+                                        *id = "lxc";
+                                return 1;
+
+                        } else if (streq(line, "container=lxc-libvirt")) {
+                                fclose(f);
+
+                                if (id)
+                                        *id = "lxc-libvirt";
+                                return 1;
+
+                        } else if (streq(line, "container=systemd-nspawn")) {
+                                fclose(f);
+
+                                if (id)
+                                        *id = "systemd-nspawn";
+                                return 1;
+
+                        } else if (startswith(line, "container=")) {
+                                fclose(f);
+
+                                if (id)
+                                        *id = "other";
+                                return 1;
+                        }
+
+                } while (!done);
+
+                fclose(f);
+        }
+
+        return 0;
+}
+
+/* Returns a short identifier for the various VM/container implementations */
+Virtualization detect_virtualization(const char **id) {
+
+        static __thread Virtualization cached_virt = _VIRTUALIZATION_INVALID;
+        static __thread const char *cached_id = NULL;
+
+        const char *_id;
+        int r;
+        Virtualization v;
+
+        if (_likely_(cached_virt >= 0)) {
+
+                if (id && cached_virt > 0)
+                        *id = cached_id;
+
+                return cached_virt;
+        }
+
+        r = detect_container(&_id);
+        if (r < 0) {
+                v = r;
+                goto finish;
+        } else if (r > 0) {
+                v = VIRTUALIZATION_CONTAINER;
+                goto finish;
+        }
+
+        r = detect_vm(&_id);
+        if (r < 0) {
+                v = r;
+                goto finish;
+        } else if (r > 0) {
+                v = VIRTUALIZATION_VM;
+                goto finish;
+        }
+
+        v = VIRTUALIZATION_NONE;
+
+finish:
+        if (v > 0) {
+                cached_id = _id;
+
+                if (id)
+                        *id = _id;
+        }
+
+        if (v >= 0)
+                cached_virt = v;
+
+        return v;
+}
diff --git a/src/virt.h b/src/virt.h
new file mode 100644 (file)
index 0000000..f55c9a6
--- /dev/null
@@ -0,0 +1,38 @@
+/*-*- Mode: C; c-basic-offset: 8; indent-tabs-mode: nil -*-*/
+
+#ifndef foovirthfoo
+#define foovirthfoo
+
+/***
+  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 <http://www.gnu.org/licenses/>.
+***/
+
+int detect_vm(const char **id);
+int detect_container(const char **id);
+
+typedef enum Virtualization {
+        VIRTUALIZATION_NONE = 0,
+        VIRTUALIZATION_VM,
+        VIRTUALIZATION_CONTAINER,
+        _VIRTUALIZATION_MAX,
+        _VIRTUALIZATION_INVALID = -1
+} Virtualization;
+
+Virtualization detect_virtualization(const char **id);
+
+#endif
diff --git a/sysctl.d/.gitignore b/sysctl.d/.gitignore
new file mode 100644 (file)
index 0000000..7563539
--- /dev/null
@@ -0,0 +1 @@
+/coredump.conf
diff --git a/sysctl.d/Makefile b/sysctl.d/Makefile
new file mode 120000 (symlink)
index 0000000..bd10475
--- /dev/null
@@ -0,0 +1 @@
+../src/Makefile
\ No newline at end of file
diff --git a/sysctl.d/coredump.conf.in b/sysctl.d/coredump.conf.in
new file mode 100644 (file)
index 0000000..ab19b1e
--- /dev/null
@@ -0,0 +1,10 @@
+#  This file is part of systemd.
+#
+#  systemd is free software; you can redistribute it and/or modify it
+#  under the terms of the GNU General Public License as published by
+#  the Free Software Foundation; either version 2 of the License, or
+#  (at your option) any later version.
+
+# See sysctl.d(5) for details
+
+kernel.core_pattern=|@rootlibexecdir@/systemd-coredump %p %u %g %s %t %e
diff --git a/test/Makefile b/test/Makefile
new file mode 100644 (file)
index 0000000..9aa46b4
--- /dev/null
@@ -0,0 +1,7 @@
+# Just a little hook script to easy building when in this directory
+
+all:
+       $(MAKE) -C ..
+
+clean:
+       $(MAKE) -C .. clean
diff --git a/test/a.service b/test/a.service
new file mode 100644 (file)
index 0000000..4168d2d
--- /dev/null
@@ -0,0 +1,7 @@
+[Unit]
+Description=A
+Requires=b.service
+Before=b.service
+
+[Service]
+ExecStart=/bin/true
diff --git a/test/b.service b/test/b.service
new file mode 100644 (file)
index 0000000..e03bae3
--- /dev/null
@@ -0,0 +1,6 @@
+[Unit]
+Description=B
+Wants=f.service
+
+[Service]
+ExecStart=/bin/true
diff --git a/test/c.service b/test/c.service
new file mode 100644 (file)
index 0000000..e2f60a8
--- /dev/null
@@ -0,0 +1,6 @@
+[Unit]
+Description=C
+Requires=a.service
+
+[Service]
+ExecStart=/bin/true
diff --git a/test/d.service b/test/d.service
new file mode 100644 (file)
index 0000000..921fd2e
--- /dev/null
@@ -0,0 +1,8 @@
+[Unit]
+Description=D:Cyclic
+After=b.service
+Before=a.service
+Requires=a.service
+
+[Service]
+ExecStart=/bin/true
diff --git a/test/e.service b/test/e.service
new file mode 100644 (file)
index 0000000..5ba98c7
--- /dev/null
@@ -0,0 +1,8 @@
+[Unit]
+Description=E:Cyclic
+After=b.service
+Before=a.service
+Wants=a.service
+
+[Service]
+ExecStart=/bin/true
diff --git a/test/f.service b/test/f.service
new file mode 100644 (file)
index 0000000..7dde681
--- /dev/null
@@ -0,0 +1,5 @@
+[Unit]
+Description=F
+
+[Service]
+ExecStart=/bin/true
diff --git a/test/g.service b/test/g.service
new file mode 100644 (file)
index 0000000..cbfa82a
--- /dev/null
@@ -0,0 +1,6 @@
+[Unit]
+Description=G
+Conflicts=e.service
+
+[Service]
+ExecStart=/bin/true
diff --git a/test/h.service b/test/h.service
new file mode 100644 (file)
index 0000000..74a7751
--- /dev/null
@@ -0,0 +1,6 @@
+[Unit]
+Description=H
+Wants=g.service
+
+[Service]
+ExecStart=/bin/true
diff --git a/tmpfiles.d/Makefile b/tmpfiles.d/Makefile
new file mode 120000 (symlink)
index 0000000..bd10475
--- /dev/null
@@ -0,0 +1 @@
+../src/Makefile
\ No newline at end of file
diff --git a/tmpfiles.d/legacy.conf b/tmpfiles.d/legacy.conf
new file mode 100644 (file)
index 0000000..9198e89
--- /dev/null
@@ -0,0 +1,22 @@
+#  This file is part of systemd.
+#
+#  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.
+
+# See tmpfiles.d(5) for details
+
+# These files are considered legacy and are unnecessary on legacy-free
+# systems. /run/lock/subsys is used for serializing SysV service
+# execution, and hence without use on SysV-less systems.
+#
+# /run/lock/lockdev is used to serialize access to tty devices via
+# LCK..xxx style lock files, For more information see:
+# http://lists.freedesktop.org/archives/systemd-devel/2011-March/001823.html
+# On modern systems a BSD file lock is a better choice if
+# serialization is needed on those devices.
+
+d /run/lock 0755 root root -
+d /run/lock/subsys 0755 root root -
+d /run/lock/lockdev 0775 root lock -
diff --git a/tmpfiles.d/systemd.conf b/tmpfiles.d/systemd.conf
new file mode 100644 (file)
index 0000000..be29c06
--- /dev/null
@@ -0,0 +1,25 @@
+#  This file is part of systemd.
+#
+#  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.
+
+# See tmpfiles.d(5) for details
+
+d /run/user 0755 root root 10d
+F /run/utmp 0664 root utmp -
+
+f /var/log/wtmp 0664 root utmp -
+f /var/log/btmp 0600 root utmp -
+
+d /var/cache/man - - - 30d
+
+r /forcefsck
+r /forcequotacheck
+r /fastboot
+
+d /run/systemd/ask-password 0755 root root -
+d /run/systemd/seats 0755 root root -
+d /run/systemd/sessions 0755 root root -
+d /run/systemd/users 0755 root root -
diff --git a/tmpfiles.d/tmp.conf b/tmpfiles.d/tmp.conf
new file mode 100644 (file)
index 0000000..8915b82
--- /dev/null
@@ -0,0 +1,12 @@
+#  This file is part of systemd.
+#
+#  systemd is free software; you can redistribute it and/or modify it
+#  under the terms of the GNU General Public License as published by
+#  the Free Software Foundation; either version 2 of the License, or
+#  (at your option) any later version.
+
+# See tmpfiles.d(5) for details
+
+# Clear tmp directories separately, to make them easier to override
+d /tmp 1777 root root 10d
+d /var/tmp 1777 root root 30d
diff --git a/tmpfiles.d/x11.conf b/tmpfiles.d/x11.conf
new file mode 100644 (file)
index 0000000..7f81af6
--- /dev/null
@@ -0,0 +1,18 @@
+#  This file is part of systemd.
+#
+#  systemd is free software; you can redistribute it and/or modify it
+#  under the terms of the GNU General Public License as published by
+#  the Free Software Foundation; either version 2 of the License, or
+#  (at your option) any later version.
+
+# See tmpfiles.d(5) for details
+
+# Make sure these are created by default so that nobody else can
+d /tmp/.X11-unix 1777 root root 10d
+d /tmp/.ICE-unix 1777 root root 10d
+d /tmp/.XIM-unix 1777 root root 10d
+d /tmp/.font-unix 1777 root root 10d
+d /tmp/.Test-unix 1777 root root 10d
+
+# Unlink the X11 lock files
+r /tmp/.X[0-9]*-lock
diff --git a/units/.gitignore b/units/.gitignore
new file mode 100644 (file)
index 0000000..94412d5
--- /dev/null
@@ -0,0 +1,42 @@
+/systemd-journald.service
+user@.service
+systemd-logind.service
+systemd-localed.service
+systemd-timedated.service
+systemd-hostnamed.service
+console-shell.service
+systemd-sysctl.service
+systemd-ask-password-console.service
+rescue.service
+systemd-ask-password-plymouth.service
+systemd-ask-password-wall.service
+quotacheck.service
+fsck@.service
+fsck-root.service
+systemd-tmpfiles-clean.service
+systemd-tmpfiles-setup.service
+halt.service
+poweroff.service
+reboot.service
+kexec.service
+systemd-user-sessions.service
+systemd-readahead-done.service
+systemd-tmpfiles.service
+systemd-readahead-collect.service
+systemd-readahead-replay.service
+serial-getty@.service
+systemd-kmsg-syslogd.service
+systemd-modules-load.service
+systemd-remount-api-vfs.service
+systemd-vconsole-setup.service
+systemd-auto-serial-getty.service
+systemd-shutdownd.service
+systemd-random-seed-load.service
+systemd-random-seed-save.service
+systemd-initctl.service
+systemd-stdout-syslog-bridge.service
+getty@.service
+systemd-update-utmp-runlevel.service
+systemd-update-utmp-shutdown.service
+test-env-replace
+systemd-binfmt.service
diff --git a/units/Makefile b/units/Makefile
new file mode 120000 (symlink)
index 0000000..bd10475
--- /dev/null
@@ -0,0 +1 @@
+../src/Makefile
\ No newline at end of file
diff --git a/units/basic.target b/units/basic.target
new file mode 100644 (file)
index 0000000..0258ca0
--- /dev/null
@@ -0,0 +1,14 @@
+#  This file is part of systemd.
+#
+#  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.
+
+# See systemd.special(7) for details
+
+[Unit]
+Description=Basic System
+Requires=sysinit.target sockets.target
+After=sysinit.target sockets.target
+RefuseManualStart=yes
diff --git a/units/bluetooth.target b/units/bluetooth.target
new file mode 100644 (file)
index 0000000..c66718e
--- /dev/null
@@ -0,0 +1,12 @@
+#  This file is part of systemd.
+#
+#  systemd is free software; you can redistribute it and/or modify it
+#  under the terms of the GNU General Public License as published by
+#  the Free Software Foundation; either version 2 of the License, or
+#  (at your option) any later version.
+
+# See systemd.special(7) for details
+
+[Unit]
+Description=Bluetooth
+StopWhenUnneeded=yes
diff --git a/units/console-shell.service.m4 b/units/console-shell.service.m4
new file mode 100644 (file)
index 0000000..fef9e1b
--- /dev/null
@@ -0,0 +1,47 @@
+#  This file is part of systemd.
+#
+#  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.
+
+[Unit]
+Description=Console Shell
+After=systemd-user-sessions.service plymouth-quit-wait.service
+m4_ifdef(`TARGET_FEDORA',
+After=rc-local.service
+)m4_dnl
+m4_ifdef(`TARGET_ARCH',
+After=rc-local.service
+)m4_dnl
+m4_ifdef(`TARGET_FRUGALWARE',
+After=local.service
+)m4_dnl
+m4_ifdef(`TARGET_ALTLINUX',
+After=rc-local.service
+)m4_dnl
+m4_ifdef(`TARGET_MANDRIVA',
+After=rc-local.service
+)m4_dnl
+m4_ifdef(`TARGET_MAGEIA',
+After=rc-local.service
+)m4_dnl
+Before=getty.target
+
+[Service]
+Environment=HOME=/root
+WorkingDirectory=/root
+ExecStart=-/sbin/sulogin
+ExecStopPost=-/bin/systemctl poweroff
+StandardInput=tty-force
+StandardOutput=inherit
+StandardError=inherit
+KillMode=process
+IgnoreSIGPIPE=no
+
+# Bash ignores SIGTERM, so we send SIGHUP instead, to ensure that bash
+# terminates cleanly.
+KillSignal=SIGHUP
+
+[Install]
+WantedBy=getty.target
diff --git a/units/cryptsetup.target b/units/cryptsetup.target
new file mode 100644 (file)
index 0000000..64ee8c6
--- /dev/null
@@ -0,0 +1,11 @@
+#  This file is part of systemd.
+#
+#  systemd is free software; you can redistribute it and/or modify it
+#  under the terms of the GNU General Public License as published by
+#  the Free Software Foundation; either version 2 of the License, or
+#  (at your option) any later version.
+
+# See systemd.special(7) for details
+
+[Unit]
+Description=Encrypted Volumes
diff --git a/units/dev-hugepages.mount b/units/dev-hugepages.mount
new file mode 100644 (file)
index 0000000..72a522e
--- /dev/null
@@ -0,0 +1,17 @@
+#  This file is part of systemd.
+#
+#  systemd is free software; you can redistribute it and/or modify it
+#  under the terms of the GNU General Public License as published by
+#  the Free Software Foundation; either version 2 of the License, or
+#  (at your option) any later version.
+
+[Unit]
+Description=Huge Pages File System
+DefaultDependencies=no
+Before=sysinit.target
+ConditionPathExists=/sys/kernel/mm/hugepages
+
+[Mount]
+What=hugetlbfs
+Where=/dev/hugepages
+Type=hugetlbfs
diff --git a/units/dev-mqueue.mount b/units/dev-mqueue.mount
new file mode 100644 (file)
index 0000000..cffdaf7
--- /dev/null
@@ -0,0 +1,17 @@
+#  This file is part of systemd.
+#
+#  systemd is free software; you can redistribute it and/or modify it
+#  under the terms of the GNU General Public License as published by
+#  the Free Software Foundation; either version 2 of the License, or
+#  (at your option) any later version.
+
+[Unit]
+Description=POSIX Message Queue File System
+DefaultDependencies=no
+Before=sysinit.target
+ConditionPathExists=/proc/sys/fs/mqueue
+
+[Mount]
+What=mqueue
+Where=/dev/mqueue
+Type=mqueue
diff --git a/units/emergency.service b/units/emergency.service
new file mode 100644 (file)
index 0000000..43a74d7
--- /dev/null
@@ -0,0 +1,31 @@
+#  This file is part of systemd.
+#
+#  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.
+
+# See systemd.special(7) for details
+
+[Unit]
+Description=Emergency Shell
+DefaultDependencies=no
+Conflicts=shutdown.target
+Before=shutdown.target
+
+[Service]
+Environment=HOME=/root
+WorkingDirectory=/root
+ExecStartPre=-/bin/plymouth quit
+ExecStartPre=-/bin/echo 'Welcome to emergency mode. Use "systemctl default" or ^D to enter default mode.'
+ExecStart=-/sbin/sulogin
+ExecStopPost=/bin/systemctl --fail --no-block default
+StandardInput=tty-force
+StandardOutput=inherit
+StandardError=inherit
+KillMode=process
+IgnoreSIGPIPE=no
+
+# Bash ignores SIGTERM, so we send SIGHUP instead, to ensure that bash
+# terminates cleanly.
+KillSignal=SIGHUP
diff --git a/units/emergency.target b/units/emergency.target
new file mode 100644 (file)
index 0000000..6a99e05
--- /dev/null
@@ -0,0 +1,14 @@
+#  This file is part of systemd.
+#
+#  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.
+
+# See systemd.special(7) for details
+
+[Unit]
+Description=Emergency Mode
+Requires=emergency.service
+After=emergency.service
+AllowIsolate=yes
diff --git a/units/fedora/Makefile b/units/fedora/Makefile
new file mode 120000 (symlink)
index 0000000..50be211
--- /dev/null
@@ -0,0 +1 @@
+../../src/Makefile
\ No newline at end of file
diff --git a/units/fedora/halt-local.service b/units/fedora/halt-local.service
new file mode 100644 (file)
index 0000000..a9f6feb
--- /dev/null
@@ -0,0 +1,20 @@
+#  This file is part of systemd.
+#
+#  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.
+
+[Unit]
+Description=/sbin/halt.local Compatibility
+ConditionFileIsExecutable=/sbin/halt.local
+DefaultDependencies=no
+After=shutdown.target
+Before=final.target
+
+[Service]
+Type=oneshot
+ExecStart=/sbin/halt.local
+TimeoutSec=0
+StandardOutput=tty
+RemainAfterExit=yes
diff --git a/units/fedora/prefdm.service b/units/fedora/prefdm.service
new file mode 100644 (file)
index 0000000..77a0e9a
--- /dev/null
@@ -0,0 +1,21 @@
+#  This file is part of systemd.
+#
+#  systemd is free software; you can redistribute it and/or modify it
+#  under the terms of the GNU General Public License as published by
+#  the Free Software Foundation; either version 2 of the License, or
+#  (at your option) any later version.
+
+[Unit]
+Description=Display Manager
+After=livesys-late.service rc-local.service systemd-user-sessions.service
+
+# On Fedora gdm/X11 is on tty1. We explicitly cancel the getty here to
+# avoid any races around that.
+Conflicts=getty@tty1.service plymouth-quit.service
+After=getty@tty1.service plymouth-quit.service
+
+[Service]
+ExecStart=/etc/X11/prefdm -nodaemon
+Restart=always
+RestartSec=0
+IgnoreSIGPIPE=no
diff --git a/units/fedora/rc-local.service b/units/fedora/rc-local.service
new file mode 100644 (file)
index 0000000..0bef5c7
--- /dev/null
@@ -0,0 +1,19 @@
+#  This file is part of systemd.
+#
+#  systemd is free software; you can redistribute it and/or modify it
+#  under the terms of the GNU General Public License as published by
+#  the Free Software Foundation; either version 2 of the License, or
+#  (at your option) any later version.
+
+# This unit gets pulled automatically into multi-user.target by
+# systemd-rc-local-generator if /etc/rc.d/rc.local is executable.
+[Unit]
+Description=/etc/rc.d/rc.local Compatibility
+After=network.target
+
+[Service]
+Type=forking
+ExecStart=/etc/rc.d/rc.local start
+TimeoutSec=0
+RemainAfterExit=yes
+SysVStartPriority=99
diff --git a/units/final.target b/units/final.target
new file mode 100644 (file)
index 0000000..9cfda19
--- /dev/null
@@ -0,0 +1,14 @@
+#  This file is part of systemd.
+#
+#  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.
+
+# See systemd.special(7) for details
+
+[Unit]
+Description=Final Step
+DefaultDependencies=no
+RefuseManualStart=yes
+After=shutdown.target umount.target
diff --git a/units/frugalware/display-manager.service b/units/frugalware/display-manager.service
new file mode 100644 (file)
index 0000000..2376e19
--- /dev/null
@@ -0,0 +1,16 @@
+#  This file is part of systemd.
+#
+#  systemd is free software; you can redistribute it and/or modify it
+#  under the terms of the GNU General Public License as published by
+#  the Free Software Foundation; either version 2 of the License, or
+#  (at your option) any later version.
+
+[Unit]
+Description=Display Manager
+After=local.service systemd-user-sessions.service
+
+[Service]
+EnvironmentFile=/etc/sysconfig/desktop
+ExecStart=/bin/bash -c "exec ${desktop}"
+Restart=always
+RestartSec=0
diff --git a/units/fsck-root.service.in b/units/fsck-root.service.in
new file mode 100644 (file)
index 0000000..4086149
--- /dev/null
@@ -0,0 +1,23 @@
+#  This file is part of systemd.
+#
+#  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.
+
+[Unit]
+Description=File System Check on Root Device
+DefaultDependencies=no
+After=systemd-readahead-collect.service systemd-readahead-replay.service
+Before=local-fs.target shutdown.target
+
+# Dracut informs us with this flag file if the root fsck was already run
+ConditionPathExists=!/run/initramfs/root-fsck
+
+[Service]
+Type=oneshot
+RemainAfterExit=no
+ExecStart=@rootlibexecdir@/systemd-fsck
+StandardOutput=journal+console
+FsckPassNo=1
+TimeoutSec=0
diff --git a/units/fsck@.service.in b/units/fsck@.service.in
new file mode 100644 (file)
index 0000000..c06684b
--- /dev/null
@@ -0,0 +1,20 @@
+#  This file is part of systemd.
+#
+#  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.
+
+[Unit]
+Description=File System Check on %f
+DefaultDependencies=no
+BindTo=%i.device
+After=systemd-readahead-collect.service systemd-readahead-replay.service %i.device
+Before=shutdown.target
+
+[Service]
+Type=oneshot
+RemainAfterExit=no
+ExecStart=@rootlibexecdir@/systemd-fsck %f
+StandardOutput=journal+console
+TimeoutSec=0
diff --git a/units/getty.target b/units/getty.target
new file mode 100644 (file)
index 0000000..e4435dc
--- /dev/null
@@ -0,0 +1,9 @@
+#  This file is part of systemd.
+#
+#  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.
+
+[Unit]
+Description=Login Prompts
diff --git a/units/getty@.service.m4 b/units/getty@.service.m4
new file mode 100644 (file)
index 0000000..a02838d
--- /dev/null
@@ -0,0 +1,58 @@
+#  This file is part of systemd.
+#
+#  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.
+
+[Unit]
+Description=Getty on %I
+BindTo=dev-%i.device
+After=dev-%i.device systemd-user-sessions.service plymouth-quit-wait.service
+m4_ifdef(`TARGET_FEDORA',
+After=rc-local.service
+)m4_dnl
+m4_ifdef(`TARGET_ARCH',
+After=rc-local.service
+)m4_dnl
+m4_ifdef(`TARGET_FRUGALWARE',
+After=local.service
+)m4_dnl
+m4_ifdef(`TARGET_ALTLINUX',
+After=rc-local.service
+)m4_dnl
+m4_ifdef(`TARGET_MANDRIVA',
+After=rc-local.service
+)m4_dnl
+m4_ifdef(`TARGET_MAGEIA',
+After=rc-local.service
+)m4_dnl
+
+# If additional gettys are spawned during boot then we should make
+# sure that this is synchronized before getty.target, even though
+# getty.target didn't actually pull it in.
+Before=getty.target
+
+[Service]
+Environment=TERM=linux
+ExecStart=-/sbin/agetty %I 38400
+Restart=always
+RestartSec=0
+UtmpIdentifier=%I
+TTYPath=/dev/%I
+TTYReset=yes
+TTYVHangup=yes
+TTYVTDisallocate=yes
+KillMode=process
+IgnoreSIGPIPE=no
+
+# Unset locale for the console getty since the console has problems
+# displaying some internationalized messages.
+Environment=LANG= LANGUAGE= LC_CTYPE= LC_NUMERIC= LC_TIME= LC_COLLATE= LC_MONETARY= LC_MESSAGES= LC_PAPER= LC_NAME= LC_ADDRESS= LC_TELEPHONE= LC_MEASUREMENT= LC_IDENTIFICATION=
+
+# Some login implementations ignore SIGTERM, so we send SIGHUP
+# instead, to ensure that login terminates cleanly.
+KillSignal=SIGHUP
+
+[Install]
+Alias=getty.target.wants/getty@tty1.service
diff --git a/units/graphical.target b/units/graphical.target
new file mode 100644 (file)
index 0000000..f2e3034
--- /dev/null
@@ -0,0 +1,18 @@
+#  This file is part of systemd.
+#
+#  systemd is free software; you can redistribute it and/or modify it
+#  under the terms of the GNU General Public License as published by
+#  the Free Software Foundation; either version 2 of the License, or
+#  (at your option) any later version.
+
+# See systemd.special(7) for details
+
+[Unit]
+Description=Graphical Interface
+Requires=multi-user.target
+After=multi-user.target
+Conflicts=rescue.target
+AllowIsolate=yes
+
+[Install]
+Alias=default.target
diff --git a/units/halt.service.in b/units/halt.service.in
new file mode 100644 (file)
index 0000000..42e3470
--- /dev/null
@@ -0,0 +1,16 @@
+#  This file is part of systemd.
+#
+#  systemd is free software; you can redistribute it and/or modify it
+#  under the terms of the GNU General Public License as published by
+#  the Free Software Foundation; either version 2 of the License, or
+#  (at your option) any later version.
+
+[Unit]
+Description=Halt
+DefaultDependencies=no
+Requires=shutdown.target umount.target final.target
+After=shutdown.target umount.target final.target
+
+[Service]
+Type=oneshot
+ExecStart=@SYSTEMCTL@ --force halt
diff --git a/units/halt.target b/units/halt.target
new file mode 100644 (file)
index 0000000..04b42cd
--- /dev/null
@@ -0,0 +1,18 @@
+#  This file is part of systemd.
+#
+#  systemd is free software; you can redistribute it and/or modify it
+#  under the terms of the GNU General Public License as published by
+#  the Free Software Foundation; either version 2 of the License, or
+#  (at your option) any later version.
+
+# See systemd.special(7) for details
+
+[Unit]
+Description=Halt
+DefaultDependencies=no
+Requires=halt.service
+After=halt.service
+AllowIsolate=yes
+
+[Install]
+Alias=ctrl-alt-del.target
diff --git a/units/http-daemon.target b/units/http-daemon.target
new file mode 100644 (file)
index 0000000..45f1018
--- /dev/null
@@ -0,0 +1,14 @@
+#  This file is part of systemd.
+#
+#  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.
+
+# See systemd.special(7) for details
+
+# This exists mostly for compatibility with SysV/LSB units, and
+# implementations lacking socket/bus activation.
+
+[Unit]
+Description=Web Server
diff --git a/units/kexec.service.in b/units/kexec.service.in
new file mode 100644 (file)
index 0000000..cf6bd65
--- /dev/null
@@ -0,0 +1,16 @@
+#  This file is part of systemd.
+#
+#  systemd is free software; you can redistribute it and/or modify it
+#  under the terms of the GNU General Public License as published by
+#  the Free Software Foundation; either version 2 of the License, or
+#  (at your option) any later version.
+
+[Unit]
+Description=Reboot via kexec
+DefaultDependencies=no
+Requires=shutdown.target umount.target final.target
+After=shutdown.target umount.target final.target
+
+[Service]
+Type=oneshot
+ExecStart=@SYSTEMCTL@ --force kexec
diff --git a/units/kexec.target b/units/kexec.target
new file mode 100644 (file)
index 0000000..b77e6a4
--- /dev/null
@@ -0,0 +1,18 @@
+#  This file is part of systemd.
+#
+#  systemd is free software; you can redistribute it and/or modify it
+#  under the terms of the GNU General Public License as published by
+#  the Free Software Foundation; either version 2 of the License, or
+#  (at your option) any later version.
+
+# See systemd.special(7) for details
+
+[Unit]
+Description=Reboot via kexec
+DefaultDependencies=no
+Requires=kexec.service
+After=kexec.service
+AllowIsolate=yes
+
+[Install]
+Alias=ctrl-alt-del.target
diff --git a/units/local-fs-pre.target b/units/local-fs-pre.target
new file mode 100644 (file)
index 0000000..11e67ba
--- /dev/null
@@ -0,0 +1,11 @@
+#  This file is part of systemd.
+#
+#  systemd is free software; you can redistribute it and/or modify it
+#  under the terms of the GNU General Public License as published by
+#  the Free Software Foundation; either version 2 of the License, or
+#  (at your option) any later version.
+
+# See systemd.special(7) for details
+
+[Unit]
+Description=Local File Systems (Pre)
diff --git a/units/local-fs.target b/units/local-fs.target
new file mode 100644 (file)
index 0000000..1886f74
--- /dev/null
@@ -0,0 +1,13 @@
+#  This file is part of systemd.
+#
+#  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.
+
+# See systemd.special(7) for details
+
+[Unit]
+Description=Local File Systems
+OnFailure=emergency.target
+OnFailureIsolate=yes
diff --git a/units/mageia/prefdm.service b/units/mageia/prefdm.service
new file mode 100644 (file)
index 0000000..4a896bf
--- /dev/null
@@ -0,0 +1,21 @@
+#  This file is part of systemd.
+#
+#  systemd is free software; you can redistribute it and/or modify it
+#  under the terms of the GNU General Public License as published by
+#  the Free Software Foundation; either version 2 of the License, or
+#  (at your option) any later version.
+
+[Unit]
+Description=Display Manager
+After=livesys-late.service rc-local.service systemd-user-sessions.service
+After=network.target acpid.service fs.service haldaemon.service
+
+# Do not stop plymouth, it is done in prefdm if required
+Conflicts=plymouth-quit.service
+After=plymouth-quit.service
+
+[Service]
+ExecStart=/etc/X11/prefdm
+Type=forking
+Restart=always
+RestartSec=0
diff --git a/units/mail-transfer-agent.target b/units/mail-transfer-agent.target
new file mode 100644 (file)
index 0000000..ebb1ea1
--- /dev/null
@@ -0,0 +1,14 @@
+#  This file is part of systemd.
+#
+#  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.
+
+# See systemd.special(7) for details
+
+# This exists mostly for compatibility with SysV/LSB units, and
+# implementations lacking socket/bus activation.
+
+[Unit]
+Description=Mail Transfer Agent
diff --git a/units/mandriva/prefdm.service b/units/mandriva/prefdm.service
new file mode 100644 (file)
index 0000000..4a896bf
--- /dev/null
@@ -0,0 +1,21 @@
+#  This file is part of systemd.
+#
+#  systemd is free software; you can redistribute it and/or modify it
+#  under the terms of the GNU General Public License as published by
+#  the Free Software Foundation; either version 2 of the License, or
+#  (at your option) any later version.
+
+[Unit]
+Description=Display Manager
+After=livesys-late.service rc-local.service systemd-user-sessions.service
+After=network.target acpid.service fs.service haldaemon.service
+
+# Do not stop plymouth, it is done in prefdm if required
+Conflicts=plymouth-quit.service
+After=plymouth-quit.service
+
+[Service]
+ExecStart=/etc/X11/prefdm
+Type=forking
+Restart=always
+RestartSec=0
diff --git a/units/multi-user.target b/units/multi-user.target
new file mode 100644 (file)
index 0000000..66f1a95
--- /dev/null
@@ -0,0 +1,18 @@
+#  This file is part of systemd.
+#
+#  systemd is free software; you can redistribute it and/or modify it
+#  under the terms of the GNU General Public License as published by
+#  the Free Software Foundation; either version 2 of the License, or
+#  (at your option) any later version.
+
+# See systemd.special(7) for details
+
+[Unit]
+Description=Multi-User
+Requires=basic.target
+Conflicts=rescue.service rescue.target
+After=basic.target rescue.service rescue.target
+AllowIsolate=yes
+
+[Install]
+Alias=default.target
diff --git a/units/network.target b/units/network.target
new file mode 100644 (file)
index 0000000..d97f64f
--- /dev/null
@@ -0,0 +1,11 @@
+#  This file is part of systemd.
+#
+#  systemd is free software; you can redistribute it and/or modify it
+#  under the terms of the GNU General Public License as published by
+#  the Free Software Foundation; either version 2 of the License, or
+#  (at your option) any later version.
+
+# See systemd.special(7) for details
+
+[Unit]
+Description=Network
diff --git a/units/nss-lookup.target b/units/nss-lookup.target
new file mode 100644 (file)
index 0000000..bdca03c
--- /dev/null
@@ -0,0 +1,15 @@
+#  This file is part of systemd.
+#
+#  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.
+
+# See systemd.special(7) for details
+
+# This exists mostly for compatibility with SysV/LSB units, and
+# implementations lacking socket/bus activation.
+
+[Unit]
+Description=Name Lookups
+After=network.target
diff --git a/units/plymouth-halt.service b/units/plymouth-halt.service
new file mode 100644 (file)
index 0000000..2e194b3
--- /dev/null
@@ -0,0 +1,18 @@
+#  This file is part of systemd.
+#
+#  systemd is free software; you can redistribute it and/or modify it
+#  under the terms of the GNU General Public License as published by
+#  the Free Software Foundation; either version 2 of the License, or
+#  (at your option) any later version.
+
+[Unit]
+Description=Show Plymouth Halt Screen
+After=getty@tty1.service prefdm.service plymouth-start.service
+Before=halt.service
+DefaultDependencies=no
+ConditionKernelCommandLine=!plymouth.enable=0
+
+[Service]
+ExecStart=/sbin/plymouthd --mode=shutdown
+ExecStartPost=-/bin/plymouth --show-splash
+Type=forking
diff --git a/units/plymouth-kexec.service b/units/plymouth-kexec.service
new file mode 100644 (file)
index 0000000..919c3f1
--- /dev/null
@@ -0,0 +1,18 @@
+#  This file is part of systemd.
+#
+#  systemd is free software; you can redistribute it and/or modify it
+#  under the terms of the GNU General Public License as published by
+#  the Free Software Foundation; either version 2 of the License, or
+#  (at your option) any later version.
+
+[Unit]
+Description=Show Plymouth Reboot with kexec Screen
+After=getty@tty1.service prefdm.service plymouth-start.service
+Before=kexec.service
+DefaultDependencies=no
+ConditionKernelCommandLine=!plymouth.enable=0
+
+[Service]
+ExecStart=/sbin/plymouthd --mode=shutdown
+ExecStartPost=-/bin/plymouth --show-splash
+Type=forking
diff --git a/units/plymouth-poweroff.service b/units/plymouth-poweroff.service
new file mode 100644 (file)
index 0000000..8fcff3b
--- /dev/null
@@ -0,0 +1,18 @@
+#  This file is part of systemd.
+#
+#  systemd is free software; you can redistribute it and/or modify it
+#  under the terms of the GNU General Public License as published by
+#  the Free Software Foundation; either version 2 of the License, or
+#  (at your option) any later version.
+
+[Unit]
+Description=Show Plymouth Power Off Screen
+After=getty@tty1.service prefdm.service plymouth-start.service
+Before=poweroff.service
+DefaultDependencies=no
+ConditionKernelCommandLine=!plymouth.enable=0
+
+[Service]
+ExecStart=/sbin/plymouthd --mode=shutdown
+ExecStartPost=-/bin/plymouth --show-splash
+Type=forking
diff --git a/units/plymouth-quit-wait.service b/units/plymouth-quit-wait.service
new file mode 100644 (file)
index 0000000..45c67bd
--- /dev/null
@@ -0,0 +1,15 @@
+#  This file is part of systemd.
+#
+#  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.
+
+[Unit]
+Description=Wait for Plymouth Boot Screen to Quit
+After=rc-local.service plymouth-start.service
+
+[Service]
+ExecStart=-/bin/plymouth --wait
+Type=oneshot
+TimeoutSec=20
diff --git a/units/plymouth-quit.service b/units/plymouth-quit.service
new file mode 100644 (file)
index 0000000..164499a
--- /dev/null
@@ -0,0 +1,15 @@
+#  This file is part of systemd.
+#
+#  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.
+
+[Unit]
+Description=Terminate Plymouth Boot Screen
+After=rc-local.service plymouth-start.service
+
+[Service]
+ExecStart=-/bin/plymouth quit
+Type=oneshot
+TimeoutSec=20
diff --git a/units/plymouth-read-write.service b/units/plymouth-read-write.service
new file mode 100644 (file)
index 0000000..09fbf7d
--- /dev/null
@@ -0,0 +1,16 @@
+#  This file is part of systemd.
+#
+#  systemd is free software; you can redistribute it and/or modify it
+#  under the terms of the GNU General Public License as published by
+#  the Free Software Foundation; either version 2 of the License, or
+#  (at your option) any later version.
+
+[Unit]
+Description=Tell Plymouth To Write Out Runtime Data
+DefaultDependencies=no
+After=local-fs.target
+Before=sysinit.target
+
+[Service]
+ExecStart=-/bin/plymouth update-root-fs --read-write
+Type=oneshot
diff --git a/units/plymouth-reboot.service b/units/plymouth-reboot.service
new file mode 100644 (file)
index 0000000..fb65bcc
--- /dev/null
@@ -0,0 +1,18 @@
+#  This file is part of systemd.
+#
+#  systemd is free software; you can redistribute it and/or modify it
+#  under the terms of the GNU General Public License as published by
+#  the Free Software Foundation; either version 2 of the License, or
+#  (at your option) any later version.
+
+[Unit]
+Description=Show Plymouth Reboot Screen
+After=getty@tty1.service prefdm.service plymouth-start.service
+Before=reboot.service
+DefaultDependencies=no
+ConditionKernelCommandLine=!plymouth.enable=0
+
+[Service]
+ExecStart=/sbin/plymouthd --mode=shutdown
+ExecStartPost=-/bin/plymouth --show-splash
+Type=forking
diff --git a/units/plymouth-start.service b/units/plymouth-start.service
new file mode 100644 (file)
index 0000000..f618257
--- /dev/null
@@ -0,0 +1,22 @@
+#  This file is part of systemd.
+#
+#  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.
+
+[Unit]
+Description=Show Plymouth Boot Screen
+DefaultDependencies=no
+Wants=systemd-ask-password-plymouth.path
+After=systemd-vconsole-setup.service udev-settle.service
+Before=systemd-ask-password-plymouth.service
+
+# Dracut informs us with this flag file if plymouth is already running
+ConditionPathExists=!/run/plymouth/pid
+ConditionKernelCommandLine=!plymouth.enable=0
+
+[Service]
+ExecStart=/sbin/plymouthd --mode=boot --pid-file=/run/plymouth/pid
+ExecStartPost=-/bin/plymouth --show-splash
+Type=forking
diff --git a/units/poweroff.service.in b/units/poweroff.service.in
new file mode 100644 (file)
index 0000000..124a4c0
--- /dev/null
@@ -0,0 +1,16 @@
+#  This file is part of systemd.
+#
+#  systemd is free software; you can redistribute it and/or modify it
+#  under the terms of the GNU General Public License as published by
+#  the Free Software Foundation; either version 2 of the License, or
+#  (at your option) any later version.
+
+[Unit]
+Description=Power-Off
+DefaultDependencies=no
+Requires=shutdown.target umount.target final.target
+After=shutdown.target umount.target final.target
+
+[Service]
+Type=oneshot
+ExecStart=@SYSTEMCTL@ --force poweroff
diff --git a/units/poweroff.target b/units/poweroff.target
new file mode 100644 (file)
index 0000000..d2ccf4b
--- /dev/null
@@ -0,0 +1,18 @@
+#  This file is part of systemd.
+#
+#  systemd is free software; you can redistribute it and/or modify it
+#  under the terms of the GNU General Public License as published by
+#  the Free Software Foundation; either version 2 of the License, or
+#  (at your option) any later version.
+
+# See systemd.special(7) for details
+
+[Unit]
+Description=Power-Off
+DefaultDependencies=no
+Requires=poweroff.service
+After=poweroff.service
+AllowIsolate=yes
+
+[Install]
+Alias=ctrl-alt-del.target
diff --git a/units/printer.target b/units/printer.target
new file mode 100644 (file)
index 0000000..14c90ff
--- /dev/null
@@ -0,0 +1,12 @@
+#  This file is part of systemd.
+#
+#  systemd is free software; you can redistribute it and/or modify it
+#  under the terms of the GNU General Public License as published by
+#  the Free Software Foundation; either version 2 of the License, or
+#  (at your option) any later version.
+
+# See systemd.special(7) for details
+
+[Unit]
+Description=Printer
+StopWhenUnneeded=yes
diff --git a/units/proc-sys-fs-binfmt_misc.automount b/units/proc-sys-fs-binfmt_misc.automount
new file mode 100644 (file)
index 0000000..acbbcbb
--- /dev/null
@@ -0,0 +1,15 @@
+#  This file is part of systemd.
+#
+#  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.
+
+[Unit]
+Description=Arbitrary Executable File Formats File System Automount Point
+DefaultDependencies=no
+Before=sysinit.target
+ConditionPathExists=/proc/sys/fs/binfmt_misc
+
+[Automount]
+Where=/proc/sys/fs/binfmt_misc
diff --git a/units/proc-sys-fs-binfmt_misc.mount b/units/proc-sys-fs-binfmt_misc.mount
new file mode 100644 (file)
index 0000000..1829c21
--- /dev/null
@@ -0,0 +1,15 @@
+#  This file is part of systemd.
+#
+#  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.
+
+[Unit]
+Description=Arbitrary Executable File Formats File System
+DefaultDependencies=no
+
+[Mount]
+What=binfmt_misc
+Where=/proc/sys/fs/binfmt_misc
+Type=binfmt_misc
diff --git a/units/quotacheck.service.in b/units/quotacheck.service.in
new file mode 100644 (file)
index 0000000..c97b7a4
--- /dev/null
@@ -0,0 +1,19 @@
+#  This file is part of systemd.
+#
+#  systemd is free software; you can redistribute it and/or modify it
+#  under the terms of the GNU General Public License as published by
+#  the Free Software Foundation; either version 2 of the License, or
+#  (at your option) any later version.
+
+[Unit]
+Description=File System Quota Check
+DefaultDependencies=no
+After=systemd-readahead-collect.service systemd-readahead-replay.service remount-rootfs.service
+Before=local-fs.target shutdown.target
+ConditionPathExists=/sbin/quotacheck
+
+[Service]
+Type=oneshot
+RemainAfterExit=yes
+ExecStart=@rootlibexecdir@/systemd-quotacheck
+TimeoutSec=0
diff --git a/units/quotaon.service b/units/quotaon.service
new file mode 100644 (file)
index 0000000..ef2fc8c
--- /dev/null
@@ -0,0 +1,18 @@
+#  This file is part of systemd.
+#
+#  systemd is free software; you can redistribute it and/or modify it
+#  under the terms of the GNU General Public License as published by
+#  the Free Software Foundation; either version 2 of the License, or
+#  (at your option) any later version.
+
+[Unit]
+Description=Enable File System Quotas
+DefaultDependencies=no
+After=systemd-readahead-collect.service systemd-readahead-replay.service quotacheck.service
+Before=local-fs.target shutdown.target
+ConditionPathExists=/sbin/quotaon
+
+[Service]
+Type=oneshot
+RemainAfterExit=yes
+ExecStart=/sbin/quotaon -aug
diff --git a/units/reboot.service.in b/units/reboot.service.in
new file mode 100644 (file)
index 0000000..f320fd8
--- /dev/null
@@ -0,0 +1,16 @@
+#  This file is part of systemd.
+#
+#  systemd is free software; you can redistribute it and/or modify it
+#  under the terms of the GNU General Public License as published by
+#  the Free Software Foundation; either version 2 of the License, or
+#  (at your option) any later version.
+
+[Unit]
+Description=Reboot
+DefaultDependencies=no
+Requires=shutdown.target umount.target final.target
+After=shutdown.target umount.target final.target
+
+[Service]
+Type=oneshot
+ExecStart=@SYSTEMCTL@ --force reboot
diff --git a/units/reboot.target b/units/reboot.target
new file mode 100644 (file)
index 0000000..41e133c
--- /dev/null
@@ -0,0 +1,18 @@
+#  This file is part of systemd.
+#
+#  systemd is free software; you can redistribute it and/or modify it
+#  under the terms of the GNU General Public License as published by
+#  the Free Software Foundation; either version 2 of the License, or
+#  (at your option) any later version.
+
+# See systemd.special(7) for details
+
+[Unit]
+Description=Reboot
+DefaultDependencies=no
+Requires=reboot.service
+After=reboot.service
+AllowIsolate=yes
+
+[Install]
+Alias=ctrl-alt-del.target
diff --git a/units/remote-fs-pre.target b/units/remote-fs-pre.target
new file mode 100644 (file)
index 0000000..8aceb08
--- /dev/null
@@ -0,0 +1,12 @@
+#  This file is part of systemd.
+#
+#  systemd is free software; you can redistribute it and/or modify it
+#  under the terms of the GNU General Public License as published by
+#  the Free Software Foundation; either version 2 of the License, or
+#  (at your option) any later version.
+
+# See systemd.special(7) for details
+
+[Unit]
+Description=Remote File Systems (Pre)
+After=network.target
diff --git a/units/remote-fs.target b/units/remote-fs.target
new file mode 100644 (file)
index 0000000..a48f87e
--- /dev/null
@@ -0,0 +1,14 @@
+#  This file is part of systemd.
+#
+#  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.
+
+# See systemd.special(7) for details
+
+[Unit]
+Description=Remote File Systems
+
+[Install]
+WantedBy=multi-user.target
diff --git a/units/remount-rootfs.service b/units/remount-rootfs.service
new file mode 100644 (file)
index 0000000..7b63752
--- /dev/null
@@ -0,0 +1,19 @@
+#  This file is part of systemd.
+#
+#  systemd is free software; you can redistribute it and/or modify it
+#  under the terms of the GNU General Public License as published by
+#  the Free Software Foundation; either version 2 of the License, or
+#  (at your option) any later version.
+
+[Unit]
+Description=Remount Root FS
+DefaultDependencies=no
+Conflicts=shutdown.target
+After=systemd-readahead-collect.service systemd-readahead-replay.service fsck-root.service
+Before=local-fs-pre.target local-fs.target shutdown.target
+Wants=local-fs-pre.target
+
+[Service]
+Type=oneshot
+RemainAfterExit=yes
+ExecStart=/bin/mount / -o remount
diff --git a/units/rescue.service.m4 b/units/rescue.service.m4
new file mode 100644 (file)
index 0000000..310bbce
--- /dev/null
@@ -0,0 +1,43 @@
+#  This file is part of systemd.
+#
+#  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.
+
+# See systemd.special(7) for details
+
+[Unit]
+Description=Rescue Shell
+DefaultDependencies=no
+Conflicts=shutdown.target
+After=basic.target plymouth-start.service
+Before=shutdown.target
+
+[Service]
+Environment=HOME=/root
+WorkingDirectory=/root
+ExecStartPre=-/bin/plymouth quit
+ExecStartPre=-/bin/echo 'Welcome to rescue mode. Use "systemctl default" or ^D to enter default mode.'
+m4_ifdef(`TARGET_FEDORA',
+`EnvironmentFile=/etc/sysconfig/init
+ExecStart=-/bin/bash -c "exec ${SINGLE}"',
+m4_ifdef(`TARGET_MANDRIVA',
+`EnvironmentFile=/etc/sysconfig/init
+ExecStart=-/bin/bash -c "exec ${SINGLE}"',
+m4_ifdef(`TARGET_MAGEIA',
+`EnvironmentFile=/etc/sysconfig/init
+ExecStart=-/bin/bash -c "exec ${SINGLE}"',
+m4_ifdef(`TARGET_MEEGO',
+`EnvironmentFile=/etc/sysconfig/init
+ExecStart=-/bin/bash -c "exec ${SINGLE}"',
+`ExecStart=-/sbin/sulogin'))))
+ExecStopPost=-/bin/systemctl --fail --no-block default
+StandardInput=tty-force
+StandardOutput=inherit
+StandardError=inherit
+KillMode=process
+
+# Bash ignores SIGTERM, so we send SIGHUP instead, to ensure that bash
+# terminates cleanly.
+KillSignal=SIGHUP
diff --git a/units/rescue.target b/units/rescue.target
new file mode 100644 (file)
index 0000000..5bf3f8e
--- /dev/null
@@ -0,0 +1,17 @@
+#  This file is part of systemd.
+#
+#  systemd is free software; you can redistribute it and/or modify it
+#  under the terms of the GNU General Public License as published by
+#  the Free Software Foundation; either version 2 of the License, or
+#  (at your option) any later version.
+
+# See systemd.special(7) for details
+
+[Unit]
+Description=Rescue Mode
+Requires=basic.target rescue.service
+After=basic.target rescue.service
+AllowIsolate=yes
+
+[Install]
+Alias=kbrequest.target
diff --git a/units/rpcbind.target b/units/rpcbind.target
new file mode 100644 (file)
index 0000000..a5cea8c
--- /dev/null
@@ -0,0 +1,14 @@
+#  This file is part of systemd.
+#
+#  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.
+
+# See systemd.special(7) for details
+
+# This exists mostly for compatibility with SysV/LSB units, and
+# implementations lacking socket/bus activation.
+
+[Unit]
+Description=RPC Port Mapper
diff --git a/units/serial-getty@.service.m4 b/units/serial-getty@.service.m4
new file mode 100644 (file)
index 0000000..fc8b57b
--- /dev/null
@@ -0,0 +1,50 @@
+#  This file is part of systemd.
+#
+#  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.
+
+[Unit]
+Description=Serial Getty on %I
+BindTo=dev-%i.device
+After=dev-%i.device systemd-user-sessions.service plymouth-quit-wait.service
+m4_ifdef(`TARGET_FEDORA',
+After=rc-local.service
+)m4_dnl
+m4_ifdef(`TARGET_ARCH',
+After=rc-local.service
+)m4_dnl
+m4_ifdef(`TARGET_FRUGALWARE',
+After=local.service
+)m4_dnl
+m4_ifdef(`TARGET_ALTLINUX',
+After=rc-local.service
+)m4_dnl
+m4_ifdef(`TARGET_MANDRIVA',
+After=rc-local.service
+)m4_dnl
+m4_ifdef(`TARGET_MAGEIA',
+After=rc-local.service
+)m4_dnl
+
+# If additional gettys are spawned during boot then we should make
+# sure that this is synchronized before getty.target, even though
+# getty.target didn't actually pull it in.
+Before=getty.target
+
+[Service]
+Environment=TERM=vt100
+ExecStart=-/sbin/agetty -s %I 115200,38400,9600
+Restart=always
+RestartSec=0
+UtmpIdentifier=%I
+TTYPath=/dev/%I
+TTYReset=yes
+TTYVHangup=yes
+KillMode=process
+IgnoreSIGPIPE=no
+
+# Some login implementations ignore SIGTERM, so we send SIGHUP
+# instead, to ensure that login terminates cleanly.
+KillSignal=SIGHUP
diff --git a/units/shutdown.target b/units/shutdown.target
new file mode 100644 (file)
index 0000000..99a659e
--- /dev/null
@@ -0,0 +1,13 @@
+#  This file is part of systemd.
+#
+#  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.
+
+# See systemd.special(7) for details
+
+[Unit]
+Description=Shutdown
+DefaultDependencies=no
+RefuseManualStart=yes
diff --git a/units/sigpwr.target b/units/sigpwr.target
new file mode 100644 (file)
index 0000000..0ca502d
--- /dev/null
@@ -0,0 +1,11 @@
+#  This file is part of systemd.
+#
+#  systemd is free software; you can redistribute it and/or modify it
+#  under the terms of the GNU General Public License as published by
+#  the Free Software Foundation; either version 2 of the License, or
+#  (at your option) any later version.
+
+# See systemd.special(7) for details
+
+[Unit]
+Description=Power Failure
diff --git a/units/smartcard.target b/units/smartcard.target
new file mode 100644 (file)
index 0000000..28dd2bb
--- /dev/null
@@ -0,0 +1,12 @@
+#  This file is part of systemd.
+#
+#  systemd is free software; you can redistribute it and/or modify it
+#  under the terms of the GNU General Public License as published by
+#  the Free Software Foundation; either version 2 of the License, or
+#  (at your option) any later version.
+
+# See systemd.special(7) for details
+
+[Unit]
+Description=Smart Card
+StopWhenUnneeded=yes
diff --git a/units/sockets.target b/units/sockets.target
new file mode 100644 (file)
index 0000000..2296312
--- /dev/null
@@ -0,0 +1,11 @@
+#  This file is part of systemd.
+#
+#  systemd is free software; you can redistribute it and/or modify it
+#  under the terms of the GNU General Public License as published by
+#  the Free Software Foundation; either version 2 of the License, or
+#  (at your option) any later version.
+
+# See systemd.special(7) for details
+
+[Unit]
+Description=Sockets
diff --git a/units/sound.target b/units/sound.target
new file mode 100644 (file)
index 0000000..e53221c
--- /dev/null
@@ -0,0 +1,12 @@
+#  This file is part of systemd.
+#
+#  systemd is free software; you can redistribute it and/or modify it
+#  under the terms of the GNU General Public License as published by
+#  the Free Software Foundation; either version 2 of the License, or
+#  (at your option) any later version.
+
+# See systemd.special(7) for details
+
+[Unit]
+Description=Sound Card
+StopWhenUnneeded=yes
diff --git a/units/suse/halt-local.service b/units/suse/halt-local.service
new file mode 100644 (file)
index 0000000..796012c
--- /dev/null
@@ -0,0 +1,20 @@
+#  This file is part of systemd.
+#
+#  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.
+
+[Unit]
+Description=/etc/init.d/halt.local Compatibility
+ConditionFileIsExecutable=/etc/init.d/halt.local
+DefaultDependencies=no
+After=shutdown.target
+Before=final.target
+
+[Service]
+Type=oneshot
+ExecStart=/etc/init.d/halt.local
+TimeoutSec=0
+StandardOutput=tty
+RemainAfterExit=yes
diff --git a/units/suse/rc-local.service b/units/suse/rc-local.service
new file mode 100644 (file)
index 0000000..2384a18
--- /dev/null
@@ -0,0 +1,19 @@
+#  This file is part of systemd.
+#
+#  systemd is free software; you can redistribute it and/or modify it
+#  under the terms of the GNU General Public License as published by
+#  the Free Software Foundation; either version 2 of the License, or
+#  (at your option) any later version.
+
+# This unit gets pulled automatically into multi-user.target by
+# systemd-rc-local-generator if /etc/init.d/boot.local is executable.
+[Unit]
+Description=/etc/init.d/boot.local Compatibility
+After=network.target
+
+[Service]
+Type=oneshot
+ExecStart=/etc/init.d/boot.local
+TimeoutSec=0
+RemainAfterExit=yes
+SysVStartPriority=99
diff --git a/units/swap.target b/units/swap.target
new file mode 100644 (file)
index 0000000..26dd261
--- /dev/null
@@ -0,0 +1,11 @@
+#  This file is part of systemd.
+#
+#  systemd is free software; you can redistribute it and/or modify it
+#  under the terms of the GNU General Public License as published by
+#  the Free Software Foundation; either version 2 of the License, or
+#  (at your option) any later version.
+
+# See systemd.special(7) for details
+
+[Unit]
+Description=Swap
diff --git a/units/sys-fs-fuse-connections.mount b/units/sys-fs-fuse-connections.mount
new file mode 100644 (file)
index 0000000..0374715
--- /dev/null
@@ -0,0 +1,18 @@
+#  This file is part of systemd.
+#
+#  systemd is free software; you can redistribute it and/or modify it
+#  under the terms of the GNU General Public License as published by
+#  the Free Software Foundation; either version 2 of the License, or
+#  (at your option) any later version.
+
+[Unit]
+Description=FUSE Control File System
+DefaultDependencies=no
+ConditionPathExists=/sys/fs/fuse/connections
+After=systemd-modules-load.service
+Before=sysinit.target
+
+[Mount]
+What=fusectl
+Where=/sys/fs/fuse/connections
+Type=fusectl
diff --git a/units/sys-kernel-config.mount b/units/sys-kernel-config.mount
new file mode 100644 (file)
index 0000000..d6862bf
--- /dev/null
@@ -0,0 +1,18 @@
+#  This file is part of systemd.
+#
+#  systemd is free software; you can redistribute it and/or modify it
+#  under the terms of the GNU General Public License as published by
+#  the Free Software Foundation; either version 2 of the License, or
+#  (at your option) any later version.
+
+[Unit]
+Description=Configuration File System
+DefaultDependencies=no
+ConditionPathExists=/sys/kernel/config
+After=systemd-modules-load.service
+Before=sysinit.target
+
+[Mount]
+What=configfs
+Where=/sys/kernel/config
+Type=configfs
diff --git a/units/sys-kernel-debug.mount b/units/sys-kernel-debug.mount
new file mode 100644 (file)
index 0000000..d9fca1f
--- /dev/null
@@ -0,0 +1,17 @@
+#  This file is part of systemd.
+#
+#  systemd is free software; you can redistribute it and/or modify it
+#  under the terms of the GNU General Public License as published by
+#  the Free Software Foundation; either version 2 of the License, or
+#  (at your option) any later version.
+
+[Unit]
+Description=Debug File System
+DefaultDependencies=no
+ConditionPathExists=/sys/kernel/debug
+Before=sysinit.target
+
+[Mount]
+What=debugfs
+Where=/sys/kernel/debug
+Type=debugfs
diff --git a/units/sysinit.target b/units/sysinit.target
new file mode 100644 (file)
index 0000000..eb9a1c7
--- /dev/null
@@ -0,0 +1,15 @@
+#  This file is part of systemd.
+#
+#  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.
+
+# See systemd.special(7) for details
+
+[Unit]
+Description=System Initialization
+Conflicts=emergency.service emergency.target
+Wants=local-fs.target swap.target
+After=local-fs.target swap.target emergency.service emergency.target
+RefuseManualStart=yes
diff --git a/units/syslog.socket b/units/syslog.socket
new file mode 100644 (file)
index 0000000..0e211e1
--- /dev/null
@@ -0,0 +1,40 @@
+#  This file is part of systemd.
+#
+#  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.
+
+# See systemd.special(7) for details
+
+[Unit]
+Description=Syslog Socket
+DefaultDependencies=no
+Before=sockets.target syslog.target
+Conflicts=shutdown.target
+Before=shutdown.target
+
+# Pull in syslog.target to tell people that /dev/log is now accessible
+Wants=syslog.target
+
+[Socket]
+ListenDatagram=/run/systemd/journal/syslog
+SocketMode=0666
+PassCredentials=yes
+PassSecurity=yes
+ReceiveBuffer=8M
+
+# The default syslog implementation should make syslog.service a
+# symlink to itself, so that this socket activates the right actual
+# syslog service.
+#
+# Examples:
+#
+# /etc/systemd/system/syslog.service -> /lib/systemd/system/rsyslog.service
+# /etc/systemd/system/syslog.service -> /lib/systemd/system/syslog-ng.service
+#
+# Best way to achieve that is by adding this to your unit file
+# (i.e. to rsyslog.service or syslog-ng.service):
+#
+# [Install]
+# Alias=syslog.service
diff --git a/units/syslog.target b/units/syslog.target
new file mode 100644 (file)
index 0000000..825b26e
--- /dev/null
@@ -0,0 +1,19 @@
+#  This file is part of systemd.
+#
+#  systemd is free software; you can redistribute it and/or modify it
+#  under the terms of the GNU General Public License as published by
+#  the Free Software Foundation; either version 2 of the License, or
+#  (at your option) any later version.
+
+# See systemd.special(7) for details
+
+# This exists mostly for compatibility with SysV/LSB units, and
+# implementations lacking socket/bus activation.
+
+[Unit]
+Description=Syslog
+
+# Avoid that we conflict with shutdown.target, so that we can stay
+# until the very end and do not cancel shutdown.target if we should
+# hapen to be activated very late.
+DefaultDependencies=no
diff --git a/units/systemd-ask-password-console.path b/units/systemd-ask-password-console.path
new file mode 100644 (file)
index 0000000..c3143d1
--- /dev/null
@@ -0,0 +1,18 @@
+#  This file is part of systemd.
+#
+#  systemd is free software; you can redistribute it and/or modify it
+#  under the terms of the GNU General Public License as published by
+#  the Free Software Foundation; either version 2 of the License, or
+#  (at your option) any later version.
+
+[Unit]
+Description=Dispatch Password Requests to Console Directory Watch
+DefaultDependencies=no
+Conflicts=shutdown.target
+After=plymouth-start.service
+Before=basic.target shutdown.target
+ConditionPathExists=!/run/plymouth/pid
+
+[Path]
+DirectoryNotEmpty=/run/systemd/ask-password
+MakeDirectory=yes
diff --git a/units/systemd-ask-password-console.service.in b/units/systemd-ask-password-console.service.in
new file mode 100644 (file)
index 0000000..5ff3ed5
--- /dev/null
@@ -0,0 +1,17 @@
+#  This file is part of systemd.
+#
+#  systemd is free software; you can redistribute it and/or modify it
+#  under the terms of the GNU General Public License as published by
+#  the Free Software Foundation; either version 2 of the License, or
+#  (at your option) any later version.
+
+[Unit]
+Description=Dispatch Password Requests to Console
+DefaultDependencies=no
+Conflicts=shutdown.target
+After=plymouth-start.service
+Before=shutdown.target
+ConditionPathExists=!/run/plymouth/pid
+
+[Service]
+ExecStart=@rootbindir@/systemd-tty-ask-password-agent --watch --console
diff --git a/units/systemd-ask-password-plymouth.path b/units/systemd-ask-password-plymouth.path
new file mode 100644 (file)
index 0000000..06a5876
--- /dev/null
@@ -0,0 +1,19 @@
+#  This file is part of systemd.
+#
+#  systemd is free software; you can redistribute it and/or modify it
+#  under the terms of the GNU General Public License as published by
+#  the Free Software Foundation; either version 2 of the License, or
+#  (at your option) any later version.
+
+[Unit]
+Description=Forward Password Requests to Plymouth Directory Watch
+DefaultDependencies=no
+Conflicts=shutdown.target
+After=plymouth-start.service
+Before=basic.target shutdown.target
+ConditionKernelCommandLine=!plymouth.enable=0
+ConditionPathExists=/run/plymouth/pid
+
+[Path]
+DirectoryNotEmpty=/run/systemd/ask-password
+MakeDirectory=yes
diff --git a/units/systemd-ask-password-plymouth.service.in b/units/systemd-ask-password-plymouth.service.in
new file mode 100644 (file)
index 0000000..92cbfdb
--- /dev/null
@@ -0,0 +1,18 @@
+#  This file is part of systemd.
+#
+#  systemd is free software; you can redistribute it and/or modify it
+#  under the terms of the GNU General Public License as published by
+#  the Free Software Foundation; either version 2 of the License, or
+#  (at your option) any later version.
+
+[Unit]
+Description=Forward Password Requests to Plymouth
+DefaultDependencies=no
+Conflicts=shutdown.target
+After=plymouth-start.service
+Before=shutdown.target
+ConditionKernelCommandLine=!plymouth.enable=0
+ConditionPathExists=/run/plymouth/pid
+
+[Service]
+ExecStart=@rootbindir@/systemd-tty-ask-password-agent --watch --plymouth
diff --git a/units/systemd-ask-password-wall.path b/units/systemd-ask-password-wall.path
new file mode 100644 (file)
index 0000000..050b73b
--- /dev/null
@@ -0,0 +1,16 @@
+#  This file is part of systemd.
+#
+#  systemd is free software; you can redistribute it and/or modify it
+#  under the terms of the GNU General Public License as published by
+#  the Free Software Foundation; either version 2 of the License, or
+#  (at your option) any later version.
+
+[Unit]
+Description=Forward Password Requests to Wall Directory Watch
+DefaultDependencies=no
+Conflicts=shutdown.target
+Before=basic.target shutdown.target
+
+[Path]
+DirectoryNotEmpty=/run/systemd/ask-password
+MakeDirectory=yes
diff --git a/units/systemd-ask-password-wall.service.in b/units/systemd-ask-password-wall.service.in
new file mode 100644 (file)
index 0000000..71ec1d6
--- /dev/null
@@ -0,0 +1,15 @@
+#  This file is part of systemd.
+#
+#  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.
+
+[Unit]
+Description=Forward Password Requests to Wall
+After=systemd-user-sessions.service
+
+[Service]
+ExecStartPre=-@rootbindir@/systemctl stop systemd-ask-password-console.path systemd-ask-password-console.service
+ExecStartPre=-@rootbindir@/systemctl stop systemd-ask-password-plymouth.path systemd-ask-password-plymouth.service
+ExecStart=@rootbindir@/systemd-tty-ask-password-agent --wall
diff --git a/units/systemd-binfmt.service.in b/units/systemd-binfmt.service.in
new file mode 100644 (file)
index 0000000..d43497c
--- /dev/null
@@ -0,0 +1,22 @@
+#  This file is part of systemd.
+#
+#  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.
+
+[Unit]
+Description=Set Up Additional Binary Formats
+DefaultDependencies=no
+Conflicts=shutdown.target
+After=systemd-readahead-collect.service systemd-readahead-replay.service proc-sys-fs-binfmt_misc.automount
+Before=sysinit.target shutdown.target
+ConditionDirectoryNotEmpty=|/usr/lib/binfmt.d
+ConditionDirectoryNotEmpty=|/usr/local/lib/binfmt.d
+ConditionDirectoryNotEmpty=|/etc/binfmt.d
+ConditionDirectoryNotEmpty=|/run/binfmt.d
+
+[Service]
+Type=oneshot
+RemainAfterExit=yes
+ExecStart=@rootlibexecdir@/systemd-binfmt
diff --git a/units/systemd-hostnamed.service.in b/units/systemd-hostnamed.service.in
new file mode 100644 (file)
index 0000000..6efab1e
--- /dev/null
@@ -0,0 +1,17 @@
+#  This file is part of systemd.
+#
+#  systemd is free software; you can redistribute it and/or modify it
+#  under the terms of the GNU General Public License as published by
+#  the Free Software Foundation; either version 2 of the License, or
+#  (at your option) any later version.
+
+# See systemd.special(7) for details
+
+[Unit]
+Description=Hostname Service
+
+[Service]
+ExecStart=@rootlibexecdir@/systemd-hostnamed
+Type=dbus
+BusName=org.freedesktop.hostname1
+CapabilityBoundingSet=CAP_SYS_ADMIN
diff --git a/units/systemd-initctl.service.in b/units/systemd-initctl.service.in
new file mode 100644 (file)
index 0000000..7df3aa6
--- /dev/null
@@ -0,0 +1,16 @@
+#  This file is part of systemd.
+#
+#  systemd is free software; you can redistribute it and/or modify it
+#  under the terms of the GNU General Public License as published by
+#  the Free Software Foundation; either version 2 of the License, or
+#  (at your option) any later version.
+
+# See systemd.special(7) for details
+
+[Unit]
+Description=/dev/initctl Compatibility Daemon
+DefaultDependencies=no
+
+[Service]
+ExecStart=@rootlibexecdir@/systemd-initctl
+NotifyAccess=all
diff --git a/units/systemd-initctl.socket b/units/systemd-initctl.socket
new file mode 100644 (file)
index 0000000..7a3a023
--- /dev/null
@@ -0,0 +1,17 @@
+#  This file is part of systemd.
+#
+#  systemd is free software; you can redistribute it and/or modify it
+#  under the terms of the GNU General Public License as published by
+#  the Free Software Foundation; either version 2 of the License, or
+#  (at your option) any later version.
+
+# See systemd.special(7) for details
+
+[Unit]
+Description=/dev/initctl Compatibility Named Pipe
+DefaultDependencies=no
+Before=sockets.target
+
+[Socket]
+ListenFIFO=/dev/initctl
+SocketMode=0600
diff --git a/units/systemd-journald.service.in b/units/systemd-journald.service.in
new file mode 100644 (file)
index 0000000..92606b0
--- /dev/null
@@ -0,0 +1,25 @@
+#  This file is part of systemd.
+#
+#  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.
+
+# See systemd.special(7) for details
+
+[Unit]
+Description=Journal Service
+DefaultDependencies=no
+Requires=systemd-journald.socket
+After=systemd-journald.socket
+After=syslog.socket
+
+[Service]
+ExecStart=@rootlibexecdir@/systemd-journald
+NotifyAccess=all
+StandardOutput=null
+CapabilityBoundingSet=CAP_SYS_ADMIN CAP_DAC_OVERRIDE CAP_SYS_PTRACE CAP_SYSLOG CAP_AUDIT_CONTROL CAP_CHOWN CAP_DAC_READ_SEARCH CAP_FOWNER CAP_SETUID CAP_SETGID
+
+# Increase the default a bit in order to allow many simultaneous
+# services being run since we keep one fd open per service.
+LimitNOFILE=16384
diff --git a/units/systemd-journald.socket b/units/systemd-journald.socket
new file mode 100644 (file)
index 0000000..15fc49e
--- /dev/null
@@ -0,0 +1,27 @@
+#  This file is part of systemd.
+#
+#  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.
+
+# See systemd.special(7) for details
+
+[Unit]
+Description=Journal Socket
+DefaultDependencies=no
+Before=sockets.target syslog.target
+
+# Mount and swap units need this. If this socket unit is removed by an
+# isolate request the mount and and swap units would be removed too,
+# hence let's exclude this from isolate requests.
+IgnoreOnIsolate=yes
+
+[Socket]
+ListenStream=/run/systemd/journal/stdout
+ListenDatagram=/run/systemd/journal/socket
+ListenDatagram=/dev/log
+SocketMode=0666
+PassCredentials=yes
+PassSecurity=yes
+ReceiveBuffer=8M
diff --git a/units/systemd-localed.service.in b/units/systemd-localed.service.in
new file mode 100644 (file)
index 0000000..4be65df
--- /dev/null
@@ -0,0 +1,17 @@
+#  This file is part of systemd.
+#
+#  systemd is free software; you can redistribute it and/or modify it
+#  under the terms of the GNU General Public License as published by
+#  the Free Software Foundation; either version 2 of the License, or
+#  (at your option) any later version.
+
+# See systemd.special(7) for details
+
+[Unit]
+Description=Locale Service
+
+[Service]
+ExecStart=@rootlibexecdir@/systemd-localed
+Type=dbus
+BusName=org.freedesktop.locale1
+CapabilityBoundingSet=
diff --git a/units/systemd-logind.service.in b/units/systemd-logind.service.in
new file mode 100644 (file)
index 0000000..531b8f7
--- /dev/null
@@ -0,0 +1,21 @@
+#  This file is part of systemd.
+#
+#  systemd is free software; you can redistribute it and/or modify it
+#  under the terms of the GNU General Public License as published by
+#  the Free Software Foundation; either version 2 of the License, or
+#  (at your option) any later version.
+
+# See systemd.special(7) for details
+
+[Unit]
+Description=Login Service
+
+[Service]
+ExecStart=@rootlibexecdir@/systemd-logind
+Type=dbus
+BusName=org.freedesktop.login1
+CapabilityBoundingSet=CAP_AUDIT_CONTROL CAP_CHOWN CAP_KILL CAP_DAC_READ_SEARCH CAP_DAC_OVERRIDE CAP_FOWNER CAP_SYS_TTY_CONFIG
+
+# Increase the default a bit in order to allow many simultaneous
+# logins since we keep one fd open per session.
+LimitNOFILE=16384
diff --git a/units/systemd-modules-load.service.in b/units/systemd-modules-load.service.in
new file mode 100644 (file)
index 0000000..5dc373d
--- /dev/null
@@ -0,0 +1,23 @@
+#  This file is part of systemd.
+#
+#  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.
+
+[Unit]
+Description=Load Kernel Modules
+DefaultDependencies=no
+Conflicts=shutdown.target
+After=systemd-readahead-collect.service systemd-readahead-replay.service
+Before=sysinit.target shutdown.target
+ConditionDirectoryNotEmpty=|/lib/modules-load.d
+ConditionDirectoryNotEmpty=|/usr/lib/modules-load.d
+ConditionDirectoryNotEmpty=|/usr/local/lib/modules-load.d
+ConditionDirectoryNotEmpty=|/etc/modules-load.d
+ConditionDirectoryNotEmpty=|/run/modules-load.d
+
+[Service]
+Type=oneshot
+RemainAfterExit=yes
+ExecStart=@rootlibexecdir@/systemd-modules-load
diff --git a/units/systemd-random-seed-load.service.in b/units/systemd-random-seed-load.service.in
new file mode 100644 (file)
index 0000000..a2b6a55
--- /dev/null
@@ -0,0 +1,18 @@
+#  This file is part of systemd.
+#
+#  systemd is free software; you can redistribute it and/or modify it
+#  under the terms of the GNU General Public License as published by
+#  the Free Software Foundation; either version 2 of the License, or
+#  (at your option) any later version.
+
+[Unit]
+Description=Load Random Seed
+DefaultDependencies=no
+Wants=local-fs.target
+Conflicts=shutdown.target
+After=systemd-readahead-collect.service systemd-readahead-replay.service local-fs.target
+Before=sysinit.target shutdown.target
+
+[Service]
+Type=oneshot
+ExecStart=@rootlibexecdir@/systemd-random-seed load
diff --git a/units/systemd-random-seed-save.service.in b/units/systemd-random-seed-save.service.in
new file mode 100644 (file)
index 0000000..9a074cf
--- /dev/null
@@ -0,0 +1,17 @@
+#  This file is part of systemd.
+#
+#  systemd is free software; you can redistribute it and/or modify it
+#  under the terms of the GNU General Public License as published by
+#  the Free Software Foundation; either version 2 of the License, or
+#  (at your option) any later version.
+
+[Unit]
+Description=Save Random Seed
+DefaultDependencies=no
+After=systemd-random-seed-load.service
+Before=shutdown.target
+Conflicts=systemd-random-seed-load.service
+
+[Service]
+Type=oneshot
+ExecStart=@rootlibexecdir@/systemd-random-seed save
diff --git a/units/systemd-readahead-collect.service.in b/units/systemd-readahead-collect.service.in
new file mode 100644 (file)
index 0000000..56ba54f
--- /dev/null
@@ -0,0 +1,22 @@
+#  This file is part of systemd.
+#
+#  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.
+
+[Unit]
+Description=Collect Read-Ahead Data
+DefaultDependencies=no
+Wants=systemd-readahead-done.timer
+Conflicts=shutdown.target
+Before=sysinit.target shutdown.target
+
+[Service]
+Type=notify
+ExecStart=@rootlibexecdir@/systemd-readahead-collect
+RemainAfterExit=yes
+StandardOutput=null
+
+[Install]
+WantedBy=default.target
diff --git a/units/systemd-readahead-done.service.in b/units/systemd-readahead-done.service.in
new file mode 100644 (file)
index 0000000..d665e45
--- /dev/null
@@ -0,0 +1,20 @@
+#  This file is part of systemd.
+#
+#  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.
+
+[Unit]
+Description=Stop Read-Ahead Data Collection
+DefaultDependencies=no
+Conflicts=shutdown.target
+After=default.target
+Before=shutdown.target
+
+[Service]
+Type=oneshot
+ExecStart=@SYSTEMD_NOTIFY@ --readahead=done
+
+[Install]
+Also=systemd-readahead-collect.service
diff --git a/units/systemd-readahead-done.timer b/units/systemd-readahead-done.timer
new file mode 100644 (file)
index 0000000..d144bfa
--- /dev/null
@@ -0,0 +1,19 @@
+#  This file is part of systemd.
+#
+#  systemd is free software; you can redistribute it and/or modify it
+#  under the terms of the GNU General Public License as published by
+#  the Free Software Foundation; either version 2 of the License, or
+#  (at your option) any later version.
+
+[Unit]
+Description=Stop Read-Ahead Data Collection 10s After Completed Startup
+DefaultDependencies=no
+Conflicts=shutdown.target
+After=default.target
+Before=shutdown.target
+
+[Timer]
+OnActiveSec=10s
+
+[Install]
+Also=systemd-readahead-collect.service
diff --git a/units/systemd-readahead-replay.service.in b/units/systemd-readahead-replay.service.in
new file mode 100644 (file)
index 0000000..7c82e40
--- /dev/null
@@ -0,0 +1,22 @@
+#  This file is part of systemd.
+#
+#  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.
+
+[Unit]
+Description=Replay Read-Ahead Data
+DefaultDependencies=no
+Conflicts=shutdown.target
+Before=sysinit.target shutdown.target
+ConditionPathExists=/.readahead
+
+[Service]
+Type=notify
+ExecStart=@rootlibexecdir@/systemd-readahead-replay
+RemainAfterExit=yes
+StandardOutput=null
+
+[Install]
+WantedBy=default.target
diff --git a/units/systemd-remount-api-vfs.service.in b/units/systemd-remount-api-vfs.service.in
new file mode 100644 (file)
index 0000000..f4df0ca
--- /dev/null
@@ -0,0 +1,19 @@
+#  This file is part of systemd.
+#
+#  systemd is free software; you can redistribute it and/or modify it
+#  under the terms of the GNU General Public License as published by
+#  the Free Software Foundation; either version 2 of the License, or
+#  (at your option) any later version.
+
+[Unit]
+Description=Remount API VFS
+DefaultDependencies=no
+Conflicts=shutdown.target
+After=systemd-readahead-collect.service systemd-readahead-replay.service
+Before=local-fs-pre.target local-fs.target shutdown.target
+Wants=local-fs-pre.target
+
+[Service]
+Type=oneshot
+RemainAfterExit=yes
+ExecStart=@rootlibexecdir@/systemd-remount-api-vfs
diff --git a/units/systemd-shutdownd.service.in b/units/systemd-shutdownd.service.in
new file mode 100644 (file)
index 0000000..657365a
--- /dev/null
@@ -0,0 +1,16 @@
+#  This file is part of systemd.
+#
+#  systemd is free software; you can redistribute it and/or modify it
+#  under the terms of the GNU General Public License as published by
+#  the Free Software Foundation; either version 2 of the License, or
+#  (at your option) any later version.
+
+# See systemd.special(7) for details
+
+[Unit]
+Description=Delayed Shutdown Service
+DefaultDependencies=no
+
+[Service]
+ExecStart=@rootlibexecdir@/systemd-shutdownd
+NotifyAccess=all
diff --git a/units/systemd-shutdownd.socket b/units/systemd-shutdownd.socket
new file mode 100644 (file)
index 0000000..7f13c93
--- /dev/null
@@ -0,0 +1,19 @@
+#  This file is part of systemd.
+#
+#  systemd is free software; you can redistribute it and/or modify it
+#  under the terms of the GNU General Public License as published by
+#  the Free Software Foundation; either version 2 of the License, or
+#  (at your option) any later version.
+
+# See systemd.special(7) for details
+
+[Unit]
+Description=Delayed Shutdown Socket
+DefaultDependencies=no
+Before=sockets.target
+
+[Socket]
+ListenDatagram=/run/systemd/shutdownd
+SocketMode=0600
+PassCredentials=yes
+PassSecurity=yes
diff --git a/units/systemd-sysctl.service.in b/units/systemd-sysctl.service.in
new file mode 100644 (file)
index 0000000..6d53422
--- /dev/null
@@ -0,0 +1,24 @@
+#  This file is part of systemd.
+#
+#  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.
+
+[Unit]
+Description=Apply Kernel Variables
+DefaultDependencies=no
+Conflicts=shutdown.target
+After=systemd-readahead-collect.service systemd-readahead-replay.service
+Before=sysinit.target shutdown.target
+ConditionPathExists=|/etc/sysctl.conf
+ConditionDirectoryNotEmpty=|/lib/sysctl.d
+ConditionDirectoryNotEmpty=|/usr/lib/sysctl.d
+ConditionDirectoryNotEmpty=|/usr/local/lib/sysctl.d
+ConditionDirectoryNotEmpty=|/etc/sysctl.d
+ConditionDirectoryNotEmpty=|/run/sysctl.d
+
+[Service]
+Type=oneshot
+RemainAfterExit=yes
+ExecStart=@rootlibexecdir@/systemd-sysctl
diff --git a/units/systemd-timedated.service.in b/units/systemd-timedated.service.in
new file mode 100644 (file)
index 0000000..90ff443
--- /dev/null
@@ -0,0 +1,17 @@
+#  This file is part of systemd.
+#
+#  systemd is free software; you can redistribute it and/or modify it
+#  under the terms of the GNU General Public License as published by
+#  the Free Software Foundation; either version 2 of the License, or
+#  (at your option) any later version.
+
+# See systemd.special(7) for details
+
+[Unit]
+Description=Time & Date Service
+
+[Service]
+ExecStart=@rootlibexecdir@/systemd-timedated
+Type=dbus
+BusName=org.freedesktop.timedate1
+CapabilityBoundingSet=CAP_SYS_TIME
diff --git a/units/systemd-tmpfiles-clean.service.in b/units/systemd-tmpfiles-clean.service.in
new file mode 100644 (file)
index 0000000..3c8e72e
--- /dev/null
@@ -0,0 +1,22 @@
+#  This file is part of systemd.
+#
+#  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.
+
+[Unit]
+Description=Cleanup of Temporary Directories
+DefaultDependencies=no
+Wants=local-fs.target
+After=systemd-readahead-collect.service systemd-readahead-replay.service local-fs.target
+Before=sysinit.target shutdown.target
+ConditionDirectoryNotEmpty=|/usr/lib/tmpfiles.d
+ConditionDirectoryNotEmpty=|/usr/local/lib/tmpfiles.d
+ConditionDirectoryNotEmpty=|/etc/tmpfiles.d
+ConditionDirectoryNotEmpty=|/run/tmpfiles.d
+
+[Service]
+Type=oneshot
+ExecStart=@rootbindir@/systemd-tmpfiles --clean
+IOSchedulingClass=idle
diff --git a/units/systemd-tmpfiles-clean.timer b/units/systemd-tmpfiles-clean.timer
new file mode 100644 (file)
index 0000000..d8529a8
--- /dev/null
@@ -0,0 +1,13 @@
+#  This file is part of systemd.
+#
+#  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.
+
+[Unit]
+Description=Daily Cleanup of Temporary Directories
+
+[Timer]
+OnBootSec=15min
+OnUnitActiveSec=1d
diff --git a/units/systemd-tmpfiles-setup.service.in b/units/systemd-tmpfiles-setup.service.in
new file mode 100644 (file)
index 0000000..f90121e
--- /dev/null
@@ -0,0 +1,22 @@
+#  This file is part of systemd.
+#
+#  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.
+
+[Unit]
+Description=Recreate Volatile Files and Directories
+DefaultDependencies=no
+Wants=local-fs.target
+After=systemd-readahead-collect.service systemd-readahead-replay.service local-fs.target
+Before=sysinit.target shutdown.target
+ConditionDirectoryNotEmpty=|/usr/lib/tmpfiles.d
+ConditionDirectoryNotEmpty=|/usr/local/lib/tmpfiles.d
+ConditionDirectoryNotEmpty=|/etc/tmpfiles.d
+ConditionDirectoryNotEmpty=|/run/tmpfiles.d
+
+[Service]
+Type=oneshot
+RemainAfterExit=yes
+ExecStart=@rootbindir@/systemd-tmpfiles --create --remove
diff --git a/units/systemd-update-utmp-runlevel.service.in b/units/systemd-update-utmp-runlevel.service.in
new file mode 100644 (file)
index 0000000..614c759
--- /dev/null
@@ -0,0 +1,16 @@
+#  This file is part of systemd.
+#
+#  systemd is free software; you can redistribute it and/or modify it
+#  under the terms of the GNU General Public License as published by
+#  the Free Software Foundation; either version 2 of the License, or
+#  (at your option) any later version.
+
+[Unit]
+Description=Notify Audit System and Update UTMP about System Runlevel Changes
+DefaultDependencies=no
+After=local-fs.target sysinit.target auditd.service runlevel1.target runlevel2.target runlevel3.target runlevel4.target runlevel5.target systemd-tmpfiles-setup.service
+Before=poweroff.service reboot.service halt.service
+
+[Service]
+Type=oneshot
+ExecStart=@rootlibexecdir@/systemd-update-utmp runlevel
diff --git a/units/systemd-update-utmp-shutdown.service.in b/units/systemd-update-utmp-shutdown.service.in
new file mode 100644 (file)
index 0000000..e7c3c04
--- /dev/null
@@ -0,0 +1,16 @@
+#  This file is part of systemd.
+#
+#  systemd is free software; you can redistribute it and/or modify it
+#  under the terms of the GNU General Public License as published by
+#  the Free Software Foundation; either version 2 of the License, or
+#  (at your option) any later version.
+
+[Unit]
+Description=Notify Audit System and Update UTMP about System Shutdown
+DefaultDependencies=no
+After=local-fs.target sysinit.target auditd.service systemd-update-utmp-runlevel.service
+Before=poweroff.service reboot.service halt.service
+
+[Service]
+Type=oneshot
+ExecStart=@rootlibexecdir@/systemd-update-utmp shutdown
diff --git a/units/systemd-user-sessions.service.in b/units/systemd-user-sessions.service.in
new file mode 100644 (file)
index 0000000..a93d586
--- /dev/null
@@ -0,0 +1,16 @@
+#  This file is part of systemd.
+#
+#  systemd is free software; you can redistribute it and/or modify it
+#  under the terms of the GNU General Public License as published by
+#  the Free Software Foundation; either version 2 of the License, or
+#  (at your option) any later version.
+
+[Unit]
+Description=Permit User Sessions
+After=local-fs.target remote-fs.target
+
+[Service]
+Type=oneshot
+RemainAfterExit=yes
+ExecStart=@rootlibexecdir@/systemd-user-sessions start
+ExecStop=@rootlibexecdir@/systemd-user-sessions stop
diff --git a/units/systemd-vconsole-setup.service.in b/units/systemd-vconsole-setup.service.in
new file mode 100644 (file)
index 0000000..673fb6c
--- /dev/null
@@ -0,0 +1,18 @@
+#  This file is part of systemd.
+#
+#  systemd is free software; you can redistribute it and/or modify it
+#  under the terms of the GNU General Public License as published by
+#  the Free Software Foundation; either version 2 of the License, or
+#  (at your option) any later version.
+
+[Unit]
+Description=Setup Virtual Console
+DefaultDependencies=no
+Conflicts=shutdown.target
+After=systemd-readahead-collect.service systemd-readahead-replay.service
+Before=sysinit.target shutdown.target
+
+[Service]
+Type=oneshot
+RemainAfterExit=yes
+ExecStart=@rootlibexecdir@/systemd-vconsole-setup
diff --git a/units/time-sync.target b/units/time-sync.target
new file mode 100644 (file)
index 0000000..aa34ecb
--- /dev/null
@@ -0,0 +1,14 @@
+#  This file is part of systemd.
+#
+#  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.
+
+# See systemd.special(7) for details
+
+# This exists mostly for compatibility with SysV/LSB units, and
+# implementations lacking socket/bus activation.
+
+[Unit]
+Description=System Time Synchronized
diff --git a/units/tmp.mount b/units/tmp.mount
new file mode 100644 (file)
index 0000000..8d0b8af
--- /dev/null
@@ -0,0 +1,16 @@
+#  This file is part of systemd.
+#
+#  systemd is free software; you can redistribute it and/or modify it
+#  under the terms of the GNU General Public License as published by
+#  the Free Software Foundation; either version 2 of the License, or
+#  (at your option) any later version.
+
+[Unit]
+Description=Temporary Directory
+Before=local-fs.target
+
+[Mount]
+What=tmpfs
+Where=/tmp
+Type=tmpfs
+Options=mode=1777
diff --git a/units/umount.target b/units/umount.target
new file mode 100644 (file)
index 0000000..b9ecca6
--- /dev/null
@@ -0,0 +1,13 @@
+#  This file is part of systemd.
+#
+#  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.
+
+# See systemd.special(7) for details
+
+[Unit]
+Description=Unmount All Filesystems
+DefaultDependencies=no
+RefuseManualStart=yes
diff --git a/units/user/.gitignore b/units/user/.gitignore
new file mode 100644 (file)
index 0000000..eeb62b3
--- /dev/null
@@ -0,0 +1 @@
+exit.service
diff --git a/units/user/Makefile b/units/user/Makefile
new file mode 120000 (symlink)
index 0000000..50be211
--- /dev/null
@@ -0,0 +1 @@
+../../src/Makefile
\ No newline at end of file
diff --git a/units/user/default.target b/units/user/default.target
new file mode 100644 (file)
index 0000000..deb310c
--- /dev/null
@@ -0,0 +1,11 @@
+#  This file is part of systemd.
+#
+#  systemd is free software; you can redistribute it and/or modify it
+#  under the terms of the GNU General Public License as published by
+#  the Free Software Foundation; either version 2 of the License, or
+#  (at your option) any later version.
+
+# See systemd.special(7) for details
+
+[Unit]
+Description=Default
diff --git a/units/user/exit.service.in b/units/user/exit.service.in
new file mode 100644 (file)
index 0000000..a20b089
--- /dev/null
@@ -0,0 +1,18 @@
+#  This file is part of systemd.
+#
+#  systemd is free software; you can redistribute it and/or modify it
+#  under the terms of the GNU General Public License as published by
+#  the Free Software Foundation; either version 2 of the License, or
+#  (at your option) any later version.
+
+# See systemd.special(7) for details
+
+[Unit]
+Description=Exit the Session
+DefaultDependencies=no
+Requires=shutdown.target
+After=shutdown.target
+
+[Service]
+Type=oneshot
+ExecStart=@SYSTEMCTL@ --user --force exit
diff --git a/units/user/exit.target b/units/user/exit.target
new file mode 100644 (file)
index 0000000..f34844c
--- /dev/null
@@ -0,0 +1,18 @@
+#  This file is part of systemd.
+#
+#  systemd is free software; you can redistribute it and/or modify it
+#  under the terms of the GNU General Public License as published by
+#  the Free Software Foundation; either version 2 of the License, or
+#  (at your option) any later version.
+
+# See systemd.special(7) for details
+
+[Unit]
+Description=Exit the Session
+DefaultDependencies=no
+Requires=exit.service
+After=exit.service
+AllowIsolate=yes
+
+[Install]
+Alias=ctrl-alt-del.target
diff --git a/units/user@.service.in b/units/user@.service.in
new file mode 100644 (file)
index 0000000..91e3b25
--- /dev/null
@@ -0,0 +1,19 @@
+#  This file is part of systemd.
+#
+#  systemd is free software; you can redistribute it and/or modify it
+#  under the terms of the GNU General Public License as published by
+#  the Free Software Foundation; either version 2 of the License, or
+#  (at your option) any later version.
+
+[Unit]
+Description=User Manager for %I
+After=systemd-user-sessions.service
+
+[Service]
+User=%I
+PAMName=systemd-shared
+ControlGroup=%R/user/%I/shared cpu:/
+ControlGroupModify=yes
+Type=notify
+ExecStart=-@rootlibexecdir@/systemd --user
+Environment=DBUS_SESSION_BUS_ADDRESS=unix:path=/run/user/%I/dbus/user_bus_socket