summaryrefslogtreecommitdiff
path: root/src/shared
diff options
context:
space:
mode:
Diffstat (limited to 'src/shared')
-rw-r--r--src/shared/MurmurHash3.c349
-rw-r--r--src/shared/MurmurHash3.h38
-rw-r--r--src/shared/acl-util.c126
-rw-r--r--src/shared/acl-util.h27
-rw-r--r--src/shared/ask-password-api.c575
-rw-r--r--src/shared/ask-password-api.h30
-rw-r--r--src/shared/audit.c128
-rw-r--r--src/shared/audit.h32
-rw-r--r--src/shared/calendarspec.c927
-rw-r--r--src/shared/calendarspec.h57
-rw-r--r--src/shared/capability.c225
-rw-r--r--src/shared/capability.h33
-rw-r--r--src/shared/cgroup-label.c77
-rw-r--r--src/shared/cgroup-show.c300
-rw-r--r--src/shared/cgroup-show.h36
-rw-r--r--src/shared/cgroup-util.c1603
-rw-r--r--src/shared/cgroup-util.h115
-rw-r--r--src/shared/conf-files.c178
-rw-r--r--src/shared/conf-files.h32
-rw-r--r--src/shared/conf-parser.c920
-rw-r--r--src/shared/conf-parser.h151
-rw-r--r--src/shared/dbus-common.c1428
-rw-r--r--src/shared/dbus-common.h246
-rw-r--r--src/shared/dbus-loop.c249
-rw-r--r--src/shared/dbus-loop.h27
-rw-r--r--src/shared/def.h35
-rw-r--r--src/shared/dev-setup.c78
-rw-r--r--src/shared/dev-setup.h24
-rw-r--r--src/shared/efivars.c503
-rw-r--r--src/shared/efivars.h47
-rw-r--r--src/shared/env-util.c415
-rw-r--r--src/shared/env-util.h44
-rw-r--r--src/shared/exit-status.c192
-rw-r--r--src/shared/exit-status.h88
-rw-r--r--src/shared/fdset.c236
-rw-r--r--src/shared/fdset.h49
-rw-r--r--src/shared/fileio-label.c55
-rw-r--r--src/shared/fileio-label.h29
-rw-r--r--src/shared/fileio.c596
-rw-r--r--src/shared/fileio.h37
-rw-r--r--src/shared/hashmap.c862
-rw-r--r--src/shared/hashmap.h105
-rw-r--r--src/shared/hwclock.c160
-rw-r--r--src/shared/hwclock.h31
-rw-r--r--src/shared/install-printf.c132
-rw-r--r--src/shared/install-printf.h25
-rw-r--r--src/shared/install.c1945
-rw-r--r--src/shared/install.h97
-rw-r--r--src/shared/ioprio.h57
-rw-r--r--src/shared/label.c386
-rw-r--r--src/shared/label.h51
-rw-r--r--src/shared/linux/auto_dev-ioctl.h229
-rw-r--r--src/shared/linux/fanotify.h98
-rw-r--r--src/shared/linux/seccomp-bpf.h76
-rw-r--r--src/shared/linux/seccomp.h47
-rw-r--r--src/shared/list.h125
-rw-r--r--src/shared/log.c927
-rw-r--r--src/shared/log.h159
-rw-r--r--src/shared/logs-show.c985
-rw-r--r--src/shared/logs-show.h67
-rw-r--r--src/shared/macro.h285
-rw-r--r--src/shared/missing.h267
-rw-r--r--src/shared/mkdir.c147
-rw-r--r--src/shared/mkdir.h32
-rw-r--r--src/shared/output-mode.h44
-rw-r--r--src/shared/pager.c146
-rw-r--r--src/shared/pager.h30
-rw-r--r--src/shared/path-lookup.c426
-rw-r--r--src/shared/path-lookup.h45
-rw-r--r--src/shared/path-util.c428
-rw-r--r--src/shared/path-util.h45
-rw-r--r--src/shared/polkit.c174
-rw-r--r--src/shared/polkit.h33
-rw-r--r--src/shared/prioq.c305
-rw-r--r--src/shared/prioq.h40
-rw-r--r--src/shared/ratelimit.c57
-rw-r--r--src/shared/ratelimit.h57
-rw-r--r--src/shared/replace-var.c110
-rw-r--r--src/shared/replace-var.h24
-rw-r--r--src/shared/selinux-util.c42
-rw-r--r--src/shared/selinux-util.h27
-rw-r--r--src/shared/set.c137
-rw-r--r--src/shared/set.h83
-rw-r--r--src/shared/sleep-config.c179
-rw-r--r--src/shared/sleep-config.h26
-rw-r--r--src/shared/socket-label.c143
-rw-r--r--src/shared/socket-util.c635
-rw-r--r--src/shared/socket-util.h103
-rw-r--r--src/shared/sparse-endian.h87
-rw-r--r--src/shared/spawn-ask-password-agent.c67
-rw-r--r--src/shared/spawn-ask-password-agent.h25
-rw-r--r--src/shared/spawn-polkit-agent.c98
-rw-r--r--src/shared/spawn-polkit-agent.h28
-rw-r--r--src/shared/specifier.c147
-rw-r--r--src/shared/specifier.h38
-rw-r--r--src/shared/strbuf.c201
-rw-r--r--src/shared/strbuf.h56
-rw-r--r--src/shared/strv.c587
-rw-r--r--src/shared/strv.h87
-rw-r--r--src/shared/strxcpyx.c104
-rw-r--r--src/shared/strxcpyx.h31
-rw-r--r--src/shared/time-dst.c332
-rw-r--r--src/shared/time-dst.h26
-rw-r--r--src/shared/time-util.c772
-rw-r--r--src/shared/time-util.h85
-rw-r--r--src/shared/unit-name.c549
-rw-r--r--src/shared/unit-name.h97
-rw-r--r--src/shared/utf8.c322
-rw-r--r--src/shared/utf8.h34
-rw-r--r--src/shared/util.c5849
-rw-r--r--src/shared/util.h734
-rw-r--r--src/shared/utmp-wtmp.c410
-rw-r--r--src/shared/utmp-wtmp.h35
-rw-r--r--src/shared/virt.c272
-rw-r--r--src/shared/virt.h35
-rw-r--r--src/shared/watchdog.c169
-rw-r--r--src/shared/watchdog.h28
117 files changed, 32276 insertions, 0 deletions
diff --git a/src/shared/MurmurHash3.c b/src/shared/MurmurHash3.c
new file mode 100644
index 0000000000..aa1387373f
--- /dev/null
+++ b/src/shared/MurmurHash3.c
@@ -0,0 +1,349 @@
+//-----------------------------------------------------------------------------
+// MurmurHash3 was written by Austin Appleby, and is placed in the public
+// domain. The author hereby disclaims copyright to this source code.
+
+// Note - The x86 and x64 versions do _not_ produce the same results, as the
+// algorithms are optimized for their respective platforms. You can still
+// compile and run any of them on any platform, but your performance with the
+// non-native version will be less than optimal.
+
+#include "MurmurHash3.h"
+
+//-----------------------------------------------------------------------------
+// Platform-specific functions and macros
+
+// Microsoft Visual Studio
+
+#if defined(_MSC_VER)
+
+#define FORCE_INLINE __forceinline
+
+#include <stdlib.h>
+
+#define ROTL32(x,y) _rotl(x,y)
+#define ROTL64(x,y) _rotl64(x,y)
+
+#define BIG_CONSTANT(x) (x)
+
+// Other compilers
+
+#else // defined(_MSC_VER)
+
+#define FORCE_INLINE inline __attribute__((always_inline))
+
+static inline uint32_t rotl32 ( uint32_t x, int8_t r )
+{
+ return (x << r) | (x >> (32 - r));
+}
+
+static inline uint64_t rotl64 ( uint64_t x, int8_t r )
+{
+ return (x << r) | (x >> (64 - r));
+}
+
+#define ROTL32(x,y) rotl32(x,y)
+#define ROTL64(x,y) rotl64(x,y)
+
+#define BIG_CONSTANT(x) (x##LLU)
+
+#endif // !defined(_MSC_VER)
+
+//-----------------------------------------------------------------------------
+// Block read - if your platform needs to do endian-swapping or can only
+// handle aligned reads, do the conversion here
+
+static FORCE_INLINE uint32_t getblock32 ( const uint32_t * p, int i )
+{
+ return p[i];
+}
+
+static FORCE_INLINE uint64_t getblock64 ( const uint64_t * p, int i )
+{
+ return p[i];
+}
+
+//-----------------------------------------------------------------------------
+// Finalization mix - force all bits of a hash block to avalanche
+
+static FORCE_INLINE uint32_t fmix32 ( uint32_t h )
+{
+ h ^= h >> 16;
+ h *= 0x85ebca6b;
+ h ^= h >> 13;
+ h *= 0xc2b2ae35;
+ h ^= h >> 16;
+
+ return h;
+}
+
+//----------
+
+static FORCE_INLINE uint64_t fmix64 ( uint64_t k )
+{
+ k ^= k >> 33;
+ k *= BIG_CONSTANT(0xff51afd7ed558ccd);
+ k ^= k >> 33;
+ k *= BIG_CONSTANT(0xc4ceb9fe1a85ec53);
+ k ^= k >> 33;
+
+ return k;
+}
+
+//-----------------------------------------------------------------------------
+
+void MurmurHash3_x86_32 ( const void * key, size_t len,
+ uint32_t seed, void * out )
+{
+ const uint8_t * data = (const uint8_t*)key;
+ const int nblocks = len / 4;
+
+ uint32_t h1 = seed;
+
+ const uint32_t c1 = 0xcc9e2d51;
+ const uint32_t c2 = 0x1b873593;
+
+ const uint8_t * tail;
+ uint32_t k1;
+
+ //----------
+ // body
+
+ const uint32_t * blocks = (const uint32_t *)(data + nblocks*4);
+
+ for(int i = -nblocks; i; i++)
+ {
+ k1 = getblock32(blocks,i);
+
+ k1 *= c1;
+ k1 = ROTL32(k1,15);
+ k1 *= c2;
+
+ h1 ^= k1;
+ h1 = ROTL32(h1,13);
+ h1 = h1*5+0xe6546b64;
+ }
+
+ //----------
+ // tail
+
+ tail = (const uint8_t*)(data + nblocks*4);
+
+ k1 = 0;
+
+ switch(len & 3)
+ {
+ case 3: k1 ^= tail[2] << 16;
+ case 2: k1 ^= tail[1] << 8;
+ case 1: k1 ^= tail[0];
+ k1 *= c1; k1 = ROTL32(k1,15); k1 *= c2; h1 ^= k1;
+ };
+
+ //----------
+ // finalization
+
+ h1 ^= len;
+
+ h1 = fmix32(h1);
+
+ *(uint32_t*)out = h1;
+}
+
+//-----------------------------------------------------------------------------
+
+void MurmurHash3_x86_128 ( const void * key, const size_t len,
+ uint32_t seed, void * out )
+{
+ const uint8_t * data = (const uint8_t*)key;
+ const int nblocks = len / 16;
+
+ uint32_t h1 = seed;
+ uint32_t h2 = seed;
+ uint32_t h3 = seed;
+ uint32_t h4 = seed;
+
+ const uint32_t c1 = 0x239b961b;
+ const uint32_t c2 = 0xab0e9789;
+ const uint32_t c3 = 0x38b34ae5;
+ const uint32_t c4 = 0xa1e38b93;
+
+ const uint8_t * tail;
+
+ uint32_t k1;
+ uint32_t k2;
+ uint32_t k3;
+ uint32_t k4;
+
+ //----------
+ // body
+
+ const uint32_t * blocks = (const uint32_t *)(data + nblocks*16);
+
+ for(int i = -nblocks; i; i++)
+ {
+ k1 = getblock32(blocks,i*4+0);
+ k2 = getblock32(blocks,i*4+1);
+ k3 = getblock32(blocks,i*4+2);
+ k4 = getblock32(blocks,i*4+3);
+
+ k1 *= c1; k1 = ROTL32(k1,15); k1 *= c2; h1 ^= k1;
+
+ h1 = ROTL32(h1,19); h1 += h2; h1 = h1*5+0x561ccd1b;
+
+ k2 *= c2; k2 = ROTL32(k2,16); k2 *= c3; h2 ^= k2;
+
+ h2 = ROTL32(h2,17); h2 += h3; h2 = h2*5+0x0bcaa747;
+
+ k3 *= c3; k3 = ROTL32(k3,17); k3 *= c4; h3 ^= k3;
+
+ h3 = ROTL32(h3,15); h3 += h4; h3 = h3*5+0x96cd1c35;
+
+ k4 *= c4; k4 = ROTL32(k4,18); k4 *= c1; h4 ^= k4;
+
+ h4 = ROTL32(h4,13); h4 += h1; h4 = h4*5+0x32ac3b17;
+ }
+
+ //----------
+ // tail
+
+ tail = (const uint8_t*)(data + nblocks*16);
+
+ k1 = 0;
+ k2 = 0;
+ k3 = 0;
+ k4 = 0;
+
+ switch(len & 15)
+ {
+ case 15: k4 ^= tail[14] << 16;
+ case 14: k4 ^= tail[13] << 8;
+ case 13: k4 ^= tail[12] << 0;
+ k4 *= c4; k4 = ROTL32(k4,18); k4 *= c1; h4 ^= k4;
+
+ case 12: k3 ^= tail[11] << 24;
+ case 11: k3 ^= tail[10] << 16;
+ case 10: k3 ^= tail[ 9] << 8;
+ case 9: k3 ^= tail[ 8] << 0;
+ k3 *= c3; k3 = ROTL32(k3,17); k3 *= c4; h3 ^= k3;
+
+ case 8: k2 ^= tail[ 7] << 24;
+ case 7: k2 ^= tail[ 6] << 16;
+ case 6: k2 ^= tail[ 5] << 8;
+ case 5: k2 ^= tail[ 4] << 0;
+ k2 *= c2; k2 = ROTL32(k2,16); k2 *= c3; h2 ^= k2;
+
+ case 4: k1 ^= tail[ 3] << 24;
+ case 3: k1 ^= tail[ 2] << 16;
+ case 2: k1 ^= tail[ 1] << 8;
+ case 1: k1 ^= tail[ 0] << 0;
+ k1 *= c1; k1 = ROTL32(k1,15); k1 *= c2; h1 ^= k1;
+ };
+
+ //----------
+ // finalization
+
+ h1 ^= len; h2 ^= len; h3 ^= len; h4 ^= len;
+
+ h1 += h2; h1 += h3; h1 += h4;
+ h2 += h1; h3 += h1; h4 += h1;
+
+ h1 = fmix32(h1);
+ h2 = fmix32(h2);
+ h3 = fmix32(h3);
+ h4 = fmix32(h4);
+
+ h1 += h2; h1 += h3; h1 += h4;
+ h2 += h1; h3 += h1; h4 += h1;
+
+ ((uint32_t*)out)[0] = h1;
+ ((uint32_t*)out)[1] = h2;
+ ((uint32_t*)out)[2] = h3;
+ ((uint32_t*)out)[3] = h4;
+}
+
+//-----------------------------------------------------------------------------
+
+void MurmurHash3_x64_128 ( const void * key, const size_t len,
+ const uint32_t seed, void * out )
+{
+ const uint8_t * data = (const uint8_t*)key;
+ const int nblocks = len / 16;
+
+ uint64_t h1 = seed;
+ uint64_t h2 = seed;
+
+ const uint64_t c1 = BIG_CONSTANT(0x87c37b91114253d5);
+ const uint64_t c2 = BIG_CONSTANT(0x4cf5ad432745937f);
+
+ const uint8_t *tail;
+
+ uint64_t k1;
+ uint64_t k2;
+
+ //----------
+ // body
+
+ const uint64_t * blocks = (const uint64_t *)(data);
+
+ for(int i = 0; i < nblocks; i++)
+ {
+ k1 = getblock64(blocks,i*2+0);
+ k2 = getblock64(blocks,i*2+1);
+
+ k1 *= c1; k1 = ROTL64(k1,31); k1 *= c2; h1 ^= k1;
+
+ h1 = ROTL64(h1,27); h1 += h2; h1 = h1*5+0x52dce729;
+
+ k2 *= c2; k2 = ROTL64(k2,33); k2 *= c1; h2 ^= k2;
+
+ h2 = ROTL64(h2,31); h2 += h1; h2 = h2*5+0x38495ab5;
+ }
+
+ //----------
+ // tail
+
+ tail = (const uint8_t*)(data + nblocks*16);
+
+ k1 = 0;
+ k2 = 0;
+
+ switch(len & 15)
+ {
+ case 15: k2 ^= (uint64_t)(tail[14]) << 48;
+ case 14: k2 ^= (uint64_t)(tail[13]) << 40;
+ case 13: k2 ^= (uint64_t)(tail[12]) << 32;
+ case 12: k2 ^= (uint64_t)(tail[11]) << 24;
+ case 11: k2 ^= (uint64_t)(tail[10]) << 16;
+ case 10: k2 ^= (uint64_t)(tail[ 9]) << 8;
+ case 9: k2 ^= (uint64_t)(tail[ 8]) << 0;
+ k2 *= c2; k2 = ROTL64(k2,33); k2 *= c1; h2 ^= k2;
+
+ case 8: k1 ^= (uint64_t)(tail[ 7]) << 56;
+ case 7: k1 ^= (uint64_t)(tail[ 6]) << 48;
+ case 6: k1 ^= (uint64_t)(tail[ 5]) << 40;
+ case 5: k1 ^= (uint64_t)(tail[ 4]) << 32;
+ case 4: k1 ^= (uint64_t)(tail[ 3]) << 24;
+ case 3: k1 ^= (uint64_t)(tail[ 2]) << 16;
+ case 2: k1 ^= (uint64_t)(tail[ 1]) << 8;
+ case 1: k1 ^= (uint64_t)(tail[ 0]) << 0;
+ k1 *= c1; k1 = ROTL64(k1,31); k1 *= c2; h1 ^= k1;
+ };
+
+ //----------
+ // finalization
+
+ h1 ^= len; h2 ^= len;
+
+ h1 += h2;
+ h2 += h1;
+
+ h1 = fmix64(h1);
+ h2 = fmix64(h2);
+
+ h1 += h2;
+ h2 += h1;
+
+ ((uint64_t*)out)[0] = h1;
+ ((uint64_t*)out)[1] = h2;
+}
+
+//-----------------------------------------------------------------------------
diff --git a/src/shared/MurmurHash3.h b/src/shared/MurmurHash3.h
new file mode 100644
index 0000000000..07c474eba4
--- /dev/null
+++ b/src/shared/MurmurHash3.h
@@ -0,0 +1,38 @@
+//-----------------------------------------------------------------------------
+// MurmurHash3 was written by Austin Appleby, and is placed in the public
+// domain. The author hereby disclaims copyright to this source code.
+
+#ifndef _MURMURHASH3_H_
+#define _MURMURHASH3_H_
+
+//-----------------------------------------------------------------------------
+// Platform-specific functions and macros
+
+// Microsoft Visual Studio
+
+#if defined(_MSC_VER)
+
+typedef unsigned char uint8_t;
+typedef unsigned long uint32_t;
+typedef unsigned __int64 uint64_t;
+
+// Other compilers
+
+#else // defined(_MSC_VER)
+
+#include <stdint.h>
+#include <sys/types.h>
+
+#endif // !defined(_MSC_VER)
+
+//-----------------------------------------------------------------------------
+
+void MurmurHash3_x86_32 ( const void * key, size_t len, uint32_t seed, void * out );
+
+void MurmurHash3_x86_128 ( const void * key, size_t len, uint32_t seed, void * out );
+
+void MurmurHash3_x64_128 ( const void * key, size_t len, uint32_t seed, void * out );
+
+//-----------------------------------------------------------------------------
+
+#endif // _MURMURHASH3_H_
diff --git a/src/shared/acl-util.c b/src/shared/acl-util.c
new file mode 100644
index 0000000000..48bb12f46b
--- /dev/null
+++ b/src/shared/acl-util.c
@@ -0,0 +1,126 @@
+/*-*- Mode: C; c-basic-offset: 8; indent-tabs-mode: nil -*-*/
+
+/***
+ 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 <assert.h>
+#include <sys/acl.h>
+#include <acl/libacl.h>
+#include <errno.h>
+#include <stdbool.h>
+
+#include "acl-util.h"
+#include "util.h"
+#include "strv.h"
+
+int acl_find_uid(acl_t acl, uid_t uid, acl_entry_t *entry) {
+ acl_entry_t i;
+ int found;
+
+ assert(acl);
+ assert(entry);
+
+ for (found = acl_get_entry(acl, ACL_FIRST_ENTRY, &i);
+ found > 0;
+ found = acl_get_entry(acl, ACL_NEXT_ENTRY, &i)) {
+
+ acl_tag_t tag;
+ uid_t *u;
+ bool b;
+
+ if (acl_get_tag_type(i, &tag) < 0)
+ return -errno;
+
+ if (tag != ACL_USER)
+ continue;
+
+ u = acl_get_qualifier(i);
+ if (!u)
+ return -errno;
+
+ b = *u == uid;
+ acl_free(u);
+
+ if (b) {
+ *entry = i;
+ return 1;
+ }
+ }
+
+ if (found < 0)
+ return -errno;
+
+ return 0;
+}
+
+int search_acl_groups(char*** dst, const char* path, bool* belong) {
+ acl_t acl;
+
+ assert(path);
+ assert(belong);
+
+ acl = acl_get_file(path, ACL_TYPE_DEFAULT);
+ if (acl) {
+ acl_entry_t entry;
+ int r;
+
+ r = acl_get_entry(acl, ACL_FIRST_ENTRY, &entry);
+ while (r > 0) {
+ acl_tag_t tag;
+ gid_t *gid;
+ char *name;
+
+ r = acl_get_tag_type(entry, &tag);
+ if (r < 0)
+ break;
+
+ if (tag != ACL_GROUP)
+ goto next;
+
+ gid = acl_get_qualifier(entry);
+ if (!gid)
+ break;
+
+ if (in_gid(*gid) > 0) {
+ *belong = true;
+ break;
+ }
+
+ name = gid_to_name(*gid);
+ if (!name) {
+ acl_free(acl);
+ return log_oom();
+ }
+
+ r = strv_push(dst, name);
+ if (r < 0) {
+ free(name);
+ acl_free(acl);
+ return log_oom();
+ }
+
+ next:
+ r = acl_get_entry(acl, ACL_NEXT_ENTRY, &entry);
+ }
+
+ acl_free(acl);
+ }
+
+ return 0;
+}
diff --git a/src/shared/acl-util.h b/src/shared/acl-util.h
new file mode 100644
index 0000000000..23090d9984
--- /dev/null
+++ b/src/shared/acl-util.h
@@ -0,0 +1,27 @@
+/*-*- Mode: C; c-basic-offset: 8; indent-tabs-mode: nil -*-*/
+
+#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/>.
+***/
+
+#include <stdbool.h>
+
+int acl_find_uid(acl_t acl, uid_t uid, acl_entry_t *entry);
+int search_acl_groups(char*** dst, const char* path, bool* belong);
diff --git a/src/shared/ask-password-api.c b/src/shared/ask-password-api.c
new file mode 100644
index 0000000000..4557155d45
--- /dev/null
+++ b/src/shared/ask-password-api.c
@@ -0,0 +1,575 @@
+/*-*- Mode: C; c-basic-offset: 8; indent-tabs-mode: nil -*-*/
+
+/***
+ This file is part 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 <termios.h>
+#include <unistd.h>
+#include <sys/poll.h>
+#include <sys/inotify.h>
+#include <errno.h>
+#include <fcntl.h>
+#include <sys/socket.h>
+#include <string.h>
+#include <sys/un.h>
+#include <stddef.h>
+#include <sys/signalfd.h>
+
+#include "util.h"
+#include "mkdir.h"
+#include "strv.h"
+
+#include "ask-password-api.h"
+
+static void backspace_chars(int ttyfd, size_t p) {
+
+ if (ttyfd < 0)
+ return;
+
+ while (p > 0) {
+ p--;
+
+ loop_write(ttyfd, "\b \b", 3, false);
+ }
+}
+
+int ask_password_tty(
+ const char *message,
+ usec_t until,
+ const char *flag_file,
+ char **_passphrase) {
+
+ struct termios old_termios, new_termios;
+ char passphrase[LINE_MAX];
+ size_t p = 0;
+ int r, ttyfd = -1, notify = -1;
+ struct pollfd pollfd[2];
+ bool reset_tty = false;
+ bool silent_mode = false;
+ bool dirty = false;
+ enum {
+ POLL_TTY,
+ POLL_INOTIFY
+ };
+
+ assert(message);
+ assert(_passphrase);
+
+ if (flag_file) {
+ if ((notify = inotify_init1(IN_CLOEXEC|IN_NONBLOCK)) < 0) {
+ r = -errno;
+ goto finish;
+ }
+
+ if (inotify_add_watch(notify, flag_file, IN_ATTRIB /* for the link count */) < 0) {
+ r = -errno;
+ goto finish;
+ }
+ }
+
+ if ((ttyfd = open("/dev/tty", O_RDWR|O_NOCTTY|O_CLOEXEC)) >= 0) {
+
+ if (tcgetattr(ttyfd, &old_termios) < 0) {
+ r = -errno;
+ goto finish;
+ }
+
+ loop_write(ttyfd, ANSI_HIGHLIGHT_ON, sizeof(ANSI_HIGHLIGHT_ON)-1, false);
+ loop_write(ttyfd, message, strlen(message), false);
+ loop_write(ttyfd, " ", 1, false);
+ loop_write(ttyfd, ANSI_HIGHLIGHT_OFF, sizeof(ANSI_HIGHLIGHT_OFF)-1, false);
+
+ new_termios = old_termios;
+ new_termios.c_lflag &= ~(ICANON|ECHO);
+ new_termios.c_cc[VMIN] = 1;
+ new_termios.c_cc[VTIME] = 0;
+
+ if (tcsetattr(ttyfd, TCSADRAIN, &new_termios) < 0) {
+ r = -errno;
+ goto finish;
+ }
+
+ reset_tty = true;
+ }
+
+ zero(pollfd);
+ pollfd[POLL_TTY].fd = ttyfd >= 0 ? ttyfd : STDIN_FILENO;
+ pollfd[POLL_TTY].events = POLLIN;
+ pollfd[POLL_INOTIFY].fd = notify;
+ pollfd[POLL_INOTIFY].events = POLLIN;
+
+ for (;;) {
+ char c;
+ int sleep_for = -1, k;
+ ssize_t n;
+
+ if (until > 0) {
+ usec_t y;
+
+ y = now(CLOCK_MONOTONIC);
+
+ if (y > until) {
+ r = -ETIME;
+ goto finish;
+ }
+
+ sleep_for = (int) ((until - y) / USEC_PER_MSEC);
+ }
+
+ if (flag_file)
+ if (access(flag_file, F_OK) < 0) {
+ r = -errno;
+ goto finish;
+ }
+
+ if ((k = poll(pollfd, notify > 0 ? 2 : 1, sleep_for)) < 0) {
+
+ if (errno == EINTR)
+ continue;
+
+ r = -errno;
+ goto finish;
+ } else if (k == 0) {
+ r = -ETIME;
+ goto finish;
+ }
+
+ if (notify > 0 && pollfd[POLL_INOTIFY].revents != 0)
+ flush_fd(notify);
+
+ if (pollfd[POLL_TTY].revents == 0)
+ continue;
+
+ if ((n = read(ttyfd >= 0 ? ttyfd : STDIN_FILENO, &c, 1)) < 0) {
+
+ if (errno == EINTR || errno == EAGAIN)
+ continue;
+
+ r = -errno;
+ goto finish;
+
+ } else if (n == 0)
+ break;
+
+ if (c == '\n')
+ break;
+ else if (c == 21) { /* C-u */
+
+ if (!silent_mode)
+ backspace_chars(ttyfd, p);
+ p = 0;
+
+ } else if (c == '\b' || c == 127) {
+
+ if (p > 0) {
+
+ if (!silent_mode)
+ backspace_chars(ttyfd, 1);
+
+ p--;
+ } else if (!dirty && !silent_mode) {
+
+ silent_mode = true;
+
+ /* There are two ways to enter silent
+ * mode. Either by pressing backspace
+ * as first key (and only as first key),
+ * or ... */
+ if (ttyfd >= 0)
+ loop_write(ttyfd, "(no echo) ", 10, false);
+
+ } else if (ttyfd >= 0)
+ loop_write(ttyfd, "\a", 1, false);
+
+ } else if (c == '\t' && !silent_mode) {
+
+ backspace_chars(ttyfd, p);
+ silent_mode = true;
+
+ /* ... or by pressing TAB at any time. */
+
+ if (ttyfd >= 0)
+ loop_write(ttyfd, "(no echo) ", 10, false);
+ } else {
+ passphrase[p++] = c;
+
+ if (!silent_mode && ttyfd >= 0)
+ loop_write(ttyfd, "*", 1, false);
+
+ dirty = true;
+ }
+ }
+
+ passphrase[p] = 0;
+
+ if (!(*_passphrase = strdup(passphrase))) {
+ r = -ENOMEM;
+ goto finish;
+ }
+
+ r = 0;
+
+finish:
+ if (notify >= 0)
+ close_nointr_nofail(notify);
+
+ if (ttyfd >= 0) {
+
+ if (reset_tty) {
+ loop_write(ttyfd, "\n", 1, false);
+ tcsetattr(ttyfd, TCSADRAIN, &old_termios);
+ }
+
+ close_nointr_nofail(ttyfd);
+ }
+
+ return r;
+}
+
+static int create_socket(char **name) {
+ int fd;
+ union {
+ struct sockaddr sa;
+ struct sockaddr_un un;
+ } sa = {
+ .un.sun_family = AF_UNIX,
+ };
+ int one = 1, r;
+ char *c;
+
+ assert(name);
+
+ fd = socket(AF_UNIX, SOCK_DGRAM|SOCK_CLOEXEC|SOCK_NONBLOCK, 0);
+ if (fd < 0) {
+ log_error("socket() failed: %m");
+ return -errno;
+ }
+
+ snprintf(sa.un.sun_path, sizeof(sa.un.sun_path)-1, "/run/systemd/ask-password/sck.%llu", random_ull());
+
+ RUN_WITH_UMASK(0177) {
+ r = bind(fd, &sa.sa, offsetof(struct sockaddr_un, sun_path) + strlen(sa.un.sun_path));
+ }
+
+ if (r < 0) {
+ r = -errno;
+ log_error("bind() failed: %m");
+ goto fail;
+ }
+
+ if (setsockopt(fd, SOL_SOCKET, SO_PASSCRED, &one, sizeof(one)) < 0) {
+ r = -errno;
+ log_error("SO_PASSCRED failed: %m");
+ goto fail;
+ }
+
+ c = strdup(sa.un.sun_path);
+ if (!c) {
+ r = log_oom();
+ goto fail;
+ }
+
+ *name = c;
+ return fd;
+
+fail:
+ close_nointr_nofail(fd);
+
+ return r;
+}
+
+int ask_password_agent(
+ const char *message,
+ const char *icon,
+ usec_t until,
+ bool accept_cached,
+ char ***_passphrases) {
+
+ enum {
+ FD_SOCKET,
+ FD_SIGNAL,
+ _FD_MAX
+ };
+
+ char temp[] = "/run/systemd/ask-password/tmp.XXXXXX";
+ char final[sizeof(temp)] = "";
+ int fd = -1, r;
+ FILE *f = NULL;
+ char *socket_name = NULL;
+ int socket_fd = -1, signal_fd = -1;
+ sigset_t mask, oldmask;
+ struct pollfd pollfd[_FD_MAX];
+
+ assert(_passphrases);
+
+ assert_se(sigemptyset(&mask) == 0);
+ sigset_add_many(&mask, SIGINT, SIGTERM, -1);
+ assert_se(sigprocmask(SIG_BLOCK, &mask, &oldmask) == 0);
+
+ mkdir_p_label("/run/systemd/ask-password", 0755);
+
+ RUN_WITH_UMASK(0022) {
+ fd = mkostemp(temp, O_CLOEXEC|O_CREAT|O_WRONLY);
+ }
+
+ if (fd < 0) {
+ log_error("Failed to create password file: %m");
+ r = -errno;
+ goto finish;
+ }
+
+ fchmod(fd, 0644);
+
+ if (!(f = fdopen(fd, "w"))) {
+ log_error("Failed to allocate FILE: %m");
+ r = -errno;
+ goto finish;
+ }
+
+ fd = -1;
+
+ if ((signal_fd = signalfd(-1, &mask, SFD_NONBLOCK|SFD_CLOEXEC)) < 0) {
+ log_error("signalfd(): %m");
+ r = -errno;
+ goto finish;
+ }
+
+ if ((socket_fd = create_socket(&socket_name)) < 0) {
+ r = socket_fd;
+ goto finish;
+ }
+
+ fprintf(f,
+ "[Ask]\n"
+ "PID=%lu\n"
+ "Socket=%s\n"
+ "AcceptCached=%i\n"
+ "NotAfter=%llu\n",
+ (unsigned long) getpid(),
+ socket_name,
+ accept_cached ? 1 : 0,
+ (unsigned long long) until);
+
+ if (message)
+ fprintf(f, "Message=%s\n", message);
+
+ if (icon)
+ fprintf(f, "Icon=%s\n", icon);
+
+ fflush(f);
+
+ if (ferror(f)) {
+ log_error("Failed to write query file: %m");
+ r = -errno;
+ goto finish;
+ }
+
+ memcpy(final, temp, sizeof(temp));
+
+ final[sizeof(final)-11] = 'a';
+ final[sizeof(final)-10] = 's';
+ final[sizeof(final)-9] = 'k';
+
+ if (rename(temp, final) < 0) {
+ log_error("Failed to rename query file: %m");
+ r = -errno;
+ goto finish;
+ }
+
+ zero(pollfd);
+ pollfd[FD_SOCKET].fd = socket_fd;
+ pollfd[FD_SOCKET].events = POLLIN;
+ pollfd[FD_SIGNAL].fd = signal_fd;
+ pollfd[FD_SIGNAL].events = POLLIN;
+
+ for (;;) {
+ char passphrase[LINE_MAX+1];
+ struct msghdr msghdr;
+ struct iovec iovec;
+ struct ucred *ucred;
+ union {
+ struct cmsghdr cmsghdr;
+ uint8_t buf[CMSG_SPACE(sizeof(struct ucred))];
+ } control;
+ ssize_t n;
+ int k;
+ usec_t t;
+
+ t = now(CLOCK_MONOTONIC);
+
+ if (until > 0 && until <= t) {
+ log_notice("Timed out");
+ r = -ETIME;
+ goto finish;
+ }
+
+ if ((k = poll(pollfd, _FD_MAX, until > 0 ? (int) ((until-t)/USEC_PER_MSEC) : -1)) < 0) {
+
+ if (errno == EINTR)
+ continue;
+
+ log_error("poll() failed: %m");
+ r = -errno;
+ goto finish;
+ }
+
+ if (k <= 0) {
+ log_notice("Timed out");
+ r = -ETIME;
+ goto finish;
+ }
+
+ if (pollfd[FD_SIGNAL].revents & POLLIN) {
+ r = -EINTR;
+ goto finish;
+ }
+
+ if (pollfd[FD_SOCKET].revents != POLLIN) {
+ log_error("Unexpected poll() event.");
+ r = -EIO;
+ goto finish;
+ }
+
+ zero(iovec);
+ iovec.iov_base = passphrase;
+ iovec.iov_len = sizeof(passphrase);
+
+ zero(control);
+ zero(msghdr);
+ msghdr.msg_iov = &iovec;
+ msghdr.msg_iovlen = 1;
+ msghdr.msg_control = &control;
+ msghdr.msg_controllen = sizeof(control);
+
+ if ((n = recvmsg(socket_fd, &msghdr, 0)) < 0) {
+
+ if (errno == EAGAIN ||
+ errno == EINTR)
+ continue;
+
+ log_error("recvmsg() failed: %m");
+ r = -errno;
+ goto finish;
+ }
+
+ if (n <= 0) {
+ log_error("Message too short");
+ continue;
+ }
+
+ if (msghdr.msg_controllen < CMSG_LEN(sizeof(struct ucred)) ||
+ control.cmsghdr.cmsg_level != SOL_SOCKET ||
+ control.cmsghdr.cmsg_type != SCM_CREDENTIALS ||
+ control.cmsghdr.cmsg_len != CMSG_LEN(sizeof(struct ucred))) {
+ log_warning("Received message without credentials. Ignoring.");
+ continue;
+ }
+
+ ucred = (struct ucred*) CMSG_DATA(&control.cmsghdr);
+ if (ucred->uid != 0) {
+ log_warning("Got request from unprivileged user. Ignoring.");
+ continue;
+ }
+
+ if (passphrase[0] == '+') {
+ char **l;
+
+ if (n == 1)
+ l = strv_new("", NULL);
+ else
+ l = strv_parse_nulstr(passphrase+1, n-1);
+ /* An empty message refers to the empty password */
+
+ if (!l) {
+ r = -ENOMEM;
+ goto finish;
+ }
+
+ if (strv_length(l) <= 0) {
+ strv_free(l);
+ log_error("Invalid packet");
+ continue;
+ }
+
+ *_passphrases = l;
+
+ } else if (passphrase[0] == '-') {
+ r = -ECANCELED;
+ goto finish;
+ } else {
+ log_error("Invalid packet");
+ continue;
+ }
+
+ break;
+ }
+
+ r = 0;
+
+finish:
+ if (fd >= 0)
+ close_nointr_nofail(fd);
+
+ if (socket_name) {
+ unlink(socket_name);
+ free(socket_name);
+ }
+
+ if (socket_fd >= 0)
+ close_nointr_nofail(socket_fd);
+
+ if (signal_fd >= 0)
+ close_nointr_nofail(signal_fd);
+
+ if (f)
+ fclose(f);
+
+ unlink(temp);
+
+ if (final[0])
+ unlink(final);
+
+ assert_se(sigprocmask(SIG_SETMASK, &oldmask, NULL) == 0);
+
+ return r;
+}
+
+int ask_password_auto(const char *message, const char *icon, usec_t until, bool accept_cached, char ***_passphrases) {
+ assert(message);
+ assert(_passphrases);
+
+ if (isatty(STDIN_FILENO)) {
+ int r;
+ char *s = NULL, **l = NULL;
+
+ if ((r = ask_password_tty(message, until, NULL, &s)) < 0)
+ return r;
+
+ l = strv_new(s, NULL);
+ free(s);
+
+ if (!l)
+ return -ENOMEM;
+
+ *_passphrases = l;
+ return r;
+
+ } else
+ return ask_password_agent(message, icon, until, accept_cached, _passphrases);
+}
diff --git a/src/shared/ask-password-api.h b/src/shared/ask-password-api.h
new file mode 100644
index 0000000000..288a0f48cf
--- /dev/null
+++ b/src/shared/ask-password-api.h
@@ -0,0 +1,30 @@
+/*-*- Mode: C; c-basic-offset: 8; indent-tabs-mode: nil -*-*/
+
+#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 "util.h"
+
+int ask_password_tty(const char *message, usec_t until, const char *flag_file, char **_passphrase);
+
+int ask_password_agent(const char *message, const char *icon, usec_t until, bool accept_cached, char ***_passphrases);
+
+int ask_password_auto(const char *message, const char *icon, usec_t until, bool accept_cached, char ***_passphrases);
diff --git a/src/shared/audit.c b/src/shared/audit.c
new file mode 100644
index 0000000000..97560cc9a3
--- /dev/null
+++ b/src/shared/audit.c
@@ -0,0 +1,128 @@
+/*-*- Mode: C; c-basic-offset: 8; indent-tabs-mode: nil -*-*/
+
+/***
+ This file is part 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 <assert.h>
+#include <string.h>
+#include <unistd.h>
+#include <errno.h>
+#include <stdlib.h>
+#include <stdio.h>
+#include <ctype.h>
+#include <sys/prctl.h>
+#include <sys/capability.h>
+
+#include "macro.h"
+#include "audit.h"
+#include "util.h"
+#include "log.h"
+#include "fileio.h"
+#include "virt.h"
+
+int audit_session_from_pid(pid_t pid, uint32_t *id) {
+ char *s;
+ uint32_t u;
+ int r;
+
+ assert(id);
+
+ if (have_effective_cap(CAP_AUDIT_CONTROL) <= 0)
+ return -ENOENT;
+
+ /* Audit doesn't support containers right now */
+ if (detect_container(NULL) > 0)
+ return -ENOTSUP;
+
+ if (pid == 0)
+ r = read_one_line_file("/proc/self/sessionid", &s);
+ else {
+ char *p;
+
+ if (asprintf(&p, "/proc/%lu/sessionid", (unsigned long) pid) < 0)
+ return -ENOMEM;
+
+ r = read_one_line_file(p, &s);
+ free(p);
+ }
+
+ if (r < 0)
+ return r;
+
+ r = safe_atou32(s, &u);
+ free(s);
+
+ if (r < 0)
+ return r;
+
+ if (u == (uint32_t) -1 || u <= 0)
+ return -ENOENT;
+
+ *id = u;
+ return 0;
+}
+
+int audit_loginuid_from_pid(pid_t pid, uid_t *uid) {
+ char *s;
+ uid_t u;
+ int r;
+
+ assert(uid);
+
+ /* Only use audit login uid if we are executed with sufficient
+ * capabilities so that pam_loginuid could do its job. If we
+ * are lacking the CAP_AUDIT_CONTROL capabality we most likely
+ * are being run in a container and /proc/self/loginuid is
+ * useless since it probably contains a uid of the host
+ * system. */
+
+ if (have_effective_cap(CAP_AUDIT_CONTROL) <= 0)
+ return -ENOENT;
+
+ /* Audit doesn't support containers right now */
+ if (detect_container(NULL) > 0)
+ return -ENOTSUP;
+
+ if (pid == 0)
+ r = read_one_line_file("/proc/self/loginuid", &s);
+ else {
+ char *p;
+
+ if (asprintf(&p, "/proc/%lu/loginuid", (unsigned long) pid) < 0)
+ return -ENOMEM;
+
+ r = read_one_line_file(p, &s);
+ free(p);
+ }
+
+ if (r < 0)
+ return r;
+
+ r = parse_uid(s, &u);
+ free(s);
+
+ if (r < 0)
+ return r;
+
+ if (u == (uid_t) -1)
+ return -ENOENT;
+
+ *uid = (uid_t) u;
+ return 0;
+}
diff --git a/src/shared/audit.h b/src/shared/audit.h
new file mode 100644
index 0000000000..490a0b0320
--- /dev/null
+++ b/src/shared/audit.h
@@ -0,0 +1,32 @@
+/*-*- Mode: C; c-basic-offset: 8; indent-tabs-mode: nil -*-*/
+
+#ifndef fooaudithfoo
+#define fooaudithfoo
+
+/***
+ This file is part 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 <sys/types.h>
+
+#include "capability.h"
+
+int audit_session_from_pid(pid_t pid, uint32_t *id);
+int audit_loginuid_from_pid(pid_t pid, uid_t *uid);
+
+#endif
diff --git a/src/shared/calendarspec.c b/src/shared/calendarspec.c
new file mode 100644
index 0000000000..7979e2384f
--- /dev/null
+++ b/src/shared/calendarspec.c
@@ -0,0 +1,927 @@
+/*-*- Mode: C; c-basic-offset: 8; indent-tabs-mode: nil -*-*/
+
+/***
+ This file is part of systemd.
+
+ Copyright 2012 Lennart Poettering
+
+ systemd is free software; you can redistribute it and/or modify it
+ under the terms of the GNU Lesser General Public License as published by
+ the Free Software Foundation; either version 2.1 of the License, or
+ (at your option) any later version.
+
+ systemd is distributed in the hope that it will be useful, but
+ WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ Lesser General 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 <stdlib.h>
+#include <string.h>
+
+#include "calendarspec.h"
+
+static void free_chain(CalendarComponent *c) {
+ CalendarComponent *n;
+
+ while (c) {
+ n = c->next;
+ free(c);
+ c = n;
+ }
+}
+
+void calendar_spec_free(CalendarSpec *c) {
+ assert(c);
+
+ free_chain(c->year);
+ free_chain(c->month);
+ free_chain(c->day);
+ free_chain(c->hour);
+ free_chain(c->minute);
+ free_chain(c->second);
+
+ free(c);
+}
+
+static int component_compare(const void *_a, const void *_b) {
+ CalendarComponent * const *a = _a, * const *b = _b;
+
+ if ((*a)->value < (*b)->value)
+ return -1;
+ if ((*a)->value > (*b)->value)
+ return 1;
+
+ if ((*a)->repeat < (*b)->repeat)
+ return -1;
+ if ((*a)->repeat > (*b)->repeat)
+ return 1;
+
+ return 0;
+}
+
+static void sort_chain(CalendarComponent **c) {
+ unsigned n = 0, k;
+ CalendarComponent **b, *i, **j, *next;
+
+ assert(c);
+
+ for (i = *c; i; i = i->next)
+ n++;
+
+ if (n <= 1)
+ return;
+
+ j = b = alloca(sizeof(CalendarComponent*) * n);
+ for (i = *c; i; i = i->next)
+ *(j++) = i;
+
+ qsort(b, n, sizeof(CalendarComponent*), component_compare);
+
+ b[n-1]->next = NULL;
+ next = b[n-1];
+
+ /* Drop non-unique entries */
+ for (k = n-1; k > 0; k--) {
+ if (b[k-1]->value == next->value &&
+ b[k-1]->repeat == next->repeat) {
+ free(b[k-1]);
+ continue;
+ }
+
+ b[k-1]->next = next;
+ next = b[k-1];
+ }
+
+ *c = next;
+}
+
+static void fix_year(CalendarComponent *c) {
+ /* Turns 12 → 2012, 89 → 1989 */
+
+ while(c) {
+ CalendarComponent *n = c->next;
+
+ if (c->value >= 0 && c->value < 70)
+ c->value += 2000;
+
+ if (c->value >= 70 && c->value < 100)
+ c->value += 1900;
+
+ c = n;
+ }
+}
+
+int calendar_spec_normalize(CalendarSpec *c) {
+ assert(c);
+
+ if (c->weekdays_bits <= 0 || c->weekdays_bits >= 127)
+ c->weekdays_bits = -1;
+
+ fix_year(c->year);
+
+ sort_chain(&c->year);
+ sort_chain(&c->month);
+ sort_chain(&c->day);
+ sort_chain(&c->hour);
+ sort_chain(&c->minute);
+ sort_chain(&c->second);
+
+ return 0;
+}
+
+_pure_ static bool chain_valid(CalendarComponent *c, int from, int to) {
+ if (!c)
+ return true;
+
+ if (c->value < from || c->value > to)
+ return false;
+
+ if (c->value + c->repeat > to)
+ return false;
+
+ if (c->next)
+ return chain_valid(c->next, from, to);
+
+ return true;
+}
+
+_pure_ bool calendar_spec_valid(CalendarSpec *c) {
+ assert(c);
+
+ if (c->weekdays_bits > 127)
+ return false;
+
+ if (!chain_valid(c->year, 1970, 2199))
+ return false;
+
+ if (!chain_valid(c->month, 1, 12))
+ return false;
+
+ if (!chain_valid(c->day, 1, 31))
+ return false;
+
+ if (!chain_valid(c->hour, 0, 23))
+ return false;
+
+ if (!chain_valid(c->minute, 0, 59))
+ return false;
+
+ if (!chain_valid(c->second, 0, 59))
+ return false;
+
+ return true;
+}
+
+static void format_weekdays(FILE *f, const CalendarSpec *c) {
+ static const char *const days[] = {
+ "Mon",
+ "Tue",
+ "Wed",
+ "Thu",
+ "Fri",
+ "Sat",
+ "Sun"
+ };
+
+ int l, x;
+ bool need_colon = false;
+
+ assert(f);
+ assert(c);
+ assert(c->weekdays_bits > 0 && c->weekdays_bits <= 127);
+
+ for (x = 0, l = -1; x < (int) ELEMENTSOF(days); x++) {
+
+ if (c->weekdays_bits & (1 << x)) {
+
+ if (l < 0) {
+ if (need_colon)
+ fputc(',', f);
+ else
+ need_colon = true;
+
+ fputs(days[x], f);
+ l = x;
+ }
+
+ } else if (l >= 0) {
+
+ if (x > l + 1) {
+ fputc(x > l + 2 ? '-' : ',', f);
+ fputs(days[x-1], f);
+ }
+
+ l = -1;
+ }
+ }
+
+ if (l >= 0 && x > l + 1) {
+ fputc(x > l + 2 ? '-' : ',', f);
+ fputs(days[x-1], f);
+ }
+}
+
+static void format_chain(FILE *f, int space, const CalendarComponent *c) {
+ assert(f);
+
+ if (!c) {
+ fputc('*', f);
+ return;
+ }
+
+ assert(c->value >= 0);
+ fprintf(f, "%0*i", space, c->value);
+
+ if (c->repeat > 0)
+ fprintf(f, "/%i", c->repeat);
+
+ if (c->next) {
+ fputc(',', f);
+ format_chain(f, space, c->next);
+ }
+}
+
+int calendar_spec_to_string(const CalendarSpec *c, char **p) {
+ char *buf = NULL;
+ size_t sz = 0;
+ FILE *f;
+
+ assert(c);
+ assert(p);
+
+ f = open_memstream(&buf, &sz);
+ if (!f)
+ return -ENOMEM;
+
+ if (c->weekdays_bits > 0 && c->weekdays_bits <= 127) {
+ format_weekdays(f, c);
+ fputc(' ', f);
+ }
+
+ format_chain(f, 4, c->year);
+ fputc('-', f);
+ format_chain(f, 2, c->month);
+ fputc('-', f);
+ format_chain(f, 2, c->day);
+ fputc(' ', f);
+ format_chain(f, 2, c->hour);
+ fputc(':', f);
+ format_chain(f, 2, c->minute);
+ fputc(':', f);
+ format_chain(f, 2, c->second);
+
+ fflush(f);
+
+ if (ferror(f)) {
+ free(buf);
+ fclose(f);
+ return -ENOMEM;
+ }
+
+ fclose(f);
+
+ *p = buf;
+ return 0;
+}
+
+static int parse_weekdays(const char **p, CalendarSpec *c) {
+ static const struct {
+ const char *name;
+ const int nr;
+ } day_nr[] = {
+ { "Monday", 0 },
+ { "Mon", 0 },
+ { "Tuesday", 1 },
+ { "Tue", 1 },
+ { "Wednesday", 2 },
+ { "Wed", 2 },
+ { "Thursday", 3 },
+ { "Thu", 3 },
+ { "Friday", 4 },
+ { "Fri", 4 },
+ { "Saturday", 5 },
+ { "Sat", 5 },
+ { "Sunday", 6 },
+ { "Sun", 6 }
+ };
+
+ int l = -1;
+ bool first = true;
+
+ assert(p);
+ assert(*p);
+ assert(c);
+
+ for (;;) {
+ unsigned i;
+
+ if (!first && **p == ' ')
+ return 0;
+
+ for (i = 0; i < ELEMENTSOF(day_nr); i++) {
+ size_t skip;
+
+ if (!startswith_no_case(*p, day_nr[i].name))
+ continue;
+
+ skip = strlen(day_nr[i].name);
+
+ if ((*p)[skip] != '-' &&
+ (*p)[skip] != ',' &&
+ (*p)[skip] != ' ' &&
+ (*p)[skip] != 0)
+ return -EINVAL;
+
+ c->weekdays_bits |= 1 << day_nr[i].nr;
+
+ if (l >= 0) {
+ int j;
+
+ if (l > day_nr[i].nr)
+ return -EINVAL;
+
+ for (j = l + 1; j < day_nr[i].nr; j++)
+ c->weekdays_bits |= 1 << j;
+ }
+
+ *p += skip;
+ break;
+ }
+
+ /* Couldn't find this prefix, so let's assume the
+ weekday was not specified and let's continue with
+ the date */
+ if (i >= ELEMENTSOF(day_nr))
+ return first ? 0 : -EINVAL;
+
+ /* We reached the end of the string */
+ if (**p == 0)
+ return 0;
+
+ /* We reached the end of the weekday spec part */
+ if (**p == ' ') {
+ *p += strspn(*p, " ");
+ return 0;
+ }
+
+ if (**p == '-') {
+ if (l >= 0)
+ return -EINVAL;
+
+ l = day_nr[i].nr;
+ } else
+ l = -1;
+
+ *p += 1;
+ first = false;
+ }
+}
+
+static int prepend_component(const char **p, CalendarComponent **c) {
+ unsigned long value, repeat = 0;
+ char *e = NULL, *ee = NULL;
+ CalendarComponent *cc;
+
+ assert(p);
+ assert(c);
+
+ errno = 0;
+ value = strtoul(*p, &e, 10);
+ if (errno > 0)
+ return -errno;
+ if (e == *p)
+ return -EINVAL;
+ if ((unsigned long) (int) value != value)
+ return -ERANGE;
+
+ if (*e == '/') {
+ repeat = strtoul(e+1, &ee, 10);
+ if (errno > 0)
+ return -errno;
+ if (ee == e+1)
+ return -EINVAL;
+ if ((unsigned long) (int) repeat != repeat)
+ return -ERANGE;
+ if (repeat <= 0)
+ return -ERANGE;
+
+ e = ee;
+ }
+
+ if (*e != 0 && *e != ' ' && *e != ',' && *e != '-' && *e != ':')
+ return -EINVAL;
+
+ cc = new0(CalendarComponent, 1);
+ if (!cc)
+ return -ENOMEM;
+
+ cc->value = value;
+ cc->repeat = repeat;
+ cc->next = *c;
+
+ *p = e;
+ *c = cc;
+
+ if (*e ==',') {
+ *p += 1;
+ return prepend_component(p, c);
+ }
+
+ return 0;
+}
+
+static int parse_chain(const char **p, CalendarComponent **c) {
+ const char *t;
+ CalendarComponent *cc = NULL;
+ int r;
+
+ assert(p);
+ assert(c);
+
+ t = *p;
+
+ if (t[0] == '*') {
+ *p = t + 1;
+ *c = NULL;
+ return 0;
+ }
+
+ r = prepend_component(&t, &cc);
+ if (r < 0) {
+ free_chain(cc);
+ return r;
+ }
+
+ *p = t;
+ *c = cc;
+ return 0;
+}
+
+static int const_chain(int value, CalendarComponent **c) {
+ CalendarComponent *cc = NULL;
+
+ assert(c);
+
+ cc = new0(CalendarComponent, 1);
+ if (!cc)
+ return -ENOMEM;
+
+ cc->value = value;
+ cc->repeat = 0;
+ cc->next = NULL;
+
+ *c = cc;
+
+ return 0;
+}
+
+static int parse_date(const char **p, CalendarSpec *c) {
+ const char *t;
+ int r;
+ CalendarComponent *first, *second, *third;
+
+ assert(p);
+ assert(*p);
+ assert(c);
+
+ t = *p;
+
+ if (*t == 0)
+ return 0;
+
+ r = parse_chain(&t, &first);
+ if (r < 0)
+ return r;
+
+ /* Already the end? A ':' as separator? In that case this was a time, not a date */
+ if (*t == 0 || *t == ':') {
+ free_chain(first);
+ return 0;
+ }
+
+ if (*t != '-') {
+ free_chain(first);
+ return -EINVAL;
+ }
+
+ t++;
+ r = parse_chain(&t, &second);
+ if (r < 0) {
+ free_chain(first);
+ return r;
+ }
+
+ /* Got two parts, hence it's month and day */
+ if (*t == ' ' || *t == 0) {
+ *p = t + strspn(t, " ");
+ c->month = first;
+ c->day = second;
+ return 0;
+ }
+
+ if (*t != '-') {
+ free_chain(first);
+ free_chain(second);
+ return -EINVAL;
+ }
+
+ t++;
+ r = parse_chain(&t, &third);
+ if (r < 0) {
+ free_chain(first);
+ free_chain(second);
+ return r;
+ }
+
+ /* Got tree parts, hence it is year, month and day */
+ if (*t == ' ' || *t == 0) {
+ *p = t + strspn(t, " ");
+ c->year = first;
+ c->month = second;
+ c->day = third;
+ return 0;
+ }
+
+ free_chain(first);
+ free_chain(second);
+ free_chain(third);
+ return -EINVAL;
+}
+
+static int parse_time(const char **p, CalendarSpec *c) {
+ CalendarComponent *h = NULL, *m = NULL, *s = NULL;
+ const char *t;
+ int r;
+
+ assert(p);
+ assert(*p);
+ assert(c);
+
+ t = *p;
+
+ if (*t == 0) {
+ /* If no time is specified at all, but a date of some
+ * kind, then this means 00:00:00 */
+ if (c->day || c->weekdays_bits > 0)
+ goto null_hour;
+
+ goto finish;
+ }
+
+ r = parse_chain(&t, &h);
+ if (r < 0)
+ goto fail;
+
+ if (*t != ':') {
+ r = -EINVAL;
+ goto fail;
+ }
+
+ t++;
+ r = parse_chain(&t, &m);
+ if (r < 0)
+ goto fail;
+
+ /* Already at the end? Then it's hours and minutes, and seconds are 0 */
+ if (*t == 0) {
+ if (m != NULL)
+ goto null_second;
+
+ goto finish;
+ }
+
+ if (*t != ':') {
+ r = -EINVAL;
+ goto fail;
+ }
+
+ t++;
+ r = parse_chain(&t, &s);
+ if (r < 0)
+ goto fail;
+
+ /* At the end? Then it's hours, minutes and seconds */
+ if (*t == 0)
+ goto finish;
+
+ r = -EINVAL;
+ goto fail;
+
+null_hour:
+ r = const_chain(0, &h);
+ if (r < 0)
+ goto fail;
+
+ r = const_chain(0, &m);
+ if (r < 0)
+ goto fail;
+
+null_second:
+ r = const_chain(0, &s);
+ if (r < 0)
+ goto fail;
+
+finish:
+ *p = t;
+ c->hour = h;
+ c->minute = m;
+ c->second = s;
+ return 0;
+
+fail:
+ free_chain(h);
+ free_chain(m);
+ free_chain(s);
+ return r;
+}
+
+int calendar_spec_from_string(const char *p, CalendarSpec **spec) {
+ CalendarSpec *c;
+ int r;
+
+ assert(p);
+ assert(spec);
+
+ if (isempty(p))
+ return -EINVAL;
+
+ c = new0(CalendarSpec, 1);
+ if (!c)
+ return -ENOMEM;
+
+ if (strcaseeq(p, "hourly")) {
+ r = const_chain(0, &c->minute);
+ if (r < 0)
+ goto fail;
+ r = const_chain(0, &c->second);
+ if (r < 0)
+ goto fail;
+
+ } else if (strcaseeq(p, "daily")) {
+ r = const_chain(0, &c->hour);
+ if (r < 0)
+ goto fail;
+ r = const_chain(0, &c->minute);
+ if (r < 0)
+ goto fail;
+ r = const_chain(0, &c->second);
+ if (r < 0)
+ goto fail;
+
+ } else if (strcaseeq(p, "monthly")) {
+ r = const_chain(1, &c->day);
+ if (r < 0)
+ goto fail;
+ r = const_chain(0, &c->hour);
+ if (r < 0)
+ goto fail;
+ r = const_chain(0, &c->minute);
+ if (r < 0)
+ goto fail;
+ r = const_chain(0, &c->second);
+ if (r < 0)
+ goto fail;
+
+ } else if (strcaseeq(p, "weekly")) {
+
+ c->weekdays_bits = 1;
+
+ r = const_chain(0, &c->hour);
+ if (r < 0)
+ goto fail;
+ r = const_chain(0, &c->minute);
+ if (r < 0)
+ goto fail;
+ r = const_chain(0, &c->second);
+ if (r < 0)
+ goto fail;
+
+ } else {
+ r = parse_weekdays(&p, c);
+ if (r < 0)
+ goto fail;
+
+ r = parse_date(&p, c);
+ if (r < 0)
+ goto fail;
+
+ r = parse_time(&p, c);
+ if (r < 0)
+ goto fail;
+
+ if (*p != 0) {
+ r = -EINVAL;
+ goto fail;
+ }
+ }
+
+ r = calendar_spec_normalize(c);
+ if (r < 0)
+ goto fail;
+
+ if (!calendar_spec_valid(c)) {
+ r = -EINVAL;
+ goto fail;
+ }
+
+ *spec = c;
+ return 0;
+
+fail:
+ calendar_spec_free(c);
+ return r;
+}
+
+static int find_matching_component(const CalendarComponent *c, int *val) {
+ const CalendarComponent *n;
+ int d = -1;
+ bool d_set = false;
+ int r;
+
+ assert(val);
+
+ if (!c)
+ return 0;
+
+ while (c) {
+ n = c->next;
+
+ if (c->value >= *val) {
+
+ if (!d_set || c->value < d) {
+ d = c->value;
+ d_set = true;
+ }
+
+ } else if (c->repeat > 0) {
+ int k;
+
+ k = c->value + c->repeat * ((*val - c->value + c->repeat -1) / c->repeat);
+
+ if (!d_set || k < d) {
+ d = k;
+ d_set = true;
+ }
+ }
+
+ c = n;
+ }
+
+ if (!d_set)
+ return -ENOENT;
+
+ r = *val != d;
+ *val = d;
+ return r;
+}
+
+static bool tm_out_of_bounds(const struct tm *tm) {
+ struct tm t;
+ assert(tm);
+
+ t = *tm;
+
+ if (mktime(&t) == (time_t) -1)
+ return true;
+
+ /* Did any normalization take place? If so, it was out of bounds before */
+ return
+ t.tm_year != tm->tm_year ||
+ t.tm_mon != tm->tm_mon ||
+ t.tm_mday != tm->tm_mday ||
+ t.tm_hour != tm->tm_hour ||
+ t.tm_min != tm->tm_min ||
+ t.tm_sec != tm->tm_sec;
+}
+
+static bool matches_weekday(int weekdays_bits, const struct tm *tm) {
+ struct tm t;
+ int k;
+
+ if (weekdays_bits < 0 || weekdays_bits >= 127)
+ return true;
+
+ t = *tm;
+ if (mktime(&t) == (time_t) -1)
+ return false;
+
+ k = t.tm_wday == 0 ? 6 : t.tm_wday - 1;
+ return (weekdays_bits & (1 << k));
+}
+
+static int find_next(const CalendarSpec *spec, struct tm *tm) {
+ struct tm c;
+ int r;
+
+ assert(spec);
+ assert(tm);
+
+ c = *tm;
+
+ for (;;) {
+ /* Normalize the current date */
+ mktime(&c);
+ c.tm_isdst = -1;
+
+ c.tm_year += 1900;
+ r = find_matching_component(spec->year, &c.tm_year);
+ c.tm_year -= 1900;
+
+ if (r > 0) {
+ c.tm_mon = 0;
+ c.tm_mday = 1;
+ c.tm_hour = c.tm_min = c.tm_sec = 0;
+ }
+ if (r < 0 || tm_out_of_bounds(&c))
+ return r;
+
+ c.tm_mon += 1;
+ r = find_matching_component(spec->month, &c.tm_mon);
+ c.tm_mon -= 1;
+
+ if (r > 0) {
+ c.tm_mday = 1;
+ c.tm_hour = c.tm_min = c.tm_sec = 0;
+ }
+ if (r < 0 || tm_out_of_bounds(&c)) {
+ c.tm_year ++;
+ c.tm_mon = 0;
+ c.tm_mday = 1;
+ c.tm_hour = c.tm_min = c.tm_sec = 0;
+ continue;
+ }
+
+ r = find_matching_component(spec->day, &c.tm_mday);
+ if (r > 0)
+ c.tm_hour = c.tm_min = c.tm_sec = 0;
+ if (r < 0 || tm_out_of_bounds(&c)) {
+ c.tm_mon ++;
+ c.tm_mday = 1;
+ c.tm_hour = c.tm_min = c.tm_sec = 0;
+ continue;
+ }
+
+ if (!matches_weekday(spec->weekdays_bits, &c)) {
+ c.tm_mday++;
+ c.tm_hour = c.tm_min = c.tm_sec = 0;
+ continue;
+ }
+
+ r = find_matching_component(spec->hour, &c.tm_hour);
+ if (r > 0)
+ c.tm_min = c.tm_sec = 0;
+ if (r < 0 || tm_out_of_bounds(&c)) {
+ c.tm_mday ++;
+ c.tm_hour = c.tm_min = c.tm_sec = 0;
+ continue;
+ }
+
+ r = find_matching_component(spec->minute, &c.tm_min);
+ if (r > 0)
+ c.tm_sec = 0;
+ if (r < 0 || tm_out_of_bounds(&c)) {
+ c.tm_hour ++;
+ c.tm_min = c.tm_sec = 0;
+ continue;
+ }
+
+ r = find_matching_component(spec->second, &c.tm_sec);
+ if (r < 0 || tm_out_of_bounds(&c)) {
+ c.tm_min ++;
+ c.tm_sec = 0;
+ continue;
+ }
+
+
+ *tm = c;
+ return 0;
+ }
+}
+
+int calendar_spec_next_usec(const CalendarSpec *spec, usec_t usec, usec_t *next) {
+ struct tm tm;
+ time_t t;
+ int r;
+
+ assert(spec);
+ assert(next);
+
+ t = (time_t) (usec / USEC_PER_SEC) + 1;
+ assert_se(localtime_r(&t, &tm));
+
+ r = find_next(spec, &tm);
+ if (r < 0)
+ return r;
+
+ t = mktime(&tm);
+ if (t == (time_t) -1)
+ return -EINVAL;
+
+
+ *next = (usec_t) t * USEC_PER_SEC;
+ return 0;
+}
diff --git a/src/shared/calendarspec.h b/src/shared/calendarspec.h
new file mode 100644
index 0000000000..7baf318249
--- /dev/null
+++ b/src/shared/calendarspec.h
@@ -0,0 +1,57 @@
+/*-*- Mode: C; c-basic-offset: 8; indent-tabs-mode: nil -*-*/
+
+#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/>.
+***/
+
+/* A structure for specifying (possibly repetitive) points in calendar
+ * time, a la cron */
+
+#include <stdbool.h>
+#include "util.h"
+
+typedef struct CalendarComponent {
+ int value;
+ int repeat;
+
+ struct CalendarComponent *next;
+} CalendarComponent;
+
+typedef struct CalendarSpec {
+ int weekdays_bits;
+
+ CalendarComponent *year;
+ CalendarComponent *month;
+ CalendarComponent *day;
+
+ CalendarComponent *hour;
+ CalendarComponent *minute;
+ CalendarComponent *second;
+} CalendarSpec;
+
+void calendar_spec_free(CalendarSpec *c);
+
+int calendar_spec_normalize(CalendarSpec *spec);
+bool calendar_spec_valid(CalendarSpec *spec);
+
+int calendar_spec_to_string(const CalendarSpec *spec, char **p);
+int calendar_spec_from_string(const char *p, CalendarSpec **spec);
+
+int calendar_spec_next_usec(const CalendarSpec *spec, usec_t usec, usec_t *next);
diff --git a/src/shared/capability.c b/src/shared/capability.c
new file mode 100644
index 0000000000..321952067d
--- /dev/null
+++ b/src/shared/capability.c
@@ -0,0 +1,225 @@
+/*-*- Mode: C; c-basic-offset: 8; indent-tabs-mode: nil -*-*/
+
+/***
+ This file is part 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 <assert.h>
+#include <string.h>
+#include <unistd.h>
+#include <errno.h>
+#include <stdio.h>
+#include <sys/types.h>
+#include <stdarg.h>
+#include <ctype.h>
+#include <sys/capability.h>
+#include <sys/prctl.h>
+
+#include "macro.h"
+#include "capability.h"
+#include "util.h"
+#include "log.h"
+#include "fileio.h"
+
+int have_effective_cap(int value) {
+ cap_t cap;
+ cap_flag_value_t fv;
+ int r;
+
+ cap = cap_get_proc();
+ if (!cap)
+ return -errno;
+
+ if (cap_get_flag(cap, value, CAP_EFFECTIVE, &fv) < 0)
+ r = -errno;
+ else
+ r = fv == CAP_SET;
+
+ cap_free(cap);
+ return r;
+}
+
+unsigned long cap_last_cap(void) {
+ static __thread unsigned long saved;
+ static __thread bool valid = false;
+ unsigned long p;
+
+ if (valid)
+ return saved;
+
+ p = (unsigned long) CAP_LAST_CAP;
+
+ if (prctl(PR_CAPBSET_READ, p) < 0) {
+
+ /* Hmm, look downwards, until we find one that
+ * works */
+ for (p--; p > 0; p --)
+ if (prctl(PR_CAPBSET_READ, p) >= 0)
+ break;
+
+ } else {
+
+ /* Hmm, look upwards, until we find one that doesn't
+ * work */
+ for (;; p++)
+ if (prctl(PR_CAPBSET_READ, p+1) < 0)
+ break;
+ }
+
+ saved = p;
+ valid = true;
+
+ return p;
+}
+
+int capability_bounding_set_drop(uint64_t drop, bool right_now) {
+ unsigned long i;
+ cap_t after_cap = NULL, temp_cap = NULL;
+ cap_flag_value_t fv;
+ int r;
+
+ /* If we are run as PID 1 we will lack CAP_SETPCAP by default
+ * in the effective set (yes, the kernel drops that when
+ * executing init!), so get it back temporarily so that we can
+ * call PR_CAPBSET_DROP. */
+
+ after_cap = cap_get_proc();
+ if (!after_cap)
+ return -errno;
+
+ if (cap_get_flag(after_cap, CAP_SETPCAP, CAP_EFFECTIVE, &fv) < 0) {
+ cap_free(after_cap);
+ return -errno;
+ }
+
+ if (fv != CAP_SET) {
+ static const cap_value_t v = CAP_SETPCAP;
+
+ temp_cap = cap_dup(after_cap);
+ if (!temp_cap) {
+ r = -errno;
+ goto finish;
+ }
+
+ if (cap_set_flag(temp_cap, CAP_EFFECTIVE, 1, &v, CAP_SET) < 0) {
+ r = -errno;
+ goto finish;
+ }
+
+ if (cap_set_proc(temp_cap) < 0) {
+ r = -errno;
+ goto finish;
+ }
+ }
+
+ for (i = 0; i <= cap_last_cap(); i++) {
+
+ if (drop & ((uint64_t) 1ULL << (uint64_t) i)) {
+ cap_value_t v;
+
+ /* Drop it from the bounding set */
+ if (prctl(PR_CAPBSET_DROP, i) < 0) {
+ r = -errno;
+ goto finish;
+ }
+ v = i;
+
+ /* Also drop it from the inheritable set, so
+ * that anything we exec() loses the
+ * capability for good. */
+ if (cap_set_flag(after_cap, CAP_INHERITABLE, 1, &v, CAP_CLEAR) < 0) {
+ r = -errno;
+ goto finish;
+ }
+
+ /* If we shall apply this right now drop it
+ * also from our own capability sets. */
+ if (right_now) {
+ if (cap_set_flag(after_cap, CAP_PERMITTED, 1, &v, CAP_CLEAR) < 0 ||
+ cap_set_flag(after_cap, CAP_EFFECTIVE, 1, &v, CAP_CLEAR) < 0) {
+ r = -errno;
+ goto finish;
+ }
+ }
+ }
+ }
+
+ r = 0;
+
+finish:
+ if (temp_cap)
+ cap_free(temp_cap);
+
+ if (after_cap) {
+ cap_set_proc(after_cap);
+ cap_free(after_cap);
+ }
+
+ return r;
+}
+
+static int drop_from_file(const char *fn, uint64_t drop) {
+ int r, k;
+ uint32_t hi, lo;
+ uint64_t current, after;
+ char *p;
+
+ r = read_one_line_file(fn, &p);
+ if (r < 0)
+ return r;
+
+ assert_cc(sizeof(hi) == sizeof(unsigned));
+ assert_cc(sizeof(lo) == sizeof(unsigned));
+
+ k = sscanf(p, "%u %u", &lo, &hi);
+ free(p);
+
+ if (k != 2)
+ return -EIO;
+
+ current = (uint64_t) lo | ((uint64_t) hi << 32ULL);
+ after = current & ~drop;
+
+ if (current == after)
+ return 0;
+
+ lo = (unsigned) (after & 0xFFFFFFFFULL);
+ hi = (unsigned) ((after >> 32ULL) & 0xFFFFFFFFULL);
+
+ if (asprintf(&p, "%u %u", lo, hi) < 0)
+ return -ENOMEM;
+
+ r = write_string_file(fn, p);
+ free(p);
+
+ return r;
+}
+
+int capability_bounding_set_drop_usermode(uint64_t drop) {
+ int r;
+
+ r = drop_from_file("/proc/sys/kernel/usermodehelper/inheritable", drop);
+ if (r < 0)
+ return r;
+
+ r = drop_from_file("/proc/sys/kernel/usermodehelper/bset", drop);
+ if (r < 0)
+ return r;
+
+ return r;
+}
diff --git a/src/shared/capability.h b/src/shared/capability.h
new file mode 100644
index 0000000000..6cb31bb510
--- /dev/null
+++ b/src/shared/capability.h
@@ -0,0 +1,33 @@
+/*-*- Mode: C; c-basic-offset: 8; indent-tabs-mode: nil -*-*/
+
+#ifndef foocapabilityhfoo
+#define foocapabilityhfoo
+
+/***
+ This file is part 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 <inttypes.h>
+#include <stdbool.h>
+
+unsigned long cap_last_cap(void);
+int have_effective_cap(int value);
+int capability_bounding_set_drop(uint64_t drop, bool right_now);
+int capability_bounding_set_drop_usermode(uint64_t drop);
+
+#endif
diff --git a/src/shared/cgroup-label.c b/src/shared/cgroup-label.c
new file mode 100644
index 0000000000..5b5163c250
--- /dev/null
+++ b/src/shared/cgroup-label.c
@@ -0,0 +1,77 @@
+/*-*- Mode: C; c-basic-offset: 8; indent-tabs-mode: nil -*-*/
+
+/***
+ This file is part 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 <unistd.h>
+#include <signal.h>
+#include <string.h>
+#include <stdlib.h>
+#include <dirent.h>
+#include <sys/stat.h>
+#include <sys/types.h>
+#include <ftw.h>
+
+#include "cgroup-util.h"
+#include "log.h"
+#include "set.h"
+#include "macro.h"
+#include "util.h"
+#include "mkdir.h"
+
+int cg_create(const char *controller, const char *path, const char *suffix) {
+ _cleanup_free_ char *fs = NULL;
+ int r;
+
+ r = cg_get_path_and_check(controller, path, suffix, &fs);
+ if (r < 0)
+ return r;
+
+ r = mkdir_parents_label(fs, 0755);
+ if (r < 0)
+ return r;
+
+ if (mkdir(fs, 0755) < 0) {
+
+ if (errno == EEXIST)
+ return 0;
+
+ return -errno;
+ }
+
+ return 1;
+}
+
+int cg_create_and_attach(const char *controller, const char *path, pid_t pid) {
+ int r, q;
+
+ assert(pid >= 0);
+
+ r = cg_create(controller, path, NULL);
+ if (r < 0)
+ return r;
+
+ q = cg_attach(controller, path, pid);
+ if (q < 0)
+ return q;
+
+ /* This does not remove the cgroup on failure */
+ return r;
+}
diff --git a/src/shared/cgroup-show.c b/src/shared/cgroup-show.c
new file mode 100644
index 0000000000..83cc0731b8
--- /dev/null
+++ b/src/shared/cgroup-show.c
@@ -0,0 +1,300 @@
+/*-*- Mode: C; c-basic-offset: 8; indent-tabs-mode: nil -*-*/
+
+/***
+ This file is part 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 <dirent.h>
+#include <errno.h>
+
+#include "util.h"
+#include "macro.h"
+#include "path-util.h"
+#include "cgroup-util.h"
+#include "cgroup-show.h"
+
+static int compare(const void *a, const void *b) {
+ const pid_t *p = a, *q = b;
+
+ if (*p < *q)
+ return -1;
+ if (*p > *q)
+ return 1;
+ return 0;
+}
+
+static void show_pid_array(int pids[], unsigned n_pids, const char *prefix, unsigned n_columns, bool extra, bool more, bool kernel_threads, OutputFlags flags) {
+ unsigned i, m, pid_width;
+ pid_t biggest = 0;
+
+ /* Filter duplicates */
+ m = 0;
+ for (i = 0; i < n_pids; i++) {
+ unsigned j;
+
+ if (pids[i] > biggest)
+ biggest = pids[i];
+
+ for (j = i+1; j < n_pids; j++)
+ if (pids[i] == pids[j])
+ break;
+
+ if (j >= n_pids)
+ pids[m++] = pids[i];
+ }
+ n_pids = m;
+ pid_width = DECIMAL_STR_WIDTH(biggest);
+
+ /* And sort */
+ qsort(pids, n_pids, sizeof(pid_t), compare);
+
+ 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++) {
+ char *t = NULL;
+
+ get_process_cmdline(pids[i], n_columns, true, &t);
+
+ printf("%s%s%*lu %s\n",
+ prefix,
+ draw_special_char(extra ? DRAW_TRIANGULAR_BULLET :
+ ((more || i < n_pids-1) ? DRAW_TREE_BRANCH : DRAW_TREE_RIGHT)),
+ pid_width,
+ (unsigned long) pids[i],
+ strna(t));
+
+ free(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;
+ char *p = NULL;
+ pid_t pid;
+ int r;
+
+ r = cg_mangle_path(path, &p);
+ if (r < 0)
+ return r;
+
+ fn = strappend(p, "/cgroup.procs");
+ free(p);
+ if (!fn)
+ return -ENOMEM;
+
+ f = fopen(fn, "re");
+ free(fn);
+ if (!f)
+ return -errno;
+
+ while ((r = cg_read_pid(f, &pid)) > 0) {
+
+ if (!kernel_threads && is_kernel_thread(pid) > 0)
+ continue;
+
+ if (n >= n_allocated) {
+ pid_t *npids;
+
+ n_allocated = MAX(16U, n*2U);
+
+ npids = realloc(pids, sizeof(pid_t) * n_allocated);
+ if (!npids)
+ return -ENOMEM;
+
+ pids = npids;
+ }
+
+ assert(n < n_allocated);
+ pids[n++] = pid;
+ }
+
+ if (r < 0)
+ return r;
+
+ if (n > 0)
+ 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, false) > 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),
+ path_get_file_name(last));
+
+ if (!p1) {
+ p1 = strappend(prefix, draw_special_char(DRAW_TREE_VERT));
+ 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),
+ path_get_file_name(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(controller);
+ 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/shared/cgroup-show.h b/src/shared/cgroup-show.h
new file mode 100644
index 0000000000..72bc8a650f
--- /dev/null
+++ b/src/shared/cgroup-show.h
@@ -0,0 +1,36 @@
+/*-*- Mode: C; c-basic-offset: 8; indent-tabs-mode: nil -*-*/
+
+#ifndef foocgroupshowhfoo
+#define foocgroupshowhfoo
+
+/***
+ This file is part of systemd.
+
+ Copyright 2010 Lennart Poettering
+
+ systemd is free software; you can redistribute it and/or modify it
+ under the terms of the GNU Lesser General Public License as published by
+ the Free Software Foundation; either version 2.1 of the License, or
+ (at your option) any later version.
+
+ systemd is distributed in the hope that it will be useful, but
+ WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ Lesser General 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 "util.h"
+#include "logs-show.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);
+
+#endif
diff --git a/src/shared/cgroup-util.c b/src/shared/cgroup-util.c
new file mode 100644
index 0000000000..43c415d760
--- /dev/null
+++ b/src/shared/cgroup-util.c
@@ -0,0 +1,1603 @@
+/*-*- Mode: C; c-basic-offset: 8; indent-tabs-mode: nil -*-*/
+
+/***
+ This file is part 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 <unistd.h>
+#include <signal.h>
+#include <string.h>
+#include <stdlib.h>
+#include <dirent.h>
+#include <sys/stat.h>
+#include <sys/types.h>
+#include <ftw.h>
+
+#include "cgroup-util.h"
+#include "log.h"
+#include "set.h"
+#include "macro.h"
+#include "util.h"
+#include "path-util.h"
+#include "strv.h"
+#include "unit-name.h"
+#include "fileio.h"
+
+int cg_enumerate_processes(const char *controller, const char *path, FILE **_f) {
+ _cleanup_free_ char *fs = NULL;
+ FILE *f;
+ int r;
+
+ assert(_f);
+
+ r = cg_get_path(controller, path, "cgroup.procs", &fs);
+ if (r < 0)
+ return r;
+
+ f = fopen(fs, "re");
+ if (!f)
+ return -errno;
+
+ *_f = f;
+ return 0;
+}
+
+int cg_enumerate_tasks(const char *controller, const char *path, FILE **_f) {
+ _cleanup_free_ char *fs = NULL;
+ FILE *f;
+ int r;
+
+ assert(_f);
+
+ r = cg_get_path(controller, path, "tasks", &fs);
+ if (r < 0)
+ return r;
+
+ f = fopen(fs, "re");
+ if (!f)
+ return -errno;
+
+ *_f = f;
+ return 0;
+}
+
+int cg_read_pid(FILE *f, pid_t *_pid) {
+ unsigned long ul;
+
+ /* Note that the cgroup.procs might contain duplicates! See
+ * cgroups.txt for details. */
+
+ assert(f);
+ assert(_pid);
+
+ errno = 0;
+ if (fscanf(f, "%lu", &ul) != 1) {
+
+ if (feof(f))
+ return 0;
+
+ return errno ? -errno : -EIO;
+ }
+
+ if (ul <= 0)
+ return -EIO;
+
+ *_pid = (pid_t) ul;
+ return 1;
+}
+
+int cg_enumerate_subgroups(const char *controller, const char *path, DIR **_d) {
+ _cleanup_free_ char *fs = NULL;
+ int r;
+ DIR *d;
+
+ assert(_d);
+
+ /* This is not recursive! */
+
+ r = cg_get_path(controller, path, NULL, &fs);
+ if (r < 0)
+ return r;
+
+ d = opendir(fs);
+ if (!d)
+ return -errno;
+
+ *_d = d;
+ return 0;
+}
+
+int cg_read_subgroup(DIR *d, char **fn) {
+ struct dirent *de;
+
+ assert(d);
+ assert(fn);
+
+ FOREACH_DIRENT(de, d, return -errno) {
+ char *b;
+
+ if (de->d_type != DT_DIR)
+ continue;
+
+ if (streq(de->d_name, ".") ||
+ streq(de->d_name, ".."))
+ continue;
+
+ b = strdup(de->d_name);
+ if (!b)
+ return -ENOMEM;
+
+ *fn = b;
+ return 1;
+ }
+
+ return 0;
+}
+
+int cg_rmdir(const char *controller, const char *path, bool honour_sticky) {
+ _cleanup_free_ char *p = NULL;
+ int r;
+
+ r = cg_get_path(controller, path, NULL, &p);
+ if (r < 0)
+ return r;
+
+ if (honour_sticky) {
+ char *tasks;
+
+ /* If the sticky bit is set don't remove the directory */
+
+ tasks = strappend(p, "/tasks");
+ if (!tasks)
+ return -ENOMEM;
+
+ r = file_is_priv_sticky(tasks);
+ free(tasks);
+
+ if (r > 0)
+ return 0;
+ }
+
+ r = rmdir(p);
+ if (r < 0 && errno != ENOENT)
+ return -errno;
+
+ return 0;
+}
+
+int cg_kill(const char *controller, const char *path, int sig, bool sigcont, bool ignore_self, Set *s) {
+ _cleanup_set_free_ Set *allocated_set = NULL;
+ bool done = false;
+ int r, ret = 0;
+ pid_t my_pid;
+
+ assert(sig >= 0);
+
+ /* This goes through the tasks list and kills them all. This
+ * is repeated until no further processes are added to the
+ * tasks list, to properly handle forking processes */
+
+ if (!s) {
+ s = allocated_set = set_new(trivial_hash_func, trivial_compare_func);
+ if (!s)
+ return -ENOMEM;
+ }
+
+ my_pid = getpid();
+
+ do {
+ _cleanup_fclose_ FILE *f = NULL;
+ pid_t pid = 0;
+ done = true;
+
+ r = cg_enumerate_processes(controller, path, &f);
+ if (r < 0) {
+ if (ret >= 0 && r != -ENOENT)
+ return r;
+
+ return ret;
+ }
+
+ while ((r = cg_read_pid(f, &pid)) > 0) {
+
+ if (ignore_self && pid == my_pid)
+ continue;
+
+ if (set_get(s, LONG_TO_PTR(pid)) == LONG_TO_PTR(pid))
+ continue;
+
+ /* If we haven't killed this process yet, kill
+ * it */
+ if (kill(pid, sig) < 0) {
+ if (ret >= 0 && errno != ESRCH)
+ ret = -errno;
+ } else if (ret == 0) {
+
+ if (sigcont)
+ kill(pid, SIGCONT);
+
+ ret = 1;
+ }
+
+ done = false;
+
+ r = set_put(s, LONG_TO_PTR(pid));
+ if (r < 0) {
+ if (ret >= 0)
+ return r;
+
+ return ret;
+ }
+ }
+
+ if (r < 0) {
+ if (ret >= 0)
+ return r;
+
+ return ret;
+ }
+
+ /* To avoid racing against processes which fork
+ * quicker than we can kill them we repeat this until
+ * no new pids need to be killed. */
+
+ } while (!done);
+
+ return ret;
+}
+
+int cg_kill_recursive(const char *controller, const char *path, int sig, bool sigcont, bool ignore_self, bool rem, Set *s) {
+ _cleanup_set_free_ Set *allocated_set = NULL;
+ _cleanup_closedir_ DIR *d = NULL;
+ int r, ret = 0;
+ char *fn;
+
+ assert(path);
+ assert(sig >= 0);
+
+ if (!s) {
+ s = allocated_set = set_new(trivial_hash_func, trivial_compare_func);
+ if (!s)
+ return -ENOMEM;
+ }
+
+ ret = cg_kill(controller, path, sig, sigcont, ignore_self, s);
+
+ r = cg_enumerate_subgroups(controller, path, &d);
+ if (r < 0) {
+ if (ret >= 0 && r != -ENOENT)
+ return r;
+
+ return ret;
+ }
+
+ while ((r = cg_read_subgroup(d, &fn)) > 0) {
+ _cleanup_free_ char *p = NULL;
+
+ p = strjoin(path, "/", fn, NULL);
+ free(fn);
+ if (!p)
+ return -ENOMEM;
+
+ r = cg_kill_recursive(controller, p, sig, sigcont, ignore_self, rem, s);
+ if (ret >= 0 && r != 0)
+ ret = r;
+ }
+
+ if (ret >= 0 && r < 0)
+ ret = r;
+
+ if (rem) {
+ r = cg_rmdir(controller, path, true);
+ if (r < 0 && ret >= 0 && r != -ENOENT && r != -EBUSY)
+ return r;
+ }
+
+ return ret;
+}
+
+int cg_kill_recursive_and_wait(const char *controller, const char *path, bool rem) {
+ unsigned i;
+
+ assert(path);
+
+ /* This safely kills all processes; first it sends a SIGTERM,
+ * then checks 8 times after 200ms whether the group is now
+ * empty, then kills everything that is left with SIGKILL and
+ * finally checks 5 times after 200ms each whether the group
+ * is finally empty. */
+
+ for (i = 0; i < 15; i++) {
+ int sig, r;
+
+ if (i <= 0)
+ sig = SIGTERM;
+ else if (i == 9)
+ sig = SIGKILL;
+ else
+ sig = 0;
+
+ r = cg_kill_recursive(controller, path, sig, true, true, rem, NULL);
+ if (r <= 0)
+ return r;
+
+ usleep(200 * USEC_PER_MSEC);
+ }
+
+ return 0;
+}
+
+int cg_migrate(const char *cfrom, const char *pfrom, const char *cto, const char *pto, bool ignore_self) {
+ bool done = false;
+ _cleanup_set_free_ Set *s = NULL;
+ int r, ret = 0;
+ pid_t my_pid;
+
+ assert(cfrom);
+ assert(pfrom);
+ assert(cto);
+ assert(pto);
+
+ s = set_new(trivial_hash_func, trivial_compare_func);
+ if (!s)
+ return -ENOMEM;
+
+ my_pid = getpid();
+
+ do {
+ _cleanup_fclose_ FILE *f = NULL;
+ pid_t pid = 0;
+ done = true;
+
+ r = cg_enumerate_tasks(cfrom, pfrom, &f);
+ if (r < 0) {
+ if (ret >= 0 && r != -ENOENT)
+ return r;
+
+ return ret;
+ }
+
+ while ((r = cg_read_pid(f, &pid)) > 0) {
+
+ /* This might do weird stuff if we aren't a
+ * single-threaded program. However, we
+ * luckily know we are not */
+ if (ignore_self && pid == my_pid)
+ continue;
+
+ if (set_get(s, LONG_TO_PTR(pid)) == LONG_TO_PTR(pid))
+ continue;
+
+ r = cg_attach(cto, pto, pid);
+ if (r < 0) {
+ if (ret >= 0 && r != -ESRCH)
+ ret = r;
+ } else if (ret == 0)
+ ret = 1;
+
+ done = false;
+
+ r = set_put(s, LONG_TO_PTR(pid));
+ if (r < 0) {
+ if (ret >= 0)
+ return r;
+
+ return ret;
+ }
+ }
+
+ if (r < 0) {
+ if (ret >= 0)
+ return r;
+
+ return ret;
+ }
+ } while (!done);
+
+ return ret;
+}
+
+int cg_migrate_recursive(const char *cfrom, const char *pfrom, const char *cto, const char *pto, bool ignore_self, bool rem) {
+ _cleanup_closedir_ DIR *d = NULL;
+ int r, ret = 0;
+ char *fn;
+
+ assert(cfrom);
+ assert(pfrom);
+ assert(cto);
+ assert(pto);
+
+ ret = cg_migrate(cfrom, pfrom, cto, pto, ignore_self);
+
+ r = cg_enumerate_subgroups(cfrom, pfrom, &d);
+ if (r < 0) {
+ if (ret >= 0 && r != -ENOENT)
+ return r;
+
+ return ret;
+ }
+
+ while ((r = cg_read_subgroup(d, &fn)) > 0) {
+ _cleanup_free_ char *p = NULL;
+
+ p = strjoin(pfrom, "/", fn, NULL);
+ free(fn);
+ if (!p) {
+ if (ret >= 0)
+ return -ENOMEM;
+
+ return ret;
+ }
+
+ r = cg_migrate_recursive(cfrom, p, cto, pto, ignore_self, rem);
+ if (r != 0 && ret >= 0)
+ ret = r;
+ }
+
+ if (r < 0 && ret >= 0)
+ ret = r;
+
+ if (rem) {
+ r = cg_rmdir(cfrom, pfrom, true);
+ if (r < 0 && ret >= 0 && r != -ENOENT && r != -EBUSY)
+ return r;
+ }
+
+ return ret;
+}
+
+static const char *normalize_controller(const char *controller) {
+
+ assert(controller);
+
+ if (streq(controller, SYSTEMD_CGROUP_CONTROLLER))
+ return "systemd";
+ else if (startswith(controller, "name="))
+ return controller + 5;
+ else
+ return controller;
+}
+
+static int join_path(const char *controller, const char *path, const char *suffix, char **fs) {
+ char *t = NULL;
+
+ if (controller) {
+ if (path && suffix)
+ t = strjoin("/sys/fs/cgroup/", controller, "/", path, "/", suffix, NULL);
+ else if (path)
+ t = strjoin("/sys/fs/cgroup/", controller, "/", path, NULL);
+ else if (suffix)
+ t = strjoin("/sys/fs/cgroup/", controller, "/", suffix, NULL);
+ else
+ t = strappend("/sys/fs/cgroup/", controller);
+ } else {
+ if (path && suffix)
+ t = strjoin(path, "/", suffix, NULL);
+ else if (path)
+ t = strdup(path);
+ else
+ return -EINVAL;
+ }
+
+ if (!t)
+ return -ENOMEM;
+
+ path_kill_slashes(t);
+
+ *fs = t;
+ return 0;
+}
+
+int cg_get_path(const char *controller, const char *path, const char *suffix, char **fs) {
+ const char *p;
+ static __thread bool good = false;
+
+ assert(fs);
+
+ if (controller && !cg_controller_is_valid(controller, true))
+ return -EINVAL;
+
+ if (_unlikely_(!good)) {
+ int r;
+
+ r = path_is_mount_point("/sys/fs/cgroup", false);
+ if (r <= 0)
+ return r < 0 ? r : -ENOENT;
+
+ /* Cache this to save a few stat()s */
+ good = true;
+ }
+
+ p = controller ? normalize_controller(controller) : NULL;
+
+ return join_path(p, path, suffix, fs);
+}
+
+static int check_hierarchy(const char *p) {
+ char *cc;
+
+ assert(p);
+
+ /* Check if this controller actually really exists */
+ cc = alloca(sizeof("/sys/fs/cgroup/") + strlen(p));
+ strcpy(stpcpy(cc, "/sys/fs/cgroup/"), p);
+ if (access(cc, F_OK) < 0)
+ return -errno;
+
+ return 0;
+}
+
+int cg_get_path_and_check(const char *controller, const char *path, const char *suffix, char **fs) {
+ const char *p;
+ int r;
+
+ assert(fs);
+
+ if (!cg_controller_is_valid(controller, true))
+ return -EINVAL;
+
+ /* Normalize the controller syntax */
+ p = normalize_controller(controller);
+
+ /* Check if this controller actually really exists */
+ r = check_hierarchy(p);
+ if (r < 0)
+ return r;
+
+ return join_path(p, path, suffix, fs);
+}
+
+static int trim_cb(const char *path, const struct stat *sb, int typeflag, struct FTW *ftwbuf) {
+ char *p;
+ bool is_sticky;
+
+ if (typeflag != FTW_DP)
+ return 0;
+
+ if (ftwbuf->level < 1)
+ return 0;
+
+ p = strappend(path, "/tasks");
+ if (!p) {
+ errno = ENOMEM;
+ return 1;
+ }
+
+ is_sticky = file_is_priv_sticky(p) > 0;
+ free(p);
+
+ if (is_sticky)
+ return 0;
+
+ rmdir(path);
+ return 0;
+}
+
+int cg_trim(const char *controller, const char *path, bool delete_root) {
+ _cleanup_free_ char *fs = NULL;
+ int r = 0;
+
+ assert(path);
+
+ r = cg_get_path(controller, path, NULL, &fs);
+ if (r < 0)
+ return r;
+
+ errno = 0;
+ if (nftw(fs, trim_cb, 64, FTW_DEPTH|FTW_MOUNT|FTW_PHYS) != 0)
+ r = errno ? -errno : -EIO;
+
+ if (delete_root) {
+ bool is_sticky;
+ char *p;
+
+ p = strappend(fs, "/tasks");
+ if (!p)
+ return -ENOMEM;
+
+ is_sticky = file_is_priv_sticky(p) > 0;
+ free(p);
+
+ if (!is_sticky)
+ if (rmdir(fs) < 0 && errno != ENOENT && r == 0)
+ return -errno;
+ }
+
+ return r;
+}
+
+int cg_delete(const char *controller, const char *path) {
+ _cleanup_free_ char *parent = NULL;
+ int r;
+
+ assert(path);
+
+ r = path_get_parent(path, &parent);
+ if (r < 0)
+ return r;
+
+ r = cg_migrate_recursive(controller, path, controller, parent, false, true);
+ return r == -ENOENT ? 0 : r;
+}
+
+int cg_attach(const char *controller, const char *path, pid_t pid) {
+ _cleanup_free_ char *fs = NULL;
+ char c[DECIMAL_STR_MAX(pid_t) + 2];
+ int r;
+
+ assert(path);
+ assert(pid >= 0);
+
+ r = cg_get_path_and_check(controller, path, "tasks", &fs);
+ if (r < 0)
+ return r;
+
+ if (pid == 0)
+ pid = getpid();
+
+ snprintf(c, sizeof(c), "%lu\n", (unsigned long) pid);
+
+ return write_string_file(fs, c);
+}
+
+int cg_set_group_access(
+ const char *controller,
+ const char *path,
+ mode_t mode,
+ uid_t uid,
+ gid_t gid) {
+
+ _cleanup_free_ char *fs = NULL;
+ int r;
+
+ assert(path);
+
+ if (mode != (mode_t) -1)
+ mode &= 0777;
+
+ r = cg_get_path(controller, path, NULL, &fs);
+ if (r < 0)
+ return r;
+
+ return chmod_and_chown(fs, mode, uid, gid);
+}
+
+int cg_set_task_access(
+ const char *controller,
+ const char *path,
+ mode_t mode,
+ uid_t uid,
+ gid_t gid,
+ int sticky) {
+
+ _cleanup_free_ char *fs = NULL, *procs = NULL;
+ int r;
+
+ assert(path);
+
+ if (mode == (mode_t) -1 && uid == (uid_t) -1 && gid == (gid_t) -1 && sticky < 0)
+ return 0;
+
+ if (mode != (mode_t) -1)
+ mode &= 0666;
+
+ r = cg_get_path(controller, path, "tasks", &fs);
+ if (r < 0)
+ return r;
+
+ if (sticky >= 0 && mode != (mode_t) -1)
+ /* Both mode and sticky param are passed */
+ mode |= (sticky ? S_ISVTX : 0);
+ else if ((sticky >= 0 && mode == (mode_t) -1) ||
+ (mode != (mode_t) -1 && sticky < 0)) {
+ struct stat st;
+
+ /* Only one param is passed, hence read the current
+ * mode from the file itself */
+
+ r = lstat(fs, &st);
+ if (r < 0)
+ return -errno;
+
+ if (mode == (mode_t) -1)
+ /* No mode set, we just shall set the sticky bit */
+ mode = (st.st_mode & ~S_ISVTX) | (sticky ? S_ISVTX : 0);
+ else
+ /* Only mode set, leave sticky bit untouched */
+ mode = (st.st_mode & ~0777) | mode;
+ }
+
+ r = chmod_and_chown(fs, mode, uid, gid);
+ if (r < 0)
+ return r;
+
+ /* Always keep values for "cgroup.procs" in sync with "tasks" */
+ r = cg_get_path(controller, path, "cgroup.procs", &procs);
+ if (r < 0)
+ return r;
+
+ return chmod_and_chown(procs, mode, uid, gid);
+}
+
+int cg_pid_get_path(const char *controller, pid_t pid, char **path) {
+ _cleanup_fclose_ FILE *f = NULL;
+ char line[LINE_MAX];
+ const char *fs;
+ size_t cs;
+
+ assert(path);
+ assert(pid >= 0);
+
+ if (controller) {
+ if (!cg_controller_is_valid(controller, true))
+ return -EINVAL;
+
+ controller = normalize_controller(controller);
+ } else
+ controller = SYSTEMD_CGROUP_CONTROLLER;
+
+ if (pid == 0)
+ fs = "/proc/self/cgroup";
+ else
+ fs = procfs_file_alloca(pid, "cgroup");
+
+ f = fopen(fs, "re");
+ if (!f)
+ return errno == ENOENT ? -ESRCH : -errno;
+
+ cs = strlen(controller);
+
+ FOREACH_LINE(line, f, return -errno) {
+ char *l, *p, *w, *e;
+ size_t k;
+ char *state;
+ bool found = false;
+
+ truncate_nl(line);
+
+ l = strchr(line, ':');
+ if (!l)
+ continue;
+
+ l++;
+ e = strchr(l, ':');
+ if (!e)
+ continue;
+
+ *e = 0;
+
+ FOREACH_WORD_SEPARATOR(w, k, l, ",", state) {
+
+ if (k == cs && memcmp(w, controller, cs) == 0) {
+ found = true;
+ break;
+ }
+
+ if (k == 5 + cs &&
+ memcmp(w, "name=", 5) == 0 &&
+ memcmp(w+5, controller, cs) == 0) {
+ found = true;
+ break;
+ }
+ }
+
+ if (!found)
+ continue;
+
+ p = strdup(e + 1);
+ if (!p)
+ return -ENOMEM;
+
+ *path = p;
+ return 0;
+ }
+
+ return -ENOENT;
+}
+
+int cg_install_release_agent(const char *controller, const char *agent) {
+ _cleanup_free_ char *fs = NULL, *contents = NULL;
+ char *sc;
+ int r;
+
+ assert(agent);
+
+ r = cg_get_path(controller, NULL, "release_agent", &fs);
+ if (r < 0)
+ return r;
+
+ r = read_one_line_file(fs, &contents);
+ if (r < 0)
+ return r;
+
+ sc = strstrip(contents);
+ if (sc[0] == 0) {
+ r = write_string_file(fs, agent);
+ if (r < 0)
+ return r;
+ } else if (!streq(sc, agent))
+ return -EEXIST;
+
+ free(fs);
+ fs = NULL;
+ r = cg_get_path(controller, NULL, "notify_on_release", &fs);
+ if (r < 0)
+ return r;
+
+ free(contents);
+ contents = NULL;
+ r = read_one_line_file(fs, &contents);
+ if (r < 0)
+ return r;
+
+ sc = strstrip(contents);
+ if (streq(sc, "0")) {
+ r = write_string_file(fs, "1");
+ if (r < 0)
+ return r;
+
+ return 1;
+ }
+
+ if (!streq(sc, "1"))
+ return -EIO;
+
+ return 0;
+}
+
+int cg_is_empty(const char *controller, const char *path, bool ignore_self) {
+ _cleanup_fclose_ FILE *f = NULL;
+ pid_t pid = 0, self_pid;
+ bool found = false;
+ int r;
+
+ assert(path);
+
+ r = cg_enumerate_tasks(controller, path, &f);
+ if (r < 0)
+ return r == -ENOENT ? 1 : r;
+
+ self_pid = getpid();
+
+ while ((r = cg_read_pid(f, &pid)) > 0) {
+
+ if (ignore_self && pid == self_pid)
+ continue;
+
+ found = true;
+ break;
+ }
+
+ if (r < 0)
+ return r;
+
+ return !found;
+}
+
+int cg_is_empty_by_spec(const char *spec, bool ignore_self) {
+ _cleanup_free_ char *controller = NULL, *path = NULL;
+ int r;
+
+ assert(spec);
+
+ r = cg_split_spec(spec, &controller, &path);
+ if (r < 0)
+ return r;
+
+ return cg_is_empty(controller, path, ignore_self);
+}
+
+int cg_is_empty_recursive(const char *controller, const char *path, bool ignore_self) {
+ _cleanup_closedir_ DIR *d = NULL;
+ char *fn;
+ int r;
+
+ assert(path);
+
+ r = cg_is_empty(controller, path, ignore_self);
+ if (r <= 0)
+ return r;
+
+ r = cg_enumerate_subgroups(controller, path, &d);
+ if (r < 0)
+ return r == -ENOENT ? 1 : r;
+
+ while ((r = cg_read_subgroup(d, &fn)) > 0) {
+ _cleanup_free_ char *p = NULL;
+
+ p = strjoin(path, "/", fn, NULL);
+ free(fn);
+ if (!p)
+ return -ENOMEM;
+
+ r = cg_is_empty_recursive(controller, p, ignore_self);
+ if (r <= 0)
+ return r;
+ }
+
+ if (r < 0)
+ return r;
+
+ return 1;
+}
+
+int cg_split_spec(const char *spec, char **controller, char **path) {
+ const char *e;
+ char *t = NULL, *u = NULL;
+ _cleanup_free_ char *v = NULL;
+
+ assert(spec);
+
+ if (*spec == '/') {
+ if (!path_is_safe(spec))
+ return -EINVAL;
+
+ if (path) {
+ t = strdup(spec);
+ if (!t)
+ return -ENOMEM;
+
+ path_kill_slashes(t);
+ *path = t;
+ }
+
+ if (controller)
+ *controller = NULL;
+
+ return 0;
+ }
+
+ e = strchr(spec, ':');
+ if (!e) {
+ if (!cg_controller_is_valid(spec, true))
+ return -EINVAL;
+
+ if (controller) {
+ t = strdup(normalize_controller(spec));
+ if (!t)
+ return -ENOMEM;
+
+ *controller = t;
+ }
+
+ if (path)
+ *path = NULL;
+
+ return 0;
+ }
+
+ v = strndup(spec, e-spec);
+ if (!v)
+ return -ENOMEM;
+ t = strdup(normalize_controller(v));
+ if (!t)
+ return -ENOMEM;
+ if (!cg_controller_is_valid(t, true)) {
+ free(t);
+ return -EINVAL;
+ }
+
+ u = strdup(e+1);
+ if (!u) {
+ free(t);
+ return -ENOMEM;
+ }
+ if (!path_is_safe(u) ||
+ !path_is_absolute(u)) {
+ free(t);
+ free(u);
+ return -EINVAL;
+ }
+
+ path_kill_slashes(u);
+
+ if (controller)
+ *controller = t;
+ else
+ free(t);
+
+ if (path)
+ *path = u;
+ else
+ free(u);
+
+ return 0;
+}
+
+int cg_join_spec(const char *controller, const char *path, char **spec) {
+ char *s;
+
+ assert(path);
+
+ if (!controller)
+ controller = "systemd";
+ else {
+ if (!cg_controller_is_valid(controller, true))
+ return -EINVAL;
+
+ controller = normalize_controller(controller);
+ }
+
+ if (!path_is_absolute(path))
+ return -EINVAL;
+
+ s = strjoin(controller, ":", path, NULL);
+ if (!s)
+ return -ENOMEM;
+
+ path_kill_slashes(s + strlen(controller) + 1);
+
+ *spec = s;
+ return 0;
+}
+
+int cg_mangle_path(const char *path, char **result) {
+ _cleanup_free_ char *c = NULL, *p = NULL;
+ char *t;
+ int r;
+
+ assert(path);
+ assert(result);
+
+ /* First check if it already is a filesystem path */
+ if (path_startswith(path, "/sys/fs/cgroup")) {
+
+ t = strdup(path);
+ if (!t)
+ return -ENOMEM;
+
+ path_kill_slashes(t);
+ *result = t;
+ return 0;
+ }
+
+ /* Otherwise treat it as cg spec */
+ r = cg_split_spec(path, &c, &p);
+ if (r < 0)
+ return r;
+
+ return cg_get_path(c ? c : SYSTEMD_CGROUP_CONTROLLER, p ? p : "/", NULL, result);
+}
+
+int cg_get_system_path(char **path) {
+ char *p;
+ int r;
+
+ assert(path);
+
+ r = cg_pid_get_path(SYSTEMD_CGROUP_CONTROLLER, 1, &p);
+ if (r < 0) {
+ p = strdup("/system");
+ if (!p)
+ return -ENOMEM;
+ }
+
+ if (endswith(p, "/system"))
+ *path = p;
+ else {
+ char *q;
+
+ q = strappend(p, "/system");
+ free(p);
+ if (!q)
+ return -ENOMEM;
+
+ *path = q;
+ }
+
+ return 0;
+}
+
+int cg_get_root_path(char **path) {
+ char *root, *e;
+ int r;
+
+ assert(path);
+
+ r = cg_pid_get_path(SYSTEMD_CGROUP_CONTROLLER, 1, &root);
+ if (r < 0)
+ return r;
+
+ e = endswith(root, "/system");
+ if (e == root)
+ e[1] = 0;
+ else if (e)
+ *e = 0;
+
+ *path = root;
+ return 0;
+}
+
+int cg_get_user_path(char **path) {
+ _cleanup_free_ char *root = NULL;
+ char *p;
+
+ assert(path);
+
+ /* Figure out the place to put user cgroups below. We use the
+ * same as PID 1 has but with the "/system" suffix replaced by
+ * "/user" */
+
+ if (cg_get_root_path(&root) < 0 || streq(root, "/"))
+ p = strdup("/user");
+ else
+ p = strappend(root, "/user");
+
+ if (!p)
+ return -ENOMEM;
+
+ *path = p;
+ return 0;
+}
+
+int cg_get_machine_path(const char *machine, char **path) {
+ _cleanup_free_ char *root = NULL, *escaped = NULL;
+ char *p;
+
+ assert(path);
+
+ if (machine) {
+ const char *name = strappenda(machine, ".nspawn");
+
+ escaped = cg_escape(name);
+ if (!escaped)
+ return -ENOMEM;
+ }
+
+ p = strjoin(cg_get_root_path(&root) >= 0 && !streq(root, "/") ? root : "",
+ "/machine", machine ? "/" : "", machine ? escaped : "", NULL);
+ if (!p)
+ return -ENOMEM;
+
+ *path = p;
+ return 0;
+}
+
+char **cg_shorten_controllers(char **controllers) {
+ char **f, **t;
+
+ if (!controllers)
+ return controllers;
+
+ for (f = controllers, t = controllers; *f; f++) {
+ const char *p;
+ int r;
+
+ p = normalize_controller(*f);
+
+ if (streq(p, "systemd")) {
+ free(*f);
+ continue;
+ }
+
+ if (!cg_controller_is_valid(p, true)) {
+ log_warning("Controller %s is not valid, removing from controllers list.", p);
+ free(*f);
+ continue;
+ }
+
+ r = check_hierarchy(p);
+ if (r < 0) {
+ log_debug("Controller %s is not available, removing from controllers list.", p);
+ free(*f);
+ continue;
+ }
+
+ *(t++) = *f;
+ }
+
+ *t = NULL;
+ return strv_uniq(controllers);
+}
+
+int cg_pid_get_path_shifted(pid_t pid, char **root, char **cgroup) {
+ _cleanup_free_ char *cg_root = NULL;
+ char *cg_process, *p;
+ int r;
+
+ r = cg_get_root_path(&cg_root);
+ if (r < 0)
+ return r;
+
+ r = cg_pid_get_path(SYSTEMD_CGROUP_CONTROLLER, pid, &cg_process);
+ if (r < 0)
+ return r;
+
+ p = path_startswith(cg_process, cg_root);
+ if (p)
+ p--;
+ else
+ p = cg_process;
+
+ if (cgroup) {
+ char* c;
+
+ c = strdup(p);
+ if (!c) {
+ free(cg_process);
+ return -ENOMEM;
+ }
+
+ *cgroup = c;
+ }
+
+ if (root) {
+ cg_process[p-cg_process] = 0;
+ *root = cg_process;
+ } else
+ free(cg_process);
+
+ return 0;
+}
+
+int cg_path_decode_unit(const char *cgroup, char **unit){
+ char *p, *e, *c, *s, *k;
+
+ assert(cgroup);
+ assert(unit);
+
+ e = strchrnul(cgroup, '/');
+ c = strndupa(cgroup, e - cgroup);
+ c = cg_unescape(c);
+
+ /* Could this be a valid unit name? */
+ if (!unit_name_is_valid(c, true))
+ return -EINVAL;
+
+ if (!unit_name_is_template(c))
+ s = strdup(c);
+ else {
+ if (*e != '/')
+ return -EINVAL;
+
+ e += strspn(e, "/");
+
+ p = strchrnul(e, '/');
+ k = strndupa(e, p - e);
+ k = cg_unescape(k);
+
+ if (!unit_name_is_valid(k, false))
+ return -EINVAL;
+
+ s = strdup(k);
+ }
+
+ if (!s)
+ return -ENOMEM;
+
+ *unit = s;
+ return 0;
+}
+
+int cg_path_get_unit(const char *path, char **unit) {
+ const char *e;
+
+ assert(path);
+ assert(unit);
+
+ e = path_startswith(path, "/system/");
+ if (!e)
+ return -ENOENT;
+
+ return cg_path_decode_unit(e, unit);
+}
+
+int cg_pid_get_unit(pid_t pid, char **unit) {
+ _cleanup_free_ char *cgroup = NULL;
+ int r;
+
+ assert(unit);
+
+ r = cg_pid_get_path_shifted(pid, NULL, &cgroup);
+ if (r < 0)
+ return r;
+
+ return cg_path_get_unit(cgroup, unit);
+}
+
+_pure_ static const char *skip_label(const char *e) {
+ assert(e);
+
+ e = strchr(e, '/');
+ if (!e)
+ return NULL;
+
+ e += strspn(e, "/");
+ return e;
+}
+
+int cg_path_get_user_unit(const char *path, char **unit) {
+ const char *e;
+
+ assert(path);
+ assert(unit);
+
+ /* We always have to parse the path from the beginning as unit
+ * cgroups might have arbitrary child cgroups and we shouldn't get
+ * confused by those */
+
+ e = path_startswith(path, "/user/");
+ if (!e)
+ return -ENOENT;
+
+ /* Skip the user name */
+ e = skip_label(e);
+ if (!e)
+ return -ENOENT;
+
+ /* Skip the session ID */
+ e = skip_label(e);
+ if (!e)
+ return -ENOENT;
+
+ /* Skip the systemd cgroup */
+ e = skip_label(e);
+ if (!e)
+ return -ENOENT;
+
+ return cg_path_decode_unit(e, unit);
+}
+
+int cg_pid_get_user_unit(pid_t pid, char **unit) {
+ _cleanup_free_ char *cgroup = NULL;
+ int r;
+
+ assert(unit);
+
+ r = cg_pid_get_path_shifted(pid, NULL, &cgroup);
+ if (r < 0)
+ return r;
+
+ return cg_path_get_user_unit(cgroup, unit);
+}
+
+int cg_path_get_machine_name(const char *path, char **machine) {
+ const char *e, *n;
+ char *s, *r;
+
+ assert(path);
+ assert(machine);
+
+ e = path_startswith(path, "/machine/");
+ if (!e)
+ return -ENOENT;
+
+ n = strchrnul(e, '/');
+ if (e == n)
+ return -ENOENT;
+
+ s = strndupa(e, n - e);
+
+ r = strdup(cg_unescape(s));
+ if (!r)
+ return -ENOMEM;
+
+ *machine = r;
+ return 0;
+}
+
+int cg_pid_get_machine_name(pid_t pid, char **machine) {
+ _cleanup_free_ char *cgroup = NULL;
+ int r;
+
+ assert(machine);
+
+ r = cg_pid_get_path_shifted(pid, NULL, &cgroup);
+ if (r < 0)
+ return r;
+
+ return cg_path_get_machine_name(cgroup, machine);
+}
+
+int cg_path_get_session(const char *path, char **session) {
+ const char *e, *n;
+ char *s;
+
+ assert(path);
+ assert(session);
+
+ e = path_startswith(path, "/user/");
+ if (!e)
+ return -ENOENT;
+
+ /* Skip the user name */
+ e = skip_label(e);
+ if (!e)
+ return -ENOENT;
+
+ n = strchrnul(e, '/');
+ if (n - e < 8)
+ return -ENOENT;
+ if (memcmp(n - 8, ".session", 8) != 0)
+ return -ENOENT;
+
+ s = strndup(e, n - e - 8);
+ if (!s)
+ return -ENOMEM;
+
+ *session = s;
+ return 0;
+}
+
+int cg_pid_get_session(pid_t pid, char **session) {
+ _cleanup_free_ char *cgroup = NULL;
+ int r;
+
+ assert(session);
+
+ r = cg_pid_get_path_shifted(pid, NULL, &cgroup);
+ if (r < 0)
+ return r;
+
+ return cg_path_get_session(cgroup, session);
+}
+
+int cg_path_get_owner_uid(const char *path, uid_t *uid) {
+ const char *e, *n;
+ char *s;
+
+ assert(path);
+ assert(uid);
+
+ e = path_startswith(path, "/user/");
+ if (!e)
+ return -ENOENT;
+
+ n = strchrnul(e, '/');
+ if (n - e < 5)
+ return -ENOENT;
+ if (memcmp(n - 5, ".user", 5) != 0)
+ return -ENOENT;
+
+ s = strndupa(e, n - e - 5);
+ if (!s)
+ return -ENOMEM;
+
+ return parse_uid(s, uid);
+}
+
+int cg_pid_get_owner_uid(pid_t pid, uid_t *uid) {
+ _cleanup_free_ char *cgroup = NULL;
+ int r;
+
+ assert(uid);
+
+ r = cg_pid_get_path_shifted(pid, NULL, &cgroup);
+ if (r < 0)
+ return r;
+
+ return cg_path_get_owner_uid(cgroup, uid);
+}
+
+int cg_controller_from_attr(const char *attr, char **controller) {
+ const char *dot;
+ char *c;
+
+ assert(attr);
+ assert(controller);
+
+ if (!filename_is_safe(attr))
+ return -EINVAL;
+
+ dot = strchr(attr, '.');
+ if (!dot) {
+ *controller = NULL;
+ return 0;
+ }
+
+ c = strndup(attr, dot - attr);
+ if (!c)
+ return -ENOMEM;
+
+ if (!cg_controller_is_valid(c, false)) {
+ free(c);
+ return -EINVAL;
+ }
+
+ *controller = c;
+ return 1;
+}
+
+char *cg_escape(const char *p) {
+ bool need_prefix = false;
+
+ /* This implements very minimal escaping for names to be used
+ * as file names in the cgroup tree: any name which might
+ * conflict with a kernel name or is prefixed with '_' is
+ * prefixed with a '_'. That way, when reading cgroup names it
+ * is sufficient to remove a single prefixing underscore if
+ * there is one. */
+
+ /* The return value of this function (unlike cg_unescape())
+ * needs free()! */
+
+ if (p[0] == 0 ||
+ p[0] == '_' ||
+ p[0] == '.' ||
+ streq(p, "notify_on_release") ||
+ streq(p, "release_agent") ||
+ streq(p, "tasks"))
+ need_prefix = true;
+ else {
+ const char *dot;
+
+ dot = strrchr(p, '.');
+ if (dot) {
+
+ if (dot - p == 6 && memcmp(p, "cgroup", 6) == 0)
+ need_prefix = true;
+ else {
+ char *n;
+
+ n = strndupa(p, dot - p);
+
+ if (check_hierarchy(n) >= 0)
+ need_prefix = true;
+ }
+ }
+ }
+
+ if (need_prefix)
+ return strappend("_", p);
+ else
+ return strdup(p);
+}
+
+char *cg_unescape(const char *p) {
+ assert(p);
+
+ /* The return value of this function (unlike cg_escape())
+ * doesn't need free()! */
+
+ if (p[0] == '_')
+ return (char*) p+1;
+
+ return (char*) p;
+}
+
+#define CONTROLLER_VALID \
+ "0123456789" \
+ "abcdefghijklmnopqrstuvwxyz" \
+ "ABCDEFGHIJKLMNOPQRSTUVWXYZ" \
+ "_"
+
+bool cg_controller_is_valid(const char *p, bool allow_named) {
+ const char *t, *s;
+
+ if (!p)
+ return false;
+
+ if (allow_named) {
+ s = startswith(p, "name=");
+ if (s)
+ p = s;
+ }
+
+ if (*p == 0 || *p == '_')
+ return false;
+
+ for (t = p; *t; t++)
+ if (!strchr(CONTROLLER_VALID, *t))
+ return false;
+
+ if (t - p > FILENAME_MAX)
+ return false;
+
+ return true;
+}
diff --git a/src/shared/cgroup-util.h b/src/shared/cgroup-util.h
new file mode 100644
index 0000000000..25dd277ba5
--- /dev/null
+++ b/src/shared/cgroup-util.h
@@ -0,0 +1,115 @@
+/*-*- Mode: C; c-basic-offset: 8; indent-tabs-mode: nil -*-*/
+
+#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 <sys/types.h>
+#include <stdio.h>
+#include <dirent.h>
+
+#include "set.h"
+#include "def.h"
+
+/*
+ * General rules:
+ *
+ * We accept named hierarchies in the syntax "foo" and "name=foo".
+ *
+ * We expect that named hierarchies do not conflict in name with a
+ * kernel hierarchy, modulo the "name=" prefix.
+ *
+ * We always generate "normalized" controller names, i.e. without the
+ * "name=" prefix.
+ *
+ * We require absolute cgroup paths. When returning, we will always
+ * generate paths with multiple adjacent / removed.
+ */
+
+int cg_enumerate_processes(const char *controller, const char *path, FILE **_f);
+int cg_enumerate_tasks(const char *controller, const char *path, FILE **_f);
+int cg_read_pid(FILE *f, pid_t *_pid);
+
+int cg_enumerate_subgroups(const char *controller, const char *path, DIR **_d);
+int cg_read_subgroup(DIR *d, char **fn);
+
+int cg_kill(const char *controller, const char *path, int sig, bool sigcont, bool ignore_self, Set *s);
+int cg_kill_recursive(const char *controller, const char *path, int sig, bool sigcont, bool ignore_self, bool remove, Set *s);
+int cg_kill_recursive_and_wait(const char *controller, const char *path, bool remove);
+
+int cg_migrate(const char *cfrom, const char *pfrom, const char *cto, const char *pto, bool ignore_self);
+int cg_migrate_recursive(const char *cfrom, const char *pfrom, const char *cto, const char *pto, bool ignore_self, bool remove);
+
+int cg_split_spec(const char *spec, char **controller, char **path);
+int cg_join_spec(const char *controller, const char *path, char **spec);
+int cg_mangle_path(const char *path, char **result);
+
+int cg_get_path(const char *controller, const char *path, const char *suffix, char **fs);
+int cg_get_path_and_check(const char *controller, const char *path, const char *suffix, char **fs);
+
+int cg_pid_get_path(const char *controller, pid_t pid, char **path);
+
+int cg_trim(const char *controller, const char *path, bool delete_root);
+
+int cg_rmdir(const char *controller, const char *path, bool honour_sticky);
+int cg_delete(const char *controller, const char *path);
+
+int cg_create(const char *controller, const char *path, const char *suffix);
+int cg_attach(const char *controller, const char *path, pid_t pid);
+int cg_create_and_attach(const char *controller, const char *path, pid_t pid);
+
+int cg_set_group_access(const char *controller, const char *path, mode_t mode, uid_t uid, gid_t gid);
+int cg_set_task_access(const char *controller, const char *path, mode_t mode, uid_t uid, gid_t gid, int sticky);
+
+int cg_install_release_agent(const char *controller, const char *agent);
+
+int cg_is_empty(const char *controller, const char *path, bool ignore_self);
+int cg_is_empty_by_spec(const char *spec, bool ignore_self);
+int cg_is_empty_recursive(const char *controller, const char *path, bool ignore_self);
+
+int cg_get_root_path(char **path);
+int cg_get_system_path(char **path);
+int cg_get_user_path(char **path);
+int cg_get_machine_path(const char *machine, char **path);
+
+int cg_path_get_session(const char *path, char **session);
+int cg_path_get_owner_uid(const char *path, uid_t *uid);
+int cg_path_get_unit(const char *path, char **unit);
+int cg_path_get_user_unit(const char *path, char **unit);
+int cg_path_get_machine_name(const char *path, char **machine);
+
+int cg_pid_get_path_shifted(pid_t pid, char **root, char **cgroup);
+
+int cg_pid_get_session(pid_t pid, char **session);
+int cg_pid_get_owner_uid(pid_t pid, uid_t *uid);
+int cg_pid_get_unit(pid_t pid, char **unit);
+int cg_pid_get_user_unit(pid_t pid, char **unit);
+int cg_pid_get_machine_name(pid_t pid, char **machine);
+
+int cg_path_decode_unit(const char *cgroup, char **unit);
+
+char **cg_shorten_controllers(char **controllers);
+
+int cg_controller_from_attr(const char *attr, char **controller);
+
+char *cg_escape(const char *p);
+char *cg_unescape(const char *p) _pure_;
+
+bool cg_controller_is_valid(const char *p, bool allow_named);
diff --git a/src/shared/conf-files.c b/src/shared/conf-files.c
new file mode 100644
index 0000000000..6d99739353
--- /dev/null
+++ b/src/shared/conf-files.c
@@ -0,0 +1,178 @@
+/*-*- Mode: C; c-basic-offset: 8; indent-tabs-mode: nil -*-*/
+
+/***
+ This file is part 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 <assert.h>
+#include <string.h>
+#include <unistd.h>
+#include <errno.h>
+#include <stdlib.h>
+#include <stdio.h>
+#include <sys/stat.h>
+#include <dirent.h>
+
+#include "macro.h"
+#include "util.h"
+#include "missing.h"
+#include "log.h"
+#include "strv.h"
+#include "path-util.h"
+#include "hashmap.h"
+#include "conf-files.h"
+
+static int files_add(Hashmap *h, const char *root, const char *path, const char *suffix) {
+ _cleanup_closedir_ DIR *dir = NULL;
+ _cleanup_free_ char *dirpath = NULL;
+
+ if (asprintf(&dirpath, "%s%s", root ? root : "", path) < 0)
+ return -ENOMEM;
+
+ dir = opendir(dirpath);
+ if (!dir) {
+ if (errno == ENOENT)
+ return 0;
+ return -errno;
+ }
+
+ for (;;) {
+ struct dirent *de;
+ union dirent_storage buf;
+ char *p;
+ int r;
+
+ r = readdir_r(dir, &buf.de, &de);
+ if (r != 0)
+ return -r;
+
+ if (!de)
+ break;
+
+ if (!dirent_is_file_with_suffix(de, suffix))
+ continue;
+
+ p = strjoin(dirpath, "/", de->d_name, NULL);
+ if (!p)
+ return -ENOMEM;
+
+ r = hashmap_put(h, path_get_file_name(p), p);
+ if (r == -EEXIST) {
+ log_debug("Skipping overridden file: %s.", p);
+ free(p);
+ } else if (r < 0) {
+ free(p);
+ return r;
+ } else if (r == 0) {
+ log_debug("Duplicate file %s", p);
+ free(p);
+ }
+ }
+
+ return 0;
+}
+
+static int base_cmp(const void *a, const void *b) {
+ const char *s1, *s2;
+
+ s1 = *(char * const *)a;
+ s2 = *(char * const *)b;
+ return strcmp(path_get_file_name(s1), path_get_file_name(s2));
+}
+
+static int conf_files_list_strv_internal(char ***strv, const char *suffix, const char *root, char **dirs) {
+ Hashmap *fh;
+ char **files, **p;
+ int r;
+
+ assert(strv);
+ assert(suffix);
+
+ /* This alters the dirs string array */
+ if (!path_strv_canonicalize_uniq(dirs))
+ return -ENOMEM;
+
+ fh = hashmap_new(string_hash_func, string_compare_func);
+ if (!fh)
+ return -ENOMEM;
+
+ STRV_FOREACH(p, dirs) {
+ r = files_add(fh, root, *p, suffix);
+ if (r == -ENOMEM) {
+ hashmap_free_free(fh);
+ return r;
+ } else if (r < 0)
+ log_debug("Failed to search for files in %s: %s",
+ *p, strerror(-r));
+ }
+
+ files = hashmap_get_strv(fh);
+ if (files == NULL) {
+ hashmap_free_free(fh);
+ return -ENOMEM;
+ }
+
+ qsort(files, hashmap_size(fh), sizeof(char *), base_cmp);
+ *strv = files;
+
+ hashmap_free(fh);
+ return 0;
+}
+
+int conf_files_list_strv(char ***strv, const char *suffix, const char *root, const char* const* dirs) {
+ _cleanup_strv_free_ char **copy = NULL;
+
+ assert(strv);
+ assert(suffix);
+
+ copy = strv_copy((char**) dirs);
+ if (!copy)
+ return -ENOMEM;
+
+ return conf_files_list_strv_internal(strv, suffix, root, copy);
+}
+
+int conf_files_list(char ***strv, const char *suffix, const char *root, const char *dir, ...) {
+ _cleanup_strv_free_ char **dirs = NULL;
+ va_list ap;
+
+ assert(strv);
+ assert(suffix);
+
+ va_start(ap, dir);
+ dirs = strv_new_ap(dir, ap);
+ va_end(ap);
+
+ if (!dirs)
+ return -ENOMEM;
+
+ return conf_files_list_strv_internal(strv, suffix, root, dirs);
+}
+
+int conf_files_list_nulstr(char ***strv, const char *suffix, const char *root, const char *d) {
+ _cleanup_strv_free_ char **dirs = NULL;
+
+ assert(strv);
+ assert(suffix);
+
+ dirs = strv_split_nulstr(d);
+ if (!dirs)
+ return -ENOMEM;
+
+ return conf_files_list_strv_internal(strv, suffix, root, dirs);
+}
diff --git a/src/shared/conf-files.h b/src/shared/conf-files.h
new file mode 100644
index 0000000000..3bd3d2f3d4
--- /dev/null
+++ b/src/shared/conf-files.h
@@ -0,0 +1,32 @@
+/*-*- Mode: C; c-basic-offset: 8; indent-tabs-mode: nil -*-*/
+
+#ifndef fooconffileshfoo
+#define fooconffileshfoo
+
+/***
+ This file is part of systemd.
+
+ Copyright 2010-2012 Lennart Poettering
+ Copyright 2010-2012 Kay Sievers
+
+ systemd is free software; you can redistribute it and/or modify it
+ under the terms of the GNU Lesser General Public License as published by
+ the Free Software Foundation; either version 2.1 of the License, or
+ (at your option) any later version.
+
+ systemd is distributed in the hope that it will be useful, but
+ WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ Lesser General Public License for more details.
+
+ You should have received a copy of the GNU Lesser General Public License
+ along with systemd; If not, see <http://www.gnu.org/licenses/>.
+***/
+
+#include "macro.h"
+
+int conf_files_list(char ***strv, const char *suffix, const char *root, const char *dir, ...);
+int conf_files_list_strv(char ***strv, const char *suffix, const char *root, const char* const* dirs);
+int conf_files_list_nulstr(char ***strv, const char *suffix, const char *root, const char *dirs);
+
+#endif
diff --git a/src/shared/conf-parser.c b/src/shared/conf-parser.c
new file mode 100644
index 0000000000..2303d9a50b
--- /dev/null
+++ b/src/shared/conf-parser.c
@@ -0,0 +1,920 @@
+/*-*- Mode: C; c-basic-offset: 8; indent-tabs-mode: nil -*-*/
+
+/***
+ This file is part 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 <string.h>
+#include <stdio.h>
+#include <errno.h>
+#include <assert.h>
+#include <stdlib.h>
+
+#include "conf-parser.h"
+#include "util.h"
+#include "macro.h"
+#include "strv.h"
+#include "log.h"
+#include "utf8.h"
+#include "path-util.h"
+#include "set.h"
+#include "exit-status.h"
+#include "sd-messages.h"
+
+int log_syntax_internal(const char *unit, int level,
+ const char *file, unsigned line, const char *func,
+ const char *config_file, unsigned config_line,
+ int error, const char *format, ...) {
+
+ _cleanup_free_ char *msg = NULL;
+ int r;
+ va_list ap;
+
+ va_start(ap, format);
+ r = vasprintf(&msg, format, ap);
+ va_end(ap);
+ if (r < 0)
+ return log_oom();
+
+ if (unit)
+ r = log_struct_internal(level,
+ file, line, func,
+ getpid() == 1 ? "UNIT=%s" : "USER_UNIT=%s", unit,
+ MESSAGE_ID(SD_MESSAGE_CONFIG_ERROR),
+ "CONFIG_FILE=%s", config_file,
+ "CONFIG_LINE=%u", config_line,
+ "ERRNO=%d", error > 0 ? error : EINVAL,
+ "MESSAGE=[%s:%u] %s", config_file, config_line, msg,
+ NULL);
+ else
+ r = log_struct_internal(level,
+ file, line, func,
+ MESSAGE_ID(SD_MESSAGE_CONFIG_ERROR),
+ "CONFIG_FILE=%s", config_file,
+ "CONFIG_LINE=%u", config_line,
+ "ERRNO=%d", error > 0 ? error : EINVAL,
+ "MESSAGE=[%s:%u] %s", config_file, config_line, msg,
+ NULL);
+
+ return r;
+}
+
+int config_item_table_lookup(
+ void *table,
+ const char *section,
+ const char *lvalue,
+ ConfigParserCallback *func,
+ int *ltype,
+ void **data,
+ void *userdata) {
+
+ ConfigTableItem *t;
+
+ assert(table);
+ assert(lvalue);
+ assert(func);
+ assert(ltype);
+ assert(data);
+
+ for (t = table; t->lvalue; t++) {
+
+ if (!streq(lvalue, t->lvalue))
+ continue;
+
+ if (!streq_ptr(section, t->section))
+ continue;
+
+ *func = t->parse;
+ *ltype = t->ltype;
+ *data = t->data;
+ return 1;
+ }
+
+ return 0;
+}
+
+int config_item_perf_lookup(
+ void *table,
+ const char *section,
+ const char *lvalue,
+ ConfigParserCallback *func,
+ int *ltype,
+ void **data,
+ void *userdata) {
+
+ ConfigPerfItemLookup lookup = (ConfigPerfItemLookup) table;
+ const ConfigPerfItem *p;
+
+ assert(table);
+ assert(lvalue);
+ assert(func);
+ assert(ltype);
+ assert(data);
+
+ if (!section)
+ p = lookup(lvalue, strlen(lvalue));
+ else {
+ char *key;
+
+ key = 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,
+ void *table,
+ const char *section,
+ const char *lvalue,
+ const char *rvalue,
+ bool relaxed,
+ void *userdata) {
+
+ ConfigParserCallback func = NULL;
+ int ltype = 0;
+ void *data = NULL;
+ int r;
+
+ assert(filename);
+ assert(line > 0);
+ assert(lookup);
+ assert(lvalue);
+ assert(rvalue);
+
+ r = lookup(table, section, lvalue, &func, &ltype, &data, userdata);
+ if (r < 0)
+ return r;
+
+ if (r > 0) {
+ if (func)
+ return func(unit, filename, line, section, 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, EINVAL,
+ "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,
+ void *table,
+ bool relaxed,
+ bool allow_include,
+ char **section,
+ char *l,
+ void *userdata) {
+
+ char *e;
+
+ assert(filename);
+ assert(line > 0);
+ assert(lookup);
+ assert(l);
+
+ l = strstrip(l);
+
+ if (!*l)
+ return 0;
+
+ if (strchr(COMMENTS "\n", *l))
+ return 0;
+
+ if (startswith(l, ".include ")) {
+ _cleanup_free_ char *fn = NULL;
+
+ if (!allow_include) {
+ log_syntax(unit, LOG_ERR, filename, line, EBADMSG,
+ ".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, 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, EBADMSG,
+ "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)
+ log_syntax(unit, LOG_WARNING, filename, line, EINVAL,
+ "Unknown section '%s'. Ignoring.", n);
+
+ free(n);
+ *section = NULL;
+ } else {
+ free(*section);
+ *section = n;
+ }
+
+ return 0;
+ }
+
+ if (sections && !*section) {
+
+ if (!relaxed)
+ log_syntax(unit, LOG_WARNING, filename, line, EINVAL,
+ "Assignment outside of section. Ignoring.");
+
+ return 0;
+ }
+
+ e = strchr(l, '=');
+ if (!e) {
+ log_syntax(unit, LOG_WARNING, filename, line, EINVAL, "Missing '='.");
+ return -EBADMSG;
+ }
+
+ *e = 0;
+ e++;
+
+ return next_assignment(unit,
+ filename,
+ line,
+ lookup,
+ table,
+ *section,
+ 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,
+ void *table,
+ bool relaxed,
+ bool allow_include,
+ void *userdata) {
+
+ _cleanup_free_ char *section = NULL, *continuation = NULL;
+ _cleanup_fclose_ FILE *ours = NULL;
+ unsigned line = 0;
+ int r;
+
+ assert(filename);
+ assert(lookup);
+
+ if (!f) {
+ f = ours = fopen(filename, "re");
+ if (!f) {
+ log_error("Failed to open configuration file '%s': %m", filename);
+ return -errno;
+ }
+ }
+
+ 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("Failed to read configuration file '%s': %m", filename);
+ return -errno;
+ }
+
+ truncate_nl(l);
+
+ if (continuation) {
+ c = strappend(continuation, l);
+ if (!c)
+ return -ENOMEM;
+
+ free(continuation);
+ continuation = NULL;
+ p = c;
+ } else
+ p = l;
+
+ for (e = p; *e; e++) {
+ if (escaped)
+ escaped = false;
+ else if (*e == '\\')
+ escaped = true;
+ }
+
+ if (escaped) {
+ *(e-1) = ' ';
+
+ if (c)
+ continuation = c;
+ else {
+ continuation = strdup(l);
+ if (!continuation)
+ return -ENOMEM;
+ }
+
+ continue;
+ }
+
+ r = parse_line(unit,
+ filename,
+ ++line,
+ sections,
+ lookup,
+ table,
+ relaxed,
+ allow_include,
+ &section,
+ p,
+ userdata);
+ free(c);
+
+ 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, \
+ 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", \
+ #vartype, rvalue); \
+ \
+ return 0; \
+ }
+
+DEFINE_PARSER(int, int, safe_atoi)
+DEFINE_PARSER(long, long, safe_atoli)
+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)
+
+
+int config_parse_bytes_size(const char* unit,
+ const char *filename,
+ unsigned line,
+ const char *section,
+ const char *lvalue,
+ int ltype,
+ const char *rvalue,
+ void *data,
+ void *userdata) {
+
+ size_t *sz = data;
+ off_t o;
+ int r;
+
+ assert(filename);
+ assert(lvalue);
+ assert(rvalue);
+ assert(data);
+
+ r = parse_bytes(rvalue, &o);
+ if (r < 0 || (off_t) (size_t) o != o) {
+ log_syntax(unit, LOG_ERR, filename, line, -r,
+ "Failed to parse byte value, ignoring: %s", rvalue);
+ return 0;
+ }
+
+ *sz = (size_t) o;
+ return 0;
+}
+
+
+int config_parse_bytes_off(const char* unit,
+ const char *filename,
+ unsigned line,
+ const char *section,
+ const char *lvalue,
+ int ltype,
+ const char *rvalue,
+ void *data,
+ void *userdata) {
+
+ off_t *bytes = data;
+ int r;
+
+ assert(filename);
+ assert(lvalue);
+ assert(rvalue);
+ assert(data);
+
+ assert_cc(sizeof(off_t) == sizeof(uint64_t));
+
+ r = parse_bytes(rvalue, bytes);
+ if (r < 0)
+ log_syntax(unit, LOG_ERR, filename, line, -r,
+ "Failed to parse bytes value, ignoring: %s", rvalue);
+
+ return 0;
+}
+
+int config_parse_bool(const char* unit,
+ const char *filename,
+ unsigned line,
+ const char *section,
+ const char *lvalue,
+ int ltype,
+ const char *rvalue,
+ void *data,
+ void *userdata) {
+
+ int k;
+ bool *b = data;
+
+ assert(filename);
+ assert(lvalue);
+ assert(rvalue);
+ assert(data);
+
+ 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,
+ const char *lvalue,
+ int ltype,
+ const char *rvalue,
+ void *data,
+ void *userdata) {
+
+ int k;
+ int *b = data;
+
+ assert(filename);
+ assert(lvalue);
+ assert(rvalue);
+ assert(data);
+
+ /* Tristates are like booleans, but can also take the 'default' value, i.e. "-1" */
+
+ k = parse_boolean(rvalue);
+ if (k < 0) {
+ log_syntax(unit, LOG_ERR, filename, line, -k,
+ "Failed to parse boolean value, ignoring: %s", rvalue);
+ return 0;
+ }
+
+ *b = !!k;
+ return 0;
+}
+
+int config_parse_string(const char *unit,
+ const char *filename,
+ unsigned line,
+ const char *section,
+ const char *lvalue,
+ int ltype,
+ const char *rvalue,
+ void *data,
+ void *userdata) {
+
+ char **s = data;
+ char *n;
+
+ assert(filename);
+ assert(lvalue);
+ assert(rvalue);
+ assert(data);
+
+ n = strdup(rvalue);
+ if (!n)
+ return log_oom();
+
+ if (!utf8_is_valid(n)) {
+ log_syntax(unit, LOG_ERR, filename, line, EINVAL,
+ "String is not UTF-8 clean, ignoring assignment: %s", rvalue);
+ free(n);
+ return 0;
+ }
+
+ free(*s);
+ if (*n)
+ *s = n;
+ else {
+ free(n);
+ *s = NULL;
+ }
+
+ return 0;
+}
+
+int config_parse_path(const char *unit,
+ const char *filename,
+ unsigned line,
+ const char *section,
+ const char *lvalue,
+ int ltype,
+ const char *rvalue,
+ void *data,
+ void *userdata) {
+
+ char **s = data;
+ char *n;
+
+ assert(filename);
+ assert(lvalue);
+ assert(rvalue);
+ assert(data);
+
+ if (!utf8_is_valid(rvalue)) {
+ log_syntax(unit, LOG_ERR, filename, line, EINVAL,
+ "Path is not UTF-8 clean, ignoring assignment: %s", rvalue);
+ return 0;
+ }
+
+ if (!path_is_absolute(rvalue)) {
+ log_syntax(unit, LOG_ERR, filename, line, EINVAL,
+ "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,
+ const char *lvalue,
+ int ltype,
+ const char *rvalue,
+ void *data,
+ void *userdata) {
+
+ char *** sv = data, *w, *state;
+ size_t l;
+ int r;
+
+ 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;
+ }
+
+ FOREACH_WORD_QUOTED(w, l, rvalue, state) {
+ _cleanup_free_ char *n;
+
+ n = cunescape_length(w, l);
+ if (!n)
+ return log_oom();
+
+ if (!utf8_is_valid(n)) {
+ log_syntax(unit, LOG_ERR, filename, line, EINVAL,
+ "String is not UTF-8 clean, ignoring: %s", rvalue);
+ continue;
+ }
+
+ r = strv_extend(sv, n);
+ if (r < 0)
+ return log_oom();
+ }
+
+ return 0;
+}
+
+int config_parse_path_strv(const char *unit,
+ const char *filename,
+ unsigned line,
+ const char *section,
+ const char *lvalue,
+ int ltype,
+ const char *rvalue,
+ void *data,
+ void *userdata) {
+
+ char*** sv = data, *w, *state;
+ size_t l;
+ int r;
+
+ assert(filename);
+ assert(lvalue);
+ assert(rvalue);
+ assert(data);
+
+ if (isempty(rvalue)) {
+ /* Empty assignment resets the list */
+ strv_free(*sv);
+ *sv = NULL;
+ return 0;
+ }
+
+ FOREACH_WORD_QUOTED(w, l, rvalue, state) {
+ _cleanup_free_ char *n;
+
+ n = strndup(w, l);
+ if (!n)
+ return log_oom();
+
+ if (!utf8_is_valid(n)) {
+ log_syntax(unit, LOG_ERR, filename, line, EINVAL,
+ "Path is not UTF-8 clean, ignoring assignment: %s", rvalue);
+ continue;
+ }
+
+ if (!path_is_absolute(n)) {
+ log_syntax(unit, LOG_ERR, filename, line, EINVAL,
+ "Not an absolute path, ignoring: %s", rvalue);
+ continue;
+ }
+
+ path_kill_slashes(n);
+ r = strv_extend(sv, n);
+ if (r < 0)
+ return log_oom();
+ }
+
+ return 0;
+}
+
+int config_parse_mode(const char *unit,
+ const char *filename,
+ unsigned line,
+ const char *section,
+ const char *lvalue,
+ int ltype,
+ const char *rvalue,
+ void *data,
+ void *userdata) {
+
+ mode_t *m = data;
+ long l;
+ char *x = NULL;
+
+ assert(filename);
+ assert(lvalue);
+ assert(rvalue);
+ assert(data);
+
+ errno = 0;
+ l = strtol(rvalue, &x, 8);
+ if (!x || x == rvalue || *x || errno) {
+ log_syntax(unit, LOG_ERR, filename, line, errno,
+ "Failed to parse mode value, ignoring: %s", rvalue);
+ return 0;
+ }
+
+ if (l < 0000 || l > 07777) {
+ log_syntax(unit, LOG_ERR, filename, line, ERANGE,
+ "Mode value out of range, ignoring: %s", rvalue);
+ return 0;
+ }
+
+ *m = (mode_t) l;
+ return 0;
+}
+
+int config_parse_facility(const char *unit,
+ const char *filename,
+ unsigned line,
+ const char *section,
+ const char *lvalue,
+ int ltype,
+ const char *rvalue,
+ void *data,
+ void *userdata) {
+
+
+ int *o = data, x;
+
+ assert(filename);
+ assert(lvalue);
+ assert(rvalue);
+ assert(data);
+
+ x = log_facility_unshifted_from_string(rvalue);
+ if (x < 0) {
+ log_syntax(unit, LOG_ERR, filename, line, EINVAL,
+ "Failed to parse log facility, ignoring: %s", rvalue);
+ return 0;
+ }
+
+ *o = (x << 3) | LOG_PRI(*o);
+
+ return 0;
+}
+
+int config_parse_level(const char *unit,
+ const char *filename,
+ unsigned line,
+ const char *section,
+ const char *lvalue,
+ int ltype,
+ const char *rvalue,
+ void *data,
+ void *userdata) {
+
+
+ int *o = data, x;
+
+ assert(filename);
+ assert(lvalue);
+ assert(rvalue);
+ assert(data);
+
+ x = log_level_from_string(rvalue);
+ if (x < 0) {
+ log_syntax(unit, LOG_ERR, filename, line, EINVAL,
+ "Failed to parse log level, ignoring: %s", rvalue);
+ return 0;
+ }
+
+ *o = (*o & LOG_FACMASK) | x;
+ return 0;
+}
+
+int config_parse_set_status(const char *unit,
+ const char *filename,
+ unsigned line,
+ const char *section,
+ const char *lvalue,
+ int ltype,
+ const char *rvalue,
+ void *data,
+ void *userdata) {
+
+ char *w;
+ size_t l;
+ char *state;
+ int r;
+ ExitStatusSet *status_set = data;
+
+ assert(filename);
+ assert(lvalue);
+ assert(rvalue);
+ assert(data);
+
+ if (isempty(rvalue)) {
+ /* Empty assignment resets the list */
+
+ set_free(status_set->signal);
+ set_free(status_set->code);
+
+ status_set->signal = status_set->code = NULL;
+ return 0;
+ }
+
+ FOREACH_WORD(w, l, rvalue, state) {
+ int val;
+ char *temp;
+
+ temp = strndup(w, l);
+ if (!temp)
+ return log_oom();
+
+ r = safe_atoi(temp, &val);
+ if (r < 0) {
+ val = signal_from_string_try_harder(temp);
+ free(temp);
+
+ if (val > 0) {
+ r = set_ensure_allocated(&status_set->signal, trivial_hash_func, trivial_compare_func);
+ if (r < 0)
+ return log_oom();
+
+ r = set_put(status_set->signal, INT_TO_PTR(val));
+ if (r < 0) {
+ log_syntax(unit, LOG_ERR, filename, line, -r,
+ "Unable to store: %s", w);
+ return r;
+ }
+ } else {
+ log_syntax(unit, LOG_ERR, filename, line, -val,
+ "Failed to parse value, ignoring: %s", w);
+ return 0;
+ }
+ } else {
+ free(temp);
+
+ if (val < 0 || val > 255)
+ log_syntax(unit, LOG_ERR, filename, line, ERANGE,
+ "Value %d is outside range 0-255, ignoring", val);
+ else {
+ r = set_ensure_allocated(&status_set->code, trivial_hash_func, trivial_compare_func);
+ if (r < 0)
+ return log_oom();
+
+ r = set_put(status_set->code, INT_TO_PTR(val));
+ if (r < 0) {
+ log_syntax(unit, LOG_ERR, filename, line, -r,
+ "Unable to store: %s", w);
+ return r;
+ }
+ }
+ }
+ }
+
+ return 0;
+}
diff --git a/src/shared/conf-parser.h b/src/shared/conf-parser.h
new file mode 100644
index 0000000000..08428a514a
--- /dev/null
+++ b/src/shared/conf-parser.h
@@ -0,0 +1,151 @@
+/*-*- Mode: C; c-basic-offset: 8; indent-tabs-mode: nil -*-*/
+
+#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 <stdio.h>
+#include <stdbool.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,
+ const char *lvalue,
+ int ltype,
+ const char *rvalue,
+ void *data,
+ void *userdata);
+
+/* Wraps information for parsing a specific configuration variable, to
+ * be stored in a simple array */
+typedef struct ConfigTableItem {
+ const char *section; /* Section */
+ const char *lvalue; /* Name of the variable */
+ ConfigParserCallback parse; /* Function that is called to parse the variable's value */
+ int ltype; /* Distinguish different variables passed to the same callback */
+ void *data; /* Where to store the variable's data */
+} ConfigTableItem;
+
+/* Wraps information for parsing a specific configuration variable, to
+ * ve srored in a gperf perfect hashtable */
+typedef struct ConfigPerfItem {
+ const char *section_and_lvalue; /* Section + "." + name of the variable */
+ ConfigParserCallback parse; /* Function that is called to parse the variable's value */
+ int ltype; /* Distinguish different variables passed to the same callback */
+ size_t offset; /* Offset where to store data, from the beginning of userdata */
+} ConfigPerfItem;
+
+/* Prototype for a low-level gperf lookup function */
+typedef const ConfigPerfItem* (*ConfigPerfItemLookup)(const char *section_and_lvalue, unsigned length);
+
+/* Prototype for a generic high-level lookup function */
+typedef int (*ConfigItemLookup)(
+ void *table,
+ const char *section,
+ const char *lvalue,
+ ConfigParserCallback *func,
+ int *ltype,
+ void **data,
+ void *userdata);
+
+/* Linear table search implementation of ConfigItemLookup, based on
+ * ConfigTableItem arrays */
+int config_item_table_lookup(void *table, const char *section, const char *lvalue, ConfigParserCallback *func, int *ltype, void **data, void *userdata);
+
+/* gperf implementation of ConfigItemLookup, based on gperf
+ * ConfigPerfItem tables */
+int config_item_perf_lookup(void *table, const char *section, const char *lvalue, ConfigParserCallback *func, int *ltype, void **data, void *userdata);
+
+int config_parse(const char *unit,
+ const char *filename,
+ FILE *f,
+ const char *sections, /* nulstr */
+ ConfigItemLookup lookup,
+ void *table,
+ bool relaxed,
+ bool allow_include,
+ void *userdata);
+
+/* Generic parsers */
+int config_parse_int(const char *unit, const char *filename, unsigned line, const char *section, const char *lvalue, int ltype, const char *rvalue, void *data, void *userdata);
+int config_parse_unsigned(const char *unit, const char *filename, unsigned line, const char *section, const char *lvalue, int ltype, const char *rvalue, void *data, void *userdata);
+int config_parse_long(const char *unit, const char *filename, unsigned line, const char *section, const char *lvalue, int ltype, const char *rvalue, void *data, void *userdata);
+int config_parse_uint64(const char *unit, const char *filename, unsigned line, const char *section, 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, const char *lvalue, int ltype, const char *rvalue, void *data, void *userdata);
+int config_parse_bytes_size(const char *unit, const char *filename, unsigned line, const char *section, const char *lvalue, int ltype, const char *rvalue, void *data, void *userdata);
+int config_parse_bytes_off(const char *unit, const char *filename, unsigned line, const char *section, const char *lvalue, int ltype, const char *rvalue, void *data, void *userdata);
+int config_parse_bool(const char *unit, const char *filename, unsigned line, const char *section, const char *lvalue, int ltype, const char *rvalue, void *data, void *userdata);
+int config_parse_tristate(const char *unit, const char *filename, unsigned line, const char *section, const char *lvalue, int ltype, const char *rvalue, void *data, void *userdata);
+int config_parse_string(const char *unit, const char *filename, unsigned line, const char *section, const char *lvalue, int ltype, const char *rvalue, void *data, void *userdata);
+int config_parse_path(const char *unit, const char *filename, unsigned line, const char *section, const char *lvalue, int ltype, const char *rvalue, void *data, void *userdata);
+int config_parse_strv(const char *unit, const char *filename, unsigned line, const char *section, const char *lvalue, int ltype, const char *rvalue, void *data, void *userdata);
+int config_parse_path_strv(const char *unit, const char *filename, unsigned line, const char *section, 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, 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, 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, const char *lvalue, int ltype, const char *rvalue, void *data, void *userdata);
+int config_parse_facility(const char *unit, const char *filename, unsigned line, const char *section, const char *lvalue, int ltype, const char *rvalue, void *data, void *userdata);
+int config_parse_level(const char *unit, const char *filename, unsigned line, const char *section, const char *lvalue, int ltype, const char *rvalue, void *data, void *userdata);
+int config_parse_set_status(const char *unit, const char *filename, unsigned line, const char *section, const char *lvalue, int ltype, const char *rvalue, void *data, void *userdata);
+
+int log_syntax_internal(const char *unit, int level,
+ const char *file, unsigned line, const char *func,
+ const char *config_file, unsigned config_line,
+ int error, const char *format, ...) _printf_attr_(9, 10);
+
+#define log_syntax(unit, level, config_file, config_line, error, ...) \
+ log_syntax_internal(unit, level, \
+ __FILE__, __LINE__, __func__, \
+ config_file, config_line, \
+ error, __VA_ARGS__)
+
+#define DEFINE_CONFIG_PARSE_ENUM(function,name,type,msg) \
+ int function(const char *unit, \
+ const char *filename, \
+ unsigned line, \
+ const char *section, \
+ const char *lvalue, \
+ int ltype, \
+ const char *rvalue, \
+ void *data, \
+ void *userdata) { \
+ \
+ type *i = data, x; \
+ \
+ assert(filename); \
+ assert(lvalue); \
+ assert(rvalue); \
+ assert(data); \
+ \
+ if ((x = name##_from_string(rvalue)) < 0) { \
+ log_syntax(unit, LOG_ERR, filename, line, -x, \
+ msg ", ignoring: %s", rvalue); \
+ return 0; \
+ } \
+ \
+ *i = x; \
+ return 0; \
+ }
diff --git a/src/shared/dbus-common.c b/src/shared/dbus-common.c
new file mode 100644
index 0000000000..b8c15cb9fc
--- /dev/null
+++ b/src/shared/dbus-common.c
@@ -0,0 +1,1428 @@
+/*-*- Mode: C; c-basic-offset: 8; indent-tabs-mode: nil -*-*/
+
+/***
+ This file is part 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 <assert.h>
+#include <sys/socket.h>
+#include <errno.h>
+#include <unistd.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <dbus/dbus.h>
+#include <string.h>
+#include <sys/epoll.h>
+
+#include "log.h"
+#include "dbus-common.h"
+#include "util.h"
+#include "missing.h"
+#include "def.h"
+#include "strv.h"
+
+int bus_check_peercred(DBusConnection *c) {
+ int fd;
+ struct ucred ucred;
+ socklen_t l;
+
+ assert(c);
+
+ assert_se(dbus_connection_get_unix_fd(c, &fd));
+
+ l = sizeof(struct ucred);
+ if (getsockopt(fd, SOL_SOCKET, SO_PEERCRED, &ucred, &l) < 0) {
+ log_error("SO_PEERCRED failed: %m");
+ return -errno;
+ }
+
+ if (l != sizeof(struct ucred)) {
+ log_error("SO_PEERCRED returned wrong size.");
+ return -E2BIG;
+ }
+
+ if (ucred.uid != 0 && ucred.uid != geteuid())
+ return -EPERM;
+
+ return 1;
+}
+
+static int sync_auth(DBusConnection *bus, DBusError *error) {
+ usec_t begin, tstamp;
+
+ assert(bus);
+
+ /* This complexity should probably move into D-Bus itself:
+ *
+ * https://bugs.freedesktop.org/show_bug.cgi?id=35189 */
+
+ begin = tstamp = now(CLOCK_MONOTONIC);
+ for (;;) {
+
+ if (tstamp > begin + DEFAULT_TIMEOUT_USEC)
+ break;
+
+ if (dbus_connection_get_is_authenticated(bus))
+ break;
+
+ if (!dbus_connection_read_write_dispatch(bus, ((begin + DEFAULT_TIMEOUT_USEC - tstamp) + USEC_PER_MSEC - 1) / USEC_PER_MSEC))
+ break;
+
+ tstamp = now(CLOCK_MONOTONIC);
+ }
+
+ if (!dbus_connection_get_is_connected(bus)) {
+ dbus_set_error_const(error, DBUS_ERROR_NO_SERVER, "Connection terminated during authentication.");
+ return -ECONNREFUSED;
+ }
+
+ if (!dbus_connection_get_is_authenticated(bus)) {
+ dbus_set_error_const(error, DBUS_ERROR_TIMEOUT, "Failed to authenticate in time.");
+ return -EACCES;
+ }
+
+ return 0;
+}
+
+int bus_connect(DBusBusType t, DBusConnection **_bus, bool *_private, DBusError *error) {
+ DBusConnection *bus = NULL;
+ int r;
+ bool private = true;
+
+ assert(_bus);
+
+ if (geteuid() == 0 && t == DBUS_BUS_SYSTEM) {
+ /* If we are root, then let's talk directly to the
+ * system instance, instead of going via the bus */
+
+ bus = dbus_connection_open_private("unix:path=/run/systemd/private", error);
+ if (!bus)
+ return -EIO;
+
+ } else {
+ if (t == DBUS_BUS_SESSION) {
+ const char *e;
+
+ /* If we are supposed to talk to the instance,
+ * try via XDG_RUNTIME_DIR first, then
+ * fallback to normal bus access */
+
+ e = secure_getenv("XDG_RUNTIME_DIR");
+ if (e) {
+ char *p;
+
+ if (asprintf(&p, "unix:path=%s/systemd/private", e) < 0)
+ return -ENOMEM;
+
+ bus = dbus_connection_open_private(p, NULL);
+ free(p);
+ }
+ }
+
+ if (!bus) {
+ bus = dbus_bus_get_private(t, error);
+ if (!bus)
+ return -EIO;
+
+ private = false;
+ }
+ }
+
+ dbus_connection_set_exit_on_disconnect(bus, FALSE);
+
+ if (private) {
+ if (bus_check_peercred(bus) < 0) {
+ dbus_connection_close(bus);
+ dbus_connection_unref(bus);
+
+ dbus_set_error_const(error, DBUS_ERROR_ACCESS_DENIED, "Failed to verify owner of bus.");
+ return -EACCES;
+ }
+ }
+
+ r = sync_auth(bus, error);
+ if (r < 0) {
+ dbus_connection_close(bus);
+ dbus_connection_unref(bus);
+ return r;
+ }
+
+ if (_private)
+ *_private = private;
+
+ *_bus = bus;
+ return 0;
+}
+
+int bus_connect_system_ssh(const char *user, const char *host, DBusConnection **_bus, DBusError *error) {
+ DBusConnection *bus;
+ char *p = NULL;
+ int r;
+
+ assert(_bus);
+ assert(user || host);
+
+ if (user && host)
+ asprintf(&p, "unixexec:path=ssh,argv1=-xT,argv2=%s@%s,argv3=systemd-stdio-bridge", user, host);
+ else if (user)
+ asprintf(&p, "unixexec:path=ssh,argv1=-xT,argv2=%s@localhost,argv3=systemd-stdio-bridge", user);
+ else if (host)
+ asprintf(&p, "unixexec:path=ssh,argv1=-xT,argv2=%s,argv3=systemd-stdio-bridge", host);
+
+ if (!p) {
+ dbus_set_error_const(error, DBUS_ERROR_NO_MEMORY, NULL);
+ return -ENOMEM;
+ }
+
+ bus = dbus_connection_open_private(p, error);
+ free(p);
+
+ if (!bus)
+ return -EIO;
+
+ dbus_connection_set_exit_on_disconnect(bus, FALSE);
+
+ if ((r = sync_auth(bus, error)) < 0) {
+ dbus_connection_close(bus);
+ dbus_connection_unref(bus);
+ return r;
+ }
+
+ if (!dbus_bus_register(bus, error)) {
+ dbus_connection_close(bus);
+ dbus_connection_unref(bus);
+ return r;
+ }
+
+ *_bus = bus;
+ return 0;
+}
+
+int bus_connect_system_polkit(DBusConnection **_bus, DBusError *error) {
+ DBusConnection *bus;
+ int r;
+
+ assert(_bus);
+
+ /* Don't bother with PolicyKit if we are root */
+ if (geteuid() == 0)
+ return bus_connect(DBUS_BUS_SYSTEM, _bus, NULL, error);
+
+ bus = dbus_connection_open_private("unixexec:path=pkexec,argv1=" SYSTEMD_STDIO_BRIDGE_BINARY_PATH, error);
+ if (!bus)
+ return -EIO;
+
+ dbus_connection_set_exit_on_disconnect(bus, FALSE);
+
+ r = sync_auth(bus, error);
+ if (r < 0) {
+ dbus_connection_close(bus);
+ dbus_connection_unref(bus);
+ return r;
+ }
+
+ if (!dbus_bus_register(bus, error)) {
+ dbus_connection_close(bus);
+ dbus_connection_unref(bus);
+ return r;
+ }
+
+ *_bus = bus;
+ return 0;
+}
+
+const char *bus_error_message(const DBusError *error) {
+ if (!error)
+ return NULL;
+
+ /* Sometimes the D-Bus server is a little bit too verbose with
+ * its error messages, so let's override them here */
+ if (dbus_error_has_name(error, DBUS_ERROR_ACCESS_DENIED))
+ return "Access denied";
+
+ return error->message;
+}
+
+const char *bus_error(const DBusError *error, int err) {
+ if (error && dbus_error_is_set(error))
+ return bus_error_message(error);
+
+ return strerror(err < 0 ? -err : err);
+}
+
+DBusHandlerResult bus_default_message_handler(
+ DBusConnection *c,
+ DBusMessage *message,
+ const char *introspection,
+ const char *interfaces,
+ const BusBoundProperties *bound_properties) {
+
+ DBusError error;
+ _cleanup_dbus_message_unref_ DBusMessage *reply = NULL;
+ int r;
+
+ assert(c);
+ assert(message);
+
+ dbus_error_init(&error);
+
+ if (dbus_message_is_method_call(message, "org.freedesktop.DBus.Introspectable", "Introspect") && introspection) {
+
+ reply = dbus_message_new_method_return(message);
+ if (!reply)
+ goto oom;
+
+ if (!dbus_message_append_args(reply, DBUS_TYPE_STRING, &introspection, DBUS_TYPE_INVALID))
+ goto oom;
+
+ } else if (dbus_message_is_method_call(message, "org.freedesktop.DBus.Properties", "Get") && bound_properties) {
+ const char *interface, *property;
+ const BusBoundProperties *bp;
+ const BusProperty *p;
+ void *data;
+ DBusMessageIter iter, sub;
+
+ if (!dbus_message_get_args(
+ message,
+ &error,
+ DBUS_TYPE_STRING, &interface,
+ DBUS_TYPE_STRING, &property,
+ DBUS_TYPE_INVALID))
+ return bus_send_error_reply(c, message, &error, -EINVAL);
+
+ for (bp = bound_properties; bp->interface; bp++) {
+ if (!streq(bp->interface, interface))
+ continue;
+
+ for (p = bp->properties; p->property; p++)
+ if (streq(p->property, property))
+ goto get_prop;
+ }
+
+ /* no match */
+ if (!nulstr_contains(interfaces, interface))
+ dbus_set_error_const(&error, DBUS_ERROR_UNKNOWN_INTERFACE, "Unknown interface");
+ else
+ dbus_set_error_const(&error, DBUS_ERROR_UNKNOWN_PROPERTY, "Unknown property");
+
+ return bus_send_error_reply(c, message, &error, -EINVAL);
+
+get_prop:
+ reply = dbus_message_new_method_return(message);
+ if (!reply)
+ goto oom;
+
+ dbus_message_iter_init_append(reply, &iter);
+
+ if (!dbus_message_iter_open_container(&iter, DBUS_TYPE_VARIANT, p->signature, &sub))
+ goto oom;
+
+ data = (char*)bp->base + p->offset;
+ if (p->indirect)
+ data = *(void**)data;
+
+ r = p->append(&sub, property, data);
+ if (r == -ENOMEM)
+ goto oom;
+ if (r < 0)
+ return bus_send_error_reply(c, message, NULL, r);
+
+ if (!dbus_message_iter_close_container(&iter, &sub))
+ goto oom;
+
+ } else if (dbus_message_is_method_call(message, "org.freedesktop.DBus.Properties", "GetAll") && bound_properties) {
+ const char *interface;
+ const BusBoundProperties *bp;
+ const BusProperty *p;
+ DBusMessageIter iter, sub, sub2, sub3;
+
+ if (!dbus_message_get_args(
+ message,
+ &error,
+ DBUS_TYPE_STRING, &interface,
+ DBUS_TYPE_INVALID))
+ return bus_send_error_reply(c, message, &error, -EINVAL);
+
+ if (interface[0] && !nulstr_contains(interfaces, interface)) {
+ dbus_set_error_const(&error, DBUS_ERROR_UNKNOWN_INTERFACE, "Unknown interface");
+ return bus_send_error_reply(c, message, &error, -EINVAL);
+ }
+
+ reply = dbus_message_new_method_return(message);
+ if (!reply)
+ goto oom;
+
+ dbus_message_iter_init_append(reply, &iter);
+
+ if (!dbus_message_iter_open_container(&iter, DBUS_TYPE_ARRAY, "{sv}", &sub))
+ goto oom;
+
+ for (bp = bound_properties; bp->interface; bp++) {
+ if (interface[0] && !streq(bp->interface, interface))
+ continue;
+
+ for (p = bp->properties; p->property; p++) {
+ void *data;
+
+ if (!dbus_message_iter_open_container(&sub, DBUS_TYPE_DICT_ENTRY, NULL, &sub2) ||
+ !dbus_message_iter_append_basic(&sub2, DBUS_TYPE_STRING, &p->property) ||
+ !dbus_message_iter_open_container(&sub2, DBUS_TYPE_VARIANT, p->signature, &sub3))
+ goto oom;
+
+ data = (char*)bp->base + p->offset;
+ if (p->indirect)
+ data = *(void**)data;
+ r = p->append(&sub3, p->property, data);
+ if (r == -ENOMEM)
+ goto oom;
+ if (r < 0)
+ return bus_send_error_reply(c, message, NULL, r);
+
+ if (!dbus_message_iter_close_container(&sub2, &sub3) ||
+ !dbus_message_iter_close_container(&sub, &sub2))
+ goto oom;
+ }
+ }
+
+ if (!dbus_message_iter_close_container(&iter, &sub))
+ goto oom;
+
+ } else if (dbus_message_is_method_call(message, "org.freedesktop.DBus.Properties", "Set") && bound_properties) {
+ const char *interface, *property;
+ DBusMessageIter iter;
+ const BusBoundProperties *bp;
+ const BusProperty *p;
+ DBusMessageIter sub;
+ char *sig;
+ void *data;
+ DBusMessage *changed;
+
+ if (!dbus_message_iter_init(message, &iter) ||
+ dbus_message_iter_get_arg_type(&iter) != DBUS_TYPE_STRING)
+ return bus_send_error_reply(c, message, NULL, -EINVAL);
+
+ dbus_message_iter_get_basic(&iter, &interface);
+
+ if (!dbus_message_iter_next(&iter) ||
+ dbus_message_iter_get_arg_type(&iter) != DBUS_TYPE_STRING)
+ return bus_send_error_reply(c, message, NULL, -EINVAL);
+
+ dbus_message_iter_get_basic(&iter, &property);
+
+ if (!dbus_message_iter_next(&iter) ||
+ dbus_message_iter_get_arg_type(&iter) != DBUS_TYPE_VARIANT ||
+ dbus_message_iter_has_next(&iter))
+ return bus_send_error_reply(c, message, NULL, -EINVAL);
+
+ for (bp = bound_properties; bp->interface; bp++) {
+ if (!streq(bp->interface, interface))
+ continue;
+
+ for (p = bp->properties; p->property; p++)
+ if (streq(p->property, property))
+ goto set_prop;
+ }
+
+ /* no match */
+ if (!nulstr_contains(interfaces, interface))
+ dbus_set_error_const(&error, DBUS_ERROR_UNKNOWN_INTERFACE, "Unknown interface");
+ else
+ dbus_set_error_const(&error, DBUS_ERROR_UNKNOWN_PROPERTY, "Unknown property");
+
+ return bus_send_error_reply(c, message, &error, -EINVAL);
+
+set_prop:
+ if (!p->set) {
+ dbus_set_error_const(&error, DBUS_ERROR_PROPERTY_READ_ONLY, "Property read-only");
+ return bus_send_error_reply(c, message, &error, -EINVAL);
+ }
+
+ dbus_message_iter_recurse(&iter, &sub);
+
+ sig = dbus_message_iter_get_signature(&sub);
+ if (!sig)
+ goto oom;
+
+ if (!streq(sig, p->signature)) {
+ dbus_free(sig);
+ return bus_send_error_reply(c, message, NULL, -EINVAL);
+ }
+ dbus_free(sig);
+
+ data = (uint8_t*) bp->base + p->offset;
+ if (p->indirect)
+ data = *(void**)data;
+
+ r = p->set(&sub, property, data);
+ if (r == -ENOMEM)
+ goto oom;
+ else if (r < 0)
+ return bus_send_error_reply(c, message, NULL, r);
+
+ reply = dbus_message_new_method_return(message);
+ if (!reply)
+ goto oom;
+
+ /* Send out a signal about this, but it doesn't really
+ * matter if this fails, so eat all errors */
+ changed = bus_properties_changed_one_new(
+ dbus_message_get_path(message),
+ interface,
+ property);
+ if (changed) {
+ dbus_connection_send(c, changed, NULL);
+ dbus_message_unref(changed);
+ }
+
+
+ } else {
+ const char *interface = dbus_message_get_interface(message);
+
+ if (!interface || !nulstr_contains(interfaces, interface)) {
+ dbus_set_error_const(&error, DBUS_ERROR_UNKNOWN_INTERFACE, "Unknown interface");
+ return bus_send_error_reply(c, message, &error, -EINVAL);
+ }
+ }
+
+ if (reply) {
+ if (!bus_maybe_send_reply(c, message, reply))
+ goto oom;
+
+ return DBUS_HANDLER_RESULT_HANDLED;
+ }
+
+ return DBUS_HANDLER_RESULT_NOT_YET_HANDLED;
+
+oom:
+ dbus_error_free(&error);
+
+ return DBUS_HANDLER_RESULT_NEED_MEMORY;
+}
+
+int bus_property_append_string(DBusMessageIter *i, const char *property, void *data) {
+ const char *t = data;
+
+ assert(i);
+ assert(property);
+
+ if (!t)
+ t = "";
+
+ if (!dbus_message_iter_append_basic(i, DBUS_TYPE_STRING, &t))
+ return -ENOMEM;
+
+ return 0;
+}
+
+int bus_property_append_strv(DBusMessageIter *i, const char *property, void *data) {
+ char **t = data;
+
+ assert(i);
+ assert(property);
+
+ return bus_append_strv_iter(i, t);
+}
+
+int bus_property_append_bool(DBusMessageIter *i, const char *property, void *data) {
+ bool *b = data;
+ dbus_bool_t db;
+
+ assert(i);
+ assert(property);
+ assert(b);
+
+ db = *b;
+
+ if (!dbus_message_iter_append_basic(i, DBUS_TYPE_BOOLEAN, &db))
+ return -ENOMEM;
+
+ return 0;
+}
+
+int bus_property_append_tristate_false(DBusMessageIter *i, const char *property, void *data) {
+ int *b = data;
+ dbus_bool_t db;
+
+ assert(i);
+ assert(property);
+ assert(b);
+
+ db = *b > 0;
+
+ if (!dbus_message_iter_append_basic(i, DBUS_TYPE_BOOLEAN, &db))
+ return -ENOMEM;
+
+ return 0;
+}
+
+int bus_property_append_uint64(DBusMessageIter *i, const char *property, void *data) {
+ assert(i);
+ assert(property);
+ assert(data);
+
+ /* Let's ensure that usec_t is actually 64bit, and hence this
+ * function can be used for usec_t */
+ assert_cc(sizeof(uint64_t) == sizeof(usec_t));
+
+ if (!dbus_message_iter_append_basic(i, DBUS_TYPE_UINT64, data))
+ return -ENOMEM;
+
+ return 0;
+}
+
+int bus_property_append_uint32(DBusMessageIter *i, const char *property, void *data) {
+ assert(i);
+ assert(property);
+ assert(data);
+
+ /* Let's ensure that pid_t, mode_t, uid_t, gid_t are actually
+ * 32bit, and hence this function can be used for
+ * pid_t/mode_t/uid_t/gid_t */
+ assert_cc(sizeof(uint32_t) == sizeof(pid_t));
+ assert_cc(sizeof(uint32_t) == sizeof(mode_t));
+ assert_cc(sizeof(uint32_t) == sizeof(unsigned));
+ assert_cc(sizeof(uint32_t) == sizeof(uid_t));
+ assert_cc(sizeof(uint32_t) == sizeof(gid_t));
+
+ if (!dbus_message_iter_append_basic(i, DBUS_TYPE_UINT32, data))
+ return -ENOMEM;
+
+ return 0;
+}
+
+int bus_property_append_int32(DBusMessageIter *i, const char *property, void *data) {
+ assert(i);
+ assert(property);
+ assert(data);
+
+ assert_cc(sizeof(int32_t) == sizeof(int));
+
+ if (!dbus_message_iter_append_basic(i, DBUS_TYPE_INT32, data))
+ return -ENOMEM;
+
+ return 0;
+}
+
+int bus_property_append_size(DBusMessageIter *i, const char *property, void *data) {
+ uint64_t u;
+
+ assert(i);
+ assert(property);
+ assert(data);
+
+ u = (uint64_t) *(size_t*) data;
+
+ if (!dbus_message_iter_append_basic(i, DBUS_TYPE_UINT64, &u))
+ return -ENOMEM;
+
+ return 0;
+}
+
+int bus_property_append_ul(DBusMessageIter *i, const char *property, void *data) {
+ uint64_t u;
+
+ assert(i);
+ assert(property);
+ assert(data);
+
+ u = (uint64_t) *(unsigned long*) data;
+
+ if (!dbus_message_iter_append_basic(i, DBUS_TYPE_UINT64, &u))
+ return -ENOMEM;
+
+ return 0;
+}
+
+int bus_property_append_long(DBusMessageIter *i, const char *property, void *data) {
+ int64_t l;
+
+ assert(i);
+ assert(property);
+ assert(data);
+
+ l = (int64_t) *(long*) data;
+
+ if (!dbus_message_iter_append_basic(i, DBUS_TYPE_INT64, &l))
+ return -ENOMEM;
+
+ return 0;
+}
+
+int bus_property_set_uint64(DBusMessageIter *i, const char *property, void *data) {
+ uint64_t *t = data;
+
+ assert(i);
+ assert(property);
+
+ dbus_message_iter_get_basic(i, t);
+ return 0;
+}
+
+const char *bus_errno_to_dbus(int error) {
+
+ switch(error) {
+
+ case -EINVAL:
+ return DBUS_ERROR_INVALID_ARGS;
+
+ case -ENOMEM:
+ return DBUS_ERROR_NO_MEMORY;
+
+ case -EPERM:
+ case -EACCES:
+ return DBUS_ERROR_ACCESS_DENIED;
+
+ case -ESRCH:
+ return DBUS_ERROR_UNIX_PROCESS_ID_UNKNOWN;
+
+ case -ENOENT:
+ return DBUS_ERROR_FILE_NOT_FOUND;
+
+ case -EEXIST:
+ return DBUS_ERROR_FILE_EXISTS;
+
+ case -ETIMEDOUT:
+ case -ETIME:
+ return DBUS_ERROR_TIMEOUT;
+
+ case -EIO:
+ return DBUS_ERROR_IO_ERROR;
+
+ case -ENETRESET:
+ case -ECONNABORTED:
+ case -ECONNRESET:
+ return DBUS_ERROR_DISCONNECTED;
+ }
+
+ return DBUS_ERROR_FAILED;
+}
+
+dbus_bool_t bus_maybe_send_reply (DBusConnection *c,
+ DBusMessage *message,
+ DBusMessage *reply)
+{
+ /* Some parts of systemd "reply" to signals, which of course
+ * have the no-reply flag set. We will be defensive here and
+ * still send out a reply if we're passed a signal.
+ */
+ if (dbus_message_get_type(message) == DBUS_MESSAGE_TYPE_METHOD_CALL &&
+ dbus_message_get_no_reply(message))
+ return TRUE;
+ return dbus_connection_send(c, reply, NULL);
+}
+
+DBusHandlerResult bus_send_error_reply(DBusConnection *c, DBusMessage *message, DBusError *berror, int error) {
+ _cleanup_dbus_message_unref_ DBusMessage *reply = NULL;
+ const char *name, *text;
+
+ if (berror && dbus_error_is_set(berror)) {
+ name = berror->name;
+ text = berror->message;
+ } else {
+ name = bus_errno_to_dbus(error);
+ text = strerror(-error);
+ }
+
+ reply = dbus_message_new_error(message, name, text);
+ if (!reply)
+ goto oom;
+
+ if (!bus_maybe_send_reply(c, message, reply))
+ goto oom;
+
+ if (berror)
+ dbus_error_free(berror);
+
+ return DBUS_HANDLER_RESULT_HANDLED;
+
+oom:
+ if (reply)
+ dbus_message_unref(reply);
+
+ if (berror)
+ dbus_error_free(berror);
+
+ return DBUS_HANDLER_RESULT_NEED_MEMORY;
+}
+
+DBusMessage* bus_properties_changed_new(const char *path, const char *interface, const char *properties) {
+ DBusMessage *m;
+ DBusMessageIter iter, sub;
+ const char *i;
+
+ assert(interface);
+ assert(properties);
+
+ m = dbus_message_new_signal(path, "org.freedesktop.DBus.Properties", "PropertiesChanged");
+ if (!m)
+ goto oom;
+
+ dbus_message_iter_init_append(m, &iter);
+
+ /* We won't send any property values, since they might be
+ * large and sometimes not cheap to generated */
+
+ if (!dbus_message_iter_append_basic(&iter, DBUS_TYPE_STRING, &interface) ||
+ !dbus_message_iter_open_container(&iter, DBUS_TYPE_ARRAY, "{sv}", &sub) ||
+ !dbus_message_iter_close_container(&iter, &sub) ||
+ !dbus_message_iter_open_container(&iter, DBUS_TYPE_ARRAY, "s", &sub))
+ goto oom;
+
+ NULSTR_FOREACH(i, properties)
+ if (!dbus_message_iter_append_basic(&sub, DBUS_TYPE_STRING, &i))
+ goto oom;
+
+ if (!dbus_message_iter_close_container(&iter, &sub))
+ goto oom;
+
+ return m;
+
+oom:
+ if (m)
+ dbus_message_unref(m);
+
+ return NULL;
+}
+
+DBusMessage* bus_properties_changed_one_new(const char *path, const char *interface, const char *property) {
+ DBusMessage *m;
+ DBusMessageIter iter, sub;
+
+ assert(interface);
+ assert(property);
+
+ m = dbus_message_new_signal(path, "org.freedesktop.DBus.Properties", "PropertiesChanged");
+ if (!m)
+ goto oom;
+
+ dbus_message_iter_init_append(m, &iter);
+
+ /* We won't send any property values, since they might be
+ * large and sometimes not cheap to generated */
+
+ if (!dbus_message_iter_append_basic(&iter, DBUS_TYPE_STRING, &interface) ||
+ !dbus_message_iter_open_container(&iter, DBUS_TYPE_ARRAY, "{sv}", &sub) ||
+ !dbus_message_iter_close_container(&iter, &sub) ||
+ !dbus_message_iter_open_container(&iter, DBUS_TYPE_ARRAY, "s", &sub))
+ goto oom;
+
+ if (!dbus_message_iter_append_basic(&sub, DBUS_TYPE_STRING, &property))
+ goto oom;
+
+ if (!dbus_message_iter_close_container(&iter, &sub))
+ goto oom;
+
+ return m;
+
+oom:
+ if (m)
+ dbus_message_unref(m);
+
+ return NULL;
+}
+
+uint32_t bus_flags_to_events(DBusWatch *bus_watch) {
+ unsigned flags;
+ uint32_t events = 0;
+
+ assert(bus_watch);
+
+ /* no watch flags for disabled watches */
+ if (!dbus_watch_get_enabled(bus_watch))
+ return 0;
+
+ flags = dbus_watch_get_flags(bus_watch);
+
+ if (flags & DBUS_WATCH_READABLE)
+ events |= EPOLLIN;
+ if (flags & DBUS_WATCH_WRITABLE)
+ events |= EPOLLOUT;
+
+ return events | EPOLLHUP | EPOLLERR;
+}
+
+unsigned bus_events_to_flags(uint32_t events) {
+ unsigned flags = 0;
+
+ if (events & EPOLLIN)
+ flags |= DBUS_WATCH_READABLE;
+ if (events & EPOLLOUT)
+ flags |= DBUS_WATCH_WRITABLE;
+ if (events & EPOLLHUP)
+ flags |= DBUS_WATCH_HANGUP;
+ if (events & EPOLLERR)
+ flags |= DBUS_WATCH_ERROR;
+
+ return flags;
+}
+
+int bus_parse_strv(DBusMessage *m, char ***_l) {
+ DBusMessageIter iter;
+
+ assert(m);
+ assert(_l);
+
+ if (!dbus_message_iter_init(m, &iter))
+ return -EINVAL;
+
+ return bus_parse_strv_iter(&iter, _l);
+}
+
+int bus_parse_strv_iter(DBusMessageIter *iter, char ***_l) {
+ DBusMessageIter sub;
+ unsigned n = 0, i = 0;
+ char **l;
+
+ assert(iter);
+ assert(_l);
+
+ if (dbus_message_iter_get_arg_type(iter) != DBUS_TYPE_ARRAY ||
+ dbus_message_iter_get_element_type(iter) != DBUS_TYPE_STRING)
+ return -EINVAL;
+
+ dbus_message_iter_recurse(iter, &sub);
+
+ while (dbus_message_iter_get_arg_type(&sub) != DBUS_TYPE_INVALID) {
+ n++;
+ dbus_message_iter_next(&sub);
+ }
+
+ l = new(char*, n+1);
+ if (!l)
+ return -ENOMEM;
+
+ dbus_message_iter_recurse(iter, &sub);
+
+ while (dbus_message_iter_get_arg_type(&sub) != DBUS_TYPE_INVALID) {
+ const char *s;
+
+ assert_se(dbus_message_iter_get_arg_type(&sub) == DBUS_TYPE_STRING);
+ dbus_message_iter_get_basic(&sub, &s);
+
+ if (!(l[i++] = strdup(s))) {
+ strv_free(l);
+ return -ENOMEM;
+ }
+
+ dbus_message_iter_next(&sub);
+ }
+
+ assert(i == n);
+ l[i] = NULL;
+
+ if (_l)
+ *_l = l;
+
+ return 0;
+}
+
+int bus_parse_strv_pairs_iter(DBusMessageIter *iter, char ***_l) {
+ DBusMessageIter sub, sub2;
+ unsigned n = 0, i = 0;
+ char **l;
+
+ assert(iter);
+ assert(_l);
+
+ if (dbus_message_iter_get_arg_type(iter) != DBUS_TYPE_ARRAY ||
+ dbus_message_iter_get_element_type(iter) != DBUS_TYPE_STRUCT)
+ return -EINVAL;
+
+ dbus_message_iter_recurse(iter, &sub);
+
+ while (dbus_message_iter_get_arg_type(&sub) != DBUS_TYPE_INVALID) {
+ n++;
+ dbus_message_iter_next(&sub);
+ }
+
+ l = new(char*, n*2+1);
+ if (!l)
+ return -ENOMEM;
+
+ dbus_message_iter_recurse(iter, &sub);
+
+ while (dbus_message_iter_get_arg_type(&sub) != DBUS_TYPE_INVALID) {
+ const char *a, *b;
+
+ assert_se(dbus_message_iter_get_arg_type(&sub) == DBUS_TYPE_STRUCT);
+
+ dbus_message_iter_recurse(&sub, &sub2);
+
+ if (bus_iter_get_basic_and_next(&sub2, DBUS_TYPE_STRING, &a, true) < 0 ||
+ bus_iter_get_basic_and_next(&sub2, DBUS_TYPE_STRING, &b, false) < 0)
+ return -EINVAL;
+
+ l[i] = strdup(a);
+ if (!l[i]) {
+ strv_free(l);
+ return -ENOMEM;
+ }
+
+ l[++i] = strdup(b);
+ if (!l[i]) {
+ strv_free(l);
+ return -ENOMEM;
+ }
+
+ i++;
+ dbus_message_iter_next(&sub);
+ }
+
+ assert(i == n*2);
+ l[i] = NULL;
+
+ if (_l)
+ *_l = l;
+
+ return 0;
+}
+
+int bus_parse_unit_info(DBusMessageIter *iter, struct unit_info *u) {
+ DBusMessageIter sub;
+
+ assert(iter);
+ assert(u);
+
+ if (dbus_message_iter_get_arg_type(iter) != DBUS_TYPE_STRUCT)
+ return -EINVAL;
+
+ dbus_message_iter_recurse(iter, &sub);
+
+ if (bus_iter_get_basic_and_next(&sub, DBUS_TYPE_STRING, &u->id, true) < 0 ||
+ bus_iter_get_basic_and_next(&sub, DBUS_TYPE_STRING, &u->description, true) < 0 ||
+ bus_iter_get_basic_and_next(&sub, DBUS_TYPE_STRING, &u->load_state, true) < 0 ||
+ bus_iter_get_basic_and_next(&sub, DBUS_TYPE_STRING, &u->active_state, true) < 0 ||
+ bus_iter_get_basic_and_next(&sub, DBUS_TYPE_STRING, &u->sub_state, true) < 0 ||
+ bus_iter_get_basic_and_next(&sub, DBUS_TYPE_STRING, &u->following, true) < 0 ||
+ bus_iter_get_basic_and_next(&sub, DBUS_TYPE_OBJECT_PATH, &u->unit_path, true) < 0 ||
+ bus_iter_get_basic_and_next(&sub, DBUS_TYPE_UINT32, &u->job_id, true) < 0 ||
+ bus_iter_get_basic_and_next(&sub, DBUS_TYPE_STRING, &u->job_type, true) < 0 ||
+ bus_iter_get_basic_and_next(&sub, DBUS_TYPE_OBJECT_PATH, &u->job_path, false) < 0) {
+ log_error("Failed to parse reply.");
+ return -EIO;
+ }
+
+ return 0;
+}
+
+int bus_append_strv_iter(DBusMessageIter *iter, char **l) {
+ DBusMessageIter sub;
+
+ assert(iter);
+
+ if (!dbus_message_iter_open_container(iter, DBUS_TYPE_ARRAY, "s", &sub))
+ return -ENOMEM;
+
+ STRV_FOREACH(l, l)
+ if (!dbus_message_iter_append_basic(&sub, DBUS_TYPE_STRING, l))
+ return -ENOMEM;
+
+ if (!dbus_message_iter_close_container(iter, &sub))
+ return -ENOMEM;
+
+ return 0;
+}
+
+int bus_iter_get_basic_and_next(DBusMessageIter *iter, int type, void *data, bool next) {
+
+ assert(iter);
+ assert(data);
+
+ if (dbus_message_iter_get_arg_type(iter) != type)
+ return -EIO;
+
+ dbus_message_iter_get_basic(iter, data);
+
+ if (!dbus_message_iter_next(iter) != !next)
+ return -EIO;
+
+ return 0;
+}
+
+int generic_print_property(const char *name, DBusMessageIter *iter, bool all) {
+ assert(name);
+ assert(iter);
+
+ switch (dbus_message_iter_get_arg_type(iter)) {
+
+ case DBUS_TYPE_STRING: {
+ const char *s;
+ dbus_message_iter_get_basic(iter, &s);
+
+ if (all || !isempty(s))
+ printf("%s=%s\n", name, s);
+
+ return 1;
+ }
+
+ case DBUS_TYPE_BOOLEAN: {
+ dbus_bool_t b;
+
+ dbus_message_iter_get_basic(iter, &b);
+ printf("%s=%s\n", name, yes_no(b));
+
+ return 1;
+ }
+
+ case DBUS_TYPE_UINT64: {
+ uint64_t u;
+ dbus_message_iter_get_basic(iter, &u);
+
+ /* Yes, heuristics! But we can change this check
+ * should it turn out to not be sufficient */
+
+ if (endswith(name, "Timestamp")) {
+ char timestamp[FORMAT_TIMESTAMP_MAX], *t;
+
+ t = format_timestamp(timestamp, sizeof(timestamp), u);
+ if (t || all)
+ printf("%s=%s\n", name, strempty(t));
+
+ } else if (strstr(name, "USec")) {
+ char timespan[FORMAT_TIMESPAN_MAX];
+
+ printf("%s=%s\n", name, format_timespan(timespan, sizeof(timespan), u, 0));
+ } else
+ printf("%s=%llu\n", name, (unsigned long long) u);
+
+ return 1;
+ }
+
+ case DBUS_TYPE_UINT32: {
+ uint32_t u;
+ dbus_message_iter_get_basic(iter, &u);
+
+ if (strstr(name, "UMask") || strstr(name, "Mode"))
+ printf("%s=%04o\n", name, u);
+ else
+ printf("%s=%u\n", name, (unsigned) u);
+
+ return 1;
+ }
+
+ case DBUS_TYPE_INT32: {
+ int32_t i;
+ dbus_message_iter_get_basic(iter, &i);
+
+ printf("%s=%i\n", name, (int) i);
+ return 1;
+ }
+
+ case DBUS_TYPE_DOUBLE: {
+ double d;
+ dbus_message_iter_get_basic(iter, &d);
+
+ printf("%s=%g\n", name, d);
+ return 1;
+ }
+
+ case DBUS_TYPE_ARRAY:
+
+ if (dbus_message_iter_get_element_type(iter) == DBUS_TYPE_STRING) {
+ DBusMessageIter sub;
+ bool space = false;
+
+ dbus_message_iter_recurse(iter, &sub);
+ if (all ||
+ dbus_message_iter_get_arg_type(&sub) != DBUS_TYPE_INVALID) {
+ printf("%s=", name);
+
+ while (dbus_message_iter_get_arg_type(&sub) != DBUS_TYPE_INVALID) {
+ const char *s;
+
+ assert(dbus_message_iter_get_arg_type(&sub) == DBUS_TYPE_STRING);
+ dbus_message_iter_get_basic(&sub, &s);
+ printf("%s%s", space ? " " : "", s);
+
+ space = true;
+ dbus_message_iter_next(&sub);
+ }
+
+ puts("");
+ }
+
+ return 1;
+
+ } else if (dbus_message_iter_get_element_type(iter) == DBUS_TYPE_BYTE) {
+ DBusMessageIter sub;
+
+ dbus_message_iter_recurse(iter, &sub);
+ if (all ||
+ dbus_message_iter_get_arg_type(&sub) != DBUS_TYPE_INVALID) {
+ printf("%s=", name);
+
+ while (dbus_message_iter_get_arg_type(&sub) != DBUS_TYPE_INVALID) {
+ uint8_t u;
+
+ assert(dbus_message_iter_get_arg_type(&sub) == DBUS_TYPE_BYTE);
+ dbus_message_iter_get_basic(&sub, &u);
+ printf("%02x", u);
+
+ dbus_message_iter_next(&sub);
+ }
+
+ puts("");
+ }
+
+ return 1;
+
+ } else if (dbus_message_iter_get_element_type(iter) == DBUS_TYPE_UINT32) {
+ DBusMessageIter sub;
+
+ dbus_message_iter_recurse(iter, &sub);
+ if (all ||
+ dbus_message_iter_get_arg_type(&sub) != DBUS_TYPE_INVALID) {
+ printf("%s=", name);
+
+ while (dbus_message_iter_get_arg_type(&sub) != DBUS_TYPE_INVALID) {
+ uint32_t u;
+
+ assert(dbus_message_iter_get_arg_type(&sub) == DBUS_TYPE_UINT32);
+ dbus_message_iter_get_basic(&sub, &u);
+ printf("%08x", u);
+
+ dbus_message_iter_next(&sub);
+ }
+
+ puts("");
+ }
+
+ return 1;
+ }
+
+ break;
+ }
+
+ return 0;
+}
+
+static void release_name_pending_cb(DBusPendingCall *pending, void *userdata) {
+ DBusMessage *reply;
+ DBusConnection *bus = userdata;
+
+ assert_se(reply = dbus_pending_call_steal_reply(pending));
+ dbus_message_unref(reply);
+
+ dbus_connection_close(bus);
+}
+
+void bus_async_unregister_and_exit(DBusConnection *bus, const char *name) {
+ _cleanup_dbus_message_unref_ DBusMessage *m = NULL;
+ DBusPendingCall *pending = NULL;
+
+ assert(bus);
+
+ /* We unregister the name here, but we continue to process
+ * requests, until we get the response for it, so that all
+ * requests are guaranteed to be processed. */
+
+ m = dbus_message_new_method_call(
+ DBUS_SERVICE_DBUS,
+ DBUS_PATH_DBUS,
+ DBUS_INTERFACE_DBUS,
+ "ReleaseName");
+ if (!m)
+ goto oom;
+
+ if (!dbus_message_append_args(
+ m,
+ DBUS_TYPE_STRING,
+ &name,
+ DBUS_TYPE_INVALID))
+ goto oom;
+
+ if (!dbus_connection_send_with_reply(bus, m, &pending, -1))
+ goto oom;
+
+ if (!dbus_pending_call_set_notify(pending, release_name_pending_cb, bus, NULL))
+ goto oom;
+
+ dbus_pending_call_unref(pending);
+
+ return;
+
+oom:
+ log_oom();
+
+ if (pending) {
+ dbus_pending_call_cancel(pending);
+ dbus_pending_call_unref(pending);
+ }
+}
+
+DBusHandlerResult bus_exit_idle_filter(DBusConnection *bus, DBusMessage *m, void *userdata) {
+ usec_t *remain_until = userdata;
+
+ assert(bus);
+ assert(m);
+ assert(remain_until);
+
+ /* Every time we get a new message we reset out timeout */
+ *remain_until = now(CLOCK_MONOTONIC) + DEFAULT_EXIT_USEC;
+
+ if (dbus_message_is_signal(m, DBUS_INTERFACE_LOCAL, "Disconnected"))
+ dbus_connection_close(bus);
+
+ return DBUS_HANDLER_RESULT_NOT_YET_HANDLED;
+}
+
+/* This mimics dbus_bus_get_unix_user() */
+pid_t bus_get_unix_process_id(
+ DBusConnection *connection,
+ const char *name,
+ DBusError *error) {
+
+ _cleanup_dbus_message_unref_ DBusMessage *m = NULL, *reply = NULL;
+ uint32_t pid = 0;
+
+ m = dbus_message_new_method_call(
+ DBUS_SERVICE_DBUS,
+ DBUS_PATH_DBUS,
+ DBUS_INTERFACE_DBUS,
+ "GetConnectionUnixProcessID");
+ if (!m) {
+ dbus_set_error_const(error, DBUS_ERROR_NO_MEMORY, NULL);
+ return 0;
+ }
+
+ if (!dbus_message_append_args(
+ m,
+ DBUS_TYPE_STRING, &name,
+ DBUS_TYPE_INVALID)) {
+ dbus_set_error_const(error, DBUS_ERROR_NO_MEMORY, NULL);
+ return 0;
+ }
+
+ reply = dbus_connection_send_with_reply_and_block(connection, m, -1, error);
+ if (!reply)
+ return 0;
+
+ if (dbus_set_error_from_message(error, reply))
+ return 0;
+
+ if (!dbus_message_get_args(
+ reply, error,
+ DBUS_TYPE_UINT32, &pid,
+ DBUS_TYPE_INVALID))
+ return 0;
+
+ return (pid_t) pid;
+}
+
+bool bus_error_is_no_service(const DBusError *error) {
+ assert(error);
+
+ if (!dbus_error_is_set(error))
+ return false;
+
+ if (dbus_error_has_name(error, DBUS_ERROR_NAME_HAS_NO_OWNER))
+ return true;
+
+ if (dbus_error_has_name(error, DBUS_ERROR_SERVICE_UNKNOWN))
+ return true;
+
+ return startswith(error->name, "org.freedesktop.DBus.Error.Spawn.");
+}
+
+int bus_method_call_with_reply(
+ DBusConnection *bus,
+ const char *destination,
+ const char *path,
+ const char *interface,
+ const char *method,
+ DBusMessage **return_reply,
+ DBusError *return_error,
+ int first_arg_type, ...) {
+
+ DBusError error;
+ _cleanup_dbus_message_unref_ DBusMessage *m = NULL;
+ DBusMessage *reply;
+ va_list ap;
+ int r = 0;
+
+ dbus_error_init(&error);
+ assert(bus);
+
+ m = dbus_message_new_method_call(destination, path, interface, method);
+ if (!m) {
+ r = log_oom();
+ goto finish;
+ }
+
+ va_start(ap, first_arg_type);
+ if (!dbus_message_append_args_valist(m, first_arg_type, ap)) {
+ va_end(ap);
+ r = log_oom();
+ goto finish;
+ }
+ va_end(ap);
+
+ reply = dbus_connection_send_with_reply_and_block(bus, m, -1, &error);
+ if (!reply) {
+ if (!return_error)
+ log_error("Failed to issue method call: %s", bus_error_message(&error));
+
+ if (bus_error_is_no_service(&error))
+ r = -ENOENT;
+ else if (dbus_error_has_name(&error, DBUS_ERROR_ACCESS_DENIED))
+ r = -EACCES;
+ else if (dbus_error_has_name(&error, DBUS_ERROR_NO_REPLY))
+ r = -ETIMEDOUT;
+ else
+ r = -EIO;
+ goto finish;
+ }
+
+ if (return_reply)
+ *return_reply = reply;
+ else
+ dbus_message_unref(reply);
+
+finish:
+ if (return_error)
+ *return_error = error;
+ else
+ dbus_error_free(&error);
+
+ return r;
+}
+
+void bus_message_unrefp(DBusMessage **reply) {
+ if (!reply)
+ return;
+
+ if (!*reply)
+ return;
+
+ dbus_message_unref(*reply);
+}
+
+const char *bus_message_get_sender_with_fallback(DBusMessage *m) {
+ const char *s;
+
+ assert(m);
+
+ s = dbus_message_get_sender(m);
+ if (s)
+ return s;
+
+ /* When the message came in from a direct connection the
+ * message will have no sender. We fix that here. */
+
+ return ":no-sender";
+}
diff --git a/src/shared/dbus-common.h b/src/shared/dbus-common.h
new file mode 100644
index 0000000000..9752f08c05
--- /dev/null
+++ b/src/shared/dbus-common.h
@@ -0,0 +1,246 @@
+/*-*- Mode: C; c-basic-offset: 8; indent-tabs-mode: nil -*-*/
+
+#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 <dbus/dbus.h>
+#include <inttypes.h>
+#include <sys/types.h>
+
+#include "macro.h"
+
+#ifndef DBUS_ERROR_UNKNOWN_OBJECT
+#define DBUS_ERROR_UNKNOWN_OBJECT "org.freedesktop.DBus.Error.UnknownObject"
+#endif
+
+#ifndef DBUS_ERROR_UNKNOWN_INTERFACE
+#define DBUS_ERROR_UNKNOWN_INTERFACE "org.freedesktop.DBus.Error.UnknownInterface"
+#endif
+
+#ifndef DBUS_ERROR_UNKNOWN_PROPERTY
+#define DBUS_ERROR_UNKNOWN_PROPERTY "org.freedesktop.DBus.Error.UnknownProperty"
+#endif
+
+#ifndef DBUS_ERROR_PROPERTY_READ_ONLY
+#define DBUS_ERROR_PROPERTY_READ_ONLY "org.freedesktop.DBus.Error.PropertyReadOnly"
+#endif
+
+#define BUS_PROPERTIES_INTERFACE \
+ " <interface name=\"org.freedesktop.DBus.Properties\">\n" \
+ " <method name=\"Get\">\n" \
+ " <arg name=\"interface\" direction=\"in\" type=\"s\"/>\n" \
+ " <arg name=\"property\" direction=\"in\" type=\"s\"/>\n" \
+ " <arg name=\"value\" direction=\"out\" type=\"v\"/>\n" \
+ " </method>\n" \
+ " <method name=\"GetAll\">\n" \
+ " <arg name=\"interface\" direction=\"in\" type=\"s\"/>\n" \
+ " <arg name=\"properties\" direction=\"out\" type=\"a{sv}\"/>\n" \
+ " </method>\n" \
+ " <method name=\"Set\">\n" \
+ " <arg name=\"interface\" direction=\"in\" type=\"s\"/>\n" \
+ " <arg name=\"property\" direction=\"in\" type=\"s\"/>\n" \
+ " <arg name=\"value\" direction=\"in\" type=\"v\"/>\n" \
+ " </method>\n" \
+ " <signal name=\"PropertiesChanged\">\n" \
+ " <arg type=\"s\" name=\"interface\"/>\n" \
+ " <arg type=\"a{sv}\" name=\"changed_properties\"/>\n" \
+ " <arg type=\"as\" name=\"invalidated_properties\"/>\n" \
+ " </signal>\n" \
+ " </interface>\n"
+
+#define BUS_INTROSPECTABLE_INTERFACE \
+ " <interface name=\"org.freedesktop.DBus.Introspectable\">\n" \
+ " <method name=\"Introspect\">\n" \
+ " <arg name=\"data\" type=\"s\" direction=\"out\"/>\n" \
+ " </method>\n" \
+ " </interface>\n"
+
+#define BUS_PEER_INTERFACE \
+ "<interface name=\"org.freedesktop.DBus.Peer\">\n" \
+ " <method name=\"Ping\"/>\n" \
+ " <method name=\"GetMachineId\">\n" \
+ " <arg type=\"s\" name=\"machine_uuid\" direction=\"out\"/>\n" \
+ " </method>\n" \
+ "</interface>\n"
+
+#define BUS_GENERIC_INTERFACES_LIST \
+ "org.freedesktop.DBus.Properties\0" \
+ "org.freedesktop.DBus.Introspectable\0" \
+ "org.freedesktop.DBus.Peer\0"
+
+int bus_check_peercred(DBusConnection *c);
+
+int bus_connect(DBusBusType t, DBusConnection **_bus, bool *private_bus, DBusError *error);
+
+int bus_connect_system_ssh(const char *user, const char *host, DBusConnection **_bus, DBusError *error);
+int bus_connect_system_polkit(DBusConnection **_bus, DBusError *error);
+
+const char *bus_error_message(const DBusError *error);
+const char *bus_error(const DBusError *e, int r);
+
+typedef int (*BusPropertyCallback)(DBusMessageIter *iter, const char *property, void *data);
+typedef int (*BusPropertySetCallback)(DBusMessageIter *iter, const char *property, void *data);
+
+typedef struct BusProperty {
+ const char *property; /* name of the property */
+ BusPropertyCallback append; /* Function that is called to serialize this property */
+ const char *signature;
+ const uint16_t offset; /* Offset from BusBoundProperties::base address to the property data.
+ * uint16_t is sufficient, because we have no structs too big.
+ * -Werror=overflow will catch it if this does not hold. */
+ bool indirect; /* data is indirect, ie. not base+offset, but *(base+offset) */
+ BusPropertySetCallback set; /* Optional: Function that is called to set this property */
+} BusProperty;
+
+typedef struct BusBoundProperties {
+ const char *interface; /* interface of the properties */
+ const BusProperty *properties; /* array of properties, ended by a NULL-filled element */
+ const void *const base; /* base pointer to which the offset must be added to reach data */
+} BusBoundProperties;
+
+dbus_bool_t bus_maybe_send_reply (DBusConnection *c,
+ DBusMessage *message,
+ DBusMessage *reply);
+
+DBusHandlerResult bus_send_error_reply(
+ DBusConnection *c,
+ DBusMessage *message,
+ DBusError *bus_error,
+ int error);
+
+DBusHandlerResult bus_default_message_handler(
+ DBusConnection *c,
+ DBusMessage *message,
+ const char *introspection,
+ const char *interfaces,
+ const BusBoundProperties *bound_properties);
+
+int bus_property_append_string(DBusMessageIter *i, const char *property, void *data);
+int bus_property_append_strv(DBusMessageIter *i, const char *property, void *data);
+int bus_property_append_bool(DBusMessageIter *i, const char *property, void *data);
+int bus_property_append_tristate_false(DBusMessageIter *i, const char *property, void *data);
+int bus_property_append_int32(DBusMessageIter *i, const char *property, void *data);
+int bus_property_append_uint32(DBusMessageIter *i, const char *property, void *data);
+int bus_property_append_uint64(DBusMessageIter *i, const char *property, void *data);
+int bus_property_append_size(DBusMessageIter *i, const char *property, void *data);
+int bus_property_append_ul(DBusMessageIter *i, const char *property, void *data);
+int bus_property_append_long(DBusMessageIter *i, const char *property, void *data);
+
+#define bus_property_append_int bus_property_append_int32
+#define bus_property_append_pid bus_property_append_uint32
+#define bus_property_append_uid bus_property_append_uint32
+#define bus_property_append_gid bus_property_append_uint32
+#define bus_property_append_mode bus_property_append_uint32
+#define bus_property_append_unsigned bus_property_append_uint32
+#define bus_property_append_usec bus_property_append_uint64
+
+int bus_property_set_uint64(DBusMessageIter *i, const char *property, void *data);
+#define bus_property_set_usec bus_property_set_uint64
+
+#define DEFINE_BUS_PROPERTY_APPEND_ENUM(function,name,type) \
+ int function(DBusMessageIter *i, const char *property, void *data) { \
+ const char *value; \
+ type *field = data; \
+ \
+ assert(i); \
+ assert(property); \
+ \
+ value = strempty(name##_to_string(*field)); \
+ \
+ if (!dbus_message_iter_append_basic(i, DBUS_TYPE_STRING, &value)) \
+ return -ENOMEM; \
+ \
+ return 0; \
+ }
+
+#define DEFINE_BUS_PROPERTY_SET_ENUM(function,name,type) \
+ int function(DBusMessageIter *i, const char *property, void *data) { \
+ const char *value; \
+ type f, *field = data; \
+ \
+ assert(i); \
+ assert(property); \
+ \
+ dbus_message_iter_get_basic(i, &value); \
+ \
+ f = name##_from_string(value); \
+ if (f < 0) \
+ return f; \
+ \
+ *field = f; \
+ return 0; \
+ }
+
+const char *bus_errno_to_dbus(int error) _const_;
+
+DBusMessage* bus_properties_changed_new(const char *path, const char *interface, const char *properties);
+DBusMessage* bus_properties_changed_one_new(const char *path, const char *interface, const char *property);
+
+uint32_t bus_flags_to_events(DBusWatch *bus_watch) _pure_;
+unsigned bus_events_to_flags(uint32_t events) _const_;
+
+int bus_parse_strv(DBusMessage *m, char ***_l);
+int bus_parse_strv_iter(DBusMessageIter *iter, char ***_l);
+int bus_parse_strv_pairs_iter(DBusMessageIter *iter, char ***_l);
+
+struct unit_info {
+ const char *id;
+ const char *description;
+ const char *load_state;
+ const char *active_state;
+ const char *sub_state;
+ const char *following;
+ const char *unit_path;
+ uint32_t job_id;
+ const char *job_type;
+ const char *job_path;
+};
+
+int bus_parse_unit_info(DBusMessageIter *iter, struct unit_info *u);
+
+int bus_append_strv_iter(DBusMessageIter *iter, char **l);
+
+int bus_iter_get_basic_and_next(DBusMessageIter *iter, int type, void *data, bool next);
+
+int generic_print_property(const char *name, DBusMessageIter *iter, bool all);
+
+void bus_async_unregister_and_exit(DBusConnection *bus, const char *name);
+
+DBusHandlerResult bus_exit_idle_filter(DBusConnection *bus, DBusMessage *m, void *userdata);
+
+pid_t bus_get_unix_process_id(DBusConnection *connection, const char *name, DBusError *error);
+
+bool bus_error_is_no_service(const DBusError *error);
+int bus_method_call_with_reply(DBusConnection *bus,
+ const char *destination,
+ const char *path,
+ const char *interface,
+ const char *method,
+ DBusMessage **return_reply,
+ DBusError *return_error,
+ int first_arg_type, ...);
+
+const char *bus_message_get_sender_with_fallback(DBusMessage *m);
+
+void bus_message_unrefp(DBusMessage **reply);
+
+#define _cleanup_dbus_message_unref_ __attribute__((cleanup(bus_message_unrefp)))
+#define _cleanup_dbus_error_free_ __attribute__((cleanup(dbus_error_free)))
diff --git a/src/shared/dbus-loop.c b/src/shared/dbus-loop.c
new file mode 100644
index 0000000000..c533242220
--- /dev/null
+++ b/src/shared/dbus-loop.c
@@ -0,0 +1,249 @@
+/*-*- Mode: C; c-basic-offset: 8; indent-tabs-mode: nil -*-*/
+
+/***
+ This file is part of systemd.
+
+ Copyright 2011 Lennart Poettering
+
+ systemd is free software; you can redistribute it and/or modify it
+ under the terms of the GNU Lesser General Public License as published by
+ the Free Software Foundation; either version 2.1 of the License, or
+ (at your option) any later version.
+
+ systemd is distributed in the hope that it will be useful, but
+ WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ Lesser General 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 <assert.h>
+#include <sys/epoll.h>
+#include <string.h>
+#include <errno.h>
+#include <sys/timerfd.h>
+#include <unistd.h>
+
+#include "dbus-loop.h"
+#include "dbus-common.h"
+#include "util.h"
+
+/* Minimal implementation of the dbus loop which integrates all dbus
+ * events into a single epoll fd which we can triviall integrate with
+ * other loops. Note that this is not used in the main systemd daemon
+ * since we run a more elaborate mainloop there. */
+
+typedef struct EpollData {
+ int fd;
+ void *object;
+ bool is_timeout:1;
+ bool fd_is_dupped:1;
+} EpollData;
+
+static dbus_bool_t add_watch(DBusWatch *watch, void *data) {
+ _cleanup_free_ EpollData *e = NULL;
+ struct epoll_event ev = {};
+
+ assert(watch);
+
+ e = new0(EpollData, 1);
+ if (!e)
+ return FALSE;
+
+ e->fd = dbus_watch_get_unix_fd(watch);
+ e->object = watch;
+ e->is_timeout = false;
+
+ ev.events = bus_flags_to_events(watch);
+ ev.data.ptr = e;
+
+ if (epoll_ctl(PTR_TO_INT(data), EPOLL_CTL_ADD, e->fd, &ev) < 0) {
+
+ if (errno != EEXIST)
+ return FALSE;
+
+ /* Hmm, bloody D-Bus creates multiple watches on the
+ * same fd. epoll() does not like that. As a dirty
+ * hack we simply dup() the fd and hence get a second
+ * one we can safely add to the epoll(). */
+
+ e->fd = dup(e->fd);
+ if (e->fd < 0)
+ return FALSE;
+
+ if (epoll_ctl(PTR_TO_INT(data), EPOLL_CTL_ADD, e->fd, &ev) < 0) {
+ close_nointr_nofail(e->fd);
+ return FALSE;
+ }
+
+ e->fd_is_dupped = true;
+ }
+
+ dbus_watch_set_data(watch, e, NULL);
+ e = NULL; /* prevent freeing */
+
+ return TRUE;
+}
+
+static void remove_watch(DBusWatch *watch, void *data) {
+ _cleanup_free_ EpollData *e = NULL;
+
+ assert(watch);
+
+ e = dbus_watch_get_data(watch);
+ if (!e)
+ return;
+
+ assert_se(epoll_ctl(PTR_TO_INT(data), EPOLL_CTL_DEL, e->fd, NULL) >= 0);
+
+ if (e->fd_is_dupped)
+ close_nointr_nofail(e->fd);
+}
+
+static void toggle_watch(DBusWatch *watch, void *data) {
+ EpollData *e;
+ struct epoll_event ev = {};
+
+ assert(watch);
+
+ e = dbus_watch_get_data(watch);
+ if (!e)
+ return;
+
+ ev.data.ptr = e;
+ ev.events = bus_flags_to_events(watch);
+
+ assert_se(epoll_ctl(PTR_TO_INT(data), EPOLL_CTL_MOD, e->fd, &ev) == 0);
+}
+
+static int timeout_arm(EpollData *e) {
+ struct itimerspec its = {};
+
+ assert(e);
+ assert(e->is_timeout);
+
+ if (dbus_timeout_get_enabled(e->object)) {
+ timespec_store(&its.it_value, dbus_timeout_get_interval(e->object) * USEC_PER_MSEC);
+ its.it_interval = its.it_value;
+ }
+
+ if (timerfd_settime(e->fd, 0, &its, NULL) < 0)
+ return -errno;
+
+ return 0;
+}
+
+static dbus_bool_t add_timeout(DBusTimeout *timeout, void *data) {
+ EpollData *e;
+ struct epoll_event ev = {};
+
+ assert(timeout);
+
+ e = new0(EpollData, 1);
+ if (!e)
+ return FALSE;
+
+ e->fd = timerfd_create(CLOCK_MONOTONIC, TFD_NONBLOCK|TFD_CLOEXEC);
+ if (e->fd < 0)
+ goto fail;
+
+ e->object = timeout;
+ e->is_timeout = true;
+
+ if (timeout_arm(e) < 0)
+ goto fail;
+
+ ev.events = EPOLLIN;
+ ev.data.ptr = e;
+
+ if (epoll_ctl(PTR_TO_INT(data), EPOLL_CTL_ADD, e->fd, &ev) < 0)
+ goto fail;
+
+ dbus_timeout_set_data(timeout, e, NULL);
+
+ return TRUE;
+
+fail:
+ if (e->fd >= 0)
+ close_nointr_nofail(e->fd);
+
+ free(e);
+ return FALSE;
+}
+
+static void remove_timeout(DBusTimeout *timeout, void *data) {
+ _cleanup_free_ EpollData *e = NULL;
+
+ assert(timeout);
+
+ e = dbus_timeout_get_data(timeout);
+ if (!e)
+ return;
+
+ assert_se(epoll_ctl(PTR_TO_INT(data), EPOLL_CTL_DEL, e->fd, NULL) >= 0);
+ close_nointr_nofail(e->fd);
+}
+
+static void toggle_timeout(DBusTimeout *timeout, void *data) {
+ EpollData *e;
+ int r;
+
+ assert(timeout);
+
+ e = dbus_timeout_get_data(timeout);
+ if (!e)
+ return;
+
+ r = timeout_arm(e);
+ if (r < 0)
+ log_error("Failed to rearm timer: %s", strerror(-r));
+}
+
+int bus_loop_open(DBusConnection *c) {
+ int fd;
+
+ assert(c);
+
+ fd = epoll_create1(EPOLL_CLOEXEC);
+ if (fd < 0)
+ return -errno;
+
+ if (!dbus_connection_set_watch_functions(c, add_watch, remove_watch, toggle_watch, INT_TO_PTR(fd), NULL) ||
+ !dbus_connection_set_timeout_functions(c, add_timeout, remove_timeout, toggle_timeout, INT_TO_PTR(fd), NULL)) {
+ close_nointr_nofail(fd);
+ return -ENOMEM;
+ }
+
+ return fd;
+}
+
+int bus_loop_dispatch(int fd) {
+ int n;
+ struct epoll_event event = {};
+ EpollData *d;
+
+ assert(fd >= 0);
+
+ n = epoll_wait(fd, &event, 1, 0);
+ if (n < 0)
+ return errno == EAGAIN || errno == EINTR ? 0 : -errno;
+
+ assert_se(d = event.data.ptr);
+
+ if (d->is_timeout) {
+ DBusTimeout *t = d->object;
+
+ if (dbus_timeout_get_enabled(t))
+ dbus_timeout_handle(t);
+ } else {
+ DBusWatch *w = d->object;
+
+ if (dbus_watch_get_enabled(w))
+ dbus_watch_handle(w, bus_events_to_flags(event.events));
+ }
+
+ return 0;
+}
diff --git a/src/shared/dbus-loop.h b/src/shared/dbus-loop.h
new file mode 100644
index 0000000000..a5e768d931
--- /dev/null
+++ b/src/shared/dbus-loop.h
@@ -0,0 +1,27 @@
+/*-*- Mode: C; c-basic-offset: 8; indent-tabs-mode: nil -*-*/
+
+#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/>.
+***/
+
+#include <dbus/dbus.h>
+
+int bus_loop_open(DBusConnection *c);
+int bus_loop_dispatch(int fd);
diff --git a/src/shared/def.h b/src/shared/def.h
new file mode 100644
index 0000000000..5ba170f965
--- /dev/null
+++ b/src/shared/def.h
@@ -0,0 +1,35 @@
+/*-*- Mode: C; c-basic-offset: 8; indent-tabs-mode: nil -*-*/
+
+#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 "util.h"
+
+#define DEFAULT_TIMEOUT_USEC (90*USEC_PER_SEC)
+#define DEFAULT_RESTART_USEC (100*USEC_PER_MSEC)
+#define DEFAULT_CONFIRM_USEC (30*USEC_PER_SEC)
+
+#define DEFAULT_EXIT_USEC (5*USEC_PER_MINUTE)
+
+#define SYSTEMD_CGROUP_CONTROLLER "name=systemd"
+
+#define SIGNALS_CRASH_HANDLER SIGSEGV,SIGILL,SIGFPE,SIGBUS,SIGQUIT,SIGABRT
+#define SIGNALS_IGNORE SIGKILL,SIGPIPE
diff --git a/src/shared/dev-setup.c b/src/shared/dev-setup.c
new file mode 100644
index 0000000000..b0ac02d461
--- /dev/null
+++ b/src/shared/dev-setup.c
@@ -0,0 +1,78 @@
+/*-*- Mode: C; c-basic-offset: 8; indent-tabs-mode: nil -*-*/
+
+/***
+ 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 <sys/stat.h>
+#include <stdlib.h>
+#include <string.h>
+#include <assert.h>
+#include <unistd.h>
+
+#include "dev-setup.h"
+#include "log.h"
+#include "macro.h"
+#include "util.h"
+#include "label.h"
+
+static int symlink_and_label(const char *old_path, const char *new_path) {
+ int r;
+
+ assert(old_path);
+ assert(new_path);
+
+ r = label_context_set(new_path, S_IFLNK);
+ if (r < 0)
+ return r;
+
+ if (symlink(old_path, new_path) < 0)
+ r = -errno;
+
+ label_context_clear();
+
+ return r;
+}
+
+void dev_setup(const char *prefix) {
+ const char *j, *k;
+
+ 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";
+
+ NULSTR_FOREACH_PAIR(j, k, symlinks) {
+
+ if (prefix) {
+ char *linkname;
+
+ if (asprintf(&linkname, "%s/%s", prefix, k) < 0) {
+ log_oom();
+ break;
+ }
+
+ symlink_and_label(j, linkname);
+ free(linkname);
+ } else
+ symlink_and_label(j, k);
+ }
+}
diff --git a/src/shared/dev-setup.h b/src/shared/dev-setup.h
new file mode 100644
index 0000000000..320c0b30ba
--- /dev/null
+++ b/src/shared/dev-setup.h
@@ -0,0 +1,24 @@
+/*-*- Mode: C; c-basic-offset: 8; indent-tabs-mode: nil -*-*/
+
+#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/>.
+***/
+
+void dev_setup(const char *pathprefix);
diff --git a/src/shared/efivars.c b/src/shared/efivars.c
new file mode 100644
index 0000000000..8d004bad33
--- /dev/null
+++ b/src/shared/efivars.c
@@ -0,0 +1,503 @@
+/*-*- Mode: C; c-basic-offset: 8; indent-tabs-mode: nil -*-*/
+
+/***
+ 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 <string.h>
+#include <fcntl.h>
+#include <ctype.h>
+
+#include "util.h"
+#include "utf8.h"
+#include "efivars.h"
+
+#ifdef ENABLE_EFI
+
+bool is_efi_boot(void) {
+ return access("/sys/firmware/efi", F_OK) >= 0;
+}
+
+static int read_flag(const char *varname) {
+ int r;
+ void *v;
+ 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) {
+ r = -EINVAL;
+ goto finish;
+ }
+
+ b = *(uint8_t *)v;
+ r = b > 0;
+finish:
+ free(v);
+ return r;
+}
+
+int is_efi_secure_boot(void) {
+ return read_flag("SecureBoot");
+}
+
+int is_efi_secure_boot_setup_mode(void) {
+ return read_flag("SetupMode");
+}
+
+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;
+ void *r;
+
+ 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;
+
+ r = malloc(st.st_size - 4 + 2);
+ if (!r)
+ return -ENOMEM;
+
+ n = read(fd, r, (size_t) st.st_size - 4);
+ if (n < 0) {
+ free(r);
+ return -errno;
+ }
+ if (n != (ssize_t) st.st_size - 4) {
+ free(r);
+ return -EIO;
+ }
+
+ /* Always NUL terminate (2 bytes, to protect UTF-16) */
+ ((char*) r)[st.st_size - 4] = 0;
+ ((char*) r)[st.st_size - 4 + 1] = 0;
+
+ *value = r;
+ *size = (size_t) st.st_size - 4;
+
+ if (attribute)
+ *attribute = a;
+
+ return 0;
+}
+
+int efi_get_variable_string(sd_id128_t vendor, const char *name, char **p) {
+ _cleanup_free_ void *s = NULL;
+ size_t ss;
+ 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) {
+
+ 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_;
+
+ char boot_id[9];
+ _cleanup_free_ uint8_t *buf = NULL;
+ size_t l;
+ struct boot_option *header;
+ size_t title_size;
+ char *s = NULL;
+ char *p = NULL;
+ sd_id128_t p_uuid = SD_ID128_NULL;
+ int err;
+
+ snprintf(boot_id, sizeof(boot_id), "Boot%04X", id);
+ err = efi_get_variable(EFI_VENDOR_GLOBAL, boot_id, NULL, (void **)&buf, &l);
+ if (err < 0)
+ return err;
+ 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) {
+ err = -ENOMEM;
+ goto err;
+ }
+ }
+
+ 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 == 0x7f && dpath->sub_type == 0xff)
+ break;
+
+ dnext += dpath->length;
+
+ /* Type 0x04 – Media Device Path */
+ if (dpath->type != 0x04)
+ continue;
+
+ /* Sub-Type 1 – Hard Drive */
+ if (dpath->sub_type == 0x01) {
+ /* 0x02 – GUID Partition Table */
+ if (dpath->drive.mbr_type != 0x02)
+ continue;
+
+ /* 0x02 – GUID signature */
+ if (dpath->drive.signature_type != 0x02)
+ continue;
+
+ if (part_uuid)
+ efi_guid_to_id128(dpath->drive.signature, &p_uuid);
+ continue;
+ }
+
+ /* Sub-Type 4 – File Path */
+ if (dpath->sub_type == 0x04 && !p && path) {
+ p = utf16_to_utf8(dpath->path, dpath->length-4);
+ continue;
+ }
+ }
+ }
+
+ if (title)
+ *title = s;
+ if (part_uuid)
+ *part_uuid = p_uuid;
+ if (path)
+ *path = p;
+
+ return 0;
+err:
+ free(s);
+ free(p);
+ return err;
+}
+
+int efi_get_boot_order(uint16_t **order) {
+ void *buf;
+ size_t l;
+ int r;
+
+ r = efi_get_variable(EFI_VENDOR_GLOBAL, "BootOrder", NULL, &buf, &l);
+ if (r < 0)
+ return r;
+
+ if (l <= 0) {
+ free(buf);
+ return -ENOENT;
+ }
+
+ if ((l % sizeof(uint16_t) > 0) ||
+ (l / sizeof(uint16_t) > INT_MAX)) {
+ free(buf);
+ return -EINVAL;
+ }
+
+ *order = buf;
+ return (int) (l / 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 -1;
+
+ 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;
+ uint16_t *list = NULL;
+ int count = 0, r;
+
+ assert(options);
+
+ dir = opendir("/sys/firmware/efi/efivars/");
+ if (!dir)
+ return -errno;
+
+ FOREACH_DIRENT(de, dir, r = -errno; goto fail) {
+ int id;
+ uint16_t *t;
+
+ 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;
+
+ t = realloc(list, (count + 1) * sizeof(uint16_t));
+ if (!t) {
+ r = -ENOMEM;
+ goto fail;
+ }
+
+ list = t;
+ list[count ++] = id;
+ }
+
+ qsort(list, count, sizeof(uint16_t), cmp_uint16);
+
+ *options = list;
+ return count;
+
+fail:
+ free(list);
+ return r;
+}
+
+static int read_usec(sd_id128_t vendor, const char *name, usec_t *u) {
+ _cleanup_free_ char *j = NULL;
+ int r;
+ uint64_t x;
+
+ 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;
+}
+
+static int 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_get_boot_timestamps(const dual_timestamp *n, dual_timestamp *firmware, dual_timestamp *loader) {
+ usec_t x, y, a;
+ int r;
+ dual_timestamp _n;
+
+ assert(firmware);
+ assert(loader);
+
+ if (!n) {
+ dual_timestamp_get(&_n);
+ n = &_n;
+ }
+
+ r = 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;
+}
+
+int efi_get_loader_device_part_uuid(sd_id128_t *u) {
+ _cleanup_free_ char *p = NULL;
+ int r, parsed[16];
+ unsigned i;
+
+ assert(u);
+
+ 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;
+
+ for (i = 0; i < ELEMENTSOF(parsed); i++)
+ u->bytes[i] = parsed[i];
+
+ return 0;
+}
+
+#endif
diff --git a/src/shared/efivars.h b/src/shared/efivars.h
new file mode 100644
index 0000000000..2b88c6075c
--- /dev/null
+++ b/src/shared/efivars.h
@@ -0,0 +1,47 @@
+/*-*- Mode: C; c-basic-offset: 8; indent-tabs-mode: nil -*-*/
+
+#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 <sys/types.h>
+#include <inttypes.h>
+#include <stdbool.h>
+
+#include "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)
+
+bool is_efi_boot(void);
+int is_efi_secure_boot(void);
+int is_efi_secure_boot_setup_mode(void);
+
+int efi_get_variable(sd_id128_t vendor, const char *name, uint32_t *attribute, 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 *partuuid, char **path);
+int efi_get_boot_order(uint16_t **order);
+int efi_get_boot_options(uint16_t **options);
+
+int efi_get_boot_timestamps(const dual_timestamp *n, dual_timestamp *firmware, dual_timestamp *loader);
+
+int efi_get_loader_device_part_uuid(sd_id128_t *u);
diff --git a/src/shared/env-util.c b/src/shared/env-util.c
new file mode 100644
index 0000000000..6a52fb960d
--- /dev/null
+++ b/src/shared/env-util.c
@@ -0,0 +1,415 @@
+/*-*- Mode: C; c-basic-offset: 8; indent-tabs-mode: nil -*-*/
+
+/***
+ This file is part of systemd.
+
+ Copyright 2012 Lennart Poettering
+
+ systemd is free software; you can redistribute it and/or modify it
+ under the terms of the GNU Lesser General Public License as published by
+ the Free Software Foundation; either version 2.1 of the License, or
+ (at your option) any later version.
+
+ systemd is distributed in the hope that it will be useful, but
+ WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ Lesser General 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 <limits.h>
+#include <sys/param.h>
+#include <unistd.h>
+
+#include "strv.h"
+#include "utf8.h"
+#include "util.h"
+#include "env-util.h"
+
+#define VALID_CHARS_ENV_NAME \
+ "0123456789" \
+ "abcdefghijklmnopqrstuvwxyz" \
+ "ABCDEFGHIJKLMNOPQRSTUVWXYZ" \
+ "_"
+
+#ifndef ARG_MAX
+#define ARG_MAX ((size_t) sysconf(_SC_ARG_MAX))
+#endif
+
+static bool env_name_is_valid_n(const char *e, size_t n) {
+ const char *p;
+
+ if (!e)
+ return false;
+
+ if (n <= 0)
+ return false;
+
+ if (e[0] >= '0' && e[0] <= '9')
+ return false;
+
+ /* POSIX says the overall size of the environment block cannot
+ * be > ARG_MAX, an individual assignment hence cannot be
+ * either. Discounting the equal sign and trailing NUL this
+ * hence leaves ARG_MAX-2 as longest possible variable
+ * name. */
+ if (n > ARG_MAX - 2)
+ return false;
+
+ for (p = e; p < e + n; p++)
+ if (!strchr(VALID_CHARS_ENV_NAME, *p))
+ return false;
+
+ return true;
+}
+
+bool env_name_is_valid(const char *e) {
+ if (!e)
+ return false;
+
+ return env_name_is_valid_n(e, strlen(e));
+}
+
+bool env_value_is_valid(const char *e) {
+ if (!e)
+ return false;
+
+ if (!utf8_is_valid(e))
+ return false;
+
+ if (string_has_cc(e))
+ return false;
+
+ /* POSIX says the overall size of the environment block cannot
+ * be > ARG_MAX, an individual assignment hence cannot be
+ * either. Discounting the shortest possible variable name of
+ * length 1, the equal sign and trailing NUL this hence leaves
+ * ARG_MAX-3 as longest possible variable value. */
+ if (strlen(e) > ARG_MAX - 3)
+ return false;
+
+ return true;
+}
+
+bool env_assignment_is_valid(const char *e) {
+ const char *eq;
+
+ eq = strchr(e, '=');
+ if (!eq)
+ return false;
+
+ if (!env_name_is_valid_n(e, eq - e))
+ return false;
+
+ if (!env_value_is_valid(eq + 1))
+ return false;
+
+ /* POSIX says the overall size of the environment block cannot
+ * be > ARG_MAX, hence the individual variable assignments
+ * cannot be either, but let's leave room for one trailing NUL
+ * byte. */
+ if (strlen(e) > ARG_MAX - 1)
+ return false;
+
+ return true;
+}
+
+bool strv_env_is_valid(char **e) {
+ char **p, **q;
+
+ STRV_FOREACH(p, e) {
+ size_t k;
+
+ if (!env_assignment_is_valid(*p))
+ return false;
+
+ /* Check if there are duplicate assginments */
+ k = strcspn(*p, "=");
+ STRV_FOREACH(q, p + 1)
+ if (strneq(*p, *q, k) && (*q)[k] == '=')
+ return false;
+ }
+
+ return true;
+}
+
+bool strv_env_name_or_assignment_is_valid(char **l) {
+ char **p, **q;
+
+ STRV_FOREACH(p, l) {
+ if (!env_assignment_is_valid(*p) && !env_name_is_valid(*p))
+ return false;
+
+ STRV_FOREACH(q, p + 1)
+ if (streq(*p, *q))
+ return false;
+ }
+
+ return true;
+}
+
+static int env_append(char **r, char ***k, char **a) {
+ assert(r);
+ assert(k);
+
+ if (!a)
+ return 0;
+
+ /* Add the entries of a to *k unless they already exist in *r
+ * in which case they are overridden instead. This assumes
+ * there is enough space in the r array. */
+
+ for (; *a; a++) {
+ char **j;
+ size_t n;
+
+ n = strcspn(*a, "=");
+
+ if ((*a)[n] == '=')
+ n++;
+
+ for (j = r; j < *k; j++)
+ if (strneq(*j, *a, n))
+ break;
+
+ if (j >= *k)
+ (*k)++;
+ else
+ free(*j);
+
+ *j = strdup(*a);
+ if (!*j)
+ return -ENOMEM;
+ }
+
+ return 0;
+}
+
+char **strv_env_merge(unsigned n_lists, ...) {
+ size_t n = 0;
+ char **l, **k, **r;
+ va_list ap;
+ unsigned i;
+
+ /* Merges an arbitrary number of environment sets */
+
+ va_start(ap, n_lists);
+ for (i = 0; i < n_lists; i++) {
+ l = va_arg(ap, char**);
+ n += strv_length(l);
+ }
+ va_end(ap);
+
+ r = new(char*, n+1);
+ if (!r)
+ return NULL;
+
+ k = r;
+
+ va_start(ap, n_lists);
+ for (i = 0; i < n_lists; i++) {
+ l = va_arg(ap, char**);
+ if (env_append(r, &k, l) < 0)
+ goto fail;
+ }
+ va_end(ap);
+
+ *k = NULL;
+
+ return r;
+
+fail:
+ va_end(ap);
+ strv_free(r);
+
+ return NULL;
+}
+
+_pure_ static bool env_match(const char *t, const char *pattern) {
+ assert(t);
+ assert(pattern);
+
+ /* pattern a matches string a
+ * a matches a=
+ * a matches a=b
+ * a= matches a=
+ * a=b matches a=b
+ * a= does not match a
+ * a=b does not match a=
+ * a=b does not match a
+ * a=b does not match a=c */
+
+ if (streq(t, pattern))
+ return true;
+
+ if (!strchr(pattern, '=')) {
+ size_t l = strlen(pattern);
+
+ return strneq(t, pattern, l) && t[l] == '=';
+ }
+
+ return false;
+}
+
+char **strv_env_delete(char **x, unsigned n_lists, ...) {
+ size_t n, i = 0;
+ char **k, **r;
+ va_list ap;
+
+ /* Deletes every entry from x that is mentioned in the other
+ * string lists */
+
+ n = strv_length(x);
+
+ r = new(char*, n+1);
+ if (!r)
+ return NULL;
+
+ STRV_FOREACH(k, x) {
+ unsigned v;
+
+ va_start(ap, n_lists);
+ for (v = 0; v < n_lists; v++) {
+ char **l, **j;
+
+ l = va_arg(ap, char**);
+ STRV_FOREACH(j, l)
+ if (env_match(*k, *j))
+ goto skip;
+ }
+ va_end(ap);
+
+ r[i] = strdup(*k);
+ if (!r[i]) {
+ strv_free(r);
+ return NULL;
+ }
+
+ i++;
+ continue;
+
+ skip:
+ va_end(ap);
+ }
+
+ r[i] = NULL;
+
+ assert(i <= n);
+
+ return r;
+}
+
+char **strv_env_unset(char **l, const char *p) {
+
+ char **f, **t;
+
+ if (!l)
+ return NULL;
+
+ assert(p);
+
+ /* Drops every occurrence of the env var setting p in the
+ * string list. edits in-place. */
+
+ for (f = t = l; *f; f++) {
+
+ if (env_match(*f, p)) {
+ free(*f);
+ continue;
+ }
+
+ *(t++) = *f;
+ }
+
+ *t = NULL;
+ return l;
+}
+
+char **strv_env_set(char **x, const char *p) {
+
+ char **k, **r;
+ char* m[2] = { (char*) p, NULL };
+
+ /* Overrides the env var setting of p, returns a new copy */
+
+ r = new(char*, strv_length(x)+2);
+ if (!r)
+ return NULL;
+
+ k = r;
+ if (env_append(r, &k, x) < 0)
+ goto fail;
+
+ if (env_append(r, &k, m) < 0)
+ goto fail;
+
+ *k = NULL;
+
+ return r;
+
+fail:
+ strv_free(r);
+ return NULL;
+}
+
+char *strv_env_get_n(char **l, const char *name, size_t k) {
+ char **i;
+
+ assert(name);
+
+ if (k <= 0)
+ return NULL;
+
+ STRV_FOREACH(i, l)
+ if (strneq(*i, name, k) &&
+ (*i)[k] == '=')
+ return *i + k + 1;
+
+ return NULL;
+}
+
+char *strv_env_get(char **l, const char *name) {
+ assert(name);
+
+ return strv_env_get_n(l, name, strlen(name));
+}
+
+char **strv_env_clean_log(char **e, const char *message) {
+ char **p, **q;
+ int k = 0;
+
+ STRV_FOREACH(p, e) {
+ size_t n;
+ bool duplicate = false;
+
+ if (!env_assignment_is_valid(*p)) {
+ if (message)
+ log_error("Ignoring invalid environment '%s': %s", *p, message);
+ free(*p);
+ continue;
+ }
+
+ n = strcspn(*p, "=");
+ STRV_FOREACH(q, p + 1)
+ if (strneq(*p, *q, n) && (*q)[n] == '=') {
+ duplicate = true;
+ break;
+ }
+
+ if (duplicate) {
+ free(*p);
+ continue;
+ }
+
+ e[k++] = *p;
+ }
+
+ e[k] = NULL;
+ return e;
+}
+
+char **strv_env_clean(char **e) {
+ return strv_env_clean_log(e, NULL);
+}
diff --git a/src/shared/env-util.h b/src/shared/env-util.h
new file mode 100644
index 0000000000..8d2114b64c
--- /dev/null
+++ b/src/shared/env-util.h
@@ -0,0 +1,44 @@
+/*-*- Mode: C; c-basic-offset: 8; indent-tabs-mode: nil -*-*/
+
+#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 <sys/types.h>
+
+bool env_name_is_valid(const char *e);
+bool env_value_is_valid(const char *e);
+bool env_assignment_is_valid(const char *e);
+
+bool strv_env_is_valid(char **e);
+char **strv_env_clean(char **l);
+char **strv_env_clean_log(char **e, const char *message);
+
+bool strv_env_name_or_assignment_is_valid(char **l);
+
+char **strv_env_merge(unsigned n_lists, ...);
+char **strv_env_delete(char **x, unsigned n_lists, ...); /* New copy */
+
+char **strv_env_set(char **x, const char *p); /* New copy ... */
+char **strv_env_unset(char **l, const char *p); /* In place ... */
+
+char *strv_env_get_n(char **l, const char *name, size_t k) _pure_;
+char *strv_env_get(char **x, const char *n) _pure_;
diff --git a/src/shared/exit-status.c b/src/shared/exit-status.c
new file mode 100644
index 0000000000..45131f2b2a
--- /dev/null
+++ b/src/shared/exit-status.c
@@ -0,0 +1,192 @@
+/*-*- Mode: C; c-basic-offset: 8; indent-tabs-mode: nil -*-*/
+
+/***
+ This file is part 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 <stdlib.h>
+#include <sys/wait.h>
+
+#include "exit-status.h"
+#include "set.h"
+#include "macro.h"
+
+const char* exit_status_to_string(ExitStatus status, ExitStatusLevel level) {
+
+ /* We cast to int here, so that -Wenum doesn't complain that
+ * EXIT_SUCCESS/EXIT_FAILURE aren't in the enum */
+
+ switch ((int) status) {
+
+ case EXIT_SUCCESS:
+ return "SUCCESS";
+
+ case EXIT_FAILURE:
+ return "FAILURE";
+ }
+
+
+ if (level == EXIT_STATUS_SYSTEMD || level == EXIT_STATUS_LSB) {
+ switch ((int) status) {
+
+ case EXIT_CHDIR:
+ return "CHDIR";
+
+ case EXIT_NICE:
+ return "NICE";
+
+ case EXIT_FDS:
+ return "FDS";
+
+ case EXIT_EXEC:
+ return "EXEC";
+
+ case EXIT_MEMORY:
+ return "MEMORY";
+
+ case EXIT_LIMITS:
+ return "LIMITS";
+
+ case EXIT_OOM_ADJUST:
+ return "OOM_ADJUST";
+
+ case EXIT_SIGNAL_MASK:
+ return "SIGNAL_MASK";
+
+ case EXIT_STDIN:
+ return "STDIN";
+
+ case EXIT_STDOUT:
+ return "STDOUT";
+
+ case EXIT_CHROOT:
+ return "CHROOT";
+
+ case EXIT_IOPRIO:
+ return "IOPRIO";
+
+ case EXIT_TIMERSLACK:
+ return "TIMERSLACK";
+
+ case EXIT_SECUREBITS:
+ return "SECUREBITS";
+
+ case EXIT_SETSCHEDULER:
+ return "SETSCHEDULER";
+
+ case EXIT_CPUAFFINITY:
+ return "CPUAFFINITY";
+
+ case EXIT_GROUP:
+ return "GROUP";
+
+ case EXIT_USER:
+ return "USER";
+
+ case EXIT_CAPABILITIES:
+ return "CAPABILITIES";
+
+ case EXIT_CGROUP:
+ return "CGROUP";
+
+ case EXIT_SETSID:
+ return "SETSID";
+
+ case EXIT_CONFIRM:
+ return "CONFIRM";
+
+ case EXIT_STDERR:
+ return "STDERR";
+
+ case EXIT_TCPWRAP:
+ return "TCPWRAP";
+
+ case EXIT_PAM:
+ return "PAM";
+
+ case EXIT_NETWORK:
+ return "NETWORK";
+
+ case EXIT_NAMESPACE:
+ return "NAMESPACE";
+
+ case EXIT_NO_NEW_PRIVILEGES:
+ return "NO_NEW_PRIVILEGES";
+
+ case EXIT_SECCOMP:
+ return "SECCOMP";
+ }
+ }
+
+ if (level == EXIT_STATUS_LSB) {
+ switch ((int) status) {
+
+ case EXIT_INVALIDARGUMENT:
+ return "INVALIDARGUMENT";
+
+ case EXIT_NOTIMPLEMENTED:
+ return "NOTIMPLEMENTED";
+
+ case EXIT_NOPERMISSION:
+ return "NOPERMISSION";
+
+ case EXIT_NOTINSTALLED:
+ return "NOTINSSTALLED";
+
+ case EXIT_NOTCONFIGURED:
+ return "NOTCONFIGURED";
+
+ case EXIT_NOTRUNNING:
+ return "NOTRUNNING";
+ }
+ }
+
+ return NULL;
+}
+
+
+bool is_clean_exit(int code, int status, ExitStatusSet *success_status) {
+
+ if (code == CLD_EXITED)
+ return status == 0 ||
+ (success_status &&
+ set_contains(success_status->code, INT_TO_PTR(status)));
+
+ /* If a daemon does not implement handlers for some of the
+ * signals that's not considered an unclean shutdown */
+ if (code == CLD_KILLED)
+ return
+ status == SIGHUP ||
+ status == SIGINT ||
+ status == SIGTERM ||
+ status == SIGPIPE ||
+ (success_status &&
+ set_contains(success_status->signal, INT_TO_PTR(status)));
+
+ return false;
+}
+
+bool is_clean_exit_lsb(int code, int status, ExitStatusSet *success_status) {
+
+ if (is_clean_exit(code, status, success_status))
+ return true;
+
+ return
+ code == CLD_EXITED &&
+ (status == EXIT_NOTINSTALLED || status == EXIT_NOTCONFIGURED);
+}
diff --git a/src/shared/exit-status.h b/src/shared/exit-status.h
new file mode 100644
index 0000000000..1f035a3007
--- /dev/null
+++ b/src/shared/exit-status.h
@@ -0,0 +1,88 @@
+/*-*- Mode: C; c-basic-offset: 8; indent-tabs-mode: nil -*-*/
+
+#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 "set.h"
+typedef enum ExitStatus {
+ /* EXIT_SUCCESS defined by libc */
+ /* EXIT_FAILURE defined by libc */
+ EXIT_INVALIDARGUMENT = 2,
+ EXIT_NOTIMPLEMENTED = 3,
+ EXIT_NOPERMISSION = 4,
+ EXIT_NOTINSTALLED = 5,
+ EXIT_NOTCONFIGURED = 6,
+ EXIT_NOTRUNNING = 7,
+
+ /* The LSB suggests that error codes >= 200 are "reserved". We
+ * use them here under the assumption that they hence are
+ * unused by init scripts.
+ *
+ * http://refspecs.freestandards.org/LSB_3.1.0/LSB-Core-generic/LSB-Core-generic/iniscrptact.html */
+
+ EXIT_CHDIR = 200,
+ EXIT_NICE,
+ EXIT_FDS,
+ EXIT_EXEC,
+ EXIT_MEMORY,
+ EXIT_LIMITS,
+ EXIT_OOM_ADJUST,
+ EXIT_SIGNAL_MASK,
+ EXIT_STDIN,
+ EXIT_STDOUT,
+ EXIT_CHROOT, /* 210 */
+ EXIT_IOPRIO,
+ EXIT_TIMERSLACK,
+ EXIT_SECUREBITS,
+ EXIT_SETSCHEDULER,
+ EXIT_CPUAFFINITY,
+ EXIT_GROUP,
+ EXIT_USER,
+ EXIT_CAPABILITIES,
+ EXIT_CGROUP,
+ EXIT_SETSID, /* 220 */
+ EXIT_CONFIRM,
+ EXIT_STDERR,
+ EXIT_TCPWRAP,
+ EXIT_PAM,
+ EXIT_NETWORK,
+ EXIT_NAMESPACE,
+ EXIT_NO_NEW_PRIVILEGES,
+ EXIT_SECCOMP
+} ExitStatus;
+
+typedef enum ExitStatusLevel {
+ EXIT_STATUS_MINIMAL,
+ EXIT_STATUS_SYSTEMD,
+ EXIT_STATUS_LSB,
+ EXIT_STATUS_FULL = EXIT_STATUS_LSB
+} ExitStatusLevel;
+
+typedef struct ExitStatusSet {
+ Set *code;
+ Set *signal;
+} ExitStatusSet;
+
+const char* exit_status_to_string(ExitStatus status, ExitStatusLevel level) _const_;
+
+bool is_clean_exit(int code, int status, ExitStatusSet *success_status);
+bool is_clean_exit_lsb(int code, int status, ExitStatusSet *success_status);
diff --git a/src/shared/fdset.c b/src/shared/fdset.c
new file mode 100644
index 0000000000..fd27398ebb
--- /dev/null
+++ b/src/shared/fdset.c
@@ -0,0 +1,236 @@
+/*-*- Mode: C; c-basic-offset: 8; indent-tabs-mode: nil -*-*/
+
+/***
+ This file is part 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 <dirent.h>
+#include <fcntl.h>
+#include <unistd.h>
+
+#include "set.h"
+#include "util.h"
+#include "macro.h"
+#include "fdset.h"
+#include "sd-daemon.h"
+
+#define MAKE_SET(s) ((Set*) s)
+#define MAKE_FDSET(s) ((FDSet*) s)
+
+/* Make sure we can distuingish fd 0 and NULL */
+#define FD_TO_PTR(fd) INT_TO_PTR((fd)+1)
+#define PTR_TO_FD(p) (PTR_TO_INT(p)-1)
+
+FDSet *fdset_new(void) {
+ return MAKE_FDSET(set_new(trivial_hash_func, trivial_compare_func));
+}
+
+void fdset_free(FDSet *s) {
+ void *p;
+
+ while ((p = set_steal_first(MAKE_SET(s)))) {
+ /* Valgrind's fd might have ended up in this set here,
+ * due to fdset_new_fill(). We'll ignore all failures
+ * here, so that the EBADFD that valgrind will return
+ * us on close() doesn't influence us */
+
+ /* When reloading duplicates of the private bus
+ * connection fds and suchlike are closed here, which
+ * has no effect at all, since they are only
+ * duplicates. So don't be surprised about these log
+ * messages. */
+
+ log_debug("Closing left-over fd %i", PTR_TO_FD(p));
+ close_nointr(PTR_TO_FD(p));
+ }
+
+ set_free(MAKE_SET(s));
+}
+
+int fdset_put(FDSet *s, int fd) {
+ assert(s);
+ assert(fd >= 0);
+
+ return set_put(MAKE_SET(s), FD_TO_PTR(fd));
+}
+
+int fdset_put_dup(FDSet *s, int fd) {
+ int copy, r;
+
+ assert(s);
+ assert(fd >= 0);
+
+ copy = fcntl(fd, F_DUPFD_CLOEXEC, 3);
+ if (copy < 0)
+ return -errno;
+
+ r = fdset_put(s, copy);
+ if (r < 0) {
+ close_nointr_nofail(copy);
+ return r;
+ }
+
+ return copy;
+}
+
+bool fdset_contains(FDSet *s, int fd) {
+ assert(s);
+ assert(fd >= 0);
+
+ return !!set_get(MAKE_SET(s), FD_TO_PTR(fd));
+}
+
+int fdset_remove(FDSet *s, int fd) {
+ assert(s);
+ assert(fd >= 0);
+
+ return set_remove(MAKE_SET(s), FD_TO_PTR(fd)) ? fd : -ENOENT;
+}
+
+int fdset_new_fill(FDSet **_s) {
+ DIR *d;
+ struct dirent *de;
+ int r = 0;
+ FDSet *s;
+
+ assert(_s);
+
+ /* Creates an fdset and fills in all currently open file
+ * descriptors. */
+
+ d = opendir("/proc/self/fd");
+ if (!d)
+ return -errno;
+
+ s = fdset_new();
+ if (!s) {
+ r = -ENOMEM;
+ goto finish;
+ }
+
+ while ((de = readdir(d))) {
+ int fd = -1;
+
+ if (ignore_file(de->d_name))
+ continue;
+
+ r = safe_atoi(de->d_name, &fd);
+ if (r < 0)
+ goto finish;
+
+ if (fd < 3)
+ continue;
+
+ if (fd == dirfd(d))
+ continue;
+
+ r = fdset_put(s, fd);
+ if (r < 0)
+ goto finish;
+ }
+
+ r = 0;
+ *_s = s;
+ s = NULL;
+
+finish:
+ closedir(d);
+
+ /* We won't close the fds here! */
+ if (s)
+ set_free(MAKE_SET(s));
+
+ return r;
+}
+
+int fdset_cloexec(FDSet *fds, bool b) {
+ Iterator i;
+ void *p;
+ int r;
+
+ assert(fds);
+
+ SET_FOREACH(p, MAKE_SET(fds), i)
+ if ((r = fd_cloexec(PTR_TO_FD(p), b)) < 0)
+ return r;
+
+ return 0;
+}
+
+int fdset_new_listen_fds(FDSet **_s, bool unset) {
+ int n, fd, r;
+ FDSet *s;
+
+ assert(_s);
+
+ /* Creates an fdset and fills in all passed file descriptors */
+
+ s = fdset_new();
+ if (!s) {
+ r = -ENOMEM;
+ goto fail;
+ }
+
+ n = sd_listen_fds(unset);
+ for (fd = SD_LISTEN_FDS_START; fd < SD_LISTEN_FDS_START + n; fd ++) {
+ r = fdset_put(s, fd);
+ if (r < 0)
+ goto fail;
+ }
+
+ *_s = s;
+ return 0;
+
+
+fail:
+ if (s)
+ set_free(MAKE_SET(s));
+
+ return r;
+}
+
+int fdset_close_others(FDSet *fds) {
+ void *e;
+ Iterator i;
+ int *a;
+ unsigned j, m;
+
+ j = 0, m = fdset_size(fds);
+ a = alloca(sizeof(int) * m);
+ SET_FOREACH(e, MAKE_SET(fds), i)
+ a[j++] = PTR_TO_FD(e);
+
+ assert(j == m);
+
+ return close_all_fds(a, j);
+}
+
+unsigned fdset_size(FDSet *fds) {
+ return set_size(MAKE_SET(fds));
+}
+
+int fdset_iterate(FDSet *s, Iterator *i) {
+ void *p;
+
+ p = set_iterate(MAKE_SET(s), i);
+ if (!p)
+ return -ENOENT;
+
+ return PTR_TO_FD(p);
+}
diff --git a/src/shared/fdset.h b/src/shared/fdset.h
new file mode 100644
index 0000000000..a7bd5e2b40
--- /dev/null
+++ b/src/shared/fdset.h
@@ -0,0 +1,49 @@
+/*-*- Mode: C; c-basic-offset: 8; indent-tabs-mode: nil -*-*/
+
+#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 "set.h"
+
+typedef struct FDSet FDSet;
+
+FDSet* fdset_new(void);
+void fdset_free(FDSet *s);
+
+int fdset_put(FDSet *s, int fd);
+int fdset_put_dup(FDSet *s, int fd);
+
+bool fdset_contains(FDSet *s, int fd);
+int fdset_remove(FDSet *s, int fd);
+
+int fdset_new_fill(FDSet **_s);
+int fdset_new_listen_fds(FDSet **_s, bool unset);
+
+int fdset_cloexec(FDSet *fds, bool b);
+
+int fdset_close_others(FDSet *fds);
+
+unsigned fdset_size(FDSet *fds);
+
+int fdset_iterate(FDSet *s, Iterator *i);
+
+#define FDSET_FOREACH(fd, fds, i) \
+ for ((i) = ITERATOR_FIRST, (fd) = fdset_iterate((fds), &(i)); (fd) >= 0; (fd) = fdset_iterate((fds), &(i)))
diff --git a/src/shared/fileio-label.c b/src/shared/fileio-label.c
new file mode 100644
index 0000000000..0711826e85
--- /dev/null
+++ b/src/shared/fileio-label.c
@@ -0,0 +1,55 @@
+/*-*- Mode: C; c-basic-offset: 8; indent-tabs-mode: nil -*-*/
+
+/***
+ This file is part of systemd.
+
+ Copyright 2010 Lennart Poettering
+ Copyright 2010 Harald Hoyer
+
+ systemd is free software; you can redistribute it and/or modify it
+ under the terms of the GNU Lesser General Public License as published by
+ the Free Software Foundation; either version 2.1 of the License, or
+ (at your option) any later version.
+
+ systemd is distributed in the hope that it will be useful, but
+ WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ Lesser General 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>
+#include <sys/stat.h>
+#include <unistd.h>
+
+#include "fileio-label.h"
+#include "label.h"
+
+int write_string_file_atomic_label(const char *fn, const char *line) {
+ int r;
+
+ r = label_context_set(fn, S_IFREG);
+ if (r < 0)
+ return r;
+
+ write_string_file_atomic(fn, line);
+
+ label_context_clear();
+
+ return r;
+}
+
+int write_env_file_label(const char *fname, char **l) {
+ int r;
+
+ r = label_context_set(fname, S_IFREG);
+ if (r < 0)
+ return r;
+
+ write_env_file(fname, l);
+
+ label_context_clear();
+
+ return r;
+}
diff --git a/src/shared/fileio-label.h b/src/shared/fileio-label.h
new file mode 100644
index 0000000000..fce4fe0d73
--- /dev/null
+++ b/src/shared/fileio-label.h
@@ -0,0 +1,29 @@
+/*-*- Mode: C; c-basic-offset: 8; indent-tabs-mode: nil -*-*/
+
+#pragma once
+
+/***
+ This file is part of systemd.
+
+ Copyright 2010 Lennart Poettering
+ Copyright 2010 Harald Hoyer
+
+ systemd is free software; you can redistribute it and/or modify it
+ under the terms of the GNU Lesser General Public License as published by
+ the Free Software Foundation; either version 2.1 of the License, or
+ (at your option) any later version.
+
+ systemd is distributed in the hope that it will be useful, but
+ WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ Lesser General 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 "fileio.h"
+
+int write_string_file_atomic_label(const char *fn, const char *line);
+int write_env_file_label(const char *fname, char **l);
diff --git a/src/shared/fileio.c b/src/shared/fileio.c
new file mode 100644
index 0000000000..ad068bf30d
--- /dev/null
+++ b/src/shared/fileio.c
@@ -0,0 +1,596 @@
+/*-*- Mode: C; c-basic-offset: 8; indent-tabs-mode: nil -*-*/
+
+/***
+ This file is part 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 <unistd.h>
+#include "fileio.h"
+#include "util.h"
+#include "strv.h"
+
+
+int write_string_to_file(FILE *f, const char *line) {
+ errno = 0;
+ fputs(line, f);
+ if (!endswith(line, "\n"))
+ fputc('\n', f);
+
+ fflush(f);
+
+ if (ferror(f))
+ return errno ? -errno : -EIO;
+
+ return 0;
+}
+
+int write_string_file(const char *fn, const char *line) {
+ _cleanup_fclose_ FILE *f = NULL;
+
+ assert(fn);
+ assert(line);
+
+ f = fopen(fn, "we");
+ if (!f)
+ return -errno;
+
+ return write_string_to_file(f, line);
+}
+
+int write_string_file_atomic(const char *fn, const char *line) {
+ _cleanup_fclose_ FILE *f = NULL;
+ _cleanup_free_ char *p = NULL;
+ int r;
+
+ assert(fn);
+ assert(line);
+
+ r = fopen_temporary(fn, &f, &p);
+ if (r < 0)
+ return r;
+
+ fchmod_umask(fileno(f), 0644);
+
+ errno = 0;
+ fputs(line, f);
+ if (!endswith(line, "\n"))
+ fputc('\n', f);
+
+ fflush(f);
+
+ if (ferror(f))
+ r = errno ? -errno : -EIO;
+ else {
+ if (rename(p, fn) < 0)
+ r = -errno;
+ else
+ r = 0;
+ }
+
+ if (r < 0)
+ unlink(p);
+
+ return r;
+}
+
+int read_one_line_file(const char *fn, char **line) {
+ _cleanup_fclose_ FILE *f = NULL;
+ char t[LINE_MAX], *c;
+
+ assert(fn);
+ assert(line);
+
+ f = fopen(fn, "re");
+ if (!f)
+ return -errno;
+
+ if (!fgets(t, sizeof(t), f)) {
+
+ if (ferror(f))
+ return errno ? -errno : -EIO;
+
+ t[0] = 0;
+ }
+
+ c = strdup(t);
+ if (!c)
+ return -ENOMEM;
+ truncate_nl(c);
+
+ *line = c;
+ return 0;
+}
+
+int read_full_file(const char *fn, char **contents, size_t *size) {
+ _cleanup_fclose_ FILE *f = NULL;
+ size_t n, l;
+ _cleanup_free_ char *buf = NULL;
+ struct stat st;
+
+ assert(fn);
+ assert(contents);
+
+ f = fopen(fn, "re");
+ if (!f)
+ return -errno;
+
+ if (fstat(fileno(f), &st) < 0)
+ return -errno;
+
+ /* Safety check */
+ if (st.st_size > 4*1024*1024)
+ return -E2BIG;
+
+ n = st.st_size > 0 ? st.st_size : LINE_MAX;
+ l = 0;
+
+ for (;;) {
+ char *t;
+ size_t k;
+
+ t = realloc(buf, n+1);
+ if (!t)
+ return -ENOMEM;
+
+ buf = t;
+ k = fread(buf + l, 1, n - l, f);
+
+ if (k <= 0) {
+ if (ferror(f))
+ return -errno;
+
+ break;
+ }
+
+ l += k;
+ n *= 2;
+
+ /* Safety check */
+ if (n > 4*1024*1024)
+ return -E2BIG;
+ }
+
+ buf[l] = 0;
+ *contents = buf;
+ buf = NULL;
+
+ if (size)
+ *size = l;
+
+ return 0;
+}
+
+static int parse_env_file_internal(
+ const char *fname,
+ const char *newline,
+ int (*push) (const char *key, char *value, void *userdata),
+ void *userdata) {
+
+ _cleanup_free_ char *contents = NULL, *key = NULL;
+ size_t key_alloc = 0, n_key = 0, value_alloc = 0, n_value = 0, last_value_whitespace = (size_t) -1, last_key_whitespace = (size_t) -1;
+ char *p, *value = NULL;
+ int r;
+
+ enum {
+ PRE_KEY,
+ KEY,
+ PRE_VALUE,
+ VALUE,
+ VALUE_ESCAPE,
+ SINGLE_QUOTE_VALUE,
+ SINGLE_QUOTE_VALUE_ESCAPE,
+ DOUBLE_QUOTE_VALUE,
+ DOUBLE_QUOTE_VALUE_ESCAPE,
+ COMMENT,
+ COMMENT_ESCAPE
+ } state = PRE_KEY;
+
+ assert(fname);
+ assert(newline);
+
+ r = read_full_file(fname, &contents, NULL);
+ if (r < 0)
+ return r;
+
+ for (p = contents; *p; p++) {
+ char c = *p;
+
+ switch (state) {
+
+ case PRE_KEY:
+ if (strchr(COMMENTS, c))
+ state = COMMENT;
+ else if (!strchr(WHITESPACE, c)) {
+ state = KEY;
+ last_key_whitespace = (size_t) -1;
+
+ if (!greedy_realloc((void**) &key, &key_alloc, n_key+2)) {
+ r = -ENOMEM;
+ goto fail;
+ }
+
+ key[n_key++] = c;
+ }
+ break;
+
+ case KEY:
+ if (strchr(newline, c)) {
+ state = PRE_KEY;
+ n_key = 0;
+ } else if (c == '=') {
+ state = PRE_VALUE;
+ last_value_whitespace = (size_t) -1;
+ } else {
+ if (!strchr(WHITESPACE, c))
+ last_key_whitespace = (size_t) -1;
+ else if (last_key_whitespace == (size_t) -1)
+ last_key_whitespace = n_key;
+
+ if (!greedy_realloc((void**) &key, &key_alloc, n_key+2)) {
+ r = -ENOMEM;
+ goto fail;
+ }
+
+ key[n_key++] = c;
+ }
+
+ break;
+
+ case PRE_VALUE:
+ if (strchr(newline, c)) {
+ state = PRE_KEY;
+ key[n_key] = 0;
+
+ if (value)
+ value[n_value] = 0;
+
+ /* strip trailing whitespace from key */
+ if (last_key_whitespace != (size_t) -1)
+ key[last_key_whitespace] = 0;
+
+ r = push(key, value, userdata);
+ if (r < 0)
+ goto fail;
+
+ n_key = 0;
+ value = NULL;
+ value_alloc = n_value = 0;
+
+ } else if (c == '\'')
+ state = SINGLE_QUOTE_VALUE;
+ else if (c == '\"')
+ state = DOUBLE_QUOTE_VALUE;
+ else if (c == '\\')
+ state = VALUE_ESCAPE;
+ else if (!strchr(WHITESPACE, c)) {
+ state = VALUE;
+
+ if (!greedy_realloc((void**) &value, &value_alloc, n_value+2)) {
+ r = -ENOMEM;
+ goto fail;
+ }
+
+ value[n_value++] = c;
+ }
+
+ break;
+
+ case VALUE:
+ if (strchr(newline, c)) {
+ state = PRE_KEY;
+
+ key[n_key] = 0;
+
+ if (value)
+ value[n_value] = 0;
+
+ /* Chomp off trailing whitespace from value */
+ if (last_value_whitespace != (size_t) -1)
+ value[last_value_whitespace] = 0;
+
+ /* strip trailing whitespace from key */
+ if (last_key_whitespace != (size_t) -1)
+ key[last_key_whitespace] = 0;
+
+ r = push(key, value, userdata);
+ if (r < 0)
+ goto fail;
+
+ n_key = 0;
+ value = NULL;
+ value_alloc = n_value = 0;
+
+ } else if (c == '\\') {
+ state = VALUE_ESCAPE;
+ last_value_whitespace = (size_t) -1;
+ } else {
+ if (!strchr(WHITESPACE, c))
+ last_value_whitespace = (size_t) -1;
+ else if (last_value_whitespace == (size_t) -1)
+ last_value_whitespace = n_value;
+
+ if (!greedy_realloc((void**) &value, &value_alloc, n_value+2)) {
+ r = -ENOMEM;
+ goto fail;
+ }
+
+ value[n_value++] = c;
+ }
+
+ break;
+
+ case VALUE_ESCAPE:
+ state = VALUE;
+
+ if (!strchr(newline, c)) {
+ /* Escaped newlines we eat up entirely */
+ if (!greedy_realloc((void**) &value, &value_alloc, n_value+2)) {
+ r = -ENOMEM;
+ goto fail;
+ }
+
+ value[n_value++] = c;
+ }
+ break;
+
+ case SINGLE_QUOTE_VALUE:
+ if (c == '\'')
+ state = PRE_VALUE;
+ else if (c == '\\')
+ state = SINGLE_QUOTE_VALUE_ESCAPE;
+ else {
+ if (!greedy_realloc((void**) &value, &value_alloc, n_value+2)) {
+ r = -ENOMEM;
+ goto fail;
+ }
+
+ value[n_value++] = c;
+ }
+
+ break;
+
+ case SINGLE_QUOTE_VALUE_ESCAPE:
+ state = SINGLE_QUOTE_VALUE;
+
+ if (!strchr(newline, c)) {
+ if (!greedy_realloc((void**) &value, &value_alloc, n_value+2)) {
+ r = -ENOMEM;
+ goto fail;
+ }
+
+ value[n_value++] = c;
+ }
+ break;
+
+ case DOUBLE_QUOTE_VALUE:
+ if (c == '\"')
+ state = PRE_VALUE;
+ else if (c == '\\')
+ state = DOUBLE_QUOTE_VALUE_ESCAPE;
+ else {
+ if (!greedy_realloc((void**) &value, &value_alloc, n_value+2)) {
+ r = -ENOMEM;
+ goto fail;
+ }
+
+ value[n_value++] = c;
+ }
+
+ break;
+
+ case DOUBLE_QUOTE_VALUE_ESCAPE:
+ state = DOUBLE_QUOTE_VALUE;
+
+ if (!strchr(newline, c)) {
+ if (!greedy_realloc((void**) &value, &value_alloc, n_value+2)) {
+ r = -ENOMEM;
+ goto fail;
+ }
+
+ value[n_value++] = c;
+ }
+ break;
+
+ case COMMENT:
+ if (c == '\\')
+ state = COMMENT_ESCAPE;
+ else if (strchr(newline, c))
+ state = PRE_KEY;
+ break;
+
+ case COMMENT_ESCAPE:
+ state = COMMENT;
+ break;
+ }
+ }
+
+ if (state == PRE_VALUE ||
+ state == VALUE ||
+ state == VALUE_ESCAPE ||
+ state == SINGLE_QUOTE_VALUE ||
+ state == SINGLE_QUOTE_VALUE_ESCAPE ||
+ state == DOUBLE_QUOTE_VALUE ||
+ state == DOUBLE_QUOTE_VALUE_ESCAPE) {
+
+ key[n_key] = 0;
+
+ if (value)
+ value[n_value] = 0;
+
+ if (state == VALUE)
+ if (last_value_whitespace != (size_t) -1)
+ value[last_value_whitespace] = 0;
+
+ /* strip trailing whitespace from key */
+ if (last_key_whitespace != (size_t) -1)
+ key[last_key_whitespace] = 0;
+
+ r = push(key, value, userdata);
+ if (r < 0)
+ goto fail;
+ }
+
+ return 0;
+
+fail:
+ free(value);
+ return r;
+}
+
+static int parse_env_file_push(const char *key, char *value, void *userdata) {
+ const char *k;
+ va_list* ap = (va_list*) userdata;
+ va_list aq;
+
+ va_copy(aq, *ap);
+
+ while ((k = va_arg(aq, const char *))) {
+ char **v;
+
+ v = va_arg(aq, char **);
+
+ if (streq(key, k)) {
+ va_end(aq);
+ free(*v);
+ *v = value;
+ return 1;
+ }
+ }
+
+ va_end(aq);
+
+ free(value);
+ return 0;
+}
+
+int parse_env_file(
+ const char *fname,
+ const char *newline, ...) {
+
+ va_list ap;
+ int r;
+
+ if (!newline)
+ newline = NEWLINE;
+
+ va_start(ap, newline);
+ r = parse_env_file_internal(fname, newline, parse_env_file_push, &ap);
+ va_end(ap);
+
+ return r;
+}
+
+static int load_env_file_push(const char *key, char *value, void *userdata) {
+ char ***m = userdata;
+ char *p;
+ int r;
+
+ p = strjoin(key, "=", strempty(value), NULL);
+ if (!p)
+ return -ENOMEM;
+
+ r = strv_push(m, p);
+ if (r < 0) {
+ free(p);
+ return r;
+ }
+
+ free(value);
+ return 0;
+}
+
+int load_env_file(const char *fname, const char *newline, char ***rl) {
+ char **m = NULL;
+ int r;
+
+ if (!newline)
+ newline = NEWLINE;
+
+ r = parse_env_file_internal(fname, newline, load_env_file_push, &m);
+ if (r < 0) {
+ strv_free(m);
+ return r;
+ }
+
+ *rl = m;
+ return 0;
+}
+
+static void write_env_var(FILE *f, const char *v) {
+ const char *p;
+
+ p = strchr(v, '=');
+ if (!p) {
+ /* Fallback */
+ fputs(v, f);
+ fputc('\n', f);
+ return;
+ }
+
+ p++;
+ fwrite(v, 1, p-v, f);
+
+ if (string_has_cc(p) || chars_intersect(p, WHITESPACE "\'\"\\`$")) {
+ fputc('\"', f);
+
+ for (; *p; p++) {
+ if (strchr("\'\"\\`$", *p))
+ fputc('\\', f);
+
+ fputc(*p, f);
+ }
+
+ fputc('\"', f);
+ } else
+ fputs(p, f);
+
+ fputc('\n', f);
+}
+
+int write_env_file(const char *fname, char **l) {
+ char **i;
+ _cleanup_free_ char *p = NULL;
+ _cleanup_fclose_ FILE *f = NULL;
+ int r;
+
+ r = fopen_temporary(fname, &f, &p);
+ if (r < 0)
+ return r;
+
+ fchmod_umask(fileno(f), 0644);
+
+ errno = 0;
+ STRV_FOREACH(i, l)
+ write_env_var(f, *i);
+
+ fflush(f);
+
+ if (ferror(f))
+ r = errno ? -errno : -EIO;
+ else {
+ if (rename(p, fname) < 0)
+ r = -errno;
+ else
+ r = 0;
+ }
+
+ if (r < 0)
+ unlink(p);
+
+ return r;
+}
diff --git a/src/shared/fileio.h b/src/shared/fileio.h
new file mode 100644
index 0000000000..0ca6878ea4
--- /dev/null
+++ b/src/shared/fileio.h
@@ -0,0 +1,37 @@
+/*-*- Mode: C; c-basic-offset: 8; indent-tabs-mode: nil -*-*/
+
+#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 <stddef.h>
+#include <stdio.h>
+
+#include "macro.h"
+
+int write_string_to_file(FILE *f, const char *line);
+int write_string_file(const char *fn, const char *line);
+int write_string_file_atomic(const char *fn, const char *line);
+
+int read_one_line_file(const char *fn, char **line);
+int read_full_file(const char *fn, char **contents, size_t *size);
+
+int parse_env_file(const char *fname, const char *separator, ...) _sentinel_;
+int load_env_file(const char *fname, const char *separator, char ***l);
+int write_env_file(const char *fname, char **l);
diff --git a/src/shared/hashmap.c b/src/shared/hashmap.c
new file mode 100644
index 0000000000..9f7db34397
--- /dev/null
+++ b/src/shared/hashmap.c
@@ -0,0 +1,862 @@
+/*-*- Mode: C; c-basic-offset: 8; indent-tabs-mode: nil -*-*/
+
+/***
+ This file is part 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 <assert.h>
+#include <stdlib.h>
+#include <string.h>
+#include <errno.h>
+
+#include "util.h"
+#include "hashmap.h"
+#include "macro.h"
+
+#define NBUCKETS 127
+
+struct hashmap_entry {
+ const void *key;
+ void *value;
+ struct hashmap_entry *bucket_next, *bucket_previous;
+ struct hashmap_entry *iterate_next, *iterate_previous;
+};
+
+struct Hashmap {
+ hash_func_t hash_func;
+ compare_func_t compare_func;
+
+ struct hashmap_entry *iterate_list_head, *iterate_list_tail;
+ unsigned n_entries;
+
+ bool from_pool;
+};
+
+#define BY_HASH(h) ((struct hashmap_entry**) ((uint8_t*) (h) + ALIGN(sizeof(Hashmap))))
+
+struct pool {
+ struct pool *next;
+ unsigned n_tiles;
+ unsigned n_used;
+};
+
+static struct pool *first_hashmap_pool = NULL;
+static void *first_hashmap_tile = NULL;
+
+static struct pool *first_entry_pool = NULL;
+static void *first_entry_tile = NULL;
+
+static void* allocate_tile(struct pool **first_pool, void **first_tile, size_t tile_size) {
+ unsigned i;
+
+ if (*first_tile) {
+ void *r;
+
+ r = *first_tile;
+ *first_tile = * (void**) (*first_tile);
+ return r;
+ }
+
+ if (_unlikely_(!*first_pool) || _unlikely_((*first_pool)->n_used >= (*first_pool)->n_tiles)) {
+ unsigned n;
+ size_t size;
+ struct pool *p;
+
+ n = *first_pool ? (*first_pool)->n_tiles : 0;
+ n = MAX(512U, n * 2);
+ size = PAGE_ALIGN(ALIGN(sizeof(struct pool)) + n*tile_size);
+ n = (size - ALIGN(sizeof(struct pool))) / tile_size;
+
+ p = malloc(size);
+ if (!p)
+ return NULL;
+
+ p->next = *first_pool;
+ p->n_tiles = n;
+ p->n_used = 0;
+
+ *first_pool = p;
+ }
+
+ i = (*first_pool)->n_used++;
+
+ return ((uint8_t*) (*first_pool)) + ALIGN(sizeof(struct pool)) + i*tile_size;
+}
+
+static void deallocate_tile(void **first_tile, void *p) {
+ * (void**) p = *first_tile;
+ *first_tile = p;
+}
+
+#ifdef VALGRIND
+
+static void drop_pool(struct pool *p) {
+ while (p) {
+ struct pool *n;
+ n = p->next;
+ free(p);
+ p = n;
+ }
+}
+
+__attribute__((destructor)) static void cleanup_pool(void) {
+ /* Be nice to valgrind */
+
+ drop_pool(first_hashmap_pool);
+ drop_pool(first_entry_pool);
+}
+
+#endif
+
+unsigned string_hash_func(const void *p) {
+ unsigned hash = 5381;
+ const signed char *c;
+
+ /* DJB's hash function */
+
+ for (c = p; *c; c++)
+ hash = (hash << 5) + hash + (unsigned) *c;
+
+ return hash;
+}
+
+int string_compare_func(const void *a, const void *b) {
+ return strcmp(a, b);
+}
+
+unsigned trivial_hash_func(const void *p) {
+ return PTR_TO_UINT(p);
+}
+
+int trivial_compare_func(const void *a, const void *b) {
+ return a < b ? -1 : (a > b ? 1 : 0);
+}
+
+unsigned uint64_hash_func(const void *p) {
+ uint64_t u;
+
+ assert_cc(sizeof(uint64_t) == 2*sizeof(unsigned));
+
+ u = *(const uint64_t*) p;
+
+ return (unsigned) ((u >> 32) ^ u);
+}
+
+int uint64_compare_func(const void *_a, const void *_b) {
+ uint64_t a, b;
+
+ a = *(const uint64_t*) _a;
+ b = *(const uint64_t*) _b;
+
+ return a < b ? -1 : (a > b ? 1 : 0);
+}
+
+Hashmap *hashmap_new(hash_func_t hash_func, compare_func_t compare_func) {
+ bool b;
+ Hashmap *h;
+ size_t size;
+
+ b = is_main_thread();
+
+ size = ALIGN(sizeof(Hashmap)) + NBUCKETS * sizeof(struct hashmap_entry*);
+
+ if (b) {
+ h = allocate_tile(&first_hashmap_pool, &first_hashmap_tile, size);
+ if (!h)
+ return NULL;
+
+ memset(h, 0, size);
+ } else {
+ h = malloc0(size);
+
+ if (!h)
+ return NULL;
+ }
+
+ h->hash_func = hash_func ? hash_func : trivial_hash_func;
+ h->compare_func = compare_func ? compare_func : trivial_compare_func;
+
+ h->n_entries = 0;
+ h->iterate_list_head = h->iterate_list_tail = NULL;
+
+ h->from_pool = b;
+
+ return h;
+}
+
+int hashmap_ensure_allocated(Hashmap **h, hash_func_t hash_func, compare_func_t compare_func) {
+ assert(h);
+
+ if (*h)
+ return 0;
+
+ if (!(*h = hashmap_new(hash_func, compare_func)))
+ return -ENOMEM;
+
+ return 0;
+}
+
+static void link_entry(Hashmap *h, struct hashmap_entry *e, unsigned hash) {
+ assert(h);
+ assert(e);
+
+ /* Insert into hash table */
+ e->bucket_next = BY_HASH(h)[hash];
+ e->bucket_previous = NULL;
+ if (BY_HASH(h)[hash])
+ BY_HASH(h)[hash]->bucket_previous = e;
+ BY_HASH(h)[hash] = e;
+
+ /* Insert into iteration list */
+ e->iterate_previous = h->iterate_list_tail;
+ e->iterate_next = NULL;
+ if (h->iterate_list_tail) {
+ assert(h->iterate_list_head);
+ h->iterate_list_tail->iterate_next = e;
+ } else {
+ assert(!h->iterate_list_head);
+ h->iterate_list_head = e;
+ }
+ h->iterate_list_tail = e;
+
+ h->n_entries++;
+ assert(h->n_entries >= 1);
+}
+
+static void unlink_entry(Hashmap *h, struct hashmap_entry *e, unsigned hash) {
+ assert(h);
+ assert(e);
+
+ /* Remove from iteration list */
+ if (e->iterate_next)
+ e->iterate_next->iterate_previous = e->iterate_previous;
+ else
+ h->iterate_list_tail = e->iterate_previous;
+
+ if (e->iterate_previous)
+ e->iterate_previous->iterate_next = e->iterate_next;
+ else
+ h->iterate_list_head = e->iterate_next;
+
+ /* Remove from hash table bucket list */
+ if (e->bucket_next)
+ e->bucket_next->bucket_previous = e->bucket_previous;
+
+ if (e->bucket_previous)
+ e->bucket_previous->bucket_next = e->bucket_next;
+ else
+ BY_HASH(h)[hash] = e->bucket_next;
+
+ assert(h->n_entries >= 1);
+ h->n_entries--;
+}
+
+static void remove_entry(Hashmap *h, struct hashmap_entry *e) {
+ unsigned hash;
+
+ assert(h);
+ assert(e);
+
+ hash = h->hash_func(e->key) % NBUCKETS;
+
+ unlink_entry(h, e, hash);
+
+ if (h->from_pool)
+ deallocate_tile(&first_entry_tile, e);
+ else
+ free(e);
+}
+
+void hashmap_free(Hashmap*h) {
+
+ /* Free the hashmap, but nothing in it */
+
+ if (!h)
+ return;
+
+ hashmap_clear(h);
+
+ if (h->from_pool)
+ deallocate_tile(&first_hashmap_tile, h);
+ else
+ free(h);
+}
+
+void hashmap_free_free(Hashmap *h) {
+
+ /* Free the hashmap and all data objects in it, but not the
+ * keys */
+
+ if (!h)
+ return;
+
+ hashmap_clear_free(h);
+ hashmap_free(h);
+}
+
+void hashmap_free_free_free(Hashmap *h) {
+
+ /* Free the hashmap and all data and key objects in it */
+
+ if (!h)
+ return;
+
+ hashmap_clear_free_free(h);
+ hashmap_free(h);
+}
+
+void hashmap_clear(Hashmap *h) {
+ if (!h)
+ return;
+
+ while (h->iterate_list_head)
+ remove_entry(h, h->iterate_list_head);
+}
+
+void hashmap_clear_free(Hashmap *h) {
+ void *p;
+
+ if (!h)
+ return;
+
+ while ((p = hashmap_steal_first(h)))
+ free(p);
+}
+
+void hashmap_clear_free_free(Hashmap *h) {
+ if (!h)
+ return;
+
+ while (h->iterate_list_head) {
+ void *a, *b;
+
+ a = h->iterate_list_head->value;
+ b = (void*) h->iterate_list_head->key;
+ remove_entry(h, h->iterate_list_head);
+ free(a);
+ free(b);
+ }
+}
+
+
+static struct hashmap_entry *hash_scan(Hashmap *h, unsigned hash, const void *key) {
+ struct hashmap_entry *e;
+ assert(h);
+ assert(hash < NBUCKETS);
+
+ for (e = BY_HASH(h)[hash]; e; e = e->bucket_next)
+ if (h->compare_func(e->key, key) == 0)
+ return e;
+
+ return NULL;
+}
+
+int hashmap_put(Hashmap *h, const void *key, void *value) {
+ struct hashmap_entry *e;
+ unsigned hash;
+
+ assert(h);
+
+ hash = h->hash_func(key) % NBUCKETS;
+
+ e = hash_scan(h, hash, key);
+ if (e) {
+
+ if (e->value == value)
+ return 0;
+
+ return -EEXIST;
+ }
+
+ if (h->from_pool)
+ e = allocate_tile(&first_entry_pool, &first_entry_tile, sizeof(struct hashmap_entry));
+ else
+ e = new(struct hashmap_entry, 1);
+
+ if (!e)
+ return -ENOMEM;
+
+ e->key = key;
+ e->value = value;
+
+ link_entry(h, e, hash);
+
+ return 1;
+}
+
+int hashmap_replace(Hashmap *h, const void *key, void *value) {
+ struct hashmap_entry *e;
+ unsigned hash;
+
+ assert(h);
+
+ hash = h->hash_func(key) % NBUCKETS;
+ e = hash_scan(h, hash, key);
+ if (e) {
+ e->key = key;
+ e->value = value;
+ return 0;
+ }
+
+ return hashmap_put(h, key, value);
+}
+
+int hashmap_update(Hashmap *h, const void *key, void *value) {
+ struct hashmap_entry *e;
+ unsigned hash;
+
+ assert(h);
+
+ hash = h->hash_func(key) % NBUCKETS;
+ e = hash_scan(h, hash, key);
+ if (!e)
+ return -ENOENT;
+
+ e->value = value;
+ return 0;
+}
+
+void* hashmap_get(Hashmap *h, const void *key) {
+ unsigned hash;
+ struct hashmap_entry *e;
+
+ if (!h)
+ return NULL;
+
+ hash = h->hash_func(key) % NBUCKETS;
+ e = hash_scan(h, hash, key);
+ if (!e)
+ return NULL;
+
+ return e->value;
+}
+
+void* hashmap_get2(Hashmap *h, const void *key, void **key2) {
+ unsigned hash;
+ struct hashmap_entry *e;
+
+ if (!h)
+ return NULL;
+
+ hash = h->hash_func(key) % NBUCKETS;
+ e = hash_scan(h, hash, key);
+ if (!e)
+ return NULL;
+
+ if (key2)
+ *key2 = (void*) e->key;
+
+ return e->value;
+}
+
+bool hashmap_contains(Hashmap *h, const void *key) {
+ unsigned hash;
+
+ if (!h)
+ return false;
+
+ hash = h->hash_func(key) % NBUCKETS;
+
+ if (!hash_scan(h, hash, key))
+ return false;
+
+ return true;
+}
+
+void* hashmap_remove(Hashmap *h, const void *key) {
+ struct hashmap_entry *e;
+ unsigned hash;
+ void *data;
+
+ if (!h)
+ return NULL;
+
+ hash = h->hash_func(key) % NBUCKETS;
+
+ if (!(e = hash_scan(h, hash, key)))
+ return NULL;
+
+ data = e->value;
+ remove_entry(h, e);
+
+ return data;
+}
+
+int hashmap_remove_and_put(Hashmap *h, const void *old_key, const void *new_key, void *value) {
+ struct hashmap_entry *e;
+ unsigned old_hash, new_hash;
+
+ if (!h)
+ return -ENOENT;
+
+ old_hash = h->hash_func(old_key) % NBUCKETS;
+ if (!(e = hash_scan(h, old_hash, old_key)))
+ return -ENOENT;
+
+ new_hash = h->hash_func(new_key) % NBUCKETS;
+ if (hash_scan(h, new_hash, new_key))
+ return -EEXIST;
+
+ unlink_entry(h, e, old_hash);
+
+ e->key = new_key;
+ e->value = value;
+
+ link_entry(h, e, new_hash);
+
+ return 0;
+}
+
+int hashmap_remove_and_replace(Hashmap *h, const void *old_key, const void *new_key, void *value) {
+ struct hashmap_entry *e, *k;
+ unsigned old_hash, new_hash;
+
+ if (!h)
+ return -ENOENT;
+
+ old_hash = h->hash_func(old_key) % NBUCKETS;
+ if (!(e = hash_scan(h, old_hash, old_key)))
+ return -ENOENT;
+
+ new_hash = h->hash_func(new_key) % NBUCKETS;
+
+ if ((k = hash_scan(h, new_hash, new_key)))
+ if (e != k)
+ remove_entry(h, k);
+
+ unlink_entry(h, e, old_hash);
+
+ e->key = new_key;
+ e->value = value;
+
+ link_entry(h, e, new_hash);
+
+ return 0;
+}
+
+void* hashmap_remove_value(Hashmap *h, const void *key, void *value) {
+ struct hashmap_entry *e;
+ unsigned hash;
+
+ if (!h)
+ return NULL;
+
+ hash = h->hash_func(key) % NBUCKETS;
+
+ if (!(e = hash_scan(h, hash, key)))
+ return NULL;
+
+ if (e->value != value)
+ return NULL;
+
+ remove_entry(h, e);
+
+ return value;
+}
+
+void *hashmap_iterate(Hashmap *h, Iterator *i, const void **key) {
+ struct hashmap_entry *e;
+
+ assert(i);
+
+ if (!h)
+ goto at_end;
+
+ if (*i == ITERATOR_LAST)
+ goto at_end;
+
+ if (*i == ITERATOR_FIRST && !h->iterate_list_head)
+ goto at_end;
+
+ e = *i == ITERATOR_FIRST ? h->iterate_list_head : (struct hashmap_entry*) *i;
+
+ if (e->iterate_next)
+ *i = (Iterator) e->iterate_next;
+ else
+ *i = ITERATOR_LAST;
+
+ if (key)
+ *key = e->key;
+
+ return e->value;
+
+at_end:
+ *i = ITERATOR_LAST;
+
+ if (key)
+ *key = NULL;
+
+ return NULL;
+}
+
+void *hashmap_iterate_backwards(Hashmap *h, Iterator *i, const void **key) {
+ struct hashmap_entry *e;
+
+ assert(i);
+
+ if (!h)
+ goto at_beginning;
+
+ if (*i == ITERATOR_FIRST)
+ goto at_beginning;
+
+ if (*i == ITERATOR_LAST && !h->iterate_list_tail)
+ goto at_beginning;
+
+ e = *i == ITERATOR_LAST ? h->iterate_list_tail : (struct hashmap_entry*) *i;
+
+ if (e->iterate_previous)
+ *i = (Iterator) e->iterate_previous;
+ else
+ *i = ITERATOR_FIRST;
+
+ if (key)
+ *key = e->key;
+
+ return e->value;
+
+at_beginning:
+ *i = ITERATOR_FIRST;
+
+ if (key)
+ *key = NULL;
+
+ return NULL;
+}
+
+void *hashmap_iterate_skip(Hashmap *h, const void *key, Iterator *i) {
+ unsigned hash;
+ struct hashmap_entry *e;
+
+ if (!h)
+ return NULL;
+
+ hash = h->hash_func(key) % NBUCKETS;
+
+ if (!(e = hash_scan(h, hash, key)))
+ return NULL;
+
+ *i = (Iterator) e;
+
+ return e->value;
+}
+
+void* hashmap_first(Hashmap *h) {
+
+ if (!h)
+ return NULL;
+
+ if (!h->iterate_list_head)
+ return NULL;
+
+ return h->iterate_list_head->value;
+}
+
+void* hashmap_first_key(Hashmap *h) {
+
+ if (!h)
+ return NULL;
+
+ if (!h->iterate_list_head)
+ return NULL;
+
+ return (void*) h->iterate_list_head->key;
+}
+
+void* hashmap_last(Hashmap *h) {
+
+ if (!h)
+ return NULL;
+
+ if (!h->iterate_list_tail)
+ return NULL;
+
+ return h->iterate_list_tail->value;
+}
+
+void* hashmap_steal_first(Hashmap *h) {
+ void *data;
+
+ if (!h)
+ return NULL;
+
+ if (!h->iterate_list_head)
+ return NULL;
+
+ data = h->iterate_list_head->value;
+ remove_entry(h, h->iterate_list_head);
+
+ return data;
+}
+
+void* hashmap_steal_first_key(Hashmap *h) {
+ void *key;
+
+ if (!h)
+ return NULL;
+
+ if (!h->iterate_list_head)
+ return NULL;
+
+ key = (void*) h->iterate_list_head->key;
+ remove_entry(h, h->iterate_list_head);
+
+ return key;
+}
+
+unsigned hashmap_size(Hashmap *h) {
+
+ if (!h)
+ return 0;
+
+ return h->n_entries;
+}
+
+bool hashmap_isempty(Hashmap *h) {
+
+ if (!h)
+ return true;
+
+ return h->n_entries == 0;
+}
+
+int hashmap_merge(Hashmap *h, Hashmap *other) {
+ struct hashmap_entry *e;
+
+ assert(h);
+
+ if (!other)
+ return 0;
+
+ for (e = other->iterate_list_head; e; e = e->iterate_next) {
+ int r;
+
+ if ((r = hashmap_put(h, e->key, e->value)) < 0)
+ if (r != -EEXIST)
+ return r;
+ }
+
+ return 0;
+}
+
+void hashmap_move(Hashmap *h, Hashmap *other) {
+ struct hashmap_entry *e, *n;
+
+ assert(h);
+
+ /* The same as hashmap_merge(), but every new item from other
+ * is moved to h. This function is guaranteed to succeed. */
+
+ if (!other)
+ return;
+
+ for (e = other->iterate_list_head; e; e = n) {
+ unsigned h_hash, other_hash;
+
+ n = e->iterate_next;
+
+ h_hash = h->hash_func(e->key) % NBUCKETS;
+
+ if (hash_scan(h, h_hash, e->key))
+ continue;
+
+ other_hash = other->hash_func(e->key) % NBUCKETS;
+
+ unlink_entry(other, e, other_hash);
+ link_entry(h, e, h_hash);
+ }
+}
+
+int hashmap_move_one(Hashmap *h, Hashmap *other, const void *key) {
+ unsigned h_hash, other_hash;
+ struct hashmap_entry *e;
+
+ if (!other)
+ return 0;
+
+ assert(h);
+
+ h_hash = h->hash_func(key) % NBUCKETS;
+ if (hash_scan(h, h_hash, key))
+ return -EEXIST;
+
+ other_hash = other->hash_func(key) % NBUCKETS;
+ if (!(e = hash_scan(other, other_hash, key)))
+ return -ENOENT;
+
+ unlink_entry(other, e, other_hash);
+ link_entry(h, e, h_hash);
+
+ return 0;
+}
+
+Hashmap *hashmap_copy(Hashmap *h) {
+ Hashmap *copy;
+
+ assert(h);
+
+ if (!(copy = hashmap_new(h->hash_func, h->compare_func)))
+ return NULL;
+
+ if (hashmap_merge(copy, h) < 0) {
+ hashmap_free(copy);
+ return NULL;
+ }
+
+ return copy;
+}
+
+char **hashmap_get_strv(Hashmap *h) {
+ char **sv;
+ Iterator it;
+ char *item;
+ int n;
+
+ sv = new(char*, h->n_entries+1);
+ if (!sv)
+ return NULL;
+
+ n = 0;
+ HASHMAP_FOREACH(item, h, it)
+ sv[n++] = item;
+ sv[n] = NULL;
+
+ return sv;
+}
+
+void *hashmap_next(Hashmap *h, const void *key) {
+ unsigned hash;
+ struct hashmap_entry *e;
+
+ assert(h);
+ assert(key);
+
+ if (!h)
+ return NULL;
+
+ hash = h->hash_func(key) % NBUCKETS;
+ e = hash_scan(h, hash, key);
+ if (!e)
+ return NULL;
+
+ e = e->iterate_next;
+ if (!e)
+ return NULL;
+
+ return e->value;
+}
diff --git a/src/shared/hashmap.h b/src/shared/hashmap.h
new file mode 100644
index 0000000000..15b7e27585
--- /dev/null
+++ b/src/shared/hashmap.h
@@ -0,0 +1,105 @@
+/*-*- Mode: C; c-basic-offset: 8; indent-tabs-mode: nil -*-*/
+
+#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"
+
+/* Pretty straightforward hash table implementation. As a minor
+ * optimization a NULL hashmap object will be treated as empty hashmap
+ * for all read operations. That way it is not necessary to
+ * instantiate an object for each Hashmap use. */
+
+typedef struct Hashmap Hashmap;
+typedef struct _IteratorStruct _IteratorStruct;
+typedef _IteratorStruct* Iterator;
+
+#define ITERATOR_FIRST ((Iterator) 0)
+#define ITERATOR_LAST ((Iterator) -1)
+
+typedef unsigned (*hash_func_t)(const void *p);
+typedef int (*compare_func_t)(const void *a, const void *b);
+
+unsigned string_hash_func(const void *p) _pure_;
+int string_compare_func(const void *a, const void *b) _pure_;
+
+/* This will compare the passed pointers directly, and will not
+ * dereference them. This is hence not useful for strings or
+ * suchlike. */
+unsigned trivial_hash_func(const void *p) _const_;
+int trivial_compare_func(const void *a, const void *b) _const_;
+
+unsigned uint64_hash_func(const void *p) _pure_;
+int uint64_compare_func(const void *a, const void *b) _pure_;
+
+Hashmap *hashmap_new(hash_func_t hash_func, compare_func_t compare_func);
+void hashmap_free(Hashmap *h);
+void hashmap_free_free(Hashmap *h);
+void hashmap_free_free_free(Hashmap *h);
+Hashmap *hashmap_copy(Hashmap *h);
+int hashmap_ensure_allocated(Hashmap **h, hash_func_t hash_func, compare_func_t compare_func);
+
+int hashmap_put(Hashmap *h, const void *key, void *value);
+int hashmap_update(Hashmap *h, const void *key, void *value);
+int hashmap_replace(Hashmap *h, const void *key, void *value);
+void *hashmap_get(Hashmap *h, const void *key);
+void *hashmap_get2(Hashmap *h, const void *key, void **rkey);
+bool hashmap_contains(Hashmap *h, const void *key);
+void *hashmap_remove(Hashmap *h, const void *key);
+void *hashmap_remove_value(Hashmap *h, const void *key, void *value);
+int hashmap_remove_and_put(Hashmap *h, const void *old_key, const void *new_key, void *value);
+int hashmap_remove_and_replace(Hashmap *h, const void *old_key, const void *new_key, void *value);
+
+int hashmap_merge(Hashmap *h, Hashmap *other);
+void hashmap_move(Hashmap *h, Hashmap *other);
+int hashmap_move_one(Hashmap *h, Hashmap *other, const void *key);
+
+unsigned hashmap_size(Hashmap *h) _pure_;
+bool hashmap_isempty(Hashmap *h) _pure_;
+
+void *hashmap_iterate(Hashmap *h, Iterator *i, const void **key);
+void *hashmap_iterate_backwards(Hashmap *h, Iterator *i, const void **key);
+void *hashmap_iterate_skip(Hashmap *h, const void *key, Iterator *i);
+
+void hashmap_clear(Hashmap *h);
+void hashmap_clear_free(Hashmap *h);
+void hashmap_clear_free_free(Hashmap *h);
+
+void *hashmap_steal_first(Hashmap *h);
+void *hashmap_steal_first_key(Hashmap *h);
+void *hashmap_first(Hashmap *h) _pure_;
+void *hashmap_first_key(Hashmap *h) _pure_;
+void *hashmap_last(Hashmap *h) _pure_;
+
+void *hashmap_next(Hashmap *h, const void *key);
+
+char **hashmap_get_strv(Hashmap *h);
+
+#define HASHMAP_FOREACH(e, h, i) \
+ for ((i) = ITERATOR_FIRST, (e) = hashmap_iterate((h), &(i), NULL); (e); (e) = hashmap_iterate((h), &(i), NULL))
+
+#define HASHMAP_FOREACH_KEY(e, k, h, i) \
+ for ((i) = ITERATOR_FIRST, (e) = hashmap_iterate((h), &(i), (const void**) &(k)); (e); (e) = hashmap_iterate((h), &(i), (const void**) &(k)))
+
+#define HASHMAP_FOREACH_BACKWARDS(e, h, i) \
+ for ((i) = ITERATOR_LAST, (e) = hashmap_iterate_backwards((h), &(i), NULL); (e); (e) = hashmap_iterate_backwards((h), &(i), NULL))
diff --git a/src/shared/hwclock.c b/src/shared/hwclock.c
new file mode 100644
index 0000000000..cc11faa6c3
--- /dev/null
+++ b/src/shared/hwclock.c
@@ -0,0 +1,160 @@
+/*-*- Mode: C; c-basic-offset: 8; indent-tabs-mode: nil -*-*/
+
+/***
+ 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 <assert.h>
+#include <string.h>
+#include <unistd.h>
+#include <errno.h>
+#include <stdlib.h>
+#include <signal.h>
+#include <stdio.h>
+#include <sys/types.h>
+#include <sys/stat.h>
+#include <fcntl.h>
+#include <sys/ioctl.h>
+#include <stdarg.h>
+#include <ctype.h>
+#include <sys/prctl.h>
+#include <sys/time.h>
+#include <linux/rtc.h>
+
+#include "macro.h"
+#include "util.h"
+#include "log.h"
+#include "strv.h"
+#include "hwclock.h"
+#include "fileio.h"
+
+int hwclock_get_time(struct tm *tm) {
+ int fd;
+ int err = 0;
+
+ assert(tm);
+
+ fd = open("/dev/rtc", O_RDONLY|O_CLOEXEC);
+ if (fd < 0)
+ return -errno;
+
+ /* This leaves the timezone fields of struct tm
+ * uninitialized! */
+ if (ioctl(fd, RTC_RD_TIME, tm) < 0)
+ err = -errno;
+
+ /* We don't know daylight saving, so we reset this in order not
+ * to confused mktime(). */
+ tm->tm_isdst = -1;
+
+ close_nointr_nofail(fd);
+
+ return err;
+}
+
+int hwclock_set_time(const struct tm *tm) {
+ int fd;
+ int err = 0;
+
+ assert(tm);
+
+ fd = open("/dev/rtc", O_RDONLY|O_CLOEXEC);
+ if (fd < 0)
+ return -errno;
+
+ if (ioctl(fd, RTC_SET_TIME, tm) < 0)
+ err = -errno;
+
+ close_nointr_nofail(fd);
+
+ return err;
+}
+
+int hwclock_is_localtime(void) {
+ _cleanup_fclose_ FILE *f;
+
+ /*
+ * The third line of adjtime is "UTC" or "LOCAL" or nothing.
+ * # /etc/adjtime
+ * 0.0 0 0
+ * 0
+ * UTC
+ */
+ f = fopen("/etc/adjtime", "re");
+ if (f) {
+ char line[LINE_MAX];
+ bool b;
+
+ b = fgets(line, sizeof(line), f) &&
+ fgets(line, sizeof(line), f) &&
+ fgets(line, sizeof(line), f);
+ if (!b)
+ return -EIO;
+
+ truncate_nl(line);
+ return streq(line, "LOCAL");
+
+ } else if (errno != ENOENT)
+ return -errno;
+
+ return 0;
+}
+
+int hwclock_set_timezone(int *min) {
+ const struct timeval *tv_null = NULL;
+ struct timespec ts;
+ struct tm *tm;
+ int minutesdelta;
+ struct timezone tz;
+
+ assert_se(clock_gettime(CLOCK_REALTIME, &ts) == 0);
+ assert_se(tm = localtime(&ts.tv_sec));
+ minutesdelta = tm->tm_gmtoff / 60;
+
+ tz.tz_minuteswest = -minutesdelta;
+ tz.tz_dsttime = 0; /* DST_NONE*/
+
+ /*
+ * If the hardware clock does not run in UTC, but in local time:
+ * The very first time we set the kernel's timezone, it will warp
+ * the clock so that it runs in UTC instead of local time.
+ */
+ if (settimeofday(tv_null, &tz) < 0)
+ return -errno;
+ if (min)
+ *min = minutesdelta;
+ return 0;
+}
+
+int hwclock_reset_timezone(void) {
+ const struct timeval *tv_null = NULL;
+ struct timezone tz;
+
+ tz.tz_minuteswest = 0;
+ tz.tz_dsttime = 0; /* DST_NONE*/
+
+ /*
+ * The very first time we set the kernel's timezone, it will warp
+ * the clock. Do a dummy call here, so the time warping is sealed
+ * and we set only the time zone with next call.
+ */
+ if (settimeofday(tv_null, &tz) < 0)
+ return -errno;
+
+ return 0;
+}
diff --git a/src/shared/hwclock.h b/src/shared/hwclock.h
new file mode 100644
index 0000000000..b2bdc78f0c
--- /dev/null
+++ b/src/shared/hwclock.h
@@ -0,0 +1,31 @@
+/*-*- Mode: C; c-basic-offset: 8; indent-tabs-mode: nil -*-*/
+
+#ifndef foohwclockhfoo
+#define foohwclockhfoo
+
+/***
+ 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/>.
+***/
+
+int hwclock_is_localtime(void);
+int hwclock_set_timezone(int *min);
+int hwclock_reset_timezone(void);
+int hwclock_get_time(struct tm *tm);
+int hwclock_set_time(const struct tm *tm);
+
+#endif
diff --git a/src/shared/install-printf.c b/src/shared/install-printf.c
new file mode 100644
index 0000000000..c44459b4e0
--- /dev/null
+++ b/src/shared/install-printf.c
@@ -0,0 +1,132 @@
+/*-*- Mode: C; c-basic-offset: 8; indent-tabs-mode: nil -*-*/
+
+/***
+ 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 <assert.h>
+#include <stdlib.h>
+
+#include "specifier.h"
+#include "unit-name.h"
+#include "util.h"
+#include "install-printf.h"
+
+static char *specifier_prefix_and_instance(char specifier, void *data, void *userdata) {
+ InstallInfo *i = userdata;
+ assert(i);
+
+ return unit_name_to_prefix_and_instance(i->name);
+}
+
+static char *specifier_prefix(char specifier, void *data, void *userdata) {
+ InstallInfo *i = userdata;
+ assert(i);
+
+ return unit_name_to_prefix(i->name);
+}
+
+static char *specifier_instance(char specifier, void *data, void *userdata) {
+ InstallInfo *i = userdata;
+ char *instance;
+ int r;
+
+ assert(i);
+
+ r = unit_name_to_instance(i->name, &instance);
+ if (r < 0)
+ return NULL;
+ if (instance != NULL)
+ return instance;
+ else
+ return strdup("");
+}
+
+static char *specifier_user_name(char specifier, void *data, void *userdata) {
+ InstallInfo *i = userdata;
+ const char *username;
+ _cleanup_free_ char *tmp = NULL;
+ char *printed = NULL;
+
+ assert(i);
+
+ if (i->user)
+ username = i->user;
+ else
+ /* get USER env from env or our own uid */
+ username = tmp = getusername_malloc();
+
+ switch (specifier) {
+ case 'u':
+ printed = strdup(username);
+ break;
+ case 'U': {
+ /* fish username from passwd */
+ uid_t uid;
+ int r;
+
+ r = get_user_creds(&username, &uid, NULL, NULL, NULL);
+ if (r < 0)
+ return NULL;
+
+ if (asprintf(&printed, "%d", uid) < 0)
+ return NULL;
+ break;
+ }}
+
+ return printed;
+}
+
+
+char *install_full_printf(InstallInfo *i, const char *format) {
+
+ /* 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 configured user or running user
+ * %u the username of the configured user or 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
+ */
+
+ 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_name, NULL },
+ { 'u', specifier_user_name, NULL },
+
+ { 'm', specifier_machine_id, NULL },
+ { 'H', specifier_host_name, NULL },
+ { 'b', specifier_boot_id, NULL },
+ { 0, NULL, NULL }
+ };
+
+ assert(i);
+ assert(format);
+
+ return specifier_printf(format, table, i);
+}
diff --git a/src/shared/install-printf.h b/src/shared/install-printf.h
new file mode 100644
index 0000000000..46f5294d21
--- /dev/null
+++ b/src/shared/install-printf.h
@@ -0,0 +1,25 @@
+/*-*- Mode: C; c-basic-offset: 8; indent-tabs-mode: nil -*-*/
+
+/***
+ 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"
+char *install_full_printf(InstallInfo *i, const char *format);
diff --git a/src/shared/install.c b/src/shared/install.c
new file mode 100644
index 0000000000..edf4d2a9fe
--- /dev/null
+++ b/src/shared/install.c
@@ -0,0 +1,1945 @@
+/*-*- Mode: C; c-basic-offset: 8; indent-tabs-mode: nil -*-*/
+
+/***
+ This file is part of systemd.
+
+ Copyright 2011 Lennart Poettering
+
+ systemd is free software; you can redistribute it and/or modify it
+ under the terms of the GNU Lesser General Public License as published by
+ the Free Software Foundation; either version 2.1 of the License, or
+ (at your option) any later version.
+
+ systemd is distributed in the hope that it will be useful, but
+ WITHOUT ANY WARRANTY; without even the implied warranty <of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ Lesser General 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 <unistd.h>
+#include <string.h>
+#include <fnmatch.h>
+
+#include "util.h"
+#include "mkdir.h"
+#include "hashmap.h"
+#include "set.h"
+#include "path-util.h"
+#include "path-lookup.h"
+#include "strv.h"
+#include "unit-name.h"
+#include "install.h"
+#include "conf-parser.h"
+#include "conf-files.h"
+#include "specifier.h"
+#include "install-printf.h"
+
+typedef struct {
+ Hashmap *will_install;
+ Hashmap *have_installed;
+} InstallContext;
+
+#define _cleanup_lookup_paths_free_ \
+ __attribute__((cleanup(lookup_paths_free)))
+#define _cleanup_install_context_done_ \
+ __attribute__((cleanup(install_context_done)))
+
+static int lookup_paths_init_from_scope(LookupPaths *paths, UnitFileScope scope) {
+ assert(paths);
+ assert(scope >= 0);
+ assert(scope < _UNIT_FILE_SCOPE_MAX);
+
+ zero(*paths);
+
+ return lookup_paths_init(paths,
+ scope == UNIT_FILE_SYSTEM ? SYSTEMD_SYSTEM : SYSTEMD_USER,
+ scope == UNIT_FILE_USER,
+ NULL, NULL, NULL);
+}
+
+static int get_config_path(UnitFileScope scope, bool runtime, const char *root_dir, char **ret) {
+ char *p = NULL;
+ int r;
+
+ assert(scope >= 0);
+ assert(scope < _UNIT_FILE_SCOPE_MAX);
+ assert(ret);
+
+ switch (scope) {
+
+ case UNIT_FILE_SYSTEM:
+
+ if (root_dir && runtime)
+ asprintf(&p, "%s/run/systemd/system", root_dir);
+ else if (runtime)
+ p = strdup("/run/systemd/system");
+ else if (root_dir)
+ asprintf(&p, "%s/%s", root_dir, SYSTEM_CONFIG_UNIT_PATH);
+ else
+ p = strdup(SYSTEM_CONFIG_UNIT_PATH);
+
+ break;
+
+ case UNIT_FILE_GLOBAL:
+
+ if (root_dir)
+ return -EINVAL;
+
+ if (runtime)
+ p = strdup("/run/systemd/user");
+ else
+ p = strdup(USER_CONFIG_UNIT_PATH);
+ break;
+
+ case UNIT_FILE_USER:
+
+ if (root_dir || runtime)
+ return -EINVAL;
+
+ r = user_config_home(&p);
+ if (r <= 0)
+ return r < 0 ? r : -ENOENT;
+
+ break;
+
+ default:
+ assert_not_reached("Bad scope");
+ }
+
+ if (!p)
+ return -ENOMEM;
+
+ *ret = p;
+ return 0;
+}
+
+static int add_file_change(
+ UnitFileChange **changes,
+ unsigned *n_changes,
+ UnitFileChangeType type,
+ const char *path,
+ const char *source) {
+
+ UnitFileChange *c;
+ unsigned i;
+
+ assert(path);
+ assert(!changes == !n_changes);
+
+ if (!changes)
+ return 0;
+
+ c = realloc(*changes, (*n_changes + 1) * sizeof(UnitFileChange));
+ if (!c)
+ return -ENOMEM;
+
+ *changes = c;
+ i = *n_changes;
+
+ c[i].type = type;
+ c[i].path = strdup(path);
+ if (!c[i].path)
+ return -ENOMEM;
+
+ if (source) {
+ c[i].source = strdup(source);
+ if (!c[i].source) {
+ free(c[i].path);
+ return -ENOMEM;
+ }
+ } else
+ c[i].source = NULL;
+
+ *n_changes = i+1;
+ return 0;
+}
+
+static int mark_symlink_for_removal(
+ Set **remove_symlinks_to,
+ const char *p) {
+
+ char *n;
+ int r;
+
+ assert(p);
+
+ r = set_ensure_allocated(remove_symlinks_to, string_hash_func, string_compare_func);
+ if (r < 0)
+ return r;
+
+ n = strdup(p);
+ if (!n)
+ return -ENOMEM;
+
+ path_kill_slashes(n);
+
+ r = set_consume(*remove_symlinks_to, n);
+ if (r < 0)
+ return r == -EEXIST ? 0 : r;
+
+ return 0;
+}
+
+static int remove_marked_symlinks_fd(
+ Set *remove_symlinks_to,
+ int fd,
+ const char *path,
+ const char *config_path,
+ bool *deleted,
+ UnitFileChange **changes,
+ unsigned *n_changes,
+ char** files) {
+
+ int r = 0;
+ _cleanup_closedir_ DIR *d = NULL;
+
+ assert(remove_symlinks_to);
+ assert(fd >= 0);
+ assert(path);
+ assert(config_path);
+ assert(deleted);
+
+ d = fdopendir(fd);
+ if (!d) {
+ close_nointr_nofail(fd);
+ return -errno;
+ }
+
+ rewinddir(d);
+
+ for (;;) {
+ struct dirent *de;
+ union dirent_storage buf;
+ int k;
+
+ k = readdir_r(d, &buf.de, &de);
+ if (k != 0) {
+ r = -errno;
+ break;
+ }
+
+ if (!de)
+ break;
+
+ if (ignore_file(de->d_name))
+ continue;
+
+ dirent_ensure_type(d, de);
+
+ if (de->d_type == DT_DIR) {
+ int nfd, q;
+ _cleanup_free_ char *p = NULL;
+
+ nfd = openat(fd, de->d_name, O_RDONLY|O_NONBLOCK|O_DIRECTORY|O_CLOEXEC|O_NOFOLLOW);
+ if (nfd < 0) {
+ if (errno == ENOENT)
+ continue;
+
+ if (r == 0)
+ r = -errno;
+ continue;
+ }
+
+ p = path_make_absolute(de->d_name, path);
+ if (!p) {
+ close_nointr_nofail(nfd);
+ return -ENOMEM;
+ }
+
+ /* This will close nfd, regardless whether it succeeds or not */
+ q = remove_marked_symlinks_fd(remove_symlinks_to, nfd, p, config_path, deleted, changes, n_changes, files);
+
+ if (r == 0)
+ r = q;
+
+ } else if (de->d_type == DT_LNK) {
+ _cleanup_free_ char *p = NULL, *dest = NULL;
+ int q;
+ bool found;
+
+ p = path_make_absolute(de->d_name, path);
+ if (!p)
+ return -ENOMEM;
+
+ q = readlink_and_canonicalize(p, &dest);
+ if (q < 0) {
+ if (q == -ENOENT)
+ continue;
+
+ if (r == 0)
+ r = q;
+ continue;
+ }
+
+ found =
+ set_get(remove_symlinks_to, dest) ||
+ set_get(remove_symlinks_to, path_get_file_name(dest));
+
+ if (unit_name_is_instance(p))
+ found = found && strv_contains(files, path_get_file_name(p));
+
+ if (found) {
+
+ if (unlink(p) < 0 && errno != ENOENT) {
+
+ if (r == 0)
+ r = -errno;
+ } else {
+ rmdir_parents(p, config_path);
+ path_kill_slashes(p);
+
+ add_file_change(changes, n_changes, UNIT_FILE_UNLINK, p, NULL);
+
+ if (!set_get(remove_symlinks_to, p)) {
+
+ q = mark_symlink_for_removal(&remove_symlinks_to, p);
+ if (q < 0) {
+ if (r == 0)
+ r = q;
+ } else
+ *deleted = true;
+ }
+ }
+ }
+ }
+ }
+
+ return r;
+}
+
+static int remove_marked_symlinks(
+ Set *remove_symlinks_to,
+ const char *config_path,
+ UnitFileChange **changes,
+ unsigned *n_changes,
+ char** files) {
+
+ int fd, r = 0;
+ bool deleted;
+
+ assert(config_path);
+
+ if (set_size(remove_symlinks_to) <= 0)
+ return 0;
+
+ fd = open(config_path, O_RDONLY|O_NONBLOCK|O_DIRECTORY|O_CLOEXEC|O_NOFOLLOW);
+ if (fd < 0)
+ return -errno;
+
+ do {
+ int q, cfd;
+ deleted = false;
+
+ cfd = dup(fd);
+ if (cfd < 0) {
+ r = -errno;
+ break;
+ }
+
+ /* This takes possession of cfd and closes it */
+ q = remove_marked_symlinks_fd(remove_symlinks_to, cfd, config_path, config_path, &deleted, changes, n_changes, files);
+ if (r == 0)
+ r = q;
+ } while (deleted);
+
+ close_nointr_nofail(fd);
+
+ return r;
+}
+
+static int find_symlinks_fd(
+ const char *name,
+ int fd,
+ const char *path,
+ const char *config_path,
+ bool *same_name_link) {
+
+ int r = 0;
+ _cleanup_closedir_ DIR *d = NULL;
+
+ assert(name);
+ assert(fd >= 0);
+ assert(path);
+ assert(config_path);
+ assert(same_name_link);
+
+ d = fdopendir(fd);
+ if (!d) {
+ close_nointr_nofail(fd);
+ return -errno;
+ }
+
+ for (;;) {
+ int k;
+ struct dirent *de;
+ union dirent_storage buf;
+
+ k = readdir_r(d, &buf.de, &de);
+ if (k != 0)
+ return -errno;
+
+ if (!de)
+ return r;
+
+ if (ignore_file(de->d_name))
+ continue;
+
+ dirent_ensure_type(d, de);
+
+ if (de->d_type == DT_DIR) {
+ int nfd, q;
+ _cleanup_free_ char *p = NULL;
+
+ nfd = openat(fd, de->d_name, O_RDONLY|O_NONBLOCK|O_DIRECTORY|O_CLOEXEC|O_NOFOLLOW);
+ if (nfd < 0) {
+ if (errno == ENOENT)
+ continue;
+
+ if (r == 0)
+ r = -errno;
+ continue;
+ }
+
+ p = path_make_absolute(de->d_name, path);
+ if (!p) {
+ close_nointr_nofail(nfd);
+ return -ENOMEM;
+ }
+
+ /* This will close nfd, regardless whether it succeeds or not */
+ q = find_symlinks_fd(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_and_canonicalize(p, &dest);
+ if (q < 0) {
+ if (q == -ENOENT)
+ continue;
+
+ if (r == 0)
+ r = q;
+ continue;
+ }
+
+ /* Check if the symlink itself matches what we
+ * are looking for */
+ if (path_is_absolute(name))
+ found_path = path_equal(p, name);
+ else
+ found_path = streq(de->d_name, name);
+
+ /* Check if what the symlink points to
+ * matches what we are looking for */
+ if (path_is_absolute(name))
+ found_dest = path_equal(dest, name);
+ else
+ found_dest = streq(path_get_file_name(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 *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(name, fd, config_path, config_path, same_name_link);
+}
+
+static int find_symlinks_in_scope(
+ UnitFileScope scope,
+ const char *root_dir,
+ const char *name,
+ UnitFileState *state) {
+
+ int r;
+ _cleanup_free_ char *path = NULL;
+ bool same_name_link_runtime = false, same_name_link = false;
+
+ assert(scope >= 0);
+ assert(scope < _UNIT_FILE_SCOPE_MAX);
+ assert(name);
+
+ if (scope == UNIT_FILE_SYSTEM || scope == UNIT_FILE_GLOBAL) {
+
+ /* First look in runtime config path */
+ r = get_config_path(scope, true, root_dir, &path);
+ if (r < 0)
+ return r;
+
+ r = find_symlinks(name, path, &same_name_link_runtime);
+ if (r < 0)
+ return r;
+ else if (r > 0) {
+ *state = UNIT_FILE_ENABLED_RUNTIME;
+ return r;
+ }
+ }
+
+ /* Then look in the normal config path */
+ r = get_config_path(scope, false, root_dir, &path);
+ if (r < 0)
+ return r;
+
+ r = find_symlinks(name, path, &same_name_link);
+ if (r < 0)
+ return r;
+ else if (r > 0) {
+ *state = UNIT_FILE_ENABLED;
+ return r;
+ }
+
+ /* Hmm, we didn't find it, but maybe we found the same name
+ * link? */
+ if (same_name_link_runtime) {
+ *state = UNIT_FILE_LINKED_RUNTIME;
+ return 1;
+ } else if (same_name_link) {
+ *state = UNIT_FILE_LINKED;
+ return 1;
+ }
+
+ return 0;
+}
+
+int unit_file_mask(
+ UnitFileScope scope,
+ bool runtime,
+ const char *root_dir,
+ char *files[],
+ bool force,
+ UnitFileChange **changes,
+ unsigned *n_changes) {
+
+ char **i;
+ _cleanup_free_ char *prefix;
+ int r;
+
+ assert(scope >= 0);
+ assert(scope < _UNIT_FILE_SCOPE_MAX);
+
+ r = get_config_path(scope, runtime, root_dir, &prefix);
+ if (r < 0)
+ return r;
+
+ STRV_FOREACH(i, files) {
+ _cleanup_free_ char *path = NULL;
+
+ if (!unit_name_is_valid(*i, true)) {
+ if (r == 0)
+ r = -EINVAL;
+ continue;
+ }
+
+ path = path_make_absolute(*i, prefix);
+ if (!path) {
+ r = -ENOMEM;
+ break;
+ }
+
+ if (symlink("/dev/null", path) >= 0) {
+ add_file_change(changes, n_changes, UNIT_FILE_SYMLINK, path, "/dev/null");
+
+ continue;
+ }
+
+ if (errno == EEXIST) {
+
+ if (null_or_empty_path(path) > 0)
+ continue;
+
+ if (force) {
+ unlink(path);
+
+ if (symlink("/dev/null", path) >= 0) {
+
+ add_file_change(changes, n_changes, UNIT_FILE_UNLINK, path, NULL);
+ add_file_change(changes, n_changes, UNIT_FILE_SYMLINK, path, "/dev/null");
+
+ continue;
+ }
+ }
+
+ if (r == 0)
+ r = -EEXIST;
+ } else {
+ if (r == 0)
+ r = -errno;
+ }
+ }
+
+ return r;
+}
+
+int unit_file_unmask(
+ UnitFileScope scope,
+ bool runtime,
+ const char *root_dir,
+ char *files[],
+ UnitFileChange **changes,
+ unsigned *n_changes) {
+
+ char **i, *config_path = NULL;
+ int r, q;
+ Set *remove_symlinks_to = NULL;
+
+ assert(scope >= 0);
+ assert(scope < _UNIT_FILE_SCOPE_MAX);
+
+ r = get_config_path(scope, runtime, root_dir, &config_path);
+ if (r < 0)
+ goto finish;
+
+ STRV_FOREACH(i, files) {
+ char *path;
+
+ if (!unit_name_is_valid(*i, true)) {
+ if (r == 0)
+ r = -EINVAL;
+ continue;
+ }
+
+ path = path_make_absolute(*i, config_path);
+ if (!path) {
+ r = -ENOMEM;
+ break;
+ }
+
+ q = null_or_empty_path(path);
+ if (q > 0) {
+ if (unlink(path) >= 0) {
+ mark_symlink_for_removal(&remove_symlinks_to, path);
+ add_file_change(changes, n_changes, UNIT_FILE_UNLINK, path, NULL);
+
+ free(path);
+ continue;
+ }
+
+ q = -errno;
+ }
+
+ if (q != -ENOENT && r == 0)
+ r = q;
+
+ free(path);
+ }
+
+
+finish:
+ q = remove_marked_symlinks(remove_symlinks_to, config_path, changes, n_changes, files);
+ if (r == 0)
+ r = q;
+
+ set_free_free(remove_symlinks_to);
+ free(config_path);
+
+ return r;
+}
+
+int unit_file_link(
+ UnitFileScope scope,
+ bool runtime,
+ const char *root_dir,
+ char *files[],
+ bool force,
+ UnitFileChange **changes,
+ unsigned *n_changes) {
+
+ _cleanup_lookup_paths_free_ LookupPaths paths = {};
+ char **i;
+ _cleanup_free_ char *config_path = NULL;
+ int r, q;
+
+ assert(scope >= 0);
+ assert(scope < _UNIT_FILE_SCOPE_MAX);
+
+ r = lookup_paths_init_from_scope(&paths, scope);
+ if (r < 0)
+ return r;
+
+ r = get_config_path(scope, runtime, root_dir, &config_path);
+ if (r < 0)
+ return r;
+
+ STRV_FOREACH(i, files) {
+ _cleanup_free_ char *path = NULL;
+ char *fn;
+ struct stat st;
+
+ fn = path_get_file_name(*i);
+
+ if (!path_is_absolute(*i) ||
+ !unit_name_is_valid(fn, true)) {
+ if (r == 0)
+ r = -EINVAL;
+ continue;
+ }
+
+ if (lstat(*i, &st) < 0) {
+ if (r == 0)
+ r = -errno;
+ continue;
+ }
+
+ if (!S_ISREG(st.st_mode)) {
+ r = -ENOENT;
+ continue;
+ }
+
+ q = in_search_path(*i, paths.unit_path);
+ if (q < 0)
+ return q;
+
+ if (q > 0)
+ continue;
+
+ path = path_make_absolute(fn, config_path);
+ if (!path)
+ return -ENOMEM;
+
+ if (symlink(*i, path) >= 0) {
+ add_file_change(changes, n_changes, UNIT_FILE_SYMLINK, path, *i);
+ continue;
+ }
+
+ if (errno == EEXIST) {
+ _cleanup_free_ char *dest = NULL;
+
+ q = readlink_and_make_absolute(path, &dest);
+
+ if (q < 0 && errno != ENOENT) {
+ if (r == 0)
+ r = q;
+ continue;
+ }
+
+ if (q >= 0 && path_equal(dest, *i))
+ continue;
+
+ if (force) {
+ unlink(path);
+
+ if (symlink(*i, path) >= 0) {
+
+ add_file_change(changes, n_changes, UNIT_FILE_UNLINK, path, NULL);
+ add_file_change(changes, n_changes, UNIT_FILE_SYMLINK, path, *i);
+
+ continue;
+ }
+ }
+
+ if (r == 0)
+ r = -EEXIST;
+ } else {
+ if (r == 0)
+ r = -errno;
+ }
+ }
+
+ return r;
+}
+
+void unit_file_list_free(Hashmap *h) {
+ UnitFileList *i;
+
+ while ((i = hashmap_steal_first(h))) {
+ free(i->path);
+ free(i);
+ }
+
+ hashmap_free(h);
+}
+
+void unit_file_changes_free(UnitFileChange *changes, unsigned n_changes) {
+ unsigned i;
+
+ assert(changes || n_changes == 0);
+
+ if (!changes)
+ return;
+
+ for (i = 0; i < n_changes; i++) {
+ free(changes[i].path);
+ free(changes[i].source);
+ }
+
+ free(changes);
+}
+
+static void install_info_free(InstallInfo *i) {
+ assert(i);
+
+ free(i->name);
+ free(i->path);
+ strv_free(i->aliases);
+ strv_free(i->wanted_by);
+ strv_free(i->required_by);
+ free(i);
+}
+
+static void install_info_hashmap_free(Hashmap *m) {
+ InstallInfo *i;
+
+ if (!m)
+ return;
+
+ while ((i = hashmap_steal_first(m)))
+ install_info_free(i);
+
+ hashmap_free(m);
+}
+
+static void install_context_done(InstallContext *c) {
+ assert(c);
+
+ install_info_hashmap_free(c->will_install);
+ install_info_hashmap_free(c->have_installed);
+
+ c->will_install = c->have_installed = NULL;
+}
+
+static int install_info_add(
+ InstallContext *c,
+ const char *name,
+ const char *path) {
+ InstallInfo *i = NULL;
+ int r;
+
+ assert(c);
+ assert(name || path);
+
+ if (!name)
+ name = path_get_file_name(path);
+
+ if (!unit_name_is_valid(name, true))
+ return -EINVAL;
+
+ if (hashmap_get(c->have_installed, name) ||
+ hashmap_get(c->will_install, name))
+ return 0;
+
+ r = hashmap_ensure_allocated(&c->will_install, string_hash_func, string_compare_func);
+ if (r < 0)
+ return r;
+
+ i = new0(InstallInfo, 1);
+ if (!i)
+ return -ENOMEM;
+
+ i->name = strdup(name);
+ if (!i->name) {
+ r = -ENOMEM;
+ goto fail;
+ }
+
+ if (path) {
+ i->path = strdup(path);
+ if (!i->path) {
+ r = -ENOMEM;
+ goto fail;
+ }
+ }
+
+ r = hashmap_put(c->will_install, i->name, i);
+ if (r < 0)
+ goto fail;
+
+ return 0;
+
+fail:
+ if (i)
+ install_info_free(i);
+
+ return r;
+}
+
+static int install_info_add_auto(
+ InstallContext *c,
+ const char *name_or_path) {
+
+ assert(c);
+ assert(name_or_path);
+
+ if (path_is_absolute(name_or_path))
+ return install_info_add(c, NULL, name_or_path);
+ else
+ return install_info_add(c, name_or_path, NULL);
+}
+
+static int config_parse_also(const char *unit,
+ const char *filename,
+ unsigned line,
+ const char *section,
+ const char *lvalue,
+ int ltype,
+ const char *rvalue,
+ void *data,
+ void *userdata) {
+
+ char *w;
+ size_t l;
+ char *state;
+ InstallContext *c = data;
+
+ assert(filename);
+ assert(lvalue);
+ assert(rvalue);
+
+ FOREACH_WORD_QUOTED(w, l, rvalue, state) {
+ _cleanup_free_ char *n;
+ int r;
+
+ n = strndup(w, l);
+ if (!n)
+ return -ENOMEM;
+
+ r = install_info_add(c, n, NULL);
+ if (r < 0)
+ return r;
+ }
+
+ return 0;
+}
+
+static int config_parse_user(const char *unit,
+ const char *filename,
+ unsigned line,
+ const char *section,
+ const char *lvalue,
+ int ltype,
+ const char *rvalue,
+ void *data,
+ void *userdata) {
+
+ InstallInfo *i = data;
+ char* printed;
+
+ assert(filename);
+ assert(lvalue);
+ assert(rvalue);
+
+ printed = install_full_printf(i, rvalue);
+ if (!printed)
+ return -ENOMEM;
+
+ free(i->user);
+ i->user = printed;
+
+ return 0;
+}
+
+static int unit_file_load(
+ InstallContext *c,
+ InstallInfo *info,
+ const char *path,
+ bool allow_symlink) {
+
+ const ConfigTableItem items[] = {
+ { "Install", "Alias", config_parse_strv, 0, &info->aliases },
+ { "Install", "WantedBy", config_parse_strv, 0, &info->wanted_by },
+ { "Install", "RequiredBy", config_parse_strv, 0, &info->required_by },
+ { "Install", "Also", config_parse_also, 0, c },
+ { "Exec", "User", config_parse_user, 0, info },
+ { NULL, NULL, NULL, 0, NULL }
+ };
+
+ int fd;
+ _cleanup_fclose_ FILE *f = NULL;
+ int r;
+
+ assert(c);
+ assert(info);
+ assert(path);
+
+ fd = open(path, O_RDONLY|O_CLOEXEC|O_NOCTTY|(allow_symlink ? 0 : O_NOFOLLOW));
+ if (fd < 0)
+ return -errno;
+
+ f = fdopen(fd, "re");
+ if (!f) {
+ close_nointr_nofail(fd);
+ return -ENOMEM;
+ }
+
+ r = config_parse(NULL, path, f, NULL,
+ config_item_table_lookup, (void*) items, true, true, info);
+ if (r < 0)
+ return r;
+
+ return
+ strv_length(info->aliases) +
+ strv_length(info->wanted_by) +
+ strv_length(info->required_by);
+}
+
+static int unit_file_search(
+ InstallContext *c,
+ InstallInfo *info,
+ LookupPaths *paths,
+ const char *root_dir,
+ bool allow_symlink) {
+
+ char **p;
+ int r;
+
+ assert(c);
+ assert(info);
+ assert(paths);
+
+ if (info->path)
+ return unit_file_load(c, info, info->path, allow_symlink);
+
+ assert(info->name);
+
+ STRV_FOREACH(p, paths->unit_path) {
+ char *path = NULL;
+
+ if (isempty(root_dir))
+ asprintf(&path, "%s/%s", *p, info->name);
+ else
+ asprintf(&path, "%s/%s/%s", root_dir, *p, info->name);
+
+ if (!path)
+ return -ENOMEM;
+
+ r = unit_file_load(c, info, path, allow_symlink);
+
+ if (r >= 0)
+ info->path = path;
+ else {
+ if (r == -ENOENT && unit_name_is_instance(info->name)) {
+ /* unit file doesn't exist, however instance enablement was request */
+ /* we will check if it is possible to load template unit file */
+ char *template = NULL,
+ *template_path = NULL,
+ *template_dir = NULL;
+
+ template = unit_name_template(info->name);
+ if (!template) {
+ free(path);
+ return -ENOMEM;
+ }
+
+ /* we will reuse path variable since we don't need it anymore */
+ template_dir = path;
+ *(strrchr(path, '/') + 1) = '\0';
+
+ template_path = strjoin(template_dir, template, NULL);
+ if (!template_path) {
+ free(path);
+ free(template);
+ return -ENOMEM;
+ }
+
+ /* let's try to load template unit */
+ r = unit_file_load(c, info, template_path, allow_symlink);
+ if (r >= 0) {
+ info->path = strdup(template_path);
+ if (!info->path) {
+ free(path);
+ free(template);
+ free(template_path);
+ return -ENOMEM;
+ }
+ }
+
+ free(template);
+ free(template_path);
+ }
+ free(path);
+ }
+
+ if (r != -ENOENT && r != -ELOOP)
+ return r;
+ }
+
+ return -ENOENT;
+}
+
+static int unit_file_can_install(
+ LookupPaths *paths,
+ const char *root_dir,
+ const char *name,
+ bool allow_symlink) {
+
+ _cleanup_install_context_done_ InstallContext c = {};
+ InstallInfo *i;
+ int r;
+
+ assert(paths);
+ assert(name);
+
+ r = install_info_add_auto(&c, name);
+ if (r < 0)
+ return r;
+
+ assert_se(i = hashmap_first(c.will_install));
+
+ r = unit_file_search(&c, i, paths, root_dir, allow_symlink);
+
+ if (r >= 0)
+ r =
+ strv_length(i->aliases) +
+ strv_length(i->wanted_by) +
+ strv_length(i->required_by);
+
+ return r;
+}
+
+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);
+
+ mkdir_parents_label(new_path, 0755);
+
+ if (symlink(old_path, new_path) >= 0) {
+ add_file_change(changes, n_changes, UNIT_FILE_SYMLINK, new_path, old_path);
+ return 0;
+ }
+
+ if (errno != EEXIST)
+ return -errno;
+
+ r = readlink_and_make_absolute(new_path, &dest);
+ if (r < 0)
+ return r;
+
+ if (path_equal(dest, old_path))
+ return 0;
+
+ if (!force)
+ return -EEXIST;
+
+ unlink(new_path);
+
+ if (symlink(old_path, new_path) >= 0) {
+ add_file_change(changes, n_changes, UNIT_FILE_UNLINK, new_path, NULL);
+ add_file_change(changes, n_changes, UNIT_FILE_SYMLINK, new_path, old_path);
+ return 0;
+ }
+
+ return -errno;
+}
+
+static int install_info_symlink_alias(
+ InstallInfo *i,
+ const char *config_path,
+ bool force,
+ UnitFileChange **changes,
+ unsigned *n_changes) {
+
+ char **s;
+ int r = 0, q;
+
+ assert(i);
+ assert(config_path);
+
+ STRV_FOREACH(s, i->aliases) {
+ _cleanup_free_ char *alias_path = NULL, *dst = NULL;
+
+ dst = install_full_printf(i, *s);
+ if (!dst)
+ return -ENOMEM;
+
+ 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(
+ InstallInfo *i,
+ const char *config_path,
+ bool force,
+ UnitFileChange **changes,
+ unsigned *n_changes) {
+
+ char **s;
+ int r = 0, q;
+
+ assert(i);
+ assert(config_path);
+
+ STRV_FOREACH(s, i->wanted_by) {
+ _cleanup_free_ char *path = NULL, *dst = NULL;
+
+ dst = install_full_printf(i, *s);
+ if (!dst)
+ return -ENOMEM;
+
+ if (!unit_name_is_valid(dst, true)) {
+ r = -EINVAL;
+ continue;
+ }
+
+ if (asprintf(&path, "%s/%s.wants/%s", config_path, dst, i->name) < 0)
+ return -ENOMEM;
+
+ q = create_symlink(i->path, path, force, changes, n_changes);
+
+ if (r == 0)
+ r = q;
+ }
+
+ return r;
+}
+
+static int install_info_symlink_requires(
+ InstallInfo *i,
+ const char *config_path,
+ bool force,
+ UnitFileChange **changes,
+ unsigned *n_changes) {
+
+ char **s;
+ int r = 0, q;
+
+ assert(i);
+ assert(config_path);
+
+ STRV_FOREACH(s, i->required_by) {
+ _cleanup_free_ char *path = NULL, *dst = NULL;
+
+ dst = install_full_printf(i, *s);
+ if (!dst)
+ return -ENOMEM;
+
+ if (!unit_name_is_valid(dst, true)) {
+ r = -EINVAL;
+ continue;
+ }
+
+ if (asprintf(&path, "%s/%s.requires/%s", config_path, dst, i->name) < 0)
+ 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(
+ InstallInfo *i,
+ LookupPaths *paths,
+ const char *config_path,
+ bool force,
+ UnitFileChange **changes,
+ unsigned *n_changes) {
+
+ int r;
+ _cleanup_free_ char *path = NULL;
+
+ assert(i);
+ assert(paths);
+ assert(config_path);
+ assert(i->path);
+
+ r = in_search_path(i->path, paths->unit_path);
+ if (r != 0)
+ return r;
+
+ if (asprintf(&path, "%s/%s", config_path, i->name) < 0)
+ return -ENOMEM;
+
+ r = create_symlink(i->path, path, force, changes, n_changes);
+ return r;
+}
+
+static int install_info_apply(
+ InstallInfo *i,
+ LookupPaths *paths,
+ const char *config_path,
+ bool force,
+ UnitFileChange **changes,
+ unsigned *n_changes) {
+
+ int r, q;
+
+ assert(i);
+ assert(paths);
+ assert(config_path);
+
+ r = install_info_symlink_alias(i, config_path, force, changes, n_changes);
+
+ q = install_info_symlink_wants(i, config_path, force, changes, n_changes);
+ if (r == 0)
+ r = q;
+
+ q = install_info_symlink_requires(i, config_path, force, changes, n_changes);
+ if (r == 0)
+ r = q;
+
+ q = install_info_symlink_link(i, paths, config_path, force, changes, n_changes);
+ if (r == 0)
+ r = q;
+
+ return r;
+}
+
+static int install_context_apply(
+ InstallContext *c,
+ LookupPaths *paths,
+ const char *config_path,
+ const char *root_dir,
+ bool force,
+ UnitFileChange **changes,
+ unsigned *n_changes) {
+
+ InstallInfo *i;
+ int r = 0, q;
+
+ assert(c);
+ assert(paths);
+ assert(config_path);
+
+ while ((i = hashmap_first(c->will_install))) {
+
+ q = hashmap_ensure_allocated(&c->have_installed, string_hash_func, string_compare_func);
+ if (q < 0)
+ return q;
+
+ assert_se(hashmap_move_one(c->have_installed, c->will_install, i->name) == 0);
+
+ q = unit_file_search(c, i, paths, root_dir, false);
+ if (q < 0) {
+ if (r >= 0)
+ r = q;
+
+ return r;
+ } else if (r >= 0)
+ r += q;
+
+ q = install_info_apply(i, paths, config_path, force, changes, n_changes);
+ if (r >= 0 && q < 0)
+ r = q;
+ }
+
+ return r;
+}
+
+static int install_context_mark_for_removal(
+ InstallContext *c,
+ LookupPaths *paths,
+ Set **remove_symlinks_to,
+ const char *config_path,
+ const char *root_dir) {
+
+ InstallInfo *i;
+ int r = 0, q;
+
+ assert(c);
+ assert(paths);
+ assert(config_path);
+
+ /* Marks all items for removal */
+
+ while ((i = hashmap_first(c->will_install))) {
+
+ q = hashmap_ensure_allocated(&c->have_installed, string_hash_func, string_compare_func);
+ if (q < 0)
+ return q;
+
+ assert_se(hashmap_move_one(c->have_installed, c->will_install, i->name) == 0);
+
+ q = unit_file_search(c, i, paths, root_dir, false);
+ if (q < 0) {
+ if (r >= 0)
+ r = q;
+
+ return r;
+ } else if (r >= 0)
+ r += q;
+
+ if (unit_name_is_instance(i->name)) {
+ char *unit_file = NULL;
+
+ unit_file = path_get_file_name(i->path);
+
+ if (unit_name_is_instance(unit_file))
+ /* unit file named as instance exists, thus all symlinks pointing to it, will be removed */
+ q = mark_symlink_for_removal(remove_symlinks_to, i->name);
+ else
+ /* does not exist, thus we will mark for removal symlinks to template unit file */
+ q = mark_symlink_for_removal(remove_symlinks_to, unit_file);
+ } else
+ q = mark_symlink_for_removal(remove_symlinks_to, i->name);
+
+ if (r >= 0 && q < 0)
+ r = q;
+ }
+
+ return r;
+}
+
+int unit_file_enable(
+ UnitFileScope scope,
+ bool runtime,
+ const char *root_dir,
+ char *files[],
+ bool force,
+ UnitFileChange **changes,
+ unsigned *n_changes) {
+
+ _cleanup_lookup_paths_free_ LookupPaths paths = {};
+ _cleanup_install_context_done_ InstallContext c = {};
+ char **i;
+ _cleanup_free_ char *config_path = NULL;
+ int r;
+
+ assert(scope >= 0);
+ assert(scope < _UNIT_FILE_SCOPE_MAX);
+
+ r = lookup_paths_init_from_scope(&paths, scope);
+ if (r < 0)
+ return r;
+
+ r = get_config_path(scope, runtime, root_dir, &config_path);
+ if (r < 0)
+ return r;
+
+ STRV_FOREACH(i, files) {
+ r = install_info_add_auto(&c, *i);
+ if (r < 0)
+ return r;
+ }
+
+ /* 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. */
+ r = install_context_apply(&c, &paths, config_path, root_dir, force, changes, n_changes);
+ return r;
+}
+
+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 = {};
+ char **i;
+ _cleanup_free_ char *config_path = NULL;
+ _cleanup_set_free_free_ Set *remove_symlinks_to = NULL;
+ int r, q;
+
+ assert(scope >= 0);
+ assert(scope < _UNIT_FILE_SCOPE_MAX);
+
+ r = lookup_paths_init_from_scope(&paths, scope);
+ if (r < 0)
+ return r;
+
+ r = get_config_path(scope, runtime, root_dir, &config_path);
+ if (r < 0)
+ return r;
+
+ STRV_FOREACH(i, files) {
+ r = install_info_add_auto(&c, *i);
+ if (r < 0)
+ return r;
+ }
+
+ r = install_context_mark_for_removal(&c, &paths, &remove_symlinks_to, config_path, root_dir);
+
+ q = remove_marked_symlinks(remove_symlinks_to, config_path, changes, n_changes, files);
+ if (r == 0)
+ r = q;
+
+ return r;
+}
+
+int unit_file_reenable(
+ 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 = {};
+ char **i;
+ _cleanup_free_ char *config_path = NULL;
+ _cleanup_set_free_free_ Set *remove_symlinks_to = NULL;
+ int r, q;
+
+ assert(scope >= 0);
+ assert(scope < _UNIT_FILE_SCOPE_MAX);
+
+ r = lookup_paths_init_from_scope(&paths, scope);
+ if (r < 0)
+ return r;
+
+ r = get_config_path(scope, runtime, root_dir, &config_path);
+ if (r < 0)
+ return r;
+
+ STRV_FOREACH(i, files) {
+ r = mark_symlink_for_removal(&remove_symlinks_to, *i);
+ if (r < 0)
+ return r;
+
+ r = install_info_add_auto(&c, *i);
+ if (r < 0)
+ return r;
+ }
+
+ r = remove_marked_symlinks(remove_symlinks_to, config_path, changes, n_changes, files);
+
+ /* Returns number of symlinks that where supposed to be installed. */
+ q = install_context_apply(&c, &paths, config_path, root_dir, force, changes, n_changes);
+ if (r == 0)
+ r = q;
+
+ return r;
+}
+
+UnitFileState unit_file_get_state(
+ UnitFileScope scope,
+ const char *root_dir,
+ const char *name) {
+
+ _cleanup_lookup_paths_free_ LookupPaths paths = {};
+ UnitFileState state = _UNIT_FILE_STATE_INVALID;
+ char **i;
+ _cleanup_free_ char *path = NULL;
+ int r;
+
+ assert(scope >= 0);
+ assert(scope < _UNIT_FILE_SCOPE_MAX);
+ assert(name);
+
+ if (root_dir && scope != UNIT_FILE_SYSTEM)
+ return -EINVAL;
+
+ if (!unit_name_is_valid(name, true))
+ return -EINVAL;
+
+ r = lookup_paths_init_from_scope(&paths, scope);
+ if (r < 0)
+ return r;
+
+ STRV_FOREACH(i, paths.unit_path) {
+ struct stat st;
+
+ free(path);
+ path = NULL;
+
+ if (root_dir)
+ asprintf(&path, "%s/%s/%s", root_dir, *i, name);
+ else
+ asprintf(&path, "%s/%s", *i, name);
+
+ if (!path)
+ return -ENOMEM;
+
+ if (lstat(path, &st) < 0) {
+ r = -errno;
+ if (errno == ENOENT)
+ continue;
+
+ return -errno;
+ }
+
+ if (!S_ISREG(st.st_mode) && !S_ISLNK(st.st_mode))
+ return -ENOENT;
+
+ r = null_or_empty_path(path);
+ if (r < 0 && r != -ENOENT)
+ return r;
+ else if (r > 0) {
+ state = path_startswith(*i, "/run") ?
+ UNIT_FILE_MASKED_RUNTIME : UNIT_FILE_MASKED;
+ return state;
+ }
+
+ r = find_symlinks_in_scope(scope, root_dir, name, &state);
+ if (r < 0)
+ return r;
+ else if (r > 0)
+ return state;
+
+ r = unit_file_can_install(&paths, root_dir, path, true);
+ if (r < 0 && errno != ENOENT)
+ return r;
+ else if (r > 0)
+ return UNIT_FILE_DISABLED;
+ else if (r == 0)
+ return UNIT_FILE_STATIC;
+ }
+
+ return r < 0 ? r : state;
+}
+
+int unit_file_query_preset(UnitFileScope scope, const char *name) {
+ _cleanup_strv_free_ char **files = NULL;
+ char **i;
+ int r;
+
+ assert(scope >= 0);
+ assert(scope < _UNIT_FILE_SCOPE_MAX);
+ assert(name);
+
+ if (scope == UNIT_FILE_SYSTEM)
+ r = conf_files_list(&files, ".preset", NULL,
+ "/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", NULL,
+ "/etc/systemd/user-preset",
+ "/usr/local/lib/systemd/user-preset",
+ "/usr/lib/systemd/user-preset",
+ NULL);
+ else
+ return 1;
+
+ if (r < 0)
+ return r;
+
+ STRV_FOREACH(i, files) {
+ _cleanup_fclose_ FILE *f;
+
+ f = fopen(*i, "re");
+ if (!f) {
+ if (errno == ENOENT)
+ continue;
+
+ return -errno;
+ }
+
+ for (;;) {
+ char line[LINE_MAX], *l;
+
+ if (!fgets(line, sizeof(line), f))
+ break;
+
+ l = strstrip(line);
+ if (!*l)
+ continue;
+
+ if (strchr(COMMENTS "\n", *l))
+ continue;
+
+ if (first_word(l, "enable")) {
+ l += 6;
+ l += strspn(l, WHITESPACE);
+
+ if (fnmatch(l, name, FNM_NOESCAPE) == 0)
+ return 1;
+
+ } else if (first_word(l, "disable")) {
+ l += 7;
+ l += strspn(l, WHITESPACE);
+
+ if (fnmatch(l, name, FNM_NOESCAPE) == 0)
+ return 0;
+
+ } else
+ log_debug("Couldn't parse line '%s'", l);
+ }
+ }
+
+ /* Default is "enable" */
+ return 1;
+}
+
+int unit_file_preset(
+ 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 plus = {}, minus = {};
+ char **i;
+ _cleanup_free_ char *config_path = NULL;
+ _cleanup_set_free_free_ Set *remove_symlinks_to = NULL;
+ int r, q;
+
+ assert(scope >= 0);
+ assert(scope < _UNIT_FILE_SCOPE_MAX);
+
+ r = lookup_paths_init_from_scope(&paths, scope);
+ if (r < 0)
+ return r;
+
+ r = get_config_path(scope, runtime, root_dir, &config_path);
+ if (r < 0)
+ return r;
+
+ STRV_FOREACH(i, files) {
+
+ if (!unit_name_is_valid(*i, true))
+ return -EINVAL;
+
+ r = unit_file_query_preset(scope, *i);
+ if (r < 0)
+ return r;
+
+ if (r)
+ r = install_info_add_auto(&plus, *i);
+ else
+ r = install_info_add_auto(&minus, *i);
+
+ if (r < 0)
+ return r;
+ }
+
+ r = install_context_mark_for_removal(&minus, &paths, &remove_symlinks_to,
+ config_path, root_dir);
+
+ q = remove_marked_symlinks(remove_symlinks_to, config_path,
+ changes, n_changes, files);
+ if (r == 0)
+ r = q;
+
+ /* Returns number of symlinks that where supposed to be installed. */
+ q = install_context_apply(&plus, &paths, config_path, root_dir, force,
+ changes, n_changes);
+ if (r == 0)
+ r = q;
+
+ return r;
+}
+
+static void unitfilelist_free(UnitFileList **f) {
+ if (!*f)
+ return;
+
+ free((*f)->path);
+ free(*f);
+}
+
+int unit_file_get_list(
+ UnitFileScope scope,
+ const char *root_dir,
+ Hashmap *h) {
+
+ _cleanup_lookup_paths_free_ LookupPaths paths = {};
+ char **i;
+ _cleanup_free_ char *buf = NULL;
+ _cleanup_closedir_ DIR *d = NULL;
+ int r;
+
+ assert(scope >= 0);
+ assert(scope < _UNIT_FILE_SCOPE_MAX);
+ assert(h);
+
+ if (root_dir && scope != UNIT_FILE_SYSTEM)
+ return -EINVAL;
+
+ r = lookup_paths_init_from_scope(&paths, scope);
+ if (r < 0)
+ return r;
+
+ STRV_FOREACH(i, paths.unit_path) {
+ const char *units_dir;
+
+ free(buf);
+ buf = NULL;
+
+ if (root_dir) {
+ if (asprintf(&buf, "%s/%s", root_dir, *i) < 0)
+ return -ENOMEM;
+
+ units_dir = buf;
+ } else
+ units_dir = *i;
+
+ if (d)
+ closedir(d);
+
+ d = opendir(units_dir);
+ if (!d) {
+ if (errno == ENOENT)
+ continue;
+
+ return -errno;
+ }
+
+ for (;;) {
+ struct dirent *de;
+ union dirent_storage buffer;
+ UnitFileList __attribute__((cleanup(unitfilelist_free)))
+ *f = NULL;
+
+ r = readdir_r(d, &buffer.de, &de);
+ if (r != 0)
+ return -r;
+
+ if (!de)
+ break;
+
+ if (ignore_file(de->d_name))
+ continue;
+
+ if (!unit_name_is_valid(de->d_name, true))
+ continue;
+
+ if (hashmap_get(h, de->d_name))
+ continue;
+
+ r = dirent_ensure_type(d, de);
+ if (r < 0) {
+ if (r == -ENOENT)
+ continue;
+
+ return r;
+ }
+
+ if (de->d_type != DT_LNK && de->d_type != 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 = null_or_empty_path(f->path);
+ if (r < 0 && r != -ENOENT)
+ return r;
+ else if (r > 0) {
+ f->state =
+ path_startswith(*i, "/run") ?
+ UNIT_FILE_MASKED_RUNTIME : UNIT_FILE_MASKED;
+ goto found;
+ }
+
+ r = find_symlinks_in_scope(scope, root_dir, de->d_name, &f->state);
+ if (r < 0)
+ return r;
+ else if (r > 0) {
+ f->state = UNIT_FILE_ENABLED;
+ goto found;
+ }
+
+ r = unit_file_can_install(&paths, root_dir, f->path, true);
+ if (r == -EINVAL || /* Invalid setting? */
+ r == -EBADMSG || /* Invalid format? */
+ r == -ENOENT /* Included file not found? */)
+ f->state = UNIT_FILE_INVALID;
+ else if (r < 0)
+ return r;
+ else if (r > 0)
+ f->state = UNIT_FILE_DISABLED;
+ else
+ f->state = UNIT_FILE_STATIC;
+
+ found:
+ r = hashmap_put(h, path_get_file_name(f->path), f);
+ if (r < 0)
+ return r;
+ f = NULL; /* prevent cleanup */
+ }
+ }
+
+ return r;
+}
+
+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_INVALID] = "invalid",
+};
+
+DEFINE_STRING_TABLE_LOOKUP(unit_file_state, UnitFileState);
+
+static const char* const unit_file_change_type_table[_UNIT_FILE_CHANGE_TYPE_MAX] = {
+ [UNIT_FILE_SYMLINK] = "symlink",
+ [UNIT_FILE_UNLINK] = "unlink",
+};
+
+DEFINE_STRING_TABLE_LOOKUP(unit_file_change_type, UnitFileChangeType);
diff --git a/src/shared/install.h b/src/shared/install.h
new file mode 100644
index 0000000000..94516c9d05
--- /dev/null
+++ b/src/shared/install.h
@@ -0,0 +1,97 @@
+/*-*- Mode: C; c-basic-offset: 8; indent-tabs-mode: nil -*-*/
+
+#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/>.
+***/
+
+#include "hashmap.h"
+
+typedef enum UnitFileScope {
+ UNIT_FILE_SYSTEM,
+ UNIT_FILE_GLOBAL,
+ UNIT_FILE_USER,
+ _UNIT_FILE_SCOPE_MAX,
+ _UNIT_FILE_SCOPE_INVALID = -1
+} UnitFileScope;
+
+typedef enum UnitFileState {
+ UNIT_FILE_ENABLED,
+ UNIT_FILE_ENABLED_RUNTIME,
+ UNIT_FILE_LINKED,
+ UNIT_FILE_LINKED_RUNTIME,
+ UNIT_FILE_MASKED,
+ UNIT_FILE_MASKED_RUNTIME,
+ UNIT_FILE_STATIC,
+ UNIT_FILE_DISABLED,
+ UNIT_FILE_INVALID,
+ _UNIT_FILE_STATE_MAX,
+ _UNIT_FILE_STATE_INVALID = -1
+} UnitFileState;
+
+typedef enum UnitFileChangeType {
+ UNIT_FILE_SYMLINK,
+ UNIT_FILE_UNLINK,
+ _UNIT_FILE_CHANGE_TYPE_MAX,
+ _UNIT_FILE_CHANGE_TYPE_INVALID = -1
+} UnitFileChangeType;
+
+typedef struct UnitFileChange {
+ UnitFileChangeType type;
+ char *path;
+ char *source;
+} UnitFileChange;
+
+typedef struct UnitFileList {
+ char *path;
+ UnitFileState state;
+} UnitFileList;
+
+typedef struct {
+ char *name;
+ char *path;
+ char *user;
+
+ char **aliases;
+ char **wanted_by;
+ char **required_by;
+} InstallInfo;
+
+int unit_file_enable(UnitFileScope scope, bool runtime, const char *root_dir, char *files[], bool force, UnitFileChange **changes, unsigned *n_changes);
+int unit_file_disable(UnitFileScope scope, bool runtime, const char *root_dir, char *files[], UnitFileChange **changes, unsigned *n_changes);
+int unit_file_reenable(UnitFileScope scope, bool runtime, const char *root_dir, char *files[], bool force, UnitFileChange **changes, unsigned *n_changes);
+int unit_file_link(UnitFileScope scope, bool runtime, const char *root_dir, char *files[], bool force, UnitFileChange **changes, unsigned *n_changes);
+int unit_file_preset(UnitFileScope scope, bool runtime, const char *root_dir, char *files[], bool force, UnitFileChange **changes, unsigned *n_changes);
+int unit_file_mask(UnitFileScope scope, bool runtime, const char *root_dir, char *files[], bool force, UnitFileChange **changes, unsigned *n_changes);
+int unit_file_unmask(UnitFileScope scope, bool runtime, const char *root_dir, char *files[], UnitFileChange **changes, unsigned *n_changes);
+
+UnitFileState unit_file_get_state(UnitFileScope scope, const char *root_dir, const char *filename);
+
+int unit_file_get_list(UnitFileScope scope, const char *root_dir, Hashmap *h);
+
+void unit_file_list_free(Hashmap *h);
+void unit_file_changes_free(UnitFileChange *changes, unsigned n_changes);
+
+int unit_file_query_preset(UnitFileScope scope, const char *name);
+
+const char *unit_file_state_to_string(UnitFileState s) _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_;
diff --git a/src/shared/ioprio.h b/src/shared/ioprio.h
new file mode 100644
index 0000000000..9800fc2553
--- /dev/null
+++ b/src/shared/ioprio.h
@@ -0,0 +1,57 @@
+#ifndef IOPRIO_H
+#define IOPRIO_H
+
+/* This is minimal version of Linux' linux/ioprio.h header file, which
+ * is licensed GPL2 */
+
+#include <unistd.h>
+#include <sys/syscall.h>
+
+/*
+ * Gives us 8 prio classes with 13-bits of data for each class
+ */
+#define IOPRIO_BITS (16)
+#define IOPRIO_CLASS_SHIFT (13)
+#define IOPRIO_PRIO_MASK ((1UL << IOPRIO_CLASS_SHIFT) - 1)
+
+#define IOPRIO_PRIO_CLASS(mask) ((mask) >> IOPRIO_CLASS_SHIFT)
+#define IOPRIO_PRIO_DATA(mask) ((mask) & IOPRIO_PRIO_MASK)
+#define IOPRIO_PRIO_VALUE(class, data) (((class) << IOPRIO_CLASS_SHIFT) | data)
+
+#define ioprio_valid(mask) (IOPRIO_PRIO_CLASS((mask)) != IOPRIO_CLASS_NONE)
+
+/*
+ * These are the io priority groups as implemented by CFQ. RT is the realtime
+ * class, it always gets premium service. BE is the best-effort scheduling
+ * class, the default for any process. IDLE is the idle scheduling class, it
+ * is only served when no one else is using the disk.
+ */
+enum {
+ IOPRIO_CLASS_NONE,
+ IOPRIO_CLASS_RT,
+ IOPRIO_CLASS_BE,
+ IOPRIO_CLASS_IDLE,
+};
+
+/*
+ * 8 best effort priority levels are supported
+ */
+#define IOPRIO_BE_NR (8)
+
+enum {
+ IOPRIO_WHO_PROCESS = 1,
+ IOPRIO_WHO_PGRP,
+ IOPRIO_WHO_USER,
+};
+
+static inline int ioprio_set(int which, int who, int ioprio)
+{
+ return syscall(__NR_ioprio_set, which, who, ioprio);
+}
+
+static inline int ioprio_get(int which, int who)
+{
+ return syscall(__NR_ioprio_get, which, who);
+}
+
+#endif
diff --git a/src/shared/label.c b/src/shared/label.c
new file mode 100644
index 0000000000..1fe4574633
--- /dev/null
+++ b/src/shared/label.c
@@ -0,0 +1,386 @@
+/*-*- Mode: C; c-basic-offset: 8; indent-tabs-mode: nil -*-*/
+
+/***
+ This file is part 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 <sys/stat.h>
+#include <unistd.h>
+#include <malloc.h>
+#include <sys/socket.h>
+#include <sys/un.h>
+#include <sys/types.h>
+#include <sys/stat.h>
+#include <fcntl.h>
+
+#include "label.h"
+#include "strv.h"
+#include "util.h"
+#include "path-util.h"
+
+#ifdef HAVE_SELINUX
+#include "selinux-util.h"
+#include <selinux/selinux.h>
+#include <selinux/label.h>
+
+static struct selabel_handle *label_hnd = NULL;
+
+#endif
+
+int label_init(const char *prefix) {
+ int r = 0;
+
+#ifdef HAVE_SELINUX
+ usec_t before_timestamp, after_timestamp;
+ struct mallinfo before_mallinfo, after_mallinfo;
+
+ if (!use_selinux())
+ return 0;
+
+ if (label_hnd)
+ return 0;
+
+ before_mallinfo = mallinfo();
+ before_timestamp = now(CLOCK_MONOTONIC);
+
+ if (prefix) {
+ struct selinux_opt options[] = {
+ { .type = SELABEL_OPT_SUBSET, .value = prefix },
+ };
+
+ label_hnd = selabel_open(SELABEL_CTX_FILE, options, ELEMENTSOF(options));
+ } else
+ label_hnd = selabel_open(SELABEL_CTX_FILE, NULL, 0);
+
+ if (!label_hnd) {
+ log_full(security_getenforce() == 1 ? LOG_ERR : LOG_DEBUG,
+ "Failed to initialize SELinux context: %m");
+ r = security_getenforce() == 1 ? -errno : 0;
+ } else {
+ char timespan[FORMAT_TIMESPAN_MAX];
+ int l;
+
+ after_timestamp = now(CLOCK_MONOTONIC);
+ after_mallinfo = mallinfo();
+
+ l = after_mallinfo.uordblks > before_mallinfo.uordblks ? after_mallinfo.uordblks - before_mallinfo.uordblks : 0;
+
+ log_debug("Successfully loaded SELinux database in %s, size on heap is %iK.",
+ format_timespan(timespan, sizeof(timespan), after_timestamp - before_timestamp, 0),
+ (l+1023)/1024);
+ }
+#endif
+
+ return r;
+}
+
+int label_fix(const char *path, bool ignore_enoent, bool ignore_erofs) {
+ int r = 0;
+
+#ifdef HAVE_SELINUX
+ struct stat st;
+ security_context_t fcon;
+
+ if (!use_selinux() || !label_hnd)
+ return 0;
+
+ r = lstat(path, &st);
+ if (r == 0) {
+ r = selabel_lookup_raw(label_hnd, &fcon, path, st.st_mode);
+
+ /* If there's no label to set, then exit without warning */
+ if (r < 0 && errno == ENOENT)
+ return 0;
+
+ if (r == 0) {
+ r = lsetfilecon(path, fcon);
+ freecon(fcon);
+
+ /* If the FS doesn't support labels, then exit without warning */
+ if (r < 0 && errno == ENOTSUP)
+ return 0;
+ }
+ }
+
+ if (r < 0) {
+ /* Ignore ENOENT in some cases */
+ if (ignore_enoent && errno == ENOENT)
+ return 0;
+
+ if (ignore_erofs && errno == EROFS)
+ return 0;
+
+ log_full(security_getenforce() == 1 ? LOG_ERR : LOG_DEBUG,
+ "Unable to fix label of %s: %m", path);
+ r = security_getenforce() == 1 ? -errno : 0;
+ }
+#endif
+
+ return r;
+}
+
+void label_finish(void) {
+
+#ifdef HAVE_SELINUX
+ if (use_selinux() && label_hnd)
+ selabel_close(label_hnd);
+#endif
+}
+
+int label_get_create_label_from_exe(const char *exe, char **label) {
+
+ int r = 0;
+
+#ifdef HAVE_SELINUX
+ security_context_t mycon = NULL, fcon = NULL;
+ security_class_t sclass;
+
+ if (!use_selinux()) {
+ *label = NULL;
+ return 0;
+ }
+
+ r = getcon(&mycon);
+ if (r < 0)
+ goto fail;
+
+ r = getfilecon(exe, &fcon);
+ if (r < 0)
+ goto fail;
+
+ sclass = string_to_security_class("process");
+ r = security_compute_create(mycon, fcon, sclass, (security_context_t *) label);
+ if (r == 0)
+ log_debug("SELinux Socket context for %s will be set to %s", exe, *label);
+
+fail:
+ if (r < 0 && security_getenforce() == 1)
+ r = -errno;
+
+ freecon(mycon);
+ freecon(fcon);
+#endif
+
+ return r;
+}
+
+int label_context_set(const char *path, mode_t mode) {
+ int r = 0;
+
+#ifdef HAVE_SELINUX
+ security_context_t filecon = NULL;
+
+ if (!use_selinux() || !label_hnd)
+ return 0;
+
+ r = selabel_lookup_raw(label_hnd, &filecon, path, mode);
+ if (r < 0 && errno != ENOENT)
+ r = -errno;
+ else if (r == 0) {
+ r = setfscreatecon(filecon);
+ if (r < 0) {
+ log_error("Failed to set SELinux file context on %s: %m", path);
+ r = -errno;
+ }
+
+ freecon(filecon);
+ }
+
+ if (r < 0 && security_getenforce() == 0)
+ r = 0;
+#endif
+
+ return r;
+}
+
+int label_socket_set(const char *label) {
+
+#ifdef HAVE_SELINUX
+ if (!use_selinux())
+ return 0;
+
+ if (setsockcreatecon((security_context_t) label) < 0) {
+ log_full(security_getenforce() == 1 ? LOG_ERR : LOG_DEBUG,
+ "Failed to set SELinux context (%s) on socket: %m", label);
+
+ if (security_getenforce() == 1)
+ return -errno;
+ }
+#endif
+
+ return 0;
+}
+
+void label_context_clear(void) {
+
+#ifdef HAVE_SELINUX
+ if (!use_selinux())
+ return;
+
+ setfscreatecon(NULL);
+#endif
+}
+
+void label_socket_clear(void) {
+
+#ifdef HAVE_SELINUX
+ if (!use_selinux())
+ return;
+
+ setsockcreatecon(NULL);
+#endif
+}
+
+void label_free(const char *label) {
+
+#ifdef HAVE_SELINUX
+ if (!use_selinux())
+ return;
+
+ freecon((security_context_t) label);
+#endif
+}
+
+int label_mkdir(const char *path, mode_t mode, bool apply) {
+
+ /* Creates a directory and labels it according to the SELinux policy */
+#ifdef HAVE_SELINUX
+ int r;
+ security_context_t fcon = NULL;
+
+ if (!apply || !use_selinux() || !label_hnd)
+ goto skipped;
+
+ if (path_is_absolute(path))
+ r = selabel_lookup_raw(label_hnd, &fcon, path, S_IFDIR);
+ else {
+ char *newpath;
+
+ newpath = path_make_absolute_cwd(path);
+ if (!newpath)
+ return -ENOMEM;
+
+ r = selabel_lookup_raw(label_hnd, &fcon, newpath, S_IFDIR);
+ free(newpath);
+ }
+
+ if (r == 0)
+ r = setfscreatecon(fcon);
+
+ if (r < 0 && errno != ENOENT) {
+ log_error("Failed to set security context %s for %s: %m", fcon, path);
+
+ if (security_getenforce() == 1) {
+ r = -errno;
+ goto finish;
+ }
+ }
+
+ r = mkdir(path, mode);
+ if (r < 0)
+ r = -errno;
+
+finish:
+ setfscreatecon(NULL);
+ freecon(fcon);
+
+ return r;
+
+skipped:
+#endif
+ return mkdir(path, mode) < 0 ? -errno : 0;
+}
+
+int label_bind(int fd, const struct sockaddr *addr, socklen_t addrlen) {
+
+ /* Binds a socket and label its file system object according to the SELinux policy */
+
+#ifdef HAVE_SELINUX
+ int r;
+ security_context_t fcon = NULL;
+ const struct sockaddr_un *un;
+ char *path = NULL;
+
+ assert(fd >= 0);
+ assert(addr);
+ assert(addrlen >= sizeof(sa_family_t));
+
+ if (!use_selinux() || !label_hnd)
+ goto skipped;
+
+ /* Filter out non-local sockets */
+ if (addr->sa_family != AF_UNIX)
+ goto skipped;
+
+ /* Filter out anonymous sockets */
+ if (addrlen < sizeof(sa_family_t) + 1)
+ goto skipped;
+
+ /* Filter out abstract namespace sockets */
+ un = (const struct sockaddr_un*) addr;
+ if (un->sun_path[0] == 0)
+ goto skipped;
+
+ path = strndup(un->sun_path, addrlen - offsetof(struct sockaddr_un, sun_path));
+ if (!path)
+ return -ENOMEM;
+
+ if (path_is_absolute(path))
+ r = selabel_lookup_raw(label_hnd, &fcon, path, S_IFSOCK);
+ else {
+ char *newpath;
+
+ newpath = path_make_absolute_cwd(path);
+
+ if (!newpath) {
+ free(path);
+ return -ENOMEM;
+ }
+
+ r = selabel_lookup_raw(label_hnd, &fcon, newpath, S_IFSOCK);
+ free(newpath);
+ }
+
+ if (r == 0)
+ r = setfscreatecon(fcon);
+
+ if (r < 0 && errno != ENOENT) {
+ log_error("Failed to set security context %s for %s: %m", fcon, path);
+
+ if (security_getenforce() == 1) {
+ r = -errno;
+ goto finish;
+ }
+ }
+
+ r = bind(fd, addr, addrlen);
+ if (r < 0)
+ r = -errno;
+
+finish:
+ setfscreatecon(NULL);
+ freecon(fcon);
+ free(path);
+
+ return r;
+
+skipped:
+#endif
+ return bind(fd, addr, addrlen) < 0 ? -errno : 0;
+}
diff --git a/src/shared/label.h b/src/shared/label.h
new file mode 100644
index 0000000000..dda4d1c024
--- /dev/null
+++ b/src/shared/label.h
@@ -0,0 +1,51 @@
+/*-*- Mode: C; c-basic-offset: 8; indent-tabs-mode: nil -*-*/
+
+#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 <sys/types.h>
+#include <stdbool.h>
+#include <sys/socket.h>
+
+int label_init(const char *prefix);
+void label_finish(void);
+
+int label_fix(const char *path, bool ignore_enoent, bool ignore_erofs);
+
+int label_socket_set(const char *label);
+void label_socket_clear(void);
+
+int label_context_set(const char *path, mode_t mode);
+void label_context_clear(void);
+
+void label_free(const char *label);
+
+int label_get_create_label_from_exe(const char *exe, char **label);
+
+int label_mkdir(const char *path, mode_t mode, bool apply);
+
+void label_retest_selinux(void);
+
+int label_bind(int fd, const struct sockaddr *addr, socklen_t addrlen);
+
+int label_write_one_line_file_atomic(const char *fn, const char *line);
+int label_write_env_file(const char *fname, char **l);
+int label_fopen_temporary(const char *path, FILE **_f, char **_temp_path);
diff --git a/src/shared/linux/auto_dev-ioctl.h b/src/shared/linux/auto_dev-ioctl.h
new file mode 100644
index 0000000000..850f39b33e
--- /dev/null
+++ b/src/shared/linux/auto_dev-ioctl.h
@@ -0,0 +1,229 @@
+/*
+ * Copyright 2008 Red Hat, Inc. All rights reserved.
+ * Copyright 2008 Ian Kent <raven@themaw.net>
+ *
+ * This file is part of the Linux kernel and is made available under
+ * the terms of the GNU General Public License, version 2, or at your
+ * option, any later version, incorporated herein by reference.
+ */
+
+#ifndef _LINUX_AUTO_DEV_IOCTL_H
+#define _LINUX_AUTO_DEV_IOCTL_H
+
+#include <linux/auto_fs.h>
+
+#ifdef __KERNEL__
+#include <linux/string.h>
+#else
+#include <string.h>
+#endif /* __KERNEL__ */
+
+#define AUTOFS_DEVICE_NAME "autofs"
+
+#define AUTOFS_DEV_IOCTL_VERSION_MAJOR 1
+#define AUTOFS_DEV_IOCTL_VERSION_MINOR 0
+
+#define AUTOFS_DEVID_LEN 16
+
+#define AUTOFS_DEV_IOCTL_SIZE sizeof(struct autofs_dev_ioctl)
+
+/*
+ * An ioctl interface for autofs mount point control.
+ */
+
+struct args_protover {
+ __u32 version;
+};
+
+struct args_protosubver {
+ __u32 sub_version;
+};
+
+struct args_openmount {
+ __u32 devid;
+};
+
+struct args_ready {
+ __u32 token;
+};
+
+struct args_fail {
+ __u32 token;
+ __s32 status;
+};
+
+struct args_setpipefd {
+ __s32 pipefd;
+};
+
+struct args_timeout {
+ __u64 timeout;
+};
+
+struct args_requester {
+ __u32 uid;
+ __u32 gid;
+};
+
+struct args_expire {
+ __u32 how;
+};
+
+struct args_askumount {
+ __u32 may_umount;
+};
+
+struct args_ismountpoint {
+ union {
+ struct args_in {
+ __u32 type;
+ } in;
+ struct args_out {
+ __u32 devid;
+ __u32 magic;
+ } out;
+ };
+};
+
+/*
+ * All the ioctls use this structure.
+ * When sending a path size must account for the total length
+ * of the chunk of memory otherwise is is the size of the
+ * structure.
+ */
+
+struct autofs_dev_ioctl {
+ __u32 ver_major;
+ __u32 ver_minor;
+ __u32 size; /* total size of data passed in
+ * including this struct */
+ __s32 ioctlfd; /* automount command fd */
+
+ /* Command parameters */
+
+ union {
+ struct args_protover protover;
+ struct args_protosubver protosubver;
+ struct args_openmount openmount;
+ struct args_ready ready;
+ struct args_fail fail;
+ struct args_setpipefd setpipefd;
+ struct args_timeout timeout;
+ struct args_requester requester;
+ struct args_expire expire;
+ struct args_askumount askumount;
+ struct args_ismountpoint ismountpoint;
+ };
+
+ char path[0];
+};
+
+static inline void init_autofs_dev_ioctl(struct autofs_dev_ioctl *in)
+{
+ memset(in, 0, sizeof(struct autofs_dev_ioctl));
+ in->ver_major = AUTOFS_DEV_IOCTL_VERSION_MAJOR;
+ in->ver_minor = AUTOFS_DEV_IOCTL_VERSION_MINOR;
+ in->size = sizeof(struct autofs_dev_ioctl);
+ in->ioctlfd = -1;
+ return;
+}
+
+/*
+ * If you change this make sure you make the corresponding change
+ * to autofs-dev-ioctl.c:lookup_ioctl()
+ */
+enum {
+ /* Get various version info */
+ AUTOFS_DEV_IOCTL_VERSION_CMD = 0x71,
+ AUTOFS_DEV_IOCTL_PROTOVER_CMD,
+ AUTOFS_DEV_IOCTL_PROTOSUBVER_CMD,
+
+ /* Open mount ioctl fd */
+ AUTOFS_DEV_IOCTL_OPENMOUNT_CMD,
+
+ /* Close mount ioctl fd */
+ AUTOFS_DEV_IOCTL_CLOSEMOUNT_CMD,
+
+ /* Mount/expire status returns */
+ AUTOFS_DEV_IOCTL_READY_CMD,
+ AUTOFS_DEV_IOCTL_FAIL_CMD,
+
+ /* Activate/deactivate autofs mount */
+ AUTOFS_DEV_IOCTL_SETPIPEFD_CMD,
+ AUTOFS_DEV_IOCTL_CATATONIC_CMD,
+
+ /* Expiry timeout */
+ AUTOFS_DEV_IOCTL_TIMEOUT_CMD,
+
+ /* Get mount last requesting uid and gid */
+ AUTOFS_DEV_IOCTL_REQUESTER_CMD,
+
+ /* Check for eligible expire candidates */
+ AUTOFS_DEV_IOCTL_EXPIRE_CMD,
+
+ /* Request busy status */
+ AUTOFS_DEV_IOCTL_ASKUMOUNT_CMD,
+
+ /* Check if path is a mountpoint */
+ AUTOFS_DEV_IOCTL_ISMOUNTPOINT_CMD,
+};
+
+#define AUTOFS_IOCTL 0x93
+
+#define AUTOFS_DEV_IOCTL_VERSION \
+ _IOWR(AUTOFS_IOCTL, \
+ AUTOFS_DEV_IOCTL_VERSION_CMD, struct autofs_dev_ioctl)
+
+#define AUTOFS_DEV_IOCTL_PROTOVER \
+ _IOWR(AUTOFS_IOCTL, \
+ AUTOFS_DEV_IOCTL_PROTOVER_CMD, struct autofs_dev_ioctl)
+
+#define AUTOFS_DEV_IOCTL_PROTOSUBVER \
+ _IOWR(AUTOFS_IOCTL, \
+ AUTOFS_DEV_IOCTL_PROTOSUBVER_CMD, struct autofs_dev_ioctl)
+
+#define AUTOFS_DEV_IOCTL_OPENMOUNT \
+ _IOWR(AUTOFS_IOCTL, \
+ AUTOFS_DEV_IOCTL_OPENMOUNT_CMD, struct autofs_dev_ioctl)
+
+#define AUTOFS_DEV_IOCTL_CLOSEMOUNT \
+ _IOWR(AUTOFS_IOCTL, \
+ AUTOFS_DEV_IOCTL_CLOSEMOUNT_CMD, struct autofs_dev_ioctl)
+
+#define AUTOFS_DEV_IOCTL_READY \
+ _IOWR(AUTOFS_IOCTL, \
+ AUTOFS_DEV_IOCTL_READY_CMD, struct autofs_dev_ioctl)
+
+#define AUTOFS_DEV_IOCTL_FAIL \
+ _IOWR(AUTOFS_IOCTL, \
+ AUTOFS_DEV_IOCTL_FAIL_CMD, struct autofs_dev_ioctl)
+
+#define AUTOFS_DEV_IOCTL_SETPIPEFD \
+ _IOWR(AUTOFS_IOCTL, \
+ AUTOFS_DEV_IOCTL_SETPIPEFD_CMD, struct autofs_dev_ioctl)
+
+#define AUTOFS_DEV_IOCTL_CATATONIC \
+ _IOWR(AUTOFS_IOCTL, \
+ AUTOFS_DEV_IOCTL_CATATONIC_CMD, struct autofs_dev_ioctl)
+
+#define AUTOFS_DEV_IOCTL_TIMEOUT \
+ _IOWR(AUTOFS_IOCTL, \
+ AUTOFS_DEV_IOCTL_TIMEOUT_CMD, struct autofs_dev_ioctl)
+
+#define AUTOFS_DEV_IOCTL_REQUESTER \
+ _IOWR(AUTOFS_IOCTL, \
+ AUTOFS_DEV_IOCTL_REQUESTER_CMD, struct autofs_dev_ioctl)
+
+#define AUTOFS_DEV_IOCTL_EXPIRE \
+ _IOWR(AUTOFS_IOCTL, \
+ AUTOFS_DEV_IOCTL_EXPIRE_CMD, struct autofs_dev_ioctl)
+
+#define AUTOFS_DEV_IOCTL_ASKUMOUNT \
+ _IOWR(AUTOFS_IOCTL, \
+ AUTOFS_DEV_IOCTL_ASKUMOUNT_CMD, struct autofs_dev_ioctl)
+
+#define AUTOFS_DEV_IOCTL_ISMOUNTPOINT \
+ _IOWR(AUTOFS_IOCTL, \
+ AUTOFS_DEV_IOCTL_ISMOUNTPOINT_CMD, struct autofs_dev_ioctl)
+
+#endif /* _LINUX_AUTO_DEV_IOCTL_H */
diff --git a/src/shared/linux/fanotify.h b/src/shared/linux/fanotify.h
new file mode 100644
index 0000000000..63531a6b4d
--- /dev/null
+++ b/src/shared/linux/fanotify.h
@@ -0,0 +1,98 @@
+#ifndef _LINUX_FANOTIFY_H
+#define _LINUX_FANOTIFY_H
+
+#include <linux/types.h>
+
+/* the following events that user-space can register for */
+#define FAN_ACCESS 0x00000001 /* File was accessed */
+#define FAN_MODIFY 0x00000002 /* File was modified */
+#define FAN_CLOSE_WRITE 0x00000008 /* Unwrittable file closed */
+#define FAN_CLOSE_NOWRITE 0x00000010 /* Writtable file closed */
+#define FAN_OPEN 0x00000020 /* File was opened */
+
+#define FAN_EVENT_ON_CHILD 0x08000000 /* interested in child events */
+
+/* FIXME currently Q's have no limit.... */
+#define FAN_Q_OVERFLOW 0x00004000 /* Event queued overflowed */
+
+#define FAN_OPEN_PERM 0x00010000 /* File open in perm check */
+#define FAN_ACCESS_PERM 0x00020000 /* File accessed in perm check */
+
+/* helper events */
+#define FAN_CLOSE (FAN_CLOSE_WRITE | FAN_CLOSE_NOWRITE) /* close */
+
+/* flags used for fanotify_init() */
+#define FAN_CLOEXEC 0x00000001
+#define FAN_NONBLOCK 0x00000002
+
+#define FAN_ALL_INIT_FLAGS (FAN_CLOEXEC | FAN_NONBLOCK)
+
+/* flags used for fanotify_modify_mark() */
+#define FAN_MARK_ADD 0x00000001
+#define FAN_MARK_REMOVE 0x00000002
+#define FAN_MARK_DONT_FOLLOW 0x00000004
+#define FAN_MARK_ONLYDIR 0x00000008
+#define FAN_MARK_MOUNT 0x00000010
+#define FAN_MARK_IGNORED_MASK 0x00000020
+#define FAN_MARK_IGNORED_SURV_MODIFY 0x00000040
+#define FAN_MARK_FLUSH 0x00000080
+
+#define FAN_ALL_MARK_FLAGS (FAN_MARK_ADD |\
+ FAN_MARK_REMOVE |\
+ FAN_MARK_DONT_FOLLOW |\
+ FAN_MARK_ONLYDIR |\
+ FAN_MARK_MOUNT |\
+ FAN_MARK_IGNORED_MASK |\
+ FAN_MARK_IGNORED_SURV_MODIFY)
+
+/*
+ * All of the events - we build the list by hand so that we can add flags in
+ * the future and not break backward compatibility. Apps will get only the
+ * events that they originally wanted. Be sure to add new events here!
+ */
+#define FAN_ALL_EVENTS (FAN_ACCESS |\
+ FAN_MODIFY |\
+ FAN_CLOSE |\
+ FAN_OPEN)
+
+/*
+ * All events which require a permission response from userspace
+ */
+#define FAN_ALL_PERM_EVENTS (FAN_OPEN_PERM |\
+ FAN_ACCESS_PERM)
+
+#define FAN_ALL_OUTGOING_EVENTS (FAN_ALL_EVENTS |\
+ FAN_ALL_PERM_EVENTS |\
+ FAN_Q_OVERFLOW)
+
+#define FANOTIFY_METADATA_VERSION 2
+
+struct fanotify_event_metadata {
+ __u32 event_len;
+ __u32 vers;
+ __u64 mask;
+ __s32 fd;
+ __s32 pid;
+} __attribute__ ((packed));
+
+struct fanotify_response {
+ __s32 fd;
+ __u32 response;
+} __attribute__ ((packed));
+
+/* Legit userspace responses to a _PERM event */
+#define FAN_ALLOW 0x01
+#define FAN_DENY 0x02
+
+/* Helper functions to deal with fanotify_event_metadata buffers */
+#define FAN_EVENT_METADATA_LEN (sizeof(struct fanotify_event_metadata))
+
+#define FAN_EVENT_NEXT(meta, len) ((len) -= (meta)->event_len, \
+ (struct fanotify_event_metadata*)(((char *)(meta)) + \
+ (meta)->event_len))
+
+#define FAN_EVENT_OK(meta, len) ((long)(len) >= (long)FAN_EVENT_METADATA_LEN && \
+ (long)(meta)->event_len >= (long)FAN_EVENT_METADATA_LEN && \
+ (long)(meta)->event_len <= (long)(len))
+
+#endif /* _LINUX_FANOTIFY_H */
diff --git a/src/shared/linux/seccomp-bpf.h b/src/shared/linux/seccomp-bpf.h
new file mode 100644
index 0000000000..1e3d136739
--- /dev/null
+++ b/src/shared/linux/seccomp-bpf.h
@@ -0,0 +1,76 @@
+/*
+ * seccomp example for x86 (32-bit and 64-bit) with BPF macros
+ *
+ * Copyright (c) 2012 The Chromium OS Authors <chromium-os-dev@chromium.org>
+ * Authors:
+ * Will Drewry <wad@chromium.org>
+ * Kees Cook <keescook@chromium.org>
+ *
+ * The code may be used by anyone for any purpose, and can serve as a
+ * starting point for developing applications using mode 2 seccomp.
+ */
+#ifndef _SECCOMP_BPF_H_
+#define _SECCOMP_BPF_H_
+
+#include <stdio.h>
+#include <stddef.h>
+#include <stdlib.h>
+#include <errno.h>
+#include <signal.h>
+#include <string.h>
+#include <unistd.h>
+
+#include <sys/prctl.h>
+
+#include <linux/unistd.h>
+#include <linux/audit.h>
+#include <linux/filter.h>
+#include <linux/seccomp.h>
+
+#ifndef SECCOMP_MODE_FILTER
+# define SECCOMP_MODE_FILTER 2 /* uses user-supplied filter. */
+# define SECCOMP_RET_KILL 0x00000000U /* kill the task immediately */
+# define SECCOMP_RET_TRAP 0x00030000U /* disallow and force a SIGSYS */
+# define SECCOMP_RET_ALLOW 0x7fff0000U /* allow */
+struct seccomp_data {
+ int nr;
+ __u32 arch;
+ __u64 instruction_pointer;
+ __u64 args[6];
+};
+#endif
+#ifndef SYS_SECCOMP
+# define SYS_SECCOMP 1
+#endif
+
+#define syscall_nr (offsetof(struct seccomp_data, nr))
+#define arch_nr (offsetof(struct seccomp_data, arch))
+
+#if defined(__i386__)
+# define REG_SYSCALL REG_EAX
+# define ARCH_NR AUDIT_ARCH_I386
+#elif defined(__x86_64__)
+# define REG_SYSCALL REG_RAX
+# define ARCH_NR AUDIT_ARCH_X86_64
+#else
+# warning "Platform does not support seccomp filter yet"
+# define REG_SYSCALL 0
+# define ARCH_NR 0
+#endif
+
+#define VALIDATE_ARCHITECTURE \
+ BPF_STMT(BPF_LD+BPF_W+BPF_ABS, arch_nr), \
+ BPF_JUMP(BPF_JMP+BPF_JEQ+BPF_K, ARCH_NR, 1, 0), \
+ BPF_STMT(BPF_RET+BPF_K, SECCOMP_RET_KILL)
+
+#define EXAMINE_SYSCALL \
+ BPF_STMT(BPF_LD+BPF_W+BPF_ABS, syscall_nr)
+
+#define ALLOW_SYSCALL(name) \
+ BPF_JUMP(BPF_JMP+BPF_JEQ+BPF_K, __NR_##name, 0, 1), \
+ BPF_STMT(BPF_RET+BPF_K, SECCOMP_RET_ALLOW)
+
+#define _KILL_PROCESS \
+ BPF_STMT(BPF_RET+BPF_K, SECCOMP_RET_KILL)
+
+#endif /* _SECCOMP_BPF_H_ */
diff --git a/src/shared/linux/seccomp.h b/src/shared/linux/seccomp.h
new file mode 100644
index 0000000000..9c03683fa0
--- /dev/null
+++ b/src/shared/linux/seccomp.h
@@ -0,0 +1,47 @@
+#ifndef _LINUX_SECCOMP_H
+#define _LINUX_SECCOMP_H
+
+
+#include <linux/types.h>
+
+
+/* Valid values for seccomp.mode and prctl(PR_SET_SECCOMP, <mode>) */
+#define SECCOMP_MODE_DISABLED 0 /* seccomp is not in use. */
+#define SECCOMP_MODE_STRICT 1 /* uses hard-coded filter. */
+#define SECCOMP_MODE_FILTER 2 /* uses user-supplied filter. */
+
+/*
+ * All BPF programs must return a 32-bit value.
+ * The bottom 16-bits are for optional return data.
+ * The upper 16-bits are ordered from least permissive values to most.
+ *
+ * The ordering ensures that a min_t() over composed return values always
+ * selects the least permissive choice.
+ */
+#define SECCOMP_RET_KILL 0x00000000U /* kill the task immediately */
+#define SECCOMP_RET_TRAP 0x00030000U /* disallow and force a SIGSYS */
+#define SECCOMP_RET_ERRNO 0x00050000U /* returns an errno */
+#define SECCOMP_RET_TRACE 0x7ff00000U /* pass to a tracer or disallow */
+#define SECCOMP_RET_ALLOW 0x7fff0000U /* allow */
+
+/* Masks for the return value sections. */
+#define SECCOMP_RET_ACTION 0x7fff0000U
+#define SECCOMP_RET_DATA 0x0000ffffU
+
+/**
+ * struct seccomp_data - the format the BPF program executes over.
+ * @nr: the system call number
+ * @arch: indicates system call convention as an AUDIT_ARCH_* value
+ * as defined in <linux/audit.h>.
+ * @instruction_pointer: at the time of the system call.
+ * @args: up to 6 system call arguments always stored as 64-bit values
+ * regardless of the architecture.
+ */
+struct seccomp_data {
+ int nr;
+ __u32 arch;
+ __u64 instruction_pointer;
+ __u64 args[6];
+};
+
+#endif /* _LINUX_SECCOMP_H */
diff --git a/src/shared/list.h b/src/shared/list.h
new file mode 100644
index 0000000000..47f275a019
--- /dev/null
+++ b/src/shared/list.h
@@ -0,0 +1,125 @@
+/*-*- Mode: C; c-basic-offset: 8; indent-tabs-mode: nil -*-*/
+
+#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/>.
+***/
+
+/* The head of the linked list. Use this in the structure that shall
+ * contain the head of the linked list */
+#define LIST_HEAD(t,name) \
+ t *name
+
+/* The pointers in the linked list's items. Use this in the item structure */
+#define LIST_FIELDS(t,name) \
+ t *name##_next, *name##_prev
+
+/* Initialize the list's head */
+#define LIST_HEAD_INIT(t,head) \
+ do { \
+ (head) = NULL; } \
+ while(false)
+
+/* Initialize a list item */
+#define LIST_INIT(t,name,item) \
+ do { \
+ t *_item = (item); \
+ assert(_item); \
+ _item->name##_prev = _item->name##_next = NULL; \
+ } while(false)
+
+/* Prepend an item to the list */
+#define LIST_PREPEND(t,name,head,item) \
+ do { \
+ t **_head = &(head), *_item = (item); \
+ assert(_item); \
+ if ((_item->name##_next = *_head)) \
+ _item->name##_next->name##_prev = _item; \
+ _item->name##_prev = NULL; \
+ *_head = _item; \
+ } while(false)
+
+/* Remove an item from the list */
+#define LIST_REMOVE(t,name,head,item) \
+ do { \
+ t **_head = &(head), *_item = (item); \
+ assert(_item); \
+ if (_item->name##_next) \
+ _item->name##_next->name##_prev = _item->name##_prev; \
+ if (_item->name##_prev) \
+ _item->name##_prev->name##_next = _item->name##_next; \
+ else { \
+ assert(*_head == _item); \
+ *_head = _item->name##_next; \
+ } \
+ _item->name##_next = _item->name##_prev = NULL; \
+ } while(false)
+
+/* Find the head of the list */
+#define LIST_FIND_HEAD(t,name,item,head) \
+ do { \
+ t *_item = (item); \
+ assert(_item); \
+ while (_item->name##_prev) \
+ _item = _item->name##_prev; \
+ (head) = _item; \
+ } while (false)
+
+/* Find the head of the list */
+#define LIST_FIND_TAIL(t,name,item,tail) \
+ do { \
+ t *_item = (item); \
+ assert(_item); \
+ while (_item->name##_next) \
+ _item = _item->name##_next; \
+ (tail) = _item; \
+ } while (false)
+
+/* Insert an item after another one (a = where, b = what) */
+#define LIST_INSERT_AFTER(t,name,head,a,b) \
+ do { \
+ t **_head = &(head), *_a = (a), *_b = (b); \
+ assert(_b); \
+ if (!_a) { \
+ if ((_b->name##_next = *_head)) \
+ _b->name##_next->name##_prev = _b; \
+ _b->name##_prev = NULL; \
+ *_head = _b; \
+ } else { \
+ if ((_b->name##_next = _a->name##_next)) \
+ _b->name##_next->name##_prev = _b; \
+ _b->name##_prev = _a; \
+ _a->name##_next = _b; \
+ } \
+ } while(false)
+
+#define LIST_JUST_US(name,item) \
+ (!(item)->name##_prev && !(item)->name##_next) \
+
+#define LIST_FOREACH(name,i,head) \
+ for ((i) = (head); (i); (i) = (i)->name##_next)
+
+#define LIST_FOREACH_SAFE(name,i,n,head) \
+ for ((i) = (head); (i) && (((n) = (i)->name##_next), 1); (i) = (n))
+
+#define LIST_FOREACH_BEFORE(name,i,p) \
+ for ((i) = (p)->name##_prev; (i); (i) = (i)->name##_prev)
+
+#define LIST_FOREACH_AFTER(name,i,p) \
+ for ((i) = (p)->name##_next; (i); (i) = (i)->name##_next)
diff --git a/src/shared/log.c b/src/shared/log.c
new file mode 100644
index 0000000000..27317f7ed3
--- /dev/null
+++ b/src/shared/log.c
@@ -0,0 +1,927 @@
+/*-*- Mode: C; c-basic-offset: 8; indent-tabs-mode: nil -*-*/
+
+/***
+ This file is part 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 <stdarg.h>
+#include <stdio.h>
+#include <errno.h>
+#include <unistd.h>
+#include <fcntl.h>
+#include <sys/socket.h>
+#include <sys/un.h>
+#include <stddef.h>
+#include <printf.h>
+
+#include "log.h"
+#include "util.h"
+#include "missing.h"
+#include "macro.h"
+#include "socket-util.h"
+
+#define SNDBUF_SIZE (8*1024*1024)
+
+static LogTarget log_target = LOG_TARGET_CONSOLE;
+static int log_max_level = LOG_INFO;
+static int log_facility = LOG_DAEMON;
+
+static int console_fd = STDERR_FILENO;
+static int syslog_fd = -1;
+static int kmsg_fd = -1;
+static int journal_fd = -1;
+
+static bool syslog_is_stream = false;
+
+static bool show_color = false;
+static bool show_location = false;
+
+/* Akin to glibc's __abort_msg; which is private and we hence cannot
+ * use here. */
+static char *log_abort_msg = NULL;
+
+void log_close_console(void) {
+
+ if (console_fd < 0)
+ return;
+
+ if (getpid() == 1) {
+ if (console_fd >= 3)
+ close_nointr_nofail(console_fd);
+
+ console_fd = -1;
+ }
+}
+
+static int log_open_console(void) {
+
+ if (console_fd >= 0)
+ return 0;
+
+ if (getpid() == 1) {
+ console_fd = open_terminal("/dev/console", O_WRONLY|O_NOCTTY|O_CLOEXEC);
+ if (console_fd < 0)
+ return console_fd;
+ } else
+ console_fd = STDERR_FILENO;
+
+ return 0;
+}
+
+void log_close_kmsg(void) {
+
+ if (kmsg_fd < 0)
+ return;
+
+ close_nointr_nofail(kmsg_fd);
+ kmsg_fd = -1;
+}
+
+static int log_open_kmsg(void) {
+
+ if (kmsg_fd >= 0)
+ return 0;
+
+ kmsg_fd = open("/dev/kmsg", O_WRONLY|O_NOCTTY|O_CLOEXEC);
+ if (kmsg_fd < 0)
+ return -errno;
+
+ return 0;
+}
+
+void log_close_syslog(void) {
+
+ if (syslog_fd < 0)
+ return;
+
+ close_nointr_nofail(syslog_fd);
+ syslog_fd = -1;
+}
+
+static int create_log_socket(int type) {
+ int fd;
+
+ /* All output to the syslog/journal fds we do asynchronously,
+ * and if the buffers are full we just drop the messages */
+
+ fd = socket(AF_UNIX, type|SOCK_CLOEXEC|SOCK_NONBLOCK, 0);
+ if (fd < 0)
+ return -errno;
+
+ fd_inc_sndbuf(fd, SNDBUF_SIZE);
+
+ return fd;
+}
+
+static int log_open_syslog(void) {
+ int r;
+ union sockaddr_union sa = {
+ .un.sun_family = AF_UNIX,
+ .un.sun_path = "/dev/log",
+ };
+
+ if (syslog_fd >= 0)
+ return 0;
+
+ syslog_fd = create_log_socket(SOCK_DGRAM);
+ if (syslog_fd < 0) {
+ r = syslog_fd;
+ goto fail;
+ }
+
+ if (connect(syslog_fd, &sa.sa, offsetof(struct sockaddr_un, sun_path) + strlen(sa.un.sun_path)) < 0) {
+ close_nointr_nofail(syslog_fd);
+
+ /* Some legacy syslog systems still use stream
+ * sockets. They really shouldn't. But what can we
+ * do... */
+ syslog_fd = create_log_socket(SOCK_STREAM);
+ if (syslog_fd < 0) {
+ r = syslog_fd;
+ goto fail;
+ }
+
+ if (connect(syslog_fd, &sa.sa, offsetof(struct sockaddr_un, sun_path) + strlen(sa.un.sun_path)) < 0) {
+ r = -errno;
+ goto fail;
+ }
+
+ syslog_is_stream = true;
+ } else
+ syslog_is_stream = false;
+
+ return 0;
+
+fail:
+ log_close_syslog();
+ return r;
+}
+
+void log_close_journal(void) {
+
+ if (journal_fd < 0)
+ return;
+
+ close_nointr_nofail(journal_fd);
+ journal_fd = -1;
+}
+
+static int log_open_journal(void) {
+ union sockaddr_union sa = {
+ .un.sun_family = AF_UNIX,
+ .un.sun_path = "/run/systemd/journal/socket",
+ };
+ int r;
+
+ if (journal_fd >= 0)
+ return 0;
+
+ journal_fd = create_log_socket(SOCK_DGRAM);
+ if (journal_fd < 0) {
+ r = journal_fd;
+ goto fail;
+ }
+
+ if (connect(journal_fd, &sa.sa, offsetof(struct sockaddr_un, sun_path) + strlen(sa.un.sun_path)) < 0) {
+ r = -errno;
+ goto fail;
+ }
+
+ return 0;
+
+fail:
+ log_close_journal();
+ return r;
+}
+
+int log_open(void) {
+ int r;
+
+ /* If we don't use the console we close it here, to not get
+ * killed by SAK. If we don't use syslog we close it here so
+ * that we are not confused by somebody deleting the socket in
+ * the fs. If we don't use /dev/kmsg we still keep it open,
+ * because there is no reason to close it. */
+
+ if (log_target == LOG_TARGET_NULL) {
+ log_close_journal();
+ log_close_syslog();
+ log_close_console();
+ return 0;
+ }
+
+ if ((log_target != LOG_TARGET_AUTO && log_target != LOG_TARGET_SAFE) ||
+ getpid() == 1 ||
+ isatty(STDERR_FILENO) <= 0) {
+
+ if (log_target == LOG_TARGET_AUTO ||
+ log_target == LOG_TARGET_JOURNAL_OR_KMSG ||
+ log_target == LOG_TARGET_JOURNAL) {
+ r = log_open_journal();
+ if (r >= 0) {
+ log_close_syslog();
+ log_close_console();
+ return r;
+ }
+ }
+
+ if (log_target == LOG_TARGET_SYSLOG_OR_KMSG ||
+ log_target == LOG_TARGET_SYSLOG) {
+ r = log_open_syslog();
+ if (r >= 0) {
+ log_close_journal();
+ log_close_console();
+ return r;
+ }
+ }
+
+ if (log_target == LOG_TARGET_AUTO ||
+ log_target == LOG_TARGET_SAFE ||
+ log_target == LOG_TARGET_JOURNAL_OR_KMSG ||
+ log_target == LOG_TARGET_SYSLOG_OR_KMSG ||
+ log_target == LOG_TARGET_KMSG) {
+ r = log_open_kmsg();
+ if (r >= 0) {
+ log_close_journal();
+ log_close_syslog();
+ log_close_console();
+ return r;
+ }
+ }
+ }
+
+ log_close_journal();
+ log_close_syslog();
+
+ /* Get the real /dev/console if we are PID=1, hence reopen */
+ log_close_console();
+ return log_open_console();
+}
+
+void log_set_target(LogTarget target) {
+ assert(target >= 0);
+ assert(target < _LOG_TARGET_MAX);
+
+ log_target = target;
+}
+
+void log_close(void) {
+ log_close_journal();
+ log_close_syslog();
+ log_close_kmsg();
+ log_close_console();
+}
+
+void log_forget_fds(void) {
+ console_fd = kmsg_fd = syslog_fd = journal_fd = -1;
+}
+
+void log_set_max_level(int level) {
+ assert((level & LOG_PRIMASK) == level);
+
+ log_max_level = level;
+}
+
+void log_set_facility(int facility) {
+ log_facility = facility;
+}
+
+static int write_to_console(
+ int level,
+ const char*file,
+ int line,
+ const char *func,
+ const char *object_name,
+ const char *object,
+ const char *buffer) {
+
+ char location[64];
+ struct iovec iovec[5] = {};
+ unsigned n = 0;
+ bool highlight;
+
+ if (console_fd < 0)
+ return 0;
+
+ highlight = LOG_PRI(level) <= LOG_ERR && show_color;
+
+ if (show_location) {
+ snprintf(location, sizeof(location), "(%s:%u) ", file, line);
+ char_array_0(location);
+ IOVEC_SET_STRING(iovec[n++], location);
+ }
+
+ if (highlight)
+ IOVEC_SET_STRING(iovec[n++], ANSI_HIGHLIGHT_RED_ON);
+ IOVEC_SET_STRING(iovec[n++], buffer);
+ if (highlight)
+ IOVEC_SET_STRING(iovec[n++], ANSI_HIGHLIGHT_OFF);
+ IOVEC_SET_STRING(iovec[n++], "\n");
+
+ if (writev(console_fd, iovec, n) < 0)
+ return -errno;
+
+ return 1;
+}
+
+static int write_to_syslog(
+ int level,
+ const char*file,
+ int line,
+ const char *func,
+ const char *object_name,
+ const char *object,
+ const char *buffer) {
+
+ char header_priority[16], header_time[64], header_pid[16];
+ struct iovec iovec[5] = {};
+ struct msghdr msghdr = {
+ .msg_iov = iovec,
+ .msg_iovlen = ELEMENTSOF(iovec),
+ };
+ time_t t;
+ struct tm *tm;
+
+ if (syslog_fd < 0)
+ return 0;
+
+ snprintf(header_priority, sizeof(header_priority), "<%i>", level);
+ char_array_0(header_priority);
+
+ t = (time_t) (now(CLOCK_REALTIME) / USEC_PER_SEC);
+ tm = localtime(&t);
+ if (!tm)
+ return -EINVAL;
+
+ if (strftime(header_time, sizeof(header_time), "%h %e %T ", tm) <= 0)
+ return -EINVAL;
+
+ snprintf(header_pid, sizeof(header_pid), "[%lu]: ", (unsigned long) getpid());
+ char_array_0(header_pid);
+
+ IOVEC_SET_STRING(iovec[0], header_priority);
+ IOVEC_SET_STRING(iovec[1], header_time);
+ IOVEC_SET_STRING(iovec[2], program_invocation_short_name);
+ IOVEC_SET_STRING(iovec[3], header_pid);
+ IOVEC_SET_STRING(iovec[4], buffer);
+
+ /* When using syslog via SOCK_STREAM separate the messages by NUL chars */
+ if (syslog_is_stream)
+ iovec[4].iov_len++;
+
+ for (;;) {
+ ssize_t n;
+
+ n = sendmsg(syslog_fd, &msghdr, MSG_NOSIGNAL);
+ if (n < 0)
+ return -errno;
+
+ if (!syslog_is_stream ||
+ (size_t) n >= IOVEC_TOTAL_SIZE(iovec, ELEMENTSOF(iovec)))
+ break;
+
+ IOVEC_INCREMENT(iovec, ELEMENTSOF(iovec), n);
+ }
+
+ return 1;
+}
+
+static int write_to_kmsg(
+ int level,
+ const char*file,
+ int line,
+ const char *func,
+ const char *object_name,
+ const char *object,
+ const char *buffer) {
+
+ char header_priority[16], header_pid[16];
+ struct iovec iovec[5] = {};
+
+ if (kmsg_fd < 0)
+ return 0;
+
+ snprintf(header_priority, sizeof(header_priority), "<%i>", level);
+ char_array_0(header_priority);
+
+ snprintf(header_pid, sizeof(header_pid), "[%lu]: ", (unsigned long) getpid());
+ char_array_0(header_pid);
+
+ IOVEC_SET_STRING(iovec[0], header_priority);
+ IOVEC_SET_STRING(iovec[1], program_invocation_short_name);
+ IOVEC_SET_STRING(iovec[2], header_pid);
+ IOVEC_SET_STRING(iovec[3], buffer);
+ IOVEC_SET_STRING(iovec[4], "\n");
+
+ if (writev(kmsg_fd, iovec, ELEMENTSOF(iovec)) < 0)
+ return -errno;
+
+ return 1;
+}
+
+static int log_do_header(char *header, size_t size,
+ int level,
+ const char *file, int line, const char *func,
+ const char *object_name, const char *object) {
+ snprintf(header, size,
+ "PRIORITY=%i\n"
+ "SYSLOG_FACILITY=%i\n"
+ "%s%.*s%s"
+ "%s%.*i%s"
+ "%s%.*s%s"
+ "%s%.*s%s"
+ "SYSLOG_IDENTIFIER=%s\n",
+ LOG_PRI(level),
+ LOG_FAC(level),
+ file ? "CODE_FILE=" : "",
+ file ? LINE_MAX : 0, file, /* %.0s means no output */
+ file ? "\n" : "",
+ line ? "CODE_LINE=" : "",
+ line ? 1 : 0, line, /* %.0d means no output too, special case for 0 */
+ line ? "\n" : "",
+ func ? "CODE_FUNCTION=" : "",
+ func ? LINE_MAX : 0, func,
+ func ? "\n" : "",
+ object ? object_name : "",
+ object ? LINE_MAX : 0, object, /* %.0s means no output */
+ object ? "\n" : "",
+ program_invocation_short_name);
+ header[size - 1] = '\0';
+ return 0;
+}
+
+static int write_to_journal(
+ int level,
+ const char*file,
+ int line,
+ const char *func,
+ const char *object_name,
+ const char *object,
+ const char *buffer) {
+
+ char header[LINE_MAX];
+ struct iovec iovec[4] = {};
+ struct msghdr mh = {};
+
+ if (journal_fd < 0)
+ return 0;
+
+ log_do_header(header, sizeof(header), level,
+ file, line, func, object_name, object);
+
+ IOVEC_SET_STRING(iovec[0], header);
+ IOVEC_SET_STRING(iovec[1], "MESSAGE=");
+ IOVEC_SET_STRING(iovec[2], buffer);
+ IOVEC_SET_STRING(iovec[3], "\n");
+
+ mh.msg_iov = iovec;
+ mh.msg_iovlen = ELEMENTSOF(iovec);
+
+ if (sendmsg(journal_fd, &mh, MSG_NOSIGNAL) < 0)
+ return -errno;
+
+ return 1;
+}
+
+static int log_dispatch(
+ int level,
+ const char*file,
+ int line,
+ const char *func,
+ const char *object_name,
+ const char *object,
+ char *buffer) {
+
+ int r = 0;
+
+ if (log_target == LOG_TARGET_NULL)
+ return 0;
+
+ /* Patch in LOG_DAEMON facility if necessary */
+ if ((level & LOG_FACMASK) == 0)
+ level = log_facility | LOG_PRI(level);
+
+ do {
+ char *e;
+ int k = 0;
+
+ buffer += strspn(buffer, NEWLINE);
+
+ if (buffer[0] == 0)
+ break;
+
+ if ((e = strpbrk(buffer, NEWLINE)))
+ *(e++) = 0;
+
+ if (log_target == LOG_TARGET_AUTO ||
+ log_target == LOG_TARGET_JOURNAL_OR_KMSG ||
+ log_target == LOG_TARGET_JOURNAL) {
+
+ k = write_to_journal(level, file, line, func,
+ object_name, object, buffer);
+ if (k < 0) {
+ if (k != -EAGAIN)
+ log_close_journal();
+ log_open_kmsg();
+ } else if (k > 0)
+ r++;
+ }
+
+ if (log_target == LOG_TARGET_SYSLOG_OR_KMSG ||
+ log_target == LOG_TARGET_SYSLOG) {
+
+ k = write_to_syslog(level, file, line, func,
+ object_name, object, buffer);
+ if (k < 0) {
+ if (k != -EAGAIN)
+ log_close_syslog();
+ log_open_kmsg();
+ } else if (k > 0)
+ r++;
+ }
+
+ if (k <= 0 &&
+ (log_target == LOG_TARGET_AUTO ||
+ log_target == LOG_TARGET_SAFE ||
+ log_target == LOG_TARGET_SYSLOG_OR_KMSG ||
+ log_target == LOG_TARGET_JOURNAL_OR_KMSG ||
+ log_target == LOG_TARGET_KMSG)) {
+
+ k = write_to_kmsg(level, file, line, func,
+ object_name, object, buffer);
+ if (k < 0) {
+ log_close_kmsg();
+ log_open_console();
+ } else if (k > 0)
+ r++;
+ }
+
+ if (k <= 0) {
+ k = write_to_console(level, file, line, func,
+ object_name, object, buffer);
+ if (k < 0)
+ return k;
+ }
+
+ buffer = e;
+ } while (buffer);
+
+ return r;
+}
+
+int log_dump_internal(
+ int level,
+ const char*file,
+ int line,
+ const char *func,
+ char *buffer) {
+
+ PROTECT_ERRNO;
+
+ /* This modifies the buffer... */
+
+ if (_likely_(LOG_PRI(level) > log_max_level))
+ return 0;
+
+ return log_dispatch(level, file, line, func, NULL, NULL, buffer);
+}
+
+int log_metav(
+ int level,
+ const char*file,
+ int line,
+ const char *func,
+ const char *format,
+ va_list ap) {
+
+ PROTECT_ERRNO;
+ char buffer[LINE_MAX];
+
+ if (_likely_(LOG_PRI(level) > log_max_level))
+ return 0;
+
+ vsnprintf(buffer, sizeof(buffer), format, ap);
+ char_array_0(buffer);
+
+ return log_dispatch(level, file, line, func, NULL, NULL, buffer);
+}
+
+int log_meta(
+ int level,
+ const char*file,
+ int line,
+ const char *func,
+ const char *format, ...) {
+
+ int r;
+ va_list ap;
+
+ va_start(ap, format);
+ r = log_metav(level, file, line, func, format, ap);
+ va_end(ap);
+
+ return r;
+}
+
+int log_metav_object(
+ int level,
+ const char*file,
+ int line,
+ const char *func,
+ const char *object_name,
+ const char *object,
+ const char *format,
+ va_list ap) {
+
+ PROTECT_ERRNO;
+ char buffer[LINE_MAX];
+
+ if (_likely_(LOG_PRI(level) > log_max_level))
+ return 0;
+
+ vsnprintf(buffer, sizeof(buffer), format, ap);
+ char_array_0(buffer);
+
+ return log_dispatch(level, file, line, func,
+ object_name, object, buffer);
+}
+
+int log_meta_object(
+ int level,
+ const char*file,
+ int line,
+ const char *func,
+ const char *object_name,
+ const char *object,
+ const char *format, ...) {
+
+ int r;
+ va_list ap;
+
+ va_start(ap, format);
+ r = log_metav_object(level, file, line, func,
+ object_name, object, format, ap);
+ va_end(ap);
+
+ return r;
+}
+
+#pragma GCC diagnostic push
+#pragma GCC diagnostic ignored "-Wformat-nonliteral"
+_noreturn_ static void log_assert(const char *text, const char *file, int line, const char *func, const char *format) {
+ static char buffer[LINE_MAX];
+
+ snprintf(buffer, sizeof(buffer), format, text, file, line, func);
+
+ char_array_0(buffer);
+ log_abort_msg = buffer;
+
+ log_dispatch(LOG_CRIT, file, line, func, NULL, NULL, buffer);
+ abort();
+}
+#pragma GCC diagnostic pop
+
+_noreturn_ void log_assert_failed(const char *text, const char *file, int line, const char *func) {
+ log_assert(text, file, line, func, "Assertion '%s' failed at %s:%u, function %s(). Aborting.");
+}
+
+_noreturn_ void log_assert_failed_unreachable(const char *text, const char *file, int line, const char *func) {
+ log_assert(text, file, line, func, "Code should not be reached '%s' at %s:%u, function %s(). Aborting.");
+}
+
+int log_oom_internal(const char *file, int line, const char *func) {
+ log_meta(LOG_ERR, file, line, func, "Out of memory.");
+ return -ENOMEM;
+}
+
+int log_struct_internal(
+ int level,
+ const char *file,
+ int line,
+ const char *func,
+ const char *format, ...) {
+
+ PROTECT_ERRNO;
+ va_list ap;
+ int r;
+
+ if (_likely_(LOG_PRI(level) > log_max_level))
+ return 0;
+
+ if (log_target == LOG_TARGET_NULL)
+ return 0;
+
+ if ((level & LOG_FACMASK) == 0)
+ level = log_facility | LOG_PRI(level);
+
+ if ((log_target == LOG_TARGET_AUTO ||
+ log_target == LOG_TARGET_JOURNAL_OR_KMSG ||
+ log_target == LOG_TARGET_JOURNAL) &&
+ journal_fd >= 0) {
+
+ char header[LINE_MAX];
+ struct iovec iovec[17] = {};
+ unsigned n = 0, i;
+ struct msghdr mh = {
+ .msg_iov = iovec,
+ };
+ static const char nl = '\n';
+
+ /* If the journal is available do structured logging */
+ log_do_header(header, sizeof(header), level,
+ file, line, func, NULL, NULL);
+ IOVEC_SET_STRING(iovec[n++], header);
+
+ va_start(ap, format);
+ while (format && n + 1 < ELEMENTSOF(iovec)) {
+ char *buf;
+ va_list aq;
+
+ /* We need to copy the va_list structure,
+ * since vasprintf() leaves it afterwards at
+ * an undefined location */
+
+ va_copy(aq, ap);
+ if (vasprintf(&buf, format, aq) < 0) {
+ va_end(aq);
+ r = -ENOMEM;
+ goto finish;
+ }
+ va_end(aq);
+
+ /* Now, jump enough ahead, so that we point to
+ * the next format string */
+ VA_FORMAT_ADVANCE(format, ap);
+
+ IOVEC_SET_STRING(iovec[n++], buf);
+
+ iovec[n].iov_base = (char*) &nl;
+ iovec[n].iov_len = 1;
+ n++;
+
+ format = va_arg(ap, char *);
+ }
+
+ mh.msg_iovlen = n;
+
+ if (sendmsg(journal_fd, &mh, MSG_NOSIGNAL) < 0)
+ r = -errno;
+ else
+ r = 1;
+
+ finish:
+ va_end(ap);
+ for (i = 1; i < n; i += 2)
+ free(iovec[i].iov_base);
+
+ } else {
+ char buf[LINE_MAX];
+ bool found = false;
+
+ /* Fallback if journal logging is not available */
+
+ va_start(ap, format);
+ while (format) {
+ va_list aq;
+
+ va_copy(aq, ap);
+ vsnprintf(buf, sizeof(buf), format, aq);
+ va_end(aq);
+ char_array_0(buf);
+
+ if (startswith(buf, "MESSAGE=")) {
+ found = true;
+ break;
+ }
+
+ VA_FORMAT_ADVANCE(format, ap);
+
+ format = va_arg(ap, char *);
+ }
+ va_end(ap);
+
+ if (found)
+ r = log_dispatch(level, file, line, func,
+ NULL, NULL, buf + 8);
+ else
+ r = -EINVAL;
+ }
+
+ return r;
+}
+
+int log_set_target_from_string(const char *e) {
+ LogTarget t;
+
+ t = log_target_from_string(e);
+ if (t < 0)
+ return -EINVAL;
+
+ log_set_target(t);
+ return 0;
+}
+
+int log_set_max_level_from_string(const char *e) {
+ int t;
+
+ t = log_level_from_string(e);
+ if (t < 0)
+ return t;
+
+ log_set_max_level(t);
+ return 0;
+}
+
+void log_parse_environment(void) {
+ const char *e;
+
+ e = secure_getenv("SYSTEMD_LOG_TARGET");
+ if (e && log_set_target_from_string(e) < 0)
+ log_warning("Failed to parse log target %s. Ignoring.", e);
+
+ e = secure_getenv("SYSTEMD_LOG_LEVEL");
+ if (e && log_set_max_level_from_string(e) < 0)
+ log_warning("Failed to parse log level %s. Ignoring.", e);
+
+ e = secure_getenv("SYSTEMD_LOG_COLOR");
+ if (e && log_show_color_from_string(e) < 0)
+ log_warning("Failed to parse bool %s. Ignoring.", e);
+
+ e = secure_getenv("SYSTEMD_LOG_LOCATION");
+ if (e && log_show_location_from_string(e) < 0)
+ log_warning("Failed to parse bool %s. Ignoring.", e);
+}
+
+LogTarget log_get_target(void) {
+ return log_target;
+}
+
+int log_get_max_level(void) {
+ return log_max_level;
+}
+
+void log_show_color(bool b) {
+ show_color = b;
+}
+
+void log_show_location(bool b) {
+ show_location = b;
+}
+
+int log_show_color_from_string(const char *e) {
+ int t;
+
+ t = parse_boolean(e);
+ if (t < 0)
+ return t;
+
+ log_show_color(t);
+ return 0;
+}
+
+int log_show_location_from_string(const char *e) {
+ int t;
+
+ t = parse_boolean(e);
+ if (t < 0)
+ return t;
+
+ log_show_location(t);
+ return 0;
+}
+
+bool log_on_console(void) {
+ if (log_target == LOG_TARGET_CONSOLE)
+ return true;
+
+ return syslog_fd < 0 && kmsg_fd < 0 && journal_fd < 0;
+}
+
+static const char *const log_target_table[] = {
+ [LOG_TARGET_CONSOLE] = "console",
+ [LOG_TARGET_KMSG] = "kmsg",
+ [LOG_TARGET_JOURNAL] = "journal",
+ [LOG_TARGET_JOURNAL_OR_KMSG] = "journal-or-kmsg",
+ [LOG_TARGET_SYSLOG] = "syslog",
+ [LOG_TARGET_SYSLOG_OR_KMSG] = "syslog-or-kmsg",
+ [LOG_TARGET_AUTO] = "auto",
+ [LOG_TARGET_SAFE] = "safe",
+ [LOG_TARGET_NULL] = "null"
+};
+
+DEFINE_STRING_TABLE_LOOKUP(log_target, LogTarget);
diff --git a/src/shared/log.h b/src/shared/log.h
new file mode 100644
index 0000000000..979f833d57
--- /dev/null
+++ b/src/shared/log.h
@@ -0,0 +1,159 @@
+/*-*- Mode: C; c-basic-offset: 8; indent-tabs-mode: nil -*-*/
+
+#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 <syslog.h>
+#include <stdbool.h>
+#include <stdarg.h>
+#include <errno.h>
+
+#include "macro.h"
+#include "sd-id128.h"
+
+typedef enum LogTarget{
+ LOG_TARGET_CONSOLE,
+ LOG_TARGET_KMSG,
+ LOG_TARGET_JOURNAL,
+ LOG_TARGET_JOURNAL_OR_KMSG,
+ LOG_TARGET_SYSLOG,
+ LOG_TARGET_SYSLOG_OR_KMSG,
+ LOG_TARGET_AUTO, /* console if stderr is tty, JOURNAL_OR_KMSG otherwise */
+ LOG_TARGET_SAFE, /* console if stderr is tty, KMSG otherwise */
+ LOG_TARGET_NULL,
+ _LOG_TARGET_MAX,
+ _LOG_TARGET_INVALID = -1
+} LogTarget;
+
+void log_set_target(LogTarget target);
+void log_set_max_level(int level);
+void log_set_facility(int facility);
+
+int log_set_target_from_string(const char *e);
+int log_set_max_level_from_string(const char *e);
+
+void log_show_color(bool b);
+void log_show_location(bool b);
+
+int log_show_color_from_string(const char *e);
+int log_show_location_from_string(const char *e);
+
+LogTarget log_get_target(void) _pure_;
+int log_get_max_level(void) _pure_;
+
+int log_open(void);
+void log_close(void);
+void log_forget_fds(void);
+
+void log_close_syslog(void);
+void log_close_journal(void);
+void log_close_kmsg(void);
+void log_close_console(void);
+
+void log_parse_environment(void);
+
+int log_meta(
+ int level,
+ const char*file,
+ int line,
+ const char *func,
+ const char *format, ...) _printf_attr_(5,6);
+
+int log_metav(
+ int level,
+ const char*file,
+ int line,
+ const char *func,
+ const char *format,
+ va_list ap) _printf_attr_(5,0);
+
+int log_meta_object(
+ int level,
+ const char*file,
+ int line,
+ const char *func,
+ const char *object_name,
+ const char *object,
+ const char *format, ...) _printf_attr_(7,8);
+
+int log_metav_object(
+ int level,
+ const char*file,
+ int line,
+ const char *func,
+ const char *object_name,
+ const char *object,
+ const char *format,
+ va_list ap) _printf_attr_(7,0);
+
+int log_struct_internal(
+ int level,
+ const char *file,
+ int line,
+ const char *func,
+ const char *format, ...) _printf_attr_(5,0) _sentinel_;
+
+int log_oom_internal(
+ const char *file,
+ int line,
+ const char *func);
+
+/* This modifies the buffer passed! */
+int log_dump_internal(
+ int level,
+ const char*file,
+ int line,
+ const char *func,
+ char *buffer);
+
+_noreturn_ void log_assert_failed(
+ const char *text,
+ const char *file,
+ int line,
+ const char *func);
+
+_noreturn_ void log_assert_failed_unreachable(
+ const char *text,
+ const char *file,
+ int line,
+ const char *func);
+
+#define log_full(level, ...) log_meta(level, __FILE__, __LINE__, __func__, __VA_ARGS__)
+
+#define log_debug(...) log_meta(LOG_DEBUG, __FILE__, __LINE__, __func__, __VA_ARGS__)
+#define log_info(...) log_meta(LOG_INFO, __FILE__, __LINE__, __func__, __VA_ARGS__)
+#define log_notice(...) log_meta(LOG_NOTICE, __FILE__, __LINE__, __func__, __VA_ARGS__)
+#define log_warning(...) log_meta(LOG_WARNING, __FILE__, __LINE__, __func__, __VA_ARGS__)
+#define log_error(...) log_meta(LOG_ERR, __FILE__, __LINE__, __func__, __VA_ARGS__)
+
+#define log_struct(level, ...) log_struct_internal(level, __FILE__, __LINE__, __func__, __VA_ARGS__)
+
+#define log_oom() log_oom_internal(__FILE__, __LINE__, __func__)
+
+/* This modifies the buffer passed! */
+#define log_dump(level, buffer) log_dump_internal(level, __FILE__, __LINE__, __func__, buffer)
+
+bool log_on_console(void) _pure_;
+
+const char *log_target_to_string(LogTarget target) _const_;
+LogTarget log_target_from_string(const char *s) _pure_;
+
+#define MESSAGE_ID(x) "MESSAGE_ID=" SD_ID128_FORMAT_STR, SD_ID128_FORMAT_VAL(x)
diff --git a/src/shared/logs-show.c b/src/shared/logs-show.c
new file mode 100644
index 0000000000..116dc8a36c
--- /dev/null
+++ b/src/shared/logs-show.c
@@ -0,0 +1,985 @@
+/*-*- Mode: C; c-basic-offset: 8; indent-tabs-mode: nil -*-*/
+
+/***
+ This file is part of systemd.
+
+ Copyright 2012 Lennart Poettering
+
+ systemd is free software; you can redistribute it and/or modify it
+ under the terms of the GNU Lesser General Public License as published by
+ the Free Software Foundation; either version 2.1 of the License, or
+ (at your option) any later version.
+
+ systemd is distributed in the hope that it will be useful, but
+ WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ Lesser General 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.h>
+#include <assert.h>
+#include <errno.h>
+#include <sys/poll.h>
+#include <string.h>
+
+#include "logs-show.h"
+#include "log.h"
+#include "util.h"
+#include "utf8.h"
+#include "hashmap.h"
+#include "journal-internal.h"
+
+#define PRINT_THRESHOLD 128
+#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;
+ void *buf;
+
+ assert(data);
+ assert(field);
+ assert(target);
+ assert(target_size);
+
+ fl = strlen(field);
+ if (length < fl)
+ return 0;
+
+ if (memcmp(data, field, fl))
+ return 0;
+
+ nl = length - fl;
+ buf = malloc(nl+1);
+ if (!buf)
+ return log_oom();
+
+ memcpy(buf, (const char*) data + fl, nl);
+ ((char*)buf)[nl] = 0;
+
+ free(*target);
+ *target = buf;
+ *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_THRESHOLD)
+ return false;
+
+ if (!utf8_is_printable_n(p, l))
+ return false;
+
+ return true;
+}
+
+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;
+ const char *color_on = "", *color_off = "";
+
+ assert(f);
+ assert(j);
+
+ sd_journal_set_data_threshold(j, flags & OUTPUT_SHOW_ALL ? 0 : PRINT_THRESHOLD);
+
+ SD_JOURNAL_FOREACH_DATA(j, data, length) {
+
+ 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 (!message)
+ 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) {
+ log_error("Failed to get monotonic: %s", strerror(-r));
+ return r;
+ }
+
+ 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;
+
+ r = -ENOENT;
+
+ if (realtime)
+ r = safe_atou64(realtime, &x);
+
+ if (r < 0)
+ r = sd_journal_get_realtime_usec(j, &x);
+
+ if (r < 0) {
+ log_error("Failed to get realtime: %s", strerror(-r));
+ return r;
+ }
+
+ t = (time_t) (x / USEC_PER_SEC);
+ if (strftime(buf, sizeof(buf), "%b %d %H:%M:%S", localtime_r(&t, &tm)) <= 0) {
+ log_error("Failed to format time.");
+ return r;
+ }
+
+ 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
+ fputc(' ', 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_COLOR) {
+ if (p <= LOG_ERR) {
+ color_on = ANSI_HIGHLIGHT_RED_ON;
+ color_off = ANSI_HIGHLIGHT_OFF;
+ } else if (p <= LOG_NOTICE) {
+ color_on = ANSI_HIGHLIGHT_ON;
+ color_off = ANSI_HIGHLIGHT_OFF;
+ }
+ }
+
+ if (flags & OUTPUT_SHOW_ALL)
+ fprintf(f, ": %s%.*s%s\n", color_on, (int) message_len, message, color_off);
+ else if (!utf8_is_printable_n(message, message_len)) {
+ char bytes[FORMAT_BYTES_MAX];
+ fprintf(f, ": [%s blob data]\n", format_bytes(bytes, sizeof(bytes), message_len));
+ } else if ((flags & OUTPUT_FULL_WIDTH) || (message_len + n + 1 < n_columns))
+ fprintf(f, ": %s%.*s%s\n", color_on, (int) message_len, message, color_off);
+ else if (n < n_columns && n_columns - n - 2 >= 3) {
+ _cleanup_free_ char *e;
+
+ e = ellipsize_mem(message, message_len, n_columns - n - 2, 90);
+
+ if (!e)
+ fprintf(f, ": %s%.*s%s\n", color_on, (int) message_len, message, color_off);
+ else
+ fprintf(f, ": %s%s%s\n", color_on, e, color_off);
+ } else
+ fputs("\n", f);
+
+ if (flags & OUTPUT_CATALOG)
+ print_catalog(f, j);
+
+ return 0;
+}
+
+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;
+ char ts[FORMAT_TIMESTAMP_MAX];
+ int r;
+
+ assert(f);
+ assert(j);
+
+ sd_journal_set_data_threshold(j, 0);
+
+ r = sd_journal_get_realtime_usec(j, &realtime);
+ if (r < 0) {
+ log_error("Failed to get realtime timestamp: %s", strerror(-r));
+ return r;
+ }
+
+ r = sd_journal_get_cursor(j, &cursor);
+ if (r < 0) {
+ log_error("Failed to get cursor: %s", strerror(-r));
+ return r;
+ }
+
+ fprintf(f, "%s [%s]\n",
+ format_timestamp(ts, sizeof(ts), realtime),
+ cursor);
+
+ SD_JOURNAL_FOREACH_DATA(j, data, length) {
+ if (!shall_print(data, length, flags)) {
+ const char *c;
+ char bytes[FORMAT_BYTES_MAX];
+
+ c = memchr(data, '=', length);
+ if (!c) {
+ log_error("Invalid field.");
+ return -EINVAL;
+ }
+
+ fprintf(f, "\t%.*s=[%s blob data]\n",
+ (int) (c - (const char*) data),
+ (const char*) data,
+ format_bytes(bytes, sizeof(bytes), length - (c - (const char *) data) - 1));
+ } else
+ fprintf(f, "\t%.*s\n", (int) length, (const char*) data);
+ }
+
+ 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) {
+ log_error("Failed to get realtime timestamp: %s", strerror(-r));
+ return r;
+ }
+
+ r = sd_journal_get_monotonic_usec(j, &monotonic, &boot_id);
+ if (r < 0) {
+ log_error("Failed to get monotonic timestamp: %s", strerror(-r));
+ return r;
+ }
+
+ r = sd_journal_get_cursor(j, &cursor);
+ if (r < 0) {
+ log_error("Failed to get cursor: %s", strerror(-r));
+ return r;
+ }
+
+ fprintf(f,
+ "__CURSOR=%s\n"
+ "__REALTIME_TIMESTAMP=%llu\n"
+ "__MONOTONIC_TIMESTAMP=%llu\n"
+ "_BOOT_ID=%s\n",
+ cursor,
+ (unsigned long long) realtime,
+ (unsigned long long) monotonic,
+ sd_id128_to_string(boot_id, sid));
+
+ SD_JOURNAL_FOREACH_DATA(j, data, length) {
+
+ /* 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;
+
+ if (!utf8_is_printable_n(data, length)) {
+ const char *c;
+ uint64_t le64;
+
+ c = memchr(data, '=', length);
+ if (!c) {
+ log_error("Invalid field.");
+ return -EINVAL;
+ }
+
+ fwrite(data, c - (const char*) data, 1, 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);
+ } else
+ fwrite(data, length, 1, f);
+
+ fputc('\n', f);
+ }
+
+ 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_n(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 < ' ')
+ fprintf(f, "\\u%04x", *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) {
+ log_error("Failed to get realtime timestamp: %s", strerror(-r));
+ return r;
+ }
+
+ r = sd_journal_get_monotonic_usec(j, &monotonic, &boot_id);
+ if (r < 0) {
+ log_error("Failed to get monotonic timestamp: %s", strerror(-r));
+ return r;
+ }
+
+ r = sd_journal_get_cursor(j, &cursor);
+ if (r < 0) {
+ log_error("Failed to get cursor: %s", strerror(-r));
+ return r;
+ }
+
+ if (mode == OUTPUT_JSON_PRETTY)
+ fprintf(f,
+ "{\n"
+ "\t\"__CURSOR\" : \"%s\",\n"
+ "\t\"__REALTIME_TIMESTAMP\" : \"%llu\",\n"
+ "\t\"__MONOTONIC_TIMESTAMP\" : \"%llu\",\n"
+ "\t\"_BOOT_ID\" : \"%s\"",
+ cursor,
+ (unsigned long long) realtime,
+ (unsigned long long) monotonic,
+ sd_id128_to_string(boot_id, sid));
+ else {
+ if (mode == OUTPUT_JSON_SSE)
+ fputs("data: ", f);
+
+ fprintf(f,
+ "{ \"__CURSOR\" : \"%s\", "
+ "\"__REALTIME_TIMESTAMP\" : \"%llu\", "
+ "\"__MONOTONIC_TIMESTAMP\" : \"%llu\", "
+ "\"_BOOT_ID\" : \"%s\"",
+ cursor,
+ (unsigned long long) realtime,
+ (unsigned long long) monotonic,
+ sd_id128_to_string(boot_id, sid));
+ }
+
+ h = hashmap_new(string_hash_func, string_compare_func);
+ if (!h)
+ return -ENOMEM;
+
+ /* First round, iterate through the entry and count how often each field appears */
+ SD_JOURNAL_FOREACH_DATA(j, data, length) {
+ 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 = -ENOMEM;
+ 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);
+ goto finish;
+ }
+ } else {
+ r = hashmap_update(h, n, UINT_TO_PTR(u + 1));
+ free(n);
+ if (r < 0)
+ goto finish;
+ }
+ }
+
+ 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 = -ENOMEM;
+ 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;
+
+ log_error("Failed to get data: %s", strerror(-r));
+ return r;
+ }
+
+ 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_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) {
+
+ 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);
+ return ret;
+}
+
+static int show_journal(FILE *f,
+ sd_journal *j,
+ OutputMode mode,
+ unsigned n_columns,
+ usec_t not_before,
+ unsigned how_many,
+ OutputFlags flags) {
+
+ 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)
+ goto finish;
+
+ r = sd_journal_previous_skip(j, how_many);
+ if (r < 0)
+ goto finish;
+
+ for (;;) {
+ for (;;) {
+ usec_t usec;
+
+ if (need_seek) {
+ r = sd_journal_next(j);
+ if (r < 0)
+ goto finish;
+ }
+
+ if (r == 0)
+ break;
+
+ need_seek = true;
+
+ if (not_before > 0) {
+ r = sd_journal_get_monotonic_usec(j, &usec, NULL);
+
+ /* -ESTALE is returned if the
+ timestamp is not from this boot */
+ if (r == -ESTALE)
+ continue;
+ else if (r < 0)
+ goto finish;
+
+ if (usec < not_before)
+ continue;
+ }
+
+ line ++;
+
+ r = output_journal(f, j, mode, n_columns, flags);
+ if (r < 0)
+ goto finish;
+ }
+
+ if (warn_cutoff && line < how_many && not_before > 0) {
+ sd_id128_t boot_id;
+ usec_t cutoff;
+
+ /* Check whether the cutoff line is too early */
+
+ r = sd_id128_get_boot(&boot_id);
+ if (r < 0)
+ goto finish;
+
+ r = sd_journal_get_cutoff_monotonic_usec(j, boot_id, &cutoff, NULL);
+ if (r < 0)
+ goto finish;
+
+ if (r > 0 && not_before < cutoff)
+ 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_t) -1);
+ if (r < 0)
+ goto finish;
+
+ }
+
+finish:
+ return r;
+}
+
+int add_matches_for_unit(sd_journal *j, const char *unit) {
+ int r;
+ _cleanup_free_ char *m1 = NULL, *m2 = NULL, *m3 = NULL;
+
+ assert(j);
+ assert(unit);
+
+ if (asprintf(&m1, "_SYSTEMD_UNIT=%s", unit) < 0 ||
+ asprintf(&m2, "COREDUMP_UNIT=%s", unit) < 0 ||
+ asprintf(&m3, "UNIT=%s", unit) < 0)
+ return -ENOMEM;
+
+ (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, 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))
+ );
+ return r;
+}
+
+int add_matches_for_user_unit(sd_journal *j, const char *unit, uid_t uid) {
+ int r;
+ _cleanup_free_ char *m1 = NULL, *m2 = NULL, *m3 = NULL, *m4 = NULL;
+
+ assert(j);
+ assert(unit);
+
+ if (asprintf(&m1, "_SYSTEMD_USER_UNIT=%s", unit) < 0 ||
+ asprintf(&m2, "USER_UNIT=%s", unit) < 0 ||
+ asprintf(&m3, "COREDUMP_USER_UNIT=%s", unit) < 0 ||
+ asprintf(&m4, "_UID=%d", uid) < 0)
+ return -ENOMEM;
+
+ (void) (
+ /* Look for messages from the user service itself */
+ (r = sd_journal_add_match(j, m1, 0)) ||
+ (r = sd_journal_add_match(j, m4, 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, m4, 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, m4, 0))
+ );
+ return r;
+}
+
+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,
+ bool system) {
+
+ _cleanup_journal_close_ sd_journal*j = NULL;
+ int r;
+ int jflags = SD_JOURNAL_LOCAL_ONLY | system * SD_JOURNAL_SYSTEM_ONLY;
+
+ assert(mode >= 0);
+ assert(mode < _OUTPUT_MODE_MAX);
+ assert(unit);
+
+ if (how_many <= 0)
+ return 0;
+
+ r = sd_journal_open(&j, jflags);
+ if (r < 0)
+ return r;
+
+ if (system)
+ r = add_matches_for_unit(j, unit);
+ else
+ r = add_matches_for_user_unit(j, unit, uid);
+ if (r < 0)
+ return r;
+
+ r = show_journal(f, j, mode, n_columns, not_before, how_many, flags);
+ if (r < 0)
+ return r;
+
+ return 0;
+}
+
+static const char *const output_mode_table[_OUTPUT_MODE_MAX] = {
+ [OUTPUT_SHORT] = "short",
+ [OUTPUT_SHORT_MONOTONIC] = "short-monotonic",
+ [OUTPUT_VERBOSE] = "verbose",
+ [OUTPUT_EXPORT] = "export",
+ [OUTPUT_JSON] = "json",
+ [OUTPUT_JSON_PRETTY] = "json-pretty",
+ [OUTPUT_JSON_SSE] = "json-sse",
+ [OUTPUT_CAT] = "cat"
+};
+
+DEFINE_STRING_TABLE_LOOKUP(output_mode, OutputMode);
diff --git a/src/shared/logs-show.h b/src/shared/logs-show.h
new file mode 100644
index 0000000000..b0f93a661a
--- /dev/null
+++ b/src/shared/logs-show.h
@@ -0,0 +1,67 @@
+/*-*- Mode: C; c-basic-offset: 8; indent-tabs-mode: nil -*-*/
+
+#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 <unistd.h>
+#include <sys/types.h>
+
+#include <systemd/sd-journal.h>
+
+#include "util.h"
+#include "output-mode.h"
+
+int output_journal(
+ FILE *f,
+ sd_journal *j,
+ OutputMode mode,
+ unsigned n_columns,
+ OutputFlags flags);
+
+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,
+ bool system);
+
+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/shared/macro.h b/src/shared/macro.h
new file mode 100644
index 0000000000..0874102ece
--- /dev/null
+++ b/src/shared/macro.h
@@ -0,0 +1,285 @@
+/*-*- Mode: C; c-basic-offset: 8; indent-tabs-mode: nil -*-*/
+
+#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 <assert.h>
+#include <sys/param.h>
+#include <sys/types.h>
+#include <sys/uio.h>
+#include <inttypes.h>
+
+#define _printf_attr_(a,b) __attribute__ ((format (printf, a, b)))
+#define _alloc_(...) __attribute__ ((alloc_size(__VA_ARGS__)))
+#define _sentinel_ __attribute__ ((sentinel))
+#define _noreturn_ __attribute__((noreturn))
+#define _unused_ __attribute__ ((unused))
+#define _destructor_ __attribute__ ((destructor))
+#define _pure_ __attribute__ ((pure))
+#define _const_ __attribute__ ((const))
+#define _deprecated_ __attribute__ ((deprecated))
+#define _packed_ __attribute__ ((packed))
+#define _malloc_ __attribute__ ((malloc))
+#define _weak_ __attribute__ ((weak))
+#define _likely_(x) (__builtin_expect(!!(x),1))
+#define _unlikely_(x) (__builtin_expect(!!(x),0))
+#define _public_ __attribute__ ((visibility("default")))
+#define _hidden_ __attribute__ ((visibility("hidden")))
+#define _weakref_(x) __attribute__((weakref(#x)))
+#define _introspect_(x) __attribute__((section("introspect." x)))
+#define _alignas_(x) __attribute__((aligned(__alignof(x))))
+#define _cleanup_(x) __attribute__((cleanup(x)))
+
+/* automake test harness */
+#define EXIT_TEST_SKIP 77
+
+#define XSTRINGIFY(x) #x
+#define STRINGIFY(x) XSTRINGIFY(x)
+
+/* Rounds up */
+
+#define ALIGN4(l) (((l) + 3) & ~3)
+#define ALIGN8(l) (((l) + 7) & ~7)
+
+#if __SIZEOF_POINTER__ == 8
+#define ALIGN(l) ALIGN8(l)
+#elif __SIZEOF_POINTER__ == 4
+#define ALIGN(l) ALIGN4(l)
+#else
+#error "Wut? Pointers are neither 4 nor 8 bytes long?"
+#endif
+
+#define ALIGN_PTR(p) ((void*) ALIGN((unsigned long) p))
+#define ALIGN4_PTR(p) ((void*) ALIGN4((unsigned long) p))
+#define ALIGN8_PTR(p) ((void*) ALIGN8((unsigned long) p))
+
+static inline size_t ALIGN_TO(size_t l, size_t ali) {
+ return ((l + ali - 1) & ~(ali - 1));
+}
+
+#define ALIGN_TO_PTR(p, ali) ((void*) ALIGN_TO((unsigned long) p))
+
+#define ELEMENTSOF(x) (sizeof(x)/sizeof((x)[0]))
+
+/*
+ * container_of - cast a member of a structure out to the containing structure
+ * @ptr: the pointer to the member.
+ * @type: the type of the container struct this is embedded in.
+ * @member: the name of the member within the struct.
+ *
+ */
+#define container_of(ptr, type, member) \
+ __extension__ ({ \
+ const typeof( ((type *)0)->member ) *__mptr = (ptr); \
+ (type *)( (char *)__mptr - offsetof(type,member) ); \
+ })
+
+#undef MAX
+#define MAX(a,b) \
+ __extension__ ({ \
+ typeof(a) _a = (a); \
+ typeof(b) _b = (b); \
+ _a > _b ? _a : _b; \
+ })
+
+#define MAX3(x,y,z) \
+ __extension__ ({ \
+ typeof(x) _c = MAX(x,y); \
+ MAX(_c, z); \
+ })
+
+#undef MIN
+#define MIN(a,b) \
+ __extension__ ({ \
+ typeof(a) _a = (a); \
+ typeof(b) _b = (b); \
+ _a < _b ? _a : _b; \
+ })
+
+#ifndef CLAMP
+#define CLAMP(x, low, high) \
+ __extension__ ({ \
+ typeof(x) _x = (x); \
+ typeof(low) _low = (low); \
+ typeof(high) _high = (high); \
+ ((_x > _high) ? _high : ((_x < _low) ? _low : _x)); \
+ })
+#endif
+
+#define assert_se(expr) \
+ do { \
+ if (_unlikely_(!(expr))) \
+ log_assert_failed(#expr, __FILE__, __LINE__, __PRETTY_FUNCTION__); \
+ } while (false) \
+
+/* We override the glibc assert() here. */
+#undef assert
+#ifdef NDEBUG
+#define assert(expr) do {} while(false)
+#else
+#define assert(expr) assert_se(expr)
+#endif
+
+#define assert_not_reached(t) \
+ do { \
+ log_assert_failed_unreachable(t, __FILE__, __LINE__, __PRETTY_FUNCTION__); \
+ } while (false)
+
+#if defined(static_assert)
+#define assert_cc(expr) \
+ do { \
+ static_assert(expr, #expr); \
+ } while (false)
+#else
+#define assert_cc(expr) \
+ do { \
+ switch (0) { \
+ case 0: \
+ case !!(expr): \
+ ; \
+ } \
+ } while (false)
+#endif
+
+#define PTR_TO_UINT(p) ((unsigned int) ((uintptr_t) (p)))
+#define UINT_TO_PTR(u) ((void*) ((uintptr_t) (u)))
+
+#define PTR_TO_UINT32(p) ((uint32_t) ((uintptr_t) (p)))
+#define UINT32_TO_PTR(u) ((void*) ((uintptr_t) (u)))
+
+#define PTR_TO_ULONG(p) ((unsigned long) ((uintptr_t) (p)))
+#define ULONG_TO_PTR(u) ((void*) ((uintptr_t) (u)))
+
+#define PTR_TO_INT(p) ((int) ((intptr_t) (p)))
+#define INT_TO_PTR(u) ((void*) ((intptr_t) (u)))
+
+#define TO_INT32(p) ((int32_t) ((intptr_t) (p)))
+#define INT32_TO_PTR(u) ((void*) ((intptr_t) (u)))
+
+#define PTR_TO_LONG(p) ((long) ((intptr_t) (p)))
+#define LONG_TO_PTR(u) ((void*) ((intptr_t) (u)))
+
+#define memzero(x,l) (memset((x), 0, (l)))
+#define zero(x) (memzero(&(x), sizeof(x)))
+
+#define CHAR_TO_STR(x) ((char[2]) { x, 0 })
+
+#define char_array_0(x) x[sizeof(x)-1] = 0;
+
+#define IOVEC_SET_STRING(i, s) \
+ do { \
+ struct iovec *_i = &(i); \
+ char *_s = (char *)(s); \
+ _i->iov_base = _s; \
+ _i->iov_len = strlen(_s); \
+ } while(false)
+
+static inline size_t IOVEC_TOTAL_SIZE(const struct iovec *i, unsigned n) {
+ unsigned j;
+ size_t r = 0;
+
+ for (j = 0; j < n; j++)
+ r += i[j].iov_len;
+
+ return r;
+}
+
+static inline size_t IOVEC_INCREMENT(struct iovec *i, unsigned n, size_t k) {
+ unsigned j;
+
+ for (j = 0; j < n; j++) {
+ size_t sub;
+
+ if (_unlikely_(k <= 0))
+ break;
+
+ sub = MIN(i[j].iov_len, k);
+ i[j].iov_len -= sub;
+ i[j].iov_base = (uint8_t*) i[j].iov_base + sub;
+ k -= sub;
+ }
+
+ return k;
+}
+
+#define VA_FORMAT_ADVANCE(format, ap) \
+do { \
+ int _argtypes[128]; \
+ size_t _i, _k; \
+ _k = parse_printf_format((format), ELEMENTSOF(_argtypes), _argtypes); \
+ assert(_k < ELEMENTSOF(_argtypes)); \
+ for (_i = 0; _i < _k; _i++) { \
+ if (_argtypes[_i] & PA_FLAG_PTR) { \
+ (void) va_arg(ap, void*); \
+ continue; \
+ } \
+ \
+ switch (_argtypes[_i]) { \
+ case PA_INT: \
+ case PA_INT|PA_FLAG_SHORT: \
+ case PA_CHAR: \
+ (void) va_arg(ap, int); \
+ break; \
+ case PA_INT|PA_FLAG_LONG: \
+ (void) va_arg(ap, long int); \
+ break; \
+ case PA_INT|PA_FLAG_LONG_LONG: \
+ (void) va_arg(ap, long long int); \
+ break; \
+ case PA_WCHAR: \
+ (void) va_arg(ap, wchar_t); \
+ break; \
+ case PA_WSTRING: \
+ case PA_STRING: \
+ case PA_POINTER: \
+ (void) va_arg(ap, void*); \
+ break; \
+ case PA_FLOAT: \
+ case PA_DOUBLE: \
+ (void) va_arg(ap, double); \
+ break; \
+ case PA_DOUBLE|PA_FLAG_LONG_DOUBLE: \
+ (void) va_arg(ap, long double); \
+ break; \
+ default: \
+ assert_not_reached("Unknown format string argument."); \
+ } \
+ } \
+} while(false)
+
+ /* Because statfs.t_type can be int on some architecures, we have to cast
+ * the const magic to the type, otherwise the compiler warns about
+ * signed/unsigned comparison, because the magic can be 32 bit unsigned.
+ */
+#define F_TYPE_CMP(a, b) (a == (typeof(a)) b)
+
+
+/* Returns the number of chars needed to format variables of the
+ * specified type as a decimal string. Adds in extra space for a
+ * negative '-' prefix. */
+
+#define DECIMAL_STR_MAX(type) \
+ (1+(sizeof(type) <= 1 ? 3 : \
+ sizeof(type) <= 2 ? 5 : \
+ sizeof(type) <= 4 ? 10 : \
+ sizeof(type) <= 8 ? 20 : sizeof(int[-2*(sizeof(type) > 8)])))
+
+#include "log.h"
diff --git a/src/shared/missing.h b/src/shared/missing.h
new file mode 100644
index 0000000000..d4ba0d3dcf
--- /dev/null
+++ b/src/shared/missing.h
@@ -0,0 +1,267 @@
+/*-*- Mode: C; c-basic-offset: 8; indent-tabs-mode: nil -*-*/
+
+#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/>.
+***/
+
+/* Missing glibc definitions to access certain kernel APIs */
+
+#include <sys/resource.h>
+#include <sys/syscall.h>
+#include <fcntl.h>
+#include <stdlib.h>
+#include <unistd.h>
+#include <linux/oom.h>
+
+#ifdef HAVE_AUDIT
+#include <libaudit.h>
+#endif
+
+#include "macro.h"
+
+#ifdef ARCH_MIPS
+#include <asm/sgidefs.h>
+#endif
+
+#ifndef RLIMIT_RTTIME
+#define RLIMIT_RTTIME 15
+#endif
+
+#ifndef F_LINUX_SPECIFIC_BASE
+#define F_LINUX_SPECIFIC_BASE 1024
+#endif
+
+#ifndef F_SETPIPE_SZ
+#define F_SETPIPE_SZ (F_LINUX_SPECIFIC_BASE + 7)
+#endif
+
+#ifndef F_GETPIPE_SZ
+#define F_GETPIPE_SZ (F_LINUX_SPECIFIC_BASE + 8)
+#endif
+
+#ifndef IP_FREEBIND
+#define IP_FREEBIND 15
+#endif
+
+#ifndef OOM_SCORE_ADJ_MIN
+#define OOM_SCORE_ADJ_MIN (-1000)
+#endif
+
+#ifndef OOM_SCORE_ADJ_MAX
+#define OOM_SCORE_ADJ_MAX 1000
+#endif
+
+#ifndef AUDIT_SERVICE_START
+#define AUDIT_SERVICE_START 1130 /* Service (daemon) start */
+#endif
+
+#ifndef AUDIT_SERVICE_STOP
+#define AUDIT_SERVICE_STOP 1131 /* Service (daemon) stop */
+#endif
+
+#ifndef TIOCVHANGUP
+#define TIOCVHANGUP 0x5437
+#endif
+
+#ifndef IP_TRANSPARENT
+#define IP_TRANSPARENT 19
+#endif
+
+#if !HAVE_DECL_PIVOT_ROOT
+static inline int pivot_root(const char *new_root, const char *put_old) {
+ return syscall(SYS_pivot_root, new_root, put_old);
+}
+#endif
+
+#ifdef __x86_64__
+# ifndef __NR_fanotify_init
+# define __NR_fanotify_init 300
+# endif
+# ifndef __NR_fanotify_mark
+# define __NR_fanotify_mark 301
+# endif
+#elif defined _MIPS_SIM
+# if _MIPS_SIM == _MIPS_SIM_ABI32
+# ifndef __NR_fanotify_init
+# define __NR_fanotify_init 4336
+# endif
+# ifndef __NR_fanotify_mark
+# define __NR_fanotify_mark 4337
+# endif
+# elif _MIPS_SIM == _MIPS_SIM_NABI32
+# ifndef __NR_fanotify_init
+# define __NR_fanotify_init 6300
+# endif
+# ifndef __NR_fanotify_mark
+# define __NR_fanotify_mark 6301
+# endif
+# elif _MIPS_SIM == _MIPS_SIM_ABI64
+# ifndef __NR_fanotify_init
+# define __NR_fanotify_init 5295
+# endif
+# ifndef __NR_fanotify_mark
+# define __NR_fanotify_mark 5296
+# endif
+# endif
+#else
+# ifndef __NR_fanotify_init
+# define __NR_fanotify_init 338
+# endif
+# ifndef __NR_fanotify_mark
+# define __NR_fanotify_mark 339
+# endif
+#endif
+
+#ifndef HAVE_FANOTIFY_INIT
+static inline int fanotify_init(unsigned int flags, unsigned int event_f_flags) {
+ return syscall(__NR_fanotify_init, flags, event_f_flags);
+}
+#endif
+
+#ifndef HAVE_FANOTIFY_MARK
+static inline int fanotify_mark(int fanotify_fd, unsigned int flags, uint64_t mask,
+ int dfd, const char *pathname) {
+#if defined _MIPS_SIM && _MIPS_SIM == _MIPS_SIM_ABI32 || defined __powerpc__ && !defined __powerpc64__
+ union {
+ uint64_t _64;
+ uint32_t _32[2];
+ } _mask;
+ _mask._64 = mask;
+
+ return syscall(__NR_fanotify_mark, fanotify_fd, flags,
+ _mask._32[0], _mask._32[1], dfd, pathname);
+#else
+ return syscall(__NR_fanotify_mark, fanotify_fd, flags, mask, dfd, pathname);
+#endif
+}
+#endif
+
+#ifndef BTRFS_IOCTL_MAGIC
+#define BTRFS_IOCTL_MAGIC 0x94
+#endif
+
+#ifndef BTRFS_PATH_NAME_MAX
+#define BTRFS_PATH_NAME_MAX 4087
+#endif
+
+struct btrfs_ioctl_vol_args {
+ int64_t fd;
+ char name[BTRFS_PATH_NAME_MAX + 1];
+};
+
+#ifndef BTRFS_IOC_DEFRAG
+#define BTRFS_IOC_DEFRAG _IOW(BTRFS_IOCTL_MAGIC, 2, struct btrfs_ioctl_vol_args)
+#endif
+
+#ifndef BTRFS_SUPER_MAGIC
+#define BTRFS_SUPER_MAGIC 0x9123683E
+#endif
+
+#ifndef MS_MOVE
+#define MS_MOVE 8192
+#endif
+
+#ifndef MS_PRIVATE
+#define MS_PRIVATE (1 << 18)
+#endif
+
+#if !HAVE_DECL_GETTID
+static inline pid_t gettid(void) {
+ return (pid_t) syscall(SYS_gettid);
+}
+#endif
+
+#ifndef SCM_SECURITY
+#define SCM_SECURITY 0x03
+#endif
+
+#ifndef MS_STRICTATIME
+#define MS_STRICTATIME (1<<24)
+#endif
+
+#ifndef MS_REC
+#define MS_REC 16384
+#endif
+
+#ifndef MS_SHARED
+#define MS_SHARED (1<<20)
+#endif
+
+#ifndef PR_SET_NO_NEW_PRIVS
+#define PR_SET_NO_NEW_PRIVS 38
+#endif
+
+#ifndef PR_SET_CHILD_SUBREAPER
+#define PR_SET_CHILD_SUBREAPER 36
+#endif
+
+#ifndef MAX_HANDLE_SZ
+#define MAX_HANDLE_SZ 128
+#endif
+
+#if defined __x86_64__
+# ifndef __NR_name_to_handle_at
+# define __NR_name_to_handle_at 303
+# endif
+#elif defined __i386__
+# ifndef __NR_name_to_handle_at
+# define __NR_name_to_handle_at 341
+# endif
+#elif defined __arm__
+# ifndef __NR_name_to_handle_at
+# define __NR_name_to_handle_at 370
+# endif
+#elif defined __powerpc__
+# ifndef __NR_name_to_handle_at
+# define __NR_name_to_handle_at 345
+# endif
+#else
+# ifndef __NR_name_to_handle_at
+# error __NR_name_to_handle_at is not defined
+# endif
+#endif
+
+#if !HAVE_DECL_NAME_TO_HANDLE_AT
+struct file_handle {
+ unsigned int handle_bytes;
+ int handle_type;
+ unsigned char f_handle[0];
+};
+
+static inline int name_to_handle_at(int fd, const char *name, struct file_handle *handle, int *mnt_id, int flags) {
+ return syscall(__NR_name_to_handle_at, fd, name, handle, mnt_id, flags);
+}
+#endif
+
+#ifndef HAVE_SECURE_GETENV
+# ifdef HAVE___SECURE_GETENV
+# define secure_getenv __secure_getenv
+# else
+# error neither secure_getenv nor __secure_getenv are available
+# endif
+#endif
+
+#ifndef CIFS_MAGIC_NUMBER
+#define CIFS_MAGIC_NUMBER 0xFF534D42
+#endif
+
+#ifndef TFD_TIMER_CANCEL_ON_SET
+#define TFD_TIMER_CANCEL_ON_SET (1 << 1)
+#endif
diff --git a/src/shared/mkdir.c b/src/shared/mkdir.c
new file mode 100644
index 0000000000..0e51b64f69
--- /dev/null
+++ b/src/shared/mkdir.c
@@ -0,0 +1,147 @@
+/*-*- Mode: C; c-basic-offset: 8; indent-tabs-mode: nil -*-*/
+
+/***
+ This file is part of systemd.
+
+ Copyright 2010 Lennart Poettering
+
+ systemd is free software; you can redistribute it and/or modify it
+ under the terms of the GNU Lesser General Public License as published by
+ the Free Software Foundation; either version 2.1 of the License, or
+ (at your option) any later version.
+
+ systemd is distributed in the hope that it will be useful, but
+ WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ Lesser General 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 <assert.h>
+#include <string.h>
+#include <unistd.h>
+#include <errno.h>
+#include <stdlib.h>
+#include <stdio.h>
+
+#include "mkdir.h"
+#include "label.h"
+#include "util.h"
+
+int mkdir_label(const char *path, mode_t mode) {
+ return label_mkdir(path, mode, true);
+}
+
+static int makedir_safe(const char *path, mode_t mode, uid_t uid, gid_t gid, bool apply) {
+ struct stat st;
+
+ if (label_mkdir(path, mode, apply) >= 0)
+ if (chmod_and_chown(path, mode, uid, gid) < 0)
+ return -errno;
+
+ if (lstat(path, &st) < 0)
+ return -errno;
+
+ if ((st.st_mode & 0777) != mode ||
+ st.st_uid != uid ||
+ st.st_gid != gid ||
+ !S_ISDIR(st.st_mode)) {
+ errno = EEXIST;
+ return -errno;
+ }
+
+ return 0;
+}
+
+int mkdir_safe(const char *path, mode_t mode, uid_t uid, gid_t gid) {
+ return makedir_safe(path, mode, uid, gid, false);
+}
+
+int mkdir_safe_label(const char *path, mode_t mode, uid_t uid, gid_t gid) {
+ return makedir_safe(path, mode, uid, gid, true);
+}
+
+static int makedir_parents(const char *path, mode_t mode, bool apply) {
+ struct stat st;
+ const char *p, *e;
+
+ assert(path);
+
+ /* return immediately if directory exists */
+ e = strrchr(path, '/');
+ if (!e)
+ return -EINVAL;
+ p = strndupa(path, e - path);
+ if (stat(p, &st) >= 0) {
+ if ((st.st_mode & S_IFMT) == S_IFDIR)
+ return 0;
+ else
+ return -ENOTDIR;
+ }
+
+ /* create every parent directory in the path, except the last component */
+ p = path + strspn(path, "/");
+ for (;;) {
+ int r;
+ char *t;
+
+ e = p + strcspn(p, "/");
+ p = e + strspn(e, "/");
+
+ /* Is this the last component? If so, then we're
+ * done */
+ if (*p == 0)
+ return 0;
+
+ t = strndup(path, e - path);
+ if (!t)
+ return -ENOMEM;
+
+ r = label_mkdir(t, mode, apply);
+ free(t);
+
+ if (r < 0 && errno != EEXIST)
+ return -errno;
+ }
+}
+
+int mkdir_parents(const char *path, mode_t mode) {
+ return makedir_parents(path, mode, false);
+}
+
+int mkdir_parents_label(const char *path, mode_t mode) {
+ return makedir_parents(path, mode, true);
+}
+
+static int is_dir(const char* path) {
+ struct stat st;
+ if (stat(path, &st) < 0)
+ return -errno;
+ return S_ISDIR(st.st_mode);
+}
+
+static int makedir_p(const char *path, mode_t mode, bool apply) {
+ int r;
+
+ /* Like mkdir -p */
+
+ r = makedir_parents(path, mode, apply);
+ if (r < 0)
+ return r;
+
+ r = label_mkdir(path, mode, apply);
+ if (r < 0 && (errno != EEXIST || is_dir(path) <= 0))
+ return -errno;
+
+ return 0;
+}
+
+int mkdir_p(const char *path, mode_t mode) {
+ return makedir_p(path, mode, false);
+}
+
+int mkdir_p_label(const char *path, mode_t mode) {
+ return makedir_p(path, mode, true);
+}
diff --git a/src/shared/mkdir.h b/src/shared/mkdir.h
new file mode 100644
index 0000000000..ce1c35e9ba
--- /dev/null
+++ b/src/shared/mkdir.h
@@ -0,0 +1,32 @@
+/*-*- Mode: C; c-basic-offset: 8; indent-tabs-mode: nil -*-*/
+
+#ifndef foomkdirhfoo
+#define foomkdirhfoo
+
+/***
+ This file is part 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/>.
+***/
+
+int mkdir_label(const char *path, mode_t mode);
+int mkdir_safe(const char *path, mode_t mode, uid_t uid, gid_t gid);
+int mkdir_safe_label(const char *path, mode_t mode, uid_t uid, gid_t gid);
+int mkdir_parents(const char *path, mode_t mode);
+int mkdir_parents_label(const char *path, mode_t mode);
+int mkdir_p(const char *path, mode_t mode);
+int mkdir_p_label(const char *path, mode_t mode);
+#endif
diff --git a/src/shared/output-mode.h b/src/shared/output-mode.h
new file mode 100644
index 0000000000..0efd430c5d
--- /dev/null
+++ b/src/shared/output-mode.h
@@ -0,0 +1,44 @@
+/*-*- Mode: C; c-basic-offset: 8; indent-tabs-mode: nil -*-*/
+
+#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_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
+} OutputFlags;
diff --git a/src/shared/pager.c b/src/shared/pager.c
new file mode 100644
index 0000000000..8dddf24f4c
--- /dev/null
+++ b/src/shared/pager.c
@@ -0,0 +1,146 @@
+/*-*- Mode: C; c-basic-offset: 8; indent-tabs-mode: nil -*-*/
+
+/***
+ This file is part 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 <sys/types.h>
+#include <fcntl.h>
+#include <stdlib.h>
+#include <unistd.h>
+#include <string.h>
+#include <sys/prctl.h>
+
+#include "pager.h"
+#include "util.h"
+#include "macro.h"
+
+static pid_t pager_pid = 0;
+
+_noreturn_ static void pager_fallback(void) {
+ ssize_t n;
+ do {
+ n = splice(STDIN_FILENO, NULL, STDOUT_FILENO, NULL, 64*1024, 0);
+ } while (n > 0);
+ if (n < 0) {
+ log_error("Internal pager failed: %m");
+ _exit(EXIT_FAILURE);
+ }
+ _exit(EXIT_SUCCESS);
+}
+
+int pager_open(bool jump_to_end) {
+ int fd[2];
+ const char *pager;
+ pid_t parent_pid;
+ int r;
+
+ if (pager_pid > 0)
+ return 1;
+
+ if ((pager = getenv("SYSTEMD_PAGER")) || (pager = getenv("PAGER")))
+ if (!*pager || streq(pager, "cat"))
+ return 0;
+
+ if (!on_tty())
+ return 0;
+
+ /* Determine and cache number of columns before we spawn the
+ * pager so that we get the value from the actual tty */
+ columns();
+
+ if (pipe(fd) < 0) {
+ log_error("Failed to create pager pipe: %m");
+ return -errno;
+ }
+
+ parent_pid = getpid();
+
+ pager_pid = fork();
+ if (pager_pid < 0) {
+ r = -errno;
+ log_error("Failed to fork pager: %m");
+ close_pipe(fd);
+ return r;
+ }
+
+ /* In the child start the pager */
+ if (pager_pid == 0) {
+
+ dup2(fd[0], STDIN_FILENO);
+ close_pipe(fd);
+
+ if (jump_to_end)
+ setenv("LESS", "FRSXMK+G", 1);
+ else
+ setenv("LESS", "FRSXMK", 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) {
+ log_error("Failed to duplicate pager pipe: %m");
+ return -errno;
+ }
+
+ close_pipe(fd);
+ return 1;
+}
+
+void pager_close(void) {
+
+ if (pager_pid <= 0)
+ return;
+
+ /* Inform pager that we are done */
+ fclose(stdout);
+ kill(pager_pid, SIGCONT);
+ wait_for_terminate(pager_pid, NULL);
+ pager_pid = 0;
+}
+
+bool pager_have(void) {
+ return pager_pid > 0;
+}
diff --git a/src/shared/pager.h b/src/shared/pager.h
new file mode 100644
index 0000000000..03dca8bc03
--- /dev/null
+++ b/src/shared/pager.h
@@ -0,0 +1,30 @@
+/*-*- Mode: C; c-basic-offset: 8; indent-tabs-mode: nil -*-*/
+
+#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_;
diff --git a/src/shared/path-lookup.c b/src/shared/path-lookup.c
new file mode 100644
index 0000000000..1a47ea9ce7
--- /dev/null
+++ b/src/shared/path-lookup.c
@@ -0,0 +1,426 @@
+/*-*- Mode: C; c-basic-offset: 8; indent-tabs-mode: nil -*-*/
+
+/***
+ This file is part 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 <assert.h>
+#include <stdlib.h>
+#include <stdio.h>
+#include <string.h>
+#include <unistd.h>
+#include <errno.h>
+
+#include "util.h"
+#include "mkdir.h"
+#include "strv.h"
+#include "path-util.h"
+#include "path-lookup.h"
+
+static const char* const systemd_running_as_table[_SYSTEMD_RUNNING_AS_MAX] = {
+ [SYSTEMD_SYSTEM] = "system",
+ [SYSTEMD_USER] = "user"
+};
+
+DEFINE_STRING_TABLE_LOOKUP(systemd_running_as, SystemdRunningAs);
+
+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;
+}
+
+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",
+ "/run/systemd/user",
+ NULL
+ };
+
+ const char * const data_unit_paths[] = {
+ "/usr/local/lib/systemd/user",
+ "/usr/local/share/systemd/user",
+ USER_DATA_UNIT_PATH,
+ "/usr/lib/systemd/user",
+ "/usr/share/systemd/user",
+ NULL
+ };
+
+ const char *home, *e;
+ char *config_home = NULL, *data_home = NULL;
+ char **config_dirs = NULL, **data_dirs = NULL;
+ char **r = NULL, **t;
+
+ /* Implement the mechanisms defined in
+ *
+ * http://standards.freedesktop.org/basedir-spec/basedir-spec-0.6.html
+ *
+ * We look in both the config and the data dirs because we
+ * want to encourage that distributors ship their unit files
+ * as data, and allow overriding as configuration.
+ */
+
+ if (user_config_home(&config_home) < 0)
+ goto fail;
+
+ home = getenv("HOME");
+
+ e = getenv("XDG_CONFIG_DIRS");
+ if (e) {
+ config_dirs = strv_split(e, ":");
+ if (!config_dirs)
+ goto fail;
+ }
+
+ /* We don't treat /etc/xdg/systemd here as the spec
+ * suggests because we assume that that is a link to
+ * /etc/systemd/ anyway. */
+
+ e = getenv("XDG_DATA_HOME");
+ if (e) {
+ if (asprintf(&data_home, "%s/systemd/user", e) < 0)
+ goto fail;
+
+ } else if (home) {
+ if (asprintf(&data_home, "%s/.local/share/systemd/user", home) < 0)
+ goto fail;
+
+ /* There is really no need for two unit dirs in $HOME,
+ * except to be fully compliant with the XDG spec. We
+ * now try to link the two dirs, so that we can
+ * minimize disk seeks a little. Further down we'll
+ * then filter out this link, if it is actually is
+ * one. */
+
+ mkdir_parents_label(data_home, 0777);
+ (void) symlink("../../../.config/systemd/user", data_home);
+ }
+
+ 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)
+ goto fail;
+
+ /* Now merge everything we found. */
+ if (generator_early) {
+ t = strv_append(r, generator_early);
+ if (!t)
+ goto fail;
+ strv_free(r);
+ r = t;
+ }
+
+ if (config_home) {
+ t = strv_append(r, config_home);
+ if (!t)
+ goto fail;
+ strv_free(r);
+ r = t;
+ }
+
+ if (!strv_isempty(config_dirs)) {
+ t = strv_merge_concat(r, config_dirs, "/systemd/user");
+ if (!t)
+ goto finish;
+ strv_free(r);
+ r = t;
+ }
+
+ t = strv_merge(r, (char**) config_unit_paths);
+ if (!t)
+ goto fail;
+ strv_free(r);
+ r = t;
+
+ if (generator) {
+ t = strv_append(r, generator);
+ if (!t)
+ goto fail;
+ strv_free(r);
+ r = t;
+ }
+
+ if (data_home) {
+ t = strv_append(r, data_home);
+ if (!t)
+ goto fail;
+ strv_free(r);
+ r = t;
+ }
+
+ if (!strv_isempty(data_dirs)) {
+ t = strv_merge_concat(r, data_dirs, "/systemd/user");
+ if (!t)
+ goto fail;
+ strv_free(r);
+ r = t;
+ }
+
+ t = strv_merge(r, (char**) data_unit_paths);
+ if (!t)
+ goto fail;
+ strv_free(r);
+ r = t;
+
+ if (generator_late) {
+ t = strv_append(r, generator_late);
+ if (!t)
+ goto fail;
+ strv_free(r);
+ r = t;
+ }
+
+ if (!path_strv_make_absolute_cwd(r))
+ goto fail;
+
+finish:
+ free(config_home);
+ strv_free(config_dirs);
+ free(data_home);
+ strv_free(data_dirs);
+
+ return r;
+
+fail:
+ strv_free(r);
+ r = NULL;
+ goto finish;
+}
+
+int lookup_paths_init(
+ LookupPaths *p,
+ SystemdRunningAs running_as,
+ bool personal,
+ const char *generator,
+ const char *generator_early,
+ const char *generator_late) {
+
+ const char *e;
+
+ assert(p);
+
+ /* First priority is whatever has been passed to us via env
+ * vars */
+ e = getenv("SYSTEMD_UNIT_PATH");
+ if (e) {
+ p->unit_path = path_split_and_make_absolute(e);
+ if (!p->unit_path)
+ return -ENOMEM;
+ } else
+ p->unit_path = NULL;
+
+ if (strv_isempty(p->unit_path)) {
+ /* Nothing is set, so let's figure something out. */
+ strv_free(p->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 == SYSTEMD_USER) {
+
+ if (personal)
+ p->unit_path = user_dirs(generator, generator_early, generator_late);
+ else
+ p->unit_path = strv_new(
+ /* If you modify this you also want to modify
+ * systemduserunitpath= in systemd.pc.in, and
+ * the arrays in user_dirs() above! */
+ 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);
+
+ if (!p->unit_path)
+ return -ENOMEM;
+
+ } else {
+ p->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 (!p->unit_path)
+ return -ENOMEM;
+ }
+ }
+
+ if (!path_strv_canonicalize(p->unit_path))
+ return -ENOMEM;
+
+ strv_uniq(p->unit_path);
+
+ 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.");
+ strv_free(p->unit_path);
+ p->unit_path = NULL;
+ }
+
+ if (running_as == SYSTEMD_SYSTEM) {
+#ifdef HAVE_SYSV_COMPAT
+ /* /etc/init.d/ compatibility does not matter to users */
+
+ e = getenv("SYSTEMD_SYSVINIT_PATH");
+ if (e) {
+ p->sysvinit_path = path_split_and_make_absolute(e);
+ if (!p->sysvinit_path)
+ return -ENOMEM;
+ } 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) {
+ p->sysvrcnd_path = path_split_and_make_absolute(e);
+ if (!p->sysvrcnd_path)
+ return -ENOMEM;
+ } 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_canonicalize(p->sysvinit_path))
+ return -ENOMEM;
+
+ if (!path_strv_canonicalize(p->sysvrcnd_path))
+ return -ENOMEM;
+
+ strv_uniq(p->sysvinit_path);
+ strv_uniq(p->sysvrcnd_path);
+
+ 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.");
+ strv_free(p->sysvinit_path);
+ p->sysvinit_path = NULL;
+ }
+
+ 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.");
+ strv_free(p->sysvrcnd_path);
+ p->sysvrcnd_path = NULL;
+ }
+#else
+ log_debug("SysV init scripts and rcN.d links support disabled");
+#endif
+ }
+
+ return 0;
+}
+
+void lookup_paths_free(LookupPaths *p) {
+ assert(p);
+
+ strv_free(p->unit_path);
+ p->unit_path = NULL;
+
+#ifdef HAVE_SYSV_COMPAT
+ strv_free(p->sysvinit_path);
+ strv_free(p->sysvrcnd_path);
+ p->sysvinit_path = p->sysvrcnd_path = NULL;
+#endif
+}
diff --git a/src/shared/path-lookup.h b/src/shared/path-lookup.h
new file mode 100644
index 0000000000..9dee85f967
--- /dev/null
+++ b/src/shared/path-lookup.h
@@ -0,0 +1,45 @@
+/*-*- Mode: C; c-basic-offset: 8; indent-tabs-mode: nil -*-*/
+
+#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 struct LookupPaths {
+ char **unit_path;
+#ifdef HAVE_SYSV_COMPAT
+ char **sysvinit_path;
+ char **sysvrcnd_path;
+#endif
+} LookupPaths;
+
+typedef enum SystemdRunningAs {
+ SYSTEMD_SYSTEM,
+ SYSTEMD_USER,
+ _SYSTEMD_RUNNING_AS_MAX,
+ _SYSTEMD_RUNNING_AS_INVALID = -1
+} SystemdRunningAs;
+
+const char* systemd_running_as_to_string(SystemdRunningAs i) _const_;
+SystemdRunningAs systemd_running_as_from_string(const char *s) _pure_;
+
+int user_config_home(char **config_home);
+
+int lookup_paths_init(LookupPaths *p, SystemdRunningAs running_as, bool personal, const char *generator, const char *generator_early, const char *generator_late);
+void lookup_paths_free(LookupPaths *p);
diff --git a/src/shared/path-util.c b/src/shared/path-util.c
new file mode 100644
index 0000000000..0c1b6a0ab0
--- /dev/null
+++ b/src/shared/path-util.c
@@ -0,0 +1,428 @@
+/*-*- Mode: C; c-basic-offset: 8; indent-tabs-mode: nil -*-*/
+
+/***
+ 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 <assert.h>
+#include <string.h>
+#include <unistd.h>
+#include <errno.h>
+#include <stdlib.h>
+#include <signal.h>
+#include <stdio.h>
+#include <fcntl.h>
+#include <dirent.h>
+#include <sys/statvfs.h>
+
+#include "macro.h"
+#include "util.h"
+#include "log.h"
+#include "strv.h"
+#include "path-util.h"
+#include "missing.h"
+
+bool path_is_absolute(const char *p) {
+ return p[0] == '/';
+}
+
+bool is_path(const char *p) {
+ return !!strchr(p, '/');
+}
+
+char *path_get_file_name(const char *p) {
+ char *r;
+
+ assert(p);
+
+ r = strrchr(p, '/');
+ if (r)
+ return r + 1;
+
+ return (char*) p;
+}
+
+int path_get_parent(const char *path, char **_r) {
+ const char *e, *a = NULL, *b = NULL, *p;
+ char *r;
+ bool slash = false;
+
+ assert(path);
+ assert(_r);
+
+ if (!*path)
+ return -EINVAL;
+
+ for (e = path; *e; e++) {
+
+ if (!slash && *e == '/') {
+ a = b;
+ b = e;
+ slash = true;
+ } else if (slash && *e != '/')
+ slash = false;
+ }
+
+ if (*(e-1) == '/')
+ p = a;
+ else
+ p = b;
+
+ if (!p)
+ return -EINVAL;
+
+ if (p == path)
+ r = strdup("/");
+ else
+ r = strndup(path, p-path);
+
+ if (!r)
+ return -ENOMEM;
+
+ *_r = r;
+ return 0;
+}
+
+char **path_split_and_make_absolute(const char *p) {
+ char **l;
+ assert(p);
+
+ if (!(l = strv_split(p, ":")))
+ return NULL;
+
+ if (!path_strv_make_absolute_cwd(l)) {
+ strv_free(l);
+ return NULL;
+ }
+
+ return l;
+}
+
+char *path_make_absolute(const char *p, const char *prefix) {
+ assert(p);
+
+ /* Makes every item in the list an absolute path by prepending
+ * the prefix, if specified and necessary */
+
+ if (path_is_absolute(p) || !prefix)
+ return strdup(p);
+
+ return strjoin(prefix, "/", p, NULL);
+}
+
+char *path_make_absolute_cwd(const char *p) {
+ char *cwd, *r;
+
+ assert(p);
+
+ /* Similar to path_make_absolute(), but prefixes with the
+ * current working directory. */
+
+ if (path_is_absolute(p))
+ return strdup(p);
+
+ cwd = get_current_dir_name();
+ if (!cwd)
+ return NULL;
+
+ r = path_make_absolute(p, cwd);
+ free(cwd);
+
+ return r;
+}
+
+char **path_strv_make_absolute_cwd(char **l) {
+ char **s;
+
+ /* Goes through every item in the string list and makes it
+ * absolute. This works in place and won't rollback any
+ * changes on failure. */
+
+ STRV_FOREACH(s, l) {
+ char *t;
+
+ if (!(t = path_make_absolute_cwd(*s)))
+ return NULL;
+
+ free(*s);
+ *s = t;
+ }
+
+ return l;
+}
+
+char **path_strv_canonicalize(char **l) {
+ char **s;
+ unsigned k = 0;
+ bool enomem = false;
+
+ if (strv_isempty(l))
+ return l;
+
+ /* Goes through every item in the string list and canonicalize
+ * the path. This works in place and won't rollback any
+ * changes on failure. */
+
+ STRV_FOREACH(s, l) {
+ char *t, *u;
+
+ t = path_make_absolute_cwd(*s);
+ free(*s);
+ *s = NULL;
+
+ if (!t) {
+ enomem = true;
+ continue;
+ }
+
+ errno = 0;
+ u = canonicalize_file_name(t);
+ if (!u) {
+ if (errno == ENOENT)
+ u = t;
+ else {
+ free(t);
+ if (errno == ENOMEM || !errno)
+ enomem = true;
+
+ continue;
+ }
+ } else
+ free(t);
+
+ l[k++] = u;
+ }
+
+ l[k] = NULL;
+
+ if (enomem)
+ return NULL;
+
+ return l;
+}
+
+char **path_strv_canonicalize_uniq(char **l) {
+ if (strv_isempty(l))
+ return l;
+
+ if (!path_strv_canonicalize(l))
+ return NULL;
+
+ return strv_uniq(l);
+}
+
+char *path_kill_slashes(char *path) {
+ char *f, *t;
+ bool slash = false;
+
+ /* Removes redundant inner and trailing slashes. Modifies the
+ * passed string in-place.
+ *
+ * ///foo///bar/ becomes /foo/bar
+ */
+
+ for (f = path, t = path; *f; f++) {
+
+ if (*f == '/') {
+ slash = true;
+ continue;
+ }
+
+ if (slash) {
+ slash = false;
+ *(t++) = '/';
+ }
+
+ *(t++) = *f;
+ }
+
+ /* Special rule, if we are talking of the root directory, a
+ trailing slash is good */
+
+ if (t == path && slash)
+ *(t++) = '/';
+
+ *t = 0;
+ return path;
+}
+
+char* path_startswith(const char *path, const char *prefix) {
+ assert(path);
+ assert(prefix);
+
+ if ((path[0] == '/') != (prefix[0] == '/'))
+ return NULL;
+
+ for (;;) {
+ size_t a, b;
+
+ path += strspn(path, "/");
+ prefix += strspn(prefix, "/");
+
+ if (*prefix == 0)
+ return (char*) path;
+
+ if (*path == 0)
+ return NULL;
+
+ a = strcspn(path, "/");
+ b = strcspn(prefix, "/");
+
+ if (a != b)
+ return NULL;
+
+ if (memcmp(path, prefix, a) != 0)
+ return NULL;
+
+ path += a;
+ prefix += b;
+ }
+}
+
+bool path_equal(const char *a, const char *b) {
+ assert(a);
+ assert(b);
+
+ if ((a[0] == '/') != (b[0] == '/'))
+ return false;
+
+ for (;;) {
+ size_t j, k;
+
+ a += strspn(a, "/");
+ b += strspn(b, "/");
+
+ if (*a == 0 && *b == 0)
+ return true;
+
+ if (*a == 0 || *b == 0)
+ return false;
+
+ j = strcspn(a, "/");
+ k = strcspn(b, "/");
+
+ if (j != k)
+ return false;
+
+ if (memcmp(a, b, j) != 0)
+ return false;
+
+ a += j;
+ b += k;
+ }
+}
+
+int path_is_mount_point(const char *t, bool allow_symlink) {
+ char *parent;
+ int r;
+ struct file_handle *h;
+ int mount_id, mount_id_parent;
+ struct stat a, b;
+
+ /* We are not actually interested in the file handles, but
+ * name_to_handle_at() also passes us the mount ID, hence use
+ * it but throw the handle away */
+
+ if (path_equal(t, "/"))
+ return 1;
+
+ h = alloca(MAX_HANDLE_SZ);
+ h->handle_bytes = MAX_HANDLE_SZ;
+
+ r = name_to_handle_at(AT_FDCWD, t, h, &mount_id, allow_symlink ? AT_SYMLINK_FOLLOW : 0);
+ if (r < 0) {
+ if (errno == ENOSYS || errno == ENOTSUP)
+ /* This kernel or file system does not support
+ * name_to_handle_at(), hence fallback to the
+ * traditional stat() logic */
+ goto fallback;
+
+ if (errno == ENOENT)
+ return 0;
+
+ return -errno;
+ }
+
+ r = path_get_parent(t, &parent);
+ if (r < 0)
+ return r;
+
+ h->handle_bytes = MAX_HANDLE_SZ;
+ r = name_to_handle_at(AT_FDCWD, parent, h, &mount_id_parent, 0);
+ free(parent);
+
+ if (r < 0) {
+ /* The parent can't do name_to_handle_at() but the
+ * directory we are interested in can? If so, it must
+ * be a mount point */
+ if (errno == ENOTSUP)
+ return 1;
+
+ return -errno;
+ }
+
+ return mount_id != mount_id_parent;
+
+fallback:
+ if (allow_symlink)
+ r = stat(t, &a);
+ else
+ r = lstat(t, &a);
+
+ if (r < 0) {
+ if (errno == ENOENT)
+ return 0;
+
+ return -errno;
+ }
+
+ r = path_get_parent(t, &parent);
+ if (r < 0)
+ return r;
+
+ r = lstat(parent, &b);
+ free(parent);
+
+ if (r < 0)
+ return -errno;
+
+ return a.st_dev != b.st_dev;
+}
+
+int path_is_read_only_fs(const char *path) {
+ struct statvfs st;
+
+ assert(path);
+
+ if (statvfs(path, &st) < 0)
+ return -errno;
+
+ return !!(st.f_flag & ST_RDONLY);
+}
+
+int path_is_os_tree(const char *path) {
+ char *p;
+ int r;
+
+ /* We use /etc/os-release as flag file if something is an OS */
+
+ p = strappenda(path, "/etc/os-release");
+ r = access(p, F_OK);
+
+ return r < 0 ? 0 : 1;
+}
diff --git a/src/shared/path-util.h b/src/shared/path-util.h
new file mode 100644
index 0000000000..d187743769
--- /dev/null
+++ b/src/shared/path-util.h
@@ -0,0 +1,45 @@
+/*-*- Mode: C; c-basic-offset: 8; indent-tabs-mode: nil -*-*/
+
+#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 <stdbool.h>
+
+#include "macro.h"
+
+bool is_path(const char *p) _pure_;
+char** path_split_and_make_absolute(const char *p);
+char* path_get_file_name(const char *p) _pure_;
+int path_get_parent(const char *path, char **parent);
+bool path_is_absolute(const char *p) _pure_;
+char* path_make_absolute(const char *p, const char *prefix);
+char* path_make_absolute_cwd(const char *p);
+char* path_kill_slashes(char *path);
+char* path_startswith(const char *path, const char *prefix) _pure_;
+bool path_equal(const char *a, const char *b) _pure_;
+
+char** path_strv_make_absolute_cwd(char **l);
+char** path_strv_canonicalize(char **l);
+char** path_strv_canonicalize_uniq(char **l);
+
+int path_is_mount_point(const char *path, bool allow_symlink);
+int path_is_read_only_fs(const char *path);
+int path_is_os_tree(const char *path);
diff --git a/src/shared/polkit.c b/src/shared/polkit.c
new file mode 100644
index 0000000000..cea7074ad3
--- /dev/null
+++ b/src/shared/polkit.c
@@ -0,0 +1,174 @@
+/*-*- Mode: C; c-basic-offset: 8; indent-tabs-mode: nil -*-*/
+
+/***
+ This file is part of systemd.
+
+ Copyright 2011 Lennart Poettering
+
+ systemd is free software; you can redistribute it and/or modify it
+ under the terms of the GNU Lesser General Public License as published by
+ the Free Software Foundation; either version 2.1 of the License, or
+ (at your option) any later version.
+
+ systemd is distributed in the hope that it will be useful, but
+ WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ Lesser General 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>
+
+#include <errno.h>
+
+#include "util.h"
+#include "dbus-common.h"
+#include "polkit.h"
+
+int verify_polkit(
+ DBusConnection *c,
+ DBusMessage *request,
+ const char *action,
+ bool interactive,
+ bool *_challenge,
+ DBusError *error) {
+
+
+#ifdef ENABLE_POLKIT
+ DBusMessage *m = NULL, *reply = NULL;
+ const char *unix_process = "unix-process", *pid = "pid", *starttime = "start-time", *cancel_id = "";
+ uint32_t flags = interactive ? 1 : 0;
+ pid_t pid_raw;
+ uint32_t pid_u32;
+ unsigned long long starttime_raw;
+ uint64_t starttime_u64;
+ DBusMessageIter iter_msg, iter_struct, iter_array, iter_dict, iter_variant;
+ int r;
+ dbus_bool_t authorized = FALSE, challenge = FALSE;
+#endif
+ const char *sender;
+ unsigned long ul;
+
+ assert(c);
+ assert(request);
+
+ sender = dbus_message_get_sender(request);
+ if (!sender)
+ return -EINVAL;
+
+ ul = dbus_bus_get_unix_user(c, sender, error);
+ if (ul == (unsigned long) -1)
+ return -EINVAL;
+
+ /* Shortcut things for root, to avoid the PK roundtrip and dependency */
+ if (ul == 0)
+ return 1;
+
+#ifdef ENABLE_POLKIT
+
+ pid_raw = bus_get_unix_process_id(c, sender, error);
+ if (pid_raw == 0)
+ return -EINVAL;
+
+ r = get_starttime_of_pid(pid_raw, &starttime_raw);
+ if (r < 0)
+ return r;
+
+ m = dbus_message_new_method_call(
+ "org.freedesktop.PolicyKit1",
+ "/org/freedesktop/PolicyKit1/Authority",
+ "org.freedesktop.PolicyKit1.Authority",
+ "CheckAuthorization");
+ if (!m)
+ return -ENOMEM;
+
+ dbus_message_iter_init_append(m, &iter_msg);
+
+ pid_u32 = (uint32_t) pid_raw;
+ starttime_u64 = (uint64_t) starttime_raw;
+
+ if (!dbus_message_iter_open_container(&iter_msg, DBUS_TYPE_STRUCT, NULL, &iter_struct) ||
+ !dbus_message_iter_append_basic(&iter_struct, DBUS_TYPE_STRING, &unix_process) ||
+ !dbus_message_iter_open_container(&iter_struct, DBUS_TYPE_ARRAY, "{sv}", &iter_array) ||
+ !dbus_message_iter_open_container(&iter_array, DBUS_TYPE_DICT_ENTRY, NULL, &iter_dict) ||
+ !dbus_message_iter_append_basic(&iter_dict, DBUS_TYPE_STRING, &pid) ||
+ !dbus_message_iter_open_container(&iter_dict, DBUS_TYPE_VARIANT, "u", &iter_variant) ||
+ !dbus_message_iter_append_basic(&iter_variant, DBUS_TYPE_UINT32, &pid_u32) ||
+ !dbus_message_iter_close_container(&iter_dict, &iter_variant) ||
+ !dbus_message_iter_close_container(&iter_array, &iter_dict) ||
+ !dbus_message_iter_open_container(&iter_array, DBUS_TYPE_DICT_ENTRY, NULL, &iter_dict) ||
+ !dbus_message_iter_append_basic(&iter_dict, DBUS_TYPE_STRING, &starttime) ||
+ !dbus_message_iter_open_container(&iter_dict, DBUS_TYPE_VARIANT, "t", &iter_variant) ||
+ !dbus_message_iter_append_basic(&iter_variant, DBUS_TYPE_UINT64, &starttime_u64) ||
+ !dbus_message_iter_close_container(&iter_dict, &iter_variant) ||
+ !dbus_message_iter_close_container(&iter_array, &iter_dict) ||
+ !dbus_message_iter_close_container(&iter_struct, &iter_array) ||
+ !dbus_message_iter_close_container(&iter_msg, &iter_struct) ||
+ !dbus_message_iter_append_basic(&iter_msg, DBUS_TYPE_STRING, &action) ||
+ !dbus_message_iter_open_container(&iter_msg, DBUS_TYPE_ARRAY, "{ss}", &iter_array) ||
+ !dbus_message_iter_close_container(&iter_msg, &iter_array) ||
+ !dbus_message_iter_append_basic(&iter_msg, DBUS_TYPE_UINT32, &flags) ||
+ !dbus_message_iter_append_basic(&iter_msg, DBUS_TYPE_STRING, &cancel_id)) {
+ r = -ENOMEM;
+ goto finish;
+ }
+
+ reply = dbus_connection_send_with_reply_and_block(c, m, -1, error);
+ if (!reply) {
+
+ /* Treat no PK available as access denied */
+ if (dbus_error_has_name(error, DBUS_ERROR_SERVICE_UNKNOWN)) {
+ r = -EACCES;
+ dbus_error_free(error);
+ goto finish;
+ }
+
+ r = -EIO;
+ goto finish;
+ }
+
+ if (!dbus_message_iter_init(reply, &iter_msg) ||
+ dbus_message_iter_get_arg_type(&iter_msg) != DBUS_TYPE_STRUCT) {
+ r = -EIO;
+ goto finish;
+ }
+
+ dbus_message_iter_recurse(&iter_msg, &iter_struct);
+
+ if (dbus_message_iter_get_arg_type(&iter_struct) != DBUS_TYPE_BOOLEAN) {
+ r = -EIO;
+ goto finish;
+ }
+
+ dbus_message_iter_get_basic(&iter_struct, &authorized);
+
+ if (!dbus_message_iter_next(&iter_struct) ||
+ dbus_message_iter_get_arg_type(&iter_struct) != DBUS_TYPE_BOOLEAN) {
+ r = -EIO;
+ goto finish;
+ }
+
+ dbus_message_iter_get_basic(&iter_struct, &challenge);
+
+ if (authorized)
+ r = 1;
+ else if (_challenge) {
+ *_challenge = !!challenge;
+ r = 0;
+ } else
+ r = -EPERM;
+
+finish:
+ if (m)
+ dbus_message_unref(m);
+
+ if (reply)
+ dbus_message_unref(reply);
+
+ return r;
+#else
+ return -EPERM;
+#endif
+}
diff --git a/src/shared/polkit.h b/src/shared/polkit.h
new file mode 100644
index 0000000000..6255d1f617
--- /dev/null
+++ b/src/shared/polkit.h
@@ -0,0 +1,33 @@
+/*-*- Mode: C; c-basic-offset: 8; indent-tabs-mode: nil -*-*/
+
+#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 <dbus/dbus.h>
+
+int verify_polkit(
+ DBusConnection *c,
+ DBusMessage *request,
+ const char *action,
+ bool interactive,
+ bool *challenge,
+ DBusError *error);
diff --git a/src/shared/prioq.c b/src/shared/prioq.c
new file mode 100644
index 0000000000..2d166360aa
--- /dev/null
+++ b/src/shared/prioq.c
@@ -0,0 +1,305 @@
+/*-*- Mode: C; c-basic-offset: 8; indent-tabs-mode: nil -*-*/
+
+/***
+ 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 "util.h"
+#include "prioq.h"
+
+struct prioq_item {
+ void *data;
+ unsigned *idx;
+};
+
+struct Prioq {
+ compare_func_t compare_func;
+ unsigned n_items, n_allocated;
+
+ struct prioq_item *items;
+};
+
+Prioq *prioq_new(compare_func_t compare_func) {
+ Prioq *q;
+
+ q = new0(Prioq, 1);
+ if (!q)
+ return q;
+
+ q->compare_func = compare_func;
+ return q;
+}
+
+void prioq_free(Prioq *q) {
+ if (!q)
+ return;
+
+ free(q->items);
+ free(q);
+}
+
+int prioq_ensure_allocated(Prioq **q, compare_func_t compare_func) {
+ assert(q);
+
+ if (*q)
+ return 0;
+
+ *q = prioq_new(compare_func);
+ if (!*q)
+ return -ENOMEM;
+
+ return 0;
+}
+
+static void swap(Prioq *q, unsigned j, unsigned k) {
+ void *saved_data;
+ unsigned *saved_idx;
+
+ assert(q);
+ assert(j < q->n_items);
+ assert(k < q->n_items);
+
+ assert(!q->items[j].idx || *(q->items[j].idx) == j);
+ assert(!q->items[k].idx || *(q->items[k].idx) == k);
+
+ saved_data = q->items[j].data;
+ saved_idx = q->items[j].idx;
+ q->items[j].data = q->items[k].data;
+ q->items[j].idx = q->items[k].idx;
+ q->items[k].data = saved_data;
+ q->items[k].idx = saved_idx;
+
+ if (q->items[j].idx)
+ *q->items[j].idx = j;
+
+ if (q->items[k].idx)
+ *q->items[k].idx = k;
+}
+
+static unsigned shuffle_up(Prioq *q, unsigned idx) {
+ assert(q);
+
+ while (idx > 0) {
+ unsigned k;
+
+ k = (idx-1)/2;
+
+ if (q->compare_func(q->items[k].data, q->items[idx].data) < 0)
+ break;
+
+ swap(q, idx, k);
+ idx = k;
+ }
+
+ return idx;
+}
+
+static unsigned shuffle_down(Prioq *q, unsigned idx) {
+ assert(q);
+
+ for (;;) {
+ unsigned j, k, s;
+
+ k = (idx+1)*2; /* right child */
+ j = k-1; /* left child */
+
+ if (j >= q->n_items)
+ break;
+
+ if (q->compare_func(q->items[j].data, q->items[idx].data) < 0)
+
+ /* So our left child is smaller than we are, let's
+ * remember this fact */
+ s = j;
+ else
+ s = idx;
+
+ if (k < q->n_items &&
+ q->compare_func(q->items[k].data, q->items[s].data) < 0)
+
+ /* So our right child is smaller than we are, let's
+ * remember this fact */
+ s = k;
+
+ /* s now points to the smallest of the three items */
+
+ if (s == idx)
+ /* No swap necessary, we're done */
+ break;
+
+ swap(q, idx, s);
+ idx = s;
+ }
+
+ return idx;
+}
+
+int prioq_put(Prioq *q, void *data, unsigned *idx) {
+ struct prioq_item *i;
+ unsigned k;
+
+ assert(q);
+
+ if (q->n_items >= q->n_allocated) {
+ unsigned n;
+ struct prioq_item *j;
+
+ n = MAX((q->n_items+1) * 2, 16u);
+ j = realloc(q->items, sizeof(struct prioq_item) * n);
+ if (!j)
+ return -ENOMEM;
+
+ q->items = j;
+ q->n_allocated = n;
+ }
+
+ k = q->n_items++;
+ i = q->items + k;
+ i->data = data;
+ i->idx = idx;
+
+ if (idx)
+ *idx = k;
+
+ shuffle_up(q, k);
+
+ return 0;
+}
+
+static void remove_item(Prioq *q, struct prioq_item *i) {
+ struct prioq_item *l;
+
+ assert(q);
+ assert(i);
+
+ l = q->items + q->n_items - 1;
+
+ if (i == l)
+ /* Last entry, let's just remove it */
+ q->n_items--;
+ else {
+ unsigned k;
+
+ /* Not last entry, let's replace the last entry with
+ * this one, and reshuffle */
+
+ k = i - q->items;
+
+ i->data = l->data;
+ i->idx = l->idx;
+ if (i->idx)
+ *i->idx = k;
+ q->n_items--;
+
+ k = shuffle_down(q, k);
+ shuffle_up(q, k);
+ }
+}
+
+_pure_ static struct prioq_item* find_item(Prioq *q, void *data, unsigned *idx) {
+ struct prioq_item *i;
+
+ assert(q);
+
+ if (idx) {
+ if (*idx > q->n_items)
+ return NULL;
+
+ i = q->items + *idx;
+ if (i->data != data)
+ return NULL;
+
+ return i;
+ } else {
+ for (i = q->items; i < q->items + q->n_items; i++)
+ if (i->data == data)
+ return i;
+ return NULL;
+ }
+}
+
+int prioq_remove(Prioq *q, void *data, unsigned *idx) {
+ struct prioq_item *i;
+
+ if (!q)
+ return 0;
+
+ i = find_item(q, data, idx);
+ if (!i)
+ return 0;
+
+ remove_item(q, i);
+ return 1;
+}
+
+int prioq_reshuffle(Prioq *q, void *data, unsigned *idx) {
+ struct prioq_item *i;
+ unsigned k;
+
+ assert(q);
+
+ i = find_item(q, data, idx);
+ if (!i)
+ return 0;
+
+ k = i - q->items;
+ k = shuffle_down(q, k);
+ shuffle_up(q, k);
+ return 1;
+}
+
+void *prioq_peek(Prioq *q) {
+
+ if (!q)
+ return NULL;
+
+ if (q->n_items <= 0)
+ return NULL;
+
+ return q->items[0].data;
+}
+
+void *prioq_pop(Prioq *q) {
+ void *data;
+
+ if (!q)
+ return NULL;
+
+ if (q->n_items <= 0)
+ return NULL;
+
+ data = q->items[0].data;
+ remove_item(q, q->items);
+ return data;
+}
+
+unsigned prioq_size(Prioq *q) {
+
+ if (!q)
+ return 0;
+
+ return q->n_items;
+
+}
+bool prioq_isempty(Prioq *q) {
+
+ if (!q)
+ return true;
+
+ return q->n_items <= 0;
+}
diff --git a/src/shared/prioq.h b/src/shared/prioq.h
new file mode 100644
index 0000000000..4a206a3e59
--- /dev/null
+++ b/src/shared/prioq.h
@@ -0,0 +1,40 @@
+/*-*- Mode: C; c-basic-offset: 8; indent-tabs-mode: nil -*-*/
+
+#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 "hashmap.h"
+
+typedef struct Prioq Prioq;
+
+Prioq *prioq_new(compare_func_t compare);
+void prioq_free(Prioq *q);
+int prioq_ensure_allocated(Prioq **q, compare_func_t compare_func);
+
+int prioq_put(Prioq *q, void *data, unsigned *idx);
+int prioq_remove(Prioq *q, void *data, unsigned *idx);
+int prioq_reshuffle(Prioq *q, void *data, unsigned *idx);
+
+void *prioq_peek(Prioq *q) _pure_;
+void *prioq_pop(Prioq *q);
+
+unsigned prioq_size(Prioq *q) _pure_;
+bool prioq_isempty(Prioq *q) _pure_;
diff --git a/src/shared/ratelimit.c b/src/shared/ratelimit.c
new file mode 100644
index 0000000000..01b62b7b38
--- /dev/null
+++ b/src/shared/ratelimit.c
@@ -0,0 +1,57 @@
+/*-*- Mode: C; c-basic-offset: 8; indent-tabs-mode: nil -*-*/
+
+/***
+ This file is part of systemd.
+
+ Copyright 2010 Lennart Poettering
+
+ systemd is free software; you can redistribute it and/or modify it
+ under the terms of the GNU Lesser General Public License as published by
+ the Free Software Foundation; either version 2.1 of the License, or
+ (at your option) any later version.
+
+ systemd is distributed in the hope that it will be useful, but
+ WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ Lesser General 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 <assert.h>
+
+#include "ratelimit.h"
+#include "log.h"
+
+/* Modelled after Linux' lib/ratelimit.c by Dave Young
+ * <hidave.darkstar@gmail.com>, which is licensed GPLv2. */
+
+bool ratelimit_test(RateLimit *r) {
+ usec_t ts;
+
+ assert(r);
+
+ if (r->interval <= 0 || r->burst <= 0)
+ return true;
+
+ ts = now(CLOCK_MONOTONIC);
+
+ if (r->begin <= 0 ||
+ r->begin + r->interval < ts) {
+ r->begin = ts;
+
+ /* Reset counter */
+ r->num = 0;
+ goto good;
+ }
+
+ if (r->num < r->burst)
+ goto good;
+
+ return false;
+
+good:
+ r->num++;
+ return true;
+}
diff --git a/src/shared/ratelimit.h b/src/shared/ratelimit.h
new file mode 100644
index 0000000000..58efca7df1
--- /dev/null
+++ b/src/shared/ratelimit.h
@@ -0,0 +1,57 @@
+/*-*- Mode: C; c-basic-offset: 8; indent-tabs-mode: nil -*-*/
+
+#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 "util.h"
+
+typedef struct RateLimit {
+ usec_t interval;
+ usec_t begin;
+ unsigned burst;
+ unsigned num;
+} RateLimit;
+
+#define RATELIMIT_DEFINE(_name, _interval, _burst) \
+ RateLimit _name = { \
+ .interval = (_interval), \
+ .burst = (_burst), \
+ .num = 0, \
+ .begin = 0 \
+ }
+
+#define RATELIMIT_INIT(v, _interval, _burst) \
+ do { \
+ RateLimit *_r = &(v); \
+ _r->interval = (_interval); \
+ _r->burst = (_burst); \
+ _r->num = 0; \
+ _r->begin = 0; \
+ } while (false)
+
+#define RATELIMIT_RESET(v) \
+ do { \
+ RateLimit *_r = &(v); \
+ _r->num = 0; \
+ _r->begin = 0; \
+ } while (false)
+
+bool ratelimit_test(RateLimit *r);
diff --git a/src/shared/replace-var.c b/src/shared/replace-var.c
new file mode 100644
index 0000000000..e11c57a43d
--- /dev/null
+++ b/src/shared/replace-var.c
@@ -0,0 +1,110 @@
+/*-*- Mode: C; c-basic-offset: 8; indent-tabs-mode: nil -*-*/
+
+/***
+ This file is part of systemd.
+
+ Copyright 2012 Lennart Poettering
+
+ systemd is free software; you can redistribute it and/or modify it
+ under the terms of the GNU Lesser General Public License as published by
+ the Free Software Foundation; either version 2.1 of the License, or
+ (at your option) any later version.
+
+ systemd is distributed in the hope that it will be useful, but
+ WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ Lesser General 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 <string.h>
+
+#include "macro.h"
+#include "util.h"
+#include "replace-var.h"
+
+/*
+ * Generic infrastructure for replacing @FOO@ style variables in
+ * strings. Will call a callback for each replacement.
+ */
+
+static int get_variable(const char *b, char **r) {
+ size_t k;
+ char *t;
+
+ assert(b);
+ assert(r);
+
+ if (*b != '@')
+ return 0;
+
+ k = strspn(b + 1, "ABCDEFGHIJKLMNOPQRSTUVWXYZ_");
+ if (k <= 0 || b[k+1] != '@')
+ return 0;
+
+ t = strndup(b + 1, k);
+ if (!t)
+ return -ENOMEM;
+
+ *r = t;
+ return 1;
+}
+
+char *replace_var(const char *text, char *(*lookup)(const char *variable, void*userdata), void *userdata) {
+ char *r, *t;
+ const char *f;
+ size_t l;
+
+ assert(text);
+ assert(lookup);
+
+ l = strlen(text);
+ r = new(char, l+1);
+ if (!r)
+ return NULL;
+
+ f = text;
+ t = r;
+ while (*f) {
+ _cleanup_free_ char *v = NULL, *n = NULL;
+ char *a;
+ int k;
+ size_t skip, d, nl;
+
+ k = get_variable(f, &v);
+ if (k < 0)
+ goto oom;
+ if (k == 0) {
+ *(t++) = *(f++);
+ continue;
+ }
+
+ n = lookup(v, userdata);
+ if (!n)
+ goto oom;
+
+ skip = strlen(v) + 2;
+
+ d = t - r;
+ nl = l - skip + strlen(n);
+ a = realloc(r, nl + 1);
+ if (!a)
+ goto oom;
+
+ l = nl;
+ r = a;
+ t = r + d;
+
+ t = stpcpy(t, n);
+ f += skip;
+ }
+
+ *t = 0;
+ return r;
+
+oom:
+ free(r);
+ return NULL;
+}
diff --git a/src/shared/replace-var.h b/src/shared/replace-var.h
new file mode 100644
index 0000000000..7eaee93a3e
--- /dev/null
+++ b/src/shared/replace-var.h
@@ -0,0 +1,24 @@
+/*-*- Mode: C; c-basic-offset: 8; indent-tabs-mode: nil -*-*/
+
+#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/>.
+***/
+
+char *replace_var(const char *text, char *(*lookup)(const char *variable, void*userdata), void *userdata);
diff --git a/src/shared/selinux-util.c b/src/shared/selinux-util.c
new file mode 100644
index 0000000000..ff3375607f
--- /dev/null
+++ b/src/shared/selinux-util.c
@@ -0,0 +1,42 @@
+/*-*- Mode: C; c-basic-offset: 8; indent-tabs-mode: nil -*-*/
+
+/***
+ This file is part 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 "selinux-util.h"
+
+#ifdef HAVE_SELINUX
+
+#include <selinux/selinux.h>
+
+static int use_selinux_cached = -1;
+
+bool use_selinux(void) {
+
+ if (use_selinux_cached < 0)
+ use_selinux_cached = is_selinux_enabled() > 0;
+
+ return use_selinux_cached;
+}
+
+void retest_selinux(void) {
+ use_selinux_cached = -1;
+}
+
+#endif
diff --git a/src/shared/selinux-util.h b/src/shared/selinux-util.h
new file mode 100644
index 0000000000..4b81202741
--- /dev/null
+++ b/src/shared/selinux-util.h
@@ -0,0 +1,27 @@
+/*-*- Mode: C; c-basic-offset: 8; indent-tabs-mode: nil -*-*/
+
+#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>
+
+bool use_selinux(void);
+void retest_selinux(void);
diff --git a/src/shared/set.c b/src/shared/set.c
new file mode 100644
index 0000000000..c338dc3a44
--- /dev/null
+++ b/src/shared/set.c
@@ -0,0 +1,137 @@
+/*-*- Mode: C; c-basic-offset: 8; indent-tabs-mode: nil -*-*/
+
+/***
+ This file is part of systemd.
+
+ Copyright 2010 Lennart Poettering
+
+ systemd is free software; you can redistribute it and/or modify it
+ under the terms of the GNU Lesser General Public License as published by
+ the Free Software Foundation; either version 2.1 of the License, or
+ (at your option) any later version.
+
+ systemd is distributed in the hope that it will be useful, but
+ WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ Lesser General 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 <stdlib.h>
+
+#include "set.h"
+#include "hashmap.h"
+
+#define MAKE_SET(h) ((Set*) (h))
+#define MAKE_HASHMAP(s) ((Hashmap*) (s))
+
+/* For now this is not much more than a wrapper around a hashmap */
+
+Set *set_new(hash_func_t hash_func, compare_func_t compare_func) {
+ return MAKE_SET(hashmap_new(hash_func, compare_func));
+}
+
+void set_free(Set* s) {
+ hashmap_free(MAKE_HASHMAP(s));
+}
+
+void set_free_free(Set *s) {
+ hashmap_free_free(MAKE_HASHMAP(s));
+}
+
+int set_ensure_allocated(Set **s, hash_func_t hash_func, compare_func_t compare_func) {
+ return hashmap_ensure_allocated((Hashmap**) s, hash_func, compare_func);
+}
+
+int set_put(Set *s, void *value) {
+ return hashmap_put(MAKE_HASHMAP(s), value, value);
+}
+
+int set_consume(Set *s, void *value) {
+ int r = set_put(s, value);
+ if (r < 0)
+ free(value);
+ return r;
+}
+
+int set_replace(Set *s, void *value) {
+ return hashmap_replace(MAKE_HASHMAP(s), value, value);
+}
+
+void *set_get(Set *s, void *value) {
+ return hashmap_get(MAKE_HASHMAP(s), value);
+}
+
+bool set_contains(Set *s, void *value) {
+ return hashmap_contains(MAKE_HASHMAP(s), value);
+}
+
+void *set_remove(Set *s, void *value) {
+ return hashmap_remove(MAKE_HASHMAP(s), value);
+}
+
+int set_remove_and_put(Set *s, void *old_value, void *new_value) {
+ return hashmap_remove_and_put(MAKE_HASHMAP(s), old_value, new_value, new_value);
+}
+
+unsigned set_size(Set *s) {
+ return hashmap_size(MAKE_HASHMAP(s));
+}
+
+bool set_isempty(Set *s) {
+ return hashmap_isempty(MAKE_HASHMAP(s));
+}
+
+void *set_iterate(Set *s, Iterator *i) {
+ return hashmap_iterate(MAKE_HASHMAP(s), i, NULL);
+}
+
+void *set_iterate_backwards(Set *s, Iterator *i) {
+ return hashmap_iterate_backwards(MAKE_HASHMAP(s), i, NULL);
+}
+
+void *set_iterate_skip(Set *s, void *value, Iterator *i) {
+ return hashmap_iterate_skip(MAKE_HASHMAP(s), value, i);
+}
+
+void *set_steal_first(Set *s) {
+ return hashmap_steal_first(MAKE_HASHMAP(s));
+}
+
+void* set_first(Set *s) {
+ return hashmap_first(MAKE_HASHMAP(s));
+}
+
+void* set_last(Set *s) {
+ return hashmap_last(MAKE_HASHMAP(s));
+}
+
+int set_merge(Set *s, Set *other) {
+ return hashmap_merge(MAKE_HASHMAP(s), MAKE_HASHMAP(other));
+}
+
+void set_move(Set *s, Set *other) {
+ return hashmap_move(MAKE_HASHMAP(s), MAKE_HASHMAP(other));
+}
+
+int set_move_one(Set *s, Set *other, void *value) {
+ return hashmap_move_one(MAKE_HASHMAP(s), MAKE_HASHMAP(other), value);
+}
+
+Set* set_copy(Set *s) {
+ return MAKE_SET(hashmap_copy(MAKE_HASHMAP(s)));
+}
+
+void set_clear(Set *s) {
+ hashmap_clear(MAKE_HASHMAP(s));
+}
+
+void set_clear_free(Set *s) {
+ hashmap_clear_free(MAKE_HASHMAP(s));
+}
+
+char **set_get_strv(Set *s) {
+ return hashmap_get_strv(MAKE_HASHMAP(s));
+}
diff --git a/src/shared/set.h b/src/shared/set.h
new file mode 100644
index 0000000000..e5d46e9a8f
--- /dev/null
+++ b/src/shared/set.h
@@ -0,0 +1,83 @@
+/*-*- Mode: C; c-basic-offset: 8; indent-tabs-mode: nil -*-*/
+
+#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/>.
+***/
+
+/* Pretty straightforward set implementation. Internally based on the
+ * hashmap. That means that as a minor optimization a NULL set
+ * object will be treated as empty set for all read
+ * operations. That way it is not necessary to instantiate an object
+ * for each set use. */
+
+#include "hashmap.h"
+
+typedef struct Set Set;
+
+Set *set_new(hash_func_t hash_func, compare_func_t compare_func);
+void set_free(Set* s);
+static inline void set_freep(Set **s) {
+ set_free(*s);
+}
+
+void set_free_free(Set *s);
+static inline void set_free_freep(Set **s) {
+ set_free_free(*s);
+}
+
+Set* set_copy(Set *s);
+int set_ensure_allocated(Set **s, hash_func_t hash_func, compare_func_t compare_func);
+
+int set_put(Set *s, void *value);
+int set_consume(Set *s, void *value);
+int set_replace(Set *s, void *value);
+void *set_get(Set *s, void *value);
+bool set_contains(Set *s, void *value);
+void *set_remove(Set *s, void *value);
+int set_remove_and_put(Set *s, void *old_value, void *new_value);
+
+int set_merge(Set *s, Set *other);
+void set_move(Set *s, Set *other);
+int set_move_one(Set *s, Set *other, void *value);
+
+unsigned set_size(Set *s);
+bool set_isempty(Set *s);
+
+void *set_iterate(Set *s, Iterator *i);
+void *set_iterate_backwards(Set *s, Iterator *i);
+void *set_iterate_skip(Set *s, void *value, Iterator *i);
+
+void set_clear(Set *s);
+void set_clear_free(Set *s);
+
+void *set_steal_first(Set *s);
+void* set_first(Set *s);
+void* set_last(Set *s);
+
+char **set_get_strv(Set *s);
+
+#define SET_FOREACH(e, s, i) \
+ for ((i) = ITERATOR_FIRST, (e) = set_iterate((s), &(i)); (e); (e) = set_iterate((s), &(i)))
+
+#define SET_FOREACH_BACKWARDS(e, s, i) \
+ for ((i) = ITERATOR_LAST, (e) = set_iterate_backwards((s), &(i)); (e); (e) = set_iterate_backwards((s), &(i)))
+
+#define _cleanup_set_free_ _cleanup_(set_freep)
+#define _cleanup_set_free_free_ _cleanup_(set_free_freep)
diff --git a/src/shared/sleep-config.c b/src/shared/sleep-config.c
new file mode 100644
index 0000000000..cd3238b405
--- /dev/null
+++ b/src/shared/sleep-config.c
@@ -0,0 +1,179 @@
+/*-*- Mode: C; c-basic-offset: 8; indent-tabs-mode: nil -*-*/
+
+/***
+ 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 "conf-parser.h"
+#include "sleep-config.h"
+#include "fileio.h"
+#include "log.h"
+#include "strv.h"
+#include "util.h"
+
+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;
+
+ 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 },
+ {}};
+
+ int r;
+ FILE _cleanup_fclose_ *f;
+
+ f = fopen(PKGSYSCONFDIR "/sleep.conf", "re");
+ if (!f)
+ log_full(errno == ENOENT ? LOG_DEBUG: LOG_WARNING,
+ "Failed to open configuration file " PKGSYSCONFDIR "/sleep.conf: %m");
+ else {
+ r = config_parse(NULL, PKGSYSCONFDIR "/sleep.conf", f, "Sleep\0",
+ config_item_table_lookup, (void*) items, false, false, NULL);
+ if (r < 0)
+ log_warning("Failed to parse configuration file: %s", strerror(-r));
+ }
+
+ if (streq(verb, "suspend")) {
+ /* empty by default */
+ *modes = suspend_mode;
+
+ if (suspend_state)
+ *states = suspend_state;
+ else
+ *states = strv_split_nulstr("mem\0standby\0freeze\0");
+
+ suspend_mode = suspend_state = NULL;
+ } else if (streq(verb, "hibernate")) {
+ if (hibernate_mode)
+ *modes = hibernate_mode;
+ else
+ *modes = strv_split_nulstr("platform\0shutdown\0");
+
+ if (hibernate_state)
+ *states = hibernate_state;
+ else
+ *states = strv_split_nulstr("disk\0");
+
+ hibernate_mode = hibernate_state = NULL;
+ } else if (streq(verb, "hybrid-sleep")) {
+ if (hybrid_mode)
+ *modes = hybrid_mode;
+ else
+ *modes = strv_split_nulstr("suspend\0platform\0shutdown\0");
+
+ if (hybrid_state)
+ *states = hybrid_state;
+ else
+ *states = strv_split_nulstr("disk\0");
+
+ hybrid_mode = hybrid_state = NULL;
+ } else
+ assert_not_reached("what verb");
+
+ if (!modes || !states) {
+ strv_free(*modes);
+ strv_free(*states);
+ return log_oom();
+ }
+
+ return 0;
+}
+
+int can_sleep_state(char **types) {
+ char *w, *state, **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) {
+ size_t l, k;
+
+ k = strlen(*type);
+ FOREACH_WORD_SEPARATOR(w, l, p, WHITESPACE, state)
+ if (l == k && memcmp(w, *type, l) == 0)
+ return true;
+ }
+
+ return false;
+}
+
+int can_sleep_disk(char **types) {
+ char *w, *state, **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) {
+ size_t l, k;
+
+ k = strlen(*type);
+ FOREACH_WORD_SEPARATOR(w, l, p, WHITESPACE, state) {
+ if (l == k && memcmp(w, *type, l) == 0)
+ return true;
+
+ if (l == k + 2 && w[0] == '[' && memcmp(w + 1, *type, l - 2) == 0 && w[l-1] == ']')
+ return true;
+ }
+ }
+
+ return false;
+}
+
+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;
+
+ return can_sleep_state(states) && can_sleep_disk(modes);
+}
diff --git a/src/shared/sleep-config.h b/src/shared/sleep-config.h
new file mode 100644
index 0000000000..51d2dec7b4
--- /dev/null
+++ b/src/shared/sleep-config.h
@@ -0,0 +1,26 @@
+/*-*- Mode: C; c-basic-offset: 8; indent-tabs-mode: nil -*-*/
+
+/***
+ 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/>.
+***/
+
+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/shared/socket-label.c b/src/shared/socket-label.c
new file mode 100644
index 0000000000..ff212de825
--- /dev/null
+++ b/src/shared/socket-label.c
@@ -0,0 +1,143 @@
+/*-*- Mode: C; c-basic-offset: 8; indent-tabs-mode: nil -*-*/
+
+/***
+ This file is part 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 <assert.h>
+#include <string.h>
+#include <unistd.h>
+#include <errno.h>
+#include <stdlib.h>
+#include <arpa/inet.h>
+#include <stdio.h>
+#include <net/if.h>
+#include <sys/types.h>
+#include <sys/stat.h>
+#include <stddef.h>
+#include <sys/ioctl.h>
+
+#include "macro.h"
+#include "util.h"
+#include "mkdir.h"
+#include "socket-util.h"
+#include "missing.h"
+#include "label.h"
+
+int socket_address_listen(
+ const SocketAddress *a,
+ int backlog,
+ SocketAddressBindIPv6Only only,
+ const char *bind_to_device,
+ bool free_bind,
+ bool transparent,
+ mode_t directory_mode,
+ mode_t socket_mode,
+ const char *label,
+ int *ret) {
+
+ int r, fd, one;
+ assert(a);
+ assert(ret);
+
+ if ((r = socket_address_verify(a)) < 0)
+ return r;
+
+ if (socket_address_family(a) == AF_INET6 && !socket_ipv6_is_supported())
+ return -EAFNOSUPPORT;
+
+ r = label_socket_set(label);
+ if (r < 0)
+ return r;
+
+ fd = socket(socket_address_family(a), a->type | SOCK_NONBLOCK | SOCK_CLOEXEC, a->protocol);
+ r = fd < 0 ? -errno : 0;
+
+ label_socket_clear();
+
+ if (r < 0)
+ return r;
+
+ if (socket_address_family(a) == AF_INET6 && only != SOCKET_ADDRESS_DEFAULT) {
+ int flag = only == SOCKET_ADDRESS_IPV6_ONLY;
+
+ if (setsockopt(fd, IPPROTO_IPV6, IPV6_V6ONLY, &flag, sizeof(flag)) < 0)
+ goto fail;
+ }
+
+ if (socket_address_family(a) == AF_INET || socket_address_family(a) == AF_INET6) {
+ if (bind_to_device)
+ if (setsockopt(fd, SOL_SOCKET, SO_BINDTODEVICE, bind_to_device, strlen(bind_to_device)+1) < 0)
+ goto fail;
+
+ if (free_bind) {
+ one = 1;
+ if (setsockopt(fd, IPPROTO_IP, IP_FREEBIND, &one, sizeof(one)) < 0)
+ log_warning("IP_FREEBIND failed: %m");
+ }
+
+ if (transparent) {
+ one = 1;
+ if (setsockopt(fd, IPPROTO_IP, IP_TRANSPARENT, &one, sizeof(one)) < 0)
+ log_warning("IP_TRANSPARENT failed: %m");
+ }
+ }
+
+ one = 1;
+ if (setsockopt(fd, SOL_SOCKET, SO_REUSEADDR, &one, sizeof(one)) < 0)
+ goto fail;
+
+ if (socket_address_family(a) == AF_UNIX && a->sockaddr.un.sun_path[0] != 0) {
+ mode_t old_mask;
+
+ /* Create parents */
+ mkdir_parents_label(a->sockaddr.un.sun_path, directory_mode);
+
+ /* Enforce the right access mode for the socket*/
+ old_mask = umask(~ socket_mode);
+
+ /* Include the original umask in our mask */
+ umask(~socket_mode | old_mask);
+
+ r = label_bind(fd, &a->sockaddr.sa, a->size);
+
+ if (r < 0 && errno == EADDRINUSE) {
+ /* Unlink and try again */
+ unlink(a->sockaddr.un.sun_path);
+ r = bind(fd, &a->sockaddr.sa, a->size);
+ }
+
+ umask(old_mask);
+ } else
+ r = bind(fd, &a->sockaddr.sa, a->size);
+
+ if (r < 0)
+ goto fail;
+
+ if (socket_address_can_accept(a))
+ if (listen(fd, backlog) < 0)
+ goto fail;
+
+ *ret = fd;
+ return 0;
+
+fail:
+ r = -errno;
+ close_nointr_nofail(fd);
+ return r;
+}
diff --git a/src/shared/socket-util.c b/src/shared/socket-util.c
new file mode 100644
index 0000000000..c583d3dfea
--- /dev/null
+++ b/src/shared/socket-util.c
@@ -0,0 +1,635 @@
+/*-*- Mode: C; c-basic-offset: 8; indent-tabs-mode: nil -*-*/
+
+/***
+ This file is part 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 <assert.h>
+#include <string.h>
+#include <unistd.h>
+#include <errno.h>
+#include <stdlib.h>
+#include <arpa/inet.h>
+#include <stdio.h>
+#include <net/if.h>
+#include <sys/types.h>
+#include <sys/stat.h>
+#include <stddef.h>
+#include <sys/ioctl.h>
+
+#include "macro.h"
+#include "util.h"
+#include "mkdir.h"
+#include "path-util.h"
+#include "socket-util.h"
+#include "missing.h"
+#include "fileio.h"
+
+int socket_address_parse(SocketAddress *a, const char *s) {
+ int r;
+ char *e, *n;
+ unsigned u;
+
+ assert(a);
+ assert(s);
+
+ zero(*a);
+ a->type = SOCK_STREAM;
+
+ if (*s == '[') {
+ /* IPv6 in [x:.....:z]:p notation */
+
+ if (!socket_ipv6_is_supported()) {
+ log_warning("Binding to IPv6 address not available since kernel does not support IPv6.");
+ return -EAFNOSUPPORT;
+ }
+
+ if (!(e = strchr(s+1, ']')))
+ return -EINVAL;
+
+ if (!(n = strndup(s+1, e-s-1)))
+ return -ENOMEM;
+
+ errno = 0;
+ if (inet_pton(AF_INET6, n, &a->sockaddr.in6.sin6_addr) <= 0) {
+ free(n);
+ return errno > 0 ? -errno : -EINVAL;
+ }
+
+ free(n);
+
+ e++;
+ if (*e != ':')
+ return -EINVAL;
+
+ e++;
+ if ((r = safe_atou(e, &u)) < 0)
+ return r;
+
+ if (u <= 0 || u > 0xFFFF)
+ return -EINVAL;
+
+ a->sockaddr.in6.sin6_family = AF_INET6;
+ a->sockaddr.in6.sin6_port = htons((uint16_t) u);
+ a->size = sizeof(struct sockaddr_in6);
+
+ } else if (*s == '/') {
+ /* AF_UNIX socket */
+
+ size_t l;
+
+ l = strlen(s);
+ if (l >= sizeof(a->sockaddr.un.sun_path))
+ return -EINVAL;
+
+ a->sockaddr.un.sun_family = AF_UNIX;
+ memcpy(a->sockaddr.un.sun_path, s, l);
+ a->size = offsetof(struct sockaddr_un, sun_path) + l + 1;
+
+ } else if (*s == '@') {
+ /* Abstract AF_UNIX socket */
+ size_t l;
+
+ l = strlen(s+1);
+ if (l >= sizeof(a->sockaddr.un.sun_path) - 1)
+ return -EINVAL;
+
+ a->sockaddr.un.sun_family = AF_UNIX;
+ memcpy(a->sockaddr.un.sun_path+1, s+1, l);
+ a->size = offsetof(struct sockaddr_un, sun_path) + 1 + l;
+
+ } else {
+
+ if ((e = strchr(s, ':'))) {
+
+ if ((r = safe_atou(e+1, &u)) < 0)
+ return r;
+
+ if (u <= 0 || u > 0xFFFF)
+ return -EINVAL;
+
+ if (!(n = strndup(s, e-s)))
+ return -ENOMEM;
+
+ /* IPv4 in w.x.y.z:p notation? */
+ if ((r = inet_pton(AF_INET, n, &a->sockaddr.in4.sin_addr)) < 0) {
+ free(n);
+ return -errno;
+ }
+
+ if (r > 0) {
+ /* Gotcha, it's a traditional IPv4 address */
+ free(n);
+
+ a->sockaddr.in4.sin_family = AF_INET;
+ a->sockaddr.in4.sin_port = htons((uint16_t) u);
+ a->size = sizeof(struct sockaddr_in);
+ } else {
+ unsigned idx;
+
+ if (strlen(n) > IF_NAMESIZE-1) {
+ free(n);
+ return -EINVAL;
+ }
+
+ /* Uh, our last resort, an interface name */
+ idx = if_nametoindex(n);
+ free(n);
+
+ if (idx == 0)
+ return -EINVAL;
+
+ if (!socket_ipv6_is_supported()) {
+ log_warning("Binding to interface is not available since kernel does not support IPv6.");
+ return -EAFNOSUPPORT;
+ }
+
+ a->sockaddr.in6.sin6_family = AF_INET6;
+ a->sockaddr.in6.sin6_port = htons((uint16_t) u);
+ a->sockaddr.in6.sin6_scope_id = idx;
+ a->sockaddr.in6.sin6_addr = in6addr_any;
+ a->size = sizeof(struct sockaddr_in6);
+ }
+ } else {
+
+ /* Just a port */
+ r = safe_atou(s, &u);
+ if (r < 0)
+ return r;
+
+ if (u <= 0 || u > 0xFFFF)
+ return -EINVAL;
+
+ if (socket_ipv6_is_supported()) {
+ a->sockaddr.in6.sin6_family = AF_INET6;
+ a->sockaddr.in6.sin6_port = htons((uint16_t) u);
+ a->sockaddr.in6.sin6_addr = in6addr_any;
+ a->size = sizeof(struct sockaddr_in6);
+ } else {
+ a->sockaddr.in4.sin_family = AF_INET;
+ a->sockaddr.in4.sin_port = htons((uint16_t) u);
+ a->sockaddr.in4.sin_addr.s_addr = INADDR_ANY;
+ a->size = sizeof(struct sockaddr_in);
+ }
+ }
+ }
+
+ return 0;
+}
+
+int socket_address_parse_netlink(SocketAddress *a, const char *s) {
+ int family;
+ unsigned group = 0;
+ _cleanup_free_ char *sfamily = NULL;
+ assert(a);
+ assert(s);
+
+ zero(*a);
+ a->type = SOCK_RAW;
+
+ errno = 0;
+ if (sscanf(s, "%ms %u", &sfamily, &group) < 1)
+ return errno > 0 ? -errno : -EINVAL;
+
+ family = netlink_family_from_string(sfamily);
+ if (family < 0)
+ return -EINVAL;
+
+ a->sockaddr.nl.nl_family = AF_NETLINK;
+ a->sockaddr.nl.nl_groups = group;
+
+ a->type = SOCK_RAW;
+ a->size = sizeof(struct sockaddr_nl);
+ a->protocol = family;
+
+ return 0;
+}
+
+int socket_address_verify(const SocketAddress *a) {
+ assert(a);
+
+ switch (socket_address_family(a)) {
+
+ case AF_INET:
+ if (a->size != sizeof(struct sockaddr_in))
+ return -EINVAL;
+
+ if (a->sockaddr.in4.sin_port == 0)
+ return -EINVAL;
+
+ if (a->type != SOCK_STREAM && a->type != SOCK_DGRAM)
+ return -EINVAL;
+
+ return 0;
+
+ case AF_INET6:
+ if (a->size != sizeof(struct sockaddr_in6))
+ return -EINVAL;
+
+ if (a->sockaddr.in6.sin6_port == 0)
+ return -EINVAL;
+
+ if (a->type != SOCK_STREAM && a->type != SOCK_DGRAM)
+ return -EINVAL;
+
+ return 0;
+
+ case AF_UNIX:
+ if (a->size < offsetof(struct sockaddr_un, sun_path))
+ return -EINVAL;
+
+ if (a->size > offsetof(struct sockaddr_un, sun_path)) {
+
+ if (a->sockaddr.un.sun_path[0] != 0) {
+ char *e;
+
+ /* path */
+ if (!(e = memchr(a->sockaddr.un.sun_path, 0, sizeof(a->sockaddr.un.sun_path))))
+ return -EINVAL;
+
+ if (a->size != offsetof(struct sockaddr_un, sun_path) + (e - a->sockaddr.un.sun_path) + 1)
+ return -EINVAL;
+ }
+ }
+
+ if (a->type != SOCK_STREAM && a->type != SOCK_DGRAM && a->type != SOCK_SEQPACKET)
+ return -EINVAL;
+
+ return 0;
+
+ case AF_NETLINK:
+
+ if (a->size != sizeof(struct sockaddr_nl))
+ return -EINVAL;
+
+ if (a->type != SOCK_RAW && a->type != SOCK_DGRAM)
+ return -EINVAL;
+
+ return 0;
+
+ default:
+ return -EAFNOSUPPORT;
+ }
+}
+
+int socket_address_print(const SocketAddress *a, char **p) {
+ int r;
+ assert(a);
+ assert(p);
+
+ if ((r = socket_address_verify(a)) < 0)
+ return r;
+
+ switch (socket_address_family(a)) {
+
+ case AF_INET: {
+ char *ret;
+
+ if (!(ret = new(char, INET_ADDRSTRLEN+1+5+1)))
+ return -ENOMEM;
+
+ if (!inet_ntop(AF_INET, &a->sockaddr.in4.sin_addr, ret, INET_ADDRSTRLEN)) {
+ free(ret);
+ return -errno;
+ }
+
+ sprintf(strchr(ret, 0), ":%u", ntohs(a->sockaddr.in4.sin_port));
+ *p = ret;
+ return 0;
+ }
+
+ case AF_INET6: {
+ char *ret;
+
+ if (!(ret = new(char, 1+INET6_ADDRSTRLEN+2+5+1)))
+ return -ENOMEM;
+
+ ret[0] = '[';
+ if (!inet_ntop(AF_INET6, &a->sockaddr.in6.sin6_addr, ret+1, INET6_ADDRSTRLEN)) {
+ free(ret);
+ return -errno;
+ }
+
+ sprintf(strchr(ret, 0), "]:%u", ntohs(a->sockaddr.in6.sin6_port));
+ *p = ret;
+ return 0;
+ }
+
+ case AF_UNIX: {
+ char *ret;
+
+ if (a->size <= offsetof(struct sockaddr_un, sun_path)) {
+
+ if (!(ret = strdup("<unnamed>")))
+ return -ENOMEM;
+
+ } else if (a->sockaddr.un.sun_path[0] == 0) {
+ /* abstract */
+
+ /* FIXME: We assume we can print the
+ * socket path here and that it hasn't
+ * more than one NUL byte. That is
+ * actually an invalid assumption */
+
+ if (!(ret = new(char, sizeof(a->sockaddr.un.sun_path)+1)))
+ return -ENOMEM;
+
+ ret[0] = '@';
+ memcpy(ret+1, a->sockaddr.un.sun_path+1, sizeof(a->sockaddr.un.sun_path)-1);
+ ret[sizeof(a->sockaddr.un.sun_path)] = 0;
+
+ } else {
+
+ if (!(ret = strdup(a->sockaddr.un.sun_path)))
+ return -ENOMEM;
+ }
+
+ *p = ret;
+ return 0;
+ }
+
+ case AF_NETLINK: {
+ _cleanup_free_ char *sfamily = NULL;
+
+ r = netlink_family_to_string_alloc(a->protocol, &sfamily);
+ if (r < 0)
+ return r;
+ r = asprintf(p, "%s %u", sfamily, a->sockaddr.nl.nl_groups);
+ if (r < 0)
+ return -ENOMEM;
+
+ return 0;
+ }
+
+ default:
+ return -EINVAL;
+ }
+}
+
+bool socket_address_can_accept(const SocketAddress *a) {
+ assert(a);
+
+ return
+ a->type == SOCK_STREAM ||
+ a->type == SOCK_SEQPACKET;
+}
+
+bool socket_address_equal(const SocketAddress *a, const SocketAddress *b) {
+ assert(a);
+ assert(b);
+
+ /* Invalid addresses are unequal to all */
+ if (socket_address_verify(a) < 0 ||
+ socket_address_verify(b) < 0)
+ return false;
+
+ if (a->type != b->type)
+ return false;
+
+ if (a->size != b->size)
+ return false;
+
+ if (socket_address_family(a) != socket_address_family(b))
+ return false;
+
+ switch (socket_address_family(a)) {
+
+ case AF_INET:
+ if (a->sockaddr.in4.sin_addr.s_addr != b->sockaddr.in4.sin_addr.s_addr)
+ return false;
+
+ if (a->sockaddr.in4.sin_port != b->sockaddr.in4.sin_port)
+ return false;
+
+ break;
+
+ case AF_INET6:
+ if (memcmp(&a->sockaddr.in6.sin6_addr, &b->sockaddr.in6.sin6_addr, sizeof(a->sockaddr.in6.sin6_addr)) != 0)
+ return false;
+
+ if (a->sockaddr.in6.sin6_port != b->sockaddr.in6.sin6_port)
+ return false;
+
+ break;
+
+ case AF_UNIX:
+
+ if ((a->sockaddr.un.sun_path[0] == 0) != (b->sockaddr.un.sun_path[0] == 0))
+ return false;
+
+ if (a->sockaddr.un.sun_path[0]) {
+ if (!strneq(a->sockaddr.un.sun_path, b->sockaddr.un.sun_path, sizeof(a->sockaddr.un.sun_path)))
+ return false;
+ } else {
+ if (memcmp(a->sockaddr.un.sun_path, b->sockaddr.un.sun_path, a->size) != 0)
+ return false;
+ }
+
+ break;
+
+ case AF_NETLINK:
+
+ if (a->protocol != b->protocol)
+ return false;
+
+ if (a->sockaddr.nl.nl_groups != b->sockaddr.nl.nl_groups)
+ return false;
+
+ break;
+
+ default:
+ /* Cannot compare, so we assume the addresses are different */
+ return false;
+ }
+
+ return true;
+}
+
+bool socket_address_is(const SocketAddress *a, const char *s, int type) {
+ struct SocketAddress b;
+
+ assert(a);
+ assert(s);
+
+ if (socket_address_parse(&b, s) < 0)
+ return false;
+
+ b.type = type;
+
+ return socket_address_equal(a, &b);
+}
+
+bool socket_address_is_netlink(const SocketAddress *a, const char *s) {
+ struct SocketAddress b;
+
+ assert(a);
+ assert(s);
+
+ if (socket_address_parse_netlink(&b, s) < 0)
+ return false;
+
+ return socket_address_equal(a, &b);
+}
+
+bool socket_address_needs_mount(const SocketAddress *a, const char *prefix) {
+ assert(a);
+
+ if (socket_address_family(a) != AF_UNIX)
+ return false;
+
+ if (a->sockaddr.un.sun_path[0] == 0)
+ return false;
+
+ return path_startswith(a->sockaddr.un.sun_path, prefix);
+}
+
+bool socket_ipv6_is_supported(void) {
+ char *l = 0;
+ bool enabled;
+
+ if (access("/sys/module/ipv6", F_OK) != 0)
+ return 0;
+
+ /* If we can't check "disable" parameter, assume enabled */
+ if (read_one_line_file("/sys/module/ipv6/parameters/disable", &l) < 0)
+ return 1;
+
+ /* If module was loaded with disable=1 no IPv6 available */
+ enabled = l[0] == '0';
+ free(l);
+
+ return enabled;
+}
+
+bool socket_address_matches_fd(const SocketAddress *a, int fd) {
+ union sockaddr_union sa;
+ socklen_t salen = sizeof(sa), solen;
+ int protocol, type;
+
+ assert(a);
+ assert(fd >= 0);
+
+ if (getsockname(fd, &sa.sa, &salen) < 0)
+ return false;
+
+ if (sa.sa.sa_family != a->sockaddr.sa.sa_family)
+ return false;
+
+ solen = sizeof(type);
+ if (getsockopt(fd, SOL_SOCKET, SO_TYPE, &type, &solen) < 0)
+ return false;
+
+ if (type != a->type)
+ return false;
+
+ if (a->protocol != 0) {
+ solen = sizeof(protocol);
+ if (getsockopt(fd, SOL_SOCKET, SO_PROTOCOL, &protocol, &solen) < 0)
+ return false;
+
+ if (protocol != a->protocol)
+ return false;
+ }
+
+ switch (sa.sa.sa_family) {
+
+ case AF_INET:
+ return sa.in4.sin_port == a->sockaddr.in4.sin_port &&
+ sa.in4.sin_addr.s_addr == a->sockaddr.in4.sin_addr.s_addr;
+
+ case AF_INET6:
+ return sa.in6.sin6_port == a->sockaddr.in6.sin6_port &&
+ memcmp(&sa.in6.sin6_addr, &a->sockaddr.in6.sin6_addr, sizeof(struct in6_addr)) == 0;
+
+ case AF_UNIX:
+ return salen == a->size &&
+ memcmp(sa.un.sun_path, a->sockaddr.un.sun_path, salen - offsetof(struct sockaddr_un, sun_path)) == 0;
+
+ }
+
+ return false;
+}
+
+int make_socket_fd(const char* address, int flags) {
+ SocketAddress a;
+ int fd, r;
+ _cleanup_free_ char *p = NULL;
+
+ r = socket_address_parse(&a, address);
+ if (r < 0) {
+ log_error("failed to parse socket: %s", strerror(-r));
+ return r;
+ }
+
+ fd = socket(socket_address_family(&a), flags, 0);
+ if (fd < 0) {
+ log_error("socket(): %m");
+ return -errno;
+ }
+
+ r = socket_address_print(&a, &p);
+ if (r < 0) {
+ log_error("socket_address_print(): %s", strerror(-r));
+ return r;
+ }
+ log_info("Listening on %s", p);
+
+ r = bind(fd, &a.sockaddr.sa, a.size);
+ if (r < 0) {
+ log_error("bind to %s: %m", address);
+ return -errno;
+ }
+
+ r = listen(fd, SOMAXCONN);
+ if (r < 0) {
+ log_error("listen on %s: %m", address);
+ return -errno;
+ }
+
+ return fd;
+}
+
+static const char* const netlink_family_table[] = {
+ [NETLINK_ROUTE] = "route",
+ [NETLINK_FIREWALL] = "firewall",
+ [NETLINK_INET_DIAG] = "inet-diag",
+ [NETLINK_NFLOG] = "nflog",
+ [NETLINK_XFRM] = "xfrm",
+ [NETLINK_SELINUX] = "selinux",
+ [NETLINK_ISCSI] = "iscsi",
+ [NETLINK_AUDIT] = "audit",
+ [NETLINK_FIB_LOOKUP] = "fib-lookup",
+ [NETLINK_CONNECTOR] = "connector",
+ [NETLINK_NETFILTER] = "netfilter",
+ [NETLINK_IP6_FW] = "ip6-fw",
+ [NETLINK_DNRTMSG] = "dnrtmsg",
+ [NETLINK_KOBJECT_UEVENT] = "kobject-uevent",
+ [NETLINK_GENERIC] = "generic",
+ [NETLINK_SCSITRANSPORT] = "scsitransport",
+ [NETLINK_ECRYPTFS] = "ecryptfs"
+};
+
+DEFINE_STRING_TABLE_LOOKUP_WITH_FALLBACK(netlink_family, int, INT_MAX);
+
+static const char* const socket_address_bind_ipv6_only_table[_SOCKET_ADDRESS_BIND_IPV6_ONLY_MAX] = {
+ [SOCKET_ADDRESS_DEFAULT] = "default",
+ [SOCKET_ADDRESS_BOTH] = "both",
+ [SOCKET_ADDRESS_IPV6_ONLY] = "ipv6-only"
+};
+
+DEFINE_STRING_TABLE_LOOKUP(socket_address_bind_ipv6_only, SocketAddressBindIPv6Only);
diff --git a/src/shared/socket-util.h b/src/shared/socket-util.h
new file mode 100644
index 0000000000..7829a337fc
--- /dev/null
+++ b/src/shared/socket-util.h
@@ -0,0 +1,103 @@
+/*-*- Mode: C; c-basic-offset: 8; indent-tabs-mode: nil -*-*/
+
+#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 <sys/socket.h>
+#include <netinet/in.h>
+#include <sys/un.h>
+#include <net/if.h>
+#include <asm/types.h>
+#include <linux/netlink.h>
+
+#include "macro.h"
+#include "util.h"
+
+union sockaddr_union {
+ struct sockaddr sa;
+ struct sockaddr_in in4;
+ struct sockaddr_in6 in6;
+ struct sockaddr_un un;
+ struct sockaddr_nl nl;
+ struct sockaddr_storage storage;
+};
+
+typedef struct SocketAddress {
+ union sockaddr_union sockaddr;
+
+ /* We store the size here explicitly due to the weird
+ * sockaddr_un semantics for abstract sockets */
+ socklen_t size;
+
+ /* Socket type, i.e. SOCK_STREAM, SOCK_DGRAM, ... */
+ int type;
+
+ /* Socket protocol, IPPROTO_xxx, usually 0, except for netlink */
+ int protocol;
+} SocketAddress;
+
+typedef enum SocketAddressBindIPv6Only {
+ SOCKET_ADDRESS_DEFAULT,
+ SOCKET_ADDRESS_BOTH,
+ SOCKET_ADDRESS_IPV6_ONLY,
+ _SOCKET_ADDRESS_BIND_IPV6_ONLY_MAX,
+ _SOCKET_ADDRESS_BIND_IPV6_ONLY_INVALID = -1
+} SocketAddressBindIPv6Only;
+
+#define socket_address_family(a) ((a)->sockaddr.sa.sa_family)
+
+int socket_address_parse(SocketAddress *a, const char *s);
+int socket_address_parse_netlink(SocketAddress *a, const char *s);
+int socket_address_print(const SocketAddress *a, char **p);
+int socket_address_verify(const SocketAddress *a) _pure_;
+
+bool socket_address_can_accept(const SocketAddress *a) _pure_;
+
+int socket_address_listen(
+ const SocketAddress *a,
+ int backlog,
+ SocketAddressBindIPv6Only only,
+ const char *bind_to_device,
+ bool free_bind,
+ bool transparent,
+ mode_t directory_mode,
+ mode_t socket_mode,
+ const char *label,
+ int *ret);
+
+bool socket_address_is(const SocketAddress *a, const char *s, int type);
+bool socket_address_is_netlink(const SocketAddress *a, const char *s);
+
+bool socket_address_matches_fd(const SocketAddress *a, int fd);
+
+int make_socket_fd(const char* address, int flags);
+
+bool socket_address_equal(const SocketAddress *a, const SocketAddress *b) _pure_;
+
+bool socket_address_needs_mount(const SocketAddress *a, const char *prefix);
+
+const char* socket_address_bind_ipv6_only_to_string(SocketAddressBindIPv6Only b) _const_;
+SocketAddressBindIPv6Only socket_address_bind_ipv6_only_from_string(const char *s) _pure_;
+
+int netlink_family_to_string_alloc(int b, char **s);
+int netlink_family_from_string(const char *s);
+
+bool socket_ipv6_is_supported(void);
diff --git a/src/shared/sparse-endian.h b/src/shared/sparse-endian.h
new file mode 100644
index 0000000000..eb4dbf3615
--- /dev/null
+++ b/src/shared/sparse-endian.h
@@ -0,0 +1,87 @@
+/* Copyright (c) 2012 Josh Triplett <josh@joshtriplett.org>
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to
+ * deal in the Software without restriction, including without limitation the
+ * rights to use, copy, modify, merge, publish, distribute, sublicense, and/or
+ * sell copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
+ * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
+ * IN THE SOFTWARE.
+ */
+#ifndef SPARSE_ENDIAN_H
+#define SPARSE_ENDIAN_H
+
+#include <endian.h>
+#include <stdint.h>
+
+#ifdef __CHECKER__
+#define __bitwise __attribute__((bitwise))
+#define __force __attribute__((force))
+#else
+#define __bitwise
+#define __force
+#endif
+
+typedef uint16_t __bitwise le16_t;
+typedef uint16_t __bitwise be16_t;
+typedef uint32_t __bitwise le32_t;
+typedef uint32_t __bitwise be32_t;
+typedef uint64_t __bitwise le64_t;
+typedef uint64_t __bitwise be64_t;
+
+#undef htobe16
+#undef htole16
+#undef be16toh
+#undef le16toh
+#undef htobe32
+#undef htole32
+#undef be32toh
+#undef le32toh
+#undef htobe64
+#undef htole64
+#undef be64toh
+#undef le64toh
+
+#if __BYTE_ORDER == __LITTLE_ENDIAN
+#define bswap_16_on_le(x) __bswap_16(x)
+#define bswap_32_on_le(x) __bswap_32(x)
+#define bswap_64_on_le(x) __bswap_64(x)
+#define bswap_16_on_be(x) (x)
+#define bswap_32_on_be(x) (x)
+#define bswap_64_on_be(x) (x)
+#elif __BYTE_ORDER == __BIG_ENDIAN
+#define bswap_16_on_le(x) (x)
+#define bswap_32_on_le(x) (x)
+#define bswap_64_on_le(x) (x)
+#define bswap_16_on_be(x) __bswap_16(x)
+#define bswap_32_on_be(x) __bswap_32(x)
+#define bswap_64_on_be(x) __bswap_64(x)
+#endif
+
+static inline le16_t htole16(uint16_t value) { return (le16_t __force) bswap_16_on_be(value); }
+static inline le32_t htole32(uint32_t value) { return (le32_t __force) bswap_32_on_be(value); }
+static inline le64_t htole64(uint64_t value) { return (le64_t __force) bswap_64_on_be(value); }
+
+static inline be16_t htobe16(uint16_t value) { return (be16_t __force) bswap_16_on_le(value); }
+static inline be32_t htobe32(uint32_t value) { return (be32_t __force) bswap_32_on_le(value); }
+static inline be64_t htobe64(uint64_t value) { return (be64_t __force) bswap_64_on_le(value); }
+
+static inline uint16_t le16toh(le16_t value) { return bswap_16_on_be((uint16_t __force)value); }
+static inline uint32_t le32toh(le32_t value) { return bswap_32_on_be((uint32_t __force)value); }
+static inline uint64_t le64toh(le64_t value) { return bswap_64_on_be((uint64_t __force)value); }
+
+static inline uint16_t be16toh(be16_t value) { return bswap_16_on_le((uint16_t __force)value); }
+static inline uint32_t be32toh(be32_t value) { return bswap_32_on_le((uint32_t __force)value); }
+static inline uint64_t be64toh(be64_t value) { return bswap_64_on_le((uint64_t __force)value); }
+
+#endif /* SPARSE_ENDIAN_H */
diff --git a/src/shared/spawn-ask-password-agent.c b/src/shared/spawn-ask-password-agent.c
new file mode 100644
index 0000000000..c1a9c58681
--- /dev/null
+++ b/src/shared/spawn-ask-password-agent.c
@@ -0,0 +1,67 @@
+/*-*- Mode: C; c-basic-offset: 8; indent-tabs-mode: nil -*-*/
+
+/***
+ This file is part of systemd.
+
+ Copyright 2011 Lennart Poettering
+
+ systemd is free software; you can redistribute it and/or modify it
+ under the terms of the GNU Lesser General Public License as published by
+ the Free Software Foundation; either version 2.1 of the License, or
+ (at your option) any later version.
+
+ systemd is distributed in the hope that it will be useful, but
+ WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ Lesser General 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>
+#include <stdlib.h>
+#include <unistd.h>
+#include <string.h>
+#include <sys/prctl.h>
+#include <signal.h>
+#include <fcntl.h>
+
+#include "log.h"
+#include "util.h"
+#include "spawn-ask-password-agent.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)
+ log_error("Failed to fork TTY ask password agent: %s", strerror(-r));
+
+ return r;
+}
+
+void ask_password_agent_close(void) {
+
+ if (agent_pid <= 0)
+ return;
+
+ /* Inform agent that we are done */
+ kill(agent_pid, SIGTERM);
+ kill(agent_pid, SIGCONT);
+ wait_for_terminate(agent_pid, NULL);
+ agent_pid = 0;
+}
diff --git a/src/shared/spawn-ask-password-agent.h b/src/shared/spawn-ask-password-agent.h
new file mode 100644
index 0000000000..31b4beab58
--- /dev/null
+++ b/src/shared/spawn-ask-password-agent.h
@@ -0,0 +1,25 @@
+/*-*- Mode: C; c-basic-offset: 8; indent-tabs-mode: nil -*-*/
+
+#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/shared/spawn-polkit-agent.c b/src/shared/spawn-polkit-agent.c
new file mode 100644
index 0000000000..f9e52cdcbd
--- /dev/null
+++ b/src/shared/spawn-polkit-agent.c
@@ -0,0 +1,98 @@
+/*-*- Mode: C; c-basic-offset: 8; indent-tabs-mode: nil -*-*/
+
+/***
+ This file is part of systemd.
+
+ Copyright 2011 Lennart Poettering
+
+ systemd is free software; you can redistribute it and/or modify it
+ under the terms of the GNU Lesser General Public License as published by
+ the Free Software Foundation; either version 2.1 of the License, or
+ (at your option) any later version.
+
+ systemd is distributed in the hope that it will be useful, but
+ WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ Lesser General 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>
+#include <stdlib.h>
+#include <unistd.h>
+#include <string.h>
+#include <sys/prctl.h>
+#include <signal.h>
+#include <fcntl.h>
+#include <errno.h>
+#include <sys/poll.h>
+
+#include "log.h"
+#include "util.h"
+#include "spawn-polkit-agent.h"
+
+#ifdef ENABLE_POLKIT
+static pid_t agent_pid = 0;
+
+int polkit_agent_open(void) {
+ int r;
+ int pipe_fd[2];
+ char notify_fd[10 + 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;
+
+ snprintf(notify_fd, sizeof(notify_fd), "%i", pipe_fd[1]);
+ char_array_0(notify_fd);
+
+ 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 */
+ close_nointr_nofail(pipe_fd[1]);
+
+ if (r < 0)
+ log_error("Failed to fork TTY ask password agent: %s", strerror(-r));
+ else
+ /* Wait until the agent closes the fd */
+ fd_wait_for_event(pipe_fd[0], POLLHUP, (usec_t) -1);
+
+ close_nointr_nofail(pipe_fd[0]);
+
+ return r;
+}
+
+void polkit_agent_close(void) {
+
+ if (agent_pid <= 0)
+ return;
+
+ /* Inform agent that we are done */
+ kill(agent_pid, SIGTERM);
+ kill(agent_pid, SIGCONT);
+ wait_for_terminate(agent_pid, NULL);
+ agent_pid = 0;
+}
+
+#else
+
+int polkit_agent_open(void) {
+ return 0;
+}
+
+void polkit_agent_close(void) {
+}
+
+#endif
diff --git a/src/shared/spawn-polkit-agent.h b/src/shared/spawn-polkit-agent.h
new file mode 100644
index 0000000000..b91d20f120
--- /dev/null
+++ b/src/shared/spawn-polkit-agent.h
@@ -0,0 +1,28 @@
+/*-*- Mode: C; c-basic-offset: 8; indent-tabs-mode: nil -*-*/
+
+#ifndef foospawnpolkitagenthfoo
+#define foospawnpolkitagenthfoo
+
+/***
+ 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);
+
+#endif
diff --git a/src/shared/specifier.c b/src/shared/specifier.c
new file mode 100644
index 0000000000..7577c91052
--- /dev/null
+++ b/src/shared/specifier.c
@@ -0,0 +1,147 @@
+/*-*- Mode: C; c-basic-offset: 8; indent-tabs-mode: nil -*-*/
+
+/***
+ This file is part of systemd.
+
+ Copyright 2010 Lennart Poettering
+
+ systemd is free software; you can redistribute it and/or modify it
+ under the terms of the GNU Lesser General Public License as published by
+ the Free Software Foundation; either version 2.1 of the License, or
+ (at your option) any later version.
+
+ systemd is distributed in the hope that it will be useful, but
+ WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ Lesser General 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 <string.h>
+
+#include "macro.h"
+#include "util.h"
+#include "specifier.h"
+
+/*
+ * Generic infrastructure for replacing %x style specifiers in
+ * strings. Will call a callback for each replacement.
+ *
+ */
+
+char *specifier_printf(const char *text, const Specifier table[], void *userdata) {
+ char *r, *t;
+ const char *f;
+ bool percent = false;
+ size_t l;
+
+ assert(text);
+ assert(table);
+
+ l = strlen(text);
+ r = new(char, l+1);
+ if (!r)
+ return NULL;
+
+ t = r;
+
+ for (f = text; *f; f++, l--) {
+
+ if (percent) {
+ if (*f == '%')
+ *(t++) = '%';
+ else {
+ const Specifier *i;
+
+ for (i = table; i->specifier; i++)
+ if (i->specifier == *f)
+ break;
+
+ if (i->lookup) {
+ char *n, *w;
+ size_t k, j;
+
+ w = i->lookup(i->specifier, i->data, userdata);
+ if (!w) {
+ free(r);
+ return NULL;
+ }
+
+ j = t - r;
+ k = strlen(w);
+
+ n = new(char, j + k + l + 1);
+ if (!n) {
+ free(r);
+ free(w);
+ return NULL;
+ }
+
+ memcpy(n, r, j);
+ memcpy(n + j, w, k);
+
+ free(r);
+ free(w);
+
+ r = n;
+ t = n + j + k;
+ } else {
+ *(t++) = '%';
+ *(t++) = *f;
+ }
+ }
+
+ percent = false;
+ } else if (*f == '%')
+ percent = true;
+ else
+ *(t++) = *f;
+ }
+
+ *t = 0;
+ return r;
+}
+
+/* Generic handler for simple string replacements */
+
+char* specifier_string(char specifier, void *data, void *userdata) {
+ return strdup(strempty(data));
+}
+
+char *specifier_machine_id(char specifier, void *data, void *userdata) {
+ sd_id128_t id;
+ char *buf;
+ int r;
+
+ r = sd_id128_get_machine(&id);
+ if (r < 0)
+ return NULL;
+
+ buf = new(char, 33);
+ if (!buf)
+ return NULL;
+
+ return sd_id128_to_string(id, buf);
+}
+
+char *specifier_boot_id(char specifier, void *data, void *userdata) {
+ sd_id128_t id;
+ char *buf;
+ int r;
+
+ r = sd_id128_get_boot(&id);
+ if (r < 0)
+ return NULL;
+
+ buf = new(char, 33);
+ if (!buf)
+ return NULL;
+
+ return sd_id128_to_string(id, buf);
+}
+
+char *specifier_host_name(char specifier, void *data, void *userdata) {
+ return gethostname_malloc();
+}
diff --git a/src/shared/specifier.h b/src/shared/specifier.h
new file mode 100644
index 0000000000..0440dcac48
--- /dev/null
+++ b/src/shared/specifier.h
@@ -0,0 +1,38 @@
+/*-*- Mode: C; c-basic-offset: 8; indent-tabs-mode: nil -*-*/
+
+#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 char* (*SpecifierCallback)(char specifier, void *data, void *userdata);
+
+typedef struct Specifier {
+ const char specifier;
+ const SpecifierCallback lookup;
+ void *data;
+} Specifier;
+
+char *specifier_printf(const char *text, const Specifier table[], void *userdata);
+
+char *specifier_string(char specifier, void *data, void *userdata);
+
+char *specifier_machine_id(char specifier, void *data, void *userdata);
+char *specifier_boot_id(char specifier, void *data, void *userdata);
+char *specifier_host_name(char specifier, void *data, void *userdata);
diff --git a/src/shared/strbuf.c b/src/shared/strbuf.c
new file mode 100644
index 0000000000..01a076c2ba
--- /dev/null
+++ b/src/shared/strbuf.c
@@ -0,0 +1,201 @@
+/*-*- Mode: C; c-basic-offset: 8; indent-tabs-mode: nil -*-*/
+
+/***
+ This file is part of systemd.
+
+ Copyright 2012 Kay Sievers <kay@vrfy.org>
+
+ systemd is free software; you can redistribute it and/or modify it
+ under the terms of the GNU Lesser General Public License as published by
+ the Free Software Foundation; either version 2.1 of the License, or
+ (at your option) any later version.
+
+ systemd is distributed in the hope that it will be useful, but
+ WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ Lesser General 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 <stdlib.h>
+#include <string.h>
+
+#include "util.h"
+#include "strbuf.h"
+
+/*
+ * Strbuf stores given strings in a single continuous allocated memory
+ * area. Identical strings are de-duplicated and return the same offset
+ * as the first string stored. If the tail of a string already exists
+ * in the buffer, the tail is returned.
+ *
+ * A trie (http://en.wikipedia.org/wiki/Trie) is used to maintain the
+ * information about the stored strings.
+ *
+ * Example of udev rules:
+ * $ ./udevadm test .
+ * ...
+ * read rules file: /usr/lib/udev/rules.d/99-systemd.rules
+ * rules contain 196608 bytes tokens (16384 * 12 bytes), 39742 bytes strings
+ * 23939 strings (207859 bytes), 20404 de-duplicated (171653 bytes), 3536 trie nodes used
+ * ...
+ */
+
+struct strbuf *strbuf_new(void) {
+ struct strbuf *str;
+
+ str = new0(struct strbuf, 1);
+ if (!str)
+ return NULL;
+
+ str->buf = new0(char, 1);
+ if (!str->buf)
+ goto err;
+ str->len = 1;
+
+ str->root = new0(struct strbuf_node, 1);
+ if (!str->root)
+ goto err;
+ str->nodes_count = 1;
+ return str;
+err:
+ free(str->buf);
+ free(str->root);
+ free(str);
+ return NULL;
+}
+
+static void strbuf_node_cleanup(struct strbuf_node *node) {
+ size_t i;
+
+ for (i = 0; i < node->children_count; i++)
+ strbuf_node_cleanup(node->children[i].child);
+ free(node->children);
+ free(node);
+}
+
+/* clean up trie data, leave only the string buffer */
+void strbuf_complete(struct strbuf *str) {
+ if (!str)
+ return;
+ if (str->root)
+ strbuf_node_cleanup(str->root);
+ str->root = NULL;
+}
+
+/* clean up everything */
+void strbuf_cleanup(struct strbuf *str) {
+ if (!str)
+ return;
+ if (str->root)
+ strbuf_node_cleanup(str->root);
+ free(str->buf);
+ free(str);
+}
+
+static int strbuf_children_cmp(const struct strbuf_child_entry *n1,
+ const struct strbuf_child_entry *n2) {
+ return n1->c - n2->c;
+}
+
+static void bubbleinsert(struct strbuf_node *node,
+ uint8_t c,
+ struct strbuf_node *node_child) {
+
+ struct strbuf_child_entry new = {
+ .c = c,
+ .child = node_child,
+ };
+ int left = 0, right = node->children_count;
+
+ while (right > left) {
+ int middle = (right + left) / 2 ;
+ if (strbuf_children_cmp(&node->children[middle], &new) <= 0)
+ left = middle + 1;
+ else
+ right = middle;
+ }
+
+ memmove(node->children + left + 1, node->children + left,
+ sizeof(struct strbuf_child_entry) * (node->children_count - left));
+ node->children[left] = new;
+
+ node->children_count ++;
+}
+
+/* add string, return the index/offset into the buffer */
+ssize_t strbuf_add_string(struct strbuf *str, const char *s, size_t len) {
+ uint8_t c;
+ struct strbuf_node *node;
+ size_t depth;
+ char *buf_new;
+ struct strbuf_child_entry *child;
+ struct strbuf_node *node_child;
+ ssize_t off;
+
+ if (!str->root)
+ return -EINVAL;
+
+ /* search string; start from last character to find possibly matching tails */
+ if (len == 0)
+ return 0;
+ str->in_count++;
+ str->in_len += len;
+
+ node = str->root;
+ c = s[len-1];
+ for (depth = 0; depth <= len; depth++) {
+ struct strbuf_child_entry search;
+
+ /* match against current node */
+ off = node->value_off + node->value_len - len;
+ if (depth == len || (node->value_len >= len && memcmp(str->buf + off, s, len) == 0)) {
+ str->dedup_len += len;
+ str->dedup_count++;
+ return off;
+ }
+
+ /* lookup child node */
+ c = s[len - 1 - depth];
+ search.c = c;
+ child = bsearch(&search, node->children, node->children_count,
+ sizeof(struct strbuf_child_entry),
+ (__compar_fn_t) strbuf_children_cmp);
+ if (!child)
+ break;
+ node = child->child;
+ }
+
+ /* add new string */
+ buf_new = realloc(str->buf, str->len + len+1);
+ if (!buf_new)
+ return -ENOMEM;
+ str->buf = buf_new;
+ off = str->len;
+ memcpy(str->buf + off, s, len);
+ str->len += len;
+ str->buf[str->len++] = '\0';
+
+ /* new node */
+ node_child = new0(struct strbuf_node, 1);
+ if (!node_child)
+ return -ENOMEM;
+ node_child->value_off = off;
+ node_child->value_len = len;
+
+ /* extend array, add new entry, sort for bisection */
+ child = realloc(node->children, (node->children_count + 1) * sizeof(struct strbuf_child_entry));
+ if (!child) {
+ free(node_child);
+ return -ENOMEM;
+ }
+
+ str->nodes_count++;
+
+ node->children = child;
+ bubbleinsert(node, c, node_child);
+
+ return off;
+}
diff --git a/src/shared/strbuf.h b/src/shared/strbuf.h
new file mode 100644
index 0000000000..2347fd4328
--- /dev/null
+++ b/src/shared/strbuf.h
@@ -0,0 +1,56 @@
+/*-*- Mode: C; c-basic-offset: 8; indent-tabs-mode: nil -*-*/
+
+#pragma once
+
+/***
+ This file is part of systemd.
+
+ Copyright 2012 Kay Sievers <kay@vrfy.org>
+
+ systemd is free software; you can redistribute it and/or modify it
+ under the terms of the GNU Lesser General Public License as published by
+ the Free Software Foundation; either version 2.1 of the License, or
+ (at your option) any later version.
+
+ systemd is distributed in the hope that it will be useful, but
+ WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ Lesser General 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 <stdarg.h>
+#include <stdint.h>
+#include <stdbool.h>
+
+struct strbuf {
+ char *buf;
+ size_t len;
+ struct strbuf_node *root;
+
+ size_t nodes_count;
+ size_t in_count;
+ size_t in_len;
+ size_t dedup_len;
+ size_t dedup_count;
+};
+
+struct strbuf_node {
+ size_t value_off;
+ size_t value_len;
+
+ struct strbuf_child_entry *children;
+ uint8_t children_count;
+};
+
+struct strbuf_child_entry {
+ uint8_t c;
+ struct strbuf_node *child;
+};
+
+struct strbuf *strbuf_new(void);
+ssize_t strbuf_add_string(struct strbuf *str, const char *s, size_t len);
+void strbuf_complete(struct strbuf *str);
+void strbuf_cleanup(struct strbuf *str);
diff --git a/src/shared/strv.c b/src/shared/strv.c
new file mode 100644
index 0000000000..a5ce7e9593
--- /dev/null
+++ b/src/shared/strv.c
@@ -0,0 +1,587 @@
+/*-*- Mode: C; c-basic-offset: 8; indent-tabs-mode: nil -*-*/
+
+/***
+ This file is part 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 <assert.h>
+#include <stdlib.h>
+#include <stdarg.h>
+#include <string.h>
+#include <errno.h>
+
+#include "util.h"
+#include "strv.h"
+
+char *strv_find(char **l, const char *name) {
+ char **i;
+
+ assert(name);
+
+ STRV_FOREACH(i, l)
+ if (streq(*i, name))
+ return *i;
+
+ return NULL;
+}
+
+char *strv_find_prefix(char **l, const char *name) {
+ char **i;
+
+ assert(name);
+
+ STRV_FOREACH(i, l)
+ if (startswith(*i, name))
+ return *i;
+
+ return NULL;
+}
+
+void strv_free(char **l) {
+ char **k;
+
+ if (!l)
+ return;
+
+ for (k = l; *k; k++)
+ free(*k);
+
+ free(l);
+}
+
+char **strv_copy(char * const *l) {
+ char **r, **k;
+
+ k = r = new(char*, strv_length(l) + 1);
+ if (!r)
+ return NULL;
+
+ if (l)
+ for (; *l; k++, l++) {
+ *k = strdup(*l);
+ if (!*k) {
+ strv_free(r);
+ return NULL;
+ }
+ }
+
+ *k = NULL;
+ return r;
+}
+
+unsigned strv_length(char * const *l) {
+ unsigned n = 0;
+
+ if (!l)
+ return 0;
+
+ for (; *l; l++)
+ n++;
+
+ return n;
+}
+
+char **strv_new_ap(const char *x, va_list ap) {
+ const char *s;
+ char **a;
+ unsigned n = 0, i = 0;
+ va_list aq;
+
+ /* As a special trick we ignore all listed strings that equal
+ * (const char*) -1. This is supposed to be used with the
+ * STRV_IFNOTNULL() macro to include possibly NULL strings in
+ * the string list. */
+
+ if (x) {
+ n = x == (const char*) -1 ? 0 : 1;
+
+ va_copy(aq, ap);
+ while ((s = va_arg(aq, const char*))) {
+ if (s == (const char*) -1)
+ continue;
+
+ n++;
+ }
+
+ va_end(aq);
+ }
+
+ a = new(char*, n+1);
+ if (!a)
+ return NULL;
+
+ if (x) {
+ if (x != (const char*) -1) {
+ a[i] = strdup(x);
+ if (!a[i])
+ goto fail;
+ i++;
+ }
+
+ while ((s = va_arg(ap, const char*))) {
+
+ if (s == (const char*) -1)
+ continue;
+
+ a[i] = strdup(s);
+ if (!a[i])
+ goto fail;
+
+ i++;
+ }
+ }
+
+ a[i] = NULL;
+
+ return a;
+
+fail:
+ strv_free(a);
+ return NULL;
+}
+
+char **strv_new(const char *x, ...) {
+ char **r;
+ va_list ap;
+
+ va_start(ap, x);
+ r = strv_new_ap(x, ap);
+ va_end(ap);
+
+ return r;
+}
+
+char **strv_merge(char **a, char **b) {
+ char **r, **k;
+
+ if (!a)
+ return strv_copy(b);
+
+ if (!b)
+ return strv_copy(a);
+
+ r = new(char*, strv_length(a) + strv_length(b) + 1);
+ if (!r)
+ return NULL;
+
+ for (k = r; *a; k++, a++) {
+ *k = strdup(*a);
+ if (!*k)
+ goto fail;
+ }
+
+ for (; *b; k++, b++) {
+ *k = strdup(*b);
+ if (!*k)
+ goto fail;
+ }
+
+ *k = NULL;
+ return r;
+
+fail:
+ strv_free(r);
+ return NULL;
+}
+
+char **strv_merge_concat(char **a, char **b, const char *suffix) {
+ char **r, **k;
+
+ /* Like strv_merge(), but appends suffix to all strings in b, before adding */
+
+ if (!b)
+ return strv_copy(a);
+
+ r = new(char*, strv_length(a) + strv_length(b) + 1);
+ if (!r)
+ return NULL;
+
+ k = r;
+ if (a)
+ for (; *a; k++, a++) {
+ *k = strdup(*a);
+ if (!*k)
+ goto fail;
+ }
+
+ for (; *b; k++, b++) {
+ *k = strappend(*b, suffix);
+ if (!*k)
+ goto fail;
+ }
+
+ *k = NULL;
+ return r;
+
+fail:
+ strv_free(r);
+ return NULL;
+
+}
+
+char **strv_split(const char *s, const char *separator) {
+ char *state;
+ char *w;
+ size_t l;
+ unsigned n, i;
+ char **r;
+
+ assert(s);
+
+ n = 0;
+ FOREACH_WORD_SEPARATOR(w, l, s, separator, state)
+ n++;
+
+ r = new(char*, n+1);
+ if (!r)
+ return NULL;
+
+ i = 0;
+ FOREACH_WORD_SEPARATOR(w, l, s, separator, state) {
+ r[i] = strndup(w, l);
+ if (!r[i]) {
+ strv_free(r);
+ return NULL;
+ }
+
+ i++;
+ }
+
+ r[i] = NULL;
+ return r;
+}
+
+char **strv_split_quoted(const char *s) {
+ char *state;
+ char *w;
+ size_t l;
+ unsigned n, i;
+ char **r;
+
+ assert(s);
+
+ n = 0;
+ FOREACH_WORD_QUOTED(w, l, s, state)
+ n++;
+
+ r = new(char*, n+1);
+ if (!r)
+ return NULL;
+
+ i = 0;
+ FOREACH_WORD_QUOTED(w, l, s, state) {
+ r[i] = cunescape_length(w, l);
+ if (!r[i]) {
+ strv_free(r);
+ return NULL;
+ }
+ i++;
+ }
+
+ r[i] = NULL;
+ return r;
+}
+
+char **strv_split_newlines(const char *s) {
+ char **l;
+ unsigned n;
+
+ assert(s);
+
+ /* Special version of strv_split() that splits on newlines and
+ * suppresses an empty string at the end */
+
+ l = strv_split(s, NEWLINE);
+ if (!l)
+ return NULL;
+
+ n = strv_length(l);
+ if (n <= 0)
+ return l;
+
+ if (isempty(l[n-1])) {
+ free(l[n-1]);
+ l[n-1] = NULL;
+ }
+
+ return l;
+}
+
+char *strv_join(char **l, const char *separator) {
+ char *r, *e;
+ char **s;
+ size_t n, k;
+
+ if (!separator)
+ separator = " ";
+
+ k = strlen(separator);
+
+ n = 0;
+ STRV_FOREACH(s, l) {
+ if (n != 0)
+ n += k;
+ n += strlen(*s);
+ }
+
+ r = new(char, n+1);
+ if (!r)
+ return NULL;
+
+ e = r;
+ STRV_FOREACH(s, l) {
+ if (e != r)
+ e = stpcpy(e, separator);
+
+ e = stpcpy(e, *s);
+ }
+
+ *e = 0;
+
+ return r;
+}
+
+char **strv_append(char **l, const char *s) {
+ char **r, **k;
+
+ if (!l)
+ return strv_new(s, NULL);
+
+ if (!s)
+ return strv_copy(l);
+
+ r = new(char*, strv_length(l)+2);
+ if (!r)
+ return NULL;
+
+ for (k = r; *l; k++, l++) {
+ *k = strdup(*l);
+ if (!*k)
+ goto fail;
+ }
+
+ k[0] = strdup(s);
+ if (!k[0])
+ goto fail;
+
+ k[1] = NULL;
+ return r;
+
+fail:
+ strv_free(r);
+ return NULL;
+}
+
+int strv_push(char ***l, char *value) {
+ char **c;
+ unsigned n;
+
+ if (!value)
+ return 0;
+
+ n = strv_length(*l);
+ c = realloc(*l, sizeof(char*) * (n + 2));
+ if (!c)
+ return -ENOMEM;
+
+ c[n] = value;
+ c[n+1] = NULL;
+
+ *l = c;
+ return 0;
+}
+
+int strv_extend(char ***l, const char *value) {
+ char *v;
+ int r;
+
+ if (!value)
+ return 0;
+
+ v = strdup(value);
+ if (!v)
+ return -ENOMEM;
+
+ r = strv_push(l, v);
+ if (r < 0)
+ free(v);
+
+ return r;
+}
+
+char **strv_uniq(char **l) {
+ char **i;
+
+ /* Drops duplicate entries. The first identical string will be
+ * kept, the others dropped */
+
+ STRV_FOREACH(i, l)
+ strv_remove(i+1, *i);
+
+ return l;
+}
+
+char **strv_remove(char **l, const char *s) {
+ char **f, **t;
+
+ if (!l)
+ return NULL;
+
+ assert(s);
+
+ /* Drops every occurrence of s in the string list, edits
+ * in-place. */
+
+ for (f = t = l; *f; f++) {
+
+ if (streq(*f, s)) {
+ free(*f);
+ continue;
+ }
+
+ *(t++) = *f;
+ }
+
+ *t = NULL;
+ return l;
+}
+
+char **strv_remove_prefix(char **l, const char *s) {
+ char **f, **t;
+
+ if (!l)
+ return NULL;
+
+ assert(s);
+
+ /* Drops every occurrence of a string prefixed with s in the
+ * string list, edits in-place. */
+
+ for (f = t = l; *f; f++) {
+
+ if (startswith(*f, s)) {
+ free(*f);
+ continue;
+ }
+
+ *(t++) = *f;
+ }
+
+ *t = NULL;
+ return l;
+}
+
+char **strv_parse_nulstr(const char *s, size_t l) {
+ const char *p;
+ unsigned c = 0, i = 0;
+ char **v;
+
+ assert(s || l <= 0);
+
+ if (l <= 0)
+ return strv_new(NULL, NULL);
+
+ for (p = s; p < s + l; p++)
+ if (*p == 0)
+ c++;
+
+ if (s[l-1] != 0)
+ c++;
+
+ v = new0(char*, c+1);
+ if (!v)
+ return NULL;
+
+ p = s;
+ while (p < s + l) {
+ const char *e;
+
+ e = memchr(p, 0, s + l - p);
+
+ v[i] = strndup(p, e ? e - p : s + l - p);
+ if (!v[i]) {
+ strv_free(v);
+ return NULL;
+ }
+
+ i++;
+
+ if (!e)
+ break;
+
+ p = e + 1;
+ }
+
+ assert(i == c);
+
+ return v;
+}
+
+char **strv_split_nulstr(const char *s) {
+ const char *i;
+ char **r = NULL;
+
+ NULSTR_FOREACH(i, s)
+ if (strv_extend(&r, i) < 0) {
+ strv_free(r);
+ return NULL;
+ }
+
+ if (!r)
+ return strv_new(NULL, NULL);
+
+ return r;
+}
+
+bool strv_overlap(char **a, char **b) {
+ char **i, **j;
+
+ STRV_FOREACH(i, a) {
+ STRV_FOREACH(j, b) {
+ if (streq(*i, *j))
+ return true;
+ }
+ }
+
+ return false;
+}
+
+static int str_compare(const void *_a, const void *_b) {
+ const char **a = (const char**) _a, **b = (const char**) _b;
+
+ return strcmp(*a, *b);
+}
+
+char **strv_sort(char **l) {
+
+ if (strv_isempty(l))
+ return l;
+
+ qsort(l, strv_length(l), sizeof(char*), str_compare);
+ return l;
+}
+
+void strv_print(char **l) {
+ char **s;
+
+ if (!l)
+ return;
+
+ STRV_FOREACH(s, l)
+ puts(*s);
+}
diff --git a/src/shared/strv.h b/src/shared/strv.h
new file mode 100644
index 0000000000..e35118752f
--- /dev/null
+++ b/src/shared/strv.h
@@ -0,0 +1,87 @@
+/*-*- Mode: C; c-basic-offset: 8; indent-tabs-mode: nil -*-*/
+
+#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 <stdarg.h>
+#include <stdbool.h>
+
+#include "macro.h"
+
+char *strv_find(char **l, const char *name) _pure_;
+char *strv_find_prefix(char **l, const char *name) _pure_;
+
+void strv_free(char **l);
+static inline void strv_freep(char ***l) {
+ strv_free(*l);
+}
+
+#define _cleanup_strv_free_ _cleanup_(strv_freep)
+
+char **strv_copy(char * const *l);
+unsigned strv_length(char * const *l) _pure_;
+
+char **strv_merge(char **a, char **b);
+char **strv_merge_concat(char **a, char **b, const char *suffix);
+char **strv_append(char **l, const char *s);
+int strv_extend(char ***l, const char *value);
+int strv_push(char ***l, char *value);
+
+char **strv_remove(char **l, const char *s);
+char **strv_remove_prefix(char **l, const char *s);
+char **strv_uniq(char **l);
+
+#define strv_contains(l, s) (!!strv_find((l), (s)))
+
+char **strv_new(const char *x, ...) _sentinel_;
+char **strv_new_ap(const char *x, va_list ap);
+
+static inline const char* STRV_IFNOTNULL(const char *x) {
+ return x ? x : (const char *) -1;
+}
+
+static inline bool strv_isempty(char * const *l) {
+ return !l || !*l;
+}
+
+char **strv_split(const char *s, const char *separator);
+char **strv_split_quoted(const char *s);
+char **strv_split_newlines(const char *s);
+
+char *strv_join(char **l, const char *separator);
+
+char **strv_parse_nulstr(const char *s, size_t l);
+char **strv_split_nulstr(const char *s);
+
+bool strv_overlap(char **a, char **b) _pure_;
+
+#define STRV_FOREACH(s, l) \
+ for ((s) = (l); (s) && *(s); (s)++)
+
+#define STRV_FOREACH_BACKWARDS(s, l) \
+ for (; (l) && ((s) >= (l)); (s)--)
+
+#define STRV_FOREACH_PAIR(x, y, l) \
+ for ((x) = (l), (y) = (x+1); (x) && *(x) && *(y); (x) += 2, (y) = (x + 1))
+
+
+char **strv_sort(char **l);
+void strv_print(char **l);
diff --git a/src/shared/strxcpyx.c b/src/shared/strxcpyx.c
new file mode 100644
index 0000000000..3cb04b25cd
--- /dev/null
+++ b/src/shared/strxcpyx.c
@@ -0,0 +1,104 @@
+/*-*- Mode: C; c-basic-offset: 8; indent-tabs-mode: nil -*-*/
+
+/***
+ 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/>.
+***/
+
+/*
+ * Concatenates/copies strings. In any case, terminates in all cases
+ * with '\0' * and moves the @dest pointer forward to the added '\0'.
+ * Returns the * remaining size, and 0 if the string was truncated.
+ */
+
+#include <stdio.h>
+#include <string.h>
+#include "strxcpyx.h"
+
+size_t strpcpy(char **dest, size_t size, const char *src)
+{
+ size_t len;
+
+ len = strlen(src);
+ if (len >= size) {
+ if (size > 1)
+ *dest = mempcpy(*dest, src, size-1);
+ size = 0;
+ } else {
+ if (len > 0) {
+ *dest = mempcpy(*dest, src, len);
+ size -= len;
+ }
+ }
+ *dest[0] = '\0';
+ return size;
+}
+
+size_t strpcpyf(char **dest, size_t size, const char *src, ...)
+{
+ va_list va;
+ int i;
+
+ va_start(va, src);
+ i = vsnprintf(*dest, size, src, va);
+ if (i < (int)size) {
+ *dest += i;
+ size -= i;
+ } else {
+ *dest += size;
+ size = 0;
+ }
+ va_end(va);
+ *dest[0] = '\0';
+ return size;
+}
+
+size_t strpcpyl(char **dest, size_t size, const char *src, ...)
+{
+ va_list va;
+
+ va_start(va, src);
+ do {
+ size = strpcpy(dest, size, src);
+ src = va_arg(va, char *);
+ } while (src != NULL);
+ va_end(va);
+ return size;
+}
+
+size_t strscpy(char *dest, size_t size, const char *src)
+{
+ char *s;
+
+ s = dest;
+ return strpcpy(&s, size, src);
+}
+
+size_t strscpyl(char *dest, size_t size, const char *src, ...) {
+ va_list va;
+ char *s;
+
+ va_start(va, src);
+ s = dest;
+ do {
+ size = strpcpy(&s, size, src);
+ src = va_arg(va, char *);
+ } while (src != NULL);
+ va_end(va);
+
+ return size;
+}
diff --git a/src/shared/strxcpyx.h b/src/shared/strxcpyx.h
new file mode 100644
index 0000000000..1229a4821d
--- /dev/null
+++ b/src/shared/strxcpyx.h
@@ -0,0 +1,31 @@
+/*-*- Mode: C; c-basic-offset: 8; indent-tabs-mode: nil -*-*/
+
+#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 <stdarg.h>
+#include <stdbool.h>
+
+size_t strpcpy(char **dest, size_t size, const char *src);
+size_t strpcpyf(char **dest, size_t size, const char *src, ...) __attribute__((format(printf, 3, 4)));
+size_t strpcpyl(char **dest, size_t size, const char *src, ...) __attribute__((sentinel));
+size_t strscpy(char *dest, size_t size, const char *src);
+size_t strscpyl(char *dest, size_t size, const char *src, ...) __attribute__((sentinel));
diff --git a/src/shared/time-dst.c b/src/shared/time-dst.c
new file mode 100644
index 0000000000..3950a882bf
--- /dev/null
+++ b/src/shared/time-dst.c
@@ -0,0 +1,332 @@
+/*-*- Mode: C; c-basic-offset: 8; indent-tabs-mode: nil -*-*/
+
+/***
+ This file is part of systemd.
+
+ Timezone file reading code from glibc 2.16.
+
+ Copyright (C) 1991-2012 Free Software Foundation, Inc.
+ Copyright 2012 Kay Sievers
+
+ systemd is free software; you can redistribute it and/or modify it
+ under the terms of the GNU Lesser General Public License as published by
+ the Free Software Foundation; either version 2.1 of the License, or
+ (at your option) any later version.
+
+ systemd is distributed in the hope that it will be useful, but
+ WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ Lesser General Public License for more details.
+
+ You should have received a copy of the GNU Lesser General Public License
+ along with systemd; If not, see <http://www.gnu.org/licenses/>.
+***/
+#include <ctype.h>
+#include <errno.h>
+#include <stddef.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <time.h>
+#include <endian.h>
+#include <byteswap.h>
+#include <assert.h>
+#include <limits.h>
+#include <unistd.h>
+#include <stdint.h>
+#include <stdbool.h>
+#include <sys/stat.h>
+
+#include "time-dst.h"
+#include "util.h"
+
+/*
+ * If tzh_version is '2' or greater, the above is followed by a second instance
+ * of tzhead and a second instance of the data in which each coded transition
+ * time uses 8 rather than 4 chars, then a POSIX-TZ-environment-variable-style
+ * string for use in handling instants after the last transition time stored in
+ * the file * (with nothing between the newlines if there is no POSIX
+ * representation for such instants).
+ */
+#define TZ_MAGIC "TZif"
+struct tzhead {
+ char tzh_magic[4]; /* TZ_MAGIC */
+ char tzh_version[1]; /* '\0' or '2' as of 2005 */
+ char tzh_reserved[15]; /* reserved--must be zero */
+ char tzh_ttisgmtcnt[4]; /* coded number of trans. time flags */
+ char tzh_ttisstdcnt[4]; /* coded number of trans. time flags */
+ char tzh_leapcnt[4]; /* coded number of leap seconds */
+ char tzh_timecnt[4]; /* coded number of transition times */
+ char tzh_typecnt[4]; /* coded number of local time types */
+ char tzh_charcnt[4]; /* coded number of abbr. chars */
+};
+
+struct ttinfo {
+ long int offset; /* Seconds east of GMT. */
+ unsigned char isdst; /* Used to set tm_isdst. */
+ unsigned char idx; /* Index into `zone_names'. */
+ unsigned char isstd; /* Transition times are in standard time. */
+ unsigned char isgmt; /* Transition times are in GMT. */
+};
+
+struct leap {
+ time_t transition; /* Time the transition takes effect. */
+ long int change; /* Seconds of correction to apply. */
+};
+
+static inline int decode(const void *ptr) {
+ return be32toh(*(int *)ptr);
+}
+
+static inline int64_t decode64(const void *ptr) {
+ return be64toh(*(int64_t *)ptr);
+}
+
+int time_get_dst(time_t date, const char *tzfile,
+ time_t *switch_cur, char **zone_cur, bool *dst_cur,
+ time_t *switch_next, int *delta_next, char **zone_next, bool *dst_next) {
+ unsigned char *type_idxs = 0;
+ size_t num_types = 0;
+ struct ttinfo *types = NULL;
+ char *zone_names = NULL;
+ struct stat st;
+ size_t num_isstd, num_isgmt;
+ struct tzhead tzhead;
+ size_t chars;
+ size_t i;
+ size_t total_size;
+ size_t types_idx;
+ int trans_width = 4;
+ size_t tzspec_len;
+ size_t num_leaps;
+ size_t lo, hi;
+ size_t num_transitions = 0;
+ _cleanup_free_ time_t *transitions = NULL;
+ _cleanup_fclose_ FILE *f;
+
+ f = fopen(tzfile, "re");
+ if (f == NULL)
+ return -errno;
+
+ if (fstat(fileno(f), &st) < 0)
+ return -errno;
+
+read_again:
+ if (fread((void *)&tzhead, sizeof(tzhead), 1, f) != 1 ||
+ memcmp(tzhead.tzh_magic, TZ_MAGIC, sizeof(tzhead.tzh_magic)) != 0)
+ return -EINVAL;
+
+ num_transitions = (size_t)decode(tzhead.tzh_timecnt);
+ num_types = (size_t)decode(tzhead.tzh_typecnt);
+ chars = (size_t)decode(tzhead.tzh_charcnt);
+ num_leaps = (size_t)decode(tzhead.tzh_leapcnt);
+ num_isstd = (size_t)decode(tzhead.tzh_ttisstdcnt);
+ num_isgmt = (size_t)decode(tzhead.tzh_ttisgmtcnt);
+
+ /* For platforms with 64-bit time_t we use the new format if available. */
+ if (sizeof(time_t) == 8 && trans_width == 4 && tzhead.tzh_version[0] != '\0') {
+ size_t to_skip;
+
+ /* We use the 8-byte format. */
+ trans_width = 8;
+
+ /* Position the stream before the second header. */
+ to_skip = (num_transitions * (4 + 1)
+ + num_types * 6
+ + chars
+ + num_leaps * 8 + num_isstd + num_isgmt);
+ if (fseek(f, to_skip, SEEK_CUR) != 0)
+ return -EINVAL;
+
+ goto read_again;
+ }
+
+ if (num_transitions > ((SIZE_MAX - (__alignof__(struct ttinfo) - 1)) / (sizeof(time_t) + 1)))
+ return -EINVAL;
+
+ total_size = num_transitions * (sizeof(time_t) + 1);
+ total_size = ((total_size + __alignof__(struct ttinfo) - 1) & ~(__alignof__(struct ttinfo) - 1));
+ types_idx = total_size;
+ if (num_leaps > (SIZE_MAX - total_size) / sizeof(struct ttinfo))
+ return -EINVAL;
+
+ total_size += num_types * sizeof(struct ttinfo);
+ if (chars > SIZE_MAX - total_size)
+ return -EINVAL;
+
+ total_size += chars;
+ if (__alignof__(struct leap) - 1 > SIZE_MAX - total_size)
+ return -EINVAL;
+
+ total_size = ((total_size + __alignof__(struct leap) - 1) & ~(__alignof__(struct leap) - 1));
+ if (num_leaps > (SIZE_MAX - total_size) / sizeof(struct leap))
+ return -EINVAL;
+
+ total_size += num_leaps * sizeof(struct leap);
+ tzspec_len = 0;
+ if (sizeof(time_t) == 8 && trans_width == 8) {
+ off_t rem = st.st_size - ftello(f);
+
+ if (rem < 0 || (size_t) rem < (num_transitions * (8 + 1) + num_types * 6 + chars))
+ return -EINVAL;
+ tzspec_len = (size_t) rem - (num_transitions * (8 + 1) + num_types * 6 + chars);
+ if (num_leaps > SIZE_MAX / 12 || tzspec_len < num_leaps * 12)
+ return -EINVAL;
+ tzspec_len -= num_leaps * 12;
+ if (tzspec_len < num_isstd)
+ return -EINVAL;
+ tzspec_len -= num_isstd;
+ if (tzspec_len == 0 || tzspec_len - 1 < num_isgmt)
+ return -EINVAL;
+ tzspec_len -= num_isgmt + 1;
+ if (SIZE_MAX - total_size < tzspec_len)
+ return -EINVAL;
+ }
+
+ transitions = (time_t *)calloc(total_size + tzspec_len, 1);
+ if (transitions == NULL)
+ return -EINVAL;
+
+ type_idxs = (unsigned char *)transitions + (num_transitions
+ * sizeof(time_t));
+ types = (struct ttinfo *)((char *)transitions + types_idx);
+ zone_names = (char *)types + num_types * sizeof(struct ttinfo);
+
+ if (sizeof(time_t) == 4 || trans_width == 8) {
+ if (fread(transitions, trans_width + 1, num_transitions, f) != num_transitions)
+ return -EINVAL;
+ } else {
+ if (fread(transitions, 4, num_transitions, f) != num_transitions ||
+ fread(type_idxs, 1, num_transitions, f) != num_transitions)
+ return -EINVAL;
+ }
+
+ /* Check for bogus indices in the data file, so we can hereafter
+ safely use type_idxs[T] as indices into `types' and never crash. */
+ for (i = 0; i < num_transitions; ++i)
+ if (type_idxs[i] >= num_types)
+ return -EINVAL;
+
+ if ((BYTE_ORDER != BIG_ENDIAN && (sizeof(time_t) == 4 || trans_width == 4)) ||
+ (BYTE_ORDER == BIG_ENDIAN && sizeof(time_t) == 8 && trans_width == 4)) {
+ /* Decode the transition times, stored as 4-byte integers in
+ network (big-endian) byte order. We work from the end of
+ the array so as not to clobber the next element to be
+ processed when sizeof (time_t) > 4. */
+ i = num_transitions;
+ while (i-- > 0)
+ transitions[i] = decode((char *)transitions + i * 4);
+ } else if (BYTE_ORDER != BIG_ENDIAN && sizeof(time_t) == 8) {
+ /* Decode the transition times, stored as 8-byte integers in
+ network (big-endian) byte order. */
+ for (i = 0; i < num_transitions; ++i)
+ transitions[i] = decode64((char *)transitions + i * 8);
+ }
+
+ for (i = 0; i < num_types; ++i) {
+ unsigned char x[4];
+ int c;
+
+ if (fread(x, 1, sizeof(x), f) != sizeof(x))
+ return -EINVAL;
+ c = getc(f);
+ if ((unsigned int)c > 1u)
+ return -EINVAL;
+ types[i].isdst = c;
+ c = getc(f);
+ if ((size_t) c > chars)
+ /* Bogus index in data file. */
+ return -EINVAL;
+ types[i].idx = c;
+ types[i].offset = (long int)decode(x);
+ }
+
+ if (fread(zone_names, 1, chars, f) != chars)
+ return -EINVAL;
+
+ for (i = 0; i < num_isstd; ++i) {
+ int c = getc(f);
+ if (c == EOF)
+ return -EINVAL;
+ types[i].isstd = c != 0;
+ }
+
+ while (i < num_types)
+ types[i++].isstd = 0;
+
+ for (i = 0; i < num_isgmt; ++i) {
+ int c = getc(f);
+ if (c == EOF)
+ return -EINVAL;
+ types[i].isgmt = c != 0;
+ }
+
+ while (i < num_types)
+ types[i++].isgmt = 0;
+
+ if (num_transitions == 0)
+ return -EINVAL;
+
+ if (date < transitions[0] || date >= transitions[num_transitions - 1])
+ return -EINVAL;
+
+ /* Find the first transition after TIMER, and
+ then pick the type of the transition before it. */
+ lo = 0;
+ hi = num_transitions - 1;
+
+ /* Assume that DST is changing twice a year and guess initial
+ search spot from it.
+ Half of a gregorian year has on average 365.2425 * 86400 / 2
+ = 15778476 seconds. */
+ i = (transitions[num_transitions - 1] - date) / 15778476;
+ if (i < num_transitions) {
+ i = num_transitions - 1 - i;
+ if (date < transitions[i]) {
+ if (i < 10 || date >= transitions[i - 10]) {
+ /* Linear search. */
+ while (date < transitions[i - 1])
+ i--;
+ goto found;
+ }
+ hi = i - 10;
+ } else {
+ if (i + 10 >= num_transitions || date < transitions[i + 10]) {
+ /* Linear search. */
+ while (date >= transitions[i])
+ i++;
+ goto found;
+ }
+ lo = i + 10;
+ }
+ }
+
+ /* Binary search. */
+ while (lo + 1 < hi) {
+ i = (lo + hi) / 2;
+ if (date < transitions[i])
+ hi = i;
+ else
+ lo = i;
+ }
+ i = hi;
+
+found:
+ if (switch_cur)
+ *switch_cur = transitions[i-1];
+ if (zone_cur)
+ *zone_cur = strdup(&zone_names[types[type_idxs[i - 1]].idx]);
+ if (dst_cur)
+ *dst_cur = types[type_idxs[i-1]].isdst;
+
+ if (switch_next)
+ *switch_next = transitions[i];
+ if (delta_next)
+ *delta_next = (types[type_idxs[i]].offset - types[type_idxs[i-1]].offset) / 60;
+ if (zone_next)
+ *zone_next = strdup(&zone_names[types[type_idxs[i]].idx]);
+ if (dst_next)
+ *dst_next = types[type_idxs[i]].isdst;
+
+ return 0;
+}
diff --git a/src/shared/time-dst.h b/src/shared/time-dst.h
new file mode 100644
index 0000000000..536b6bbdfb
--- /dev/null
+++ b/src/shared/time-dst.h
@@ -0,0 +1,26 @@
+/*-*- Mode: C; c-basic-offset: 8; indent-tabs-mode: nil -*-*/
+
+#pragma once
+
+/***
+ This file is part of systemd.
+
+ Copyright 2012 Kay Sievers
+
+ systemd is free software; you can redistribute it and/or modify it
+ under the terms of the GNU Lesser General Public License as published by
+ the Free Software Foundation; either version 2.1 of the License, or
+ (at your option) any later version.
+
+ systemd is distributed in the hope that it will be useful, but
+ WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ Lesser General Public License for more details.
+
+ You should have received a copy of the GNU Lesser General Public License
+ along with systemd; If not, see <http://www.gnu.org/licenses/>.
+***/
+
+int time_get_dst(time_t date, const char *tzfile,
+ time_t *switch_cur, char **zone_cur, bool *dst_cur,
+ time_t *switch_next, int *delta_next, char **zone_next, bool *dst_next);
diff --git a/src/shared/time-util.c b/src/shared/time-util.c
new file mode 100644
index 0000000000..9ee711a49e
--- /dev/null
+++ b/src/shared/time-util.c
@@ -0,0 +1,772 @@
+/*-*- Mode: C; c-basic-offset: 8; indent-tabs-mode: nil -*-*/
+
+/***
+ This file is part 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 <time.h>
+#include <string.h>
+
+#include "util.h"
+#include "time-util.h"
+
+usec_t now(clockid_t clock_id) {
+ struct timespec ts;
+
+ assert_se(clock_gettime(clock_id, &ts) == 0);
+
+ return timespec_load(&ts);
+}
+
+dual_timestamp* dual_timestamp_get(dual_timestamp *ts) {
+ assert(ts);
+
+ ts->realtime = now(CLOCK_REALTIME);
+ ts->monotonic = now(CLOCK_MONOTONIC);
+
+ return ts;
+}
+
+dual_timestamp* dual_timestamp_from_realtime(dual_timestamp *ts, usec_t u) {
+ int64_t delta;
+ assert(ts);
+
+ if (u == (usec_t) -1) {
+ ts->realtime = ts->monotonic = (usec_t) -1;
+ return ts;
+ }
+
+ ts->realtime = u;
+
+ if (u == 0)
+ ts->monotonic = 0;
+ else {
+ delta = (int64_t) now(CLOCK_REALTIME) - (int64_t) u;
+
+ ts->monotonic = now(CLOCK_MONOTONIC);
+
+ if ((int64_t) ts->monotonic > delta)
+ ts->monotonic -= delta;
+ else
+ ts->monotonic = 0;
+ }
+
+ return ts;
+}
+
+dual_timestamp* dual_timestamp_from_monotonic(dual_timestamp *ts, usec_t u) {
+ int64_t delta;
+ assert(ts);
+
+ if (u == (usec_t) -1) {
+ ts->realtime = ts->monotonic = (usec_t) -1;
+ return ts;
+ }
+
+ ts->monotonic = u;
+ delta = (int64_t) now(CLOCK_MONOTONIC) - (int64_t) u;
+
+ ts->realtime = now(CLOCK_REALTIME);
+ if ((int64_t) ts->realtime > delta)
+ ts->realtime -= delta;
+ else
+ ts->realtime = 0;
+
+ return ts;
+}
+
+usec_t timespec_load(const struct timespec *ts) {
+ assert(ts);
+
+ if (ts->tv_sec == (time_t) -1 &&
+ ts->tv_nsec == (long) -1)
+ return (usec_t) -1;
+
+ if ((usec_t) ts->tv_sec > (UINT64_MAX - (ts->tv_nsec / NSEC_PER_USEC)) / USEC_PER_SEC)
+ return (usec_t) -1;
+
+ return
+ (usec_t) ts->tv_sec * USEC_PER_SEC +
+ (usec_t) ts->tv_nsec / NSEC_PER_USEC;
+}
+
+struct timespec *timespec_store(struct timespec *ts, usec_t u) {
+ assert(ts);
+
+ if (u == (usec_t) -1) {
+ ts->tv_sec = (time_t) -1;
+ ts->tv_nsec = (long) -1;
+ return ts;
+ }
+
+ ts->tv_sec = (time_t) (u / USEC_PER_SEC);
+ ts->tv_nsec = (long int) ((u % USEC_PER_SEC) * NSEC_PER_USEC);
+
+ return ts;
+}
+
+usec_t timeval_load(const struct timeval *tv) {
+ assert(tv);
+
+ if (tv->tv_sec == (time_t) -1 &&
+ tv->tv_usec == (suseconds_t) -1)
+ return (usec_t) -1;
+
+ if ((usec_t) tv->tv_sec > (UINT64_MAX - tv->tv_usec) / USEC_PER_SEC)
+ return (usec_t) -1;
+
+ return
+ (usec_t) tv->tv_sec * USEC_PER_SEC +
+ (usec_t) tv->tv_usec;
+}
+
+struct timeval *timeval_store(struct timeval *tv, usec_t u) {
+ assert(tv);
+
+ if (u == (usec_t) -1) {
+ tv->tv_sec = (time_t) -1;
+ tv->tv_usec = (suseconds_t) -1;
+ return tv;
+ }
+
+ tv->tv_sec = (time_t) (u / USEC_PER_SEC);
+ tv->tv_usec = (suseconds_t) (u % USEC_PER_SEC);
+
+ return tv;
+}
+
+char *format_timestamp(char *buf, size_t l, usec_t t) {
+ struct tm tm;
+ time_t sec;
+
+ assert(buf);
+ assert(l > 0);
+
+ if (t <= 0)
+ return NULL;
+
+ sec = (time_t) (t / USEC_PER_SEC);
+
+ if (strftime(buf, l, "%a %Y-%m-%d %H:%M:%S %Z", localtime_r(&sec, &tm)) <= 0)
+ return NULL;
+
+ return buf;
+}
+
+char *format_timestamp_relative(char *buf, size_t l, usec_t t) {
+ usec_t n, d;
+
+ n = now(CLOCK_REALTIME);
+
+ if (t <= 0 || t > n || t + USEC_PER_DAY*7 <= t)
+ return NULL;
+
+ d = n - t;
+
+ if (d >= USEC_PER_YEAR)
+ snprintf(buf, l, "%llu years %llu months ago",
+ (unsigned long long) (d / USEC_PER_YEAR),
+ (unsigned long long) ((d % USEC_PER_YEAR) / USEC_PER_MONTH));
+ else if (d >= USEC_PER_MONTH)
+ snprintf(buf, l, "%llu months %llu days ago",
+ (unsigned long long) (d / USEC_PER_MONTH),
+ (unsigned long long) ((d % USEC_PER_MONTH) / USEC_PER_DAY));
+ else if (d >= USEC_PER_WEEK)
+ snprintf(buf, l, "%llu weeks %llu days ago",
+ (unsigned long long) (d / USEC_PER_WEEK),
+ (unsigned long long) ((d % USEC_PER_WEEK) / USEC_PER_DAY));
+ else if (d >= 2*USEC_PER_DAY)
+ snprintf(buf, l, "%llu days ago", (unsigned long long) (d / USEC_PER_DAY));
+ else if (d >= 25*USEC_PER_HOUR)
+ snprintf(buf, l, "1 day %lluh ago",
+ (unsigned long long) ((d - USEC_PER_DAY) / USEC_PER_HOUR));
+ else if (d >= 6*USEC_PER_HOUR)
+ snprintf(buf, l, "%lluh ago",
+ (unsigned long long) (d / USEC_PER_HOUR));
+ else if (d >= USEC_PER_HOUR)
+ snprintf(buf, l, "%lluh %llumin ago",
+ (unsigned long long) (d / USEC_PER_HOUR),
+ (unsigned long long) ((d % USEC_PER_HOUR) / USEC_PER_MINUTE));
+ else if (d >= 5*USEC_PER_MINUTE)
+ snprintf(buf, l, "%llumin ago",
+ (unsigned long long) (d / USEC_PER_MINUTE));
+ else if (d >= USEC_PER_MINUTE)
+ snprintf(buf, l, "%llumin %llus ago",
+ (unsigned long long) (d / USEC_PER_MINUTE),
+ (unsigned long long) ((d % USEC_PER_MINUTE) / USEC_PER_SEC));
+ else if (d >= USEC_PER_SEC)
+ snprintf(buf, l, "%llus ago",
+ (unsigned long long) (d / USEC_PER_SEC));
+ else if (d >= USEC_PER_MSEC)
+ snprintf(buf, l, "%llums ago",
+ (unsigned long long) (d / USEC_PER_MSEC));
+ else if (d > 0)
+ snprintf(buf, l, "%lluus ago",
+ (unsigned long long) d);
+ else
+ snprintf(buf, l, "now");
+
+ buf[l-1] = 0;
+ return buf;
+}
+
+char *format_timespan(char *buf, size_t l, usec_t t, usec_t accuracy) {
+ static const struct {
+ const char *suffix;
+ usec_t usec;
+ } table[] = {
+ { "y", USEC_PER_YEAR },
+ { "month", USEC_PER_MONTH },
+ { "w", USEC_PER_WEEK },
+ { "d", USEC_PER_DAY },
+ { "h", USEC_PER_HOUR },
+ { "min", USEC_PER_MINUTE },
+ { "s", USEC_PER_SEC },
+ { "ms", USEC_PER_MSEC },
+ { "us", 1 },
+ };
+
+ unsigned i;
+ char *p = buf;
+ bool something = false;
+
+ assert(buf);
+ assert(l > 0);
+
+ if (t == (usec_t) -1)
+ return NULL;
+
+ if (t <= 0) {
+ snprintf(p, l, "0");
+ p[l-1] = 0;
+ return p;
+ }
+
+ /* The result of this function can be parsed with parse_sec */
+
+ for (i = 0; i < ELEMENTSOF(table); i++) {
+ int k;
+ size_t n;
+ bool done = false;
+ usec_t a, b;
+
+ if (t <= 0)
+ break;
+
+ if (t < accuracy && something)
+ break;
+
+ if (t < table[i].usec)
+ continue;
+
+ if (l <= 1)
+ break;
+
+ a = t / table[i].usec;
+ b = t % table[i].usec;
+
+ /* Let's see if we should shows this in dot notation */
+ if (t < USEC_PER_MINUTE && b > 0) {
+ usec_t cc;
+ int j;
+
+ j = 0;
+ for (cc = table[i].usec; cc > 1; cc /= 10)
+ j++;
+
+ for (cc = accuracy; cc > 1; cc /= 10) {
+ b /= 10;
+ j--;
+ }
+
+ if (j > 0) {
+ k = snprintf(p, l,
+ "%s%llu.%0*llu%s",
+ p > buf ? " " : "",
+ (unsigned long long) a,
+ j,
+ (unsigned long long) b,
+ table[i].suffix);
+
+ t = 0;
+ done = true;
+ }
+ }
+
+ /* No? Then let's show it normally */
+ if (!done) {
+ k = snprintf(p, l,
+ "%s%llu%s",
+ p > buf ? " " : "",
+ (unsigned long long) a,
+ table[i].suffix);
+
+ t = b;
+ }
+
+ n = MIN((size_t) k, l);
+
+ l -= n;
+ p += n;
+
+ something = true;
+ }
+
+ *p = 0;
+
+ return buf;
+}
+
+void dual_timestamp_serialize(FILE *f, const char *name, dual_timestamp *t) {
+
+ assert(f);
+ assert(name);
+ assert(t);
+
+ if (!dual_timestamp_is_set(t))
+ return;
+
+ fprintf(f, "%s=%llu %llu\n",
+ name,
+ (unsigned long long) t->realtime,
+ (unsigned long long) t->monotonic);
+}
+
+void dual_timestamp_deserialize(const char *value, dual_timestamp *t) {
+ unsigned long long a, b;
+
+ assert(value);
+ assert(t);
+
+ if (sscanf(value, "%lli %llu", &a, &b) != 2)
+ log_debug("Failed to parse finish timestamp value %s", value);
+ else {
+ t->realtime = a;
+ t->monotonic = b;
+ }
+}
+
+int parse_timestamp(const char *t, usec_t *usec) {
+ static const struct {
+ const char *name;
+ const int nr;
+ } day_nr[] = {
+ { "Sunday", 0 },
+ { "Sun", 0 },
+ { "Monday", 1 },
+ { "Mon", 1 },
+ { "Tuesday", 2 },
+ { "Tue", 2 },
+ { "Wednesday", 3 },
+ { "Wed", 3 },
+ { "Thursday", 4 },
+ { "Thu", 4 },
+ { "Friday", 5 },
+ { "Fri", 5 },
+ { "Saturday", 6 },
+ { "Sat", 6 },
+ };
+
+ const char *k;
+ struct tm tm, copy;
+ time_t x;
+ usec_t plus = 0, minus = 0, ret;
+ int r, weekday = -1;
+ unsigned i;
+
+ /*
+ * Allowed syntaxes:
+ *
+ * 2012-09-22 16:34:22
+ * 2012-09-22 16:34 (seconds will be set to 0)
+ * 2012-09-22 (time will be set to 00:00:00)
+ * 16:34:22 (date will be set to today)
+ * 16:34 (date will be set to today, seconds to 0)
+ * now
+ * yesterday (time is set to 00:00:00)
+ * today (time is set to 00:00:00)
+ * tomorrow (time is set to 00:00:00)
+ * +5min
+ * -5days
+ *
+ */
+
+ assert(t);
+ assert(usec);
+
+ x = time(NULL);
+ assert_se(localtime_r(&x, &tm));
+ tm.tm_isdst = -1;
+
+ if (streq(t, "now"))
+ goto finish;
+
+ else if (streq(t, "today")) {
+ tm.tm_sec = tm.tm_min = tm.tm_hour = 0;
+ goto finish;
+
+ } else if (streq(t, "yesterday")) {
+ tm.tm_mday --;
+ tm.tm_sec = tm.tm_min = tm.tm_hour = 0;
+ goto finish;
+
+ } else if (streq(t, "tomorrow")) {
+ tm.tm_mday ++;
+ tm.tm_sec = tm.tm_min = tm.tm_hour = 0;
+ goto finish;
+
+ } else if (t[0] == '+') {
+
+ r = parse_sec(t+1, &plus);
+ if (r < 0)
+ return r;
+
+ goto finish;
+ } else if (t[0] == '-') {
+
+ r = parse_sec(t+1, &minus);
+ if (r < 0)
+ return r;
+
+ goto finish;
+
+ } else if (endswith(t, " ago")) {
+ _cleanup_free_ char *z;
+
+ z = strndup(t, strlen(t) - 4);
+ if (!z)
+ return -ENOMEM;
+
+ r = parse_sec(z, &minus);
+ if (r < 0)
+ return r;
+
+ goto finish;
+ }
+
+ for (i = 0; i < ELEMENTSOF(day_nr); i++) {
+ size_t skip;
+
+ if (!startswith_no_case(t, day_nr[i].name))
+ continue;
+
+ skip = strlen(day_nr[i].name);
+ if (t[skip] != ' ')
+ continue;
+
+ weekday = day_nr[i].nr;
+ t += skip + 1;
+ break;
+ }
+
+ copy = tm;
+ k = strptime(t, "%y-%m-%d %H:%M:%S", &tm);
+ if (k && *k == 0)
+ goto finish;
+
+ tm = copy;
+ k = strptime(t, "%Y-%m-%d %H:%M:%S", &tm);
+ if (k && *k == 0)
+ goto finish;
+
+ tm = copy;
+ k = strptime(t, "%y-%m-%d %H:%M", &tm);
+ if (k && *k == 0) {
+ tm.tm_sec = 0;
+ goto finish;
+ }
+
+ tm = copy;
+ k = strptime(t, "%Y-%m-%d %H:%M", &tm);
+ if (k && *k == 0) {
+ tm.tm_sec = 0;
+ goto finish;
+ }
+
+ tm = copy;
+ k = strptime(t, "%y-%m-%d", &tm);
+ if (k && *k == 0) {
+ tm.tm_sec = tm.tm_min = tm.tm_hour = 0;
+ goto finish;
+ }
+
+ tm = copy;
+ k = strptime(t, "%Y-%m-%d", &tm);
+ if (k && *k == 0) {
+ tm.tm_sec = tm.tm_min = tm.tm_hour = 0;
+ goto finish;
+ }
+
+ tm = copy;
+ k = strptime(t, "%H:%M:%S", &tm);
+ if (k && *k == 0)
+ goto finish;
+
+ tm = copy;
+ k = strptime(t, "%H:%M", &tm);
+ if (k && *k == 0) {
+ tm.tm_sec = 0;
+ goto finish;
+ }
+
+ return -EINVAL;
+
+finish:
+ x = mktime(&tm);
+ if (x == (time_t) -1)
+ return -EINVAL;
+
+ if (weekday >= 0 && tm.tm_wday != weekday)
+ return -EINVAL;
+
+ ret = (usec_t) x * USEC_PER_SEC;
+
+ ret += plus;
+ if (ret > minus)
+ ret -= minus;
+ else
+ ret = 0;
+
+ *usec = ret;
+
+ return 0;
+}
+
+int parse_sec(const char *t, usec_t *usec) {
+ static const struct {
+ const char *suffix;
+ usec_t usec;
+ } table[] = {
+ { "seconds", USEC_PER_SEC },
+ { "second", USEC_PER_SEC },
+ { "sec", USEC_PER_SEC },
+ { "s", USEC_PER_SEC },
+ { "minutes", USEC_PER_MINUTE },
+ { "minute", USEC_PER_MINUTE },
+ { "min", USEC_PER_MINUTE },
+ { "months", USEC_PER_MONTH },
+ { "month", USEC_PER_MONTH },
+ { "msec", USEC_PER_MSEC },
+ { "ms", USEC_PER_MSEC },
+ { "m", USEC_PER_MINUTE },
+ { "hours", USEC_PER_HOUR },
+ { "hour", USEC_PER_HOUR },
+ { "hr", USEC_PER_HOUR },
+ { "h", USEC_PER_HOUR },
+ { "days", USEC_PER_DAY },
+ { "day", USEC_PER_DAY },
+ { "d", USEC_PER_DAY },
+ { "weeks", USEC_PER_WEEK },
+ { "week", USEC_PER_WEEK },
+ { "w", USEC_PER_WEEK },
+ { "years", USEC_PER_YEAR },
+ { "year", USEC_PER_YEAR },
+ { "y", USEC_PER_YEAR },
+ { "usec", 1ULL },
+ { "us", 1ULL },
+ { "", USEC_PER_SEC }, /* default is sec */
+ };
+
+ const char *p;
+ usec_t r = 0;
+ bool something = false;
+
+ assert(t);
+ assert(usec);
+
+ p = t;
+ for (;;) {
+ long long l, z = 0;
+ char *e;
+ unsigned i, n = 0;
+
+ p += strspn(p, WHITESPACE);
+
+ if (*p == 0) {
+ if (!something)
+ return -EINVAL;
+
+ break;
+ }
+
+ errno = 0;
+ l = strtoll(p, &e, 10);
+
+ if (errno > 0)
+ return -errno;
+
+ if (l < 0)
+ return -ERANGE;
+
+ if (*e == '.') {
+ char *b = e + 1;
+
+ errno = 0;
+ z = strtoll(b, &e, 10);
+ if (errno > 0)
+ return -errno;
+
+ if (z < 0)
+ return -ERANGE;
+
+ if (e == b)
+ return -EINVAL;
+
+ n = e - b;
+
+ } else if (e == p)
+ return -EINVAL;
+
+ e += strspn(e, WHITESPACE);
+
+ for (i = 0; i < ELEMENTSOF(table); i++)
+ if (startswith(e, table[i].suffix)) {
+ usec_t k = (usec_t) z * table[i].usec;
+
+ for (; n > 0; n--)
+ k /= 10;
+
+ r += (usec_t) l * table[i].usec + k;
+ p = e + strlen(table[i].suffix);
+
+ something = true;
+ break;
+ }
+
+ if (i >= ELEMENTSOF(table))
+ return -EINVAL;
+
+ }
+
+ *usec = r;
+
+ return 0;
+}
+
+int parse_nsec(const char *t, nsec_t *nsec) {
+ static const struct {
+ const char *suffix;
+ nsec_t nsec;
+ } table[] = {
+ { "seconds", NSEC_PER_SEC },
+ { "second", NSEC_PER_SEC },
+ { "sec", NSEC_PER_SEC },
+ { "s", NSEC_PER_SEC },
+ { "minutes", NSEC_PER_MINUTE },
+ { "minute", NSEC_PER_MINUTE },
+ { "min", NSEC_PER_MINUTE },
+ { "months", NSEC_PER_MONTH },
+ { "month", NSEC_PER_MONTH },
+ { "msec", NSEC_PER_MSEC },
+ { "ms", NSEC_PER_MSEC },
+ { "m", NSEC_PER_MINUTE },
+ { "hours", NSEC_PER_HOUR },
+ { "hour", NSEC_PER_HOUR },
+ { "hr", NSEC_PER_HOUR },
+ { "h", NSEC_PER_HOUR },
+ { "days", NSEC_PER_DAY },
+ { "day", NSEC_PER_DAY },
+ { "d", NSEC_PER_DAY },
+ { "weeks", NSEC_PER_WEEK },
+ { "week", NSEC_PER_WEEK },
+ { "w", NSEC_PER_WEEK },
+ { "years", NSEC_PER_YEAR },
+ { "year", NSEC_PER_YEAR },
+ { "y", NSEC_PER_YEAR },
+ { "usec", NSEC_PER_USEC },
+ { "us", NSEC_PER_USEC },
+ { "nsec", 1ULL },
+ { "ns", 1ULL },
+ { "", 1ULL }, /* default is nsec */
+ };
+
+ const char *p;
+ nsec_t r = 0;
+ bool something = false;
+
+ assert(t);
+ assert(nsec);
+
+ p = t;
+ for (;;) {
+ long long l, z = 0;
+ char *e;
+ unsigned i, n = 0;
+
+ p += strspn(p, WHITESPACE);
+
+ if (*p == 0) {
+ if (!something)
+ return -EINVAL;
+
+ break;
+ }
+
+ errno = 0;
+ l = strtoll(p, &e, 10);
+
+ if (errno > 0)
+ return -errno;
+
+ if (l < 0)
+ return -ERANGE;
+
+ if (*e == '.') {
+ char *b = e + 1;
+
+ errno = 0;
+ z = strtoll(b, &e, 10);
+ if (errno > 0)
+ return -errno;
+
+ if (z < 0)
+ return -ERANGE;
+
+ if (e == b)
+ return -EINVAL;
+
+ n = e - b;
+
+ } else if (e == p)
+ return -EINVAL;
+
+ e += strspn(e, WHITESPACE);
+
+ for (i = 0; i < ELEMENTSOF(table); i++)
+ if (startswith(e, table[i].suffix)) {
+ nsec_t k = (nsec_t) z * table[i].nsec;
+
+ for (; n > 0; n--)
+ k /= 10;
+
+ r += (nsec_t) l * table[i].nsec + k;
+ p = e + strlen(table[i].suffix);
+
+ something = true;
+ break;
+ }
+
+ if (i >= ELEMENTSOF(table))
+ return -EINVAL;
+
+ }
+
+ *nsec = r;
+
+ return 0;
+}
diff --git a/src/shared/time-util.h b/src/shared/time-util.h
new file mode 100644
index 0000000000..f27a006891
--- /dev/null
+++ b/src/shared/time-util.h
@@ -0,0 +1,85 @@
+/*-*- Mode: C; c-basic-offset: 8; indent-tabs-mode: nil -*-*/
+
+#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 <stdio.h>
+#include <inttypes.h>
+
+typedef uint64_t usec_t;
+typedef uint64_t nsec_t;
+
+#include "macro.h"
+
+typedef struct dual_timestamp {
+ usec_t realtime;
+ usec_t monotonic;
+} dual_timestamp;
+
+#define MSEC_PER_SEC 1000ULL
+#define USEC_PER_SEC 1000000ULL
+#define USEC_PER_MSEC 1000ULL
+#define NSEC_PER_SEC 1000000000ULL
+#define NSEC_PER_MSEC 1000000ULL
+#define NSEC_PER_USEC 1000ULL
+
+#define USEC_PER_MINUTE (60ULL*USEC_PER_SEC)
+#define NSEC_PER_MINUTE (60ULL*NSEC_PER_SEC)
+#define USEC_PER_HOUR (60ULL*USEC_PER_MINUTE)
+#define NSEC_PER_HOUR (60ULL*NSEC_PER_MINUTE)
+#define USEC_PER_DAY (24ULL*USEC_PER_HOUR)
+#define NSEC_PER_DAY (24ULL*NSEC_PER_HOUR)
+#define USEC_PER_WEEK (7ULL*USEC_PER_DAY)
+#define NSEC_PER_WEEK (7ULL*NSEC_PER_DAY)
+#define USEC_PER_MONTH (2629800ULL*USEC_PER_SEC)
+#define NSEC_PER_MONTH (2629800ULL*NSEC_PER_SEC)
+#define USEC_PER_YEAR (31557600ULL*USEC_PER_SEC)
+#define NSEC_PER_YEAR (31557600ULL*NSEC_PER_SEC)
+
+#define FORMAT_TIMESTAMP_MAX ((4*4+1)+11+9+4+1) /* weekdays can be unicode */
+#define FORMAT_TIMESTAMP_RELATIVE_MAX 256
+#define FORMAT_TIMESPAN_MAX 64
+
+usec_t now(clockid_t clock);
+
+dual_timestamp* dual_timestamp_get(dual_timestamp *ts);
+dual_timestamp* dual_timestamp_from_realtime(dual_timestamp *ts, usec_t u);
+dual_timestamp* dual_timestamp_from_monotonic(dual_timestamp *ts, usec_t u);
+
+#define dual_timestamp_is_set(ts) ((ts)->realtime > 0)
+
+usec_t timespec_load(const struct timespec *ts) _pure_;
+struct timespec *timespec_store(struct timespec *ts, usec_t u);
+
+usec_t timeval_load(const struct timeval *tv) _pure_;
+struct timeval *timeval_store(struct timeval *tv, usec_t u);
+
+char *format_timestamp(char *buf, size_t l, usec_t t);
+char *format_timestamp_relative(char *buf, size_t l, usec_t t);
+char *format_timespan(char *buf, size_t l, usec_t t, usec_t accuracy);
+
+void dual_timestamp_serialize(FILE *f, const char *name, dual_timestamp *t);
+void dual_timestamp_deserialize(const char *value, dual_timestamp *t);
+
+int parse_timestamp(const char *t, usec_t *usec);
+
+int parse_sec(const char *t, usec_t *usec);
+int parse_nsec(const char *t, nsec_t *nsec);
diff --git a/src/shared/unit-name.c b/src/shared/unit-name.c
new file mode 100644
index 0000000000..a809713595
--- /dev/null
+++ b/src/shared/unit-name.c
@@ -0,0 +1,549 @@
+/*-*- Mode: C; c-basic-offset: 8; indent-tabs-mode: nil -*-*/
+
+/***
+ This file is part 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 <string.h>
+#include <assert.h>
+
+#include "path-util.h"
+#include "util.h"
+#include "unit-name.h"
+
+#define VALID_CHARS \
+ "0123456789" \
+ "abcdefghijklmnopqrstuvwxyz" \
+ "ABCDEFGHIJKLMNOPQRSTUVWXYZ" \
+ ":-_.\\"
+
+static const char* const unit_type_table[_UNIT_TYPE_MAX] = {
+ [UNIT_SERVICE] = "service",
+ [UNIT_SOCKET] = "socket",
+ [UNIT_TARGET] = "target",
+ [UNIT_DEVICE] = "device",
+ [UNIT_MOUNT] = "mount",
+ [UNIT_AUTOMOUNT] = "automount",
+ [UNIT_SNAPSHOT] = "snapshot",
+ [UNIT_TIMER] = "timer",
+ [UNIT_SWAP] = "swap",
+ [UNIT_PATH] = "path",
+};
+
+DEFINE_STRING_TABLE_LOOKUP(unit_type, UnitType);
+
+static const char* const unit_load_state_table[_UNIT_LOAD_STATE_MAX] = {
+ [UNIT_STUB] = "stub",
+ [UNIT_LOADED] = "loaded",
+ [UNIT_ERROR] = "error",
+ [UNIT_MERGED] = "merged",
+ [UNIT_MASKED] = "masked"
+};
+
+DEFINE_STRING_TABLE_LOOKUP(unit_load_state, UnitLoadState);
+
+bool unit_name_is_valid(const char *n, bool template_ok) {
+ const char *e, *i, *at;
+
+ /* Valid formats:
+ *
+ * string@instance.suffix
+ * string.suffix
+ */
+
+ assert(n);
+
+ if (strlen(n) >= UNIT_NAME_MAX)
+ return false;
+
+ e = strrchr(n, '.');
+ if (!e || e == n)
+ return false;
+
+ if (unit_type_from_string(e + 1) < 0)
+ return false;
+
+ for (i = n, at = NULL; i < e; i++) {
+
+ if (*i == '@' && !at)
+ at = i;
+
+ if (!strchr("@" VALID_CHARS, *i))
+ return false;
+ }
+
+ if (at) {
+ if (at == n)
+ return false;
+
+ if (!template_ok && at+1 == e)
+ return false;
+ }
+
+ return true;
+}
+
+bool unit_instance_is_valid(const char *i) {
+ assert(i);
+
+ /* The max length depends on the length of the string, so we
+ * don't really check this here. */
+
+ if (i[0] == 0)
+ return false;
+
+ /* We allow additional @ in the instance string, we do not
+ * allow them in the prefix! */
+
+ for (; *i; i++)
+ if (!strchr("@" VALID_CHARS, *i))
+ return false;
+
+ return true;
+}
+
+bool unit_prefix_is_valid(const char *p) {
+
+ /* We don't allow additional @ in the instance string */
+
+ if (p[0] == 0)
+ return false;
+
+ for (; *p; p++)
+ if (!strchr(VALID_CHARS, *p))
+ return false;
+
+ return true;
+}
+
+int unit_name_to_instance(const char *n, char **instance) {
+ const char *p, *d;
+ char *i;
+
+ assert(n);
+ assert(instance);
+
+ /* Everything past the first @ and before the last . is the instance */
+ p = strchr(n, '@');
+ if (!p) {
+ *instance = NULL;
+ return 0;
+ }
+
+ assert_se(d = strrchr(n, '.'));
+ assert(p < d);
+
+ i = strndup(p+1, d-p-1);
+ if (!i)
+ return -ENOMEM;
+
+ *instance = i;
+ return 0;
+}
+
+char *unit_name_to_prefix_and_instance(const char *n) {
+ const char *d;
+
+ assert(n);
+
+ assert_se(d = strrchr(n, '.'));
+
+ return strndup(n, d - n);
+}
+
+char *unit_name_to_prefix(const char *n) {
+ const char *p;
+
+ p = strchr(n, '@');
+ if (p)
+ return strndup(n, p - n);
+
+ return unit_name_to_prefix_and_instance(n);
+}
+
+char *unit_name_change_suffix(const char *n, const char *suffix) {
+ char *e, *r;
+ size_t a, b;
+
+ assert(n);
+ assert(unit_name_is_valid(n, true));
+ assert(suffix);
+
+ assert_se(e = strrchr(n, '.'));
+ a = e - n;
+ b = strlen(suffix);
+
+ r = new(char, a + b + 1);
+ if (!r)
+ return NULL;
+
+ memcpy(r, n, a);
+ memcpy(r+a, suffix, b+1);
+
+ return r;
+}
+
+char *unit_name_build(const char *prefix, const char *instance, const char *suffix) {
+ assert(prefix);
+ assert(unit_prefix_is_valid(prefix));
+ assert(!instance || unit_instance_is_valid(instance));
+ assert(suffix);
+
+ if (!instance)
+ return strappend(prefix, suffix);
+
+ return strjoin(prefix, "@", instance, suffix, NULL);
+}
+
+static char *do_escape_char(char c, char *t) {
+ *(t++) = '\\';
+ *(t++) = 'x';
+ *(t++) = hexchar(c >> 4);
+ *(t++) = hexchar(c);
+ return t;
+}
+
+static char *do_escape(const char *f, char *t) {
+ assert(f);
+ assert(t);
+
+ /* do not create units with a leading '.', like for "/.dotdir" mount points */
+ if (*f == '.') {
+ t = do_escape_char(*f, t);
+ f++;
+ }
+
+ for (; *f; f++) {
+ if (*f == '/')
+ *(t++) = '-';
+ else if (*f == '-' || *f == '\\' || !strchr(VALID_CHARS, *f))
+ t = do_escape_char(*f, t);
+ else
+ *(t++) = *f;
+ }
+
+ return t;
+}
+
+char *unit_name_escape(const char *f) {
+ char *r, *t;
+
+ r = new(char, strlen(f)*4+1);
+ if (!r)
+ return NULL;
+
+ t = do_escape(f, r);
+ *t = 0;
+
+ return r;
+}
+
+char *unit_name_unescape(const char *f) {
+ char *r, *t;
+
+ assert(f);
+
+ r = strdup(f);
+ if (!r)
+ return NULL;
+
+ for (t = r; *f; f++) {
+ if (*f == '-')
+ *(t++) = '/';
+ else if (*f == '\\') {
+ int a, b;
+
+ if (f[1] != 'x' ||
+ (a = unhexchar(f[2])) < 0 ||
+ (b = unhexchar(f[3])) < 0) {
+ /* Invalid escape code, let's take it literal then */
+ *(t++) = '\\';
+ } else {
+ *(t++) = (char) ((a << 4) | b);
+ f += 3;
+ }
+ } else
+ *(t++) = *f;
+ }
+
+ *t = 0;
+
+ return r;
+}
+
+char *unit_name_path_escape(const char *f) {
+ char *p, *e;
+
+ assert(f);
+
+ p = strdup(f);
+ if (!p)
+ return NULL;
+
+ path_kill_slashes(p);
+
+ if (streq(p, "/")) {
+ free(p);
+ return strdup("-");
+ }
+
+ e = unit_name_escape(p[0] == '/' ? p + 1 : p);
+ free(p);
+
+ return e;
+}
+
+char *unit_name_path_unescape(const char *f) {
+ char *e;
+
+ assert(f);
+
+ e = unit_name_unescape(f);
+ if (!e)
+ return NULL;
+
+ if (e[0] != '/') {
+ char *w;
+
+ w = strappend("/", e);
+ free(e);
+
+ return w;
+ }
+
+ return e;
+}
+
+bool unit_name_is_template(const char *n) {
+ const char *p;
+
+ assert(n);
+
+ p = strchr(n, '@');
+ if (!p)
+ return false;
+
+ return p[1] == '.';
+}
+
+bool unit_name_is_instance(const char *n) {
+ const char *p;
+
+ assert(n);
+
+ p = strchr(n, '@');
+ if (!p)
+ return false;
+
+ return p[1] != '.';
+}
+
+char *unit_name_replace_instance(const char *f, const char *i) {
+ const char *p, *e;
+ char *r, *k;
+ size_t a, b;
+
+ assert(f);
+
+ p = strchr(f, '@');
+ if (!p)
+ return strdup(f);
+
+ e = strrchr(f, '.');
+ if (!e)
+ assert_se(e = strchr(f, 0));
+
+ a = p - f;
+ b = strlen(i);
+
+ r = new(char, a + 1 + b + strlen(e) + 1);
+ if (!r)
+ return NULL;
+
+ k = mempcpy(r, f, a + 1);
+ k = mempcpy(k, i, b);
+ strcpy(k, e);
+
+ return r;
+}
+
+char *unit_name_template(const char *f) {
+ const char *p, *e;
+ char *r;
+ size_t a;
+
+ p = strchr(f, '@');
+ if (!p)
+ return strdup(f);
+
+ assert_se(e = strrchr(f, '.'));
+ a = p - f + 1;
+
+ r = new(char, a + strlen(e) + 1);
+ if (!r)
+ return NULL;
+
+ strcpy(mempcpy(r, f, a), e);
+ return r;
+
+}
+
+char *unit_name_from_path(const char *path, const char *suffix) {
+ char *p, *r;
+
+ assert(path);
+ assert(suffix);
+
+ p = unit_name_path_escape(path);
+ if (!p)
+ return NULL;
+
+ r = strappend(p, suffix);
+ free(p);
+
+ return r;
+}
+
+char *unit_name_from_path_instance(const char *prefix, const char *path, const char *suffix) {
+ char *p, *r;
+
+ assert(prefix);
+ assert(path);
+ assert(suffix);
+
+ p = unit_name_path_escape(path);
+ if (!p)
+ return NULL;
+
+ r = strjoin(prefix, "@", p, suffix, NULL);
+ free(p);
+
+ return r;
+}
+
+char *unit_name_to_path(const char *name) {
+ char *w, *e;
+
+ assert(name);
+
+ w = unit_name_to_prefix(name);
+ if (!w)
+ return NULL;
+
+ e = unit_name_path_unescape(w);
+ free(w);
+
+ return e;
+}
+
+char *unit_dbus_path_from_name(const char *name) {
+ char *e, *p;
+
+ assert(name);
+
+ e = bus_path_escape(name);
+ if (!e)
+ return NULL;
+
+ p = strappend("/org/freedesktop/systemd1/unit/", e);
+ free(e);
+
+ return p;
+}
+
+char *unit_name_mangle(const char *name) {
+ char *r, *t;
+ const char *f;
+
+ assert(name);
+
+ /* Try to turn a string that might not be a unit name into a
+ * sensible unit name. */
+
+ if (is_device_path(name))
+ return unit_name_from_path(name, ".device");
+
+ if (path_is_absolute(name))
+ return unit_name_from_path(name, ".mount");
+
+ /* We'll only escape the obvious characters here, to play
+ * safe. */
+
+ r = new(char, strlen(name) * 4 + 1 + sizeof(".service")-1);
+ if (!r)
+ return NULL;
+
+ for (f = name, t = r; *f; f++) {
+ if (*f == '/')
+ *(t++) = '-';
+ else if (!strchr("@" VALID_CHARS, *f))
+ t = do_escape_char(*f, t);
+ else
+ *(t++) = *f;
+ }
+
+ if (unit_name_to_type(name) < 0)
+ strcpy(t, ".service");
+ else
+ *t = 0;
+
+ return r;
+}
+
+char *snapshot_name_mangle(const char *name) {
+ char *r, *t;
+ const char *f;
+
+ assert(name);
+
+ /* Similar to unit_name_mangle(), but is called when we know
+ * that this is about snapshot units. */
+
+ r = new(char, strlen(name) * 4 + 1 + sizeof(".snapshot")-1);
+ if (!r)
+ return NULL;
+
+ for (f = name, t = r; *f; f++) {
+ if (*f == '/')
+ *(t++) = '-';
+ else if (!strchr(VALID_CHARS, *f))
+ t = do_escape_char(*f, t);
+ else
+ *(t++) = *f;
+ }
+
+ if (!endswith(name, ".snapshot"))
+ strcpy(t, ".snapshot");
+ else
+ *t = 0;
+
+ return r;
+}
+
+UnitType unit_name_to_type(const char *n) {
+ const char *e;
+
+ assert(n);
+
+ e = strrchr(n, '.');
+ if (!e)
+ return _UNIT_TYPE_INVALID;
+
+ return unit_type_from_string(e + 1);
+}
diff --git a/src/shared/unit-name.h b/src/shared/unit-name.h
new file mode 100644
index 0000000000..9eca8eb3c1
--- /dev/null
+++ b/src/shared/unit-name.h
@@ -0,0 +1,97 @@
+/*-*- Mode: C; c-basic-offset: 8; indent-tabs-mode: nil -*-*/
+
+#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"
+
+#define UNIT_NAME_MAX 256
+
+typedef enum UnitType UnitType;
+typedef enum UnitLoadState UnitLoadState;
+
+enum UnitType {
+ UNIT_SERVICE = 0,
+ UNIT_SOCKET,
+ UNIT_TARGET,
+ UNIT_DEVICE,
+ UNIT_MOUNT,
+ UNIT_AUTOMOUNT,
+ UNIT_SNAPSHOT,
+ UNIT_TIMER,
+ UNIT_SWAP,
+ UNIT_PATH,
+ _UNIT_TYPE_MAX,
+ _UNIT_TYPE_INVALID = -1
+};
+
+enum UnitLoadState {
+ UNIT_STUB = 0,
+ UNIT_LOADED,
+ UNIT_ERROR,
+ UNIT_MERGED,
+ UNIT_MASKED,
+ _UNIT_LOAD_STATE_MAX,
+ _UNIT_LOAD_STATE_INVALID = -1
+};
+
+const char *unit_type_to_string(UnitType i) _const_;
+UnitType unit_type_from_string(const char *s) _pure_;
+
+const char *unit_load_state_to_string(UnitLoadState i) _const_;
+UnitLoadState unit_load_state_from_string(const char *s) _pure_;
+
+int unit_name_to_instance(const char *n, char **instance);
+char* unit_name_to_prefix(const char *n);
+char* unit_name_to_prefix_and_instance(const char *n);
+
+bool unit_name_is_valid(const char *n, bool template_ok) _pure_;
+bool unit_prefix_is_valid(const char *p) _pure_;
+bool unit_instance_is_valid(const char *i) _pure_;
+
+UnitType unit_name_to_type(const char *n) _pure_;
+
+char *unit_name_change_suffix(const char *n, const char *suffix);
+
+char *unit_name_build(const char *prefix, const char *instance, const char *suffix);
+
+char *unit_name_escape(const char *f);
+char *unit_name_unescape(const char *f);
+char *unit_name_path_escape(const char *f);
+char *unit_name_path_unescape(const char *f);
+
+bool unit_name_is_template(const char *n) _pure_;
+bool unit_name_is_instance(const char *n) _pure_;
+
+char *unit_name_replace_instance(const char *f, const char *i);
+
+char *unit_name_template(const char *f);
+
+char *unit_name_from_path(const char *path, const char *suffix);
+char *unit_name_from_path_instance(const char *prefix, const char *path, const char *suffix);
+char *unit_name_to_path(const char *name);
+
+char *unit_dbus_path_from_name(const char *name);
+
+char *unit_name_mangle(const char *name);
+char *snapshot_name_mangle(const char *name);
diff --git a/src/shared/utf8.c b/src/shared/utf8.c
new file mode 100644
index 0000000000..3964e8b1ce
--- /dev/null
+++ b/src/shared/utf8.c
@@ -0,0 +1,322 @@
+/*-*- Mode: C; c-basic-offset: 8; indent-tabs-mode: nil -*-*/
+
+/***
+ This file is part of systemd.
+
+ Copyright 2012 Lennart Poettering
+
+ systemd is free software; you can redistribute it and/or modify it
+ under the terms of the GNU Lesser General Public License as published by
+ the Free Software Foundation; either version 2.1 of the License, or
+ (at your option) any later version.
+
+ systemd is distributed in the hope that it will be useful, but
+ WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ Lesser General 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/>.
+***/
+
+/* This file is based on the GLIB utf8 validation functions. The
+ * original license text follows. */
+
+/* gutf8.c - Operations on UTF-8 strings.
+ *
+ * Copyright (C) 1999 Tom Tromey
+ * Copyright (C) 2000 Red Hat, Inc.
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Library General Public
+ * License as published by the Free Software Foundation; either
+ * version 2 of the License, or (at your option) any later version.
+ *
+ * This library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Library General Public License for more details.
+ *
+ * You should have received a copy of the GNU Library General Public
+ * License along with this library; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
+ */
+
+#include <errno.h>
+#include <stdlib.h>
+#include <inttypes.h>
+#include <string.h>
+#include <stdbool.h>
+
+#include "utf8.h"
+#include "util.h"
+
+#define FILTER_CHAR '_'
+
+static inline bool is_unicode_valid(uint32_t ch) {
+
+ if (ch >= 0x110000) /* End of unicode space */
+ return false;
+ if ((ch & 0xFFFFF800) == 0xD800) /* Reserved area for UTF-16 */
+ return false;
+ if ((ch >= 0xFDD0) && (ch <= 0xFDEF)) /* Reserved */
+ return false;
+ if ((ch & 0xFFFE) == 0xFFFE) /* BOM (Byte Order Mark) */
+ return false;
+
+ return true;
+}
+
+static inline bool is_continuation_char(uint8_t ch) {
+ if ((ch & 0xc0) != 0x80) /* 10xxxxxx */
+ return false;
+ return true;
+}
+
+static inline void merge_continuation_char(uint32_t *u_ch, uint8_t ch) {
+ *u_ch <<= 6;
+ *u_ch |= ch & 0x3f;
+}
+
+static bool is_unicode_control(uint32_t ch) {
+
+ /*
+ 0 to ' '-1 is the C0 range.
+ DEL=0x7F, and DEL+1 to 0x9F is C1 range.
+ '\t' is in C0 range, but more or less harmless and commonly used.
+ */
+
+ return (ch < ' ' && ch != '\t') ||
+ (0x7F <= ch && ch <= 0x9F);
+}
+
+char* utf8_is_printable_n(const char* str, size_t length) {
+ uint32_t val = 0;
+ uint32_t min = 0;
+ const uint8_t *p;
+
+ assert(str);
+
+ for (p = (const uint8_t*) str; length; p++, length--) {
+ if (*p < 128) {
+ val = *p;
+ } else {
+ if ((*p & 0xe0) == 0xc0) { /* 110xxxxx two-char seq. */
+ min = 128;
+ val = (uint32_t) (*p & 0x1e);
+ goto ONE_REMAINING;
+ } else if ((*p & 0xf0) == 0xe0) { /* 1110xxxx three-char seq.*/
+ min = (1 << 11);
+ val = (uint32_t) (*p & 0x0f);
+ goto TWO_REMAINING;
+ } else if ((*p & 0xf8) == 0xf0) { /* 11110xxx four-char seq */
+ min = (1 << 16);
+ val = (uint32_t) (*p & 0x07);
+ } else
+ goto error;
+
+ p++;
+ length--;
+ if (!length || !is_continuation_char(*p))
+ goto error;
+ merge_continuation_char(&val, *p);
+
+ TWO_REMAINING:
+ p++;
+ length--;
+ if (!is_continuation_char(*p))
+ goto error;
+ merge_continuation_char(&val, *p);
+
+ ONE_REMAINING:
+ p++;
+ length--;
+ if (!is_continuation_char(*p))
+ goto error;
+ merge_continuation_char(&val, *p);
+
+ if (val < min)
+ goto error;
+ }
+
+ if (is_unicode_control(val))
+ goto error;
+ }
+
+ return (char*) str;
+
+error:
+ return NULL;
+}
+
+static char* utf8_validate(const char *str, char *output) {
+ uint32_t val = 0;
+ uint32_t min = 0;
+ const uint8_t *p, *last;
+ int size;
+ uint8_t *o;
+
+ assert(str);
+
+ o = (uint8_t*) output;
+ for (p = (const uint8_t*) str; *p; p++) {
+ if (*p < 128) {
+ if (o)
+ *o = *p;
+ } else {
+ last = p;
+
+ if ((*p & 0xe0) == 0xc0) { /* 110xxxxx two-char seq. */
+ size = 2;
+ min = 128;
+ val = (uint32_t) (*p & 0x1e);
+ goto ONE_REMAINING;
+ } else if ((*p & 0xf0) == 0xe0) { /* 1110xxxx three-char seq.*/
+ size = 3;
+ min = (1 << 11);
+ val = (uint32_t) (*p & 0x0f);
+ goto TWO_REMAINING;
+ } else if ((*p & 0xf8) == 0xf0) { /* 11110xxx four-char seq */
+ size = 4;
+ min = (1 << 16);
+ val = (uint32_t) (*p & 0x07);
+ } else
+ goto error;
+
+ p++;
+ if (!is_continuation_char(*p))
+ goto error;
+ merge_continuation_char(&val, *p);
+
+ TWO_REMAINING:
+ p++;
+ if (!is_continuation_char(*p))
+ goto error;
+ merge_continuation_char(&val, *p);
+
+ ONE_REMAINING:
+ p++;
+ if (!is_continuation_char(*p))
+ goto error;
+ merge_continuation_char(&val, *p);
+
+ if (val < min)
+ goto error;
+
+ if (!is_unicode_valid(val))
+ goto error;
+
+ if (o) {
+ memcpy(o, last, (size_t) size);
+ o += size;
+ }
+
+ continue;
+
+ error:
+ if (o) {
+ *o = FILTER_CHAR;
+ p = last; /* We retry at the next character */
+ } else
+ goto failure;
+ }
+
+ if (o)
+ o++;
+ }
+
+ if (o) {
+ *o = '\0';
+ return output;
+ }
+
+ return (char*) str;
+
+failure:
+ return NULL;
+}
+
+char* utf8_is_valid (const char *str) {
+ return utf8_validate(str, NULL);
+}
+
+char* utf8_filter (const char *str) {
+ char *new_str;
+
+ assert(str);
+
+ new_str = malloc(strlen(str) + 1);
+ if (!new_str)
+ return NULL;
+
+ return utf8_validate(str, new_str);
+}
+
+char *ascii_is_valid(const char *str) {
+ const char *p;
+
+ assert(str);
+
+ for (p = str; *p; p++)
+ if ((unsigned char) *p >= 128)
+ return NULL;
+
+ return (char*) str;
+}
+
+char *ascii_filter(const char *str) {
+ const char *s;
+ char *r, *d;
+ size_t l;
+
+ assert(str);
+
+ l = strlen(str);
+ r = malloc(l + 1);
+ if (!r)
+ return NULL;
+
+ for (s = str, d = r; *s; s++)
+ if ((unsigned char) *s < 128)
+ *(d++) = *s;
+
+ *d = 0;
+
+ return r;
+}
+
+char *utf16_to_utf8(const void *s, size_t length) {
+ char *r;
+ const uint8_t *f;
+ uint8_t *t;
+
+ r = new(char, (length*3+1)/2 + 1);
+ if (!r)
+ return NULL;
+
+ t = (uint8_t*) r;
+
+ for (f = s; f < (const uint8_t*) s + length; f += 2) {
+ uint16_t c;
+
+ c = (f[1] << 8) | f[0];
+
+ if (c == 0) {
+ *t = 0;
+ return r;
+ } else if (c < 0x80) {
+ *(t++) = (uint8_t) c;
+ } else if (c < 0x800) {
+ *(t++) = (uint8_t) (0xc0 | (c >> 6));
+ *(t++) = (uint8_t) (0x80 | (c & 0x3f));
+ } else {
+ *(t++) = (uint8_t) (0xe0 | (c >> 12));
+ *(t++) = (uint8_t) (0x80 | ((c >> 6) & 0x3f));
+ *(t++) = (uint8_t) (0x80 | (c & 0x3f));
+ }
+ }
+
+ *t = 0;
+
+ return r;
+}
diff --git a/src/shared/utf8.h b/src/shared/utf8.h
new file mode 100644
index 0000000000..794ae15ab9
--- /dev/null
+++ b/src/shared/utf8.h
@@ -0,0 +1,34 @@
+/*-*- Mode: C; c-basic-offset: 8; indent-tabs-mode: nil -*-*/
+
+#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 "macro.h"
+
+char *utf8_is_valid(const char *s) _pure_;
+char *ascii_is_valid(const char *s) _pure_;
+
+char *utf8_is_printable_n(const char* str, size_t length) _pure_;
+
+char *utf8_filter(const char *s);
+char *ascii_filter(const char *s);
+
+char *utf16_to_utf8(const void *s, size_t length);
diff --git a/src/shared/util.c b/src/shared/util.c
new file mode 100644
index 0000000000..673e0da6b6
--- /dev/null
+++ b/src/shared/util.c
@@ -0,0 +1,5849 @@
+/*-*- Mode: C; c-basic-offset: 8; indent-tabs-mode: nil -*-*/
+
+/***
+ This file is part 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 <assert.h>
+#include <string.h>
+#include <unistd.h>
+#include <errno.h>
+#include <stdlib.h>
+#include <signal.h>
+#include <stdio.h>
+#include <syslog.h>
+#include <sched.h>
+#include <sys/resource.h>
+#include <linux/sched.h>
+#include <sys/types.h>
+#include <sys/stat.h>
+#include <fcntl.h>
+#include <dirent.h>
+#include <sys/ioctl.h>
+#include <linux/vt.h>
+#include <linux/tiocl.h>
+#include <termios.h>
+#include <stdarg.h>
+#include <sys/inotify.h>
+#include <sys/poll.h>
+#include <libgen.h>
+#include <ctype.h>
+#include <sys/prctl.h>
+#include <sys/utsname.h>
+#include <pwd.h>
+#include <netinet/ip.h>
+#include <linux/kd.h>
+#include <dlfcn.h>
+#include <sys/wait.h>
+#include <sys/time.h>
+#include <glob.h>
+#include <grp.h>
+#include <sys/mman.h>
+#include <sys/vfs.h>
+#include <linux/magic.h>
+#include <limits.h>
+#include <langinfo.h>
+#include <locale.h>
+#include <libgen.h>
+
+#include "macro.h"
+#include "util.h"
+#include "ioprio.h"
+#include "missing.h"
+#include "log.h"
+#include "strv.h"
+#include "label.h"
+#include "path-util.h"
+#include "exit-status.h"
+#include "hashmap.h"
+#include "env-util.h"
+#include "fileio.h"
+
+int saved_argc = 0;
+char **saved_argv = NULL;
+
+static volatile unsigned cached_columns = 0;
+static volatile unsigned cached_lines = 0;
+
+size_t page_size(void) {
+ static __thread size_t pgsz = 0;
+ long r;
+
+ if (_likely_(pgsz > 0))
+ return pgsz;
+
+ r = sysconf(_SC_PAGESIZE);
+ assert(r > 0);
+
+ pgsz = (size_t) r;
+ return pgsz;
+}
+
+bool streq_ptr(const char *a, const char *b) {
+
+ /* Like streq(), but tries to make sense of NULL pointers */
+
+ if (a && b)
+ return streq(a, b);
+
+ if (!a && !b)
+ return true;
+
+ return false;
+}
+
+char* endswith(const char *s, const char *postfix) {
+ size_t sl, pl;
+
+ assert(s);
+ assert(postfix);
+
+ sl = strlen(s);
+ pl = strlen(postfix);
+
+ if (pl == 0)
+ return (char*) s + sl;
+
+ if (sl < pl)
+ return NULL;
+
+ if (memcmp(s + sl - pl, postfix, pl) != 0)
+ return NULL;
+
+ return (char*) s + sl - pl;
+}
+
+char* startswith(const char *s, const char *prefix) {
+ const char *a, *b;
+
+ assert(s);
+ assert(prefix);
+
+ a = s, b = prefix;
+ for (;;) {
+ if (*b == 0)
+ return (char*) a;
+ if (*a != *b)
+ return NULL;
+
+ a++, b++;
+ }
+}
+
+char* startswith_no_case(const char *s, const char *prefix) {
+ const char *a, *b;
+
+ assert(s);
+ assert(prefix);
+
+ a = s, b = prefix;
+ for (;;) {
+ if (*b == 0)
+ return (char*) a;
+ if (tolower(*a) != tolower(*b))
+ return NULL;
+
+ a++, b++;
+ }
+}
+
+bool first_word(const char *s, const char *word) {
+ size_t sl, wl;
+
+ assert(s);
+ assert(word);
+
+ sl = strlen(s);
+ wl = strlen(word);
+
+ if (sl < wl)
+ return false;
+
+ if (wl == 0)
+ return true;
+
+ if (memcmp(s, word, wl) != 0)
+ return false;
+
+ return s[wl] == 0 ||
+ strchr(WHITESPACE, s[wl]);
+}
+
+int close_nointr(int fd) {
+ int r;
+
+ assert(fd >= 0);
+ r = close(fd);
+
+ /* Just ignore EINTR; a retry loop is the wrong
+ * thing to do on Linux.
+ *
+ * http://lkml.indiana.edu/hypermail/linux/kernel/0509.1/0877.html
+ * https://bugzilla.gnome.org/show_bug.cgi?id=682819
+ * http://utcc.utoronto.ca/~cks/space/blog/unix/CloseEINTR
+ * https://sites.google.com/site/michaelsafyan/software-engineering/checkforeintrwheninvokingclosethinkagain
+ */
+ if (_unlikely_(r < 0 && errno == EINTR))
+ return 0;
+ else if (r >= 0)
+ return r;
+ else
+ return -errno;
+}
+
+void close_nointr_nofail(int fd) {
+ PROTECT_ERRNO;
+
+ /* like close_nointr() but cannot fail, and guarantees errno
+ * is unchanged */
+
+ assert_se(close_nointr(fd) == 0);
+}
+
+void close_many(const int fds[], unsigned n_fd) {
+ unsigned i;
+
+ assert(fds || n_fd <= 0);
+
+ for (i = 0; i < n_fd; i++)
+ close_nointr_nofail(fds[i]);
+}
+
+int unlink_noerrno(const char *path) {
+ PROTECT_ERRNO;
+ int r;
+
+ r = unlink(path);
+ if (r < 0)
+ return -errno;
+
+ return 0;
+}
+
+int parse_boolean(const char *v) {
+ assert(v);
+
+ if (streq(v, "1") || v[0] == 'y' || v[0] == 'Y' || v[0] == 't' || v[0] == 'T' || strcaseeq(v, "on"))
+ return 1;
+ else if (streq(v, "0") || v[0] == 'n' || v[0] == 'N' || v[0] == 'f' || v[0] == 'F' || strcaseeq(v, "off"))
+ return 0;
+
+ return -EINVAL;
+}
+
+int parse_pid(const char *s, pid_t* ret_pid) {
+ unsigned long ul = 0;
+ pid_t pid;
+ int r;
+
+ assert(s);
+ assert(ret_pid);
+
+ r = safe_atolu(s, &ul);
+ if (r < 0)
+ return r;
+
+ pid = (pid_t) ul;
+
+ if ((unsigned long) pid != ul)
+ return -ERANGE;
+
+ if (pid <= 0)
+ return -ERANGE;
+
+ *ret_pid = pid;
+ return 0;
+}
+
+int parse_uid(const char *s, uid_t* ret_uid) {
+ unsigned long ul = 0;
+ uid_t uid;
+ int r;
+
+ assert(s);
+ assert(ret_uid);
+
+ r = safe_atolu(s, &ul);
+ if (r < 0)
+ return r;
+
+ uid = (uid_t) ul;
+
+ if ((unsigned long) uid != ul)
+ return -ERANGE;
+
+ *ret_uid = uid;
+ return 0;
+}
+
+int safe_atou(const char *s, unsigned *ret_u) {
+ char *x = NULL;
+ unsigned long l;
+
+ assert(s);
+ assert(ret_u);
+
+ errno = 0;
+ l = strtoul(s, &x, 0);
+
+ if (!x || x == s || *x || errno)
+ return errno > 0 ? -errno : -EINVAL;
+
+ if ((unsigned long) (unsigned) l != l)
+ return -ERANGE;
+
+ *ret_u = (unsigned) l;
+ return 0;
+}
+
+int safe_atoi(const char *s, int *ret_i) {
+ char *x = NULL;
+ long l;
+
+ assert(s);
+ assert(ret_i);
+
+ errno = 0;
+ l = strtol(s, &x, 0);
+
+ if (!x || x == s || *x || errno)
+ return errno > 0 ? -errno : -EINVAL;
+
+ if ((long) (int) l != l)
+ return -ERANGE;
+
+ *ret_i = (int) l;
+ return 0;
+}
+
+int safe_atollu(const char *s, long long unsigned *ret_llu) {
+ char *x = NULL;
+ unsigned long long l;
+
+ assert(s);
+ assert(ret_llu);
+
+ errno = 0;
+ l = strtoull(s, &x, 0);
+
+ if (!x || x == s || *x || errno)
+ return errno ? -errno : -EINVAL;
+
+ *ret_llu = l;
+ return 0;
+}
+
+int safe_atolli(const char *s, long long int *ret_lli) {
+ char *x = NULL;
+ long long l;
+
+ assert(s);
+ assert(ret_lli);
+
+ errno = 0;
+ l = strtoll(s, &x, 0);
+
+ if (!x || x == s || *x || errno)
+ return errno ? -errno : -EINVAL;
+
+ *ret_lli = l;
+ return 0;
+}
+
+int safe_atod(const char *s, double *ret_d) {
+ char *x = NULL;
+ double d;
+
+ assert(s);
+ assert(ret_d);
+
+ RUN_WITH_LOCALE(LC_NUMERIC_MASK, "C") {
+ errno = 0;
+ d = strtod(s, &x);
+ }
+
+ if (!x || x == s || *x || errno)
+ return errno ? -errno : -EINVAL;
+
+ *ret_d = (double) d;
+ return 0;
+}
+
+/* Split a string into words. */
+char *split(const char *c, size_t *l, const char *separator, char **state) {
+ char *current;
+
+ current = *state ? *state : (char*) c;
+
+ if (!*current || *c == 0)
+ return NULL;
+
+ current += strspn(current, separator);
+ *l = strcspn(current, separator);
+ *state = current+*l;
+
+ return (char*) current;
+}
+
+/* Split a string into words, but consider strings enclosed in '' and
+ * "" as words even if they include spaces. */
+char *split_quoted(const char *c, size_t *l, char **state) {
+ char *current, *e;
+ bool escaped = false;
+
+ current = *state ? *state : (char*) c;
+
+ if (!*current || *c == 0)
+ return NULL;
+
+ current += strspn(current, WHITESPACE);
+
+ if (*current == '\'') {
+ current ++;
+
+ for (e = current; *e; e++) {
+ if (escaped)
+ escaped = false;
+ else if (*e == '\\')
+ escaped = true;
+ else if (*e == '\'')
+ break;
+ }
+
+ *l = e-current;
+ *state = *e == 0 ? e : e+1;
+ } else if (*current == '\"') {
+ current ++;
+
+ for (e = current; *e; e++) {
+ if (escaped)
+ escaped = false;
+ else if (*e == '\\')
+ escaped = true;
+ else if (*e == '\"')
+ break;
+ }
+
+ *l = e-current;
+ *state = *e == 0 ? e : e+1;
+ } else {
+ for (e = current; *e; e++) {
+ if (escaped)
+ escaped = false;
+ else if (*e == '\\')
+ escaped = true;
+ else if (strchr(WHITESPACE, *e))
+ break;
+ }
+ *l = e-current;
+ *state = e;
+ }
+
+ return (char*) current;
+}
+
+int get_parent_of_pid(pid_t pid, pid_t *_ppid) {
+ int r;
+ _cleanup_fclose_ FILE *f = NULL;
+ char line[LINE_MAX];
+ long unsigned ppid;
+ const char *p;
+
+ assert(pid >= 0);
+ assert(_ppid);
+
+ if (pid == 0) {
+ *_ppid = getppid();
+ return 0;
+ }
+
+ p = procfs_file_alloca(pid, "stat");
+ f = fopen(p, "re");
+ if (!f)
+ return -errno;
+
+ if (!fgets(line, sizeof(line), f)) {
+ r = feof(f) ? -EIO : -errno;
+ return r;
+ }
+
+ /* Let's skip the pid and comm fields. The latter is enclosed
+ * in () but does not escape any () in its value, so let's
+ * skip over it manually */
+
+ p = strrchr(line, ')');
+ if (!p)
+ return -EIO;
+
+ p++;
+
+ if (sscanf(p, " "
+ "%*c " /* state */
+ "%lu ", /* ppid */
+ &ppid) != 1)
+ return -EIO;
+
+ if ((long unsigned) (pid_t) ppid != ppid)
+ return -ERANGE;
+
+ *_ppid = (pid_t) ppid;
+
+ return 0;
+}
+
+int get_starttime_of_pid(pid_t pid, unsigned long long *st) {
+ _cleanup_fclose_ FILE *f = NULL;
+ char line[LINE_MAX];
+ const char *p;
+
+ assert(pid >= 0);
+ assert(st);
+
+ if (pid == 0)
+ p = "/proc/self/stat";
+ else
+ p = procfs_file_alloca(pid, "stat");
+
+ f = fopen(p, "re");
+ if (!f)
+ return -errno;
+
+ if (!fgets(line, sizeof(line), f)) {
+ if (ferror(f))
+ return -errno;
+
+ return -EIO;
+ }
+
+ /* Let's skip the pid and comm fields. The latter is enclosed
+ * in () but does not escape any () in its value, so let's
+ * skip over it manually */
+
+ p = strrchr(line, ')');
+ if (!p)
+ return -EIO;
+
+ p++;
+
+ if (sscanf(p, " "
+ "%*c " /* state */
+ "%*d " /* ppid */
+ "%*d " /* pgrp */
+ "%*d " /* session */
+ "%*d " /* tty_nr */
+ "%*d " /* tpgid */
+ "%*u " /* flags */
+ "%*u " /* minflt */
+ "%*u " /* cminflt */
+ "%*u " /* majflt */
+ "%*u " /* cmajflt */
+ "%*u " /* utime */
+ "%*u " /* stime */
+ "%*d " /* cutime */
+ "%*d " /* cstime */
+ "%*d " /* priority */
+ "%*d " /* nice */
+ "%*d " /* num_threads */
+ "%*d " /* itrealvalue */
+ "%llu " /* starttime */,
+ st) != 1)
+ return -EIO;
+
+ return 0;
+}
+
+int fchmod_umask(int fd, mode_t m) {
+ mode_t u;
+ int r;
+
+ u = umask(0777);
+ r = fchmod(fd, m & (~u)) < 0 ? -errno : 0;
+ umask(u);
+
+ return r;
+}
+
+char *truncate_nl(char *s) {
+ assert(s);
+
+ s[strcspn(s, NEWLINE)] = 0;
+ return s;
+}
+
+int get_process_comm(pid_t pid, char **name) {
+ const char *p;
+
+ assert(name);
+ assert(pid >= 0);
+
+ if (pid == 0)
+ p = "/proc/self/comm";
+ else
+ p = procfs_file_alloca(pid, "comm");
+
+ return read_one_line_file(p, name);
+}
+
+int get_process_cmdline(pid_t pid, size_t max_length, bool comm_fallback, char **line) {
+ _cleanup_fclose_ FILE *f = NULL;
+ char *r = NULL, *k;
+ const char *p;
+ int c;
+
+ assert(line);
+ assert(pid >= 0);
+
+ if (pid == 0)
+ p = "/proc/self/cmdline";
+ else
+ p = procfs_file_alloca(pid, "cmdline");
+
+ f = fopen(p, "re");
+ if (!f)
+ return -errno;
+
+ if (max_length == 0) {
+ size_t len = 0, allocated = 0;
+
+ while ((c = getc(f)) != EOF) {
+
+ if (!GREEDY_REALLOC(r, allocated, len+2)) {
+ free(r);
+ return -ENOMEM;
+ }
+
+ r[len++] = isprint(c) ? c : ' ';
+ }
+
+ if (len > 0)
+ r[len-1] = 0;
+
+ } else {
+ bool space = false;
+ size_t left;
+
+ r = new(char, max_length);
+ if (!r)
+ return -ENOMEM;
+
+ k = r;
+ left = max_length;
+ while ((c = getc(f)) != EOF) {
+
+ if (isprint(c)) {
+ if (space) {
+ if (left <= 4)
+ break;
+
+ *(k++) = ' ';
+ left--;
+ space = false;
+ }
+
+ if (left <= 4)
+ break;
+
+ *(k++) = (char) c;
+ left--;
+ } else
+ space = true;
+ }
+
+ if (left <= 4) {
+ size_t n = MIN(left-1, 3U);
+ memcpy(k, "...", n);
+ k[n] = 0;
+ } else
+ *k = 0;
+ }
+
+ /* Kernel threads have no argv[] */
+ if (r == NULL || r[0] == 0) {
+ char *t;
+ int h;
+
+ free(r);
+
+ if (!comm_fallback)
+ return -ENOENT;
+
+ h = get_process_comm(pid, &t);
+ if (h < 0)
+ return h;
+
+ r = strjoin("[", t, "]", NULL);
+ free(t);
+
+ if (!r)
+ return -ENOMEM;
+ }
+
+ *line = r;
+ return 0;
+}
+
+int is_kernel_thread(pid_t pid) {
+ const char *p;
+ size_t count;
+ char c;
+ bool eof;
+ FILE *f;
+
+ if (pid == 0)
+ return 0;
+
+ assert(pid > 0);
+
+ p = procfs_file_alloca(pid, "cmdline");
+ f = fopen(p, "re");
+ if (!f)
+ return -errno;
+
+ count = fread(&c, 1, 1, f);
+ eof = feof(f);
+ fclose(f);
+
+ /* Kernel threads have an empty cmdline */
+
+ if (count <= 0)
+ return eof ? 1 : -errno;
+
+ return 0;
+}
+
+
+int get_process_exe(pid_t pid, char **name) {
+ const char *p;
+
+ assert(pid >= 0);
+ assert(name);
+
+ if (pid == 0)
+ p = "/proc/self/exe";
+ else
+ p = procfs_file_alloca(pid, "exe");
+
+ return readlink_malloc(p, name);
+}
+
+static int get_process_id(pid_t pid, const char *field, uid_t *uid) {
+ _cleanup_fclose_ FILE *f = NULL;
+ char line[LINE_MAX];
+ const char *p;
+
+ assert(field);
+ assert(uid);
+
+ if (pid == 0)
+ return getuid();
+
+ p = procfs_file_alloca(pid, "status");
+ f = fopen(p, "re");
+ if (!f)
+ return -errno;
+
+ FOREACH_LINE(line, f, return -errno) {
+ char *l;
+
+ l = strstrip(line);
+
+ if (startswith(l, field)) {
+ l += strlen(field);
+ l += strspn(l, WHITESPACE);
+
+ l[strcspn(l, WHITESPACE)] = 0;
+
+ return parse_uid(l, uid);
+ }
+ }
+
+ return -EIO;
+}
+
+int get_process_uid(pid_t pid, uid_t *uid) {
+ return get_process_id(pid, "Uid:", uid);
+}
+
+int get_process_gid(pid_t pid, gid_t *gid) {
+ assert_cc(sizeof(uid_t) == sizeof(gid_t));
+ return get_process_id(pid, "Gid:", gid);
+}
+
+char *strnappend(const char *s, const char *suffix, size_t b) {
+ size_t a;
+ char *r;
+
+ if (!s && !suffix)
+ return strdup("");
+
+ if (!s)
+ return strndup(suffix, b);
+
+ if (!suffix)
+ return strdup(s);
+
+ assert(s);
+ assert(suffix);
+
+ a = strlen(s);
+ if (b > ((size_t) -1) - a)
+ return NULL;
+
+ r = new(char, a+b+1);
+ if (!r)
+ return NULL;
+
+ memcpy(r, s, a);
+ memcpy(r+a, suffix, b);
+ r[a+b] = 0;
+
+ return r;
+}
+
+char *strappend(const char *s, const char *suffix) {
+ return strnappend(s, suffix, suffix ? strlen(suffix) : 0);
+}
+
+int readlink_malloc(const char *p, char **r) {
+ size_t l = 100;
+
+ assert(p);
+ assert(r);
+
+ for (;;) {
+ char *c;
+ ssize_t n;
+
+ if (!(c = new(char, l)))
+ return -ENOMEM;
+
+ if ((n = readlink(p, c, l-1)) < 0) {
+ int ret = -errno;
+ free(c);
+ return ret;
+ }
+
+ if ((size_t) n < l-1) {
+ c[n] = 0;
+ *r = c;
+ return 0;
+ }
+
+ free(c);
+ l *= 2;
+ }
+}
+
+int readlink_and_make_absolute(const char *p, char **r) {
+ char *target, *k;
+ int j;
+
+ assert(p);
+ assert(r);
+
+ if ((j = readlink_malloc(p, &target)) < 0)
+ return j;
+
+ k = file_in_same_dir(p, target);
+ free(target);
+
+ if (!k)
+ return -ENOMEM;
+
+ *r = k;
+ return 0;
+}
+
+int readlink_and_canonicalize(const char *p, char **r) {
+ char *t, *s;
+ int j;
+
+ assert(p);
+ assert(r);
+
+ j = readlink_and_make_absolute(p, &t);
+ if (j < 0)
+ return j;
+
+ s = canonicalize_file_name(t);
+ if (s) {
+ free(t);
+ *r = s;
+ } else
+ *r = t;
+
+ path_kill_slashes(*r);
+
+ return 0;
+}
+
+int reset_all_signal_handlers(void) {
+ int sig;
+
+ for (sig = 1; sig < _NSIG; sig++) {
+ struct sigaction sa = {
+ .sa_handler = SIG_DFL,
+ .sa_flags = SA_RESTART,
+ };
+
+ if (sig == SIGKILL || sig == SIGSTOP)
+ continue;
+
+ /* On Linux the first two RT signals are reserved by
+ * glibc, and sigaction() will return EINVAL for them. */
+ if ((sigaction(sig, &sa, NULL) < 0))
+ if (errno != EINVAL)
+ return -errno;
+ }
+
+ return 0;
+}
+
+char *strstrip(char *s) {
+ char *e;
+
+ /* Drops trailing whitespace. Modifies the string in
+ * place. Returns pointer to first non-space character */
+
+ s += strspn(s, WHITESPACE);
+
+ for (e = strchr(s, 0); e > s; e --)
+ if (!strchr(WHITESPACE, e[-1]))
+ break;
+
+ *e = 0;
+
+ return s;
+}
+
+char *delete_chars(char *s, const char *bad) {
+ char *f, *t;
+
+ /* Drops all whitespace, regardless where in the string */
+
+ for (f = s, t = s; *f; f++) {
+ if (strchr(bad, *f))
+ continue;
+
+ *(t++) = *f;
+ }
+
+ *t = 0;
+
+ return s;
+}
+
+bool in_charset(const char *s, const char* charset) {
+ const char *i;
+
+ assert(s);
+ assert(charset);
+
+ for (i = s; *i; i++)
+ if (!strchr(charset, *i))
+ return false;
+
+ return true;
+}
+
+char *file_in_same_dir(const char *path, const char *filename) {
+ char *e, *r;
+ size_t k;
+
+ assert(path);
+ assert(filename);
+
+ /* This removes the last component of path and appends
+ * filename, unless the latter is absolute anyway or the
+ * former isn't */
+
+ if (path_is_absolute(filename))
+ return strdup(filename);
+
+ if (!(e = strrchr(path, '/')))
+ return strdup(filename);
+
+ k = strlen(filename);
+ if (!(r = new(char, e-path+1+k+1)))
+ return NULL;
+
+ memcpy(r, path, e-path+1);
+ memcpy(r+(e-path)+1, filename, k+1);
+
+ return r;
+}
+
+int rmdir_parents(const char *path, const char *stop) {
+ size_t l;
+ int r = 0;
+
+ assert(path);
+ assert(stop);
+
+ l = strlen(path);
+
+ /* Skip trailing slashes */
+ while (l > 0 && path[l-1] == '/')
+ l--;
+
+ while (l > 0) {
+ char *t;
+
+ /* Skip last component */
+ while (l > 0 && path[l-1] != '/')
+ l--;
+
+ /* Skip trailing slashes */
+ while (l > 0 && path[l-1] == '/')
+ l--;
+
+ if (l <= 0)
+ break;
+
+ if (!(t = strndup(path, l)))
+ return -ENOMEM;
+
+ if (path_startswith(stop, t)) {
+ free(t);
+ return 0;
+ }
+
+ r = rmdir(t);
+ free(t);
+
+ if (r < 0)
+ if (errno != ENOENT)
+ return -errno;
+ }
+
+ return 0;
+}
+
+char hexchar(int x) {
+ static const char table[16] = "0123456789abcdef";
+
+ return table[x & 15];
+}
+
+int unhexchar(char c) {
+
+ if (c >= '0' && c <= '9')
+ return c - '0';
+
+ if (c >= 'a' && c <= 'f')
+ return c - 'a' + 10;
+
+ if (c >= 'A' && c <= 'F')
+ return c - 'A' + 10;
+
+ return -1;
+}
+
+char *hexmem(const void *p, size_t l) {
+ char *r, *z;
+ const uint8_t *x;
+
+ z = r = malloc(l * 2 + 1);
+ if (!r)
+ return NULL;
+
+ for (x = p; x < (const uint8_t*) p + l; x++) {
+ *(z++) = hexchar(*x >> 4);
+ *(z++) = hexchar(*x & 15);
+ }
+
+ *z = 0;
+ return r;
+}
+
+void *unhexmem(const char *p, size_t l) {
+ uint8_t *r, *z;
+ const char *x;
+
+ assert(p);
+
+ z = r = malloc((l + 1) / 2 + 1);
+ if (!r)
+ return NULL;
+
+ for (x = p; x < p + l; x += 2) {
+ int a, b;
+
+ a = unhexchar(x[0]);
+ if (x+1 < p + l)
+ b = unhexchar(x[1]);
+ else
+ b = 0;
+
+ *(z++) = (uint8_t) a << 4 | (uint8_t) b;
+ }
+
+ *z = 0;
+ return r;
+}
+
+char octchar(int x) {
+ return '0' + (x & 7);
+}
+
+int unoctchar(char c) {
+
+ if (c >= '0' && c <= '7')
+ return c - '0';
+
+ return -1;
+}
+
+char decchar(int x) {
+ return '0' + (x % 10);
+}
+
+int undecchar(char c) {
+
+ if (c >= '0' && c <= '9')
+ return c - '0';
+
+ return -1;
+}
+
+char *cescape(const char *s) {
+ char *r, *t;
+ const char *f;
+
+ assert(s);
+
+ /* Does C style string escaping. */
+
+ r = new(char, strlen(s)*4 + 1);
+ if (!r)
+ return NULL;
+
+ for (f = s, t = r; *f; f++)
+
+ switch (*f) {
+
+ case '\a':
+ *(t++) = '\\';
+ *(t++) = 'a';
+ break;
+ case '\b':
+ *(t++) = '\\';
+ *(t++) = 'b';
+ break;
+ case '\f':
+ *(t++) = '\\';
+ *(t++) = 'f';
+ break;
+ case '\n':
+ *(t++) = '\\';
+ *(t++) = 'n';
+ break;
+ case '\r':
+ *(t++) = '\\';
+ *(t++) = 'r';
+ break;
+ case '\t':
+ *(t++) = '\\';
+ *(t++) = 't';
+ break;
+ case '\v':
+ *(t++) = '\\';
+ *(t++) = 'v';
+ break;
+ case '\\':
+ *(t++) = '\\';
+ *(t++) = '\\';
+ break;
+ case '"':
+ *(t++) = '\\';
+ *(t++) = '"';
+ break;
+ case '\'':
+ *(t++) = '\\';
+ *(t++) = '\'';
+ break;
+
+ default:
+ /* For special chars we prefer octal over
+ * hexadecimal encoding, simply because glib's
+ * g_strescape() does the same */
+ if ((*f < ' ') || (*f >= 127)) {
+ *(t++) = '\\';
+ *(t++) = octchar((unsigned char) *f >> 6);
+ *(t++) = octchar((unsigned char) *f >> 3);
+ *(t++) = octchar((unsigned char) *f);
+ } else
+ *(t++) = *f;
+ break;
+ }
+
+ *t = 0;
+
+ return r;
+}
+
+char *cunescape_length_with_prefix(const char *s, size_t length, const char *prefix) {
+ char *r, *t;
+ const char *f;
+ size_t pl;
+
+ assert(s);
+
+ /* Undoes C style string escaping, and optionally prefixes it. */
+
+ pl = prefix ? strlen(prefix) : 0;
+
+ r = new(char, pl+length+1);
+ if (!r)
+ return r;
+
+ if (prefix)
+ memcpy(r, prefix, pl);
+
+ for (f = s, t = r + pl; f < s + length; f++) {
+
+ if (*f != '\\') {
+ *(t++) = *f;
+ continue;
+ }
+
+ f++;
+
+ switch (*f) {
+
+ case 'a':
+ *(t++) = '\a';
+ break;
+ case 'b':
+ *(t++) = '\b';
+ break;
+ case 'f':
+ *(t++) = '\f';
+ break;
+ case 'n':
+ *(t++) = '\n';
+ break;
+ case 'r':
+ *(t++) = '\r';
+ break;
+ case 't':
+ *(t++) = '\t';
+ break;
+ case 'v':
+ *(t++) = '\v';
+ break;
+ case '\\':
+ *(t++) = '\\';
+ break;
+ case '"':
+ *(t++) = '"';
+ break;
+ case '\'':
+ *(t++) = '\'';
+ break;
+
+ case 's':
+ /* This is an extension of the XDG syntax files */
+ *(t++) = ' ';
+ break;
+
+ case 'x': {
+ /* hexadecimal encoding */
+ int a, b;
+
+ a = unhexchar(f[1]);
+ b = unhexchar(f[2]);
+
+ if (a < 0 || b < 0) {
+ /* Invalid escape code, let's take it literal then */
+ *(t++) = '\\';
+ *(t++) = 'x';
+ } else {
+ *(t++) = (char) ((a << 4) | b);
+ f += 2;
+ }
+
+ break;
+ }
+
+ case '0':
+ case '1':
+ case '2':
+ case '3':
+ case '4':
+ case '5':
+ case '6':
+ case '7': {
+ /* octal encoding */
+ int a, b, c;
+
+ a = unoctchar(f[0]);
+ b = unoctchar(f[1]);
+ c = unoctchar(f[2]);
+
+ if (a < 0 || b < 0 || c < 0) {
+ /* Invalid escape code, let's take it literal then */
+ *(t++) = '\\';
+ *(t++) = f[0];
+ } else {
+ *(t++) = (char) ((a << 6) | (b << 3) | c);
+ f += 2;
+ }
+
+ break;
+ }
+
+ case 0:
+ /* premature end of string.*/
+ *(t++) = '\\';
+ goto finish;
+
+ default:
+ /* Invalid escape code, let's take it literal then */
+ *(t++) = '\\';
+ *(t++) = *f;
+ break;
+ }
+ }
+
+finish:
+ *t = 0;
+ return r;
+}
+
+char *cunescape_length(const char *s, size_t length) {
+ return cunescape_length_with_prefix(s, length, NULL);
+}
+
+char *cunescape(const char *s) {
+ assert(s);
+
+ return cunescape_length(s, strlen(s));
+}
+
+char *xescape(const char *s, const char *bad) {
+ char *r, *t;
+ const char *f;
+
+ /* Escapes all chars in bad, in addition to \ and all special
+ * chars, in \xFF style escaping. May be reversed with
+ * cunescape. */
+
+ r = new(char, strlen(s) * 4 + 1);
+ if (!r)
+ return NULL;
+
+ for (f = s, t = r; *f; f++) {
+
+ if ((*f < ' ') || (*f >= 127) ||
+ (*f == '\\') || strchr(bad, *f)) {
+ *(t++) = '\\';
+ *(t++) = 'x';
+ *(t++) = hexchar(*f >> 4);
+ *(t++) = hexchar(*f);
+ } else
+ *(t++) = *f;
+ }
+
+ *t = 0;
+
+ return r;
+}
+
+char *bus_path_escape(const char *s) {
+ char *r, *t;
+ const char *f;
+
+ assert(s);
+
+ /* Escapes all chars that D-Bus' object path cannot deal
+ * with. Can be reverse with bus_path_unescape(). We special
+ * case the empty string. */
+
+ if (*s == 0)
+ return strdup("_");
+
+ r = new(char, strlen(s)*3 + 1);
+ if (!r)
+ return NULL;
+
+ for (f = s, t = r; *f; f++) {
+
+ /* Escape everything that is not a-zA-Z0-9. We also
+ * escape 0-9 if it's the first character */
+
+ if (!(*f >= 'A' && *f <= 'Z') &&
+ !(*f >= 'a' && *f <= 'z') &&
+ !(f > s && *f >= '0' && *f <= '9')) {
+ *(t++) = '_';
+ *(t++) = hexchar(*f >> 4);
+ *(t++) = hexchar(*f);
+ } else
+ *(t++) = *f;
+ }
+
+ *t = 0;
+
+ return r;
+}
+
+char *bus_path_unescape(const char *f) {
+ char *r, *t;
+
+ assert(f);
+
+ /* Special case for the empty string */
+ if (streq(f, "_"))
+ return strdup("");
+
+ r = new(char, strlen(f) + 1);
+ if (!r)
+ return NULL;
+
+ for (t = r; *f; f++) {
+
+ if (*f == '_') {
+ int a, b;
+
+ if ((a = unhexchar(f[1])) < 0 ||
+ (b = unhexchar(f[2])) < 0) {
+ /* Invalid escape code, let's take it literal then */
+ *(t++) = '_';
+ } else {
+ *(t++) = (char) ((a << 4) | b);
+ f += 2;
+ }
+ } else
+ *(t++) = *f;
+ }
+
+ *t = 0;
+
+ return r;
+}
+
+char *ascii_strlower(char *t) {
+ char *p;
+
+ assert(t);
+
+ for (p = t; *p; p++)
+ if (*p >= 'A' && *p <= 'Z')
+ *p = *p - 'A' + 'a';
+
+ return t;
+}
+
+_pure_ static bool ignore_file_allow_backup(const char *filename) {
+ assert(filename);
+
+ return
+ filename[0] == '.' ||
+ streq(filename, "lost+found") ||
+ streq(filename, "aquota.user") ||
+ streq(filename, "aquota.group") ||
+ endswith(filename, ".rpmnew") ||
+ endswith(filename, ".rpmsave") ||
+ endswith(filename, ".rpmorig") ||
+ endswith(filename, ".dpkg-old") ||
+ endswith(filename, ".dpkg-new") ||
+ endswith(filename, ".swp");
+}
+
+bool ignore_file(const char *filename) {
+ assert(filename);
+
+ if (endswith(filename, "~"))
+ return false;
+
+ return ignore_file_allow_backup(filename);
+}
+
+int fd_nonblock(int fd, bool nonblock) {
+ int flags;
+
+ assert(fd >= 0);
+
+ if ((flags = fcntl(fd, F_GETFL, 0)) < 0)
+ return -errno;
+
+ if (nonblock)
+ flags |= O_NONBLOCK;
+ else
+ flags &= ~O_NONBLOCK;
+
+ if (fcntl(fd, F_SETFL, flags) < 0)
+ return -errno;
+
+ return 0;
+}
+
+int fd_cloexec(int fd, bool cloexec) {
+ int flags;
+
+ assert(fd >= 0);
+
+ if ((flags = fcntl(fd, F_GETFD, 0)) < 0)
+ return -errno;
+
+ if (cloexec)
+ flags |= FD_CLOEXEC;
+ else
+ flags &= ~FD_CLOEXEC;
+
+ if (fcntl(fd, F_SETFD, flags) < 0)
+ return -errno;
+
+ return 0;
+}
+
+_pure_ static bool fd_in_set(int fd, const int fdset[], unsigned n_fdset) {
+ unsigned i;
+
+ assert(n_fdset == 0 || fdset);
+
+ for (i = 0; i < n_fdset; i++)
+ if (fdset[i] == fd)
+ return true;
+
+ return false;
+}
+
+int close_all_fds(const int except[], unsigned n_except) {
+ DIR *d;
+ struct dirent *de;
+ int r = 0;
+
+ assert(n_except == 0 || except);
+
+ d = opendir("/proc/self/fd");
+ if (!d) {
+ int fd;
+ struct rlimit rl;
+
+ /* When /proc isn't available (for example in chroots)
+ * the fallback is brute forcing through the fd
+ * table */
+
+ assert_se(getrlimit(RLIMIT_NOFILE, &rl) >= 0);
+ for (fd = 3; fd < (int) rl.rlim_max; fd ++) {
+
+ if (fd_in_set(fd, except, n_except))
+ continue;
+
+ if (close_nointr(fd) < 0)
+ if (errno != EBADF && r == 0)
+ r = -errno;
+ }
+
+ return r;
+ }
+
+ while ((de = readdir(d))) {
+ int fd = -1;
+
+ if (ignore_file(de->d_name))
+ continue;
+
+ if (safe_atoi(de->d_name, &fd) < 0)
+ /* Let's better ignore this, just in case */
+ continue;
+
+ if (fd < 3)
+ continue;
+
+ if (fd == dirfd(d))
+ continue;
+
+ if (fd_in_set(fd, except, n_except))
+ continue;
+
+ if (close_nointr(fd) < 0) {
+ /* Valgrind has its own FD and doesn't want to have it closed */
+ if (errno != EBADF && r == 0)
+ r = -errno;
+ }
+ }
+
+ closedir(d);
+ return r;
+}
+
+bool chars_intersect(const char *a, const char *b) {
+ const char *p;
+
+ /* Returns true if any of the chars in a are in b. */
+ for (p = a; *p; p++)
+ if (strchr(b, *p))
+ return true;
+
+ return false;
+}
+
+bool fstype_is_network(const char *fstype) {
+ static const char table[] =
+ "cifs\0"
+ "smbfs\0"
+ "ncpfs\0"
+ "nfs\0"
+ "nfs4\0"
+ "gfs\0"
+ "gfs2\0";
+
+ return nulstr_contains(table, fstype);
+}
+
+int chvt(int vt) {
+ _cleanup_close_ int fd;
+
+ fd = open_terminal("/dev/tty0", O_RDWR|O_NOCTTY|O_CLOEXEC);
+ if (fd < 0)
+ return -errno;
+
+ if (vt < 0) {
+ int tiocl[2] = {
+ TIOCL_GETKMSGREDIRECT,
+ 0
+ };
+
+ if (ioctl(fd, TIOCLINUX, tiocl) < 0)
+ return -errno;
+
+ vt = tiocl[0] <= 0 ? 1 : tiocl[0];
+ }
+
+ if (ioctl(fd, VT_ACTIVATE, vt) < 0)
+ return -errno;
+
+ return 0;
+}
+
+int read_one_char(FILE *f, char *ret, usec_t t, bool *need_nl) {
+ struct termios old_termios, new_termios;
+ char c;
+ char line[LINE_MAX];
+
+ assert(f);
+ assert(ret);
+
+ if (tcgetattr(fileno(f), &old_termios) >= 0) {
+ new_termios = old_termios;
+
+ new_termios.c_lflag &= ~ICANON;
+ new_termios.c_cc[VMIN] = 1;
+ new_termios.c_cc[VTIME] = 0;
+
+ if (tcsetattr(fileno(f), TCSADRAIN, &new_termios) >= 0) {
+ size_t k;
+
+ if (t != (usec_t) -1) {
+ if (fd_wait_for_event(fileno(f), POLLIN, t) <= 0) {
+ tcsetattr(fileno(f), TCSADRAIN, &old_termios);
+ return -ETIMEDOUT;
+ }
+ }
+
+ k = fread(&c, 1, 1, f);
+
+ tcsetattr(fileno(f), TCSADRAIN, &old_termios);
+
+ if (k <= 0)
+ return -EIO;
+
+ if (need_nl)
+ *need_nl = c != '\n';
+
+ *ret = c;
+ return 0;
+ }
+ }
+
+ if (t != (usec_t) -1)
+ if (fd_wait_for_event(fileno(f), POLLIN, t) <= 0)
+ return -ETIMEDOUT;
+
+ if (!fgets(line, sizeof(line), f))
+ return -EIO;
+
+ truncate_nl(line);
+
+ if (strlen(line) != 1)
+ return -EBADMSG;
+
+ if (need_nl)
+ *need_nl = false;
+
+ *ret = line[0];
+ return 0;
+}
+
+int ask(char *ret, const char *replies, const char *text, ...) {
+
+ assert(ret);
+ assert(replies);
+ assert(text);
+
+ for (;;) {
+ va_list ap;
+ char c;
+ int r;
+ bool need_nl = true;
+
+ if (on_tty())
+ fputs(ANSI_HIGHLIGHT_ON, stdout);
+
+ va_start(ap, text);
+ vprintf(text, ap);
+ va_end(ap);
+
+ if (on_tty())
+ fputs(ANSI_HIGHLIGHT_OFF, stdout);
+
+ fflush(stdout);
+
+ r = read_one_char(stdin, &c, (usec_t) -1, &need_nl);
+ if (r < 0) {
+
+ if (r == -EBADMSG) {
+ puts("Bad input, please try again.");
+ continue;
+ }
+
+ putchar('\n');
+ return r;
+ }
+
+ if (need_nl)
+ putchar('\n');
+
+ if (strchr(replies, c)) {
+ *ret = c;
+ return 0;
+ }
+
+ puts("Read unexpected character, please try again.");
+ }
+}
+
+int reset_terminal_fd(int fd, bool switch_to_text) {
+ struct termios termios;
+ int r = 0;
+
+ /* Set terminal to some sane defaults */
+
+ assert(fd >= 0);
+
+ /* We leave locked terminal attributes untouched, so that
+ * Plymouth may set whatever it wants to set, and we don't
+ * interfere with that. */
+
+ /* Disable exclusive mode, just in case */
+ ioctl(fd, TIOCNXCL);
+
+ /* Switch to text mode */
+ if (switch_to_text)
+ ioctl(fd, KDSETMODE, KD_TEXT);
+
+ /* Enable console unicode mode */
+ ioctl(fd, KDSKBMODE, K_UNICODE);
+
+ if (tcgetattr(fd, &termios) < 0) {
+ r = -errno;
+ goto finish;
+ }
+
+ /* We only reset the stuff that matters to the software. How
+ * hardware is set up we don't touch assuming that somebody
+ * else will do that for us */
+
+ termios.c_iflag &= ~(IGNBRK | BRKINT | ISTRIP | INLCR | IGNCR | IUCLC);
+ termios.c_iflag |= ICRNL | IMAXBEL | IUTF8;
+ termios.c_oflag |= ONLCR;
+ termios.c_cflag |= CREAD;
+ termios.c_lflag = ISIG | ICANON | IEXTEN | ECHO | ECHOE | ECHOK | ECHOCTL | ECHOPRT | ECHOKE;
+
+ termios.c_cc[VINTR] = 03; /* ^C */
+ termios.c_cc[VQUIT] = 034; /* ^\ */
+ termios.c_cc[VERASE] = 0177;
+ termios.c_cc[VKILL] = 025; /* ^X */
+ termios.c_cc[VEOF] = 04; /* ^D */
+ termios.c_cc[VSTART] = 021; /* ^Q */
+ termios.c_cc[VSTOP] = 023; /* ^S */
+ termios.c_cc[VSUSP] = 032; /* ^Z */
+ termios.c_cc[VLNEXT] = 026; /* ^V */
+ termios.c_cc[VWERASE] = 027; /* ^W */
+ termios.c_cc[VREPRINT] = 022; /* ^R */
+ termios.c_cc[VEOL] = 0;
+ termios.c_cc[VEOL2] = 0;
+
+ termios.c_cc[VTIME] = 0;
+ termios.c_cc[VMIN] = 1;
+
+ if (tcsetattr(fd, TCSANOW, &termios) < 0)
+ r = -errno;
+
+finish:
+ /* Just in case, flush all crap out */
+ tcflush(fd, TCIOFLUSH);
+
+ return r;
+}
+
+int reset_terminal(const char *name) {
+ int fd, r;
+
+ fd = open_terminal(name, O_RDWR|O_NOCTTY|O_CLOEXEC);
+ if (fd < 0)
+ return fd;
+
+ r = reset_terminal_fd(fd, true);
+ close_nointr_nofail(fd);
+
+ return r;
+}
+
+int open_terminal(const char *name, int mode) {
+ int fd, r;
+ unsigned c = 0;
+
+ /*
+ * If a TTY is in the process of being closed opening it might
+ * cause EIO. This is horribly awful, but unlikely to be
+ * changed in the kernel. Hence we work around this problem by
+ * retrying a couple of times.
+ *
+ * https://bugs.launchpad.net/ubuntu/+source/linux/+bug/554172/comments/245
+ */
+
+ for (;;) {
+ fd = open(name, mode);
+ if (fd >= 0)
+ break;
+
+ if (errno != EIO)
+ return -errno;
+
+ /* Max 1s in total */
+ if (c >= 20)
+ return -errno;
+
+ usleep(50 * USEC_PER_MSEC);
+ c++;
+ }
+
+ if (fd < 0)
+ return -errno;
+
+ r = isatty(fd);
+ if (r < 0) {
+ close_nointr_nofail(fd);
+ return -errno;
+ }
+
+ if (!r) {
+ close_nointr_nofail(fd);
+ return -ENOTTY;
+ }
+
+ return fd;
+}
+
+int flush_fd(int fd) {
+ struct pollfd pollfd = {
+ .fd = fd,
+ .events = POLLIN,
+ };
+
+ for (;;) {
+ char buf[LINE_MAX];
+ ssize_t l;
+ int r;
+
+ r = poll(&pollfd, 1, 0);
+ if (r < 0) {
+ if (errno == EINTR)
+ continue;
+
+ return -errno;
+
+ } else if (r == 0)
+ return 0;
+
+ l = read(fd, buf, sizeof(buf));
+ if (l < 0) {
+
+ if (errno == EINTR)
+ continue;
+
+ if (errno == EAGAIN)
+ return 0;
+
+ return -errno;
+ } else if (l == 0)
+ return 0;
+ }
+}
+
+int acquire_terminal(
+ const char *name,
+ bool fail,
+ bool force,
+ bool ignore_tiocstty_eperm,
+ usec_t timeout) {
+
+ int fd = -1, notify = -1, r = 0, wd = -1;
+ usec_t ts = 0;
+
+ assert(name);
+
+ /* We use inotify to be notified when the tty is closed. We
+ * create the watch before checking if we can actually acquire
+ * it, so that we don't lose any event.
+ *
+ * Note: strictly speaking this actually watches for the
+ * device being closed, it does *not* really watch whether a
+ * tty loses its controlling process. However, unless some
+ * rogue process uses TIOCNOTTY on /dev/tty *after* closing
+ * its tty otherwise this will not become a problem. As long
+ * as the administrator makes sure not configure any service
+ * on the same tty as an untrusted user this should not be a
+ * problem. (Which he probably should not do anyway.) */
+
+ if (timeout != (usec_t) -1)
+ ts = now(CLOCK_MONOTONIC);
+
+ if (!fail && !force) {
+ notify = inotify_init1(IN_CLOEXEC | (timeout != (usec_t) -1 ? IN_NONBLOCK : 0));
+ if (notify < 0) {
+ r = -errno;
+ goto fail;
+ }
+
+ wd = inotify_add_watch(notify, name, IN_CLOSE);
+ if (wd < 0) {
+ r = -errno;
+ goto fail;
+ }
+ }
+
+ for (;;) {
+ struct sigaction sa_old, sa_new = {
+ .sa_handler = SIG_IGN,
+ .sa_flags = SA_RESTART,
+ };
+
+ if (notify >= 0) {
+ r = flush_fd(notify);
+ if (r < 0)
+ goto fail;
+ }
+
+ /* We pass here O_NOCTTY only so that we can check the return
+ * value TIOCSCTTY and have a reliable way to figure out if we
+ * successfully became the controlling process of the tty */
+ fd = open_terminal(name, O_RDWR|O_NOCTTY|O_CLOEXEC);
+ if (fd < 0)
+ return fd;
+
+ /* Temporarily ignore SIGHUP, so that we don't get SIGHUP'ed
+ * if we already own the tty. */
+ assert_se(sigaction(SIGHUP, &sa_new, &sa_old) == 0);
+
+ /* First, try to get the tty */
+ if (ioctl(fd, TIOCSCTTY, force) < 0)
+ r = -errno;
+
+ assert_se(sigaction(SIGHUP, &sa_old, NULL) == 0);
+
+ /* Sometimes it makes sense to ignore TIOCSCTTY
+ * returning EPERM, i.e. when very likely we already
+ * are have this controlling terminal. */
+ if (r < 0 && r == -EPERM && ignore_tiocstty_eperm)
+ r = 0;
+
+ if (r < 0 && (force || fail || r != -EPERM)) {
+ goto fail;
+ }
+
+ if (r >= 0)
+ break;
+
+ assert(!fail);
+ assert(!force);
+ assert(notify >= 0);
+
+ for (;;) {
+ uint8_t inotify_buffer[sizeof(struct inotify_event) + FILENAME_MAX];
+ ssize_t l;
+ struct inotify_event *e;
+
+ if (timeout != (usec_t) -1) {
+ usec_t n;
+
+ n = now(CLOCK_MONOTONIC);
+ if (ts + timeout < n) {
+ r = -ETIMEDOUT;
+ goto fail;
+ }
+
+ r = fd_wait_for_event(fd, POLLIN, ts + timeout - n);
+ if (r < 0)
+ goto fail;
+
+ if (r == 0) {
+ r = -ETIMEDOUT;
+ goto fail;
+ }
+ }
+
+ l = read(notify, inotify_buffer, sizeof(inotify_buffer));
+ if (l < 0) {
+
+ if (errno == EINTR || errno == EAGAIN)
+ continue;
+
+ r = -errno;
+ goto fail;
+ }
+
+ e = (struct inotify_event*) inotify_buffer;
+
+ while (l > 0) {
+ size_t step;
+
+ if (e->wd != wd || !(e->mask & IN_CLOSE)) {
+ r = -EIO;
+ goto fail;
+ }
+
+ step = sizeof(struct inotify_event) + e->len;
+ assert(step <= (size_t) l);
+
+ e = (struct inotify_event*) ((uint8_t*) e + step);
+ l -= step;
+ }
+
+ break;
+ }
+
+ /* We close the tty fd here since if the old session
+ * ended our handle will be dead. It's important that
+ * we do this after sleeping, so that we don't enter
+ * an endless loop. */
+ close_nointr_nofail(fd);
+ }
+
+ if (notify >= 0)
+ close_nointr_nofail(notify);
+
+ r = reset_terminal_fd(fd, true);
+ if (r < 0)
+ log_warning("Failed to reset terminal: %s", strerror(-r));
+
+ return fd;
+
+fail:
+ if (fd >= 0)
+ close_nointr_nofail(fd);
+
+ if (notify >= 0)
+ close_nointr_nofail(notify);
+
+ return r;
+}
+
+int release_terminal(void) {
+ int r = 0;
+ struct sigaction sa_old, sa_new = {
+ .sa_handler = SIG_IGN,
+ .sa_flags = SA_RESTART,
+ };
+ _cleanup_close_ int fd;
+
+ fd = open("/dev/tty", O_RDWR|O_NOCTTY|O_NDELAY|O_CLOEXEC);
+ if (fd < 0)
+ return -errno;
+
+ /* Temporarily ignore SIGHUP, so that we don't get SIGHUP'ed
+ * by our own TIOCNOTTY */
+ assert_se(sigaction(SIGHUP, &sa_new, &sa_old) == 0);
+
+ if (ioctl(fd, TIOCNOTTY) < 0)
+ r = -errno;
+
+ assert_se(sigaction(SIGHUP, &sa_old, NULL) == 0);
+
+ return r;
+}
+
+int sigaction_many(const struct sigaction *sa, ...) {
+ va_list ap;
+ int r = 0, sig;
+
+ va_start(ap, sa);
+ while ((sig = va_arg(ap, int)) > 0)
+ if (sigaction(sig, sa, NULL) < 0)
+ r = -errno;
+ va_end(ap);
+
+ return r;
+}
+
+int ignore_signals(int sig, ...) {
+ struct sigaction sa = {
+ .sa_handler = SIG_IGN,
+ .sa_flags = SA_RESTART,
+ };
+ va_list ap;
+ int r = 0;
+
+
+ if (sigaction(sig, &sa, NULL) < 0)
+ r = -errno;
+
+ va_start(ap, sig);
+ while ((sig = va_arg(ap, int)) > 0)
+ if (sigaction(sig, &sa, NULL) < 0)
+ r = -errno;
+ va_end(ap);
+
+ return r;
+}
+
+int default_signals(int sig, ...) {
+ struct sigaction sa = {
+ .sa_handler = SIG_DFL,
+ .sa_flags = SA_RESTART,
+ };
+ va_list ap;
+ int r = 0;
+
+ if (sigaction(sig, &sa, NULL) < 0)
+ r = -errno;
+
+ va_start(ap, sig);
+ while ((sig = va_arg(ap, int)) > 0)
+ if (sigaction(sig, &sa, NULL) < 0)
+ r = -errno;
+ va_end(ap);
+
+ return r;
+}
+
+int close_pipe(int p[]) {
+ int a = 0, b = 0;
+
+ assert(p);
+
+ if (p[0] >= 0) {
+ a = close_nointr(p[0]);
+ p[0] = -1;
+ }
+
+ if (p[1] >= 0) {
+ b = close_nointr(p[1]);
+ p[1] = -1;
+ }
+
+ return a < 0 ? a : b;
+}
+
+ssize_t loop_read(int fd, void *buf, size_t nbytes, bool do_poll) {
+ uint8_t *p;
+ ssize_t n = 0;
+
+ assert(fd >= 0);
+ assert(buf);
+
+ p = buf;
+
+ while (nbytes > 0) {
+ ssize_t k;
+
+ if ((k = read(fd, p, nbytes)) <= 0) {
+
+ if (k < 0 && errno == EINTR)
+ continue;
+
+ if (k < 0 && errno == EAGAIN && do_poll) {
+ struct pollfd pollfd = {
+ .fd = fd,
+ .events = POLLIN,
+ };
+
+ if (poll(&pollfd, 1, -1) < 0) {
+ if (errno == EINTR)
+ continue;
+
+ return n > 0 ? n : -errno;
+ }
+
+ if (pollfd.revents != POLLIN)
+ return n > 0 ? n : -EIO;
+
+ continue;
+ }
+
+ return n > 0 ? n : (k < 0 ? -errno : 0);
+ }
+
+ p += k;
+ nbytes -= k;
+ n += k;
+ }
+
+ return n;
+}
+
+ssize_t loop_write(int fd, const void *buf, size_t nbytes, bool do_poll) {
+ const uint8_t *p;
+ ssize_t n = 0;
+
+ assert(fd >= 0);
+ assert(buf);
+
+ p = buf;
+
+ while (nbytes > 0) {
+ ssize_t k;
+
+ k = write(fd, p, nbytes);
+ if (k <= 0) {
+
+ if (k < 0 && errno == EINTR)
+ continue;
+
+ if (k < 0 && errno == EAGAIN && do_poll) {
+ struct pollfd pollfd = {
+ .fd = fd,
+ .events = POLLOUT,
+ };
+
+ if (poll(&pollfd, 1, -1) < 0) {
+ if (errno == EINTR)
+ continue;
+
+ return n > 0 ? n : -errno;
+ }
+
+ if (pollfd.revents != POLLOUT)
+ return n > 0 ? n : -EIO;
+
+ continue;
+ }
+
+ return n > 0 ? n : (k < 0 ? -errno : 0);
+ }
+
+ p += k;
+ nbytes -= k;
+ n += k;
+ }
+
+ return n;
+}
+
+int parse_bytes(const char *t, off_t *bytes) {
+ static const struct {
+ const char *suffix;
+ off_t factor;
+ } table[] = {
+ { "B", 1 },
+ { "K", 1024ULL },
+ { "M", 1024ULL*1024ULL },
+ { "G", 1024ULL*1024ULL*1024ULL },
+ { "T", 1024ULL*1024ULL*1024ULL*1024ULL },
+ { "P", 1024ULL*1024ULL*1024ULL*1024ULL*1024ULL },
+ { "E", 1024ULL*1024ULL*1024ULL*1024ULL*1024ULL*1024ULL },
+ { "", 1 },
+ };
+
+ const char *p;
+ off_t r = 0;
+
+ assert(t);
+ assert(bytes);
+
+ p = t;
+ do {
+ long long l;
+ char *e;
+ unsigned i;
+
+ errno = 0;
+ l = strtoll(p, &e, 10);
+
+ if (errno > 0)
+ return -errno;
+
+ if (l < 0)
+ return -ERANGE;
+
+ if (e == p)
+ return -EINVAL;
+
+ e += strspn(e, WHITESPACE);
+
+ for (i = 0; i < ELEMENTSOF(table); i++)
+ if (startswith(e, table[i].suffix)) {
+ r += (off_t) l * table[i].factor;
+ p = e + strlen(table[i].suffix);
+ break;
+ }
+
+ if (i >= ELEMENTSOF(table))
+ return -EINVAL;
+
+ } while (*p != 0);
+
+ *bytes = r;
+
+ return 0;
+}
+
+int make_stdio(int fd) {
+ int r, s, t;
+
+ assert(fd >= 0);
+
+ r = dup3(fd, STDIN_FILENO, 0);
+ s = dup3(fd, STDOUT_FILENO, 0);
+ t = dup3(fd, STDERR_FILENO, 0);
+
+ if (fd >= 3)
+ close_nointr_nofail(fd);
+
+ if (r < 0 || s < 0 || t < 0)
+ return -errno;
+
+ /* We rely here that the new fd has O_CLOEXEC not set */
+
+ return 0;
+}
+
+int make_null_stdio(void) {
+ int null_fd;
+
+ null_fd = open("/dev/null", O_RDWR|O_NOCTTY);
+ if (null_fd < 0)
+ return -errno;
+
+ return make_stdio(null_fd);
+}
+
+bool is_device_path(const char *path) {
+
+ /* Returns true on paths that refer to a device, either in
+ * sysfs or in /dev */
+
+ return
+ path_startswith(path, "/dev/") ||
+ path_startswith(path, "/sys/");
+}
+
+int dir_is_empty(const char *path) {
+ _cleanup_closedir_ DIR *d;
+ int r;
+
+ d = opendir(path);
+ if (!d)
+ return -errno;
+
+ for (;;) {
+ struct dirent *de;
+ union dirent_storage buf;
+
+ r = readdir_r(d, &buf.de, &de);
+ if (r > 0)
+ return -r;
+
+ if (!de)
+ return 1;
+
+ if (!ignore_file(de->d_name))
+ return 0;
+ }
+}
+
+char* dirname_malloc(const char *path) {
+ char *d, *dir, *dir2;
+
+ d = strdup(path);
+ if (!d)
+ return NULL;
+ dir = dirname(d);
+ assert(dir);
+
+ if (dir != d) {
+ dir2 = strdup(dir);
+ free(d);
+ return dir2;
+ }
+
+ return dir;
+}
+
+unsigned long long random_ull(void) {
+ _cleanup_close_ int fd;
+ uint64_t ull;
+ ssize_t r;
+
+ fd = open("/dev/urandom", O_RDONLY|O_CLOEXEC|O_NOCTTY);
+ if (fd < 0)
+ goto fallback;
+
+ r = loop_read(fd, &ull, sizeof(ull), true);
+ if (r != sizeof(ull))
+ goto fallback;
+
+ return ull;
+
+fallback:
+ return random() * RAND_MAX + random();
+}
+
+void rename_process(const char name[8]) {
+ assert(name);
+
+ /* This is a like a poor man's setproctitle(). It changes the
+ * comm field, argv[0], and also the glibc's internally used
+ * name of the process. For the first one a limit of 16 chars
+ * applies, to the second one usually one of 10 (i.e. length
+ * of "/sbin/init"), to the third one one of 7 (i.e. length of
+ * "systemd"). If you pass a longer string it will be
+ * truncated */
+
+ prctl(PR_SET_NAME, name);
+
+ if (program_invocation_name)
+ strncpy(program_invocation_name, name, strlen(program_invocation_name));
+
+ if (saved_argc > 0) {
+ int i;
+
+ if (saved_argv[0])
+ strncpy(saved_argv[0], name, strlen(saved_argv[0]));
+
+ for (i = 1; i < saved_argc; i++) {
+ if (!saved_argv[i])
+ break;
+
+ memset(saved_argv[i], 0, strlen(saved_argv[i]));
+ }
+ }
+}
+
+void sigset_add_many(sigset_t *ss, ...) {
+ va_list ap;
+ int sig;
+
+ assert(ss);
+
+ va_start(ap, ss);
+ while ((sig = va_arg(ap, int)) > 0)
+ assert_se(sigaddset(ss, sig) == 0);
+ va_end(ap);
+}
+
+char* gethostname_malloc(void) {
+ struct utsname u;
+
+ assert_se(uname(&u) >= 0);
+
+ if (!isempty(u.nodename) && !streq(u.nodename, "(none)"))
+ return strdup(u.nodename);
+
+ return strdup(u.sysname);
+}
+
+bool hostname_is_set(void) {
+ struct utsname u;
+
+ assert_se(uname(&u) >= 0);
+
+ return !isempty(u.nodename) && !streq(u.nodename, "(none)");
+}
+
+static char *lookup_uid(uid_t uid) {
+ long bufsize;
+ char *name;
+ _cleanup_free_ char *buf = NULL;
+ struct passwd pwbuf, *pw = NULL;
+
+ /* Shortcut things to avoid NSS lookups */
+ if (uid == 0)
+ return strdup("root");
+
+ bufsize = sysconf(_SC_GETPW_R_SIZE_MAX);
+ if (bufsize <= 0)
+ bufsize = 4096;
+
+ buf = malloc(bufsize);
+ if (!buf)
+ return NULL;
+
+ if (getpwuid_r(uid, &pwbuf, buf, bufsize, &pw) == 0 && pw)
+ return strdup(pw->pw_name);
+
+ if (asprintf(&name, "%lu", (unsigned long) uid) < 0)
+ return NULL;
+
+ return name;
+}
+
+char* getlogname_malloc(void) {
+ uid_t uid;
+ struct stat st;
+
+ if (isatty(STDIN_FILENO) && fstat(STDIN_FILENO, &st) >= 0)
+ uid = st.st_uid;
+ else
+ uid = getuid();
+
+ return lookup_uid(uid);
+}
+
+char *getusername_malloc(void) {
+ const char *e;
+
+ e = getenv("USER");
+ if (e)
+ return strdup(e);
+
+ return lookup_uid(getuid());
+}
+
+int getttyname_malloc(int fd, char **r) {
+ char path[PATH_MAX], *c;
+ int k;
+
+ assert(r);
+
+ k = ttyname_r(fd, path, sizeof(path));
+ if (k != 0)
+ return -k;
+
+ char_array_0(path);
+
+ c = strdup(startswith(path, "/dev/") ? path + 5 : path);
+ if (!c)
+ return -ENOMEM;
+
+ *r = c;
+ return 0;
+}
+
+int getttyname_harder(int fd, char **r) {
+ int k;
+ char *s;
+
+ k = getttyname_malloc(fd, &s);
+ if (k < 0)
+ return k;
+
+ if (streq(s, "tty")) {
+ free(s);
+ return get_ctty(0, NULL, r);
+ }
+
+ *r = s;
+ return 0;
+}
+
+int get_ctty_devnr(pid_t pid, dev_t *d) {
+ _cleanup_fclose_ FILE *f = NULL;
+ char line[LINE_MAX], *p;
+ unsigned long ttynr;
+ const char *fn;
+ int k;
+
+ assert(pid >= 0);
+ assert(d);
+
+ if (pid == 0)
+ fn = "/proc/self/stat";
+ else
+ fn = procfs_file_alloca(pid, "stat");
+
+ f = fopen(fn, "re");
+ if (!f)
+ return -errno;
+
+ if (!fgets(line, sizeof(line), f)) {
+ k = feof(f) ? -EIO : -errno;
+ return k;
+ }
+
+ p = strrchr(line, ')');
+ if (!p)
+ return -EIO;
+
+ p++;
+
+ if (sscanf(p, " "
+ "%*c " /* state */
+ "%*d " /* ppid */
+ "%*d " /* pgrp */
+ "%*d " /* session */
+ "%lu ", /* ttynr */
+ &ttynr) != 1)
+ return -EIO;
+
+ if (major(ttynr) == 0 && minor(ttynr) == 0)
+ return -ENOENT;
+
+ *d = (dev_t) ttynr;
+ return 0;
+}
+
+int get_ctty(pid_t pid, dev_t *_devnr, char **r) {
+ int k;
+ char fn[sizeof("/dev/char/")-1 + 2*DECIMAL_STR_MAX(unsigned) + 1 + 1], *s, *b, *p;
+ dev_t devnr;
+
+ assert(r);
+
+ k = get_ctty_devnr(pid, &devnr);
+ if (k < 0)
+ return k;
+
+ snprintf(fn, sizeof(fn), "/dev/char/%u:%u", major(devnr), minor(devnr));
+
+ k = readlink_malloc(fn, &s);
+ if (k < 0) {
+
+ if (k != -ENOENT)
+ return k;
+
+ /* This is an ugly hack */
+ if (major(devnr) == 136) {
+ if (asprintf(&b, "pts/%lu", (unsigned long) minor(devnr)) < 0)
+ return -ENOMEM;
+
+ *r = b;
+ if (_devnr)
+ *_devnr = devnr;
+
+ return 0;
+ }
+
+ /* Probably something like the ptys which have no
+ * symlink in /dev/char. Let's return something
+ * vaguely useful. */
+
+ b = strdup(fn + 5);
+ if (!b)
+ return -ENOMEM;
+
+ *r = b;
+ if (_devnr)
+ *_devnr = devnr;
+
+ return 0;
+ }
+
+ if (startswith(s, "/dev/"))
+ p = s + 5;
+ else if (startswith(s, "../"))
+ p = s + 3;
+ else
+ p = s;
+
+ b = strdup(p);
+ free(s);
+
+ if (!b)
+ return -ENOMEM;
+
+ *r = b;
+ if (_devnr)
+ *_devnr = devnr;
+
+ return 0;
+}
+
+int rm_rf_children_dangerous(int fd, bool only_dirs, bool honour_sticky, struct stat *root_dev) {
+ DIR *d;
+ int ret = 0;
+
+ assert(fd >= 0);
+
+ /* This returns the first error we run into, but nevertheless
+ * tries to go on. This closes the passed fd. */
+
+ d = fdopendir(fd);
+ if (!d) {
+ close_nointr_nofail(fd);
+
+ return errno == ENOENT ? 0 : -errno;
+ }
+
+ for (;;) {
+ struct dirent *de;
+ union dirent_storage buf;
+ bool is_dir, keep_around;
+ struct stat st;
+ int r;
+
+ r = readdir_r(d, &buf.de, &de);
+ if (r != 0 && ret == 0) {
+ ret = -r;
+ break;
+ }
+
+ if (!de)
+ break;
+
+ if (streq(de->d_name, ".") || streq(de->d_name, ".."))
+ continue;
+
+ if (de->d_type == DT_UNKNOWN ||
+ honour_sticky ||
+ (de->d_type == DT_DIR && root_dev)) {
+ if (fstatat(fd, de->d_name, &st, AT_SYMLINK_NOFOLLOW) < 0) {
+ if (ret == 0 && errno != ENOENT)
+ ret = -errno;
+ continue;
+ }
+
+ is_dir = S_ISDIR(st.st_mode);
+ keep_around =
+ honour_sticky &&
+ (st.st_uid == 0 || st.st_uid == getuid()) &&
+ (st.st_mode & S_ISVTX);
+ } else {
+ is_dir = de->d_type == DT_DIR;
+ keep_around = false;
+ }
+
+ if (is_dir) {
+ int subdir_fd;
+
+ /* if root_dev is set, remove subdirectories only, if device is same as dir */
+ if (root_dev && st.st_dev != root_dev->st_dev)
+ continue;
+
+ subdir_fd = openat(fd, de->d_name,
+ O_RDONLY|O_NONBLOCK|O_DIRECTORY|O_CLOEXEC|O_NOFOLLOW|O_NOATIME);
+ if (subdir_fd < 0) {
+ if (ret == 0 && errno != ENOENT)
+ ret = -errno;
+ continue;
+ }
+
+ r = rm_rf_children_dangerous(subdir_fd, only_dirs, honour_sticky, root_dev);
+ if (r < 0 && ret == 0)
+ ret = r;
+
+ if (!keep_around)
+ if (unlinkat(fd, de->d_name, AT_REMOVEDIR) < 0) {
+ if (ret == 0 && errno != ENOENT)
+ ret = -errno;
+ }
+
+ } else if (!only_dirs && !keep_around) {
+
+ if (unlinkat(fd, de->d_name, 0) < 0) {
+ if (ret == 0 && errno != ENOENT)
+ ret = -errno;
+ }
+ }
+ }
+
+ closedir(d);
+
+ return ret;
+}
+
+_pure_ static int is_temporary_fs(struct statfs *s) {
+ assert(s);
+ return
+ F_TYPE_CMP(s->f_type, TMPFS_MAGIC) ||
+ F_TYPE_CMP(s->f_type, RAMFS_MAGIC);
+}
+
+int rm_rf_children(int fd, bool only_dirs, bool honour_sticky, struct stat *root_dev) {
+ struct statfs s;
+
+ assert(fd >= 0);
+
+ if (fstatfs(fd, &s) < 0) {
+ close_nointr_nofail(fd);
+ return -errno;
+ }
+
+ /* We refuse to clean disk file systems with this call. This
+ * is extra paranoia just to be sure we never ever remove
+ * non-state data */
+ if (!is_temporary_fs(&s)) {
+ log_error("Attempted to remove disk file system, and we can't allow that.");
+ close_nointr_nofail(fd);
+ return -EPERM;
+ }
+
+ return rm_rf_children_dangerous(fd, only_dirs, honour_sticky, root_dev);
+}
+
+static int rm_rf_internal(const char *path, bool only_dirs, bool delete_root, bool honour_sticky, bool dangerous) {
+ int fd, r;
+ struct statfs s;
+
+ assert(path);
+
+ /* We refuse to clean the root file system with this
+ * call. This is extra paranoia to never cause a really
+ * seriously broken system. */
+ if (path_equal(path, "/")) {
+ log_error("Attempted to remove entire root file system, and we can't allow that.");
+ return -EPERM;
+ }
+
+ fd = open(path, O_RDONLY|O_NONBLOCK|O_DIRECTORY|O_CLOEXEC|O_NOFOLLOW|O_NOATIME);
+ if (fd < 0) {
+
+ if (errno != ENOTDIR)
+ return -errno;
+
+ if (!dangerous) {
+ if (statfs(path, &s) < 0)
+ return -errno;
+
+ if (!is_temporary_fs(&s)) {
+ log_error("Attempted to remove disk file system, and we can't allow that.");
+ return -EPERM;
+ }
+ }
+
+ if (delete_root && !only_dirs)
+ if (unlink(path) < 0 && errno != ENOENT)
+ return -errno;
+
+ return 0;
+ }
+
+ if (!dangerous) {
+ if (fstatfs(fd, &s) < 0) {
+ close_nointr_nofail(fd);
+ return -errno;
+ }
+
+ if (!is_temporary_fs(&s)) {
+ log_error("Attempted to remove disk file system, and we can't allow that.");
+ close_nointr_nofail(fd);
+ return -EPERM;
+ }
+ }
+
+ r = rm_rf_children_dangerous(fd, only_dirs, honour_sticky, NULL);
+ if (delete_root) {
+
+ if (honour_sticky && file_is_priv_sticky(path) > 0)
+ return r;
+
+ if (rmdir(path) < 0 && errno != ENOENT) {
+ if (r == 0)
+ r = -errno;
+ }
+ }
+
+ return r;
+}
+
+int rm_rf(const char *path, bool only_dirs, bool delete_root, bool honour_sticky) {
+ return rm_rf_internal(path, only_dirs, delete_root, honour_sticky, false);
+}
+
+int rm_rf_dangerous(const char *path, bool only_dirs, bool delete_root, bool honour_sticky) {
+ return rm_rf_internal(path, only_dirs, delete_root, honour_sticky, true);
+}
+
+int chmod_and_chown(const char *path, mode_t mode, uid_t uid, gid_t gid) {
+ assert(path);
+
+ /* Under the assumption that we are running privileged we
+ * first change the access mode and only then hand out
+ * ownership to avoid a window where access is too open. */
+
+ if (mode != (mode_t) -1)
+ if (chmod(path, mode) < 0)
+ return -errno;
+
+ if (uid != (uid_t) -1 || gid != (gid_t) -1)
+ if (chown(path, uid, gid) < 0)
+ return -errno;
+
+ return 0;
+}
+
+int fchmod_and_fchown(int fd, mode_t mode, uid_t uid, gid_t gid) {
+ assert(fd >= 0);
+
+ /* Under the assumption that we are running privileged we
+ * first change the access mode and only then hand out
+ * ownership to avoid a window where access is too open. */
+
+ if (fchmod(fd, mode) < 0)
+ return -errno;
+
+ if (fchown(fd, uid, gid) < 0)
+ return -errno;
+
+ return 0;
+}
+
+cpu_set_t* cpu_set_malloc(unsigned *ncpus) {
+ cpu_set_t *r;
+ unsigned n = 1024;
+
+ /* Allocates the cpuset in the right size */
+
+ for (;;) {
+ if (!(r = CPU_ALLOC(n)))
+ return NULL;
+
+ if (sched_getaffinity(0, CPU_ALLOC_SIZE(n), r) >= 0) {
+ CPU_ZERO_S(CPU_ALLOC_SIZE(n), r);
+
+ if (ncpus)
+ *ncpus = n;
+
+ return r;
+ }
+
+ CPU_FREE(r);
+
+ if (errno != EINVAL)
+ return NULL;
+
+ n *= 2;
+ }
+}
+
+int status_vprintf(const char *status, bool ellipse, bool ephemeral, const char *format, va_list ap) {
+ static const char status_indent[] = " "; /* "[" STATUS "] " */
+ _cleanup_free_ char *s = NULL;
+ _cleanup_close_ int fd = -1;
+ struct iovec iovec[6] = {};
+ int n = 0;
+ static bool prev_ephemeral;
+
+ assert(format);
+
+ /* This is independent of logging, as status messages are
+ * optional and go exclusively to the console. */
+
+ if (vasprintf(&s, format, ap) < 0)
+ return log_oom();
+
+ fd = open_terminal("/dev/console", O_WRONLY|O_NOCTTY|O_CLOEXEC);
+ if (fd < 0)
+ return fd;
+
+ if (ellipse) {
+ char *e;
+ size_t emax, sl;
+ int c;
+
+ c = fd_columns(fd);
+ if (c <= 0)
+ c = 80;
+
+ sl = status ? sizeof(status_indent)-1 : 0;
+
+ emax = c - sl - 1;
+ if (emax < 3)
+ emax = 3;
+
+ e = ellipsize(s, emax, 75);
+ if (e) {
+ free(s);
+ s = e;
+ }
+ }
+
+ if (prev_ephemeral)
+ IOVEC_SET_STRING(iovec[n++], "\r" ANSI_ERASE_TO_END_OF_LINE);
+ prev_ephemeral = ephemeral;
+
+ if (status) {
+ if (!isempty(status)) {
+ IOVEC_SET_STRING(iovec[n++], "[");
+ IOVEC_SET_STRING(iovec[n++], status);
+ IOVEC_SET_STRING(iovec[n++], "] ");
+ } else
+ IOVEC_SET_STRING(iovec[n++], status_indent);
+ }
+
+ IOVEC_SET_STRING(iovec[n++], s);
+ if (!ephemeral)
+ IOVEC_SET_STRING(iovec[n++], "\n");
+
+ if (writev(fd, iovec, n) < 0)
+ return -errno;
+
+ return 0;
+}
+
+int status_printf(const char *status, bool ellipse, bool ephemeral, const char *format, ...) {
+ va_list ap;
+ int r;
+
+ assert(format);
+
+ va_start(ap, format);
+ r = status_vprintf(status, ellipse, ephemeral, format, ap);
+ va_end(ap);
+
+ return r;
+}
+
+int status_welcome(void) {
+ int r;
+ _cleanup_free_ char *pretty_name = NULL, *ansi_color = NULL;
+
+ r = parse_env_file("/etc/os-release", NEWLINE,
+ "PRETTY_NAME", &pretty_name,
+ "ANSI_COLOR", &ansi_color,
+ NULL);
+ if (r < 0 && r != -ENOENT)
+ log_warning("Failed to read /etc/os-release: %s", strerror(-r));
+
+ return status_printf(NULL, false, false,
+ "\nWelcome to \x1B[%sm%s\x1B[0m!\n",
+ isempty(ansi_color) ? "1" : ansi_color,
+ isempty(pretty_name) ? "Linux" : pretty_name);
+}
+
+char *replace_env(const char *format, char **env) {
+ enum {
+ WORD,
+ CURLY,
+ VARIABLE
+ } state = WORD;
+
+ const char *e, *word = format;
+ char *r = NULL, *k;
+
+ assert(format);
+
+ for (e = format; *e; e ++) {
+
+ switch (state) {
+
+ case WORD:
+ if (*e == '$')
+ state = CURLY;
+ break;
+
+ case CURLY:
+ if (*e == '{') {
+ if (!(k = strnappend(r, word, e-word-1)))
+ goto fail;
+
+ free(r);
+ r = k;
+
+ word = e-1;
+ state = VARIABLE;
+
+ } else if (*e == '$') {
+ if (!(k = strnappend(r, word, e-word)))
+ goto fail;
+
+ free(r);
+ r = k;
+
+ word = e+1;
+ state = WORD;
+ } else
+ state = WORD;
+ break;
+
+ case VARIABLE:
+ if (*e == '}') {
+ const char *t;
+
+ t = strempty(strv_env_get_n(env, word+2, e-word-2));
+
+ k = strappend(r, t);
+ if (!k)
+ goto fail;
+
+ free(r);
+ r = k;
+
+ word = e+1;
+ state = WORD;
+ }
+ break;
+ }
+ }
+
+ if (!(k = strnappend(r, word, e-word)))
+ goto fail;
+
+ free(r);
+ return k;
+
+fail:
+ free(r);
+ return NULL;
+}
+
+char **replace_env_argv(char **argv, char **env) {
+ char **r, **i;
+ unsigned k = 0, l = 0;
+
+ l = strv_length(argv);
+
+ if (!(r = new(char*, l+1)))
+ return NULL;
+
+ STRV_FOREACH(i, argv) {
+
+ /* If $FOO appears as single word, replace it by the split up variable */
+ if ((*i)[0] == '$' && (*i)[1] != '{') {
+ char *e;
+ char **w, **m;
+ unsigned q;
+
+ e = strv_env_get(env, *i+1);
+ if (e) {
+
+ if (!(m = strv_split_quoted(e))) {
+ r[k] = NULL;
+ strv_free(r);
+ return NULL;
+ }
+ } else
+ m = NULL;
+
+ q = strv_length(m);
+ l = l + q - 1;
+
+ if (!(w = realloc(r, sizeof(char*) * (l+1)))) {
+ r[k] = NULL;
+ strv_free(r);
+ strv_free(m);
+ return NULL;
+ }
+
+ r = w;
+ if (m) {
+ memcpy(r + k, m, q * sizeof(char*));
+ free(m);
+ }
+
+ k += q;
+ continue;
+ }
+
+ /* If ${FOO} appears as part of a word, replace it by the variable as-is */
+ if (!(r[k++] = replace_env(*i, env))) {
+ strv_free(r);
+ return NULL;
+ }
+ }
+
+ r[k] = NULL;
+ return r;
+}
+
+int fd_columns(int fd) {
+ struct winsize ws = {};
+
+ if (ioctl(fd, TIOCGWINSZ, &ws) < 0)
+ return -errno;
+
+ if (ws.ws_col <= 0)
+ return -EIO;
+
+ return ws.ws_col;
+}
+
+unsigned columns(void) {
+ const char *e;
+ int c;
+
+ if (_likely_(cached_columns > 0))
+ return cached_columns;
+
+ c = 0;
+ e = getenv("COLUMNS");
+ if (e)
+ safe_atoi(e, &c);
+
+ if (c <= 0)
+ c = fd_columns(STDOUT_FILENO);
+
+ if (c <= 0)
+ c = 80;
+
+ cached_columns = c;
+ return c;
+}
+
+int fd_lines(int fd) {
+ struct winsize ws = {};
+
+ if (ioctl(fd, TIOCGWINSZ, &ws) < 0)
+ return -errno;
+
+ if (ws.ws_row <= 0)
+ return -EIO;
+
+ return ws.ws_row;
+}
+
+unsigned lines(void) {
+ const char *e;
+ unsigned l;
+
+ if (_likely_(cached_lines > 0))
+ return cached_lines;
+
+ l = 0;
+ e = getenv("LINES");
+ if (e)
+ safe_atou(e, &l);
+
+ if (l <= 0)
+ l = fd_lines(STDOUT_FILENO);
+
+ if (l <= 0)
+ l = 24;
+
+ cached_lines = l;
+ return cached_lines;
+}
+
+/* intended to be used as a SIGWINCH sighandler */
+void columns_lines_cache_reset(int signum) {
+ cached_columns = 0;
+ cached_lines = 0;
+}
+
+bool on_tty(void) {
+ static int cached_on_tty = -1;
+
+ if (_unlikely_(cached_on_tty < 0))
+ cached_on_tty = isatty(STDOUT_FILENO) > 0;
+
+ return cached_on_tty;
+}
+
+int running_in_chroot(void) {
+ struct stat a = {}, b = {};
+
+ /* Only works as root */
+ if (stat("/proc/1/root", &a) < 0)
+ return -errno;
+
+ if (stat("/", &b) < 0)
+ return -errno;
+
+ return
+ a.st_dev != b.st_dev ||
+ a.st_ino != b.st_ino;
+}
+
+char *ellipsize_mem(const char *s, size_t old_length, size_t new_length, unsigned percent) {
+ size_t x;
+ char *r;
+
+ assert(s);
+ assert(percent <= 100);
+ assert(new_length >= 3);
+
+ if (old_length <= 3 || old_length <= new_length)
+ return strndup(s, old_length);
+
+ r = new0(char, new_length+1);
+ if (!r)
+ return r;
+
+ x = (new_length * percent) / 100;
+
+ if (x > new_length - 3)
+ x = new_length - 3;
+
+ memcpy(r, s, x);
+ r[x] = '.';
+ r[x+1] = '.';
+ r[x+2] = '.';
+ memcpy(r + x + 3,
+ s + old_length - (new_length - x - 3),
+ new_length - x - 3);
+
+ return r;
+}
+
+char *ellipsize(const char *s, size_t length, unsigned percent) {
+ return ellipsize_mem(s, strlen(s), length, percent);
+}
+
+int touch(const char *path) {
+ int fd;
+
+ assert(path);
+
+ /* This just opens the file for writing, ensuring it
+ * exists. It doesn't call utimensat() the way /usr/bin/touch
+ * does it. */
+
+ fd = open(path, O_WRONLY|O_CREAT|O_CLOEXEC|O_NOCTTY, 0644);
+ if (fd < 0)
+ return -errno;
+
+ close_nointr_nofail(fd);
+ return 0;
+}
+
+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. We should make this smarter one
+ * day...*/
+
+ l = strlen(s);
+ if (l < 2)
+ return strdup(s);
+
+ if (strchr(quotes, s[0]) && s[l-1] == s[0])
+ return strndup(s+1, l-2);
+
+ return strdup(s);
+}
+
+char *normalize_env_assignment(const char *s) {
+ _cleanup_free_ char *name = NULL, *value = NULL, *p = NULL;
+ char *eq, *r;
+
+ eq = strchr(s, '=');
+ if (!eq) {
+ char *t;
+
+ r = strdup(s);
+ if (!r)
+ return NULL;
+
+ t = strstrip(r);
+ if (t == r)
+ return r;
+
+ memmove(r, t, strlen(t) + 1);
+ return r;
+ }
+
+ name = strndup(s, eq - s);
+ if (!name)
+ return NULL;
+
+ p = strdup(eq + 1);
+ if (!p)
+ return NULL;
+
+ value = unquote(strstrip(p), QUOTES);
+ if (!value)
+ return NULL;
+
+ if (asprintf(&r, "%s=%s", strstrip(name), value) < 0)
+ r = NULL;
+
+ return r;
+}
+
+int wait_for_terminate(pid_t pid, siginfo_t *status) {
+ siginfo_t dummy;
+
+ assert(pid >= 1);
+
+ if (!status)
+ status = &dummy;
+
+ for (;;) {
+ zero(*status);
+
+ if (waitid(P_PID, pid, status, WEXITED) < 0) {
+
+ if (errno == EINTR)
+ continue;
+
+ return -errno;
+ }
+
+ return 0;
+ }
+}
+
+int wait_for_terminate_and_warn(const char *name, pid_t pid) {
+ int r;
+ siginfo_t status;
+
+ assert(name);
+ assert(pid > 1);
+
+ r = wait_for_terminate(pid, &status);
+ if (r < 0) {
+ log_warning("Failed to wait for %s: %s", name, strerror(-r));
+ return r;
+ }
+
+ if (status.si_code == CLD_EXITED) {
+ if (status.si_status != 0) {
+ log_warning("%s failed with error code %i.", name, status.si_status);
+ return status.si_status;
+ }
+
+ log_debug("%s succeeded.", name);
+ return 0;
+
+ } else if (status.si_code == CLD_KILLED ||
+ status.si_code == CLD_DUMPED) {
+
+ log_warning("%s terminated by signal %s.", name, signal_to_string(status.si_status));
+ return -EPROTO;
+ }
+
+ log_warning("%s failed due to unknown reason.", name);
+ return -EPROTO;
+}
+
+_noreturn_ void freeze(void) {
+
+ /* Make sure nobody waits for us on a socket anymore */
+ close_all_fds(NULL, 0);
+
+ sync();
+
+ for (;;)
+ pause();
+}
+
+bool null_or_empty(struct stat *st) {
+ assert(st);
+
+ if (S_ISREG(st->st_mode) && st->st_size <= 0)
+ return true;
+
+ if (S_ISCHR(st->st_mode) || S_ISBLK(st->st_mode))
+ return true;
+
+ return false;
+}
+
+int null_or_empty_path(const char *fn) {
+ struct stat st;
+
+ assert(fn);
+
+ if (stat(fn, &st) < 0)
+ return -errno;
+
+ return null_or_empty(&st);
+}
+
+DIR *xopendirat(int fd, const char *name, int flags) {
+ int nfd;
+ DIR *d;
+
+ nfd = openat(fd, name, O_RDONLY|O_NONBLOCK|O_DIRECTORY|O_CLOEXEC|flags);
+ if (nfd < 0)
+ return NULL;
+
+ d = fdopendir(nfd);
+ if (!d) {
+ close_nointr_nofail(nfd);
+ return NULL;
+ }
+
+ return d;
+}
+
+int signal_from_string_try_harder(const char *s) {
+ int signo;
+ assert(s);
+
+ signo = signal_from_string(s);
+ if (signo <= 0)
+ if (startswith(s, "SIG"))
+ return signal_from_string(s+3);
+
+ return signo;
+}
+
+static char *tag_to_udev_node(const char *tagvalue, const char *by) {
+ char *dn, *t, *u;
+ int r;
+
+ /* FIXME: to follow udev's logic 100% we need to leave valid
+ * UTF8 chars unescaped */
+
+ u = unquote(tagvalue, "\"\'");
+ if (u == NULL)
+ return NULL;
+
+ t = xescape(u, "/ ");
+ free(u);
+
+ if (t == NULL)
+ return NULL;
+
+ r = asprintf(&dn, "/dev/disk/by-%s/%s", by, t);
+ free(t);
+
+ if (r < 0)
+ return NULL;
+
+ return dn;
+}
+
+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);
+}
+
+bool tty_is_vc(const char *tty) {
+ assert(tty);
+
+ if (startswith(tty, "/dev/"))
+ tty += 5;
+
+ return vtnr_from_tty(tty) >= 0;
+}
+
+bool tty_is_console(const char *tty) {
+ assert(tty);
+
+ if (startswith(tty, "/dev/"))
+ tty += 5;
+
+ return streq(tty, "console");
+}
+
+int vtnr_from_tty(const char *tty) {
+ int i, r;
+
+ assert(tty);
+
+ if (startswith(tty, "/dev/"))
+ tty += 5;
+
+ if (!startswith(tty, "tty") )
+ return -EINVAL;
+
+ if (tty[3] < '0' || tty[3] > '9')
+ return -EINVAL;
+
+ r = safe_atoi(tty+3, &i);
+ if (r < 0)
+ return r;
+
+ if (i < 0 || i > 63)
+ return -EINVAL;
+
+ return i;
+}
+
+char *resolve_dev_console(char **active) {
+ char *tty;
+
+ /* Resolve where /dev/console is pointing to, if /sys is actually ours
+ * (i.e. not read-only-mounted which is a sign for container setups) */
+
+ if (path_is_read_only_fs("/sys") > 0)
+ return NULL;
+
+ if (read_one_line_file("/sys/class/tty/console/active", active) < 0)
+ return NULL;
+
+ /* If multiple log outputs are configured the last one is what
+ * /dev/console points to */
+ tty = strrchr(*active, ' ');
+ if (tty)
+ tty++;
+ else
+ tty = *active;
+
+ return tty;
+}
+
+bool tty_is_vc_resolve(const char *tty) {
+ char *active = NULL;
+ bool b;
+
+ assert(tty);
+
+ if (startswith(tty, "/dev/"))
+ tty += 5;
+
+ if (streq(tty, "console")) {
+ tty = resolve_dev_console(&active);
+ if (!tty)
+ return false;
+ }
+
+ b = tty_is_vc(tty);
+ free(active);
+
+ return b;
+}
+
+const char *default_term_for_tty(const char *tty) {
+ assert(tty);
+
+ return tty_is_vc_resolve(tty) ? "TERM=linux" : "TERM=vt102";
+}
+
+bool dirent_is_file(const struct dirent *de) {
+ assert(de);
+
+ if (ignore_file(de->d_name))
+ return false;
+
+ if (de->d_type != DT_REG &&
+ de->d_type != DT_LNK &&
+ de->d_type != DT_UNKNOWN)
+ return false;
+
+ return true;
+}
+
+bool dirent_is_file_with_suffix(const struct dirent *de, const char *suffix) {
+ assert(de);
+
+ if (de->d_type != DT_REG &&
+ de->d_type != DT_LNK &&
+ de->d_type != DT_UNKNOWN)
+ return false;
+
+ if (ignore_file_allow_backup(de->d_name))
+ return false;
+
+ return endswith(de->d_name, suffix);
+}
+
+void execute_directory(const char *directory, DIR *d, char *argv[]) {
+ DIR *_d = NULL;
+ struct dirent *de;
+ Hashmap *pids = NULL;
+
+ assert(directory);
+
+ /* Executes all binaries in a directory in parallel and
+ * waits for them to finish. */
+
+ if (!d) {
+ if (!(_d = opendir(directory))) {
+
+ if (errno == ENOENT)
+ return;
+
+ log_error("Failed to enumerate directory %s: %m", directory);
+ return;
+ }
+
+ d = _d;
+ }
+
+ if (!(pids = hashmap_new(trivial_hash_func, trivial_compare_func))) {
+ log_error("Failed to allocate set.");
+ goto finish;
+ }
+
+ while ((de = readdir(d))) {
+ char *path;
+ pid_t pid;
+ int k;
+
+ if (!dirent_is_file(de))
+ continue;
+
+ if (asprintf(&path, "%s/%s", directory, de->d_name) < 0) {
+ log_oom();
+ continue;
+ }
+
+ if ((pid = fork()) < 0) {
+ log_error("Failed to fork: %m");
+ free(path);
+ continue;
+ }
+
+ if (pid == 0) {
+ char *_argv[2];
+ /* Child */
+
+ if (!argv) {
+ _argv[0] = path;
+ _argv[1] = NULL;
+ argv = _argv;
+ } else
+ argv[0] = path;
+
+ execv(path, argv);
+
+ log_error("Failed to execute %s: %m", path);
+ _exit(EXIT_FAILURE);
+ }
+
+ log_debug("Spawned %s as %lu", path, (unsigned long) pid);
+
+ if ((k = hashmap_put(pids, UINT_TO_PTR(pid), path)) < 0) {
+ log_error("Failed to add PID to set: %s", strerror(-k));
+ free(path);
+ }
+ }
+
+ while (!hashmap_isempty(pids)) {
+ pid_t pid = PTR_TO_UINT(hashmap_first_key(pids));
+ siginfo_t si = {};
+ char *path;
+
+ if (waitid(P_PID, pid, &si, WEXITED) < 0) {
+
+ if (errno == EINTR)
+ continue;
+
+ log_error("waitid() failed: %m");
+ goto finish;
+ }
+
+ if ((path = hashmap_remove(pids, UINT_TO_PTR(si.si_pid)))) {
+ if (!is_clean_exit(si.si_code, si.si_status, NULL)) {
+ if (si.si_code == CLD_EXITED)
+ log_error("%s exited with exit status %i.", path, si.si_status);
+ else
+ log_error("%s terminated by signal %s.", path, signal_to_string(si.si_status));
+ } else
+ log_debug("%s exited successfully.", path);
+
+ free(path);
+ }
+ }
+
+finish:
+ if (_d)
+ closedir(_d);
+
+ if (pids)
+ hashmap_free_free(pids);
+}
+
+int kill_and_sigcont(pid_t pid, int sig) {
+ int r;
+
+ r = kill(pid, sig) < 0 ? -errno : 0;
+
+ if (r >= 0)
+ kill(pid, SIGCONT);
+
+ return r;
+}
+
+bool nulstr_contains(const char*nulstr, const char *needle) {
+ const char *i;
+
+ if (!nulstr)
+ return false;
+
+ NULSTR_FOREACH(i, nulstr)
+ if (streq(i, needle))
+ return true;
+
+ return false;
+}
+
+bool plymouth_running(void) {
+ return access("/run/plymouth/pid", F_OK) >= 0;
+}
+
+char* strshorten(char *s, size_t l) {
+ assert(s);
+
+ if (l < strlen(s))
+ s[l] = 0;
+
+ return s;
+}
+
+static bool hostname_valid_char(char c) {
+ return
+ (c >= 'a' && c <= 'z') ||
+ (c >= 'A' && c <= 'Z') ||
+ (c >= '0' && c <= '9') ||
+ c == '-' ||
+ c == '_' ||
+ c == '.';
+}
+
+bool hostname_is_valid(const char *s) {
+ const char *p;
+ bool dot;
+
+ if (isempty(s))
+ return false;
+
+ for (p = s, dot = true; *p; p++) {
+ if (*p == '.') {
+ if (dot)
+ return false;
+
+ dot = true;
+ } else {
+ if (!hostname_valid_char(*p))
+ return false;
+
+ dot = false;
+ }
+ }
+
+ if (dot)
+ return false;
+
+ if (p-s > HOST_NAME_MAX)
+ return false;
+
+ return true;
+}
+
+char* hostname_cleanup(char *s, bool lowercase) {
+ char *p, *d;
+ bool dot;
+
+ for (p = s, d = s, dot = true; *p; p++) {
+ if (*p == '.') {
+ if (dot)
+ continue;
+
+ *(d++) = '.';
+ dot = true;
+ } else if (hostname_valid_char(*p)) {
+ *(d++) = lowercase ? tolower(*p) : *p;
+ dot = false;
+ }
+
+ }
+
+ if (dot && d > s)
+ d[-1] = 0;
+ else
+ *d = 0;
+
+ strshorten(s, HOST_NAME_MAX);
+
+ return s;
+}
+
+int pipe_eof(int fd) {
+ int r;
+ struct pollfd pollfd = {
+ .fd = fd,
+ .events = POLLIN|POLLHUP,
+ };
+
+ r = poll(&pollfd, 1, 0);
+ if (r < 0)
+ return -errno;
+
+ if (r == 0)
+ return 0;
+
+ return pollfd.revents & POLLHUP;
+}
+
+int fd_wait_for_event(int fd, int event, usec_t t) {
+ int r;
+ struct pollfd pollfd = {
+ .fd = fd,
+ .events = event,
+ };
+
+ r = poll(&pollfd, 1, t == (usec_t) -1 ? -1 : (int) (t / USEC_PER_MSEC));
+ if (r < 0)
+ return -errno;
+
+ if (r == 0)
+ return 0;
+
+ return pollfd.revents;
+}
+
+int fopen_temporary(const char *path, FILE **_f, char **_temp_path) {
+ FILE *f;
+ char *t;
+ const char *fn;
+ size_t k;
+ int fd;
+
+ assert(path);
+ assert(_f);
+ assert(_temp_path);
+
+ t = new(char, strlen(path) + 1 + 6 + 1);
+ if (!t)
+ return -ENOMEM;
+
+ fn = path_get_file_name(path);
+ k = fn-path;
+ memcpy(t, path, k);
+ t[k] = '.';
+ stpcpy(stpcpy(t+k+1, fn), "XXXXXX");
+
+ fd = mkostemp(t, O_WRONLY|O_CLOEXEC);
+ if (fd < 0) {
+ free(t);
+ return -errno;
+ }
+
+ f = fdopen(fd, "we");
+ if (!f) {
+ unlink(t);
+ free(t);
+ return -errno;
+ }
+
+ *_f = f;
+ *_temp_path = t;
+
+ return 0;
+}
+
+int terminal_vhangup_fd(int fd) {
+ assert(fd >= 0);
+
+ if (ioctl(fd, TIOCVHANGUP) < 0)
+ return -errno;
+
+ return 0;
+}
+
+int terminal_vhangup(const char *name) {
+ int fd, r;
+
+ fd = open_terminal(name, O_RDWR|O_NOCTTY|O_CLOEXEC);
+ if (fd < 0)
+ return fd;
+
+ r = terminal_vhangup_fd(fd);
+ close_nointr_nofail(fd);
+
+ return r;
+}
+
+int vt_disallocate(const char *name) {
+ int fd, r;
+ unsigned u;
+
+ /* Deallocate the VT if possible. If not possible
+ * (i.e. because it is the active one), at least clear it
+ * entirely (including the scrollback buffer) */
+
+ if (!startswith(name, "/dev/"))
+ return -EINVAL;
+
+ if (!tty_is_vc(name)) {
+ /* So this is not a VT. I guess we cannot deallocate
+ * it then. But let's at least clear the screen */
+
+ fd = open_terminal(name, O_RDWR|O_NOCTTY|O_CLOEXEC);
+ if (fd < 0)
+ return fd;
+
+ loop_write(fd,
+ "\033[r" /* clear scrolling region */
+ "\033[H" /* move home */
+ "\033[2J", /* clear screen */
+ 10, false);
+ close_nointr_nofail(fd);
+
+ return 0;
+ }
+
+ if (!startswith(name, "/dev/tty"))
+ return -EINVAL;
+
+ r = safe_atou(name+8, &u);
+ if (r < 0)
+ return r;
+
+ if (u <= 0)
+ return -EINVAL;
+
+ /* Try to deallocate */
+ fd = open_terminal("/dev/tty0", O_RDWR|O_NOCTTY|O_CLOEXEC);
+ if (fd < 0)
+ return fd;
+
+ r = ioctl(fd, VT_DISALLOCATE, u);
+ close_nointr_nofail(fd);
+
+ if (r >= 0)
+ return 0;
+
+ if (errno != EBUSY)
+ return -errno;
+
+ /* Couldn't deallocate, so let's clear it fully with
+ * scrollback */
+ fd = open_terminal(name, O_RDWR|O_NOCTTY|O_CLOEXEC);
+ if (fd < 0)
+ return fd;
+
+ loop_write(fd,
+ "\033[r" /* clear scrolling region */
+ "\033[H" /* move home */
+ "\033[3J", /* clear screen including scrollback, requires Linux 2.6.40 */
+ 10, false);
+ close_nointr_nofail(fd);
+
+ return 0;
+}
+
+int copy_file(const char *from, const char *to) {
+ int r, fdf, fdt;
+
+ assert(from);
+ assert(to);
+
+ fdf = open(from, O_RDONLY|O_CLOEXEC|O_NOCTTY);
+ if (fdf < 0)
+ return -errno;
+
+ fdt = open(to, O_WRONLY|O_CREAT|O_EXCL|O_CLOEXEC|O_NOCTTY, 0644);
+ if (fdt < 0) {
+ close_nointr_nofail(fdf);
+ return -errno;
+ }
+
+ for (;;) {
+ char buf[PIPE_BUF];
+ ssize_t n, k;
+
+ n = read(fdf, buf, sizeof(buf));
+ if (n < 0) {
+ r = -errno;
+
+ close_nointr_nofail(fdf);
+ close_nointr(fdt);
+ unlink(to);
+
+ return r;
+ }
+
+ if (n == 0)
+ break;
+
+ errno = 0;
+ k = loop_write(fdt, buf, n, false);
+ if (n != k) {
+ r = k < 0 ? k : (errno ? -errno : -EIO);
+
+ close_nointr_nofail(fdf);
+ close_nointr(fdt);
+
+ unlink(to);
+ return r;
+ }
+ }
+
+ close_nointr_nofail(fdf);
+ r = close_nointr(fdt);
+
+ if (r < 0) {
+ unlink(to);
+ return r;
+ }
+
+ return 0;
+}
+
+int symlink_atomic(const char *from, const char *to) {
+ char *x;
+ _cleanup_free_ char *t;
+ const char *fn;
+ size_t k;
+ unsigned long long ull;
+ unsigned i;
+ int r;
+
+ assert(from);
+ assert(to);
+
+ t = new(char, strlen(to) + 1 + 16 + 1);
+ if (!t)
+ return -ENOMEM;
+
+ fn = path_get_file_name(to);
+ k = fn-to;
+ memcpy(t, to, k);
+ t[k] = '.';
+ x = stpcpy(t+k+1, fn);
+
+ ull = random_ull();
+ for (i = 0; i < 16; i++) {
+ *(x++) = hexchar(ull & 0xF);
+ ull >>= 4;
+ }
+
+ *x = 0;
+
+ if (symlink(from, t) < 0)
+ return -errno;
+
+ if (rename(t, to) < 0) {
+ r = -errno;
+ unlink(t);
+ return r;
+ }
+
+ return 0;
+}
+
+bool display_is_local(const char *display) {
+ assert(display);
+
+ return
+ display[0] == ':' &&
+ display[1] >= '0' &&
+ display[1] <= '9';
+}
+
+int socket_from_display(const char *display, char **path) {
+ size_t k;
+ char *f, *c;
+
+ assert(display);
+ assert(path);
+
+ if (!display_is_local(display))
+ return -EINVAL;
+
+ k = strspn(display+1, "0123456789");
+
+ f = new(char, sizeof("/tmp/.X11-unix/X") + k);
+ if (!f)
+ return -ENOMEM;
+
+ c = stpcpy(f, "/tmp/.X11-unix/X");
+ memcpy(c, display+1, k);
+ c[k] = 0;
+
+ *path = f;
+
+ return 0;
+}
+
+int get_user_creds(
+ const char **username,
+ uid_t *uid, gid_t *gid,
+ const char **home,
+ const char **shell) {
+
+ struct passwd *p;
+ uid_t u;
+
+ assert(username);
+ assert(*username);
+
+ /* We enforce some special rules for uid=0: in order to avoid
+ * NSS lookups for root we hardcode its data. */
+
+ if (streq(*username, "root") || streq(*username, "0")) {
+ *username = "root";
+
+ if (uid)
+ *uid = 0;
+
+ if (gid)
+ *gid = 0;
+
+ if (home)
+ *home = "/root";
+
+ if (shell)
+ *shell = "/bin/sh";
+
+ return 0;
+ }
+
+ if (parse_uid(*username, &u) >= 0) {
+ errno = 0;
+ p = getpwuid(u);
+
+ /* If there are multiple users with the same id, make
+ * sure to leave $USER to the configured value instead
+ * of the first occurrence in the database. However if
+ * the uid was configured by a numeric uid, then let's
+ * pick the real username from /etc/passwd. */
+ if (p)
+ *username = p->pw_name;
+ } else {
+ errno = 0;
+ p = getpwnam(*username);
+ }
+
+ if (!p)
+ return errno > 0 ? -errno : -ESRCH;
+
+ if (uid)
+ *uid = p->pw_uid;
+
+ if (gid)
+ *gid = p->pw_gid;
+
+ if (home)
+ *home = p->pw_dir;
+
+ if (shell)
+ *shell = p->pw_shell;
+
+ return 0;
+}
+
+char* uid_to_name(uid_t uid) {
+ struct passwd *p;
+ char *r;
+
+ if (uid == 0)
+ return strdup("root");
+
+ p = getpwuid(uid);
+ if (p)
+ return strdup(p->pw_name);
+
+ if (asprintf(&r, "%lu", (unsigned long) uid) < 0)
+ return NULL;
+
+ return r;
+}
+
+char* gid_to_name(gid_t gid) {
+ struct group *p;
+ char *r;
+
+ if (gid == 0)
+ return strdup("root");
+
+ p = getgrgid(gid);
+ if (p)
+ return strdup(p->gr_name);
+
+ if (asprintf(&r, "%lu", (unsigned long) gid) < 0)
+ return NULL;
+
+ return r;
+}
+
+int get_group_creds(const char **groupname, gid_t *gid) {
+ struct group *g;
+ gid_t id;
+
+ assert(groupname);
+
+ /* We enforce some special rules for gid=0: in order to avoid
+ * NSS lookups for root we hardcode its data. */
+
+ if (streq(*groupname, "root") || streq(*groupname, "0")) {
+ *groupname = "root";
+
+ if (gid)
+ *gid = 0;
+
+ return 0;
+ }
+
+ if (parse_gid(*groupname, &id) >= 0) {
+ errno = 0;
+ g = getgrgid(id);
+
+ if (g)
+ *groupname = g->gr_name;
+ } else {
+ errno = 0;
+ g = getgrnam(*groupname);
+ }
+
+ if (!g)
+ return errno > 0 ? -errno : -ESRCH;
+
+ if (gid)
+ *gid = g->gr_gid;
+
+ return 0;
+}
+
+int in_gid(gid_t gid) {
+ gid_t *gids;
+ int ngroups_max, r, i;
+
+ if (getgid() == gid)
+ return 1;
+
+ if (getegid() == gid)
+ return 1;
+
+ ngroups_max = sysconf(_SC_NGROUPS_MAX);
+ assert(ngroups_max > 0);
+
+ gids = alloca(sizeof(gid_t) * ngroups_max);
+
+ r = getgroups(ngroups_max, gids);
+ if (r < 0)
+ return -errno;
+
+ for (i = 0; i < r; i++)
+ if (gids[i] == gid)
+ return 1;
+
+ return 0;
+}
+
+int in_group(const char *name) {
+ int r;
+ gid_t gid;
+
+ r = get_group_creds(&name, &gid);
+ if (r < 0)
+ return r;
+
+ return in_gid(gid);
+}
+
+int glob_exists(const char *path) {
+ _cleanup_globfree_ glob_t g = {};
+ int r, k;
+
+ assert(path);
+
+ errno = 0;
+ k = glob(path, GLOB_NOSORT|GLOB_BRACE, NULL, &g);
+
+ if (k == GLOB_NOMATCH)
+ r = 0;
+ else if (k == GLOB_NOSPACE)
+ r = -ENOMEM;
+ else if (k == 0)
+ r = !strv_isempty(g.gl_pathv);
+ else
+ r = errno ? -errno : -EIO;
+
+ return r;
+}
+
+int dirent_ensure_type(DIR *d, struct dirent *de) {
+ struct stat st;
+
+ assert(d);
+ assert(de);
+
+ if (de->d_type != DT_UNKNOWN)
+ return 0;
+
+ if (fstatat(dirfd(d), de->d_name, &st, AT_SYMLINK_NOFOLLOW) < 0)
+ return -errno;
+
+ de->d_type =
+ S_ISREG(st.st_mode) ? DT_REG :
+ S_ISDIR(st.st_mode) ? DT_DIR :
+ S_ISLNK(st.st_mode) ? DT_LNK :
+ S_ISFIFO(st.st_mode) ? DT_FIFO :
+ S_ISSOCK(st.st_mode) ? DT_SOCK :
+ S_ISCHR(st.st_mode) ? DT_CHR :
+ S_ISBLK(st.st_mode) ? DT_BLK :
+ DT_UNKNOWN;
+
+ return 0;
+}
+
+int in_search_path(const char *path, char **search) {
+ char **i, *parent;
+ int r;
+
+ r = path_get_parent(path, &parent);
+ if (r < 0)
+ return r;
+
+ r = 0;
+
+ STRV_FOREACH(i, search) {
+ if (path_equal(parent, *i)) {
+ r = 1;
+ break;
+ }
+ }
+
+ free(parent);
+
+ return r;
+}
+
+int get_files_in_directory(const char *path, char ***list) {
+ DIR *d;
+ int r = 0;
+ unsigned n = 0;
+ char **l = NULL;
+
+ assert(path);
+
+ /* Returns all files in a directory in *list, and the number
+ * of files as return value. If list is NULL returns only the
+ * number */
+
+ d = opendir(path);
+ if (!d)
+ return -errno;
+
+ for (;;) {
+ struct dirent *de;
+ union dirent_storage buf;
+ int k;
+
+ k = readdir_r(d, &buf.de, &de);
+ if (k != 0) {
+ r = -k;
+ goto finish;
+ }
+
+ if (!de)
+ break;
+
+ dirent_ensure_type(d, de);
+
+ if (!dirent_is_file(de))
+ continue;
+
+ if (list) {
+ if ((unsigned) r >= n) {
+ char **t;
+
+ n = MAX(16, 2*r);
+ t = realloc(l, sizeof(char*) * n);
+ if (!t) {
+ r = -ENOMEM;
+ goto finish;
+ }
+
+ l = t;
+ }
+
+ assert((unsigned) r < n);
+
+ l[r] = strdup(de->d_name);
+ if (!l[r]) {
+ r = -ENOMEM;
+ goto finish;
+ }
+
+ l[++r] = NULL;
+ } else
+ r++;
+ }
+
+finish:
+ if (d)
+ closedir(d);
+
+ if (r >= 0) {
+ if (list)
+ *list = l;
+ } else
+ strv_free(l);
+
+ return r;
+}
+
+char *strjoin(const char *x, ...) {
+ va_list ap;
+ size_t l;
+ char *r, *p;
+
+ va_start(ap, x);
+
+ if (x) {
+ l = strlen(x);
+
+ for (;;) {
+ const char *t;
+ size_t n;
+
+ t = va_arg(ap, const char *);
+ if (!t)
+ break;
+
+ n = strlen(t);
+ if (n > ((size_t) -1) - l) {
+ va_end(ap);
+ return NULL;
+ }
+
+ l += n;
+ }
+ } else
+ l = 0;
+
+ va_end(ap);
+
+ r = new(char, l+1);
+ if (!r)
+ return NULL;
+
+ if (x) {
+ p = stpcpy(r, x);
+
+ va_start(ap, x);
+
+ for (;;) {
+ const char *t;
+
+ t = va_arg(ap, const char *);
+ if (!t)
+ break;
+
+ p = stpcpy(p, t);
+ }
+
+ va_end(ap);
+ } else
+ r[0] = 0;
+
+ return r;
+}
+
+bool is_main_thread(void) {
+ static __thread int cached = 0;
+
+ if (_unlikely_(cached == 0))
+ cached = getpid() == gettid() ? 1 : -1;
+
+ return cached > 0;
+}
+
+int block_get_whole_disk(dev_t d, dev_t *ret) {
+ char *p, *s;
+ int r;
+ unsigned n, m;
+
+ assert(ret);
+
+ /* If it has a queue this is good enough for us */
+ if (asprintf(&p, "/sys/dev/block/%u:%u/queue", major(d), minor(d)) < 0)
+ return -ENOMEM;
+
+ r = access(p, F_OK);
+ free(p);
+
+ if (r >= 0) {
+ *ret = d;
+ return 0;
+ }
+
+ /* If it is a partition find the originating device */
+ if (asprintf(&p, "/sys/dev/block/%u:%u/partition", major(d), minor(d)) < 0)
+ return -ENOMEM;
+
+ r = access(p, F_OK);
+ free(p);
+
+ if (r < 0)
+ return -ENOENT;
+
+ /* Get parent dev_t */
+ if (asprintf(&p, "/sys/dev/block/%u:%u/../dev", major(d), minor(d)) < 0)
+ return -ENOMEM;
+
+ r = read_one_line_file(p, &s);
+ free(p);
+
+ if (r < 0)
+ return r;
+
+ r = sscanf(s, "%u:%u", &m, &n);
+ free(s);
+
+ if (r != 2)
+ return -EINVAL;
+
+ /* Only return this if it is really good enough for us. */
+ if (asprintf(&p, "/sys/dev/block/%u:%u/queue", m, n) < 0)
+ return -ENOMEM;
+
+ r = access(p, F_OK);
+ free(p);
+
+ if (r >= 0) {
+ *ret = makedev(m, n);
+ return 0;
+ }
+
+ return -ENOENT;
+}
+
+int file_is_priv_sticky(const char *p) {
+ struct stat st;
+
+ assert(p);
+
+ if (lstat(p, &st) < 0)
+ return -errno;
+
+ return
+ (st.st_uid == 0 || st.st_uid == getuid()) &&
+ (st.st_mode & S_ISVTX);
+}
+
+static const char *const ioprio_class_table[] = {
+ [IOPRIO_CLASS_NONE] = "none",
+ [IOPRIO_CLASS_RT] = "realtime",
+ [IOPRIO_CLASS_BE] = "best-effort",
+ [IOPRIO_CLASS_IDLE] = "idle"
+};
+
+DEFINE_STRING_TABLE_LOOKUP_WITH_FALLBACK(ioprio_class, int, INT_MAX);
+
+static const char *const sigchld_code_table[] = {
+ [CLD_EXITED] = "exited",
+ [CLD_KILLED] = "killed",
+ [CLD_DUMPED] = "dumped",
+ [CLD_TRAPPED] = "trapped",
+ [CLD_STOPPED] = "stopped",
+ [CLD_CONTINUED] = "continued",
+};
+
+DEFINE_STRING_TABLE_LOOKUP(sigchld_code, int);
+
+static const char *const log_facility_unshifted_table[LOG_NFACILITIES] = {
+ [LOG_FAC(LOG_KERN)] = "kern",
+ [LOG_FAC(LOG_USER)] = "user",
+ [LOG_FAC(LOG_MAIL)] = "mail",
+ [LOG_FAC(LOG_DAEMON)] = "daemon",
+ [LOG_FAC(LOG_AUTH)] = "auth",
+ [LOG_FAC(LOG_SYSLOG)] = "syslog",
+ [LOG_FAC(LOG_LPR)] = "lpr",
+ [LOG_FAC(LOG_NEWS)] = "news",
+ [LOG_FAC(LOG_UUCP)] = "uucp",
+ [LOG_FAC(LOG_CRON)] = "cron",
+ [LOG_FAC(LOG_AUTHPRIV)] = "authpriv",
+ [LOG_FAC(LOG_FTP)] = "ftp",
+ [LOG_FAC(LOG_LOCAL0)] = "local0",
+ [LOG_FAC(LOG_LOCAL1)] = "local1",
+ [LOG_FAC(LOG_LOCAL2)] = "local2",
+ [LOG_FAC(LOG_LOCAL3)] = "local3",
+ [LOG_FAC(LOG_LOCAL4)] = "local4",
+ [LOG_FAC(LOG_LOCAL5)] = "local5",
+ [LOG_FAC(LOG_LOCAL6)] = "local6",
+ [LOG_FAC(LOG_LOCAL7)] = "local7"
+};
+
+DEFINE_STRING_TABLE_LOOKUP_WITH_FALLBACK(log_facility_unshifted, int, LOG_FAC(~0));
+
+static const char *const log_level_table[] = {
+ [LOG_EMERG] = "emerg",
+ [LOG_ALERT] = "alert",
+ [LOG_CRIT] = "crit",
+ [LOG_ERR] = "err",
+ [LOG_WARNING] = "warning",
+ [LOG_NOTICE] = "notice",
+ [LOG_INFO] = "info",
+ [LOG_DEBUG] = "debug"
+};
+
+DEFINE_STRING_TABLE_LOOKUP_WITH_FALLBACK(log_level, int, LOG_DEBUG);
+
+static const char* const sched_policy_table[] = {
+ [SCHED_OTHER] = "other",
+ [SCHED_BATCH] = "batch",
+ [SCHED_IDLE] = "idle",
+ [SCHED_FIFO] = "fifo",
+ [SCHED_RR] = "rr"
+};
+
+DEFINE_STRING_TABLE_LOOKUP_WITH_FALLBACK(sched_policy, int, INT_MAX);
+
+static const char* const rlimit_table[] = {
+ [RLIMIT_CPU] = "LimitCPU",
+ [RLIMIT_FSIZE] = "LimitFSIZE",
+ [RLIMIT_DATA] = "LimitDATA",
+ [RLIMIT_STACK] = "LimitSTACK",
+ [RLIMIT_CORE] = "LimitCORE",
+ [RLIMIT_RSS] = "LimitRSS",
+ [RLIMIT_NOFILE] = "LimitNOFILE",
+ [RLIMIT_AS] = "LimitAS",
+ [RLIMIT_NPROC] = "LimitNPROC",
+ [RLIMIT_MEMLOCK] = "LimitMEMLOCK",
+ [RLIMIT_LOCKS] = "LimitLOCKS",
+ [RLIMIT_SIGPENDING] = "LimitSIGPENDING",
+ [RLIMIT_MSGQUEUE] = "LimitMSGQUEUE",
+ [RLIMIT_NICE] = "LimitNICE",
+ [RLIMIT_RTPRIO] = "LimitRTPRIO",
+ [RLIMIT_RTTIME] = "LimitRTTIME"
+};
+
+DEFINE_STRING_TABLE_LOOKUP(rlimit, int);
+
+static const char* const ip_tos_table[] = {
+ [IPTOS_LOWDELAY] = "low-delay",
+ [IPTOS_THROUGHPUT] = "throughput",
+ [IPTOS_RELIABILITY] = "reliability",
+ [IPTOS_LOWCOST] = "low-cost",
+};
+
+DEFINE_STRING_TABLE_LOOKUP_WITH_FALLBACK(ip_tos, int, 0xff);
+
+static const char *const __signal_table[] = {
+ [SIGHUP] = "HUP",
+ [SIGINT] = "INT",
+ [SIGQUIT] = "QUIT",
+ [SIGILL] = "ILL",
+ [SIGTRAP] = "TRAP",
+ [SIGABRT] = "ABRT",
+ [SIGBUS] = "BUS",
+ [SIGFPE] = "FPE",
+ [SIGKILL] = "KILL",
+ [SIGUSR1] = "USR1",
+ [SIGSEGV] = "SEGV",
+ [SIGUSR2] = "USR2",
+ [SIGPIPE] = "PIPE",
+ [SIGALRM] = "ALRM",
+ [SIGTERM] = "TERM",
+#ifdef SIGSTKFLT
+ [SIGSTKFLT] = "STKFLT", /* Linux on SPARC doesn't know SIGSTKFLT */
+#endif
+ [SIGCHLD] = "CHLD",
+ [SIGCONT] = "CONT",
+ [SIGSTOP] = "STOP",
+ [SIGTSTP] = "TSTP",
+ [SIGTTIN] = "TTIN",
+ [SIGTTOU] = "TTOU",
+ [SIGURG] = "URG",
+ [SIGXCPU] = "XCPU",
+ [SIGXFSZ] = "XFSZ",
+ [SIGVTALRM] = "VTALRM",
+ [SIGPROF] = "PROF",
+ [SIGWINCH] = "WINCH",
+ [SIGIO] = "IO",
+ [SIGPWR] = "PWR",
+ [SIGSYS] = "SYS"
+};
+
+DEFINE_PRIVATE_STRING_TABLE_LOOKUP(__signal, int);
+
+const char *signal_to_string(int signo) {
+ static __thread char buf[sizeof("RTMIN+")-1 + DECIMAL_STR_MAX(int) + 1];
+ const char *name;
+
+ name = __signal_to_string(signo);
+ if (name)
+ return name;
+
+ if (signo >= SIGRTMIN && signo <= SIGRTMAX)
+ snprintf(buf, sizeof(buf), "RTMIN+%d", signo - SIGRTMIN);
+ else
+ snprintf(buf, sizeof(buf), "%d", signo);
+
+ return buf;
+}
+
+int signal_from_string(const char *s) {
+ int signo;
+ int offset = 0;
+ unsigned u;
+
+ signo = __signal_from_string(s);
+ if (signo > 0)
+ return signo;
+
+ if (startswith(s, "RTMIN+")) {
+ s += 6;
+ offset = SIGRTMIN;
+ }
+ if (safe_atou(s, &u) >= 0) {
+ signo = (int) u + offset;
+ if (signo > 0 && signo < _NSIG)
+ return signo;
+ }
+ return -1;
+}
+
+bool kexec_loaded(void) {
+ bool loaded = false;
+ char *s;
+
+ if (read_one_line_file("/sys/kernel/kexec_loaded", &s) >= 0) {
+ if (s[0] == '1')
+ loaded = true;
+ free(s);
+ }
+ return loaded;
+}
+
+int strdup_or_null(const char *a, char **b) {
+ char *c;
+
+ assert(b);
+
+ if (!a) {
+ *b = NULL;
+ return 0;
+ }
+
+ c = strdup(a);
+ if (!c)
+ return -ENOMEM;
+
+ *b = c;
+ return 0;
+}
+
+int prot_from_flags(int flags) {
+
+ switch (flags & O_ACCMODE) {
+
+ case O_RDONLY:
+ return PROT_READ;
+
+ case O_WRONLY:
+ return PROT_WRITE;
+
+ case O_RDWR:
+ return PROT_READ|PROT_WRITE;
+
+ default:
+ return -EINVAL;
+ }
+}
+
+char *format_bytes(char *buf, size_t l, off_t t) {
+ unsigned i;
+
+ static const struct {
+ const char *suffix;
+ off_t factor;
+ } table[] = {
+ { "E", 1024ULL*1024ULL*1024ULL*1024ULL*1024ULL*1024ULL },
+ { "P", 1024ULL*1024ULL*1024ULL*1024ULL*1024ULL },
+ { "T", 1024ULL*1024ULL*1024ULL*1024ULL },
+ { "G", 1024ULL*1024ULL*1024ULL },
+ { "M", 1024ULL*1024ULL },
+ { "K", 1024ULL },
+ };
+
+ for (i = 0; i < ELEMENTSOF(table); i++) {
+
+ if (t >= table[i].factor) {
+ snprintf(buf, l,
+ "%llu.%llu%s",
+ (unsigned long long) (t / table[i].factor),
+ (unsigned long long) (((t*10ULL) / table[i].factor) % 10ULL),
+ table[i].suffix);
+
+ goto finish;
+ }
+ }
+
+ snprintf(buf, l, "%lluB", (unsigned long long) t);
+
+finish:
+ buf[l-1] = 0;
+ return buf;
+
+}
+
+void* memdup(const void *p, size_t l) {
+ void *r;
+
+ assert(p);
+
+ r = malloc(l);
+ if (!r)
+ return NULL;
+
+ memcpy(r, p, l);
+ return r;
+}
+
+int fd_inc_sndbuf(int fd, size_t n) {
+ int r, value;
+ socklen_t l = sizeof(value);
+
+ r = getsockopt(fd, SOL_SOCKET, SO_SNDBUF, &value, &l);
+ if (r >= 0 &&
+ l == sizeof(value) &&
+ (size_t) value >= n*2)
+ return 0;
+
+ value = (int) n;
+ r = setsockopt(fd, SOL_SOCKET, SO_SNDBUF, &value, sizeof(value));
+ if (r < 0)
+ return -errno;
+
+ return 1;
+}
+
+int fd_inc_rcvbuf(int fd, size_t n) {
+ int r, value;
+ socklen_t l = sizeof(value);
+
+ r = getsockopt(fd, SOL_SOCKET, SO_RCVBUF, &value, &l);
+ if (r >= 0 &&
+ l == sizeof(value) &&
+ (size_t) value >= n*2)
+ return 0;
+
+ value = (int) n;
+ r = setsockopt(fd, SOL_SOCKET, SO_RCVBUF, &value, sizeof(value));
+ if (r < 0)
+ return -errno;
+
+ return 1;
+}
+
+int fork_agent(pid_t *pid, const int except[], unsigned n_except, const char *path, ...) {
+ pid_t parent_pid, agent_pid;
+ int fd;
+ bool stdout_is_tty, stderr_is_tty;
+ unsigned n, i;
+ va_list ap;
+ char **l;
+
+ assert(pid);
+ assert(path);
+
+ parent_pid = getpid();
+
+ /* Spawns a temporary TTY agent, making sure it goes away when
+ * we go away */
+
+ agent_pid = fork();
+ if (agent_pid < 0)
+ return -errno;
+
+ if (agent_pid != 0) {
+ *pid = agent_pid;
+ return 0;
+ }
+
+ /* In the child:
+ *
+ * Make sure the agent goes away when the parent dies */
+ if (prctl(PR_SET_PDEATHSIG, SIGTERM) < 0)
+ _exit(EXIT_FAILURE);
+
+ /* Check whether our parent died before we were able
+ * to set the death signal */
+ if (getppid() != parent_pid)
+ _exit(EXIT_SUCCESS);
+
+ /* Don't leak fds to the agent */
+ close_all_fds(except, n_except);
+
+ stdout_is_tty = isatty(STDOUT_FILENO);
+ stderr_is_tty = isatty(STDERR_FILENO);
+
+ if (!stdout_is_tty || !stderr_is_tty) {
+ /* Detach from stdout/stderr. and reopen
+ * /dev/tty for them. This is important to
+ * ensure that when systemctl is started via
+ * popen() or a similar call that expects to
+ * read EOF we actually do generate EOF and
+ * not delay this indefinitely by because we
+ * keep an unused copy of stdin around. */
+ fd = open("/dev/tty", O_WRONLY);
+ if (fd < 0) {
+ log_error("Failed to open /dev/tty: %m");
+ _exit(EXIT_FAILURE);
+ }
+
+ if (!stdout_is_tty)
+ dup2(fd, STDOUT_FILENO);
+
+ if (!stderr_is_tty)
+ dup2(fd, STDERR_FILENO);
+
+ if (fd > 2)
+ close(fd);
+ }
+
+ /* Count arguments */
+ va_start(ap, path);
+ for (n = 0; va_arg(ap, char*); n++)
+ ;
+ va_end(ap);
+
+ /* Allocate strv */
+ l = alloca(sizeof(char *) * (n + 1));
+
+ /* Fill in arguments */
+ va_start(ap, path);
+ for (i = 0; i <= n; i++)
+ l[i] = va_arg(ap, char*);
+ va_end(ap);
+
+ execv(path, l);
+ _exit(EXIT_FAILURE);
+}
+
+int setrlimit_closest(int resource, const struct rlimit *rlim) {
+ struct rlimit highest, fixed;
+
+ assert(rlim);
+
+ if (setrlimit(resource, rlim) >= 0)
+ return 0;
+
+ if (errno != EPERM)
+ return -errno;
+
+ /* So we failed to set the desired setrlimit, then let's try
+ * to get as close as we can */
+ assert_se(getrlimit(resource, &highest) == 0);
+
+ fixed.rlim_cur = MIN(rlim->rlim_cur, highest.rlim_max);
+ fixed.rlim_max = MIN(rlim->rlim_max, highest.rlim_max);
+
+ if (setrlimit(resource, &fixed) < 0)
+ return -errno;
+
+ return 0;
+}
+
+int getenv_for_pid(pid_t pid, const char *field, char **_value) {
+ _cleanup_fclose_ FILE *f = NULL;
+ char *value = NULL;
+ int r;
+ bool done = false;
+ size_t l;
+ const char *path;
+
+ assert(pid >= 0);
+ assert(field);
+ assert(_value);
+
+ if (pid == 0)
+ path = "/proc/self/environ";
+ else
+ path = procfs_file_alloca(pid, "environ");
+
+ f = fopen(path, "re");
+ if (!f)
+ return -errno;
+
+ l = strlen(field);
+ r = 0;
+
+ do {
+ char line[LINE_MAX];
+ unsigned i;
+
+ for (i = 0; i < sizeof(line)-1; i++) {
+ int c;
+
+ c = getc(f);
+ if (_unlikely_(c == EOF)) {
+ done = true;
+ break;
+ } else if (c == 0)
+ break;
+
+ line[i] = c;
+ }
+ line[i] = 0;
+
+ if (memcmp(line, field, l) == 0 && line[l] == '=') {
+ value = strdup(line + l + 1);
+ if (!value)
+ return -ENOMEM;
+
+ r = 1;
+ break;
+ }
+
+ } while (!done);
+
+ *_value = value;
+ return r;
+}
+
+bool is_valid_documentation_url(const char *url) {
+ assert(url);
+
+ if (startswith(url, "http://") && url[7])
+ return true;
+
+ if (startswith(url, "https://") && url[8])
+ return true;
+
+ if (startswith(url, "file:") && url[5])
+ return true;
+
+ if (startswith(url, "info:") && url[5])
+ return true;
+
+ if (startswith(url, "man:") && url[4])
+ return true;
+
+ return false;
+}
+
+bool in_initrd(void) {
+ static __thread int saved = -1;
+ struct statfs s;
+
+ if (saved >= 0)
+ return saved;
+
+ /* We make two checks here:
+ *
+ * 1. the flag file /etc/initrd-release must exist
+ * 2. the root file system must be a memory file system
+ *
+ * The second check is extra paranoia, since misdetecting an
+ * initrd can have bad bad consequences due the initrd
+ * emptying when transititioning to the main systemd.
+ */
+
+ saved = access("/etc/initrd-release", F_OK) >= 0 &&
+ statfs("/", &s) >= 0 &&
+ is_temporary_fs(&s);
+
+ return saved;
+}
+
+void warn_melody(void) {
+ _cleanup_close_ int fd = -1;
+
+ fd = open("/dev/console", O_WRONLY|O_CLOEXEC|O_NOCTTY);
+ if (fd < 0)
+ return;
+
+ /* Yeah, this is synchronous. Kinda sucks. But well... */
+
+ ioctl(fd, KIOCSOUND, (int)(1193180/440));
+ usleep(125*USEC_PER_MSEC);
+
+ ioctl(fd, KIOCSOUND, (int)(1193180/220));
+ usleep(125*USEC_PER_MSEC);
+
+ ioctl(fd, KIOCSOUND, (int)(1193180/220));
+ usleep(125*USEC_PER_MSEC);
+
+ ioctl(fd, KIOCSOUND, 0);
+}
+
+int make_console_stdio(void) {
+ int fd, r;
+
+ /* Make /dev/console the controlling terminal and stdin/stdout/stderr */
+
+ fd = acquire_terminal("/dev/console", false, true, true, (usec_t) -1);
+ if (fd < 0) {
+ log_error("Failed to acquire terminal: %s", strerror(-fd));
+ return fd;
+ }
+
+ r = make_stdio(fd);
+ if (r < 0) {
+ log_error("Failed to duplicate terminal fd: %s", strerror(-r));
+ return r;
+ }
+
+ return 0;
+}
+
+int get_home_dir(char **_h) {
+ char *h;
+ const char *e;
+ uid_t u;
+ struct passwd *p;
+
+ assert(_h);
+
+ /* Take the user specified one */
+ e = getenv("HOME");
+ if (e) {
+ h = strdup(e);
+ if (!h)
+ return -ENOMEM;
+
+ *_h = h;
+ return 0;
+ }
+
+ /* Hardcode home directory for root to avoid NSS */
+ u = getuid();
+ if (u == 0) {
+ h = strdup("/root");
+ if (!h)
+ return -ENOMEM;
+
+ *_h = h;
+ return 0;
+ }
+
+ /* Check the database... */
+ errno = 0;
+ p = getpwuid(u);
+ if (!p)
+ return errno > 0 ? -errno : -ESRCH;
+
+ if (!path_is_absolute(p->pw_dir))
+ return -EINVAL;
+
+ h = strdup(p->pw_dir);
+ if (!h)
+ return -ENOMEM;
+
+ *_h = h;
+ return 0;
+}
+
+bool filename_is_safe(const char *p) {
+
+ if (isempty(p))
+ return false;
+
+ if (strchr(p, '/'))
+ return false;
+
+ if (streq(p, "."))
+ return false;
+
+ if (streq(p, ".."))
+ return false;
+
+ if (strlen(p) > FILENAME_MAX)
+ return false;
+
+ return true;
+}
+
+bool string_is_safe(const char *p) {
+ const char *t;
+
+ assert(p);
+
+ for (t = p; *t; t++) {
+ if (*t > 0 && *t < ' ')
+ return false;
+
+ if (strchr("\\\"\'", *t))
+ return false;
+ }
+
+ return true;
+}
+
+bool string_has_cc(const char *p) {
+ const char *t;
+
+ assert(p);
+
+ for (t = p; *t; t++)
+ if (*t > 0 && *t < ' ')
+ return true;
+
+ return false;
+}
+
+bool path_is_safe(const char *p) {
+
+ if (isempty(p))
+ return false;
+
+ if (streq(p, "..") || startswith(p, "../") || endswith(p, "/..") || strstr(p, "/../"))
+ return false;
+
+ if (strlen(p) > PATH_MAX)
+ return false;
+
+ /* The following two checks are not really dangerous, but hey, they still are confusing */
+ if (streq(p, ".") || startswith(p, "./") || endswith(p, "/.") || strstr(p, "/./"))
+ return false;
+
+ if (strstr(p, "//"))
+ return false;
+
+ return true;
+}
+
+/* hey glibc, APIs with callbacks without a user pointer are so useless */
+void *xbsearch_r(const void *key, const void *base, size_t nmemb, size_t size,
+ int (*compar) (const void *, const void *, void *), void *arg) {
+ size_t l, u, idx;
+ const void *p;
+ int comparison;
+
+ l = 0;
+ u = nmemb;
+ while (l < u) {
+ idx = (l + u) / 2;
+ p = (void *)(((const char *) base) + (idx * size));
+ comparison = compar(key, p, arg);
+ if (comparison < 0)
+ u = idx;
+ else if (comparison > 0)
+ l = idx + 1;
+ else
+ return (void *)p;
+ }
+ return NULL;
+}
+
+bool is_locale_utf8(void) {
+ const char *set;
+ static int cached_answer = -1;
+
+ if (cached_answer >= 0)
+ goto out;
+
+ if (!setlocale(LC_ALL, "")) {
+ cached_answer = true;
+ goto out;
+ }
+
+ set = nl_langinfo(CODESET);
+ if (!set) {
+ cached_answer = true;
+ goto out;
+ }
+
+ if(streq(set, "UTF-8")) {
+ cached_answer = true;
+ goto out;
+ }
+
+ /* For LC_CTYPE=="C" return true,
+ * because CTYPE is effectly unset and
+ * everything defaults to UTF-8 nowadays. */
+
+ set = setlocale(LC_CTYPE, NULL);
+ if (!set) {
+ cached_answer = true;
+ goto out;
+ }
+
+ cached_answer = streq(set, "C");
+
+out:
+ return (bool)cached_answer;
+}
+
+const char *draw_special_char(DrawSpecialChar ch) {
+ static const char *draw_table[2][_DRAW_SPECIAL_CHAR_MAX] = {
+ /* UTF-8 */ {
+ [DRAW_TREE_VERT] = "\342\224\202 ", /* │ */
+ [DRAW_TREE_BRANCH] = "\342\224\234\342\224\200", /* ├─ */
+ [DRAW_TREE_RIGHT] = "\342\224\224\342\224\200", /* └─ */
+ [DRAW_TREE_SPACE] = " ", /* */
+ [DRAW_TRIANGULAR_BULLET] = "\342\200\243 ", /* ‣ */
+ },
+ /* ASCII fallback */ {
+ [DRAW_TREE_VERT] = "| ",
+ [DRAW_TREE_BRANCH] = "|-",
+ [DRAW_TREE_RIGHT] = "`-",
+ [DRAW_TREE_SPACE] = " ",
+ [DRAW_TRIANGULAR_BULLET] = "> ",
+ }
+ };
+
+ return draw_table[!is_locale_utf8()][ch];
+}
+
+char *strreplace(const char *text, const char *old_string, const char *new_string) {
+ const char *f;
+ char *t, *r;
+ size_t l, old_len, new_len;
+
+ assert(text);
+ assert(old_string);
+ assert(new_string);
+
+ old_len = strlen(old_string);
+ new_len = strlen(new_string);
+
+ l = strlen(text);
+ r = new(char, l+1);
+ if (!r)
+ return NULL;
+
+ f = text;
+ t = r;
+ while (*f) {
+ char *a;
+ size_t d, nl;
+
+ if (!startswith(f, old_string)) {
+ *(t++) = *(f++);
+ continue;
+ }
+
+ d = t - r;
+ nl = l - old_len + new_len;
+ a = realloc(r, nl + 1);
+ if (!a)
+ goto oom;
+
+ l = nl;
+ r = a;
+ t = r + d;
+
+ t = stpcpy(t, new_string);
+ f += old_len;
+ }
+
+ *t = 0;
+ return r;
+
+oom:
+ free(r);
+ return NULL;
+}
+
+char *strip_tab_ansi(char **ibuf, size_t *_isz) {
+ const char *i, *begin = NULL;
+ enum {
+ STATE_OTHER,
+ STATE_ESCAPE,
+ STATE_BRACKET
+ } state = STATE_OTHER;
+ char *obuf = NULL;
+ size_t osz = 0, isz;
+ FILE *f;
+
+ assert(ibuf);
+ assert(*ibuf);
+
+ /* Strips ANSI color and replaces TABs by 8 spaces */
+
+ isz = _isz ? *_isz : strlen(*ibuf);
+
+ f = open_memstream(&obuf, &osz);
+ if (!f)
+ return NULL;
+
+ for (i = *ibuf; i < *ibuf + isz + 1; i++) {
+
+ switch (state) {
+
+ case STATE_OTHER:
+ if (i >= *ibuf + isz) /* EOT */
+ break;
+ else if (*i == '\x1B')
+ state = STATE_ESCAPE;
+ else if (*i == '\t')
+ fputs(" ", f);
+ else
+ fputc(*i, f);
+ break;
+
+ case STATE_ESCAPE:
+ if (i >= *ibuf + isz) { /* EOT */
+ fputc('\x1B', f);
+ break;
+ } else if (*i == '[') {
+ state = STATE_BRACKET;
+ begin = i + 1;
+ } else {
+ fputc('\x1B', f);
+ fputc(*i, f);
+ state = STATE_OTHER;
+ }
+
+ break;
+
+ case STATE_BRACKET:
+
+ if (i >= *ibuf + isz || /* EOT */
+ (!(*i >= '0' && *i <= '9') && *i != ';' && *i != 'm')) {
+ fputc('\x1B', f);
+ fputc('[', f);
+ state = STATE_OTHER;
+ i = begin-1;
+ } else if (*i == 'm')
+ state = STATE_OTHER;
+ break;
+ }
+ }
+
+ if (ferror(f)) {
+ fclose(f);
+ free(obuf);
+ return NULL;
+ }
+
+ fclose(f);
+
+ free(*ibuf);
+ *ibuf = obuf;
+
+ if (_isz)
+ *_isz = osz;
+
+ return obuf;
+}
+
+int on_ac_power(void) {
+ bool found_offline = false, found_online = false;
+ _cleanup_closedir_ DIR *d = NULL;
+
+ d = opendir("/sys/class/power_supply");
+ if (!d)
+ return -errno;
+
+ for (;;) {
+ struct dirent *de;
+ union dirent_storage buf;
+ _cleanup_close_ int fd = -1, device = -1;
+ char contents[6];
+ ssize_t n;
+ int k;
+
+ k = readdir_r(d, &buf.de, &de);
+ if (k != 0)
+ return -k;
+
+ if (!de)
+ break;
+
+ if (ignore_file(de->d_name))
+ continue;
+
+ device = openat(dirfd(d), de->d_name, O_DIRECTORY|O_RDONLY|O_CLOEXEC|O_NOCTTY);
+ if (device < 0) {
+ if (errno == ENOENT || errno == ENOTDIR)
+ continue;
+
+ return -errno;
+ }
+
+ fd = openat(device, "type", O_RDONLY|O_CLOEXEC|O_NOCTTY);
+ if (fd < 0) {
+ if (errno == ENOENT)
+ continue;
+
+ return -errno;
+ }
+
+ n = read(fd, contents, sizeof(contents));
+ if (n < 0)
+ return -errno;
+
+ if (n != 6 || memcmp(contents, "Mains\n", 6))
+ continue;
+
+ close_nointr_nofail(fd);
+ fd = openat(device, "online", O_RDONLY|O_CLOEXEC|O_NOCTTY);
+ if (fd < 0) {
+ if (errno == ENOENT)
+ continue;
+
+ return -errno;
+ }
+
+ n = read(fd, contents, sizeof(contents));
+ if (n < 0)
+ return -errno;
+
+ if (n != 2 || contents[1] != '\n')
+ return -EIO;
+
+ if (contents[0] == '1') {
+ found_online = true;
+ break;
+ } else if (contents[0] == '0')
+ found_offline = true;
+ else
+ return -EIO;
+ }
+
+ return found_online || !found_offline;
+}
+
+static int search_and_fopen_internal(const char *path, const char *mode, char **search, FILE **_f) {
+ char **i;
+
+ assert(path);
+ assert(mode);
+ assert(_f);
+
+ if (!path_strv_canonicalize_uniq(search))
+ return -ENOMEM;
+
+ STRV_FOREACH(i, search) {
+ _cleanup_free_ char *p = NULL;
+ FILE *f;
+
+ p = strjoin(*i, "/", path, NULL);
+ if (!p)
+ return -ENOMEM;
+
+ f = fopen(p, mode);
+ if (f) {
+ *_f = f;
+ return 0;
+ }
+
+ if (errno != ENOENT)
+ return -errno;
+ }
+
+ return -ENOENT;
+}
+
+int search_and_fopen(const char *path, const char *mode, const char **search, FILE **_f) {
+ _cleanup_strv_free_ char **copy = NULL;
+
+ assert(path);
+ assert(mode);
+ assert(_f);
+
+ if (path_is_absolute(path)) {
+ FILE *f;
+
+ f = fopen(path, mode);
+ if (f) {
+ *_f = f;
+ return 0;
+ }
+
+ return -errno;
+ }
+
+ copy = strv_copy((char**) search);
+ if (!copy)
+ return -ENOMEM;
+
+ return search_and_fopen_internal(path, mode, copy, _f);
+}
+
+int search_and_fopen_nulstr(const char *path, const char *mode, const char *search, FILE **_f) {
+ _cleanup_strv_free_ char **s = NULL;
+
+ if (path_is_absolute(path)) {
+ FILE *f;
+
+ f = fopen(path, mode);
+ if (f) {
+ *_f = f;
+ return 0;
+ }
+
+ return -errno;
+ }
+
+ s = strv_split_nulstr(search);
+ if (!s)
+ return -ENOMEM;
+
+ return search_and_fopen_internal(path, mode, s, _f);
+}
+
+int create_tmp_dir(char template[], char** dir_name) {
+ int r = 0;
+ char *d, *dt;
+
+ assert(dir_name);
+
+ RUN_WITH_UMASK(0077) {
+ d = mkdtemp(template);
+ }
+ if (!d) {
+ log_error("Can't create directory %s: %m", template);
+ return -errno;
+ }
+
+ dt = strjoin(d, "/tmp", NULL);
+ if (!dt) {
+ r = log_oom();
+ goto fail3;
+ }
+
+ RUN_WITH_UMASK(0000) {
+ r = mkdir(dt, 0777);
+ }
+ if (r < 0) {
+ log_error("Can't create directory %s: %m", dt);
+ r = -errno;
+ goto fail2;
+ }
+ log_debug("Created temporary directory %s", dt);
+
+ r = chmod(dt, 0777 | S_ISVTX);
+ if (r < 0) {
+ log_error("Failed to chmod %s: %m", dt);
+ r = -errno;
+ goto fail1;
+ }
+ log_debug("Set sticky bit on %s", dt);
+
+ *dir_name = dt;
+
+ return 0;
+fail1:
+ rmdir(dt);
+fail2:
+ free(dt);
+fail3:
+ rmdir(template);
+ return r;
+}
+
+char *strextend(char **x, ...) {
+ va_list ap;
+ size_t f, l;
+ char *r, *p;
+
+ assert(x);
+
+ l = f = *x ? strlen(*x) : 0;
+
+ va_start(ap, x);
+ for (;;) {
+ const char *t;
+ size_t n;
+
+ t = va_arg(ap, const char *);
+ if (!t)
+ break;
+
+ n = strlen(t);
+ if (n > ((size_t) -1) - l) {
+ va_end(ap);
+ return NULL;
+ }
+
+ l += n;
+ }
+ va_end(ap);
+
+ r = realloc(*x, l+1);
+ if (!r)
+ return NULL;
+
+ p = r + f;
+
+ va_start(ap, x);
+ for (;;) {
+ const char *t;
+
+ t = va_arg(ap, const char *);
+ if (!t)
+ break;
+
+ p = stpcpy(p, t);
+ }
+ va_end(ap);
+
+ *p = 0;
+ *x = r;
+
+ return r + l;
+}
+
+char *strrep(const char *s, unsigned n) {
+ size_t l;
+ char *r, *p;
+ unsigned i;
+
+ assert(s);
+
+ l = strlen(s);
+ p = r = malloc(l * n + 1);
+ if (!r)
+ return NULL;
+
+ for (i = 0; i < n; i++)
+ p = stpcpy(p, s);
+
+ *p = 0;
+ return r;
+}
+
+void* greedy_realloc(void **p, size_t *allocated, size_t need) {
+ size_t a;
+ void *q;
+
+ if (*allocated >= need)
+ return *p;
+
+ a = MAX(64u, need * 2);
+ q = realloc(*p, a);
+ if (!q)
+ return NULL;
+
+ *p = q;
+ *allocated = a;
+ return q;
+}
+
+bool id128_is_valid(const char *s) {
+ size_t i, l;
+
+ l = strlen(s);
+ if (l == 32) {
+
+ /* Simple formatted 128bit hex string */
+
+ for (i = 0; i < l; i++) {
+ char c = s[i];
+
+ if (!(c >= '0' && c <= '9') &&
+ !(c >= 'a' && c <= 'z') &&
+ !(c >= 'A' && c <= 'Z'))
+ return false;
+ }
+
+ } else if (l == 36) {
+
+ /* Formatted UUID */
+
+ for (i = 0; i < l; i++) {
+ char c = s[i];
+
+ if ((i == 8 || i == 13 || i == 18 || i == 23)) {
+ if (c != '-')
+ return false;
+ } else {
+ if (!(c >= '0' && c <= '9') &&
+ !(c >= 'a' && c <= 'z') &&
+ !(c >= 'A' && c <= 'Z'))
+ return false;
+ }
+ }
+
+ } else
+ return false;
+
+ return true;
+}
diff --git a/src/shared/util.h b/src/shared/util.h
new file mode 100644
index 0000000000..64e63b8c07
--- /dev/null
+++ b/src/shared/util.h
@@ -0,0 +1,734 @@
+/*-*- Mode: C; c-basic-offset: 8; indent-tabs-mode: nil -*-*/
+
+#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 <alloca.h>
+#include <inttypes.h>
+#include <time.h>
+#include <sys/time.h>
+#include <stdarg.h>
+#include <stdbool.h>
+#include <stdlib.h>
+#include <stdio.h>
+#include <signal.h>
+#include <sched.h>
+#include <limits.h>
+#include <sys/types.h>
+#include <sys/stat.h>
+#include <dirent.h>
+#include <sys/resource.h>
+#include <stddef.h>
+#include <unistd.h>
+#include <locale.h>
+
+#include "macro.h"
+#include "time-util.h"
+
+union dirent_storage {
+ struct dirent de;
+ uint8_t storage[offsetof(struct dirent, d_name) +
+ ((NAME_MAX + 1 + sizeof(long)) & ~(sizeof(long) - 1))];
+};
+
+/* What is interpreted as whitespace? */
+#define WHITESPACE " \t\n\r"
+#define NEWLINE "\n\r"
+#define QUOTES "\"\'"
+#define COMMENTS "#;"
+
+#define FORMAT_BYTES_MAX 8
+
+#define ANSI_HIGHLIGHT_ON "\x1B[1;39m"
+#define ANSI_RED_ON "\x1B[31m"
+#define ANSI_HIGHLIGHT_RED_ON "\x1B[1;31m"
+#define ANSI_GREEN_ON "\x1B[32m"
+#define ANSI_HIGHLIGHT_GREEN_ON "\x1B[1;32m"
+#define ANSI_HIGHLIGHT_YELLOW_ON "\x1B[1;33m"
+#define ANSI_HIGHLIGHT_OFF "\x1B[0m"
+#define ANSI_ERASE_TO_END_OF_LINE "\x1B[K"
+
+size_t page_size(void);
+#define PAGE_ALIGN(l) ALIGN_TO((l), page_size())
+
+#define streq(a,b) (strcmp((a),(b)) == 0)
+#define strneq(a, b, n) (strncmp((a), (b), (n)) == 0)
+#define strcaseeq(a,b) (strcasecmp((a),(b)) == 0)
+#define strncaseeq(a, b, n) (strncasecmp((a), (b), (n)) == 0)
+
+bool streq_ptr(const char *a, const char *b) _pure_;
+
+#define new(t, n) ((t*) malloc_multiply(sizeof(t), (n)))
+
+#define new0(t, n) ((t*) calloc((n), sizeof(t)))
+
+#define newa(t, n) ((t*) alloca(sizeof(t)*(n)))
+
+#define newdup(t, p, n) ((t*) memdup_multiply(p, sizeof(t), (n)))
+
+#define malloc0(n) (calloc((n), 1))
+
+static inline const char* yes_no(bool b) {
+ return b ? "yes" : "no";
+}
+
+static inline const char* strempty(const char *s) {
+ return s ? s : "";
+}
+
+static inline const char* strnull(const char *s) {
+ return s ? s : "(null)";
+}
+
+static inline const char *strna(const char *s) {
+ return s ? s : "n/a";
+}
+
+static inline bool isempty(const char *p) {
+ return !p || !p[0];
+}
+
+char *endswith(const char *s, const char *postfix) _pure_;
+char *startswith(const char *s, const char *prefix) _pure_;
+char *startswith_no_case(const char *s, const char *prefix) _pure_;
+
+bool first_word(const char *s, const char *word) _pure_;
+
+int close_nointr(int fd);
+void close_nointr_nofail(int fd);
+void close_many(const int fds[], unsigned n_fd);
+
+int parse_boolean(const char *v) _pure_;
+int parse_bytes(const char *t, off_t *bytes);
+int parse_pid(const char *s, pid_t* ret_pid);
+int parse_uid(const char *s, uid_t* ret_uid);
+#define parse_gid(s, ret_uid) parse_uid(s, ret_uid)
+
+int safe_atou(const char *s, unsigned *ret_u);
+int safe_atoi(const char *s, int *ret_i);
+
+int safe_atollu(const char *s, unsigned long long *ret_u);
+int safe_atolli(const char *s, long long int *ret_i);
+
+int safe_atod(const char *s, double *ret_d);
+
+#if __WORDSIZE == 32
+static inline int safe_atolu(const char *s, unsigned long *ret_u) {
+ assert_cc(sizeof(unsigned long) == sizeof(unsigned));
+ return safe_atou(s, (unsigned*) ret_u);
+}
+static inline int safe_atoli(const char *s, long int *ret_u) {
+ assert_cc(sizeof(long int) == sizeof(int));
+ return safe_atoi(s, (int*) ret_u);
+}
+#else
+static inline int safe_atolu(const char *s, unsigned long *ret_u) {
+ assert_cc(sizeof(unsigned long) == sizeof(unsigned long long));
+ return safe_atollu(s, (unsigned long long*) ret_u);
+}
+static inline int safe_atoli(const char *s, long int *ret_u) {
+ assert_cc(sizeof(long int) == sizeof(long long int));
+ return safe_atolli(s, (long long int*) ret_u);
+}
+#endif
+
+static inline int safe_atou32(const char *s, uint32_t *ret_u) {
+ assert_cc(sizeof(uint32_t) == sizeof(unsigned));
+ return safe_atou(s, (unsigned*) ret_u);
+}
+
+static inline int safe_atoi32(const char *s, int32_t *ret_i) {
+ assert_cc(sizeof(int32_t) == sizeof(int));
+ return safe_atoi(s, (int*) ret_i);
+}
+
+static inline int safe_atou64(const char *s, uint64_t *ret_u) {
+ assert_cc(sizeof(uint64_t) == sizeof(unsigned long long));
+ return safe_atollu(s, (unsigned long long*) ret_u);
+}
+
+static inline int safe_atoi64(const char *s, int64_t *ret_i) {
+ assert_cc(sizeof(int64_t) == sizeof(long long int));
+ return safe_atolli(s, (long long int*) ret_i);
+}
+
+char *split(const char *c, size_t *l, const char *separator, char **state);
+char *split_quoted(const char *c, size_t *l, char **state);
+
+#define FOREACH_WORD(word, length, s, state) \
+ for ((state) = NULL, (word) = split((s), &(length), WHITESPACE, &(state)); (word); (word) = split((s), &(length), WHITESPACE, &(state)))
+
+#define FOREACH_WORD_SEPARATOR(word, length, s, separator, state) \
+ for ((state) = NULL, (word) = split((s), &(length), (separator), &(state)); (word); (word) = split((s), &(length), (separator), &(state)))
+
+#define FOREACH_WORD_QUOTED(word, length, s, state) \
+ for ((state) = NULL, (word) = split_quoted((s), &(length), &(state)); (word); (word) = split_quoted((s), &(length), &(state)))
+
+pid_t get_parent_of_pid(pid_t pid, pid_t *ppid);
+int get_starttime_of_pid(pid_t pid, unsigned long long *st);
+
+char *strappend(const char *s, const char *suffix);
+char *strnappend(const char *s, const char *suffix, size_t length);
+
+char *replace_env(const char *format, char **env);
+char **replace_env_argv(char **argv, char **env);
+
+int readlink_malloc(const char *p, char **r);
+int readlink_and_make_absolute(const char *p, char **r);
+int readlink_and_canonicalize(const char *p, char **r);
+
+int reset_all_signal_handlers(void);
+
+char *strstrip(char *s);
+char *delete_chars(char *s, const char *bad);
+char *truncate_nl(char *s);
+
+char *file_in_same_dir(const char *path, const char *filename);
+
+int rmdir_parents(const char *path, const char *stop);
+
+int get_process_comm(pid_t pid, char **name);
+int get_process_cmdline(pid_t pid, size_t max_length, bool comm_fallback, char **line);
+int get_process_exe(pid_t pid, char **name);
+int get_process_uid(pid_t pid, uid_t *uid);
+int get_process_gid(pid_t pid, gid_t *gid);
+
+char hexchar(int x) _const_;
+int unhexchar(char c) _const_;
+char octchar(int x) _const_;
+int unoctchar(char c) _const_;
+char decchar(int x) _const_;
+int undecchar(char c) _const_;
+
+char *cescape(const char *s);
+char *cunescape(const char *s);
+char *cunescape_length(const char *s, size_t length);
+char *cunescape_length_with_prefix(const char *s, size_t length, const char *prefix);
+
+char *xescape(const char *s, const char *bad);
+
+char *bus_path_escape(const char *s);
+char *bus_path_unescape(const char *s);
+
+char *ascii_strlower(char *path);
+
+bool dirent_is_file(const struct dirent *de) _pure_;
+bool dirent_is_file_with_suffix(const struct dirent *de, const char *suffix) _pure_;
+
+bool ignore_file(const char *filename) _pure_;
+
+bool chars_intersect(const char *a, const char *b) _pure_;
+
+int make_stdio(int fd);
+int make_null_stdio(void);
+int make_console_stdio(void);
+
+unsigned long long random_ull(void);
+
+/* For basic lookup tables with strictly enumerated entries */
+#define __DEFINE_STRING_TABLE_LOOKUP(name,type,scope) \
+ scope const char *name##_to_string(type i) { \
+ if (i < 0 || i >= (type) ELEMENTSOF(name##_table)) \
+ return NULL; \
+ return name##_table[i]; \
+ } \
+ scope type name##_from_string(const char *s) { \
+ type i; \
+ assert(s); \
+ for (i = 0; i < (type)ELEMENTSOF(name##_table); i++) \
+ if (name##_table[i] && \
+ streq(name##_table[i], s)) \
+ return i; \
+ return (type) -1; \
+ } \
+ struct __useless_struct_to_allow_trailing_semicolon__
+
+#define DEFINE_STRING_TABLE_LOOKUP(name,type) __DEFINE_STRING_TABLE_LOOKUP(name,type,)
+#define DEFINE_PRIVATE_STRING_TABLE_LOOKUP(name,type) __DEFINE_STRING_TABLE_LOOKUP(name,type,static)
+
+/* For string conversions where numbers are also acceptable */
+#define DEFINE_STRING_TABLE_LOOKUP_WITH_FALLBACK(name,type,max) \
+ int name##_to_string_alloc(type i, char **str) { \
+ char *s; \
+ int r; \
+ if (i < 0 || i > max) \
+ return -ERANGE; \
+ if (i < (type) ELEMENTSOF(name##_table)) { \
+ s = strdup(name##_table[i]); \
+ if (!s) \
+ return log_oom(); \
+ } else { \
+ r = asprintf(&s, "%u", i); \
+ if (r < 0) \
+ return log_oom(); \
+ } \
+ *str = s; \
+ return 0; \
+ } \
+ type name##_from_string(const char *s) { \
+ type i; \
+ unsigned u = 0; \
+ assert(s); \
+ for (i = 0; i < (type)ELEMENTSOF(name##_table); i++) \
+ if (name##_table[i] && \
+ streq(name##_table[i], s)) \
+ return i; \
+ if (safe_atou(s, &u) >= 0 && u <= max) \
+ return (type) u; \
+ return (type) -1; \
+ } \
+ struct __useless_struct_to_allow_trailing_semicolon__
+
+int fd_nonblock(int fd, bool nonblock);
+int fd_cloexec(int fd, bool cloexec);
+
+int close_all_fds(const int except[], unsigned n_except);
+
+bool fstype_is_network(const char *fstype);
+
+int chvt(int vt);
+
+int read_one_char(FILE *f, char *ret, usec_t timeout, bool *need_nl);
+int ask(char *ret, const char *replies, const char *text, ...) _printf_attr_(3, 4);
+
+int reset_terminal_fd(int fd, bool switch_to_text);
+int reset_terminal(const char *name);
+
+int open_terminal(const char *name, int mode);
+int acquire_terminal(const char *name, bool fail, bool force, bool ignore_tiocstty_eperm, usec_t timeout);
+int release_terminal(void);
+
+int flush_fd(int fd);
+
+int ignore_signals(int sig, ...);
+int default_signals(int sig, ...);
+int sigaction_many(const struct sigaction *sa, ...);
+
+int close_pipe(int p[]);
+int fopen_temporary(const char *path, FILE **_f, char **_temp_path);
+
+ssize_t loop_read(int fd, void *buf, size_t nbytes, bool do_poll);
+ssize_t loop_write(int fd, const void *buf, size_t nbytes, bool do_poll);
+
+bool is_device_path(const char *path);
+
+int dir_is_empty(const char *path);
+char* dirname_malloc(const char *path);
+
+void rename_process(const char name[8]);
+
+void sigset_add_many(sigset_t *ss, ...);
+
+bool hostname_is_set(void);
+
+char* gethostname_malloc(void);
+char* getlogname_malloc(void);
+char* getusername_malloc(void);
+
+int getttyname_malloc(int fd, char **r);
+int getttyname_harder(int fd, char **r);
+
+int get_ctty_devnr(pid_t pid, dev_t *d);
+int get_ctty(pid_t, dev_t *_devnr, char **r);
+
+int chmod_and_chown(const char *path, mode_t mode, uid_t uid, gid_t gid);
+int fchmod_and_fchown(int fd, mode_t mode, uid_t uid, gid_t gid);
+
+int rm_rf_children(int fd, bool only_dirs, bool honour_sticky, struct stat *root_dev);
+int rm_rf_children_dangerous(int fd, bool only_dirs, bool honour_sticky, struct stat *root_dev);
+int rm_rf(const char *path, bool only_dirs, bool delete_root, bool honour_sticky);
+int rm_rf_dangerous(const char *path, bool only_dirs, bool delete_root, bool honour_sticky);
+
+int pipe_eof(int fd);
+
+cpu_set_t* cpu_set_malloc(unsigned *ncpus);
+
+int status_vprintf(const char *status, bool ellipse, bool ephemeral, const char *format, va_list ap) _printf_attr_(4,0);
+int status_printf(const char *status, bool ellipse, bool ephemeral, const char *format, ...) _printf_attr_(4,5);
+int status_welcome(void);
+
+int fd_columns(int fd);
+unsigned columns(void);
+int fd_lines(int fd);
+unsigned lines(void);
+void columns_lines_cache_reset(int _unused_ signum);
+
+bool on_tty(void);
+
+int running_in_chroot(void);
+
+char *ellipsize(const char *s, size_t length, unsigned percent);
+char *ellipsize_mem(const char *s, size_t old_length, size_t new_length, unsigned percent);
+
+int touch(const char *path);
+
+char *unquote(const char *s, const char *quotes);
+char *normalize_env_assignment(const char *s);
+
+int wait_for_terminate(pid_t pid, siginfo_t *status);
+int wait_for_terminate_and_warn(const char *name, pid_t pid);
+
+_noreturn_ void freeze(void);
+
+bool null_or_empty(struct stat *st) _pure_;
+int null_or_empty_path(const char *fn);
+
+DIR *xopendirat(int dirfd, const char *name, int flags);
+
+char *fstab_node_to_udev_node(const char *p);
+
+char *resolve_dev_console(char **active);
+bool tty_is_vc(const char *tty);
+bool tty_is_vc_resolve(const char *tty);
+bool tty_is_console(const char *tty) _pure_;
+int vtnr_from_tty(const char *tty);
+const char *default_term_for_tty(const char *tty);
+
+void execute_directory(const char *directory, DIR *_d, char *argv[]);
+
+int kill_and_sigcont(pid_t pid, int sig);
+
+bool nulstr_contains(const char*nulstr, const char *needle);
+
+bool plymouth_running(void);
+
+bool hostname_is_valid(const char *s) _pure_;
+char* hostname_cleanup(char *s, bool lowercase);
+
+char* strshorten(char *s, size_t l);
+
+int terminal_vhangup_fd(int fd);
+int terminal_vhangup(const char *name);
+
+int vt_disallocate(const char *name);
+
+int copy_file(const char *from, const char *to);
+
+int symlink_atomic(const char *from, const char *to);
+
+int fchmod_umask(int fd, mode_t mode);
+
+bool display_is_local(const char *display) _pure_;
+int socket_from_display(const char *display, char **path);
+
+int get_user_creds(const char **username, uid_t *uid, gid_t *gid, const char **home, const char **shell);
+int get_group_creds(const char **groupname, gid_t *gid);
+
+int in_gid(gid_t gid);
+int in_group(const char *name);
+
+char* uid_to_name(uid_t uid);
+char* gid_to_name(gid_t gid);
+
+int glob_exists(const char *path);
+
+int dirent_ensure_type(DIR *d, struct dirent *de);
+
+int in_search_path(const char *path, char **search);
+int get_files_in_directory(const char *path, char ***list);
+
+char *strjoin(const char *x, ...) _sentinel_;
+
+bool is_main_thread(void);
+
+bool in_charset(const char *s, const char* charset) _pure_;
+
+int block_get_whole_disk(dev_t d, dev_t *ret);
+
+int file_is_priv_sticky(const char *p);
+
+int strdup_or_null(const char *a, char **b);
+
+#define NULSTR_FOREACH(i, l) \
+ for ((i) = (l); (i) && *(i); (i) = strchr((i), 0)+1)
+
+#define NULSTR_FOREACH_PAIR(i, j, l) \
+ for ((i) = (l), (j) = strchr((i), 0)+1; (i) && *(i); (i) = strchr((j), 0)+1, (j) = *(i) ? strchr((i), 0)+1 : (i))
+
+int ioprio_class_to_string_alloc(int i, char **s);
+int ioprio_class_from_string(const char *s);
+
+const char *sigchld_code_to_string(int i) _const_;
+int sigchld_code_from_string(const char *s) _pure_;
+
+int log_facility_unshifted_to_string_alloc(int i, char **s);
+int log_facility_unshifted_from_string(const char *s);
+
+int log_level_to_string_alloc(int i, char **s);
+int log_level_from_string(const char *s);
+
+int sched_policy_to_string_alloc(int i, char **s);
+int sched_policy_from_string(const char *s);
+
+const char *rlimit_to_string(int i) _const_;
+int rlimit_from_string(const char *s) _pure_;
+
+int ip_tos_to_string_alloc(int i, char **s);
+int ip_tos_from_string(const char *s);
+
+const char *signal_to_string(int i) _const_;
+int signal_from_string(const char *s) _pure_;
+
+int signal_from_string_try_harder(const char *s);
+
+extern int saved_argc;
+extern char **saved_argv;
+
+bool kexec_loaded(void);
+
+int prot_from_flags(int flags) _const_;
+
+char *format_bytes(char *buf, size_t l, off_t t);
+
+int fd_wait_for_event(int fd, int event, usec_t timeout);
+
+void* memdup(const void *p, size_t l) _alloc_(2);
+
+int is_kernel_thread(pid_t pid);
+
+int fd_inc_sndbuf(int fd, size_t n);
+int fd_inc_rcvbuf(int fd, size_t n);
+
+int fork_agent(pid_t *pid, const int except[], unsigned n_except, const char *path, ...);
+
+int setrlimit_closest(int resource, const struct rlimit *rlim);
+
+int getenv_for_pid(pid_t pid, const char *field, char **_value);
+
+bool is_valid_documentation_url(const char *url) _pure_;
+
+bool in_initrd(void);
+
+void warn_melody(void);
+
+int get_home_dir(char **ret);
+
+static inline void freep(void *p) {
+ free(*(void**) p);
+}
+
+static inline void fclosep(FILE **f) {
+ if (*f)
+ fclose(*f);
+}
+
+static inline void pclosep(FILE **f) {
+ if (*f)
+ pclose(*f);
+}
+
+static inline void closep(int *fd) {
+ if (*fd >= 0)
+ close_nointr_nofail(*fd);
+}
+
+static inline void closedirp(DIR **d) {
+ if (*d)
+ closedir(*d);
+}
+
+static inline void umaskp(mode_t *u) {
+ umask(*u);
+}
+
+#define _cleanup_free_ _cleanup_(freep)
+#define _cleanup_fclose_ _cleanup_(fclosep)
+#define _cleanup_pclose_ _cleanup_(pclosep)
+#define _cleanup_close_ _cleanup_(closep)
+#define _cleanup_closedir_ _cleanup_(closedirp)
+#define _cleanup_umask_ _cleanup_(umaskp)
+#define _cleanup_globfree_ _cleanup_(globfree)
+
+_malloc_ _alloc_(1, 2) static inline void *malloc_multiply(size_t a, size_t b) {
+ if (_unlikely_(b == 0 || a > ((size_t) -1) / b))
+ return NULL;
+
+ return malloc(a * b);
+}
+
+_alloc_(2, 3) static inline void *memdup_multiply(const void *p, size_t a, size_t b) {
+ if (_unlikely_(b == 0 || a > ((size_t) -1) / b))
+ return NULL;
+
+ return memdup(p, a * b);
+}
+
+bool filename_is_safe(const char *p) _pure_;
+bool path_is_safe(const char *p) _pure_;
+bool string_is_safe(const char *p) _pure_;
+bool string_has_cc(const char *p) _pure_;
+
+void *xbsearch_r(const void *key, const void *base, size_t nmemb, size_t size,
+ int (*compar) (const void *, const void *, void *),
+ void *arg);
+
+bool is_locale_utf8(void);
+
+typedef enum DrawSpecialChar {
+ DRAW_TREE_VERT,
+ DRAW_TREE_BRANCH,
+ DRAW_TREE_RIGHT,
+ DRAW_TREE_SPACE,
+ DRAW_TRIANGULAR_BULLET,
+ _DRAW_SPECIAL_CHAR_MAX
+} DrawSpecialChar;
+const char *draw_special_char(DrawSpecialChar ch);
+
+char *strreplace(const char *text, const char *old_string, const char *new_string);
+
+char *strip_tab_ansi(char **p, size_t *l);
+
+int on_ac_power(void);
+
+int search_and_fopen(const char *path, const char *mode, const char **search, FILE **_f);
+int search_and_fopen_nulstr(const char *path, const char *mode, const char *search, FILE **_f);
+int create_tmp_dir(char template[], char** dir_name);
+
+#define FOREACH_LINE(line, f, on_error) \
+ for (;;) \
+ if (!fgets(line, sizeof(line), f)) { \
+ if (ferror(f)) { \
+ on_error; \
+ } \
+ break; \
+ } else
+
+#define FOREACH_DIRENT(de, d, on_error) \
+ for (errno = 0, de = readdir(d);; errno = 0, de = readdir(d)) \
+ if (!de) { \
+ if (errno > 0) { \
+ on_error; \
+ } \
+ break; \
+ } else if (ignore_file((de)->d_name)) \
+ continue; \
+ else
+
+static inline void *mempset(void *s, int c, size_t n) {
+ memset(s, c, n);
+ return (uint8_t*)s + n;
+}
+
+char *hexmem(const void *p, size_t l);
+void *unhexmem(const char *p, size_t l);
+
+char *strextend(char **x, ...) _sentinel_;
+char *strrep(const char *s, unsigned n);
+
+void* greedy_realloc(void **p, size_t *allocated, size_t need);
+#define GREEDY_REALLOC(array, allocated, need) \
+ greedy_realloc((void**) &(array), &(allocated), sizeof((array)[0]) * (need))
+
+static inline void _reset_errno_(int *saved_errno) {
+ errno = *saved_errno;
+}
+
+#define PROTECT_ERRNO _cleanup_(_reset_errno_) __attribute__((unused)) int _saved_errno_ = errno
+
+struct _umask_struct_ {
+ mode_t mask;
+ bool quit;
+};
+
+static inline void _reset_umask_(struct _umask_struct_ *s) {
+ umask(s->mask);
+};
+
+#define RUN_WITH_UMASK(mask) \
+ for (_cleanup_(_reset_umask_) struct _umask_struct_ _saved_umask_ = { umask(mask), false }; \
+ !_saved_umask_.quit ; \
+ _saved_umask_.quit = true)
+
+static inline unsigned u64log2(uint64_t n) {
+ return (n > 1) ? __builtin_clzll(n) ^ 63U : 0;
+}
+
+static inline bool logind_running(void) {
+ return access("/run/systemd/seats/", F_OK) >= 0;
+}
+
+#define DECIMAL_STR_WIDTH(x) \
+ ({ \
+ typeof(x) _x_ = (x); \
+ unsigned ans = 1; \
+ while (_x_ /= 10) \
+ ans++; \
+ ans; \
+ })
+
+int unlink_noerrno(const char *path);
+
+#define alloca0(n) \
+ ({ \
+ char *_new_; \
+ size_t _len_ = n; \
+ _new_ = alloca(_len_); \
+ (void *) memset(_new_, 0, _len_); \
+ })
+
+#define strappenda(a, b) \
+ ({ \
+ const char *_a_ = (a), *_b_ = (b); \
+ char *_c_; \
+ size_t _x_, _y_; \
+ _x_ = strlen(_a_); \
+ _y_ = strlen(_b_); \
+ _c_ = alloca(_x_ + _y_ + 1); \
+ strcpy(stpcpy(_c_, _a_), _b_); \
+ _c_; \
+ })
+
+#define procfs_file_alloca(pid, field) \
+ ({ \
+ pid_t _pid_ = (pid); \
+ char *_r_; \
+ _r_ = alloca(sizeof("/proc/") -1 + DECIMAL_STR_MAX(pid_t) + 1 + sizeof(field)); \
+ sprintf(_r_, "/proc/%lu/" field, (unsigned long) _pid_); \
+ _r_; \
+ })
+
+struct _locale_struct_ {
+ locale_t saved_locale;
+ locale_t new_locale;
+ bool quit;
+};
+
+static inline void _reset_locale_(struct _locale_struct_ *s) {
+ PROTECT_ERRNO;
+ if (s->saved_locale != (locale_t) 0)
+ uselocale(s->saved_locale);
+ if (s->new_locale != (locale_t) 0)
+ freelocale(s->new_locale);
+}
+
+#define RUN_WITH_LOCALE(mask, loc) \
+ for (_cleanup_(_reset_locale_) struct _locale_struct_ _saved_locale_ = { (locale_t) 0, (locale_t) 0, false }; \
+ ({ \
+ if (!_saved_locale_.quit) { \
+ PROTECT_ERRNO; \
+ _saved_locale_.new_locale = newlocale((mask), (loc), (locale_t) 0); \
+ if (_saved_locale_.new_locale != (locale_t) 0) \
+ _saved_locale_.saved_locale = uselocale(_saved_locale_.new_locale); \
+ } \
+ !_saved_locale_.quit; }) ; \
+ _saved_locale_.quit = true)
+
+bool id128_is_valid(const char *s) _pure_;
diff --git a/src/shared/utmp-wtmp.c b/src/shared/utmp-wtmp.c
new file mode 100644
index 0000000000..5d88405e13
--- /dev/null
+++ b/src/shared/utmp-wtmp.c
@@ -0,0 +1,410 @@
+/*-*- Mode: C; c-basic-offset: 8; indent-tabs-mode: nil -*-*/
+
+/***
+ This file is part 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 <utmpx.h>
+#include <errno.h>
+#include <assert.h>
+#include <string.h>
+#include <sys/utsname.h>
+#include <fcntl.h>
+#include <unistd.h>
+#include <sys/poll.h>
+
+#include "macro.h"
+#include "path-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. */
+
+ if ((e = getenv("RUNLEVEL")) && e[0] > 0) {
+ *runlevel = e[0];
+
+ if (previous) {
+ /* $PREVLEVEL seems to be an Upstart thing */
+
+ if ((e = getenv("PREVLEVEL")) && e[0] > 0)
+ *previous = e[0];
+ else
+ *previous = 0;
+ }
+
+ return 0;
+ }
+
+ if (utmpxname(_PATH_UTMPX) < 0)
+ return -errno;
+
+ setutxent();
+
+ if (!(found = getutxid(&lookup)))
+ 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);
+
+ zero(*store);
+
+ if (t <= 0)
+ t = now(CLOCK_REALTIME);
+
+ store->ut_tv.tv_sec = t / USEC_PER_SEC;
+ store->ut_tv.tv_usec = t % USEC_PER_SEC;
+}
+
+static void init_entry(struct utmpx *store, usec_t t) {
+ struct utsname uts = {};
+
+ assert(store);
+
+ init_timestamp(store, t);
+
+ 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) {
+ struct utmpx store;
+
+ assert(id);
+
+ init_timestamp(&store, 0);
+
+ store.ut_type = INIT_PROCESS;
+ store.ut_pid = pid;
+ store.ut_session = sid;
+
+ strncpy(store.ut_id, sanitize_id(id), sizeof(store.ut_id));
+
+ if (line)
+ strncpy(store.ut_line, path_get_file_name(line), sizeof(store.ut_line));
+
+ return write_entry_both(&store);
+}
+
+int utmp_put_dead_process(const char *id, pid_t pid, int code, int status) {
+ struct utmpx lookup, store, store_wtmp, *found;
+
+ assert(id);
+
+ setutxent();
+
+ zero(lookup);
+ lookup.ut_type = INIT_PROCESS; /* looks for DEAD_PROCESS, LOGIN_PROCESS, USER_PROCESS, too */
+ strncpy(lookup.ut_id, sanitize_id(id), sizeof(lookup.ut_id));
+
+ if (!(found = getutxid(&lookup)))
+ return 0;
+
+ if (found->ut_pid != pid)
+ return 0;
+
+ memcpy(&store, found, sizeof(store));
+ store.ut_type = DEAD_PROCESS;
+ store.ut_exit.e_termination = code;
+ store.ut_exit.e_exit = status;
+
+ zero(store.ut_user);
+ zero(store.ut_host);
+ zero(store.ut_tv);
+
+ memcpy(&store_wtmp, &store, sizeof(store_wtmp));
+ /* wtmp wants the current time */
+ init_timestamp(&store_wtmp, 0);
+
+ return write_utmp_wtmp(&store, &store_wtmp);
+}
+
+
+int utmp_put_runlevel(int runlevel, int previous) {
+ struct utmpx store;
+ int r;
+
+ assert(runlevel > 0);
+
+ if (previous <= 0) {
+ /* Find the old runlevel automatically */
+
+ if ((r = utmp_get_runlevel(&previous, NULL)) < 0) {
+ if (r != -ESRCH)
+ return r;
+
+ previous = 0;
+ }
+ }
+
+ if (previous == runlevel)
+ return 0;
+
+ init_entry(&store, 0);
+
+ store.ut_type = RUN_LVL;
+ store.ut_pid = (runlevel & 0xFF) | ((previous & 0xFF) << 8);
+ strncpy(store.ut_user, "runlevel", sizeof(store.ut_user));
+
+ return write_entry_both(&store);
+}
+
+#define TIMEOUT_MSEC 50
+
+static int write_to_terminal(const char *tty, const char *message) {
+ _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, bool (*match_tty)(const char *tty)) {
+ struct utmpx *u;
+ char date[FORMAT_TIMESTAMP_MAX];
+ char *text = NULL, *hn = NULL, *un = NULL, *tty = NULL;
+ int r;
+
+ if (!(hn = gethostname_malloc()) ||
+ !(un = getlogname_malloc())) {
+ r = -ENOMEM;
+ goto finish;
+ }
+
+ getttyname_harder(STDIN_FILENO, &tty);
+
+ if (asprintf(&text,
+ "\a\r\n"
+ "Broadcast message from %s@%s%s%s (%s):\r\n\r\n"
+ "%s\r\n\r\n",
+ un, hn,
+ tty ? " on " : "", strempty(tty),
+ format_timestamp(date, sizeof(date), now(CLOCK_REALTIME)),
+ message) < 0) {
+ r = -ENOMEM;
+ goto finish;
+ }
+
+ setutxent();
+
+ r = 0;
+
+ while ((u = getutxent())) {
+ int q;
+ const char *path;
+ char *buf = NULL;
+
+ if (u->ut_type != USER_PROCESS || u->ut_user[0] == 0)
+ continue;
+
+ /* 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) {
+ r = -ENOMEM;
+ goto finish;
+ }
+
+ path = buf;
+ }
+
+ if (!match_tty || match_tty(path))
+ if ((q = write_to_terminal(path, text)) < 0)
+ r = q;
+
+ free(buf);
+ }
+
+finish:
+ free(hn);
+ free(un);
+ free(tty);
+ free(text);
+
+ return r;
+}
diff --git a/src/shared/utmp-wtmp.h b/src/shared/utmp-wtmp.h
new file mode 100644
index 0000000000..5924023203
--- /dev/null
+++ b/src/shared/utmp-wtmp.h
@@ -0,0 +1,35 @@
+/*-*- Mode: C; c-basic-offset: 8; indent-tabs-mode: nil -*-*/
+
+#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 "util.h"
+
+int utmp_get_runlevel(int *runlevel, int *previous);
+
+int utmp_put_shutdown(void);
+int utmp_put_reboot(usec_t timestamp);
+int utmp_put_runlevel(int runlevel, int previous);
+
+int utmp_put_dead_process(const char *id, pid_t pid, int code, int status);
+int utmp_put_init_process(const char *id, pid_t pid, pid_t sid, const char *line);
+
+int utmp_wall(const char *message, bool (*match_tty)(const char *tty));
diff --git a/src/shared/virt.c b/src/shared/virt.c
new file mode 100644
index 0000000000..1c86a3dd1e
--- /dev/null
+++ b/src/shared/virt.c
@@ -0,0 +1,272 @@
+/*-*- Mode: C; c-basic-offset: 8; indent-tabs-mode: nil -*-*/
+
+/***
+ This file is part of systemd.
+
+ Copyright 2011 Lennart Poettering
+
+ systemd is free software; you can redistribute it and/or modify it
+ under the terms of the GNU Lesser General Public License as published by
+ the Free Software Foundation; either version 2.1 of the License, or
+ (at your option) any later version.
+
+ systemd is distributed in the hope that it will be useful, but
+ WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ Lesser General 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 <string.h>
+#include <errno.h>
+#include <unistd.h>
+
+#include "util.h"
+#include "virt.h"
+#include "fileio.h"
+
+/* Returns a short identifier for the various VM implementations */
+int detect_vm(const char **id) {
+
+#if defined(__i386__) || defined(__x86_64__)
+
+ /* Both CPUID and DMI are x86 specific interfaces... */
+
+ static const char *const dmi_vendors[] = {
+ "/sys/class/dmi/id/sys_vendor",
+ "/sys/class/dmi/id/board_vendor",
+ "/sys/class/dmi/id/bios_vendor"
+ };
+
+ static const char dmi_vendor_table[] =
+ "QEMU\0" "qemu\0"
+ /* http://kb.vmware.com/selfservice/microsites/search.do?language=en_US&cmd=displayKC&externalId=1009458 */
+ "VMware\0" "vmware\0"
+ "VMW\0" "vmware\0"
+ "Microsoft Corporation\0" "microsoft\0"
+ "innotek GmbH\0" "oracle\0"
+ "Xen\0" "xen\0"
+ "Bochs\0" "bochs\0";
+
+ static const char cpuid_vendor_table[] =
+ "XenVMMXenVMM\0" "xen\0"
+ "KVMKVMKVM\0" "kvm\0"
+ /* http://kb.vmware.com/selfservice/microsites/search.do?language=en_US&cmd=displayKC&externalId=1009458 */
+ "VMwareVMware\0" "vmware\0"
+ /* http://msdn.microsoft.com/en-us/library/ff542428.aspx */
+ "Microsoft Hv\0" "microsoft\0";
+
+ uint32_t eax, ecx;
+ union {
+ uint32_t sig32[3];
+ char text[13];
+ } sig = {};
+ unsigned i;
+ const char *j, *k;
+ bool hypervisor;
+ _cleanup_free_ char *hvtype = NULL;
+ int r;
+
+ /* Try high-level hypervisor sysfs file first:
+ *
+ * https://bugs.freedesktop.org/show_bug.cgi?id=61491 */
+ r = read_one_line_file("/sys/hypervisor/type", &hvtype);
+ if (r >= 0) {
+ if (streq(hvtype, "xen")) {
+ if (id)
+ *id = "xen";
+
+ return 1;
+ }
+ } else if (r != -ENOENT)
+ return r;
+
+ /* http://lwn.net/Articles/301888/ */
+
+#if defined (__i386__)
+#define REG_a "eax"
+#define REG_b "ebx"
+#elif defined (__amd64__)
+#define REG_a "rax"
+#define REG_b "rbx"
+#endif
+
+ /* First detect whether there is a hypervisor */
+ eax = 1;
+ __asm__ __volatile__ (
+ /* ebx/rbx is being used for PIC! */
+ " push %%"REG_b" \n\t"
+ " cpuid \n\t"
+ " pop %%"REG_b" \n\t"
+
+ : "=a" (eax), "=c" (ecx)
+ : "0" (eax)
+ );
+
+ hypervisor = !!(ecx & 0x80000000U);
+
+ if (hypervisor) {
+
+ /* There is a hypervisor, see what it is */
+ eax = 0x40000000U;
+ __asm__ __volatile__ (
+ /* ebx/rbx is being used for PIC! */
+ " push %%"REG_b" \n\t"
+ " cpuid \n\t"
+ " mov %%ebx, %1 \n\t"
+ " pop %%"REG_b" \n\t"
+
+ : "=a" (eax), "=r" (sig.sig32[0]), "=c" (sig.sig32[1]), "=d" (sig.sig32[2])
+ : "0" (eax)
+ );
+
+ NULSTR_FOREACH_PAIR(j, k, cpuid_vendor_table)
+ if (streq(sig.text, j)) {
+
+ if (id)
+ *id = k;
+
+ return 1;
+ }
+ }
+
+ for (i = 0; i < ELEMENTSOF(dmi_vendors); i++) {
+ _cleanup_free_ char *s = NULL;
+ const char *found = NULL;
+
+ r = read_one_line_file(dmi_vendors[i], &s);
+ if (r < 0) {
+ if (r != -ENOENT)
+ return r;
+
+ continue;
+ }
+
+ NULSTR_FOREACH_PAIR(j, k, dmi_vendor_table)
+ if (startswith(s, j))
+ found = k;
+
+ if (found) {
+ if (id)
+ *id = found;
+
+ return 1;
+ }
+ }
+
+ if (hypervisor || hvtype) {
+ if (id)
+ *id = "other";
+
+ return 1;
+ }
+
+#endif
+ return 0;
+}
+
+int detect_container(const char **id) {
+ _cleanup_free_ char *e = NULL;
+ int r;
+
+ /* Unfortunately many of these operations require root access
+ * in one way or another */
+
+ r = running_in_chroot();
+ if (r < 0)
+ return r;
+ if (r > 0) {
+
+ if (id)
+ *id = "chroot";
+
+ return 1;
+ }
+
+ /* /proc/vz exists in container and outside of the container,
+ * /proc/bc only outside of the container. */
+ if (access("/proc/vz", F_OK) >= 0 &&
+ access("/proc/bc", F_OK) < 0) {
+
+ if (id)
+ *id = "openvz";
+
+ return 1;
+ }
+
+ r = getenv_for_pid(1, "container", &e);
+ if (r <= 0)
+ return r;
+
+ /* We only recognize a selected few here, since we want to
+ * enforce a redacted namespace */
+ if (streq(e, "lxc")) {
+ if (id)
+ *id = "lxc";
+ } else if (streq(e, "lxc-libvirt")) {
+ if (id)
+ *id = "lxc-libvirt";
+ } else if (streq(e, "systemd-nspawn")) {
+ if (id)
+ *id = "systemd-nspawn";
+ } else {
+ if (id)
+ *id = "other";
+ }
+
+ return r;
+}
+
+/* Returns a short identifier for the various VM/container implementations */
+Virtualization detect_virtualization(const char **id) {
+
+ static __thread Virtualization cached_virt = _VIRTUALIZATION_INVALID;
+ static __thread const char *cached_id = NULL;
+
+ const char *_id;
+ int r;
+ Virtualization v;
+
+ if (_likely_(cached_virt >= 0)) {
+
+ if (id && cached_virt > 0)
+ *id = cached_id;
+
+ return cached_virt;
+ }
+
+ r = detect_container(&_id);
+ if (r < 0) {
+ v = r;
+ goto finish;
+ } else if (r > 0) {
+ v = VIRTUALIZATION_CONTAINER;
+ goto finish;
+ }
+
+ r = detect_vm(&_id);
+ if (r < 0) {
+ v = r;
+ goto finish;
+ } else if (r > 0) {
+ v = VIRTUALIZATION_VM;
+ goto finish;
+ }
+
+ v = VIRTUALIZATION_NONE;
+
+finish:
+ if (v > 0) {
+ cached_id = _id;
+
+ if (id)
+ *id = _id;
+ }
+
+ if (v >= 0)
+ cached_virt = v;
+
+ return v;
+}
diff --git a/src/shared/virt.h b/src/shared/virt.h
new file mode 100644
index 0000000000..aa6ad35ba9
--- /dev/null
+++ b/src/shared/virt.h
@@ -0,0 +1,35 @@
+/*-*- Mode: C; c-basic-offset: 8; indent-tabs-mode: nil -*-*/
+
+#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 detect_vm(const char **id);
+int detect_container(const char **id);
+
+typedef enum Virtualization {
+ VIRTUALIZATION_NONE = 0,
+ VIRTUALIZATION_VM,
+ VIRTUALIZATION_CONTAINER,
+ _VIRTUALIZATION_MAX,
+ _VIRTUALIZATION_INVALID = -1
+} Virtualization;
+
+Virtualization detect_virtualization(const char **id);
diff --git a/src/shared/watchdog.c b/src/shared/watchdog.c
new file mode 100644
index 0000000000..ddbe7afd3c
--- /dev/null
+++ b/src/shared/watchdog.c
@@ -0,0 +1,169 @@
+/*-*- Mode: C; c-basic-offset: 8; indent-tabs-mode: nil -*-*/
+
+/***
+ This file is part of systemd.
+
+ Copyright 2012 Lennart Poettering
+
+ systemd is free software; you can redistribute it and/or modify it
+ under the terms of the GNU Lesser General Public License as published by
+ the Free Software Foundation; either version 2.1 of the License, or
+ (at your option) any later version.
+
+ systemd is distributed in the hope that it will be useful, but
+ WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ Lesser General 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/ioctl.h>
+#include <errno.h>
+#include <fcntl.h>
+#include <unistd.h>
+#include <linux/watchdog.h>
+
+#include "watchdog.h"
+#include "log.h"
+
+static int watchdog_fd = -1;
+static usec_t watchdog_timeout = (usec_t) -1;
+
+static int update_timeout(void) {
+ int r;
+
+ if (watchdog_fd < 0)
+ return 0;
+
+ if (watchdog_timeout == (usec_t) -1)
+ return 0;
+ else if (watchdog_timeout == 0) {
+ int flags;
+
+ flags = WDIOS_DISABLECARD;
+ r = ioctl(watchdog_fd, WDIOC_SETOPTIONS, &flags);
+ if (r < 0) {
+ log_warning("Failed to disable hardware watchdog: %m");
+ return -errno;
+ }
+ } 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) {
+ log_warning("Failed to set timeout to %is: %m", sec);
+ return -errno;
+ }
+
+ 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) {
+ log_warning("Failed to enable hardware watchdog: %m");
+ return -errno;
+ }
+
+ r = ioctl(watchdog_fd, WDIOC_KEEPALIVE, 0);
+ if (r < 0) {
+ log_warning("Failed to ping hardware watchdog: %m");
+ return -errno;
+ }
+ }
+
+ 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_t) -1)
+ 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) {
+ log_warning("Failed to ping hardware watchdog: %m");
+ return -errno;
+ }
+
+ 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("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("Failed to disarm watchdog timer: %m");
+ break;
+ }
+ }
+ }
+
+ close_nointr_nofail(watchdog_fd);
+ watchdog_fd = -1;
+}
diff --git a/src/shared/watchdog.h b/src/shared/watchdog.h
new file mode 100644
index 0000000000..b748b15857
--- /dev/null
+++ b/src/shared/watchdog.h
@@ -0,0 +1,28 @@
+/*-*- Mode: C; c-basic-offset: 8; indent-tabs-mode: nil -*-*/
+
+#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 "util.h"
+
+int watchdog_set_timeout(usec_t *usec);
+int watchdog_ping(void);
+void watchdog_close(bool disarm);