summaryrefslogtreecommitdiff
path: root/src/libshared
diff options
context:
space:
mode:
authorLuke Shumaker <lukeshu@sbcglobal.net>2016-06-01 15:25:37 -0400
committerLuke Shumaker <lukeshu@sbcglobal.net>2016-06-01 15:25:37 -0400
commitaed55ee5932c11a7bead1d0d727dd60c8bb1528e (patch)
treebe8ba56ef6a0cafaca6c8d4dac3eea791f9b32d0 /src/libshared
parent2ed13bce1704bfb3f9b80134acc25cbf091b530e (diff)
./move.sh
Diffstat (limited to 'src/libshared')
-rw-r--r--src/libshared/Makefile140
-rw-r--r--src/libshared/acl-util.c429
-rw-r--r--src/libshared/acl-util.h48
-rw-r--r--src/libshared/acpi-fpdt.c160
-rw-r--r--src/libshared/acpi-fpdt.h24
-rw-r--r--src/libshared/apparmor-util.c39
-rw-r--r--src/libshared/apparmor-util.h24
-rw-r--r--src/libshared/architecture.c176
-rw-r--r--src/libshared/architecture.h207
-rw-r--r--src/libshared/ask-password-api.c736
-rw-r--r--src/libshared/ask-password-api.h38
-rw-r--r--src/libshared/base-filesystem.c129
-rw-r--r--src/libshared/base-filesystem.h24
-rw-r--r--src/libshared/boot-timestamps.c64
-rw-r--r--src/libshared/boot-timestamps.h25
-rw-r--r--src/libshared/bus-util.c2412
-rw-r--r--src/libshared/bus-util.h196
-rw-r--r--src/libshared/cgroup-show.c278
-rw-r--r--src/libshared/cgroup-show.h32
-rw-r--r--src/libshared/clean-ipc.c365
-rw-r--r--src/libshared/clean-ipc.h24
-rw-r--r--src/libshared/condition.c541
-rw-r--r--src/libshared/condition.h94
-rw-r--r--src/libshared/conf-parser.c870
-rw-r--r--src/libshared/conf-parser.h228
-rw-r--r--src/libshared/dev-setup.c73
-rw-r--r--src/libshared/dev-setup.h24
-rw-r--r--src/libshared/dns-domain.c1322
-rw-r--r--src/libshared/dns-domain.h110
-rw-r--r--src/libshared/dropin.c251
-rw-r--r--src/libshared/dropin.h61
-rw-r--r--src/libshared/efivars.c715
-rw-r--r--src/libshared/efivars.h131
-rw-r--r--src/libshared/fstab-util.c263
-rw-r--r--src/libshared/fstab-util.h52
-rw-r--r--src/libshared/generator.c193
-rw-r--r--src/libshared/generator.h36
-rw-r--r--src/libshared/gpt.h66
-rw-r--r--src/libshared/ima-util.c32
-rw-r--r--src/libshared/ima-util.h24
-rw-r--r--src/libshared/import-util.c185
-rw-r--r--src/libshared/import-util.h43
-rw-r--r--src/libshared/initreq.h77
-rw-r--r--src/libshared/install-printf.c133
-rw-r--r--src/libshared/install-printf.h24
-rw-r--r--src/libshared/install.c2474
-rw-r--r--src/libshared/install.h156
-rw-r--r--src/libshared/logs-show.c1305
-rw-r--r--src/libshared/logs-show.h73
-rw-r--r--src/libshared/machine-image.c813
-rw-r--r--src/libshared/machine-image.h77
-rw-r--r--src/libshared/machine-pool.c427
-rw-r--r--src/libshared/machine-pool.h30
-rw-r--r--src/libshared/output-mode.h46
-rw-r--r--src/libshared/pager.c223
-rw-r--r--src/libshared/pager.h30
-rw-r--r--src/libshared/path-lookup.c441
-rw-r--r--src/libshared/path-lookup.h60
-rw-r--r--src/libshared/ptyfwd.c487
-rw-r--r--src/libshared/ptyfwd.h48
-rw-r--r--src/libshared/resolve-util.c39
-rw-r--r--src/libshared/resolve-util.h60
-rw-r--r--src/libshared/seccomp-util.c90
-rw-r--r--src/libshared/seccomp-util.h28
-rw-r--r--src/libshared/sleep-config.c274
-rw-r--r--src/libshared/sleep-config.h26
-rw-r--r--src/libshared/spawn-ask-password-agent.c62
-rw-r--r--src/libshared/spawn-ask-password-agent.h23
-rw-r--r--src/libshared/spawn-polkit-agent.c98
-rw-r--r--src/libshared/spawn-polkit-agent.h23
-rw-r--r--src/libshared/specifier.c188
-rw-r--r--src/libshared/specifier.h37
-rw-r--r--src/libshared/switch-root.c156
-rw-r--r--src/libshared/switch-root.h23
-rw-r--r--src/libshared/sysctl-util.c73
-rw-r--r--src/libshared/sysctl-util.h25
-rw-r--r--src/libshared/test-tables.h60
-rw-r--r--src/libshared/udev-util.h44
-rw-r--r--src/libshared/uid-range.c208
-rw-r--r--src/libshared/uid-range.h33
-rw-r--r--src/libshared/utmp-wtmp.c445
-rw-r--r--src/libshared/utmp-wtmp.h74
-rw-r--r--src/libshared/watchdog.c164
-rw-r--r--src/libshared/watchdog.h29
84 files changed, 20090 insertions, 0 deletions
diff --git a/src/libshared/Makefile b/src/libshared/Makefile
new file mode 100644
index 0000000000..d0141969b3
--- /dev/null
+++ b/src/libshared/Makefile
@@ -0,0 +1,140 @@
+# -*- Mode: makefile; indent-tabs-mode: t -*-
+#
+# This file is part of systemd.
+#
+# Copyright 2010-2012 Lennart Poettering
+# Copyright 2010-2012 Kay Sievers
+# Copyright 2013 Zbigniew Jędrzejewski-Szmek
+# Copyright 2013 David Strauss
+# Copyright 2016 Luke Shumaker
+#
+# systemd is free software; you can redistribute it and/or modify it
+# under the terms of the GNU Lesser General Public License as published by
+# the Free Software Foundation; either version 2.1 of the License, or
+# (at your option) any later version.
+#
+# systemd is distributed in the hope that it will be useful, but
+# WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+# Lesser General Public License for more details.
+#
+# You should have received a copy of the GNU Lesser General Public License
+# along with systemd; If not, see <http://www.gnu.org/licenses/>.
+include $(dir $(lastword $(MAKEFILE_LIST)))/../../config.mk
+include $(topsrcdir)/build-aux/Makefile.head.mk
+
+noinst_LTLIBRARIES += \
+ libshared.la
+
+libshared_la_SOURCES = \
+ src/shared/output-mode.h \
+ src/shared/gpt.h \
+ src/shared/udev-util.h \
+ src/shared/linux/auto_dev-ioctl.h \
+ src/shared/initreq.h \
+ src/shared/dns-domain.c \
+ src/shared/dns-domain.h \
+ src/shared/architecture.c \
+ src/shared/architecture.h \
+ src/shared/efivars.c \
+ src/shared/efivars.h \
+ src/shared/fstab-util.c \
+ src/shared/fstab-util.h \
+ src/shared/sleep-config.c \
+ src/shared/sleep-config.h \
+ src/shared/conf-parser.c \
+ src/shared/conf-parser.h \
+ src/shared/pager.c \
+ src/shared/pager.h \
+ src/shared/spawn-polkit-agent.c \
+ src/shared/spawn-polkit-agent.h \
+ src/shared/apparmor-util.c \
+ src/shared/apparmor-util.h \
+ src/shared/ima-util.c \
+ src/shared/ima-util.h \
+ src/shared/ptyfwd.c \
+ src/shared/ptyfwd.h \
+ src/shared/base-filesystem.c \
+ src/shared/base-filesystem.h \
+ src/shared/uid-range.c \
+ src/shared/uid-range.h \
+ src/shared/install.c \
+ src/shared/install.h \
+ src/shared/install-printf.c \
+ src/shared/install-printf.h \
+ src/shared/path-lookup.c \
+ src/shared/path-lookup.h \
+ src/shared/specifier.c \
+ src/shared/specifier.h \
+ src/shared/dev-setup.c \
+ src/shared/dev-setup.h \
+ src/shared/dropin.c \
+ src/shared/dropin.h \
+ src/shared/condition.c \
+ src/shared/condition.h \
+ src/shared/clean-ipc.c \
+ src/shared/clean-ipc.h \
+ src/shared/generator.h \
+ src/shared/generator.c \
+ src/shared/acpi-fpdt.h \
+ src/shared/acpi-fpdt.c \
+ src/shared/boot-timestamps.h \
+ src/shared/boot-timestamps.c \
+ src/shared/cgroup-show.c \
+ src/shared/cgroup-show.h \
+ src/shared/utmp-wtmp.h \
+ src/shared/watchdog.c \
+ src/shared/watchdog.h \
+ src/shared/spawn-ask-password-agent.c \
+ src/shared/spawn-ask-password-agent.h \
+ src/shared/ask-password-api.c \
+ src/shared/ask-password-api.h \
+ src/shared/switch-root.h \
+ src/shared/switch-root.c \
+ src/shared/import-util.c \
+ src/shared/import-util.h \
+ src/shared/sysctl-util.c \
+ src/shared/sysctl-util.h \
+ src/shared/bus-util.c \
+ src/shared/bus-util.h \
+ src/shared/logs-show.c \
+ src/shared/logs-show.h \
+ src/shared/machine-image.c \
+ src/shared/machine-image.h \
+ src/shared/machine-pool.c \
+ src/shared/machine-pool.h \
+ src/shared/resolve-util.c \
+ src/shared/resolve-util.h
+
+ifneq ($(HAVE_UTMP),)
+libshared_la_SOURCES += \
+ src/shared/utmp-wtmp.c
+endif
+
+ifneq ($(HAVE_SECCOMP),)
+libshared_la_SOURCES += \
+ src/shared/seccomp-util.h \
+ src/shared/seccomp-util.c
+endif
+
+ifneq ($(HAVE_ACL),)
+libshared_la_SOURCES += \
+ src/shared/acl-util.c \
+ src/shared/acl-util.h
+endif
+
+libshared_la_CFLAGS = \
+ $(AM_CFLAGS) \
+ $(ACL_CFLAGS) \
+ $(LIBIDN_CFLAGS) \
+ $(SECCOMP_CFLAGS)
+
+libshared_la_LIBADD = \
+ libsystemd-internal.la \
+ libsystemd-journal-internal.la \
+ libudev-internal.la \
+ $(ACL_LIBS) \
+ $(LIBIDN_LIBS) \
+ $(SECCOMP_LIBS)
+
+include $(topsrcdir)/build-aux/Makefile.tail.mk
diff --git a/src/libshared/acl-util.c b/src/libshared/acl-util.c
new file mode 100644
index 0000000000..2aa951fce9
--- /dev/null
+++ b/src/libshared/acl-util.c
@@ -0,0 +1,429 @@
+/***
+ This file is part of systemd.
+
+ Copyright 2011,2013 Lennart Poettering
+
+ systemd is free software; you can redistribute it and/or modify it
+ under the terms of the GNU Lesser General Public License as published by
+ the Free Software Foundation; either version 2.1 of the License, or
+ (at your option) any later version.
+
+ systemd is distributed in the hope that it will be useful, but
+ WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ Lesser General Public License for more details.
+
+ You should have received a copy of the GNU Lesser General Public License
+ along with systemd; If not, see <http://www.gnu.org/licenses/>.
+***/
+
+#include <errno.h>
+#include <stdbool.h>
+
+#include "acl-util.h"
+#include "alloc-util.h"
+#include "string-util.h"
+#include "strv.h"
+#include "user-util.h"
+#include "util.h"
+
+int acl_find_uid(acl_t acl, uid_t uid, acl_entry_t *entry) {
+ acl_entry_t i;
+ int r;
+
+ assert(acl);
+ assert(entry);
+
+ for (r = acl_get_entry(acl, ACL_FIRST_ENTRY, &i);
+ r > 0;
+ r = acl_get_entry(acl, ACL_NEXT_ENTRY, &i)) {
+
+ acl_tag_t tag;
+ uid_t *u;
+ bool b;
+
+ if (acl_get_tag_type(i, &tag) < 0)
+ return -errno;
+
+ if (tag != ACL_USER)
+ continue;
+
+ u = acl_get_qualifier(i);
+ if (!u)
+ return -errno;
+
+ b = *u == uid;
+ acl_free(u);
+
+ if (b) {
+ *entry = i;
+ return 1;
+ }
+ }
+ if (r < 0)
+ return -errno;
+
+ return 0;
+}
+
+int calc_acl_mask_if_needed(acl_t *acl_p) {
+ acl_entry_t i;
+ int r;
+ bool need = false;
+
+ assert(acl_p);
+
+ for (r = acl_get_entry(*acl_p, ACL_FIRST_ENTRY, &i);
+ r > 0;
+ r = acl_get_entry(*acl_p, ACL_NEXT_ENTRY, &i)) {
+ acl_tag_t tag;
+
+ if (acl_get_tag_type(i, &tag) < 0)
+ return -errno;
+
+ if (tag == ACL_MASK)
+ return 0;
+
+ if (IN_SET(tag, ACL_USER, ACL_GROUP))
+ need = true;
+ }
+ if (r < 0)
+ return -errno;
+
+ if (need && acl_calc_mask(acl_p) < 0)
+ return -errno;
+
+ return need;
+}
+
+int add_base_acls_if_needed(acl_t *acl_p, const char *path) {
+ acl_entry_t i;
+ int r;
+ bool have_user_obj = false, have_group_obj = false, have_other = false;
+ struct stat st;
+ _cleanup_(acl_freep) acl_t basic = NULL;
+
+ assert(acl_p);
+
+ for (r = acl_get_entry(*acl_p, ACL_FIRST_ENTRY, &i);
+ r > 0;
+ r = acl_get_entry(*acl_p, ACL_NEXT_ENTRY, &i)) {
+ acl_tag_t tag;
+
+ if (acl_get_tag_type(i, &tag) < 0)
+ return -errno;
+
+ if (tag == ACL_USER_OBJ)
+ have_user_obj = true;
+ else if (tag == ACL_GROUP_OBJ)
+ have_group_obj = true;
+ else if (tag == ACL_OTHER)
+ have_other = true;
+ if (have_user_obj && have_group_obj && have_other)
+ return 0;
+ }
+ if (r < 0)
+ return -errno;
+
+ r = stat(path, &st);
+ if (r < 0)
+ return -errno;
+
+ basic = acl_from_mode(st.st_mode);
+ if (!basic)
+ return -errno;
+
+ for (r = acl_get_entry(basic, ACL_FIRST_ENTRY, &i);
+ r > 0;
+ r = acl_get_entry(basic, ACL_NEXT_ENTRY, &i)) {
+ acl_tag_t tag;
+ acl_entry_t dst;
+
+ if (acl_get_tag_type(i, &tag) < 0)
+ return -errno;
+
+ if ((tag == ACL_USER_OBJ && have_user_obj) ||
+ (tag == ACL_GROUP_OBJ && have_group_obj) ||
+ (tag == ACL_OTHER && have_other))
+ continue;
+
+ r = acl_create_entry(acl_p, &dst);
+ if (r < 0)
+ return -errno;
+
+ r = acl_copy_entry(dst, i);
+ if (r < 0)
+ return -errno;
+ }
+ if (r < 0)
+ return -errno;
+ return 0;
+}
+
+int acl_search_groups(const char *path, char ***ret_groups) {
+ _cleanup_strv_free_ char **g = NULL;
+ _cleanup_(acl_free) acl_t acl = NULL;
+ bool ret = false;
+ acl_entry_t entry;
+ int r;
+
+ assert(path);
+
+ acl = acl_get_file(path, ACL_TYPE_DEFAULT);
+ if (!acl)
+ return -errno;
+
+ r = acl_get_entry(acl, ACL_FIRST_ENTRY, &entry);
+ for (;;) {
+ _cleanup_(acl_free_gid_tpp) gid_t *gid = NULL;
+ acl_tag_t tag;
+
+ if (r < 0)
+ return -errno;
+ if (r == 0)
+ break;
+
+ if (acl_get_tag_type(entry, &tag) < 0)
+ return -errno;
+
+ if (tag != ACL_GROUP)
+ goto next;
+
+ gid = acl_get_qualifier(entry);
+ if (!gid)
+ return -errno;
+
+ if (in_gid(*gid) > 0) {
+ if (!ret_groups)
+ return true;
+
+ ret = true;
+ }
+
+ if (ret_groups) {
+ char *name;
+
+ name = gid_to_name(*gid);
+ if (!name)
+ return -ENOMEM;
+
+ r = strv_consume(&g, name);
+ if (r < 0)
+ return r;
+ }
+
+ next:
+ r = acl_get_entry(acl, ACL_NEXT_ENTRY, &entry);
+ }
+
+ if (ret_groups) {
+ *ret_groups = g;
+ g = NULL;
+ }
+
+ return ret;
+}
+
+int parse_acl(const char *text, acl_t *acl_access, acl_t *acl_default, bool want_mask) {
+ _cleanup_free_ char **a = NULL, **d = NULL; /* strings are not be freed */
+ _cleanup_strv_free_ char **split;
+ char **entry;
+ int r = -EINVAL;
+ _cleanup_(acl_freep) acl_t a_acl = NULL, d_acl = NULL;
+
+ split = strv_split(text, ",");
+ if (!split)
+ return -ENOMEM;
+
+ STRV_FOREACH(entry, split) {
+ char *p;
+
+ p = startswith(*entry, "default:");
+ if (!p)
+ p = startswith(*entry, "d:");
+
+ if (p)
+ r = strv_push(&d, p);
+ else
+ r = strv_push(&a, *entry);
+ if (r < 0)
+ return r;
+ }
+
+ if (!strv_isempty(a)) {
+ _cleanup_free_ char *join;
+
+ join = strv_join(a, ",");
+ if (!join)
+ return -ENOMEM;
+
+ a_acl = acl_from_text(join);
+ if (!a_acl)
+ return -errno;
+
+ if (want_mask) {
+ r = calc_acl_mask_if_needed(&a_acl);
+ if (r < 0)
+ return r;
+ }
+ }
+
+ if (!strv_isempty(d)) {
+ _cleanup_free_ char *join;
+
+ join = strv_join(d, ",");
+ if (!join)
+ return -ENOMEM;
+
+ d_acl = acl_from_text(join);
+ if (!d_acl)
+ return -errno;
+
+ if (want_mask) {
+ r = calc_acl_mask_if_needed(&d_acl);
+ if (r < 0)
+ return r;
+ }
+ }
+
+ *acl_access = a_acl;
+ *acl_default = d_acl;
+ a_acl = d_acl = NULL;
+
+ return 0;
+}
+
+static int acl_entry_equal(acl_entry_t a, acl_entry_t b) {
+ acl_tag_t tag_a, tag_b;
+
+ if (acl_get_tag_type(a, &tag_a) < 0)
+ return -errno;
+
+ if (acl_get_tag_type(b, &tag_b) < 0)
+ return -errno;
+
+ if (tag_a != tag_b)
+ return false;
+
+ switch (tag_a) {
+ case ACL_USER_OBJ:
+ case ACL_GROUP_OBJ:
+ case ACL_MASK:
+ case ACL_OTHER:
+ /* can have only one of those */
+ return true;
+ case ACL_USER: {
+ _cleanup_(acl_free_uid_tpp) uid_t *uid_a = NULL, *uid_b = NULL;
+
+ uid_a = acl_get_qualifier(a);
+ if (!uid_a)
+ return -errno;
+
+ uid_b = acl_get_qualifier(b);
+ if (!uid_b)
+ return -errno;
+
+ return *uid_a == *uid_b;
+ }
+ case ACL_GROUP: {
+ _cleanup_(acl_free_gid_tpp) gid_t *gid_a = NULL, *gid_b = NULL;
+
+ gid_a = acl_get_qualifier(a);
+ if (!gid_a)
+ return -errno;
+
+ gid_b = acl_get_qualifier(b);
+ if (!gid_b)
+ return -errno;
+
+ return *gid_a == *gid_b;
+ }
+ default:
+ assert_not_reached("Unknown acl tag type");
+ }
+}
+
+static int find_acl_entry(acl_t acl, acl_entry_t entry, acl_entry_t *out) {
+ acl_entry_t i;
+ int r;
+
+ for (r = acl_get_entry(acl, ACL_FIRST_ENTRY, &i);
+ r > 0;
+ r = acl_get_entry(acl, ACL_NEXT_ENTRY, &i)) {
+
+ r = acl_entry_equal(i, entry);
+ if (r < 0)
+ return r;
+ if (r > 0) {
+ *out = i;
+ return 1;
+ }
+ }
+ if (r < 0)
+ return -errno;
+ return 0;
+}
+
+int acls_for_file(const char *path, acl_type_t type, acl_t new, acl_t *acl) {
+ _cleanup_(acl_freep) acl_t old;
+ acl_entry_t i;
+ int r;
+
+ old = acl_get_file(path, type);
+ if (!old)
+ return -errno;
+
+ for (r = acl_get_entry(new, ACL_FIRST_ENTRY, &i);
+ r > 0;
+ r = acl_get_entry(new, ACL_NEXT_ENTRY, &i)) {
+
+ acl_entry_t j;
+
+ r = find_acl_entry(old, i, &j);
+ if (r < 0)
+ return r;
+ if (r == 0)
+ if (acl_create_entry(&old, &j) < 0)
+ return -errno;
+
+ if (acl_copy_entry(j, i) < 0)
+ return -errno;
+ }
+ if (r < 0)
+ return -errno;
+
+ *acl = old;
+ old = NULL;
+ return 0;
+}
+
+int add_acls_for_user(int fd, uid_t uid) {
+ _cleanup_(acl_freep) acl_t acl = NULL;
+ acl_entry_t entry;
+ acl_permset_t permset;
+ int r;
+
+ acl = acl_get_fd(fd);
+ if (!acl)
+ return -errno;
+
+ r = acl_find_uid(acl, uid, &entry);
+ if (r <= 0) {
+ if (acl_create_entry(&acl, &entry) < 0 ||
+ acl_set_tag_type(entry, ACL_USER) < 0 ||
+ acl_set_qualifier(entry, &uid) < 0)
+ return -errno;
+ }
+
+ /* We do not recalculate the mask unconditionally here,
+ * so that the fchmod() mask above stays intact. */
+ if (acl_get_permset(entry, &permset) < 0 ||
+ acl_add_perm(permset, ACL_READ) < 0)
+ return -errno;
+
+ r = calc_acl_mask_if_needed(&acl);
+ if (r < 0)
+ return r;
+
+ return acl_set_fd(fd, acl);
+}
diff --git a/src/libshared/acl-util.h b/src/libshared/acl-util.h
new file mode 100644
index 0000000000..396e9e067e
--- /dev/null
+++ b/src/libshared/acl-util.h
@@ -0,0 +1,48 @@
+#pragma once
+
+/***
+ This file is part of systemd.
+
+ Copyright 2011 Lennart Poettering
+
+ systemd is free software; you can redistribute it and/or modify it
+ under the terms of the GNU Lesser General Public License as published by
+ the Free Software Foundation; either version 2.1 of the License, or
+ (at your option) any later version.
+
+ systemd is distributed in the hope that it will be useful, but
+ WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ Lesser General Public License for more details.
+
+ You should have received a copy of the GNU Lesser General Public License
+ along with systemd; If not, see <http://www.gnu.org/licenses/>.
+***/
+
+#ifdef HAVE_ACL
+
+#include <acl/libacl.h>
+#include <stdbool.h>
+#include <sys/acl.h>
+
+#include "macro.h"
+
+int acl_find_uid(acl_t acl, uid_t uid, acl_entry_t *entry);
+int calc_acl_mask_if_needed(acl_t *acl_p);
+int add_base_acls_if_needed(acl_t *acl_p, const char *path);
+int acl_search_groups(const char* path, char ***ret_groups);
+int parse_acl(const char *text, acl_t *acl_access, acl_t *acl_default, bool want_mask);
+int acls_for_file(const char *path, acl_type_t type, acl_t new, acl_t *acl);
+int add_acls_for_user(int fd, uid_t uid);
+
+/* acl_free takes multiple argument types.
+ * Multiple cleanup functions are necessary. */
+DEFINE_TRIVIAL_CLEANUP_FUNC(acl_t, acl_free);
+#define acl_free_charp acl_free
+DEFINE_TRIVIAL_CLEANUP_FUNC(char*, acl_free_charp);
+#define acl_free_uid_tp acl_free
+DEFINE_TRIVIAL_CLEANUP_FUNC(uid_t*, acl_free_uid_tp);
+#define acl_free_gid_tp acl_free
+DEFINE_TRIVIAL_CLEANUP_FUNC(gid_t*, acl_free_gid_tp);
+
+#endif
diff --git a/src/libshared/acpi-fpdt.c b/src/libshared/acpi-fpdt.c
new file mode 100644
index 0000000000..3cb9e781fd
--- /dev/null
+++ b/src/libshared/acpi-fpdt.c
@@ -0,0 +1,160 @@
+/***
+ This file is part of systemd.
+
+ Copyright 2013 Kay Sievers
+
+ systemd is free software; you can redistribute it and/or modify it
+ under the terms of the GNU Lesser General Public License as published by
+ the Free Software Foundation; either version 2.1 of the License, or
+ (at your option) any later version.
+
+ systemd is distributed in the hope that it will be useful, but
+ WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ Lesser General Public License for more details.
+
+ You should have received a copy of the GNU Lesser General Public License
+ along with systemd; If not, see <http://www.gnu.org/licenses/>.
+***/
+
+#include <errno.h>
+#include <fcntl.h>
+#include <stddef.h>
+#include <stdint.h>
+#include <string.h>
+#include <unistd.h>
+
+#include "acpi-fpdt.h"
+#include "alloc-util.h"
+#include "fd-util.h"
+#include "fileio.h"
+#include "time-util.h"
+
+struct acpi_table_header {
+ char signature[4];
+ uint32_t length;
+ uint8_t revision;
+ uint8_t checksum;
+ char oem_id[6];
+ char oem_table_id[8];
+ uint32_t oem_revision;
+ char asl_compiler_id[4];
+ uint32_t asl_compiler_revision;
+};
+
+enum {
+ ACPI_FPDT_TYPE_BOOT = 0,
+ ACPI_FPDT_TYPE_S3PERF = 1,
+};
+
+struct acpi_fpdt_header {
+ uint16_t type;
+ uint8_t length;
+ uint8_t revision;
+ uint8_t reserved[4];
+ uint64_t ptr;
+};
+
+struct acpi_fpdt_boot_header {
+ char signature[4];
+ uint32_t length;
+};
+
+enum {
+ ACPI_FPDT_S3PERF_RESUME_REC = 0,
+ ACPI_FPDT_S3PERF_SUSPEND_REC = 1,
+ ACPI_FPDT_BOOT_REC = 2,
+};
+
+struct acpi_fpdt_boot {
+ uint16_t type;
+ uint8_t length;
+ uint8_t revision;
+ uint8_t reserved[4];
+ uint64_t reset_end;
+ uint64_t load_start;
+ uint64_t startup_start;
+ uint64_t exit_services_entry;
+ uint64_t exit_services_exit;
+};
+
+int acpi_get_boot_usec(usec_t *loader_start, usec_t *loader_exit) {
+ _cleanup_free_ char *buf = NULL;
+ struct acpi_table_header *tbl;
+ size_t l = 0;
+ struct acpi_fpdt_header *rec;
+ int r;
+ uint64_t ptr = 0;
+ _cleanup_close_ int fd = -1;
+ struct acpi_fpdt_boot_header hbrec;
+ struct acpi_fpdt_boot brec;
+
+ r = read_full_file("/sys/firmware/acpi/tables/FPDT", &buf, &l);
+ if (r < 0)
+ return r;
+
+ if (l < sizeof(struct acpi_table_header) + sizeof(struct acpi_fpdt_header))
+ return -EINVAL;
+
+ tbl = (struct acpi_table_header *)buf;
+ if (l != tbl->length)
+ return -EINVAL;
+
+ if (memcmp(tbl->signature, "FPDT", 4) != 0)
+ return -EINVAL;
+
+ /* find Firmware Basic Boot Performance Pointer Record */
+ for (rec = (struct acpi_fpdt_header *)(buf + sizeof(struct acpi_table_header));
+ (char *)rec < buf + l;
+ rec = (struct acpi_fpdt_header *)((char *)rec + rec->length)) {
+ if (rec->length <= 0)
+ break;
+ if (rec->type != ACPI_FPDT_TYPE_BOOT)
+ continue;
+ if (rec->length != sizeof(struct acpi_fpdt_header))
+ continue;
+
+ ptr = rec->ptr;
+ break;
+ }
+
+ if (ptr == 0)
+ return -EINVAL;
+
+ /* read Firmware Basic Boot Performance Data Record */
+ fd = open("/dev/mem", O_CLOEXEC|O_RDONLY);
+ if (fd < 0)
+ return -errno;
+
+ l = pread(fd, &hbrec, sizeof(struct acpi_fpdt_boot_header), ptr);
+ if (l != sizeof(struct acpi_fpdt_boot_header))
+ return -EINVAL;
+
+ if (memcmp(hbrec.signature, "FBPT", 4) != 0)
+ return -EINVAL;
+
+ if (hbrec.length < sizeof(struct acpi_fpdt_boot_header) + sizeof(struct acpi_fpdt_boot))
+ return -EINVAL;
+
+ l = pread(fd, &brec, sizeof(struct acpi_fpdt_boot), ptr + sizeof(struct acpi_fpdt_boot_header));
+ if (l != sizeof(struct acpi_fpdt_boot))
+ return -EINVAL;
+
+ if (brec.length != sizeof(struct acpi_fpdt_boot))
+ return -EINVAL;
+
+ if (brec.type != ACPI_FPDT_BOOT_REC)
+ return -EINVAL;
+
+ if (brec.startup_start == 0 || brec.exit_services_exit < brec.startup_start)
+ return -EINVAL;
+ if (brec.exit_services_exit > NSEC_PER_HOUR)
+ return -EINVAL;
+
+ if (loader_start)
+ *loader_start = brec.startup_start / 1000;
+ if (loader_exit)
+ *loader_exit = brec.exit_services_exit / 1000;
+
+ return 0;
+}
diff --git a/src/libshared/acpi-fpdt.h b/src/libshared/acpi-fpdt.h
new file mode 100644
index 0000000000..fc28175d0a
--- /dev/null
+++ b/src/libshared/acpi-fpdt.h
@@ -0,0 +1,24 @@
+#pragma once
+
+/***
+ This file is part of systemd.
+
+ Copyright 2013 Kay Sievers
+
+ systemd is free software; you can redistribute it and/or modify it
+ under the terms of the GNU Lesser General Public License as published by
+ the Free Software Foundation; either version 2.1 of the License, or
+ (at your option) any later version.
+
+ systemd is distributed in the hope that it will be useful, but
+ WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ Lesser General Public License for more details.
+
+ You should have received a copy of the GNU Lesser General Public License
+ along with systemd; If not, see <http://www.gnu.org/licenses/>.
+***/
+
+#include <time-util.h>
+
+int acpi_get_boot_usec(usec_t *loader_start, usec_t *loader_exit);
diff --git a/src/libshared/apparmor-util.c b/src/libshared/apparmor-util.c
new file mode 100644
index 0000000000..edd695fd23
--- /dev/null
+++ b/src/libshared/apparmor-util.c
@@ -0,0 +1,39 @@
+/***
+ This file is part of systemd.
+
+ Copyright 2013 Lennart Poettering
+
+ systemd is free software; you can redistribute it and/or modify it
+ under the terms of the GNU Lesser General Public License as published by
+ the Free Software Foundation; either version 2.1 of the License, or
+ (at your option) any later version.
+
+ systemd is distributed in the hope that it will be useful, but
+ WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ Lesser General Public License for more details.
+
+ You should have received a copy of the GNU Lesser General Public License
+ along with systemd; If not, see <http://www.gnu.org/licenses/>.
+***/
+
+#include <stddef.h>
+
+#include "alloc-util.h"
+#include "apparmor-util.h"
+#include "fileio.h"
+#include "parse-util.h"
+
+bool mac_apparmor_use(void) {
+ static int cached_use = -1;
+
+ if (cached_use < 0) {
+ _cleanup_free_ char *p = NULL;
+
+ cached_use =
+ read_one_line_file("/sys/module/apparmor/parameters/enabled", &p) >= 0 &&
+ parse_boolean(p) > 0;
+ }
+
+ return cached_use;
+}
diff --git a/src/libshared/apparmor-util.h b/src/libshared/apparmor-util.h
new file mode 100644
index 0000000000..524f740152
--- /dev/null
+++ b/src/libshared/apparmor-util.h
@@ -0,0 +1,24 @@
+#pragma once
+
+/***
+ This file is part of systemd.
+
+ Copyright 2013 Lennart Poettering
+
+ systemd is free software; you can redistribute it and/or modify it
+ under the terms of the GNU Lesser General Public License as published by
+ the Free Software Foundation; either version 2.1 of the License, or
+ (at your option) any later version.
+
+ systemd is distributed in the hope that it will be useful, but
+ WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ Lesser General Public License for more details.
+
+ You should have received a copy of the GNU Lesser General Public License
+ along with systemd; If not, see <http://www.gnu.org/licenses/>.
+***/
+
+#include <stdbool.h>
+
+bool mac_apparmor_use(void);
diff --git a/src/libshared/architecture.c b/src/libshared/architecture.c
new file mode 100644
index 0000000000..a9ecfc1cd6
--- /dev/null
+++ b/src/libshared/architecture.c
@@ -0,0 +1,176 @@
+/***
+ This file is part of systemd.
+
+ Copyright 2014 Lennart Poettering
+
+ systemd is free software; you can redistribute it and/or modify it
+ under the terms of the GNU Lesser General Public License as published by
+ the Free Software Foundation; either version 2.1 of the License, or
+ (at your option) any later version.
+
+ systemd is distributed in the hope that it will be useful, but
+ WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ Lesser General Public License for more details.
+
+ You should have received a copy of the GNU Lesser General Public License
+ along with systemd; If not, see <http://www.gnu.org/licenses/>.
+***/
+
+#include <sys/utsname.h>
+
+#include "architecture.h"
+#include "macro.h"
+#include "string-table.h"
+#include "string-util.h"
+
+int uname_architecture(void) {
+
+ /* Return a sanitized enum identifying the architecture we are
+ * running on. This is based on uname(), and the user may
+ * hence control what this returns by using
+ * personality(). This puts the user in control on systems
+ * that can run binaries of multiple architectures.
+ *
+ * We do not translate the string returned by uname()
+ * 1:1. Instead we try to clean it up and break down the
+ * confusion on x86 and arm in particular.
+ *
+ * We do not try to distinguish CPUs not CPU features, but
+ * actual architectures, i.e. that have genuinely different
+ * code. */
+
+ static const struct {
+ const char *machine;
+ int arch;
+ } arch_map[] = {
+#if defined(__x86_64__) || defined(__i386__)
+ { "x86_64", ARCHITECTURE_X86_64 },
+ { "i686", ARCHITECTURE_X86 },
+ { "i586", ARCHITECTURE_X86 },
+ { "i486", ARCHITECTURE_X86 },
+ { "i386", ARCHITECTURE_X86 },
+#elif defined(__powerpc__) || defined(__powerpc64__)
+ { "ppc64", ARCHITECTURE_PPC64 },
+ { "ppc64le", ARCHITECTURE_PPC64_LE },
+ { "ppc", ARCHITECTURE_PPC },
+ { "ppcle", ARCHITECTURE_PPC_LE },
+#elif defined(__ia64__)
+ { "ia64", ARCHITECTURE_IA64 },
+#elif defined(__hppa__) || defined(__hppa64__)
+ { "parisc64", ARCHITECTURE_PARISC64 },
+ { "parisc", ARCHITECTURE_PARISC },
+#elif defined(__s390__) || defined(__s390x__)
+ { "s390x", ARCHITECTURE_S390X },
+ { "s390", ARCHITECTURE_S390 },
+#elif defined(__sparc__) || defined(__sparc64__)
+ { "sparc64", ARCHITECTURE_SPARC64 },
+ { "sparc", ARCHITECTURE_SPARC },
+#elif defined(__mips__) || defined(__mips64__)
+ { "mips64", ARCHITECTURE_MIPS64 },
+ { "mips", ARCHITECTURE_MIPS },
+#elif defined(__alpha__)
+ { "alpha" , ARCHITECTURE_ALPHA },
+#elif defined(__arm__) || defined(__aarch64__)
+ { "aarch64", ARCHITECTURE_ARM64 },
+ { "aarch64_be", ARCHITECTURE_ARM64_BE },
+ { "armv4l", ARCHITECTURE_ARM },
+ { "armv4b", ARCHITECTURE_ARM_BE },
+ { "armv4tl", ARCHITECTURE_ARM },
+ { "armv4tb", ARCHITECTURE_ARM_BE },
+ { "armv5tl", ARCHITECTURE_ARM },
+ { "armv5tb", ARCHITECTURE_ARM_BE },
+ { "armv5tel", ARCHITECTURE_ARM },
+ { "armv5teb" , ARCHITECTURE_ARM_BE },
+ { "armv5tejl", ARCHITECTURE_ARM },
+ { "armv5tejb", ARCHITECTURE_ARM_BE },
+ { "armv6l", ARCHITECTURE_ARM },
+ { "armv6b", ARCHITECTURE_ARM_BE },
+ { "armv7l", ARCHITECTURE_ARM },
+ { "armv7b", ARCHITECTURE_ARM_BE },
+ { "armv7ml", ARCHITECTURE_ARM },
+ { "armv7mb", ARCHITECTURE_ARM_BE },
+ { "armv4l", ARCHITECTURE_ARM },
+ { "armv4b", ARCHITECTURE_ARM_BE },
+ { "armv4tl", ARCHITECTURE_ARM },
+ { "armv4tb", ARCHITECTURE_ARM_BE },
+ { "armv5tl", ARCHITECTURE_ARM },
+ { "armv5tb", ARCHITECTURE_ARM_BE },
+ { "armv5tel", ARCHITECTURE_ARM },
+ { "armv5teb", ARCHITECTURE_ARM_BE },
+ { "armv5tejl", ARCHITECTURE_ARM },
+ { "armv5tejb", ARCHITECTURE_ARM_BE },
+ { "armv6l", ARCHITECTURE_ARM },
+ { "armv6b", ARCHITECTURE_ARM_BE },
+ { "armv7l", ARCHITECTURE_ARM },
+ { "armv7b", ARCHITECTURE_ARM_BE },
+ { "armv7ml", ARCHITECTURE_ARM },
+ { "armv7mb", ARCHITECTURE_ARM_BE },
+ { "armv8l", ARCHITECTURE_ARM },
+ { "armv8b", ARCHITECTURE_ARM_BE },
+#elif defined(__sh__) || defined(__sh64__)
+ { "sh5", ARCHITECTURE_SH64 },
+ { "sh2", ARCHITECTURE_SH },
+ { "sh2a", ARCHITECTURE_SH },
+ { "sh3", ARCHITECTURE_SH },
+ { "sh4", ARCHITECTURE_SH },
+ { "sh4a", ARCHITECTURE_SH },
+#elif defined(__m68k__)
+ { "m68k", ARCHITECTURE_M68K },
+#elif defined(__tilegx__)
+ { "tilegx", ARCHITECTURE_TILEGX },
+#elif defined(__cris__)
+ { "crisv32", ARCHITECTURE_CRIS },
+#else
+#error "Please register your architecture here!"
+#endif
+ };
+
+ static int cached = _ARCHITECTURE_INVALID;
+ struct utsname u;
+ unsigned i;
+
+ if (cached != _ARCHITECTURE_INVALID)
+ return cached;
+
+ assert_se(uname(&u) >= 0);
+
+ for (i = 0; i < ELEMENTSOF(arch_map); i++)
+ if (streq(arch_map[i].machine, u.machine))
+ return cached = arch_map[i].arch;
+
+ assert_not_reached("Couldn't identify architecture. You need to patch systemd.");
+ return _ARCHITECTURE_INVALID;
+}
+
+static const char *const architecture_table[_ARCHITECTURE_MAX] = {
+ [ARCHITECTURE_X86] = "x86",
+ [ARCHITECTURE_X86_64] = "x86-64",
+ [ARCHITECTURE_PPC] = "ppc",
+ [ARCHITECTURE_PPC_LE] = "ppc-le",
+ [ARCHITECTURE_PPC64] = "ppc64",
+ [ARCHITECTURE_PPC64_LE] = "ppc64-le",
+ [ARCHITECTURE_IA64] = "ia64",
+ [ARCHITECTURE_PARISC] = "parisc",
+ [ARCHITECTURE_PARISC64] = "parisc64",
+ [ARCHITECTURE_S390] = "s390",
+ [ARCHITECTURE_S390X] = "s390x",
+ [ARCHITECTURE_SPARC] = "sparc",
+ [ARCHITECTURE_SPARC64] = "sparc64",
+ [ARCHITECTURE_MIPS] = "mips",
+ [ARCHITECTURE_MIPS_LE] = "mips-le",
+ [ARCHITECTURE_MIPS64] = "mips64",
+ [ARCHITECTURE_MIPS64_LE] = "mips64-le",
+ [ARCHITECTURE_ALPHA] = "alpha",
+ [ARCHITECTURE_ARM] = "arm",
+ [ARCHITECTURE_ARM_BE] = "arm-be",
+ [ARCHITECTURE_ARM64] = "arm64",
+ [ARCHITECTURE_ARM64_BE] = "arm64-be",
+ [ARCHITECTURE_SH] = "sh",
+ [ARCHITECTURE_SH64] = "sh64",
+ [ARCHITECTURE_M68K] = "m68k",
+ [ARCHITECTURE_TILEGX] = "tilegx",
+ [ARCHITECTURE_CRIS] = "cris",
+};
+
+DEFINE_STRING_TABLE_LOOKUP(architecture, int);
diff --git a/src/libshared/architecture.h b/src/libshared/architecture.h
new file mode 100644
index 0000000000..26679e28c6
--- /dev/null
+++ b/src/libshared/architecture.h
@@ -0,0 +1,207 @@
+#pragma once
+
+/***
+ This file is part of systemd.
+
+ Copyright 2014 Lennart Poettering
+
+ systemd is free software; you can redistribute it and/or modify it
+ under the terms of the GNU Lesser General Public License as published by
+ the Free Software Foundation; either version 2.1 of the License, or
+ (at your option) any later version.
+
+ systemd is distributed in the hope that it will be useful, but
+ WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ Lesser General Public License for more details.
+
+ You should have received a copy of the GNU Lesser General Public License
+ along with systemd; If not, see <http://www.gnu.org/licenses/>.
+***/
+
+#include <endian.h>
+
+#include "macro.h"
+#include "util.h"
+
+/* A cleaned up architecture definition. We don't want to get lost in
+ * processor features, models, generations or even ABIs. Hence we
+ * focus on general family, and distinguish word width and
+ * endianness. */
+
+enum {
+ ARCHITECTURE_X86 = 0,
+ ARCHITECTURE_X86_64,
+ ARCHITECTURE_PPC,
+ ARCHITECTURE_PPC_LE,
+ ARCHITECTURE_PPC64,
+ ARCHITECTURE_PPC64_LE,
+ ARCHITECTURE_IA64,
+ ARCHITECTURE_PARISC,
+ ARCHITECTURE_PARISC64,
+ ARCHITECTURE_S390,
+ ARCHITECTURE_S390X,
+ ARCHITECTURE_SPARC,
+ ARCHITECTURE_SPARC64,
+ ARCHITECTURE_MIPS,
+ ARCHITECTURE_MIPS_LE,
+ ARCHITECTURE_MIPS64,
+ ARCHITECTURE_MIPS64_LE,
+ ARCHITECTURE_ALPHA,
+ ARCHITECTURE_ARM,
+ ARCHITECTURE_ARM_BE,
+ ARCHITECTURE_ARM64,
+ ARCHITECTURE_ARM64_BE,
+ ARCHITECTURE_SH,
+ ARCHITECTURE_SH64,
+ ARCHITECTURE_M68K,
+ ARCHITECTURE_TILEGX,
+ ARCHITECTURE_CRIS,
+ _ARCHITECTURE_MAX,
+ _ARCHITECTURE_INVALID = -1
+};
+
+int uname_architecture(void);
+
+/*
+ * LIB_ARCH_TUPLE should resolve to the local library path
+ * architecture tuple systemd is built for, according to the Debian
+ * tuple list:
+ *
+ * https://wiki.debian.org/Multiarch/Tuples
+ *
+ * This is used in library search paths that should understand
+ * Debian's paths on all distributions.
+ */
+
+#if defined(__x86_64__)
+# define native_architecture() ARCHITECTURE_X86_64
+# define LIB_ARCH_TUPLE "x86_64-linux-gnu"
+# define PROC_CPUINFO_MODEL "model name"
+#elif defined(__i386__)
+# define native_architecture() ARCHITECTURE_X86
+# define LIB_ARCH_TUPLE "i386-linux-gnu"
+# define PROC_CPUINFO_MODEL "model name"
+#elif defined(__powerpc64__)
+# if __BYTE_ORDER == __BIG_ENDIAN
+# define native_architecture() ARCHITECTURE_PPC64
+# define LIB_ARCH_TUPLE "ppc64-linux-gnu"
+# else
+# define native_architecture() ARCHITECTURE_PPC64_LE
+# define LIB_ARCH_TUPLE "powerpc64le-linux-gnu"
+# endif
+# define PROC_CPUINFO_MODEL "cpu"
+#elif defined(__powerpc__)
+# if __BYTE_ORDER == __BIG_ENDIAN
+# define native_architecture() ARCHITECTURE_PPC
+# define LIB_ARCH_TUPLE "powerpc-linux-gnu"
+# else
+# define native_architecture() ARCHITECTURE_PPC_LE
+# error "Missing LIB_ARCH_TUPLE for PPCLE"
+# endif
+# define PROC_CPUINFO_MODEL "cpu"
+#elif defined(__ia64__)
+# define native_architecture() ARCHITECTURE_IA64
+# define LIB_ARCH_TUPLE "ia64-linux-gnu"
+#elif defined(__hppa64__)
+# define native_architecture() ARCHITECTURE_PARISC64
+# error "Missing LIB_ARCH_TUPLE for HPPA64"
+# define PROC_CPUINFO_MODEL "cpu"
+#elif defined(__hppa__)
+# define native_architecture() ARCHITECTURE_PARISC
+# define LIB_ARCH_TUPLE "hppa‑linux‑gnu"
+# define PROC_CPUINFO_MODEL "cpu"
+#elif defined(__s390x__)
+# define native_architecture() ARCHITECTURE_S390X
+# define LIB_ARCH_TUPLE "s390x-linux-gnu"
+#elif defined(__s390__)
+# define native_architecture() ARCHITECTURE_S390
+# define LIB_ARCH_TUPLE "s390-linux-gnu"
+#elif defined(__sparc64__)
+# define native_architecture() ARCHITECTURE_SPARC64
+# define LIB_ARCH_TUPLE "sparc64-linux-gnu"
+# define PROC_CPUINFO_MODEL "cpu"
+#elif defined(__sparc__)
+# define native_architecture() ARCHITECTURE_SPARC
+# define LIB_ARCH_TUPLE "sparc-linux-gnu"
+# define PROC_CPUINFO_MODEL "cpu"
+#elif defined(__mips64__)
+# if __BYTE_ORDER == __BIG_ENDIAN
+# define native_architecture() ARCHITECTURE_MIPS64
+# error "Missing LIB_ARCH_TUPLE for MIPS64"
+# else
+# define native_architecture() ARCHITECTURE_MIPS64_LE
+# error "Missing LIB_ARCH_TUPLE for MIPS64_LE"
+# endif
+# define PROC_CPUINFO_MODEL "cpu model"
+#elif defined(__mips__)
+# if __BYTE_ORDER == __BIG_ENDIAN
+# define native_architecture() ARCHITECTURE_MIPS
+# define LIB_ARCH_TUPLE "mips-linux-gnu"
+# else
+# define native_architecture() ARCHITECTURE_MIPS_LE
+# define LIB_ARCH_TUPLE "mipsel-linux-gnu"
+# endif
+# define PROC_CPUINFO_MODEL "cpu model"
+#elif defined(__alpha__)
+# define native_architecture() ARCHITECTURE_ALPHA
+# define LIB_ARCH_TUPLE "alpha-linux-gnu"
+#elif defined(__aarch64__)
+# if __BYTE_ORDER == __BIG_ENDIAN
+# define native_architecture() ARCHITECTURE_ARM64_BE
+# define LIB_ARCH_TUPLE "aarch64_be-linux-gnu"
+# else
+# define native_architecture() ARCHITECTURE_ARM64
+# define LIB_ARCH_TUPLE "aarch64-linux-gnu"
+# endif
+#elif defined(__arm__)
+# if __BYTE_ORDER == __BIG_ENDIAN
+# define native_architecture() ARCHITECTURE_ARM_BE
+# if defined(__ARM_EABI__)
+# if defined(__ARM_PCS_VFP)
+# define LIB_ARCH_TUPLE "armeb-linux-gnueabihf"
+# else
+# define LIB_ARCH_TUPLE "armeb-linux-gnueabi"
+# endif
+# else
+# define LIB_ARCH_TUPLE "armeb-linux-gnu"
+# endif
+# else
+# define native_architecture() ARCHITECTURE_ARM
+# if defined(__ARM_EABI__)
+# if defined(__ARM_PCS_VFP)
+# define LIB_ARCH_TUPLE "arm-linux-gnueabihf"
+# else
+# define LIB_ARCH_TUPLE "arm-linux-gnueabi"
+# endif
+# else
+# define LIB_ARCH_TUPLE "arm-linux-gnu"
+# endif
+# endif
+# define PROC_CPUINFO_MODEL "model name"
+#elif defined(__sh64__)
+# define native_architecture() ARCHITECTURE_SH64
+# error "Missing LIB_ARCH_TUPLE for SH64"
+#elif defined(__sh__)
+# define native_architecture() ARCHITECTURE_SH
+# define LIB_ARCH_TUPLE "sh4-linux-gnu"
+#elif defined(__m68k__)
+# define native_architecture() ARCHITECTURE_M68K
+# define LIB_ARCH_TUPLE "m68k-linux-gnu"
+#elif defined(__tilegx__)
+# define native_architecture() ARCHITECTURE_TILEGX
+# error "Missing LIB_ARCH_TUPLE for TILEGX"
+#elif defined(__cris__)
+# define native_architecture() ARCHITECTURE_CRIS
+# error "Missing LIB_ARCH_TUPLE for CRIS"
+#else
+# error "Please register your architecture here!"
+#endif
+
+#ifndef PROC_CPUINFO_MODEL
+#warning "PROC_CPUINFO_MODEL not defined for your architecture"
+#define PROC_CPUINFO_MODEL "model name"
+#endif
+
+const char *architecture_to_string(int a) _const_;
+int architecture_from_string(const char *s) _pure_;
diff --git a/src/libshared/ask-password-api.c b/src/libshared/ask-password-api.c
new file mode 100644
index 0000000000..6805873f9e
--- /dev/null
+++ b/src/libshared/ask-password-api.c
@@ -0,0 +1,736 @@
+/***
+ This file is part of systemd.
+
+ Copyright 2010 Lennart Poettering
+
+ systemd is free software; you can redistribute it and/or modify it
+ under the terms of the GNU Lesser General Public License as published by
+ the Free Software Foundation; either version 2.1 of the License, or
+ (at your option) any later version.
+
+ systemd is distributed in the hope that it will be useful, but
+ WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ Lesser General Public License for more details.
+
+ You should have received a copy of the GNU Lesser General Public License
+ along with systemd; If not, see <http://www.gnu.org/licenses/>.
+***/
+
+#include <errno.h>
+#include <fcntl.h>
+#include <inttypes.h>
+#include <limits.h>
+#include <poll.h>
+#include <signal.h>
+#include <stdbool.h>
+#include <stddef.h>
+#include <stdint.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <sys/inotify.h>
+#include <sys/signalfd.h>
+#include <sys/socket.h>
+#include <sys/stat.h>
+#include <sys/time.h>
+#include <sys/uio.h>
+#include <sys/un.h>
+#include <termios.h>
+#include <unistd.h>
+
+#include "alloc-util.h"
+#include "ask-password-api.h"
+#include "fd-util.h"
+#include "fileio.h"
+#include "formats-util.h"
+#include "io-util.h"
+#include "log.h"
+#include "macro.h"
+#include "missing.h"
+#include "mkdir.h"
+#include "random-util.h"
+#include "signal-util.h"
+#include "socket-util.h"
+#include "string-util.h"
+#include "strv.h"
+#include "terminal-util.h"
+#include "time-util.h"
+#include "umask-util.h"
+#include "utf8.h"
+#include "util.h"
+
+#define KEYRING_TIMEOUT_USEC ((5 * USEC_PER_MINUTE) / 2)
+
+static int lookup_key(const char *keyname, key_serial_t *ret) {
+ key_serial_t serial;
+
+ assert(keyname);
+ assert(ret);
+
+ serial = request_key("user", keyname, NULL, 0);
+ if (serial == -1)
+ return negative_errno();
+
+ *ret = serial;
+ return 0;
+}
+
+static int retrieve_key(key_serial_t serial, char ***ret) {
+ _cleanup_free_ char *p = NULL;
+ long m = 100, n;
+ char **l;
+
+ assert(ret);
+
+ for (;;) {
+ p = new(char, m);
+ if (!p)
+ return -ENOMEM;
+
+ n = keyctl(KEYCTL_READ, (unsigned long) serial, (unsigned long) p, (unsigned long) m, 0);
+ if (n < 0)
+ return -errno;
+
+ if (n < m)
+ break;
+
+ memory_erase(p, n);
+ free(p);
+ m *= 2;
+ }
+
+ l = strv_parse_nulstr(p, n);
+ if (!l)
+ return -ENOMEM;
+
+ memory_erase(p, n);
+
+ *ret = l;
+ return 0;
+}
+
+static int add_to_keyring(const char *keyname, AskPasswordFlags flags, char **passwords) {
+ _cleanup_strv_free_erase_ char **l = NULL;
+ _cleanup_free_ char *p = NULL;
+ key_serial_t serial;
+ size_t n;
+ int r;
+
+ assert(keyname);
+ assert(passwords);
+
+ if (!(flags & ASK_PASSWORD_PUSH_CACHE))
+ return 0;
+
+ r = lookup_key(keyname, &serial);
+ if (r >= 0) {
+ r = retrieve_key(serial, &l);
+ if (r < 0)
+ return r;
+ } else if (r != -ENOKEY)
+ return r;
+
+ r = strv_extend_strv(&l, passwords, true);
+ if (r <= 0)
+ return r;
+
+ r = strv_make_nulstr(l, &p, &n);
+ if (r < 0)
+ return r;
+
+ /* Truncate trailing NUL */
+ assert(n > 0);
+ assert(p[n-1] == 0);
+
+ serial = add_key("user", keyname, p, n-1, KEY_SPEC_USER_KEYRING);
+ memory_erase(p, n);
+ if (serial == -1)
+ return -errno;
+
+ if (keyctl(KEYCTL_SET_TIMEOUT,
+ (unsigned long) serial,
+ (unsigned long) DIV_ROUND_UP(KEYRING_TIMEOUT_USEC, USEC_PER_SEC), 0, 0) < 0)
+ log_debug_errno(errno, "Failed to adjust timeout: %m");
+
+ log_debug("Added key to keyring as %" PRIi32 ".", serial);
+
+ return 1;
+}
+
+static int add_to_keyring_and_log(const char *keyname, AskPasswordFlags flags, char **passwords) {
+ int r;
+
+ assert(keyname);
+ assert(passwords);
+
+ r = add_to_keyring(keyname, flags, passwords);
+ if (r < 0)
+ return log_debug_errno(r, "Failed to add password to keyring: %m");
+
+ return 0;
+}
+
+int ask_password_keyring(const char *keyname, AskPasswordFlags flags, char ***ret) {
+
+ key_serial_t serial;
+ int r;
+
+ assert(keyname);
+ assert(ret);
+
+ if (!(flags & ASK_PASSWORD_ACCEPT_CACHED))
+ return -EUNATCH;
+
+ r = lookup_key(keyname, &serial);
+ if (r == -ENOSYS) /* when retrieving the distinction doesn't matter */
+ return -ENOKEY;
+ if (r < 0)
+ return r;
+
+ return retrieve_key(serial, ret);
+}
+
+static void backspace_chars(int ttyfd, size_t p) {
+
+ if (ttyfd < 0)
+ return;
+
+ while (p > 0) {
+ p--;
+
+ loop_write(ttyfd, "\b \b", 3, false);
+ }
+}
+
+int ask_password_tty(
+ const char *message,
+ const char *keyname,
+ usec_t until,
+ AskPasswordFlags flags,
+ const char *flag_file,
+ char **ret) {
+
+ struct termios old_termios, new_termios;
+ char passphrase[LINE_MAX + 1] = {}, *x;
+ size_t p = 0, codepoint = 0;
+ int r;
+ _cleanup_close_ int ttyfd = -1, notify = -1;
+ struct pollfd pollfd[2];
+ bool reset_tty = false;
+ bool dirty = false;
+ enum {
+ POLL_TTY,
+ POLL_INOTIFY
+ };
+
+ assert(ret);
+
+ if (flags & ASK_PASSWORD_NO_TTY)
+ return -EUNATCH;
+
+ if (!message)
+ message = "Password:";
+
+ if (flag_file) {
+ notify = inotify_init1(IN_CLOEXEC|IN_NONBLOCK);
+ if (notify < 0) {
+ r = -errno;
+ goto finish;
+ }
+
+ if (inotify_add_watch(notify, flag_file, IN_ATTRIB /* for the link count */) < 0) {
+ r = -errno;
+ goto finish;
+ }
+ }
+
+ ttyfd = open("/dev/tty", O_RDWR|O_NOCTTY|O_CLOEXEC);
+ if (ttyfd >= 0) {
+
+ if (tcgetattr(ttyfd, &old_termios) < 0) {
+ r = -errno;
+ goto finish;
+ }
+
+ loop_write(ttyfd, ANSI_HIGHLIGHT, strlen(ANSI_HIGHLIGHT), false);
+ loop_write(ttyfd, message, strlen(message), false);
+ loop_write(ttyfd, " ", 1, false);
+ loop_write(ttyfd, ANSI_NORMAL, strlen(ANSI_NORMAL), false);
+
+ new_termios = old_termios;
+ new_termios.c_lflag &= ~(ICANON|ECHO);
+ new_termios.c_cc[VMIN] = 1;
+ new_termios.c_cc[VTIME] = 0;
+
+ if (tcsetattr(ttyfd, TCSADRAIN, &new_termios) < 0) {
+ r = -errno;
+ goto finish;
+ }
+
+ reset_tty = true;
+ }
+
+ zero(pollfd);
+ pollfd[POLL_TTY].fd = ttyfd >= 0 ? ttyfd : STDIN_FILENO;
+ pollfd[POLL_TTY].events = POLLIN;
+ pollfd[POLL_INOTIFY].fd = notify;
+ pollfd[POLL_INOTIFY].events = POLLIN;
+
+ for (;;) {
+ char c;
+ int sleep_for = -1, k;
+ ssize_t n;
+
+ if (until > 0) {
+ usec_t y;
+
+ y = now(CLOCK_MONOTONIC);
+
+ if (y > until) {
+ r = -ETIME;
+ goto finish;
+ }
+
+ sleep_for = (int) ((until - y) / USEC_PER_MSEC);
+ }
+
+ if (flag_file)
+ if (access(flag_file, F_OK) < 0) {
+ r = -errno;
+ goto finish;
+ }
+
+ k = poll(pollfd, notify >= 0 ? 2 : 1, sleep_for);
+ if (k < 0) {
+ if (errno == EINTR)
+ continue;
+
+ r = -errno;
+ goto finish;
+ } else if (k == 0) {
+ r = -ETIME;
+ goto finish;
+ }
+
+ if (notify >= 0 && pollfd[POLL_INOTIFY].revents != 0)
+ flush_fd(notify);
+
+ if (pollfd[POLL_TTY].revents == 0)
+ continue;
+
+ n = read(ttyfd >= 0 ? ttyfd : STDIN_FILENO, &c, 1);
+ if (n < 0) {
+ if (errno == EINTR || errno == EAGAIN)
+ continue;
+
+ r = -errno;
+ goto finish;
+
+ } else if (n == 0)
+ break;
+
+ if (c == '\n')
+ break;
+ else if (c == 21) { /* C-u */
+
+ if (!(flags & ASK_PASSWORD_SILENT))
+ backspace_chars(ttyfd, p);
+ p = 0;
+
+ } else if (c == '\b' || c == 127) {
+
+ if (p > 0) {
+
+ if (!(flags & ASK_PASSWORD_SILENT))
+ backspace_chars(ttyfd, 1);
+
+ p--;
+ } else if (!dirty && !(flags & ASK_PASSWORD_SILENT)) {
+
+ flags |= ASK_PASSWORD_SILENT;
+
+ /* There are two ways to enter silent
+ * mode. Either by pressing backspace
+ * as first key (and only as first
+ * key), or ... */
+ if (ttyfd >= 0)
+ loop_write(ttyfd, "(no echo) ", 10, false);
+
+ } else if (ttyfd >= 0)
+ loop_write(ttyfd, "\a", 1, false);
+
+ } else if (c == '\t' && !(flags & ASK_PASSWORD_SILENT)) {
+
+ backspace_chars(ttyfd, p);
+ flags |= ASK_PASSWORD_SILENT;
+
+ /* ... or by pressing TAB at any time. */
+
+ if (ttyfd >= 0)
+ loop_write(ttyfd, "(no echo) ", 10, false);
+ } else {
+ if (p >= sizeof(passphrase)-1) {
+ loop_write(ttyfd, "\a", 1, false);
+ continue;
+ }
+
+ passphrase[p++] = c;
+
+ if (!(flags & ASK_PASSWORD_SILENT) && ttyfd >= 0) {
+ n = utf8_encoded_valid_unichar(passphrase + codepoint);
+ if (n >= 0) {
+ codepoint = p;
+ loop_write(ttyfd, (flags & ASK_PASSWORD_ECHO) ? &c : "*", 1, false);
+ }
+ }
+
+ dirty = true;
+ }
+
+ c = 'x';
+ }
+
+ x = strndup(passphrase, p);
+ memory_erase(passphrase, p);
+ if (!x) {
+ r = -ENOMEM;
+ goto finish;
+ }
+
+ if (keyname)
+ (void) add_to_keyring_and_log(keyname, flags, STRV_MAKE(x));
+
+ *ret = x;
+ r = 0;
+
+finish:
+ if (ttyfd >= 0 && reset_tty) {
+ loop_write(ttyfd, "\n", 1, false);
+ tcsetattr(ttyfd, TCSADRAIN, &old_termios);
+ }
+
+ return r;
+}
+
+static int create_socket(char **name) {
+ union sockaddr_union sa = {
+ .un.sun_family = AF_UNIX,
+ };
+ _cleanup_close_ int fd = -1;
+ static const int one = 1;
+ char *c;
+ int r;
+
+ assert(name);
+
+ fd = socket(AF_UNIX, SOCK_DGRAM|SOCK_CLOEXEC|SOCK_NONBLOCK, 0);
+ if (fd < 0)
+ return -errno;
+
+ snprintf(sa.un.sun_path, sizeof(sa.un.sun_path)-1, "/run/systemd/ask-password/sck.%" PRIx64, random_u64());
+
+ RUN_WITH_UMASK(0177) {
+ if (bind(fd, &sa.sa, offsetof(struct sockaddr_un, sun_path) + strlen(sa.un.sun_path)) < 0)
+ return -errno;
+ }
+
+ if (setsockopt(fd, SOL_SOCKET, SO_PASSCRED, &one, sizeof(one)) < 0)
+ return -errno;
+
+ c = strdup(sa.un.sun_path);
+ if (!c)
+ return -ENOMEM;
+
+ *name = c;
+
+ r = fd;
+ fd = -1;
+
+ return r;
+}
+
+int ask_password_agent(
+ const char *message,
+ const char *icon,
+ const char *id,
+ const char *keyname,
+ usec_t until,
+ AskPasswordFlags flags,
+ char ***ret) {
+
+ enum {
+ FD_SOCKET,
+ FD_SIGNAL,
+ _FD_MAX
+ };
+
+ _cleanup_close_ int socket_fd = -1, signal_fd = -1, fd = -1;
+ char temp[] = "/run/systemd/ask-password/tmp.XXXXXX";
+ char final[sizeof(temp)] = "";
+ _cleanup_free_ char *socket_name = NULL;
+ _cleanup_strv_free_ char **l = NULL;
+ _cleanup_fclose_ FILE *f = NULL;
+ struct pollfd pollfd[_FD_MAX];
+ sigset_t mask, oldmask;
+ int r;
+
+ assert(ret);
+
+ if (flags & ASK_PASSWORD_NO_AGENT)
+ return -EUNATCH;
+
+ assert_se(sigemptyset(&mask) >= 0);
+ assert_se(sigset_add_many(&mask, SIGINT, SIGTERM, -1) >= 0);
+ assert_se(sigprocmask(SIG_BLOCK, &mask, &oldmask) >= 0);
+
+ (void) mkdir_p_label("/run/systemd/ask-password", 0755);
+
+ fd = mkostemp_safe(temp, O_WRONLY|O_CLOEXEC);
+ if (fd < 0) {
+ r = fd;
+ goto finish;
+ }
+
+ (void) fchmod(fd, 0644);
+
+ f = fdopen(fd, "w");
+ if (!f) {
+ r = -errno;
+ goto finish;
+ }
+
+ fd = -1;
+
+ signal_fd = signalfd(-1, &mask, SFD_NONBLOCK|SFD_CLOEXEC);
+ if (signal_fd < 0) {
+ r = -errno;
+ goto finish;
+ }
+
+ socket_fd = create_socket(&socket_name);
+ if (socket_fd < 0) {
+ r = socket_fd;
+ goto finish;
+ }
+
+ fprintf(f,
+ "[Ask]\n"
+ "PID="PID_FMT"\n"
+ "Socket=%s\n"
+ "AcceptCached=%i\n"
+ "Echo=%i\n"
+ "NotAfter="USEC_FMT"\n",
+ getpid(),
+ socket_name,
+ (flags & ASK_PASSWORD_ACCEPT_CACHED) ? 1 : 0,
+ (flags & ASK_PASSWORD_ECHO) ? 1 : 0,
+ until);
+
+ if (message)
+ fprintf(f, "Message=%s\n", message);
+
+ if (icon)
+ fprintf(f, "Icon=%s\n", icon);
+
+ if (id)
+ fprintf(f, "Id=%s\n", id);
+
+ r = fflush_and_check(f);
+ if (r < 0)
+ goto finish;
+
+ memcpy(final, temp, sizeof(temp));
+
+ final[sizeof(final)-11] = 'a';
+ final[sizeof(final)-10] = 's';
+ final[sizeof(final)-9] = 'k';
+
+ if (rename(temp, final) < 0) {
+ r = -errno;
+ goto finish;
+ }
+
+ zero(pollfd);
+ pollfd[FD_SOCKET].fd = socket_fd;
+ pollfd[FD_SOCKET].events = POLLIN;
+ pollfd[FD_SIGNAL].fd = signal_fd;
+ pollfd[FD_SIGNAL].events = POLLIN;
+
+ for (;;) {
+ char passphrase[LINE_MAX+1];
+ struct msghdr msghdr;
+ struct iovec iovec;
+ struct ucred *ucred;
+ union {
+ struct cmsghdr cmsghdr;
+ uint8_t buf[CMSG_SPACE(sizeof(struct ucred))];
+ } control;
+ ssize_t n;
+ int k;
+ usec_t t;
+
+ t = now(CLOCK_MONOTONIC);
+
+ if (until > 0 && until <= t) {
+ r = -ETIME;
+ goto finish;
+ }
+
+ k = poll(pollfd, _FD_MAX, until > 0 ? (int) ((until-t)/USEC_PER_MSEC) : -1);
+ if (k < 0) {
+ if (errno == EINTR)
+ continue;
+
+ r = -errno;
+ goto finish;
+ }
+
+ if (k <= 0) {
+ r = -ETIME;
+ goto finish;
+ }
+
+ if (pollfd[FD_SIGNAL].revents & POLLIN) {
+ r = -EINTR;
+ goto finish;
+ }
+
+ if (pollfd[FD_SOCKET].revents != POLLIN) {
+ r = -EIO;
+ goto finish;
+ }
+
+ zero(iovec);
+ iovec.iov_base = passphrase;
+ iovec.iov_len = sizeof(passphrase);
+
+ zero(control);
+ zero(msghdr);
+ msghdr.msg_iov = &iovec;
+ msghdr.msg_iovlen = 1;
+ msghdr.msg_control = &control;
+ msghdr.msg_controllen = sizeof(control);
+
+ n = recvmsg(socket_fd, &msghdr, 0);
+ if (n < 0) {
+ if (errno == EAGAIN ||
+ errno == EINTR)
+ continue;
+
+ r = -errno;
+ goto finish;
+ }
+
+ cmsg_close_all(&msghdr);
+
+ if (n <= 0) {
+ log_debug("Message too short");
+ continue;
+ }
+
+ if (msghdr.msg_controllen < CMSG_LEN(sizeof(struct ucred)) ||
+ control.cmsghdr.cmsg_level != SOL_SOCKET ||
+ control.cmsghdr.cmsg_type != SCM_CREDENTIALS ||
+ control.cmsghdr.cmsg_len != CMSG_LEN(sizeof(struct ucred))) {
+ log_debug("Received message without credentials. Ignoring.");
+ continue;
+ }
+
+ ucred = (struct ucred*) CMSG_DATA(&control.cmsghdr);
+ if (ucred->uid != 0) {
+ log_debug("Got request from unprivileged user. Ignoring.");
+ continue;
+ }
+
+ if (passphrase[0] == '+') {
+ /* An empty message refers to the empty password */
+ if (n == 1)
+ l = strv_new("", NULL);
+ else
+ l = strv_parse_nulstr(passphrase+1, n-1);
+ memory_erase(passphrase, n);
+ if (!l) {
+ r = -ENOMEM;
+ goto finish;
+ }
+
+ if (strv_length(l) <= 0) {
+ l = strv_free(l);
+ log_debug("Invalid packet");
+ continue;
+ }
+
+ break;
+ }
+
+ if (passphrase[0] == '-') {
+ r = -ECANCELED;
+ goto finish;
+ }
+
+ log_debug("Invalid packet");
+ }
+
+ if (keyname)
+ (void) add_to_keyring_and_log(keyname, flags, l);
+
+ *ret = l;
+ l = NULL;
+ r = 0;
+
+finish:
+ if (socket_name)
+ (void) unlink(socket_name);
+
+ (void) unlink(temp);
+
+ if (final[0])
+ (void) unlink(final);
+
+ assert_se(sigprocmask(SIG_SETMASK, &oldmask, NULL) == 0);
+ return r;
+}
+
+int ask_password_auto(
+ const char *message,
+ const char *icon,
+ const char *id,
+ const char *keyname,
+ usec_t until,
+ AskPasswordFlags flags,
+ char ***ret) {
+
+ int r;
+
+ assert(ret);
+
+ if ((flags & ASK_PASSWORD_ACCEPT_CACHED) && keyname) {
+ r = ask_password_keyring(keyname, flags, ret);
+ if (r != -ENOKEY)
+ return r;
+ }
+
+ if (!(flags & ASK_PASSWORD_NO_TTY) && isatty(STDIN_FILENO)) {
+ char *s = NULL, **l = NULL;
+
+ r = ask_password_tty(message, keyname, until, flags, NULL, &s);
+ if (r < 0)
+ return r;
+
+ r = strv_push(&l, s);
+ if (r < 0) {
+ string_erase(s);
+ free(s);
+ return -ENOMEM;
+ }
+
+ *ret = l;
+ return 0;
+ }
+
+ if (!(flags & ASK_PASSWORD_NO_AGENT))
+ return ask_password_agent(message, icon, id, keyname, until, flags, ret);
+
+ return -EUNATCH;
+}
diff --git a/src/libshared/ask-password-api.h b/src/libshared/ask-password-api.h
new file mode 100644
index 0000000000..9d7f65130c
--- /dev/null
+++ b/src/libshared/ask-password-api.h
@@ -0,0 +1,38 @@
+#pragma once
+
+/***
+ This file is part of systemd.
+
+ Copyright 2010 Lennart Poettering
+
+ systemd is free software; you can redistribute it and/or modify it
+ under the terms of the GNU Lesser General Public License as published by
+ the Free Software Foundation; either version 2.1 of the License, or
+ (at your option) any later version.
+
+ systemd is distributed in the hope that it will be useful, but
+ WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ Lesser General Public License for more details.
+
+ You should have received a copy of the GNU Lesser General Public License
+ along with systemd; If not, see <http://www.gnu.org/licenses/>.
+***/
+
+#include <stdbool.h>
+
+#include "time-util.h"
+
+typedef enum AskPasswordFlags {
+ ASK_PASSWORD_ACCEPT_CACHED = 1,
+ ASK_PASSWORD_PUSH_CACHE = 2,
+ ASK_PASSWORD_ECHO = 4, /* show the password literally while reading, instead of "*" */
+ ASK_PASSWORD_SILENT = 8, /* do no show any password at all while reading */
+ ASK_PASSWORD_NO_TTY = 16,
+ ASK_PASSWORD_NO_AGENT = 32,
+} AskPasswordFlags;
+
+int ask_password_tty(const char *message, const char *keyname, usec_t until, AskPasswordFlags flags, const char *flag_file, char **ret);
+int ask_password_agent(const char *message, const char *icon, const char *id, const char *keyname, usec_t until, AskPasswordFlags flag, char ***ret);
+int ask_password_keyring(const char *keyname, AskPasswordFlags flags, char ***ret);
+int ask_password_auto(const char *message, const char *icon, const char *id, const char *keyname, usec_t until, AskPasswordFlags flag, char ***ret);
diff --git a/src/libshared/base-filesystem.c b/src/libshared/base-filesystem.c
new file mode 100644
index 0000000000..59a34a9d11
--- /dev/null
+++ b/src/libshared/base-filesystem.c
@@ -0,0 +1,129 @@
+/***
+ This file is part of systemd.
+
+ Copyright 2014 Kay Sievers
+
+ systemd is free software; you can redistribute it and/or modify it
+ under the terms of the GNU Lesser General Public License as published by
+ the Free Software Foundation; either version 2.1 of the License, or
+ (at your option) any later version.
+
+ systemd is distributed in the hope that it will be useful, but
+ WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ Lesser General Public License for more details.
+
+ You should have received a copy of the GNU Lesser General Public License
+ along with systemd; If not, see <http://www.gnu.org/licenses/>.
+***/
+
+#include <errno.h>
+#include <fcntl.h>
+#include <stdbool.h>
+#include <stdlib.h>
+#include <sys/stat.h>
+#include <syslog.h>
+#include <unistd.h>
+
+#include "alloc-util.h"
+#include "base-filesystem.h"
+#include "fd-util.h"
+#include "log.h"
+#include "macro.h"
+#include "string-util.h"
+#include "umask-util.h"
+#include "user-util.h"
+#include "util.h"
+
+typedef struct BaseFilesystem {
+ const char *dir;
+ mode_t mode;
+ const char *target;
+ const char *exists;
+ bool ignore_failure;
+} BaseFilesystem;
+
+static const BaseFilesystem table[] = {
+ { "bin", 0, "usr/bin\0", NULL },
+ { "lib", 0, "usr/lib\0", NULL },
+ { "root", 0755, NULL, NULL, true },
+ { "sbin", 0, "usr/sbin\0", NULL },
+ { "usr", 0755, NULL, NULL },
+ { "var", 0755, NULL, NULL },
+ { "etc", 0755, NULL, NULL },
+#if defined(__i386__) || defined(__x86_64__)
+ { "lib64", 0, "usr/lib/x86_64-linux-gnu\0"
+ "usr/lib64\0", "ld-linux-x86-64.so.2" },
+#endif
+};
+
+int base_filesystem_create(const char *root, uid_t uid, gid_t gid) {
+ _cleanup_close_ int fd = -1;
+ unsigned i;
+ int r = 0;
+
+ fd = open(root, O_RDONLY|O_NONBLOCK|O_DIRECTORY|O_CLOEXEC|O_NOFOLLOW);
+ if (fd < 0)
+ return log_error_errno(errno, "Failed to open root file system: %m");
+
+ for (i = 0; i < ELEMENTSOF(table); i ++) {
+ if (faccessat(fd, table[i].dir, F_OK, AT_SYMLINK_NOFOLLOW) >= 0)
+ continue;
+
+ if (table[i].target) {
+ const char *target = NULL, *s;
+
+ /* check if one of the targets exists */
+ NULSTR_FOREACH(s, table[i].target) {
+ if (faccessat(fd, s, F_OK, AT_SYMLINK_NOFOLLOW) < 0)
+ continue;
+
+ /* check if a specific file exists at the target path */
+ if (table[i].exists) {
+ _cleanup_free_ char *p = NULL;
+
+ p = strjoin(s, "/", table[i].exists, NULL);
+ if (!p)
+ return log_oom();
+
+ if (faccessat(fd, p, F_OK, AT_SYMLINK_NOFOLLOW) < 0)
+ continue;
+ }
+
+ target = s;
+ break;
+ }
+
+ if (!target)
+ continue;
+
+ r = symlinkat(target, fd, table[i].dir);
+ if (r < 0 && errno != EEXIST)
+ return log_error_errno(errno, "Failed to create symlink at %s/%s: %m", root, table[i].dir);
+
+ if (uid != UID_INVALID || gid != UID_INVALID) {
+ if (fchownat(fd, table[i].dir, uid, gid, AT_SYMLINK_NOFOLLOW) < 0)
+ return log_error_errno(errno, "Failed to chown symlink at %s/%s: %m", root, table[i].dir);
+ }
+
+ continue;
+ }
+
+ RUN_WITH_UMASK(0000)
+ r = mkdirat(fd, table[i].dir, table[i].mode);
+ if (r < 0 && errno != EEXIST) {
+ log_full_errno(table[i].ignore_failure ? LOG_DEBUG : LOG_ERR, errno,
+ "Failed to create directory at %s/%s: %m", root, table[i].dir);
+
+ if (!table[i].ignore_failure)
+ return -errno;
+ }
+
+ if (uid != UID_INVALID || gid != UID_INVALID) {
+ if (fchownat(fd, table[i].dir, uid, gid, AT_SYMLINK_NOFOLLOW) < 0)
+ return log_error_errno(errno, "Failed to chown directory at %s/%s: %m", root, table[i].dir);
+ }
+ }
+
+ return 0;
+}
diff --git a/src/libshared/base-filesystem.h b/src/libshared/base-filesystem.h
new file mode 100644
index 0000000000..49599f0a60
--- /dev/null
+++ b/src/libshared/base-filesystem.h
@@ -0,0 +1,24 @@
+#pragma once
+
+/***
+ This file is part of systemd.
+
+ Copyright 2014 Kay Sievers
+
+ systemd is free software; you can redistribute it and/or modify it
+ under the terms of the GNU Lesser General Public License as published by
+ the Free Software Foundation; either version 2.1 of the License, or
+ (at your option) any later version.
+
+ systemd is distributed in the hope that it will be useful, but
+ WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ Lesser General Public License for more details.
+
+ You should have received a copy of the GNU Lesser General Public License
+ along with systemd; If not, see <http://www.gnu.org/licenses/>.
+***/
+
+#include <sys/types.h>
+
+int base_filesystem_create(const char *root, uid_t uid, gid_t gid);
diff --git a/src/libshared/boot-timestamps.c b/src/libshared/boot-timestamps.c
new file mode 100644
index 0000000000..7e0152761c
--- /dev/null
+++ b/src/libshared/boot-timestamps.c
@@ -0,0 +1,64 @@
+/***
+ This file is part of systemd.
+
+ Copyright 2012 Lennart Poettering
+ Copyright 2013 Kay Sievers
+
+ systemd is free software; you can redistribute it and/or modify it
+ under the terms of the GNU Lesser General Public License as published by
+ the Free Software Foundation; either version 2.1 of the License, or
+ (at your option) any later version.
+
+ systemd is distributed in the hope that it will be useful, but
+ WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ Lesser General Public License for more details.
+
+ You should have received a copy of the GNU Lesser General Public License
+ along with systemd; If not, see <http://www.gnu.org/licenses/>.
+***/
+
+#include "acpi-fpdt.h"
+#include "boot-timestamps.h"
+#include "efivars.h"
+#include "macro.h"
+#include "time-util.h"
+
+int boot_timestamps(const dual_timestamp *n, dual_timestamp *firmware, dual_timestamp *loader) {
+ usec_t x = 0, y = 0, a;
+ int r;
+ dual_timestamp _n;
+
+ assert(firmware);
+ assert(loader);
+
+ if (!n) {
+ dual_timestamp_get(&_n);
+ n = &_n;
+ }
+
+ r = acpi_get_boot_usec(&x, &y);
+ if (r < 0) {
+ r = efi_loader_get_boot_usec(&x, &y);
+ if (r < 0)
+ return r;
+ }
+
+ /* Let's convert this to timestamps where the firmware
+ * began/loader began working. To make this more confusing:
+ * since usec_t is unsigned and the kernel's monotonic clock
+ * begins at kernel initialization we'll actually initialize
+ * the monotonic timestamps here as negative of the actual
+ * value. */
+
+ firmware->monotonic = y;
+ loader->monotonic = y - x;
+
+ a = n->monotonic + firmware->monotonic;
+ firmware->realtime = n->realtime > a ? n->realtime - a : 0;
+
+ a = n->monotonic + loader->monotonic;
+ loader->realtime = n->realtime > a ? n->realtime - a : 0;
+
+ return 0;
+}
diff --git a/src/libshared/boot-timestamps.h b/src/libshared/boot-timestamps.h
new file mode 100644
index 0000000000..6f691026be
--- /dev/null
+++ b/src/libshared/boot-timestamps.h
@@ -0,0 +1,25 @@
+#pragma once
+
+/***
+ This file is part of systemd.
+
+ Copyright 2012 Lennart Poettering
+ Copyright 2013 Kay Sievers
+
+ systemd is free software; you can redistribute it and/or modify it
+ under the terms of the GNU Lesser General Public License as published by
+ the Free Software Foundation; either version 2.1 of the License, or
+ (at your option) any later version.
+
+ systemd is distributed in the hope that it will be useful, but
+ WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ Lesser General Public License for more details.
+
+ You should have received a copy of the GNU Lesser General Public License
+ along with systemd; If not, see <http://www.gnu.org/licenses/>.
+***/
+
+#include <time-util.h>
+
+int boot_timestamps(const dual_timestamp *n, dual_timestamp *firmware, dual_timestamp *loader);
diff --git a/src/libshared/bus-util.c b/src/libshared/bus-util.c
new file mode 100644
index 0000000000..a874bb84cc
--- /dev/null
+++ b/src/libshared/bus-util.c
@@ -0,0 +1,2412 @@
+/***
+ This file is part of systemd.
+
+ Copyright 2013 Lennart Poettering
+
+ systemd is free software; you can redistribute it and/or modify it
+ under the terms of the GNU Lesser General Public License as published by
+ the Free Software Foundation; either version 2.1 of the License, or
+ (at your option) any later version.
+
+ systemd is distributed in the hope that it will be useful, but
+ WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ Lesser General Public License for more details.
+
+ You should have received a copy of the GNU Lesser General Public License
+ along with systemd; If not, see <http://www.gnu.org/licenses/>.
+***/
+
+#include <errno.h>
+#include <fcntl.h>
+#include <inttypes.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <sys/ioctl.h>
+#include <sys/resource.h>
+#include <sys/socket.h>
+#include <unistd.h>
+
+#include <systemd/sd-bus-protocol.h>
+#include <systemd/sd-bus.h>
+#include <systemd/sd-daemon.h>
+#include <systemd/sd-event.h>
+#include <systemd/sd-id128.h>
+
+#include "alloc-util.h"
+#include "bus-internal.h"
+#include "bus-label.h"
+#include "bus-message.h"
+#include "bus-util.h"
+#include "cgroup-util.h"
+#include "def.h"
+#include "env-util.h"
+#include "escape.h"
+#include "extract-word.h"
+#include "fd-util.h"
+#include "hashmap.h"
+#include "install.h"
+#include "kdbus.h"
+#include "log.h"
+#include "macro.h"
+#include "missing.h"
+#include "parse-util.h"
+#include "path-util.h"
+#include "proc-cmdline.h"
+#include "process-util.h"
+#include "rlimit-util.h"
+#include "set.h"
+#include "signal-util.h"
+#include "stdio-util.h"
+#include "string-util.h"
+#include "strv.h"
+#include "syslog-util.h"
+#include "time-util.h"
+#include "unit-name.h"
+#include "user-util.h"
+#include "utf8.h"
+#include "util.h"
+
+static int name_owner_change_callback(sd_bus_message *m, void *userdata, sd_bus_error *ret_error) {
+ sd_event *e = userdata;
+
+ assert(m);
+ assert(e);
+
+ sd_bus_close(sd_bus_message_get_bus(m));
+ sd_event_exit(e, 0);
+
+ return 1;
+}
+
+int bus_async_unregister_and_exit(sd_event *e, sd_bus *bus, const char *name) {
+ _cleanup_free_ char *match = NULL;
+ const char *unique;
+ int r;
+
+ assert(e);
+ assert(bus);
+ assert(name);
+
+ /* We unregister the name here and then wait for the
+ * NameOwnerChanged signal for this event to arrive before we
+ * quit. We do this in order to make sure that any queued
+ * requests are still processed before we really exit. */
+
+ r = sd_bus_get_unique_name(bus, &unique);
+ if (r < 0)
+ return r;
+
+ r = asprintf(&match,
+ "sender='org.freedesktop.DBus',"
+ "type='signal',"
+ "interface='org.freedesktop.DBus',"
+ "member='NameOwnerChanged',"
+ "path='/org/freedesktop/DBus',"
+ "arg0='%s',"
+ "arg1='%s',"
+ "arg2=''", name, unique);
+ if (r < 0)
+ return -ENOMEM;
+
+ r = sd_bus_add_match(bus, NULL, match, name_owner_change_callback, e);
+ if (r < 0)
+ return r;
+
+ r = sd_bus_release_name(bus, name);
+ if (r < 0)
+ return r;
+
+ return 0;
+}
+
+int bus_event_loop_with_idle(
+ sd_event *e,
+ sd_bus *bus,
+ const char *name,
+ usec_t timeout,
+ check_idle_t check_idle,
+ void *userdata) {
+ bool exiting = false;
+ int r, code;
+
+ assert(e);
+ assert(bus);
+ assert(name);
+
+ for (;;) {
+ bool idle;
+
+ r = sd_event_get_state(e);
+ if (r < 0)
+ return r;
+ if (r == SD_EVENT_FINISHED)
+ break;
+
+ if (check_idle)
+ idle = check_idle(userdata);
+ else
+ idle = true;
+
+ r = sd_event_run(e, exiting || !idle ? (uint64_t) -1 : timeout);
+ if (r < 0)
+ return r;
+
+ if (r == 0 && !exiting && idle) {
+
+ r = sd_bus_try_close(bus);
+ if (r == -EBUSY)
+ continue;
+
+ /* Fallback for dbus1 connections: we
+ * unregister the name and wait for the
+ * response to come through for it */
+ if (r == -EOPNOTSUPP) {
+
+ /* Inform the service manager that we
+ * are going down, so that it will
+ * queue all further start requests,
+ * instead of assuming we are already
+ * running. */
+ sd_notify(false, "STOPPING=1");
+
+ r = bus_async_unregister_and_exit(e, bus, name);
+ if (r < 0)
+ return r;
+
+ exiting = true;
+ continue;
+ }
+
+ if (r < 0)
+ return r;
+
+ sd_event_exit(e, 0);
+ break;
+ }
+ }
+
+ r = sd_event_get_exit_code(e, &code);
+ if (r < 0)
+ return r;
+
+ return code;
+}
+
+int bus_name_has_owner(sd_bus *c, const char *name, sd_bus_error *error) {
+ _cleanup_(sd_bus_message_unrefp) sd_bus_message *rep = NULL;
+ int r, has_owner = 0;
+
+ assert(c);
+ assert(name);
+
+ r = sd_bus_call_method(c,
+ "org.freedesktop.DBus",
+ "/org/freedesktop/dbus",
+ "org.freedesktop.DBus",
+ "NameHasOwner",
+ error,
+ &rep,
+ "s",
+ name);
+ if (r < 0)
+ return r;
+
+ r = sd_bus_message_read_basic(rep, 'b', &has_owner);
+ if (r < 0)
+ return sd_bus_error_set_errno(error, r);
+
+ return has_owner;
+}
+
+static int check_good_user(sd_bus_message *m, uid_t good_user) {
+ _cleanup_(sd_bus_creds_unrefp) sd_bus_creds *creds = NULL;
+ uid_t sender_uid;
+ int r;
+
+ assert(m);
+
+ if (good_user == UID_INVALID)
+ return 0;
+
+ r = sd_bus_query_sender_creds(m, SD_BUS_CREDS_EUID, &creds);
+ if (r < 0)
+ return r;
+
+ /* Don't trust augmented credentials for authorization */
+ assert_return((sd_bus_creds_get_augmented_mask(creds) & SD_BUS_CREDS_EUID) == 0, -EPERM);
+
+ r = sd_bus_creds_get_euid(creds, &sender_uid);
+ if (r < 0)
+ return r;
+
+ return sender_uid == good_user;
+}
+
+int bus_test_polkit(
+ sd_bus_message *call,
+ int capability,
+ const char *action,
+ const char **details,
+ uid_t good_user,
+ bool *_challenge,
+ sd_bus_error *e) {
+
+ int r;
+
+ assert(call);
+ assert(action);
+
+ /* Tests non-interactively! */
+
+ r = check_good_user(call, good_user);
+ if (r != 0)
+ return r;
+
+ r = sd_bus_query_sender_privilege(call, capability);
+ if (r < 0)
+ return r;
+ else if (r > 0)
+ return 1;
+#ifdef ENABLE_POLKIT
+ else {
+ _cleanup_(sd_bus_message_unrefp) sd_bus_message *request = NULL;
+ _cleanup_(sd_bus_message_unrefp) sd_bus_message *reply = NULL;
+ int authorized = false, challenge = false;
+ const char *sender, **k, **v;
+
+ sender = sd_bus_message_get_sender(call);
+ if (!sender)
+ return -EBADMSG;
+
+ r = sd_bus_message_new_method_call(
+ call->bus,
+ &request,
+ "org.freedesktop.PolicyKit1",
+ "/org/freedesktop/PolicyKit1/Authority",
+ "org.freedesktop.PolicyKit1.Authority",
+ "CheckAuthorization");
+ if (r < 0)
+ return r;
+
+ r = sd_bus_message_append(
+ request,
+ "(sa{sv})s",
+ "system-bus-name", 1, "name", "s", sender,
+ action);
+ if (r < 0)
+ return r;
+
+ r = sd_bus_message_open_container(request, 'a', "{ss}");
+ if (r < 0)
+ return r;
+
+ STRV_FOREACH_PAIR(k, v, details) {
+ r = sd_bus_message_append(request, "{ss}", *k, *v);
+ if (r < 0)
+ return r;
+ }
+
+ r = sd_bus_message_close_container(request);
+ if (r < 0)
+ return r;
+
+ r = sd_bus_message_append(request, "us", 0, NULL);
+ if (r < 0)
+ return r;
+
+ r = sd_bus_call(call->bus, request, 0, e, &reply);
+ if (r < 0) {
+ /* Treat no PK available as access denied */
+ if (sd_bus_error_has_name(e, SD_BUS_ERROR_SERVICE_UNKNOWN)) {
+ sd_bus_error_free(e);
+ return -EACCES;
+ }
+
+ return r;
+ }
+
+ r = sd_bus_message_enter_container(reply, 'r', "bba{ss}");
+ if (r < 0)
+ return r;
+
+ r = sd_bus_message_read(reply, "bb", &authorized, &challenge);
+ if (r < 0)
+ return r;
+
+ if (authorized)
+ return 1;
+
+ if (_challenge) {
+ *_challenge = challenge;
+ return 0;
+ }
+ }
+#endif
+
+ return -EACCES;
+}
+
+#ifdef ENABLE_POLKIT
+
+typedef struct AsyncPolkitQuery {
+ sd_bus_message *request, *reply;
+ sd_bus_message_handler_t callback;
+ void *userdata;
+ sd_bus_slot *slot;
+ Hashmap *registry;
+} AsyncPolkitQuery;
+
+static void async_polkit_query_free(AsyncPolkitQuery *q) {
+
+ if (!q)
+ return;
+
+ sd_bus_slot_unref(q->slot);
+
+ if (q->registry && q->request)
+ hashmap_remove(q->registry, q->request);
+
+ sd_bus_message_unref(q->request);
+ sd_bus_message_unref(q->reply);
+
+ free(q);
+}
+
+static int async_polkit_callback(sd_bus_message *reply, void *userdata, sd_bus_error *error) {
+ _cleanup_(sd_bus_error_free) sd_bus_error error_buffer = SD_BUS_ERROR_NULL;
+ AsyncPolkitQuery *q = userdata;
+ int r;
+
+ assert(reply);
+ assert(q);
+
+ q->slot = sd_bus_slot_unref(q->slot);
+ q->reply = sd_bus_message_ref(reply);
+
+ r = sd_bus_message_rewind(q->request, true);
+ if (r < 0) {
+ r = sd_bus_reply_method_errno(q->request, r, NULL);
+ goto finish;
+ }
+
+ r = q->callback(q->request, q->userdata, &error_buffer);
+ r = bus_maybe_reply_error(q->request, r, &error_buffer);
+
+finish:
+ async_polkit_query_free(q);
+
+ return r;
+}
+
+#endif
+
+int bus_verify_polkit_async(
+ sd_bus_message *call,
+ int capability,
+ const char *action,
+ const char **details,
+ bool interactive,
+ uid_t good_user,
+ Hashmap **registry,
+ sd_bus_error *error) {
+
+#ifdef ENABLE_POLKIT
+ _cleanup_(sd_bus_message_unrefp) sd_bus_message *pk = NULL;
+ AsyncPolkitQuery *q;
+ const char *sender, **k, **v;
+ sd_bus_message_handler_t callback;
+ void *userdata;
+ int c;
+#endif
+ int r;
+
+ assert(call);
+ assert(action);
+ assert(registry);
+
+ r = check_good_user(call, good_user);
+ if (r != 0)
+ return r;
+
+#ifdef ENABLE_POLKIT
+ q = hashmap_get(*registry, call);
+ if (q) {
+ int authorized, challenge;
+
+ /* This is the second invocation of this function, and
+ * there's already a response from polkit, let's
+ * process it */
+ assert(q->reply);
+
+ if (sd_bus_message_is_method_error(q->reply, NULL)) {
+ const sd_bus_error *e;
+
+ /* Copy error from polkit reply */
+ e = sd_bus_message_get_error(q->reply);
+ sd_bus_error_copy(error, e);
+
+ /* Treat no PK available as access denied */
+ if (sd_bus_error_has_name(e, SD_BUS_ERROR_SERVICE_UNKNOWN))
+ return -EACCES;
+
+ return -sd_bus_error_get_errno(e);
+ }
+
+ r = sd_bus_message_enter_container(q->reply, 'r', "bba{ss}");
+ if (r >= 0)
+ r = sd_bus_message_read(q->reply, "bb", &authorized, &challenge);
+
+ if (r < 0)
+ return r;
+
+ if (authorized)
+ return 1;
+
+ if (challenge)
+ return sd_bus_error_set(error, SD_BUS_ERROR_INTERACTIVE_AUTHORIZATION_REQUIRED, "Interactive authentication required.");
+
+ return -EACCES;
+ }
+#endif
+
+ r = sd_bus_query_sender_privilege(call, capability);
+ if (r < 0)
+ return r;
+ else if (r > 0)
+ return 1;
+
+#ifdef ENABLE_POLKIT
+ if (sd_bus_get_current_message(call->bus) != call)
+ return -EINVAL;
+
+ callback = sd_bus_get_current_handler(call->bus);
+ if (!callback)
+ return -EINVAL;
+
+ userdata = sd_bus_get_current_userdata(call->bus);
+
+ sender = sd_bus_message_get_sender(call);
+ if (!sender)
+ return -EBADMSG;
+
+ c = sd_bus_message_get_allow_interactive_authorization(call);
+ if (c < 0)
+ return c;
+ if (c > 0)
+ interactive = true;
+
+ r = hashmap_ensure_allocated(registry, NULL);
+ if (r < 0)
+ return r;
+
+ r = sd_bus_message_new_method_call(
+ call->bus,
+ &pk,
+ "org.freedesktop.PolicyKit1",
+ "/org/freedesktop/PolicyKit1/Authority",
+ "org.freedesktop.PolicyKit1.Authority",
+ "CheckAuthorization");
+ if (r < 0)
+ return r;
+
+ r = sd_bus_message_append(
+ pk,
+ "(sa{sv})s",
+ "system-bus-name", 1, "name", "s", sender,
+ action);
+ if (r < 0)
+ return r;
+
+ r = sd_bus_message_open_container(pk, 'a', "{ss}");
+ if (r < 0)
+ return r;
+
+ STRV_FOREACH_PAIR(k, v, details) {
+ r = sd_bus_message_append(pk, "{ss}", *k, *v);
+ if (r < 0)
+ return r;
+ }
+
+ r = sd_bus_message_close_container(pk);
+ if (r < 0)
+ return r;
+
+ r = sd_bus_message_append(pk, "us", !!interactive, NULL);
+ if (r < 0)
+ return r;
+
+ q = new0(AsyncPolkitQuery, 1);
+ if (!q)
+ return -ENOMEM;
+
+ q->request = sd_bus_message_ref(call);
+ q->callback = callback;
+ q->userdata = userdata;
+
+ r = hashmap_put(*registry, call, q);
+ if (r < 0) {
+ async_polkit_query_free(q);
+ return r;
+ }
+
+ q->registry = *registry;
+
+ r = sd_bus_call_async(call->bus, &q->slot, pk, async_polkit_callback, q, 0);
+ if (r < 0) {
+ async_polkit_query_free(q);
+ return r;
+ }
+
+ return 0;
+#endif
+
+ return -EACCES;
+}
+
+void bus_verify_polkit_async_registry_free(Hashmap *registry) {
+#ifdef ENABLE_POLKIT
+ AsyncPolkitQuery *q;
+
+ while ((q = hashmap_steal_first(registry)))
+ async_polkit_query_free(q);
+
+ hashmap_free(registry);
+#endif
+}
+
+int bus_check_peercred(sd_bus *c) {
+ struct ucred ucred;
+ socklen_t l;
+ int fd;
+
+ assert(c);
+
+ fd = sd_bus_get_fd(c);
+ if (fd < 0)
+ return fd;
+
+ l = sizeof(struct ucred);
+ if (getsockopt(fd, SOL_SOCKET, SO_PEERCRED, &ucred, &l) < 0)
+ return -errno;
+
+ if (l != sizeof(struct ucred))
+ return -E2BIG;
+
+ if (ucred.uid != 0 && ucred.uid != geteuid())
+ return -EPERM;
+
+ return 1;
+}
+
+int bus_connect_system_systemd(sd_bus **_bus) {
+ _cleanup_(sd_bus_unrefp) sd_bus *bus = NULL;
+ int r;
+
+ assert(_bus);
+
+ if (geteuid() != 0)
+ return sd_bus_default_system(_bus);
+
+ /* If we are root and kdbus is not available, then let's talk
+ * directly to the system instance, instead of going via the
+ * bus */
+
+ r = sd_bus_new(&bus);
+ if (r < 0)
+ return r;
+
+ r = sd_bus_set_address(bus, KERNEL_SYSTEM_BUS_ADDRESS);
+ if (r < 0)
+ return r;
+
+ bus->bus_client = true;
+
+ r = sd_bus_start(bus);
+ if (r >= 0) {
+ *_bus = bus;
+ bus = NULL;
+ return 0;
+ }
+
+ bus = sd_bus_unref(bus);
+
+ r = sd_bus_new(&bus);
+ if (r < 0)
+ return r;
+
+ r = sd_bus_set_address(bus, "unix:path=/run/systemd/private");
+ if (r < 0)
+ return r;
+
+ r = sd_bus_start(bus);
+ if (r < 0)
+ return sd_bus_default_system(_bus);
+
+ r = bus_check_peercred(bus);
+ if (r < 0)
+ return r;
+
+ *_bus = bus;
+ bus = NULL;
+
+ return 0;
+}
+
+int bus_connect_user_systemd(sd_bus **_bus) {
+ _cleanup_(sd_bus_unrefp) sd_bus *bus = NULL;
+ _cleanup_free_ char *ee = NULL;
+ const char *e;
+ int r;
+
+ /* Try via kdbus first, and then directly */
+
+ assert(_bus);
+
+ r = sd_bus_new(&bus);
+ if (r < 0)
+ return r;
+
+ if (asprintf(&bus->address, KERNEL_USER_BUS_ADDRESS_FMT, getuid()) < 0)
+ return -ENOMEM;
+
+ bus->bus_client = true;
+
+ r = sd_bus_start(bus);
+ if (r >= 0) {
+ *_bus = bus;
+ bus = NULL;
+ return 0;
+ }
+
+ bus = sd_bus_unref(bus);
+
+ e = secure_getenv("XDG_RUNTIME_DIR");
+ if (!e)
+ return sd_bus_default_user(_bus);
+
+ ee = bus_address_escape(e);
+ if (!ee)
+ return -ENOMEM;
+
+ r = sd_bus_new(&bus);
+ if (r < 0)
+ return r;
+
+ bus->address = strjoin("unix:path=", ee, "/systemd/private", NULL);
+ if (!bus->address)
+ return -ENOMEM;
+
+ r = sd_bus_start(bus);
+ if (r < 0)
+ return sd_bus_default_user(_bus);
+
+ r = bus_check_peercred(bus);
+ if (r < 0)
+ return r;
+
+ *_bus = bus;
+ bus = NULL;
+
+ return 0;
+}
+
+int bus_print_property(const char *name, sd_bus_message *property, bool all) {
+ char type;
+ const char *contents;
+ int r;
+
+ assert(name);
+ assert(property);
+
+ r = sd_bus_message_peek_type(property, &type, &contents);
+ if (r < 0)
+ return r;
+
+ switch (type) {
+
+ case SD_BUS_TYPE_STRING: {
+ const char *s;
+
+ r = sd_bus_message_read_basic(property, type, &s);
+ if (r < 0)
+ return r;
+
+ if (all || !isempty(s)) {
+ _cleanup_free_ char *escaped = NULL;
+
+ escaped = xescape(s, "\n");
+ if (!escaped)
+ return -ENOMEM;
+
+ printf("%s=%s\n", name, escaped);
+ }
+
+ return 1;
+ }
+
+ case SD_BUS_TYPE_BOOLEAN: {
+ int b;
+
+ r = sd_bus_message_read_basic(property, type, &b);
+ if (r < 0)
+ return r;
+
+ printf("%s=%s\n", name, yes_no(b));
+
+ return 1;
+ }
+
+ case SD_BUS_TYPE_UINT64: {
+ uint64_t u;
+
+ r = sd_bus_message_read_basic(property, type, &u);
+ if (r < 0)
+ return r;
+
+ /* Yes, heuristics! But we can change this check
+ * should it turn out to not be sufficient */
+
+ if (endswith(name, "Timestamp")) {
+ char timestamp[FORMAT_TIMESTAMP_MAX], *t;
+
+ t = format_timestamp(timestamp, sizeof(timestamp), u);
+ if (t || all)
+ printf("%s=%s\n", name, strempty(t));
+
+ } else if (strstr(name, "USec")) {
+ char timespan[FORMAT_TIMESPAN_MAX];
+
+ printf("%s=%s\n", name, format_timespan(timespan, sizeof(timespan), u, 0));
+ } else
+ printf("%s=%llu\n", name, (unsigned long long) u);
+
+ return 1;
+ }
+
+ case SD_BUS_TYPE_INT64: {
+ int64_t i;
+
+ r = sd_bus_message_read_basic(property, type, &i);
+ if (r < 0)
+ return r;
+
+ printf("%s=%lld\n", name, (long long) i);
+
+ return 1;
+ }
+
+ case SD_BUS_TYPE_UINT32: {
+ uint32_t u;
+
+ r = sd_bus_message_read_basic(property, type, &u);
+ if (r < 0)
+ return r;
+
+ if (strstr(name, "UMask") || strstr(name, "Mode"))
+ printf("%s=%04o\n", name, u);
+ else
+ printf("%s=%u\n", name, (unsigned) u);
+
+ return 1;
+ }
+
+ case SD_BUS_TYPE_INT32: {
+ int32_t i;
+
+ r = sd_bus_message_read_basic(property, type, &i);
+ if (r < 0)
+ return r;
+
+ printf("%s=%i\n", name, (int) i);
+ return 1;
+ }
+
+ case SD_BUS_TYPE_DOUBLE: {
+ double d;
+
+ r = sd_bus_message_read_basic(property, type, &d);
+ if (r < 0)
+ return r;
+
+ printf("%s=%g\n", name, d);
+ return 1;
+ }
+
+ case SD_BUS_TYPE_ARRAY:
+ if (streq(contents, "s")) {
+ bool first = true;
+ const char *str;
+
+ r = sd_bus_message_enter_container(property, SD_BUS_TYPE_ARRAY, contents);
+ if (r < 0)
+ return r;
+
+ while((r = sd_bus_message_read_basic(property, SD_BUS_TYPE_STRING, &str)) > 0) {
+ _cleanup_free_ char *escaped = NULL;
+
+ if (first)
+ printf("%s=", name);
+
+ escaped = xescape(str, "\n ");
+ if (!escaped)
+ return -ENOMEM;
+
+ printf("%s%s", first ? "" : " ", escaped);
+
+ first = false;
+ }
+ if (r < 0)
+ return r;
+
+ if (first && all)
+ printf("%s=", name);
+ if (!first || all)
+ puts("");
+
+ r = sd_bus_message_exit_container(property);
+ if (r < 0)
+ return r;
+
+ return 1;
+
+ } else if (streq(contents, "y")) {
+ const uint8_t *u;
+ size_t n;
+
+ r = sd_bus_message_read_array(property, SD_BUS_TYPE_BYTE, (const void**) &u, &n);
+ if (r < 0)
+ return r;
+
+ if (all || n > 0) {
+ unsigned int i;
+
+ printf("%s=", name);
+
+ for (i = 0; i < n; i++)
+ printf("%02x", u[i]);
+
+ puts("");
+ }
+
+ return 1;
+
+ } else if (streq(contents, "u")) {
+ uint32_t *u;
+ size_t n;
+
+ r = sd_bus_message_read_array(property, SD_BUS_TYPE_UINT32, (const void**) &u, &n);
+ if (r < 0)
+ return r;
+
+ if (all || n > 0) {
+ unsigned int i;
+
+ printf("%s=", name);
+
+ for (i = 0; i < n; i++)
+ printf("%08x", u[i]);
+
+ puts("");
+ }
+
+ return 1;
+ }
+
+ break;
+ }
+
+ return 0;
+}
+
+int bus_print_all_properties(sd_bus *bus, const char *dest, const char *path, char **filter, bool all) {
+ _cleanup_(sd_bus_message_unrefp) sd_bus_message *reply = NULL;
+ _cleanup_(sd_bus_error_free) sd_bus_error error = SD_BUS_ERROR_NULL;
+ int r;
+
+ assert(bus);
+ assert(path);
+
+ r = sd_bus_call_method(bus,
+ dest,
+ path,
+ "org.freedesktop.DBus.Properties",
+ "GetAll",
+ &error,
+ &reply,
+ "s", "");
+ if (r < 0)
+ return r;
+
+ r = sd_bus_message_enter_container(reply, SD_BUS_TYPE_ARRAY, "{sv}");
+ if (r < 0)
+ return r;
+
+ while ((r = sd_bus_message_enter_container(reply, SD_BUS_TYPE_DICT_ENTRY, "sv")) > 0) {
+ const char *name;
+ const char *contents;
+
+ r = sd_bus_message_read_basic(reply, SD_BUS_TYPE_STRING, &name);
+ if (r < 0)
+ return r;
+
+ if (!filter || strv_find(filter, name)) {
+ r = sd_bus_message_peek_type(reply, NULL, &contents);
+ if (r < 0)
+ return r;
+
+ r = sd_bus_message_enter_container(reply, SD_BUS_TYPE_VARIANT, contents);
+ if (r < 0)
+ return r;
+
+ r = bus_print_property(name, reply, all);
+ if (r < 0)
+ return r;
+ if (r == 0) {
+ if (all)
+ printf("%s=[unprintable]\n", name);
+ /* skip what we didn't read */
+ r = sd_bus_message_skip(reply, contents);
+ if (r < 0)
+ return r;
+ }
+
+ r = sd_bus_message_exit_container(reply);
+ if (r < 0)
+ return r;
+ } else {
+ r = sd_bus_message_skip(reply, "v");
+ if (r < 0)
+ return r;
+ }
+
+ r = sd_bus_message_exit_container(reply);
+ if (r < 0)
+ return r;
+ }
+ if (r < 0)
+ return r;
+
+ r = sd_bus_message_exit_container(reply);
+ if (r < 0)
+ return r;
+
+ return 0;
+}
+
+int bus_map_id128(sd_bus *bus, const char *member, sd_bus_message *m, sd_bus_error *error, void *userdata) {
+ sd_id128_t *p = userdata;
+ const void *v;
+ size_t n;
+ int r;
+
+ r = sd_bus_message_read_array(m, SD_BUS_TYPE_BYTE, &v, &n);
+ if (r < 0)
+ return r;
+
+ if (n == 0)
+ *p = SD_ID128_NULL;
+ else if (n == 16)
+ memcpy((*p).bytes, v, n);
+ else
+ return -EINVAL;
+
+ return 0;
+}
+
+static int map_basic(sd_bus *bus, const char *member, sd_bus_message *m, sd_bus_error *error, void *userdata) {
+ char type;
+ int r;
+
+ r = sd_bus_message_peek_type(m, &type, NULL);
+ if (r < 0)
+ return r;
+
+ switch (type) {
+ case SD_BUS_TYPE_STRING: {
+ const char *s;
+ char **p = userdata;
+
+ r = sd_bus_message_read_basic(m, type, &s);
+ if (r < 0)
+ break;
+
+ if (isempty(s))
+ break;
+
+ r = free_and_strdup(p, s);
+ break;
+ }
+
+ case SD_BUS_TYPE_ARRAY: {
+ _cleanup_strv_free_ char **l = NULL;
+ char ***p = userdata;
+
+ r = bus_message_read_strv_extend(m, &l);
+ if (r < 0)
+ break;
+
+ strv_free(*p);
+ *p = l;
+ l = NULL;
+
+ break;
+ }
+
+ case SD_BUS_TYPE_BOOLEAN: {
+ unsigned b;
+ bool *p = userdata;
+
+ r = sd_bus_message_read_basic(m, type, &b);
+ if (r < 0)
+ break;
+
+ *p = b;
+
+ break;
+ }
+
+ case SD_BUS_TYPE_UINT32: {
+ uint64_t u;
+ uint32_t *p = userdata;
+
+ r = sd_bus_message_read_basic(m, type, &u);
+ if (r < 0)
+ break;
+
+ *p = u;
+
+ break;
+ }
+
+ case SD_BUS_TYPE_UINT64: {
+ uint64_t t;
+ uint64_t *p = userdata;
+
+ r = sd_bus_message_read_basic(m, type, &t);
+ if (r < 0)
+ break;
+
+ *p = t;
+
+ break;
+ }
+
+ default:
+ break;
+ }
+
+ return r;
+}
+
+int bus_message_map_all_properties(
+ sd_bus_message *m,
+ const struct bus_properties_map *map,
+ void *userdata) {
+
+ _cleanup_(sd_bus_error_free) sd_bus_error error = SD_BUS_ERROR_NULL;
+ int r;
+
+ assert(m);
+ assert(map);
+
+ r = sd_bus_message_enter_container(m, SD_BUS_TYPE_ARRAY, "{sv}");
+ if (r < 0)
+ return r;
+
+ while ((r = sd_bus_message_enter_container(m, SD_BUS_TYPE_DICT_ENTRY, "sv")) > 0) {
+ const struct bus_properties_map *prop;
+ const char *member;
+ const char *contents;
+ void *v;
+ unsigned i;
+
+ r = sd_bus_message_read_basic(m, SD_BUS_TYPE_STRING, &member);
+ if (r < 0)
+ return r;
+
+ for (i = 0, prop = NULL; map[i].member; i++)
+ if (streq(map[i].member, member)) {
+ prop = &map[i];
+ break;
+ }
+
+ if (prop) {
+ r = sd_bus_message_peek_type(m, NULL, &contents);
+ if (r < 0)
+ return r;
+
+ r = sd_bus_message_enter_container(m, SD_BUS_TYPE_VARIANT, contents);
+ if (r < 0)
+ return r;
+
+ v = (uint8_t *)userdata + prop->offset;
+ if (map[i].set)
+ r = prop->set(sd_bus_message_get_bus(m), member, m, &error, v);
+ else
+ r = map_basic(sd_bus_message_get_bus(m), member, m, &error, v);
+ if (r < 0)
+ return r;
+
+ r = sd_bus_message_exit_container(m);
+ if (r < 0)
+ return r;
+ } else {
+ r = sd_bus_message_skip(m, "v");
+ if (r < 0)
+ return r;
+ }
+
+ r = sd_bus_message_exit_container(m);
+ if (r < 0)
+ return r;
+ }
+ if (r < 0)
+ return r;
+
+ return sd_bus_message_exit_container(m);
+}
+
+int bus_message_map_properties_changed(
+ sd_bus_message *m,
+ const struct bus_properties_map *map,
+ void *userdata) {
+
+ const char *member;
+ int r, invalidated, i;
+
+ assert(m);
+ assert(map);
+
+ r = bus_message_map_all_properties(m, map, userdata);
+ if (r < 0)
+ return r;
+
+ r = sd_bus_message_enter_container(m, SD_BUS_TYPE_ARRAY, "s");
+ if (r < 0)
+ return r;
+
+ invalidated = 0;
+ while ((r = sd_bus_message_read_basic(m, SD_BUS_TYPE_STRING, &member)) > 0)
+ for (i = 0; map[i].member; i++)
+ if (streq(map[i].member, member)) {
+ ++invalidated;
+ break;
+ }
+ if (r < 0)
+ return r;
+
+ r = sd_bus_message_exit_container(m);
+ if (r < 0)
+ return r;
+
+ return invalidated;
+}
+
+int bus_map_all_properties(
+ sd_bus *bus,
+ const char *destination,
+ const char *path,
+ const struct bus_properties_map *map,
+ void *userdata) {
+
+ _cleanup_(sd_bus_message_unrefp) sd_bus_message *m = NULL;
+ _cleanup_(sd_bus_error_free) sd_bus_error error = SD_BUS_ERROR_NULL;
+ int r;
+
+ assert(bus);
+ assert(destination);
+ assert(path);
+ assert(map);
+
+ r = sd_bus_call_method(
+ bus,
+ destination,
+ path,
+ "org.freedesktop.DBus.Properties",
+ "GetAll",
+ &error,
+ &m,
+ "s", "");
+ if (r < 0)
+ return r;
+
+ return bus_message_map_all_properties(m, map, userdata);
+}
+
+int bus_connect_transport(BusTransport transport, const char *host, bool user, sd_bus **bus) {
+ int r;
+
+ assert(transport >= 0);
+ assert(transport < _BUS_TRANSPORT_MAX);
+ assert(bus);
+
+ assert_return((transport == BUS_TRANSPORT_LOCAL) == !host, -EINVAL);
+ assert_return(transport == BUS_TRANSPORT_LOCAL || !user, -EOPNOTSUPP);
+
+ switch (transport) {
+
+ case BUS_TRANSPORT_LOCAL:
+ if (user)
+ r = sd_bus_default_user(bus);
+ else
+ r = sd_bus_default_system(bus);
+
+ break;
+
+ case BUS_TRANSPORT_REMOTE:
+ r = sd_bus_open_system_remote(bus, host);
+ break;
+
+ case BUS_TRANSPORT_MACHINE:
+ r = sd_bus_open_system_machine(bus, host);
+ break;
+
+ default:
+ assert_not_reached("Hmm, unknown transport type.");
+ }
+
+ return r;
+}
+
+int bus_connect_transport_systemd(BusTransport transport, const char *host, bool user, sd_bus **bus) {
+ int r;
+
+ assert(transport >= 0);
+ assert(transport < _BUS_TRANSPORT_MAX);
+ assert(bus);
+
+ assert_return((transport == BUS_TRANSPORT_LOCAL) == !host, -EINVAL);
+ assert_return(transport == BUS_TRANSPORT_LOCAL || !user, -EOPNOTSUPP);
+
+ switch (transport) {
+
+ case BUS_TRANSPORT_LOCAL:
+ if (user)
+ r = bus_connect_user_systemd(bus);
+ else
+ r = bus_connect_system_systemd(bus);
+
+ break;
+
+ case BUS_TRANSPORT_REMOTE:
+ r = sd_bus_open_system_remote(bus, host);
+ break;
+
+ case BUS_TRANSPORT_MACHINE:
+ r = sd_bus_open_system_machine(bus, host);
+ break;
+
+ default:
+ assert_not_reached("Hmm, unknown transport type.");
+ }
+
+ return r;
+}
+
+int bus_property_get_bool(
+ sd_bus *bus,
+ const char *path,
+ const char *interface,
+ const char *property,
+ sd_bus_message *reply,
+ void *userdata,
+ sd_bus_error *error) {
+
+ int b = *(bool*) userdata;
+
+ return sd_bus_message_append_basic(reply, 'b', &b);
+}
+
+#if __SIZEOF_SIZE_T__ != 8
+int bus_property_get_size(
+ sd_bus *bus,
+ const char *path,
+ const char *interface,
+ const char *property,
+ sd_bus_message *reply,
+ void *userdata,
+ sd_bus_error *error) {
+
+ uint64_t sz = *(size_t*) userdata;
+
+ return sd_bus_message_append_basic(reply, 't', &sz);
+}
+#endif
+
+#if __SIZEOF_LONG__ != 8
+int bus_property_get_long(
+ sd_bus *bus,
+ const char *path,
+ const char *interface,
+ const char *property,
+ sd_bus_message *reply,
+ void *userdata,
+ sd_bus_error *error) {
+
+ int64_t l = *(long*) userdata;
+
+ return sd_bus_message_append_basic(reply, 'x', &l);
+}
+
+int bus_property_get_ulong(
+ sd_bus *bus,
+ const char *path,
+ const char *interface,
+ const char *property,
+ sd_bus_message *reply,
+ void *userdata,
+ sd_bus_error *error) {
+
+ uint64_t ul = *(unsigned long*) userdata;
+
+ return sd_bus_message_append_basic(reply, 't', &ul);
+}
+#endif
+
+int bus_log_parse_error(int r) {
+ return log_error_errno(r, "Failed to parse bus message: %m");
+}
+
+int bus_log_create_error(int r) {
+ return log_error_errno(r, "Failed to create bus message: %m");
+}
+
+int bus_parse_unit_info(sd_bus_message *message, UnitInfo *u) {
+ assert(message);
+ assert(u);
+
+ u->machine = NULL;
+
+ return sd_bus_message_read(
+ message,
+ "(ssssssouso)",
+ &u->id,
+ &u->description,
+ &u->load_state,
+ &u->active_state,
+ &u->sub_state,
+ &u->following,
+ &u->unit_path,
+ &u->job_id,
+ &u->job_type,
+ &u->job_path);
+}
+
+int bus_append_unit_property_assignment(sd_bus_message *m, const char *assignment) {
+ const char *eq, *field;
+ int r, rl;
+
+ assert(m);
+ assert(assignment);
+
+ eq = strchr(assignment, '=');
+ if (!eq) {
+ log_error("Not an assignment: %s", assignment);
+ return -EINVAL;
+ }
+
+ r = sd_bus_message_open_container(m, SD_BUS_TYPE_STRUCT, "sv");
+ if (r < 0)
+ return bus_log_create_error(r);
+
+ field = strndupa(assignment, eq - assignment);
+ eq ++;
+
+ if (streq(field, "CPUQuota")) {
+
+ if (isempty(eq))
+ r = sd_bus_message_append(m, "sv", "CPUQuotaPerSecUSec", "t", USEC_INFINITY);
+ else if (endswith(eq, "%")) {
+ double percent;
+
+ if (sscanf(eq, "%lf%%", &percent) != 1 || percent <= 0) {
+ log_error("CPU quota '%s' invalid.", eq);
+ return -EINVAL;
+ }
+
+ r = sd_bus_message_append(m, "sv", "CPUQuotaPerSecUSec", "t", (usec_t) percent * USEC_PER_SEC / 100);
+ } else {
+ log_error("CPU quota needs to be in percent.");
+ return -EINVAL;
+ }
+
+ goto finish;
+
+ } else if (streq(field, "EnvironmentFile")) {
+
+ r = sd_bus_message_append(m, "sv", "EnvironmentFiles", "a(sb)", 1,
+ eq[0] == '-' ? eq + 1 : eq,
+ eq[0] == '-');
+ goto finish;
+
+ } else if (STR_IN_SET(field, "AccuracySec", "RandomizedDelaySec", "RuntimeMaxSec")) {
+ char *n;
+ usec_t t;
+ size_t l;
+ r = parse_sec(eq, &t);
+ if (r < 0)
+ return log_error_errno(r, "Failed to parse %s= parameter: %s", field, eq);
+
+ l = strlen(field);
+ n = newa(char, l + 2);
+ if (!n)
+ return log_oom();
+
+ /* Change suffix Sec → USec */
+ strcpy(mempcpy(n, field, l - 3), "USec");
+ r = sd_bus_message_append(m, "sv", n, "t", t);
+ goto finish;
+ }
+
+ r = sd_bus_message_append_basic(m, SD_BUS_TYPE_STRING, field);
+ if (r < 0)
+ return bus_log_create_error(r);
+
+ rl = rlimit_from_string(field);
+ if (rl >= 0) {
+ const char *sn;
+ struct rlimit l;
+
+ r = rlimit_parse(rl, eq, &l);
+ if (r < 0)
+ return log_error_errno(r, "Failed to parse resource limit: %s", eq);
+
+ r = sd_bus_message_append(m, "v", "t", l.rlim_max);
+ if (r < 0)
+ return bus_log_create_error(r);
+
+ r = sd_bus_message_close_container(m);
+ if (r < 0)
+ return bus_log_create_error(r);
+
+ r = sd_bus_message_open_container(m, SD_BUS_TYPE_STRUCT, "sv");
+ if (r < 0)
+ return bus_log_create_error(r);
+
+ sn = strjoina(field, "Soft");
+ r = sd_bus_message_append(m, "sv", sn, "t", l.rlim_cur);
+
+ } else if (STR_IN_SET(field,
+ "CPUAccounting", "MemoryAccounting", "BlockIOAccounting", "TasksAccounting",
+ "SendSIGHUP", "SendSIGKILL", "WakeSystem", "DefaultDependencies",
+ "IgnoreSIGPIPE", "TTYVHangup", "TTYReset", "RemainAfterExit",
+ "PrivateTmp", "PrivateDevices", "PrivateNetwork", "NoNewPrivileges",
+ "SyslogLevelPrefix", "Delegate", "RemainAfterElapse")) {
+
+ r = parse_boolean(eq);
+ if (r < 0)
+ return log_error_errno(r, "Failed to parse boolean assignment %s.", assignment);
+
+ r = sd_bus_message_append(m, "v", "b", r);
+
+ } else if (streq(field, "MemoryLimit")) {
+ uint64_t bytes;
+
+ if (isempty(eq) || streq(eq, "infinity"))
+ bytes = (uint64_t) -1;
+ else {
+ r = parse_size(eq, 1024, &bytes);
+ if (r < 0) {
+ log_error("Failed to parse bytes specification %s", assignment);
+ return -EINVAL;
+ }
+ }
+
+ r = sd_bus_message_append(m, "v", "t", bytes);
+
+ } else if (streq(field, "TasksMax")) {
+ uint64_t n;
+
+ if (isempty(eq) || streq(eq, "infinity"))
+ n = (uint64_t) -1;
+ else {
+ r = safe_atou64(eq, &n);
+ if (r < 0) {
+ log_error("Failed to parse maximum tasks specification %s", assignment);
+ return -EINVAL;
+ }
+ }
+
+ r = sd_bus_message_append(m, "v", "t", n);
+
+ } else if (STR_IN_SET(field, "CPUShares", "StartupCPUShares")) {
+ uint64_t u;
+
+ r = cg_cpu_shares_parse(eq, &u);
+ if (r < 0) {
+ log_error("Failed to parse %s value %s.", field, eq);
+ return -EINVAL;
+ }
+
+ r = sd_bus_message_append(m, "v", "t", u);
+
+ } else if (STR_IN_SET(field, "BlockIOWeight", "StartupBlockIOWeight")) {
+ uint64_t u;
+
+ r = cg_cpu_shares_parse(eq, &u);
+ if (r < 0) {
+ log_error("Failed to parse %s value %s.", field, eq);
+ return -EINVAL;
+ }
+
+ r = sd_bus_message_append(m, "v", "t", u);
+
+ } else if (STR_IN_SET(field,
+ "User", "Group", "DevicePolicy", "KillMode",
+ "UtmpIdentifier", "UtmpMode", "PAMName", "TTYPath",
+ "StandardInput", "StandardOutput", "StandardError",
+ "Description", "Slice", "Type", "WorkingDirectory",
+ "RootDirectory", "SyslogIdentifier", "ProtectSystem",
+ "ProtectHome"))
+ r = sd_bus_message_append(m, "v", "s", eq);
+
+ else if (streq(field, "SyslogLevel")) {
+ int level;
+
+ level = log_level_from_string(eq);
+ if (level < 0) {
+ log_error("Failed to parse %s value %s.", field, eq);
+ return -EINVAL;
+ }
+
+ r = sd_bus_message_append(m, "v", "i", level);
+
+ } else if (streq(field, "SyslogFacility")) {
+ int facility;
+
+ facility = log_facility_unshifted_from_string(eq);
+ if (facility < 0) {
+ log_error("Failed to parse %s value %s.", field, eq);
+ return -EINVAL;
+ }
+
+ r = sd_bus_message_append(m, "v", "i", facility);
+
+ } else if (streq(field, "DeviceAllow")) {
+
+ if (isempty(eq))
+ r = sd_bus_message_append(m, "v", "a(ss)", 0);
+ else {
+ const char *path, *rwm, *e;
+
+ e = strchr(eq, ' ');
+ if (e) {
+ path = strndupa(eq, e - eq);
+ rwm = e+1;
+ } else {
+ path = eq;
+ rwm = "";
+ }
+
+ if (!path_startswith(path, "/dev")) {
+ log_error("%s is not a device file in /dev.", path);
+ return -EINVAL;
+ }
+
+ r = sd_bus_message_append(m, "v", "a(ss)", 1, path, rwm);
+ }
+
+ } else if (STR_IN_SET(field, "BlockIOReadBandwidth", "BlockIOWriteBandwidth")) {
+
+ if (isempty(eq))
+ r = sd_bus_message_append(m, "v", "a(st)", 0);
+ else {
+ const char *path, *bandwidth, *e;
+ uint64_t bytes;
+
+ e = strchr(eq, ' ');
+ if (e) {
+ path = strndupa(eq, e - eq);
+ bandwidth = e+1;
+ } else {
+ log_error("Failed to parse %s value %s.", field, eq);
+ return -EINVAL;
+ }
+
+ if (!path_startswith(path, "/dev")) {
+ log_error("%s is not a device file in /dev.", path);
+ return -EINVAL;
+ }
+
+ r = parse_size(bandwidth, 1000, &bytes);
+ if (r < 0) {
+ log_error("Failed to parse byte value %s.", bandwidth);
+ return -EINVAL;
+ }
+
+ r = sd_bus_message_append(m, "v", "a(st)", 1, path, bytes);
+ }
+
+ } else if (streq(field, "BlockIODeviceWeight")) {
+
+ if (isempty(eq))
+ r = sd_bus_message_append(m, "v", "a(st)", 0);
+ else {
+ const char *path, *weight, *e;
+ uint64_t u;
+
+ e = strchr(eq, ' ');
+ if (e) {
+ path = strndupa(eq, e - eq);
+ weight = e+1;
+ } else {
+ log_error("Failed to parse %s value %s.", field, eq);
+ return -EINVAL;
+ }
+
+ if (!path_startswith(path, "/dev")) {
+ log_error("%s is not a device file in /dev.", path);
+ return -EINVAL;
+ }
+
+ r = safe_atou64(weight, &u);
+ if (r < 0) {
+ log_error("Failed to parse %s value %s.", field, weight);
+ return -EINVAL;
+ }
+ r = sd_bus_message_append(m, "v", "a(st)", path, u);
+ }
+
+ } else if (streq(field, "Nice")) {
+ int32_t i;
+
+ r = safe_atoi32(eq, &i);
+ if (r < 0) {
+ log_error("Failed to parse %s value %s.", field, eq);
+ return -EINVAL;
+ }
+
+ r = sd_bus_message_append(m, "v", "i", i);
+
+ } else if (STR_IN_SET(field, "Environment", "PassEnvironment")) {
+ const char *p;
+
+ r = sd_bus_message_open_container(m, 'v', "as");
+ if (r < 0)
+ return bus_log_create_error(r);
+
+ r = sd_bus_message_open_container(m, 'a', "s");
+ if (r < 0)
+ return bus_log_create_error(r);
+
+ p = eq;
+
+ for (;;) {
+ _cleanup_free_ char *word = NULL;
+
+ r = extract_first_word(&p, &word, NULL, EXTRACT_QUOTES|EXTRACT_CUNESCAPE);
+ if (r < 0) {
+ log_error("Failed to parse Environment value %s", eq);
+ return -EINVAL;
+ }
+ if (r == 0)
+ break;
+
+ if (streq(field, "Environment")) {
+ if (!env_assignment_is_valid(word)) {
+ log_error("Invalid environment assignment: %s", word);
+ return -EINVAL;
+ }
+ } else { /* PassEnvironment */
+ if (!env_name_is_valid(word)) {
+ log_error("Invalid environment variable name: %s", word);
+ return -EINVAL;
+ }
+ }
+
+ r = sd_bus_message_append_basic(m, 's', word);
+ if (r < 0)
+ return bus_log_create_error(r);
+ }
+
+ r = sd_bus_message_close_container(m);
+ if (r < 0)
+ return bus_log_create_error(r);
+
+ r = sd_bus_message_close_container(m);
+
+ } else if (streq(field, "KillSignal")) {
+ int sig;
+
+ sig = signal_from_string_try_harder(eq);
+ if (sig < 0) {
+ log_error("Failed to parse %s value %s.", field, eq);
+ return -EINVAL;
+ }
+
+ r = sd_bus_message_append(m, "v", "i", sig);
+
+ } else if (streq(field, "TimerSlackNSec")) {
+ nsec_t n;
+
+ r = parse_nsec(eq, &n);
+ if (r < 0) {
+ log_error("Failed to parse %s value %s", field, eq);
+ return -EINVAL;
+ }
+
+ r = sd_bus_message_append(m, "v", "t", n);
+ } else if (streq(field, "OOMScoreAdjust")) {
+ int oa;
+
+ r = safe_atoi(eq, &oa);
+ if (r < 0) {
+ log_error("Failed to parse %s value %s", field, eq);
+ return -EINVAL;
+ }
+
+ if (!oom_score_adjust_is_valid(oa)) {
+ log_error("OOM score adjust value out of range");
+ return -EINVAL;
+ }
+
+ r = sd_bus_message_append(m, "v", "i", oa);
+ } else if (STR_IN_SET(field, "ReadWriteDirectories", "ReadOnlyDirectories", "InaccessibleDirectories")) {
+ const char *p;
+
+ r = sd_bus_message_open_container(m, 'v', "as");
+ if (r < 0)
+ return bus_log_create_error(r);
+
+ r = sd_bus_message_open_container(m, 'a', "s");
+ if (r < 0)
+ return bus_log_create_error(r);
+
+ p = eq;
+
+ for (;;) {
+ _cleanup_free_ char *word = NULL;
+ int offset;
+
+ r = extract_first_word(&p, &word, NULL, EXTRACT_QUOTES);
+ if (r < 0) {
+ log_error("Failed to parse %s value %s", field, eq);
+ return -EINVAL;
+ }
+ if (r == 0)
+ break;
+
+ if (!utf8_is_valid(word)) {
+ log_error("Failed to parse %s value %s", field, eq);
+ return -EINVAL;
+ }
+
+ offset = word[0] == '-';
+ if (!path_is_absolute(word + offset)) {
+ log_error("Failed to parse %s value %s", field, eq);
+ return -EINVAL;
+ }
+
+ path_kill_slashes(word + offset);
+
+ r = sd_bus_message_append_basic(m, 's', word);
+ if (r < 0)
+ return bus_log_create_error(r);
+ }
+
+ r = sd_bus_message_close_container(m);
+ if (r < 0)
+ return bus_log_create_error(r);
+
+ r = sd_bus_message_close_container(m);
+
+ } else if (streq(field, "RuntimeDirectory")) {
+ const char *p;
+
+ r = sd_bus_message_open_container(m, 'v', "as");
+ if (r < 0)
+ return bus_log_create_error(r);
+
+ r = sd_bus_message_open_container(m, 'a', "s");
+ if (r < 0)
+ return bus_log_create_error(r);
+
+ p = eq;
+
+ for (;;) {
+ _cleanup_free_ char *word = NULL;
+
+ r = extract_first_word(&p, &word, NULL, EXTRACT_QUOTES);
+ if (r < 0)
+ return log_error_errno(r, "Failed to parse %s value %s", field, eq);
+
+ if (r == 0)
+ break;
+
+ r = sd_bus_message_append_basic(m, 's', word);
+ if (r < 0)
+ return bus_log_create_error(r);
+ }
+
+ r = sd_bus_message_close_container(m);
+ if (r < 0)
+ return bus_log_create_error(r);
+
+ r = sd_bus_message_close_container(m);
+
+ } else {
+ log_error("Unknown assignment %s.", assignment);
+ return -EINVAL;
+ }
+
+finish:
+ if (r < 0)
+ return bus_log_create_error(r);
+
+ r = sd_bus_message_close_container(m);
+ if (r < 0)
+ return bus_log_create_error(r);
+
+ return 0;
+}
+
+typedef struct BusWaitForJobs {
+ sd_bus *bus;
+ Set *jobs;
+
+ char *name;
+ char *result;
+
+ sd_bus_slot *slot_job_removed;
+ sd_bus_slot *slot_disconnected;
+} BusWaitForJobs;
+
+static int match_disconnected(sd_bus_message *m, void *userdata, sd_bus_error *error) {
+ assert(m);
+
+ log_error("Warning! D-Bus connection terminated.");
+ sd_bus_close(sd_bus_message_get_bus(m));
+
+ return 0;
+}
+
+static int match_job_removed(sd_bus_message *m, void *userdata, sd_bus_error *error) {
+ const char *path, *unit, *result;
+ BusWaitForJobs *d = userdata;
+ uint32_t id;
+ char *found;
+ int r;
+
+ assert(m);
+ assert(d);
+
+ r = sd_bus_message_read(m, "uoss", &id, &path, &unit, &result);
+ if (r < 0) {
+ bus_log_parse_error(r);
+ return 0;
+ }
+
+ found = set_remove(d->jobs, (char*) path);
+ if (!found)
+ return 0;
+
+ free(found);
+
+ if (!isempty(result))
+ d->result = strdup(result);
+
+ if (!isempty(unit))
+ d->name = strdup(unit);
+
+ return 0;
+}
+
+void bus_wait_for_jobs_free(BusWaitForJobs *d) {
+ if (!d)
+ return;
+
+ set_free_free(d->jobs);
+
+ sd_bus_slot_unref(d->slot_disconnected);
+ sd_bus_slot_unref(d->slot_job_removed);
+
+ sd_bus_unref(d->bus);
+
+ free(d->name);
+ free(d->result);
+
+ free(d);
+}
+
+int bus_wait_for_jobs_new(sd_bus *bus, BusWaitForJobs **ret) {
+ _cleanup_(bus_wait_for_jobs_freep) BusWaitForJobs *d = NULL;
+ int r;
+
+ assert(bus);
+ assert(ret);
+
+ d = new0(BusWaitForJobs, 1);
+ if (!d)
+ return -ENOMEM;
+
+ d->bus = sd_bus_ref(bus);
+
+ /* When we are a bus client we match by sender. Direct
+ * connections OTOH have no initialized sender field, and
+ * hence we ignore the sender then */
+ r = sd_bus_add_match(
+ bus,
+ &d->slot_job_removed,
+ bus->bus_client ?
+ "type='signal',"
+ "sender='org.freedesktop.systemd1',"
+ "interface='org.freedesktop.systemd1.Manager',"
+ "member='JobRemoved',"
+ "path='/org/freedesktop/systemd1'" :
+ "type='signal',"
+ "interface='org.freedesktop.systemd1.Manager',"
+ "member='JobRemoved',"
+ "path='/org/freedesktop/systemd1'",
+ match_job_removed, d);
+ if (r < 0)
+ return r;
+
+ r = sd_bus_add_match(
+ bus,
+ &d->slot_disconnected,
+ "type='signal',"
+ "sender='org.freedesktop.DBus.Local',"
+ "interface='org.freedesktop.DBus.Local',"
+ "member='Disconnected'",
+ match_disconnected, d);
+ if (r < 0)
+ return r;
+
+ *ret = d;
+ d = NULL;
+
+ return 0;
+}
+
+static int bus_process_wait(sd_bus *bus) {
+ int r;
+
+ for (;;) {
+ r = sd_bus_process(bus, NULL);
+ if (r < 0)
+ return r;
+ if (r > 0)
+ return 0;
+
+ r = sd_bus_wait(bus, (uint64_t) -1);
+ if (r < 0)
+ return r;
+ }
+}
+
+static int bus_job_get_service_result(BusWaitForJobs *d, char **result) {
+ _cleanup_free_ char *dbus_path = NULL;
+
+ assert(d);
+ assert(d->name);
+ assert(result);
+
+ dbus_path = unit_dbus_path_from_name(d->name);
+ if (!dbus_path)
+ return -ENOMEM;
+
+ return sd_bus_get_property_string(d->bus,
+ "org.freedesktop.systemd1",
+ dbus_path,
+ "org.freedesktop.systemd1.Service",
+ "Result",
+ NULL,
+ result);
+}
+
+static const struct {
+ const char *result, *explanation;
+} explanations [] = {
+ { "resources", "a configured resource limit was exceeded" },
+ { "timeout", "a timeout was exceeded" },
+ { "exit-code", "the control process exited with error code" },
+ { "signal", "a fatal signal was delivered to the control process" },
+ { "core-dump", "a fatal signal was delivered causing the control process to dump core" },
+ { "watchdog", "the service failed to send watchdog ping" },
+ { "start-limit", "start of the service was attempted too often" }
+};
+
+static void log_job_error_with_service_result(const char* service, const char *result, const char *extra_args) {
+ _cleanup_free_ char *service_shell_quoted = NULL, *systemctl_extra_args = NULL;
+
+ assert(service);
+
+ service_shell_quoted = shell_maybe_quote(service);
+
+ systemctl_extra_args = strjoin("systemctl ", extra_args, " ", NULL);
+ if (!systemctl_extra_args) {
+ log_oom();
+ return;
+ }
+
+ systemctl_extra_args = strstrip(systemctl_extra_args);
+
+ if (!isempty(result)) {
+ unsigned i;
+
+ for (i = 0; i < ELEMENTSOF(explanations); ++i)
+ if (streq(result, explanations[i].result))
+ break;
+
+ if (i < ELEMENTSOF(explanations)) {
+ log_error("Job for %s failed because %s. See \"%s status %s\" and \"journalctl -xe\" for details.\n",
+ service,
+ explanations[i].explanation,
+ systemctl_extra_args,
+ strna(service_shell_quoted));
+
+ goto finish;
+ }
+ }
+
+ log_error("Job for %s failed. See \"%s status %s\" and \"journalctl -xe\" for details.\n",
+ service,
+ systemctl_extra_args,
+ strna(service_shell_quoted));
+
+finish:
+ /* For some results maybe additional explanation is required */
+ if (streq_ptr(result, "start-limit"))
+ log_info("To force a start use \"%1$s reset-failed %2$s\" followed by \"%1$s start %2$s\" again.",
+ systemctl_extra_args,
+ strna(service_shell_quoted));
+}
+
+static int check_wait_response(BusWaitForJobs *d, bool quiet, const char *extra_args) {
+ int r = 0;
+
+ assert(d->result);
+
+ if (!quiet) {
+ if (streq(d->result, "canceled"))
+ log_error("Job for %s canceled.", strna(d->name));
+ else if (streq(d->result, "timeout"))
+ log_error("Job for %s timed out.", strna(d->name));
+ else if (streq(d->result, "dependency"))
+ log_error("A dependency job for %s failed. See 'journalctl -xe' for details.", strna(d->name));
+ else if (streq(d->result, "invalid"))
+ log_error("%s is not active, cannot reload.", strna(d->name));
+ else if (streq(d->result, "assert"))
+ log_error("Assertion failed on job for %s.", strna(d->name));
+ else if (streq(d->result, "unsupported"))
+ log_error("Operation on or unit type of %s not supported on this system.", strna(d->name));
+ else if (!streq(d->result, "done") && !streq(d->result, "skipped")) {
+ if (d->name) {
+ int q;
+ _cleanup_free_ char *result = NULL;
+
+ q = bus_job_get_service_result(d, &result);
+ if (q < 0)
+ log_debug_errno(q, "Failed to get Result property of service %s: %m", d->name);
+
+ log_job_error_with_service_result(d->name, result, extra_args);
+ } else
+ log_error("Job failed. See \"journalctl -xe\" for details.");
+ }
+ }
+
+ if (streq(d->result, "canceled"))
+ r = -ECANCELED;
+ else if (streq(d->result, "timeout"))
+ r = -ETIME;
+ else if (streq(d->result, "dependency"))
+ r = -EIO;
+ else if (streq(d->result, "invalid"))
+ r = -ENOEXEC;
+ else if (streq(d->result, "assert"))
+ r = -EPROTO;
+ else if (streq(d->result, "unsupported"))
+ r = -EOPNOTSUPP;
+ else if (!streq(d->result, "done") && !streq(d->result, "skipped"))
+ r = -EIO;
+
+ return r;
+}
+
+int bus_wait_for_jobs(BusWaitForJobs *d, bool quiet, const char *extra_args) {
+ int r = 0;
+
+ assert(d);
+
+ while (!set_isempty(d->jobs)) {
+ int q;
+
+ q = bus_process_wait(d->bus);
+ if (q < 0)
+ return log_error_errno(q, "Failed to wait for response: %m");
+
+ if (d->result) {
+ q = check_wait_response(d, quiet, extra_args);
+ /* Return the first error as it is most likely to be
+ * meaningful. */
+ if (q < 0 && r == 0)
+ r = q;
+
+ log_debug_errno(q, "Got result %s/%m for job %s", strna(d->result), strna(d->name));
+ }
+
+ d->name = mfree(d->name);
+ d->result = mfree(d->result);
+ }
+
+ return r;
+}
+
+int bus_wait_for_jobs_add(BusWaitForJobs *d, const char *path) {
+ int r;
+
+ assert(d);
+
+ r = set_ensure_allocated(&d->jobs, &string_hash_ops);
+ if (r < 0)
+ return r;
+
+ return set_put_strdup(d->jobs, path);
+}
+
+int bus_wait_for_jobs_one(BusWaitForJobs *d, const char *path, bool quiet) {
+ int r;
+
+ r = bus_wait_for_jobs_add(d, path);
+ if (r < 0)
+ return log_oom();
+
+ return bus_wait_for_jobs(d, quiet, NULL);
+}
+
+int bus_deserialize_and_dump_unit_file_changes(sd_bus_message *m, bool quiet, UnitFileChange **changes, unsigned *n_changes) {
+ const char *type, *path, *source;
+ int r;
+
+ r = sd_bus_message_enter_container(m, SD_BUS_TYPE_ARRAY, "(sss)");
+ if (r < 0)
+ return bus_log_parse_error(r);
+
+ while ((r = sd_bus_message_read(m, "(sss)", &type, &path, &source)) > 0) {
+ if (!quiet) {
+ if (streq(type, "symlink"))
+ log_info("Created symlink from %s to %s.", path, source);
+ else
+ log_info("Removed symlink %s.", path);
+ }
+
+ r = unit_file_changes_add(changes, n_changes, streq(type, "symlink") ? UNIT_FILE_SYMLINK : UNIT_FILE_UNLINK, path, source);
+ if (r < 0)
+ return r;
+ }
+ if (r < 0)
+ return bus_log_parse_error(r);
+
+ r = sd_bus_message_exit_container(m);
+ if (r < 0)
+ return bus_log_parse_error(r);
+
+ return 0;
+}
+
+/**
+ * bus_path_encode_unique() - encode unique object path
+ * @b: bus connection or NULL
+ * @prefix: object path prefix
+ * @sender_id: unique-name of client, or NULL
+ * @external_id: external ID to be chosen by client, or NULL
+ * @ret_path: storage for encoded object path pointer
+ *
+ * Whenever we provide a bus API that allows clients to create and manage
+ * server-side objects, we need to provide a unique name for these objects. If
+ * we let the server choose the name, we suffer from a race condition: If a
+ * client creates an object asynchronously, it cannot destroy that object until
+ * it received the method reply. It cannot know the name of the new object,
+ * thus, it cannot destroy it. Furthermore, it enforces a round-trip.
+ *
+ * Therefore, many APIs allow the client to choose the unique name for newly
+ * created objects. There're two problems to solve, though:
+ * 1) Object names are usually defined via dbus object paths, which are
+ * usually globally namespaced. Therefore, multiple clients must be able
+ * to choose unique object names without interference.
+ * 2) If multiple libraries share the same bus connection, they must be
+ * able to choose unique object names without interference.
+ * The first problem is solved easily by prefixing a name with the
+ * unique-bus-name of a connection. The server side must enforce this and
+ * reject any other name. The second problem is solved by providing unique
+ * suffixes from within sd-bus.
+ *
+ * This helper allows clients to create unique object-paths. It uses the
+ * template '/prefix/sender_id/external_id' and returns the new path in
+ * @ret_path (must be freed by the caller).
+ * If @sender_id is NULL, the unique-name of @b is used. If @external_id is
+ * NULL, this function allocates a unique suffix via @b (by requesting a new
+ * cookie). If both @sender_id and @external_id are given, @b can be passed as
+ * NULL.
+ *
+ * Returns: 0 on success, negative error code on failure.
+ */
+int bus_path_encode_unique(sd_bus *b, const char *prefix, const char *sender_id, const char *external_id, char **ret_path) {
+ _cleanup_free_ char *sender_label = NULL, *external_label = NULL;
+ char external_buf[DECIMAL_STR_MAX(uint64_t)], *p;
+ int r;
+
+ assert_return(b || (sender_id && external_id), -EINVAL);
+ assert_return(object_path_is_valid(prefix), -EINVAL);
+ assert_return(ret_path, -EINVAL);
+
+ if (!sender_id) {
+ r = sd_bus_get_unique_name(b, &sender_id);
+ if (r < 0)
+ return r;
+ }
+
+ if (!external_id) {
+ xsprintf(external_buf, "%"PRIu64, ++b->cookie);
+ external_id = external_buf;
+ }
+
+ sender_label = bus_label_escape(sender_id);
+ if (!sender_label)
+ return -ENOMEM;
+
+ external_label = bus_label_escape(external_id);
+ if (!external_label)
+ return -ENOMEM;
+
+ p = strjoin(prefix, "/", sender_label, "/", external_label, NULL);
+ if (!p)
+ return -ENOMEM;
+
+ *ret_path = p;
+ return 0;
+}
+
+/**
+ * bus_path_decode_unique() - decode unique object path
+ * @path: object path to decode
+ * @prefix: object path prefix
+ * @ret_sender: output parameter for sender-id label
+ * @ret_external: output parameter for external-id label
+ *
+ * This does the reverse of bus_path_encode_unique() (see its description for
+ * details). Both trailing labels, sender-id and external-id, are unescaped and
+ * returned in the given output parameters (the caller must free them).
+ *
+ * Note that this function returns 0 if the path does not match the template
+ * (see bus_path_encode_unique()), 1 if it matched.
+ *
+ * Returns: Negative error code on failure, 0 if the given object path does not
+ * match the template (return parameters are set to NULL), 1 if it was
+ * parsed successfully (return parameters contain allocated labels).
+ */
+int bus_path_decode_unique(const char *path, const char *prefix, char **ret_sender, char **ret_external) {
+ const char *p, *q;
+ char *sender, *external;
+
+ assert(object_path_is_valid(path));
+ assert(object_path_is_valid(prefix));
+ assert(ret_sender);
+ assert(ret_external);
+
+ p = object_path_startswith(path, prefix);
+ if (!p) {
+ *ret_sender = NULL;
+ *ret_external = NULL;
+ return 0;
+ }
+
+ q = strchr(p, '/');
+ if (!q) {
+ *ret_sender = NULL;
+ *ret_external = NULL;
+ return 0;
+ }
+
+ sender = bus_label_unescape_n(p, q - p);
+ external = bus_label_unescape(q + 1);
+ if (!sender || !external) {
+ free(sender);
+ free(external);
+ return -ENOMEM;
+ }
+
+ *ret_sender = sender;
+ *ret_external = external;
+ return 1;
+}
+
+bool is_kdbus_wanted(void) {
+ _cleanup_free_ char *value = NULL;
+#ifdef ENABLE_KDBUS
+ const bool configured = true;
+#else
+ const bool configured = false;
+#endif
+
+ int r;
+
+ if (get_proc_cmdline_key("kdbus", NULL) > 0)
+ return true;
+
+ r = get_proc_cmdline_key("kdbus=", &value);
+ if (r <= 0)
+ return configured;
+
+ return parse_boolean(value) == 1;
+}
+
+bool is_kdbus_available(void) {
+ _cleanup_close_ int fd = -1;
+ struct kdbus_cmd cmd = { .size = sizeof(cmd), .flags = KDBUS_FLAG_NEGOTIATE };
+
+ if (!is_kdbus_wanted())
+ return false;
+
+ fd = open("/sys/fs/kdbus/control", O_RDWR | O_CLOEXEC | O_NONBLOCK | O_NOCTTY);
+ if (fd < 0)
+ return false;
+
+ return ioctl(fd, KDBUS_CMD_BUS_MAKE, &cmd) >= 0;
+}
+
+int bus_property_get_rlimit(
+ sd_bus *bus,
+ const char *path,
+ const char *interface,
+ const char *property,
+ sd_bus_message *reply,
+ void *userdata,
+ sd_bus_error *error) {
+
+ struct rlimit *rl;
+ uint64_t u;
+ rlim_t x;
+ const char *is_soft;
+
+ assert(bus);
+ assert(reply);
+ assert(userdata);
+
+ is_soft = endswith(property, "Soft");
+ rl = *(struct rlimit**) userdata;
+ if (rl)
+ x = is_soft ? rl->rlim_cur : rl->rlim_max;
+ else {
+ struct rlimit buf = {};
+ int z;
+ const char *s;
+
+ s = is_soft ? strndupa(property, is_soft - property) : property;
+
+ z = rlimit_from_string(strstr(s, "Limit"));
+ assert(z >= 0);
+
+ getrlimit(z, &buf);
+ x = is_soft ? buf.rlim_cur : buf.rlim_max;
+ }
+
+ /* rlim_t might have different sizes, let's map
+ * RLIMIT_INFINITY to (uint64_t) -1, so that it is the same on
+ * all archs */
+ u = x == RLIM_INFINITY ? (uint64_t) -1 : (uint64_t) x;
+
+ return sd_bus_message_append(reply, "t", u);
+}
diff --git a/src/libshared/bus-util.h b/src/libshared/bus-util.h
new file mode 100644
index 0000000000..77474522be
--- /dev/null
+++ b/src/libshared/bus-util.h
@@ -0,0 +1,196 @@
+#pragma once
+
+/***
+ This file is part of systemd.
+
+ Copyright 2013 Lennart Poettering
+
+ systemd is free software; you can redistribute it and/or modify it
+ under the terms of the GNU Lesser General Public License as published by
+ the Free Software Foundation; either version 2.1 of the License, or
+ (at your option) any later version.
+
+ systemd is distributed in the hope that it will be useful, but
+ WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ Lesser General Public License for more details.
+
+ You should have received a copy of the GNU Lesser General Public License
+ along with systemd; If not, see <http://www.gnu.org/licenses/>.
+***/
+
+#include <stdbool.h>
+#include <stddef.h>
+#include <stdint.h>
+#include <sys/types.h>
+
+#include <systemd/sd-bus-vtable.h>
+#include <systemd/sd-bus.h>
+#include <systemd/sd-event.h>
+
+#include "hashmap.h"
+#include "install.h"
+#include "macro.h"
+#include "string-util.h"
+#include "time-util.h"
+
+typedef enum BusTransport {
+ BUS_TRANSPORT_LOCAL,
+ BUS_TRANSPORT_REMOTE,
+ BUS_TRANSPORT_MACHINE,
+ _BUS_TRANSPORT_MAX,
+ _BUS_TRANSPORT_INVALID = -1
+} BusTransport;
+
+typedef int (*bus_property_set_t) (sd_bus *bus, const char *member, sd_bus_message *m, sd_bus_error *error, void *userdata);
+
+struct bus_properties_map {
+ const char *member;
+ const char *signature;
+ bus_property_set_t set;
+ size_t offset;
+};
+
+int bus_map_id128(sd_bus *bus, const char *member, sd_bus_message *m, sd_bus_error *error, void *userdata);
+
+int bus_message_map_all_properties(sd_bus_message *m, const struct bus_properties_map *map, void *userdata);
+int bus_message_map_properties_changed(sd_bus_message *m, const struct bus_properties_map *map, void *userdata);
+int bus_map_all_properties(sd_bus *bus, const char *destination, const char *path, const struct bus_properties_map *map, void *userdata);
+
+int bus_async_unregister_and_exit(sd_event *e, sd_bus *bus, const char *name);
+
+typedef bool (*check_idle_t)(void *userdata);
+
+int bus_event_loop_with_idle(sd_event *e, sd_bus *bus, const char *name, usec_t timeout, check_idle_t check_idle, void *userdata);
+
+int bus_name_has_owner(sd_bus *c, const char *name, sd_bus_error *error);
+
+int bus_check_peercred(sd_bus *c);
+
+int bus_test_polkit(sd_bus_message *call, int capability, const char *action, const char **details, uid_t good_user, bool *_challenge, sd_bus_error *e);
+
+int bus_verify_polkit_async(sd_bus_message *call, int capability, const char *action, const char **details, bool interactive, uid_t good_user, Hashmap **registry, sd_bus_error *error);
+void bus_verify_polkit_async_registry_free(Hashmap *registry);
+
+int bus_connect_system_systemd(sd_bus **_bus);
+int bus_connect_user_systemd(sd_bus **_bus);
+
+int bus_connect_transport(BusTransport transport, const char *host, bool user, sd_bus **bus);
+int bus_connect_transport_systemd(BusTransport transport, const char *host, bool user, sd_bus **bus);
+
+int bus_print_property(const char *name, sd_bus_message *property, bool all);
+int bus_print_all_properties(sd_bus *bus, const char *dest, const char *path, char **filter, bool all);
+
+int bus_property_get_bool(sd_bus *bus, const char *path, const char *interface, const char *property, sd_bus_message *reply, void *userdata, sd_bus_error *error);
+
+#define bus_property_get_usec ((sd_bus_property_get_t) NULL)
+#define bus_property_set_usec ((sd_bus_property_set_t) NULL)
+
+assert_cc(sizeof(int) == sizeof(int32_t));
+#define bus_property_get_int ((sd_bus_property_get_t) NULL)
+
+assert_cc(sizeof(unsigned) == sizeof(unsigned));
+#define bus_property_get_unsigned ((sd_bus_property_get_t) NULL)
+
+/* On 64bit machines we can use the default serializer for size_t and
+ * friends, otherwise we need to cast this manually */
+#if __SIZEOF_SIZE_T__ == 8
+#define bus_property_get_size ((sd_bus_property_get_t) NULL)
+#else
+int bus_property_get_size(sd_bus *bus, const char *path, const char *interface, const char *property, sd_bus_message *reply, void *userdata, sd_bus_error *error);
+#endif
+
+#if __SIZEOF_LONG__ == 8
+#define bus_property_get_long ((sd_bus_property_get_t) NULL)
+#define bus_property_get_ulong ((sd_bus_property_get_t) NULL)
+#else
+int bus_property_get_long(sd_bus *bus, const char *path, const char *interface, const char *property, sd_bus_message *reply, void *userdata, sd_bus_error *error);
+int bus_property_get_ulong(sd_bus *bus, const char *path, const char *interface, const char *property, sd_bus_message *reply, void *userdata, sd_bus_error *error);
+#endif
+
+/* uid_t and friends on Linux 32 bit. This means we can just use the
+ * default serializer for 32bit unsigned, for serializing it, and map
+ * it to NULL here */
+assert_cc(sizeof(uid_t) == sizeof(uint32_t));
+#define bus_property_get_uid ((sd_bus_property_get_t) NULL)
+
+assert_cc(sizeof(gid_t) == sizeof(uint32_t));
+#define bus_property_get_gid ((sd_bus_property_get_t) NULL)
+
+assert_cc(sizeof(pid_t) == sizeof(uint32_t));
+#define bus_property_get_pid ((sd_bus_property_get_t) NULL)
+
+assert_cc(sizeof(mode_t) == sizeof(uint32_t));
+#define bus_property_get_mode ((sd_bus_property_get_t) NULL)
+
+int bus_log_parse_error(int r);
+int bus_log_create_error(int r);
+
+typedef struct UnitInfo {
+ const char *machine;
+ const char *id;
+ const char *description;
+ const char *load_state;
+ const char *active_state;
+ const char *sub_state;
+ const char *following;
+ const char *unit_path;
+ uint32_t job_id;
+ const char *job_type;
+ const char *job_path;
+} UnitInfo;
+
+int bus_parse_unit_info(sd_bus_message *message, UnitInfo *u);
+
+#define BUS_DEFINE_PROPERTY_GET_ENUM(function, name, type) \
+ int function(sd_bus *bus, \
+ const char *path, \
+ const char *interface, \
+ const char *property, \
+ sd_bus_message *reply, \
+ void *userdata, \
+ sd_bus_error *error) { \
+ \
+ const char *value; \
+ type *field = userdata; \
+ int r; \
+ \
+ assert(bus); \
+ assert(reply); \
+ assert(field); \
+ \
+ value = strempty(name##_to_string(*field)); \
+ \
+ r = sd_bus_message_append_basic(reply, 's', value); \
+ if (r < 0) \
+ return r; \
+ \
+ return 1; \
+ } \
+ struct __useless_struct_to_allow_trailing_semicolon__
+
+#define BUS_PROPERTY_DUAL_TIMESTAMP(name, offset, flags) \
+ SD_BUS_PROPERTY(name, "t", bus_property_get_usec, (offset) + offsetof(struct dual_timestamp, realtime), (flags)), \
+ SD_BUS_PROPERTY(name "Monotonic", "t", bus_property_get_usec, (offset) + offsetof(struct dual_timestamp, monotonic), (flags))
+
+int bus_append_unit_property_assignment(sd_bus_message *m, const char *assignment);
+
+typedef struct BusWaitForJobs BusWaitForJobs;
+
+int bus_wait_for_jobs_new(sd_bus *bus, BusWaitForJobs **ret);
+void bus_wait_for_jobs_free(BusWaitForJobs *d);
+int bus_wait_for_jobs_add(BusWaitForJobs *d, const char *path);
+int bus_wait_for_jobs(BusWaitForJobs *d, bool quiet, const char *extra_args);
+int bus_wait_for_jobs_one(BusWaitForJobs *d, const char *path, bool quiet);
+
+DEFINE_TRIVIAL_CLEANUP_FUNC(BusWaitForJobs*, bus_wait_for_jobs_free);
+
+int bus_deserialize_and_dump_unit_file_changes(sd_bus_message *m, bool quiet, UnitFileChange **changes, unsigned *n_changes);
+
+int bus_path_encode_unique(sd_bus *b, const char *prefix, const char *sender_id, const char *external_id, char **ret_path);
+int bus_path_decode_unique(const char *path, const char *prefix, char **ret_sender, char **ret_external);
+
+bool is_kdbus_wanted(void);
+bool is_kdbus_available(void);
+
+int bus_property_get_rlimit(sd_bus *bus, const char *path, const char *interface, const char *property, sd_bus_message *reply, void *userdata, sd_bus_error *error);
diff --git a/src/libshared/cgroup-show.c b/src/libshared/cgroup-show.c
new file mode 100644
index 0000000000..f3039b23f7
--- /dev/null
+++ b/src/libshared/cgroup-show.c
@@ -0,0 +1,278 @@
+/***
+ This file is part of systemd.
+
+ Copyright 2010 Lennart Poettering
+
+ systemd is free software; you can redistribute it and/or modify it
+ under the terms of the GNU Lesser General Public License as published by
+ the Free Software Foundation; either version 2.1 of the License, or
+ (at your option) any later version.
+
+ systemd is distributed in the hope that it will be useful, but
+ WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ Lesser General Public License for more details.
+
+ You should have received a copy of the GNU Lesser General Public License
+ along with systemd; If not, see <http://www.gnu.org/licenses/>.
+***/
+
+#include <dirent.h>
+#include <errno.h>
+#include <stddef.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+
+#include "alloc-util.h"
+#include "cgroup-show.h"
+#include "cgroup-util.h"
+#include "fd-util.h"
+#include "formats-util.h"
+#include "locale-util.h"
+#include "macro.h"
+#include "output-mode.h"
+#include "path-util.h"
+#include "process-util.h"
+#include "string-util.h"
+#include "terminal-util.h"
+
+static int compare(const void *a, const void *b) {
+ const pid_t *p = a, *q = b;
+
+ if (*p < *q)
+ return -1;
+ if (*p > *q)
+ return 1;
+ return 0;
+}
+
+static void show_pid_array(pid_t pids[], unsigned n_pids, const char *prefix, unsigned n_columns, bool extra, bool more, bool kernel_threads, OutputFlags flags) {
+ unsigned i, j, pid_width;
+
+ if (n_pids == 0)
+ return;
+
+ qsort(pids, n_pids, sizeof(pid_t), compare);
+
+ /* Filter duplicates */
+ for (j = 0, i = 1; i < n_pids; i++) {
+ if (pids[i] == pids[j])
+ continue;
+ pids[++j] = pids[i];
+ }
+ n_pids = j + 1;
+ pid_width = DECIMAL_STR_WIDTH(pids[j]);
+
+ if (flags & OUTPUT_FULL_WIDTH)
+ n_columns = 0;
+ else {
+ if (n_columns > pid_width+2)
+ n_columns -= pid_width+2;
+ else
+ n_columns = 20;
+ }
+ for (i = 0; i < n_pids; i++) {
+ _cleanup_free_ char *t = NULL;
+
+ get_process_cmdline(pids[i], n_columns, true, &t);
+
+ if (extra)
+ printf("%s%s ", prefix, draw_special_char(DRAW_TRIANGULAR_BULLET));
+ else
+ printf("%s%s", prefix, draw_special_char(((more || i < n_pids-1) ? DRAW_TREE_BRANCH : DRAW_TREE_RIGHT)));
+
+ printf("%*"PID_PRI" %s\n", pid_width, pids[i], strna(t));
+ }
+}
+
+
+static int show_cgroup_one_by_path(const char *path, const char *prefix, unsigned n_columns, bool more, bool kernel_threads, OutputFlags flags) {
+ char *fn;
+ _cleanup_fclose_ FILE *f = NULL;
+ size_t n = 0, n_allocated = 0;
+ _cleanup_free_ pid_t *pids = NULL;
+ _cleanup_free_ char *p = NULL;
+ pid_t pid;
+ int r;
+
+ r = cg_mangle_path(path, &p);
+ if (r < 0)
+ return r;
+
+ fn = strjoina(p, "/cgroup.procs");
+ f = fopen(fn, "re");
+ if (!f)
+ return -errno;
+
+ while ((r = cg_read_pid(f, &pid)) > 0) {
+
+ if (!kernel_threads && is_kernel_thread(pid) > 0)
+ continue;
+
+ if (!GREEDY_REALLOC(pids, n_allocated, n + 1))
+ return -ENOMEM;
+
+ assert(n < n_allocated);
+ pids[n++] = pid;
+ }
+
+ if (r < 0)
+ return r;
+
+ show_pid_array(pids, n, prefix, n_columns, false, more, kernel_threads, flags);
+
+ return 0;
+}
+
+int show_cgroup_by_path(const char *path, const char *prefix, unsigned n_columns, bool kernel_threads, OutputFlags flags) {
+ _cleanup_free_ char *fn = NULL, *p1 = NULL, *last = NULL, *p2 = NULL;
+ _cleanup_closedir_ DIR *d = NULL;
+ char *gn = NULL;
+ bool shown_pids = false;
+ int r;
+
+ assert(path);
+
+ if (n_columns <= 0)
+ n_columns = columns();
+
+ if (!prefix)
+ prefix = "";
+
+ r = cg_mangle_path(path, &fn);
+ if (r < 0)
+ return r;
+
+ d = opendir(fn);
+ if (!d)
+ return -errno;
+
+ while ((r = cg_read_subgroup(d, &gn)) > 0) {
+ _cleanup_free_ char *k = NULL;
+
+ k = strjoin(fn, "/", gn, NULL);
+ free(gn);
+ if (!k)
+ return -ENOMEM;
+
+ if (!(flags & OUTPUT_SHOW_ALL) && cg_is_empty_recursive(NULL, k) > 0)
+ continue;
+
+ if (!shown_pids) {
+ show_cgroup_one_by_path(path, prefix, n_columns, true, kernel_threads, flags);
+ shown_pids = true;
+ }
+
+ if (last) {
+ printf("%s%s%s\n", prefix, draw_special_char(DRAW_TREE_BRANCH), cg_unescape(basename(last)));
+
+ if (!p1) {
+ p1 = strappend(prefix, draw_special_char(DRAW_TREE_VERTICAL));
+ if (!p1)
+ return -ENOMEM;
+ }
+
+ show_cgroup_by_path(last, p1, n_columns-2, kernel_threads, flags);
+ free(last);
+ }
+
+ last = k;
+ k = NULL;
+ }
+
+ if (r < 0)
+ return r;
+
+ if (!shown_pids)
+ show_cgroup_one_by_path(path, prefix, n_columns, !!last, kernel_threads, flags);
+
+ if (last) {
+ printf("%s%s%s\n", prefix, draw_special_char(DRAW_TREE_RIGHT), cg_unescape(basename(last)));
+
+ if (!p2) {
+ p2 = strappend(prefix, " ");
+ if (!p2)
+ return -ENOMEM;
+ }
+
+ show_cgroup_by_path(last, p2, n_columns-2, kernel_threads, flags);
+ }
+
+ return 0;
+}
+
+int show_cgroup(const char *controller, const char *path, const char *prefix, unsigned n_columns, bool kernel_threads, OutputFlags flags) {
+ _cleanup_free_ char *p = NULL;
+ int r;
+
+ assert(path);
+
+ r = cg_get_path(controller, path, NULL, &p);
+ if (r < 0)
+ return r;
+
+ return show_cgroup_by_path(p, prefix, n_columns, kernel_threads, flags);
+}
+
+static int show_extra_pids(const char *controller, const char *path, const char *prefix, unsigned n_columns, const pid_t pids[], unsigned n_pids, OutputFlags flags) {
+ _cleanup_free_ pid_t *copy = NULL;
+ unsigned i, j;
+ int r;
+
+ assert(path);
+
+ if (n_pids <= 0)
+ return 0;
+
+ if (n_columns <= 0)
+ n_columns = columns();
+
+ prefix = strempty(prefix);
+
+ copy = new(pid_t, n_pids);
+ if (!copy)
+ return -ENOMEM;
+
+ for (i = 0, j = 0; i < n_pids; i++) {
+ _cleanup_free_ char *k = NULL;
+
+ r = cg_pid_get_path(controller, pids[i], &k);
+ if (r < 0)
+ return r;
+
+ if (path_startswith(k, path))
+ continue;
+
+ copy[j++] = pids[i];
+ }
+
+ show_pid_array(copy, j, prefix, n_columns, true, false, false, flags);
+
+ return 0;
+}
+
+int show_cgroup_and_extra(const char *controller, const char *path, const char *prefix, unsigned n_columns, bool kernel_threads, const pid_t extra_pids[], unsigned n_extra_pids, OutputFlags flags) {
+ int r;
+
+ assert(path);
+
+ r = show_cgroup(controller, path, prefix, n_columns, kernel_threads, flags);
+ if (r < 0)
+ return r;
+
+ return show_extra_pids(controller, path, prefix, n_columns, extra_pids, n_extra_pids, flags);
+}
+
+int show_cgroup_and_extra_by_spec(const char *spec, const char *prefix, unsigned n_columns, bool kernel_threads, const pid_t extra_pids[], unsigned n_extra_pids, OutputFlags flags) {
+ _cleanup_free_ char *controller = NULL, *path = NULL;
+ int r;
+
+ assert(spec);
+
+ r = cg_split_spec(spec, &controller, &path);
+ if (r < 0)
+ return r;
+
+ return show_cgroup_and_extra(controller, path, prefix, n_columns, kernel_threads, extra_pids, n_extra_pids, flags);
+}
diff --git a/src/libshared/cgroup-show.h b/src/libshared/cgroup-show.h
new file mode 100644
index 0000000000..3ab7dfb33c
--- /dev/null
+++ b/src/libshared/cgroup-show.h
@@ -0,0 +1,32 @@
+#pragma once
+
+/***
+ This file is part of systemd.
+
+ Copyright 2010 Lennart Poettering
+
+ systemd is free software; you can redistribute it and/or modify it
+ under the terms of the GNU Lesser General Public License as published by
+ the Free Software Foundation; either version 2.1 of the License, or
+ (at your option) any later version.
+
+ systemd is distributed in the hope that it will be useful, but
+ WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ Lesser General Public License for more details.
+
+ You should have received a copy of the GNU Lesser General Public License
+ along with systemd; If not, see <http://www.gnu.org/licenses/>.
+***/
+
+#include <stdbool.h>
+#include <sys/types.h>
+
+#include "logs-show.h"
+#include "output-mode.h"
+
+int show_cgroup_by_path(const char *path, const char *prefix, unsigned columns, bool kernel_threads, OutputFlags flags);
+int show_cgroup(const char *controller, const char *path, const char *prefix, unsigned columns, bool kernel_threads, OutputFlags flags);
+
+int show_cgroup_and_extra_by_spec(const char *spec, const char *prefix, unsigned n_columns, bool kernel_threads, const pid_t extra_pids[], unsigned n_extra_pids, OutputFlags flags);
+int show_cgroup_and_extra(const char *controller, const char *path, const char *prefix, unsigned n_columns, bool kernel_threads, const pid_t extra_pids[], unsigned n_extra_pids, OutputFlags flags);
diff --git a/src/libshared/clean-ipc.c b/src/libshared/clean-ipc.c
new file mode 100644
index 0000000000..a3ac7aeb82
--- /dev/null
+++ b/src/libshared/clean-ipc.c
@@ -0,0 +1,365 @@
+/***
+ This file is part of systemd.
+
+ Copyright 2014 Lennart Poettering
+
+ systemd is free software; you can redistribute it and/or modify it
+ under the terms of the GNU Lesser General Public License as published by
+ the Free Software Foundation; either version 2.1 of the License, or
+ (at your option) any later version.
+
+ systemd is distributed in the hope that it will be useful, but
+ WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ Lesser General Public License for more details.
+
+ You should have received a copy of the GNU Lesser General Public License
+ along with systemd; If not, see <http://www.gnu.org/licenses/>.
+***/
+
+#include <dirent.h>
+#include <errno.h>
+#include <fcntl.h>
+#include <limits.h>
+#include <mqueue.h>
+#include <stdbool.h>
+#include <stdio.h>
+#include <string.h>
+#include <sys/ipc.h>
+#include <sys/msg.h>
+#include <sys/sem.h>
+#include <sys/shm.h>
+#include <sys/stat.h>
+#include <unistd.h>
+
+#include "clean-ipc.h"
+#include "dirent-util.h"
+#include "fd-util.h"
+#include "fileio.h"
+#include "formats-util.h"
+#include "log.h"
+#include "macro.h"
+#include "string-util.h"
+#include "strv.h"
+
+static int clean_sysvipc_shm(uid_t delete_uid) {
+ _cleanup_fclose_ FILE *f = NULL;
+ char line[LINE_MAX];
+ bool first = true;
+ int ret = 0;
+
+ f = fopen("/proc/sysvipc/shm", "re");
+ if (!f) {
+ if (errno == ENOENT)
+ return 0;
+
+ return log_warning_errno(errno, "Failed to open /proc/sysvipc/shm: %m");
+ }
+
+ FOREACH_LINE(line, f, goto fail) {
+ unsigned n_attached;
+ pid_t cpid, lpid;
+ uid_t uid, cuid;
+ gid_t gid, cgid;
+ int shmid;
+
+ if (first) {
+ first = false;
+ continue;
+ }
+
+ truncate_nl(line);
+
+ if (sscanf(line, "%*i %i %*o %*u " PID_FMT " " PID_FMT " %u " UID_FMT " " GID_FMT " " UID_FMT " " GID_FMT,
+ &shmid, &cpid, &lpid, &n_attached, &uid, &gid, &cuid, &cgid) != 8)
+ continue;
+
+ if (n_attached > 0)
+ continue;
+
+ if (uid != delete_uid)
+ continue;
+
+ if (shmctl(shmid, IPC_RMID, NULL) < 0) {
+
+ /* Ignore entries that are already deleted */
+ if (errno == EIDRM || errno == EINVAL)
+ continue;
+
+ ret = log_warning_errno(errno,
+ "Failed to remove SysV shared memory segment %i: %m",
+ shmid);
+ }
+ }
+
+ return ret;
+
+fail:
+ return log_warning_errno(errno, "Failed to read /proc/sysvipc/shm: %m");
+}
+
+static int clean_sysvipc_sem(uid_t delete_uid) {
+ _cleanup_fclose_ FILE *f = NULL;
+ char line[LINE_MAX];
+ bool first = true;
+ int ret = 0;
+
+ f = fopen("/proc/sysvipc/sem", "re");
+ if (!f) {
+ if (errno == ENOENT)
+ return 0;
+
+ return log_warning_errno(errno, "Failed to open /proc/sysvipc/sem: %m");
+ }
+
+ FOREACH_LINE(line, f, goto fail) {
+ uid_t uid, cuid;
+ gid_t gid, cgid;
+ int semid;
+
+ if (first) {
+ first = false;
+ continue;
+ }
+
+ truncate_nl(line);
+
+ if (sscanf(line, "%*i %i %*o %*u " UID_FMT " " GID_FMT " " UID_FMT " " GID_FMT,
+ &semid, &uid, &gid, &cuid, &cgid) != 5)
+ continue;
+
+ if (uid != delete_uid)
+ continue;
+
+ if (semctl(semid, 0, IPC_RMID) < 0) {
+
+ /* Ignore entries that are already deleted */
+ if (errno == EIDRM || errno == EINVAL)
+ continue;
+
+ ret = log_warning_errno(errno,
+ "Failed to remove SysV semaphores object %i: %m",
+ semid);
+ }
+ }
+
+ return ret;
+
+fail:
+ return log_warning_errno(errno, "Failed to read /proc/sysvipc/sem: %m");
+}
+
+static int clean_sysvipc_msg(uid_t delete_uid) {
+ _cleanup_fclose_ FILE *f = NULL;
+ char line[LINE_MAX];
+ bool first = true;
+ int ret = 0;
+
+ f = fopen("/proc/sysvipc/msg", "re");
+ if (!f) {
+ if (errno == ENOENT)
+ return 0;
+
+ return log_warning_errno(errno, "Failed to open /proc/sysvipc/msg: %m");
+ }
+
+ FOREACH_LINE(line, f, goto fail) {
+ uid_t uid, cuid;
+ gid_t gid, cgid;
+ pid_t cpid, lpid;
+ int msgid;
+
+ if (first) {
+ first = false;
+ continue;
+ }
+
+ truncate_nl(line);
+
+ if (sscanf(line, "%*i %i %*o %*u %*u " PID_FMT " " PID_FMT " " UID_FMT " " GID_FMT " " UID_FMT " " GID_FMT,
+ &msgid, &cpid, &lpid, &uid, &gid, &cuid, &cgid) != 7)
+ continue;
+
+ if (uid != delete_uid)
+ continue;
+
+ if (msgctl(msgid, IPC_RMID, NULL) < 0) {
+
+ /* Ignore entries that are already deleted */
+ if (errno == EIDRM || errno == EINVAL)
+ continue;
+
+ ret = log_warning_errno(errno,
+ "Failed to remove SysV message queue %i: %m",
+ msgid);
+ }
+ }
+
+ return ret;
+
+fail:
+ return log_warning_errno(errno, "Failed to read /proc/sysvipc/msg: %m");
+}
+
+static int clean_posix_shm_internal(DIR *dir, uid_t uid) {
+ struct dirent *de;
+ int ret = 0, r;
+
+ assert(dir);
+
+ FOREACH_DIRENT(de, dir, goto fail) {
+ struct stat st;
+
+ if (STR_IN_SET(de->d_name, "..", "."))
+ continue;
+
+ if (fstatat(dirfd(dir), de->d_name, &st, AT_SYMLINK_NOFOLLOW) < 0) {
+ if (errno == ENOENT)
+ continue;
+
+ log_warning_errno(errno, "Failed to stat() POSIX shared memory segment %s: %m", de->d_name);
+ ret = -errno;
+ continue;
+ }
+
+ if (st.st_uid != uid)
+ continue;
+
+ if (S_ISDIR(st.st_mode)) {
+ _cleanup_closedir_ DIR *kid;
+
+ kid = xopendirat(dirfd(dir), de->d_name, O_NOFOLLOW|O_NOATIME);
+ if (!kid) {
+ if (errno != ENOENT) {
+ log_warning_errno(errno, "Failed to enter shared memory directory %s: %m", de->d_name);
+ ret = -errno;
+ }
+ } else {
+ r = clean_posix_shm_internal(kid, uid);
+ if (r < 0)
+ ret = r;
+ }
+
+ if (unlinkat(dirfd(dir), de->d_name, AT_REMOVEDIR) < 0) {
+
+ if (errno == ENOENT)
+ continue;
+
+ log_warning_errno(errno, "Failed to remove POSIX shared memory directory %s: %m", de->d_name);
+ ret = -errno;
+ }
+ } else {
+
+ if (unlinkat(dirfd(dir), de->d_name, 0) < 0) {
+
+ if (errno == ENOENT)
+ continue;
+
+ log_warning_errno(errno, "Failed to remove POSIX shared memory segment %s: %m", de->d_name);
+ ret = -errno;
+ }
+ }
+ }
+
+ return ret;
+
+fail:
+ log_warning_errno(errno, "Failed to read /dev/shm: %m");
+ return -errno;
+}
+
+static int clean_posix_shm(uid_t uid) {
+ _cleanup_closedir_ DIR *dir = NULL;
+
+ dir = opendir("/dev/shm");
+ if (!dir) {
+ if (errno == ENOENT)
+ return 0;
+
+ return log_warning_errno(errno, "Failed to open /dev/shm: %m");
+ }
+
+ return clean_posix_shm_internal(dir, uid);
+}
+
+static int clean_posix_mq(uid_t uid) {
+ _cleanup_closedir_ DIR *dir = NULL;
+ struct dirent *de;
+ int ret = 0;
+
+ dir = opendir("/dev/mqueue");
+ if (!dir) {
+ if (errno == ENOENT)
+ return 0;
+
+ return log_warning_errno(errno, "Failed to open /dev/mqueue: %m");
+ }
+
+ FOREACH_DIRENT(de, dir, goto fail) {
+ struct stat st;
+ char fn[1+strlen(de->d_name)+1];
+
+ if (STR_IN_SET(de->d_name, "..", "."))
+ continue;
+
+ if (fstatat(dirfd(dir), de->d_name, &st, AT_SYMLINK_NOFOLLOW) < 0) {
+ if (errno == ENOENT)
+ continue;
+
+ ret = log_warning_errno(errno,
+ "Failed to stat() MQ segment %s: %m",
+ de->d_name);
+ continue;
+ }
+
+ if (st.st_uid != uid)
+ continue;
+
+ fn[0] = '/';
+ strcpy(fn+1, de->d_name);
+
+ if (mq_unlink(fn) < 0) {
+ if (errno == ENOENT)
+ continue;
+
+ ret = log_warning_errno(errno,
+ "Failed to unlink POSIX message queue %s: %m",
+ fn);
+ }
+ }
+
+ return ret;
+
+fail:
+ return log_warning_errno(errno, "Failed to read /dev/mqueue: %m");
+}
+
+int clean_ipc(uid_t uid) {
+ int ret = 0, r;
+
+ /* Refuse to clean IPC of the root and system users */
+ if (uid <= SYSTEM_UID_MAX)
+ return 0;
+
+ r = clean_sysvipc_shm(uid);
+ if (r < 0)
+ ret = r;
+
+ r = clean_sysvipc_sem(uid);
+ if (r < 0)
+ ret = r;
+
+ r = clean_sysvipc_msg(uid);
+ if (r < 0)
+ ret = r;
+
+ r = clean_posix_shm(uid);
+ if (r < 0)
+ ret = r;
+
+ r = clean_posix_mq(uid);
+ if (r < 0)
+ ret = r;
+
+ return ret;
+}
diff --git a/src/libshared/clean-ipc.h b/src/libshared/clean-ipc.h
new file mode 100644
index 0000000000..44a83afcf7
--- /dev/null
+++ b/src/libshared/clean-ipc.h
@@ -0,0 +1,24 @@
+#pragma once
+
+/***
+ This file is part of systemd.
+
+ Copyright 2014 Lennart Poettering
+
+ systemd is free software; you can redistribute it and/or modify it
+ under the terms of the GNU Lesser General Public License as published by
+ the Free Software Foundation; either version 2.1 of the License, or
+ (at your option) any later version.
+
+ systemd is distributed in the hope that it will be useful, but
+ WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ Lesser General Public License for more details.
+
+ You should have received a copy of the GNU Lesser General Public License
+ along with systemd; If not, see <http://www.gnu.org/licenses/>.
+***/
+
+#include <sys/types.h>
+
+int clean_ipc(uid_t uid);
diff --git a/src/libshared/condition.c b/src/libshared/condition.c
new file mode 100644
index 0000000000..1719a4c7a6
--- /dev/null
+++ b/src/libshared/condition.c
@@ -0,0 +1,541 @@
+/***
+ This file is part of systemd.
+
+ Copyright 2010 Lennart Poettering
+
+ systemd is free software; you can redistribute it and/or modify it
+ under the terms of the GNU Lesser General Public License as published by
+ the Free Software Foundation; either version 2.1 of the License, or
+ (at your option) any later version.
+
+ systemd is distributed in the hope that it will be useful, but
+ WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ Lesser General Public License for more details.
+
+ You should have received a copy of the GNU Lesser General Public License
+ along with systemd; If not, see <http://www.gnu.org/licenses/>.
+***/
+
+#include <errno.h>
+#include <fcntl.h>
+#include <fnmatch.h>
+#include <limits.h>
+#include <stdlib.h>
+#include <string.h>
+#include <sys/stat.h>
+#include <time.h>
+#include <unistd.h>
+
+#include <systemd/sd-id128.h>
+
+#include "alloc-util.h"
+#include "apparmor-util.h"
+#include "architecture.h"
+#include "audit-util.h"
+#include "cap-list.h"
+#include "condition.h"
+#include "extract-word.h"
+#include "fd-util.h"
+#include "glob-util.h"
+#include "hostname-util.h"
+#include "ima-util.h"
+#include "list.h"
+#include "macro.h"
+#include "mount-util.h"
+#include "parse-util.h"
+#include "path-util.h"
+#include "proc-cmdline.h"
+#include "selinux-util.h"
+#include "smack-util.h"
+#include "stat-util.h"
+#include "string-table.h"
+#include "string-util.h"
+#include "util.h"
+#include "virt.h"
+
+Condition* condition_new(ConditionType type, const char *parameter, bool trigger, bool negate) {
+ Condition *c;
+ int r;
+
+ assert(type >= 0);
+ assert(type < _CONDITION_TYPE_MAX);
+ assert((!parameter) == (type == CONDITION_NULL));
+
+ c = new0(Condition, 1);
+ if (!c)
+ return NULL;
+
+ c->type = type;
+ c->trigger = trigger;
+ c->negate = negate;
+
+ r = free_and_strdup(&c->parameter, parameter);
+ if (r < 0) {
+ free(c);
+ return NULL;
+ }
+
+ return c;
+}
+
+void condition_free(Condition *c) {
+ assert(c);
+
+ free(c->parameter);
+ free(c);
+}
+
+Condition* condition_free_list(Condition *first) {
+ Condition *c, *n;
+
+ LIST_FOREACH_SAFE(conditions, c, n, first)
+ condition_free(c);
+
+ return NULL;
+}
+
+static int condition_test_kernel_command_line(Condition *c) {
+ _cleanup_free_ char *line = NULL;
+ const char *p;
+ bool equal;
+ int r;
+
+ assert(c);
+ assert(c->parameter);
+ assert(c->type == CONDITION_KERNEL_COMMAND_LINE);
+
+ r = proc_cmdline(&line);
+ if (r < 0)
+ return r;
+
+ equal = !!strchr(c->parameter, '=');
+ p = line;
+
+ for (;;) {
+ _cleanup_free_ char *word = NULL;
+ bool found;
+
+ r = extract_first_word(&p, &word, NULL, EXTRACT_QUOTES|EXTRACT_RELAX);
+ if (r < 0)
+ return r;
+ if (r == 0)
+ break;
+
+ if (equal)
+ found = streq(word, c->parameter);
+ else {
+ const char *f;
+
+ f = startswith(word, c->parameter);
+ found = f && (*f == '=' || *f == 0);
+ }
+
+ if (found)
+ return true;
+ }
+
+ return false;
+}
+
+static int condition_test_virtualization(Condition *c) {
+ int b, v;
+
+ assert(c);
+ assert(c->parameter);
+ assert(c->type == CONDITION_VIRTUALIZATION);
+
+ v = detect_virtualization();
+ if (v < 0)
+ return v;
+
+ /* First, compare with yes/no */
+ b = parse_boolean(c->parameter);
+
+ if (v > 0 && b > 0)
+ return true;
+
+ if (v == 0 && b == 0)
+ return true;
+
+ /* Then, compare categorization */
+ if (VIRTUALIZATION_IS_VM(v) && streq(c->parameter, "vm"))
+ return true;
+
+ if (VIRTUALIZATION_IS_CONTAINER(v) && streq(c->parameter, "container"))
+ return true;
+
+ /* Finally compare id */
+ return v != VIRTUALIZATION_NONE && streq(c->parameter, virtualization_to_string(v));
+}
+
+static int condition_test_architecture(Condition *c) {
+ int a, b;
+
+ assert(c);
+ assert(c->parameter);
+ assert(c->type == CONDITION_ARCHITECTURE);
+
+ a = uname_architecture();
+ if (a < 0)
+ return a;
+
+ if (streq(c->parameter, "native"))
+ b = native_architecture();
+ else
+ b = architecture_from_string(c->parameter);
+ if (b < 0)
+ return b;
+
+ return a == b;
+}
+
+static int condition_test_host(Condition *c) {
+ _cleanup_free_ char *h = NULL;
+ sd_id128_t x, y;
+ int r;
+
+ assert(c);
+ assert(c->parameter);
+ assert(c->type == CONDITION_HOST);
+
+ if (sd_id128_from_string(c->parameter, &x) >= 0) {
+
+ r = sd_id128_get_machine(&y);
+ if (r < 0)
+ return r;
+
+ return sd_id128_equal(x, y);
+ }
+
+ h = gethostname_malloc();
+ if (!h)
+ return -ENOMEM;
+
+ return fnmatch(c->parameter, h, FNM_CASEFOLD) == 0;
+}
+
+static int condition_test_ac_power(Condition *c) {
+ int r;
+
+ assert(c);
+ assert(c->parameter);
+ assert(c->type == CONDITION_AC_POWER);
+
+ r = parse_boolean(c->parameter);
+ if (r < 0)
+ return r;
+
+ return (on_ac_power() != 0) == !!r;
+}
+
+static int condition_test_security(Condition *c) {
+ assert(c);
+ assert(c->parameter);
+ assert(c->type == CONDITION_SECURITY);
+
+ if (streq(c->parameter, "selinux"))
+ return mac_selinux_have();
+ if (streq(c->parameter, "smack"))
+ return mac_smack_use();
+ if (streq(c->parameter, "apparmor"))
+ return mac_apparmor_use();
+ if (streq(c->parameter, "audit"))
+ return use_audit();
+ if (streq(c->parameter, "ima"))
+ return use_ima();
+
+ return false;
+}
+
+static int condition_test_capability(Condition *c) {
+ _cleanup_fclose_ FILE *f = NULL;
+ int value;
+ char line[LINE_MAX];
+ unsigned long long capabilities = -1;
+
+ assert(c);
+ assert(c->parameter);
+ assert(c->type == CONDITION_CAPABILITY);
+
+ /* If it's an invalid capability, we don't have it */
+ value = capability_from_name(c->parameter);
+ if (value < 0)
+ return -EINVAL;
+
+ /* If it's a valid capability we default to assume
+ * that we have it */
+
+ f = fopen("/proc/self/status", "re");
+ if (!f)
+ return -errno;
+
+ while (fgets(line, sizeof(line), f)) {
+ truncate_nl(line);
+
+ if (startswith(line, "CapBnd:")) {
+ (void) sscanf(line+7, "%llx", &capabilities);
+ break;
+ }
+ }
+
+ return !!(capabilities & (1ULL << value));
+}
+
+static int condition_test_needs_update(Condition *c) {
+ const char *p;
+ struct stat usr, other;
+
+ assert(c);
+ assert(c->parameter);
+ assert(c->type == CONDITION_NEEDS_UPDATE);
+
+ /* If the file system is read-only we shouldn't suggest an update */
+ if (path_is_read_only_fs(c->parameter) > 0)
+ return false;
+
+ /* Any other failure means we should allow the condition to be true,
+ * so that we rather invoke too many update tools then too
+ * few. */
+
+ if (!path_is_absolute(c->parameter))
+ return true;
+
+ p = strjoina(c->parameter, "/.updated");
+ if (lstat(p, &other) < 0)
+ return true;
+
+ if (lstat("/usr/", &usr) < 0)
+ return true;
+
+ return usr.st_mtim.tv_sec > other.st_mtim.tv_sec ||
+ (usr.st_mtim.tv_sec == other.st_mtim.tv_sec && usr.st_mtim.tv_nsec > other.st_mtim.tv_nsec);
+}
+
+static int condition_test_first_boot(Condition *c) {
+ int r;
+
+ assert(c);
+ assert(c->parameter);
+ assert(c->type == CONDITION_FIRST_BOOT);
+
+ r = parse_boolean(c->parameter);
+ if (r < 0)
+ return r;
+
+ return (access("/run/systemd/first-boot", F_OK) >= 0) == !!r;
+}
+
+static int condition_test_path_exists(Condition *c) {
+ assert(c);
+ assert(c->parameter);
+ assert(c->type == CONDITION_PATH_EXISTS);
+
+ return access(c->parameter, F_OK) >= 0;
+}
+
+static int condition_test_path_exists_glob(Condition *c) {
+ assert(c);
+ assert(c->parameter);
+ assert(c->type == CONDITION_PATH_EXISTS_GLOB);
+
+ return glob_exists(c->parameter) > 0;
+}
+
+static int condition_test_path_is_directory(Condition *c) {
+ assert(c);
+ assert(c->parameter);
+ assert(c->type == CONDITION_PATH_IS_DIRECTORY);
+
+ return is_dir(c->parameter, true) > 0;
+}
+
+static int condition_test_path_is_symbolic_link(Condition *c) {
+ assert(c);
+ assert(c->parameter);
+ assert(c->type == CONDITION_PATH_IS_SYMBOLIC_LINK);
+
+ return is_symlink(c->parameter) > 0;
+}
+
+static int condition_test_path_is_mount_point(Condition *c) {
+ assert(c);
+ assert(c->parameter);
+ assert(c->type == CONDITION_PATH_IS_MOUNT_POINT);
+
+ return path_is_mount_point(c->parameter, AT_SYMLINK_FOLLOW) > 0;
+}
+
+static int condition_test_path_is_read_write(Condition *c) {
+ assert(c);
+ assert(c->parameter);
+ assert(c->type == CONDITION_PATH_IS_READ_WRITE);
+
+ return path_is_read_only_fs(c->parameter) <= 0;
+}
+
+static int condition_test_directory_not_empty(Condition *c) {
+ int r;
+
+ assert(c);
+ assert(c->parameter);
+ assert(c->type == CONDITION_DIRECTORY_NOT_EMPTY);
+
+ r = dir_is_empty(c->parameter);
+ return r <= 0 && r != -ENOENT;
+}
+
+static int condition_test_file_not_empty(Condition *c) {
+ struct stat st;
+
+ assert(c);
+ assert(c->parameter);
+ assert(c->type == CONDITION_FILE_NOT_EMPTY);
+
+ return (stat(c->parameter, &st) >= 0 &&
+ S_ISREG(st.st_mode) &&
+ st.st_size > 0);
+}
+
+static int condition_test_file_is_executable(Condition *c) {
+ struct stat st;
+
+ assert(c);
+ assert(c->parameter);
+ assert(c->type == CONDITION_FILE_IS_EXECUTABLE);
+
+ return (stat(c->parameter, &st) >= 0 &&
+ S_ISREG(st.st_mode) &&
+ (st.st_mode & 0111));
+}
+
+static int condition_test_null(Condition *c) {
+ assert(c);
+ assert(c->type == CONDITION_NULL);
+
+ /* Note that during parsing we already evaluate the string and
+ * store it in c->negate */
+ return true;
+}
+
+int condition_test(Condition *c) {
+
+ static int (*const condition_tests[_CONDITION_TYPE_MAX])(Condition *c) = {
+ [CONDITION_PATH_EXISTS] = condition_test_path_exists,
+ [CONDITION_PATH_EXISTS_GLOB] = condition_test_path_exists_glob,
+ [CONDITION_PATH_IS_DIRECTORY] = condition_test_path_is_directory,
+ [CONDITION_PATH_IS_SYMBOLIC_LINK] = condition_test_path_is_symbolic_link,
+ [CONDITION_PATH_IS_MOUNT_POINT] = condition_test_path_is_mount_point,
+ [CONDITION_PATH_IS_READ_WRITE] = condition_test_path_is_read_write,
+ [CONDITION_DIRECTORY_NOT_EMPTY] = condition_test_directory_not_empty,
+ [CONDITION_FILE_NOT_EMPTY] = condition_test_file_not_empty,
+ [CONDITION_FILE_IS_EXECUTABLE] = condition_test_file_is_executable,
+ [CONDITION_KERNEL_COMMAND_LINE] = condition_test_kernel_command_line,
+ [CONDITION_VIRTUALIZATION] = condition_test_virtualization,
+ [CONDITION_SECURITY] = condition_test_security,
+ [CONDITION_CAPABILITY] = condition_test_capability,
+ [CONDITION_HOST] = condition_test_host,
+ [CONDITION_AC_POWER] = condition_test_ac_power,
+ [CONDITION_ARCHITECTURE] = condition_test_architecture,
+ [CONDITION_NEEDS_UPDATE] = condition_test_needs_update,
+ [CONDITION_FIRST_BOOT] = condition_test_first_boot,
+ [CONDITION_NULL] = condition_test_null,
+ };
+
+ int r, b;
+
+ assert(c);
+ assert(c->type >= 0);
+ assert(c->type < _CONDITION_TYPE_MAX);
+
+ r = condition_tests[c->type](c);
+ if (r < 0) {
+ c->result = CONDITION_ERROR;
+ return r;
+ }
+
+ b = (r > 0) == !c->negate;
+ c->result = b ? CONDITION_SUCCEEDED : CONDITION_FAILED;
+ return b;
+}
+
+void condition_dump(Condition *c, FILE *f, const char *prefix, const char *(*to_string)(ConditionType t)) {
+ assert(c);
+ assert(f);
+
+ if (!prefix)
+ prefix = "";
+
+ fprintf(f,
+ "%s\t%s: %s%s%s %s\n",
+ prefix,
+ to_string(c->type),
+ c->trigger ? "|" : "",
+ c->negate ? "!" : "",
+ c->parameter,
+ condition_result_to_string(c->result));
+}
+
+void condition_dump_list(Condition *first, FILE *f, const char *prefix, const char *(*to_string)(ConditionType t)) {
+ Condition *c;
+
+ LIST_FOREACH(conditions, c, first)
+ condition_dump(c, f, prefix, to_string);
+}
+
+static const char* const condition_type_table[_CONDITION_TYPE_MAX] = {
+ [CONDITION_ARCHITECTURE] = "ConditionArchitecture",
+ [CONDITION_VIRTUALIZATION] = "ConditionVirtualization",
+ [CONDITION_HOST] = "ConditionHost",
+ [CONDITION_KERNEL_COMMAND_LINE] = "ConditionKernelCommandLine",
+ [CONDITION_SECURITY] = "ConditionSecurity",
+ [CONDITION_CAPABILITY] = "ConditionCapability",
+ [CONDITION_AC_POWER] = "ConditionACPower",
+ [CONDITION_NEEDS_UPDATE] = "ConditionNeedsUpdate",
+ [CONDITION_FIRST_BOOT] = "ConditionFirstBoot",
+ [CONDITION_PATH_EXISTS] = "ConditionPathExists",
+ [CONDITION_PATH_EXISTS_GLOB] = "ConditionPathExistsGlob",
+ [CONDITION_PATH_IS_DIRECTORY] = "ConditionPathIsDirectory",
+ [CONDITION_PATH_IS_SYMBOLIC_LINK] = "ConditionPathIsSymbolicLink",
+ [CONDITION_PATH_IS_MOUNT_POINT] = "ConditionPathIsMountPoint",
+ [CONDITION_PATH_IS_READ_WRITE] = "ConditionPathIsReadWrite",
+ [CONDITION_DIRECTORY_NOT_EMPTY] = "ConditionDirectoryNotEmpty",
+ [CONDITION_FILE_NOT_EMPTY] = "ConditionFileNotEmpty",
+ [CONDITION_FILE_IS_EXECUTABLE] = "ConditionFileIsExecutable",
+ [CONDITION_NULL] = "ConditionNull"
+};
+
+DEFINE_STRING_TABLE_LOOKUP(condition_type, ConditionType);
+
+static const char* const assert_type_table[_CONDITION_TYPE_MAX] = {
+ [CONDITION_ARCHITECTURE] = "AssertArchitecture",
+ [CONDITION_VIRTUALIZATION] = "AssertVirtualization",
+ [CONDITION_HOST] = "AssertHost",
+ [CONDITION_KERNEL_COMMAND_LINE] = "AssertKernelCommandLine",
+ [CONDITION_SECURITY] = "AssertSecurity",
+ [CONDITION_CAPABILITY] = "AssertCapability",
+ [CONDITION_AC_POWER] = "AssertACPower",
+ [CONDITION_NEEDS_UPDATE] = "AssertNeedsUpdate",
+ [CONDITION_FIRST_BOOT] = "AssertFirstBoot",
+ [CONDITION_PATH_EXISTS] = "AssertPathExists",
+ [CONDITION_PATH_EXISTS_GLOB] = "AssertPathExistsGlob",
+ [CONDITION_PATH_IS_DIRECTORY] = "AssertPathIsDirectory",
+ [CONDITION_PATH_IS_SYMBOLIC_LINK] = "AssertPathIsSymbolicLink",
+ [CONDITION_PATH_IS_MOUNT_POINT] = "AssertPathIsMountPoint",
+ [CONDITION_PATH_IS_READ_WRITE] = "AssertPathIsReadWrite",
+ [CONDITION_DIRECTORY_NOT_EMPTY] = "AssertDirectoryNotEmpty",
+ [CONDITION_FILE_NOT_EMPTY] = "AssertFileNotEmpty",
+ [CONDITION_FILE_IS_EXECUTABLE] = "AssertFileIsExecutable",
+ [CONDITION_NULL] = "AssertNull"
+};
+
+DEFINE_STRING_TABLE_LOOKUP(assert_type, ConditionType);
+
+static const char* const condition_result_table[_CONDITION_RESULT_MAX] = {
+ [CONDITION_UNTESTED] = "untested",
+ [CONDITION_SUCCEEDED] = "succeeded",
+ [CONDITION_FAILED] = "failed",
+ [CONDITION_ERROR] = "error",
+};
+
+DEFINE_STRING_TABLE_LOOKUP(condition_result, ConditionResult);
diff --git a/src/libshared/condition.h b/src/libshared/condition.h
new file mode 100644
index 0000000000..bdda04b770
--- /dev/null
+++ b/src/libshared/condition.h
@@ -0,0 +1,94 @@
+#pragma once
+
+/***
+ This file is part of systemd.
+
+ Copyright 2010 Lennart Poettering
+
+ systemd is free software; you can redistribute it and/or modify it
+ under the terms of the GNU Lesser General Public License as published by
+ the Free Software Foundation; either version 2.1 of the License, or
+ (at your option) any later version.
+
+ systemd is distributed in the hope that it will be useful, but
+ WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ Lesser General Public License for more details.
+
+ You should have received a copy of the GNU Lesser General Public License
+ along with systemd; If not, see <http://www.gnu.org/licenses/>.
+***/
+
+#include <stdbool.h>
+#include <stdio.h>
+
+#include "list.h"
+#include "macro.h"
+
+typedef enum ConditionType {
+ CONDITION_ARCHITECTURE,
+ CONDITION_VIRTUALIZATION,
+ CONDITION_HOST,
+ CONDITION_KERNEL_COMMAND_LINE,
+ CONDITION_SECURITY,
+ CONDITION_CAPABILITY,
+ CONDITION_AC_POWER,
+
+ CONDITION_NEEDS_UPDATE,
+ CONDITION_FIRST_BOOT,
+
+ CONDITION_PATH_EXISTS,
+ CONDITION_PATH_EXISTS_GLOB,
+ CONDITION_PATH_IS_DIRECTORY,
+ CONDITION_PATH_IS_SYMBOLIC_LINK,
+ CONDITION_PATH_IS_MOUNT_POINT,
+ CONDITION_PATH_IS_READ_WRITE,
+ CONDITION_DIRECTORY_NOT_EMPTY,
+ CONDITION_FILE_NOT_EMPTY,
+ CONDITION_FILE_IS_EXECUTABLE,
+
+ CONDITION_NULL,
+
+ _CONDITION_TYPE_MAX,
+ _CONDITION_TYPE_INVALID = -1
+} ConditionType;
+
+typedef enum ConditionResult {
+ CONDITION_UNTESTED,
+ CONDITION_SUCCEEDED,
+ CONDITION_FAILED,
+ CONDITION_ERROR,
+ _CONDITION_RESULT_MAX,
+ _CONDITION_RESULT_INVALID = -1
+} ConditionResult;
+
+typedef struct Condition {
+ ConditionType type:8;
+
+ bool trigger:1;
+ bool negate:1;
+
+ ConditionResult result:6;
+
+ char *parameter;
+
+ LIST_FIELDS(struct Condition, conditions);
+} Condition;
+
+Condition* condition_new(ConditionType type, const char *parameter, bool trigger, bool negate);
+void condition_free(Condition *c);
+Condition* condition_free_list(Condition *c);
+
+int condition_test(Condition *c);
+
+void condition_dump(Condition *c, FILE *f, const char *prefix, const char *(*to_string)(ConditionType t));
+void condition_dump_list(Condition *c, FILE *f, const char *prefix, const char *(*to_string)(ConditionType t));
+
+const char* condition_type_to_string(ConditionType t) _const_;
+ConditionType condition_type_from_string(const char *s) _pure_;
+
+const char* assert_type_to_string(ConditionType t) _const_;
+ConditionType assert_type_from_string(const char *s) _pure_;
+
+const char* condition_result_to_string(ConditionResult r) _const_;
+ConditionResult condition_result_from_string(const char *s) _pure_;
diff --git a/src/libshared/conf-parser.c b/src/libshared/conf-parser.c
new file mode 100644
index 0000000000..e7fe9ac21e
--- /dev/null
+++ b/src/libshared/conf-parser.c
@@ -0,0 +1,870 @@
+/***
+ This file is part of systemd.
+
+ Copyright 2010 Lennart Poettering
+
+ systemd is free software; you can redistribute it and/or modify it
+ under the terms of the GNU Lesser General Public License as published by
+ the Free Software Foundation; either version 2.1 of the License, or
+ (at your option) any later version.
+
+ systemd is distributed in the hope that it will be useful, but
+ WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ Lesser General Public License for more details.
+
+ You should have received a copy of the GNU Lesser General Public License
+ along with systemd; If not, see <http://www.gnu.org/licenses/>.
+***/
+
+#include <errno.h>
+#include <limits.h>
+#include <stdint.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <sys/types.h>
+
+#include "alloc-util.h"
+#include "conf-files.h"
+#include "conf-parser.h"
+#include "extract-word.h"
+#include "fd-util.h"
+#include "fs-util.h"
+#include "log.h"
+#include "macro.h"
+#include "parse-util.h"
+#include "path-util.h"
+#include "process-util.h"
+#include "signal-util.h"
+#include "string-util.h"
+#include "strv.h"
+#include "syslog-util.h"
+#include "time-util.h"
+#include "utf8.h"
+
+int config_item_table_lookup(
+ const void *table,
+ const char *section,
+ const char *lvalue,
+ ConfigParserCallback *func,
+ int *ltype,
+ void **data,
+ void *userdata) {
+
+ const ConfigTableItem *t;
+
+ assert(table);
+ assert(lvalue);
+ assert(func);
+ assert(ltype);
+ assert(data);
+
+ for (t = table; t->lvalue; t++) {
+
+ if (!streq(lvalue, t->lvalue))
+ continue;
+
+ if (!streq_ptr(section, t->section))
+ continue;
+
+ *func = t->parse;
+ *ltype = t->ltype;
+ *data = t->data;
+ return 1;
+ }
+
+ return 0;
+}
+
+int config_item_perf_lookup(
+ const void *table,
+ const char *section,
+ const char *lvalue,
+ ConfigParserCallback *func,
+ int *ltype,
+ void **data,
+ void *userdata) {
+
+ ConfigPerfItemLookup lookup = (ConfigPerfItemLookup) table;
+ const ConfigPerfItem *p;
+
+ assert(table);
+ assert(lvalue);
+ assert(func);
+ assert(ltype);
+ assert(data);
+
+ if (!section)
+ p = lookup(lvalue, strlen(lvalue));
+ else {
+ char *key;
+
+ key = strjoin(section, ".", lvalue, NULL);
+ if (!key)
+ return -ENOMEM;
+
+ p = lookup(key, strlen(key));
+ free(key);
+ }
+
+ if (!p)
+ return 0;
+
+ *func = p->parse;
+ *ltype = p->ltype;
+ *data = (uint8_t*) userdata + p->offset;
+ return 1;
+}
+
+/* Run the user supplied parser for an assignment */
+static int next_assignment(const char *unit,
+ const char *filename,
+ unsigned line,
+ ConfigItemLookup lookup,
+ const void *table,
+ const char *section,
+ unsigned section_line,
+ const char *lvalue,
+ const char *rvalue,
+ bool relaxed,
+ void *userdata) {
+
+ ConfigParserCallback func = NULL;
+ int ltype = 0;
+ void *data = NULL;
+ int r;
+
+ assert(filename);
+ assert(line > 0);
+ assert(lookup);
+ assert(lvalue);
+ assert(rvalue);
+
+ r = lookup(table, section, lvalue, &func, &ltype, &data, userdata);
+ if (r < 0)
+ return r;
+
+ if (r > 0) {
+ if (func)
+ return func(unit, filename, line, section, section_line,
+ lvalue, ltype, rvalue, data, userdata);
+
+ return 0;
+ }
+
+ /* Warn about unknown non-extension fields. */
+ if (!relaxed && !startswith(lvalue, "X-"))
+ log_syntax(unit, LOG_WARNING, filename, line, 0, "Unknown lvalue '%s' in section '%s'", lvalue, section);
+
+ return 0;
+}
+
+/* Parse a variable assignment line */
+static int parse_line(const char* unit,
+ const char *filename,
+ unsigned line,
+ const char *sections,
+ ConfigItemLookup lookup,
+ const void *table,
+ bool relaxed,
+ bool allow_include,
+ char **section,
+ unsigned *section_line,
+ bool *section_ignored,
+ char *l,
+ void *userdata) {
+
+ char *e;
+
+ assert(filename);
+ assert(line > 0);
+ assert(lookup);
+ assert(l);
+
+ l = strstrip(l);
+
+ if (!*l)
+ return 0;
+
+ if (strchr(COMMENTS "\n", *l))
+ return 0;
+
+ if (startswith(l, ".include ")) {
+ _cleanup_free_ char *fn = NULL;
+
+ /* .includes are a bad idea, we only support them here
+ * for historical reasons. They create cyclic include
+ * problems and make it difficult to detect
+ * configuration file changes with an easy
+ * stat(). Better approaches, such as .d/ drop-in
+ * snippets exist.
+ *
+ * Support for them should be eventually removed. */
+
+ if (!allow_include) {
+ log_syntax(unit, LOG_ERR, filename, line, 0, ".include not allowed here. Ignoring.");
+ return 0;
+ }
+
+ fn = file_in_same_dir(filename, strstrip(l+9));
+ if (!fn)
+ return -ENOMEM;
+
+ return config_parse(unit, fn, NULL, sections, lookup, table, relaxed, false, false, userdata);
+ }
+
+ if (*l == '[') {
+ size_t k;
+ char *n;
+
+ k = strlen(l);
+ assert(k > 0);
+
+ if (l[k-1] != ']') {
+ log_syntax(unit, LOG_ERR, filename, line, 0, "Invalid section header '%s'", l);
+ return -EBADMSG;
+ }
+
+ n = strndup(l+1, k-2);
+ if (!n)
+ return -ENOMEM;
+
+ if (sections && !nulstr_contains(sections, n)) {
+
+ if (!relaxed && !startswith(n, "X-"))
+ log_syntax(unit, LOG_WARNING, filename, line, 0, "Unknown section '%s'. Ignoring.", n);
+
+ free(n);
+ *section = mfree(*section);
+ *section_line = 0;
+ *section_ignored = true;
+ } else {
+ free(*section);
+ *section = n;
+ *section_line = line;
+ *section_ignored = false;
+ }
+
+ return 0;
+ }
+
+ if (sections && !*section) {
+
+ if (!relaxed && !*section_ignored)
+ log_syntax(unit, LOG_WARNING, filename, line, 0, "Assignment outside of section. Ignoring.");
+
+ return 0;
+ }
+
+ e = strchr(l, '=');
+ if (!e) {
+ log_syntax(unit, LOG_WARNING, filename, line, 0, "Missing '='.");
+ return -EINVAL;
+ }
+
+ *e = 0;
+ e++;
+
+ return next_assignment(unit,
+ filename,
+ line,
+ lookup,
+ table,
+ *section,
+ *section_line,
+ strstrip(l),
+ strstrip(e),
+ relaxed,
+ userdata);
+}
+
+/* Go through the file and parse each line */
+int config_parse(const char *unit,
+ const char *filename,
+ FILE *f,
+ const char *sections,
+ ConfigItemLookup lookup,
+ const void *table,
+ bool relaxed,
+ bool allow_include,
+ bool warn,
+ void *userdata) {
+
+ _cleanup_free_ char *section = NULL, *continuation = NULL;
+ _cleanup_fclose_ FILE *ours = NULL;
+ unsigned line = 0, section_line = 0;
+ bool section_ignored = false;
+ int r;
+
+ assert(filename);
+ assert(lookup);
+
+ if (!f) {
+ f = ours = fopen(filename, "re");
+ if (!f) {
+ /* Only log on request, except for ENOENT,
+ * since we return 0 to the caller. */
+ if (warn || errno == ENOENT)
+ log_full(errno == ENOENT ? LOG_DEBUG : LOG_ERR,
+ "Failed to open configuration file '%s': %m", filename);
+ return errno == ENOENT ? 0 : -errno;
+ }
+ }
+
+ fd_warn_permissions(filename, fileno(f));
+
+ while (!feof(f)) {
+ char l[LINE_MAX], *p, *c = NULL, *e;
+ bool escaped = false;
+
+ if (!fgets(l, sizeof(l), f)) {
+ if (feof(f))
+ break;
+
+ log_error_errno(errno, "Failed to read configuration file '%s': %m", filename);
+ return -errno;
+ }
+
+ truncate_nl(l);
+
+ if (continuation) {
+ c = strappend(continuation, l);
+ if (!c) {
+ if (warn)
+ log_oom();
+ return -ENOMEM;
+ }
+
+ continuation = mfree(continuation);
+ p = c;
+ } else
+ p = l;
+
+ for (e = p; *e; e++) {
+ if (escaped)
+ escaped = false;
+ else if (*e == '\\')
+ escaped = true;
+ }
+
+ if (escaped) {
+ *(e-1) = ' ';
+
+ if (c)
+ continuation = c;
+ else {
+ continuation = strdup(l);
+ if (!continuation) {
+ if (warn)
+ log_oom();
+ return -ENOMEM;
+ }
+ }
+
+ continue;
+ }
+
+ r = parse_line(unit,
+ filename,
+ ++line,
+ sections,
+ lookup,
+ table,
+ relaxed,
+ allow_include,
+ &section,
+ &section_line,
+ &section_ignored,
+ p,
+ userdata);
+ free(c);
+
+ if (r < 0) {
+ if (warn)
+ log_warning_errno(r, "Failed to parse file '%s': %m",
+ filename);
+ return r;
+ }
+ }
+
+ return 0;
+}
+
+/* Parse each config file in the specified directories. */
+int config_parse_many(const char *conf_file,
+ const char *conf_file_dirs,
+ const char *sections,
+ ConfigItemLookup lookup,
+ const void *table,
+ bool relaxed,
+ void *userdata) {
+ _cleanup_strv_free_ char **files = NULL;
+ char **fn;
+ int r;
+
+ r = conf_files_list_nulstr(&files, ".conf", NULL, conf_file_dirs);
+ if (r < 0)
+ return r;
+
+ if (conf_file) {
+ r = config_parse(NULL, conf_file, NULL, sections, lookup, table, relaxed, false, true, userdata);
+ if (r < 0)
+ return r;
+ }
+
+ STRV_FOREACH(fn, files) {
+ r = config_parse(NULL, *fn, NULL, sections, lookup, table, relaxed, false, true, userdata);
+ if (r < 0)
+ return r;
+ }
+
+ return 0;
+}
+
+#define DEFINE_PARSER(type, vartype, conv_func) \
+ int config_parse_##type( \
+ const char *unit, \
+ const char *filename, \
+ unsigned line, \
+ const char *section, \
+ unsigned section_line, \
+ const char *lvalue, \
+ int ltype, \
+ const char *rvalue, \
+ void *data, \
+ void *userdata) { \
+ \
+ vartype *i = data; \
+ int r; \
+ \
+ assert(filename); \
+ assert(lvalue); \
+ assert(rvalue); \
+ assert(data); \
+ \
+ r = conv_func(rvalue, i); \
+ if (r < 0) \
+ log_syntax(unit, LOG_ERR, filename, line, r, \
+ "Failed to parse %s value, ignoring: %s", \
+ #type, rvalue); \
+ \
+ return 0; \
+ } \
+ struct __useless_struct_to_allow_trailing_semicolon__
+
+DEFINE_PARSER(int, int, safe_atoi);
+DEFINE_PARSER(long, long, safe_atoli);
+DEFINE_PARSER(uint32, uint32_t, safe_atou32);
+DEFINE_PARSER(uint64, uint64_t, safe_atou64);
+DEFINE_PARSER(unsigned, unsigned, safe_atou);
+DEFINE_PARSER(double, double, safe_atod);
+DEFINE_PARSER(nsec, nsec_t, parse_nsec);
+DEFINE_PARSER(sec, usec_t, parse_sec);
+DEFINE_PARSER(mode, mode_t, parse_mode);
+
+int config_parse_iec_size(const char* unit,
+ const char *filename,
+ unsigned line,
+ const char *section,
+ unsigned section_line,
+ const char *lvalue,
+ int ltype,
+ const char *rvalue,
+ void *data,
+ void *userdata) {
+
+ size_t *sz = data;
+ uint64_t v;
+ int r;
+
+ assert(filename);
+ assert(lvalue);
+ assert(rvalue);
+ assert(data);
+
+ r = parse_size(rvalue, 1024, &v);
+ if (r < 0 || (uint64_t) (size_t) v != v) {
+ log_syntax(unit, LOG_ERR, filename, line, r, "Failed to parse size value, ignoring: %s", rvalue);
+ return 0;
+ }
+
+ *sz = (size_t) v;
+ return 0;
+}
+
+int config_parse_si_size(const char* unit,
+ const char *filename,
+ unsigned line,
+ const char *section,
+ unsigned section_line,
+ const char *lvalue,
+ int ltype,
+ const char *rvalue,
+ void *data,
+ void *userdata) {
+
+ size_t *sz = data;
+ uint64_t v;
+ int r;
+
+ assert(filename);
+ assert(lvalue);
+ assert(rvalue);
+ assert(data);
+
+ r = parse_size(rvalue, 1000, &v);
+ if (r < 0 || (uint64_t) (size_t) v != v) {
+ log_syntax(unit, LOG_ERR, filename, line, r, "Failed to parse size value, ignoring: %s", rvalue);
+ return 0;
+ }
+
+ *sz = (size_t) v;
+ return 0;
+}
+
+int config_parse_iec_uint64(const char* unit,
+ const char *filename,
+ unsigned line,
+ const char *section,
+ unsigned section_line,
+ const char *lvalue,
+ int ltype,
+ const char *rvalue,
+ void *data,
+ void *userdata) {
+
+ uint64_t *bytes = data;
+ int r;
+
+ assert(filename);
+ assert(lvalue);
+ assert(rvalue);
+ assert(data);
+
+ r = parse_size(rvalue, 1024, bytes);
+ if (r < 0)
+ log_syntax(unit, LOG_ERR, filename, line, r, "Failed to parse size value, ignoring: %s", rvalue);
+
+ return 0;
+}
+
+int config_parse_bool(const char* unit,
+ const char *filename,
+ unsigned line,
+ const char *section,
+ unsigned section_line,
+ const char *lvalue,
+ int ltype,
+ const char *rvalue,
+ void *data,
+ void *userdata) {
+
+ int k;
+ bool *b = data;
+
+ assert(filename);
+ assert(lvalue);
+ assert(rvalue);
+ assert(data);
+
+ k = parse_boolean(rvalue);
+ if (k < 0) {
+ log_syntax(unit, LOG_ERR, filename, line, k, "Failed to parse boolean value, ignoring: %s", rvalue);
+ return 0;
+ }
+
+ *b = !!k;
+ return 0;
+}
+
+int config_parse_tristate(
+ const char* unit,
+ const char *filename,
+ unsigned line,
+ const char *section,
+ unsigned section_line,
+ const char *lvalue,
+ int ltype,
+ const char *rvalue,
+ void *data,
+ void *userdata) {
+
+ int k, *t = data;
+
+ assert(filename);
+ assert(lvalue);
+ assert(rvalue);
+ assert(data);
+
+ /* A tristate is pretty much a boolean, except that it can
+ * also take the special value -1, indicating "uninitialized",
+ * much like NULL is for a pointer type. */
+
+ k = parse_boolean(rvalue);
+ if (k < 0) {
+ log_syntax(unit, LOG_ERR, filename, line, k, "Failed to parse boolean value, ignoring: %s", rvalue);
+ return 0;
+ }
+
+ *t = !!k;
+ return 0;
+}
+
+int config_parse_string(
+ const char *unit,
+ const char *filename,
+ unsigned line,
+ const char *section,
+ unsigned section_line,
+ const char *lvalue,
+ int ltype,
+ const char *rvalue,
+ void *data,
+ void *userdata) {
+
+ char **s = data, *n;
+
+ assert(filename);
+ assert(lvalue);
+ assert(rvalue);
+ assert(data);
+
+ if (!utf8_is_valid(rvalue)) {
+ log_syntax_invalid_utf8(unit, LOG_ERR, filename, line, rvalue);
+ return 0;
+ }
+
+ if (isempty(rvalue))
+ n = NULL;
+ else {
+ n = strdup(rvalue);
+ if (!n)
+ return log_oom();
+ }
+
+ free(*s);
+ *s = n;
+
+ return 0;
+}
+
+int config_parse_path(
+ const char *unit,
+ const char *filename,
+ unsigned line,
+ const char *section,
+ unsigned section_line,
+ const char *lvalue,
+ int ltype,
+ const char *rvalue,
+ void *data,
+ void *userdata) {
+
+ char **s = data, *n;
+
+ assert(filename);
+ assert(lvalue);
+ assert(rvalue);
+ assert(data);
+
+ if (!utf8_is_valid(rvalue)) {
+ log_syntax_invalid_utf8(unit, LOG_ERR, filename, line, rvalue);
+ return 0;
+ }
+
+ if (!path_is_absolute(rvalue)) {
+ log_syntax(unit, LOG_ERR, filename, line, 0, "Not an absolute path, ignoring: %s", rvalue);
+ return 0;
+ }
+
+ n = strdup(rvalue);
+ if (!n)
+ return log_oom();
+
+ path_kill_slashes(n);
+
+ free(*s);
+ *s = n;
+
+ return 0;
+}
+
+int config_parse_strv(const char *unit,
+ const char *filename,
+ unsigned line,
+ const char *section,
+ unsigned section_line,
+ const char *lvalue,
+ int ltype,
+ const char *rvalue,
+ void *data,
+ void *userdata) {
+
+ char ***sv = data;
+
+ assert(filename);
+ assert(lvalue);
+ assert(rvalue);
+ assert(data);
+
+ if (isempty(rvalue)) {
+ char **empty;
+
+ /* Empty assignment resets the list. As a special rule
+ * we actually fill in a real empty array here rather
+ * than NULL, since some code wants to know if
+ * something was set at all... */
+ empty = strv_new(NULL, NULL);
+ if (!empty)
+ return log_oom();
+
+ strv_free(*sv);
+ *sv = empty;
+ return 0;
+ }
+
+ for (;;) {
+ char *word = NULL;
+ int r;
+ r = extract_first_word(&rvalue, &word, WHITESPACE, EXTRACT_QUOTES);
+ if (r == 0)
+ break;
+ if (r == -ENOMEM)
+ return log_oom();
+ if (r < 0) {
+ log_syntax(unit, LOG_ERR, filename, line, r, "Invalid syntax, ignoring: %s", rvalue);
+ break;
+ }
+
+ if (!utf8_is_valid(word)) {
+ log_syntax_invalid_utf8(unit, LOG_ERR, filename, line, rvalue);
+ free(word);
+ continue;
+ }
+ r = strv_consume(sv, word);
+ if (r < 0)
+ return log_oom();
+ }
+
+ return 0;
+}
+
+int config_parse_log_facility(
+ const char *unit,
+ const char *filename,
+ unsigned line,
+ const char *section,
+ unsigned section_line,
+ const char *lvalue,
+ int ltype,
+ const char *rvalue,
+ void *data,
+ void *userdata) {
+
+
+ int *o = data, x;
+
+ assert(filename);
+ assert(lvalue);
+ assert(rvalue);
+ assert(data);
+
+ x = log_facility_unshifted_from_string(rvalue);
+ if (x < 0) {
+ log_syntax(unit, LOG_ERR, filename, line, 0, "Failed to parse log facility, ignoring: %s", rvalue);
+ return 0;
+ }
+
+ *o = (x << 3) | LOG_PRI(*o);
+
+ return 0;
+}
+
+int config_parse_log_level(
+ const char *unit,
+ const char *filename,
+ unsigned line,
+ const char *section,
+ unsigned section_line,
+ const char *lvalue,
+ int ltype,
+ const char *rvalue,
+ void *data,
+ void *userdata) {
+
+
+ int *o = data, x;
+
+ assert(filename);
+ assert(lvalue);
+ assert(rvalue);
+ assert(data);
+
+ x = log_level_from_string(rvalue);
+ if (x < 0) {
+ log_syntax(unit, LOG_ERR, filename, line, 0, "Failed to parse log level, ignoring: %s", rvalue);
+ return 0;
+ }
+
+ *o = (*o & LOG_FACMASK) | x;
+ return 0;
+}
+
+int config_parse_signal(
+ const char *unit,
+ const char *filename,
+ unsigned line,
+ const char *section,
+ unsigned section_line,
+ const char *lvalue,
+ int ltype,
+ const char *rvalue,
+ void *data,
+ void *userdata) {
+
+ int *sig = data, r;
+
+ assert(filename);
+ assert(lvalue);
+ assert(rvalue);
+ assert(sig);
+
+ r = signal_from_string_try_harder(rvalue);
+ if (r <= 0) {
+ log_syntax(unit, LOG_ERR, filename, line, 0, "Failed to parse signal name, ignoring: %s", rvalue);
+ return 0;
+ }
+
+ *sig = r;
+ return 0;
+}
+
+int config_parse_personality(
+ const char *unit,
+ const char *filename,
+ unsigned line,
+ const char *section,
+ unsigned section_line,
+ const char *lvalue,
+ int ltype,
+ const char *rvalue,
+ void *data,
+ void *userdata) {
+
+ unsigned long *personality = data, p;
+
+ assert(filename);
+ assert(lvalue);
+ assert(rvalue);
+ assert(personality);
+
+ p = personality_from_string(rvalue);
+ if (p == PERSONALITY_INVALID) {
+ log_syntax(unit, LOG_ERR, filename, line, 0, "Failed to parse personality, ignoring: %s", rvalue);
+ return 0;
+ }
+
+ *personality = p;
+ return 0;
+}
diff --git a/src/libshared/conf-parser.h b/src/libshared/conf-parser.h
new file mode 100644
index 0000000000..a91c94c322
--- /dev/null
+++ b/src/libshared/conf-parser.h
@@ -0,0 +1,228 @@
+#pragma once
+
+/***
+ This file is part of systemd.
+
+ Copyright 2010 Lennart Poettering
+
+ systemd is free software; you can redistribute it and/or modify it
+ under the terms of the GNU Lesser General Public License as published by
+ the Free Software Foundation; either version 2.1 of the License, or
+ (at your option) any later version.
+
+ systemd is distributed in the hope that it will be useful, but
+ WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ Lesser General Public License for more details.
+
+ You should have received a copy of the GNU Lesser General Public License
+ along with systemd; If not, see <http://www.gnu.org/licenses/>.
+***/
+
+#include <errno.h>
+#include <stdbool.h>
+#include <stddef.h>
+#include <stdio.h>
+#include <syslog.h>
+
+#include "alloc-util.h"
+#include "log.h"
+#include "macro.h"
+
+/* An abstract parser for simple, line based, shallow configuration
+ * files consisting of variable assignments only. */
+
+/* Prototype for a parser for a specific configuration setting */
+typedef int (*ConfigParserCallback)(const char *unit,
+ const char *filename,
+ unsigned line,
+ const char *section,
+ unsigned section_line,
+ const char *lvalue,
+ int ltype,
+ const char *rvalue,
+ void *data,
+ void *userdata);
+
+/* Wraps information for parsing a specific configuration variable, to
+ * be stored in a simple array */
+typedef struct ConfigTableItem {
+ const char *section; /* Section */
+ const char *lvalue; /* Name of the variable */
+ ConfigParserCallback parse; /* Function that is called to parse the variable's value */
+ int ltype; /* Distinguish different variables passed to the same callback */
+ void *data; /* Where to store the variable's data */
+} ConfigTableItem;
+
+/* Wraps information for parsing a specific configuration variable, to
+ * be stored in a gperf perfect hashtable */
+typedef struct ConfigPerfItem {
+ const char *section_and_lvalue; /* Section + "." + name of the variable */
+ ConfigParserCallback parse; /* Function that is called to parse the variable's value */
+ int ltype; /* Distinguish different variables passed to the same callback */
+ size_t offset; /* Offset where to store data, from the beginning of userdata */
+} ConfigPerfItem;
+
+/* Prototype for a low-level gperf lookup function */
+typedef const ConfigPerfItem* (*ConfigPerfItemLookup)(const char *section_and_lvalue, unsigned length);
+
+/* Prototype for a generic high-level lookup function */
+typedef int (*ConfigItemLookup)(
+ const void *table,
+ const char *section,
+ const char *lvalue,
+ ConfigParserCallback *func,
+ int *ltype,
+ void **data,
+ void *userdata);
+
+/* Linear table search implementation of ConfigItemLookup, based on
+ * ConfigTableItem arrays */
+int config_item_table_lookup(const void *table, const char *section, const char *lvalue, ConfigParserCallback *func, int *ltype, void **data, void *userdata);
+
+/* gperf implementation of ConfigItemLookup, based on gperf
+ * ConfigPerfItem tables */
+int config_item_perf_lookup(const void *table, const char *section, const char *lvalue, ConfigParserCallback *func, int *ltype, void **data, void *userdata);
+
+int config_parse(const char *unit,
+ const char *filename,
+ FILE *f,
+ const char *sections, /* nulstr */
+ ConfigItemLookup lookup,
+ const void *table,
+ bool relaxed,
+ bool allow_include,
+ bool warn,
+ void *userdata);
+
+int config_parse_many(const char *conf_file, /* possibly NULL */
+ const char *conf_file_dirs, /* nulstr */
+ const char *sections, /* nulstr */
+ ConfigItemLookup lookup,
+ const void *table,
+ bool relaxed,
+ void *userdata);
+
+/* Generic parsers */
+int config_parse_int(const char *unit, const char *filename, unsigned line, const char *section, unsigned section_line, const char *lvalue, int ltype, const char *rvalue, void *data, void *userdata);
+int config_parse_unsigned(const char *unit, const char *filename, unsigned line, const char *section, unsigned section_line, const char *lvalue, int ltype, const char *rvalue, void *data, void *userdata);
+int config_parse_long(const char *unit, const char *filename, unsigned line, const char *section, unsigned section_line, const char *lvalue, int ltype, const char *rvalue, void *data, void *userdata);
+int config_parse_uint32(const char *unit, const char *filename, unsigned line, const char *section, unsigned section_line, const char *lvalue, int ltype, const char *rvalue, void *data, void *userdata);
+int config_parse_uint64(const char *unit, const char *filename, unsigned line, const char *section, unsigned section_line, const char *lvalue, int ltype, const char *rvalue, void *data, void *userdata);
+int config_parse_double(const char *unit, const char *filename, unsigned line, const char *section, unsigned section_line, const char *lvalue, int ltype, const char *rvalue, void *data, void *userdata);
+int config_parse_iec_size(const char *unit, const char *filename, unsigned line, const char *section, unsigned section_line, const char *lvalue, int ltype, const char *rvalue, void *data, void *userdata);
+int config_parse_si_size(const char *unit, const char *filename, unsigned line, const char *section, unsigned section_line, const char *lvalue, int ltype, const char *rvalue, void *data, void *userdata);
+int config_parse_iec_uint64(const char *unit, const char *filename, unsigned line, const char *section, unsigned section_line, const char *lvalue, int ltype, const char *rvalue, void *data, void *userdata);
+int config_parse_bool(const char *unit, const char *filename, unsigned line, const char *section, unsigned section_line, const char *lvalue, int ltype, const char *rvalue, void *data, void *userdata);
+int config_parse_tristate(const char *unit, const char *filename, unsigned line, const char *section, unsigned section_line, const char *lvalue, int ltype, const char *rvalue, void *data, void *userdata);
+int config_parse_string(const char *unit, const char *filename, unsigned line, const char *section, unsigned section_line, const char *lvalue, int ltype, const char *rvalue, void *data, void *userdata);
+int config_parse_path(const char *unit, const char *filename, unsigned line, const char *section, unsigned section_line, const char *lvalue, int ltype, const char *rvalue, void *data, void *userdata);
+int config_parse_strv(const char *unit, const char *filename, unsigned line, const char *section, unsigned section_line, const char *lvalue, int ltype, const char *rvalue, void *data, void *userdata);
+int config_parse_sec(const char *unit, const char *filename, unsigned line, const char *section, unsigned section_line, const char *lvalue, int ltype, const char *rvalue, void *data, void *userdata);
+int config_parse_nsec(const char *unit, const char *filename, unsigned line, const char *section, unsigned section_line, const char *lvalue, int ltype, const char *rvalue, void *data, void *userdata);
+int config_parse_mode(const char *unit, const char *filename, unsigned line, const char *section, unsigned section_line, const char *lvalue, int ltype, const char *rvalue, void *data, void *userdata);
+int config_parse_log_facility(const char *unit, const char *filename, unsigned line, const char *section, unsigned section_line, const char *lvalue, int ltype, const char *rvalue, void *data, void *userdata);
+int config_parse_log_level(const char *unit, const char *filename, unsigned line, const char *section, unsigned section_line, const char *lvalue, int ltype, const char *rvalue, void *data, void *userdata);
+int config_parse_signal(const char *unit, const char *filename, unsigned line, const char *section, unsigned section_line, const char *lvalue, int ltype, const char *rvalue, void *data, void *userdata);
+int config_parse_personality(const char *unit, const char *filename, unsigned line, const char *section, unsigned section_line, const char *lvalue, int ltype, const char *rvalue, void *data, void *userdata);
+
+#define DEFINE_CONFIG_PARSE_ENUM(function,name,type,msg) \
+ int function(const char *unit, \
+ const char *filename, \
+ unsigned line, \
+ const char *section, \
+ unsigned section_line, \
+ const char *lvalue, \
+ int ltype, \
+ const char *rvalue, \
+ void *data, \
+ void *userdata) { \
+ \
+ type *i = data, x; \
+ \
+ assert(filename); \
+ assert(lvalue); \
+ assert(rvalue); \
+ assert(data); \
+ \
+ if ((x = name##_from_string(rvalue)) < 0) { \
+ log_syntax(unit, LOG_ERR, filename, line, -x, \
+ msg ", ignoring: %s", rvalue); \
+ return 0; \
+ } \
+ \
+ *i = x; \
+ return 0; \
+ }
+
+#define DEFINE_CONFIG_PARSE_ENUMV(function,name,type,invalid,msg) \
+ int function(const char *unit, \
+ const char *filename, \
+ unsigned line, \
+ const char *section, \
+ unsigned section_line, \
+ const char *lvalue, \
+ int ltype, \
+ const char *rvalue, \
+ void *data, \
+ void *userdata) { \
+ \
+ type **enums = data, x, *ys; \
+ _cleanup_free_ type *xs = NULL; \
+ const char *word, *state; \
+ size_t l, i = 0; \
+ \
+ assert(filename); \
+ assert(lvalue); \
+ assert(rvalue); \
+ assert(data); \
+ \
+ xs = new0(type, 1); \
+ if(!xs) \
+ return -ENOMEM; \
+ \
+ *xs = invalid; \
+ \
+ FOREACH_WORD(word, l, rvalue, state) { \
+ _cleanup_free_ char *en = NULL; \
+ type *new_xs; \
+ \
+ en = strndup(word, l); \
+ if (!en) \
+ return -ENOMEM; \
+ \
+ if ((x = name##_from_string(en)) < 0) { \
+ log_syntax(unit, LOG_ERR, filename, line, \
+ -x, msg ", ignoring: %s", en); \
+ continue; \
+ } \
+ \
+ for (ys = xs; x != invalid && *ys != invalid; ys++) { \
+ if (*ys == x) { \
+ log_syntax(unit, LOG_ERR, filename, \
+ line, -x, \
+ "Duplicate entry, ignoring: %s", \
+ en); \
+ x = invalid; \
+ } \
+ } \
+ \
+ if (x == invalid) \
+ continue; \
+ \
+ *(xs + i) = x; \
+ new_xs = realloc(xs, (++i + 1) * sizeof(type)); \
+ if (new_xs) \
+ xs = new_xs; \
+ else \
+ return -ENOMEM; \
+ \
+ *(xs + i) = invalid; \
+ } \
+ \
+ free(*enums); \
+ *enums = xs; \
+ xs = NULL; \
+ \
+ return 0; \
+ }
diff --git a/src/libshared/dev-setup.c b/src/libshared/dev-setup.c
new file mode 100644
index 0000000000..b2d464c117
--- /dev/null
+++ b/src/libshared/dev-setup.c
@@ -0,0 +1,73 @@
+/***
+ This file is part of systemd.
+
+ Copyright 2010-2012 Lennart Poettering
+
+ systemd is free software; you can redistribute it and/or modify it
+ under the terms of the GNU Lesser General Public License as published by
+ the Free Software Foundation; either version 2.1 of the License, or
+ (at your option) any later version.
+
+ systemd is distributed in the hope that it will be useful, but
+ WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ Lesser General Public License for more details.
+
+ You should have received a copy of the GNU Lesser General Public License
+ along with systemd; If not, see <http://www.gnu.org/licenses/>.
+***/
+
+#include <errno.h>
+#include <stdlib.h>
+#include <unistd.h>
+
+#include "alloc-util.h"
+#include "dev-setup.h"
+#include "label.h"
+#include "log.h"
+#include "path-util.h"
+#include "user-util.h"
+#include "util.h"
+
+int dev_setup(const char *prefix, uid_t uid, gid_t gid) {
+ static const char symlinks[] =
+ "-/proc/kcore\0" "/dev/core\0"
+ "/proc/self/fd\0" "/dev/fd\0"
+ "/proc/self/fd/0\0" "/dev/stdin\0"
+ "/proc/self/fd/1\0" "/dev/stdout\0"
+ "/proc/self/fd/2\0" "/dev/stderr\0";
+
+ const char *j, *k;
+ int r;
+
+ NULSTR_FOREACH_PAIR(j, k, symlinks) {
+ _cleanup_free_ char *link_name = NULL;
+ const char *n;
+
+ if (j[0] == '-') {
+ j++;
+
+ if (access(j, F_OK) < 0)
+ continue;
+ }
+
+ if (prefix) {
+ link_name = prefix_root(prefix, k);
+ if (!link_name)
+ return -ENOMEM;
+
+ n = link_name;
+ } else
+ n = k;
+
+ r = symlink_label(j, n);
+ if (r < 0)
+ log_debug_errno(r, "Failed to symlink %s to %s: %m", j, n);
+
+ if (uid != UID_INVALID || gid != GID_INVALID)
+ if (lchown(n, uid, gid) < 0)
+ log_debug_errno(errno, "Failed to chown %s: %m", n);
+ }
+
+ return 0;
+}
diff --git a/src/libshared/dev-setup.h b/src/libshared/dev-setup.h
new file mode 100644
index 0000000000..5766a62060
--- /dev/null
+++ b/src/libshared/dev-setup.h
@@ -0,0 +1,24 @@
+#pragma once
+
+/***
+ This file is part of systemd.
+
+ Copyright 2010-2012 Lennart Poettering
+
+ systemd is free software; you can redistribute it and/or modify it
+ under the terms of the GNU Lesser General Public License as published by
+ the Free Software Foundation; either version 2.1 of the License, or
+ (at your option) any later version.
+
+ systemd is distributed in the hope that it will be useful, but
+ WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ Lesser General Public License for more details.
+
+ You should have received a copy of the GNU Lesser General Public License
+ along with systemd; If not, see <http://www.gnu.org/licenses/>.
+***/
+
+#include <sys/types.h>
+
+int dev_setup(const char *prefix, uid_t uid, gid_t gid);
diff --git a/src/libshared/dns-domain.c b/src/libshared/dns-domain.c
new file mode 100644
index 0000000000..45d24c0079
--- /dev/null
+++ b/src/libshared/dns-domain.c
@@ -0,0 +1,1322 @@
+/***
+ This file is part of systemd.
+
+ Copyright 2014 Lennart Poettering
+
+ systemd is free software; you can redistribute it and/or modify it
+ under the terms of the GNU Lesser General Public License as published by
+ the Free Software Foundation; either version 2.1 of the License, or
+ (at your option) any later version.
+
+ systemd is distributed in the hope that it will be useful, but
+ WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ Lesser General Public License for more details.
+
+ You should have received a copy of the GNU Lesser General Public License
+ along with systemd; If not, see <http://www.gnu.org/licenses/>.
+ ***/
+
+#ifdef HAVE_LIBIDN
+#include <idna.h>
+#include <stringprep.h>
+#endif
+
+#include <endian.h>
+#include <netinet/in.h>
+#include <stdio.h>
+#include <string.h>
+#include <sys/socket.h>
+
+#include "alloc-util.h"
+#include "dns-domain.h"
+#include "hashmap.h"
+#include "hexdecoct.h"
+#include "in-addr-util.h"
+#include "macro.h"
+#include "parse-util.h"
+#include "string-util.h"
+#include "strv.h"
+#include "utf8.h"
+
+int dns_label_unescape(const char **name, char *dest, size_t sz) {
+ const char *n;
+ char *d;
+ int r = 0;
+
+ assert(name);
+ assert(*name);
+
+ n = *name;
+ d = dest;
+
+ for (;;) {
+ if (*n == '.') {
+ n++;
+ break;
+ }
+
+ if (*n == 0)
+ break;
+
+ if (r >= DNS_LABEL_MAX)
+ return -EINVAL;
+
+ if (sz <= 0)
+ return -ENOBUFS;
+
+ if (*n == '\\') {
+ /* Escaped character */
+
+ n++;
+
+ if (*n == 0)
+ /* Ending NUL */
+ return -EINVAL;
+
+ else if (*n == '\\' || *n == '.') {
+ /* Escaped backslash or dot */
+
+ if (d)
+ *(d++) = *n;
+ sz--;
+ r++;
+ n++;
+
+ } else if (n[0] >= '0' && n[0] <= '9') {
+ unsigned k;
+
+ /* Escaped literal ASCII character */
+
+ if (!(n[1] >= '0' && n[1] <= '9') ||
+ !(n[2] >= '0' && n[2] <= '9'))
+ return -EINVAL;
+
+ k = ((unsigned) (n[0] - '0') * 100) +
+ ((unsigned) (n[1] - '0') * 10) +
+ ((unsigned) (n[2] - '0'));
+
+ /* Don't allow anything that doesn't
+ * fit in 8bit. Note that we do allow
+ * control characters, as some servers
+ * (e.g. cloudflare) are happy to
+ * generate labels with them
+ * inside. */
+ if (k > 255)
+ return -EINVAL;
+
+ if (d)
+ *(d++) = (char) k;
+ sz--;
+ r++;
+
+ n += 3;
+ } else
+ return -EINVAL;
+
+ } else if ((uint8_t) *n >= (uint8_t) ' ' && *n != 127) {
+
+ /* Normal character */
+
+ if (d)
+ *(d++) = *n;
+ sz--;
+ r++;
+ n++;
+ } else
+ return -EINVAL;
+ }
+
+ /* Empty label that is not at the end? */
+ if (r == 0 && *n)
+ return -EINVAL;
+
+ if (sz >= 1 && d)
+ *d = 0;
+
+ *name = n;
+ return r;
+}
+
+/* @label_terminal: terminal character of a label, updated to point to the terminal character of
+ * the previous label (always skipping one dot) or to NULL if there are no more
+ * labels. */
+int dns_label_unescape_suffix(const char *name, const char **label_terminal, char *dest, size_t sz) {
+ const char *terminal;
+ int r;
+
+ assert(name);
+ assert(label_terminal);
+ assert(dest);
+
+ /* no more labels */
+ if (!*label_terminal) {
+ if (sz >= 1)
+ *dest = 0;
+
+ return 0;
+ }
+
+ terminal = *label_terminal;
+ assert(*terminal == '.' || *terminal == 0);
+
+ /* Skip current terminal character (and accept domain names ending it ".") */
+ if (*terminal == 0)
+ terminal--;
+ if (terminal >= name && *terminal == '.')
+ terminal--;
+
+ /* Point name to the last label, and terminal to the preceding terminal symbol (or make it a NULL pointer) */
+ for (;;) {
+ if (terminal < name) {
+ /* Reached the first label, so indicate that there are no more */
+ terminal = NULL;
+ break;
+ }
+
+ /* Find the start of the last label */
+ if (*terminal == '.') {
+ const char *y;
+ unsigned slashes = 0;
+
+ for (y = terminal - 1; y >= name && *y == '\\'; y--)
+ slashes ++;
+
+ if (slashes % 2 == 0) {
+ /* The '.' was not escaped */
+ name = terminal + 1;
+ break;
+ } else {
+ terminal = y;
+ continue;
+ }
+ }
+
+ terminal --;
+ }
+
+ r = dns_label_unescape(&name, dest, sz);
+ if (r < 0)
+ return r;
+
+ *label_terminal = terminal;
+
+ return r;
+}
+
+int dns_label_escape(const char *p, size_t l, char *dest, size_t sz) {
+ char *q;
+
+ /* DNS labels must be between 1 and 63 characters long. A
+ * zero-length label does not exist. See RFC 2182, Section
+ * 11. */
+
+ if (l <= 0 || l > DNS_LABEL_MAX)
+ return -EINVAL;
+ if (sz < 1)
+ return -ENOBUFS;
+
+ assert(p);
+ assert(dest);
+
+ q = dest;
+ while (l > 0) {
+
+ if (*p == '.' || *p == '\\') {
+
+ /* Dot or backslash */
+
+ if (sz < 3)
+ return -ENOBUFS;
+
+ *(q++) = '\\';
+ *(q++) = *p;
+
+ sz -= 2;
+
+ } else if (*p == '_' ||
+ *p == '-' ||
+ (*p >= '0' && *p <= '9') ||
+ (*p >= 'a' && *p <= 'z') ||
+ (*p >= 'A' && *p <= 'Z')) {
+
+ /* Proper character */
+
+ if (sz < 2)
+ return -ENOBUFS;
+
+ *(q++) = *p;
+ sz -= 1;
+
+ } else {
+
+ /* Everything else */
+
+ if (sz < 5)
+ return -ENOBUFS;
+
+ *(q++) = '\\';
+ *(q++) = '0' + (char) ((uint8_t) *p / 100);
+ *(q++) = '0' + (char) (((uint8_t) *p / 10) % 10);
+ *(q++) = '0' + (char) ((uint8_t) *p % 10);
+
+ sz -= 4;
+ }
+
+ p++;
+ l--;
+ }
+
+ *q = 0;
+ return (int) (q - dest);
+}
+
+int dns_label_escape_new(const char *p, size_t l, char **ret) {
+ _cleanup_free_ char *s = NULL;
+ int r;
+
+ assert(p);
+ assert(ret);
+
+ if (l <= 0 || l > DNS_LABEL_MAX)
+ return -EINVAL;
+
+ s = new(char, DNS_LABEL_ESCAPED_MAX);
+ if (!s)
+ return -ENOMEM;
+
+ r = dns_label_escape(p, l, s, DNS_LABEL_ESCAPED_MAX);
+ if (r < 0)
+ return r;
+
+ *ret = s;
+ s = NULL;
+
+ return r;
+}
+
+int dns_label_apply_idna(const char *encoded, size_t encoded_size, char *decoded, size_t decoded_max) {
+#ifdef HAVE_LIBIDN
+ _cleanup_free_ uint32_t *input = NULL;
+ size_t input_size, l;
+ const char *p;
+ bool contains_8bit = false;
+ char buffer[DNS_LABEL_MAX+1];
+
+ assert(encoded);
+ assert(decoded);
+
+ /* Converts an U-label into an A-label */
+
+ if (encoded_size <= 0)
+ return -EINVAL;
+
+ for (p = encoded; p < encoded + encoded_size; p++)
+ if ((uint8_t) *p > 127)
+ contains_8bit = true;
+
+ if (!contains_8bit) {
+ if (encoded_size > DNS_LABEL_MAX)
+ return -EINVAL;
+
+ return 0;
+ }
+
+ input = stringprep_utf8_to_ucs4(encoded, encoded_size, &input_size);
+ if (!input)
+ return -ENOMEM;
+
+ if (idna_to_ascii_4i(input, input_size, buffer, 0) != 0)
+ return -EINVAL;
+
+ l = strlen(buffer);
+
+ /* Verify that the the result is not longer than one DNS label. */
+ if (l <= 0 || l > DNS_LABEL_MAX)
+ return -EINVAL;
+ if (l > decoded_max)
+ return -ENOBUFS;
+
+ memcpy(decoded, buffer, l);
+
+ /* If there's room, append a trailing NUL byte, but only then */
+ if (decoded_max > l)
+ decoded[l] = 0;
+
+ return (int) l;
+#else
+ return 0;
+#endif
+}
+
+int dns_label_undo_idna(const char *encoded, size_t encoded_size, char *decoded, size_t decoded_max) {
+#ifdef HAVE_LIBIDN
+ size_t input_size, output_size;
+ _cleanup_free_ uint32_t *input = NULL;
+ _cleanup_free_ char *result = NULL;
+ uint32_t *output = NULL;
+ size_t w;
+
+ /* To be invoked after unescaping. Converts an A-label into an U-label. */
+
+ assert(encoded);
+ assert(decoded);
+
+ if (encoded_size <= 0 || encoded_size > DNS_LABEL_MAX)
+ return -EINVAL;
+
+ if (encoded_size < sizeof(IDNA_ACE_PREFIX)-1)
+ return 0;
+
+ if (memcmp(encoded, IDNA_ACE_PREFIX, sizeof(IDNA_ACE_PREFIX) -1) != 0)
+ return 0;
+
+ input = stringprep_utf8_to_ucs4(encoded, encoded_size, &input_size);
+ if (!input)
+ return -ENOMEM;
+
+ output_size = input_size;
+ output = newa(uint32_t, output_size);
+
+ idna_to_unicode_44i(input, input_size, output, &output_size, 0);
+
+ result = stringprep_ucs4_to_utf8(output, output_size, NULL, &w);
+ if (!result)
+ return -ENOMEM;
+ if (w <= 0)
+ return -EINVAL;
+ if (w > decoded_max)
+ return -ENOBUFS;
+
+ memcpy(decoded, result, w);
+
+ /* Append trailing NUL byte if there's space, but only then. */
+ if (decoded_max > w)
+ decoded[w] = 0;
+
+ return w;
+#else
+ return 0;
+#endif
+}
+
+int dns_name_concat(const char *a, const char *b, char **_ret) {
+ _cleanup_free_ char *ret = NULL;
+ size_t n = 0, allocated = 0;
+ const char *p;
+ bool first = true;
+ int r;
+
+ if (a)
+ p = a;
+ else if (b) {
+ p = b;
+ b = NULL;
+ } else
+ goto finish;
+
+ for (;;) {
+ char label[DNS_LABEL_MAX];
+
+ r = dns_label_unescape(&p, label, sizeof(label));
+ if (r < 0)
+ return r;
+ if (r == 0) {
+ if (*p != 0)
+ return -EINVAL;
+
+ if (b) {
+ /* Now continue with the second string, if there is one */
+ p = b;
+ b = NULL;
+ continue;
+ }
+
+ break;
+ }
+
+ if (_ret) {
+ if (!GREEDY_REALLOC(ret, allocated, n + !first + DNS_LABEL_ESCAPED_MAX))
+ return -ENOMEM;
+
+ r = dns_label_escape(label, r, ret + n + !first, DNS_LABEL_ESCAPED_MAX);
+ if (r < 0)
+ return r;
+
+ if (!first)
+ ret[n] = '.';
+ } else {
+ char escaped[DNS_LABEL_ESCAPED_MAX];
+
+ r = dns_label_escape(label, r, escaped, sizeof(escaped));
+ if (r < 0)
+ return r;
+ }
+
+ if (!first)
+ n++;
+ else
+ first = false;
+
+ n += r;
+ }
+
+finish:
+ if (n > DNS_HOSTNAME_MAX)
+ return -EINVAL;
+
+ if (_ret) {
+ if (n == 0) {
+ /* Nothing appended? If so, generate at least a single dot, to indicate the DNS root domain */
+ if (!GREEDY_REALLOC(ret, allocated, 2))
+ return -ENOMEM;
+
+ ret[n++] = '.';
+ } else {
+ if (!GREEDY_REALLOC(ret, allocated, n + 1))
+ return -ENOMEM;
+ }
+
+ ret[n] = 0;
+ *_ret = ret;
+ ret = NULL;
+ }
+
+ return 0;
+}
+
+void dns_name_hash_func(const void *s, struct siphash *state) {
+ const char *p = s;
+ int r;
+
+ assert(p);
+
+ for (;;) {
+ char label[DNS_LABEL_MAX+1];
+
+ r = dns_label_unescape(&p, label, sizeof(label));
+ if (r < 0)
+ break;
+ if (r == 0)
+ break;
+
+ ascii_strlower_n(label, r);
+ siphash24_compress(label, r, state);
+ siphash24_compress_byte(0, state); /* make sure foobar and foo.bar result in different hashes */
+ }
+
+ /* enforce that all names are terminated by the empty label */
+ string_hash_func("", state);
+}
+
+int dns_name_compare_func(const void *a, const void *b) {
+ const char *x, *y;
+ int r, q;
+
+ assert(a);
+ assert(b);
+
+ x = (const char *) a + strlen(a);
+ y = (const char *) b + strlen(b);
+
+ for (;;) {
+ char la[DNS_LABEL_MAX], lb[DNS_LABEL_MAX];
+
+ if (x == NULL && y == NULL)
+ return 0;
+
+ r = dns_label_unescape_suffix(a, &x, la, sizeof(la));
+ q = dns_label_unescape_suffix(b, &y, lb, sizeof(lb));
+ if (r < 0 || q < 0)
+ return r - q;
+
+ r = ascii_strcasecmp_nn(la, r, lb, q);
+ if (r != 0)
+ return r;
+ }
+}
+
+const struct hash_ops dns_name_hash_ops = {
+ .hash = dns_name_hash_func,
+ .compare = dns_name_compare_func
+};
+
+int dns_name_equal(const char *x, const char *y) {
+ int r, q;
+
+ assert(x);
+ assert(y);
+
+ for (;;) {
+ char la[DNS_LABEL_MAX], lb[DNS_LABEL_MAX];
+
+ r = dns_label_unescape(&x, la, sizeof(la));
+ if (r < 0)
+ return r;
+
+ q = dns_label_unescape(&y, lb, sizeof(lb));
+ if (q < 0)
+ return q;
+
+ if (r != q)
+ return false;
+ if (r == 0)
+ return true;
+
+ if (ascii_strcasecmp_n(la, lb, r) != 0)
+ return false;
+ }
+}
+
+int dns_name_endswith(const char *name, const char *suffix) {
+ const char *n, *s, *saved_n = NULL;
+ int r, q;
+
+ assert(name);
+ assert(suffix);
+
+ n = name;
+ s = suffix;
+
+ for (;;) {
+ char ln[DNS_LABEL_MAX], ls[DNS_LABEL_MAX];
+
+ r = dns_label_unescape(&n, ln, sizeof(ln));
+ if (r < 0)
+ return r;
+
+ if (!saved_n)
+ saved_n = n;
+
+ q = dns_label_unescape(&s, ls, sizeof(ls));
+ if (q < 0)
+ return q;
+
+ if (r == 0 && q == 0)
+ return true;
+ if (r == 0 && saved_n == n)
+ return false;
+
+ if (r != q || ascii_strcasecmp_n(ln, ls, r) != 0) {
+
+ /* Not the same, let's jump back, and try with the next label again */
+ s = suffix;
+ n = saved_n;
+ saved_n = NULL;
+ }
+ }
+}
+
+int dns_name_startswith(const char *name, const char *prefix) {
+ const char *n, *p;
+ int r, q;
+
+ assert(name);
+ assert(prefix);
+
+ n = name;
+ p = prefix;
+
+ for (;;) {
+ char ln[DNS_LABEL_MAX], lp[DNS_LABEL_MAX];
+
+ r = dns_label_unescape(&p, lp, sizeof(lp));
+ if (r < 0)
+ return r;
+ if (r == 0)
+ return true;
+
+ q = dns_label_unescape(&n, ln, sizeof(ln));
+ if (q < 0)
+ return q;
+
+ if (r != q)
+ return false;
+ if (ascii_strcasecmp_n(ln, lp, r) != 0)
+ return false;
+ }
+}
+
+int dns_name_change_suffix(const char *name, const char *old_suffix, const char *new_suffix, char **ret) {
+ const char *n, *s, *saved_before = NULL, *saved_after = NULL, *prefix;
+ int r, q;
+
+ assert(name);
+ assert(old_suffix);
+ assert(new_suffix);
+ assert(ret);
+
+ n = name;
+ s = old_suffix;
+
+ for (;;) {
+ char ln[DNS_LABEL_MAX], ls[DNS_LABEL_MAX];
+
+ if (!saved_before)
+ saved_before = n;
+
+ r = dns_label_unescape(&n, ln, sizeof(ln));
+ if (r < 0)
+ return r;
+
+ if (!saved_after)
+ saved_after = n;
+
+ q = dns_label_unescape(&s, ls, sizeof(ls));
+ if (q < 0)
+ return q;
+
+ if (r == 0 && q == 0)
+ break;
+ if (r == 0 && saved_after == n) {
+ *ret = NULL; /* doesn't match */
+ return 0;
+ }
+
+ if (r != q || ascii_strcasecmp_n(ln, ls, r) != 0) {
+
+ /* Not the same, let's jump back, and try with the next label again */
+ s = old_suffix;
+ n = saved_after;
+ saved_after = saved_before = NULL;
+ }
+ }
+
+ /* Found it! Now generate the new name */
+ prefix = strndupa(name, saved_before - name);
+
+ r = dns_name_concat(prefix, new_suffix, ret);
+ if (r < 0)
+ return r;
+
+ return 1;
+}
+
+int dns_name_between(const char *a, const char *b, const char *c) {
+ int n;
+
+ /* Determine if b is strictly greater than a and strictly smaller than c.
+ We consider the order of names to be circular, so that if a is
+ strictly greater than c, we consider b to be between them if it is
+ either greater than a or smaller than c. This is how the canonical
+ DNS name order used in NSEC records work. */
+
+ n = dns_name_compare_func(a, c);
+ if (n == 0)
+ return -EINVAL;
+ else if (n < 0)
+ /* a<---b--->c */
+ return dns_name_compare_func(a, b) < 0 &&
+ dns_name_compare_func(b, c) < 0;
+ else
+ /* <--b--c a--b--> */
+ return dns_name_compare_func(b, c) < 0 ||
+ dns_name_compare_func(a, b) < 0;
+}
+
+int dns_name_reverse(int family, const union in_addr_union *a, char **ret) {
+ const uint8_t *p;
+ int r;
+
+ assert(a);
+ assert(ret);
+
+ p = (const uint8_t*) a;
+
+ if (family == AF_INET)
+ r = asprintf(ret, "%u.%u.%u.%u.in-addr.arpa", p[3], p[2], p[1], p[0]);
+ else if (family == AF_INET6)
+ r = asprintf(ret, "%c.%c.%c.%c.%c.%c.%c.%c.%c.%c.%c.%c.%c.%c.%c.%c.%c.%c.%c.%c.%c.%c.%c.%c.%c.%c.%c.%c.%c.%c.%c.%c.ip6.arpa",
+ hexchar(p[15] & 0xF), hexchar(p[15] >> 4), hexchar(p[14] & 0xF), hexchar(p[14] >> 4),
+ hexchar(p[13] & 0xF), hexchar(p[13] >> 4), hexchar(p[12] & 0xF), hexchar(p[12] >> 4),
+ hexchar(p[11] & 0xF), hexchar(p[11] >> 4), hexchar(p[10] & 0xF), hexchar(p[10] >> 4),
+ hexchar(p[ 9] & 0xF), hexchar(p[ 9] >> 4), hexchar(p[ 8] & 0xF), hexchar(p[ 8] >> 4),
+ hexchar(p[ 7] & 0xF), hexchar(p[ 7] >> 4), hexchar(p[ 6] & 0xF), hexchar(p[ 6] >> 4),
+ hexchar(p[ 5] & 0xF), hexchar(p[ 5] >> 4), hexchar(p[ 4] & 0xF), hexchar(p[ 4] >> 4),
+ hexchar(p[ 3] & 0xF), hexchar(p[ 3] >> 4), hexchar(p[ 2] & 0xF), hexchar(p[ 2] >> 4),
+ hexchar(p[ 1] & 0xF), hexchar(p[ 1] >> 4), hexchar(p[ 0] & 0xF), hexchar(p[ 0] >> 4));
+ else
+ return -EAFNOSUPPORT;
+ if (r < 0)
+ return -ENOMEM;
+
+ return 0;
+}
+
+int dns_name_address(const char *p, int *family, union in_addr_union *address) {
+ int r;
+
+ assert(p);
+ assert(family);
+ assert(address);
+
+ r = dns_name_endswith(p, "in-addr.arpa");
+ if (r < 0)
+ return r;
+ if (r > 0) {
+ uint8_t a[4];
+ unsigned i;
+
+ for (i = 0; i < ELEMENTSOF(a); i++) {
+ char label[DNS_LABEL_MAX+1];
+
+ r = dns_label_unescape(&p, label, sizeof(label));
+ if (r < 0)
+ return r;
+ if (r == 0)
+ return -EINVAL;
+ if (r > 3)
+ return -EINVAL;
+
+ r = safe_atou8(label, &a[i]);
+ if (r < 0)
+ return r;
+ }
+
+ r = dns_name_equal(p, "in-addr.arpa");
+ if (r <= 0)
+ return r;
+
+ *family = AF_INET;
+ address->in.s_addr = htobe32(((uint32_t) a[3] << 24) |
+ ((uint32_t) a[2] << 16) |
+ ((uint32_t) a[1] << 8) |
+ (uint32_t) a[0]);
+
+ return 1;
+ }
+
+ r = dns_name_endswith(p, "ip6.arpa");
+ if (r < 0)
+ return r;
+ if (r > 0) {
+ struct in6_addr a;
+ unsigned i;
+
+ for (i = 0; i < ELEMENTSOF(a.s6_addr); i++) {
+ char label[DNS_LABEL_MAX+1];
+ int x, y;
+
+ r = dns_label_unescape(&p, label, sizeof(label));
+ if (r <= 0)
+ return r;
+ if (r != 1)
+ return -EINVAL;
+ x = unhexchar(label[0]);
+ if (x < 0)
+ return -EINVAL;
+
+ r = dns_label_unescape(&p, label, sizeof(label));
+ if (r <= 0)
+ return r;
+ if (r != 1)
+ return -EINVAL;
+ y = unhexchar(label[0]);
+ if (y < 0)
+ return -EINVAL;
+
+ a.s6_addr[ELEMENTSOF(a.s6_addr) - i - 1] = (uint8_t) y << 4 | (uint8_t) x;
+ }
+
+ r = dns_name_equal(p, "ip6.arpa");
+ if (r <= 0)
+ return r;
+
+ *family = AF_INET6;
+ address->in6 = a;
+ return 1;
+ }
+
+ return 0;
+}
+
+bool dns_name_is_root(const char *name) {
+
+ assert(name);
+
+ /* There are exactly two ways to encode the root domain name:
+ * as empty string, or with a single dot. */
+
+ return STR_IN_SET(name, "", ".");
+}
+
+bool dns_name_is_single_label(const char *name) {
+ int r;
+
+ assert(name);
+
+ r = dns_name_parent(&name);
+ if (r <= 0)
+ return false;
+
+ return dns_name_is_root(name);
+}
+
+/* Encode a domain name according to RFC 1035 Section 3.1, without compression */
+int dns_name_to_wire_format(const char *domain, uint8_t *buffer, size_t len, bool canonical) {
+ uint8_t *label_length, *out;
+ int r;
+
+ assert(domain);
+ assert(buffer);
+
+ out = buffer;
+
+ do {
+ /* Reserve a byte for label length */
+ if (len <= 0)
+ return -ENOBUFS;
+ len--;
+ label_length = out;
+ out++;
+
+ /* Convert and copy a single label. Note that
+ * dns_label_unescape() returns 0 when it hits the end
+ * of the domain name, which we rely on here to encode
+ * the trailing NUL byte. */
+ r = dns_label_unescape(&domain, (char *) out, len);
+ if (r < 0)
+ return r;
+
+ /* Optionally, output the name in DNSSEC canonical
+ * format, as described in RFC 4034, section 6.2. Or
+ * in other words: in lower-case. */
+ if (canonical)
+ ascii_strlower_n((char*) out, (size_t) r);
+
+ /* Fill label length, move forward */
+ *label_length = r;
+ out += r;
+ len -= r;
+
+ } while (r != 0);
+
+ /* Verify the maximum size of the encoded name. The trailing
+ * dot + NUL byte account are included this time, hence
+ * compare against DNS_HOSTNAME_MAX + 2 (which is 255) this
+ * time. */
+ if (out - buffer > DNS_HOSTNAME_MAX + 2)
+ return -EINVAL;
+
+ return out - buffer;
+}
+
+static bool srv_type_label_is_valid(const char *label, size_t n) {
+ size_t k;
+
+ assert(label);
+
+ if (n < 2) /* Label needs to be at least 2 chars long */
+ return false;
+
+ if (label[0] != '_') /* First label char needs to be underscore */
+ return false;
+
+ /* Second char must be a letter */
+ if (!(label[1] >= 'A' && label[1] <= 'Z') &&
+ !(label[1] >= 'a' && label[1] <= 'z'))
+ return false;
+
+ /* Third and further chars must be alphanumeric or a hyphen */
+ for (k = 2; k < n; k++) {
+ if (!(label[k] >= 'A' && label[k] <= 'Z') &&
+ !(label[k] >= 'a' && label[k] <= 'z') &&
+ !(label[k] >= '0' && label[k] <= '9') &&
+ label[k] != '-')
+ return false;
+ }
+
+ return true;
+}
+
+bool dns_srv_type_is_valid(const char *name) {
+ unsigned c = 0;
+ int r;
+
+ if (!name)
+ return false;
+
+ for (;;) {
+ char label[DNS_LABEL_MAX];
+
+ /* This more or less implements RFC 6335, Section 5.1 */
+
+ r = dns_label_unescape(&name, label, sizeof(label));
+ if (r < 0)
+ return false;
+ if (r == 0)
+ break;
+
+ if (c >= 2)
+ return false;
+
+ if (!srv_type_label_is_valid(label, r))
+ return false;
+
+ c++;
+ }
+
+ return c == 2; /* exactly two labels */
+}
+
+bool dns_service_name_is_valid(const char *name) {
+ size_t l;
+
+ /* This more or less implements RFC 6763, Section 4.1.1 */
+
+ if (!name)
+ return false;
+
+ if (!utf8_is_valid(name))
+ return false;
+
+ if (string_has_cc(name, NULL))
+ return false;
+
+ l = strlen(name);
+ if (l <= 0)
+ return false;
+ if (l > 63)
+ return false;
+
+ return true;
+}
+
+int dns_service_join(const char *name, const char *type, const char *domain, char **ret) {
+ char escaped[DNS_LABEL_ESCAPED_MAX];
+ _cleanup_free_ char *n = NULL;
+ int r;
+
+ assert(type);
+ assert(domain);
+ assert(ret);
+
+ if (!dns_srv_type_is_valid(type))
+ return -EINVAL;
+
+ if (!name)
+ return dns_name_concat(type, domain, ret);
+
+ if (!dns_service_name_is_valid(name))
+ return -EINVAL;
+
+ r = dns_label_escape(name, strlen(name), escaped, sizeof(escaped));
+ if (r < 0)
+ return r;
+
+ r = dns_name_concat(type, domain, &n);
+ if (r < 0)
+ return r;
+
+ return dns_name_concat(escaped, n, ret);
+}
+
+static bool dns_service_name_label_is_valid(const char *label, size_t n) {
+ char *s;
+
+ assert(label);
+
+ if (memchr(label, 0, n))
+ return false;
+
+ s = strndupa(label, n);
+ return dns_service_name_is_valid(s);
+}
+
+int dns_service_split(const char *joined, char **_name, char **_type, char **_domain) {
+ _cleanup_free_ char *name = NULL, *type = NULL, *domain = NULL;
+ const char *p = joined, *q = NULL, *d = NULL;
+ char a[DNS_LABEL_MAX], b[DNS_LABEL_MAX], c[DNS_LABEL_MAX];
+ int an, bn, cn, r;
+ unsigned x = 0;
+
+ assert(joined);
+
+ /* Get first label from the full name */
+ an = dns_label_unescape(&p, a, sizeof(a));
+ if (an < 0)
+ return an;
+
+ if (an > 0) {
+ x++;
+
+ /* If there was a first label, try to get the second one */
+ bn = dns_label_unescape(&p, b, sizeof(b));
+ if (bn < 0)
+ return bn;
+
+ if (bn > 0) {
+ x++;
+
+ /* If there was a second label, try to get the third one */
+ q = p;
+ cn = dns_label_unescape(&p, c, sizeof(c));
+ if (cn < 0)
+ return cn;
+
+ if (cn > 0)
+ x++;
+ } else
+ cn = 0;
+ } else
+ an = 0;
+
+ if (x >= 2 && srv_type_label_is_valid(b, bn)) {
+
+ if (x >= 3 && srv_type_label_is_valid(c, cn)) {
+
+ if (dns_service_name_label_is_valid(a, an)) {
+ /* OK, got <name> . <type> . <type2> . <domain> */
+
+ name = strndup(a, an);
+ if (!name)
+ return -ENOMEM;
+
+ type = strjoin(b, ".", c, NULL);
+ if (!type)
+ return -ENOMEM;
+
+ d = p;
+ goto finish;
+ }
+
+ } else if (srv_type_label_is_valid(a, an)) {
+
+ /* OK, got <type> . <type2> . <domain> */
+
+ name = NULL;
+
+ type = strjoin(a, ".", b, NULL);
+ if (!type)
+ return -ENOMEM;
+
+ d = q;
+ goto finish;
+ }
+ }
+
+ name = NULL;
+ type = NULL;
+ d = joined;
+
+finish:
+ r = dns_name_normalize(d, &domain);
+ if (r < 0)
+ return r;
+
+ if (_domain) {
+ *_domain = domain;
+ domain = NULL;
+ }
+
+ if (_type) {
+ *_type = type;
+ type = NULL;
+ }
+
+ if (_name) {
+ *_name = name;
+ name = NULL;
+ }
+
+ return 0;
+}
+
+static int dns_name_build_suffix_table(const char *name, const char*table[]) {
+ const char *p;
+ unsigned n = 0;
+ int r;
+
+ assert(name);
+ assert(table);
+
+ p = name;
+ for (;;) {
+ if (n > DNS_N_LABELS_MAX)
+ return -EINVAL;
+
+ table[n] = p;
+ r = dns_name_parent(&p);
+ if (r < 0)
+ return r;
+ if (r == 0)
+ break;
+
+ n++;
+ }
+
+ return (int) n;
+}
+
+int dns_name_suffix(const char *name, unsigned n_labels, const char **ret) {
+ const char* labels[DNS_N_LABELS_MAX+1];
+ int n;
+
+ assert(name);
+ assert(ret);
+
+ n = dns_name_build_suffix_table(name, labels);
+ if (n < 0)
+ return n;
+
+ if ((unsigned) n < n_labels)
+ return -EINVAL;
+
+ *ret = labels[n - n_labels];
+ return (int) (n - n_labels);
+}
+
+int dns_name_skip(const char *a, unsigned n_labels, const char **ret) {
+ int r;
+
+ assert(a);
+ assert(ret);
+
+ for (; n_labels > 0; n_labels --) {
+ r = dns_name_parent(&a);
+ if (r < 0)
+ return r;
+ if (r == 0) {
+ *ret = "";
+ return 0;
+ }
+ }
+
+ *ret = a;
+ return 1;
+}
+
+int dns_name_count_labels(const char *name) {
+ unsigned n = 0;
+ const char *p;
+ int r;
+
+ assert(name);
+
+ p = name;
+ for (;;) {
+ r = dns_name_parent(&p);
+ if (r < 0)
+ return r;
+ if (r == 0)
+ break;
+
+ if (n >= DNS_N_LABELS_MAX)
+ return -EINVAL;
+
+ n++;
+ }
+
+ return (int) n;
+}
+
+int dns_name_equal_skip(const char *a, unsigned n_labels, const char *b) {
+ int r;
+
+ assert(a);
+ assert(b);
+
+ r = dns_name_skip(a, n_labels, &a);
+ if (r <= 0)
+ return r;
+
+ return dns_name_equal(a, b);
+}
+
+int dns_name_common_suffix(const char *a, const char *b, const char **ret) {
+ const char *a_labels[DNS_N_LABELS_MAX+1], *b_labels[DNS_N_LABELS_MAX+1];
+ int n = 0, m = 0, k = 0, r, q;
+
+ assert(a);
+ assert(b);
+ assert(ret);
+
+ /* Determines the common suffix of domain names a and b */
+
+ n = dns_name_build_suffix_table(a, a_labels);
+ if (n < 0)
+ return n;
+
+ m = dns_name_build_suffix_table(b, b_labels);
+ if (m < 0)
+ return m;
+
+ for (;;) {
+ char la[DNS_LABEL_MAX], lb[DNS_LABEL_MAX];
+ const char *x, *y;
+
+ if (k >= n || k >= m) {
+ *ret = a_labels[n - k];
+ return 0;
+ }
+
+ x = a_labels[n - 1 - k];
+ r = dns_label_unescape(&x, la, sizeof(la));
+ if (r < 0)
+ return r;
+
+ y = b_labels[m - 1 - k];
+ q = dns_label_unescape(&y, lb, sizeof(lb));
+ if (q < 0)
+ return q;
+
+ if (r != q || ascii_strcasecmp_n(la, lb, r) != 0) {
+ *ret = a_labels[n - k];
+ return 0;
+ }
+
+ k++;
+ }
+}
+
+int dns_name_apply_idna(const char *name, char **ret) {
+ _cleanup_free_ char *buf = NULL;
+ size_t n = 0, allocated = 0;
+ bool first = true;
+ int r, q;
+
+ assert(name);
+ assert(ret);
+
+ for (;;) {
+ char label[DNS_LABEL_MAX];
+
+ r = dns_label_unescape(&name, label, sizeof(label));
+ if (r < 0)
+ return r;
+ if (r == 0)
+ break;
+
+ q = dns_label_apply_idna(label, r, label, sizeof(label));
+ if (q < 0)
+ return q;
+ if (q > 0)
+ r = q;
+
+ if (!GREEDY_REALLOC(buf, allocated, n + !first + DNS_LABEL_ESCAPED_MAX))
+ return -ENOMEM;
+
+ r = dns_label_escape(label, r, buf + n + !first, DNS_LABEL_ESCAPED_MAX);
+ if (r < 0)
+ return r;
+
+ if (first)
+ first = false;
+ else
+ buf[n++] = '.';
+
+ n +=r;
+ }
+
+ if (n > DNS_HOSTNAME_MAX)
+ return -EINVAL;
+
+ if (!GREEDY_REALLOC(buf, allocated, n + 1))
+ return -ENOMEM;
+
+ buf[n] = 0;
+ *ret = buf;
+ buf = NULL;
+
+ return (int) n;
+}
diff --git a/src/libshared/dns-domain.h b/src/libshared/dns-domain.h
new file mode 100644
index 0000000000..2de3642cb3
--- /dev/null
+++ b/src/libshared/dns-domain.h
@@ -0,0 +1,110 @@
+/***
+ This file is part of systemd.
+
+ Copyright 2014 Lennart Poettering
+
+ systemd is free software; you can redistribute it and/or modify it
+ under the terms of the GNU Lesser General Public License as published by
+ the Free Software Foundation; either version 2.1 of the License, or
+ (at your option) any later version.
+
+ systemd is distributed in the hope that it will be useful, but
+ WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ Lesser General Public License for more details.
+
+ You should have received a copy of the GNU Lesser General Public License
+ along with systemd; If not, see <http://www.gnu.org/licenses/>.
+ ***/
+
+#pragma once
+
+
+#include <errno.h>
+#include <stdbool.h>
+#include <stddef.h>
+#include <stdint.h>
+
+#include "hashmap.h"
+#include "in-addr-util.h"
+
+/* Length of a single label, with all escaping removed, excluding any trailing dot or NUL byte */
+#define DNS_LABEL_MAX 63
+
+/* Worst case length of a single label, with all escaping applied and room for a trailing NUL byte. */
+#define DNS_LABEL_ESCAPED_MAX (DNS_LABEL_MAX*4+1)
+
+/* Maximum length of a full hostname, consisting of a series of unescaped labels, and no trailing dot or NUL byte */
+#define DNS_HOSTNAME_MAX 253
+
+/* Maximum length of a full hostname, on the wire, including the final NUL byte */
+#define DNS_WIRE_FOMAT_HOSTNAME_MAX 255
+
+/* Maximum number of labels per valid hostname */
+#define DNS_N_LABELS_MAX 127
+
+int dns_label_unescape(const char **name, char *dest, size_t sz);
+int dns_label_unescape_suffix(const char *name, const char **label_end, char *dest, size_t sz);
+int dns_label_escape(const char *p, size_t l, char *dest, size_t sz);
+int dns_label_escape_new(const char *p, size_t l, char **ret);
+
+static inline int dns_name_parent(const char **name) {
+ return dns_label_unescape(name, NULL, DNS_LABEL_MAX);
+}
+
+int dns_label_apply_idna(const char *encoded, size_t encoded_size, char *decoded, size_t decoded_max);
+int dns_label_undo_idna(const char *encoded, size_t encoded_size, char *decoded, size_t decoded_max);
+
+int dns_name_concat(const char *a, const char *b, char **ret);
+
+static inline int dns_name_normalize(const char *s, char **ret) {
+ /* dns_name_concat() normalizes as a side-effect */
+ return dns_name_concat(s, NULL, ret);
+}
+
+static inline int dns_name_is_valid(const char *s) {
+ int r;
+
+ /* dns_name_normalize() verifies as a side effect */
+ r = dns_name_normalize(s, NULL);
+ if (r == -EINVAL)
+ return 0;
+ if (r < 0)
+ return r;
+ return 1;
+}
+
+void dns_name_hash_func(const void *s, struct siphash *state);
+int dns_name_compare_func(const void *a, const void *b);
+extern const struct hash_ops dns_name_hash_ops;
+
+int dns_name_between(const char *a, const char *b, const char *c);
+int dns_name_equal(const char *x, const char *y);
+int dns_name_endswith(const char *name, const char *suffix);
+int dns_name_startswith(const char *name, const char *prefix);
+
+int dns_name_change_suffix(const char *name, const char *old_suffix, const char *new_suffix, char **ret);
+
+int dns_name_reverse(int family, const union in_addr_union *a, char **ret);
+int dns_name_address(const char *p, int *family, union in_addr_union *a);
+
+bool dns_name_is_root(const char *name);
+bool dns_name_is_single_label(const char *name);
+
+int dns_name_to_wire_format(const char *domain, uint8_t *buffer, size_t len, bool canonical);
+
+bool dns_srv_type_is_valid(const char *name);
+bool dns_service_name_is_valid(const char *name);
+
+int dns_service_join(const char *name, const char *type, const char *domain, char **ret);
+int dns_service_split(const char *joined, char **name, char **type, char **domain);
+
+int dns_name_suffix(const char *name, unsigned n_labels, const char **ret);
+int dns_name_count_labels(const char *name);
+
+int dns_name_skip(const char *a, unsigned n_labels, const char **ret);
+int dns_name_equal_skip(const char *a, unsigned n_labels, const char *b);
+
+int dns_name_common_suffix(const char *a, const char *b, const char **ret);
+
+int dns_name_apply_idna(const char *name, char **ret);
diff --git a/src/libshared/dropin.c b/src/libshared/dropin.c
new file mode 100644
index 0000000000..cc1acd6f23
--- /dev/null
+++ b/src/libshared/dropin.c
@@ -0,0 +1,251 @@
+/***
+ This file is part of systemd.
+
+ Copyright 2014 Zbigniew Jędrzejewski-Szmek
+
+ systemd is free software; you can redistribute it and/or modify it
+ under the terms of the GNU Lesser General Public License as published by
+ the Free Software Foundation; either version 2.1 of the License, or
+ (at your option) any later version.
+
+ systemd is distributed in the hope that it will be useful, but
+ WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ Lesser General Public License for more details.
+
+ You should have received a copy of the GNU Lesser General Public License
+ along with systemd; If not, see <http://www.gnu.org/licenses/>.
+***/
+
+#include <dirent.h>
+#include <errno.h>
+#include <stdarg.h>
+#include <stdio.h>
+#include <stdlib.h>
+
+#include "alloc-util.h"
+#include "conf-files.h"
+#include "dropin.h"
+#include "escape.h"
+#include "fd-util.h"
+#include "fileio-label.h"
+#include "hashmap.h"
+#include "log.h"
+#include "macro.h"
+#include "mkdir.h"
+#include "path-util.h"
+#include "set.h"
+#include "string-util.h"
+#include "strv.h"
+#include "unit-name.h"
+
+int drop_in_file(const char *dir, const char *unit, unsigned level,
+ const char *name, char **_p, char **_q) {
+
+ _cleanup_free_ char *b = NULL;
+ char *p, *q;
+
+ char prefix[DECIMAL_STR_MAX(unsigned)];
+
+ assert(unit);
+ assert(name);
+ assert(_p);
+ assert(_q);
+
+ sprintf(prefix, "%u", level);
+
+ b = xescape(name, "/.");
+ if (!b)
+ return -ENOMEM;
+
+ if (!filename_is_valid(b))
+ return -EINVAL;
+
+ p = strjoin(dir, "/", unit, ".d", NULL);
+ if (!p)
+ return -ENOMEM;
+
+ q = strjoin(p, "/", prefix, "-", b, ".conf", NULL);
+ if (!q) {
+ free(p);
+ return -ENOMEM;
+ }
+
+ *_p = p;
+ *_q = q;
+ return 0;
+}
+
+int write_drop_in(const char *dir, const char *unit, unsigned level,
+ const char *name, const char *data) {
+
+ _cleanup_free_ char *p = NULL, *q = NULL;
+ int r;
+
+ assert(dir);
+ assert(unit);
+ assert(name);
+ assert(data);
+
+ r = drop_in_file(dir, unit, level, name, &p, &q);
+ if (r < 0)
+ return r;
+
+ (void) mkdir_p(p, 0755);
+ return write_string_file_atomic_label(q, data);
+}
+
+int write_drop_in_format(const char *dir, const char *unit, unsigned level,
+ const char *name, const char *format, ...) {
+ _cleanup_free_ char *p = NULL;
+ va_list ap;
+ int r;
+
+ assert(dir);
+ assert(unit);
+ assert(name);
+ assert(format);
+
+ va_start(ap, format);
+ r = vasprintf(&p, format, ap);
+ va_end(ap);
+
+ if (r < 0)
+ return -ENOMEM;
+
+ return write_drop_in(dir, unit, level, name, p);
+}
+
+static int iterate_dir(
+ const char *path,
+ UnitDependency dependency,
+ dependency_consumer_t consumer,
+ void *arg,
+ char ***strv) {
+
+ _cleanup_closedir_ DIR *d = NULL;
+ int r;
+
+ assert(path);
+
+ /* The config directories are special, since the order of the
+ * drop-ins matters */
+ if (dependency < 0) {
+ r = strv_extend(strv, path);
+ if (r < 0)
+ return log_oom();
+
+ return 0;
+ }
+
+ assert(consumer);
+
+ d = opendir(path);
+ if (!d) {
+ if (errno == ENOENT)
+ return 0;
+
+ return log_error_errno(errno, "Failed to open directory %s: %m", path);
+ }
+
+ for (;;) {
+ struct dirent *de;
+ _cleanup_free_ char *f = NULL;
+
+ errno = 0;
+ de = readdir(d);
+ if (!de && errno > 0)
+ return log_error_errno(errno, "Failed to read directory %s: %m", path);
+
+ if (!de)
+ break;
+
+ if (hidden_file(de->d_name))
+ continue;
+
+ f = strjoin(path, "/", de->d_name, NULL);
+ if (!f)
+ return log_oom();
+
+ r = consumer(dependency, de->d_name, f, arg);
+ if (r < 0)
+ return r;
+ }
+
+ return 0;
+}
+
+int unit_file_process_dir(
+ Set *unit_path_cache,
+ const char *unit_path,
+ const char *name,
+ const char *suffix,
+ UnitDependency dependency,
+ dependency_consumer_t consumer,
+ void *arg,
+ char ***strv) {
+
+ _cleanup_free_ char *path = NULL;
+ int r;
+
+ assert(unit_path);
+ assert(name);
+ assert(suffix);
+
+ path = strjoin(unit_path, "/", name, suffix, NULL);
+ if (!path)
+ return log_oom();
+
+ if (!unit_path_cache || set_get(unit_path_cache, path))
+ (void) iterate_dir(path, dependency, consumer, arg, strv);
+
+ if (unit_name_is_valid(name, UNIT_NAME_INSTANCE)) {
+ _cleanup_free_ char *template = NULL, *p = NULL;
+ /* Also try the template dir */
+
+ r = unit_name_template(name, &template);
+ if (r < 0)
+ return log_error_errno(r, "Failed to generate template from unit name: %m");
+
+ p = strjoin(unit_path, "/", template, suffix, NULL);
+ if (!p)
+ return log_oom();
+
+ if (!unit_path_cache || set_get(unit_path_cache, p))
+ (void) iterate_dir(p, dependency, consumer, arg, strv);
+ }
+
+ return 0;
+}
+
+int unit_file_find_dropin_paths(
+ char **lookup_path,
+ Set *unit_path_cache,
+ Set *names,
+ char ***paths) {
+
+ _cleanup_strv_free_ char **strv = NULL, **ans = NULL;
+ Iterator i;
+ char *t;
+ int r;
+
+ assert(paths);
+
+ SET_FOREACH(t, names, i) {
+ char **p;
+
+ STRV_FOREACH(p, lookup_path)
+ unit_file_process_dir(unit_path_cache, *p, t, ".d", _UNIT_DEPENDENCY_INVALID, NULL, NULL, &strv);
+ }
+
+ if (strv_isempty(strv))
+ return 0;
+
+ r = conf_files_list_strv(&ans, ".conf", NULL, (const char**) strv);
+ if (r < 0)
+ return log_warning_errno(r, "Failed to get list of configuration files: %m");
+
+ *paths = ans;
+ ans = NULL;
+ return 1;
+}
diff --git a/src/libshared/dropin.h b/src/libshared/dropin.h
new file mode 100644
index 0000000000..c1936f397b
--- /dev/null
+++ b/src/libshared/dropin.h
@@ -0,0 +1,61 @@
+#pragma once
+
+/***
+ This file is part of systemd.
+
+ Copyright 2014 Zbigniew Jędrzejewski-Szmek
+
+ systemd is free software; you can redistribute it and/or modify it
+ under the terms of the GNU Lesser General Public License as published by
+ the Free Software Foundation; either version 2.1 of the License, or
+ (at your option) any later version.
+
+ systemd is distributed in the hope that it will be useful, but
+ WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ Lesser General Public License for more details.
+
+ You should have received a copy of the GNU Lesser General Public License
+ along with systemd; If not, see <http://www.gnu.org/licenses/>.
+***/
+
+#include "hashmap.h"
+#include "macro.h"
+#include "set.h"
+#include "unit-name.h"
+
+int drop_in_file(const char *dir, const char *unit, unsigned level,
+ const char *name, char **_p, char **_q);
+
+int write_drop_in(const char *dir, const char *unit, unsigned level,
+ const char *name, const char *data);
+
+int write_drop_in_format(const char *dir, const char *unit, unsigned level,
+ const char *name, const char *format, ...) _printf_(5, 6);
+
+/**
+ * This callback will be called for each directory entry @entry,
+ * with @filepath being the full path to the entry.
+ *
+ * If return value is negative, loop will be aborted.
+ */
+typedef int (*dependency_consumer_t)(UnitDependency dependency,
+ const char *entry,
+ const char* filepath,
+ void *arg);
+
+int unit_file_process_dir(
+ Set * unit_path_cache,
+ const char *unit_path,
+ const char *name,
+ const char *suffix,
+ UnitDependency dependency,
+ dependency_consumer_t consumer,
+ void *arg,
+ char ***strv);
+
+int unit_file_find_dropin_paths(
+ char **lookup_path,
+ Set *unit_path_cache,
+ Set *names,
+ char ***paths);
diff --git a/src/libshared/efivars.c b/src/libshared/efivars.c
new file mode 100644
index 0000000000..5073c61740
--- /dev/null
+++ b/src/libshared/efivars.c
@@ -0,0 +1,715 @@
+/***
+ This file is part of systemd.
+
+ Copyright 2013 Lennart Poettering
+
+ systemd is free software; you can redistribute it and/or modify it
+ under the terms of the GNU Lesser General Public License as published by
+ the Free Software Foundation; either version 2.1 of the License, or
+ (at your option) any later version.
+
+ systemd is distributed in the hope that it will be useful, but
+ WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ Lesser General Public License for more details.
+
+ You should have received a copy of the GNU Lesser General Public License
+ along with systemd; If not, see <http://www.gnu.org/licenses/>.
+***/
+
+#include <dirent.h>
+#include <errno.h>
+#include <fcntl.h>
+#include <limits.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <sys/stat.h>
+#include <unistd.h>
+
+#include <systemd/sd-id128.h>
+
+#include "alloc-util.h"
+#include "dirent-util.h"
+#include "efivars.h"
+#include "fd-util.h"
+#include "io-util.h"
+#include "macro.h"
+#include "parse-util.h"
+#include "stdio-util.h"
+#include "time-util.h"
+#include "utf8.h"
+#include "util.h"
+#include "virt.h"
+
+#ifdef ENABLE_EFI
+
+#define LOAD_OPTION_ACTIVE 0x00000001
+#define MEDIA_DEVICE_PATH 0x04
+#define MEDIA_HARDDRIVE_DP 0x01
+#define MEDIA_FILEPATH_DP 0x04
+#define SIGNATURE_TYPE_GUID 0x02
+#define MBR_TYPE_EFI_PARTITION_TABLE_HEADER 0x02
+#define END_DEVICE_PATH_TYPE 0x7f
+#define END_ENTIRE_DEVICE_PATH_SUBTYPE 0xff
+#define EFI_OS_INDICATIONS_BOOT_TO_FW_UI 0x0000000000000001
+
+struct boot_option {
+ uint32_t attr;
+ uint16_t path_len;
+ uint16_t title[];
+} _packed_;
+
+struct drive_path {
+ uint32_t part_nr;
+ uint64_t part_start;
+ uint64_t part_size;
+ char signature[16];
+ uint8_t mbr_type;
+ uint8_t signature_type;
+} _packed_;
+
+struct device_path {
+ uint8_t type;
+ uint8_t sub_type;
+ uint16_t length;
+ union {
+ uint16_t path[0];
+ struct drive_path drive;
+ };
+} _packed_;
+
+bool is_efi_boot(void) {
+ return access("/sys/firmware/efi", F_OK) >= 0;
+}
+
+static int read_flag(const char *varname) {
+ int r;
+ _cleanup_free_ void *v = NULL;
+ size_t s;
+ uint8_t b;
+
+ r = efi_get_variable(EFI_VENDOR_GLOBAL, varname, NULL, &v, &s);
+ if (r < 0)
+ return r;
+
+ if (s != 1)
+ return -EINVAL;
+
+ b = *(uint8_t *)v;
+ r = b > 0;
+ return r;
+}
+
+bool is_efi_secure_boot(void) {
+ return read_flag("SecureBoot") > 0;
+}
+
+bool is_efi_secure_boot_setup_mode(void) {
+ return read_flag("SetupMode") > 0;
+}
+
+int efi_reboot_to_firmware_supported(void) {
+ int r;
+ size_t s;
+ uint64_t b;
+ _cleanup_free_ void *v = NULL;
+
+ if (!is_efi_boot() || detect_container() > 0)
+ return -EOPNOTSUPP;
+
+ r = efi_get_variable(EFI_VENDOR_GLOBAL, "OsIndicationsSupported", NULL, &v, &s);
+ if (r < 0)
+ return r;
+ else if (s != sizeof(uint64_t))
+ return -EINVAL;
+
+ b = *(uint64_t *)v;
+ b &= EFI_OS_INDICATIONS_BOOT_TO_FW_UI;
+ return b > 0 ? 0 : -EOPNOTSUPP;
+}
+
+static int get_os_indications(uint64_t *os_indication) {
+ int r;
+ size_t s;
+ _cleanup_free_ void *v = NULL;
+
+ r = efi_reboot_to_firmware_supported();
+ if (r < 0)
+ return r;
+
+ r = efi_get_variable(EFI_VENDOR_GLOBAL, "OsIndications", NULL, &v, &s);
+ if (r == -ENOENT) {
+ /* Some firmware implementations that do support
+ * OsIndications and report that with
+ * OsIndicationsSupported will remove the
+ * OsIndications variable when it is unset. Let's
+ * pretend it's 0 then, to hide this implementation
+ * detail. Note that this call will return -ENOENT
+ * then only if the support for OsIndications is
+ * missing entirely, as determined by
+ * efi_reboot_to_firmware_supported() above. */
+ *os_indication = 0;
+ return 0;
+ } else if (r < 0)
+ return r;
+ else if (s != sizeof(uint64_t))
+ return -EINVAL;
+
+ *os_indication = *(uint64_t *)v;
+ return 0;
+}
+
+int efi_get_reboot_to_firmware(void) {
+ int r;
+ uint64_t b;
+
+ r = get_os_indications(&b);
+ if (r < 0)
+ return r;
+
+ return !!(b & EFI_OS_INDICATIONS_BOOT_TO_FW_UI);
+}
+
+int efi_set_reboot_to_firmware(bool value) {
+ int r;
+ uint64_t b, b_new;
+
+ r = get_os_indications(&b);
+ if (r < 0)
+ return r;
+
+ if (value)
+ b_new = b | EFI_OS_INDICATIONS_BOOT_TO_FW_UI;
+ else
+ b_new = b & ~EFI_OS_INDICATIONS_BOOT_TO_FW_UI;
+
+ /* Avoid writing to efi vars store if we can due to firmware bugs. */
+ if (b != b_new)
+ return efi_set_variable(EFI_VENDOR_GLOBAL, "OsIndications", &b_new, sizeof(uint64_t));
+
+ return 0;
+}
+
+int efi_get_variable(
+ sd_id128_t vendor,
+ const char *name,
+ uint32_t *attribute,
+ void **value,
+ size_t *size) {
+
+ _cleanup_close_ int fd = -1;
+ _cleanup_free_ char *p = NULL;
+ uint32_t a;
+ ssize_t n;
+ struct stat st;
+ _cleanup_free_ void *buf = NULL;
+
+ assert(name);
+ assert(value);
+ assert(size);
+
+ if (asprintf(&p,
+ "/sys/firmware/efi/efivars/%s-%02x%02x%02x%02x-%02x%02x-%02x%02x-%02x%02x-%02x%02x%02x%02x%02x%02x",
+ name, SD_ID128_FORMAT_VAL(vendor)) < 0)
+ return -ENOMEM;
+
+ fd = open(p, O_RDONLY|O_NOCTTY|O_CLOEXEC);
+ if (fd < 0)
+ return -errno;
+
+ if (fstat(fd, &st) < 0)
+ return -errno;
+ if (st.st_size < 4)
+ return -EIO;
+ if (st.st_size > 4*1024*1024 + 4)
+ return -E2BIG;
+
+ n = read(fd, &a, sizeof(a));
+ if (n < 0)
+ return -errno;
+ if (n != sizeof(a))
+ return -EIO;
+
+ buf = malloc(st.st_size - 4 + 2);
+ if (!buf)
+ return -ENOMEM;
+
+ n = read(fd, buf, (size_t) st.st_size - 4);
+ if (n < 0)
+ return -errno;
+ if (n != (ssize_t) st.st_size - 4)
+ return -EIO;
+
+ /* Always NUL terminate (2 bytes, to protect UTF-16) */
+ ((char*) buf)[st.st_size - 4] = 0;
+ ((char*) buf)[st.st_size - 4 + 1] = 0;
+
+ *value = buf;
+ buf = NULL;
+ *size = (size_t) st.st_size - 4;
+
+ if (attribute)
+ *attribute = a;
+
+ return 0;
+}
+
+int efi_set_variable(
+ sd_id128_t vendor,
+ const char *name,
+ const void *value,
+ size_t size) {
+
+ struct var {
+ uint32_t attr;
+ char buf[];
+ } _packed_ * _cleanup_free_ buf = NULL;
+ _cleanup_free_ char *p = NULL;
+ _cleanup_close_ int fd = -1;
+
+ assert(name);
+
+ if (asprintf(&p,
+ "/sys/firmware/efi/efivars/%s-%02x%02x%02x%02x-%02x%02x-%02x%02x-%02x%02x-%02x%02x%02x%02x%02x%02x",
+ name, SD_ID128_FORMAT_VAL(vendor)) < 0)
+ return -ENOMEM;
+
+ if (size == 0) {
+ if (unlink(p) < 0)
+ return -errno;
+ return 0;
+ }
+
+ fd = open(p, O_WRONLY|O_CREAT|O_NOCTTY|O_CLOEXEC, 0644);
+ if (fd < 0)
+ return -errno;
+
+ buf = malloc(sizeof(uint32_t) + size);
+ if (!buf)
+ return -ENOMEM;
+
+ buf->attr = EFI_VARIABLE_NON_VOLATILE|EFI_VARIABLE_BOOTSERVICE_ACCESS|EFI_VARIABLE_RUNTIME_ACCESS;
+ memcpy(buf->buf, value, size);
+
+ return loop_write(fd, buf, sizeof(uint32_t) + size, false);
+}
+
+int efi_get_variable_string(sd_id128_t vendor, const char *name, char **p) {
+ _cleanup_free_ void *s = NULL;
+ size_t ss = 0;
+ int r;
+ char *x;
+
+ r = efi_get_variable(vendor, name, NULL, &s, &ss);
+ if (r < 0)
+ return r;
+
+ x = utf16_to_utf8(s, ss);
+ if (!x)
+ return -ENOMEM;
+
+ *p = x;
+ return 0;
+}
+
+static size_t utf16_size(const uint16_t *s) {
+ size_t l = 0;
+
+ while (s[l] > 0)
+ l++;
+
+ return (l+1) * sizeof(uint16_t);
+}
+
+static void efi_guid_to_id128(const void *guid, sd_id128_t *id128) {
+ struct uuid {
+ uint32_t u1;
+ uint16_t u2;
+ uint16_t u3;
+ uint8_t u4[8];
+ } _packed_;
+ const struct uuid *uuid = guid;
+
+ id128->bytes[0] = (uuid->u1 >> 24) & 0xff;
+ id128->bytes[1] = (uuid->u1 >> 16) & 0xff;
+ id128->bytes[2] = (uuid->u1 >> 8) & 0xff;
+ id128->bytes[3] = (uuid->u1) & 0xff;
+ id128->bytes[4] = (uuid->u2 >> 8) & 0xff;
+ id128->bytes[5] = (uuid->u2) & 0xff;
+ id128->bytes[6] = (uuid->u3 >> 8) & 0xff;
+ id128->bytes[7] = (uuid->u3) & 0xff;
+ memcpy(&id128->bytes[8], uuid->u4, sizeof(uuid->u4));
+}
+
+int efi_get_boot_option(
+ uint16_t id,
+ char **title,
+ sd_id128_t *part_uuid,
+ char **path,
+ bool *active) {
+
+ char boot_id[9];
+ _cleanup_free_ uint8_t *buf = NULL;
+ size_t l;
+ struct boot_option *header;
+ size_t title_size;
+ _cleanup_free_ char *s = NULL, *p = NULL;
+ sd_id128_t p_uuid = SD_ID128_NULL;
+ int r;
+
+ xsprintf(boot_id, "Boot%04X", id);
+ r = efi_get_variable(EFI_VENDOR_GLOBAL, boot_id, NULL, (void **)&buf, &l);
+ if (r < 0)
+ return r;
+ if (l < sizeof(struct boot_option))
+ return -ENOENT;
+
+ header = (struct boot_option *)buf;
+ title_size = utf16_size(header->title);
+ if (title_size > l - offsetof(struct boot_option, title))
+ return -EINVAL;
+
+ if (title) {
+ s = utf16_to_utf8(header->title, title_size);
+ if (!s)
+ return -ENOMEM;
+ }
+
+ if (header->path_len > 0) {
+ uint8_t *dbuf;
+ size_t dnext;
+
+ dbuf = buf + offsetof(struct boot_option, title) + title_size;
+ dnext = 0;
+ while (dnext < header->path_len) {
+ struct device_path *dpath;
+
+ dpath = (struct device_path *)(dbuf + dnext);
+ if (dpath->length < 4)
+ break;
+
+ /* Type 0x7F – End of Hardware Device Path, Sub-Type 0xFF – End Entire Device Path */
+ if (dpath->type == END_DEVICE_PATH_TYPE && dpath->sub_type == END_ENTIRE_DEVICE_PATH_SUBTYPE)
+ break;
+
+ dnext += dpath->length;
+
+ /* Type 0x04 – Media Device Path */
+ if (dpath->type != MEDIA_DEVICE_PATH)
+ continue;
+
+ /* Sub-Type 1 – Hard Drive */
+ if (dpath->sub_type == MEDIA_HARDDRIVE_DP) {
+ /* 0x02 – GUID Partition Table */
+ if (dpath->drive.mbr_type != MBR_TYPE_EFI_PARTITION_TABLE_HEADER)
+ continue;
+
+ /* 0x02 – GUID signature */
+ if (dpath->drive.signature_type != SIGNATURE_TYPE_GUID)
+ continue;
+
+ if (part_uuid)
+ efi_guid_to_id128(dpath->drive.signature, &p_uuid);
+ continue;
+ }
+
+ /* Sub-Type 4 – File Path */
+ if (dpath->sub_type == MEDIA_FILEPATH_DP && !p && path) {
+ p = utf16_to_utf8(dpath->path, dpath->length-4);
+ efi_tilt_backslashes(p);
+ continue;
+ }
+ }
+ }
+
+ if (title) {
+ *title = s;
+ s = NULL;
+ }
+ if (part_uuid)
+ *part_uuid = p_uuid;
+ if (path) {
+ *path = p;
+ p = NULL;
+ }
+ if (active)
+ *active = !!(header->attr & LOAD_OPTION_ACTIVE);
+
+ return 0;
+}
+
+static void to_utf16(uint16_t *dest, const char *src) {
+ int i;
+
+ for (i = 0; src[i] != '\0'; i++)
+ dest[i] = src[i];
+ dest[i] = '\0';
+}
+
+struct guid {
+ uint32_t u1;
+ uint16_t u2;
+ uint16_t u3;
+ uint8_t u4[8];
+} _packed_;
+
+static void id128_to_efi_guid(sd_id128_t id, void *guid) {
+ struct guid *uuid = guid;
+
+ uuid->u1 = id.bytes[0] << 24 | id.bytes[1] << 16 | id.bytes[2] << 8 | id.bytes[3];
+ uuid->u2 = id.bytes[4] << 8 | id.bytes[5];
+ uuid->u3 = id.bytes[6] << 8 | id.bytes[7];
+ memcpy(uuid->u4, id.bytes+8, sizeof(uuid->u4));
+}
+
+static uint16_t *tilt_slashes(uint16_t *s) {
+ uint16_t *p;
+
+ for (p = s; *p; p++)
+ if (*p == '/')
+ *p = '\\';
+
+ return s;
+}
+
+int efi_add_boot_option(uint16_t id, const char *title,
+ uint32_t part, uint64_t pstart, uint64_t psize,
+ sd_id128_t part_uuid, const char *path) {
+ char boot_id[9];
+ size_t size;
+ size_t title_len;
+ size_t path_len;
+ struct boot_option *option;
+ struct device_path *devicep;
+ _cleanup_free_ char *buf = NULL;
+
+ title_len = (strlen(title)+1) * 2;
+ path_len = (strlen(path)+1) * 2;
+
+ buf = calloc(sizeof(struct boot_option) + title_len +
+ sizeof(struct drive_path) +
+ sizeof(struct device_path) + path_len, 1);
+ if (!buf)
+ return -ENOMEM;
+
+ /* header */
+ option = (struct boot_option *)buf;
+ option->attr = LOAD_OPTION_ACTIVE;
+ option->path_len = offsetof(struct device_path, drive) + sizeof(struct drive_path) +
+ offsetof(struct device_path, path) + path_len +
+ offsetof(struct device_path, path);
+ to_utf16(option->title, title);
+ size = offsetof(struct boot_option, title) + title_len;
+
+ /* partition info */
+ devicep = (struct device_path *)(buf + size);
+ devicep->type = MEDIA_DEVICE_PATH;
+ devicep->sub_type = MEDIA_HARDDRIVE_DP;
+ devicep->length = offsetof(struct device_path, drive) + sizeof(struct drive_path);
+ devicep->drive.part_nr = part;
+ devicep->drive.part_start = pstart;
+ devicep->drive.part_size = psize;
+ devicep->drive.signature_type = SIGNATURE_TYPE_GUID;
+ devicep->drive.mbr_type = MBR_TYPE_EFI_PARTITION_TABLE_HEADER;
+ id128_to_efi_guid(part_uuid, devicep->drive.signature);
+ size += devicep->length;
+
+ /* path to loader */
+ devicep = (struct device_path *)(buf + size);
+ devicep->type = MEDIA_DEVICE_PATH;
+ devicep->sub_type = MEDIA_FILEPATH_DP;
+ devicep->length = offsetof(struct device_path, path) + path_len;
+ to_utf16(devicep->path, path);
+ tilt_slashes(devicep->path);
+ size += devicep->length;
+
+ /* end of path */
+ devicep = (struct device_path *)(buf + size);
+ devicep->type = END_DEVICE_PATH_TYPE;
+ devicep->sub_type = END_ENTIRE_DEVICE_PATH_SUBTYPE;
+ devicep->length = offsetof(struct device_path, path);
+ size += devicep->length;
+
+ xsprintf(boot_id, "Boot%04X", id);
+ return efi_set_variable(EFI_VENDOR_GLOBAL, boot_id, buf, size);
+}
+
+int efi_remove_boot_option(uint16_t id) {
+ char boot_id[9];
+
+ xsprintf(boot_id, "Boot%04X", id);
+ return efi_set_variable(EFI_VENDOR_GLOBAL, boot_id, NULL, 0);
+}
+
+int efi_get_boot_order(uint16_t **order) {
+ _cleanup_free_ void *buf = NULL;
+ size_t l;
+ int r;
+
+ r = efi_get_variable(EFI_VENDOR_GLOBAL, "BootOrder", NULL, &buf, &l);
+ if (r < 0)
+ return r;
+
+ if (l <= 0)
+ return -ENOENT;
+
+ if (l % sizeof(uint16_t) > 0 ||
+ l / sizeof(uint16_t) > INT_MAX)
+ return -EINVAL;
+
+ *order = buf;
+ buf = NULL;
+ return (int) (l / sizeof(uint16_t));
+}
+
+int efi_set_boot_order(uint16_t *order, size_t n) {
+ return efi_set_variable(EFI_VENDOR_GLOBAL, "BootOrder", order, n * sizeof(uint16_t));
+}
+
+static int boot_id_hex(const char s[4]) {
+ int i;
+ int id = 0;
+
+ for (i = 0; i < 4; i++)
+ if (s[i] >= '0' && s[i] <= '9')
+ id |= (s[i] - '0') << (3 - i) * 4;
+ else if (s[i] >= 'A' && s[i] <= 'F')
+ id |= (s[i] - 'A' + 10) << (3 - i) * 4;
+ else
+ return -EINVAL;
+
+ return id;
+}
+
+static int cmp_uint16(const void *_a, const void *_b) {
+ const uint16_t *a = _a, *b = _b;
+
+ return (int)*a - (int)*b;
+}
+
+int efi_get_boot_options(uint16_t **options) {
+ _cleanup_closedir_ DIR *dir = NULL;
+ struct dirent *de;
+ _cleanup_free_ uint16_t *list = NULL;
+ size_t alloc = 0;
+ int count = 0;
+
+ assert(options);
+
+ dir = opendir("/sys/firmware/efi/efivars/");
+ if (!dir)
+ return -errno;
+
+ FOREACH_DIRENT(de, dir, return -errno) {
+ int id;
+
+ if (strncmp(de->d_name, "Boot", 4) != 0)
+ continue;
+
+ if (strlen(de->d_name) != 45)
+ continue;
+
+ if (strcmp(de->d_name + 8, "-8be4df61-93ca-11d2-aa0d-00e098032b8c") != 0)
+ continue;
+
+ id = boot_id_hex(de->d_name + 4);
+ if (id < 0)
+ continue;
+
+ if (!GREEDY_REALLOC(list, alloc, count + 1))
+ return -ENOMEM;
+
+ list[count++] = id;
+ }
+
+ qsort_safe(list, count, sizeof(uint16_t), cmp_uint16);
+
+ *options = list;
+ list = NULL;
+ return count;
+}
+
+static int read_usec(sd_id128_t vendor, const char *name, usec_t *u) {
+ _cleanup_free_ char *j = NULL;
+ int r;
+ uint64_t x = 0;
+
+ assert(name);
+ assert(u);
+
+ r = efi_get_variable_string(EFI_VENDOR_LOADER, name, &j);
+ if (r < 0)
+ return r;
+
+ r = safe_atou64(j, &x);
+ if (r < 0)
+ return r;
+
+ *u = x;
+ return 0;
+}
+
+int efi_loader_get_boot_usec(usec_t *firmware, usec_t *loader) {
+ uint64_t x, y;
+ int r;
+
+ assert(firmware);
+ assert(loader);
+
+ r = read_usec(EFI_VENDOR_LOADER, "LoaderTimeInitUSec", &x);
+ if (r < 0)
+ return r;
+
+ r = read_usec(EFI_VENDOR_LOADER, "LoaderTimeExecUSec", &y);
+ if (r < 0)
+ return r;
+
+ if (y == 0 || y < x)
+ return -EIO;
+
+ if (y > USEC_PER_HOUR)
+ return -EIO;
+
+ *firmware = x;
+ *loader = y;
+
+ return 0;
+}
+
+int efi_loader_get_device_part_uuid(sd_id128_t *u) {
+ _cleanup_free_ char *p = NULL;
+ int r, parsed[16];
+
+ r = efi_get_variable_string(EFI_VENDOR_LOADER, "LoaderDevicePartUUID", &p);
+ if (r < 0)
+ return r;
+
+ if (sscanf(p, "%02x%02x%02x%02x-%02x%02x-%02x%02x-%02x%02x-%02x%02x%02x%02x%02x%02x",
+ &parsed[0], &parsed[1], &parsed[2], &parsed[3],
+ &parsed[4], &parsed[5], &parsed[6], &parsed[7],
+ &parsed[8], &parsed[9], &parsed[10], &parsed[11],
+ &parsed[12], &parsed[13], &parsed[14], &parsed[15]) != 16)
+ return -EIO;
+
+ if (u) {
+ unsigned i;
+
+ for (i = 0; i < ELEMENTSOF(parsed); i++)
+ u->bytes[i] = parsed[i];
+ }
+
+ return 0;
+}
+
+#endif
+
+char *efi_tilt_backslashes(char *s) {
+ char *p;
+
+ for (p = s; *p; p++)
+ if (*p == '\\')
+ *p = '/';
+
+ return s;
+}
diff --git a/src/libshared/efivars.h b/src/libshared/efivars.h
new file mode 100644
index 0000000000..243151f922
--- /dev/null
+++ b/src/libshared/efivars.h
@@ -0,0 +1,131 @@
+#pragma once
+
+/***
+ This file is part of systemd.
+
+ Copyright 2013 Lennart Poettering
+
+ systemd is free software; you can redistribute it and/or modify it
+ under the terms of the GNU Lesser General Public License as published by
+ the Free Software Foundation; either version 2.1 of the License, or
+ (at your option) any later version.
+
+ systemd is distributed in the hope that it will be useful, but
+ WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ Lesser General Public License for more details.
+
+ You should have received a copy of the GNU Lesser General Public License
+ along with systemd; If not, see <http://www.gnu.org/licenses/>.
+***/
+
+#include <stdbool.h>
+#include <stddef.h>
+#include <stdint.h>
+
+#include <systemd/sd-id128.h>
+
+#include "time-util.h"
+
+#define EFI_VENDOR_LOADER SD_ID128_MAKE(4a,67,b0,82,0a,4c,41,cf,b6,c7,44,0b,29,bb,8c,4f)
+#define EFI_VENDOR_GLOBAL SD_ID128_MAKE(8b,e4,df,61,93,ca,11,d2,aa,0d,00,e0,98,03,2b,8c)
+#define EFI_VARIABLE_NON_VOLATILE 0x0000000000000001
+#define EFI_VARIABLE_BOOTSERVICE_ACCESS 0x0000000000000002
+#define EFI_VARIABLE_RUNTIME_ACCESS 0x0000000000000004
+
+#ifdef ENABLE_EFI
+
+bool is_efi_boot(void);
+bool is_efi_secure_boot(void);
+bool is_efi_secure_boot_setup_mode(void);
+int efi_reboot_to_firmware_supported(void);
+int efi_get_reboot_to_firmware(void);
+int efi_set_reboot_to_firmware(bool value);
+
+int efi_get_variable(sd_id128_t vendor, const char *name, uint32_t *attribute, void **value, size_t *size);
+int efi_set_variable(sd_id128_t vendor, const char *name, const void *value, size_t size);
+int efi_get_variable_string(sd_id128_t vendor, const char *name, char **p);
+
+int efi_get_boot_option(uint16_t nr, char **title, sd_id128_t *part_uuid, char **path, bool *active);
+int efi_add_boot_option(uint16_t id, const char *title, uint32_t part, uint64_t pstart, uint64_t psize, sd_id128_t part_uuid, const char *path);
+int efi_remove_boot_option(uint16_t id);
+int efi_get_boot_order(uint16_t **order);
+int efi_set_boot_order(uint16_t *order, size_t n);
+int efi_get_boot_options(uint16_t **options);
+
+int efi_loader_get_device_part_uuid(sd_id128_t *u);
+int efi_loader_get_boot_usec(usec_t *firmware, usec_t *loader);
+
+#else
+
+static inline bool is_efi_boot(void) {
+ return false;
+}
+
+static inline bool is_efi_secure_boot(void) {
+ return false;
+}
+
+static inline bool is_efi_secure_boot_setup_mode(void) {
+ return false;
+}
+
+static inline int efi_reboot_to_firmware_supported(void) {
+ return -EOPNOTSUPP;
+}
+
+static inline int efi_get_reboot_to_firmware(void) {
+ return -EOPNOTSUPP;
+}
+
+static inline int efi_set_reboot_to_firmware(bool value) {
+ return -EOPNOTSUPP;
+}
+
+static inline int efi_get_variable(sd_id128_t vendor, const char *name, uint32_t *attribute, void **value, size_t *size) {
+ return -EOPNOTSUPP;
+}
+
+static inline int efi_set_variable(sd_id128_t vendor, const char *name, const void *value, size_t size) {
+ return -EOPNOTSUPP;
+}
+
+static inline int efi_get_variable_string(sd_id128_t vendor, const char *name, char **p) {
+ return -EOPNOTSUPP;
+}
+
+static inline int efi_get_boot_option(uint16_t nr, char **title, sd_id128_t *part_uuid, char **path, bool *active) {
+ return -EOPNOTSUPP;
+}
+
+static inline int efi_add_boot_option(uint16_t id, const char *title, uint32_t part, uint64_t pstart, uint64_t psize, sd_id128_t part_uuid, const char *path) {
+ return -EOPNOTSUPP;
+}
+
+static inline int efi_remove_boot_option(uint16_t id) {
+ return -EOPNOTSUPP;
+}
+
+static inline int efi_get_boot_order(uint16_t **order) {
+ return -EOPNOTSUPP;
+}
+
+static inline int efi_set_boot_order(uint16_t *order, size_t n) {
+ return -EOPNOTSUPP;
+}
+
+static inline int efi_get_boot_options(uint16_t **options) {
+ return -EOPNOTSUPP;
+}
+
+static inline int efi_loader_get_device_part_uuid(sd_id128_t *u) {
+ return -EOPNOTSUPP;
+}
+
+static inline int efi_loader_get_boot_usec(usec_t *firmware, usec_t *loader) {
+ return -EOPNOTSUPP;
+}
+
+#endif
+
+char *efi_tilt_backslashes(char *s);
diff --git a/src/libshared/fstab-util.c b/src/libshared/fstab-util.c
new file mode 100644
index 0000000000..a4e0cd3267
--- /dev/null
+++ b/src/libshared/fstab-util.c
@@ -0,0 +1,263 @@
+/***
+ This file is part of systemd.
+
+ Copyright 2015 Zbigniew Jędrzejewski-Szmek
+
+ systemd is free software; you can redistribute it and/or modify it
+ under the terms of the GNU Lesser General Public License as published by
+ the Free Software Foundation; either version 2.1 of the License, or
+ (at your option) any later version.
+
+ systemd is distributed in the hope that it will be useful, but
+ WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ Lesser General Public License for more details.
+
+ You should have received a copy of the GNU Lesser General Public License
+ along with systemd; If not, see <http://www.gnu.org/licenses/>.
+***/
+
+#include <errno.h>
+#include <mntent.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+
+#include "alloc-util.h"
+#include "device-nodes.h"
+#include "fstab-util.h"
+#include "macro.h"
+#include "mount-util.h"
+#include "parse-util.h"
+#include "path-util.h"
+#include "string-util.h"
+#include "strv.h"
+#include "util.h"
+
+bool fstab_is_mount_point(const char *mount) {
+ _cleanup_endmntent_ FILE *f = NULL;
+ struct mntent *m;
+
+ f = setmntent("/etc/fstab", "r");
+ if (!f)
+ return false;
+
+ while ((m = getmntent(f)))
+ if (path_equal(m->mnt_dir, mount))
+ return true;
+
+ return false;
+}
+
+int fstab_filter_options(const char *opts, const char *names,
+ const char **namefound, char **value, char **filtered) {
+ const char *name, *n = NULL, *x;
+ _cleanup_strv_free_ char **stor = NULL;
+ _cleanup_free_ char *v = NULL, **strv = NULL;
+
+ assert(names && *names);
+
+ if (!opts)
+ goto answer;
+
+ /* If !value and !filtered, this function is not allowed to fail. */
+
+ if (!filtered) {
+ const char *word, *state;
+ size_t l;
+
+ FOREACH_WORD_SEPARATOR(word, l, opts, ",", state)
+ NULSTR_FOREACH(name, names) {
+ if (l < strlen(name))
+ continue;
+ if (!strneq(word, name, strlen(name)))
+ continue;
+
+ /* we know that the string is NUL
+ * terminated, so *x is valid */
+ x = word + strlen(name);
+ if (IN_SET(*x, '\0', '=', ',')) {
+ n = name;
+ if (value) {
+ free(v);
+ if (IN_SET(*x, '\0', ','))
+ v = NULL;
+ else {
+ assert(*x == '=');
+ x++;
+ v = strndup(x, l - strlen(name) - 1);
+ if (!v)
+ return -ENOMEM;
+ }
+ }
+ }
+ }
+ } else {
+ char **t, **s;
+
+ stor = strv_split(opts, ",");
+ if (!stor)
+ return -ENOMEM;
+ strv = memdup(stor, sizeof(char*) * (strv_length(stor) + 1));
+ if (!strv)
+ return -ENOMEM;
+
+ for (s = t = strv; *s; s++) {
+ NULSTR_FOREACH(name, names) {
+ x = startswith(*s, name);
+ if (x && IN_SET(*x, '\0', '='))
+ goto found;
+ }
+
+ *t = *s;
+ t++;
+ continue;
+ found:
+ /* Keep the last occurence found */
+ n = name;
+ if (value) {
+ free(v);
+ if (*x == '\0')
+ v = NULL;
+ else {
+ assert(*x == '=');
+ x++;
+ v = strdup(x);
+ if (!v)
+ return -ENOMEM;
+ }
+ }
+ }
+ *t = NULL;
+ }
+
+answer:
+ if (namefound)
+ *namefound = n;
+ if (filtered) {
+ char *f;
+
+ f = strv_join(strv, ",");
+ if (!f)
+ return -ENOMEM;
+
+ *filtered = f;
+ }
+ if (value) {
+ *value = v;
+ v = NULL;
+ }
+
+ return !!n;
+}
+
+int fstab_extract_values(const char *opts, const char *name, char ***values) {
+ _cleanup_strv_free_ char **optsv = NULL, **res = NULL;
+ char **s;
+
+ assert(opts);
+ assert(name);
+ assert(values);
+
+ optsv = strv_split(opts, ",");
+ if (!optsv)
+ return -ENOMEM;
+
+ STRV_FOREACH(s, optsv) {
+ char *arg;
+ int r;
+
+ arg = startswith(*s, name);
+ if (!arg || *arg != '=')
+ continue;
+ r = strv_extend(&res, arg + 1);
+ if (r < 0)
+ return r;
+ }
+
+ *values = res;
+ res = NULL;
+
+ return !!*values;
+}
+
+int fstab_find_pri(const char *options, int *ret) {
+ _cleanup_free_ char *opt = NULL;
+ int r;
+ unsigned pri;
+
+ assert(ret);
+
+ r = fstab_filter_options(options, "pri\0", NULL, &opt, NULL);
+ if (r < 0)
+ return r;
+ if (r == 0 || !opt)
+ return 0;
+
+ r = safe_atou(opt, &pri);
+ if (r < 0)
+ return r;
+
+ if ((int) pri < 0)
+ return -ERANGE;
+
+ *ret = (int) pri;
+ return 1;
+}
+
+static char *unquote(const char *s, const char* quotes) {
+ size_t l;
+ assert(s);
+
+ /* This is rather stupid, simply removes the heading and
+ * trailing quotes if there is one. Doesn't care about
+ * escaping or anything.
+ *
+ * DON'T USE THIS FOR NEW CODE ANYMORE!*/
+
+ l = strlen(s);
+ if (l < 2)
+ return strdup(s);
+
+ if (strchr(quotes, s[0]) && s[l-1] == s[0])
+ return strndup(s+1, l-2);
+
+ return strdup(s);
+}
+
+static char *tag_to_udev_node(const char *tagvalue, const char *by) {
+ _cleanup_free_ char *t = NULL, *u = NULL;
+ size_t enc_len;
+
+ u = unquote(tagvalue, QUOTES);
+ if (!u)
+ return NULL;
+
+ enc_len = strlen(u) * 4 + 1;
+ t = new(char, enc_len);
+ if (!t)
+ return NULL;
+
+ if (encode_devnode_name(u, t, enc_len) < 0)
+ return NULL;
+
+ return strjoin("/dev/disk/by-", by, "/", t, NULL);
+}
+
+char *fstab_node_to_udev_node(const char *p) {
+ assert(p);
+
+ if (startswith(p, "LABEL="))
+ return tag_to_udev_node(p+6, "label");
+
+ if (startswith(p, "UUID="))
+ return tag_to_udev_node(p+5, "uuid");
+
+ if (startswith(p, "PARTUUID="))
+ return tag_to_udev_node(p+9, "partuuid");
+
+ if (startswith(p, "PARTLABEL="))
+ return tag_to_udev_node(p+10, "partlabel");
+
+ return strdup(p);
+}
diff --git a/src/libshared/fstab-util.h b/src/libshared/fstab-util.h
new file mode 100644
index 0000000000..679f6902f7
--- /dev/null
+++ b/src/libshared/fstab-util.h
@@ -0,0 +1,52 @@
+#pragma once
+
+/***
+ This file is part of systemd.
+
+ Copyright 2015 Zbigniew Jędrzejewski-Szmek
+
+ systemd is free software; you can redistribute it and/or modify it
+ under the terms of the GNU Lesser General Public License as published by
+ the Free Software Foundation; either version 2.1 of the License, or
+ (at your option) any later version.
+
+ systemd is distributed in the hope that it will be useful, but
+ WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ Lesser General Public License for more details.
+
+ You should have received a copy of the GNU Lesser General Public License
+ along with systemd; If not, see <http://www.gnu.org/licenses/>.
+***/
+
+#include <stdbool.h>
+#include <stddef.h>
+
+#include "macro.h"
+
+bool fstab_is_mount_point(const char *mount);
+
+int fstab_filter_options(const char *opts, const char *names, const char **namefound, char **value, char **filtered);
+
+int fstab_extract_values(const char *opts, const char *name, char ***values);
+
+static inline bool fstab_test_option(const char *opts, const char *names) {
+ return !!fstab_filter_options(opts, names, NULL, NULL, NULL);
+}
+
+int fstab_find_pri(const char *options, int *ret);
+
+static inline bool fstab_test_yes_no_option(const char *opts, const char *yes_no) {
+ int r;
+ const char *opt;
+
+ /* If first name given is last, return 1.
+ * If second name given is last or neither is found, return 0. */
+
+ r = fstab_filter_options(opts, yes_no, &opt, NULL, NULL);
+ assert(r >= 0);
+
+ return opt == yes_no;
+}
+
+char *fstab_node_to_udev_node(const char *p);
diff --git a/src/libshared/generator.c b/src/libshared/generator.c
new file mode 100644
index 0000000000..cd3c35cd55
--- /dev/null
+++ b/src/libshared/generator.c
@@ -0,0 +1,193 @@
+/***
+ This file is part of systemd.
+
+ Copyright 2014 Lennart Poettering
+
+ systemd is free software; you can redistribute it and/or modify it
+ under the terms of the GNU Lesser General Public License as published by
+ the Free Software Foundation; either version 2.1 of the License, or
+ (at your option) any later version.
+
+ systemd is distributed in the hope that it will be useful, but
+ WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ Lesser General Public License for more details.
+
+ You should have received a copy of the GNU Lesser General Public License
+ along with systemd; If not, see <http://www.gnu.org/licenses/>.
+***/
+
+#include <errno.h>
+#include <unistd.h>
+
+#include "alloc-util.h"
+#include "dropin.h"
+#include "escape.h"
+#include "fd-util.h"
+#include "fileio.h"
+#include "fstab-util.h"
+#include "generator.h"
+#include "log.h"
+#include "macro.h"
+#include "mkdir.h"
+#include "path-util.h"
+#include "special.h"
+#include "string-util.h"
+#include "time-util.h"
+#include "unit-name.h"
+#include "util.h"
+
+static int write_fsck_sysroot_service(const char *dir, const char *what) {
+ _cleanup_free_ char *device = NULL, *escaped = NULL;
+ _cleanup_fclose_ FILE *f = NULL;
+ const char *unit;
+ int r;
+
+ escaped = cescape(what);
+ if (!escaped)
+ return log_oom();
+
+ unit = strjoina(dir, "/systemd-fsck-root.service");
+ log_debug("Creating %s", unit);
+
+ r = unit_name_from_path(what, ".device", &device);
+ if (r < 0)
+ return log_error_errno(r, "Failed to convert device \"%s\" to unit name: %m", what);
+
+ f = fopen(unit, "wxe");
+ if (!f)
+ return log_error_errno(errno, "Failed to create unit file %s: %m", unit);
+
+ fprintf(f,
+ "# Automatically generated by %1$s\n\n"
+ "[Unit]\n"
+ "Documentation=man:systemd-fsck-root.service(8)\n"
+ "Description=File System Check on %2$s\n"
+ "DefaultDependencies=no\n"
+ "BindsTo=%3$s\n"
+ "After=%3$s local-fs-pre.target\n"
+ "Before=shutdown.target\n"
+ "\n"
+ "[Service]\n"
+ "Type=oneshot\n"
+ "RemainAfterExit=yes\n"
+ "ExecStart=" SYSTEMD_FSCK_PATH " %4$s\n"
+ "TimeoutSec=0\n",
+ program_invocation_short_name,
+ what,
+ device,
+ escaped);
+
+ r = fflush_and_check(f);
+ if (r < 0)
+ return log_error_errno(r, "Failed to write unit file %s: %m", unit);
+
+ return 0;
+}
+
+int generator_write_fsck_deps(
+ FILE *f,
+ const char *dir,
+ const char *what,
+ const char *where,
+ const char *fstype) {
+
+ int r;
+
+ assert(f);
+ assert(dir);
+ assert(what);
+ assert(where);
+
+ if (!is_device_path(what)) {
+ log_warning("Checking was requested for \"%s\", but it is not a device.", what);
+ return 0;
+ }
+
+ if (!isempty(fstype) && !streq(fstype, "auto")) {
+ r = fsck_exists(fstype);
+ if (r < 0)
+ log_warning_errno(r, "Checking was requested for %s, but couldn't detect if fsck.%s may be used, proceeding: %m", what, fstype);
+ else if (r == 0) {
+ /* treat missing check as essentially OK */
+ log_debug("Checking was requested for %s, but fsck.%s does not exist.", what, fstype);
+ return 0;
+ }
+ }
+
+ if (path_equal(where, "/")) {
+ const char *lnk;
+
+ lnk = strjoina(dir, "/" SPECIAL_LOCAL_FS_TARGET ".wants/systemd-fsck-root.service");
+
+ mkdir_parents(lnk, 0755);
+ if (symlink(SYSTEM_DATA_UNIT_PATH "/systemd-fsck-root.service", lnk) < 0)
+ return log_error_errno(errno, "Failed to create symlink %s: %m", lnk);
+
+ } else {
+ _cleanup_free_ char *_fsck = NULL;
+ const char *fsck;
+
+ if (in_initrd() && path_equal(where, "/sysroot")) {
+ r = write_fsck_sysroot_service(dir, what);
+ if (r < 0)
+ return r;
+
+ fsck = "systemd-fsck-root.service";
+ } else {
+ r = unit_name_from_path_instance("systemd-fsck", what, ".service", &_fsck);
+ if (r < 0)
+ return log_error_errno(r, "Failed to create fsck service name: %m");
+
+ fsck = _fsck;
+ }
+
+ fprintf(f,
+ "Requires=%1$s\n"
+ "After=%1$s\n",
+ fsck);
+ }
+
+ return 0;
+}
+
+int generator_write_timeouts(
+ const char *dir,
+ const char *what,
+ const char *where,
+ const char *opts,
+ char **filtered) {
+
+ /* Allow configuration how long we wait for a device that
+ * backs a mount point to show up. This is useful to support
+ * endless device timeouts for devices that show up only after
+ * user input, like crypto devices. */
+
+ _cleanup_free_ char *node = NULL, *unit = NULL, *timeout = NULL;
+ usec_t u;
+ int r;
+
+ r = fstab_filter_options(opts, "comment=systemd.device-timeout\0" "x-systemd.device-timeout\0",
+ NULL, &timeout, filtered);
+ if (r <= 0)
+ return r;
+
+ r = parse_sec(timeout, &u);
+ if (r < 0) {
+ log_warning("Failed to parse timeout for %s, ignoring: %s", where, timeout);
+ return 0;
+ }
+
+ node = fstab_node_to_udev_node(what);
+ if (!node)
+ return log_oom();
+
+ r = unit_name_from_path(node, ".device", &unit);
+ if (r < 0)
+ return log_error_errno(r, "Failed to make unit name from path: %m");
+
+ return write_drop_in_format(dir, unit, 50, "device-timeout",
+ "# Automatically generated by %s\n\n"
+ "[Unit]\nJobTimeoutSec=%s",
+ program_invocation_short_name, timeout);
+}
diff --git a/src/libshared/generator.h b/src/libshared/generator.h
new file mode 100644
index 0000000000..a734e13970
--- /dev/null
+++ b/src/libshared/generator.h
@@ -0,0 +1,36 @@
+#pragma once
+
+/***
+ This file is part of systemd.
+
+ Copyright 2014 Lennart Poettering
+
+ systemd is free software; you can redistribute it and/or modify it
+ under the terms of the GNU Lesser General Public License as published by
+ the Free Software Foundation; either version 2.1 of the License, or
+ (at your option) any later version.
+
+ systemd is distributed in the hope that it will be useful, but
+ WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ Lesser General Public License for more details.
+
+ You should have received a copy of the GNU Lesser General Public License
+ along with systemd; If not, see <http://www.gnu.org/licenses/>.
+***/
+
+#include <stdio.h>
+
+int generator_write_fsck_deps(
+ FILE *f,
+ const char *dir,
+ const char *what,
+ const char *where,
+ const char *type);
+
+int generator_write_timeouts(
+ const char *dir,
+ const char *what,
+ const char *where,
+ const char *opts,
+ char **filtered);
diff --git a/src/libshared/gpt.h b/src/libshared/gpt.h
new file mode 100644
index 0000000000..9b8f59abee
--- /dev/null
+++ b/src/libshared/gpt.h
@@ -0,0 +1,66 @@
+/***
+ This file is part of systemd.
+
+ Copyright 2014 Lennart Poettering
+
+ systemd is free software; you can redistribute it and/or modify it
+ under the terms of the GNU Lesser General Public License as published by
+ the Free Software Foundation; either version 2.1 of the License, or
+ (at your option) any later version.
+
+ systemd is distributed in the hope that it will be useful, but
+ WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ Lesser General Public License for more details.
+
+ You should have received a copy of the GNU Lesser General Public License
+ along with systemd; If not, see <http://www.gnu.org/licenses/>.
+***/
+
+#pragma once
+
+#include <endian.h>
+
+#include <systemd/sd-id128.h>
+
+/* We only support root disk discovery for x86, x86-64, Itanium and ARM for
+ * now, since EFI for anything else doesn't really exist, and we only
+ * care for root partitions on the same disk as the EFI ESP. */
+
+#define GPT_ROOT_X86 SD_ID128_MAKE(44,47,95,40,f2,97,41,b2,9a,f7,d1,31,d5,f0,45,8a)
+#define GPT_ROOT_X86_64 SD_ID128_MAKE(4f,68,bc,e3,e8,cd,4d,b1,96,e7,fb,ca,f9,84,b7,09)
+#define GPT_ROOT_ARM SD_ID128_MAKE(69,da,d7,10,2c,e4,4e,3c,b1,6c,21,a1,d4,9a,be,d3)
+#define GPT_ROOT_ARM_64 SD_ID128_MAKE(b9,21,b0,45,1d,f0,41,c3,af,44,4c,6f,28,0d,3f,ae)
+#define GPT_ROOT_IA64 SD_ID128_MAKE(99,3d,8d,3d,f8,0e,42,25,85,5a,9d,af,8e,d7,ea,97)
+
+#define GPT_ESP SD_ID128_MAKE(c1,2a,73,28,f8,1f,11,d2,ba,4b,00,a0,c9,3e,c9,3b)
+#define GPT_SWAP SD_ID128_MAKE(06,57,fd,6d,a4,ab,43,c4,84,e5,09,33,c8,4b,4f,4f)
+#define GPT_HOME SD_ID128_MAKE(93,3a,c7,e1,2e,b4,4f,13,b8,44,0e,14,e2,ae,f9,15)
+#define GPT_SRV SD_ID128_MAKE(3b,8f,84,25,20,e0,4f,3b,90,7f,1a,25,a7,6f,98,e8)
+
+#if defined(__x86_64__)
+# define GPT_ROOT_NATIVE GPT_ROOT_X86_64
+# define GPT_ROOT_SECONDARY GPT_ROOT_X86
+#elif defined(__i386__)
+# define GPT_ROOT_NATIVE GPT_ROOT_X86
+#endif
+
+#if defined(__ia64__)
+# define GPT_ROOT_NATIVE GPT_ROOT_IA64
+#endif
+
+#if defined(__aarch64__) && (__BYTE_ORDER != __BIG_ENDIAN)
+# define GPT_ROOT_NATIVE GPT_ROOT_ARM_64
+# define GPT_ROOT_SECONDARY GPT_ROOT_ARM
+#elif defined(__arm__) && (__BYTE_ORDER != __BIG_ENDIAN)
+# define GPT_ROOT_NATIVE GPT_ROOT_ARM
+#endif
+
+/* Flags we recognize on the root, swap, home and srv partitions when
+ * doing auto-discovery. These happen to be identical to what
+ * Microsoft defines for its own Basic Data Partitions, but that's
+ * just because we saw no point in defining any other values here. */
+#define GPT_FLAG_READ_ONLY (1ULL << 60)
+#define GPT_FLAG_NO_AUTO (1ULL << 63)
+
+#define GPT_LINUX_GENERIC SD_ID128_MAKE(0f,c6,3d,af,84,83,47,72,8e,79,3d,69,d8,47,7d,e4)
diff --git a/src/libshared/ima-util.c b/src/libshared/ima-util.c
new file mode 100644
index 0000000000..789064d653
--- /dev/null
+++ b/src/libshared/ima-util.c
@@ -0,0 +1,32 @@
+/***
+ This file is part of systemd.
+
+ Copyright 2013 Lennart Poettering
+
+ systemd is free software; you can redistribute it and/or modify it
+ under the terms of the GNU Lesser General Public License as published by
+ the Free Software Foundation; either version 2.1 of the License, or
+ (at your option) any later version.
+
+ systemd is distributed in the hope that it will be useful, but
+ WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ Lesser General Public License for more details.
+
+ You should have received a copy of the GNU Lesser General Public License
+ along with systemd; If not, see <http://www.gnu.org/licenses/>.
+***/
+
+#include <unistd.h>
+
+#include "ima-util.h"
+
+static int use_ima_cached = -1;
+
+bool use_ima(void) {
+
+ if (use_ima_cached < 0)
+ use_ima_cached = access("/sys/kernel/security/ima/", F_OK) >= 0;
+
+ return use_ima_cached;
+}
diff --git a/src/libshared/ima-util.h b/src/libshared/ima-util.h
new file mode 100644
index 0000000000..5be94761fd
--- /dev/null
+++ b/src/libshared/ima-util.h
@@ -0,0 +1,24 @@
+#pragma once
+
+/***
+ This file is part of systemd.
+
+ Copyright 2013 Lennart Poettering
+
+ systemd is free software; you can redistribute it and/or modify it
+ under the terms of the GNU Lesser General Public License as published by
+ the Free Software Foundation; either version 2.1 of the License, or
+ (at your option) any later version.
+
+ systemd is distributed in the hope that it will be useful, but
+ WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ Lesser General Public License for more details.
+
+ You should have received a copy of the GNU Lesser General Public License
+ along with systemd; If not, see <http://www.gnu.org/licenses/>.
+***/
+
+#include <stdbool.h>
+
+bool use_ima(void);
diff --git a/src/libshared/import-util.c b/src/libshared/import-util.c
new file mode 100644
index 0000000000..ab701ad8b2
--- /dev/null
+++ b/src/libshared/import-util.c
@@ -0,0 +1,185 @@
+/***
+ This file is part of systemd.
+
+ Copyright 2015 Lennart Poettering
+
+ systemd is free software; you can redistribute it and/or modify it
+ under the terms of the GNU Lesser General Public License as published by
+ the Free Software Foundation; either version 2.1 of the License, or
+ (at your option) any later version.
+
+ systemd is distributed in the hope that it will be useful, but
+ WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ Lesser General Public License for more details.
+
+ You should have received a copy of the GNU Lesser General Public License
+ along with systemd; If not, see <http://www.gnu.org/licenses/>.
+***/
+
+#include <errno.h>
+#include <string.h>
+
+#include "alloc-util.h"
+#include "btrfs-util.h"
+#include "import-util.h"
+#include "log.h"
+#include "macro.h"
+#include "path-util.h"
+#include "string-table.h"
+#include "string-util.h"
+#include "util.h"
+
+int import_url_last_component(const char *url, char **ret) {
+ const char *e, *p;
+ char *s;
+
+ e = strchrnul(url, '?');
+
+ while (e > url && e[-1] == '/')
+ e--;
+
+ p = e;
+ while (p > url && p[-1] != '/')
+ p--;
+
+ if (e <= p)
+ return -EINVAL;
+
+ s = strndup(p, e - p);
+ if (!s)
+ return -ENOMEM;
+
+ *ret = s;
+ return 0;
+}
+
+
+int import_url_change_last_component(const char *url, const char *suffix, char **ret) {
+ const char *e;
+ char *s;
+
+ assert(url);
+ assert(ret);
+
+ e = strchrnul(url, '?');
+
+ while (e > url && e[-1] == '/')
+ e--;
+
+ while (e > url && e[-1] != '/')
+ e--;
+
+ if (e <= url)
+ return -EINVAL;
+
+ s = new(char, (e - url) + strlen(suffix) + 1);
+ if (!s)
+ return -ENOMEM;
+
+ strcpy(mempcpy(s, url, e - url), suffix);
+ *ret = s;
+ return 0;
+}
+
+static const char* const import_verify_table[_IMPORT_VERIFY_MAX] = {
+ [IMPORT_VERIFY_NO] = "no",
+ [IMPORT_VERIFY_CHECKSUM] = "checksum",
+ [IMPORT_VERIFY_SIGNATURE] = "signature",
+};
+
+DEFINE_STRING_TABLE_LOOKUP(import_verify, ImportVerify);
+
+int tar_strip_suffixes(const char *name, char **ret) {
+ const char *e;
+ char *s;
+
+ e = endswith(name, ".tar");
+ if (!e)
+ e = endswith(name, ".tar.xz");
+ if (!e)
+ e = endswith(name, ".tar.gz");
+ if (!e)
+ e = endswith(name, ".tar.bz2");
+ if (!e)
+ e = endswith(name, ".tgz");
+ if (!e)
+ e = strchr(name, 0);
+
+ if (e <= name)
+ return -EINVAL;
+
+ s = strndup(name, e - name);
+ if (!s)
+ return -ENOMEM;
+
+ *ret = s;
+ return 0;
+}
+
+int raw_strip_suffixes(const char *p, char **ret) {
+
+ static const char suffixes[] =
+ ".xz\0"
+ ".gz\0"
+ ".bz2\0"
+ ".raw\0"
+ ".qcow2\0"
+ ".img\0"
+ ".bin\0";
+
+ _cleanup_free_ char *q = NULL;
+
+ q = strdup(p);
+ if (!q)
+ return -ENOMEM;
+
+ for (;;) {
+ const char *sfx;
+ bool changed = false;
+
+ NULSTR_FOREACH(sfx, suffixes) {
+ char *e;
+
+ e = endswith(q, sfx);
+ if (e) {
+ *e = 0;
+ changed = true;
+ }
+ }
+
+ if (!changed)
+ break;
+ }
+
+ *ret = q;
+ q = NULL;
+
+ return 0;
+}
+
+int import_assign_pool_quota_and_warn(const char *path) {
+ int r;
+
+ r = btrfs_subvol_auto_qgroup("/var/lib/machines", 0, true);
+ if (r == -ENOTTY) {
+ log_debug_errno(r, "Failed to set up default quota hierarchy for /var/lib/machines, as directory is not on btrfs or not a subvolume. Ignoring.");
+ return 0;
+ }
+ if (r < 0)
+ return log_error_errno(r, "Failed to set up default quota hierarchy for /var/lib/machines: %m");
+ if (r > 0)
+ log_info("Set up default quota hierarchy for /var/lib/machines.");
+
+ r = btrfs_subvol_auto_qgroup(path, 0, true);
+ if (r == -ENOTTY) {
+ log_debug_errno(r, "Failed to set up quota hierarchy for %s, as directory is not on btrfs or not a subvolume. Ignoring.", path);
+ return 0;
+ }
+ if (r < 0)
+ return log_error_errno(r, "Failed to set up default quota hierarchy for %s: %m", path);
+ if (r > 0)
+ log_info("Set up default quota hierarchy for %s.", path);
+
+ return 0;
+}
diff --git a/src/libshared/import-util.h b/src/libshared/import-util.h
new file mode 100644
index 0000000000..77b17d91f3
--- /dev/null
+++ b/src/libshared/import-util.h
@@ -0,0 +1,43 @@
+#pragma once
+
+/***
+ This file is part of systemd.
+
+ Copyright 2015 Lennart Poettering
+
+ systemd is free software; you can redistribute it and/or modify it
+ under the terms of the GNU Lesser General Public License as published by
+ the Free Software Foundation; either version 2.1 of the License, or
+ (at your option) any later version.
+
+ systemd is distributed in the hope that it will be useful, but
+ WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ Lesser General Public License for more details.
+
+ You should have received a copy of the GNU Lesser General Public License
+ along with systemd; If not, see <http://www.gnu.org/licenses/>.
+***/
+
+#include <stdbool.h>
+
+#include "macro.h"
+
+typedef enum ImportVerify {
+ IMPORT_VERIFY_NO,
+ IMPORT_VERIFY_CHECKSUM,
+ IMPORT_VERIFY_SIGNATURE,
+ _IMPORT_VERIFY_MAX,
+ _IMPORT_VERIFY_INVALID = -1,
+} ImportVerify;
+
+int import_url_last_component(const char *url, char **ret);
+int import_url_change_last_component(const char *url, const char *suffix, char **ret);
+
+const char* import_verify_to_string(ImportVerify v) _const_;
+ImportVerify import_verify_from_string(const char *s) _pure_;
+
+int tar_strip_suffixes(const char *name, char **ret);
+int raw_strip_suffixes(const char *name, char **ret);
+
+int import_assign_pool_quota_and_warn(const char *path);
diff --git a/src/libshared/initreq.h b/src/libshared/initreq.h
new file mode 100644
index 0000000000..710037d84b
--- /dev/null
+++ b/src/libshared/initreq.h
@@ -0,0 +1,77 @@
+/*
+ * initreq.h Interface to talk to init through /dev/initctl.
+ *
+ * Copyright (C) 1995-2004 Miquel van Smoorenburg
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2 of the License, or (at your option) any later version.
+ *
+ * Version: @(#)initreq.h 1.28 31-Mar-2004 MvS
+ *
+ */
+#ifndef _INITREQ_H
+#define _INITREQ_H
+
+#include <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/libshared/install-printf.c b/src/libshared/install-printf.c
new file mode 100644
index 0000000000..88143361da
--- /dev/null
+++ b/src/libshared/install-printf.c
@@ -0,0 +1,133 @@
+/***
+ This file is part of systemd.
+
+ Copyright 2013 Zbigniew Jędrzejewski-Szmek
+
+ systemd is free software; you can redistribute it and/or modify it
+ under the terms of the GNU Lesser General Public License as published by
+ the Free Software Foundation; either version 2.1 of the License, or
+ (at your option) any later version.
+
+ systemd is distributed in the hope that it will be useful, but
+ WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ Lesser General Public License for more details.
+
+ You should have received a copy of the GNU Lesser General Public License
+ along with systemd; If not, see <http://www.gnu.org/licenses/>.
+***/
+
+#include <errno.h>
+#include <stdio.h>
+#include <string.h>
+#include <unistd.h>
+
+#include "formats-util.h"
+#include "install-printf.h"
+#include "install.h"
+#include "macro.h"
+#include "specifier.h"
+#include "unit-name.h"
+#include "user-util.h"
+
+static int specifier_prefix_and_instance(char specifier, void *data, void *userdata, char **ret) {
+ UnitFileInstallInfo *i = userdata;
+
+ assert(i);
+
+ return unit_name_to_prefix_and_instance(i->name, ret);
+}
+
+static int specifier_prefix(char specifier, void *data, void *userdata, char **ret) {
+ UnitFileInstallInfo *i = userdata;
+
+ assert(i);
+
+ return unit_name_to_prefix(i->name, ret);
+}
+
+static int specifier_instance(char specifier, void *data, void *userdata, char **ret) {
+ UnitFileInstallInfo *i = userdata;
+ char *instance;
+ int r;
+
+ assert(i);
+
+ r = unit_name_to_instance(i->name, &instance);
+ if (r < 0)
+ return r;
+
+ if (!instance) {
+ instance = strdup("");
+ if (!instance)
+ return -ENOMEM;
+ }
+
+ *ret = instance;
+ return 0;
+}
+
+static int specifier_user_name(char specifier, void *data, void *userdata, char **ret) {
+ char *t;
+
+ /* If we are UID 0 (root), this will not result in NSS,
+ * otherwise it might. This is good, as we want to be able to
+ * run this in PID 1, where our user ID is 0, but where NSS
+ * lookups are not allowed. */
+
+ t = getusername_malloc();
+ if (!t)
+ return -ENOMEM;
+
+ *ret = t;
+ return 0;
+}
+
+static int specifier_user_id(char specifier, void *data, void *userdata, char **ret) {
+
+ if (asprintf(ret, UID_FMT, getuid()) < 0)
+ return -ENOMEM;
+
+ return 0;
+}
+
+int install_full_printf(UnitFileInstallInfo *i, const char *format, char **ret) {
+
+ /* This is similar to unit_full_printf() but does not support
+ * anything path-related.
+ *
+ * %n: the full id of the unit (foo@bar.waldo)
+ * %N: the id of the unit without the suffix (foo@bar)
+ * %p: the prefix (foo)
+ * %i: the instance (bar)
+
+ * %U the UID of the running user
+ * %u the username of running user
+ * %m the machine ID of the running system
+ * %H the host name of the running system
+ * %b the boot ID of the running system
+ * %v `uname -r` of the running system
+ */
+
+ const Specifier table[] = {
+ { 'n', specifier_string, i->name },
+ { 'N', specifier_prefix_and_instance, NULL },
+ { 'p', specifier_prefix, NULL },
+ { 'i', specifier_instance, NULL },
+
+ { 'U', specifier_user_id, NULL },
+ { 'u', specifier_user_name, NULL },
+
+ { 'm', specifier_machine_id, NULL },
+ { 'H', specifier_host_name, NULL },
+ { 'b', specifier_boot_id, NULL },
+ { 'v', specifier_kernel_release, NULL },
+ {}
+ };
+
+ assert(i);
+ assert(format);
+ assert(ret);
+
+ return specifier_printf(format, table, i, ret);
+}
diff --git a/src/libshared/install-printf.h b/src/libshared/install-printf.h
new file mode 100644
index 0000000000..acf519f4f7
--- /dev/null
+++ b/src/libshared/install-printf.h
@@ -0,0 +1,24 @@
+/***
+ This file is part of systemd.
+
+ Copyright 2013 Zbigniew Jędrzejewski-Szmek
+
+ systemd is free software; you can redistribute it and/or modify it
+ under the terms of the GNU Lesser General Public License as published by
+ the Free Software Foundation; either version 2.1 of the License, or
+ (at your option) any later version.
+
+ systemd is distributed in the hope that it will be useful, but
+ WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ Lesser General Public License for more details.
+
+ You should have received a copy of the GNU Lesser General Public License
+ along with systemd; If not, see <http://www.gnu.org/licenses/>.
+***/
+
+#pragma once
+
+#include "install.h"
+
+int install_full_printf(UnitFileInstallInfo *i, const char *format, char **ret);
diff --git a/src/libshared/install.c b/src/libshared/install.c
new file mode 100644
index 0000000000..ef8f485cae
--- /dev/null
+++ b/src/libshared/install.c
@@ -0,0 +1,2474 @@
+/***
+ This file is part of systemd.
+
+ Copyright 2011 Lennart Poettering
+
+ systemd is free software; you can redistribute it and/or modify it
+ under the terms of the GNU Lesser General Public License as published by
+ the Free Software Foundation; either version 2.1 of the License, or
+ (at your option) any later version.
+
+ systemd is distributed in the hope that it will be useful, but
+ WITHOUT ANY WARRANTY; without even the implied warranty <of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ Lesser General Public License for more details.
+
+ You should have received a copy of the GNU Lesser General Public License
+ along with systemd; If not, see <http://www.gnu.org/licenses/>.
+***/
+
+#include <dirent.h>
+#include <errno.h>
+#include <fcntl.h>
+#include <fnmatch.h>
+#include <limits.h>
+#include <stddef.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <sys/stat.h>
+#include <unistd.h>
+
+#include "alloc-util.h"
+#include "conf-files.h"
+#include "conf-parser.h"
+#include "dirent-util.h"
+#include "extract-word.h"
+#include "fd-util.h"
+#include "fileio.h"
+#include "fs-util.h"
+#include "hashmap.h"
+#include "install-printf.h"
+#include "install.h"
+#include "log.h"
+#include "macro.h"
+#include "mkdir.h"
+#include "path-lookup.h"
+#include "path-util.h"
+#include "set.h"
+#include "special.h"
+#include "stat-util.h"
+#include "string-table.h"
+#include "string-util.h"
+#include "strv.h"
+#include "unit-name.h"
+
+#define UNIT_FILE_FOLLOW_SYMLINK_MAX 64
+
+typedef enum SearchFlags {
+ SEARCH_LOAD = 1,
+ SEARCH_FOLLOW_CONFIG_SYMLINKS = 2,
+} SearchFlags;
+
+typedef struct {
+ OrderedHashmap *will_process;
+ OrderedHashmap *have_processed;
+} InstallContext;
+
+static int in_search_path(const char *path, char **search) {
+ _cleanup_free_ char *parent = NULL;
+ char **i;
+
+ assert(path);
+
+ parent = dirname_malloc(path);
+ if (!parent)
+ return -ENOMEM;
+
+ STRV_FOREACH(i, search)
+ if (path_equal(parent, *i))
+ return true;
+
+ return false;
+}
+
+static int get_config_path(UnitFileScope scope, bool runtime, const char *root_dir, char **ret) {
+ char *p = NULL;
+ int r;
+
+ assert(scope >= 0);
+ assert(scope < _UNIT_FILE_SCOPE_MAX);
+ assert(ret);
+
+ /* This determines where we shall create or remove our
+ * installation ("configuration") symlinks */
+
+ switch (scope) {
+
+ case UNIT_FILE_SYSTEM:
+
+ if (runtime)
+ p = path_join(root_dir, "/run/systemd/system", NULL);
+ else
+ p = path_join(root_dir, SYSTEM_CONFIG_UNIT_PATH, NULL);
+ break;
+
+ case UNIT_FILE_GLOBAL:
+
+ if (root_dir)
+ return -EINVAL;
+
+ if (runtime)
+ p = strdup("/run/systemd/user");
+ else
+ p = strdup(USER_CONFIG_UNIT_PATH);
+ break;
+
+ case UNIT_FILE_USER:
+
+ if (root_dir)
+ return -EINVAL;
+
+ if (runtime)
+ r = user_runtime_dir(&p);
+ else
+ r = user_config_home(&p);
+ if (r < 0)
+ return r;
+ if (r == 0)
+ return -ENOENT;
+
+ break;
+
+ default:
+ assert_not_reached("Bad scope");
+ }
+
+ if (!p)
+ return -ENOMEM;
+
+ *ret = p;
+ return 0;
+}
+
+static bool is_config_path(UnitFileScope scope, const char *path) {
+ int r;
+
+ assert(scope >= 0);
+ assert(scope < _UNIT_FILE_SCOPE_MAX);
+ assert(path);
+
+ /* Checks whether the specified path is intended for
+ * configuration or is outside of it */
+
+ switch (scope) {
+
+ case UNIT_FILE_SYSTEM:
+ case UNIT_FILE_GLOBAL:
+ return path_startswith(path, "/etc") ||
+ path_startswith(path, SYSTEM_CONFIG_UNIT_PATH) ||
+ path_startswith(path, "/run");
+
+
+ case UNIT_FILE_USER: {
+ _cleanup_free_ char *p = NULL;
+
+ r = user_config_home(&p);
+ if (r < 0)
+ return r;
+ if (r > 0 && path_startswith(path, p))
+ return true;
+
+ p = mfree(p);
+
+ r = user_runtime_dir(&p);
+ if (r < 0)
+ return r;
+ if (r > 0 && path_startswith(path, p))
+ return true;
+
+ return false;
+ }
+
+ default:
+ assert_not_reached("Bad scope");
+ }
+}
+
+
+static int verify_root_dir(UnitFileScope scope, const char **root_dir) {
+ int r;
+
+ assert(root_dir);
+
+ /* Verifies that the specified root directory to operate on
+ * makes sense. Reset it to NULL if it is the root directory
+ * or set to empty */
+
+ if (isempty(*root_dir) || path_equal(*root_dir, "/")) {
+ *root_dir = NULL;
+ return 0;
+ }
+
+ if (scope != UNIT_FILE_SYSTEM)
+ return -EINVAL;
+
+ r = is_dir(*root_dir, true);
+ if (r < 0)
+ return r;
+ if (r == 0)
+ return -ENOTDIR;
+
+ return 0;
+}
+
+int unit_file_changes_add(
+ UnitFileChange **changes,
+ unsigned *n_changes,
+ UnitFileChangeType type,
+ const char *path,
+ const char *source) {
+
+ UnitFileChange *c;
+ unsigned i;
+
+ assert(path);
+ assert(!changes == !n_changes);
+
+ if (!changes)
+ return 0;
+
+ c = realloc(*changes, (*n_changes + 1) * sizeof(UnitFileChange));
+ if (!c)
+ return -ENOMEM;
+
+ *changes = c;
+ i = *n_changes;
+
+ c[i].type = type;
+ c[i].path = strdup(path);
+ if (!c[i].path)
+ return -ENOMEM;
+
+ path_kill_slashes(c[i].path);
+
+ if (source) {
+ c[i].source = strdup(source);
+ if (!c[i].source) {
+ free(c[i].path);
+ return -ENOMEM;
+ }
+
+ path_kill_slashes(c[i].path);
+ } else
+ c[i].source = NULL;
+
+ *n_changes = i+1;
+ return 0;
+}
+
+void unit_file_changes_free(UnitFileChange *changes, unsigned n_changes) {
+ unsigned i;
+
+ assert(changes || n_changes == 0);
+
+ if (!changes)
+ return;
+
+ for (i = 0; i < n_changes; i++) {
+ free(changes[i].path);
+ free(changes[i].source);
+ }
+
+ free(changes);
+}
+
+static int create_symlink(
+ const char *old_path,
+ const char *new_path,
+ bool force,
+ UnitFileChange **changes,
+ unsigned *n_changes) {
+
+ _cleanup_free_ char *dest = NULL;
+ int r;
+
+ assert(old_path);
+ assert(new_path);
+
+ /* Actually create a symlink, and remember that we did. Is
+ * smart enough to check if there's already a valid symlink in
+ * place. */
+
+ mkdir_parents_label(new_path, 0755);
+
+ if (symlink(old_path, new_path) >= 0) {
+ unit_file_changes_add(changes, n_changes, UNIT_FILE_SYMLINK, new_path, old_path);
+ return 0;
+ }
+
+ if (errno != EEXIST)
+ return -errno;
+
+ r = readlink_malloc(new_path, &dest);
+ if (r < 0)
+ return r;
+
+ if (path_equal(dest, old_path))
+ return 0;
+
+ if (!force)
+ return -EEXIST;
+
+ r = symlink_atomic(old_path, new_path);
+ if (r < 0)
+ return r;
+
+ unit_file_changes_add(changes, n_changes, UNIT_FILE_UNLINK, new_path, NULL);
+ unit_file_changes_add(changes, n_changes, UNIT_FILE_SYMLINK, new_path, old_path);
+
+ return 0;
+}
+
+static int mark_symlink_for_removal(
+ Set **remove_symlinks_to,
+ const char *p) {
+
+ char *n;
+ int r;
+
+ assert(p);
+
+ r = set_ensure_allocated(remove_symlinks_to, &string_hash_ops);
+ if (r < 0)
+ return r;
+
+ n = strdup(p);
+ if (!n)
+ return -ENOMEM;
+
+ path_kill_slashes(n);
+
+ r = set_consume(*remove_symlinks_to, n);
+ if (r == -EEXIST)
+ return 0;
+ if (r < 0)
+ return r;
+
+ return 1;
+}
+
+static int remove_marked_symlinks_fd(
+ Set *remove_symlinks_to,
+ int fd,
+ const char *path,
+ const char *config_path,
+ bool *restart,
+ UnitFileChange **changes,
+ unsigned *n_changes) {
+
+ _cleanup_closedir_ DIR *d = NULL;
+ struct dirent *de;
+ int r = 0;
+
+ assert(remove_symlinks_to);
+ assert(fd >= 0);
+ assert(path);
+ assert(config_path);
+ assert(restart);
+
+ d = fdopendir(fd);
+ if (!d) {
+ safe_close(fd);
+ return -errno;
+ }
+
+ rewinddir(d);
+
+ FOREACH_DIRENT(de, d, return -errno) {
+
+ dirent_ensure_type(d, de);
+
+ if (de->d_type == DT_DIR) {
+ _cleanup_free_ char *p = NULL;
+ int nfd, q;
+
+ nfd = openat(fd, de->d_name, O_RDONLY|O_NONBLOCK|O_DIRECTORY|O_CLOEXEC|O_NOFOLLOW);
+ if (nfd < 0) {
+ if (errno == ENOENT)
+ continue;
+
+ if (r == 0)
+ r = -errno;
+ continue;
+ }
+
+ p = path_make_absolute(de->d_name, path);
+ if (!p) {
+ safe_close(nfd);
+ return -ENOMEM;
+ }
+
+ /* This will close nfd, regardless whether it succeeds or not */
+ q = remove_marked_symlinks_fd(remove_symlinks_to, nfd, p, config_path, restart, changes, n_changes);
+ if (q < 0 && r == 0)
+ r = q;
+
+ } else if (de->d_type == DT_LNK) {
+ _cleanup_free_ char *p = NULL, *dest = NULL;
+ bool found;
+ int q;
+
+ if (!unit_name_is_valid(de->d_name, UNIT_NAME_ANY))
+ continue;
+
+ p = path_make_absolute(de->d_name, path);
+ if (!p)
+ return -ENOMEM;
+
+ q = readlink_malloc(p, &dest);
+ if (q < 0) {
+ if (q == -ENOENT)
+ continue;
+
+ if (r == 0)
+ r = q;
+ continue;
+ }
+
+ /* We remove all links pointing to a file or
+ * path that is marked, as well as all files
+ * sharing the same name as a file that is
+ * marked. */
+
+ found =
+ set_contains(remove_symlinks_to, dest) ||
+ set_contains(remove_symlinks_to, basename(dest)) ||
+ set_contains(remove_symlinks_to, de->d_name);
+
+ if (!found)
+ continue;
+
+ if (unlink(p) < 0 && errno != ENOENT) {
+ if (r == 0)
+ r = -errno;
+ continue;
+ }
+
+ path_kill_slashes(p);
+ (void) rmdir_parents(p, config_path);
+
+ unit_file_changes_add(changes, n_changes, UNIT_FILE_UNLINK, p, NULL);
+
+ q = mark_symlink_for_removal(&remove_symlinks_to, p);
+ if (q < 0)
+ return q;
+ if (q > 0)
+ *restart = true;
+ }
+ }
+
+ return r;
+}
+
+static int remove_marked_symlinks(
+ Set *remove_symlinks_to,
+ const char *config_path,
+ UnitFileChange **changes,
+ unsigned *n_changes) {
+
+ _cleanup_close_ int fd = -1;
+ bool restart;
+ int r = 0;
+
+ assert(config_path);
+
+ if (set_size(remove_symlinks_to) <= 0)
+ return 0;
+
+ fd = open(config_path, O_RDONLY|O_NONBLOCK|O_DIRECTORY|O_CLOEXEC|O_NOFOLLOW);
+ if (fd < 0)
+ return -errno;
+
+ do {
+ int q, cfd;
+ restart = false;
+
+ cfd = fcntl(fd, F_DUPFD_CLOEXEC, 3);
+ if (cfd < 0)
+ return -errno;
+
+ /* This takes possession of cfd and closes it */
+ q = remove_marked_symlinks_fd(remove_symlinks_to, cfd, config_path, config_path, &restart, changes, n_changes);
+ if (r == 0)
+ r = q;
+ } while (restart);
+
+ return r;
+}
+
+static int find_symlinks_fd(
+ const char *root_dir,
+ const char *name,
+ int fd,
+ const char *path,
+ const char *config_path,
+ bool *same_name_link) {
+
+ _cleanup_closedir_ DIR *d = NULL;
+ struct dirent *de;
+ int r = 0;
+
+ assert(name);
+ assert(fd >= 0);
+ assert(path);
+ assert(config_path);
+ assert(same_name_link);
+
+ d = fdopendir(fd);
+ if (!d) {
+ safe_close(fd);
+ return -errno;
+ }
+
+ FOREACH_DIRENT(de, d, return -errno) {
+
+ dirent_ensure_type(d, de);
+
+ if (de->d_type == DT_DIR) {
+ _cleanup_free_ char *p = NULL;
+ int nfd, q;
+
+ nfd = openat(fd, de->d_name, O_RDONLY|O_NONBLOCK|O_DIRECTORY|O_CLOEXEC|O_NOFOLLOW);
+ if (nfd < 0) {
+ if (errno == ENOENT)
+ continue;
+
+ if (r == 0)
+ r = -errno;
+ continue;
+ }
+
+ p = path_make_absolute(de->d_name, path);
+ if (!p) {
+ safe_close(nfd);
+ return -ENOMEM;
+ }
+
+ /* This will close nfd, regardless whether it succeeds or not */
+ q = find_symlinks_fd(root_dir, name, nfd, p, config_path, same_name_link);
+ if (q > 0)
+ return 1;
+ if (r == 0)
+ r = q;
+
+ } else if (de->d_type == DT_LNK) {
+ _cleanup_free_ char *p = NULL, *dest = NULL;
+ bool found_path, found_dest, b = false;
+ int q;
+
+ /* Acquire symlink name */
+ p = path_make_absolute(de->d_name, path);
+ if (!p)
+ return -ENOMEM;
+
+ /* Acquire symlink destination */
+ q = readlink_malloc(p, &dest);
+ if (q == -ENOENT)
+ continue;
+ if (q < 0) {
+ if (r == 0)
+ r = q;
+ continue;
+ }
+
+ /* Make absolute */
+ if (!path_is_absolute(dest)) {
+ char *x;
+
+ x = prefix_root(root_dir, dest);
+ if (!x)
+ return -ENOMEM;
+
+ free(dest);
+ dest = x;
+ }
+
+ /* Check if the symlink itself matches what we
+ * are looking for */
+ if (path_is_absolute(name))
+ found_path = path_equal(p, name);
+ else
+ found_path = streq(de->d_name, name);
+
+ /* Check if what the symlink points to
+ * matches what we are looking for */
+ if (path_is_absolute(name))
+ found_dest = path_equal(dest, name);
+ else
+ found_dest = streq(basename(dest), name);
+
+ if (found_path && found_dest) {
+ _cleanup_free_ char *t = NULL;
+
+ /* Filter out same name links in the main
+ * config path */
+ t = path_make_absolute(name, config_path);
+ if (!t)
+ return -ENOMEM;
+
+ b = path_equal(t, p);
+ }
+
+ if (b)
+ *same_name_link = true;
+ else if (found_path || found_dest)
+ return 1;
+ }
+ }
+
+ return r;
+}
+
+static int find_symlinks(
+ const char *root_dir,
+ const char *name,
+ const char *config_path,
+ bool *same_name_link) {
+
+ int fd;
+
+ assert(name);
+ assert(config_path);
+ assert(same_name_link);
+
+ fd = open(config_path, O_RDONLY|O_NONBLOCK|O_DIRECTORY|O_CLOEXEC|O_NOFOLLOW);
+ if (fd < 0) {
+ if (errno == ENOENT)
+ return 0;
+ return -errno;
+ }
+
+ /* This takes possession of fd and closes it */
+ return find_symlinks_fd(root_dir, name, fd, config_path, config_path, same_name_link);
+}
+
+static int find_symlinks_in_scope(
+ UnitFileScope scope,
+ const char *root_dir,
+ const char *name,
+ UnitFileState *state) {
+
+ _cleanup_free_ char *normal_path = NULL, *runtime_path = NULL;
+ bool same_name_link_runtime = false, same_name_link = false;
+ int r;
+
+ assert(scope >= 0);
+ assert(scope < _UNIT_FILE_SCOPE_MAX);
+ assert(name);
+
+ /* First look in the normal config path */
+ r = get_config_path(scope, false, root_dir, &normal_path);
+ if (r < 0)
+ return r;
+
+ r = find_symlinks(root_dir, name, normal_path, &same_name_link);
+ if (r < 0)
+ return r;
+ if (r > 0) {
+ *state = UNIT_FILE_ENABLED;
+ return r;
+ }
+
+ /* Then look in runtime config path */
+ r = get_config_path(scope, true, root_dir, &runtime_path);
+ if (r < 0)
+ return r;
+
+ r = find_symlinks(root_dir, name, runtime_path, &same_name_link_runtime);
+ if (r < 0)
+ return r;
+ if (r > 0) {
+ *state = UNIT_FILE_ENABLED_RUNTIME;
+ return r;
+ }
+
+ /* Hmm, we didn't find it, but maybe we found the same name
+ * link? */
+ if (same_name_link) {
+ *state = UNIT_FILE_LINKED;
+ return 1;
+ }
+ if (same_name_link_runtime) {
+ *state = UNIT_FILE_LINKED_RUNTIME;
+ return 1;
+ }
+
+ return 0;
+}
+
+static void install_info_free(UnitFileInstallInfo *i) {
+
+ if (!i)
+ return;
+
+ free(i->name);
+ free(i->path);
+ strv_free(i->aliases);
+ strv_free(i->wanted_by);
+ strv_free(i->required_by);
+ strv_free(i->also);
+ free(i->default_instance);
+ free(i->symlink_target);
+ free(i);
+}
+
+static OrderedHashmap* install_info_hashmap_free(OrderedHashmap *m) {
+ UnitFileInstallInfo *i;
+
+ if (!m)
+ return NULL;
+
+ while ((i = ordered_hashmap_steal_first(m)))
+ install_info_free(i);
+
+ return ordered_hashmap_free(m);
+}
+
+static void install_context_done(InstallContext *c) {
+ assert(c);
+
+ c->will_process = install_info_hashmap_free(c->will_process);
+ c->have_processed = install_info_hashmap_free(c->have_processed);
+}
+
+static UnitFileInstallInfo *install_info_find(InstallContext *c, const char *name) {
+ UnitFileInstallInfo *i;
+
+ i = ordered_hashmap_get(c->have_processed, name);
+ if (i)
+ return i;
+
+ return ordered_hashmap_get(c->will_process, name);
+}
+
+static int install_info_add(
+ InstallContext *c,
+ const char *name,
+ const char *path,
+ UnitFileInstallInfo **ret) {
+
+ UnitFileInstallInfo *i = NULL;
+ int r;
+
+ assert(c);
+ assert(name || path);
+
+ if (!name)
+ name = basename(path);
+
+ if (!unit_name_is_valid(name, UNIT_NAME_ANY))
+ return -EINVAL;
+
+ i = install_info_find(c, name);
+ if (i) {
+ if (ret)
+ *ret = i;
+ return 0;
+ }
+
+ r = ordered_hashmap_ensure_allocated(&c->will_process, &string_hash_ops);
+ if (r < 0)
+ return r;
+
+ i = new0(UnitFileInstallInfo, 1);
+ if (!i)
+ return -ENOMEM;
+ i->type = _UNIT_FILE_TYPE_INVALID;
+
+ i->name = strdup(name);
+ if (!i->name) {
+ r = -ENOMEM;
+ goto fail;
+ }
+
+ if (path) {
+ i->path = strdup(path);
+ if (!i->path) {
+ r = -ENOMEM;
+ goto fail;
+ }
+ }
+
+ r = ordered_hashmap_put(c->will_process, i->name, i);
+ if (r < 0)
+ goto fail;
+
+ if (ret)
+ *ret = i;
+
+ return 0;
+
+fail:
+ install_info_free(i);
+ return r;
+}
+
+static int install_info_add_auto(
+ InstallContext *c,
+ const char *name_or_path,
+ UnitFileInstallInfo **ret) {
+
+ assert(c);
+ assert(name_or_path);
+
+ if (path_is_absolute(name_or_path))
+ return install_info_add(c, NULL, name_or_path, ret);
+ else
+ return install_info_add(c, name_or_path, NULL, ret);
+}
+
+static int config_parse_also(
+ const char *unit,
+ const char *filename,
+ unsigned line,
+ const char *section,
+ unsigned section_line,
+ const char *lvalue,
+ int ltype,
+ const char *rvalue,
+ void *data,
+ void *userdata) {
+
+ UnitFileInstallInfo *i = userdata;
+ InstallContext *c = data;
+ int r;
+
+ assert(filename);
+ assert(lvalue);
+ assert(rvalue);
+
+ for (;;) {
+ _cleanup_free_ char *word = NULL;
+
+ r = extract_first_word(&rvalue, &word, NULL, 0);
+ if (r < 0)
+ return r;
+ if (r == 0)
+ break;
+
+ r = install_info_add(c, word, NULL, NULL);
+ if (r < 0)
+ return r;
+
+ r = strv_push(&i->also, word);
+ if (r < 0)
+ return r;
+
+ word = NULL;
+ }
+
+ return 0;
+}
+
+static int config_parse_default_instance(
+ const char *unit,
+ const char *filename,
+ unsigned line,
+ const char *section,
+ unsigned section_line,
+ const char *lvalue,
+ int ltype,
+ const char *rvalue,
+ void *data,
+ void *userdata) {
+
+ UnitFileInstallInfo *i = data;
+ char *printed;
+ int r;
+
+ assert(filename);
+ assert(lvalue);
+ assert(rvalue);
+
+ r = install_full_printf(i, rvalue, &printed);
+ if (r < 0)
+ return r;
+
+ if (!unit_instance_is_valid(printed)) {
+ free(printed);
+ return -EINVAL;
+ }
+
+ free(i->default_instance);
+ i->default_instance = printed;
+
+ return 0;
+}
+
+static int unit_file_load(
+ InstallContext *c,
+ UnitFileInstallInfo *info,
+ const char *path,
+ const char *root_dir,
+ SearchFlags flags) {
+
+ const ConfigTableItem items[] = {
+ { "Install", "Alias", config_parse_strv, 0, &info->aliases },
+ { "Install", "WantedBy", config_parse_strv, 0, &info->wanted_by },
+ { "Install", "RequiredBy", config_parse_strv, 0, &info->required_by },
+ { "Install", "DefaultInstance", config_parse_default_instance, 0, info },
+ { "Install", "Also", config_parse_also, 0, c },
+ {}
+ };
+
+ _cleanup_fclose_ FILE *f = NULL;
+ _cleanup_close_ int fd = -1;
+ struct stat st;
+ int r;
+
+ assert(c);
+ assert(info);
+ assert(path);
+
+ path = prefix_roota(root_dir, path);
+
+ if (!(flags & SEARCH_LOAD)) {
+ r = lstat(path, &st);
+ if (r < 0)
+ return -errno;
+
+ if (null_or_empty(&st))
+ info->type = UNIT_FILE_TYPE_MASKED;
+ else if (S_ISREG(st.st_mode))
+ info->type = UNIT_FILE_TYPE_REGULAR;
+ else if (S_ISLNK(st.st_mode))
+ return -ELOOP;
+ else if (S_ISDIR(st.st_mode))
+ return -EISDIR;
+ else
+ return -ENOTTY;
+
+ return 0;
+ }
+
+ fd = open(path, O_RDONLY|O_CLOEXEC|O_NOCTTY|O_NOFOLLOW);
+ if (fd < 0)
+ return -errno;
+ if (fstat(fd, &st) < 0)
+ return -errno;
+ if (null_or_empty(&st)) {
+ info->type = UNIT_FILE_TYPE_MASKED;
+ return 0;
+ }
+ if (S_ISDIR(st.st_mode))
+ return -EISDIR;
+ if (!S_ISREG(st.st_mode))
+ return -ENOTTY;
+
+ f = fdopen(fd, "re");
+ if (!f)
+ return -errno;
+ fd = -1;
+
+ r = config_parse(NULL, path, f,
+ NULL,
+ config_item_table_lookup, items,
+ true, true, false, info);
+ if (r < 0)
+ return r;
+
+ info->type = UNIT_FILE_TYPE_REGULAR;
+
+ return
+ (int) strv_length(info->aliases) +
+ (int) strv_length(info->wanted_by) +
+ (int) strv_length(info->required_by);
+}
+
+static int unit_file_load_or_readlink(
+ InstallContext *c,
+ UnitFileInstallInfo *info,
+ const char *path,
+ const char *root_dir,
+ SearchFlags flags) {
+
+ _cleanup_free_ char *np = NULL;
+ int r;
+
+ r = unit_file_load(c, info, path, root_dir, flags);
+ if (r != -ELOOP)
+ return r;
+
+ /* This is a symlink, let's read it. */
+
+ r = readlink_and_make_absolute_root(root_dir, path, &np);
+ if (r < 0)
+ return r;
+
+ if (path_equal(np, "/dev/null"))
+ info->type = UNIT_FILE_TYPE_MASKED;
+ else {
+ const char *bn;
+ UnitType a, b;
+
+ bn = basename(np);
+
+ if (unit_name_is_valid(info->name, UNIT_NAME_PLAIN)) {
+
+ if (!unit_name_is_valid(bn, UNIT_NAME_PLAIN))
+ return -EINVAL;
+
+ } else if (unit_name_is_valid(info->name, UNIT_NAME_INSTANCE)) {
+
+ if (!unit_name_is_valid(bn, UNIT_NAME_INSTANCE|UNIT_NAME_TEMPLATE))
+ return -EINVAL;
+
+ } else if (unit_name_is_valid(info->name, UNIT_NAME_TEMPLATE)) {
+
+ if (!unit_name_is_valid(bn, UNIT_NAME_TEMPLATE))
+ return -EINVAL;
+ } else
+ return -EINVAL;
+
+ /* Enforce that the symlink destination does not
+ * change the unit file type. */
+
+ a = unit_name_to_type(info->name);
+ b = unit_name_to_type(bn);
+ if (a < 0 || b < 0 || a != b)
+ return -EINVAL;
+
+ info->type = UNIT_FILE_TYPE_SYMLINK;
+ info->symlink_target = np;
+ np = NULL;
+ }
+
+ return 0;
+}
+
+static int unit_file_search(
+ InstallContext *c,
+ UnitFileInstallInfo *info,
+ const LookupPaths *paths,
+ const char *root_dir,
+ SearchFlags flags) {
+
+ char **p;
+ int r;
+
+ assert(c);
+ assert(info);
+ assert(paths);
+
+ /* Was this unit already loaded? */
+ if (info->type != _UNIT_FILE_TYPE_INVALID)
+ return 0;
+
+ if (info->path)
+ return unit_file_load_or_readlink(c, info, info->path, root_dir, flags);
+
+ assert(info->name);
+
+ STRV_FOREACH(p, paths->unit_path) {
+ _cleanup_free_ char *path = NULL;
+
+ path = strjoin(*p, "/", info->name, NULL);
+ if (!path)
+ return -ENOMEM;
+
+ r = unit_file_load_or_readlink(c, info, path, root_dir, flags);
+ if (r < 0) {
+ if (r != -ENOENT)
+ return r;
+ } else {
+ info->path = path;
+ path = NULL;
+ return r;
+ }
+ }
+
+ if (unit_name_is_valid(info->name, UNIT_NAME_INSTANCE)) {
+
+ /* Unit file doesn't exist, however instance
+ * enablement was requested. We will check if it is
+ * possible to load template unit file. */
+
+ _cleanup_free_ char *template = NULL;
+
+ r = unit_name_template(info->name, &template);
+ if (r < 0)
+ return r;
+
+ STRV_FOREACH(p, paths->unit_path) {
+ _cleanup_free_ char *path = NULL;
+
+ path = strjoin(*p, "/", template, NULL);
+ if (!path)
+ return -ENOMEM;
+
+ r = unit_file_load_or_readlink(c, info, path, root_dir, flags);
+ if (r < 0) {
+ if (r != -ENOENT)
+ return r;
+ } else {
+ info->path = path;
+ path = NULL;
+ return r;
+ }
+ }
+ }
+
+ return -ENOENT;
+}
+
+static int install_info_follow(
+ InstallContext *c,
+ UnitFileInstallInfo *i,
+ const char *root_dir,
+ SearchFlags flags) {
+
+ assert(c);
+ assert(i);
+
+ if (i->type != UNIT_FILE_TYPE_SYMLINK)
+ return -EINVAL;
+ if (!i->symlink_target)
+ return -EINVAL;
+
+ /* If the basename doesn't match, the caller should add a
+ * complete new entry for this. */
+
+ if (!streq(basename(i->symlink_target), i->name))
+ return -EXDEV;
+
+ free(i->path);
+ i->path = i->symlink_target;
+ i->symlink_target = NULL;
+ i->type = _UNIT_FILE_TYPE_INVALID;
+
+ return unit_file_load_or_readlink(c, i, i->path, root_dir, flags);
+}
+
+static int install_info_traverse(
+ UnitFileScope scope,
+ InstallContext *c,
+ const char *root_dir,
+ const LookupPaths *paths,
+ UnitFileInstallInfo *start,
+ SearchFlags flags,
+ UnitFileInstallInfo **ret) {
+
+ UnitFileInstallInfo *i;
+ unsigned k = 0;
+ int r;
+
+ assert(paths);
+ assert(start);
+ assert(c);
+
+ r = unit_file_search(c, start, paths, root_dir, flags);
+ if (r < 0)
+ return r;
+
+ i = start;
+ while (i->type == UNIT_FILE_TYPE_SYMLINK) {
+ /* Follow the symlink */
+
+ if (++k > UNIT_FILE_FOLLOW_SYMLINK_MAX)
+ return -ELOOP;
+
+ if (!(flags & SEARCH_FOLLOW_CONFIG_SYMLINKS) && is_config_path(scope, i->path))
+ return -ELOOP;
+
+ r = install_info_follow(c, i, root_dir, flags);
+ if (r < 0) {
+ _cleanup_free_ char *buffer = NULL;
+ const char *bn;
+
+ if (r != -EXDEV)
+ return r;
+
+ /* Target has a different name, create a new
+ * install info object for that, and continue
+ * with that. */
+
+ bn = basename(i->symlink_target);
+
+ if (unit_name_is_valid(i->name, UNIT_NAME_INSTANCE) &&
+ unit_name_is_valid(bn, UNIT_NAME_TEMPLATE)) {
+
+ _cleanup_free_ char *instance = NULL;
+
+ r = unit_name_to_instance(i->name, &instance);
+ if (r < 0)
+ return r;
+
+ r = unit_name_replace_instance(bn, instance, &buffer);
+ if (r < 0)
+ return r;
+
+ bn = buffer;
+ }
+
+ r = install_info_add(c, bn, NULL, &i);
+ if (r < 0)
+ return r;
+
+ r = unit_file_search(c, i, paths, root_dir, flags);
+ if (r < 0)
+ return r;
+ }
+
+ /* Try again, with the new target we found. */
+ }
+
+ if (ret)
+ *ret = i;
+
+ return 0;
+}
+
+static int install_info_discover(
+ UnitFileScope scope,
+ InstallContext *c,
+ const char *root_dir,
+ const LookupPaths *paths,
+ const char *name,
+ SearchFlags flags,
+ UnitFileInstallInfo **ret) {
+
+ UnitFileInstallInfo *i;
+ int r;
+
+ assert(c);
+ assert(paths);
+ assert(name);
+
+ r = install_info_add_auto(c, name, &i);
+ if (r < 0)
+ return r;
+
+ return install_info_traverse(scope, c, root_dir, paths, i, flags, ret);
+}
+
+static int install_info_symlink_alias(
+ UnitFileInstallInfo *i,
+ const char *config_path,
+ bool force,
+ UnitFileChange **changes,
+ unsigned *n_changes) {
+
+ char **s;
+ int r = 0, q;
+
+ assert(i);
+ assert(config_path);
+
+ STRV_FOREACH(s, i->aliases) {
+ _cleanup_free_ char *alias_path = NULL, *dst = NULL;
+
+ q = install_full_printf(i, *s, &dst);
+ if (q < 0)
+ return q;
+
+ alias_path = path_make_absolute(dst, config_path);
+ if (!alias_path)
+ return -ENOMEM;
+
+ q = create_symlink(i->path, alias_path, force, changes, n_changes);
+ if (r == 0)
+ r = q;
+ }
+
+ return r;
+}
+
+static int install_info_symlink_wants(
+ UnitFileInstallInfo *i,
+ const char *config_path,
+ char **list,
+ const char *suffix,
+ bool force,
+ UnitFileChange **changes,
+ unsigned *n_changes) {
+
+ _cleanup_free_ char *buf = NULL;
+ const char *n;
+ char **s;
+ int r = 0, q;
+
+ assert(i);
+ assert(config_path);
+
+ if (unit_name_is_valid(i->name, UNIT_NAME_TEMPLATE)) {
+
+ /* Don't install any symlink if there's no default
+ * instance configured */
+
+ if (!i->default_instance)
+ return 0;
+
+ r = unit_name_replace_instance(i->name, i->default_instance, &buf);
+ if (r < 0)
+ return r;
+
+ n = buf;
+ } else
+ n = i->name;
+
+ STRV_FOREACH(s, list) {
+ _cleanup_free_ char *path = NULL, *dst = NULL;
+
+ q = install_full_printf(i, *s, &dst);
+ if (q < 0)
+ return q;
+
+ if (!unit_name_is_valid(dst, UNIT_NAME_ANY)) {
+ r = -EINVAL;
+ continue;
+ }
+
+ path = strjoin(config_path, "/", dst, suffix, n, NULL);
+ if (!path)
+ return -ENOMEM;
+
+ q = create_symlink(i->path, path, force, changes, n_changes);
+ if (r == 0)
+ r = q;
+ }
+
+ return r;
+}
+
+static int install_info_symlink_link(
+ UnitFileInstallInfo *i,
+ const LookupPaths *paths,
+ const char *config_path,
+ const char *root_dir,
+ bool force,
+ UnitFileChange **changes,
+ unsigned *n_changes) {
+
+ _cleanup_free_ char *path = NULL;
+ int r;
+
+ assert(i);
+ assert(paths);
+ assert(config_path);
+ assert(i->path);
+
+ r = in_search_path(i->path, paths->unit_path);
+ if (r != 0)
+ return r;
+
+ path = strjoin(config_path, "/", i->name, NULL);
+ if (!path)
+ return -ENOMEM;
+
+ return create_symlink(i->path, path, force, changes, n_changes);
+}
+
+static int install_info_apply(
+ UnitFileInstallInfo *i,
+ const LookupPaths *paths,
+ const char *config_path,
+ const char *root_dir,
+ bool force,
+ UnitFileChange **changes,
+ unsigned *n_changes) {
+
+ int r, q;
+
+ assert(i);
+ assert(paths);
+ assert(config_path);
+
+ if (i->type != UNIT_FILE_TYPE_REGULAR)
+ return 0;
+
+ r = install_info_symlink_alias(i, config_path, force, changes, n_changes);
+
+ q = install_info_symlink_wants(i, config_path, i->wanted_by, ".wants/", force, changes, n_changes);
+ if (r == 0)
+ r = q;
+
+ q = install_info_symlink_wants(i, config_path, i->required_by, ".requires/", force, changes, n_changes);
+ if (r == 0)
+ r = q;
+
+ q = install_info_symlink_link(i, paths, config_path, root_dir, force, changes, n_changes);
+ if (r == 0)
+ r = q;
+
+ return r;
+}
+
+static int install_context_apply(
+ UnitFileScope scope,
+ InstallContext *c,
+ const LookupPaths *paths,
+ const char *config_path,
+ const char *root_dir,
+ bool force,
+ SearchFlags flags,
+ UnitFileChange **changes,
+ unsigned *n_changes) {
+
+ UnitFileInstallInfo *i;
+ int r;
+
+ assert(c);
+ assert(paths);
+ assert(config_path);
+
+ if (ordered_hashmap_isempty(c->will_process))
+ return 0;
+
+ r = ordered_hashmap_ensure_allocated(&c->have_processed, &string_hash_ops);
+ if (r < 0)
+ return r;
+
+ r = 0;
+ while ((i = ordered_hashmap_first(c->will_process))) {
+ int q;
+
+ q = ordered_hashmap_move_one(c->have_processed, c->will_process, i->name);
+ if (q < 0)
+ return q;
+
+ r = install_info_traverse(scope, c, root_dir, paths, i, flags, NULL);
+ if (r < 0)
+ return r;
+
+ if (i->type != UNIT_FILE_TYPE_REGULAR)
+ continue;
+
+ q = install_info_apply(i, paths, config_path, root_dir, force, changes, n_changes);
+ if (r >= 0) {
+ if (q < 0)
+ r = q;
+ else
+ r+= q;
+ }
+ }
+
+ return r;
+}
+
+static int install_context_mark_for_removal(
+ UnitFileScope scope,
+ InstallContext *c,
+ const LookupPaths *paths,
+ Set **remove_symlinks_to,
+ const char *config_path,
+ const char *root_dir) {
+
+ UnitFileInstallInfo *i;
+ int r;
+
+ assert(c);
+ assert(paths);
+ assert(config_path);
+
+ /* Marks all items for removal */
+
+ if (ordered_hashmap_isempty(c->will_process))
+ return 0;
+
+ r = ordered_hashmap_ensure_allocated(&c->have_processed, &string_hash_ops);
+ if (r < 0)
+ return r;
+
+ while ((i = ordered_hashmap_first(c->will_process))) {
+
+ r = ordered_hashmap_move_one(c->have_processed, c->will_process, i->name);
+ if (r < 0)
+ return r;
+
+ r = install_info_traverse(scope, c, root_dir, paths, i, SEARCH_LOAD|SEARCH_FOLLOW_CONFIG_SYMLINKS, NULL);
+ if (r < 0)
+ return r;
+
+ if (i->type != UNIT_FILE_TYPE_REGULAR)
+ continue;
+
+ r = mark_symlink_for_removal(remove_symlinks_to, i->name);
+ if (r < 0)
+ return r;
+ }
+
+ return 0;
+}
+
+int unit_file_mask(
+ UnitFileScope scope,
+ bool runtime,
+ const char *root_dir,
+ char **files,
+ bool force,
+ UnitFileChange **changes,
+ unsigned *n_changes) {
+
+ _cleanup_free_ char *prefix = NULL;
+ char **i;
+ int r;
+
+ assert(scope >= 0);
+ assert(scope < _UNIT_FILE_SCOPE_MAX);
+
+ r = verify_root_dir(scope, &root_dir);
+ if (r < 0)
+ return r;
+
+ r = get_config_path(scope, runtime, root_dir, &prefix);
+ if (r < 0)
+ return r;
+
+ STRV_FOREACH(i, files) {
+ _cleanup_free_ char *path = NULL;
+ int q;
+
+ if (!unit_name_is_valid(*i, UNIT_NAME_ANY)) {
+ if (r == 0)
+ r = -EINVAL;
+ continue;
+ }
+
+ path = path_make_absolute(*i, prefix);
+ if (!path)
+ return -ENOMEM;
+
+ q = create_symlink("/dev/null", path, force, changes, n_changes);
+ if (q < 0 && r >= 0)
+ r = q;
+ }
+
+ return r;
+}
+
+int unit_file_unmask(
+ UnitFileScope scope,
+ bool runtime,
+ const char *root_dir,
+ char **files,
+ UnitFileChange **changes,
+ unsigned *n_changes) {
+
+ _cleanup_set_free_free_ Set *remove_symlinks_to = NULL;
+ _cleanup_free_ char *config_path = NULL;
+ _cleanup_free_ char **todo = NULL;
+ size_t n_todo = 0, n_allocated = 0;
+ char **i;
+ int r, q;
+
+ assert(scope >= 0);
+ assert(scope < _UNIT_FILE_SCOPE_MAX);
+
+ r = verify_root_dir(scope, &root_dir);
+ if (r < 0)
+ return r;
+
+ r = get_config_path(scope, runtime, root_dir, &config_path);
+ if (r < 0)
+ return r;
+
+ STRV_FOREACH(i, files) {
+ _cleanup_free_ char *path = NULL;
+
+ if (!unit_name_is_valid(*i, UNIT_NAME_ANY))
+ return -EINVAL;
+
+ path = path_make_absolute(*i, config_path);
+ if (!path)
+ return -ENOMEM;
+
+ r = null_or_empty_path(path);
+ if (r == -ENOENT)
+ continue;
+ if (r < 0)
+ return r;
+ if (r == 0)
+ continue;
+
+ if (!GREEDY_REALLOC0(todo, n_allocated, n_todo + 2))
+ return -ENOMEM;
+
+ todo[n_todo++] = *i;
+ }
+
+ strv_uniq(todo);
+
+ r = 0;
+ STRV_FOREACH(i, todo) {
+ _cleanup_free_ char *path = NULL;
+
+ path = path_make_absolute(*i, config_path);
+ if (!path)
+ return -ENOMEM;
+
+ if (unlink(path) < 0) {
+ if (errno != -ENOENT && r >= 0)
+ r = -errno;
+ } else {
+ q = mark_symlink_for_removal(&remove_symlinks_to, path);
+ if (q < 0)
+ return q;
+
+ unit_file_changes_add(changes, n_changes, UNIT_FILE_UNLINK, path, NULL);
+ }
+ }
+
+ q = remove_marked_symlinks(remove_symlinks_to, config_path, changes, n_changes);
+ if (r >= 0)
+ r = q;
+
+ return r;
+}
+
+int unit_file_link(
+ UnitFileScope scope,
+ bool runtime,
+ const char *root_dir,
+ char **files,
+ bool force,
+ UnitFileChange **changes,
+ unsigned *n_changes) {
+
+ _cleanup_lookup_paths_free_ LookupPaths paths = {};
+ _cleanup_free_ char *config_path = NULL;
+ _cleanup_free_ char **todo = NULL;
+ size_t n_todo = 0, n_allocated = 0;
+ char **i;
+ int r, q;
+
+ assert(scope >= 0);
+ assert(scope < _UNIT_FILE_SCOPE_MAX);
+
+ r = verify_root_dir(scope, &root_dir);
+ if (r < 0)
+ return r;
+
+ r = lookup_paths_init_from_scope(&paths, scope, root_dir);
+ if (r < 0)
+ return r;
+
+ r = get_config_path(scope, runtime, root_dir, &config_path);
+ if (r < 0)
+ return r;
+
+ STRV_FOREACH(i, files) {
+ _cleanup_free_ char *full = NULL;
+ struct stat st;
+ char *fn;
+
+ if (!path_is_absolute(*i))
+ return -EINVAL;
+
+ fn = basename(*i);
+ if (!unit_name_is_valid(fn, UNIT_NAME_ANY))
+ return -EINVAL;
+
+ full = prefix_root(root_dir, *i);
+ if (!full)
+ return -ENOMEM;
+
+ if (lstat(full, &st) < 0)
+ return -errno;
+ if (S_ISLNK(st.st_mode))
+ return -ELOOP;
+ if (S_ISDIR(st.st_mode))
+ return -EISDIR;
+ if (!S_ISREG(st.st_mode))
+ return -ENOTTY;
+
+ q = in_search_path(*i, paths.unit_path);
+ if (q < 0)
+ return q;
+ if (q > 0)
+ continue;
+
+ if (!GREEDY_REALLOC0(todo, n_allocated, n_todo + 2))
+ return -ENOMEM;
+
+ todo[n_todo++] = *i;
+ }
+
+ strv_uniq(todo);
+
+ r = 0;
+ STRV_FOREACH(i, todo) {
+ _cleanup_free_ char *path = NULL;
+
+ path = path_make_absolute(basename(*i), config_path);
+ if (!path)
+ return -ENOMEM;
+
+ q = create_symlink(*i, path, force, changes, n_changes);
+ if (q < 0 && r >= 0)
+ r = q;
+ }
+
+ return r;
+}
+
+int unit_file_add_dependency(
+ UnitFileScope scope,
+ bool runtime,
+ const char *root_dir,
+ char **files,
+ const char *target,
+ UnitDependency dep,
+ bool force,
+ UnitFileChange **changes,
+ unsigned *n_changes) {
+
+ _cleanup_lookup_paths_free_ LookupPaths paths = {};
+ _cleanup_(install_context_done) InstallContext c = {};
+ _cleanup_free_ char *config_path = NULL;
+ UnitFileInstallInfo *i, *target_info;
+ char **f;
+ int r;
+
+ assert(scope >= 0);
+ assert(scope < _UNIT_FILE_SCOPE_MAX);
+ assert(target);
+
+ if (!IN_SET(dep, UNIT_WANTS, UNIT_REQUIRES))
+ return -EINVAL;
+
+ if (!unit_name_is_valid(target, UNIT_NAME_ANY))
+ return -EINVAL;
+
+ r = verify_root_dir(scope, &root_dir);
+ if (r < 0)
+ return r;
+
+ r = lookup_paths_init_from_scope(&paths, scope, root_dir);
+ if (r < 0)
+ return r;
+
+ r = get_config_path(scope, runtime, root_dir, &config_path);
+ if (r < 0)
+ return r;
+
+ r = install_info_discover(scope, &c, root_dir, &paths, target, SEARCH_FOLLOW_CONFIG_SYMLINKS, &target_info);
+ if (r < 0)
+ return r;
+ if (target_info->type == UNIT_FILE_TYPE_MASKED)
+ return -ESHUTDOWN;
+
+ assert(target_info->type == UNIT_FILE_TYPE_REGULAR);
+
+ STRV_FOREACH(f, files) {
+ char ***l;
+
+ r = install_info_discover(scope, &c, root_dir, &paths, *f, SEARCH_FOLLOW_CONFIG_SYMLINKS, &i);
+ if (r < 0)
+ return r;
+ if (i->type == UNIT_FILE_TYPE_MASKED)
+ return -ESHUTDOWN;
+
+ assert(i->type == UNIT_FILE_TYPE_REGULAR);
+
+ /* We didn't actually load anything from the unit
+ * file, but instead just add in our new symlink to
+ * create. */
+
+ if (dep == UNIT_WANTS)
+ l = &i->wanted_by;
+ else
+ l = &i->required_by;
+
+ strv_free(*l);
+ *l = strv_new(target_info->name, NULL);
+ if (!*l)
+ return -ENOMEM;
+ }
+
+ return install_context_apply(scope, &c, &paths, config_path, root_dir, force, SEARCH_FOLLOW_CONFIG_SYMLINKS, changes, n_changes);
+}
+
+int unit_file_enable(
+ UnitFileScope scope,
+ bool runtime,
+ const char *root_dir,
+ char **files,
+ bool force,
+ UnitFileChange **changes,
+ unsigned *n_changes) {
+
+ _cleanup_lookup_paths_free_ LookupPaths paths = {};
+ _cleanup_(install_context_done) InstallContext c = {};
+ _cleanup_free_ char *config_path = NULL;
+ UnitFileInstallInfo *i;
+ char **f;
+ int r;
+
+ assert(scope >= 0);
+ assert(scope < _UNIT_FILE_SCOPE_MAX);
+
+ r = verify_root_dir(scope, &root_dir);
+ if (r < 0)
+ return r;
+
+ r = lookup_paths_init_from_scope(&paths, scope, root_dir);
+ if (r < 0)
+ return r;
+
+ r = get_config_path(scope, runtime, root_dir, &config_path);
+ if (r < 0)
+ return r;
+
+ STRV_FOREACH(f, files) {
+ r = install_info_discover(scope, &c, root_dir, &paths, *f, SEARCH_LOAD, &i);
+ if (r < 0)
+ return r;
+ if (i->type == UNIT_FILE_TYPE_MASKED)
+ return -ESHUTDOWN;
+
+ assert(i->type == UNIT_FILE_TYPE_REGULAR);
+ }
+
+ /* This will return the number of symlink rules that were
+ supposed to be created, not the ones actually created. This
+ is useful to determine whether the passed files had any
+ installation data at all. */
+
+ return install_context_apply(scope, &c, &paths, config_path, root_dir, force, SEARCH_LOAD, changes, n_changes);
+}
+
+int unit_file_disable(
+ UnitFileScope scope,
+ bool runtime,
+ const char *root_dir,
+ char **files,
+ UnitFileChange **changes,
+ unsigned *n_changes) {
+
+ _cleanup_lookup_paths_free_ LookupPaths paths = {};
+ _cleanup_(install_context_done) InstallContext c = {};
+ _cleanup_free_ char *config_path = NULL;
+ _cleanup_set_free_free_ Set *remove_symlinks_to = NULL;
+ char **i;
+ int r;
+
+ assert(scope >= 0);
+ assert(scope < _UNIT_FILE_SCOPE_MAX);
+
+ r = verify_root_dir(scope, &root_dir);
+ if (r < 0)
+ return r;
+
+ r = lookup_paths_init_from_scope(&paths, scope, root_dir);
+ if (r < 0)
+ return r;
+
+ r = get_config_path(scope, runtime, root_dir, &config_path);
+ if (r < 0)
+ return r;
+
+ STRV_FOREACH(i, files) {
+ if (!unit_name_is_valid(*i, UNIT_NAME_ANY))
+ return -EINVAL;
+
+ r = install_info_add(&c, *i, NULL, NULL);
+ if (r < 0)
+ return r;
+ }
+
+ r = install_context_mark_for_removal(scope, &c, &paths, &remove_symlinks_to, config_path, root_dir);
+ if (r < 0)
+ return r;
+
+ return remove_marked_symlinks(remove_symlinks_to, config_path, changes, n_changes);
+}
+
+int unit_file_reenable(
+ UnitFileScope scope,
+ bool runtime,
+ const char *root_dir,
+ char **files,
+ bool force,
+ UnitFileChange **changes,
+ unsigned *n_changes) {
+
+ char **n;
+ int r;
+ size_t l, i;
+
+ /* First, we invoke the disable command with only the basename... */
+ l = strv_length(files);
+ n = newa(char*, l+1);
+ for (i = 0; i < l; i++)
+ n[i] = basename(files[i]);
+ n[i] = NULL;
+
+ r = unit_file_disable(scope, runtime, root_dir, n, changes, n_changes);
+ if (r < 0)
+ return r;
+
+ /* But the enable command with the full name */
+ return unit_file_enable(scope, runtime, root_dir, files, force, changes, n_changes);
+}
+
+int unit_file_set_default(
+ UnitFileScope scope,
+ const char *root_dir,
+ const char *name,
+ bool force,
+ UnitFileChange **changes,
+ unsigned *n_changes) {
+
+ _cleanup_lookup_paths_free_ LookupPaths paths = {};
+ _cleanup_(install_context_done) InstallContext c = {};
+ _cleanup_free_ char *config_path = NULL;
+ UnitFileInstallInfo *i;
+ const char *path;
+ int r;
+
+ assert(scope >= 0);
+ assert(scope < _UNIT_FILE_SCOPE_MAX);
+ assert(name);
+
+ if (unit_name_to_type(name) != UNIT_TARGET)
+ return -EINVAL;
+ if (streq(name, SPECIAL_DEFAULT_TARGET))
+ return -EINVAL;
+
+ r = verify_root_dir(scope, &root_dir);
+ if (r < 0)
+ return r;
+
+ r = lookup_paths_init_from_scope(&paths, scope, root_dir);
+ if (r < 0)
+ return r;
+
+ r = get_config_path(scope, false, root_dir, &config_path);
+ if (r < 0)
+ return r;
+
+ r = install_info_discover(scope, &c, root_dir, &paths, name, 0, &i);
+ if (r < 0)
+ return r;
+ if (i->type == UNIT_FILE_TYPE_MASKED)
+ return -ESHUTDOWN;
+
+ path = strjoina(config_path, "/" SPECIAL_DEFAULT_TARGET);
+
+ return create_symlink(i->path, path, force, changes, n_changes);
+}
+
+int unit_file_get_default(
+ UnitFileScope scope,
+ const char *root_dir,
+ char **name) {
+
+ _cleanup_lookup_paths_free_ LookupPaths paths = {};
+ _cleanup_(install_context_done) InstallContext c = {};
+ UnitFileInstallInfo *i;
+ char *n;
+ int r;
+
+ assert(scope >= 0);
+ assert(scope < _UNIT_FILE_SCOPE_MAX);
+ assert(name);
+
+ r = verify_root_dir(scope, &root_dir);
+ if (r < 0)
+ return r;
+
+ r = lookup_paths_init_from_scope(&paths, scope, root_dir);
+ if (r < 0)
+ return r;
+
+ r = install_info_discover(scope, &c, root_dir, &paths, SPECIAL_DEFAULT_TARGET, SEARCH_FOLLOW_CONFIG_SYMLINKS, &i);
+ if (r < 0)
+ return r;
+ if (i->type == UNIT_FILE_TYPE_MASKED)
+ return -ESHUTDOWN;
+
+ n = strdup(i->name);
+ if (!n)
+ return -ENOMEM;
+
+ *name = n;
+ return 0;
+}
+
+int unit_file_lookup_state(
+ UnitFileScope scope,
+ const char *root_dir,
+ const LookupPaths *paths,
+ const char *name,
+ UnitFileState *ret) {
+
+ _cleanup_(install_context_done) InstallContext c = {};
+ UnitFileInstallInfo *i;
+ UnitFileState state;
+ int r;
+
+ assert(paths);
+ assert(name);
+
+ if (!unit_name_is_valid(name, UNIT_NAME_ANY))
+ return -EINVAL;
+
+ r = verify_root_dir(scope, &root_dir);
+ if (r < 0)
+ return r;
+
+ r = install_info_discover(scope, &c, root_dir, paths, name, SEARCH_LOAD|SEARCH_FOLLOW_CONFIG_SYMLINKS, &i);
+ if (r < 0)
+ return r;
+
+ /* Shortcut things, if the caller just wants to know if this unit exists. */
+ if (!ret)
+ return 0;
+
+ switch (i->type) {
+
+ case UNIT_FILE_TYPE_MASKED:
+ state = path_startswith(i->path, "/run") ? UNIT_FILE_MASKED_RUNTIME : UNIT_FILE_MASKED;
+ break;
+
+ case UNIT_FILE_TYPE_REGULAR:
+ r = find_symlinks_in_scope(scope, root_dir, i->name, &state);
+ if (r < 0)
+ return r;
+ if (r == 0) {
+ if (UNIT_FILE_INSTALL_INFO_HAS_RULES(i))
+ state = UNIT_FILE_DISABLED;
+ else if (UNIT_FILE_INSTALL_INFO_HAS_ALSO(i))
+ state = UNIT_FILE_INDIRECT;
+ else
+ state = UNIT_FILE_STATIC;
+ }
+
+ break;
+
+ default:
+ assert_not_reached("Unexpect unit file type.");
+ }
+
+ *ret = state;
+ return 0;
+}
+
+int unit_file_get_state(
+ UnitFileScope scope,
+ const char *root_dir,
+ const char *name,
+ UnitFileState *ret) {
+
+ _cleanup_lookup_paths_free_ LookupPaths paths = {};
+ int r;
+
+ assert(scope >= 0);
+ assert(scope < _UNIT_FILE_SCOPE_MAX);
+ assert(name);
+
+ r = verify_root_dir(scope, &root_dir);
+ if (r < 0)
+ return r;
+
+ r = lookup_paths_init_from_scope(&paths, scope, root_dir);
+ if (r < 0)
+ return r;
+
+ return unit_file_lookup_state(scope, root_dir, &paths, name, ret);
+}
+
+int unit_file_query_preset(UnitFileScope scope, const char *root_dir, const char *name) {
+ _cleanup_strv_free_ char **files = NULL;
+ char **p;
+ int r;
+
+ assert(scope >= 0);
+ assert(scope < _UNIT_FILE_SCOPE_MAX);
+ assert(name);
+
+ r = verify_root_dir(scope, &root_dir);
+ if (r < 0)
+ return r;
+
+ if (!unit_name_is_valid(name, UNIT_NAME_ANY))
+ return -EINVAL;
+
+ if (scope == UNIT_FILE_SYSTEM)
+ r = conf_files_list(&files, ".preset", root_dir,
+ "/etc/systemd/system-preset",
+ "/usr/local/lib/systemd/system-preset",
+ "/usr/lib/systemd/system-preset",
+#ifdef HAVE_SPLIT_USR
+ "/lib/systemd/system-preset",
+#endif
+ NULL);
+ else if (scope == UNIT_FILE_GLOBAL)
+ r = conf_files_list(&files, ".preset", root_dir,
+ "/etc/systemd/user-preset",
+ "/usr/local/lib/systemd/user-preset",
+ "/usr/lib/systemd/user-preset",
+ NULL);
+ else
+ return 1; /* Default is "enable" */
+
+ if (r < 0)
+ return r;
+
+ STRV_FOREACH(p, files) {
+ _cleanup_fclose_ FILE *f;
+ char line[LINE_MAX];
+
+ f = fopen(*p, "re");
+ if (!f) {
+ if (errno == ENOENT)
+ continue;
+
+ return -errno;
+ }
+
+ FOREACH_LINE(line, f, return -errno) {
+ const char *parameter;
+ char *l;
+
+ l = strstrip(line);
+
+ if (isempty(l))
+ continue;
+ if (strchr(COMMENTS, *l))
+ continue;
+
+ parameter = first_word(l, "enable");
+ if (parameter) {
+ if (fnmatch(parameter, name, FNM_NOESCAPE) == 0) {
+ log_debug("Preset file says enable %s.", name);
+ return 1;
+ }
+
+ continue;
+ }
+
+ parameter = first_word(l, "disable");
+ if (parameter) {
+ if (fnmatch(parameter, name, FNM_NOESCAPE) == 0) {
+ log_debug("Preset file says disable %s.", name);
+ return 0;
+ }
+
+ continue;
+ }
+
+ log_debug("Couldn't parse line '%s'", l);
+ }
+ }
+
+ /* Default is "enable" */
+ log_debug("Preset file doesn't say anything about %s, enabling.", name);
+ return 1;
+}
+
+static int execute_preset(
+ UnitFileScope scope,
+ InstallContext *plus,
+ InstallContext *minus,
+ const LookupPaths *paths,
+ const char *config_path,
+ const char *root_dir,
+ char **files,
+ UnitFilePresetMode mode,
+ bool force,
+ UnitFileChange **changes,
+ unsigned *n_changes) {
+
+ int r;
+
+ assert(plus);
+ assert(minus);
+ assert(paths);
+ assert(config_path);
+
+ if (mode != UNIT_FILE_PRESET_ENABLE_ONLY) {
+ _cleanup_set_free_free_ Set *remove_symlinks_to = NULL;
+
+ r = install_context_mark_for_removal(scope, minus, paths, &remove_symlinks_to, config_path, root_dir);
+ if (r < 0)
+ return r;
+
+ r = remove_marked_symlinks(remove_symlinks_to, config_path, changes, n_changes);
+ } else
+ r = 0;
+
+ if (mode != UNIT_FILE_PRESET_DISABLE_ONLY) {
+ int q;
+
+ /* Returns number of symlinks that where supposed to be installed. */
+ q = install_context_apply(scope, plus, paths, config_path, root_dir, force, SEARCH_LOAD, changes, n_changes);
+ if (r >= 0) {
+ if (q < 0)
+ r = q;
+ else
+ r+= q;
+ }
+ }
+
+ return r;
+}
+
+static int preset_prepare_one(
+ UnitFileScope scope,
+ InstallContext *plus,
+ InstallContext *minus,
+ LookupPaths *paths,
+ const char *root_dir,
+ UnitFilePresetMode mode,
+ const char *name) {
+
+ UnitFileInstallInfo *i;
+ int r;
+
+ if (install_info_find(plus, name) ||
+ install_info_find(minus, name))
+ return 0;
+
+ r = unit_file_query_preset(scope, root_dir, name);
+ if (r < 0)
+ return r;
+
+ if (r > 0) {
+ r = install_info_discover(scope, plus, root_dir, paths, name, SEARCH_LOAD|SEARCH_FOLLOW_CONFIG_SYMLINKS, &i);
+ if (r < 0)
+ return r;
+
+ if (i->type == UNIT_FILE_TYPE_MASKED)
+ return -ESHUTDOWN;
+ } else
+ r = install_info_discover(scope, minus, root_dir, paths, name, SEARCH_FOLLOW_CONFIG_SYMLINKS, &i);
+
+ return r;
+}
+
+int unit_file_preset(
+ UnitFileScope scope,
+ bool runtime,
+ const char *root_dir,
+ char **files,
+ UnitFilePresetMode mode,
+ bool force,
+ UnitFileChange **changes,
+ unsigned *n_changes) {
+
+ _cleanup_(install_context_done) InstallContext plus = {}, minus = {};
+ _cleanup_lookup_paths_free_ LookupPaths paths = {};
+ _cleanup_free_ char *config_path = NULL;
+ char **i;
+ int r;
+
+ assert(scope >= 0);
+ assert(scope < _UNIT_FILE_SCOPE_MAX);
+ assert(mode < _UNIT_FILE_PRESET_MAX);
+
+ r = verify_root_dir(scope, &root_dir);
+ if (r < 0)
+ return r;
+
+ r = lookup_paths_init_from_scope(&paths, scope, root_dir);
+ if (r < 0)
+ return r;
+
+ r = get_config_path(scope, runtime, root_dir, &config_path);
+ if (r < 0)
+ return r;
+
+ STRV_FOREACH(i, files) {
+ if (!unit_name_is_valid(*i, UNIT_NAME_ANY))
+ return -EINVAL;
+
+ r = preset_prepare_one(scope, &plus, &minus, &paths, root_dir, mode, *i);
+ if (r < 0)
+ return r;
+ }
+
+ return execute_preset(scope, &plus, &minus, &paths, config_path, root_dir, files, mode, force, changes, n_changes);
+}
+
+int unit_file_preset_all(
+ UnitFileScope scope,
+ bool runtime,
+ const char *root_dir,
+ UnitFilePresetMode mode,
+ bool force,
+ UnitFileChange **changes,
+ unsigned *n_changes) {
+
+ _cleanup_(install_context_done) InstallContext plus = {}, minus = {};
+ _cleanup_lookup_paths_free_ LookupPaths paths = {};
+ _cleanup_free_ char *config_path = NULL;
+ char **i;
+ int r;
+
+ assert(scope >= 0);
+ assert(scope < _UNIT_FILE_SCOPE_MAX);
+ assert(mode < _UNIT_FILE_PRESET_MAX);
+
+ r = verify_root_dir(scope, &root_dir);
+ if (r < 0)
+ return r;
+
+ r = lookup_paths_init_from_scope(&paths, scope, root_dir);
+ if (r < 0)
+ return r;
+
+ r = get_config_path(scope, runtime, root_dir, &config_path);
+ if (r < 0)
+ return r;
+
+ STRV_FOREACH(i, paths.unit_path) {
+ _cleanup_closedir_ DIR *d = NULL;
+ _cleanup_free_ char *units_dir;
+ struct dirent *de;
+
+ units_dir = path_join(root_dir, *i, NULL);
+ if (!units_dir)
+ return -ENOMEM;
+
+ d = opendir(units_dir);
+ if (!d) {
+ if (errno == ENOENT)
+ continue;
+
+ return -errno;
+ }
+
+ FOREACH_DIRENT(de, d, return -errno) {
+
+ if (!unit_name_is_valid(de->d_name, UNIT_NAME_ANY))
+ continue;
+
+ dirent_ensure_type(d, de);
+
+ if (!IN_SET(de->d_type, DT_LNK, DT_REG))
+ continue;
+
+ r = preset_prepare_one(scope, &plus, &minus, &paths, root_dir, mode, de->d_name);
+ if (r < 0)
+ return r;
+ }
+ }
+
+ return execute_preset(scope, &plus, &minus, &paths, config_path, root_dir, NULL, mode, force, changes, n_changes);
+}
+
+static void unit_file_list_free_one(UnitFileList *f) {
+ if (!f)
+ return;
+
+ free(f->path);
+ free(f);
+}
+
+Hashmap* unit_file_list_free(Hashmap *h) {
+ UnitFileList *i;
+
+ while ((i = hashmap_steal_first(h)))
+ unit_file_list_free_one(i);
+
+ return hashmap_free(h);
+}
+
+DEFINE_TRIVIAL_CLEANUP_FUNC(UnitFileList*, unit_file_list_free_one);
+
+int unit_file_get_list(
+ UnitFileScope scope,
+ const char *root_dir,
+ Hashmap *h) {
+
+ _cleanup_lookup_paths_free_ LookupPaths paths = {};
+ char **i;
+ int r;
+
+ assert(scope >= 0);
+ assert(scope < _UNIT_FILE_SCOPE_MAX);
+ assert(h);
+
+ r = verify_root_dir(scope, &root_dir);
+ if (r < 0)
+ return r;
+
+ r = lookup_paths_init_from_scope(&paths, scope, root_dir);
+ if (r < 0)
+ return r;
+
+ STRV_FOREACH(i, paths.unit_path) {
+ _cleanup_closedir_ DIR *d = NULL;
+ _cleanup_free_ char *units_dir;
+ struct dirent *de;
+
+ units_dir = path_join(root_dir, *i, NULL);
+ if (!units_dir)
+ return -ENOMEM;
+
+ d = opendir(units_dir);
+ if (!d) {
+ if (errno == ENOENT)
+ continue;
+
+ return -errno;
+ }
+
+ FOREACH_DIRENT(de, d, return -errno) {
+ _cleanup_(unit_file_list_free_onep) UnitFileList *f = NULL;
+
+ if (!unit_name_is_valid(de->d_name, UNIT_NAME_ANY))
+ continue;
+
+ if (hashmap_get(h, de->d_name))
+ continue;
+
+ dirent_ensure_type(d, de);
+
+ if (!IN_SET(de->d_type, DT_LNK, DT_REG))
+ continue;
+
+ f = new0(UnitFileList, 1);
+ if (!f)
+ return -ENOMEM;
+
+ f->path = path_make_absolute(de->d_name, units_dir);
+ if (!f->path)
+ return -ENOMEM;
+
+ r = unit_file_lookup_state(scope, root_dir, &paths, basename(f->path), &f->state);
+ if (r < 0)
+ f->state = UNIT_FILE_BAD;
+
+ r = hashmap_put(h, basename(f->path), f);
+ if (r < 0)
+ return r;
+
+ f = NULL; /* prevent cleanup */
+ }
+ }
+
+ return 0;
+}
+
+static const char* const unit_file_state_table[_UNIT_FILE_STATE_MAX] = {
+ [UNIT_FILE_ENABLED] = "enabled",
+ [UNIT_FILE_ENABLED_RUNTIME] = "enabled-runtime",
+ [UNIT_FILE_LINKED] = "linked",
+ [UNIT_FILE_LINKED_RUNTIME] = "linked-runtime",
+ [UNIT_FILE_MASKED] = "masked",
+ [UNIT_FILE_MASKED_RUNTIME] = "masked-runtime",
+ [UNIT_FILE_STATIC] = "static",
+ [UNIT_FILE_DISABLED] = "disabled",
+ [UNIT_FILE_INDIRECT] = "indirect",
+ [UNIT_FILE_BAD] = "bad",
+};
+
+DEFINE_STRING_TABLE_LOOKUP(unit_file_state, UnitFileState);
+
+static const char* const unit_file_change_type_table[_UNIT_FILE_CHANGE_TYPE_MAX] = {
+ [UNIT_FILE_SYMLINK] = "symlink",
+ [UNIT_FILE_UNLINK] = "unlink",
+};
+
+DEFINE_STRING_TABLE_LOOKUP(unit_file_change_type, UnitFileChangeType);
+
+static const char* const unit_file_preset_mode_table[_UNIT_FILE_PRESET_MAX] = {
+ [UNIT_FILE_PRESET_FULL] = "full",
+ [UNIT_FILE_PRESET_ENABLE_ONLY] = "enable-only",
+ [UNIT_FILE_PRESET_DISABLE_ONLY] = "disable-only",
+};
+
+DEFINE_STRING_TABLE_LOOKUP(unit_file_preset_mode, UnitFilePresetMode);
diff --git a/src/libshared/install.h b/src/libshared/install.h
new file mode 100644
index 0000000000..c1a43e23e7
--- /dev/null
+++ b/src/libshared/install.h
@@ -0,0 +1,156 @@
+#pragma once
+
+/***
+ This file is part of systemd.
+
+ Copyright 2011 Lennart Poettering
+
+ systemd is free software; you can redistribute it and/or modify it
+ under the terms of the GNU Lesser General Public License as published by
+ the Free Software Foundation; either version 2.1 of the License, or
+ (at your option) any later version.
+
+ systemd is distributed in the hope that it will be useful, but
+ WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ Lesser General Public License for more details.
+
+ You should have received a copy of the GNU Lesser General Public License
+ along with systemd; If not, see <http://www.gnu.org/licenses/>.
+***/
+
+typedef enum UnitFileScope UnitFileScope;
+typedef enum UnitFileState UnitFileState;
+typedef enum UnitFilePresetMode UnitFilePresetMode;
+typedef enum UnitFileChangeType UnitFileChangeType;
+typedef enum UnitFileType UnitFileType;
+typedef struct UnitFileChange UnitFileChange;
+typedef struct UnitFileList UnitFileList;
+typedef struct UnitFileInstallInfo UnitFileInstallInfo;
+
+#include <stdbool.h>
+
+#include "hashmap.h"
+#include "macro.h"
+#include "path-lookup.h"
+#include "strv.h"
+#include "unit-name.h"
+
+enum UnitFileScope {
+ UNIT_FILE_SYSTEM,
+ UNIT_FILE_GLOBAL,
+ UNIT_FILE_USER,
+ _UNIT_FILE_SCOPE_MAX,
+ _UNIT_FILE_SCOPE_INVALID = -1
+};
+
+enum UnitFileState {
+ UNIT_FILE_ENABLED,
+ UNIT_FILE_ENABLED_RUNTIME,
+ UNIT_FILE_LINKED,
+ UNIT_FILE_LINKED_RUNTIME,
+ UNIT_FILE_MASKED,
+ UNIT_FILE_MASKED_RUNTIME,
+ UNIT_FILE_STATIC,
+ UNIT_FILE_DISABLED,
+ UNIT_FILE_INDIRECT,
+ UNIT_FILE_BAD,
+ _UNIT_FILE_STATE_MAX,
+ _UNIT_FILE_STATE_INVALID = -1
+};
+
+enum UnitFilePresetMode {
+ UNIT_FILE_PRESET_FULL,
+ UNIT_FILE_PRESET_ENABLE_ONLY,
+ UNIT_FILE_PRESET_DISABLE_ONLY,
+ _UNIT_FILE_PRESET_MAX,
+ _UNIT_FILE_PRESET_INVALID = -1
+};
+
+enum UnitFileChangeType {
+ UNIT_FILE_SYMLINK,
+ UNIT_FILE_UNLINK,
+ _UNIT_FILE_CHANGE_TYPE_MAX,
+ _UNIT_FILE_CHANGE_TYPE_INVALID = -1
+};
+
+struct UnitFileChange {
+ UnitFileChangeType type;
+ char *path;
+ char *source;
+};
+
+struct UnitFileList {
+ char *path;
+ UnitFileState state;
+};
+
+enum UnitFileType {
+ UNIT_FILE_TYPE_REGULAR,
+ UNIT_FILE_TYPE_SYMLINK,
+ UNIT_FILE_TYPE_MASKED,
+ _UNIT_FILE_TYPE_MAX,
+ _UNIT_FILE_TYPE_INVALID = -1,
+};
+
+struct UnitFileInstallInfo {
+ char *name;
+ char *path;
+
+ char **aliases;
+ char **wanted_by;
+ char **required_by;
+ char **also;
+
+ char *default_instance;
+
+ UnitFileType type;
+
+ char *symlink_target;
+};
+
+static inline bool UNIT_FILE_INSTALL_INFO_HAS_RULES(UnitFileInstallInfo *i) {
+ assert(i);
+
+ return !strv_isempty(i->aliases) ||
+ !strv_isempty(i->wanted_by) ||
+ !strv_isempty(i->required_by);
+}
+
+static inline bool UNIT_FILE_INSTALL_INFO_HAS_ALSO(UnitFileInstallInfo *i) {
+ assert(i);
+
+ return !strv_isempty(i->also);
+}
+
+int unit_file_enable(UnitFileScope scope, bool runtime, const char *root_dir, char **files, bool force, UnitFileChange **changes, unsigned *n_changes);
+int unit_file_disable(UnitFileScope scope, bool runtime, const char *root_dir, char **files, UnitFileChange **changes, unsigned *n_changes);
+int unit_file_reenable(UnitFileScope scope, bool runtime, const char *root_dir, char **files, bool force, UnitFileChange **changes, unsigned *n_changes);
+int unit_file_link(UnitFileScope scope, bool runtime, const char *root_dir, char **files, bool force, UnitFileChange **changes, unsigned *n_changes);
+int unit_file_preset(UnitFileScope scope, bool runtime, const char *root_dir, char **files, UnitFilePresetMode mode, bool force, UnitFileChange **changes, unsigned *n_changes);
+int unit_file_preset_all(UnitFileScope scope, bool runtime, const char *root_dir, UnitFilePresetMode mode, bool force, UnitFileChange **changes, unsigned *n_changes);
+int unit_file_mask(UnitFileScope scope, bool runtime, const char *root_dir, char **files, bool force, UnitFileChange **changes, unsigned *n_changes);
+int unit_file_unmask(UnitFileScope scope, bool runtime, const char *root_dir, char **files, UnitFileChange **changes, unsigned *n_changes);
+int unit_file_set_default(UnitFileScope scope, const char *root_dir, const char *file, bool force, UnitFileChange **changes, unsigned *n_changes);
+int unit_file_get_default(UnitFileScope scope, const char *root_dir, char **name);
+int unit_file_add_dependency(UnitFileScope scope, bool runtime, const char *root_dir, char **files, const char *target, UnitDependency dep, bool force, UnitFileChange **changes, unsigned *n_changes);
+
+int unit_file_lookup_state(UnitFileScope scope, const char *root_dir,const LookupPaths *paths, const char *name, UnitFileState *ret);
+int unit_file_get_state(UnitFileScope scope, const char *root_dir, const char *filename, UnitFileState *ret);
+
+int unit_file_get_list(UnitFileScope scope, const char *root_dir, Hashmap *h);
+Hashmap* unit_file_list_free(Hashmap *h);
+
+int unit_file_changes_add(UnitFileChange **changes, unsigned *n_changes, UnitFileChangeType type, const char *path, const char *source);
+void unit_file_changes_free(UnitFileChange *changes, unsigned n_changes);
+
+int unit_file_query_preset(UnitFileScope scope, const char *root_dir, const char *name);
+
+const char *unit_file_state_to_string(UnitFileState s) _const_;
+UnitFileState unit_file_state_from_string(const char *s) _pure_;
+
+const char *unit_file_change_type_to_string(UnitFileChangeType s) _const_;
+UnitFileChangeType unit_file_change_type_from_string(const char *s) _pure_;
+
+const char *unit_file_preset_mode_to_string(UnitFilePresetMode m) _const_;
+UnitFilePresetMode unit_file_preset_mode_from_string(const char *s) _pure_;
diff --git a/src/libshared/logs-show.c b/src/libshared/logs-show.c
new file mode 100644
index 0000000000..7ac6d49549
--- /dev/null
+++ b/src/libshared/logs-show.c
@@ -0,0 +1,1305 @@
+/***
+ This file is part of systemd.
+
+ Copyright 2012 Lennart Poettering
+
+ systemd is free software; you can redistribute it and/or modify it
+ under the terms of the GNU Lesser General Public License as published by
+ the Free Software Foundation; either version 2.1 of the License, or
+ (at your option) any later version.
+
+ systemd is distributed in the hope that it will be useful, but
+ WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ Lesser General Public License for more details.
+
+ You should have received a copy of the GNU Lesser General Public License
+ along with systemd; If not, see <http://www.gnu.org/licenses/>.
+***/
+
+#include <errno.h>
+#include <fcntl.h>
+#include <signal.h>
+#include <stdint.h>
+#include <stdlib.h>
+#include <string.h>
+#include <sys/socket.h>
+#include <syslog.h>
+#include <time.h>
+#include <unistd.h>
+
+#include <systemd/sd-id128.h>
+#include <systemd/sd-journal.h>
+
+#include "alloc-util.h"
+#include "fd-util.h"
+#include "formats-util.h"
+#include "hashmap.h"
+#include "hostname-util.h"
+#include "io-util.h"
+#include "journal-internal.h"
+#include "log.h"
+#include "logs-show.h"
+#include "macro.h"
+#include "output-mode.h"
+#include "parse-util.h"
+#include "process-util.h"
+#include "sparse-endian.h"
+#include "string-table.h"
+#include "string-util.h"
+#include "terminal-util.h"
+#include "time-util.h"
+#include "utf8.h"
+#include "util.h"
+
+/* up to three lines (each up to 100 characters) or 300 characters, whichever is less */
+#define PRINT_LINE_THRESHOLD 3
+#define PRINT_CHAR_THRESHOLD 300
+
+#define JSON_THRESHOLD 4096
+
+static int print_catalog(FILE *f, sd_journal *j) {
+ int r;
+ _cleanup_free_ char *t = NULL, *z = NULL;
+
+
+ r = sd_journal_get_catalog(j, &t);
+ if (r < 0)
+ return r;
+
+ z = strreplace(strstrip(t), "\n", "\n-- ");
+ if (!z)
+ return log_oom();
+
+ fputs("-- ", f);
+ fputs(z, f);
+ fputc('\n', f);
+
+ return 0;
+}
+
+static int parse_field(const void *data, size_t length, const char *field, char **target, size_t *target_size) {
+ size_t fl, nl;
+ char *buf;
+
+ assert(data);
+ assert(field);
+ assert(target);
+
+ fl = strlen(field);
+ if (length < fl)
+ return 0;
+
+ if (memcmp(data, field, fl))
+ return 0;
+
+ nl = length - fl;
+ buf = new(char, nl+1);
+ if (!buf)
+ return log_oom();
+
+ memcpy(buf, (const char*) data + fl, nl);
+ buf[nl] = 0;
+
+ free(*target);
+ *target = buf;
+
+ if (target_size)
+ *target_size = nl;
+
+ return 1;
+}
+
+static bool shall_print(const char *p, size_t l, OutputFlags flags) {
+ assert(p);
+
+ if (flags & OUTPUT_SHOW_ALL)
+ return true;
+
+ if (l >= PRINT_CHAR_THRESHOLD)
+ return false;
+
+ if (!utf8_is_printable(p, l))
+ return false;
+
+ return true;
+}
+
+static bool print_multiline(FILE *f, unsigned prefix, unsigned n_columns, OutputFlags flags, int priority, const char* message, size_t message_len) {
+ const char *color_on = "", *color_off = "";
+ const char *pos, *end;
+ bool ellipsized = false;
+ int line = 0;
+
+ if (flags & OUTPUT_COLOR) {
+ if (priority <= LOG_ERR) {
+ color_on = ANSI_HIGHLIGHT_RED;
+ color_off = ANSI_NORMAL;
+ } else if (priority <= LOG_NOTICE) {
+ color_on = ANSI_HIGHLIGHT;
+ color_off = ANSI_NORMAL;
+ }
+ }
+
+ /* A special case: make sure that we print a newline when
+ the message is empty. */
+ if (message_len == 0)
+ fputs("\n", f);
+
+ for (pos = message;
+ pos < message + message_len;
+ pos = end + 1, line++) {
+ bool continuation = line > 0;
+ bool tail_line;
+ int len;
+ for (end = pos; end < message + message_len && *end != '\n'; end++)
+ ;
+ len = end - pos;
+ assert(len >= 0);
+
+ /* We need to figure out when we are showing not-last line, *and*
+ * will skip subsequent lines. In that case, we will put the dots
+ * at the end of the line, instead of putting dots in the middle
+ * or not at all.
+ */
+ tail_line =
+ line + 1 == PRINT_LINE_THRESHOLD ||
+ end + 1 >= message + PRINT_CHAR_THRESHOLD;
+
+ if (flags & (OUTPUT_FULL_WIDTH | OUTPUT_SHOW_ALL) ||
+ (prefix + len + 1 < n_columns && !tail_line)) {
+ fprintf(f, "%*s%s%.*s%s\n",
+ continuation * prefix, "",
+ color_on, len, pos, color_off);
+ continue;
+ }
+
+ /* Beyond this point, ellipsization will happen. */
+ ellipsized = true;
+
+ if (prefix < n_columns && n_columns - prefix >= 3) {
+ if (n_columns - prefix > (unsigned) len + 3)
+ fprintf(f, "%*s%s%.*s...%s\n",
+ continuation * prefix, "",
+ color_on, len, pos, color_off);
+ else {
+ _cleanup_free_ char *e;
+
+ e = ellipsize_mem(pos, len, n_columns - prefix,
+ tail_line ? 100 : 90);
+ if (!e)
+ fprintf(f, "%*s%s%.*s%s\n",
+ continuation * prefix, "",
+ color_on, len, pos, color_off);
+ else
+ fprintf(f, "%*s%s%s%s\n",
+ continuation * prefix, "",
+ color_on, e, color_off);
+ }
+ } else
+ fputs("...\n", f);
+
+ if (tail_line)
+ break;
+ }
+
+ return ellipsized;
+}
+
+static int output_short(
+ FILE *f,
+ sd_journal *j,
+ OutputMode mode,
+ unsigned n_columns,
+ OutputFlags flags) {
+
+ int r;
+ const void *data;
+ size_t length;
+ size_t n = 0;
+ _cleanup_free_ char *hostname = NULL, *identifier = NULL, *comm = NULL, *pid = NULL, *fake_pid = NULL, *message = NULL, *realtime = NULL, *monotonic = NULL, *priority = NULL;
+ size_t hostname_len = 0, identifier_len = 0, comm_len = 0, pid_len = 0, fake_pid_len = 0, message_len = 0, realtime_len = 0, monotonic_len = 0, priority_len = 0;
+ int p = LOG_INFO;
+ bool ellipsized = false;
+
+ assert(f);
+ assert(j);
+
+ /* Set the threshold to one bigger than the actual print
+ * threshold, so that if the line is actually longer than what
+ * we're willing to print, ellipsization will occur. This way
+ * we won't output a misleading line without any indication of
+ * truncation.
+ */
+ sd_journal_set_data_threshold(j, flags & (OUTPUT_SHOW_ALL|OUTPUT_FULL_WIDTH) ? 0 : PRINT_CHAR_THRESHOLD + 1);
+
+ JOURNAL_FOREACH_DATA_RETVAL(j, data, length, r) {
+
+ r = parse_field(data, length, "PRIORITY=", &priority, &priority_len);
+ if (r < 0)
+ return r;
+ else if (r > 0)
+ continue;
+
+ r = parse_field(data, length, "_HOSTNAME=", &hostname, &hostname_len);
+ if (r < 0)
+ return r;
+ else if (r > 0)
+ continue;
+
+ r = parse_field(data, length, "SYSLOG_IDENTIFIER=", &identifier, &identifier_len);
+ if (r < 0)
+ return r;
+ else if (r > 0)
+ continue;
+
+ r = parse_field(data, length, "_COMM=", &comm, &comm_len);
+ if (r < 0)
+ return r;
+ else if (r > 0)
+ continue;
+
+ r = parse_field(data, length, "_PID=", &pid, &pid_len);
+ if (r < 0)
+ return r;
+ else if (r > 0)
+ continue;
+
+ r = parse_field(data, length, "SYSLOG_PID=", &fake_pid, &fake_pid_len);
+ if (r < 0)
+ return r;
+ else if (r > 0)
+ continue;
+
+ r = parse_field(data, length, "_SOURCE_REALTIME_TIMESTAMP=", &realtime, &realtime_len);
+ if (r < 0)
+ return r;
+ else if (r > 0)
+ continue;
+
+ r = parse_field(data, length, "_SOURCE_MONOTONIC_TIMESTAMP=", &monotonic, &monotonic_len);
+ if (r < 0)
+ return r;
+ else if (r > 0)
+ continue;
+
+ r = parse_field(data, length, "MESSAGE=", &message, &message_len);
+ if (r < 0)
+ return r;
+ }
+
+ if (r < 0)
+ return log_error_errno(r, "Failed to get journal fields: %m");
+
+ if (!message) {
+ log_debug("Skipping message without MESSAGE= field.");
+ return 0;
+ }
+
+ if (!(flags & OUTPUT_SHOW_ALL))
+ strip_tab_ansi(&message, &message_len);
+
+ if (priority_len == 1 && *priority >= '0' && *priority <= '7')
+ p = *priority - '0';
+
+ if (mode == OUTPUT_SHORT_MONOTONIC) {
+ uint64_t t;
+ sd_id128_t boot_id;
+
+ r = -ENOENT;
+
+ if (monotonic)
+ r = safe_atou64(monotonic, &t);
+
+ if (r < 0)
+ r = sd_journal_get_monotonic_usec(j, &t, &boot_id);
+
+ if (r < 0)
+ return log_error_errno(r, "Failed to get monotonic timestamp: %m");
+
+ fprintf(f, "[%5llu.%06llu]",
+ (unsigned long long) (t / USEC_PER_SEC),
+ (unsigned long long) (t % USEC_PER_SEC));
+
+ n += 1 + 5 + 1 + 6 + 1;
+
+ } else {
+ char buf[64];
+ uint64_t x;
+ time_t t;
+ struct tm tm;
+ struct tm *(*gettime_r)(const time_t *, struct tm *);
+
+ r = -ENOENT;
+ gettime_r = (flags & OUTPUT_UTC) ? gmtime_r : localtime_r;
+
+ if (realtime)
+ r = safe_atou64(realtime, &x);
+
+ if (r < 0)
+ r = sd_journal_get_realtime_usec(j, &x);
+
+ if (r < 0)
+ return log_error_errno(r, "Failed to get realtime timestamp: %m");
+
+ t = (time_t) (x / USEC_PER_SEC);
+
+ switch(mode) {
+ case OUTPUT_SHORT_ISO:
+ r = strftime(buf, sizeof(buf), "%Y-%m-%dT%H:%M:%S%z", gettime_r(&t, &tm));
+ break;
+ case OUTPUT_SHORT_PRECISE:
+ r = strftime(buf, sizeof(buf), "%b %d %H:%M:%S", gettime_r(&t, &tm));
+ if (r > 0)
+ snprintf(buf + strlen(buf), sizeof(buf) - strlen(buf),
+ ".%06llu", (unsigned long long) (x % USEC_PER_SEC));
+ break;
+ default:
+ r = strftime(buf, sizeof(buf), "%b %d %H:%M:%S", gettime_r(&t, &tm));
+ }
+
+ if (r <= 0) {
+ log_error("Failed to format time.");
+ return -EINVAL;
+ }
+
+ fputs(buf, f);
+ n += strlen(buf);
+ }
+
+ if (hostname && shall_print(hostname, hostname_len, flags)) {
+ fprintf(f, " %.*s", (int) hostname_len, hostname);
+ n += hostname_len + 1;
+ }
+
+ if (identifier && shall_print(identifier, identifier_len, flags)) {
+ fprintf(f, " %.*s", (int) identifier_len, identifier);
+ n += identifier_len + 1;
+ } else if (comm && shall_print(comm, comm_len, flags)) {
+ fprintf(f, " %.*s", (int) comm_len, comm);
+ n += comm_len + 1;
+ } else
+ fputs(" unknown", f);
+
+ if (pid && shall_print(pid, pid_len, flags)) {
+ fprintf(f, "[%.*s]", (int) pid_len, pid);
+ n += pid_len + 2;
+ } else if (fake_pid && shall_print(fake_pid, fake_pid_len, flags)) {
+ fprintf(f, "[%.*s]", (int) fake_pid_len, fake_pid);
+ n += fake_pid_len + 2;
+ }
+
+ if (!(flags & OUTPUT_SHOW_ALL) && !utf8_is_printable(message, message_len)) {
+ char bytes[FORMAT_BYTES_MAX];
+ fprintf(f, ": [%s blob data]\n", format_bytes(bytes, sizeof(bytes), message_len));
+ } else {
+ fputs(": ", f);
+ ellipsized |=
+ print_multiline(f, n + 2, n_columns, flags, p, message, message_len);
+ }
+
+ if (flags & OUTPUT_CATALOG)
+ print_catalog(f, j);
+
+ return ellipsized;
+}
+
+static int output_verbose(
+ FILE *f,
+ sd_journal *j,
+ OutputMode mode,
+ unsigned n_columns,
+ OutputFlags flags) {
+
+ const void *data;
+ size_t length;
+ _cleanup_free_ char *cursor = NULL;
+ uint64_t realtime = 0;
+ char ts[FORMAT_TIMESTAMP_MAX + 7];
+ int r;
+
+ assert(f);
+ assert(j);
+
+ sd_journal_set_data_threshold(j, 0);
+
+ r = sd_journal_get_data(j, "_SOURCE_REALTIME_TIMESTAMP", &data, &length);
+ if (r == -ENOENT)
+ log_debug("Source realtime timestamp not found");
+ else if (r < 0)
+ return log_full_errno(r == -EADDRNOTAVAIL ? LOG_DEBUG : LOG_ERR, r, "Failed to get source realtime timestamp: %m");
+ else {
+ _cleanup_free_ char *value = NULL;
+
+ r = parse_field(data, length, "_SOURCE_REALTIME_TIMESTAMP=", &value, NULL);
+ if (r < 0)
+ return r;
+ assert(r > 0);
+
+ r = safe_atou64(value, &realtime);
+ if (r < 0)
+ log_debug_errno(r, "Failed to parse realtime timestamp: %m");
+ }
+
+ if (r < 0) {
+ r = sd_journal_get_realtime_usec(j, &realtime);
+ if (r < 0)
+ return log_full_errno(r == -EADDRNOTAVAIL ? LOG_DEBUG : LOG_ERR, r, "Failed to get realtime timestamp: %m");
+ }
+
+ r = sd_journal_get_cursor(j, &cursor);
+ if (r < 0)
+ return log_error_errno(r, "Failed to get cursor: %m");
+
+ fprintf(f, "%s [%s]\n",
+ flags & OUTPUT_UTC ?
+ format_timestamp_us_utc(ts, sizeof(ts), realtime) :
+ format_timestamp_us(ts, sizeof(ts), realtime),
+ cursor);
+
+ JOURNAL_FOREACH_DATA_RETVAL(j, data, length, r) {
+ const char *c;
+ int fieldlen;
+ const char *on = "", *off = "";
+
+ c = memchr(data, '=', length);
+ if (!c) {
+ log_error("Invalid field.");
+ return -EINVAL;
+ }
+ fieldlen = c - (const char*) data;
+
+ if (flags & OUTPUT_COLOR && startswith(data, "MESSAGE=")) {
+ on = ANSI_HIGHLIGHT;
+ off = ANSI_NORMAL;
+ }
+
+ if (flags & OUTPUT_SHOW_ALL ||
+ (((length < PRINT_CHAR_THRESHOLD) || flags & OUTPUT_FULL_WIDTH)
+ && utf8_is_printable(data, length))) {
+ fprintf(f, " %s%.*s=", on, fieldlen, (const char*)data);
+ print_multiline(f, 4 + fieldlen + 1, 0, OUTPUT_FULL_WIDTH, 0, c + 1, length - fieldlen - 1);
+ fputs(off, f);
+ } else {
+ char bytes[FORMAT_BYTES_MAX];
+
+ fprintf(f, " %s%.*s=[%s blob data]%s\n",
+ on,
+ (int) (c - (const char*) data),
+ (const char*) data,
+ format_bytes(bytes, sizeof(bytes), length - (c - (const char *) data) - 1),
+ off);
+ }
+ }
+
+ if (r < 0)
+ return r;
+
+ if (flags & OUTPUT_CATALOG)
+ print_catalog(f, j);
+
+ return 0;
+}
+
+static int output_export(
+ FILE *f,
+ sd_journal *j,
+ OutputMode mode,
+ unsigned n_columns,
+ OutputFlags flags) {
+
+ sd_id128_t boot_id;
+ char sid[33];
+ int r;
+ usec_t realtime, monotonic;
+ _cleanup_free_ char *cursor = NULL;
+ const void *data;
+ size_t length;
+
+ assert(j);
+
+ sd_journal_set_data_threshold(j, 0);
+
+ r = sd_journal_get_realtime_usec(j, &realtime);
+ if (r < 0)
+ return log_error_errno(r, "Failed to get realtime timestamp: %m");
+
+ r = sd_journal_get_monotonic_usec(j, &monotonic, &boot_id);
+ if (r < 0)
+ return log_error_errno(r, "Failed to get monotonic timestamp: %m");
+
+ r = sd_journal_get_cursor(j, &cursor);
+ if (r < 0)
+ return log_error_errno(r, "Failed to get cursor: %m");
+
+ fprintf(f,
+ "__CURSOR=%s\n"
+ "__REALTIME_TIMESTAMP="USEC_FMT"\n"
+ "__MONOTONIC_TIMESTAMP="USEC_FMT"\n"
+ "_BOOT_ID=%s\n",
+ cursor,
+ realtime,
+ monotonic,
+ sd_id128_to_string(boot_id, sid));
+
+ JOURNAL_FOREACH_DATA_RETVAL(j, data, length, r) {
+
+ /* We already printed the boot id, from the data in
+ * the header, hence let's suppress it here */
+ if (length >= 9 &&
+ startswith(data, "_BOOT_ID="))
+ continue;
+
+ if (utf8_is_printable_newline(data, length, false))
+ fwrite(data, length, 1, f);
+ else {
+ const char *c;
+ uint64_t le64;
+
+ c = memchr(data, '=', length);
+ if (!c) {
+ log_error("Invalid field.");
+ return -EINVAL;
+ }
+
+ fwrite(data, c - (const char*) data, 1, f);
+ fputc('\n', f);
+ le64 = htole64(length - (c - (const char*) data) - 1);
+ fwrite(&le64, sizeof(le64), 1, f);
+ fwrite(c + 1, length - (c - (const char*) data) - 1, 1, f);
+ }
+
+ fputc('\n', f);
+ }
+
+ if (r < 0)
+ return r;
+
+ fputc('\n', f);
+
+ return 0;
+}
+
+void json_escape(
+ FILE *f,
+ const char* p,
+ size_t l,
+ OutputFlags flags) {
+
+ assert(f);
+ assert(p);
+
+ if (!(flags & OUTPUT_SHOW_ALL) && l >= JSON_THRESHOLD)
+ fputs("null", f);
+
+ else if (!utf8_is_printable(p, l)) {
+ bool not_first = false;
+
+ fputs("[ ", f);
+
+ while (l > 0) {
+ if (not_first)
+ fprintf(f, ", %u", (uint8_t) *p);
+ else {
+ not_first = true;
+ fprintf(f, "%u", (uint8_t) *p);
+ }
+
+ p++;
+ l--;
+ }
+
+ fputs(" ]", f);
+ } else {
+ fputc('\"', f);
+
+ while (l > 0) {
+ if (*p == '"' || *p == '\\') {
+ fputc('\\', f);
+ fputc(*p, f);
+ } else if (*p == '\n')
+ fputs("\\n", f);
+ else if ((uint8_t) *p < ' ')
+ fprintf(f, "\\u%04x", (uint8_t) *p);
+ else
+ fputc(*p, f);
+
+ p++;
+ l--;
+ }
+
+ fputc('\"', f);
+ }
+}
+
+static int output_json(
+ FILE *f,
+ sd_journal *j,
+ OutputMode mode,
+ unsigned n_columns,
+ OutputFlags flags) {
+
+ uint64_t realtime, monotonic;
+ _cleanup_free_ char *cursor = NULL;
+ const void *data;
+ size_t length;
+ sd_id128_t boot_id;
+ char sid[33], *k;
+ int r;
+ Hashmap *h = NULL;
+ bool done, separator;
+
+ assert(j);
+
+ sd_journal_set_data_threshold(j, flags & OUTPUT_SHOW_ALL ? 0 : JSON_THRESHOLD);
+
+ r = sd_journal_get_realtime_usec(j, &realtime);
+ if (r < 0)
+ return log_error_errno(r, "Failed to get realtime timestamp: %m");
+
+ r = sd_journal_get_monotonic_usec(j, &monotonic, &boot_id);
+ if (r < 0)
+ return log_error_errno(r, "Failed to get monotonic timestamp: %m");
+
+ r = sd_journal_get_cursor(j, &cursor);
+ if (r < 0)
+ return log_error_errno(r, "Failed to get cursor: %m");
+
+ if (mode == OUTPUT_JSON_PRETTY)
+ fprintf(f,
+ "{\n"
+ "\t\"__CURSOR\" : \"%s\",\n"
+ "\t\"__REALTIME_TIMESTAMP\" : \""USEC_FMT"\",\n"
+ "\t\"__MONOTONIC_TIMESTAMP\" : \""USEC_FMT"\",\n"
+ "\t\"_BOOT_ID\" : \"%s\"",
+ cursor,
+ realtime,
+ monotonic,
+ sd_id128_to_string(boot_id, sid));
+ else {
+ if (mode == OUTPUT_JSON_SSE)
+ fputs("data: ", f);
+
+ fprintf(f,
+ "{ \"__CURSOR\" : \"%s\", "
+ "\"__REALTIME_TIMESTAMP\" : \""USEC_FMT"\", "
+ "\"__MONOTONIC_TIMESTAMP\" : \""USEC_FMT"\", "
+ "\"_BOOT_ID\" : \"%s\"",
+ cursor,
+ realtime,
+ monotonic,
+ sd_id128_to_string(boot_id, sid));
+ }
+
+ h = hashmap_new(&string_hash_ops);
+ if (!h)
+ return log_oom();
+
+ /* First round, iterate through the entry and count how often each field appears */
+ JOURNAL_FOREACH_DATA_RETVAL(j, data, length, r) {
+ const char *eq;
+ char *n;
+ unsigned u;
+
+ if (length >= 9 &&
+ memcmp(data, "_BOOT_ID=", 9) == 0)
+ continue;
+
+ eq = memchr(data, '=', length);
+ if (!eq)
+ continue;
+
+ n = strndup(data, eq - (const char*) data);
+ if (!n) {
+ r = log_oom();
+ goto finish;
+ }
+
+ u = PTR_TO_UINT(hashmap_get(h, n));
+ if (u == 0) {
+ r = hashmap_put(h, n, UINT_TO_PTR(1));
+ if (r < 0) {
+ free(n);
+ log_oom();
+ goto finish;
+ }
+ } else {
+ r = hashmap_update(h, n, UINT_TO_PTR(u + 1));
+ free(n);
+ if (r < 0) {
+ log_oom();
+ goto finish;
+ }
+ }
+ }
+
+ if (r < 0)
+ return r;
+
+ separator = true;
+ do {
+ done = true;
+
+ SD_JOURNAL_FOREACH_DATA(j, data, length) {
+ const char *eq;
+ char *kk, *n;
+ size_t m;
+ unsigned u;
+
+ /* We already printed the boot id, from the data in
+ * the header, hence let's suppress it here */
+ if (length >= 9 &&
+ memcmp(data, "_BOOT_ID=", 9) == 0)
+ continue;
+
+ eq = memchr(data, '=', length);
+ if (!eq)
+ continue;
+
+ if (separator) {
+ if (mode == OUTPUT_JSON_PRETTY)
+ fputs(",\n\t", f);
+ else
+ fputs(", ", f);
+ }
+
+ m = eq - (const char*) data;
+
+ n = strndup(data, m);
+ if (!n) {
+ r = log_oom();
+ goto finish;
+ }
+
+ u = PTR_TO_UINT(hashmap_get2(h, n, (void**) &kk));
+ if (u == 0) {
+ /* We already printed this, let's jump to the next */
+ free(n);
+ separator = false;
+
+ continue;
+ } else if (u == 1) {
+ /* Field only appears once, output it directly */
+
+ json_escape(f, data, m, flags);
+ fputs(" : ", f);
+
+ json_escape(f, eq + 1, length - m - 1, flags);
+
+ hashmap_remove(h, n);
+ free(kk);
+ free(n);
+
+ separator = true;
+
+ continue;
+
+ } else {
+ /* Field appears multiple times, output it as array */
+ json_escape(f, data, m, flags);
+ fputs(" : [ ", f);
+ json_escape(f, eq + 1, length - m - 1, flags);
+
+ /* Iterate through the end of the list */
+
+ while (sd_journal_enumerate_data(j, &data, &length) > 0) {
+ if (length < m + 1)
+ continue;
+
+ if (memcmp(data, n, m) != 0)
+ continue;
+
+ if (((const char*) data)[m] != '=')
+ continue;
+
+ fputs(", ", f);
+ json_escape(f, (const char*) data + m + 1, length - m - 1, flags);
+ }
+
+ fputs(" ]", f);
+
+ hashmap_remove(h, n);
+ free(kk);
+ free(n);
+
+ /* Iterate data fields form the beginning */
+ done = false;
+ separator = true;
+
+ break;
+ }
+ }
+
+ } while (!done);
+
+ if (mode == OUTPUT_JSON_PRETTY)
+ fputs("\n}\n", f);
+ else if (mode == OUTPUT_JSON_SSE)
+ fputs("}\n\n", f);
+ else
+ fputs(" }\n", f);
+
+ r = 0;
+
+finish:
+ while ((k = hashmap_steal_first_key(h)))
+ free(k);
+
+ hashmap_free(h);
+
+ return r;
+}
+
+static int output_cat(
+ FILE *f,
+ sd_journal *j,
+ OutputMode mode,
+ unsigned n_columns,
+ OutputFlags flags) {
+
+ const void *data;
+ size_t l;
+ int r;
+
+ assert(j);
+ assert(f);
+
+ sd_journal_set_data_threshold(j, 0);
+
+ r = sd_journal_get_data(j, "MESSAGE", &data, &l);
+ if (r < 0) {
+ /* An entry without MESSAGE=? */
+ if (r == -ENOENT)
+ return 0;
+
+ return log_error_errno(r, "Failed to get data: %m");
+ }
+
+ assert(l >= 8);
+
+ fwrite((const char*) data + 8, 1, l - 8, f);
+ fputc('\n', f);
+
+ return 0;
+}
+
+static int (*output_funcs[_OUTPUT_MODE_MAX])(
+ FILE *f,
+ sd_journal*j,
+ OutputMode mode,
+ unsigned n_columns,
+ OutputFlags flags) = {
+
+ [OUTPUT_SHORT] = output_short,
+ [OUTPUT_SHORT_ISO] = output_short,
+ [OUTPUT_SHORT_PRECISE] = output_short,
+ [OUTPUT_SHORT_MONOTONIC] = output_short,
+ [OUTPUT_VERBOSE] = output_verbose,
+ [OUTPUT_EXPORT] = output_export,
+ [OUTPUT_JSON] = output_json,
+ [OUTPUT_JSON_PRETTY] = output_json,
+ [OUTPUT_JSON_SSE] = output_json,
+ [OUTPUT_CAT] = output_cat
+};
+
+int output_journal(
+ FILE *f,
+ sd_journal *j,
+ OutputMode mode,
+ unsigned n_columns,
+ OutputFlags flags,
+ bool *ellipsized) {
+
+ int ret;
+ assert(mode >= 0);
+ assert(mode < _OUTPUT_MODE_MAX);
+
+ if (n_columns <= 0)
+ n_columns = columns();
+
+ ret = output_funcs[mode](f, j, mode, n_columns, flags);
+ fflush(stdout);
+
+ if (ellipsized && ret > 0)
+ *ellipsized = true;
+
+ return ret;
+}
+
+static int maybe_print_begin_newline(FILE *f, OutputFlags *flags) {
+ assert(f);
+ assert(flags);
+
+ if (!(*flags & OUTPUT_BEGIN_NEWLINE))
+ return 0;
+
+ /* Print a beginning new line if that's request, but only once
+ * on the first line we print. */
+
+ fputc('\n', f);
+ *flags &= ~OUTPUT_BEGIN_NEWLINE;
+ return 0;
+}
+
+static int show_journal(FILE *f,
+ sd_journal *j,
+ OutputMode mode,
+ unsigned n_columns,
+ usec_t not_before,
+ unsigned how_many,
+ OutputFlags flags,
+ bool *ellipsized) {
+
+ int r;
+ unsigned line = 0;
+ bool need_seek = false;
+ int warn_cutoff = flags & OUTPUT_WARN_CUTOFF;
+
+ assert(j);
+ assert(mode >= 0);
+ assert(mode < _OUTPUT_MODE_MAX);
+
+ /* Seek to end */
+ r = sd_journal_seek_tail(j);
+ if (r < 0)
+ return log_error_errno(r, "Failed to seek to tail: %m");
+
+ r = sd_journal_previous_skip(j, how_many);
+ if (r < 0)
+ return log_error_errno(r, "Failed to skip previous: %m");
+
+ for (;;) {
+ for (;;) {
+ usec_t usec;
+
+ if (need_seek) {
+ r = sd_journal_next(j);
+ if (r < 0)
+ return log_error_errno(r, "Failed to iterate through journal: %m");
+ }
+
+ if (r == 0)
+ break;
+
+ need_seek = true;
+
+ if (not_before > 0) {
+ r = sd_journal_get_monotonic_usec(j, &usec, NULL);
+
+ /* -ESTALE is returned if the
+ timestamp is not from this boot */
+ if (r == -ESTALE)
+ continue;
+ else if (r < 0)
+ return log_error_errno(r, "Failed to get journal time: %m");
+
+ if (usec < not_before)
+ continue;
+ }
+
+ line ++;
+ maybe_print_begin_newline(f, &flags);
+
+ r = output_journal(f, j, mode, n_columns, flags, ellipsized);
+ if (r < 0)
+ return r;
+ }
+
+ if (warn_cutoff && line < how_many && not_before > 0) {
+ sd_id128_t boot_id;
+ usec_t cutoff = 0;
+
+ /* Check whether the cutoff line is too early */
+
+ r = sd_id128_get_boot(&boot_id);
+ if (r < 0)
+ return log_error_errno(r, "Failed to get boot id: %m");
+
+ r = sd_journal_get_cutoff_monotonic_usec(j, boot_id, &cutoff, NULL);
+ if (r < 0)
+ return log_error_errno(r, "Failed to get journal cutoff time: %m");
+
+ if (r > 0 && not_before < cutoff) {
+ maybe_print_begin_newline(f, &flags);
+ fprintf(f, "Warning: Journal has been rotated since unit was started. Log output is incomplete or unavailable.\n");
+ }
+
+ warn_cutoff = false;
+ }
+
+ if (!(flags & OUTPUT_FOLLOW))
+ break;
+
+ r = sd_journal_wait(j, USEC_INFINITY);
+ if (r < 0)
+ return log_error_errno(r, "Failed to wait for journal: %m");
+
+ }
+
+ return 0;
+}
+
+int add_matches_for_unit(sd_journal *j, const char *unit) {
+ int r;
+ char *m1, *m2, *m3, *m4;
+
+ assert(j);
+ assert(unit);
+
+ m1 = strjoina("_SYSTEMD_UNIT=", unit);
+ m2 = strjoina("COREDUMP_UNIT=", unit);
+ m3 = strjoina("UNIT=", unit);
+ m4 = strjoina("OBJECT_SYSTEMD_UNIT=", unit);
+
+ (void)(
+ /* Look for messages from the service itself */
+ (r = sd_journal_add_match(j, m1, 0)) ||
+
+ /* Look for coredumps of the service */
+ (r = sd_journal_add_disjunction(j)) ||
+ (r = sd_journal_add_match(j, "MESSAGE_ID=fc2e22bc6ee647b6b90729ab34a250b1", 0)) ||
+ (r = sd_journal_add_match(j, "_UID=0", 0)) ||
+ (r = sd_journal_add_match(j, m2, 0)) ||
+
+ /* Look for messages from PID 1 about this service */
+ (r = sd_journal_add_disjunction(j)) ||
+ (r = sd_journal_add_match(j, "_PID=1", 0)) ||
+ (r = sd_journal_add_match(j, m3, 0)) ||
+
+ /* Look for messages from authorized daemons about this service */
+ (r = sd_journal_add_disjunction(j)) ||
+ (r = sd_journal_add_match(j, "_UID=0", 0)) ||
+ (r = sd_journal_add_match(j, m4, 0))
+ );
+
+ if (r == 0 && endswith(unit, ".slice")) {
+ char *m5 = strappend("_SYSTEMD_SLICE=", unit);
+
+ /* Show all messages belonging to a slice */
+ (void)(
+ (r = sd_journal_add_disjunction(j)) ||
+ (r = sd_journal_add_match(j, m5, 0))
+ );
+ }
+
+ return r;
+}
+
+int add_matches_for_user_unit(sd_journal *j, const char *unit, uid_t uid) {
+ int r;
+ char *m1, *m2, *m3, *m4;
+ char muid[sizeof("_UID=") + DECIMAL_STR_MAX(uid_t)];
+
+ assert(j);
+ assert(unit);
+
+ m1 = strjoina("_SYSTEMD_USER_UNIT=", unit);
+ m2 = strjoina("USER_UNIT=", unit);
+ m3 = strjoina("COREDUMP_USER_UNIT=", unit);
+ m4 = strjoina("OBJECT_SYSTEMD_USER_UNIT=", unit);
+ sprintf(muid, "_UID="UID_FMT, uid);
+
+ (void) (
+ /* Look for messages from the user service itself */
+ (r = sd_journal_add_match(j, m1, 0)) ||
+ (r = sd_journal_add_match(j, muid, 0)) ||
+
+ /* Look for messages from systemd about this service */
+ (r = sd_journal_add_disjunction(j)) ||
+ (r = sd_journal_add_match(j, m2, 0)) ||
+ (r = sd_journal_add_match(j, muid, 0)) ||
+
+ /* Look for coredumps of the service */
+ (r = sd_journal_add_disjunction(j)) ||
+ (r = sd_journal_add_match(j, m3, 0)) ||
+ (r = sd_journal_add_match(j, muid, 0)) ||
+ (r = sd_journal_add_match(j, "_UID=0", 0)) ||
+
+ /* Look for messages from authorized daemons about this service */
+ (r = sd_journal_add_disjunction(j)) ||
+ (r = sd_journal_add_match(j, m4, 0)) ||
+ (r = sd_journal_add_match(j, muid, 0)) ||
+ (r = sd_journal_add_match(j, "_UID=0", 0))
+ );
+
+ if (r == 0 && endswith(unit, ".slice")) {
+ char *m5 = strappend("_SYSTEMD_SLICE=", unit);
+
+ /* Show all messages belonging to a slice */
+ (void)(
+ (r = sd_journal_add_disjunction(j)) ||
+ (r = sd_journal_add_match(j, m5, 0)) ||
+ (r = sd_journal_add_match(j, muid, 0))
+ );
+ }
+
+ return r;
+}
+
+static int get_boot_id_for_machine(const char *machine, sd_id128_t *boot_id) {
+ _cleanup_close_pair_ int pair[2] = { -1, -1 };
+ _cleanup_close_ int pidnsfd = -1, mntnsfd = -1, rootfd = -1;
+ pid_t pid, child;
+ siginfo_t si;
+ char buf[37];
+ ssize_t k;
+ int r;
+
+ assert(machine);
+ assert(boot_id);
+
+ if (!machine_name_is_valid(machine))
+ return -EINVAL;
+
+ r = container_get_leader(machine, &pid);
+ if (r < 0)
+ return r;
+
+ r = namespace_open(pid, &pidnsfd, &mntnsfd, NULL, NULL, &rootfd);
+ if (r < 0)
+ return r;
+
+ if (socketpair(AF_UNIX, SOCK_DGRAM, 0, pair) < 0)
+ return -errno;
+
+ child = fork();
+ if (child < 0)
+ return -errno;
+
+ if (child == 0) {
+ int fd;
+
+ pair[0] = safe_close(pair[0]);
+
+ r = namespace_enter(pidnsfd, mntnsfd, -1, -1, rootfd);
+ if (r < 0)
+ _exit(EXIT_FAILURE);
+
+ fd = open("/proc/sys/kernel/random/boot_id", O_RDONLY|O_CLOEXEC|O_NOCTTY);
+ if (fd < 0)
+ _exit(EXIT_FAILURE);
+
+ r = loop_read_exact(fd, buf, 36, false);
+ safe_close(fd);
+ if (r < 0)
+ _exit(EXIT_FAILURE);
+
+ k = send(pair[1], buf, 36, MSG_NOSIGNAL);
+ if (k != 36)
+ _exit(EXIT_FAILURE);
+
+ _exit(EXIT_SUCCESS);
+ }
+
+ pair[1] = safe_close(pair[1]);
+
+ r = wait_for_terminate(child, &si);
+ if (r < 0 || si.si_code != CLD_EXITED || si.si_status != EXIT_SUCCESS)
+ return r < 0 ? r : -EIO;
+
+ k = recv(pair[0], buf, 36, 0);
+ if (k != 36)
+ return -EIO;
+
+ buf[36] = 0;
+ r = sd_id128_from_string(buf, boot_id);
+ if (r < 0)
+ return r;
+
+ return 0;
+}
+
+int add_match_this_boot(sd_journal *j, const char *machine) {
+ char match[9+32+1] = "_BOOT_ID=";
+ sd_id128_t boot_id;
+ int r;
+
+ assert(j);
+
+ if (machine) {
+ r = get_boot_id_for_machine(machine, &boot_id);
+ if (r < 0)
+ return log_error_errno(r, "Failed to get boot id of container %s: %m", machine);
+ } else {
+ r = sd_id128_get_boot(&boot_id);
+ if (r < 0)
+ return log_error_errno(r, "Failed to get boot id: %m");
+ }
+
+ sd_id128_to_string(boot_id, match + 9);
+ r = sd_journal_add_match(j, match, strlen(match));
+ if (r < 0)
+ return log_error_errno(r, "Failed to add match: %m");
+
+ r = sd_journal_add_conjunction(j);
+ if (r < 0)
+ return log_error_errno(r, "Failed to add conjunction: %m");
+
+ return 0;
+}
+
+int show_journal_by_unit(
+ FILE *f,
+ const char *unit,
+ OutputMode mode,
+ unsigned n_columns,
+ usec_t not_before,
+ unsigned how_many,
+ uid_t uid,
+ OutputFlags flags,
+ int journal_open_flags,
+ bool system_unit,
+ bool *ellipsized) {
+
+ _cleanup_(sd_journal_closep) sd_journal *j = NULL;
+ int r;
+
+ assert(mode >= 0);
+ assert(mode < _OUTPUT_MODE_MAX);
+ assert(unit);
+
+ if (how_many <= 0)
+ return 0;
+
+ r = sd_journal_open(&j, journal_open_flags);
+ if (r < 0)
+ return log_error_errno(r, "Failed to open journal: %m");
+
+ r = add_match_this_boot(j, NULL);
+ if (r < 0)
+ return r;
+
+ if (system_unit)
+ r = add_matches_for_unit(j, unit);
+ else
+ r = add_matches_for_user_unit(j, unit, uid);
+ if (r < 0)
+ return log_error_errno(r, "Failed to add unit matches: %m");
+
+ if (_unlikely_(log_get_max_level() >= LOG_DEBUG)) {
+ _cleanup_free_ char *filter;
+
+ filter = journal_make_match_string(j);
+ if (!filter)
+ return log_oom();
+
+ log_debug("Journal filter: %s", filter);
+ }
+
+ return show_journal(f, j, mode, n_columns, not_before, how_many, flags, ellipsized);
+}
+
+static const char *const output_mode_table[_OUTPUT_MODE_MAX] = {
+ [OUTPUT_SHORT] = "short",
+ [OUTPUT_SHORT_ISO] = "short-iso",
+ [OUTPUT_SHORT_PRECISE] = "short-precise",
+ [OUTPUT_SHORT_MONOTONIC] = "short-monotonic",
+ [OUTPUT_VERBOSE] = "verbose",
+ [OUTPUT_EXPORT] = "export",
+ [OUTPUT_JSON] = "json",
+ [OUTPUT_JSON_PRETTY] = "json-pretty",
+ [OUTPUT_JSON_SSE] = "json-sse",
+ [OUTPUT_CAT] = "cat"
+};
+
+DEFINE_STRING_TABLE_LOOKUP(output_mode, OutputMode);
diff --git a/src/libshared/logs-show.h b/src/libshared/logs-show.h
new file mode 100644
index 0000000000..682a4adc7b
--- /dev/null
+++ b/src/libshared/logs-show.h
@@ -0,0 +1,73 @@
+#pragma once
+
+/***
+ This file is part of systemd.
+
+ Copyright 2012 Lennart Poettering
+
+ systemd is free software; you can redistribute it and/or modify it
+ under the terms of the GNU Lesser General Public License as published by
+ the Free Software Foundation; either version 2.1 of the License, or
+ (at your option) any later version.
+
+ systemd is distributed in the hope that it will be useful, but
+ WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ Lesser General Public License for more details.
+
+ You should have received a copy of the GNU Lesser General Public License
+ along with systemd; If not, see <http://www.gnu.org/licenses/>.
+***/
+
+#include <stdbool.h>
+#include <stddef.h>
+#include <stdio.h>
+#include <sys/types.h>
+
+#include <systemd/sd-journal.h>
+
+#include "macro.h"
+#include "output-mode.h"
+#include "time-util.h"
+#include "util.h"
+
+int output_journal(
+ FILE *f,
+ sd_journal *j,
+ OutputMode mode,
+ unsigned n_columns,
+ OutputFlags flags,
+ bool *ellipsized);
+
+int add_match_this_boot(sd_journal *j, const char *machine);
+
+int add_matches_for_unit(
+ sd_journal *j,
+ const char *unit);
+
+int add_matches_for_user_unit(
+ sd_journal *j,
+ const char *unit,
+ uid_t uid);
+
+int show_journal_by_unit(
+ FILE *f,
+ const char *unit,
+ OutputMode mode,
+ unsigned n_columns,
+ usec_t not_before,
+ unsigned how_many,
+ uid_t uid,
+ OutputFlags flags,
+ int journal_open_flags,
+ bool system_unit,
+ bool *ellipsized);
+
+void json_escape(
+ FILE *f,
+ const char* p,
+ size_t l,
+ OutputFlags flags);
+
+const char* output_mode_to_string(OutputMode m) _const_;
+OutputMode output_mode_from_string(const char *s) _pure_;
diff --git a/src/libshared/machine-image.c b/src/libshared/machine-image.c
new file mode 100644
index 0000000000..ed8a29c575
--- /dev/null
+++ b/src/libshared/machine-image.c
@@ -0,0 +1,813 @@
+/***
+ This file is part of systemd.
+
+ Copyright 2013 Lennart Poettering
+
+ systemd is free software; you can redistribute it and/or modify it
+ under the terms of the GNU Lesser General Public License as published by
+ the Free Software Foundation; either version 2.1 of the License, or
+ (at your option) any later version.
+
+ systemd is distributed in the hope that it will be useful, but
+ WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ Lesser General Public License for more details.
+
+ You should have received a copy of the GNU Lesser General Public License
+ along with systemd; If not, see <http://www.gnu.org/licenses/>.
+***/
+
+#include <dirent.h>
+#include <errno.h>
+#include <fcntl.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <sys/stat.h>
+#include <unistd.h>
+#include <linux/fs.h>
+#include "alloc-util.h"
+#include "btrfs-util.h"
+#include "chattr-util.h"
+#include "copy.h"
+#include "dirent-util.h"
+#include "fd-util.h"
+#include "fs-util.h"
+#include "hashmap.h"
+#include "lockfile-util.h"
+#include "log.h"
+#include "macro.h"
+#include "machine-image.h"
+#include "mkdir.h"
+#include "path-util.h"
+#include "rm-rf.h"
+#include "string-table.h"
+#include "string-util.h"
+#include "strv.h"
+#include "time-util.h"
+#include "utf8.h"
+#include "util.h"
+#include "xattr-util.h"
+
+static const char image_search_path[] =
+ "/var/lib/machines\0"
+ "/var/lib/container\0" /* legacy */
+ "/usr/local/lib/machines\0"
+ "/usr/lib/machines\0";
+
+Image *image_unref(Image *i) {
+ if (!i)
+ return NULL;
+
+ free(i->name);
+ free(i->path);
+ free(i);
+ return NULL;
+}
+
+static char **image_settings_path(Image *image) {
+ _cleanup_strv_free_ char **l = NULL;
+ char **ret;
+ const char *fn, *s;
+ unsigned i = 0;
+
+ assert(image);
+
+ l = new0(char*, 4);
+ if (!l)
+ return NULL;
+
+ fn = strjoina(image->name, ".nspawn");
+
+ FOREACH_STRING(s, "/etc/systemd/nspawn/", "/run/systemd/nspawn/") {
+ l[i] = strappend(s, fn);
+ if (!l[i])
+ return NULL;
+
+ i++;
+ }
+
+ l[i] = file_in_same_dir(image->path, fn);
+ if (!l[i])
+ return NULL;
+
+ ret = l;
+ l = NULL;
+
+ return ret;
+}
+
+static int image_new(
+ ImageType t,
+ const char *pretty,
+ const char *path,
+ const char *filename,
+ bool read_only,
+ usec_t crtime,
+ usec_t mtime,
+ Image **ret) {
+
+ _cleanup_(image_unrefp) Image *i = NULL;
+
+ assert(t >= 0);
+ assert(t < _IMAGE_TYPE_MAX);
+ assert(pretty);
+ assert(filename);
+ assert(ret);
+
+ i = new0(Image, 1);
+ if (!i)
+ return -ENOMEM;
+
+ i->type = t;
+ i->read_only = read_only;
+ i->crtime = crtime;
+ i->mtime = mtime;
+ i->usage = i->usage_exclusive = (uint64_t) -1;
+ i->limit = i->limit_exclusive = (uint64_t) -1;
+
+ i->name = strdup(pretty);
+ if (!i->name)
+ return -ENOMEM;
+
+ if (path)
+ i->path = strjoin(path, "/", filename, NULL);
+ else
+ i->path = strdup(filename);
+
+ if (!i->path)
+ return -ENOMEM;
+
+ path_kill_slashes(i->path);
+
+ *ret = i;
+ i = NULL;
+
+ return 0;
+}
+
+static int image_make(
+ const char *pretty,
+ int dfd,
+ const char *path,
+ const char *filename,
+ Image **ret) {
+
+ struct stat st;
+ bool read_only;
+ int r;
+
+ assert(filename);
+
+ /* We explicitly *do* follow symlinks here, since we want to
+ * allow symlinking trees into /var/lib/machines/, and treat
+ * them normally. */
+
+ if (fstatat(dfd, filename, &st, 0) < 0)
+ return -errno;
+
+ read_only =
+ (path && path_startswith(path, "/usr")) ||
+ (faccessat(dfd, filename, W_OK, AT_EACCESS) < 0 && errno == EROFS);
+
+ if (S_ISDIR(st.st_mode)) {
+ _cleanup_close_ int fd = -1;
+ unsigned file_attr = 0;
+
+ if (!ret)
+ return 1;
+
+ if (!pretty)
+ pretty = filename;
+
+ fd = openat(dfd, filename, O_CLOEXEC|O_NOCTTY|O_DIRECTORY);
+ if (fd < 0)
+ return -errno;
+
+ /* btrfs subvolumes have inode 256 */
+ if (st.st_ino == 256) {
+
+ r = btrfs_is_filesystem(fd);
+ if (r < 0)
+ return r;
+ if (r) {
+ BtrfsSubvolInfo info;
+
+ /* It's a btrfs subvolume */
+
+ r = btrfs_subvol_get_info_fd(fd, 0, &info);
+ if (r < 0)
+ return r;
+
+ r = image_new(IMAGE_SUBVOLUME,
+ pretty,
+ path,
+ filename,
+ info.read_only || read_only,
+ info.otime,
+ 0,
+ ret);
+ if (r < 0)
+ return r;
+
+ if (btrfs_quota_scan_ongoing(fd) == 0) {
+ BtrfsQuotaInfo quota;
+
+ r = btrfs_subvol_get_subtree_quota_fd(fd, 0, &quota);
+ if (r >= 0) {
+ (*ret)->usage = quota.referenced;
+ (*ret)->usage_exclusive = quota.exclusive;
+
+ (*ret)->limit = quota.referenced_max;
+ (*ret)->limit_exclusive = quota.exclusive_max;
+ }
+ }
+
+ return 1;
+ }
+ }
+
+ /* If the IMMUTABLE bit is set, we consider the
+ * directory read-only. Since the ioctl is not
+ * supported everywhere we ignore failures. */
+ (void) read_attr_fd(fd, &file_attr);
+
+ /* It's just a normal directory. */
+ r = image_new(IMAGE_DIRECTORY,
+ pretty,
+ path,
+ filename,
+ read_only || (file_attr & FS_IMMUTABLE_FL),
+ 0,
+ 0,
+ ret);
+ if (r < 0)
+ return r;
+
+ return 1;
+
+ } else if (S_ISREG(st.st_mode) && endswith(filename, ".raw")) {
+ usec_t crtime = 0;
+
+ /* It's a RAW disk image */
+
+ if (!ret)
+ return 1;
+
+ fd_getcrtime_at(dfd, filename, &crtime, 0);
+
+ if (!pretty)
+ pretty = strndupa(filename, strlen(filename) - 4);
+
+ r = image_new(IMAGE_RAW,
+ pretty,
+ path,
+ filename,
+ !(st.st_mode & 0222) || read_only,
+ crtime,
+ timespec_load(&st.st_mtim),
+ ret);
+ if (r < 0)
+ return r;
+
+ (*ret)->usage = (*ret)->usage_exclusive = st.st_blocks * 512;
+ (*ret)->limit = (*ret)->limit_exclusive = st.st_size;
+
+ return 1;
+ }
+
+ return 0;
+}
+
+int image_find(const char *name, Image **ret) {
+ const char *path;
+ int r;
+
+ assert(name);
+
+ /* There are no images with invalid names */
+ if (!image_name_is_valid(name))
+ return 0;
+
+ NULSTR_FOREACH(path, image_search_path) {
+ _cleanup_closedir_ DIR *d = NULL;
+
+ d = opendir(path);
+ if (!d) {
+ if (errno == ENOENT)
+ continue;
+
+ return -errno;
+ }
+
+ r = image_make(NULL, dirfd(d), path, name, ret);
+ if (r == 0 || r == -ENOENT) {
+ _cleanup_free_ char *raw = NULL;
+
+ raw = strappend(name, ".raw");
+ if (!raw)
+ return -ENOMEM;
+
+ r = image_make(NULL, dirfd(d), path, raw, ret);
+ if (r == 0 || r == -ENOENT)
+ continue;
+ }
+ if (r < 0)
+ return r;
+
+ return 1;
+ }
+
+ if (streq(name, ".host"))
+ return image_make(".host", AT_FDCWD, NULL, "/", ret);
+
+ return 0;
+};
+
+int image_discover(Hashmap *h) {
+ const char *path;
+ int r;
+
+ assert(h);
+
+ NULSTR_FOREACH(path, image_search_path) {
+ _cleanup_closedir_ DIR *d = NULL;
+ struct dirent *de;
+
+ d = opendir(path);
+ if (!d) {
+ if (errno == ENOENT)
+ continue;
+
+ return -errno;
+ }
+
+ FOREACH_DIRENT_ALL(de, d, return -errno) {
+ _cleanup_(image_unrefp) Image *image = NULL;
+
+ if (!image_name_is_valid(de->d_name))
+ continue;
+
+ if (hashmap_contains(h, de->d_name))
+ continue;
+
+ r = image_make(NULL, dirfd(d), path, de->d_name, &image);
+ if (r == 0 || r == -ENOENT)
+ continue;
+ if (r < 0)
+ return r;
+
+ r = hashmap_put(h, image->name, image);
+ if (r < 0)
+ return r;
+
+ image = NULL;
+ }
+ }
+
+ if (!hashmap_contains(h, ".host")) {
+ _cleanup_(image_unrefp) Image *image = NULL;
+
+ r = image_make(".host", AT_FDCWD, NULL, "/", &image);
+ if (r < 0)
+ return r;
+
+ r = hashmap_put(h, image->name, image);
+ if (r < 0)
+ return r;
+
+ image = NULL;
+
+ }
+
+ return 0;
+}
+
+void image_hashmap_free(Hashmap *map) {
+ Image *i;
+
+ while ((i = hashmap_steal_first(map)))
+ image_unref(i);
+
+ hashmap_free(map);
+}
+
+int image_remove(Image *i) {
+ _cleanup_release_lock_file_ LockFile global_lock = LOCK_FILE_INIT, local_lock = LOCK_FILE_INIT;
+ _cleanup_strv_free_ char **settings = NULL;
+ char **j;
+ int r;
+
+ assert(i);
+
+ if (path_equal(i->path, "/") ||
+ path_startswith(i->path, "/usr"))
+ return -EROFS;
+
+ settings = image_settings_path(i);
+ if (!settings)
+ return -ENOMEM;
+
+ /* Make sure we don't interfere with a running nspawn */
+ r = image_path_lock(i->path, LOCK_EX|LOCK_NB, &global_lock, &local_lock);
+ if (r < 0)
+ return r;
+
+ switch (i->type) {
+
+ case IMAGE_SUBVOLUME:
+ r = btrfs_subvol_remove(i->path, BTRFS_REMOVE_RECURSIVE|BTRFS_REMOVE_QUOTA);
+ if (r < 0)
+ return r;
+ break;
+
+ case IMAGE_DIRECTORY:
+ /* Allow deletion of read-only directories */
+ (void) chattr_path(i->path, false, FS_IMMUTABLE_FL);
+ r = rm_rf(i->path, REMOVE_ROOT|REMOVE_PHYSICAL|REMOVE_SUBVOLUME);
+ if (r < 0)
+ return r;
+
+ break;
+
+ case IMAGE_RAW:
+ if (unlink(i->path) < 0)
+ return -errno;
+ break;
+
+ default:
+ return -EOPNOTSUPP;
+ }
+
+ STRV_FOREACH(j, settings) {
+ if (unlink(*j) < 0 && errno != ENOENT)
+ log_debug_errno(errno, "Failed to unlink %s, ignoring: %m", *j);
+ }
+
+ return 0;
+}
+
+static int rename_settings_file(const char *path, const char *new_name) {
+ _cleanup_free_ char *rs = NULL;
+ const char *fn;
+
+ fn = strjoina(new_name, ".nspawn");
+
+ rs = file_in_same_dir(path, fn);
+ if (!rs)
+ return -ENOMEM;
+
+ return rename_noreplace(AT_FDCWD, path, AT_FDCWD, rs);
+}
+
+int image_rename(Image *i, const char *new_name) {
+ _cleanup_release_lock_file_ LockFile global_lock = LOCK_FILE_INIT, local_lock = LOCK_FILE_INIT, name_lock = LOCK_FILE_INIT;
+ _cleanup_free_ char *new_path = NULL, *nn = NULL;
+ _cleanup_strv_free_ char **settings = NULL;
+ unsigned file_attr = 0;
+ char **j;
+ int r;
+
+ assert(i);
+
+ if (!image_name_is_valid(new_name))
+ return -EINVAL;
+
+ if (path_equal(i->path, "/") ||
+ path_startswith(i->path, "/usr"))
+ return -EROFS;
+
+ settings = image_settings_path(i);
+ if (!settings)
+ return -ENOMEM;
+
+ /* Make sure we don't interfere with a running nspawn */
+ r = image_path_lock(i->path, LOCK_EX|LOCK_NB, &global_lock, &local_lock);
+ if (r < 0)
+ return r;
+
+ /* Make sure nobody takes the new name, between the time we
+ * checked it is currently unused in all search paths, and the
+ * time we take possesion of it */
+ r = image_name_lock(new_name, LOCK_EX|LOCK_NB, &name_lock);
+ if (r < 0)
+ return r;
+
+ r = image_find(new_name, NULL);
+ if (r < 0)
+ return r;
+ if (r > 0)
+ return -EEXIST;
+
+ switch (i->type) {
+
+ case IMAGE_DIRECTORY:
+ /* Turn of the immutable bit while we rename the image, so that we can rename it */
+ (void) read_attr_path(i->path, &file_attr);
+
+ if (file_attr & FS_IMMUTABLE_FL)
+ (void) chattr_path(i->path, false, FS_IMMUTABLE_FL);
+
+ /* fall through */
+
+ case IMAGE_SUBVOLUME:
+ new_path = file_in_same_dir(i->path, new_name);
+ break;
+
+ case IMAGE_RAW: {
+ const char *fn;
+
+ fn = strjoina(new_name, ".raw");
+ new_path = file_in_same_dir(i->path, fn);
+ break;
+ }
+
+ default:
+ return -EOPNOTSUPP;
+ }
+
+ if (!new_path)
+ return -ENOMEM;
+
+ nn = strdup(new_name);
+ if (!nn)
+ return -ENOMEM;
+
+ r = rename_noreplace(AT_FDCWD, i->path, AT_FDCWD, new_path);
+ if (r < 0)
+ return r;
+
+ /* Restore the immutable bit, if it was set before */
+ if (file_attr & FS_IMMUTABLE_FL)
+ (void) chattr_path(new_path, true, FS_IMMUTABLE_FL);
+
+ free(i->path);
+ i->path = new_path;
+ new_path = NULL;
+
+ free(i->name);
+ i->name = nn;
+ nn = NULL;
+
+ STRV_FOREACH(j, settings) {
+ r = rename_settings_file(*j, new_name);
+ if (r < 0 && r != -ENOENT)
+ log_debug_errno(r, "Failed to rename settings file %s, ignoring: %m", *j);
+ }
+
+ return 0;
+}
+
+static int clone_settings_file(const char *path, const char *new_name) {
+ _cleanup_free_ char *rs = NULL;
+ const char *fn;
+
+ fn = strjoina(new_name, ".nspawn");
+
+ rs = file_in_same_dir(path, fn);
+ if (!rs)
+ return -ENOMEM;
+
+ return copy_file_atomic(path, rs, 0664, false, 0);
+}
+
+int image_clone(Image *i, const char *new_name, bool read_only) {
+ _cleanup_release_lock_file_ LockFile name_lock = LOCK_FILE_INIT;
+ _cleanup_strv_free_ char **settings = NULL;
+ const char *new_path;
+ char **j;
+ int r;
+
+ assert(i);
+
+ if (!image_name_is_valid(new_name))
+ return -EINVAL;
+
+ settings = image_settings_path(i);
+ if (!settings)
+ return -ENOMEM;
+
+ /* Make sure nobody takes the new name, between the time we
+ * checked it is currently unused in all search paths, and the
+ * time we take possesion of it */
+ r = image_name_lock(new_name, LOCK_EX|LOCK_NB, &name_lock);
+ if (r < 0)
+ return r;
+
+ r = image_find(new_name, NULL);
+ if (r < 0)
+ return r;
+ if (r > 0)
+ return -EEXIST;
+
+ switch (i->type) {
+
+ case IMAGE_SUBVOLUME:
+ case IMAGE_DIRECTORY:
+ new_path = strjoina("/var/lib/machines/", new_name);
+
+ r = btrfs_subvol_snapshot(i->path, new_path, (read_only ? BTRFS_SNAPSHOT_READ_ONLY : 0) | BTRFS_SNAPSHOT_FALLBACK_COPY | BTRFS_SNAPSHOT_RECURSIVE | BTRFS_SNAPSHOT_QUOTA);
+
+ /* Enable "subtree" quotas for the copy, if we didn't
+ * copy any quota from the source. */
+ (void) btrfs_subvol_auto_qgroup(i->path, 0, true);
+
+ break;
+
+ case IMAGE_RAW:
+ new_path = strjoina("/var/lib/machines/", new_name, ".raw");
+
+ r = copy_file_atomic(i->path, new_path, read_only ? 0444 : 0644, false, FS_NOCOW_FL);
+ break;
+
+ default:
+ return -EOPNOTSUPP;
+ }
+
+ if (r < 0)
+ return r;
+
+ STRV_FOREACH(j, settings) {
+ r = clone_settings_file(*j, new_name);
+ if (r < 0 && r != -ENOENT)
+ log_debug_errno(r, "Failed to clone settings %s, ignoring: %m", *j);
+ }
+
+ return 0;
+}
+
+int image_read_only(Image *i, bool b) {
+ _cleanup_release_lock_file_ LockFile global_lock = LOCK_FILE_INIT, local_lock = LOCK_FILE_INIT;
+ int r;
+ assert(i);
+
+ if (path_equal(i->path, "/") ||
+ path_startswith(i->path, "/usr"))
+ return -EROFS;
+
+ /* Make sure we don't interfere with a running nspawn */
+ r = image_path_lock(i->path, LOCK_EX|LOCK_NB, &global_lock, &local_lock);
+ if (r < 0)
+ return r;
+
+ switch (i->type) {
+
+ case IMAGE_SUBVOLUME:
+
+ /* Note that we set the flag only on the top-level
+ * subvolume of the image. */
+
+ r = btrfs_subvol_set_read_only(i->path, b);
+ if (r < 0)
+ return r;
+
+ break;
+
+ case IMAGE_DIRECTORY:
+ /* For simple directory trees we cannot use the access
+ mode of the top-level directory, since it has an
+ effect on the container itself. However, we can
+ use the "immutable" flag, to at least make the
+ top-level directory read-only. It's not as good as
+ a read-only subvolume, but at least something, and
+ we can read the value back.*/
+
+ r = chattr_path(i->path, b, FS_IMMUTABLE_FL);
+ if (r < 0)
+ return r;
+
+ break;
+
+ case IMAGE_RAW: {
+ struct stat st;
+
+ if (stat(i->path, &st) < 0)
+ return -errno;
+
+ if (chmod(i->path, (st.st_mode & 0444) | (b ? 0000 : 0200)) < 0)
+ return -errno;
+
+ /* If the images is now read-only, it's a good time to
+ * defrag it, given that no write patterns will
+ * fragment it again. */
+ if (b)
+ (void) btrfs_defrag(i->path);
+ break;
+ }
+
+ default:
+ return -EOPNOTSUPP;
+ }
+
+ return 0;
+}
+
+int image_path_lock(const char *path, int operation, LockFile *global, LockFile *local) {
+ _cleanup_free_ char *p = NULL;
+ LockFile t = LOCK_FILE_INIT;
+ struct stat st;
+ int r;
+
+ assert(path);
+ assert(global);
+ assert(local);
+
+ /* Locks an image path. This actually creates two locks: one
+ * "local" one, next to the image path itself, which might be
+ * shared via NFS. And another "global" one, in /run, that
+ * uses the device/inode number. This has the benefit that we
+ * can even lock a tree that is a mount point, correctly. */
+
+ if (path_equal(path, "/"))
+ return -EBUSY;
+
+ if (!path_is_absolute(path))
+ return -EINVAL;
+
+ if (stat(path, &st) >= 0) {
+ if (asprintf(&p, "/run/systemd/nspawn/locks/inode-%lu:%lu", (unsigned long) st.st_dev, (unsigned long) st.st_ino) < 0)
+ return -ENOMEM;
+ }
+
+ r = make_lock_file_for(path, operation, &t);
+ if (r < 0)
+ return r;
+
+ if (p) {
+ mkdir_p("/run/systemd/nspawn/locks", 0700);
+
+ r = make_lock_file(p, operation, global);
+ if (r < 0) {
+ release_lock_file(&t);
+ return r;
+ }
+ }
+
+ *local = t;
+ return 0;
+}
+
+int image_set_limit(Image *i, uint64_t referenced_max) {
+ assert(i);
+
+ if (path_equal(i->path, "/") ||
+ path_startswith(i->path, "/usr"))
+ return -EROFS;
+
+ if (i->type != IMAGE_SUBVOLUME)
+ return -EOPNOTSUPP;
+
+ /* We set the quota both for the subvolume as well as for the
+ * subtree. The latter is mostly for historical reasons, since
+ * we didn't use to have a concept of subtree quota, and hence
+ * only modified the subvolume quota. */
+
+ (void) btrfs_qgroup_set_limit(i->path, 0, referenced_max);
+ (void) btrfs_subvol_auto_qgroup(i->path, 0, true);
+ return btrfs_subvol_set_subtree_quota_limit(i->path, 0, referenced_max);
+}
+
+int image_name_lock(const char *name, int operation, LockFile *ret) {
+ const char *p;
+
+ assert(name);
+ assert(ret);
+
+ /* Locks an image name, regardless of the precise path used. */
+
+ if (!image_name_is_valid(name))
+ return -EINVAL;
+
+ if (streq(name, ".host"))
+ return -EBUSY;
+
+ mkdir_p("/run/systemd/nspawn/locks", 0700);
+ p = strjoina("/run/systemd/nspawn/locks/name-", name);
+
+ return make_lock_file(p, operation, ret);
+}
+
+bool image_name_is_valid(const char *s) {
+ if (!filename_is_valid(s))
+ return false;
+
+ if (string_has_cc(s, NULL))
+ return false;
+
+ if (!utf8_is_valid(s))
+ return false;
+
+ /* Temporary files for atomically creating new files */
+ if (startswith(s, ".#"))
+ return false;
+
+ return true;
+}
+
+static const char* const image_type_table[_IMAGE_TYPE_MAX] = {
+ [IMAGE_DIRECTORY] = "directory",
+ [IMAGE_SUBVOLUME] = "subvolume",
+ [IMAGE_RAW] = "raw",
+};
+
+DEFINE_STRING_TABLE_LOOKUP(image_type, ImageType);
diff --git a/src/libshared/machine-image.h b/src/libshared/machine-image.h
new file mode 100644
index 0000000000..31b720d50c
--- /dev/null
+++ b/src/libshared/machine-image.h
@@ -0,0 +1,77 @@
+#pragma once
+
+/***
+ This file is part of systemd.
+
+ Copyright 2014 Lennart Poettering
+
+ systemd is free software; you can redistribute it and/or modify it
+ under the terms of the GNU Lesser General Public License as published by
+ the Free Software Foundation; either version 2.1 of the License, or
+ (at your option) any later version.
+
+ systemd is distributed in the hope that it will be useful, but
+ WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ Lesser General Public License for more details.
+
+ You should have received a copy of the GNU Lesser General Public License
+ along with systemd; If not, see <http://www.gnu.org/licenses/>.
+***/
+
+#include <stdbool.h>
+#include <stdint.h>
+
+#include "hashmap.h"
+#include "lockfile-util.h"
+#include "macro.h"
+#include "time-util.h"
+
+typedef enum ImageType {
+ IMAGE_DIRECTORY,
+ IMAGE_SUBVOLUME,
+ IMAGE_RAW,
+ _IMAGE_TYPE_MAX,
+ _IMAGE_TYPE_INVALID = -1
+} ImageType;
+
+typedef struct Image {
+ ImageType type;
+ char *name;
+ char *path;
+ bool read_only;
+
+ usec_t crtime;
+ usec_t mtime;
+
+ uint64_t usage;
+ uint64_t usage_exclusive;
+ uint64_t limit;
+ uint64_t limit_exclusive;
+
+ void *userdata;
+} Image;
+
+Image *image_unref(Image *i);
+void image_hashmap_free(Hashmap *map);
+
+DEFINE_TRIVIAL_CLEANUP_FUNC(Image*, image_unref);
+DEFINE_TRIVIAL_CLEANUP_FUNC(Hashmap*, image_hashmap_free);
+
+int image_find(const char *name, Image **ret);
+int image_discover(Hashmap *map);
+
+int image_remove(Image *i);
+int image_rename(Image *i, const char *new_name);
+int image_clone(Image *i, const char *new_name, bool read_only);
+int image_read_only(Image *i, bool b);
+
+const char* image_type_to_string(ImageType t) _const_;
+ImageType image_type_from_string(const char *s) _pure_;
+
+bool image_name_is_valid(const char *s) _pure_;
+
+int image_path_lock(const char *path, int operation, LockFile *global, LockFile *local);
+int image_name_lock(const char *name, int operation, LockFile *ret);
+
+int image_set_limit(Image *i, uint64_t referenced_max);
diff --git a/src/libshared/machine-pool.c b/src/libshared/machine-pool.c
new file mode 100644
index 0000000000..056b5bfae5
--- /dev/null
+++ b/src/libshared/machine-pool.c
@@ -0,0 +1,427 @@
+/***
+ This file is part of systemd.
+
+ Copyright 2015 Lennart Poettering
+
+ systemd is free software; you can redistribute it and/or modify it
+ under the terms of the GNU Lesser General Public License as published by
+ the Free Software Foundation; either version 2.1 of the License, or
+ (at your option) any later version.
+
+ systemd is distributed in the hope that it will be useful, but
+ WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ Lesser General Public License for more details.
+
+ You should have received a copy of the GNU Lesser General Public License
+ along with systemd; If not, see <http://www.gnu.org/licenses/>.
+***/
+
+#include <errno.h>
+#include <fcntl.h>
+#include <linux/loop.h>
+#include <signal.h>
+#include <stdbool.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <sys/ioctl.h>
+#include <sys/mount.h>
+#include <sys/prctl.h>
+#include <sys/stat.h>
+#include <sys/statfs.h>
+#include <sys/statvfs.h>
+#include <unistd.h>
+
+#include <systemd/sd-bus-protocol.h>
+#include <systemd/sd-bus.h>
+
+#include "alloc-util.h"
+#include "btrfs-util.h"
+#include "fd-util.h"
+#include "fileio.h"
+#include "fs-util.h"
+#include "lockfile-util.h"
+#include "log.h"
+#include "machine-pool.h"
+#include "macro.h"
+#include "missing.h"
+#include "mkdir.h"
+#include "mount-util.h"
+#include "parse-util.h"
+#include "path-util.h"
+#include "process-util.h"
+#include "signal-util.h"
+#include "stat-util.h"
+#include "string-util.h"
+
+#define VAR_LIB_MACHINES_SIZE_START (1024UL*1024UL*500UL)
+#define VAR_LIB_MACHINES_FREE_MIN (1024UL*1024UL*750UL)
+
+static int check_btrfs(void) {
+ struct statfs sfs;
+
+ if (statfs("/var/lib/machines", &sfs) < 0) {
+ if (errno != ENOENT)
+ return -errno;
+
+ if (statfs("/var/lib", &sfs) < 0)
+ return -errno;
+ }
+
+ return F_TYPE_EQUAL(sfs.f_type, BTRFS_SUPER_MAGIC);
+}
+
+static int setup_machine_raw(uint64_t size, sd_bus_error *error) {
+ _cleanup_free_ char *tmp = NULL;
+ _cleanup_close_ int fd = -1;
+ struct statvfs ss;
+ pid_t pid = 0;
+ siginfo_t si;
+ int r;
+
+ /* We want to be able to make use of btrfs-specific file
+ * system features, in particular subvolumes, reflinks and
+ * quota. Hence, if we detect that /var/lib/machines.raw is
+ * not located on btrfs, let's create a loopback file, place a
+ * btrfs file system into it, and mount it to
+ * /var/lib/machines. */
+
+ fd = open("/var/lib/machines.raw", O_RDWR|O_CLOEXEC|O_NONBLOCK|O_NOCTTY);
+ if (fd >= 0) {
+ r = fd;
+ fd = -1;
+ return r;
+ }
+
+ if (errno != ENOENT)
+ return sd_bus_error_set_errnof(error, errno, "Failed to open /var/lib/machines.raw: %m");
+
+ r = tempfn_xxxxxx("/var/lib/machines.raw", NULL, &tmp);
+ if (r < 0)
+ return r;
+
+ (void) mkdir_p_label("/var/lib", 0755);
+ fd = open(tmp, O_RDWR|O_CREAT|O_EXCL|O_NOCTTY|O_CLOEXEC, 0600);
+ if (fd < 0)
+ return sd_bus_error_set_errnof(error, errno, "Failed to create /var/lib/machines.raw: %m");
+
+ if (fstatvfs(fd, &ss) < 0) {
+ r = sd_bus_error_set_errnof(error, errno, "Failed to determine free space on /var/lib/machines.raw: %m");
+ goto fail;
+ }
+
+ if (ss.f_bsize * ss.f_bavail < VAR_LIB_MACHINES_FREE_MIN) {
+ r = sd_bus_error_setf(error, SD_BUS_ERROR_FAILED, "Not enough free disk space to set up /var/lib/machines.");
+ goto fail;
+ }
+
+ if (ftruncate(fd, size) < 0) {
+ r = sd_bus_error_set_errnof(error, errno, "Failed to enlarge /var/lib/machines.raw: %m");
+ goto fail;
+ }
+
+ pid = fork();
+ if (pid < 0) {
+ r = sd_bus_error_set_errnof(error, errno, "Failed to fork mkfs.btrfs: %m");
+ goto fail;
+ }
+
+ if (pid == 0) {
+
+ /* Child */
+
+ (void) reset_all_signal_handlers();
+ (void) reset_signal_mask();
+ assert_se(prctl(PR_SET_PDEATHSIG, SIGTERM) == 0);
+
+ fd = safe_close(fd);
+
+ execlp("mkfs.btrfs", "-Lvar-lib-machines", tmp, NULL);
+ if (errno == ENOENT)
+ return 99;
+
+ _exit(EXIT_FAILURE);
+ }
+
+ r = wait_for_terminate(pid, &si);
+ if (r < 0) {
+ sd_bus_error_set_errnof(error, r, "Failed to wait for mkfs.btrfs: %m");
+ goto fail;
+ }
+
+ pid = 0;
+
+ if (si.si_code != CLD_EXITED) {
+ r = sd_bus_error_setf(error, SD_BUS_ERROR_FAILED, "mkfs.btrfs died abnormally.");
+ goto fail;
+ }
+ if (si.si_status == 99) {
+ r = sd_bus_error_set_errnof(error, ENOENT, "Cannot set up /var/lib/machines, mkfs.btrfs is missing");
+ goto fail;
+ }
+ if (si.si_status != 0) {
+ r = sd_bus_error_setf(error, SD_BUS_ERROR_FAILED, "mkfs.btrfs failed with error code %i", si.si_status);
+ goto fail;
+ }
+
+ r = rename_noreplace(AT_FDCWD, tmp, AT_FDCWD, "/var/lib/machines.raw");
+ if (r < 0) {
+ sd_bus_error_set_errnof(error, r, "Failed to move /var/lib/machines.raw into place: %m");
+ goto fail;
+ }
+
+ r = fd;
+ fd = -1;
+
+ return r;
+
+fail:
+ unlink_noerrno(tmp);
+
+ if (pid > 1)
+ kill_and_sigcont(pid, SIGKILL);
+
+ return r;
+}
+
+int setup_machine_directory(uint64_t size, sd_bus_error *error) {
+ _cleanup_release_lock_file_ LockFile lock_file = LOCK_FILE_INIT;
+ struct loop_info64 info = {
+ .lo_flags = LO_FLAGS_AUTOCLEAR,
+ };
+ _cleanup_close_ int fd = -1, control = -1, loop = -1;
+ _cleanup_free_ char* loopdev = NULL;
+ char tmpdir[] = "/tmp/machine-pool.XXXXXX", *mntdir = NULL;
+ bool tmpdir_made = false, mntdir_made = false, mntdir_mounted = false;
+ char buf[FORMAT_BYTES_MAX];
+ int r, nr = -1;
+
+ /* btrfs cannot handle file systems < 16M, hence use this as minimum */
+ if (size == (uint64_t) -1)
+ size = VAR_LIB_MACHINES_SIZE_START;
+ else if (size < 16*1024*1024)
+ size = 16*1024*1024;
+
+ /* Make sure we only set the directory up once at a time */
+ r = make_lock_file("/run/systemd/machines.lock", LOCK_EX, &lock_file);
+ if (r < 0)
+ return r;
+
+ r = check_btrfs();
+ if (r < 0)
+ return sd_bus_error_set_errnof(error, r, "Failed to determine whether /var/lib/machines is located on btrfs: %m");
+ if (r > 0) {
+ (void) btrfs_subvol_make_label("/var/lib/machines");
+
+ r = btrfs_quota_enable("/var/lib/machines", true);
+ if (r < 0)
+ log_warning_errno(r, "Failed to enable quota for /var/lib/machines, ignoring: %m");
+
+ r = btrfs_subvol_auto_qgroup("/var/lib/machines", 0, true);
+ if (r < 0)
+ log_warning_errno(r, "Failed to set up default quota hierarchy for /var/lib/machines, ignoring: %m");
+
+ return 1;
+ }
+
+ if (path_is_mount_point("/var/lib/machines", AT_SYMLINK_FOLLOW) > 0) {
+ log_debug("/var/lib/machines is already a mount point, not creating loopback file for it.");
+ return 0;
+ }
+
+ r = dir_is_populated("/var/lib/machines");
+ if (r < 0 && r != -ENOENT)
+ return r;
+ if (r > 0) {
+ log_debug("/var/log/machines is already populated, not creating loopback file for it.");
+ return 0;
+ }
+
+ r = mkfs_exists("btrfs");
+ if (r == -ENOENT) {
+ log_debug("mkfs.btrfs is missing, cannot create loopback file for /var/lib/machines.");
+ return 0;
+ }
+ if (r < 0)
+ return r;
+
+ fd = setup_machine_raw(size, error);
+ if (fd < 0)
+ return fd;
+
+ control = open("/dev/loop-control", O_RDWR|O_CLOEXEC|O_NOCTTY|O_NONBLOCK);
+ if (control < 0)
+ return sd_bus_error_set_errnof(error, errno, "Failed to open /dev/loop-control: %m");
+
+ nr = ioctl(control, LOOP_CTL_GET_FREE);
+ if (nr < 0)
+ return sd_bus_error_set_errnof(error, errno, "Failed to allocate loop device: %m");
+
+ if (asprintf(&loopdev, "/dev/loop%i", nr) < 0) {
+ r = -ENOMEM;
+ goto fail;
+ }
+
+ loop = open(loopdev, O_CLOEXEC|O_RDWR|O_NOCTTY|O_NONBLOCK);
+ if (loop < 0) {
+ r = sd_bus_error_set_errnof(error, errno, "Failed to open loopback device: %m");
+ goto fail;
+ }
+
+ if (ioctl(loop, LOOP_SET_FD, fd) < 0) {
+ r = sd_bus_error_set_errnof(error, errno, "Failed to bind loopback device: %m");
+ goto fail;
+ }
+
+ if (ioctl(loop, LOOP_SET_STATUS64, &info) < 0) {
+ r = sd_bus_error_set_errnof(error, errno, "Failed to enable auto-clear for loopback device: %m");
+ goto fail;
+ }
+
+ /* We need to make sure the new /var/lib/machines directory
+ * has an access mode of 0700 at the time it is first made
+ * available. mkfs will create it with 0755 however. Hence,
+ * let's mount the directory into an inaccessible directory
+ * below /tmp first, fix the access mode, and move it to the
+ * public place then. */
+
+ if (!mkdtemp(tmpdir)) {
+ r = sd_bus_error_set_errnof(error, errno, "Failed to create temporary mount parent directory: %m");
+ goto fail;
+ }
+ tmpdir_made = true;
+
+ mntdir = strjoina(tmpdir, "/mnt");
+ if (mkdir(mntdir, 0700) < 0) {
+ r = sd_bus_error_set_errnof(error, errno, "Failed to create temporary mount directory: %m");
+ goto fail;
+ }
+ mntdir_made = true;
+
+ if (mount(loopdev, mntdir, "btrfs", 0, NULL) < 0) {
+ r = sd_bus_error_set_errnof(error, errno, "Failed to mount loopback device: %m");
+ goto fail;
+ }
+ mntdir_mounted = true;
+
+ r = btrfs_quota_enable(mntdir, true);
+ if (r < 0)
+ log_warning_errno(r, "Failed to enable quota, ignoring: %m");
+
+ r = btrfs_subvol_auto_qgroup(mntdir, 0, true);
+ if (r < 0)
+ log_warning_errno(r, "Failed to set up default quota hierarchy, ignoring: %m");
+
+ if (chmod(mntdir, 0700) < 0) {
+ r = sd_bus_error_set_errnof(error, errno, "Failed to fix owner: %m");
+ goto fail;
+ }
+
+ (void) mkdir_p_label("/var/lib/machines", 0700);
+
+ if (mount(mntdir, "/var/lib/machines", NULL, MS_BIND, NULL) < 0) {
+ r = sd_bus_error_set_errnof(error, errno, "Failed to mount directory into right place: %m");
+ goto fail;
+ }
+
+ (void) syncfs(fd);
+
+ log_info("Set up /var/lib/machines as btrfs loopback file system of size %s mounted on /var/lib/machines.raw.", format_bytes(buf, sizeof(buf), size));
+
+ (void) umount2(mntdir, MNT_DETACH);
+ (void) rmdir(mntdir);
+ (void) rmdir(tmpdir);
+
+ return 1;
+
+fail:
+ if (mntdir_mounted)
+ (void) umount2(mntdir, MNT_DETACH);
+
+ if (mntdir_made)
+ (void) rmdir(mntdir);
+ if (tmpdir_made)
+ (void) rmdir(tmpdir);
+
+ if (loop >= 0) {
+ (void) ioctl(loop, LOOP_CLR_FD);
+ loop = safe_close(loop);
+ }
+
+ if (control >= 0 && nr >= 0)
+ (void) ioctl(control, LOOP_CTL_REMOVE, nr);
+
+ return r;
+}
+
+static int sync_path(const char *p) {
+ _cleanup_close_ int fd = -1;
+
+ fd = open(p, O_RDONLY|O_CLOEXEC|O_NOCTTY);
+ if (fd < 0)
+ return -errno;
+
+ if (syncfs(fd) < 0)
+ return -errno;
+
+ return 0;
+}
+
+int grow_machine_directory(void) {
+ char buf[FORMAT_BYTES_MAX];
+ struct statvfs a, b;
+ uint64_t old_size, new_size, max_add;
+ int r;
+
+ /* Ensure the disk space data is accurate */
+ sync_path("/var/lib/machines");
+ sync_path("/var/lib/machines.raw");
+
+ if (statvfs("/var/lib/machines.raw", &a) < 0)
+ return -errno;
+
+ if (statvfs("/var/lib/machines", &b) < 0)
+ return -errno;
+
+ /* Don't grow if not enough disk space is available on the host */
+ if (((uint64_t) a.f_bavail * (uint64_t) a.f_bsize) <= VAR_LIB_MACHINES_FREE_MIN)
+ return 0;
+
+ /* Don't grow if at least 1/3th of the fs is still free */
+ if (b.f_bavail > b.f_blocks / 3)
+ return 0;
+
+ /* Calculate how much we are willing to add at most */
+ max_add = ((uint64_t) a.f_bavail * (uint64_t) a.f_bsize) - VAR_LIB_MACHINES_FREE_MIN;
+
+ /* Calculate the old size */
+ old_size = (uint64_t) b.f_blocks * (uint64_t) b.f_bsize;
+
+ /* Calculate the new size as three times the size of what is used right now */
+ new_size = ((uint64_t) b.f_blocks - (uint64_t) b.f_bavail) * (uint64_t) b.f_bsize * 3;
+
+ /* Always, grow at least to the start size */
+ if (new_size < VAR_LIB_MACHINES_SIZE_START)
+ new_size = VAR_LIB_MACHINES_SIZE_START;
+
+ /* If the new size is smaller than the old size, don't grow */
+ if (new_size < old_size)
+ return 0;
+
+ /* Ensure we never add more than the maximum */
+ if (new_size > old_size + max_add)
+ new_size = old_size + max_add;
+
+ r = btrfs_resize_loopback("/var/lib/machines", new_size, true);
+ if (r <= 0)
+ return r;
+
+ /* Also bump the quota, of both the subvolume leaf qgroup, as
+ * well as of any subtree quota group by the same id but a
+ * higher level, if it exists. */
+ (void) btrfs_qgroup_set_limit("/var/lib/machines", 0, new_size);
+ (void) btrfs_subvol_set_subtree_quota_limit("/var/lib/machines", 0, new_size);
+
+ log_info("Grew /var/lib/machines btrfs loopback file system to %s.", format_bytes(buf, sizeof(buf), new_size));
+ return 1;
+}
diff --git a/src/libshared/machine-pool.h b/src/libshared/machine-pool.h
new file mode 100644
index 0000000000..fe99b7e0ae
--- /dev/null
+++ b/src/libshared/machine-pool.h
@@ -0,0 +1,30 @@
+#pragma once
+
+/***
+ This file is part of systemd.
+
+ Copyright 2015 Lennart Poettering
+
+ systemd is free software; you can redistribute it and/or modify it
+ under the terms of the GNU Lesser General Public License as published by
+ the Free Software Foundation; either version 2.1 of the License, or
+ (at your option) any later version.
+
+ systemd is distributed in the hope that it will be useful, but
+ WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ Lesser General Public License for more details.
+
+ You should have received a copy of the GNU Lesser General Public License
+ along with systemd; If not, see <http://www.gnu.org/licenses/>.
+***/
+
+#include <stdint.h>
+
+#include <systemd/sd-bus.h>
+
+/* Grow the /var/lib/machines directory after each 10MiB written */
+#define GROW_INTERVAL_BYTES (UINT64_C(10) * UINT64_C(1024) * UINT64_C(1024))
+
+int setup_machine_directory(uint64_t size, sd_bus_error *error);
+int grow_machine_directory(void);
diff --git a/src/libshared/output-mode.h b/src/libshared/output-mode.h
new file mode 100644
index 0000000000..c5470e7c1b
--- /dev/null
+++ b/src/libshared/output-mode.h
@@ -0,0 +1,46 @@
+#pragma once
+
+/***
+ This file is part of systemd.
+
+ Copyright 2013 Lennart Poettering
+
+ systemd is free software; you can redistribute it and/or modify it
+ under the terms of the GNU Lesser General Public License as published by
+ the Free Software Foundation; either version 2.1 of the License, or
+ (at your option) any later version.
+
+ systemd is distributed in the hope that it will be useful, but
+ WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ Lesser General Public License for more details.
+
+ You should have received a copy of the GNU Lesser General Public License
+ along with systemd; If not, see <http://www.gnu.org/licenses/>.
+***/
+
+typedef enum OutputMode {
+ OUTPUT_SHORT,
+ OUTPUT_SHORT_ISO,
+ OUTPUT_SHORT_PRECISE,
+ OUTPUT_SHORT_MONOTONIC,
+ OUTPUT_VERBOSE,
+ OUTPUT_EXPORT,
+ OUTPUT_JSON,
+ OUTPUT_JSON_PRETTY,
+ OUTPUT_JSON_SSE,
+ OUTPUT_CAT,
+ _OUTPUT_MODE_MAX,
+ _OUTPUT_MODE_INVALID = -1
+} OutputMode;
+
+typedef enum OutputFlags {
+ OUTPUT_SHOW_ALL = 1 << 0,
+ OUTPUT_FOLLOW = 1 << 1,
+ OUTPUT_WARN_CUTOFF = 1 << 2,
+ OUTPUT_FULL_WIDTH = 1 << 3,
+ OUTPUT_COLOR = 1 << 4,
+ OUTPUT_CATALOG = 1 << 5,
+ OUTPUT_BEGIN_NEWLINE = 1 << 6,
+ OUTPUT_UTC = 1 << 7,
+} OutputFlags;
diff --git a/src/libshared/pager.c b/src/libshared/pager.c
new file mode 100644
index 0000000000..05b2b15e40
--- /dev/null
+++ b/src/libshared/pager.c
@@ -0,0 +1,223 @@
+/***
+ This file is part of systemd.
+
+ Copyright 2010 Lennart Poettering
+
+ systemd is free software; you can redistribute it and/or modify it
+ under the terms of the GNU Lesser General Public License as published by
+ the Free Software Foundation; either version 2.1 of the License, or
+ (at your option) any later version.
+
+ systemd is distributed in the hope that it will be useful, but
+ WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ Lesser General Public License for more details.
+
+ You should have received a copy of the GNU Lesser General Public License
+ along with systemd; If not, see <http://www.gnu.org/licenses/>.
+***/
+
+#include <errno.h>
+#include <signal.h>
+#include <stddef.h>
+#include <stdint.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <sys/prctl.h>
+#include <unistd.h>
+
+#include "copy.h"
+#include "fd-util.h"
+#include "locale-util.h"
+#include "log.h"
+#include "macro.h"
+#include "pager.h"
+#include "process-util.h"
+#include "signal-util.h"
+#include "string-util.h"
+#include "terminal-util.h"
+
+static pid_t pager_pid = 0;
+
+noreturn static void pager_fallback(void) {
+ int r;
+
+ r = copy_bytes(STDIN_FILENO, STDOUT_FILENO, (uint64_t) -1, false);
+ if (r < 0) {
+ log_error_errno(r, "Internal pager failed: %m");
+ _exit(EXIT_FAILURE);
+ }
+
+ _exit(EXIT_SUCCESS);
+}
+
+int pager_open(bool jump_to_end) {
+ _cleanup_close_pair_ int fd[2] = { -1, -1 };
+ const char *pager;
+ pid_t parent_pid;
+
+ if (pager_pid > 0)
+ return 1;
+
+ if (!on_tty())
+ return 0;
+
+ pager = getenv("SYSTEMD_PAGER");
+ if (!pager)
+ pager = getenv("PAGER");
+
+ /* If the pager is explicitly turned off, honour it */
+ if (pager && (pager[0] == 0 || streq(pager, "cat")))
+ return 0;
+
+ /* Determine and cache number of columns before we spawn the
+ * pager so that we get the value from the actual tty */
+ (void) columns();
+
+ if (pipe(fd) < 0)
+ return log_error_errno(errno, "Failed to create pager pipe: %m");
+
+ parent_pid = getpid();
+
+ pager_pid = fork();
+ if (pager_pid < 0)
+ return log_error_errno(errno, "Failed to fork pager: %m");
+
+ /* In the child start the pager */
+ if (pager_pid == 0) {
+ const char* less_opts, *less_charset;
+
+ (void) reset_all_signal_handlers();
+ (void) reset_signal_mask();
+
+ (void) dup2(fd[0], STDIN_FILENO);
+ safe_close_pair(fd);
+
+ /* Initialize a good set of less options */
+ less_opts = getenv("SYSTEMD_LESS");
+ if (!less_opts)
+ less_opts = "FRSXMK";
+ if (jump_to_end)
+ less_opts = strjoina(less_opts, " +G");
+ setenv("LESS", less_opts, 1);
+
+ /* Initialize a good charset for less. This is
+ * particularly important if we output UTF-8
+ * characters. */
+ less_charset = getenv("SYSTEMD_LESSCHARSET");
+ if (!less_charset && is_locale_utf8())
+ less_charset = "utf-8";
+ if (less_charset)
+ setenv("LESSCHARSET", less_charset, 1);
+
+ /* Make sure the pager goes away when the parent dies */
+ if (prctl(PR_SET_PDEATHSIG, SIGTERM) < 0)
+ _exit(EXIT_FAILURE);
+
+ /* Check whether our parent died before we were able
+ * to set the death signal */
+ if (getppid() != parent_pid)
+ _exit(EXIT_SUCCESS);
+
+ if (pager) {
+ execlp(pager, pager, NULL);
+ execl("/bin/sh", "sh", "-c", pager, NULL);
+ }
+
+ /* Debian's alternatives command for pagers is
+ * called 'pager'. Note that we do not call
+ * sensible-pagers here, since that is just a
+ * shell script that implements a logic that
+ * is similar to this one anyway, but is
+ * Debian-specific. */
+ execlp("pager", "pager", NULL);
+
+ execlp("less", "less", NULL);
+ execlp("more", "more", NULL);
+
+ pager_fallback();
+ /* not reached */
+ }
+
+ /* Return in the parent */
+ if (dup2(fd[1], STDOUT_FILENO) < 0)
+ return log_error_errno(errno, "Failed to duplicate pager pipe: %m");
+ if (dup2(fd[1], STDERR_FILENO) < 0)
+ return log_error_errno(errno, "Failed to duplicate pager pipe: %m");
+
+ return 1;
+}
+
+void pager_close(void) {
+
+ if (pager_pid <= 0)
+ return;
+
+ /* Inform pager that we are done */
+ stdout = safe_fclose(stdout);
+ stderr = safe_fclose(stderr);
+
+ (void) kill(pager_pid, SIGCONT);
+ (void) wait_for_terminate(pager_pid, NULL);
+ pager_pid = 0;
+}
+
+bool pager_have(void) {
+ return pager_pid > 0;
+}
+
+int show_man_page(const char *desc, bool null_stdio) {
+ const char *args[4] = { "man", NULL, NULL, NULL };
+ char *e = NULL;
+ pid_t pid;
+ size_t k;
+ int r;
+ siginfo_t status;
+
+ k = strlen(desc);
+
+ if (desc[k-1] == ')')
+ e = strrchr(desc, '(');
+
+ if (e) {
+ char *page = NULL, *section = NULL;
+
+ page = strndupa(desc, e - desc);
+ section = strndupa(e + 1, desc + k - e - 2);
+
+ args[1] = section;
+ args[2] = page;
+ } else
+ args[1] = desc;
+
+ pid = fork();
+ if (pid < 0)
+ return log_error_errno(errno, "Failed to fork: %m");
+
+ if (pid == 0) {
+ /* Child */
+
+ (void) reset_all_signal_handlers();
+ (void) reset_signal_mask();
+
+ if (null_stdio) {
+ r = make_null_stdio();
+ if (r < 0) {
+ log_error_errno(r, "Failed to kill stdio: %m");
+ _exit(EXIT_FAILURE);
+ }
+ }
+
+ execvp(args[0], (char**) args);
+ log_error_errno(errno, "Failed to execute man: %m");
+ _exit(EXIT_FAILURE);
+ }
+
+ r = wait_for_terminate(pid, &status);
+ if (r < 0)
+ return r;
+
+ log_debug("Exit code %i status %i", status.si_code, status.si_status);
+ return status.si_status;
+}
diff --git a/src/libshared/pager.h b/src/libshared/pager.h
new file mode 100644
index 0000000000..9fb05796bb
--- /dev/null
+++ b/src/libshared/pager.h
@@ -0,0 +1,30 @@
+#pragma once
+
+/***
+ This file is part of systemd.
+
+ Copyright 2010 Lennart Poettering
+
+ systemd is free software; you can redistribute it and/or modify it
+ under the terms of the GNU Lesser General Public License as published by
+ the Free Software Foundation; either version 2.1 of the License, or
+ (at your option) any later version.
+
+ systemd is distributed in the hope that it will be useful, but
+ WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ Lesser General Public License for more details.
+
+ You should have received a copy of the GNU Lesser General Public License
+ along with systemd; If not, see <http://www.gnu.org/licenses/>.
+***/
+
+#include <stdbool.h>
+
+#include "macro.h"
+
+int pager_open(bool jump_to_end);
+void pager_close(void);
+bool pager_have(void) _pure_;
+
+int show_man_page(const char *page, bool null_stdio);
diff --git a/src/libshared/path-lookup.c b/src/libshared/path-lookup.c
new file mode 100644
index 0000000000..5410620725
--- /dev/null
+++ b/src/libshared/path-lookup.c
@@ -0,0 +1,441 @@
+/***
+ This file is part of systemd.
+
+ Copyright 2010 Lennart Poettering
+
+ systemd is free software; you can redistribute it and/or modify it
+ under the terms of the GNU Lesser General Public License as published by
+ the Free Software Foundation; either version 2.1 of the License, or
+ (at your option) any later version.
+
+ systemd is distributed in the hope that it will be useful, but
+ WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ Lesser General Public License for more details.
+
+ You should have received a copy of the GNU Lesser General Public License
+ along with systemd; If not, see <http://www.gnu.org/licenses/>.
+***/
+
+#include <errno.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+
+#include "alloc-util.h"
+#include "install.h"
+#include "log.h"
+#include "macro.h"
+#include "path-lookup.h"
+#include "path-util.h"
+#include "string-util.h"
+#include "strv.h"
+#include "util.h"
+
+int user_config_home(char **config_home) {
+ const char *e;
+ char *r;
+
+ e = getenv("XDG_CONFIG_HOME");
+ if (e) {
+ r = strappend(e, "/systemd/user");
+ if (!r)
+ return -ENOMEM;
+
+ *config_home = r;
+ return 1;
+ } else {
+ const char *home;
+
+ home = getenv("HOME");
+ if (home) {
+ r = strappend(home, "/.config/systemd/user");
+ if (!r)
+ return -ENOMEM;
+
+ *config_home = r;
+ return 1;
+ }
+ }
+
+ return 0;
+}
+
+int user_runtime_dir(char **runtime_dir) {
+ const char *e;
+ char *r;
+
+ e = getenv("XDG_RUNTIME_DIR");
+ if (e) {
+ r = strappend(e, "/systemd/user");
+ if (!r)
+ return -ENOMEM;
+
+ *runtime_dir = r;
+ return 1;
+ }
+
+ return 0;
+}
+
+static int user_data_home_dir(char **dir, const char *suffix) {
+ const char *e;
+ char *res;
+
+ /* We don't treat /etc/xdg/systemd here as the spec
+ * suggests because we assume that that is a link to
+ * /etc/systemd/ anyway. */
+
+ e = getenv("XDG_DATA_HOME");
+ if (e)
+ res = strappend(e, suffix);
+ else {
+ const char *home;
+
+ home = getenv("HOME");
+ if (home)
+ res = strjoin(home, "/.local/share", suffix, NULL);
+ else
+ return 0;
+ }
+ if (!res)
+ return -ENOMEM;
+
+ *dir = res;
+ return 0;
+}
+
+static char** user_dirs(
+ const char *generator,
+ const char *generator_early,
+ const char *generator_late) {
+
+ const char * const config_unit_paths[] = {
+ USER_CONFIG_UNIT_PATH,
+ "/etc/systemd/user",
+ NULL
+ };
+
+ const char * const runtime_unit_path = "/run/systemd/user";
+
+ const char * const data_unit_paths[] = {
+ "/usr/local/lib/systemd/user",
+ "/usr/local/share/systemd/user",
+ USER_DATA_UNIT_PATH,
+ "/usr/lib/systemd/user",
+ "/usr/share/systemd/user",
+ NULL
+ };
+
+ const char *e;
+ _cleanup_free_ char *config_home = NULL, *runtime_dir = NULL, *data_home = NULL;
+ _cleanup_strv_free_ char **config_dirs = NULL, **data_dirs = NULL;
+ _cleanup_free_ char **res = NULL;
+ char **tmp;
+ int r;
+
+ /* Implement the mechanisms defined in
+ *
+ * http://standards.freedesktop.org/basedir-spec/basedir-spec-0.6.html
+ *
+ * We look in both the config and the data dirs because we
+ * want to encourage that distributors ship their unit files
+ * as data, and allow overriding as configuration.
+ */
+
+ if (user_config_home(&config_home) < 0)
+ return NULL;
+
+ if (user_runtime_dir(&runtime_dir) < 0)
+ return NULL;
+
+ e = getenv("XDG_CONFIG_DIRS");
+ if (e) {
+ config_dirs = strv_split(e, ":");
+ if (!config_dirs)
+ return NULL;
+ }
+
+ r = user_data_home_dir(&data_home, "/systemd/user");
+ if (r < 0)
+ return NULL;
+
+ e = getenv("XDG_DATA_DIRS");
+ if (e)
+ data_dirs = strv_split(e, ":");
+ else
+ data_dirs = strv_new("/usr/local/share",
+ "/usr/share",
+ NULL);
+ if (!data_dirs)
+ return NULL;
+
+ /* Now merge everything we found. */
+ if (generator_early)
+ if (strv_extend(&res, generator_early) < 0)
+ return NULL;
+
+ if (config_home)
+ if (strv_extend(&res, config_home) < 0)
+ return NULL;
+
+ if (!strv_isempty(config_dirs))
+ if (strv_extend_strv_concat(&res, config_dirs, "/systemd/user") < 0)
+ return NULL;
+
+ if (strv_extend_strv(&res, (char**) config_unit_paths, false) < 0)
+ return NULL;
+
+ if (runtime_dir)
+ if (strv_extend(&res, runtime_dir) < 0)
+ return NULL;
+
+ if (strv_extend(&res, runtime_unit_path) < 0)
+ return NULL;
+
+ if (generator)
+ if (strv_extend(&res, generator) < 0)
+ return NULL;
+
+ if (data_home)
+ if (strv_extend(&res, data_home) < 0)
+ return NULL;
+
+ if (!strv_isempty(data_dirs))
+ if (strv_extend_strv_concat(&res, data_dirs, "/systemd/user") < 0)
+ return NULL;
+
+ if (strv_extend_strv(&res, (char**) data_unit_paths, false) < 0)
+ return NULL;
+
+ if (generator_late)
+ if (strv_extend(&res, generator_late) < 0)
+ return NULL;
+
+ if (path_strv_make_absolute_cwd(res) < 0)
+ return NULL;
+
+ tmp = res;
+ res = NULL;
+ return tmp;
+}
+
+char **generator_paths(ManagerRunningAs running_as) {
+ if (running_as == MANAGER_USER)
+ return strv_new("/run/systemd/user-generators",
+ "/etc/systemd/user-generators",
+ "/usr/local/lib/systemd/user-generators",
+ USER_GENERATOR_PATH,
+ NULL);
+ else
+ return strv_new("/run/systemd/system-generators",
+ "/etc/systemd/system-generators",
+ "/usr/local/lib/systemd/system-generators",
+ SYSTEM_GENERATOR_PATH,
+ NULL);
+}
+
+int lookup_paths_init(
+ LookupPaths *p,
+ ManagerRunningAs running_as,
+ bool personal,
+ const char *root_dir,
+ const char *generator,
+ const char *generator_early,
+ const char *generator_late) {
+
+ const char *e;
+ bool append = false; /* Add items from SYSTEMD_UNIT_PATH before normal directories */
+ int r;
+
+ assert(p);
+
+ /* First priority is whatever has been passed to us via env
+ * vars */
+ e = getenv("SYSTEMD_UNIT_PATH");
+ if (e) {
+ if (endswith(e, ":")) {
+ e = strndupa(e, strlen(e) - 1);
+ append = true;
+ }
+
+ /* FIXME: empty components in other places should be
+ * rejected. */
+
+ r = path_split_and_make_absolute(e, &p->unit_path);
+ if (r < 0)
+ return r;
+ } else
+ p->unit_path = NULL;
+
+ if (!p->unit_path || append) {
+ /* Let's figure something out. */
+
+ _cleanup_strv_free_ char **unit_path;
+
+ /* For the user units we include share/ in the search
+ * path in order to comply with the XDG basedir spec.
+ * For the system stuff we avoid such nonsense. OTOH
+ * we include /lib in the search path for the system
+ * stuff but avoid it for user stuff. */
+
+ if (running_as == MANAGER_USER) {
+ if (personal)
+ unit_path = user_dirs(generator, generator_early, generator_late);
+ else
+ unit_path = strv_new(
+ /* If you modify this you also want to modify
+ * systemduserunitpath= in systemd.pc.in, and
+ * the arrays in user_dirs() above! */
+ STRV_IFNOTNULL(generator_early),
+ USER_CONFIG_UNIT_PATH,
+ "/etc/systemd/user",
+ "/run/systemd/user",
+ STRV_IFNOTNULL(generator),
+ "/usr/local/lib/systemd/user",
+ "/usr/local/share/systemd/user",
+ USER_DATA_UNIT_PATH,
+ "/usr/lib/systemd/user",
+ "/usr/share/systemd/user",
+ STRV_IFNOTNULL(generator_late),
+ NULL);
+ } else
+ unit_path = strv_new(
+ /* If you modify this you also want to modify
+ * systemdsystemunitpath= in systemd.pc.in! */
+ STRV_IFNOTNULL(generator_early),
+ SYSTEM_CONFIG_UNIT_PATH,
+ "/etc/systemd/system",
+ "/run/systemd/system",
+ STRV_IFNOTNULL(generator),
+ "/usr/local/lib/systemd/system",
+ SYSTEM_DATA_UNIT_PATH,
+ "/usr/lib/systemd/system",
+#ifdef HAVE_SPLIT_USR
+ "/lib/systemd/system",
+#endif
+ STRV_IFNOTNULL(generator_late),
+ NULL);
+
+ if (!unit_path)
+ return -ENOMEM;
+
+ r = strv_extend_strv(&p->unit_path, unit_path, false);
+ if (r < 0)
+ return r;
+ }
+
+ if (!path_strv_resolve_uniq(p->unit_path, root_dir))
+ return -ENOMEM;
+
+ if (!strv_isempty(p->unit_path)) {
+ _cleanup_free_ char *t = strv_join(p->unit_path, "\n\t");
+ if (!t)
+ return -ENOMEM;
+ log_debug("Looking for unit files in (higher priority first):\n\t%s", t);
+ } else {
+ log_debug("Ignoring unit files.");
+ p->unit_path = strv_free(p->unit_path);
+ }
+
+ if (running_as == MANAGER_SYSTEM) {
+#ifdef HAVE_SYSV_COMPAT
+ /* /etc/init.d/ compatibility does not matter to users */
+
+ e = getenv("SYSTEMD_SYSVINIT_PATH");
+ if (e) {
+ r = path_split_and_make_absolute(e, &p->sysvinit_path);
+ if (r < 0)
+ return r;
+ } else
+ p->sysvinit_path = NULL;
+
+ if (strv_isempty(p->sysvinit_path)) {
+ strv_free(p->sysvinit_path);
+
+ p->sysvinit_path = strv_new(
+ SYSTEM_SYSVINIT_PATH, /* /etc/init.d/ */
+ NULL);
+ if (!p->sysvinit_path)
+ return -ENOMEM;
+ }
+
+ e = getenv("SYSTEMD_SYSVRCND_PATH");
+ if (e) {
+ r = path_split_and_make_absolute(e, &p->sysvrcnd_path);
+ if (r < 0)
+ return r;
+ } else
+ p->sysvrcnd_path = NULL;
+
+ if (strv_isempty(p->sysvrcnd_path)) {
+ strv_free(p->sysvrcnd_path);
+
+ p->sysvrcnd_path = strv_new(
+ SYSTEM_SYSVRCND_PATH, /* /etc/rcN.d/ */
+ NULL);
+ if (!p->sysvrcnd_path)
+ return -ENOMEM;
+ }
+
+ if (!path_strv_resolve_uniq(p->sysvinit_path, root_dir))
+ return -ENOMEM;
+
+ if (!path_strv_resolve_uniq(p->sysvrcnd_path, root_dir))
+ return -ENOMEM;
+
+ if (!strv_isempty(p->sysvinit_path)) {
+ _cleanup_free_ char *t = strv_join(p->sysvinit_path, "\n\t");
+ if (!t)
+ return -ENOMEM;
+ log_debug("Looking for SysV init scripts in:\n\t%s", t);
+ } else {
+ log_debug("Ignoring SysV init scripts.");
+ p->sysvinit_path = strv_free(p->sysvinit_path);
+ }
+
+ if (!strv_isempty(p->sysvrcnd_path)) {
+ _cleanup_free_ char *t =
+ strv_join(p->sysvrcnd_path, "\n\t");
+ if (!t)
+ return -ENOMEM;
+
+ log_debug("Looking for SysV rcN.d links in:\n\t%s", t);
+ } else {
+ log_debug("Ignoring SysV rcN.d links.");
+ p->sysvrcnd_path = strv_free(p->sysvrcnd_path);
+ }
+#else
+ log_debug("SysV init scripts and rcN.d links support disabled");
+#endif
+ }
+
+ return 0;
+}
+
+void lookup_paths_free(LookupPaths *p) {
+ assert(p);
+
+ p->unit_path = strv_free(p->unit_path);
+
+#ifdef HAVE_SYSV_COMPAT
+ p->sysvinit_path = strv_free(p->sysvinit_path);
+ p->sysvrcnd_path = strv_free(p->sysvrcnd_path);
+#endif
+}
+
+int lookup_paths_init_from_scope(LookupPaths *paths,
+ UnitFileScope scope,
+ const char *root_dir) {
+ assert(paths);
+ assert(scope >= 0);
+ assert(scope < _UNIT_FILE_SCOPE_MAX);
+
+ zero(*paths);
+
+ return lookup_paths_init(paths,
+ scope == UNIT_FILE_SYSTEM ? MANAGER_SYSTEM : MANAGER_USER,
+ scope == UNIT_FILE_USER,
+ root_dir,
+ NULL, NULL, NULL);
+}
diff --git a/src/libshared/path-lookup.h b/src/libshared/path-lookup.h
new file mode 100644
index 0000000000..26c83d6111
--- /dev/null
+++ b/src/libshared/path-lookup.h
@@ -0,0 +1,60 @@
+#pragma once
+
+/***
+ This file is part of systemd.
+
+ Copyright 2010 Lennart Poettering
+
+ systemd is free software; you can redistribute it and/or modify it
+ under the terms of the GNU Lesser General Public License as published by
+ the Free Software Foundation; either version 2.1 of the License, or
+ (at your option) any later version.
+
+ systemd is distributed in the hope that it will be useful, but
+ WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ Lesser General Public License for more details.
+
+ You should have received a copy of the GNU Lesser General Public License
+ along with systemd; If not, see <http://www.gnu.org/licenses/>.
+***/
+
+#include <stdbool.h>
+#include "macro.h"
+
+typedef struct LookupPaths {
+ char **unit_path;
+#ifdef HAVE_SYSV_COMPAT
+ char **sysvinit_path;
+ char **sysvrcnd_path;
+#endif
+} LookupPaths;
+
+typedef enum ManagerRunningAs {
+ MANAGER_SYSTEM,
+ MANAGER_USER,
+ _MANAGER_RUNNING_AS_MAX,
+ _MANAGER_RUNNING_AS_INVALID = -1
+} ManagerRunningAs;
+
+int user_config_home(char **config_home);
+int user_runtime_dir(char **runtime_dir);
+
+char **generator_paths(ManagerRunningAs running_as);
+
+int lookup_paths_init(LookupPaths *p,
+ ManagerRunningAs running_as,
+ bool personal,
+ const char *root_dir,
+ const char *generator,
+ const char *generator_early,
+ const char *generator_late);
+
+#include "install.h"
+
+int lookup_paths_init_from_scope(LookupPaths *paths,
+ UnitFileScope scope,
+ const char *root_dir);
+
+void lookup_paths_free(LookupPaths *p);
+#define _cleanup_lookup_paths_free_ _cleanup_(lookup_paths_free)
diff --git a/src/libshared/ptyfwd.c b/src/libshared/ptyfwd.c
new file mode 100644
index 0000000000..a97e0d5143
--- /dev/null
+++ b/src/libshared/ptyfwd.c
@@ -0,0 +1,487 @@
+/***
+ This file is part of systemd.
+
+ Copyright 2010-2013 Lennart Poettering
+
+ systemd is free software; you can redistribute it and/or modify it
+ under the terms of the GNU Lesser General Public License as published by
+ the Free Software Foundation; either version 2.1 of the License, or
+ (at your option) any later version.
+
+ systemd is distributed in the hope that it will be useful, but
+ WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ Lesser General Public License for more details.
+
+ You should have received a copy of the GNU Lesser General Public License
+ along with systemd; If not, see <http://www.gnu.org/licenses/>.
+***/
+
+#include <errno.h>
+#include <limits.h>
+#include <signal.h>
+#include <stddef.h>
+#include <stdint.h>
+#include <stdlib.h>
+#include <string.h>
+#include <sys/epoll.h>
+#include <sys/ioctl.h>
+#include <sys/time.h>
+#include <termios.h>
+#include <unistd.h>
+
+#include <systemd/sd-event.h>
+
+#include "alloc-util.h"
+#include "fd-util.h"
+#include "log.h"
+#include "macro.h"
+#include "ptyfwd.h"
+#include "time-util.h"
+
+struct PTYForward {
+ sd_event *event;
+
+ int master;
+
+ PTYForwardFlags flags;
+
+ sd_event_source *stdin_event_source;
+ sd_event_source *stdout_event_source;
+ sd_event_source *master_event_source;
+
+ sd_event_source *sigwinch_event_source;
+
+ struct termios saved_stdin_attr;
+ struct termios saved_stdout_attr;
+
+ bool saved_stdin:1;
+ bool saved_stdout:1;
+
+ bool stdin_readable:1;
+ bool stdin_hangup:1;
+ bool stdout_writable:1;
+ bool stdout_hangup:1;
+ bool master_readable:1;
+ bool master_writable:1;
+ bool master_hangup:1;
+
+ bool read_from_master:1;
+
+ bool last_char_set:1;
+ char last_char;
+
+ char in_buffer[LINE_MAX], out_buffer[LINE_MAX];
+ size_t in_buffer_full, out_buffer_full;
+
+ usec_t escape_timestamp;
+ unsigned escape_counter;
+};
+
+#define ESCAPE_USEC (1*USEC_PER_SEC)
+
+static bool look_for_escape(PTYForward *f, const char *buffer, size_t n) {
+ const char *p;
+
+ assert(f);
+ assert(buffer);
+ assert(n > 0);
+
+ for (p = buffer; p < buffer + n; p++) {
+
+ /* Check for ^] */
+ if (*p == 0x1D) {
+ usec_t nw = now(CLOCK_MONOTONIC);
+
+ if (f->escape_counter == 0 || nw > f->escape_timestamp + ESCAPE_USEC) {
+ f->escape_timestamp = nw;
+ f->escape_counter = 1;
+ } else {
+ (f->escape_counter)++;
+
+ if (f->escape_counter >= 3)
+ return true;
+ }
+ } else {
+ f->escape_timestamp = 0;
+ f->escape_counter = 0;
+ }
+ }
+
+ return false;
+}
+
+static bool ignore_vhangup(PTYForward *f) {
+ assert(f);
+
+ if (f->flags & PTY_FORWARD_IGNORE_VHANGUP)
+ return true;
+
+ if ((f->flags & PTY_FORWARD_IGNORE_INITIAL_VHANGUP) && !f->read_from_master)
+ return true;
+
+ return false;
+}
+
+static int shovel(PTYForward *f) {
+ ssize_t k;
+
+ assert(f);
+
+ while ((f->stdin_readable && f->in_buffer_full <= 0) ||
+ (f->master_writable && f->in_buffer_full > 0) ||
+ (f->master_readable && f->out_buffer_full <= 0) ||
+ (f->stdout_writable && f->out_buffer_full > 0)) {
+
+ if (f->stdin_readable && f->in_buffer_full < LINE_MAX) {
+
+ k = read(STDIN_FILENO, f->in_buffer + f->in_buffer_full, LINE_MAX - f->in_buffer_full);
+ if (k < 0) {
+
+ if (errno == EAGAIN)
+ f->stdin_readable = false;
+ else if (errno == EIO || errno == EPIPE || errno == ECONNRESET) {
+ f->stdin_readable = false;
+ f->stdin_hangup = true;
+
+ f->stdin_event_source = sd_event_source_unref(f->stdin_event_source);
+ } else {
+ log_error_errno(errno, "read(): %m");
+ return sd_event_exit(f->event, EXIT_FAILURE);
+ }
+ } else if (k == 0) {
+ /* EOF on stdin */
+ f->stdin_readable = false;
+ f->stdin_hangup = true;
+
+ f->stdin_event_source = sd_event_source_unref(f->stdin_event_source);
+ } else {
+ /* Check if ^] has been
+ * pressed three times within
+ * one second. If we get this
+ * we quite immediately. */
+ if (look_for_escape(f, f->in_buffer + f->in_buffer_full, k))
+ return sd_event_exit(f->event, EXIT_FAILURE);
+
+ f->in_buffer_full += (size_t) k;
+ }
+ }
+
+ if (f->master_writable && f->in_buffer_full > 0) {
+
+ k = write(f->master, f->in_buffer, f->in_buffer_full);
+ if (k < 0) {
+
+ if (errno == EAGAIN || errno == EIO)
+ f->master_writable = false;
+ else if (errno == EPIPE || errno == ECONNRESET) {
+ f->master_writable = f->master_readable = false;
+ f->master_hangup = true;
+
+ f->master_event_source = sd_event_source_unref(f->master_event_source);
+ } else {
+ log_error_errno(errno, "write(): %m");
+ return sd_event_exit(f->event, EXIT_FAILURE);
+ }
+ } else {
+ assert(f->in_buffer_full >= (size_t) k);
+ memmove(f->in_buffer, f->in_buffer + k, f->in_buffer_full - k);
+ f->in_buffer_full -= k;
+ }
+ }
+
+ if (f->master_readable && f->out_buffer_full < LINE_MAX) {
+
+ k = read(f->master, f->out_buffer + f->out_buffer_full, LINE_MAX - f->out_buffer_full);
+ if (k < 0) {
+
+ /* Note that EIO on the master device
+ * might be caused by vhangup() or
+ * temporary closing of everything on
+ * the other side, we treat it like
+ * EAGAIN here and try again, unless
+ * ignore_vhangup is off. */
+
+ if (errno == EAGAIN || (errno == EIO && ignore_vhangup(f)))
+ f->master_readable = false;
+ else if (errno == EPIPE || errno == ECONNRESET || errno == EIO) {
+ f->master_readable = f->master_writable = false;
+ f->master_hangup = true;
+
+ f->master_event_source = sd_event_source_unref(f->master_event_source);
+ } else {
+ log_error_errno(errno, "read(): %m");
+ return sd_event_exit(f->event, EXIT_FAILURE);
+ }
+ } else {
+ f->read_from_master = true;
+ f->out_buffer_full += (size_t) k;
+ }
+ }
+
+ if (f->stdout_writable && f->out_buffer_full > 0) {
+
+ k = write(STDOUT_FILENO, f->out_buffer, f->out_buffer_full);
+ if (k < 0) {
+
+ if (errno == EAGAIN)
+ f->stdout_writable = false;
+ else if (errno == EIO || errno == EPIPE || errno == ECONNRESET) {
+ f->stdout_writable = false;
+ f->stdout_hangup = true;
+ f->stdout_event_source = sd_event_source_unref(f->stdout_event_source);
+ } else {
+ log_error_errno(errno, "write(): %m");
+ return sd_event_exit(f->event, EXIT_FAILURE);
+ }
+
+ } else {
+
+ if (k > 0) {
+ f->last_char = f->out_buffer[k-1];
+ f->last_char_set = true;
+ }
+
+ assert(f->out_buffer_full >= (size_t) k);
+ memmove(f->out_buffer, f->out_buffer + k, f->out_buffer_full - k);
+ f->out_buffer_full -= k;
+ }
+ }
+ }
+
+ if (f->stdin_hangup || f->stdout_hangup || f->master_hangup) {
+ /* Exit the loop if any side hung up and if there's
+ * nothing more to write or nothing we could write. */
+
+ if ((f->out_buffer_full <= 0 || f->stdout_hangup) &&
+ (f->in_buffer_full <= 0 || f->master_hangup))
+ return sd_event_exit(f->event, EXIT_SUCCESS);
+ }
+
+ return 0;
+}
+
+static int on_master_event(sd_event_source *e, int fd, uint32_t revents, void *userdata) {
+ PTYForward *f = userdata;
+
+ assert(f);
+ assert(e);
+ assert(e == f->master_event_source);
+ assert(fd >= 0);
+ assert(fd == f->master);
+
+ if (revents & (EPOLLIN|EPOLLHUP))
+ f->master_readable = true;
+
+ if (revents & (EPOLLOUT|EPOLLHUP))
+ f->master_writable = true;
+
+ return shovel(f);
+}
+
+static int on_stdin_event(sd_event_source *e, int fd, uint32_t revents, void *userdata) {
+ PTYForward *f = userdata;
+
+ assert(f);
+ assert(e);
+ assert(e == f->stdin_event_source);
+ assert(fd >= 0);
+ assert(fd == STDIN_FILENO);
+
+ if (revents & (EPOLLIN|EPOLLHUP))
+ f->stdin_readable = true;
+
+ return shovel(f);
+}
+
+static int on_stdout_event(sd_event_source *e, int fd, uint32_t revents, void *userdata) {
+ PTYForward *f = userdata;
+
+ assert(f);
+ assert(e);
+ assert(e == f->stdout_event_source);
+ assert(fd >= 0);
+ assert(fd == STDOUT_FILENO);
+
+ if (revents & (EPOLLOUT|EPOLLHUP))
+ f->stdout_writable = true;
+
+ return shovel(f);
+}
+
+static int on_sigwinch_event(sd_event_source *e, const struct signalfd_siginfo *si, void *userdata) {
+ PTYForward *f = userdata;
+ struct winsize ws;
+
+ assert(f);
+ assert(e);
+ assert(e == f->sigwinch_event_source);
+
+ /* The window size changed, let's forward that. */
+ if (ioctl(STDOUT_FILENO, TIOCGWINSZ, &ws) >= 0)
+ (void) ioctl(f->master, TIOCSWINSZ, &ws);
+
+ return 0;
+}
+
+int pty_forward_new(
+ sd_event *event,
+ int master,
+ PTYForwardFlags flags,
+ PTYForward **ret) {
+
+ _cleanup_(pty_forward_freep) PTYForward *f = NULL;
+ struct winsize ws;
+ int r;
+
+ f = new0(PTYForward, 1);
+ if (!f)
+ return -ENOMEM;
+
+ f->flags = flags;
+
+ if (event)
+ f->event = sd_event_ref(event);
+ else {
+ r = sd_event_default(&f->event);
+ if (r < 0)
+ return r;
+ }
+
+ if (!(flags & PTY_FORWARD_READ_ONLY)) {
+ r = fd_nonblock(STDIN_FILENO, true);
+ if (r < 0)
+ return r;
+
+ r = fd_nonblock(STDOUT_FILENO, true);
+ if (r < 0)
+ return r;
+ }
+
+ r = fd_nonblock(master, true);
+ if (r < 0)
+ return r;
+
+ f->master = master;
+
+ if (ioctl(STDOUT_FILENO, TIOCGWINSZ, &ws) >= 0)
+ (void) ioctl(master, TIOCSWINSZ, &ws);
+
+ if (!(flags & PTY_FORWARD_READ_ONLY)) {
+ if (tcgetattr(STDIN_FILENO, &f->saved_stdin_attr) >= 0) {
+ struct termios raw_stdin_attr;
+
+ f->saved_stdin = true;
+
+ raw_stdin_attr = f->saved_stdin_attr;
+ cfmakeraw(&raw_stdin_attr);
+ raw_stdin_attr.c_oflag = f->saved_stdin_attr.c_oflag;
+ tcsetattr(STDIN_FILENO, TCSANOW, &raw_stdin_attr);
+ }
+
+ if (tcgetattr(STDOUT_FILENO, &f->saved_stdout_attr) >= 0) {
+ struct termios raw_stdout_attr;
+
+ f->saved_stdout = true;
+
+ raw_stdout_attr = f->saved_stdout_attr;
+ cfmakeraw(&raw_stdout_attr);
+ raw_stdout_attr.c_iflag = f->saved_stdout_attr.c_iflag;
+ raw_stdout_attr.c_lflag = f->saved_stdout_attr.c_lflag;
+ tcsetattr(STDOUT_FILENO, TCSANOW, &raw_stdout_attr);
+ }
+
+ r = sd_event_add_io(f->event, &f->stdin_event_source, STDIN_FILENO, EPOLLIN|EPOLLET, on_stdin_event, f);
+ if (r < 0 && r != -EPERM)
+ return r;
+ }
+
+ r = sd_event_add_io(f->event, &f->stdout_event_source, STDOUT_FILENO, EPOLLOUT|EPOLLET, on_stdout_event, f);
+ if (r == -EPERM)
+ /* stdout without epoll support. Likely redirected to regular file. */
+ f->stdout_writable = true;
+ else if (r < 0)
+ return r;
+
+ r = sd_event_add_io(f->event, &f->master_event_source, master, EPOLLIN|EPOLLOUT|EPOLLET, on_master_event, f);
+ if (r < 0)
+ return r;
+
+ r = sd_event_add_signal(f->event, &f->sigwinch_event_source, SIGWINCH, on_sigwinch_event, f);
+ if (r < 0)
+ return r;
+
+ *ret = f;
+ f = NULL;
+
+ return 0;
+}
+
+PTYForward *pty_forward_free(PTYForward *f) {
+
+ if (f) {
+ sd_event_source_unref(f->stdin_event_source);
+ sd_event_source_unref(f->stdout_event_source);
+ sd_event_source_unref(f->master_event_source);
+ sd_event_source_unref(f->sigwinch_event_source);
+ sd_event_unref(f->event);
+
+ if (f->saved_stdout)
+ tcsetattr(STDOUT_FILENO, TCSANOW, &f->saved_stdout_attr);
+ if (f->saved_stdin)
+ tcsetattr(STDIN_FILENO, TCSANOW, &f->saved_stdin_attr);
+
+ free(f);
+ }
+
+ /* STDIN/STDOUT should not be nonblocking normally, so let's
+ * unconditionally reset it */
+ fd_nonblock(STDIN_FILENO, false);
+ fd_nonblock(STDOUT_FILENO, false);
+
+ return NULL;
+}
+
+int pty_forward_get_last_char(PTYForward *f, char *ch) {
+ assert(f);
+ assert(ch);
+
+ if (!f->last_char_set)
+ return -ENXIO;
+
+ *ch = f->last_char;
+ return 0;
+}
+
+int pty_forward_set_ignore_vhangup(PTYForward *f, bool b) {
+ int r;
+
+ assert(f);
+
+ if (!!(f->flags & PTY_FORWARD_IGNORE_VHANGUP) == b)
+ return 0;
+
+ if (b)
+ f->flags |= PTY_FORWARD_IGNORE_VHANGUP;
+ else
+ f->flags &= ~PTY_FORWARD_IGNORE_VHANGUP;
+
+ if (!ignore_vhangup(f)) {
+
+ /* We shall now react to vhangup()s? Let's check
+ * immediately if we might be in one */
+
+ f->master_readable = true;
+ r = shovel(f);
+ if (r < 0)
+ return r;
+ }
+
+ return 0;
+}
+
+int pty_forward_get_ignore_vhangup(PTYForward *f) {
+ assert(f);
+
+ return !!(f->flags & PTY_FORWARD_IGNORE_VHANGUP);
+}
diff --git a/src/libshared/ptyfwd.h b/src/libshared/ptyfwd.h
new file mode 100644
index 0000000000..83c1f60970
--- /dev/null
+++ b/src/libshared/ptyfwd.h
@@ -0,0 +1,48 @@
+#pragma once
+
+/***
+ This file is part of systemd.
+
+ Copyright 2010-2013 Lennart Poettering
+
+ systemd is free software; you can redistribute it and/or modify it
+ under the terms of the GNU Lesser General Public License as published by
+ the Free Software Foundation; either version 2.1 of the License, or
+ (at your option) any later version.
+
+ systemd is distributed in the hope that it will be useful, but
+ WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ Lesser General Public License for more details.
+
+ You should have received a copy of the GNU Lesser General Public License
+ along with systemd; If not, see <http://www.gnu.org/licenses/>.
+***/
+
+#include <stdbool.h>
+
+#include <systemd/sd-event.h>
+
+#include "macro.h"
+
+typedef struct PTYForward PTYForward;
+
+typedef enum PTYForwardFlags {
+ PTY_FORWARD_READ_ONLY = 1,
+
+ /* Continue reading after hangup? */
+ PTY_FORWARD_IGNORE_VHANGUP = 2,
+
+ /* Continue reading after hangup but only if we never read anything else? */
+ PTY_FORWARD_IGNORE_INITIAL_VHANGUP = 4,
+} PTYForwardFlags;
+
+int pty_forward_new(sd_event *event, int master, PTYForwardFlags flags, PTYForward **f);
+PTYForward *pty_forward_free(PTYForward *f);
+
+int pty_forward_get_last_char(PTYForward *f, char *ch);
+
+int pty_forward_set_ignore_vhangup(PTYForward *f, bool ignore_vhangup);
+int pty_forward_get_ignore_vhangup(PTYForward *f);
+
+DEFINE_TRIVIAL_CLEANUP_FUNC(PTYForward*, pty_forward_free);
diff --git a/src/libshared/resolve-util.c b/src/libshared/resolve-util.c
new file mode 100644
index 0000000000..e2da81bab7
--- /dev/null
+++ b/src/libshared/resolve-util.c
@@ -0,0 +1,39 @@
+/***
+ This file is part of systemd.
+
+ Copyright 2016 Lennart Poettering
+
+ systemd is free software; you can redistribute it and/or modify it
+ under the terms of the GNU Lesser General Public License as published by
+ the Free Software Foundation; either version 2.1 of the License, or
+ (at your option) any later version.
+
+ systemd is distributed in the hope that it will be useful, but
+ WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ Lesser General Public License for more details.
+
+ You should have received a copy of the GNU Lesser General Public License
+ along with systemd; If not, see <http://www.gnu.org/licenses/>.
+***/
+
+#include "conf-parser.h"
+#include "resolve-util.h"
+#include "string-table.h"
+
+DEFINE_CONFIG_PARSE_ENUM(config_parse_resolve_support, resolve_support, ResolveSupport, "Failed to parse resolve support setting");
+DEFINE_CONFIG_PARSE_ENUM(config_parse_dnssec_mode, dnssec_mode, DnssecMode, "Failed to parse DNSSEC mode setting");
+
+static const char* const resolve_support_table[_RESOLVE_SUPPORT_MAX] = {
+ [RESOLVE_SUPPORT_NO] = "no",
+ [RESOLVE_SUPPORT_YES] = "yes",
+ [RESOLVE_SUPPORT_RESOLVE] = "resolve",
+};
+DEFINE_STRING_TABLE_LOOKUP_WITH_BOOLEAN(resolve_support, ResolveSupport, RESOLVE_SUPPORT_YES);
+
+static const char* const dnssec_mode_table[_DNSSEC_MODE_MAX] = {
+ [DNSSEC_NO] = "no",
+ [DNSSEC_ALLOW_DOWNGRADE] = "allow-downgrade",
+ [DNSSEC_YES] = "yes",
+};
+DEFINE_STRING_TABLE_LOOKUP_WITH_BOOLEAN(dnssec_mode, DnssecMode, DNSSEC_YES);
diff --git a/src/libshared/resolve-util.h b/src/libshared/resolve-util.h
new file mode 100644
index 0000000000..8636a6c134
--- /dev/null
+++ b/src/libshared/resolve-util.h
@@ -0,0 +1,60 @@
+#pragma once
+
+/***
+ This file is part of systemd.
+
+ Copyright 2016 Lennart Poettering
+
+ systemd is free software; you can redistribute it and/or modify it
+ under the terms of the GNU Lesser General Public License as published by
+ the Free Software Foundation; either version 2.1 of the License, or
+ (at your option) any later version.
+
+ systemd is distributed in the hope that it will be useful, but
+ WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ Lesser General Public License for more details.
+
+ You should have received a copy of the GNU Lesser General Public License
+ along with systemd; If not, see <http://www.gnu.org/licenses/>.
+***/
+
+#include "macro.h"
+
+typedef enum ResolveSupport ResolveSupport;
+typedef enum DnssecMode DnssecMode;
+
+enum ResolveSupport {
+ RESOLVE_SUPPORT_NO,
+ RESOLVE_SUPPORT_YES,
+ RESOLVE_SUPPORT_RESOLVE,
+ _RESOLVE_SUPPORT_MAX,
+ _RESOLVE_SUPPORT_INVALID = -1
+};
+
+enum DnssecMode {
+ /* No DNSSEC validation is done */
+ DNSSEC_NO,
+
+ /* Validate locally, if the server knows DO, but if not,
+ * don't. Don't trust the AD bit. If the server doesn't do
+ * DNSSEC properly, downgrade to non-DNSSEC operation. Of
+ * course, we then are vulnerable to a downgrade attack, but
+ * that's life and what is configured. */
+ DNSSEC_ALLOW_DOWNGRADE,
+
+ /* Insist on DNSSEC server support, and rather fail than downgrading. */
+ DNSSEC_YES,
+
+ _DNSSEC_MODE_MAX,
+ _DNSSEC_MODE_INVALID = -1
+};
+
+int config_parse_resolve_support(const char *unit, const char *filename, unsigned line, const char *section, unsigned section_line, const char *lvalue, int ltype, const char *rvalue, void *data, void *userdata);
+int config_parse_dnssec_mode(const char *unit, const char *filename, unsigned line, const char *section, unsigned section_line, const char *lvalue, int ltype, const char *rvalue, void *data, void *userdata);
+
+const char* resolve_support_to_string(ResolveSupport p) _const_;
+ResolveSupport resolve_support_from_string(const char *s) _pure_;
+
+const char* dnssec_mode_to_string(DnssecMode p) _const_;
+DnssecMode dnssec_mode_from_string(const char *s) _pure_;
diff --git a/src/libshared/seccomp-util.c b/src/libshared/seccomp-util.c
new file mode 100644
index 0000000000..cebe0fce2a
--- /dev/null
+++ b/src/libshared/seccomp-util.c
@@ -0,0 +1,90 @@
+/***
+ This file is part of systemd.
+
+ Copyright 2014 Lennart Poettering
+
+ systemd is free software; you can redistribute it and/or modify it
+ under the terms of the GNU Lesser General Public License as published by
+ the Free Software Foundation; either version 2.1 of the License, or
+ (at your option) any later version.
+
+ systemd is distributed in the hope that it will be useful, but
+ WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ Lesser General Public License for more details.
+
+ You should have received a copy of the GNU Lesser General Public License
+ along with systemd; If not, see <http://www.gnu.org/licenses/>.
+***/
+
+#include <errno.h>
+#include <seccomp.h>
+#include <stddef.h>
+
+#include "macro.h"
+#include "seccomp-util.h"
+#include "string-util.h"
+
+const char* seccomp_arch_to_string(uint32_t c) {
+
+ if (c == SCMP_ARCH_NATIVE)
+ return "native";
+ if (c == SCMP_ARCH_X86)
+ return "x86";
+ if (c == SCMP_ARCH_X86_64)
+ return "x86-64";
+ if (c == SCMP_ARCH_X32)
+ return "x32";
+ if (c == SCMP_ARCH_ARM)
+ return "arm";
+
+ return NULL;
+}
+
+int seccomp_arch_from_string(const char *n, uint32_t *ret) {
+ if (!n)
+ return -EINVAL;
+
+ assert(ret);
+
+ if (streq(n, "native"))
+ *ret = SCMP_ARCH_NATIVE;
+ else if (streq(n, "x86"))
+ *ret = SCMP_ARCH_X86;
+ else if (streq(n, "x86-64"))
+ *ret = SCMP_ARCH_X86_64;
+ else if (streq(n, "x32"))
+ *ret = SCMP_ARCH_X32;
+ else if (streq(n, "arm"))
+ *ret = SCMP_ARCH_ARM;
+ else
+ return -EINVAL;
+
+ return 0;
+}
+
+int seccomp_add_secondary_archs(scmp_filter_ctx *c) {
+
+#if defined(__i386__) || defined(__x86_64__)
+ int r;
+
+ /* Add in all possible secondary archs we are aware of that
+ * this kernel might support. */
+
+ r = seccomp_arch_add(c, SCMP_ARCH_X86);
+ if (r < 0 && r != -EEXIST)
+ return r;
+
+ r = seccomp_arch_add(c, SCMP_ARCH_X86_64);
+ if (r < 0 && r != -EEXIST)
+ return r;
+
+ r = seccomp_arch_add(c, SCMP_ARCH_X32);
+ if (r < 0 && r != -EEXIST)
+ return r;
+
+#endif
+
+ return 0;
+
+}
diff --git a/src/libshared/seccomp-util.h b/src/libshared/seccomp-util.h
new file mode 100644
index 0000000000..4ed2afc1b2
--- /dev/null
+++ b/src/libshared/seccomp-util.h
@@ -0,0 +1,28 @@
+#pragma once
+
+/***
+ This file is part of systemd.
+
+ Copyright 2014 Lennart Poettering
+
+ systemd is free software; you can redistribute it and/or modify it
+ under the terms of the GNU Lesser General Public License as published by
+ the Free Software Foundation; either version 2.1 of the License, or
+ (at your option) any later version.
+
+ systemd is distributed in the hope that it will be useful, but
+ WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ Lesser General Public License for more details.
+
+ You should have received a copy of the GNU Lesser General Public License
+ along with systemd; If not, see <http://www.gnu.org/licenses/>.
+***/
+
+#include <seccomp.h>
+#include <stdint.h>
+
+const char* seccomp_arch_to_string(uint32_t c);
+int seccomp_arch_from_string(const char *n, uint32_t *ret);
+
+int seccomp_add_secondary_archs(scmp_filter_ctx *c);
diff --git a/src/libshared/sleep-config.c b/src/libshared/sleep-config.c
new file mode 100644
index 0000000000..a0aef66bc8
--- /dev/null
+++ b/src/libshared/sleep-config.c
@@ -0,0 +1,274 @@
+/***
+ This file is part of systemd.
+
+ Copyright 2013 Zbigniew Jędrzejewski-Szmek
+
+ systemd is free software; you can redistribute it and/or modify it
+ under the terms of the GNU Lesser General Public License as published by
+ the Free Software Foundation; either version 2.1 of the License, or
+ (at your option) any later version.
+
+ systemd is distributed in the hope that it will be useful, but
+ WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ Lesser General Public License for more details.
+
+ You should have received a copy of the GNU Lesser General Public License
+ along with systemd; If not, see <http://www.gnu.org/licenses/>.
+***/
+
+#include <errno.h>
+#include <stdbool.h>
+#include <stddef.h>
+#include <stdio.h>
+#include <string.h>
+#include <syslog.h>
+#include <unistd.h>
+
+#include "alloc-util.h"
+#include "conf-parser.h"
+#include "def.h"
+#include "fd-util.h"
+#include "fileio.h"
+#include "log.h"
+#include "macro.h"
+#include "parse-util.h"
+#include "sleep-config.h"
+#include "string-util.h"
+#include "strv.h"
+
+#define USE(x, y) do{ (x) = (y); (y) = NULL; } while(0)
+
+int parse_sleep_config(const char *verb, char ***_modes, char ***_states) {
+
+ _cleanup_strv_free_ char
+ **suspend_mode = NULL, **suspend_state = NULL,
+ **hibernate_mode = NULL, **hibernate_state = NULL,
+ **hybrid_mode = NULL, **hybrid_state = NULL;
+ char **modes, **states;
+
+ const ConfigTableItem items[] = {
+ { "Sleep", "SuspendMode", config_parse_strv, 0, &suspend_mode },
+ { "Sleep", "SuspendState", config_parse_strv, 0, &suspend_state },
+ { "Sleep", "HibernateMode", config_parse_strv, 0, &hibernate_mode },
+ { "Sleep", "HibernateState", config_parse_strv, 0, &hibernate_state },
+ { "Sleep", "HybridSleepMode", config_parse_strv, 0, &hybrid_mode },
+ { "Sleep", "HybridSleepState", config_parse_strv, 0, &hybrid_state },
+ {}
+ };
+
+ config_parse_many(PKGSYSCONFDIR "/sleep.conf",
+ CONF_PATHS_NULSTR("systemd/sleep.conf.d"),
+ "Sleep\0", config_item_table_lookup, items,
+ false, NULL);
+
+ if (streq(verb, "suspend")) {
+ /* empty by default */
+ USE(modes, suspend_mode);
+
+ if (suspend_state)
+ USE(states, suspend_state);
+ else
+ states = strv_new("mem", "standby", "freeze", NULL);
+
+ } else if (streq(verb, "hibernate")) {
+ if (hibernate_mode)
+ USE(modes, hibernate_mode);
+ else
+ modes = strv_new("platform", "shutdown", NULL);
+
+ if (hibernate_state)
+ USE(states, hibernate_state);
+ else
+ states = strv_new("disk", NULL);
+
+ } else if (streq(verb, "hybrid-sleep")) {
+ if (hybrid_mode)
+ USE(modes, hybrid_mode);
+ else
+ modes = strv_new("suspend", "platform", "shutdown", NULL);
+
+ if (hybrid_state)
+ USE(states, hybrid_state);
+ else
+ states = strv_new("disk", NULL);
+
+ } else
+ assert_not_reached("what verb");
+
+ if ((!modes && !streq(verb, "suspend")) || !states) {
+ strv_free(modes);
+ strv_free(states);
+ return log_oom();
+ }
+
+ *_modes = modes;
+ *_states = states;
+ return 0;
+}
+
+int can_sleep_state(char **types) {
+ char **type;
+ int r;
+ _cleanup_free_ char *p = NULL;
+
+ if (strv_isempty(types))
+ return true;
+
+ /* If /sys is read-only we cannot sleep */
+ if (access("/sys/power/state", W_OK) < 0)
+ return false;
+
+ r = read_one_line_file("/sys/power/state", &p);
+ if (r < 0)
+ return false;
+
+ STRV_FOREACH(type, types) {
+ const char *word, *state;
+ size_t l, k;
+
+ k = strlen(*type);
+ FOREACH_WORD_SEPARATOR(word, l, p, WHITESPACE, state)
+ if (l == k && memcmp(word, *type, l) == 0)
+ return true;
+ }
+
+ return false;
+}
+
+int can_sleep_disk(char **types) {
+ char **type;
+ int r;
+ _cleanup_free_ char *p = NULL;
+
+ if (strv_isempty(types))
+ return true;
+
+ /* If /sys is read-only we cannot sleep */
+ if (access("/sys/power/disk", W_OK) < 0)
+ return false;
+
+ r = read_one_line_file("/sys/power/disk", &p);
+ if (r < 0)
+ return false;
+
+ STRV_FOREACH(type, types) {
+ const char *word, *state;
+ size_t l, k;
+
+ k = strlen(*type);
+ FOREACH_WORD_SEPARATOR(word, l, p, WHITESPACE, state) {
+ if (l == k && memcmp(word, *type, l) == 0)
+ return true;
+
+ if (l == k + 2 &&
+ word[0] == '[' &&
+ memcmp(word + 1, *type, l - 2) == 0 &&
+ word[l-1] == ']')
+ return true;
+ }
+ }
+
+ return false;
+}
+
+#define HIBERNATION_SWAP_THRESHOLD 0.98
+
+static int hibernation_partition_size(size_t *size, size_t *used) {
+ _cleanup_fclose_ FILE *f;
+ unsigned i;
+
+ assert(size);
+ assert(used);
+
+ f = fopen("/proc/swaps", "re");
+ if (!f) {
+ log_full(errno == ENOENT ? LOG_DEBUG : LOG_WARNING,
+ "Failed to retrieve open /proc/swaps: %m");
+ assert(errno > 0);
+ return -errno;
+ }
+
+ (void) fscanf(f, "%*s %*s %*s %*s %*s\n");
+
+ for (i = 1;; i++) {
+ _cleanup_free_ char *dev = NULL, *type = NULL;
+ size_t size_field, used_field;
+ int k;
+
+ k = fscanf(f,
+ "%ms " /* device/file */
+ "%ms " /* type of swap */
+ "%zu " /* swap size */
+ "%zu " /* used */
+ "%*i\n", /* priority */
+ &dev, &type, &size_field, &used_field);
+ if (k != 4) {
+ if (k == EOF)
+ break;
+
+ log_warning("Failed to parse /proc/swaps:%u", i);
+ continue;
+ }
+
+ if (streq(type, "partition") && endswith(dev, "\\040(deleted)")) {
+ log_warning("Ignoring deleted swapfile '%s'.", dev);
+ continue;
+ }
+
+ *size = size_field;
+ *used = used_field;
+ return 0;
+ }
+
+ log_debug("No swap partitions were found.");
+ return -ENOSYS;
+}
+
+static bool enough_memory_for_hibernation(void) {
+ _cleanup_free_ char *active = NULL;
+ unsigned long long act = 0;
+ size_t size = 0, used = 0;
+ int r;
+
+ r = hibernation_partition_size(&size, &used);
+ if (r < 0)
+ return false;
+
+ r = get_proc_field("/proc/meminfo", "Active(anon)", WHITESPACE, &active);
+ if (r < 0) {
+ log_error_errno(r, "Failed to retrieve Active(anon) from /proc/meminfo: %m");
+ return false;
+ }
+
+ r = safe_atollu(active, &act);
+ if (r < 0) {
+ log_error_errno(r, "Failed to parse Active(anon) from /proc/meminfo: %s: %m",
+ active);
+ return false;
+ }
+
+ r = act <= (size - used) * HIBERNATION_SWAP_THRESHOLD;
+ log_debug("Hibernation is %spossible, Active(anon)=%llu kB, size=%zu kB, used=%zu kB, threshold=%.2g%%",
+ r ? "" : "im", act, size, used, 100*HIBERNATION_SWAP_THRESHOLD);
+
+ return r;
+}
+
+int can_sleep(const char *verb) {
+ _cleanup_strv_free_ char **modes = NULL, **states = NULL;
+ int r;
+
+ assert(streq(verb, "suspend") ||
+ streq(verb, "hibernate") ||
+ streq(verb, "hybrid-sleep"));
+
+ r = parse_sleep_config(verb, &modes, &states);
+ if (r < 0)
+ return false;
+
+ if (!can_sleep_state(states) || !can_sleep_disk(modes))
+ return false;
+
+ return streq(verb, "suspend") || enough_memory_for_hibernation();
+}
diff --git a/src/libshared/sleep-config.h b/src/libshared/sleep-config.h
new file mode 100644
index 0000000000..51f4621844
--- /dev/null
+++ b/src/libshared/sleep-config.h
@@ -0,0 +1,26 @@
+/***
+ This file is part of systemd.
+
+ Copyright 2013 Zbigniew Jędrzejewski-Szmek
+
+ systemd is free software; you can redistribute it and/or modify it
+ under the terms of the GNU Lesser General Public License as published by
+ the Free Software Foundation; either version 2.1 of the License, or
+ (at your option) any later version.
+
+ systemd is distributed in the hope that it will be useful, but
+ WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ Lesser General Public License for more details.
+
+ You should have received a copy of the GNU Lesser General Public License
+ along with systemd; If not, see <http://www.gnu.org/licenses/>.
+***/
+
+#pragma once
+
+int parse_sleep_config(const char *verb, char ***modes, char ***states);
+
+int can_sleep(const char *verb);
+int can_sleep_disk(char **types);
+int can_sleep_state(char **types);
diff --git a/src/libshared/spawn-ask-password-agent.c b/src/libshared/spawn-ask-password-agent.c
new file mode 100644
index 0000000000..a46b7525f0
--- /dev/null
+++ b/src/libshared/spawn-ask-password-agent.c
@@ -0,0 +1,62 @@
+/***
+ This file is part of systemd.
+
+ Copyright 2011 Lennart Poettering
+
+ systemd is free software; you can redistribute it and/or modify it
+ under the terms of the GNU Lesser General Public License as published by
+ the Free Software Foundation; either version 2.1 of the License, or
+ (at your option) any later version.
+
+ systemd is distributed in the hope that it will be useful, but
+ WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ Lesser General Public License for more details.
+
+ You should have received a copy of the GNU Lesser General Public License
+ along with systemd; If not, see <http://www.gnu.org/licenses/>.
+***/
+
+#include <signal.h>
+#include <stdlib.h>
+#include <unistd.h>
+
+#include "log.h"
+#include "process-util.h"
+#include "spawn-ask-password-agent.h"
+#include "util.h"
+
+static pid_t agent_pid = 0;
+
+int ask_password_agent_open(void) {
+ int r;
+
+ if (agent_pid > 0)
+ return 0;
+
+ /* We check STDIN here, not STDOUT, since this is about input,
+ * not output */
+ if (!isatty(STDIN_FILENO))
+ return 0;
+
+ r = fork_agent(&agent_pid,
+ NULL, 0,
+ SYSTEMD_TTY_ASK_PASSWORD_AGENT_BINARY_PATH,
+ SYSTEMD_TTY_ASK_PASSWORD_AGENT_BINARY_PATH, "--watch", NULL);
+ if (r < 0)
+ return log_error_errno(r, "Failed to fork TTY ask password agent: %m");
+
+ return 1;
+}
+
+void ask_password_agent_close(void) {
+
+ if (agent_pid <= 0)
+ return;
+
+ /* Inform agent that we are done */
+ (void) kill(agent_pid, SIGTERM);
+ (void) kill(agent_pid, SIGCONT);
+ (void) wait_for_terminate(agent_pid, NULL);
+ agent_pid = 0;
+}
diff --git a/src/libshared/spawn-ask-password-agent.h b/src/libshared/spawn-ask-password-agent.h
new file mode 100644
index 0000000000..fb0749b13f
--- /dev/null
+++ b/src/libshared/spawn-ask-password-agent.h
@@ -0,0 +1,23 @@
+#pragma once
+
+/***
+ This file is part of systemd.
+
+ Copyright 2011 Lennart Poettering
+
+ systemd is free software; you can redistribute it and/or modify it
+ under the terms of the GNU Lesser General Public License as published by
+ the Free Software Foundation; either version 2.1 of the License, or
+ (at your option) any later version.
+
+ systemd is distributed in the hope that it will be useful, but
+ WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ Lesser General Public License for more details.
+
+ You should have received a copy of the GNU Lesser General Public License
+ along with systemd; If not, see <http://www.gnu.org/licenses/>.
+***/
+
+int ask_password_agent_open(void);
+void ask_password_agent_close(void);
diff --git a/src/libshared/spawn-polkit-agent.c b/src/libshared/spawn-polkit-agent.c
new file mode 100644
index 0000000000..cf3c8ad5a3
--- /dev/null
+++ b/src/libshared/spawn-polkit-agent.c
@@ -0,0 +1,98 @@
+/***
+ This file is part of systemd.
+
+ Copyright 2011 Lennart Poettering
+
+ systemd is free software; you can redistribute it and/or modify it
+ under the terms of the GNU Lesser General Public License as published by
+ the Free Software Foundation; either version 2.1 of the License, or
+ (at your option) any later version.
+
+ systemd is distributed in the hope that it will be useful, but
+ WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ Lesser General Public License for more details.
+
+ You should have received a copy of the GNU Lesser General Public License
+ along with systemd; If not, see <http://www.gnu.org/licenses/>.
+***/
+
+#include <errno.h>
+#include <poll.h>
+#include <signal.h>
+#include <stdlib.h>
+#include <unistd.h>
+
+#include "fd-util.h"
+#include "io-util.h"
+#include "log.h"
+#include "macro.h"
+#include "process-util.h"
+#include "spawn-polkit-agent.h"
+#include "stdio-util.h"
+#include "time-util.h"
+#include "util.h"
+
+#ifdef ENABLE_POLKIT
+static pid_t agent_pid = 0;
+
+int polkit_agent_open(void) {
+ int r;
+ int pipe_fd[2];
+ char notify_fd[DECIMAL_STR_MAX(int) + 1];
+
+ if (agent_pid > 0)
+ return 0;
+
+ /* We check STDIN here, not STDOUT, since this is about input,
+ * not output */
+ if (!isatty(STDIN_FILENO))
+ return 0;
+
+ if (pipe2(pipe_fd, 0) < 0)
+ return -errno;
+
+ xsprintf(notify_fd, "%i", pipe_fd[1]);
+
+ r = fork_agent(&agent_pid,
+ &pipe_fd[1], 1,
+ POLKIT_AGENT_BINARY_PATH,
+ POLKIT_AGENT_BINARY_PATH, "--notify-fd", notify_fd, "--fallback", NULL);
+
+ /* Close the writing side, because that's the one for the agent */
+ safe_close(pipe_fd[1]);
+
+ if (r < 0)
+ log_error_errno(r, "Failed to fork TTY ask password agent: %m");
+ else
+ /* Wait until the agent closes the fd */
+ fd_wait_for_event(pipe_fd[0], POLLHUP, USEC_INFINITY);
+
+ safe_close(pipe_fd[0]);
+
+ return r;
+}
+
+void polkit_agent_close(void) {
+
+ if (agent_pid <= 0)
+ return;
+
+ /* Inform agent that we are done */
+ (void) kill(agent_pid, SIGTERM);
+ (void) kill(agent_pid, SIGCONT);
+
+ (void) wait_for_terminate(agent_pid, NULL);
+ agent_pid = 0;
+}
+
+#else
+
+int polkit_agent_open(void) {
+ return 0;
+}
+
+void polkit_agent_close(void) {
+}
+
+#endif
diff --git a/src/libshared/spawn-polkit-agent.h b/src/libshared/spawn-polkit-agent.h
new file mode 100644
index 0000000000..42b2989ded
--- /dev/null
+++ b/src/libshared/spawn-polkit-agent.h
@@ -0,0 +1,23 @@
+#pragma once
+
+/***
+ This file is part of systemd.
+
+ Copyright 2012 Lennart Poettering
+
+ systemd is free software; you can redistribute it and/or modify it
+ under the terms of the GNU Lesser General Public License as published by
+ the Free Software Foundation; either version 2.1 of the License, or
+ (at your option) any later version.
+
+ systemd is distributed in the hope that it will be useful, but
+ WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ Lesser General Public License for more details.
+
+ You should have received a copy of the GNU Lesser General Public License
+ along with systemd; If not, see <http://www.gnu.org/licenses/>.
+***/
+
+int polkit_agent_open(void);
+void polkit_agent_close(void);
diff --git a/src/libshared/specifier.c b/src/libshared/specifier.c
new file mode 100644
index 0000000000..303ddf0401
--- /dev/null
+++ b/src/libshared/specifier.c
@@ -0,0 +1,188 @@
+/***
+ This file is part of systemd.
+
+ Copyright 2010 Lennart Poettering
+
+ systemd is free software; you can redistribute it and/or modify it
+ under the terms of the GNU Lesser General Public License as published by
+ the Free Software Foundation; either version 2.1 of the License, or
+ (at your option) any later version.
+
+ systemd is distributed in the hope that it will be useful, but
+ WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ Lesser General Public License for more details.
+
+ You should have received a copy of the GNU Lesser General Public License
+ along with systemd; If not, see <http://www.gnu.org/licenses/>.
+***/
+
+#include <errno.h>
+#include <stdbool.h>
+#include <stddef.h>
+#include <stdlib.h>
+#include <string.h>
+#include <sys/utsname.h>
+
+#include <systemd/sd-id128.h>
+
+#include "alloc-util.h"
+#include "hostname-util.h"
+#include "macro.h"
+#include "specifier.h"
+#include "string-util.h"
+
+/*
+ * Generic infrastructure for replacing %x style specifiers in
+ * strings. Will call a callback for each replacement.
+ *
+ */
+
+int specifier_printf(const char *text, const Specifier table[], void *userdata, char **_ret) {
+ char *ret, *t;
+ const char *f;
+ bool percent = false;
+ size_t l;
+ int r;
+
+ assert(text);
+ assert(table);
+
+ l = strlen(text);
+ ret = new(char, l+1);
+ if (!ret)
+ return -ENOMEM;
+
+ t = ret;
+
+ for (f = text; *f; f++, l--) {
+
+ if (percent) {
+ if (*f == '%')
+ *(t++) = '%';
+ else {
+ const Specifier *i;
+
+ for (i = table; i->specifier; i++)
+ if (i->specifier == *f)
+ break;
+
+ if (i->lookup) {
+ _cleanup_free_ char *w = NULL;
+ char *n;
+ size_t k, j;
+
+ r = i->lookup(i->specifier, i->data, userdata, &w);
+ if (r < 0) {
+ free(ret);
+ return r;
+ }
+
+ j = t - ret;
+ k = strlen(w);
+
+ n = new(char, j + k + l + 1);
+ if (!n) {
+ free(ret);
+ return -ENOMEM;
+ }
+
+ memcpy(n, ret, j);
+ memcpy(n + j, w, k);
+
+ free(ret);
+
+ ret = n;
+ t = n + j + k;
+ } else {
+ *(t++) = '%';
+ *(t++) = *f;
+ }
+ }
+
+ percent = false;
+ } else if (*f == '%')
+ percent = true;
+ else
+ *(t++) = *f;
+ }
+
+ *t = 0;
+ *_ret = ret;
+ return 0;
+}
+
+/* Generic handler for simple string replacements */
+
+int specifier_string(char specifier, void *data, void *userdata, char **ret) {
+ char *n;
+
+ n = strdup(strempty(data));
+ if (!n)
+ return -ENOMEM;
+
+ *ret = n;
+ return 0;
+}
+
+int specifier_machine_id(char specifier, void *data, void *userdata, char **ret) {
+ sd_id128_t id;
+ char *n;
+ int r;
+
+ r = sd_id128_get_machine(&id);
+ if (r < 0)
+ return r;
+
+ n = new(char, 33);
+ if (!n)
+ return -ENOMEM;
+
+ *ret = sd_id128_to_string(id, n);
+ return 0;
+}
+
+int specifier_boot_id(char specifier, void *data, void *userdata, char **ret) {
+ sd_id128_t id;
+ char *n;
+ int r;
+
+ r = sd_id128_get_boot(&id);
+ if (r < 0)
+ return r;
+
+ n = new(char, 33);
+ if (!n)
+ return -ENOMEM;
+
+ *ret = sd_id128_to_string(id, n);
+ return 0;
+}
+
+int specifier_host_name(char specifier, void *data, void *userdata, char **ret) {
+ char *n;
+
+ n = gethostname_malloc();
+ if (!n)
+ return -ENOMEM;
+
+ *ret = n;
+ return 0;
+}
+
+int specifier_kernel_release(char specifier, void *data, void *userdata, char **ret) {
+ struct utsname uts;
+ char *n;
+ int r;
+
+ r = uname(&uts);
+ if (r < 0)
+ return -errno;
+
+ n = strdup(uts.release);
+ if (!n)
+ return -ENOMEM;
+
+ *ret = n;
+ return 0;
+}
diff --git a/src/libshared/specifier.h b/src/libshared/specifier.h
new file mode 100644
index 0000000000..6b1623ee61
--- /dev/null
+++ b/src/libshared/specifier.h
@@ -0,0 +1,37 @@
+#pragma once
+
+/***
+ This file is part of systemd.
+
+ Copyright 2010 Lennart Poettering
+
+ systemd is free software; you can redistribute it and/or modify it
+ under the terms of the GNU Lesser General Public License as published by
+ the Free Software Foundation; either version 2.1 of the License, or
+ (at your option) any later version.
+
+ systemd is distributed in the hope that it will be useful, but
+ WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ Lesser General Public License for more details.
+
+ You should have received a copy of the GNU Lesser General Public License
+ along with systemd; If not, see <http://www.gnu.org/licenses/>.
+***/
+
+typedef int (*SpecifierCallback)(char specifier, void *data, void *userdata, char **ret);
+
+typedef struct Specifier {
+ const char specifier;
+ const SpecifierCallback lookup;
+ void *data;
+} Specifier;
+
+int specifier_printf(const char *text, const Specifier table[], void *userdata, char **ret);
+
+int specifier_string(char specifier, void *data, void *userdata, char **ret);
+
+int specifier_machine_id(char specifier, void *data, void *userdata, char **ret);
+int specifier_boot_id(char specifier, void *data, void *userdata, char **ret);
+int specifier_host_name(char specifier, void *data, void *userdata, char **ret);
+int specifier_kernel_release(char specifier, void *data, void *userdata, char **ret);
diff --git a/src/libshared/switch-root.c b/src/libshared/switch-root.c
new file mode 100644
index 0000000000..47d3a5a1fa
--- /dev/null
+++ b/src/libshared/switch-root.c
@@ -0,0 +1,156 @@
+/***
+ This file is part of systemd.
+
+ Copyright 2012 Harald Hoyer, Lennart Poettering
+
+ systemd is free software; you can redistribute it and/or modify it
+ under the terms of the GNU Lesser General Public License as published by
+ the Free Software Foundation; either version 2.1 of the License, or
+ (at your option) any later version.
+
+ systemd is distributed in the hope that it will be useful, but
+ WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ Lesser General Public License for more details.
+
+ You should have received a copy of the GNU Lesser General Public License
+ along with systemd; If not, see <http://www.gnu.org/licenses/>.
+***/
+
+#include <errno.h>
+#include <fcntl.h>
+#include <limits.h>
+#include <stdbool.h>
+#include <stdio.h>
+#include <sys/mount.h>
+#include <sys/stat.h>
+#include <unistd.h>
+
+#include "base-filesystem.h"
+#include "fd-util.h"
+#include "log.h"
+#include "missing.h"
+#include "mkdir.h"
+#include "path-util.h"
+#include "rm-rf.h"
+#include "stdio-util.h"
+#include "string-util.h"
+#include "switch-root.h"
+#include "user-util.h"
+#include "util.h"
+
+int switch_root(const char *new_root, const char *oldroot, bool detach_oldroot, unsigned long mountflags) {
+
+ /* Don't try to unmount/move the old "/", there's no way to do it. */
+ static const char move_mounts[] =
+ "/dev\0"
+ "/proc\0"
+ "/sys\0"
+ "/run\0";
+
+ _cleanup_close_ int old_root_fd = -1;
+ struct stat new_root_stat;
+ bool old_root_remove;
+ const char *i, *temporary_old_root;
+
+ if (path_equal(new_root, "/"))
+ return 0;
+
+ temporary_old_root = strjoina(new_root, oldroot);
+ mkdir_p_label(temporary_old_root, 0755);
+
+ old_root_remove = in_initrd();
+
+ if (stat(new_root, &new_root_stat) < 0)
+ return log_error_errno(errno, "Failed to stat directory %s: %m", new_root);
+
+ /* Work-around for kernel design: the kernel refuses switching
+ * root if any file systems are mounted MS_SHARED. Hence
+ * remount them MS_PRIVATE here as a work-around.
+ *
+ * https://bugzilla.redhat.com/show_bug.cgi?id=847418 */
+ if (mount(NULL, "/", NULL, MS_REC|MS_PRIVATE, NULL) < 0)
+ log_warning_errno(errno, "Failed to make \"/\" private mount: %m");
+
+ NULSTR_FOREACH(i, move_mounts) {
+ char new_mount[PATH_MAX];
+ struct stat sb;
+
+ xsprintf(new_mount, "%s%s", new_root, i);
+
+ mkdir_p_label(new_mount, 0755);
+
+ if ((stat(new_mount, &sb) < 0) ||
+ sb.st_dev != new_root_stat.st_dev) {
+
+ /* Mount point seems to be mounted already or
+ * stat failed. Unmount the old mount
+ * point. */
+ if (umount2(i, MNT_DETACH) < 0)
+ log_warning_errno(errno, "Failed to unmount %s: %m", i);
+ continue;
+ }
+
+ if (mount(i, new_mount, NULL, mountflags, NULL) < 0) {
+ if (mountflags & MS_MOVE) {
+ log_error_errno(errno, "Failed to move mount %s to %s, forcing unmount: %m", i, new_mount);
+
+ if (umount2(i, MNT_FORCE) < 0)
+ log_warning_errno(errno, "Failed to unmount %s: %m", i);
+ }
+ if (mountflags & MS_BIND)
+ log_error_errno(errno, "Failed to bind mount %s to %s: %m", i, new_mount);
+
+ }
+ }
+
+ /* Do not fail, if base_filesystem_create() fails. Not all
+ * switch roots are like base_filesystem_create() wants them
+ * to look like. They might even boot, if they are RO and
+ * don't have the FS layout. Just ignore the error and
+ * switch_root() nevertheless. */
+ (void) base_filesystem_create(new_root, UID_INVALID, GID_INVALID);
+
+ if (chdir(new_root) < 0)
+ return log_error_errno(errno, "Failed to change directory to %s: %m", new_root);
+
+ if (old_root_remove) {
+ old_root_fd = open("/", O_RDONLY|O_NONBLOCK|O_CLOEXEC|O_NOCTTY|O_DIRECTORY);
+ if (old_root_fd < 0)
+ log_warning_errno(errno, "Failed to open root directory: %m");
+ }
+
+ /* We first try a pivot_root() so that we can umount the old
+ * root dir. In many cases (i.e. where rootfs is /), that's
+ * not possible however, and hence we simply overmount root */
+ if (pivot_root(new_root, temporary_old_root) >= 0) {
+
+ /* Immediately get rid of the old root, if detach_oldroot is set.
+ * Since we are running off it we need to do this lazily. */
+ if (detach_oldroot && umount2(oldroot, MNT_DETACH) < 0)
+ log_error_errno(errno, "Failed to lazily umount old root dir %s, %s: %m",
+ oldroot,
+ errno == ENOENT ? "ignoring" : "leaving it around");
+
+ } else if (mount(new_root, "/", NULL, MS_MOVE, NULL) < 0)
+ return log_error_errno(errno, "Failed to mount moving %s to /: %m", new_root);
+
+ if (chroot(".") < 0)
+ return log_error_errno(errno, "Failed to change root: %m");
+
+ if (chdir("/") < 0)
+ return log_error_errno(errno, "Failed to change directory: %m");
+
+ if (old_root_fd >= 0) {
+ struct stat rb;
+
+ if (fstat(old_root_fd, &rb) < 0)
+ log_warning_errno(errno, "Failed to stat old root directory, leaving: %m");
+ else {
+ (void) rm_rf_children(old_root_fd, 0, &rb);
+ old_root_fd = -1;
+ }
+ }
+
+ return 0;
+}
diff --git a/src/libshared/switch-root.h b/src/libshared/switch-root.h
new file mode 100644
index 0000000000..a7a080b3e8
--- /dev/null
+++ b/src/libshared/switch-root.h
@@ -0,0 +1,23 @@
+#pragma once
+
+#include <stdbool.h>
+/***
+ This file is part of systemd.
+
+ Copyright 2012 Harald Hoyer, Lennart Poettering
+
+ systemd is free software; you can redistribute it and/or modify it
+ under the terms of the GNU Lesser General Public License as published by
+ the Free Software Foundation; either version 2.1 of the License, or
+ (at your option) any later version.
+
+ systemd is distributed in the hope that it will be useful, but
+ WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ Lesser General Public License for more details.
+
+ You should have received a copy of the GNU Lesser General Public License
+ along with systemd; If not, see <http://www.gnu.org/licenses/>.
+***/
+
+int switch_root(const char *new_root, const char *oldroot, bool detach_oldroot, unsigned long mountflags);
diff --git a/src/libshared/sysctl-util.c b/src/libshared/sysctl-util.c
new file mode 100644
index 0000000000..e1ccb3294c
--- /dev/null
+++ b/src/libshared/sysctl-util.c
@@ -0,0 +1,73 @@
+/***
+ This file is part of systemd.
+
+ Copyright 2010 Lennart Poettering
+
+ systemd is free software; you can redistribute it and/or modify it
+ under the terms of the GNU Lesser General Public License as published by
+ the Free Software Foundation; either version 2.1 of the License, or
+ (at your option) any later version.
+
+ systemd is distributed in the hope that it will be useful, but
+ WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ Lesser General Public License for more details.
+
+ You should have received a copy of the GNU Lesser General Public License
+ along with systemd; If not, see <http://www.gnu.org/licenses/>.
+***/
+
+#include <stdio.h>
+#include <string.h>
+
+#include "fileio.h"
+#include "log.h"
+#include "macro.h"
+#include "string-util.h"
+#include "sysctl-util.h"
+
+char *sysctl_normalize(char *s) {
+ char *n;
+
+ n = strpbrk(s, "/.");
+ /* If the first separator is a slash, the path is
+ * assumed to be normalized and slashes remain slashes
+ * and dots remains dots. */
+ if (!n || *n == '/')
+ return s;
+
+ /* Otherwise, dots become slashes and slashes become
+ * dots. Fun. */
+ while (n) {
+ if (*n == '.')
+ *n = '/';
+ else
+ *n = '.';
+
+ n = strpbrk(n + 1, "/.");
+ }
+
+ return s;
+}
+
+int sysctl_write(const char *property, const char *value) {
+ char *p;
+
+ assert(property);
+ assert(value);
+
+ log_debug("Setting '%s' to '%s'", property, value);
+
+ p = strjoina("/proc/sys/", property);
+ return write_string_file(p, value, 0);
+}
+
+int sysctl_read(const char *property, char **content) {
+ char *p;
+
+ assert(property);
+ assert(content);
+
+ p = strjoina("/proc/sys/", property);
+ return read_full_file(p, content, NULL);
+}
diff --git a/src/libshared/sysctl-util.h b/src/libshared/sysctl-util.h
new file mode 100644
index 0000000000..2decb39f58
--- /dev/null
+++ b/src/libshared/sysctl-util.h
@@ -0,0 +1,25 @@
+#pragma once
+
+/***
+ This file is part of systemd.
+
+ Copyright 2011 Lennart Poettering
+
+ systemd is free software; you can redistribute it and/or modify it
+ under the terms of the GNU Lesser General Public License as published by
+ the Free Software Foundation; either version 2.1 of the License, or
+ (at your option) any later version.
+
+ systemd is distributed in the hope that it will be useful, but
+ WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ Lesser General Public License for more details.
+
+ You should have received a copy of the GNU Lesser General Public License
+ along with systemd; If not, see <http://www.gnu.org/licenses/>.
+***/
+
+char *sysctl_normalize(char *s);
+int sysctl_read(const char *property, char **value);
+int sysctl_write(const char *property, const char *value);
+
diff --git a/src/libshared/test-tables.h b/src/libshared/test-tables.h
new file mode 100644
index 0000000000..228e510104
--- /dev/null
+++ b/src/libshared/test-tables.h
@@ -0,0 +1,60 @@
+/***
+ This file is part of systemd
+
+ Copyright 2013 Zbigniew Jędrzejewski-Szmek
+
+ systemd is free software; you can redistribute it and/or modify it
+ under the terms of the GNU Lesser General Public License as published by
+ the Free Software Foundation; either version 2.1 of the License, or
+ (at your option) any later version.
+
+ systemd is distributed in the hope that it will be useful, but
+ WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ Lesser General Public License for more details.
+
+ You should have received a copy of the GNU Lesser General Public License
+ along with systemd; If not, see <http://www.gnu.org/licenses/>.
+***/
+
+#include <stdio.h>
+#include <stdlib.h>
+
+typedef const char* (*lookup_t)(int);
+typedef int (*reverse_t)(const char*);
+
+static inline void _test_table(const char *name,
+ lookup_t lookup,
+ reverse_t reverse,
+ int size,
+ bool sparse) {
+ int i, boring = 0;
+
+ for (i = -1; i < size + 1; i++) {
+ const char* val = lookup(i);
+ int rev;
+
+ if (val) {
+ rev = reverse(val);
+ boring = 0;
+ } else {
+ rev = reverse("--no-such--value----");
+ boring += i >= 0;
+ }
+
+ if (boring < 1 || i == size)
+ printf("%s: %d → %s → %d\n", name, i, val, rev);
+ else if (boring == 1)
+ printf("%*s ...\n", (int) strlen(name), "");
+
+ assert_se(!(i >= 0 && i < size ?
+ sparse ? rev != i && rev != -1 : val == NULL || rev != i :
+ val != NULL || rev != -1));
+ }
+}
+
+#define test_table(lower, upper) \
+ _test_table(STRINGIFY(lower), lower##_to_string, lower##_from_string, _##upper##_MAX, false)
+
+#define test_table_sparse(lower, upper) \
+ _test_table(STRINGIFY(lower), lower##_to_string, lower##_from_string, _##upper##_MAX, true)
diff --git a/src/libshared/udev-util.h b/src/libshared/udev-util.h
new file mode 100644
index 0000000000..ca0889f8a6
--- /dev/null
+++ b/src/libshared/udev-util.h
@@ -0,0 +1,44 @@
+#pragma once
+
+/***
+ This file is part of systemd.
+
+ Copyright 2013 Zbigniew Jędrzejewski-Szmek
+
+ systemd is free software; you can redistribute it and/or modify it
+ under the terms of the GNU Lesser General Public License as published by
+ the Free Software Foundation; either version 2.1 of the License, or
+ (at your option) any later version.
+
+ systemd is distributed in the hope that it will be useful, but
+ WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ Lesser General Public License for more details.
+
+ You should have received a copy of the GNU Lesser General Public License
+ along with systemd; If not, see <http://www.gnu.org/licenses/>.
+***/
+
+#include "udev.h"
+#include "util.h"
+
+DEFINE_TRIVIAL_CLEANUP_FUNC(struct udev*, udev_unref);
+DEFINE_TRIVIAL_CLEANUP_FUNC(struct udev_device*, udev_device_unref);
+DEFINE_TRIVIAL_CLEANUP_FUNC(struct udev_enumerate*, udev_enumerate_unref);
+DEFINE_TRIVIAL_CLEANUP_FUNC(struct udev_event*, udev_event_unref);
+DEFINE_TRIVIAL_CLEANUP_FUNC(struct udev_rules*, udev_rules_unref);
+DEFINE_TRIVIAL_CLEANUP_FUNC(struct udev_ctrl*, udev_ctrl_unref);
+DEFINE_TRIVIAL_CLEANUP_FUNC(struct udev_ctrl_connection*, udev_ctrl_connection_unref);
+DEFINE_TRIVIAL_CLEANUP_FUNC(struct udev_ctrl_msg*, udev_ctrl_msg_unref);
+DEFINE_TRIVIAL_CLEANUP_FUNC(struct udev_monitor*, udev_monitor_unref);
+
+#define _cleanup_udev_unref_ _cleanup_(udev_unrefp)
+#define _cleanup_udev_device_unref_ _cleanup_(udev_device_unrefp)
+#define _cleanup_udev_enumerate_unref_ _cleanup_(udev_enumerate_unrefp)
+#define _cleanup_udev_event_unref_ _cleanup_(udev_event_unrefp)
+#define _cleanup_udev_rules_unref_ _cleanup_(udev_rules_unrefp)
+#define _cleanup_udev_ctrl_unref_ _cleanup_(udev_ctrl_unrefp)
+#define _cleanup_udev_ctrl_connection_unref_ _cleanup_(udev_ctrl_connection_unrefp)
+#define _cleanup_udev_ctrl_msg_unref_ _cleanup_(udev_ctrl_msg_unrefp)
+#define _cleanup_udev_monitor_unref_ _cleanup_(udev_monitor_unrefp)
+#define _cleanup_udev_list_cleanup_ _cleanup_(udev_list_cleanup)
diff --git a/src/libshared/uid-range.c b/src/libshared/uid-range.c
new file mode 100644
index 0000000000..eb251492c3
--- /dev/null
+++ b/src/libshared/uid-range.c
@@ -0,0 +1,208 @@
+/***
+ This file is part of systemd.
+
+ Copyright 2014 Lennart Poettering
+
+ systemd is free software; you can redistribute it and/or modify it
+ under the terms of the GNU Lesser General Public License as published by
+ the Free Software Foundation; either version 2.1 of the License, or
+ (at your option) any later version.
+
+ systemd is distributed in the hope that it will be useful, but
+ WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ Lesser General Public License for more details.
+
+ You should have received a copy of the GNU Lesser General Public License
+ along with systemd; If not, see <http://www.gnu.org/licenses/>.
+***/
+
+#include <errno.h>
+#include <stdlib.h>
+#include <string.h>
+
+#include "macro.h"
+#include "uid-range.h"
+#include "user-util.h"
+
+static bool uid_range_intersect(UidRange *range, uid_t start, uid_t nr) {
+ assert(range);
+
+ return range->start <= start + nr &&
+ range->start + range->nr >= start;
+}
+
+static void uid_range_coalesce(UidRange **p, unsigned *n) {
+ unsigned i, j;
+
+ assert(p);
+ assert(n);
+
+ for (i = 0; i < *n; i++) {
+ for (j = i + 1; j < *n; j++) {
+ UidRange *x = (*p)+i, *y = (*p)+j;
+
+ if (uid_range_intersect(x, y->start, y->nr)) {
+ uid_t begin, end;
+
+ begin = MIN(x->start, y->start);
+ end = MAX(x->start + x->nr, y->start + y->nr);
+
+ x->start = begin;
+ x->nr = end - begin;
+
+ if (*n > j+1)
+ memmove(y, y+1, sizeof(UidRange) * (*n - j -1));
+
+ (*n) --;
+ j--;
+ }
+ }
+ }
+
+}
+
+static int uid_range_compare(const void *a, const void *b) {
+ const UidRange *x = a, *y = b;
+
+ if (x->start < y->start)
+ return -1;
+ if (x->start > y->start)
+ return 1;
+
+ if (x->nr < y->nr)
+ return -1;
+ if (x->nr > y->nr)
+ return 1;
+
+ return 0;
+}
+
+int uid_range_add(UidRange **p, unsigned *n, uid_t start, uid_t nr) {
+ bool found = false;
+ UidRange *x;
+ unsigned i;
+
+ assert(p);
+ assert(n);
+
+ if (nr <= 0)
+ return 0;
+
+ for (i = 0; i < *n; i++) {
+ x = (*p) + i;
+ if (uid_range_intersect(x, start, nr)) {
+ found = true;
+ break;
+ }
+ }
+
+ if (found) {
+ uid_t begin, end;
+
+ begin = MIN(x->start, start);
+ end = MAX(x->start + x->nr, start + nr);
+
+ x->start = begin;
+ x->nr = end - begin;
+ } else {
+ UidRange *t;
+
+ t = realloc(*p, sizeof(UidRange) * (*n + 1));
+ if (!t)
+ return -ENOMEM;
+
+ *p = t;
+ x = t + ((*n) ++);
+
+ x->start = start;
+ x->nr = nr;
+ }
+
+ qsort(*p, *n, sizeof(UidRange), uid_range_compare);
+ uid_range_coalesce(p, n);
+
+ return *n;
+}
+
+int uid_range_add_str(UidRange **p, unsigned *n, const char *s) {
+ uid_t start, nr;
+ const char *t;
+ int r;
+
+ assert(p);
+ assert(n);
+ assert(s);
+
+ t = strchr(s, '-');
+ if (t) {
+ char *b;
+ uid_t end;
+
+ b = strndupa(s, t - s);
+ r = parse_uid(b, &start);
+ if (r < 0)
+ return r;
+
+ r = parse_uid(t+1, &end);
+ if (r < 0)
+ return r;
+
+ if (end < start)
+ return -EINVAL;
+
+ nr = end - start + 1;
+ } else {
+ r = parse_uid(s, &start);
+ if (r < 0)
+ return r;
+
+ nr = 1;
+ }
+
+ return uid_range_add(p, n, start, nr);
+}
+
+int uid_range_next_lower(const UidRange *p, unsigned n, uid_t *uid) {
+ uid_t closest = UID_INVALID, candidate;
+ unsigned i;
+
+ assert(p);
+ assert(uid);
+
+ candidate = *uid - 1;
+
+ for (i = 0; i < n; i++) {
+ uid_t begin, end;
+
+ begin = p[i].start;
+ end = p[i].start + p[i].nr - 1;
+
+ if (candidate >= begin && candidate <= end) {
+ *uid = candidate;
+ return 1;
+ }
+
+ if (end < candidate)
+ closest = end;
+ }
+
+ if (closest == UID_INVALID)
+ return -EBUSY;
+
+ *uid = closest;
+ return 1;
+}
+
+bool uid_range_contains(const UidRange *p, unsigned n, uid_t uid) {
+ unsigned i;
+
+ assert(p);
+ assert(uid);
+
+ for (i = 0; i < n; i++)
+ if (uid >= p[i].start && uid < p[i].start + p[i].nr)
+ return true;
+
+ return false;
+}
diff --git a/src/libshared/uid-range.h b/src/libshared/uid-range.h
new file mode 100644
index 0000000000..4044eb4c9c
--- /dev/null
+++ b/src/libshared/uid-range.h
@@ -0,0 +1,33 @@
+#pragma once
+
+/***
+ This file is part of systemd.
+
+ Copyright 2014 Lennart Poettering
+
+ systemd is free software; you can redistribute it and/or modify it
+ under the terms of the GNU Lesser General Public License as published by
+ the Free Software Foundation; either version 2.1 of the License, or
+ (at your option) any later version.
+
+ systemd is distributed in the hope that it will be useful, but
+ WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ Lesser General Public License for more details.
+
+ You should have received a copy of the GNU Lesser General Public License
+ along with systemd; If not, see <http://www.gnu.org/licenses/>.
+***/
+
+#include <stdbool.h>
+#include <sys/types.h>
+
+typedef struct UidRange {
+ uid_t start, nr;
+} UidRange;
+
+int uid_range_add(UidRange **p, unsigned *n, uid_t start, uid_t nr);
+int uid_range_add_str(UidRange **p, unsigned *n, const char *s);
+
+int uid_range_next_lower(const UidRange *p, unsigned n, uid_t *uid);
+bool uid_range_contains(const UidRange *p, unsigned n, uid_t uid);
diff --git a/src/libshared/utmp-wtmp.c b/src/libshared/utmp-wtmp.c
new file mode 100644
index 0000000000..9750dcd817
--- /dev/null
+++ b/src/libshared/utmp-wtmp.c
@@ -0,0 +1,445 @@
+/***
+ This file is part of systemd.
+
+ Copyright 2010 Lennart Poettering
+
+ systemd is free software; you can redistribute it and/or modify it
+ under the terms of the GNU Lesser General Public License as published by
+ the Free Software Foundation; either version 2.1 of the License, or
+ (at your option) any later version.
+
+ systemd is distributed in the hope that it will be useful, but
+ WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ Lesser General Public License for more details.
+
+ You should have received a copy of the GNU Lesser General Public License
+ along with systemd; If not, see <http://www.gnu.org/licenses/>.
+***/
+
+#include <errno.h>
+#include <fcntl.h>
+#include <poll.h>
+#include <stddef.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <sys/time.h>
+#include <sys/utsname.h>
+#include <unistd.h>
+#include <utmpx.h>
+
+#include "alloc-util.h"
+#include "fd-util.h"
+#include "hostname-util.h"
+#include "macro.h"
+#include "path-util.h"
+#include "string-util.h"
+#include "terminal-util.h"
+#include "time-util.h"
+#include "user-util.h"
+#include "util.h"
+#include "utmp-wtmp.h"
+
+int utmp_get_runlevel(int *runlevel, int *previous) {
+ struct utmpx *found, lookup = { .ut_type = RUN_LVL };
+ int r;
+ const char *e;
+
+ assert(runlevel);
+
+ /* If these values are set in the environment this takes
+ * precedence. Presumably, sysvinit does this to work around a
+ * race condition that would otherwise exist where we'd always
+ * go to disk and hence might read runlevel data that might be
+ * very new and does not apply to the current script being
+ * executed. */
+
+ e = getenv("RUNLEVEL");
+ if (e && e[0] > 0) {
+ *runlevel = e[0];
+
+ if (previous) {
+ /* $PREVLEVEL seems to be an Upstart thing */
+
+ e = getenv("PREVLEVEL");
+ if (e && e[0] > 0)
+ *previous = e[0];
+ else
+ *previous = 0;
+ }
+
+ return 0;
+ }
+
+ if (utmpxname(_PATH_UTMPX) < 0)
+ return -errno;
+
+ setutxent();
+
+ found = getutxid(&lookup);
+ if (!found)
+ r = -errno;
+ else {
+ int a, b;
+
+ a = found->ut_pid & 0xFF;
+ b = (found->ut_pid >> 8) & 0xFF;
+
+ *runlevel = a;
+ if (previous)
+ *previous = b;
+
+ r = 0;
+ }
+
+ endutxent();
+
+ return r;
+}
+
+static void init_timestamp(struct utmpx *store, usec_t t) {
+ assert(store);
+
+ if (t <= 0)
+ t = now(CLOCK_REALTIME);
+
+ store->ut_tv.tv_sec = t / USEC_PER_SEC;
+ store->ut_tv.tv_usec = t % USEC_PER_SEC;
+}
+
+static void init_entry(struct utmpx *store, usec_t t) {
+ struct utsname uts = {};
+
+ assert(store);
+
+ init_timestamp(store, t);
+
+ if (uname(&uts) >= 0)
+ strncpy(store->ut_host, uts.release, sizeof(store->ut_host));
+
+ strncpy(store->ut_line, "~", sizeof(store->ut_line)); /* or ~~ ? */
+ strncpy(store->ut_id, "~~", sizeof(store->ut_id));
+}
+
+static int write_entry_utmp(const struct utmpx *store) {
+ int r;
+
+ assert(store);
+
+ /* utmp is similar to wtmp, but there is only one entry for
+ * each entry type resp. user; i.e. basically a key/value
+ * table. */
+
+ if (utmpxname(_PATH_UTMPX) < 0)
+ return -errno;
+
+ setutxent();
+
+ if (!pututxline(store))
+ r = -errno;
+ else
+ r = 0;
+
+ endutxent();
+
+ return r;
+}
+
+static int write_entry_wtmp(const struct utmpx *store) {
+ assert(store);
+
+ /* wtmp is a simple append-only file where each entry is
+ simply appended to the end; i.e. basically a log. */
+
+ errno = 0;
+ updwtmpx(_PATH_WTMPX, store);
+ return -errno;
+}
+
+static int write_utmp_wtmp(const struct utmpx *store_utmp, const struct utmpx *store_wtmp) {
+ int r, s;
+
+ r = write_entry_utmp(store_utmp);
+ s = write_entry_wtmp(store_wtmp);
+
+ if (r >= 0)
+ r = s;
+
+ /* If utmp/wtmp have been disabled, that's a good thing, hence
+ * ignore the errors */
+ if (r == -ENOENT)
+ r = 0;
+
+ return r;
+}
+
+static int write_entry_both(const struct utmpx *store) {
+ return write_utmp_wtmp(store, store);
+}
+
+int utmp_put_shutdown(void) {
+ struct utmpx store = {};
+
+ init_entry(&store, 0);
+
+ store.ut_type = RUN_LVL;
+ strncpy(store.ut_user, "shutdown", sizeof(store.ut_user));
+
+ return write_entry_both(&store);
+}
+
+int utmp_put_reboot(usec_t t) {
+ struct utmpx store = {};
+
+ init_entry(&store, t);
+
+ store.ut_type = BOOT_TIME;
+ strncpy(store.ut_user, "reboot", sizeof(store.ut_user));
+
+ return write_entry_both(&store);
+}
+
+_pure_ static const char *sanitize_id(const char *id) {
+ size_t l;
+
+ assert(id);
+ l = strlen(id);
+
+ if (l <= sizeof(((struct utmpx*) NULL)->ut_id))
+ return id;
+
+ return id + l - sizeof(((struct utmpx*) NULL)->ut_id);
+}
+
+int utmp_put_init_process(const char *id, pid_t pid, pid_t sid, const char *line, int ut_type, const char *user) {
+ struct utmpx store = {
+ .ut_type = INIT_PROCESS,
+ .ut_pid = pid,
+ .ut_session = sid,
+ };
+ int r;
+
+ assert(id);
+
+ init_timestamp(&store, 0);
+
+ /* ut_id needs only be nul-terminated if it is shorter than sizeof(ut_id) */
+ strncpy(store.ut_id, sanitize_id(id), sizeof(store.ut_id));
+
+ if (line)
+ strncpy(store.ut_line, basename(line), sizeof(store.ut_line));
+
+ r = write_entry_both(&store);
+ if (r < 0)
+ return r;
+
+ if (ut_type == LOGIN_PROCESS || ut_type == USER_PROCESS) {
+ store.ut_type = LOGIN_PROCESS;
+ r = write_entry_both(&store);
+ if (r < 0)
+ return r;
+ }
+
+ if (ut_type == USER_PROCESS) {
+ store.ut_type = USER_PROCESS;
+ strncpy(store.ut_user, user, sizeof(store.ut_user)-1);
+ r = write_entry_both(&store);
+ if (r < 0)
+ return r;
+ }
+
+ return 0;
+}
+
+int utmp_put_dead_process(const char *id, pid_t pid, int code, int status) {
+ struct utmpx lookup = {
+ .ut_type = INIT_PROCESS /* looks for DEAD_PROCESS, LOGIN_PROCESS, USER_PROCESS, too */
+ }, store, store_wtmp, *found;
+
+ assert(id);
+
+ setutxent();
+
+ /* ut_id needs only be nul-terminated if it is shorter than sizeof(ut_id) */
+ strncpy(lookup.ut_id, sanitize_id(id), sizeof(lookup.ut_id));
+
+ found = getutxid(&lookup);
+ if (!found)
+ return 0;
+
+ if (found->ut_pid != pid)
+ return 0;
+
+ memcpy(&store, found, sizeof(store));
+ store.ut_type = DEAD_PROCESS;
+ store.ut_exit.e_termination = code;
+ store.ut_exit.e_exit = status;
+
+ zero(store.ut_user);
+ zero(store.ut_host);
+ zero(store.ut_tv);
+
+ memcpy(&store_wtmp, &store, sizeof(store_wtmp));
+ /* wtmp wants the current time */
+ init_timestamp(&store_wtmp, 0);
+
+ return write_utmp_wtmp(&store, &store_wtmp);
+}
+
+
+int utmp_put_runlevel(int runlevel, int previous) {
+ struct utmpx store = {};
+ int r;
+
+ assert(runlevel > 0);
+
+ if (previous <= 0) {
+ /* Find the old runlevel automatically */
+
+ r = utmp_get_runlevel(&previous, NULL);
+ if (r < 0) {
+ if (r != -ESRCH)
+ return r;
+
+ previous = 0;
+ }
+ }
+
+ if (previous == runlevel)
+ return 0;
+
+ init_entry(&store, 0);
+
+ store.ut_type = RUN_LVL;
+ store.ut_pid = (runlevel & 0xFF) | ((previous & 0xFF) << 8);
+ strncpy(store.ut_user, "runlevel", sizeof(store.ut_user));
+
+ return write_entry_both(&store);
+}
+
+#define TIMEOUT_MSEC 50
+
+static int write_to_terminal(const char *tty, const char *message) {
+ _cleanup_close_ int fd = -1;
+ const char *p;
+ size_t left;
+ usec_t end;
+
+ assert(tty);
+ assert(message);
+
+ fd = open(tty, O_WRONLY|O_NDELAY|O_NOCTTY|O_CLOEXEC);
+ if (fd < 0 || !isatty(fd))
+ return -errno;
+
+ p = message;
+ left = strlen(message);
+
+ end = now(CLOCK_MONOTONIC) + TIMEOUT_MSEC*USEC_PER_MSEC;
+
+ while (left > 0) {
+ ssize_t n;
+ struct pollfd pollfd = {
+ .fd = fd,
+ .events = POLLOUT,
+ };
+ usec_t t;
+ int k;
+
+ t = now(CLOCK_MONOTONIC);
+
+ if (t >= end)
+ return -ETIME;
+
+ k = poll(&pollfd, 1, (end - t) / USEC_PER_MSEC);
+ if (k < 0)
+ return -errno;
+
+ if (k == 0)
+ return -ETIME;
+
+ n = write(fd, p, left);
+ if (n < 0) {
+ if (errno == EAGAIN)
+ continue;
+
+ return -errno;
+ }
+
+ assert((size_t) n <= left);
+
+ p += n;
+ left -= n;
+ }
+
+ return 0;
+}
+
+int utmp_wall(
+ const char *message,
+ const char *username,
+ const char *origin_tty,
+ bool (*match_tty)(const char *tty, void *userdata),
+ void *userdata) {
+
+ _cleanup_free_ char *text = NULL, *hn = NULL, *un = NULL, *stdin_tty = NULL;
+ char date[FORMAT_TIMESTAMP_MAX];
+ struct utmpx *u;
+ int r;
+
+ hn = gethostname_malloc();
+ if (!hn)
+ return -ENOMEM;
+ if (!username) {
+ un = getlogname_malloc();
+ if (!un)
+ return -ENOMEM;
+ }
+
+ if (!origin_tty) {
+ getttyname_harder(STDIN_FILENO, &stdin_tty);
+ origin_tty = stdin_tty;
+ }
+
+ if (asprintf(&text,
+ "\a\r\n"
+ "Broadcast message from %s@%s%s%s (%s):\r\n\r\n"
+ "%s\r\n\r\n",
+ un ?: username, hn,
+ origin_tty ? " on " : "", strempty(origin_tty),
+ format_timestamp(date, sizeof(date), now(CLOCK_REALTIME)),
+ message) < 0)
+ return -ENOMEM;
+
+ setutxent();
+
+ r = 0;
+
+ while ((u = getutxent())) {
+ _cleanup_free_ char *buf = NULL;
+ const char *path;
+ int q;
+
+ if (u->ut_type != USER_PROCESS || u->ut_user[0] == 0)
+ continue;
+
+ /* this access is fine, because strlen("/dev/") << 32 (UT_LINESIZE) */
+ if (path_startswith(u->ut_line, "/dev/"))
+ path = u->ut_line;
+ else {
+ if (asprintf(&buf, "/dev/%.*s", (int) sizeof(u->ut_line), u->ut_line) < 0)
+ return -ENOMEM;
+
+ path = buf;
+ }
+
+ if (!match_tty || match_tty(path, userdata)) {
+ q = write_to_terminal(path, text);
+ if (q < 0)
+ r = q;
+ }
+ }
+
+ return r;
+}
diff --git a/src/libshared/utmp-wtmp.h b/src/libshared/utmp-wtmp.h
new file mode 100644
index 0000000000..438e270a26
--- /dev/null
+++ b/src/libshared/utmp-wtmp.h
@@ -0,0 +1,74 @@
+#pragma once
+
+/***
+ This file is part of systemd.
+
+ Copyright 2010 Lennart Poettering
+
+ systemd is free software; you can redistribute it and/or modify it
+ under the terms of the GNU Lesser General Public License as published by
+ the Free Software Foundation; either version 2.1 of the License, or
+ (at your option) any later version.
+
+ systemd is distributed in the hope that it will be useful, but
+ WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ Lesser General Public License for more details.
+
+ You should have received a copy of the GNU Lesser General Public License
+ along with systemd; If not, see <http://www.gnu.org/licenses/>.
+***/
+
+#include <stdbool.h>
+#include <sys/types.h>
+
+#include "time-util.h"
+#include "util.h"
+
+#ifdef HAVE_UTMP
+int utmp_get_runlevel(int *runlevel, int *previous);
+
+int utmp_put_shutdown(void);
+int utmp_put_reboot(usec_t timestamp);
+int utmp_put_runlevel(int runlevel, int previous);
+
+int utmp_put_dead_process(const char *id, pid_t pid, int code, int status);
+int utmp_put_init_process(const char *id, pid_t pid, pid_t sid, const char *line, int ut_type, const char *user);
+
+int utmp_wall(
+ const char *message,
+ const char *username,
+ const char *origin_tty,
+ bool (*match_tty)(const char *tty, void *userdata),
+ void *userdata);
+
+#else /* HAVE_UTMP */
+
+static inline int utmp_get_runlevel(int *runlevel, int *previous) {
+ return -ESRCH;
+}
+static inline int utmp_put_shutdown(void) {
+ return 0;
+}
+static inline int utmp_put_reboot(usec_t timestamp) {
+ return 0;
+}
+static inline int utmp_put_runlevel(int runlevel, int previous) {
+ return 0;
+}
+static inline int utmp_put_dead_process(const char *id, pid_t pid, int code, int status) {
+ return 0;
+}
+static inline int utmp_put_init_process(const char *id, pid_t pid, pid_t sid, const char *line, int ut_type, const char *user) {
+ return 0;
+}
+static inline int utmp_wall(
+ const char *message,
+ const char *username,
+ const char *origin_tty,
+ bool (*match_tty)(const char *tty, void *userdata),
+ void *userdata) {
+ return 0;
+}
+
+#endif /* HAVE_UTMP */
diff --git a/src/libshared/watchdog.c b/src/libshared/watchdog.c
new file mode 100644
index 0000000000..4f3e0125f3
--- /dev/null
+++ b/src/libshared/watchdog.c
@@ -0,0 +1,164 @@
+/***
+ This file is part of systemd.
+
+ Copyright 2012 Lennart Poettering
+
+ systemd is free software; you can redistribute it and/or modify it
+ under the terms of the GNU Lesser General Public License as published by
+ the Free Software Foundation; either version 2.1 of the License, or
+ (at your option) any later version.
+
+ systemd is distributed in the hope that it will be useful, but
+ WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ Lesser General Public License for more details.
+
+ You should have received a copy of the GNU Lesser General Public License
+ along with systemd; If not, see <http://www.gnu.org/licenses/>.
+***/
+
+#include <errno.h>
+#include <fcntl.h>
+#include <sys/ioctl.h>
+#include <syslog.h>
+#include <unistd.h>
+#include <linux/watchdog.h>
+
+#include "fd-util.h"
+#include "log.h"
+#include "time-util.h"
+#include "watchdog.h"
+
+static int watchdog_fd = -1;
+static usec_t watchdog_timeout = USEC_INFINITY;
+
+static int update_timeout(void) {
+ int r;
+
+ if (watchdog_fd < 0)
+ return 0;
+
+ if (watchdog_timeout == USEC_INFINITY)
+ return 0;
+ else if (watchdog_timeout == 0) {
+ int flags;
+
+ flags = WDIOS_DISABLECARD;
+ r = ioctl(watchdog_fd, WDIOC_SETOPTIONS, &flags);
+ if (r < 0)
+ return log_warning_errno(errno, "Failed to disable hardware watchdog: %m");
+ } else {
+ int sec, flags;
+ char buf[FORMAT_TIMESPAN_MAX];
+
+ sec = (int) ((watchdog_timeout + USEC_PER_SEC - 1) / USEC_PER_SEC);
+ r = ioctl(watchdog_fd, WDIOC_SETTIMEOUT, &sec);
+ if (r < 0)
+ return log_warning_errno(errno, "Failed to set timeout to %is: %m", sec);
+
+ watchdog_timeout = (usec_t) sec * USEC_PER_SEC;
+ log_info("Set hardware watchdog to %s.", format_timespan(buf, sizeof(buf), watchdog_timeout, 0));
+
+ flags = WDIOS_ENABLECARD;
+ r = ioctl(watchdog_fd, WDIOC_SETOPTIONS, &flags);
+ if (r < 0) {
+ /* ENOTTY means the watchdog is always enabled so we're fine */
+ log_full(errno == ENOTTY ? LOG_DEBUG : LOG_WARNING,
+ "Failed to enable hardware watchdog: %m");
+ if (errno != ENOTTY)
+ return -errno;
+ }
+
+ r = ioctl(watchdog_fd, WDIOC_KEEPALIVE, 0);
+ if (r < 0)
+ return log_warning_errno(errno, "Failed to ping hardware watchdog: %m");
+ }
+
+ return 0;
+}
+
+static int open_watchdog(void) {
+ struct watchdog_info ident;
+
+ if (watchdog_fd >= 0)
+ return 0;
+
+ watchdog_fd = open("/dev/watchdog", O_WRONLY|O_CLOEXEC);
+ if (watchdog_fd < 0)
+ return -errno;
+
+ if (ioctl(watchdog_fd, WDIOC_GETSUPPORT, &ident) >= 0)
+ log_info("Hardware watchdog '%s', version %x",
+ ident.identity,
+ ident.firmware_version);
+
+ return update_timeout();
+}
+
+int watchdog_set_timeout(usec_t *usec) {
+ int r;
+
+ watchdog_timeout = *usec;
+
+ /* If we didn't open the watchdog yet and didn't get any
+ * explicit timeout value set, don't do anything */
+ if (watchdog_fd < 0 && watchdog_timeout == USEC_INFINITY)
+ return 0;
+
+ if (watchdog_fd < 0)
+ r = open_watchdog();
+ else
+ r = update_timeout();
+
+ *usec = watchdog_timeout;
+
+ return r;
+}
+
+int watchdog_ping(void) {
+ int r;
+
+ if (watchdog_fd < 0) {
+ r = open_watchdog();
+ if (r < 0)
+ return r;
+ }
+
+ r = ioctl(watchdog_fd, WDIOC_KEEPALIVE, 0);
+ if (r < 0)
+ return log_warning_errno(errno, "Failed to ping hardware watchdog: %m");
+
+ return 0;
+}
+
+void watchdog_close(bool disarm) {
+ int r;
+
+ if (watchdog_fd < 0)
+ return;
+
+ if (disarm) {
+ int flags;
+
+ /* Explicitly disarm it */
+ flags = WDIOS_DISABLECARD;
+ r = ioctl(watchdog_fd, WDIOC_SETOPTIONS, &flags);
+ if (r < 0)
+ log_warning_errno(errno, "Failed to disable hardware watchdog: %m");
+
+ /* To be sure, use magic close logic, too */
+ for (;;) {
+ static const char v = 'V';
+
+ if (write(watchdog_fd, &v, 1) > 0)
+ break;
+
+ if (errno != EINTR) {
+ log_error_errno(errno, "Failed to disarm watchdog timer: %m");
+ break;
+ }
+ }
+ }
+
+ watchdog_fd = safe_close(watchdog_fd);
+}
diff --git a/src/libshared/watchdog.h b/src/libshared/watchdog.h
new file mode 100644
index 0000000000..f6ec178ea1
--- /dev/null
+++ b/src/libshared/watchdog.h
@@ -0,0 +1,29 @@
+#pragma once
+
+/***
+ This file is part of systemd.
+
+ Copyright 2012 Lennart Poettering
+
+ systemd is free software; you can redistribute it and/or modify it
+ under the terms of the GNU Lesser General Public License as published by
+ the Free Software Foundation; either version 2.1 of the License, or
+ (at your option) any later version.
+
+ systemd is distributed in the hope that it will be useful, but
+ WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ Lesser General Public License for more details.
+
+ You should have received a copy of the GNU Lesser General Public License
+ along with systemd; If not, see <http://www.gnu.org/licenses/>.
+***/
+
+#include <stdbool.h>
+
+#include "time-util.h"
+#include "util.h"
+
+int watchdog_set_timeout(usec_t *usec);
+int watchdog_ping(void);
+void watchdog_close(bool disarm);