summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--.dir-locals.el7
-rw-r--r--.gitignore139
-rw-r--r--.mailmap4
-rw-r--r--CODING_STYLE27
-rw-r--r--DISTRO_PORTING58
-rw-r--r--LICENSE339
-rw-r--r--Makefile.am2960
-rw-r--r--NEWS1922
-rw-r--r--README205
-rw-r--r--TODO350
-rwxr-xr-xautogen.sh68
-rw-r--r--configure.ac857
-rw-r--r--introspect.awk13
-rw-r--r--m4/.gitignore8
-rw-r--r--m4/acx_libwrap.m419
-rw-r--r--m4/attributes.m4288
l---------man/Makefile1
-rw-r--r--man/binfmt.d.xml111
-rw-r--r--man/custom-html.xsl29
-rw-r--r--man/daemon.xml948
-rw-r--r--man/halt.xml181
-rw-r--r--man/hostname.xml95
-rw-r--r--man/journalctl.xml260
-rw-r--r--man/journald.conf.xml254
-rw-r--r--man/locale.conf.xml146
-rw-r--r--man/loginctl.xml457
-rw-r--r--man/logind.conf.xml175
-rw-r--r--man/machine-id.xml143
-rw-r--r--man/machine-info.xml147
-rw-r--r--man/modules-load.d.xml112
-rw-r--r--man/os-release.xml350
-rw-r--r--man/pam_systemd.xml316
-rw-r--r--man/runlevel.xml154
-rw-r--r--man/sd-daemon.xml172
-rw-r--r--man/sd-login.xml146
-rw-r--r--man/sd-readahead.xml117
-rw-r--r--man/sd_booted.xml128
-rw-r--r--man/sd_get_seats.xml127
-rw-r--r--man/sd_is_fifo.xml217
-rw-r--r--man/sd_listen_fds.xml203
-rw-r--r--man/sd_login_monitor_new.xml172
-rw-r--r--man/sd_notify.xml312
-rw-r--r--man/sd_pid_get_session.xml160
-rw-r--r--man/sd_readahead.xml178
-rw-r--r--man/sd_seat_get_active.xml157
-rw-r--r--man/sd_session_is_active.xml206
-rw-r--r--man/sd_uid_get_state.xml185
-rw-r--r--man/shutdown.xml188
-rw-r--r--man/sysctl.d.xml123
-rw-r--r--man/systemctl.xml1190
-rw-r--r--man/systemd-ask-password.xml183
-rw-r--r--man/systemd-cat.xml205
-rw-r--r--man/systemd-cgls.xml123
-rw-r--r--man/systemd-cgtop.xml246
-rw-r--r--man/systemd-machine-id-setup.xml114
-rw-r--r--man/systemd-notify.xml218
-rw-r--r--man/systemd-nspawn.xml215
-rw-r--r--man/systemd-tmpfiles.xml152
-rw-r--r--man/systemd.automount.xml167
-rw-r--r--man/systemd.conf.xml162
-rw-r--r--man/systemd.device.xml168
-rw-r--r--man/systemd.exec.xml1106
-rw-r--r--man/systemd.mount.xml278
-rw-r--r--man/systemd.path.xml220
-rw-r--r--man/systemd.service.xml837
-rw-r--r--man/systemd.snapshot.xml87
-rw-r--r--man/systemd.socket.xml674
-rw-r--r--man/systemd.special.xml725
-rw-r--r--man/systemd.swap.xml222
-rw-r--r--man/systemd.target.xml108
-rw-r--r--man/systemd.timer.xml192
-rw-r--r--man/systemd.unit.xml969
-rw-r--r--man/systemd.xml1167
-rw-r--r--man/telinit.xml195
-rw-r--r--man/timezone.xml90
-rw-r--r--man/tmpfiles.d.xml300
-rw-r--r--man/vconsole.conf.xml146
-rw-r--r--po/.gitignore5
-rw-r--r--po/POTFILES.in4
-rw-r--r--po/POTFILES.skip19
-rw-r--r--po/pl.po175
-rw-r--r--src/.gitignore16
-rw-r--r--src/99-systemd.rules.in55
-rw-r--r--src/Makefile28
-rw-r--r--src/ac-power.c111
-rw-r--r--src/acl-util.c68
-rw-r--r--src/acl-util.h27
-rw-r--r--src/ask-password-api.c576
-rw-r--r--src/ask-password-api.h33
-rw-r--r--src/ask-password.c184
-rw-r--r--src/automount.c887
-rw-r--r--src/automount.h76
l---------src/binfmt/Makefile1
-rw-r--r--src/binfmt/binfmt.c169
-rw-r--r--src/bridge.c367
-rw-r--r--src/build.h69
-rw-r--r--src/bus-errors.h58
-rw-r--r--src/cgls.c164
-rw-r--r--src/cgroup-attr.c102
-rw-r--r--src/cgroup-attr.h49
-rw-r--r--src/cgroup-show.c261
-rw-r--r--src/cgroup-show.h30
-rw-r--r--src/cgroup-util.c1111
-rw-r--r--src/cgroup-util.h72
-rw-r--r--src/cgroup.c556
-rw-r--r--src/cgroup.h94
-rw-r--r--src/cgroups-agent.c101
-rw-r--r--src/cgtop.c729
-rw-r--r--src/condition.c323
-rw-r--r--src/condition.h69
-rw-r--r--src/conf-parser.c852
-rw-r--r--src/conf-parser.h135
l---------src/cryptsetup/Makefile1
-rw-r--r--src/cryptsetup/cryptsetup-generator.c298
-rw-r--r--src/cryptsetup/cryptsetup.c529
-rw-r--r--src/dbus-automount.c72
-rw-r--r--src/dbus-automount.h34
-rw-r--r--src/dbus-common.c1092
-rw-r--r--src/dbus-common.h183
-rw-r--r--src/dbus-device.c65
-rw-r--r--src/dbus-device.h34
-rw-r--r--src/dbus-execute.c422
-rw-r--r--src/dbus-execute.h125
-rw-r--r--src/dbus-job.c354
-rw-r--r--src/dbus-job.h36
-rw-r--r--src/dbus-loop.c263
-rw-r--r--src/dbus-loop.h30
-rw-r--r--src/dbus-manager.c1537
-rw-r--r--src/dbus-manager.h31
-rw-r--r--src/dbus-mount.c168
-rw-r--r--src/dbus-mount.h34
-rw-r--r--src/dbus-path.c119
-rw-r--r--src/dbus-path.h35
-rw-r--r--src/dbus-service.c168
-rw-r--r--src/dbus-service.h34
-rw-r--r--src/dbus-snapshot.c93
-rw-r--r--src/dbus-snapshot.h33
-rw-r--r--src/dbus-socket.c139
-rw-r--r--src/dbus-socket.h34
-rw-r--r--src/dbus-swap.c111
-rw-r--r--src/dbus-swap.h35
-rw-r--r--src/dbus-target.c55
-rw-r--r--src/dbus-target.h33
-rw-r--r--src/dbus-timer.c137
-rw-r--r--src/dbus-timer.h34
-rw-r--r--src/dbus-unit.c850
-rw-r--r--src/dbus-unit.h139
-rw-r--r--src/dbus.c1482
-rw-r--r--src/dbus.h53
-rw-r--r--src/def.h37
-rw-r--r--src/detect-virt.c48
-rw-r--r--src/device.c616
-rw-r--r--src/device.h59
-rw-r--r--src/execute.c2112
-rw-r--r--src/execute.h233
-rw-r--r--src/exit-status.c180
-rw-r--r--src/exit-status.h85
-rw-r--r--src/fdset.c167
-rw-r--r--src/fdset.h40
-rw-r--r--src/fsck.c406
-rw-r--r--src/getty-generator.c182
-rw-r--r--src/hashmap.c731
-rw-r--r--src/hashmap.h91
-rw-r--r--src/hostname-setup.c187
-rw-r--r--src/hostname-setup.h27
-rw-r--r--src/hostname/.gitignore1
l---------src/hostname/Makefile1
-rw-r--r--src/hostname/hostnamed.c629
-rw-r--r--src/hostname/org.freedesktop.hostname1.conf27
-rw-r--r--src/hostname/org.freedesktop.hostname1.policy.in49
-rw-r--r--src/hostname/org.freedesktop.hostname1.service12
-rw-r--r--src/ima-setup.c115
-rw-r--r--src/ima-setup.h29
-rw-r--r--src/initctl.c451
-rw-r--r--src/initreq.h77
-rw-r--r--src/install.c1953
-rw-r--r--src/install.h89
-rw-r--r--src/ioprio.h57
-rw-r--r--src/job.c754
-rw-r--r--src/job.h179
-rw-r--r--src/journal/.gitignore2
l---------src/journal/Makefile1
-rw-r--r--src/journal/cat.c182
-rw-r--r--src/journal/compress.c208
-rw-r--r--src/journal/compress.h38
-rw-r--r--src/journal/coredump.c271
-rw-r--r--src/journal/journal-def.h165
-rw-r--r--src/journal/journal-file.c2274
-rw-r--r--src/journal/journal-file.h128
-rw-r--r--src/journal/journal-internal.h84
-rw-r--r--src/journal/journal-rate-limit.c275
-rw-r--r--src/journal/journal-rate-limit.h34
-rw-r--r--src/journal/journal-send.c516
-rw-r--r--src/journal/journalctl.c327
-rw-r--r--src/journal/journald-gperf.gperf31
-rw-r--r--src/journal/journald.c2821
-rw-r--r--src/journal/journald.conf25
-rw-r--r--src/journal/journald.h86
-rw-r--r--src/journal/libsystemd-journal.pc.in19
-rw-r--r--src/journal/libsystemd-journal.sym53
-rw-r--r--src/journal/lookup3.c1003
-rw-r--r--src/journal/lookup3.h25
-rw-r--r--src/journal/sd-journal.c1636
-rw-r--r--src/journal/sparse-endian.h87
-rw-r--r--src/journal/test-journal-send.c32
-rw-r--r--src/journal/test-journal.c120
-rw-r--r--src/kmod-setup.c96
-rw-r--r--src/kmod-setup.h27
-rw-r--r--src/label.c413
-rw-r--r--src/label.h51
-rw-r--r--src/libsystemd-daemon.pc.in19
-rw-r--r--src/libsystemd-daemon.sym27
-rw-r--r--src/libsystemd-id128.pc.in18
-rw-r--r--src/libsystemd-id128.sym21
l---------src/linux/Makefile1
-rw-r--r--src/linux/auto_dev-ioctl.h229
-rw-r--r--src/linux/fanotify.h98
-rw-r--r--src/list.h128
-rw-r--r--src/load-dropin.c150
-rw-r--r--src/load-dropin.h31
-rw-r--r--src/load-fragment-gperf.gperf.m4230
-rw-r--r--src/load-fragment.c2445
-rw-r--r--src/load-fragment.h91
-rw-r--r--src/locale-setup.c251
-rw-r--r--src/locale-setup.h27
-rw-r--r--src/locale/.gitignore1
l---------src/locale/Makefile1
-rwxr-xr-xsrc/locale/generate-kbd-model-map33
-rw-r--r--src/locale/kbd-model-map72
-rw-r--r--src/locale/localed.c1437
-rw-r--r--src/locale/org.freedesktop.locale1.conf27
-rw-r--r--src/locale/org.freedesktop.locale1.policy.in39
-rw-r--r--src/locale/org.freedesktop.locale1.service12
-rw-r--r--src/log.c747
-rw-r--r--src/log.h111
-rw-r--r--src/login/.gitignore3
-rw-r--r--src/login/70-uaccess.rules72
-rw-r--r--src/login/71-seat.rules25
-rw-r--r--src/login/73-seat-late.rules.in17
l---------src/login/Makefile1
-rw-r--r--src/login/libsystemd-login.pc.in18
-rw-r--r--src/login/libsystemd-login.sym48
-rw-r--r--src/login/loginctl.c1926
-rw-r--r--src/login/logind-acl.c248
-rw-r--r--src/login/logind-acl.h60
-rw-r--r--src/login/logind-dbus.c1697
-rw-r--r--src/login/logind-device.c86
-rw-r--r--src/login/logind-device.h48
-rw-r--r--src/login/logind-gperf.gperf22
-rw-r--r--src/login/logind-seat-dbus.c408
-rw-r--r--src/login/logind-seat.c514
-rw-r--r--src/login/logind-seat.h83
-rw-r--r--src/login/logind-session-dbus.c528
-rw-r--r--src/login/logind-session.c982
-rw-r--r--src/login/logind-session.h136
-rw-r--r--src/login/logind-user-dbus.c417
-rw-r--r--src/login/logind-user.c589
-rw-r--r--src/login/logind-user.h86
-rw-r--r--src/login/logind.c1288
-rw-r--r--src/login/logind.conf16
-rw-r--r--src/login/logind.h131
-rw-r--r--src/login/multi-seat-x.c195
-rw-r--r--src/login/org.freedesktop.login1.conf118
-rw-r--r--src/login/org.freedesktop.login1.policy.in89
-rw-r--r--src/login/org.freedesktop.login1.service12
-rw-r--r--src/login/pam-module.c695
-rw-r--r--src/login/sd-login.c835
-rw-r--r--src/login/sysfs-show.c187
-rw-r--r--src/login/test-login.c188
-rw-r--r--src/login/uaccess.c91
-rw-r--r--src/login/user-sessions.c100
-rw-r--r--src/logs-show.c664
-rw-r--r--src/logs-show.h56
-rw-r--r--src/loopback-setup.c274
-rw-r--r--src/loopback-setup.h27
-rw-r--r--src/machine-id-main.c35
-rw-r--r--src/machine-id-setup.c267
-rw-r--r--src/machine-id-setup.h27
-rw-r--r--src/macro.h181
-rw-r--r--src/main.c1632
-rw-r--r--src/manager.c3199
-rw-r--r--src/manager.h301
-rw-r--r--src/missing.h187
-rw-r--r--src/modules-load.c155
-rw-r--r--src/mount-setup.c422
-rw-r--r--src/mount-setup.h36
-rw-r--r--src/mount.c1929
-rw-r--r--src/mount.h124
-rw-r--r--src/namespace.c346
-rw-r--r--src/namespace.h34
-rw-r--r--src/notify.c228
-rw-r--r--src/nspawn.c894
-rw-r--r--src/org.freedesktop.systemd1.conf92
-rw-r--r--src/org.freedesktop.systemd1.policy.in.in41
-rw-r--r--src/org.freedesktop.systemd1.service11
-rw-r--r--src/pager.c134
-rw-r--r--src/pager.h28
-rw-r--r--src/path-lookup.c347
-rw-r--r--src/path-lookup.h40
-rw-r--r--src/path.c770
-rw-r--r--src/path.h113
-rw-r--r--src/polkit.c205
-rw-r--r--src/polkit.h36
-rw-r--r--src/quotacheck.c120
-rw-r--r--src/random-seed.c147
-rw-r--r--src/ratelimit.c57
-rw-r--r--src/ratelimit.h53
-rw-r--r--src/rc-local-generator.c107
l---------src/readahead/Makefile1
-rw-r--r--src/readahead/readahead-collect.c693
-rw-r--r--src/readahead/readahead-common.c269
-rw-r--r--src/readahead/readahead-common.h50
-rw-r--r--src/readahead/readahead-replay.c378
-rw-r--r--src/readahead/sd-readahead.c88
-rw-r--r--src/remount-api-vfs.c161
-rw-r--r--src/reply-password.c109
-rw-r--r--src/sd-id128.c221
-rw-r--r--src/securebits.h45
-rw-r--r--src/selinux-setup.c112
-rw-r--r--src/selinux-setup.h29
-rw-r--r--src/service.c3797
-rw-r--r--src/service.h220
-rw-r--r--src/set.c118
-rw-r--r--src/set.h69
-rw-r--r--src/shutdown.c485
-rw-r--r--src/shutdownd.c366
-rw-r--r--src/shutdownd.h46
-rw-r--r--src/snapshot.c309
-rw-r--r--src/snapshot.h53
-rw-r--r--src/socket-util.c652
-rw-r--r--src/socket-util.h102
-rw-r--r--src/socket.c2217
-rw-r--r--src/socket.h173
-rw-r--r--src/spawn-agent.c120
-rw-r--r--src/spawn-agent.h28
-rw-r--r--src/special.h88
-rw-r--r--src/specifier.c108
-rw-r--r--src/specifier.h37
-rw-r--r--src/strv.c690
-rw-r--r--src/strv.h79
-rw-r--r--src/swap.c1415
-rw-r--r--src/swap.h128
-rw-r--r--src/sysctl.c272
-rw-r--r--src/sysfs-show.h27
-rw-r--r--src/system.conf26
-rw-r--r--src/systemctl.c5489
-rwxr-xr-xsrc/systemd-analyze276
-rw-r--r--src/systemd-bash-completion.sh281
-rw-r--r--src/systemd.pc.in21
l---------src/systemd/Makefile1
-rw-r--r--src/systemd/sd-daemon.h (renamed from src/sd-daemon.h)0
-rw-r--r--src/systemd/sd-id128.h69
-rw-r--r--src/systemd/sd-journal.h132
-rw-r--r--src/systemd/sd-login.h145
-rw-r--r--src/systemd/sd-messages.h41
-rw-r--r--src/systemd/sd-readahead.h73
-rw-r--r--src/target.c224
-rw-r--r--src/target.h47
-rw-r--r--src/tcpwrap.c68
-rw-r--r--src/tcpwrap.h29
-rw-r--r--src/test-cgroup.c104
-rw-r--r--src/test-daemon.c37
-rw-r--r--src/test-engine.c99
-rw-r--r--src/test-env-replace.c127
-rw-r--r--src/test-hostname.c37
-rw-r--r--src/test-id128.c52
-rw-r--r--src/test-install.c264
-rw-r--r--src/test-job-type.c84
-rw-r--r--src/test-loopback.c37
-rw-r--r--src/test-ns.c60
-rw-r--r--src/test-strv.c66
-rw-r--r--src/timedate/.gitignore1
l---------src/timedate/Makefile1
-rw-r--r--src/timedate/org.freedesktop.timedate1.conf27
-rw-r--r--src/timedate/org.freedesktop.timedate1.policy.in61
-rw-r--r--src/timedate/org.freedesktop.timedate1.service12
-rw-r--r--src/timedate/timedated.c930
-rw-r--r--src/timer.c520
-rw-r--r--src/timer.h93
-rw-r--r--src/timestamp.c39
-rw-r--r--src/tmpfiles.c1314
-rw-r--r--src/tty-ask-password-agent.c753
-rw-r--r--src/udev/.gitignore40
-rw-r--r--src/udev/.vimrc4
-rw-r--r--src/udev/COPYING (renamed from COPYING)0
-rw-r--r--src/udev/ChangeLog (renamed from ChangeLog)0
-rw-r--r--src/udev/INSTALL (renamed from INSTALL)0
-rw-r--r--src/udev/Makefile.am712
-rw-r--r--src/udev/NEWS1735
-rw-r--r--src/udev/README101
-rw-r--r--src/udev/TODO22
-rwxr-xr-xsrc/udev/autogen.sh44
-rw-r--r--src/udev/configure.ac242
-rw-r--r--src/udev/m4/.gitignore4
-rw-r--r--src/udev/rules/42-usb-hid-pm.rules (renamed from rules/42-usb-hid-pm.rules)0
-rw-r--r--src/udev/rules/50-udev-default.rules (renamed from rules/50-udev-default.rules)0
-rw-r--r--src/udev/rules/60-persistent-alsa.rules (renamed from rules/60-persistent-alsa.rules)0
-rw-r--r--src/udev/rules/60-persistent-input.rules (renamed from rules/60-persistent-input.rules)0
-rw-r--r--src/udev/rules/60-persistent-serial.rules (renamed from rules/60-persistent-serial.rules)0
-rw-r--r--src/udev/rules/60-persistent-storage-tape.rules (renamed from rules/60-persistent-storage-tape.rules)0
-rw-r--r--src/udev/rules/60-persistent-storage.rules (renamed from rules/60-persistent-storage.rules)0
-rw-r--r--src/udev/rules/75-net-description.rules (renamed from rules/75-net-description.rules)0
-rw-r--r--src/udev/rules/75-tty-description.rules (renamed from rules/75-tty-description.rules)0
-rw-r--r--src/udev/rules/78-sound-card.rules (renamed from rules/78-sound-card.rules)0
-rw-r--r--src/udev/rules/80-drivers.rules (renamed from rules/80-drivers.rules)0
-rw-r--r--src/udev/rules/95-udev-late.rules (renamed from rules/95-udev-late.rules)0
-rw-r--r--src/udev/src/.gitignore5
-rw-r--r--src/udev/src/COPYING (renamed from src/COPYING)0
-rw-r--r--src/udev/src/accelerometer/61-accelerometer.rules (renamed from src/accelerometer/61-accelerometer.rules)0
-rw-r--r--src/udev/src/accelerometer/accelerometer.c (renamed from src/accelerometer/accelerometer.c)0
-rw-r--r--src/udev/src/ata_id/ata_id.c (renamed from src/ata_id/ata_id.c)0
-rw-r--r--src/udev/src/cdrom_id/60-cdrom_id.rules (renamed from src/cdrom_id/60-cdrom_id.rules)0
-rw-r--r--src/udev/src/cdrom_id/cdrom_id.c (renamed from src/cdrom_id/cdrom_id.c)0
-rw-r--r--src/udev/src/collect/collect.c (renamed from src/collect/collect.c)0
-rw-r--r--src/udev/src/docs/.gitignore (renamed from src/docs/.gitignore)0
-rw-r--r--src/udev/src/docs/Makefile.am (renamed from src/docs/Makefile.am)0
-rw-r--r--src/udev/src/docs/libudev-docs.xml (renamed from src/docs/libudev-docs.xml)0
-rw-r--r--src/udev/src/docs/libudev-sections.txt (renamed from src/docs/libudev-sections.txt)0
-rw-r--r--src/udev/src/docs/libudev.types (renamed from src/docs/libudev.types)0
-rw-r--r--src/udev/src/docs/version.xml.in (renamed from src/docs/version.xml.in)0
-rw-r--r--src/udev/src/floppy/60-floppy.rules (renamed from src/floppy/60-floppy.rules)0
-rw-r--r--src/udev/src/floppy/create_floppy_devices.c (renamed from src/floppy/create_floppy_devices.c)0
-rw-r--r--src/udev/src/gudev/.gitignore (renamed from src/gudev/.gitignore)0
-rw-r--r--src/udev/src/gudev/COPYING (renamed from src/gudev/COPYING)0
-rw-r--r--src/udev/src/gudev/docs/.gitignore (renamed from src/gudev/docs/.gitignore)0
-rw-r--r--src/udev/src/gudev/docs/Makefile.am (renamed from src/gudev/docs/Makefile.am)0
-rw-r--r--src/udev/src/gudev/docs/gudev-docs.xml (renamed from src/gudev/docs/gudev-docs.xml)0
-rw-r--r--src/udev/src/gudev/docs/gudev-sections.txt (renamed from src/gudev/docs/gudev-sections.txt)0
-rw-r--r--src/udev/src/gudev/docs/gudev.types (renamed from src/gudev/docs/gudev.types)0
-rw-r--r--src/udev/src/gudev/docs/version.xml.in (renamed from src/gudev/docs/version.xml.in)0
-rwxr-xr-xsrc/udev/src/gudev/gjs-example.js (renamed from src/gudev/gjs-example.js)0
-rw-r--r--src/udev/src/gudev/gudev-1.0.pc.in (renamed from src/gudev/gudev-1.0.pc.in)0
-rw-r--r--src/udev/src/gudev/gudev.h (renamed from src/gudev/gudev.h)0
-rw-r--r--src/udev/src/gudev/gudevclient.c (renamed from src/gudev/gudevclient.c)0
-rw-r--r--src/udev/src/gudev/gudevclient.h (renamed from src/gudev/gudevclient.h)0
-rw-r--r--src/udev/src/gudev/gudevdevice.c (renamed from src/gudev/gudevdevice.c)0
-rw-r--r--src/udev/src/gudev/gudevdevice.h (renamed from src/gudev/gudevdevice.h)0
-rw-r--r--src/udev/src/gudev/gudevenumerator.c (renamed from src/gudev/gudevenumerator.c)0
-rw-r--r--src/udev/src/gudev/gudevenumerator.h (renamed from src/gudev/gudevenumerator.h)0
-rw-r--r--src/udev/src/gudev/gudevenums.h (renamed from src/gudev/gudevenums.h)0
-rw-r--r--src/udev/src/gudev/gudevenumtypes.c.template (renamed from src/gudev/gudevenumtypes.c.template)0
-rw-r--r--src/udev/src/gudev/gudevenumtypes.h.template (renamed from src/gudev/gudevenumtypes.h.template)0
-rw-r--r--src/udev/src/gudev/gudevmarshal.list (renamed from src/gudev/gudevmarshal.list)0
-rw-r--r--src/udev/src/gudev/gudevprivate.h (renamed from src/gudev/gudevprivate.h)0
-rw-r--r--src/udev/src/gudev/gudevtypes.h (renamed from src/gudev/gudevtypes.h)0
-rwxr-xr-xsrc/udev/src/gudev/seed-example-enum.js (renamed from src/gudev/seed-example-enum.js)0
-rwxr-xr-xsrc/udev/src/gudev/seed-example.js (renamed from src/gudev/seed-example.js)0
-rw-r--r--src/udev/src/keymap/.gitignore (renamed from src/keymap/.gitignore)0
-rw-r--r--src/udev/src/keymap/95-keyboard-force-release.rules (renamed from src/keymap/95-keyboard-force-release.rules)0
-rw-r--r--src/udev/src/keymap/95-keymap.rules (renamed from src/keymap/95-keymap.rules)0
-rw-r--r--src/udev/src/keymap/README.keymap.txt (renamed from src/keymap/README.keymap.txt)0
-rwxr-xr-xsrc/udev/src/keymap/check-keymaps.sh (renamed from src/keymap/check-keymaps.sh)0
-rwxr-xr-xsrc/udev/src/keymap/findkeyboards (renamed from src/keymap/findkeyboards)0
-rw-r--r--src/udev/src/keymap/force-release-maps/common-volume-keys (renamed from src/keymap/force-release-maps/common-volume-keys)0
-rw-r--r--src/udev/src/keymap/force-release-maps/dell-touchpad (renamed from src/keymap/force-release-maps/dell-touchpad)0
-rw-r--r--src/udev/src/keymap/force-release-maps/hp-other (renamed from src/keymap/force-release-maps/hp-other)0
-rw-r--r--src/udev/src/keymap/force-release-maps/samsung-90x3a (renamed from src/keymap/force-release-maps/samsung-90x3a)0
-rw-r--r--src/udev/src/keymap/force-release-maps/samsung-other (renamed from src/keymap/force-release-maps/samsung-other)0
-rwxr-xr-xsrc/udev/src/keymap/keyboard-force-release.sh.in (renamed from src/keymap/keyboard-force-release.sh.in)0
-rw-r--r--src/udev/src/keymap/keymap.c (renamed from src/keymap/keymap.c)0
-rw-r--r--src/udev/src/keymap/keymaps/acer (renamed from src/keymap/keymaps/acer)0
-rw-r--r--src/udev/src/keymap/keymaps/acer-aspire_5720 (renamed from src/keymap/keymaps/acer-aspire_5720)0
-rw-r--r--src/udev/src/keymap/keymaps/acer-aspire_5920g (renamed from src/keymap/keymaps/acer-aspire_5920g)0
-rw-r--r--src/udev/src/keymap/keymaps/acer-aspire_6920 (renamed from src/keymap/keymaps/acer-aspire_6920)0
-rw-r--r--src/udev/src/keymap/keymaps/acer-aspire_8930 (renamed from src/keymap/keymaps/acer-aspire_8930)0
-rw-r--r--src/udev/src/keymap/keymaps/acer-travelmate_c300 (renamed from src/keymap/keymaps/acer-travelmate_c300)0
-rw-r--r--src/udev/src/keymap/keymaps/asus (renamed from src/keymap/keymaps/asus)0
-rw-r--r--src/udev/src/keymap/keymaps/compaq-e_evo (renamed from src/keymap/keymaps/compaq-e_evo)0
-rw-r--r--src/udev/src/keymap/keymaps/dell (renamed from src/keymap/keymaps/dell)0
-rw-r--r--src/udev/src/keymap/keymaps/dell-latitude-xt2 (renamed from src/keymap/keymaps/dell-latitude-xt2)0
-rw-r--r--src/udev/src/keymap/keymaps/everex-xt5000 (renamed from src/keymap/keymaps/everex-xt5000)0
-rw-r--r--src/udev/src/keymap/keymaps/fujitsu-amilo_li_2732 (renamed from src/keymap/keymaps/fujitsu-amilo_li_2732)0
-rw-r--r--src/udev/src/keymap/keymaps/fujitsu-amilo_pa_2548 (renamed from src/keymap/keymaps/fujitsu-amilo_pa_2548)0
-rw-r--r--src/udev/src/keymap/keymaps/fujitsu-amilo_pro_edition_v3505 (renamed from src/keymap/keymaps/fujitsu-amilo_pro_edition_v3505)0
-rw-r--r--src/udev/src/keymap/keymaps/fujitsu-amilo_pro_v3205 (renamed from src/keymap/keymaps/fujitsu-amilo_pro_v3205)0
-rw-r--r--src/udev/src/keymap/keymaps/fujitsu-amilo_si_1520 (renamed from src/keymap/keymaps/fujitsu-amilo_si_1520)0
-rw-r--r--src/udev/src/keymap/keymaps/fujitsu-esprimo_mobile_v5 (renamed from src/keymap/keymaps/fujitsu-esprimo_mobile_v5)0
-rw-r--r--src/udev/src/keymap/keymaps/fujitsu-esprimo_mobile_v6 (renamed from src/keymap/keymaps/fujitsu-esprimo_mobile_v6)0
-rw-r--r--src/udev/src/keymap/keymaps/genius-slimstar-320 (renamed from src/keymap/keymaps/genius-slimstar-320)0
-rw-r--r--src/udev/src/keymap/keymaps/hewlett-packard (renamed from src/keymap/keymaps/hewlett-packard)0
-rw-r--r--src/udev/src/keymap/keymaps/hewlett-packard-2510p_2530p (renamed from src/keymap/keymaps/hewlett-packard-2510p_2530p)0
-rw-r--r--src/udev/src/keymap/keymaps/hewlett-packard-compaq_elitebook (renamed from src/keymap/keymaps/hewlett-packard-compaq_elitebook)0
-rw-r--r--src/udev/src/keymap/keymaps/hewlett-packard-pavilion (renamed from src/keymap/keymaps/hewlett-packard-pavilion)0
-rw-r--r--src/udev/src/keymap/keymaps/hewlett-packard-presario-2100 (renamed from src/keymap/keymaps/hewlett-packard-presario-2100)0
-rw-r--r--src/udev/src/keymap/keymaps/hewlett-packard-tablet (renamed from src/keymap/keymaps/hewlett-packard-tablet)0
-rw-r--r--src/udev/src/keymap/keymaps/hewlett-packard-tx2 (renamed from src/keymap/keymaps/hewlett-packard-tx2)0
-rw-r--r--src/udev/src/keymap/keymaps/ibm-thinkpad-usb-keyboard-trackpoint (renamed from src/keymap/keymaps/ibm-thinkpad-usb-keyboard-trackpoint)0
-rw-r--r--src/udev/src/keymap/keymaps/inventec-symphony_6.0_7.0 (renamed from src/keymap/keymaps/inventec-symphony_6.0_7.0)0
-rw-r--r--src/udev/src/keymap/keymaps/lenovo-3000 (renamed from src/keymap/keymaps/lenovo-3000)0
-rw-r--r--src/udev/src/keymap/keymaps/lenovo-ideapad (renamed from src/keymap/keymaps/lenovo-ideapad)0
-rw-r--r--src/udev/src/keymap/keymaps/lenovo-thinkpad-usb-keyboard-trackpoint (renamed from src/keymap/keymaps/lenovo-thinkpad-usb-keyboard-trackpoint)0
-rw-r--r--src/udev/src/keymap/keymaps/lenovo-thinkpad_x200_tablet (renamed from src/keymap/keymaps/lenovo-thinkpad_x200_tablet)0
-rw-r--r--src/udev/src/keymap/keymaps/lenovo-thinkpad_x6_tablet (renamed from src/keymap/keymaps/lenovo-thinkpad_x6_tablet)0
-rw-r--r--src/udev/src/keymap/keymaps/lg-x110 (renamed from src/keymap/keymaps/lg-x110)0
-rw-r--r--src/udev/src/keymap/keymaps/logitech-wave (renamed from src/keymap/keymaps/logitech-wave)0
-rw-r--r--src/udev/src/keymap/keymaps/logitech-wave-cordless (renamed from src/keymap/keymaps/logitech-wave-cordless)0
-rw-r--r--src/udev/src/keymap/keymaps/logitech-wave-pro-cordless (renamed from src/keymap/keymaps/logitech-wave-pro-cordless)0
-rw-r--r--src/udev/src/keymap/keymaps/maxdata-pro_7000 (renamed from src/keymap/keymaps/maxdata-pro_7000)0
-rw-r--r--src/udev/src/keymap/keymaps/medion-fid2060 (renamed from src/keymap/keymaps/medion-fid2060)0
-rw-r--r--src/udev/src/keymap/keymaps/medionnb-a555 (renamed from src/keymap/keymaps/medionnb-a555)0
-rw-r--r--src/udev/src/keymap/keymaps/micro-star (renamed from src/keymap/keymaps/micro-star)0
-rw-r--r--src/udev/src/keymap/keymaps/module-asus-w3j (renamed from src/keymap/keymaps/module-asus-w3j)0
-rw-r--r--src/udev/src/keymap/keymaps/module-ibm (renamed from src/keymap/keymaps/module-ibm)0
-rw-r--r--src/udev/src/keymap/keymaps/module-lenovo (renamed from src/keymap/keymaps/module-lenovo)0
-rw-r--r--src/udev/src/keymap/keymaps/module-sony (renamed from src/keymap/keymaps/module-sony)0
-rw-r--r--src/udev/src/keymap/keymaps/module-sony-old (renamed from src/keymap/keymaps/module-sony-old)0
-rw-r--r--src/udev/src/keymap/keymaps/module-sony-vgn (renamed from src/keymap/keymaps/module-sony-vgn)0
-rw-r--r--src/udev/src/keymap/keymaps/olpc-xo (renamed from src/keymap/keymaps/olpc-xo)0
-rw-r--r--src/udev/src/keymap/keymaps/onkyo (renamed from src/keymap/keymaps/onkyo)0
-rw-r--r--src/udev/src/keymap/keymaps/oqo-model2 (renamed from src/keymap/keymaps/oqo-model2)0
-rw-r--r--src/udev/src/keymap/keymaps/samsung-90x3a (renamed from src/keymap/keymaps/samsung-90x3a)0
-rw-r--r--src/udev/src/keymap/keymaps/samsung-other (renamed from src/keymap/keymaps/samsung-other)0
-rw-r--r--src/udev/src/keymap/keymaps/samsung-sq1us (renamed from src/keymap/keymaps/samsung-sq1us)0
-rw-r--r--src/udev/src/keymap/keymaps/samsung-sx20s (renamed from src/keymap/keymaps/samsung-sx20s)0
-rw-r--r--src/udev/src/keymap/keymaps/toshiba-satellite_a100 (renamed from src/keymap/keymaps/toshiba-satellite_a100)0
-rw-r--r--src/udev/src/keymap/keymaps/toshiba-satellite_a110 (renamed from src/keymap/keymaps/toshiba-satellite_a110)0
-rw-r--r--src/udev/src/keymap/keymaps/toshiba-satellite_m30x (renamed from src/keymap/keymaps/toshiba-satellite_m30x)0
-rw-r--r--src/udev/src/keymap/keymaps/zepto-znote (renamed from src/keymap/keymaps/zepto-znote)0
-rw-r--r--src/udev/src/libudev-device-private.c (renamed from src/libudev-device-private.c)0
-rw-r--r--src/udev/src/libudev-device.c (renamed from src/libudev-device.c)0
-rw-r--r--src/udev/src/libudev-enumerate.c (renamed from src/libudev-enumerate.c)0
-rw-r--r--src/udev/src/libudev-list.c (renamed from src/libudev-list.c)0
-rw-r--r--src/udev/src/libudev-monitor.c (renamed from src/libudev-monitor.c)0
-rw-r--r--src/udev/src/libudev-private.h (renamed from src/libudev-private.h)0
-rw-r--r--src/udev/src/libudev-queue-private.c (renamed from src/libudev-queue-private.c)0
-rw-r--r--src/udev/src/libudev-queue.c (renamed from src/libudev-queue.c)0
-rw-r--r--src/udev/src/libudev-selinux-private.c (renamed from src/libudev-selinux-private.c)0
-rw-r--r--src/udev/src/libudev-util-private.c (renamed from src/libudev-util-private.c)0
-rw-r--r--src/udev/src/libudev-util.c (renamed from src/libudev-util.c)0
-rw-r--r--src/udev/src/libudev.c (renamed from src/libudev.c)0
-rw-r--r--src/udev/src/libudev.h (renamed from src/libudev.h)0
-rw-r--r--src/udev/src/libudev.pc.in (renamed from src/libudev.pc.in)0
-rw-r--r--src/udev/src/mtd_probe/75-probe_mtd.rules (renamed from src/mtd_probe/75-probe_mtd.rules)0
-rw-r--r--src/udev/src/mtd_probe/mtd_probe.c (renamed from src/mtd_probe/mtd_probe.c)0
-rw-r--r--src/udev/src/mtd_probe/mtd_probe.h (renamed from src/mtd_probe/mtd_probe.h)0
-rw-r--r--src/udev/src/mtd_probe/probe_smartmedia.c (renamed from src/mtd_probe/probe_smartmedia.c)0
-rw-r--r--src/udev/src/rule_generator/75-cd-aliases-generator.rules (renamed from src/rule_generator/75-cd-aliases-generator.rules)0
-rw-r--r--src/udev/src/rule_generator/75-persistent-net-generator.rules (renamed from src/rule_generator/75-persistent-net-generator.rules)0
-rw-r--r--src/udev/src/rule_generator/rule_generator.functions (renamed from src/rule_generator/rule_generator.functions)0
-rw-r--r--src/udev/src/rule_generator/write_cd_rules (renamed from src/rule_generator/write_cd_rules)0
-rw-r--r--src/udev/src/rule_generator/write_net_rules (renamed from src/rule_generator/write_net_rules)0
-rw-r--r--src/udev/src/scsi_id/.gitignore (renamed from src/scsi_id/.gitignore)0
-rw-r--r--src/udev/src/scsi_id/README (renamed from src/scsi_id/README)0
-rw-r--r--src/udev/src/scsi_id/scsi.h (renamed from src/scsi_id/scsi.h)0
-rw-r--r--src/udev/src/scsi_id/scsi_id.8 (renamed from src/scsi_id/scsi_id.8)0
-rw-r--r--src/udev/src/scsi_id/scsi_id.c (renamed from src/scsi_id/scsi_id.c)0
-rw-r--r--src/udev/src/scsi_id/scsi_id.h (renamed from src/scsi_id/scsi_id.h)0
-rw-r--r--src/udev/src/scsi_id/scsi_serial.c (renamed from src/scsi_id/scsi_serial.c)0
-rw-r--r--src/udev/src/sd-daemon.c530
-rw-r--r--src/udev/src/sd-daemon.h282
-rw-r--r--src/udev/src/test-libudev.c (renamed from src/test-libudev.c)0
-rw-r--r--src/udev/src/test-udev.c (renamed from src/test-udev.c)0
-rw-r--r--src/udev/src/udev-builtin-blkid.c (renamed from src/udev-builtin-blkid.c)0
-rw-r--r--src/udev/src/udev-builtin-firmware.c (renamed from src/udev-builtin-firmware.c)0
-rw-r--r--src/udev/src/udev-builtin-hwdb.c (renamed from src/udev-builtin-hwdb.c)0
-rw-r--r--src/udev/src/udev-builtin-input_id.c (renamed from src/udev-builtin-input_id.c)0
-rw-r--r--src/udev/src/udev-builtin-kmod.c (renamed from src/udev-builtin-kmod.c)0
-rw-r--r--src/udev/src/udev-builtin-path_id.c (renamed from src/udev-builtin-path_id.c)0
-rw-r--r--src/udev/src/udev-builtin-usb_id.c (renamed from src/udev-builtin-usb_id.c)0
-rw-r--r--src/udev/src/udev-builtin.c (renamed from src/udev-builtin.c)0
-rw-r--r--src/udev/src/udev-control.socket (renamed from src/udev-control.socket)0
-rw-r--r--src/udev/src/udev-ctrl.c (renamed from src/udev-ctrl.c)0
-rw-r--r--src/udev/src/udev-event.c (renamed from src/udev-event.c)0
-rw-r--r--src/udev/src/udev-kernel.socket (renamed from src/udev-kernel.socket)0
-rw-r--r--src/udev/src/udev-node.c (renamed from src/udev-node.c)0
-rw-r--r--src/udev/src/udev-rules.c (renamed from src/udev-rules.c)0
-rw-r--r--src/udev/src/udev-settle.service.in (renamed from src/udev-settle.service.in)0
-rw-r--r--src/udev/src/udev-trigger.service.in (renamed from src/udev-trigger.service.in)0
-rw-r--r--src/udev/src/udev-watch.c (renamed from src/udev-watch.c)0
-rw-r--r--src/udev/src/udev.conf (renamed from src/udev.conf)0
-rw-r--r--src/udev/src/udev.h (renamed from src/udev.h)0
-rw-r--r--src/udev/src/udev.pc.in (renamed from src/udev.pc.in)0
-rw-r--r--src/udev/src/udev.service.in (renamed from src/udev.service.in)0
-rw-r--r--src/udev/src/udev.xml (renamed from src/udev.xml)0
-rw-r--r--src/udev/src/udevadm-control.c (renamed from src/udevadm-control.c)0
-rw-r--r--src/udev/src/udevadm-info.c (renamed from src/udevadm-info.c)0
-rw-r--r--src/udev/src/udevadm-monitor.c (renamed from src/udevadm-monitor.c)0
-rw-r--r--src/udev/src/udevadm-settle.c (renamed from src/udevadm-settle.c)0
-rw-r--r--src/udev/src/udevadm-test-builtin.c (renamed from src/udevadm-test-builtin.c)0
-rw-r--r--src/udev/src/udevadm-test.c (renamed from src/udevadm-test.c)0
-rw-r--r--src/udev/src/udevadm-trigger.c (renamed from src/udevadm-trigger.c)0
-rw-r--r--src/udev/src/udevadm.c (renamed from src/udevadm.c)0
-rw-r--r--src/udev/src/udevadm.xml (renamed from src/udevadm.xml)0
-rw-r--r--src/udev/src/udevd.c (renamed from src/udevd.c)0
-rw-r--r--src/udev/src/udevd.xml (renamed from src/udevd.xml)0
-rw-r--r--src/udev/src/v4l_id/60-persistent-v4l.rules (renamed from src/v4l_id/60-persistent-v4l.rules)0
-rw-r--r--src/udev/src/v4l_id/v4l_id.c (renamed from src/v4l_id/v4l_id.c)0
-rw-r--r--src/udev/test/.gitignore (renamed from test/.gitignore)0
-rwxr-xr-xsrc/udev/test/rule-syntax-check.py (renamed from test/rule-syntax-check.py)0
-rwxr-xr-xsrc/udev/test/rules-test.sh (renamed from test/rules-test.sh)0
-rw-r--r--src/udev/test/sys.tar.xz (renamed from test/sys.tar.xz)bin165116 -> 165116 bytes
-rwxr-xr-xsrc/udev/test/udev-test.pl (renamed from test/udev-test.pl)0
-rw-r--r--src/umount.c644
-rw-r--r--src/umount.h33
-rw-r--r--src/unit-name.c448
-rw-r--r--src/unit-name.h57
-rw-r--r--src/unit.c2676
-rw-r--r--src/unit.h562
-rw-r--r--src/update-utmp.c423
-rw-r--r--src/user.conf17
-rw-r--r--src/utf8.c214
-rw-r--r--src/utf8.h33
-rw-r--r--src/util.c6256
-rw-r--r--src/util.h544
-rw-r--r--src/utmp-wtmp.c430
-rw-r--r--src/utmp-wtmp.h38
l---------src/vconsole/Makefile1
-rw-r--r--src/vconsole/vconsole-setup.c459
-rw-r--r--src/virt.c292
-rw-r--r--src/virt.h38
-rw-r--r--sysctl.d/.gitignore1
l---------sysctl.d/Makefile1
-rw-r--r--sysctl.d/coredump.conf.in10
-rw-r--r--test/Makefile7
-rw-r--r--test/a.service7
-rw-r--r--test/b.service6
-rw-r--r--test/c.service6
-rw-r--r--test/d.service8
-rw-r--r--test/e.service8
-rw-r--r--test/f.service5
-rw-r--r--test/g.service6
-rw-r--r--test/h.service6
l---------tmpfiles.d/Makefile1
-rw-r--r--tmpfiles.d/legacy.conf22
-rw-r--r--tmpfiles.d/systemd.conf25
-rw-r--r--tmpfiles.d/tmp.conf12
-rw-r--r--tmpfiles.d/x11.conf18
-rw-r--r--units/.gitignore42
l---------units/Makefile1
-rw-r--r--units/basic.target14
-rw-r--r--units/bluetooth.target12
-rw-r--r--units/console-shell.service.m447
-rw-r--r--units/cryptsetup.target11
-rw-r--r--units/dev-hugepages.mount17
-rw-r--r--units/dev-mqueue.mount17
-rw-r--r--units/emergency.service31
-rw-r--r--units/emergency.target14
l---------units/fedora/Makefile1
-rw-r--r--units/fedora/halt-local.service20
-rw-r--r--units/fedora/prefdm.service21
-rw-r--r--units/fedora/rc-local.service19
-rw-r--r--units/final.target14
-rw-r--r--units/frugalware/display-manager.service16
-rw-r--r--units/fsck-root.service.in23
-rw-r--r--units/fsck@.service.in20
-rw-r--r--units/getty.target9
-rw-r--r--units/getty@.service.m458
-rw-r--r--units/graphical.target18
-rw-r--r--units/halt.service.in16
-rw-r--r--units/halt.target18
-rw-r--r--units/http-daemon.target14
-rw-r--r--units/kexec.service.in16
-rw-r--r--units/kexec.target18
-rw-r--r--units/local-fs-pre.target11
-rw-r--r--units/local-fs.target13
-rw-r--r--units/mageia/prefdm.service21
-rw-r--r--units/mail-transfer-agent.target14
-rw-r--r--units/mandriva/prefdm.service21
-rw-r--r--units/multi-user.target18
-rw-r--r--units/network.target11
-rw-r--r--units/nss-lookup.target15
-rw-r--r--units/plymouth-halt.service18
-rw-r--r--units/plymouth-kexec.service18
-rw-r--r--units/plymouth-poweroff.service18
-rw-r--r--units/plymouth-quit-wait.service15
-rw-r--r--units/plymouth-quit.service15
-rw-r--r--units/plymouth-read-write.service16
-rw-r--r--units/plymouth-reboot.service18
-rw-r--r--units/plymouth-start.service22
-rw-r--r--units/poweroff.service.in16
-rw-r--r--units/poweroff.target18
-rw-r--r--units/printer.target12
-rw-r--r--units/proc-sys-fs-binfmt_misc.automount15
-rw-r--r--units/proc-sys-fs-binfmt_misc.mount15
-rw-r--r--units/quotacheck.service.in19
-rw-r--r--units/quotaon.service18
-rw-r--r--units/reboot.service.in16
-rw-r--r--units/reboot.target18
-rw-r--r--units/remote-fs-pre.target12
-rw-r--r--units/remote-fs.target14
-rw-r--r--units/remount-rootfs.service19
-rw-r--r--units/rescue.service.m443
-rw-r--r--units/rescue.target17
-rw-r--r--units/rpcbind.target14
-rw-r--r--units/serial-getty@.service.m450
-rw-r--r--units/shutdown.target13
-rw-r--r--units/sigpwr.target11
-rw-r--r--units/smartcard.target12
-rw-r--r--units/sockets.target11
-rw-r--r--units/sound.target12
-rw-r--r--units/suse/halt-local.service20
-rw-r--r--units/suse/rc-local.service19
-rw-r--r--units/swap.target11
-rw-r--r--units/sys-fs-fuse-connections.mount18
-rw-r--r--units/sys-kernel-config.mount18
-rw-r--r--units/sys-kernel-debug.mount17
-rw-r--r--units/sysinit.target15
-rw-r--r--units/syslog.socket40
-rw-r--r--units/syslog.target19
-rw-r--r--units/systemd-ask-password-console.path18
-rw-r--r--units/systemd-ask-password-console.service.in17
-rw-r--r--units/systemd-ask-password-plymouth.path19
-rw-r--r--units/systemd-ask-password-plymouth.service.in18
-rw-r--r--units/systemd-ask-password-wall.path16
-rw-r--r--units/systemd-ask-password-wall.service.in15
-rw-r--r--units/systemd-binfmt.service.in22
-rw-r--r--units/systemd-hostnamed.service.in17
-rw-r--r--units/systemd-initctl.service.in16
-rw-r--r--units/systemd-initctl.socket17
-rw-r--r--units/systemd-journald.service.in25
-rw-r--r--units/systemd-journald.socket27
-rw-r--r--units/systemd-localed.service.in17
-rw-r--r--units/systemd-logind.service.in21
-rw-r--r--units/systemd-modules-load.service.in23
-rw-r--r--units/systemd-random-seed-load.service.in18
-rw-r--r--units/systemd-random-seed-save.service.in17
-rw-r--r--units/systemd-readahead-collect.service.in22
-rw-r--r--units/systemd-readahead-done.service.in20
-rw-r--r--units/systemd-readahead-done.timer19
-rw-r--r--units/systemd-readahead-replay.service.in22
-rw-r--r--units/systemd-remount-api-vfs.service.in19
-rw-r--r--units/systemd-shutdownd.service.in16
-rw-r--r--units/systemd-shutdownd.socket19
-rw-r--r--units/systemd-sysctl.service.in24
-rw-r--r--units/systemd-timedated.service.in17
-rw-r--r--units/systemd-tmpfiles-clean.service.in22
-rw-r--r--units/systemd-tmpfiles-clean.timer13
-rw-r--r--units/systemd-tmpfiles-setup.service.in22
-rw-r--r--units/systemd-update-utmp-runlevel.service.in16
-rw-r--r--units/systemd-update-utmp-shutdown.service.in16
-rw-r--r--units/systemd-user-sessions.service.in16
-rw-r--r--units/systemd-vconsole-setup.service.in18
-rw-r--r--units/time-sync.target14
-rw-r--r--units/tmp.mount16
-rw-r--r--units/umount.target13
-rw-r--r--units/user/.gitignore1
l---------units/user/Makefile1
-rw-r--r--units/user/default.target11
-rw-r--r--units/user/exit.service.in18
-rw-r--r--units/user/exit.target18
-rw-r--r--units/user@.service.in19
741 files changed, 134817 insertions, 2710 deletions
diff --git a/.dir-locals.el b/.dir-locals.el
new file mode 100644
index 0000000000..9d9f8cd178
--- /dev/null
+++ b/.dir-locals.el
@@ -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)))
+)
diff --git a/.gitignore b/.gitignore
index fa3500ba96..f36dd8a81a 100644
--- a/.gitignore
+++ b/.gitignore
@@ -1,40 +1,109 @@
+/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
index 0000000000..04b1c6cba8
--- /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
index 0000000000..9341b48d41
--- /dev/null
+++ b/CODING_STYLE
@@ -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
index 0000000000..2b08bf8116
--- /dev/null
+++ b/DISTRO_PORTING
@@ -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
index 0000000000..d511905c16
--- /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.
diff --git a/Makefile.am b/Makefile.am
index 1c7f86b081..219d8ded8e 100644
--- a/Makefile.am
+++ b/Makefile.am
@@ -1,712 +1,2444 @@
-# 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 f4f6f4e327..c82e637d0e 100644
--- a/NEWS
+++ b/NEWS
@@ -1,1735 +1,279 @@
-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 38459c6b22..6b0eb51ecd 100644
--- a/README
+++ b/README
@@ -1,101 +1,104 @@
-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 8b8b9c8f8c..4f3b157040 100644
--- a/TODO
+++ b/TODO
@@ -1,22 +1,344 @@
- - 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()!
diff --git a/autogen.sh b/autogen.sh
index 55ee03afd1..9ca53772a4 100755
--- a/autogen.sh
+++ b/autogen.sh
@@ -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
diff --git a/configure.ac b/configure.ac
index b31b62f289..9a9a789235 100644
--- a/configure.ac
+++ b/configure.ac
@@ -1,242 +1,657 @@
-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
index 0000000000..593191384a
--- /dev/null
+++ b/introspect.awk
@@ -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>"
+}
diff --git a/m4/.gitignore b/m4/.gitignore
index 0ca2c03722..55eaa803a1 100644
--- a/m4/.gitignore
+++ b/m4/.gitignore
@@ -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
index 0000000000..ccf8afc0aa
--- /dev/null
+++ b/m4/acx_libwrap.m4
@@ -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
index 0000000000..e354375e34
--- /dev/null
+++ b/m4/attributes.m4
@@ -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
index 0000000000..bd1047548b
--- /dev/null
+++ b/man/Makefile
@@ -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
index 0000000000..f5ec805e29
--- /dev/null
+++ b/man/binfmt.d.xml
@@ -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
index 0000000000..2d2f458793
--- /dev/null
+++ b/man/custom-html.xsl
@@ -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
index 0000000000..997ee5b253
--- /dev/null
+++ b/man/daemon.xml
@@ -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
index 0000000000..97a53ba350
--- /dev/null
+++ b/man/halt.xml
@@ -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
index 0000000000..1acda1af5d
--- /dev/null
+++ b/man/hostname.xml
@@ -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
index 0000000000..f6e46cfbc7
--- /dev/null
+++ b/man/journalctl.xml
@@ -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
index 0000000000..a9b0f66de9
--- /dev/null
+++ b/man/journald.conf.xml
@@ -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
index 0000000000..37239974b6
--- /dev/null
+++ b/man/locale.conf.xml
@@ -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
index 0000000000..af1d631cf4
--- /dev/null
+++ b/man/loginctl.xml
@@ -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
index 0000000000..950f81fa93
--- /dev/null
+++ b/man/logind.conf.xml
@@ -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
index 0000000000..97c622c6fa
--- /dev/null
+++ b/man/machine-id.xml
@@ -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
index 0000000000..240da25a6b
--- /dev/null
+++ b/man/machine-info.xml
@@ -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
index 0000000000..e2f7d5c68c
--- /dev/null
+++ b/man/modules-load.d.xml
@@ -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
index 0000000000..ff8fdf16be
--- /dev/null
+++ b/man/os-release.xml
@@ -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
index 0000000000..c07b46bab2
--- /dev/null
+++ b/man/pam_systemd.xml
@@ -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
index 0000000000..160d1b14e7
--- /dev/null
+++ b/man/runlevel.xml
@@ -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
index 0000000000..4ea88e43d7
--- /dev/null
+++ b/man/sd-daemon.xml
@@ -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
index 0000000000..3fc0e16f69
--- /dev/null
+++ b/man/sd-login.xml
@@ -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
index 0000000000..7fb8634120
--- /dev/null
+++ b/man/sd-readahead.xml
@@ -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
index 0000000000..141625d9ad
--- /dev/null
+++ b/man/sd_booted.xml
@@ -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
index 0000000000..2ac76500ec
--- /dev/null
+++ b/man/sd_get_seats.xml
@@ -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
index 0000000000..4db512012c
--- /dev/null
+++ b/man/sd_is_fifo.xml
@@ -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
index 0000000000..c3c70a0df0
--- /dev/null
+++ b/man/sd_listen_fds.xml
@@ -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
index 0000000000..de484329a9
--- /dev/null
+++ b/man/sd_login_monitor_new.xml
@@ -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
index 0000000000..9d9ea4132f
--- /dev/null
+++ b/man/sd_notify.xml
@@ -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
index 0000000000..94f5330222
--- /dev/null
+++ b/man/sd_pid_get_session.xml
@@ -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
index 0000000000..2e7e09c5ec
--- /dev/null
+++ b/man/sd_readahead.xml
@@ -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
index 0000000000..acc6ee4ea7
--- /dev/null
+++ b/man/sd_seat_get_active.xml
@@ -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
index 0000000000..afdeed55d6
--- /dev/null
+++ b/man/sd_session_is_active.xml
@@ -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
index 0000000000..9249021aa1
--- /dev/null
+++ b/man/sd_uid_get_state.xml
@@ -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
index 0000000000..c8c4b54620
--- /dev/null
+++ b/man/shutdown.xml
@@ -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
index 0000000000..20f2e24820
--- /dev/null
+++ b/man/sysctl.d.xml
@@ -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
index 0000000000..167a72b58b
--- /dev/null
+++ b/man/systemctl.xml
@@ -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
index 0000000000..c607b1a367
--- /dev/null
+++ b/man/systemd-ask-password.xml
@@ -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
index 0000000000..350a345d43
--- /dev/null
+++ b/man/systemd-cat.xml
@@ -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
index 0000000000..1e53147e1b
--- /dev/null
+++ b/man/systemd-cgls.xml
@@ -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
index 0000000000..2d67ae5ef4
--- /dev/null
+++ b/man/systemd-cgtop.xml
@@ -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
index 0000000000..49b92f6891
--- /dev/null
+++ b/man/systemd-machine-id-setup.xml
@@ -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
index 0000000000..c5ffafe895
--- /dev/null
+++ b/man/systemd-notify.xml
@@ -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
index 0000000000..dbd2ff5a8a
--- /dev/null
+++ b/man/systemd-nspawn.xml
@@ -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
index 0000000000..bbb80b2f98
--- /dev/null
+++ b/man/systemd-tmpfiles.xml
@@ -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
index 0000000000..754d1e37f3
--- /dev/null
+++ b/man/systemd.automount.xml
@@ -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
index 0000000000..ba144da8cf
--- /dev/null
+++ b/man/systemd.conf.xml
@@ -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
index 0000000000..63863bebdf
--- /dev/null
+++ b/man/systemd.device.xml
@@ -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
index 0000000000..e6f49c9fd0
--- /dev/null
+++ b/man/systemd.exec.xml
@@ -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
index 0000000000..8f1cc514cf
--- /dev/null
+++ b/man/systemd.mount.xml
@@ -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
index 0000000000..5b1ff75f7a
--- /dev/null
+++ b/man/systemd.path.xml
@@ -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
index 0000000000..837a992ba4
--- /dev/null
+++ b/man/systemd.service.xml
@@ -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
index 0000000000..a3e23225c6
--- /dev/null
+++ b/man/systemd.snapshot.xml
@@ -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
index 0000000000..d9921e496d
--- /dev/null
+++ b/man/systemd.socket.xml
@@ -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
index 0000000000..116a43ccfb
--- /dev/null
+++ b/man/systemd.special.xml
@@ -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
index 0000000000..ab00f9f318
--- /dev/null
+++ b/man/systemd.swap.xml
@@ -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
index 0000000000..6b1dbfbde3
--- /dev/null
+++ b/man/systemd.target.xml
@@ -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
index 0000000000..9b6b486bf4
--- /dev/null
+++ b/man/systemd.timer.xml
@@ -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
index 0000000000..3cc126b12a
--- /dev/null
+++ b/man/systemd.unit.xml
@@ -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
index 0000000000..aef65e30c1
--- /dev/null
+++ b/man/systemd.xml
@@ -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
index 0000000000..fec059aa66
--- /dev/null
+++ b/man/telinit.xml
@@ -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
index 0000000000..4e33279158
--- /dev/null
+++ b/man/timezone.xml
@@ -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
index 0000000000..f70bf0ef9f
--- /dev/null
+++ b/man/tmpfiles.d.xml
@@ -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
index 0000000000..a73db00d1c
--- /dev/null
+++ b/man/vconsole.conf.xml
@@ -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
index 0000000000..1fa8d3fd56
--- /dev/null
+++ b/po/.gitignore
@@ -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
index 0000000000..ed2c308db8
--- /dev/null
+++ b/po/POTFILES.in
@@ -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
index 0000000000..5a5d027493
--- /dev/null
+++ b/po/POTFILES.skip
@@ -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
index 0000000000..2581d01fcc
--- /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ć"
diff --git a/src/.gitignore b/src/.gitignore
index beb8604bc6..4b123f86d2 100644
--- a/src/.gitignore
+++ b/src/.gitignore
@@ -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
index 0000000000..d306f71b63
--- /dev/null
+++ b/src/99-systemd.rules.in
@@ -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
index 0000000000..bc7e9fa363
--- /dev/null
+++ b/src/Makefile
@@ -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
index 0000000000..24a68e717e
--- /dev/null
+++ b/src/ac-power.c
@@ -0,0 +1,111 @@
+/*-*- Mode: C; c-basic-offset: 8; indent-tabs-mode: nil -*-*/
+
+/***
+ This file is part of systemd.
+
+ Copyright 2010 Lennart Poettering
+
+ systemd is free software; you can redistribute it and/or modify it
+ under the terms of the GNU General Public License as published by
+ the Free Software Foundation; either version 2 of the License, or
+ (at your option) any later version.
+
+ systemd is distributed in the hope that it will be useful, but
+ WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ General Public License for more 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
index 0000000000..a2a9f9a22b
--- /dev/null
+++ b/src/acl-util.c
@@ -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
index 0000000000..798ce43364
--- /dev/null
+++ b/src/acl-util.h
@@ -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
index 0000000000..ce2f3cbe77
--- /dev/null
+++ b/src/ask-password-api.c
@@ -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
index 0000000000..fec8625a0f
--- /dev/null
+++ b/src/ask-password-api.h
@@ -0,0 +1,33 @@
+/*-*- Mode: C; c-basic-offset: 8; indent-tabs-mode: nil -*-*/
+
+#ifndef fooaskpasswordapihfoo
+#define fooaskpasswordapihfoo
+
+/***
+ This file is part of systemd.
+
+ Copyright 2010 Lennart Poettering
+
+ systemd is free software; you can redistribute it and/or modify it
+ under the terms of the GNU General Public License as published by
+ the Free Software Foundation; either version 2 of the License, or
+ (at your option) any later version.
+
+ systemd is distributed in the hope that it will be useful, but
+ WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with systemd; If not, see <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
index 0000000000..5162f62eee
--- /dev/null
+++ b/src/ask-password.c
@@ -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
index 0000000000..cf2fb60cdf
--- /dev/null
+++ b/src/automount.c
@@ -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
index 0000000000..19baee208b
--- /dev/null
+++ b/src/automount.h
@@ -0,0 +1,76 @@
+/*-*- Mode: C; c-basic-offset: 8; indent-tabs-mode: nil -*-*/
+
+#ifndef fooautomounthfoo
+#define fooautomounthfoo
+
+/***
+ This file is part of systemd.
+
+ Copyright 2010 Lennart Poettering
+
+ systemd is free software; you can redistribute it and/or modify it
+ under the terms of the GNU General Public License as published by
+ the Free Software Foundation; either version 2 of the License, or
+ (at your option) any later version.
+
+ systemd is distributed in the hope that it will be useful, but
+ WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with systemd; If not, see <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
index 0000000000..d0b0e8e008
--- /dev/null
+++ b/src/binfmt/Makefile
@@ -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
index 0000000000..3c8d815d64
--- /dev/null
+++ b/src/binfmt/binfmt.c
@@ -0,0 +1,169 @@
+/*-*- Mode: C; c-basic-offset: 8; indent-tabs-mode: nil -*-*/
+
+/***
+ This file is part of systemd.
+
+ Copyright 2010 Lennart Poettering
+
+ systemd is free software; you can redistribute it and/or modify it
+ under the terms of the GNU General Public License as published by
+ the Free Software Foundation; either version 2 of the License, or
+ (at your option) any later version.
+
+ systemd is distributed in the hope that it will be useful, but
+ WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with systemd; If not, see <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
index 0000000000..bfb38a8bb4
--- /dev/null
+++ b/src/bridge.c
@@ -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
index 0000000000..0619013141
--- /dev/null
+++ b/src/build.h
@@ -0,0 +1,69 @@
+/*-*- Mode: C; c-basic-offset: 8; indent-tabs-mode: nil -*-*/
+
+#ifndef foobuildhfoo
+#define foobuildhfoo
+
+/***
+ This file is part of systemd.
+
+ Copyright 2010 Lennart Poettering
+
+ systemd is free software; you can redistribute it and/or modify it
+ under the terms of the GNU General Public License as published by
+ the Free Software Foundation; either version 2 of the License, or
+ (at your option) any later version.
+
+ systemd is distributed in the hope that it will be useful, but
+ WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with systemd; If not, see <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
index 0000000000..82d4e99eef
--- /dev/null
+++ b/src/bus-errors.h
@@ -0,0 +1,58 @@
+/*-*- Mode: C; c-basic-offset: 8; indent-tabs-mode: nil -*-*/
+
+#ifndef foobuserrorshfoo
+#define foobuserrorshfoo
+
+/***
+ This file is part of systemd.
+
+ Copyright 2010 Lennart Poettering
+
+ systemd is free software; you can redistribute it and/or modify it
+ under the terms of the GNU General Public License as published by
+ the Free Software Foundation; either version 2 of the License, or
+ (at your option) any later version.
+
+ systemd is distributed in the hope that it will be useful, but
+ WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with systemd; If not, see <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
index 0000000000..d5417f9505
--- /dev/null
+++ b/src/cgls.c
@@ -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
index 0000000000..474a6865c4
--- /dev/null
+++ b/src/cgroup-attr.c
@@ -0,0 +1,102 @@
+/*-*- Mode: C; c-basic-offset: 8; indent-tabs-mode: nil -*-*/
+
+/***
+ This file is part of systemd.
+
+ Copyright 2011 Lennart Poettering
+
+ systemd is free software; you can redistribute it and/or modify it
+ under the terms of the GNU General Public License as published by
+ the Free Software Foundation; either version 2 of the License, or
+ (at your option) any later version.
+
+ systemd is distributed in the hope that it will be useful, but
+ WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with systemd; If not, see <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
index 0000000000..63a73b8101
--- /dev/null
+++ b/src/cgroup-attr.h
@@ -0,0 +1,49 @@
+/*-*- Mode: C; c-basic-offset: 8; indent-tabs-mode: nil -*-*/
+
+#ifndef foocgroupattrhfoo
+#define foocgroupattrhfoo
+
+/***
+ This file is part of systemd.
+
+ Copyright 2011 Lennart Poettering
+
+ systemd is free software; you can redistribute it and/or modify it
+ under the terms of the GNU General Public License as published by
+ the Free Software Foundation; either version 2 of the License, or
+ (at your option) any later version.
+
+ systemd is distributed in the hope that it will be useful, but
+ WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with systemd; If not, see <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
index 0000000000..ee2a241c9f
--- /dev/null
+++ b/src/cgroup-show.c
@@ -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
index 0000000000..992e17b986
--- /dev/null
+++ b/src/cgroup-show.h
@@ -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
index 0000000000..904d300952
--- /dev/null
+++ b/src/cgroup-util.c
@@ -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
index 0000000000..37e4255a9c
--- /dev/null
+++ b/src/cgroup-util.h
@@ -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
index 0000000000..1f6139e25f
--- /dev/null
+++ b/src/cgroup.c
@@ -0,0 +1,556 @@
+/*-*- Mode: C; c-basic-offset: 8; indent-tabs-mode: nil -*-*/
+
+/***
+ This file is part of systemd.
+
+ Copyright 2010 Lennart Poettering
+
+ systemd is free software; you can redistribute it and/or modify it
+ under the terms of the GNU General Public License as published by
+ the Free Software Foundation; either version 2 of the License, or
+ (at your option) any later version.
+
+ systemd is distributed in the hope that it will be useful, but
+ WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with systemd; If not, see <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
index 0000000000..5faa7dc0f7
--- /dev/null
+++ b/src/cgroup.h
@@ -0,0 +1,94 @@
+/*-*- Mode: C; c-basic-offset: 8; indent-tabs-mode: nil -*-*/
+
+#ifndef foocgrouphfoo
+#define foocgrouphfoo
+
+/***
+ This file is part of systemd.
+
+ Copyright 2010 Lennart Poettering
+
+ systemd is free software; you can redistribute it and/or modify it
+ under the terms of the GNU General Public License as published by
+ the Free Software Foundation; either version 2 of the License, or
+ (at your option) any later version.
+
+ systemd is distributed in the hope that it will be useful, but
+ WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with systemd; If not, see <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
index 0000000000..1bbc8827d7
--- /dev/null
+++ b/src/cgroups-agent.c
@@ -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
index 0000000000..8b8617dc1c
--- /dev/null
+++ b/src/cgtop.c
@@ -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
index 0000000000..2b51a16f17
--- /dev/null
+++ b/src/condition.c
@@ -0,0 +1,323 @@
+/*-*- Mode: C; c-basic-offset: 8; indent-tabs-mode: nil -*-*/
+
+/***
+ This file is part of systemd.
+
+ Copyright 2010 Lennart Poettering
+
+ systemd is free software; you can redistribute it and/or modify it
+ under the terms of the GNU General Public License as published by
+ the Free Software Foundation; either version 2 of the License, or
+ (at your option) any later version.
+
+ systemd is distributed in the hope that it will be useful, but
+ WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with systemd; If not, see <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
index 0000000000..71b1c6761e
--- /dev/null
+++ b/src/condition.h
@@ -0,0 +1,69 @@
+/*-*- Mode: C; c-basic-offset: 8; indent-tabs-mode: nil -*-*/
+
+#ifndef fooconditionhfoo
+#define fooconditionhfoo
+
+/***
+ This file is part of systemd.
+
+ Copyright 2010 Lennart Poettering
+
+ systemd is free software; you can redistribute it and/or modify it
+ under the terms of the GNU General Public License as published by
+ the Free Software Foundation; either version 2 of the License, or
+ (at your option) any later version.
+
+ systemd is distributed in the hope that it will be useful, but
+ WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with systemd; If not, see <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
index 0000000000..a9b01135e6
--- /dev/null
+++ b/src/conf-parser.c
@@ -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
index 0000000000..be7d708171
--- /dev/null
+++ b/src/conf-parser.h
@@ -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
index 0000000000..d0b0e8e008
--- /dev/null
+++ b/src/cryptsetup/Makefile
@@ -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
index 0000000000..ba59b49b01
--- /dev/null
+++ b/src/cryptsetup/cryptsetup-generator.c
@@ -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
index 0000000000..ac7b6d6c38
--- /dev/null
+++ b/src/cryptsetup/cryptsetup.c
@@ -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
index 0000000000..8e45f81fcf
--- /dev/null
+++ b/src/dbus-automount.c
@@ -0,0 +1,72 @@
+/*-*- Mode: C; c-basic-offset: 8; indent-tabs-mode: nil -*-*/
+
+/***
+ This file is part of systemd.
+
+ Copyright 2010 Lennart Poettering
+
+ systemd is free software; you can redistribute it and/or modify it
+ under the terms of the GNU General Public License as published by
+ the Free Software Foundation; either version 2 of the License, or
+ (at your option) any later version.
+
+ systemd is distributed in the hope that it will be useful, but
+ WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with systemd; If not, see <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
index 0000000000..2fc8345048
--- /dev/null
+++ b/src/dbus-automount.h
@@ -0,0 +1,34 @@
+/*-*- Mode: C; c-basic-offset: 8; indent-tabs-mode: nil -*-*/
+
+#ifndef foodbusautomounthfoo
+#define foodbusautomounthfoo
+
+/***
+ This file is part of systemd.
+
+ Copyright 2010 Lennart Poettering
+
+ systemd is free software; you can redistribute it and/or modify it
+ under the terms of the GNU General Public License as published by
+ the Free Software Foundation; either version 2 of the License, or
+ (at your option) any later version.
+
+ systemd is distributed in the hope that it will be useful, but
+ WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with systemd; If not, see <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
index 0000000000..2905ac3c83
--- /dev/null
+++ b/src/dbus-common.c
@@ -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
index 0000000000..15811a7e50
--- /dev/null
+++ b/src/dbus-common.h
@@ -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
index 0000000000..b39fb9d381
--- /dev/null
+++ b/src/dbus-device.c
@@ -0,0 +1,65 @@
+/*-*- Mode: C; c-basic-offset: 8; indent-tabs-mode: nil -*-*/
+
+/***
+ This file is part of systemd.
+
+ Copyright 2010 Lennart Poettering
+
+ systemd is free software; you can redistribute it and/or modify it
+ under the terms of the GNU General Public License as published by
+ the Free Software Foundation; either version 2 of the License, or
+ (at your option) any later version.
+
+ systemd is distributed in the hope that it will be useful, but
+ WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with systemd; If not, see <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
index 0000000000..fba270b4e6
--- /dev/null
+++ b/src/dbus-device.h
@@ -0,0 +1,34 @@
+/*-*- Mode: C; c-basic-offset: 8; indent-tabs-mode: nil -*-*/
+
+#ifndef foodbusdevicehfoo
+#define foodbusdevicehfoo
+
+/***
+ This file is part of systemd.
+
+ Copyright 2010 Lennart Poettering
+
+ systemd is free software; you can redistribute it and/or modify it
+ under the terms of the GNU General Public License as published by
+ the Free Software Foundation; either version 2 of the License, or
+ (at your option) any later version.
+
+ systemd is distributed in the hope that it will be useful, but
+ WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with systemd; If not, see <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
index 0000000000..1fd2b21336
--- /dev/null
+++ b/src/dbus-execute.c
@@ -0,0 +1,422 @@
+/*-*- Mode: C; c-basic-offset: 8; indent-tabs-mode: nil -*-*/
+
+/***
+ This file is part of systemd.
+
+ Copyright 2010 Lennart Poettering
+
+ systemd is free software; you can redistribute it and/or modify it
+ under the terms of the GNU General Public License as published by
+ the Free Software Foundation; either version 2 of the License, or
+ (at your option) any later version.
+
+ systemd is distributed in the hope that it will be useful, but
+ WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with systemd; If not, see <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
index 0000000000..03cd69d126
--- /dev/null
+++ b/src/dbus-execute.h
@@ -0,0 +1,125 @@
+/*-*- Mode: C; c-basic-offset: 8; indent-tabs-mode: nil -*-*/
+
+#ifndef foodbusexecutehfoo
+#define foodbusexecutehfoo
+
+/***
+ This file is part of systemd.
+
+ Copyright 2010 Lennart Poettering
+
+ systemd is free software; you can redistribute it and/or modify it
+ under the terms of the GNU General Public License as published by
+ the Free Software Foundation; either version 2 of the License, or
+ (at your option) any later version.
+
+ systemd is distributed in the hope that it will be useful, but
+ WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with systemd; If not, see <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
index 0000000000..ab6d610243
--- /dev/null
+++ b/src/dbus-job.c
@@ -0,0 +1,354 @@
+/*-*- Mode: C; c-basic-offset: 8; indent-tabs-mode: nil -*-*/
+
+/***
+ This file is part of systemd.
+
+ Copyright 2010 Lennart Poettering
+
+ systemd is free software; you can redistribute it and/or modify it
+ under the terms of the GNU General Public License as published by
+ the Free Software Foundation; either version 2 of the License, or
+ (at your option) any later version.
+
+ systemd is distributed in the hope that it will be useful, but
+ WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with systemd; If not, see <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
index 0000000000..103c2ff3ec
--- /dev/null
+++ b/src/dbus-job.h
@@ -0,0 +1,36 @@
+/*-*- Mode: C; c-basic-offset: 8; indent-tabs-mode: nil -*-*/
+
+#ifndef foodbusjobhfoo
+#define foodbusjobhfoo
+
+/***
+ This file is part of systemd.
+
+ Copyright 2010 Lennart Poettering
+
+ systemd is free software; you can redistribute it and/or modify it
+ under the terms of the GNU General Public License as published by
+ the Free Software Foundation; either version 2 of the License, or
+ (at your option) any later version.
+
+ systemd is distributed in the hope that it will be useful, but
+ WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with systemd; If not, see <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
index 0000000000..8eb1d171a7
--- /dev/null
+++ b/src/dbus-loop.c
@@ -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
index 0000000000..0bbdfe5925
--- /dev/null
+++ b/src/dbus-loop.h
@@ -0,0 +1,30 @@
+/*-*- Mode: C; c-basic-offset: 8; indent-tabs-mode: nil -*-*/
+
+#ifndef foodbusloophfoo
+#define foodbusloophfoo
+
+/***
+ This file is part of systemd.
+
+ Copyright 2011 Lennart Poettering
+
+ systemd is free software; you can redistribute it and/or modify it
+ under the terms of the GNU General Public License as published by
+ the Free Software Foundation; either version 2 of the License, or
+ (at your option) any later version.
+
+ systemd is distributed in the hope that it will be useful, but
+ WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with systemd; If not, see <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
index 0000000000..6d272cb321
--- /dev/null
+++ b/src/dbus-manager.c
@@ -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
index 0000000000..2eb2fbbed6
--- /dev/null
+++ b/src/dbus-manager.h
@@ -0,0 +1,31 @@
+/*-*- Mode: C; c-basic-offset: 8; indent-tabs-mode: nil -*-*/
+
+#ifndef foodbusmanagerhfoo
+#define foodbusmanagerhfoo
+
+/***
+ This file is part of systemd.
+
+ Copyright 2010 Lennart Poettering
+
+ systemd is free software; you can redistribute it and/or modify it
+ under the terms of the GNU General Public License as published by
+ the Free Software Foundation; either version 2 of the License, or
+ (at your option) any later version.
+
+ systemd is distributed in the hope that it will be useful, but
+ WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with systemd; If not, see <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
index 0000000000..35d6ea7a1d
--- /dev/null
+++ b/src/dbus-mount.c
@@ -0,0 +1,168 @@
+/*-*- Mode: C; c-basic-offset: 8; indent-tabs-mode: nil -*-*/
+
+/***
+ This file is part of systemd.
+
+ Copyright 2010 Lennart Poettering
+
+ systemd is free software; you can redistribute it and/or modify it
+ under the terms of the GNU General Public License as published by
+ the Free Software Foundation; either version 2 of the License, or
+ (at your option) any later version.
+
+ systemd is distributed in the hope that it will be useful, but
+ WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with systemd; If not, see <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
index 0000000000..b5613fa7b6
--- /dev/null
+++ b/src/dbus-mount.h
@@ -0,0 +1,34 @@
+/*-*- Mode: C; c-basic-offset: 8; indent-tabs-mode: nil -*-*/
+
+#ifndef foodbusmounthfoo
+#define foodbusmounthfoo
+
+/***
+ This file is part of systemd.
+
+ Copyright 2010 Lennart Poettering
+
+ systemd is free software; you can redistribute it and/or modify it
+ under the terms of the GNU General Public License as published by
+ the Free Software Foundation; either version 2 of the License, or
+ (at your option) any later version.
+
+ systemd is distributed in the hope that it will be useful, but
+ WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with systemd; If not, see <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
index 0000000000..5506784c38
--- /dev/null
+++ b/src/dbus-path.c
@@ -0,0 +1,119 @@
+/*-*- Mode: C; c-basic-offset: 8; indent-tabs-mode: nil -*-*/
+
+/***
+ This file is part of systemd.
+
+ Copyright 2010 Lennart Poettering
+
+ systemd is free software; you can redistribute it and/or modify it
+ under the terms of the GNU General Public License as published by
+ the Free Software Foundation; either version 2 of the License, or
+ (at your option) any later version.
+
+ systemd is distributed in the hope that it will be useful, but
+ WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with systemd; If not, see <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
index 0000000000..2888400a13
--- /dev/null
+++ b/src/dbus-path.h
@@ -0,0 +1,35 @@
+/*-*- Mode: C; c-basic-offset: 8; indent-tabs-mode: nil -*-*/
+
+#ifndef foodbuspathhfoo
+#define foodbuspathhfoo
+
+/***
+ This file is part of systemd.
+
+ Copyright 2010 Lennart Poettering
+
+ systemd is free software; you can redistribute it and/or modify it
+ under the terms of the GNU General Public License as published by
+ the Free Software Foundation; either version 2 of the License, or
+ (at your option) any later version.
+
+ systemd is distributed in the hope that it will be useful, but
+ WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with systemd; If not, see <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
index 0000000000..780916419b
--- /dev/null
+++ b/src/dbus-service.c
@@ -0,0 +1,168 @@
+/*-*- Mode: C; c-basic-offset: 8; indent-tabs-mode: nil -*-*/
+
+/***
+ This file is part of systemd.
+
+ Copyright 2010 Lennart Poettering
+
+ systemd is free software; you can redistribute it and/or modify it
+ under the terms of the GNU General Public License as published by
+ the Free Software Foundation; either version 2 of the License, or
+ (at your option) any later version.
+
+ systemd is distributed in the hope that it will be useful, but
+ WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with systemd; If not, see <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
index 0000000000..d6eab65c52
--- /dev/null
+++ b/src/dbus-service.h
@@ -0,0 +1,34 @@
+/*-*- Mode: C; c-basic-offset: 8; indent-tabs-mode: nil -*-*/
+
+#ifndef foodbusservicehfoo
+#define foodbusservicehfoo
+
+/***
+ This file is part of systemd.
+
+ Copyright 2010 Lennart Poettering
+
+ systemd is free software; you can redistribute it and/or modify it
+ under the terms of the GNU General Public License as published by
+ the Free Software Foundation; either version 2 of the License, or
+ (at your option) any later version.
+
+ systemd is distributed in the hope that it will be useful, but
+ WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with systemd; If not, see <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
index 0000000000..e69388a524
--- /dev/null
+++ b/src/dbus-snapshot.c
@@ -0,0 +1,93 @@
+/*-*- Mode: C; c-basic-offset: 8; indent-tabs-mode: nil -*-*/
+
+/***
+ This file is part of systemd.
+
+ Copyright 2010 Lennart Poettering
+
+ systemd is free software; you can redistribute it and/or modify it
+ under the terms of the GNU General Public License as published by
+ the Free Software Foundation; either version 2 of the License, or
+ (at your option) any later version.
+
+ systemd is distributed in the hope that it will be useful, but
+ WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with systemd; If not, see <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
index 0000000000..0b82279f1c
--- /dev/null
+++ b/src/dbus-snapshot.h
@@ -0,0 +1,33 @@
+/*-*- Mode: C; c-basic-offset: 8; indent-tabs-mode: nil -*-*/
+
+#ifndef foodbussnapshothfoo
+#define foodbussnapshothfoo
+
+/***
+ This file is part of systemd.
+
+ Copyright 2010 Lennart Poettering
+
+ systemd is free software; you can redistribute it and/or modify it
+ under the terms of the GNU General Public License as published by
+ the Free Software Foundation; either version 2 of the License, or
+ (at your option) any later version.
+
+ systemd is distributed in the hope that it will be useful, but
+ WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with systemd; If not, see <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
index 0000000000..2e3342cb55
--- /dev/null
+++ b/src/dbus-socket.c
@@ -0,0 +1,139 @@
+/*-*- Mode: C; c-basic-offset: 8; indent-tabs-mode: nil -*-*/
+
+/***
+ This file is part of systemd.
+
+ Copyright 2010 Lennart Poettering
+
+ systemd is free software; you can redistribute it and/or modify it
+ under the terms of the GNU General Public License as published by
+ the Free Software Foundation; either version 2 of the License, or
+ (at your option) any later version.
+
+ systemd is distributed in the hope that it will be useful, but
+ WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with systemd; If not, see <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
index 0000000000..069a2f5df0
--- /dev/null
+++ b/src/dbus-socket.h
@@ -0,0 +1,34 @@
+/*-*- Mode: C; c-basic-offset: 8; indent-tabs-mode: nil -*-*/
+
+#ifndef foodbussockethfoo
+#define foodbussockethfoo
+
+/***
+ This file is part of systemd.
+
+ Copyright 2010 Lennart Poettering
+
+ systemd is free software; you can redistribute it and/or modify it
+ under the terms of the GNU General Public License as published by
+ the Free Software Foundation; either version 2 of the License, or
+ (at your option) any later version.
+
+ systemd is distributed in the hope that it will be useful, but
+ WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with systemd; If not, see <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
index 0000000000..09cd1e8b9c
--- /dev/null
+++ b/src/dbus-swap.c
@@ -0,0 +1,111 @@
+/*-*- Mode: C; c-basic-offset: 8; indent-tabs-mode: nil -*-*/
+
+/***
+ This file is part of systemd.
+
+ Copyright 2010 Lennart Poettering
+ Copyright 2010 Maarten Lankhorst
+
+ systemd is free software; you can redistribute it and/or modify it
+ under the terms of the GNU General Public License as published by
+ the Free Software Foundation; either version 2 of the License, or
+ (at your option) any later version.
+
+ systemd is distributed in the hope that it will be useful, but
+ WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with systemd; If not, see <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
index 0000000000..15b914726b
--- /dev/null
+++ b/src/dbus-swap.h
@@ -0,0 +1,35 @@
+/*-*- Mode: C; c-basic-offset: 8; indent-tabs-mode: nil -*-*/
+
+#ifndef foodbusswaphfoo
+#define foodbusswaphfoo
+
+/***
+ This file is part of systemd.
+
+ Copyright 2010 Lennart Poettering
+ Copyright 2010 Maarten Lankhorst
+
+ systemd is free software; you can redistribute it and/or modify it
+ under the terms of the GNU General Public License as published by
+ the Free Software Foundation; either version 2 of the License, or
+ (at your option) any later version.
+
+ systemd is distributed in the hope that it will be useful, but
+ WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with systemd; If not, see <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
index 0000000000..55cf862de0
--- /dev/null
+++ b/src/dbus-target.c
@@ -0,0 +1,55 @@
+/*-*- Mode: C; c-basic-offset: 8; indent-tabs-mode: nil -*-*/
+
+/***
+ This file is part of systemd.
+
+ Copyright 2010 Lennart Poettering
+
+ systemd is free software; you can redistribute it and/or modify it
+ under the terms of the GNU General Public License as published by
+ the Free Software Foundation; either version 2 of the License, or
+ (at your option) any later version.
+
+ systemd is distributed in the hope that it will be useful, but
+ WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with systemd; If not, see <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
index 0000000000..13d38764a2
--- /dev/null
+++ b/src/dbus-target.h
@@ -0,0 +1,33 @@
+/*-*- Mode: C; c-basic-offset: 8; indent-tabs-mode: nil -*-*/
+
+#ifndef foodbustargethfoo
+#define foodbustargethfoo
+
+/***
+ This file is part of systemd.
+
+ Copyright 2010 Lennart Poettering
+
+ systemd is free software; you can redistribute it and/or modify it
+ under the terms of the GNU General Public License as published by
+ the Free Software Foundation; either version 2 of the License, or
+ (at your option) any later version.
+
+ systemd is distributed in the hope that it will be useful, but
+ WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with systemd; If not, see <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
index 0000000000..b396aed047
--- /dev/null
+++ b/src/dbus-timer.c
@@ -0,0 +1,137 @@
+/*-*- Mode: C; c-basic-offset: 8; indent-tabs-mode: nil -*-*/
+
+/***
+ This file is part of systemd.
+
+ Copyright 2010 Lennart Poettering
+
+ systemd is free software; you can redistribute it and/or modify it
+ under the terms of the GNU General Public License as published by
+ the Free Software Foundation; either version 2 of the License, or
+ (at your option) any later version.
+
+ systemd is distributed in the hope that it will be useful, but
+ WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with systemd; If not, see <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
index 0000000000..e692e12fcb
--- /dev/null
+++ b/src/dbus-timer.h
@@ -0,0 +1,34 @@
+/*-*- Mode: C; c-basic-offset: 8; indent-tabs-mode: nil -*-*/
+
+#ifndef foodbustimerhfoo
+#define foodbustimerhfoo
+
+/***
+ This file is part of systemd.
+
+ Copyright 2010 Lennart Poettering
+
+ systemd is free software; you can redistribute it and/or modify it
+ under the terms of the GNU General Public License as published by
+ the Free Software Foundation; either version 2 of the License, or
+ (at your option) any later version.
+
+ systemd is distributed in the hope that it will be useful, but
+ WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with systemd; If not, see <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
index 0000000000..c7532c7255
--- /dev/null
+++ b/src/dbus-unit.c
@@ -0,0 +1,850 @@
+/*-*- Mode: C; c-basic-offset: 8; indent-tabs-mode: nil -*-*/
+
+/***
+ This file is part of systemd.
+
+ Copyright 2010 Lennart Poettering
+
+ systemd is free software; you can redistribute it and/or modify it
+ under the terms of the GNU General Public License as published by
+ the Free Software Foundation; either version 2 of the License, or
+ (at your option) any later version.
+
+ systemd is distributed in the hope that it will be useful, but
+ WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with systemd; If not, see <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
index 0000000000..4f19a808bf
--- /dev/null
+++ b/src/dbus-unit.h
@@ -0,0 +1,139 @@
+/*-*- Mode: C; c-basic-offset: 8; indent-tabs-mode: nil -*-*/
+
+#ifndef foodbusunithfoo
+#define foodbusunithfoo
+
+/***
+ This file is part of systemd.
+
+ Copyright 2010 Lennart Poettering
+
+ systemd is free software; you can redistribute it and/or modify it
+ under the terms of the GNU General Public License as published by
+ the Free Software Foundation; either version 2 of the License, or
+ (at your option) any later version.
+
+ systemd is distributed in the hope that it will be useful, but
+ WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with systemd; If not, see <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
index 0000000000..8e6e9fd520
--- /dev/null
+++ b/src/dbus.c
@@ -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
index 0000000000..bd539d0e72
--- /dev/null
+++ b/src/dbus.h
@@ -0,0 +1,53 @@
+/*-*- Mode: C; c-basic-offset: 8; indent-tabs-mode: nil -*-*/
+
+#ifndef foodbushfoo
+#define foodbushfoo
+
+/***
+ This file is part of systemd.
+
+ Copyright 2010 Lennart Poettering
+
+ systemd is free software; you can redistribute it and/or modify it
+ under the terms of the GNU General Public License as published by
+ the Free Software Foundation; either version 2 of the License, or
+ (at your option) any later version.
+
+ systemd is distributed in the hope that it will be useful, but
+ WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with systemd; If not, see <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
index 0000000000..20aaa7c580
--- /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
index 0000000000..79cad5d8ab
--- /dev/null
+++ b/src/detect-virt.c
@@ -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
index 0000000000..0575379d80
--- /dev/null
+++ b/src/device.c
@@ -0,0 +1,616 @@
+/*-*- Mode: C; c-basic-offset: 8; indent-tabs-mode: nil -*-*/
+
+/***
+ This file is part of systemd.
+
+ Copyright 2010 Lennart Poettering
+
+ systemd is free software; you can redistribute it and/or modify it
+ under the terms of the GNU General Public License as published by
+ the Free Software Foundation; either version 2 of the License, or
+ (at your option) any later version.
+
+ systemd is distributed in the hope that it will be useful, but
+ WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with systemd; If not, see <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
index 0000000000..a05c3d37b0
--- /dev/null
+++ b/src/device.h
@@ -0,0 +1,59 @@
+/*-*- Mode: C; c-basic-offset: 8; indent-tabs-mode: nil -*-*/
+
+#ifndef foodevicehfoo
+#define foodevicehfoo
+
+/***
+ This file is part of systemd.
+
+ Copyright 2010 Lennart Poettering
+
+ systemd is free software; you can redistribute it and/or modify it
+ under the terms of the GNU General Public License as published by
+ the Free Software Foundation; either version 2 of the License, or
+ (at your option) any later version.
+
+ systemd is distributed in the hope that it will be useful, but
+ WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with systemd; If not, see <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
index 0000000000..dab485682b
--- /dev/null
+++ b/src/execute.c
@@ -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
index 0000000000..0d7e7dd65d
--- /dev/null
+++ b/src/execute.h
@@ -0,0 +1,233 @@
+/*-*- Mode: C; c-basic-offset: 8; indent-tabs-mode: nil -*-*/
+
+#ifndef fooexecutehfoo
+#define fooexecutehfoo
+
+/***
+ This file is part of systemd.
+
+ Copyright 2010 Lennart Poettering
+
+ systemd is free software; you can redistribute it and/or modify it
+ under the terms of the GNU General Public License as published by
+ the Free Software Foundation; either version 2 of the License, or
+ (at your option) any later version.
+
+ systemd is distributed in the hope that it will be useful, but
+ WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with systemd; If not, see <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
index 0000000000..ab8907d32c
--- /dev/null
+++ b/src/exit-status.c
@@ -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
index 0000000000..44ef879562
--- /dev/null
+++ b/src/exit-status.h
@@ -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
index 0000000000..e67fe6fabf
--- /dev/null
+++ b/src/fdset.c
@@ -0,0 +1,167 @@
+/*-*- Mode: C; c-basic-offset: 8; indent-tabs-mode: nil -*-*/
+
+/***
+ This file is part of systemd.
+
+ Copyright 2010 Lennart Poettering
+
+ systemd is free software; you can redistribute it and/or modify it
+ under the terms of the GNU General Public License as published by
+ the Free Software Foundation; either version 2 of the License, or
+ (at your option) any later version.
+
+ systemd is distributed in the hope that it will be useful, but
+ WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with systemd; If not, see <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
index 0000000000..044a9e6d1f
--- /dev/null
+++ b/src/fdset.h
@@ -0,0 +1,40 @@
+/*-*- Mode: C; c-basic-offset: 8; indent-tabs-mode: nil -*-*/
+
+#ifndef foofdsethfoo
+#define foofdsethfoo
+
+/***
+ This file is part of systemd.
+
+ Copyright 2010 Lennart Poettering
+
+ systemd is free software; you can redistribute it and/or modify it
+ under the terms of the GNU General Public License as published by
+ the Free Software Foundation; either version 2 of the License, or
+ (at your option) any later version.
+
+ systemd is distributed in the hope that it will be useful, but
+ WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with systemd; If not, see <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
index 0000000000..d3ac83c25e
--- /dev/null
+++ b/src/fsck.c
@@ -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
index 0000000000..7fac43a0ba
--- /dev/null
+++ b/src/getty-generator.c
@@ -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
index 0000000000..6928118615
--- /dev/null
+++ b/src/hashmap.c
@@ -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
index 0000000000..ab4363a7a3
--- /dev/null
+++ b/src/hashmap.h
@@ -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
index 0000000000..2c2f10cfd1
--- /dev/null
+++ b/src/hostname-setup.c
@@ -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
index 0000000000..ff11df945e
--- /dev/null
+++ b/src/hostname-setup.h
@@ -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
index 0000000000..1ff281b231
--- /dev/null
+++ b/src/hostname/.gitignore
@@ -0,0 +1 @@
+org.freedesktop.hostname1.policy
diff --git a/src/hostname/Makefile b/src/hostname/Makefile
new file mode 120000
index 0000000000..d0b0e8e008
--- /dev/null
+++ b/src/hostname/Makefile
@@ -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
index 0000000000..ad72440845
--- /dev/null
+++ b/src/hostname/hostnamed.c
@@ -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
index 0000000000..eb241c022f
--- /dev/null
+++ b/src/hostname/org.freedesktop.hostname1.conf
@@ -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
index 0000000000..7d56b22c28
--- /dev/null
+++ b/src/hostname/org.freedesktop.hostname1.policy.in
@@ -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
index 0000000000..42e4adb2c9
--- /dev/null
+++ b/src/hostname/org.freedesktop.hostname1.service
@@ -0,0 +1,12 @@
+# This file is part of systemd.
+#
+# systemd is free software; you can redistribute it and/or modify it
+# under the terms of the GNU 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
index 0000000000..03e43dcf16
--- /dev/null
+++ b/src/ima-setup.c
@@ -0,0 +1,115 @@
+/*-*- Mode: C; c-basic-offset: 8; indent-tabs-mode: nil -*-*/
+
+/***
+ This file is part of systemd.
+
+ Copyright 2010 Lennart Poettering
+ Copyright (C) 2012 Roberto Sassu - Politecnico di Torino, Italy
+ TORSEC group -- http://security.polito.it
+
+ systemd is free software; you can redistribute it and/or modify it
+ under the terms of the GNU General Public License as published by
+ the Free Software Foundation; either version 2 of the License, or
+ (at your option) any later version.
+
+ systemd is distributed in the hope that it will be useful, but
+ WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with systemd; If not, see <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
index 0000000000..7d677cf852
--- /dev/null
+++ b/src/ima-setup.h
@@ -0,0 +1,29 @@
+/*-*- Mode: C; c-basic-offset: 8; indent-tabs-mode: nil -*-*/
+
+#ifndef fooimasetuphfoo
+#define fooimasetuphfoo
+
+/***
+ This file is part of systemd.
+
+ Copyright 2010 Lennart Poettering
+ Copyright (C) 2012 Roberto Sassu - Politecnico di Torino, Italy
+ TORSEC group -- http://security.polito.it
+
+ systemd is free software; you can redistribute it and/or modify it
+ under the terms of the GNU General Public License as published by
+ the Free Software Foundation; either version 2 of the License, or
+ (at your option) any later version.
+
+ systemd is distributed in the hope that it will be useful, but
+ WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with systemd; If not, see <http://www.gnu.org/licenses/>.
+***/
+
+int ima_setup(void);
+
+#endif
diff --git a/src/initctl.c b/src/initctl.c
new file mode 100644
index 0000000000..53d03a9e10
--- /dev/null
+++ b/src/initctl.c
@@ -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
index 0000000000..859042ce42
--- /dev/null
+++ b/src/initreq.h
@@ -0,0 +1,77 @@
+/*
+ * initreq.h Interface to talk to init through /dev/initctl.
+ *
+ * Copyright (C) 1995-2004 Miquel van Smoorenburg
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2 of the License, or (at your option) any later version.
+ *
+ * Version: @(#)initreq.h 1.28 31-Mar-2004 MvS
+ *
+ */
+#ifndef _INITREQ_H
+#define _INITREQ_H
+
+#include <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
index 0000000000..9256116805
--- /dev/null
+++ b/src/install.c
@@ -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
index 0000000000..0505a82f00
--- /dev/null
+++ b/src/install.h
@@ -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
index 0000000000..9800fc2553
--- /dev/null
+++ b/src/ioprio.h
@@ -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
index 0000000000..8e944b35db
--- /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
index 0000000000..2121426b33
--- /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
index 0000000000..d6a79460cd
--- /dev/null
+++ b/src/journal/.gitignore
@@ -0,0 +1,2 @@
+/journald-gperf.c
+/libsystemd-journal.pc
diff --git a/src/journal/Makefile b/src/journal/Makefile
new file mode 120000
index 0000000000..d0b0e8e008
--- /dev/null
+++ b/src/journal/Makefile
@@ -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
index 0000000000..f0a666642c
--- /dev/null
+++ b/src/journal/cat.c
@@ -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
index 0000000000..ff906581f0
--- /dev/null
+++ b/src/journal/compress.c
@@ -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
index 0000000000..f187a6e00c
--- /dev/null
+++ b/src/journal/compress.h
@@ -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
index 0000000000..7dea66e6fd
--- /dev/null
+++ b/src/journal/coredump.c
@@ -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
index 0000000000..9cb8051082
--- /dev/null
+++ b/src/journal/journal-def.h
@@ -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
index 0000000000..be92c90447
--- /dev/null
+++ b/src/journal/journal-file.c
@@ -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
index 0000000000..57d66cafc5
--- /dev/null
+++ b/src/journal/journal-file.h
@@ -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
index 0000000000..17f1d317c7
--- /dev/null
+++ b/src/journal/journal-internal.h
@@ -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
index 0000000000..243ff2a378
--- /dev/null
+++ b/src/journal/journal-rate-limit.c
@@ -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
index 0000000000..2bbdd5f9fe
--- /dev/null
+++ b/src/journal/journal-rate-limit.h
@@ -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
index 0000000000..32e94af91f
--- /dev/null
+++ b/src/journal/journal-send.c
@@ -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
index 0000000000..01dceca237
--- /dev/null
+++ b/src/journal/journalctl.c
@@ -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
index 0000000000..a56f6d966e
--- /dev/null
+++ b/src/journal/journald-gperf.gperf
@@ -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
index 0000000000..555d74f049
--- /dev/null
+++ b/src/journal/journald.c
@@ -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
index 0000000000..710b0aa941
--- /dev/null
+++ b/src/journal/journald.conf
@@ -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
index 0000000000..6160991ed8
--- /dev/null
+++ b/src/journal/journald.h
@@ -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
index 0000000000..13cc8208df
--- /dev/null
+++ b/src/journal/libsystemd-journal.pc.in
@@ -0,0 +1,19 @@
+# This file is part of systemd.
+#
+# systemd is free software; you can redistribute it and/or modify it
+# under the terms of the GNU 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
index 0000000000..cd434aea26
--- /dev/null
+++ b/src/journal/libsystemd-journal.sym
@@ -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
index 0000000000..b90093a5e2
--- /dev/null
+++ b/src/journal/lookup3.c
@@ -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
index 0000000000..31cc2f57b0
--- /dev/null
+++ b/src/journal/lookup3.h
@@ -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
index 0000000000..92ba57822b
--- /dev/null
+++ b/src/journal/sd-journal.c
@@ -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
index 0000000000..eb4dbf3615
--- /dev/null
+++ b/src/journal/sparse-endian.h
@@ -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
index 0000000000..2f9987dcde
--- /dev/null
+++ b/src/journal/test-journal-send.c
@@ -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
index 0000000000..a023509b70
--- /dev/null
+++ b/src/journal/test-journal.c
@@ -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
index 0000000000..debf87130d
--- /dev/null
+++ b/src/kmod-setup.c
@@ -0,0 +1,96 @@
+/*-*- Mode: C; c-basic-offset: 8; indent-tabs-mode: nil -*-*/
+
+/***
+ This file is part of systemd.
+
+ Copyright 2010 Lennart Poettering
+
+ systemd is free software; you can redistribute it and/or modify it
+ under the terms of the GNU General Public License as published by
+ the Free Software Foundation; either version 2 of the License, or
+ (at your option) any later version.
+
+ systemd is distributed in the hope that it will be useful, but
+ WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with systemd; If not, see <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
index 0000000000..496aef3e15
--- /dev/null
+++ b/src/kmod-setup.h
@@ -0,0 +1,27 @@
+/*-*- Mode: C; c-basic-offset: 8; indent-tabs-mode: nil -*-*/
+
+#ifndef fookmodsetuphfoo
+#define fookmodsetuphfoo
+
+/***
+ This file is part of systemd.
+
+ Copyright 2010 Lennart Poettering
+
+ systemd is free software; you can redistribute it and/or modify it
+ under the terms of the GNU General Public License as published by
+ the Free Software Foundation; either version 2 of the License, or
+ (at your option) any later version.
+
+ systemd is distributed in the hope that it will be useful, but
+ WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with systemd; If not, see <http://www.gnu.org/licenses/>.
+***/
+
+int kmod_setup(void);
+
+#endif
diff --git a/src/label.c b/src/label.c
new file mode 100644
index 0000000000..2c887a0fe5
--- /dev/null
+++ b/src/label.c
@@ -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
index 0000000000..ead44837a4
--- /dev/null
+++ b/src/label.h
@@ -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
index 0000000000..8bb3a74c87
--- /dev/null
+++ b/src/libsystemd-daemon.pc.in
@@ -0,0 +1,19 @@
+# Permission is hereby granted, free of charge, to any person
+# obtaining a copy of this software and associated documentation files
+# (the "Software"), to deal in the Software without restriction,
+# including without limitation the rights to use, copy, modify, merge,
+# publish, distribute, sublicense, and/or sell copies of the Software,
+# and to permit persons to whom the Software is furnished to do so,
+# subject to the following conditions:
+
+prefix=@prefix@
+exec_prefix=@exec_prefix@
+libdir=@libdir@
+includedir=@includedir@
+
+Name: systemd
+Description: systemd Daemon Utility Library
+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
index 0000000000..f440238931
--- /dev/null
+++ b/src/libsystemd-daemon.sym
@@ -0,0 +1,27 @@
+/***
+ Permission is hereby granted, free of charge, to any person
+ obtaining a copy of this software and associated documentation files
+ (the "Software"), to deal in the Software without restriction,
+ including without limitation the rights to use, copy, modify, merge,
+ publish, distribute, sublicense, and/or sell copies of the Software,
+ and to permit persons to whom the Software is furnished to do so,
+ subject to the following conditions:
+***/
+
+/* Original symbols from systemd v31 */
+
+LIBSYSTEMD_DAEMON_31 {
+global:
+ sd_booted;
+ sd_is_fifo;
+ sd_is_mq;
+ sd_is_socket;
+ sd_is_socket_inet;
+ sd_is_socket_unix;
+ sd_is_special;
+ sd_listen_fds;
+ sd_notify;
+ sd_notifyf;
+local:
+ *;
+};
diff --git a/src/libsystemd-id128.pc.in b/src/libsystemd-id128.pc.in
new file mode 100644
index 0000000000..4d984fdff5
--- /dev/null
+++ b/src/libsystemd-id128.pc.in
@@ -0,0 +1,18 @@
+# This file is part of systemd.
+#
+# systemd is free software; you can redistribute it and/or modify it
+# under the terms of the GNU 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
index 0000000000..2373fe6646
--- /dev/null
+++ b/src/libsystemd-id128.sym
@@ -0,0 +1,21 @@
+/***
+ This file is part of systemd.
+
+ systemd is free software; you can redistribute it and/or modify it
+ under the terms of the GNU 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
index 0000000000..d0b0e8e008
--- /dev/null
+++ b/src/linux/Makefile
@@ -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
index 0000000000..850f39b33e
--- /dev/null
+++ b/src/linux/auto_dev-ioctl.h
@@ -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
index 0000000000..63531a6b4d
--- /dev/null
+++ b/src/linux/fanotify.h
@@ -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
index 0000000000..2bec8c9e73
--- /dev/null
+++ b/src/list.h
@@ -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
index 0000000000..d869ee0c76
--- /dev/null
+++ b/src/load-dropin.c
@@ -0,0 +1,150 @@
+/*-*- Mode: C; c-basic-offset: 8; indent-tabs-mode: nil -*-*/
+
+/***
+ This file is part of systemd.
+
+ Copyright 2010 Lennart Poettering
+
+ systemd is free software; you can redistribute it and/or modify it
+ under the terms of the GNU General Public License as published by
+ the Free Software Foundation; either version 2 of the License, or
+ (at your option) any later version.
+
+ systemd is distributed in the hope that it will be useful, but
+ WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with systemd; If not, see <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
index 0000000000..cf3a799381
--- /dev/null
+++ b/src/load-dropin.h
@@ -0,0 +1,31 @@
+/*-*- Mode: C; c-basic-offset: 8; indent-tabs-mode: nil -*-*/
+
+#ifndef fooloaddropinhfoo
+#define fooloaddropinhfoo
+
+/***
+ This file is part of systemd.
+
+ Copyright 2010 Lennart Poettering
+
+ systemd is free software; you can redistribute it and/or modify it
+ under the terms of the GNU General Public License as published by
+ the Free Software Foundation; either version 2 of the License, or
+ (at your option) any later version.
+
+ systemd is distributed in the hope that it will be useful, but
+ WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with systemd; If not, see <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
index 0000000000..4b02e3157e
--- /dev/null
+++ b/src/load-fragment-gperf.gperf.m4
@@ -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
index 0000000000..637c82b427
--- /dev/null
+++ b/src/load-fragment.c
@@ -0,0 +1,2445 @@
+/*-*- Mode: C; c-basic-offset: 8; indent-tabs-mode: nil -*-*/
+
+/***
+ This file is part of systemd.
+
+ Copyright 2010 Lennart Poettering
+
+ systemd is free software; you can redistribute it and/or modify it
+ under the terms of the GNU General Public License as published by
+ the Free Software Foundation; either version 2 of the License, or
+ (at your option) any later version.
+
+ systemd is distributed in the hope that it will be useful, but
+ WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with systemd; If not, see <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
index 0000000000..79fc76da92
--- /dev/null
+++ b/src/load-fragment.h
@@ -0,0 +1,91 @@
+/*-*- Mode: C; c-basic-offset: 8; indent-tabs-mode: nil -*-*/
+
+#ifndef fooloadfragmenthfoo
+#define fooloadfragmenthfoo
+
+/***
+ This file is part of systemd.
+
+ Copyright 2010 Lennart Poettering
+
+ systemd is free software; you can redistribute it and/or modify it
+ under the terms of the GNU General Public License as published by
+ the Free Software Foundation; either version 2 of the License, or
+ (at your option) any later version.
+
+ systemd is distributed in the hope that it will be useful, but
+ WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with systemd; If not, see <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
index 0000000000..7f692e9c5d
--- /dev/null
+++ b/src/locale-setup.c
@@ -0,0 +1,251 @@
+/*-*- Mode: C; c-basic-offset: 8; indent-tabs-mode: nil -*-*/
+
+/***
+ This file is part of systemd.
+
+ Copyright 2010 Lennart Poettering
+
+ systemd is free software; you can redistribute it and/or modify it
+ under the terms of the GNU General Public License as published by
+ the Free Software Foundation; either version 2 of the License, or
+ (at your option) any later version.
+
+ systemd is distributed in the hope that it will be useful, but
+ WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with systemd; If not, see <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
index 0000000000..09a6bc682d
--- /dev/null
+++ b/src/locale-setup.h
@@ -0,0 +1,27 @@
+/*-*- Mode: C; c-basic-offset: 8; indent-tabs-mode: nil -*-*/
+
+#ifndef foolocalesetuphfoo
+#define foolocalesetuphfoo
+
+/***
+ This file is part of systemd.
+
+ Copyright 2010 Lennart Poettering
+
+ systemd is free software; you can redistribute it and/or modify it
+ under the terms of the GNU General Public License as published by
+ the Free Software Foundation; either version 2 of the License, or
+ (at your option) any later version.
+
+ systemd is distributed in the hope that it will be useful, but
+ WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with systemd; If not, see <http://www.gnu.org/licenses/>.
+***/
+
+int locale_setup(void);
+
+#endif
diff --git a/src/locale/.gitignore b/src/locale/.gitignore
new file mode 100644
index 0000000000..b1e0ba755e
--- /dev/null
+++ b/src/locale/.gitignore
@@ -0,0 +1 @@
+org.freedesktop.locale1.policy
diff --git a/src/locale/Makefile b/src/locale/Makefile
new file mode 120000
index 0000000000..d0b0e8e008
--- /dev/null
+++ b/src/locale/Makefile
@@ -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
index 0000000000..624c5179fa
--- /dev/null
+++ b/src/locale/generate-kbd-model-map
@@ -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
index 0000000000..a895880269
--- /dev/null
+++ b/src/locale/kbd-model-map
@@ -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
index 0000000000..e9f9f86878
--- /dev/null
+++ b/src/locale/localed.c
@@ -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
index 0000000000..68273311e7
--- /dev/null
+++ b/src/locale/org.freedesktop.locale1.conf
@@ -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
index 0000000000..1ac50bf86c
--- /dev/null
+++ b/src/locale/org.freedesktop.locale1.policy.in
@@ -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
index 0000000000..29bd582459
--- /dev/null
+++ b/src/locale/org.freedesktop.locale1.service
@@ -0,0 +1,12 @@
+# This file is part of systemd.
+#
+# systemd is free software; you can redistribute it and/or modify it
+# under the terms of the GNU 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
index 0000000000..9fffc1dbc0
--- /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
index 0000000000..3283808ff8
--- /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
index 0000000000..1c0f3995ed
--- /dev/null
+++ b/src/login/.gitignore
@@ -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
index 0000000000..6932492260
--- /dev/null
+++ b/src/login/70-uaccess.rules
@@ -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
index 0000000000..04ccac7570
--- /dev/null
+++ b/src/login/71-seat.rules
@@ -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
index 0000000000..0847932d77
--- /dev/null
+++ b/src/login/73-seat-late.rules.in
@@ -0,0 +1,17 @@
+# This file is part of systemd.
+#
+# systemd is free software; you can redistribute it and/or modify it
+# under the terms of the GNU 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
index 0000000000..d0b0e8e008
--- /dev/null
+++ b/src/login/Makefile
@@ -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
index 0000000000..cd36a9cb35
--- /dev/null
+++ b/src/login/libsystemd-login.pc.in
@@ -0,0 +1,18 @@
+# This file is part of systemd.
+#
+# systemd is free software; you can redistribute it and/or modify it
+# under the terms of the GNU 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
index 0000000000..a5e6c1e756
--- /dev/null
+++ b/src/login/libsystemd-login.sym
@@ -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
index 0000000000..30e97e352a
--- /dev/null
+++ b/src/login/loginctl.c
@@ -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
index 0000000000..eb8a48d191
--- /dev/null
+++ b/src/login/logind-acl.c
@@ -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
index 0000000000..72740f5b95
--- /dev/null
+++ b/src/login/logind-acl.h
@@ -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
index 0000000000..ea6b89faa1
--- /dev/null
+++ b/src/login/logind-dbus.c
@@ -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
index 0000000000..bbd370fbff
--- /dev/null
+++ b/src/login/logind-device.c
@@ -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
index 0000000000..e25a5344ef
--- /dev/null
+++ b/src/login/logind-device.h
@@ -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
index 0000000000..940fe10e89
--- /dev/null
+++ b/src/login/logind-gperf.gperf
@@ -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
index 0000000000..95ef5ffdb5
--- /dev/null
+++ b/src/login/logind-seat-dbus.c
@@ -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
index 0000000000..be37c1cc2e
--- /dev/null
+++ b/src/login/logind-seat.c
@@ -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
index 0000000000..3b2c7f0965
--- /dev/null
+++ b/src/login/logind-seat.h
@@ -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
index 0000000000..102f8ac99b
--- /dev/null
+++ b/src/login/logind-session-dbus.c
@@ -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
index 0000000000..4e0af8656b
--- /dev/null
+++ b/src/login/logind-session.c
@@ -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
index 0000000000..d0b8c87fab
--- /dev/null
+++ b/src/login/logind-session.h
@@ -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
index 0000000000..a9d680f899
--- /dev/null
+++ b/src/login/logind-user-dbus.c
@@ -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
index 0000000000..717f0e20a2
--- /dev/null
+++ b/src/login/logind-user.c
@@ -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
index 0000000000..db9a5f6a34
--- /dev/null
+++ b/src/login/logind-user.h
@@ -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
index 0000000000..a54195cebb
--- /dev/null
+++ b/src/login/logind.c
@@ -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
index 0000000000..24b9d77a67
--- /dev/null
+++ b/src/login/logind.conf
@@ -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
index 0000000000..a4b4602675
--- /dev/null
+++ b/src/login/logind.h
@@ -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
index 0000000000..7133e026dc
--- /dev/null
+++ b/src/login/multi-seat-x.c
@@ -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
index 0000000000..9ef852bb73
--- /dev/null
+++ b/src/login/org.freedesktop.login1.conf
@@ -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
index 0000000000..adc904886d
--- /dev/null
+++ b/src/login/org.freedesktop.login1.policy.in
@@ -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
index 0000000000..4a64177e30
--- /dev/null
+++ b/src/login/org.freedesktop.login1.service
@@ -0,0 +1,12 @@
+# This file is part of systemd.
+#
+# systemd is free software; you can redistribute it and/or modify it
+# under the terms of the GNU 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
index 0000000000..4106d2ba4b
--- /dev/null
+++ b/src/login/pam-module.c
@@ -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
index 0000000000..887c421009
--- /dev/null
+++ b/src/login/sd-login.c
@@ -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
index 0000000000..b8b356d77b
--- /dev/null
+++ b/src/login/sysfs-show.c
@@ -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
index 0000000000..dd8404285b
--- /dev/null
+++ b/src/login/test-login.c
@@ -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
index 0000000000..e1af5bf431
--- /dev/null
+++ b/src/login/uaccess.c
@@ -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
index 0000000000..64aa3bb1c6
--- /dev/null
+++ b/src/login/user-sessions.c
@@ -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
index 0000000000..0a07a77bed
--- /dev/null
+++ b/src/logs-show.c
@@ -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
index 0000000000..db9c7e34ab
--- /dev/null
+++ b/src/logs-show.h
@@ -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
index 0000000000..b6359def7f
--- /dev/null
+++ b/src/loopback-setup.c
@@ -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
index 0000000000..81f4529630
--- /dev/null
+++ b/src/loopback-setup.h
@@ -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
index 0000000000..03970a2b06
--- /dev/null
+++ b/src/machine-id-main.c
@@ -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
index 0000000000..0f97433804
--- /dev/null
+++ b/src/machine-id-setup.c
@@ -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
index 0000000000..4d0a9cf331
--- /dev/null
+++ b/src/machine-id-setup.h
@@ -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
index 0000000000..19f259e4f1
--- /dev/null
+++ b/src/macro.h
@@ -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
index 0000000000..7ae88414a9
--- /dev/null
+++ b/src/main.c
@@ -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
index 0000000000..74bd740747
--- /dev/null
+++ b/src/manager.c
@@ -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
index 0000000000..a9d08f0a25
--- /dev/null
+++ b/src/manager.h
@@ -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
index 0000000000..095bf1fe04
--- /dev/null
+++ b/src/missing.h
@@ -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
index 0000000000..ff1f690aac
--- /dev/null
+++ b/src/modules-load.c
@@ -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
index 0000000000..aaffb655ee
--- /dev/null
+++ b/src/mount-setup.c
@@ -0,0 +1,422 @@
+/*-*- Mode: C; c-basic-offset: 8; indent-tabs-mode: nil -*-*/
+
+/***
+ This file is part of systemd.
+
+ Copyright 2010 Lennart Poettering
+
+ systemd is free software; you can redistribute it and/or modify it
+ under the terms of the GNU General Public License as published by
+ the Free Software Foundation; either version 2 of the License, or
+ (at your option) any later version.
+
+ systemd is distributed in the hope that it will be useful, but
+ WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with systemd; If not, see <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
index 0000000000..c1a27ba6bd
--- /dev/null
+++ b/src/mount-setup.h
@@ -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
index 0000000000..ed0f819c7b
--- /dev/null
+++ b/src/mount.c
@@ -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
index 0000000000..9318444249
--- /dev/null
+++ b/src/mount.h
@@ -0,0 +1,124 @@
+/*-*- Mode: C; c-basic-offset: 8; indent-tabs-mode: nil -*-*/
+
+#ifndef foomounthfoo
+#define foomounthfoo
+
+/***
+ This file is part of systemd.
+
+ Copyright 2010 Lennart Poettering
+
+ systemd is free software; you can redistribute it and/or modify it
+ under the terms of the GNU General Public License as published by
+ the Free Software Foundation; either version 2 of the License, or
+ (at your option) any later version.
+
+ systemd is distributed in the hope that it will be useful, but
+ WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with systemd; If not, see <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
index 0000000000..09bc82909f
--- /dev/null
+++ b/src/namespace.c
@@ -0,0 +1,346 @@
+/*-*- Mode: C; c-basic-offset: 8; indent-tabs-mode: nil -*-*/
+
+/***
+ This file is part of systemd.
+
+ Copyright 2010 Lennart Poettering
+
+ systemd is free software; you can redistribute it and/or modify it
+ under the terms of the GNU General Public License as published by
+ the Free Software Foundation; either version 2 of the License, or
+ (at your option) any later version.
+
+ systemd is distributed in the hope that it will be useful, but
+ WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with systemd; If not, see <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
index 0000000000..7cf1dedd32
--- /dev/null
+++ b/src/namespace.h
@@ -0,0 +1,34 @@
+/*-*- Mode: C; c-basic-offset: 8; indent-tabs-mode: nil -*-*/
+
+#ifndef foonamespacehfoo
+#define foonamespacehfoo
+
+/***
+ This file is part of systemd.
+
+ Copyright 2010 Lennart Poettering
+
+ systemd is free software; you can redistribute it and/or modify it
+ under the terms of the GNU General Public License as published by
+ the Free Software Foundation; either version 2 of the License, or
+ (at your option) any later version.
+
+ systemd is distributed in the hope that it will be useful, but
+ WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with systemd; If not, see <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
index 0000000000..943808eb0d
--- /dev/null
+++ b/src/notify.c
@@ -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
index 0000000000..6f5a9d9547
--- /dev/null
+++ b/src/nspawn.c
@@ -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
index 0000000000..201afe65be
--- /dev/null
+++ b/src/org.freedesktop.systemd1.conf
@@ -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
index 0000000000..1771314e09
--- /dev/null
+++ b/src/org.freedesktop.systemd1.policy.in.in
@@ -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
index 0000000000..7e1dfd47e0
--- /dev/null
+++ b/src/org.freedesktop.systemd1.service
@@ -0,0 +1,11 @@
+# This file is part of systemd.
+#
+# systemd is free software; you can redistribute it and/or modify it
+# under the terms of the GNU 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
index 0000000000..3fc81820e9
--- /dev/null
+++ b/src/pager.c
@@ -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
index 0000000000..b5b4998445
--- /dev/null
+++ b/src/pager.h
@@ -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
index 0000000000..5464cedbbd
--- /dev/null
+++ b/src/path-lookup.c
@@ -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
index 0000000000..fc2887d3c7
--- /dev/null
+++ b/src/path-lookup.h
@@ -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
index 0000000000..e97cd09810
--- /dev/null
+++ b/src/path.c
@@ -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
index 0000000000..efb6b5eb44
--- /dev/null
+++ b/src/path.h
@@ -0,0 +1,113 @@
+/*-*- Mode: C; c-basic-offset: 8; indent-tabs-mode: nil -*-*/
+
+#ifndef foopathhfoo
+#define foopathhfoo
+
+/***
+ This file is part of systemd.
+
+ Copyright 2010 Lennart Poettering
+
+ systemd is free software; you can redistribute it and/or modify it
+ under the terms of the GNU General Public License as published by
+ the Free Software Foundation; either version 2 of the License, or
+ (at your option) any later version.
+
+ systemd is distributed in the hope that it will be useful, but
+ WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with systemd; If not, see <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
index 0000000000..3acbdc6135
--- /dev/null
+++ b/src/polkit.c
@@ -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
index 0000000000..0d08194b26
--- /dev/null
+++ b/src/polkit.h
@@ -0,0 +1,36 @@
+/*-*- Mode: C; c-basic-offset: 8; indent-tabs-mode: nil -*-*/
+
+#ifndef foopolkithfoo
+#define foopolkithfoo
+
+/***
+ This file is part of systemd.
+
+ Copyright 2010 Lennart Poettering
+
+ systemd is free software; you can redistribute it and/or modify it
+ under the terms of the GNU General Public License as published by
+ the Free Software Foundation; either version 2 of the License, or
+ (at your option) any later version.
+
+ systemd is distributed in the hope that it will be useful, but
+ WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with systemd; If not, see <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
index 0000000000..b6648b8369
--- /dev/null
+++ b/src/quotacheck.c
@@ -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
index 0000000000..8b43bacadc
--- /dev/null
+++ b/src/random-seed.c
@@ -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
index 0000000000..93157c7a2e
--- /dev/null
+++ b/src/ratelimit.c
@@ -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
index 0000000000..a6443e7f87
--- /dev/null
+++ b/src/ratelimit.h
@@ -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
index 0000000000..56785cf402
--- /dev/null
+++ b/src/rc-local-generator.c
@@ -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
index 0000000000..d0b0e8e008
--- /dev/null
+++ b/src/readahead/Makefile
@@ -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
index 0000000000..7e6c243b5e
--- /dev/null
+++ b/src/readahead/readahead-collect.c
@@ -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
index 0000000000..67214ec379
--- /dev/null
+++ b/src/readahead/readahead-common.c
@@ -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
index 0000000000..9547ad201c
--- /dev/null
+++ b/src/readahead/readahead-common.h
@@ -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
index 0000000000..0c739c82be
--- /dev/null
+++ b/src/readahead/readahead-replay.c
@@ -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
index 0000000000..a3340666dd
--- /dev/null
+++ b/src/readahead/sd-readahead.c
@@ -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
index 0000000000..3e146ebb5c
--- /dev/null
+++ b/src/remount-api-vfs.c
@@ -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
index 0000000000..3a96049d7f
--- /dev/null
+++ b/src/reply-password.c
@@ -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
index 0000000000..b4184e1c7e
--- /dev/null
+++ b/src/sd-id128.c
@@ -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
index 0000000000..ba0bba5353
--- /dev/null
+++ b/src/securebits.h
@@ -0,0 +1,45 @@
+#ifndef _LINUX_SECUREBITS_H
+#define _LINUX_SECUREBITS_H 1
+
+/* This is minimal version of Linux' linux/securebits.h header file,
+ * which is licensed GPL2 */
+
+#define SECUREBITS_DEFAULT 0x00000000
+
+/* When set UID 0 has no special privileges. When unset, we support
+ inheritance of root-permissions and suid-root executable under
+ compatibility mode. We raise the effective and inheritable bitmasks
+ *of the executable file* if the effective uid of the new process is
+ 0. If the real uid is 0, we raise the effective (legacy) bit of the
+ executable file. */
+#define SECURE_NOROOT 0
+#define SECURE_NOROOT_LOCKED 1 /* make bit-0 immutable */
+
+/* When set, setuid to/from uid 0 does not trigger capability-"fixup".
+ When unset, to provide compatibility with old programs relying on
+ set*uid to gain/lose privilege, transitions to/from uid 0 cause
+ capabilities to be gained/lost. */
+#define SECURE_NO_SETUID_FIXUP 2
+#define SECURE_NO_SETUID_FIXUP_LOCKED 3 /* make bit-2 immutable */
+
+/* When set, a process can retain its capabilities even after
+ transitioning to a non-root user (the set-uid fixup suppressed by
+ bit 2). Bit-4 is cleared when a process calls exec(); setting both
+ bit 4 and 5 will create a barrier through exec that no exec()'d
+ child can use this feature again. */
+#define SECURE_KEEP_CAPS 4
+#define SECURE_KEEP_CAPS_LOCKED 5 /* make bit-4 immutable */
+
+/* Each securesetting is implemented using two bits. One bit specifies
+ whether the setting is on or off. The other bit specify whether the
+ setting is locked or not. A setting which is locked cannot be
+ changed from user-level. */
+#define issecure_mask(X) (1 << (X))
+#define issecure(X) (issecure_mask(X) & current_cred_xxx(securebits))
+
+#define SECURE_ALL_BITS (issecure_mask(SECURE_NOROOT) | \
+ issecure_mask(SECURE_NO_SETUID_FIXUP) | \
+ issecure_mask(SECURE_KEEP_CAPS))
+#define SECURE_ALL_LOCKS (SECURE_ALL_BITS << 1)
+
+#endif /* !_LINUX_SECUREBITS_H */
diff --git a/src/selinux-setup.c b/src/selinux-setup.c
new file mode 100644
index 0000000000..a7e1fa4007
--- /dev/null
+++ b/src/selinux-setup.c
@@ -0,0 +1,112 @@
+/*-*- Mode: C; c-basic-offset: 8; indent-tabs-mode: nil -*-*/
+
+/***
+ This file is part of systemd.
+
+ Copyright 2010 Lennart Poettering
+
+ systemd is free software; you can redistribute it and/or modify it
+ under the terms of the GNU General Public License as published by
+ the Free Software Foundation; either version 2 of the License, or
+ (at your option) any later version.
+
+ systemd is distributed in the hope that it will be useful, but
+ WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with systemd; If not, see <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
index 0000000000..6b8fe00b7d
--- /dev/null
+++ b/src/selinux-setup.h
@@ -0,0 +1,29 @@
+/*-*- Mode: C; c-basic-offset: 8; indent-tabs-mode: nil -*-*/
+
+#ifndef fooselinuxsetuphfoo
+#define fooselinuxsetuphfoo
+
+/***
+ This file is part of systemd.
+
+ Copyright 2010 Lennart Poettering
+
+ systemd is free software; you can redistribute it and/or modify it
+ under the terms of the GNU General Public License as published by
+ the Free Software Foundation; either version 2 of the License, or
+ (at your option) any later version.
+
+ systemd is distributed in the hope that it will be useful, but
+ WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with systemd; If not, see <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
index 0000000000..bf2e0a9d98
--- /dev/null
+++ b/src/service.c
@@ -0,0 +1,3797 @@
+/*-*- Mode: C; c-basic-offset: 8; indent-tabs-mode: nil -*-*/
+
+/***
+ This file is part of systemd.
+
+ Copyright 2010 Lennart Poettering
+
+ systemd is free software; you can redistribute it and/or modify it
+ under the terms of the GNU General Public License as published by
+ the Free Software Foundation; either version 2 of the License, or
+ (at your option) any later version.
+
+ systemd is distributed in the hope that it will be useful, but
+ WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with systemd; If not, see <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
index 0000000000..60b10516eb
--- /dev/null
+++ b/src/service.h
@@ -0,0 +1,220 @@
+/*-*- Mode: C; c-basic-offset: 8; indent-tabs-mode: nil -*-*/
+
+#ifndef fooservicehfoo
+#define fooservicehfoo
+
+/***
+ This file is part of systemd.
+
+ Copyright 2010 Lennart Poettering
+
+ systemd is free software; you can redistribute it and/or modify it
+ under the terms of the GNU General Public License as published by
+ the Free Software Foundation; either version 2 of the License, or
+ (at your option) any later version.
+
+ systemd is distributed in the hope that it will be useful, but
+ WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with systemd; If not, see <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
index 0000000000..097b9d3aae
--- /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
index 0000000000..885780c4c0
--- /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
index 0000000000..d157e0fbfe
--- /dev/null
+++ b/src/shutdown.c
@@ -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
index 0000000000..b4052d4933
--- /dev/null
+++ b/src/shutdownd.c
@@ -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
index 0000000000..4581649733
--- /dev/null
+++ b/src/shutdownd.h
@@ -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
index 0000000000..82ec5104db
--- /dev/null
+++ b/src/snapshot.c
@@ -0,0 +1,309 @@
+/*-*- Mode: C; c-basic-offset: 8; indent-tabs-mode: nil -*-*/
+
+/***
+ This file is part of systemd.
+
+ Copyright 2010 Lennart Poettering
+
+ systemd is free software; you can redistribute it and/or modify it
+ under the terms of the GNU General Public License as published by
+ the Free Software Foundation; either version 2 of the License, or
+ (at your option) any later version.
+
+ systemd is distributed in the hope that it will be useful, but
+ WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with systemd; If not, see <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
index 0000000000..bf92e99a8b
--- /dev/null
+++ b/src/snapshot.h
@@ -0,0 +1,53 @@
+/*-*- Mode: C; c-basic-offset: 8; indent-tabs-mode: nil -*-*/
+
+#ifndef foosnapshothfoo
+#define foosnapshothfoo
+
+/***
+ This file is part of systemd.
+
+ Copyright 2010 Lennart Poettering
+
+ systemd is free software; you can redistribute it and/or modify it
+ under the terms of the GNU General Public License as published by
+ the Free Software Foundation; either version 2 of the License, or
+ (at your option) any later version.
+
+ systemd is distributed in the hope that it will be useful, but
+ WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with systemd; If not, see <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
index 0000000000..acc4d33372
--- /dev/null
+++ b/src/socket-util.c
@@ -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
index 0000000000..8ccbd371cf
--- /dev/null
+++ b/src/socket-util.h
@@ -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
index 0000000000..bb75d960ab
--- /dev/null
+++ b/src/socket.c
@@ -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
index 0000000000..6470d8b63e
--- /dev/null
+++ b/src/socket.h
@@ -0,0 +1,173 @@
+/*-*- Mode: C; c-basic-offset: 8; indent-tabs-mode: nil -*-*/
+
+#ifndef foosockethfoo
+#define foosockethfoo
+
+/***
+ This file is part of systemd.
+
+ Copyright 2010 Lennart Poettering
+
+ systemd is free software; you can redistribute it and/or modify it
+ under the terms of the GNU General Public License as published by
+ the Free Software Foundation; either version 2 of the License, or
+ (at your option) any later version.
+
+ systemd is distributed in the hope that it will be useful, but
+ WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with systemd; If not, see <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
index 0000000000..2de2530885
--- /dev/null
+++ b/src/spawn-agent.c
@@ -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
index 0000000000..fd0a9109b8
--- /dev/null
+++ b/src/spawn-agent.h
@@ -0,0 +1,28 @@
+/*-*- Mode: C; c-basic-offset: 8; indent-tabs-mode: nil -*-*/
+
+#ifndef foospawnagenthfoo
+#define foospawnagenthfoo
+
+/***
+ This file is part of systemd.
+
+ Copyright 2011 Lennart Poettering
+
+ systemd is free software; you can redistribute it and/or modify it
+ under the terms of the GNU General Public License as published by
+ the Free Software Foundation; either version 2 of the License, or
+ (at your option) any later version.
+
+ systemd is distributed in the hope that it will be useful, but
+ WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with systemd; If not, see <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
index 0000000000..8185eaf60e
--- /dev/null
+++ b/src/special.h
@@ -0,0 +1,88 @@
+/*-*- Mode: C; c-basic-offset: 8; indent-tabs-mode: nil -*-*/
+
+#ifndef foospecialhfoo
+#define foospecialhfoo
+
+/***
+ This file is part of systemd.
+
+ Copyright 2010 Lennart Poettering
+
+ systemd is free software; you can redistribute it and/or modify it
+ under the terms of the GNU General Public License as published by
+ the Free Software Foundation; either version 2 of the License, or
+ (at your option) any later version.
+
+ systemd is distributed in the hope that it will be useful, but
+ WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with systemd; If not, see <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
index 0000000000..a9fff88d35
--- /dev/null
+++ b/src/specifier.c
@@ -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
index 0000000000..041166c90e
--- /dev/null
+++ b/src/specifier.h
@@ -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
index 0000000000..bb309d9f92
--- /dev/null
+++ b/src/strv.c
@@ -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
index 0000000000..d038c9f3b1
--- /dev/null
+++ b/src/strv.h
@@ -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
index 0000000000..9c72732b94
--- /dev/null
+++ b/src/swap.c
@@ -0,0 +1,1415 @@
+/*-*- Mode: C; c-basic-offset: 8; indent-tabs-mode: nil -*-*/
+
+/***
+ This file is part of systemd.
+
+ Copyright 2010 Lennart Poettering
+
+ systemd is free software; you can redistribute it and/or modify it
+ under the terms of the GNU General Public License as published by
+ the Free Software Foundation; either version 2 of the License, or
+ (at your option) any later version.
+
+ systemd is distributed in the hope that it will be useful, but
+ WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with systemd; If not, see <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
index 0000000000..62d08da30b
--- /dev/null
+++ b/src/swap.h
@@ -0,0 +1,128 @@
+/*-*- Mode: C; c-basic-offset: 8; indent-tabs-mode: nil -*-*/
+
+#ifndef fooswaphfoo
+#define fooswaphfoo
+
+/***
+ This file is part of systemd.
+
+ Copyright 2010 Lennart Poettering
+ Copyright 2010 Maarten Lankhorst
+
+ systemd is free software; you can redistribute it and/or modify it
+ under the terms of the GNU General Public License as published by
+ the Free Software Foundation; either version 2 of the License, or
+ (at your option) any later version.
+
+ systemd is distributed in the hope that it will be useful, but
+ WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with systemd; If not, see <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
index 0000000000..17c6719841
--- /dev/null
+++ b/src/sysctl.c
@@ -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
index 0000000000..9939e8b069
--- /dev/null
+++ b/src/sysfs-show.h
@@ -0,0 +1,27 @@
+/*-*- Mode: C; c-basic-offset: 8; indent-tabs-mode: nil -*-*/
+
+#ifndef foosysfsshowhfoo
+#define foosysfsshowhfoo
+
+/***
+ This file is part of systemd.
+
+ Copyright 2011 Lennart Poettering
+
+ systemd is free software; you can redistribute it and/or modify it
+ under the terms of the GNU General Public License as published by
+ the Free Software Foundation; either version 2 of the License, or
+ (at your option) any later version.
+
+ systemd is distributed in the hope that it will be useful, but
+ WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with systemd; If not, see <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
index 0000000000..33d09bccea
--- /dev/null
+++ b/src/system.conf
@@ -0,0 +1,26 @@
+# This file is part of systemd.
+#
+# systemd is free software; you can redistribute it and/or modify it
+# under the terms of the GNU 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
index 0000000000..4b27b69c55
--- /dev/null
+++ b/src/systemctl.c
@@ -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
index 0000000000..a49fbb7eb0
--- /dev/null
+++ b/src/systemd-analyze
@@ -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
index 0000000000..62601de90a
--- /dev/null
+++ b/src/systemd-bash-completion.sh
@@ -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
index 0000000000..79470f4edd
--- /dev/null
+++ b/src/systemd.pc.in
@@ -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
index 0000000000..d0b0e8e008
--- /dev/null
+++ b/src/systemd/Makefile
@@ -0,0 +1 @@
+../Makefile \ No newline at end of file
diff --git a/src/sd-daemon.h b/src/systemd/sd-daemon.h
index fe51159ee6..fe51159ee6 100644
--- a/src/sd-daemon.h
+++ b/src/systemd/sd-daemon.h
diff --git a/src/systemd/sd-id128.h b/src/systemd/sd-id128.h
new file mode 100644
index 0000000000..af2841eb77
--- /dev/null
+++ b/src/systemd/sd-id128.h
@@ -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
index 0000000000..f27e461b03
--- /dev/null
+++ b/src/systemd/sd-journal.h
@@ -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
index 0000000000..6e99cfc951
--- /dev/null
+++ b/src/systemd/sd-login.h
@@ -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
index 0000000000..c5ac3abd0c
--- /dev/null
+++ b/src/systemd/sd-messages.h
@@ -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
index 0000000000..1f8c5a0caf
--- /dev/null
+++ b/src/systemd/sd-readahead.h
@@ -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
index 0000000000..6c1e0c368a
--- /dev/null
+++ b/src/target.c
@@ -0,0 +1,224 @@
+/*-*- Mode: C; c-basic-offset: 8; indent-tabs-mode: nil -*-*/
+
+/***
+ This file is part of systemd.
+
+ Copyright 2010 Lennart Poettering
+
+ systemd is free software; you can redistribute it and/or modify it
+ under the terms of the GNU General Public License as published by
+ the Free Software Foundation; either version 2 of the License, or
+ (at your option) any later version.
+
+ systemd is distributed in the hope that it will be useful, but
+ WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with systemd; If not, see <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
index 0000000000..5b97c86fbf
--- /dev/null
+++ b/src/target.h
@@ -0,0 +1,47 @@
+/*-*- Mode: C; c-basic-offset: 8; indent-tabs-mode: nil -*-*/
+
+#ifndef footargethfoo
+#define footargethfoo
+
+/***
+ This file is part of systemd.
+
+ Copyright 2010 Lennart Poettering
+
+ systemd is free software; you can redistribute it and/or modify it
+ under the terms of the GNU General Public License as published by
+ the Free Software Foundation; either version 2 of the License, or
+ (at your option) any later version.
+
+ systemd is distributed in the hope that it will be useful, but
+ WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with systemd; If not, see <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
index 0000000000..0aab1427dd
--- /dev/null
+++ b/src/tcpwrap.c
@@ -0,0 +1,68 @@
+/*-*- Mode: C; c-basic-offset: 8; indent-tabs-mode: nil -*-*/
+
+/***
+ This file is part of systemd.
+
+ Copyright 2010 Lennart Poettering
+
+ systemd is free software; you can redistribute it and/or modify it
+ under the terms of the GNU General Public License as published by
+ the Free Software Foundation; either version 2 of the License, or
+ (at your option) any later version.
+
+ systemd is distributed in the hope that it will be useful, but
+ WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with systemd; If not, see <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
index 0000000000..4d4553e8ae
--- /dev/null
+++ b/src/tcpwrap.h
@@ -0,0 +1,29 @@
+/*-*- Mode: C; c-basic-offset: 8; indent-tabs-mode: nil -*-*/
+
+#ifndef foolibwraphfoo
+#define foolibwraphfoo
+
+/***
+ This file is part of systemd.
+
+ Copyright 2010 Lennart Poettering
+
+ systemd is free software; you can redistribute it and/or modify it
+ under the terms of the GNU General Public License as published by
+ the Free Software Foundation; either version 2 of the License, or
+ (at your option) any later version.
+
+ systemd is distributed in the hope that it will be useful, but
+ WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with systemd; If not, see <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
index 0000000000..eb189374fa
--- /dev/null
+++ b/src/test-cgroup.c
@@ -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
index 0000000000..20c5d1517e
--- /dev/null
+++ b/src/test-daemon.c
@@ -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
index 0000000000..46a2d2cda0
--- /dev/null
+++ b/src/test-engine.c
@@ -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
index 0000000000..05dbacd7df
--- /dev/null
+++ b/src/test-env-replace.c
@@ -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
index 0000000000..0a08416a17
--- /dev/null
+++ b/src/test-hostname.c
@@ -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
index 0000000000..617c95568b
--- /dev/null
+++ b/src/test-id128.c
@@ -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
index 0000000000..f8e87e0c74
--- /dev/null
+++ b/src/test-install.c
@@ -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
index 0000000000..9de21e1823
--- /dev/null
+++ b/src/test-job-type.c
@@ -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
index 0000000000..9784aaf24b
--- /dev/null
+++ b/src/test-loopback.c
@@ -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
index 0000000000..e2bdfc5894
--- /dev/null
+++ b/src/test-ns.c
@@ -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
index 0000000000..1d577dfd3c
--- /dev/null
+++ b/src/test-strv.c
@@ -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
index 0000000000..48757f0968
--- /dev/null
+++ b/src/timedate/.gitignore
@@ -0,0 +1 @@
+org.freedesktop.timedate1.policy
diff --git a/src/timedate/Makefile b/src/timedate/Makefile
new file mode 120000
index 0000000000..d0b0e8e008
--- /dev/null
+++ b/src/timedate/Makefile
@@ -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
index 0000000000..c9c221b644
--- /dev/null
+++ b/src/timedate/org.freedesktop.timedate1.conf
@@ -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
index 0000000000..3d9c2081d6
--- /dev/null
+++ b/src/timedate/org.freedesktop.timedate1.policy.in
@@ -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
index 0000000000..c3120b66cb
--- /dev/null
+++ b/src/timedate/org.freedesktop.timedate1.service
@@ -0,0 +1,12 @@
+# This file is part of systemd.
+#
+# systemd is free software; you can redistribute it and/or modify it
+# under the terms of the GNU 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
index 0000000000..6a7d980c3e
--- /dev/null
+++ b/src/timedate/timedated.c
@@ -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
index 0000000000..e318fee201
--- /dev/null
+++ b/src/timer.c
@@ -0,0 +1,520 @@
+/*-*- Mode: C; c-basic-offset: 8; indent-tabs-mode: nil -*-*/
+
+/***
+ This file is part of systemd.
+
+ Copyright 2010 Lennart Poettering
+
+ systemd is free software; you can redistribute it and/or modify it
+ under the terms of the GNU General Public License as published by
+ the Free Software Foundation; either version 2 of the License, or
+ (at your option) any later version.
+
+ systemd is distributed in the hope that it will be useful, but
+ WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with systemd; If not, see <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
index 0000000000..f5c5c64f25
--- /dev/null
+++ b/src/timer.h
@@ -0,0 +1,93 @@
+/*-*- Mode: C; c-basic-offset: 8; indent-tabs-mode: nil -*-*/
+
+#ifndef footimerhfoo
+#define footimerhfoo
+
+/***
+ This file is part of systemd.
+
+ Copyright 2010 Lennart Poettering
+
+ systemd is free software; you can redistribute it and/or modify it
+ under the terms of the GNU General Public License as published by
+ the Free Software Foundation; either version 2 of the License, or
+ (at your option) any later version.
+
+ systemd is distributed in the hope that it will be useful, but
+ WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with systemd; If not, see <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
index 0000000000..ce5142946a
--- /dev/null
+++ b/src/timestamp.c
@@ -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
index 0000000000..873bf233fd
--- /dev/null
+++ b/src/tmpfiles.c
@@ -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
index 0000000000..13481b29e9
--- /dev/null
+++ b/src/tty-ask-password-agent.c
@@ -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
index 0000000000..fa3500ba96
--- /dev/null
+++ b/src/udev/.gitignore
@@ -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
index 0000000000..366fbdca4b
--- /dev/null
+++ b/src/udev/.vimrc
@@ -0,0 +1,4 @@
+" 'set exrc' in ~/.vimrc will read .vimrc from the current directory
+set tabstop=8
+set shiftwidth=8
+set expandtab
diff --git a/COPYING b/src/udev/COPYING
index d159169d10..d159169d10 100644
--- a/COPYING
+++ b/src/udev/COPYING
diff --git a/ChangeLog b/src/udev/ChangeLog
index dd58138263..dd58138263 100644
--- a/ChangeLog
+++ b/src/udev/ChangeLog
diff --git a/INSTALL b/src/udev/INSTALL
index 0a34e77df2..0a34e77df2 100644
--- a/INSTALL
+++ b/src/udev/INSTALL
diff --git a/src/udev/Makefile.am b/src/udev/Makefile.am
new file mode 100644
index 0000000000..1c7f86b081
--- /dev/null
+++ b/src/udev/Makefile.am
@@ -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
index 0000000000..f4f6f4e327
--- /dev/null
+++ b/src/udev/NEWS
@@ -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
index 0000000000..38459c6b22
--- /dev/null
+++ b/src/udev/README
@@ -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
index 0000000000..8b8b9c8f8c
--- /dev/null
+++ b/src/udev/TODO
@@ -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
index 0000000000..55ee03afd1
--- /dev/null
+++ b/src/udev/autogen.sh
@@ -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
index 0000000000..b31b62f289
--- /dev/null
+++ b/src/udev/configure.ac
@@ -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
index 0000000000..0ca2c03722
--- /dev/null
+++ b/src/udev/m4/.gitignore
@@ -0,0 +1,4 @@
+libtool.m4
+lt*m4
+gtk-doc.m4
+
diff --git a/rules/42-usb-hid-pm.rules b/src/udev/rules/42-usb-hid-pm.rules
index d5d5897c31..d5d5897c31 100644
--- a/rules/42-usb-hid-pm.rules
+++ b/src/udev/rules/42-usb-hid-pm.rules
diff --git a/rules/50-udev-default.rules b/src/udev/rules/50-udev-default.rules
index 5ad787fc76..5ad787fc76 100644
--- a/rules/50-udev-default.rules
+++ b/src/udev/rules/50-udev-default.rules
diff --git a/rules/60-persistent-alsa.rules b/src/udev/rules/60-persistent-alsa.rules
index 8154e2dbb5..8154e2dbb5 100644
--- a/rules/60-persistent-alsa.rules
+++ b/src/udev/rules/60-persistent-alsa.rules
diff --git a/rules/60-persistent-input.rules b/src/udev/rules/60-persistent-input.rules
index fb798ddb05..fb798ddb05 100644
--- a/rules/60-persistent-input.rules
+++ b/src/udev/rules/60-persistent-input.rules
diff --git a/rules/60-persistent-serial.rules b/src/udev/rules/60-persistent-serial.rules
index 2948200c53..2948200c53 100644
--- a/rules/60-persistent-serial.rules
+++ b/src/udev/rules/60-persistent-serial.rules
diff --git a/rules/60-persistent-storage-tape.rules b/src/udev/rules/60-persistent-storage-tape.rules
index f2eabd92a8..f2eabd92a8 100644
--- a/rules/60-persistent-storage-tape.rules
+++ b/src/udev/rules/60-persistent-storage-tape.rules
diff --git a/rules/60-persistent-storage.rules b/src/udev/rules/60-persistent-storage.rules
index b74821edd4..b74821edd4 100644
--- a/rules/60-persistent-storage.rules
+++ b/src/udev/rules/60-persistent-storage.rules
diff --git a/rules/75-net-description.rules b/src/udev/rules/75-net-description.rules
index ce57d48e86..ce57d48e86 100644
--- a/rules/75-net-description.rules
+++ b/src/udev/rules/75-net-description.rules
diff --git a/rules/75-tty-description.rules b/src/udev/rules/75-tty-description.rules
index 2e63e140cb..2e63e140cb 100644
--- a/rules/75-tty-description.rules
+++ b/src/udev/rules/75-tty-description.rules
diff --git a/rules/78-sound-card.rules b/src/udev/rules/78-sound-card.rules
index e564441893..e564441893 100644
--- a/rules/78-sound-card.rules
+++ b/src/udev/rules/78-sound-card.rules
diff --git a/rules/80-drivers.rules b/src/udev/rules/80-drivers.rules
index 38ebfeb0e6..38ebfeb0e6 100644
--- a/rules/80-drivers.rules
+++ b/src/udev/rules/80-drivers.rules
diff --git a/rules/95-udev-late.rules b/src/udev/rules/95-udev-late.rules
index eca0faa5c5..eca0faa5c5 100644
--- a/rules/95-udev-late.rules
+++ b/src/udev/rules/95-udev-late.rules
diff --git a/src/udev/src/.gitignore b/src/udev/src/.gitignore
new file mode 100644
index 0000000000..beb8604bc6
--- /dev/null
+++ b/src/udev/src/.gitignore
@@ -0,0 +1,5 @@
+*.[78]
+*.html
+udev.pc
+libudev.pc
+udev*.service
diff --git a/src/COPYING b/src/udev/src/COPYING
index d2e31278b0..d2e31278b0 100644
--- a/src/COPYING
+++ b/src/udev/src/COPYING
diff --git a/src/accelerometer/61-accelerometer.rules b/src/udev/src/accelerometer/61-accelerometer.rules
index a6a2bfd088..a6a2bfd088 100644
--- a/src/accelerometer/61-accelerometer.rules
+++ b/src/udev/src/accelerometer/61-accelerometer.rules
diff --git a/src/accelerometer/accelerometer.c b/src/udev/src/accelerometer/accelerometer.c
index bc9715b264..bc9715b264 100644
--- a/src/accelerometer/accelerometer.c
+++ b/src/udev/src/accelerometer/accelerometer.c
diff --git a/src/ata_id/ata_id.c b/src/udev/src/ata_id/ata_id.c
index 846a73b547..846a73b547 100644
--- a/src/ata_id/ata_id.c
+++ b/src/udev/src/ata_id/ata_id.c
diff --git a/src/cdrom_id/60-cdrom_id.rules b/src/udev/src/cdrom_id/60-cdrom_id.rules
index 6eaf76a72c..6eaf76a72c 100644
--- a/src/cdrom_id/60-cdrom_id.rules
+++ b/src/udev/src/cdrom_id/60-cdrom_id.rules
diff --git a/src/cdrom_id/cdrom_id.c b/src/udev/src/cdrom_id/cdrom_id.c
index f90d52ec9c..f90d52ec9c 100644
--- a/src/cdrom_id/cdrom_id.c
+++ b/src/udev/src/cdrom_id/cdrom_id.c
diff --git a/src/collect/collect.c b/src/udev/src/collect/collect.c
index 076fe479e2..076fe479e2 100644
--- a/src/collect/collect.c
+++ b/src/udev/src/collect/collect.c
diff --git a/src/docs/.gitignore b/src/udev/src/docs/.gitignore
index dca700a998..dca700a998 100644
--- a/src/docs/.gitignore
+++ b/src/udev/src/docs/.gitignore
diff --git a/src/docs/Makefile.am b/src/udev/src/docs/Makefile.am
index 07d06eb14f..07d06eb14f 100644
--- a/src/docs/Makefile.am
+++ b/src/udev/src/docs/Makefile.am
diff --git a/src/docs/libudev-docs.xml b/src/udev/src/docs/libudev-docs.xml
index b7feb45529..b7feb45529 100644
--- a/src/docs/libudev-docs.xml
+++ b/src/udev/src/docs/libudev-docs.xml
diff --git a/src/docs/libudev-sections.txt b/src/udev/src/docs/libudev-sections.txt
index 15c3e934b5..15c3e934b5 100644
--- a/src/docs/libudev-sections.txt
+++ b/src/udev/src/docs/libudev-sections.txt
diff --git a/src/docs/libudev.types b/src/udev/src/docs/libudev.types
index e69de29bb2..e69de29bb2 100644
--- a/src/docs/libudev.types
+++ b/src/udev/src/docs/libudev.types
diff --git a/src/docs/version.xml.in b/src/udev/src/docs/version.xml.in
index d78bda9342..d78bda9342 100644
--- a/src/docs/version.xml.in
+++ b/src/udev/src/docs/version.xml.in
diff --git a/src/floppy/60-floppy.rules b/src/udev/src/floppy/60-floppy.rules
index 53e4a9e59a..53e4a9e59a 100644
--- a/src/floppy/60-floppy.rules
+++ b/src/udev/src/floppy/60-floppy.rules
diff --git a/src/floppy/create_floppy_devices.c b/src/udev/src/floppy/create_floppy_devices.c
index f71ef0d809..f71ef0d809 100644
--- a/src/floppy/create_floppy_devices.c
+++ b/src/udev/src/floppy/create_floppy_devices.c
diff --git a/src/gudev/.gitignore b/src/udev/src/gudev/.gitignore
index d20fa523e4..d20fa523e4 100644
--- a/src/gudev/.gitignore
+++ b/src/udev/src/gudev/.gitignore
diff --git a/src/gudev/COPYING b/src/udev/src/gudev/COPYING
index da97db2bd0..da97db2bd0 100644
--- a/src/gudev/COPYING
+++ b/src/udev/src/gudev/COPYING
diff --git a/src/gudev/docs/.gitignore b/src/udev/src/gudev/docs/.gitignore
index 8eada6d409..8eada6d409 100644
--- a/src/gudev/docs/.gitignore
+++ b/src/udev/src/gudev/docs/.gitignore
diff --git a/src/gudev/docs/Makefile.am b/src/udev/src/gudev/docs/Makefile.am
index cfe696c503..cfe696c503 100644
--- a/src/gudev/docs/Makefile.am
+++ b/src/udev/src/gudev/docs/Makefile.am
diff --git a/src/gudev/docs/gudev-docs.xml b/src/udev/src/gudev/docs/gudev-docs.xml
index f876c3bc05..f876c3bc05 100644
--- a/src/gudev/docs/gudev-docs.xml
+++ b/src/udev/src/gudev/docs/gudev-docs.xml
diff --git a/src/gudev/docs/gudev-sections.txt b/src/udev/src/gudev/docs/gudev-sections.txt
index 213e1a7465..213e1a7465 100644
--- a/src/gudev/docs/gudev-sections.txt
+++ b/src/udev/src/gudev/docs/gudev-sections.txt
diff --git a/src/gudev/docs/gudev.types b/src/udev/src/gudev/docs/gudev.types
index a89857a04d..a89857a04d 100644
--- a/src/gudev/docs/gudev.types
+++ b/src/udev/src/gudev/docs/gudev.types
diff --git a/src/gudev/docs/version.xml.in b/src/udev/src/gudev/docs/version.xml.in
index d78bda9342..d78bda9342 100644
--- a/src/gudev/docs/version.xml.in
+++ b/src/udev/src/gudev/docs/version.xml.in
diff --git a/src/gudev/gjs-example.js b/src/udev/src/gudev/gjs-example.js
index 5586fd6a61..5586fd6a61 100755
--- a/src/gudev/gjs-example.js
+++ b/src/udev/src/gudev/gjs-example.js
diff --git a/src/gudev/gudev-1.0.pc.in b/src/udev/src/gudev/gudev-1.0.pc.in
index 058262d767..058262d767 100644
--- a/src/gudev/gudev-1.0.pc.in
+++ b/src/udev/src/gudev/gudev-1.0.pc.in
diff --git a/src/gudev/gudev.h b/src/udev/src/gudev/gudev.h
index a313460817..a313460817 100644
--- a/src/gudev/gudev.h
+++ b/src/udev/src/gudev/gudev.h
diff --git a/src/gudev/gudevclient.c b/src/udev/src/gudev/gudevclient.c
index 2b94102ac5..2b94102ac5 100644
--- a/src/gudev/gudevclient.c
+++ b/src/udev/src/gudev/gudevclient.c
diff --git a/src/gudev/gudevclient.h b/src/udev/src/gudev/gudevclient.h
index b425d03d48..b425d03d48 100644
--- a/src/gudev/gudevclient.h
+++ b/src/udev/src/gudev/gudevclient.h
diff --git a/src/gudev/gudevdevice.c b/src/udev/src/gudev/gudevdevice.c
index 62a26f99b7..62a26f99b7 100644
--- a/src/gudev/gudevdevice.c
+++ b/src/udev/src/gudev/gudevdevice.c
diff --git a/src/gudev/gudevdevice.h b/src/udev/src/gudev/gudevdevice.h
index d4873bad0f..d4873bad0f 100644
--- a/src/gudev/gudevdevice.h
+++ b/src/udev/src/gudev/gudevdevice.h
diff --git a/src/gudev/gudevenumerator.c b/src/udev/src/gudev/gudevenumerator.c
index db09074625..db09074625 100644
--- a/src/gudev/gudevenumerator.c
+++ b/src/udev/src/gudev/gudevenumerator.c
diff --git a/src/gudev/gudevenumerator.h b/src/udev/src/gudev/gudevenumerator.h
index 3fddccf573..3fddccf573 100644
--- a/src/gudev/gudevenumerator.h
+++ b/src/udev/src/gudev/gudevenumerator.h
diff --git a/src/gudev/gudevenums.h b/src/udev/src/gudev/gudevenums.h
index c3a0aa8747..c3a0aa8747 100644
--- a/src/gudev/gudevenums.h
+++ b/src/udev/src/gudev/gudevenums.h
diff --git a/src/gudev/gudevenumtypes.c.template b/src/udev/src/gudev/gudevenumtypes.c.template
index fc30b39e2e..fc30b39e2e 100644
--- a/src/gudev/gudevenumtypes.c.template
+++ b/src/udev/src/gudev/gudevenumtypes.c.template
diff --git a/src/gudev/gudevenumtypes.h.template b/src/udev/src/gudev/gudevenumtypes.h.template
index d0ab3393e6..d0ab3393e6 100644
--- a/src/gudev/gudevenumtypes.h.template
+++ b/src/udev/src/gudev/gudevenumtypes.h.template
diff --git a/src/gudev/gudevmarshal.list b/src/udev/src/gudev/gudevmarshal.list
index 7e665999e8..7e665999e8 100644
--- a/src/gudev/gudevmarshal.list
+++ b/src/udev/src/gudev/gudevmarshal.list
diff --git a/src/gudev/gudevprivate.h b/src/udev/src/gudev/gudevprivate.h
index 8866f52b88..8866f52b88 100644
--- a/src/gudev/gudevprivate.h
+++ b/src/udev/src/gudev/gudevprivate.h
diff --git a/src/gudev/gudevtypes.h b/src/udev/src/gudev/gudevtypes.h
index 888482783d..888482783d 100644
--- a/src/gudev/gudevtypes.h
+++ b/src/udev/src/gudev/gudevtypes.h
diff --git a/src/gudev/seed-example-enum.js b/src/udev/src/gudev/seed-example-enum.js
index 66206ad806..66206ad806 100755
--- a/src/gudev/seed-example-enum.js
+++ b/src/udev/src/gudev/seed-example-enum.js
diff --git a/src/gudev/seed-example.js b/src/udev/src/gudev/seed-example.js
index e2ac324d23..e2ac324d23 100755
--- a/src/gudev/seed-example.js
+++ b/src/udev/src/gudev/seed-example.js
diff --git a/src/keymap/.gitignore b/src/udev/src/keymap/.gitignore
index 4567584f4e..4567584f4e 100644
--- a/src/keymap/.gitignore
+++ b/src/udev/src/keymap/.gitignore
diff --git a/src/keymap/95-keyboard-force-release.rules b/src/udev/src/keymap/95-keyboard-force-release.rules
index 03d56e8aa4..03d56e8aa4 100644
--- a/src/keymap/95-keyboard-force-release.rules
+++ b/src/udev/src/keymap/95-keyboard-force-release.rules
diff --git a/src/keymap/95-keymap.rules b/src/udev/src/keymap/95-keymap.rules
index bbf311a17a..bbf311a17a 100644
--- a/src/keymap/95-keymap.rules
+++ b/src/udev/src/keymap/95-keymap.rules
diff --git a/src/keymap/README.keymap.txt b/src/udev/src/keymap/README.keymap.txt
index 52d50ed2de..52d50ed2de 100644
--- a/src/keymap/README.keymap.txt
+++ b/src/udev/src/keymap/README.keymap.txt
diff --git a/src/keymap/check-keymaps.sh b/src/udev/src/keymap/check-keymaps.sh
index 405168c667..405168c667 100755
--- a/src/keymap/check-keymaps.sh
+++ b/src/udev/src/keymap/check-keymaps.sh
diff --git a/src/keymap/findkeyboards b/src/udev/src/keymap/findkeyboards
index 9ce27429b2..9ce27429b2 100755
--- a/src/keymap/findkeyboards
+++ b/src/udev/src/keymap/findkeyboards
diff --git a/src/keymap/force-release-maps/common-volume-keys b/src/udev/src/keymap/force-release-maps/common-volume-keys
index 3a7654d735..3a7654d735 100644
--- a/src/keymap/force-release-maps/common-volume-keys
+++ b/src/udev/src/keymap/force-release-maps/common-volume-keys
diff --git a/src/keymap/force-release-maps/dell-touchpad b/src/udev/src/keymap/force-release-maps/dell-touchpad
index 18e9bdee66..18e9bdee66 100644
--- a/src/keymap/force-release-maps/dell-touchpad
+++ b/src/udev/src/keymap/force-release-maps/dell-touchpad
diff --git a/src/keymap/force-release-maps/hp-other b/src/udev/src/keymap/force-release-maps/hp-other
index 6621370095..6621370095 100644
--- a/src/keymap/force-release-maps/hp-other
+++ b/src/udev/src/keymap/force-release-maps/hp-other
diff --git a/src/keymap/force-release-maps/samsung-90x3a b/src/udev/src/keymap/force-release-maps/samsung-90x3a
index 65707effb7..65707effb7 100644
--- a/src/keymap/force-release-maps/samsung-90x3a
+++ b/src/udev/src/keymap/force-release-maps/samsung-90x3a
diff --git a/src/keymap/force-release-maps/samsung-other b/src/udev/src/keymap/force-release-maps/samsung-other
index c51123a0b6..c51123a0b6 100644
--- a/src/keymap/force-release-maps/samsung-other
+++ b/src/udev/src/keymap/force-release-maps/samsung-other
diff --git a/src/keymap/keyboard-force-release.sh.in b/src/udev/src/keymap/keyboard-force-release.sh.in
index dd040cebc3..dd040cebc3 100755
--- a/src/keymap/keyboard-force-release.sh.in
+++ b/src/udev/src/keymap/keyboard-force-release.sh.in
diff --git a/src/keymap/keymap.c b/src/udev/src/keymap/keymap.c
index 92ec67b3a6..92ec67b3a6 100644
--- a/src/keymap/keymap.c
+++ b/src/udev/src/keymap/keymap.c
diff --git a/src/keymap/keymaps/acer b/src/udev/src/keymap/keymaps/acer
index 4e7c297dea..4e7c297dea 100644
--- a/src/keymap/keymaps/acer
+++ b/src/udev/src/keymap/keymaps/acer
diff --git a/src/keymap/keymaps/acer-aspire_5720 b/src/udev/src/keymap/keymaps/acer-aspire_5720
index 1496d63a52..1496d63a52 100644
--- a/src/keymap/keymaps/acer-aspire_5720
+++ b/src/udev/src/keymap/keymaps/acer-aspire_5720
diff --git a/src/keymap/keymaps/acer-aspire_5920g b/src/udev/src/keymap/keymaps/acer-aspire_5920g
index 633c4e854c..633c4e854c 100644
--- a/src/keymap/keymaps/acer-aspire_5920g
+++ b/src/udev/src/keymap/keymaps/acer-aspire_5920g
diff --git a/src/keymap/keymaps/acer-aspire_6920 b/src/udev/src/keymap/keymaps/acer-aspire_6920
index 699c954b4e..699c954b4e 100644
--- a/src/keymap/keymaps/acer-aspire_6920
+++ b/src/udev/src/keymap/keymaps/acer-aspire_6920
diff --git a/src/keymap/keymaps/acer-aspire_8930 b/src/udev/src/keymap/keymaps/acer-aspire_8930
index fb27bfb4f5..fb27bfb4f5 100644
--- a/src/keymap/keymaps/acer-aspire_8930
+++ b/src/udev/src/keymap/keymaps/acer-aspire_8930
diff --git a/src/keymap/keymaps/acer-travelmate_c300 b/src/udev/src/keymap/keymaps/acer-travelmate_c300
index bfef4cf868..bfef4cf868 100644
--- a/src/keymap/keymaps/acer-travelmate_c300
+++ b/src/udev/src/keymap/keymaps/acer-travelmate_c300
diff --git a/src/keymap/keymaps/asus b/src/udev/src/keymap/keymaps/asus
index 2a5995f982..2a5995f982 100644
--- a/src/keymap/keymaps/asus
+++ b/src/udev/src/keymap/keymaps/asus
diff --git a/src/keymap/keymaps/compaq-e_evo b/src/udev/src/keymap/keymaps/compaq-e_evo
index 5fbc573aa4..5fbc573aa4 100644
--- a/src/keymap/keymaps/compaq-e_evo
+++ b/src/udev/src/keymap/keymaps/compaq-e_evo
diff --git a/src/keymap/keymaps/dell b/src/udev/src/keymap/keymaps/dell
index 4f907b3eef..4f907b3eef 100644
--- a/src/keymap/keymaps/dell
+++ b/src/udev/src/keymap/keymaps/dell
diff --git a/src/keymap/keymaps/dell-latitude-xt2 b/src/udev/src/keymap/keymaps/dell-latitude-xt2
index 39872f559d..39872f559d 100644
--- a/src/keymap/keymaps/dell-latitude-xt2
+++ b/src/udev/src/keymap/keymaps/dell-latitude-xt2
diff --git a/src/keymap/keymaps/everex-xt5000 b/src/udev/src/keymap/keymaps/everex-xt5000
index 4823a832f5..4823a832f5 100644
--- a/src/keymap/keymaps/everex-xt5000
+++ b/src/udev/src/keymap/keymaps/everex-xt5000
diff --git a/src/keymap/keymaps/fujitsu-amilo_li_2732 b/src/udev/src/keymap/keymaps/fujitsu-amilo_li_2732
index 9b8b36a170..9b8b36a170 100644
--- a/src/keymap/keymaps/fujitsu-amilo_li_2732
+++ b/src/udev/src/keymap/keymaps/fujitsu-amilo_li_2732
diff --git a/src/keymap/keymaps/fujitsu-amilo_pa_2548 b/src/udev/src/keymap/keymaps/fujitsu-amilo_pa_2548
index f7b0c52444..f7b0c52444 100644
--- a/src/keymap/keymaps/fujitsu-amilo_pa_2548
+++ b/src/udev/src/keymap/keymaps/fujitsu-amilo_pa_2548
diff --git a/src/keymap/keymaps/fujitsu-amilo_pro_edition_v3505 b/src/udev/src/keymap/keymaps/fujitsu-amilo_pro_edition_v3505
index d2e38cbb23..d2e38cbb23 100644
--- a/src/keymap/keymaps/fujitsu-amilo_pro_edition_v3505
+++ b/src/udev/src/keymap/keymaps/fujitsu-amilo_pro_edition_v3505
diff --git a/src/keymap/keymaps/fujitsu-amilo_pro_v3205 b/src/udev/src/keymap/keymaps/fujitsu-amilo_pro_v3205
index 43e3199d59..43e3199d59 100644
--- a/src/keymap/keymaps/fujitsu-amilo_pro_v3205
+++ b/src/udev/src/keymap/keymaps/fujitsu-amilo_pro_v3205
diff --git a/src/keymap/keymaps/fujitsu-amilo_si_1520 b/src/udev/src/keymap/keymaps/fujitsu-amilo_si_1520
index 1419bd9b5e..1419bd9b5e 100644
--- a/src/keymap/keymaps/fujitsu-amilo_si_1520
+++ b/src/udev/src/keymap/keymaps/fujitsu-amilo_si_1520
diff --git a/src/keymap/keymaps/fujitsu-esprimo_mobile_v5 b/src/udev/src/keymap/keymaps/fujitsu-esprimo_mobile_v5
index d3d056b366..d3d056b366 100644
--- a/src/keymap/keymaps/fujitsu-esprimo_mobile_v5
+++ b/src/udev/src/keymap/keymaps/fujitsu-esprimo_mobile_v5
diff --git a/src/keymap/keymaps/fujitsu-esprimo_mobile_v6 b/src/udev/src/keymap/keymaps/fujitsu-esprimo_mobile_v6
index 52c70c50cb..52c70c50cb 100644
--- a/src/keymap/keymaps/fujitsu-esprimo_mobile_v6
+++ b/src/udev/src/keymap/keymaps/fujitsu-esprimo_mobile_v6
diff --git a/src/keymap/keymaps/genius-slimstar-320 b/src/udev/src/keymap/keymaps/genius-slimstar-320
index d0a3656dd8..d0a3656dd8 100644
--- a/src/keymap/keymaps/genius-slimstar-320
+++ b/src/udev/src/keymap/keymaps/genius-slimstar-320
diff --git a/src/keymap/keymaps/hewlett-packard b/src/udev/src/keymap/keymaps/hewlett-packard
index 4461fa2ce5..4461fa2ce5 100644
--- a/src/keymap/keymaps/hewlett-packard
+++ b/src/udev/src/keymap/keymaps/hewlett-packard
diff --git a/src/keymap/keymaps/hewlett-packard-2510p_2530p b/src/udev/src/keymap/keymaps/hewlett-packard-2510p_2530p
index 41ad2e9b5a..41ad2e9b5a 100644
--- a/src/keymap/keymaps/hewlett-packard-2510p_2530p
+++ b/src/udev/src/keymap/keymaps/hewlett-packard-2510p_2530p
diff --git a/src/keymap/keymaps/hewlett-packard-compaq_elitebook b/src/udev/src/keymap/keymaps/hewlett-packard-compaq_elitebook
index 42007c5483..42007c5483 100644
--- a/src/keymap/keymaps/hewlett-packard-compaq_elitebook
+++ b/src/udev/src/keymap/keymaps/hewlett-packard-compaq_elitebook
diff --git a/src/keymap/keymaps/hewlett-packard-pavilion b/src/udev/src/keymap/keymaps/hewlett-packard-pavilion
index 3d3cefc8e6..3d3cefc8e6 100644
--- a/src/keymap/keymaps/hewlett-packard-pavilion
+++ b/src/udev/src/keymap/keymaps/hewlett-packard-pavilion
diff --git a/src/keymap/keymaps/hewlett-packard-presario-2100 b/src/udev/src/keymap/keymaps/hewlett-packard-presario-2100
index 1df39dcbd2..1df39dcbd2 100644
--- a/src/keymap/keymaps/hewlett-packard-presario-2100
+++ b/src/udev/src/keymap/keymaps/hewlett-packard-presario-2100
diff --git a/src/keymap/keymaps/hewlett-packard-tablet b/src/udev/src/keymap/keymaps/hewlett-packard-tablet
index d19005ab90..d19005ab90 100644
--- a/src/keymap/keymaps/hewlett-packard-tablet
+++ b/src/udev/src/keymap/keymaps/hewlett-packard-tablet
diff --git a/src/keymap/keymaps/hewlett-packard-tx2 b/src/udev/src/keymap/keymaps/hewlett-packard-tx2
index 36a690fcf6..36a690fcf6 100644
--- a/src/keymap/keymaps/hewlett-packard-tx2
+++ b/src/udev/src/keymap/keymaps/hewlett-packard-tx2
diff --git a/src/keymap/keymaps/ibm-thinkpad-usb-keyboard-trackpoint b/src/udev/src/keymap/keymaps/ibm-thinkpad-usb-keyboard-trackpoint
index 027e50bf88..027e50bf88 100644
--- a/src/keymap/keymaps/ibm-thinkpad-usb-keyboard-trackpoint
+++ b/src/udev/src/keymap/keymaps/ibm-thinkpad-usb-keyboard-trackpoint
diff --git a/src/keymap/keymaps/inventec-symphony_6.0_7.0 b/src/udev/src/keymap/keymaps/inventec-symphony_6.0_7.0
index 4a8b4ba5a7..4a8b4ba5a7 100644
--- a/src/keymap/keymaps/inventec-symphony_6.0_7.0
+++ b/src/udev/src/keymap/keymaps/inventec-symphony_6.0_7.0
diff --git a/src/keymap/keymaps/lenovo-3000 b/src/udev/src/keymap/keymaps/lenovo-3000
index 5bd165654a..5bd165654a 100644
--- a/src/keymap/keymaps/lenovo-3000
+++ b/src/udev/src/keymap/keymaps/lenovo-3000
diff --git a/src/keymap/keymaps/lenovo-ideapad b/src/udev/src/keymap/keymaps/lenovo-ideapad
index fc339839f2..fc339839f2 100644
--- a/src/keymap/keymaps/lenovo-ideapad
+++ b/src/udev/src/keymap/keymaps/lenovo-ideapad
diff --git a/src/keymap/keymaps/lenovo-thinkpad-usb-keyboard-trackpoint b/src/udev/src/keymap/keymaps/lenovo-thinkpad-usb-keyboard-trackpoint
index 47e8846a68..47e8846a68 100644
--- a/src/keymap/keymaps/lenovo-thinkpad-usb-keyboard-trackpoint
+++ b/src/udev/src/keymap/keymaps/lenovo-thinkpad-usb-keyboard-trackpoint
diff --git a/src/keymap/keymaps/lenovo-thinkpad_x200_tablet b/src/udev/src/keymap/keymaps/lenovo-thinkpad_x200_tablet
index 31ea3b2c70..31ea3b2c70 100644
--- a/src/keymap/keymaps/lenovo-thinkpad_x200_tablet
+++ b/src/udev/src/keymap/keymaps/lenovo-thinkpad_x200_tablet
diff --git a/src/keymap/keymaps/lenovo-thinkpad_x6_tablet b/src/udev/src/keymap/keymaps/lenovo-thinkpad_x6_tablet
index 6fd16b5662..6fd16b5662 100644
--- a/src/keymap/keymaps/lenovo-thinkpad_x6_tablet
+++ b/src/udev/src/keymap/keymaps/lenovo-thinkpad_x6_tablet
diff --git a/src/keymap/keymaps/lg-x110 b/src/udev/src/keymap/keymaps/lg-x110
index ba08cba3fe..ba08cba3fe 100644
--- a/src/keymap/keymaps/lg-x110
+++ b/src/udev/src/keymap/keymaps/lg-x110
diff --git a/src/keymap/keymaps/logitech-wave b/src/udev/src/keymap/keymaps/logitech-wave
index caa5d5d310..caa5d5d310 100644
--- a/src/keymap/keymaps/logitech-wave
+++ b/src/udev/src/keymap/keymaps/logitech-wave
diff --git a/src/keymap/keymaps/logitech-wave-cordless b/src/udev/src/keymap/keymaps/logitech-wave-cordless
index a10dad5e4d..a10dad5e4d 100644
--- a/src/keymap/keymaps/logitech-wave-cordless
+++ b/src/udev/src/keymap/keymaps/logitech-wave-cordless
diff --git a/src/keymap/keymaps/logitech-wave-pro-cordless b/src/udev/src/keymap/keymaps/logitech-wave-pro-cordless
index e7aa02206c..e7aa02206c 100644
--- a/src/keymap/keymaps/logitech-wave-pro-cordless
+++ b/src/udev/src/keymap/keymaps/logitech-wave-pro-cordless
diff --git a/src/keymap/keymaps/maxdata-pro_7000 b/src/udev/src/keymap/keymaps/maxdata-pro_7000
index c0e4f77af4..c0e4f77af4 100644
--- a/src/keymap/keymaps/maxdata-pro_7000
+++ b/src/udev/src/keymap/keymaps/maxdata-pro_7000
diff --git a/src/keymap/keymaps/medion-fid2060 b/src/udev/src/keymap/keymaps/medion-fid2060
index 5a76c76799..5a76c76799 100644
--- a/src/keymap/keymaps/medion-fid2060
+++ b/src/udev/src/keymap/keymaps/medion-fid2060
diff --git a/src/keymap/keymaps/medionnb-a555 b/src/udev/src/keymap/keymaps/medionnb-a555
index c3b5dfa60b..c3b5dfa60b 100644
--- a/src/keymap/keymaps/medionnb-a555
+++ b/src/udev/src/keymap/keymaps/medionnb-a555
diff --git a/src/keymap/keymaps/micro-star b/src/udev/src/keymap/keymaps/micro-star
index 4a438698ed..4a438698ed 100644
--- a/src/keymap/keymaps/micro-star
+++ b/src/udev/src/keymap/keymaps/micro-star
diff --git a/src/keymap/keymaps/module-asus-w3j b/src/udev/src/keymap/keymaps/module-asus-w3j
index 773e0b3e82..773e0b3e82 100644
--- a/src/keymap/keymaps/module-asus-w3j
+++ b/src/udev/src/keymap/keymaps/module-asus-w3j
diff --git a/src/keymap/keymaps/module-ibm b/src/udev/src/keymap/keymaps/module-ibm
index a92dfa2506..a92dfa2506 100644
--- a/src/keymap/keymaps/module-ibm
+++ b/src/udev/src/keymap/keymaps/module-ibm
diff --git a/src/keymap/keymaps/module-lenovo b/src/udev/src/keymap/keymaps/module-lenovo
index 8e38883091..8e38883091 100644
--- a/src/keymap/keymaps/module-lenovo
+++ b/src/udev/src/keymap/keymaps/module-lenovo
diff --git a/src/keymap/keymaps/module-sony b/src/udev/src/keymap/keymaps/module-sony
index 7c000131d1..7c000131d1 100644
--- a/src/keymap/keymaps/module-sony
+++ b/src/udev/src/keymap/keymaps/module-sony
diff --git a/src/keymap/keymaps/module-sony-old b/src/udev/src/keymap/keymaps/module-sony-old
index 596a34258a..596a34258a 100644
--- a/src/keymap/keymaps/module-sony-old
+++ b/src/udev/src/keymap/keymaps/module-sony-old
diff --git a/src/keymap/keymaps/module-sony-vgn b/src/udev/src/keymap/keymaps/module-sony-vgn
index c8ba001516..c8ba001516 100644
--- a/src/keymap/keymaps/module-sony-vgn
+++ b/src/udev/src/keymap/keymaps/module-sony-vgn
diff --git a/src/keymap/keymaps/olpc-xo b/src/udev/src/keymap/keymaps/olpc-xo
index 34434a121d..34434a121d 100644
--- a/src/keymap/keymaps/olpc-xo
+++ b/src/udev/src/keymap/keymaps/olpc-xo
diff --git a/src/keymap/keymaps/onkyo b/src/udev/src/keymap/keymaps/onkyo
index ee864ade4d..ee864ade4d 100644
--- a/src/keymap/keymaps/onkyo
+++ b/src/udev/src/keymap/keymaps/onkyo
diff --git a/src/keymap/keymaps/oqo-model2 b/src/udev/src/keymap/keymaps/oqo-model2
index b7f4851abe..b7f4851abe 100644
--- a/src/keymap/keymaps/oqo-model2
+++ b/src/udev/src/keymap/keymaps/oqo-model2
diff --git a/src/keymap/keymaps/samsung-90x3a b/src/udev/src/keymap/keymaps/samsung-90x3a
index 8b65eb6d03..8b65eb6d03 100644
--- a/src/keymap/keymaps/samsung-90x3a
+++ b/src/udev/src/keymap/keymaps/samsung-90x3a
diff --git a/src/keymap/keymaps/samsung-other b/src/udev/src/keymap/keymaps/samsung-other
index 3ac0c2f10c..3ac0c2f10c 100644
--- a/src/keymap/keymaps/samsung-other
+++ b/src/udev/src/keymap/keymaps/samsung-other
diff --git a/src/keymap/keymaps/samsung-sq1us b/src/udev/src/keymap/keymaps/samsung-sq1us
index ea2141ef84..ea2141ef84 100644
--- a/src/keymap/keymaps/samsung-sq1us
+++ b/src/udev/src/keymap/keymaps/samsung-sq1us
diff --git a/src/keymap/keymaps/samsung-sx20s b/src/udev/src/keymap/keymaps/samsung-sx20s
index 9d954ee415..9d954ee415 100644
--- a/src/keymap/keymaps/samsung-sx20s
+++ b/src/udev/src/keymap/keymaps/samsung-sx20s
diff --git a/src/keymap/keymaps/toshiba-satellite_a100 b/src/udev/src/keymap/keymaps/toshiba-satellite_a100
index 22007be71b..22007be71b 100644
--- a/src/keymap/keymaps/toshiba-satellite_a100
+++ b/src/udev/src/keymap/keymaps/toshiba-satellite_a100
diff --git a/src/keymap/keymaps/toshiba-satellite_a110 b/src/udev/src/keymap/keymaps/toshiba-satellite_a110
index 1429409351..1429409351 100644
--- a/src/keymap/keymaps/toshiba-satellite_a110
+++ b/src/udev/src/keymap/keymaps/toshiba-satellite_a110
diff --git a/src/keymap/keymaps/toshiba-satellite_m30x b/src/udev/src/keymap/keymaps/toshiba-satellite_m30x
index ae8e34941b..ae8e34941b 100644
--- a/src/keymap/keymaps/toshiba-satellite_m30x
+++ b/src/udev/src/keymap/keymaps/toshiba-satellite_m30x
diff --git a/src/keymap/keymaps/zepto-znote b/src/udev/src/keymap/keymaps/zepto-znote
index cf72fda47b..cf72fda47b 100644
--- a/src/keymap/keymaps/zepto-znote
+++ b/src/udev/src/keymap/keymaps/zepto-znote
diff --git a/src/libudev-device-private.c b/src/udev/src/libudev-device-private.c
index 13fdb8eb57..13fdb8eb57 100644
--- a/src/libudev-device-private.c
+++ b/src/udev/src/libudev-device-private.c
diff --git a/src/libudev-device.c b/src/udev/src/libudev-device.c
index 10f28b8cd5..10f28b8cd5 100644
--- a/src/libudev-device.c
+++ b/src/udev/src/libudev-device.c
diff --git a/src/libudev-enumerate.c b/src/udev/src/libudev-enumerate.c
index 034d96feba..034d96feba 100644
--- a/src/libudev-enumerate.c
+++ b/src/udev/src/libudev-enumerate.c
diff --git a/src/libudev-list.c b/src/udev/src/libudev-list.c
index 4bdef35ae8..4bdef35ae8 100644
--- a/src/libudev-list.c
+++ b/src/udev/src/libudev-list.c
diff --git a/src/libudev-monitor.c b/src/udev/src/libudev-monitor.c
index 77dc55572f..77dc55572f 100644
--- a/src/libudev-monitor.c
+++ b/src/udev/src/libudev-monitor.c
diff --git a/src/libudev-private.h b/src/udev/src/libudev-private.h
index 5f5c64a63d..5f5c64a63d 100644
--- a/src/libudev-private.h
+++ b/src/udev/src/libudev-private.h
diff --git a/src/libudev-queue-private.c b/src/udev/src/libudev-queue-private.c
index 71771950aa..71771950aa 100644
--- a/src/libudev-queue-private.c
+++ b/src/udev/src/libudev-queue-private.c
diff --git a/src/libudev-queue.c b/src/udev/src/libudev-queue.c
index 0e82cb6ae8..0e82cb6ae8 100644
--- a/src/libudev-queue.c
+++ b/src/udev/src/libudev-queue.c
diff --git a/src/libudev-selinux-private.c b/src/udev/src/libudev-selinux-private.c
index 0f2a617b18..0f2a617b18 100644
--- a/src/libudev-selinux-private.c
+++ b/src/udev/src/libudev-selinux-private.c
diff --git a/src/libudev-util-private.c b/src/udev/src/libudev-util-private.c
index 08f0ba2228..08f0ba2228 100644
--- a/src/libudev-util-private.c
+++ b/src/udev/src/libudev-util-private.c
diff --git a/src/libudev-util.c b/src/udev/src/libudev-util.c
index 7e345f0fb6..7e345f0fb6 100644
--- a/src/libudev-util.c
+++ b/src/udev/src/libudev-util.c
diff --git a/src/libudev.c b/src/udev/src/libudev.c
index d954daef68..d954daef68 100644
--- a/src/libudev.c
+++ b/src/udev/src/libudev.c
diff --git a/src/libudev.h b/src/udev/src/libudev.h
index 10e098d4f7..10e098d4f7 100644
--- a/src/libudev.h
+++ b/src/udev/src/libudev.h
diff --git a/src/libudev.pc.in b/src/udev/src/libudev.pc.in
index c9a47fc9b8..c9a47fc9b8 100644
--- a/src/libudev.pc.in
+++ b/src/udev/src/libudev.pc.in
diff --git a/src/mtd_probe/75-probe_mtd.rules b/src/udev/src/mtd_probe/75-probe_mtd.rules
index c0e0839785..c0e0839785 100644
--- a/src/mtd_probe/75-probe_mtd.rules
+++ b/src/udev/src/mtd_probe/75-probe_mtd.rules
diff --git a/src/mtd_probe/mtd_probe.c b/src/udev/src/mtd_probe/mtd_probe.c
index 1aa08d3851..1aa08d3851 100644
--- a/src/mtd_probe/mtd_probe.c
+++ b/src/udev/src/mtd_probe/mtd_probe.c
diff --git a/src/mtd_probe/mtd_probe.h b/src/udev/src/mtd_probe/mtd_probe.h
index 2a37ede578..2a37ede578 100644
--- a/src/mtd_probe/mtd_probe.h
+++ b/src/udev/src/mtd_probe/mtd_probe.h
diff --git a/src/mtd_probe/probe_smartmedia.c b/src/udev/src/mtd_probe/probe_smartmedia.c
index b3cdefc633..b3cdefc633 100644
--- a/src/mtd_probe/probe_smartmedia.c
+++ b/src/udev/src/mtd_probe/probe_smartmedia.c
diff --git a/src/rule_generator/75-cd-aliases-generator.rules b/src/udev/src/rule_generator/75-cd-aliases-generator.rules
index e6da0101d6..e6da0101d6 100644
--- a/src/rule_generator/75-cd-aliases-generator.rules
+++ b/src/udev/src/rule_generator/75-cd-aliases-generator.rules
diff --git a/src/rule_generator/75-persistent-net-generator.rules b/src/udev/src/rule_generator/75-persistent-net-generator.rules
index 4f80573478..4f80573478 100644
--- a/src/rule_generator/75-persistent-net-generator.rules
+++ b/src/udev/src/rule_generator/75-persistent-net-generator.rules
diff --git a/src/rule_generator/rule_generator.functions b/src/udev/src/rule_generator/rule_generator.functions
index 2eec1b6abf..2eec1b6abf 100644
--- a/src/rule_generator/rule_generator.functions
+++ b/src/udev/src/rule_generator/rule_generator.functions
diff --git a/src/rule_generator/write_cd_rules b/src/udev/src/rule_generator/write_cd_rules
index 645b9cd521..645b9cd521 100644
--- a/src/rule_generator/write_cd_rules
+++ b/src/udev/src/rule_generator/write_cd_rules
diff --git a/src/rule_generator/write_net_rules b/src/udev/src/rule_generator/write_net_rules
index bcea4b09dc..bcea4b09dc 100644
--- a/src/rule_generator/write_net_rules
+++ b/src/udev/src/rule_generator/write_net_rules
diff --git a/src/scsi_id/.gitignore b/src/udev/src/scsi_id/.gitignore
index 6aebddd809..6aebddd809 100644
--- a/src/scsi_id/.gitignore
+++ b/src/udev/src/scsi_id/.gitignore
diff --git a/src/scsi_id/README b/src/udev/src/scsi_id/README
index 9cfe73991c..9cfe73991c 100644
--- a/src/scsi_id/README
+++ b/src/udev/src/scsi_id/README
diff --git a/src/scsi_id/scsi.h b/src/udev/src/scsi_id/scsi.h
index c423cac574..c423cac574 100644
--- a/src/scsi_id/scsi.h
+++ b/src/udev/src/scsi_id/scsi.h
diff --git a/src/scsi_id/scsi_id.8 b/src/udev/src/scsi_id/scsi_id.8
index 0d4dba9149..0d4dba9149 100644
--- a/src/scsi_id/scsi_id.8
+++ b/src/udev/src/scsi_id/scsi_id.8
diff --git a/src/scsi_id/scsi_id.c b/src/udev/src/scsi_id/scsi_id.c
index 9bb0d7f538..9bb0d7f538 100644
--- a/src/scsi_id/scsi_id.c
+++ b/src/udev/src/scsi_id/scsi_id.c
diff --git a/src/scsi_id/scsi_id.h b/src/udev/src/scsi_id/scsi_id.h
index 828a98305f..828a98305f 100644
--- a/src/scsi_id/scsi_id.h
+++ b/src/udev/src/scsi_id/scsi_id.h
diff --git a/src/scsi_id/scsi_serial.c b/src/udev/src/scsi_id/scsi_serial.c
index f1d63f40cc..f1d63f40cc 100644
--- a/src/scsi_id/scsi_serial.c
+++ b/src/udev/src/scsi_id/scsi_serial.c
diff --git a/src/udev/src/sd-daemon.c b/src/udev/src/sd-daemon.c
new file mode 100644
index 0000000000..763e079b4e
--- /dev/null
+++ b/src/udev/src/sd-daemon.c
@@ -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
index 0000000000..fe51159ee6
--- /dev/null
+++ b/src/udev/src/sd-daemon.h
@@ -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
diff --git a/src/test-libudev.c b/src/udev/src/test-libudev.c
index 6161fb3e31..6161fb3e31 100644
--- a/src/test-libudev.c
+++ b/src/udev/src/test-libudev.c
diff --git a/src/test-udev.c b/src/udev/src/test-udev.c
index c9712e974d..c9712e974d 100644
--- a/src/test-udev.c
+++ b/src/udev/src/test-udev.c
diff --git a/src/udev-builtin-blkid.c b/src/udev/src/udev-builtin-blkid.c
index e57f03e5a1..e57f03e5a1 100644
--- a/src/udev-builtin-blkid.c
+++ b/src/udev/src/udev-builtin-blkid.c
diff --git a/src/udev-builtin-firmware.c b/src/udev/src/udev-builtin-firmware.c
index d212c64b4d..d212c64b4d 100644
--- a/src/udev-builtin-firmware.c
+++ b/src/udev/src/udev-builtin-firmware.c
diff --git a/src/udev-builtin-hwdb.c b/src/udev/src/udev-builtin-hwdb.c
index aa996f375d..aa996f375d 100644
--- a/src/udev-builtin-hwdb.c
+++ b/src/udev/src/udev-builtin-hwdb.c
diff --git a/src/udev-builtin-input_id.c b/src/udev/src/udev-builtin-input_id.c
index a062ef7c7a..a062ef7c7a 100644
--- a/src/udev-builtin-input_id.c
+++ b/src/udev/src/udev-builtin-input_id.c
diff --git a/src/udev-builtin-kmod.c b/src/udev/src/udev-builtin-kmod.c
index 57e813f863..57e813f863 100644
--- a/src/udev-builtin-kmod.c
+++ b/src/udev/src/udev-builtin-kmod.c
diff --git a/src/udev-builtin-path_id.c b/src/udev/src/udev-builtin-path_id.c
index a8559d2dd4..a8559d2dd4 100644
--- a/src/udev-builtin-path_id.c
+++ b/src/udev/src/udev-builtin-path_id.c
diff --git a/src/udev-builtin-usb_id.c b/src/udev/src/udev-builtin-usb_id.c
index 85828e32d7..85828e32d7 100644
--- a/src/udev-builtin-usb_id.c
+++ b/src/udev/src/udev-builtin-usb_id.c
diff --git a/src/udev-builtin.c b/src/udev/src/udev-builtin.c
index 5bc5fa68f6..5bc5fa68f6 100644
--- a/src/udev-builtin.c
+++ b/src/udev/src/udev-builtin.c
diff --git a/src/udev-control.socket b/src/udev/src/udev-control.socket
index f80f774427..f80f774427 100644
--- a/src/udev-control.socket
+++ b/src/udev/src/udev-control.socket
diff --git a/src/udev-ctrl.c b/src/udev/src/udev-ctrl.c
index 5556f1a77c..5556f1a77c 100644
--- a/src/udev-ctrl.c
+++ b/src/udev/src/udev-ctrl.c
diff --git a/src/udev-event.c b/src/udev/src/udev-event.c
index 45dd77ba2e..45dd77ba2e 100644
--- a/src/udev-event.c
+++ b/src/udev/src/udev-event.c
diff --git a/src/udev-kernel.socket b/src/udev/src/udev-kernel.socket
index 23fa9d5e11..23fa9d5e11 100644
--- a/src/udev-kernel.socket
+++ b/src/udev/src/udev-kernel.socket
diff --git a/src/udev-node.c b/src/udev/src/udev-node.c
index 7a01a479ee..7a01a479ee 100644
--- a/src/udev-node.c
+++ b/src/udev/src/udev-node.c
diff --git a/src/udev-rules.c b/src/udev/src/udev-rules.c
index 8a85eae717..8a85eae717 100644
--- a/src/udev-rules.c
+++ b/src/udev/src/udev-rules.c
diff --git a/src/udev-settle.service.in b/src/udev/src/udev-settle.service.in
index b0a4964f76..b0a4964f76 100644
--- a/src/udev-settle.service.in
+++ b/src/udev/src/udev-settle.service.in
diff --git a/src/udev-trigger.service.in b/src/udev/src/udev-trigger.service.in
index cd81945c88..cd81945c88 100644
--- a/src/udev-trigger.service.in
+++ b/src/udev/src/udev-trigger.service.in
diff --git a/src/udev-watch.c b/src/udev/src/udev-watch.c
index 228d18fedf..228d18fedf 100644
--- a/src/udev-watch.c
+++ b/src/udev/src/udev-watch.c
diff --git a/src/udev.conf b/src/udev/src/udev.conf
index f39253eb67..f39253eb67 100644
--- a/src/udev.conf
+++ b/src/udev/src/udev.conf
diff --git a/src/udev.h b/src/udev/src/udev.h
index bc051c9b65..bc051c9b65 100644
--- a/src/udev.h
+++ b/src/udev/src/udev.h
diff --git a/src/udev.pc.in b/src/udev/src/udev.pc.in
index 0b04c02ef6..0b04c02ef6 100644
--- a/src/udev.pc.in
+++ b/src/udev/src/udev.pc.in
diff --git a/src/udev.service.in b/src/udev/src/udev.service.in
index c27eb1baf5..c27eb1baf5 100644
--- a/src/udev.service.in
+++ b/src/udev/src/udev.service.in
diff --git a/src/udev.xml b/src/udev/src/udev.xml
index 8eb583a823..8eb583a823 100644
--- a/src/udev.xml
+++ b/src/udev/src/udev.xml
diff --git a/src/udevadm-control.c b/src/udev/src/udevadm-control.c
index cafa214944..cafa214944 100644
--- a/src/udevadm-control.c
+++ b/src/udev/src/udevadm-control.c
diff --git a/src/udevadm-info.c b/src/udev/src/udevadm-info.c
index ee9b59fea8..ee9b59fea8 100644
--- a/src/udevadm-info.c
+++ b/src/udev/src/udevadm-info.c
diff --git a/src/udevadm-monitor.c b/src/udev/src/udevadm-monitor.c
index 5997dd8e18..5997dd8e18 100644
--- a/src/udevadm-monitor.c
+++ b/src/udev/src/udevadm-monitor.c
diff --git a/src/udevadm-settle.c b/src/udev/src/udevadm-settle.c
index b168defd90..b168defd90 100644
--- a/src/udevadm-settle.c
+++ b/src/udev/src/udevadm-settle.c
diff --git a/src/udevadm-test-builtin.c b/src/udev/src/udevadm-test-builtin.c
index 3a49f7ce9c..3a49f7ce9c 100644
--- a/src/udevadm-test-builtin.c
+++ b/src/udev/src/udevadm-test-builtin.c
diff --git a/src/udevadm-test.c b/src/udev/src/udevadm-test.c
index 6275cff899..6275cff899 100644
--- a/src/udevadm-test.c
+++ b/src/udev/src/udevadm-test.c
diff --git a/src/udevadm-trigger.c b/src/udev/src/udevadm-trigger.c
index 3cce23dfb2..3cce23dfb2 100644
--- a/src/udevadm-trigger.c
+++ b/src/udev/src/udevadm-trigger.c
diff --git a/src/udevadm.c b/src/udev/src/udevadm.c
index 224ece0bb7..224ece0bb7 100644
--- a/src/udevadm.c
+++ b/src/udev/src/udevadm.c
diff --git a/src/udevadm.xml b/src/udev/src/udevadm.xml
index 455ce80ca9..455ce80ca9 100644
--- a/src/udevadm.xml
+++ b/src/udev/src/udevadm.xml
diff --git a/src/udevd.c b/src/udev/src/udevd.c
index 170221790a..170221790a 100644
--- a/src/udevd.c
+++ b/src/udev/src/udevd.c
diff --git a/src/udevd.xml b/src/udev/src/udevd.xml
index c516eb9793..c516eb9793 100644
--- a/src/udevd.xml
+++ b/src/udev/src/udevd.xml
diff --git a/src/v4l_id/60-persistent-v4l.rules b/src/udev/src/v4l_id/60-persistent-v4l.rules
index 93c5ee8c27..93c5ee8c27 100644
--- a/src/v4l_id/60-persistent-v4l.rules
+++ b/src/udev/src/v4l_id/60-persistent-v4l.rules
diff --git a/src/v4l_id/v4l_id.c b/src/udev/src/v4l_id/v4l_id.c
index a2a80b5f43..a2a80b5f43 100644
--- a/src/v4l_id/v4l_id.c
+++ b/src/udev/src/v4l_id/v4l_id.c
diff --git a/test/.gitignore b/src/udev/test/.gitignore
index 98fa886530..98fa886530 100644
--- a/test/.gitignore
+++ b/src/udev/test/.gitignore
diff --git a/test/rule-syntax-check.py b/src/udev/test/rule-syntax-check.py
index ff1b63d075..ff1b63d075 100755
--- a/test/rule-syntax-check.py
+++ b/src/udev/test/rule-syntax-check.py
diff --git a/test/rules-test.sh b/src/udev/test/rules-test.sh
index 1e224ff8b5..1e224ff8b5 100755
--- a/test/rules-test.sh
+++ b/src/udev/test/rules-test.sh
diff --git a/test/sys.tar.xz b/src/udev/test/sys.tar.xz
index 49ee8027b2..49ee8027b2 100644
--- a/test/sys.tar.xz
+++ b/src/udev/test/sys.tar.xz
Binary files differ
diff --git a/test/udev-test.pl b/src/udev/test/udev-test.pl
index 0b379b0d9a..0b379b0d9a 100755
--- a/test/udev-test.pl
+++ b/src/udev/test/udev-test.pl
diff --git a/src/umount.c b/src/umount.c
new file mode 100644
index 0000000000..0a63d23a01
--- /dev/null
+++ b/src/umount.c
@@ -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
index 0000000000..acdf09acf1
--- /dev/null
+++ b/src/umount.h
@@ -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
index 0000000000..1cbb804561
--- /dev/null
+++ b/src/unit-name.c
@@ -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
index 0000000000..e369910aea
--- /dev/null
+++ b/src/unit-name.h
@@ -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
index 0000000000..9e33701c8f
--- /dev/null
+++ b/src/unit.c
@@ -0,0 +1,2676 @@
+/*-*- Mode: C; c-basic-offset: 8; indent-tabs-mode: nil -*-*/
+
+/***
+ This file is part of systemd.
+
+ Copyright 2010 Lennart Poettering
+
+ systemd is free software; you can redistribute it and/or modify it
+ under the terms of the GNU General Public License as published by
+ the Free Software Foundation; either version 2 of the License, or
+ (at your option) any later version.
+
+ systemd is distributed in the hope that it will be useful, but
+ WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with systemd; If not, see <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
index 0000000000..756f465da3
--- /dev/null
+++ b/src/unit.h
@@ -0,0 +1,562 @@
+/*-*- Mode: C; c-basic-offset: 8; indent-tabs-mode: nil -*-*/
+
+#ifndef foounithfoo
+#define foounithfoo
+
+/***
+ This file is part of systemd.
+
+ Copyright 2010 Lennart Poettering
+
+ systemd is free software; you can redistribute it and/or modify it
+ under the terms of the GNU General Public License as published by
+ the Free Software Foundation; either version 2 of the License, or
+ (at your option) any later version.
+
+ systemd is distributed in the hope that it will be useful, but
+ WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with systemd; If not, see <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
index 0000000000..0d177d6164
--- /dev/null
+++ b/src/update-utmp.c
@@ -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
index 0000000000..9508a02eec
--- /dev/null
+++ b/src/user.conf
@@ -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
index 0000000000..11619dce2f
--- /dev/null
+++ b/src/utf8.c
@@ -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
index 0000000000..9a72bec084
--- /dev/null
+++ b/src/utf8.h
@@ -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
index 0000000000..dfc1dc6b85
--- /dev/null
+++ b/src/util.c
@@ -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
index 0000000000..b1af6dbe7c
--- /dev/null
+++ b/src/util.h
@@ -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
index 0000000000..217ae1e2c7
--- /dev/null
+++ b/src/utmp-wtmp.c
@@ -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
index 0000000000..a5998ebb21
--- /dev/null
+++ b/src/utmp-wtmp.h
@@ -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
index 0000000000..d0b0e8e008
--- /dev/null
+++ b/src/vconsole/Makefile
@@ -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
index 0000000000..91967891f1
--- /dev/null
+++ b/src/vconsole/vconsole-setup.c
@@ -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
index 0000000000..4c526ff454
--- /dev/null
+++ b/src/virt.c
@@ -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
index 0000000000..f55c9a68fd
--- /dev/null
+++ b/src/virt.h
@@ -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
index 0000000000..7563539ab0
--- /dev/null
+++ b/sysctl.d/.gitignore
@@ -0,0 +1 @@
+/coredump.conf
diff --git a/sysctl.d/Makefile b/sysctl.d/Makefile
new file mode 120000
index 0000000000..bd1047548b
--- /dev/null
+++ b/sysctl.d/Makefile
@@ -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
index 0000000000..ab19b1e988
--- /dev/null
+++ b/sysctl.d/coredump.conf.in
@@ -0,0 +1,10 @@
+# This file is part of systemd.
+#
+# systemd is free software; you can redistribute it and/or modify it
+# under the terms of the GNU 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
index 0000000000..9aa46b4eb6
--- /dev/null
+++ b/test/Makefile
@@ -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
index 0000000000..4168d2d051
--- /dev/null
+++ b/test/a.service
@@ -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
index 0000000000..e03bae36be
--- /dev/null
+++ b/test/b.service
@@ -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
index 0000000000..e2f60a8fbf
--- /dev/null
+++ b/test/c.service
@@ -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
index 0000000000..921fd2ee1b
--- /dev/null
+++ b/test/d.service
@@ -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
index 0000000000..5ba98c7c43
--- /dev/null
+++ b/test/e.service
@@ -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
index 0000000000..7dde681c17
--- /dev/null
+++ b/test/f.service
@@ -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
index 0000000000..cbfa82a454
--- /dev/null
+++ b/test/g.service
@@ -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
index 0000000000..74a7751cad
--- /dev/null
+++ b/test/h.service
@@ -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
index 0000000000..bd1047548b
--- /dev/null
+++ b/tmpfiles.d/Makefile
@@ -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
index 0000000000..9198e89dd1
--- /dev/null
+++ b/tmpfiles.d/legacy.conf
@@ -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
index 0000000000..be29c068af
--- /dev/null
+++ b/tmpfiles.d/systemd.conf
@@ -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
index 0000000000..8915b82ab5
--- /dev/null
+++ b/tmpfiles.d/tmp.conf
@@ -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
index 0000000000..7f81af62da
--- /dev/null
+++ b/tmpfiles.d/x11.conf
@@ -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
index 0000000000..94412d52e7
--- /dev/null
+++ b/units/.gitignore
@@ -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
index 0000000000..bd1047548b
--- /dev/null
+++ b/units/Makefile
@@ -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
index 0000000000..0258ca0c03
--- /dev/null
+++ b/units/basic.target
@@ -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
index 0000000000..c66718e113
--- /dev/null
+++ b/units/bluetooth.target
@@ -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
index 0000000000..fef9e1b176
--- /dev/null
+++ b/units/console-shell.service.m4
@@ -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
index 0000000000..64ee8c6144
--- /dev/null
+++ b/units/cryptsetup.target
@@ -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
index 0000000000..72a522e69c
--- /dev/null
+++ b/units/dev-hugepages.mount
@@ -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
index 0000000000..cffdaf773f
--- /dev/null
+++ b/units/dev-mqueue.mount
@@ -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
index 0000000000..43a74d7a36
--- /dev/null
+++ b/units/emergency.service
@@ -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
index 0000000000..6a99e05f03
--- /dev/null
+++ b/units/emergency.target
@@ -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
index 0000000000..50be21181f
--- /dev/null
+++ b/units/fedora/Makefile
@@ -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
index 0000000000..a9f6feb320
--- /dev/null
+++ b/units/fedora/halt-local.service
@@ -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
index 0000000000..77a0e9ad7c
--- /dev/null
+++ b/units/fedora/prefdm.service
@@ -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
index 0000000000..0bef5c72ab
--- /dev/null
+++ b/units/fedora/rc-local.service
@@ -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
index 0000000000..9cfda197b0
--- /dev/null
+++ b/units/final.target
@@ -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
index 0000000000..2376e1977c
--- /dev/null
+++ b/units/frugalware/display-manager.service
@@ -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
index 0000000000..4086149128
--- /dev/null
+++ b/units/fsck-root.service.in
@@ -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
index 0000000000..c06684b634
--- /dev/null
+++ b/units/fsck@.service.in
@@ -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
index 0000000000..e4435dc8e7
--- /dev/null
+++ b/units/getty.target
@@ -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
index 0000000000..a02838d785
--- /dev/null
+++ b/units/getty@.service.m4
@@ -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
index 0000000000..f2e30341d4
--- /dev/null
+++ b/units/graphical.target
@@ -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
index 0000000000..42e347043e
--- /dev/null
+++ b/units/halt.service.in
@@ -0,0 +1,16 @@
+# This file is part of systemd.
+#
+# systemd is free software; you can redistribute it and/or modify it
+# under the terms of the GNU 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
index 0000000000..04b42cd3d1
--- /dev/null
+++ b/units/halt.target
@@ -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
index 0000000000..45f10182e9
--- /dev/null
+++ b/units/http-daemon.target
@@ -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
index 0000000000..cf6bd65615
--- /dev/null
+++ b/units/kexec.service.in
@@ -0,0 +1,16 @@
+# This file is part of systemd.
+#
+# systemd is free software; you can redistribute it and/or modify it
+# under the terms of the GNU 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
index 0000000000..b77e6a43f1
--- /dev/null
+++ b/units/kexec.target
@@ -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
index 0000000000..11e67bac1c
--- /dev/null
+++ b/units/local-fs-pre.target
@@ -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
index 0000000000..1886f7499d
--- /dev/null
+++ b/units/local-fs.target
@@ -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
index 0000000000..4a896bf582
--- /dev/null
+++ b/units/mageia/prefdm.service
@@ -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
index 0000000000..ebb1ea125a
--- /dev/null
+++ b/units/mail-transfer-agent.target
@@ -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
index 0000000000..4a896bf582
--- /dev/null
+++ b/units/mandriva/prefdm.service
@@ -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
index 0000000000..66f1a950f9
--- /dev/null
+++ b/units/multi-user.target
@@ -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
index 0000000000..d97f64f674
--- /dev/null
+++ b/units/network.target
@@ -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
index 0000000000..bdca03cd88
--- /dev/null
+++ b/units/nss-lookup.target
@@ -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
index 0000000000..2e194b360e
--- /dev/null
+++ b/units/plymouth-halt.service
@@ -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
index 0000000000..919c3f1294
--- /dev/null
+++ b/units/plymouth-kexec.service
@@ -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
index 0000000000..8fcff3bab1
--- /dev/null
+++ b/units/plymouth-poweroff.service
@@ -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
index 0000000000..45c67bdabd
--- /dev/null
+++ b/units/plymouth-quit-wait.service
@@ -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
index 0000000000..164499a2a6
--- /dev/null
+++ b/units/plymouth-quit.service
@@ -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
index 0000000000..09fbf7d4c4
--- /dev/null
+++ b/units/plymouth-read-write.service
@@ -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
index 0000000000..fb65bcc836
--- /dev/null
+++ b/units/plymouth-reboot.service
@@ -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
index 0000000000..f618257a93
--- /dev/null
+++ b/units/plymouth-start.service
@@ -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
index 0000000000..124a4c0f36
--- /dev/null
+++ b/units/poweroff.service.in
@@ -0,0 +1,16 @@
+# This file is part of systemd.
+#
+# systemd is free software; you can redistribute it and/or modify it
+# under the terms of the GNU 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
index 0000000000..d2ccf4b2c7
--- /dev/null
+++ b/units/poweroff.target
@@ -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
index 0000000000..14c90ff84b
--- /dev/null
+++ b/units/printer.target
@@ -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
index 0000000000..acbbcbb0cb
--- /dev/null
+++ b/units/proc-sys-fs-binfmt_misc.automount
@@ -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
index 0000000000..1829c2162a
--- /dev/null
+++ b/units/proc-sys-fs-binfmt_misc.mount
@@ -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
index 0000000000..c97b7a4687
--- /dev/null
+++ b/units/quotacheck.service.in
@@ -0,0 +1,19 @@
+# This file is part of systemd.
+#
+# systemd is free software; you can redistribute it and/or modify it
+# under the terms of the GNU 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
index 0000000000..ef2fc8c976
--- /dev/null
+++ b/units/quotaon.service
@@ -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
index 0000000000..f320fd886d
--- /dev/null
+++ b/units/reboot.service.in
@@ -0,0 +1,16 @@
+# This file is part of systemd.
+#
+# systemd is free software; you can redistribute it and/or modify it
+# under the terms of the GNU 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
index 0000000000..41e133cb76
--- /dev/null
+++ b/units/reboot.target
@@ -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
index 0000000000..8aceb08b9d
--- /dev/null
+++ b/units/remote-fs-pre.target
@@ -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
index 0000000000..a48f87e5dd
--- /dev/null
+++ b/units/remote-fs.target
@@ -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
index 0000000000..7b63752c7e
--- /dev/null
+++ b/units/remount-rootfs.service
@@ -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
index 0000000000..310bbce1e6
--- /dev/null
+++ b/units/rescue.service.m4
@@ -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
index 0000000000..5bf3f8e8ee
--- /dev/null
+++ b/units/rescue.target
@@ -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
index 0000000000..a5cea8c576
--- /dev/null
+++ b/units/rpcbind.target
@@ -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
index 0000000000..fc8b57b93e
--- /dev/null
+++ b/units/serial-getty@.service.m4
@@ -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
index 0000000000..99a659e92f
--- /dev/null
+++ b/units/shutdown.target
@@ -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
index 0000000000..0ca502dbbe
--- /dev/null
+++ b/units/sigpwr.target
@@ -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
index 0000000000..28dd2bba1a
--- /dev/null
+++ b/units/smartcard.target
@@ -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
index 0000000000..22963128d7
--- /dev/null
+++ b/units/sockets.target
@@ -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
index 0000000000..e53221c7a6
--- /dev/null
+++ b/units/sound.target
@@ -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
index 0000000000..796012c0c4
--- /dev/null
+++ b/units/suse/halt-local.service
@@ -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
index 0000000000..2384a18f94
--- /dev/null
+++ b/units/suse/rc-local.service
@@ -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
index 0000000000..26dd261d17
--- /dev/null
+++ b/units/swap.target
@@ -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
index 0000000000..037471537b
--- /dev/null
+++ b/units/sys-fs-fuse-connections.mount
@@ -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
index 0000000000..d6862bf6bd
--- /dev/null
+++ b/units/sys-kernel-config.mount
@@ -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
index 0000000000..d9fca1ff3d
--- /dev/null
+++ b/units/sys-kernel-debug.mount
@@ -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
index 0000000000..eb9a1c7cc2
--- /dev/null
+++ b/units/sysinit.target
@@ -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
index 0000000000..0e211e16e7
--- /dev/null
+++ b/units/syslog.socket
@@ -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
index 0000000000..825b26e7bf
--- /dev/null
+++ b/units/syslog.target
@@ -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
index 0000000000..c3143d1da6
--- /dev/null
+++ b/units/systemd-ask-password-console.path
@@ -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
index 0000000000..5ff3ed55d7
--- /dev/null
+++ b/units/systemd-ask-password-console.service.in
@@ -0,0 +1,17 @@
+# This file is part of systemd.
+#
+# systemd is free software; you can redistribute it and/or modify it
+# under the terms of the GNU 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
index 0000000000..06a587620f
--- /dev/null
+++ b/units/systemd-ask-password-plymouth.path
@@ -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
index 0000000000..92cbfdbf09
--- /dev/null
+++ b/units/systemd-ask-password-plymouth.service.in
@@ -0,0 +1,18 @@
+# This file is part of systemd.
+#
+# systemd is free software; you can redistribute it and/or modify it
+# under the terms of the GNU 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
index 0000000000..050b73b2e1
--- /dev/null
+++ b/units/systemd-ask-password-wall.path
@@ -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
index 0000000000..71ec1d68f8
--- /dev/null
+++ b/units/systemd-ask-password-wall.service.in
@@ -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
index 0000000000..d43497c151
--- /dev/null
+++ b/units/systemd-binfmt.service.in
@@ -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
index 0000000000..6efab1e25f
--- /dev/null
+++ b/units/systemd-hostnamed.service.in
@@ -0,0 +1,17 @@
+# This file is part of systemd.
+#
+# systemd is free software; you can redistribute it and/or modify it
+# under the terms of the GNU 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
index 0000000000..7df3aa6db3
--- /dev/null
+++ b/units/systemd-initctl.service.in
@@ -0,0 +1,16 @@
+# This file is part of systemd.
+#
+# systemd is free software; you can redistribute it and/or modify it
+# under the terms of the GNU 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
index 0000000000..7a3a0236eb
--- /dev/null
+++ b/units/systemd-initctl.socket
@@ -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
index 0000000000..92606b0d88
--- /dev/null
+++ b/units/systemd-journald.service.in
@@ -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
index 0000000000..15fc49ef29
--- /dev/null
+++ b/units/systemd-journald.socket
@@ -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
index 0000000000..4be65df75d
--- /dev/null
+++ b/units/systemd-localed.service.in
@@ -0,0 +1,17 @@
+# This file is part of systemd.
+#
+# systemd is free software; you can redistribute it and/or modify it
+# under the terms of the GNU 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
index 0000000000..531b8f7e93
--- /dev/null
+++ b/units/systemd-logind.service.in
@@ -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
index 0000000000..5dc373d208
--- /dev/null
+++ b/units/systemd-modules-load.service.in
@@ -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
index 0000000000..a2b6a557dc
--- /dev/null
+++ b/units/systemd-random-seed-load.service.in
@@ -0,0 +1,18 @@
+# This file is part of systemd.
+#
+# systemd is free software; you can redistribute it and/or modify it
+# under the terms of the GNU 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
index 0000000000..9a074cf3fe
--- /dev/null
+++ b/units/systemd-random-seed-save.service.in
@@ -0,0 +1,17 @@
+# This file is part of systemd.
+#
+# systemd is free software; you can redistribute it and/or modify it
+# under the terms of the GNU 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
index 0000000000..56ba54f0b3
--- /dev/null
+++ b/units/systemd-readahead-collect.service.in
@@ -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
index 0000000000..d665e45f24
--- /dev/null
+++ b/units/systemd-readahead-done.service.in
@@ -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
index 0000000000..d144bfaece
--- /dev/null
+++ b/units/systemd-readahead-done.timer
@@ -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
index 0000000000..7c82e408e2
--- /dev/null
+++ b/units/systemd-readahead-replay.service.in
@@ -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
index 0000000000..f4df0ca263
--- /dev/null
+++ b/units/systemd-remount-api-vfs.service.in
@@ -0,0 +1,19 @@
+# This file is part of systemd.
+#
+# systemd is free software; you can redistribute it and/or modify it
+# under the terms of the GNU 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
index 0000000000..657365a451
--- /dev/null
+++ b/units/systemd-shutdownd.service.in
@@ -0,0 +1,16 @@
+# This file is part of systemd.
+#
+# systemd is free software; you can redistribute it and/or modify it
+# under the terms of the GNU 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
index 0000000000..7f13c9386e
--- /dev/null
+++ b/units/systemd-shutdownd.socket
@@ -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
index 0000000000..6d53422630
--- /dev/null
+++ b/units/systemd-sysctl.service.in
@@ -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
index 0000000000..90ff4432ba
--- /dev/null
+++ b/units/systemd-timedated.service.in
@@ -0,0 +1,17 @@
+# This file is part of systemd.
+#
+# systemd is free software; you can redistribute it and/or modify it
+# under the terms of the GNU 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
index 0000000000..3c8e72ebf5
--- /dev/null
+++ b/units/systemd-tmpfiles-clean.service.in
@@ -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
index 0000000000..d8529a8d7e
--- /dev/null
+++ b/units/systemd-tmpfiles-clean.timer
@@ -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
index 0000000000..f90121e12e
--- /dev/null
+++ b/units/systemd-tmpfiles-setup.service.in
@@ -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
index 0000000000..614c759a63
--- /dev/null
+++ b/units/systemd-update-utmp-runlevel.service.in
@@ -0,0 +1,16 @@
+# This file is part of systemd.
+#
+# systemd is free software; you can redistribute it and/or modify it
+# under the terms of the GNU 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
index 0000000000..e7c3c04a00
--- /dev/null
+++ b/units/systemd-update-utmp-shutdown.service.in
@@ -0,0 +1,16 @@
+# This file is part of systemd.
+#
+# systemd is free software; you can redistribute it and/or modify it
+# under the terms of the GNU 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
index 0000000000..a93d586a43
--- /dev/null
+++ b/units/systemd-user-sessions.service.in
@@ -0,0 +1,16 @@
+# This file is part of systemd.
+#
+# systemd is free software; you can redistribute it and/or modify it
+# under the terms of the GNU 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
index 0000000000..673fb6ccf6
--- /dev/null
+++ b/units/systemd-vconsole-setup.service.in
@@ -0,0 +1,18 @@
+# This file is part of systemd.
+#
+# systemd is free software; you can redistribute it and/or modify it
+# under the terms of the GNU 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
index 0000000000..aa34ecb5f4
--- /dev/null
+++ b/units/time-sync.target
@@ -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
index 0000000000..8d0b8afb17
--- /dev/null
+++ b/units/tmp.mount
@@ -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
index 0000000000..b9ecca6fb1
--- /dev/null
+++ b/units/umount.target
@@ -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
index 0000000000..eeb62b32ba
--- /dev/null
+++ b/units/user/.gitignore
@@ -0,0 +1 @@
+exit.service
diff --git a/units/user/Makefile b/units/user/Makefile
new file mode 120000
index 0000000000..50be21181f
--- /dev/null
+++ b/units/user/Makefile
@@ -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
index 0000000000..deb310c2f7
--- /dev/null
+++ b/units/user/default.target
@@ -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
index 0000000000..a20b089c9a
--- /dev/null
+++ b/units/user/exit.service.in
@@ -0,0 +1,18 @@
+# This file is part of systemd.
+#
+# systemd is free software; you can redistribute it and/or modify it
+# under the terms of the GNU 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
index 0000000000..f34844c0d3
--- /dev/null
+++ b/units/user/exit.target
@@ -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
index 0000000000..91e3b25158
--- /dev/null
+++ b/units/user@.service.in
@@ -0,0 +1,19 @@
+# This file is part of systemd.
+#
+# systemd is free software; you can redistribute it and/or modify it
+# under the terms of the GNU 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