+static int config_parse_io_priority(
+ const char *filename,
+ unsigned line,
+ const char *section,
+ const char *lvalue,
+ const char *rvalue,
+ void *data,
+ void *userdata) {
+
+ ExecContext *c = data;
+ int i;
+
+ assert(filename);
+ assert(lvalue);
+ assert(rvalue);
+ assert(data);
+
+ if (safe_atoi(rvalue, &i) < 0 || i < 0 || i >= IOPRIO_BE_NR) {
+ log_error("[%s:%u] Failed to parse io priority: %s", filename, line, rvalue);
+ return -EBADMSG;
+ }
+
+ c->ioprio = IOPRIO_PRIO_VALUE(IOPRIO_PRIO_CLASS(c->ioprio), i);
+ c->ioprio_set = true;
+
+ return 0;
+}
+
+static int config_parse_cpu_sched_policy(
+ const char *filename,
+ unsigned line,
+ const char *section,
+ const char *lvalue,
+ const char *rvalue,
+ void *data,
+ void *userdata) {
+
+
+ ExecContext *c = data;
+ int x;
+
+ assert(filename);
+ assert(lvalue);
+ assert(rvalue);
+ assert(data);
+
+ if ((x = sched_policy_from_string(rvalue)) < 0) {
+ log_error("[%s:%u] Failed to parse CPU scheduling policy: %s", filename, line, rvalue);
+ return -EBADMSG;
+ }
+
+ c->cpu_sched_policy = x;
+ c->cpu_sched_set = true;
+
+ return 0;
+}
+
+static int config_parse_cpu_sched_prio(
+ const char *filename,
+ unsigned line,
+ const char *section,
+ const char *lvalue,
+ const char *rvalue,
+ void *data,
+ void *userdata) {
+
+ ExecContext *c = data;
+ int i;
+
+ assert(filename);
+ assert(lvalue);
+ assert(rvalue);
+ assert(data);
+
+ /* On Linux RR/FIFO have the same range */
+ if (safe_atoi(rvalue, &i) < 0 || i < sched_get_priority_min(SCHED_RR) || i > sched_get_priority_max(SCHED_RR)) {
+ log_error("[%s:%u] Failed to parse CPU scheduling priority: %s", filename, line, rvalue);
+ return -EBADMSG;
+ }
+
+ c->cpu_sched_priority = i;
+ c->cpu_sched_set = true;
+
+ return 0;
+}
+
+static int config_parse_cpu_affinity(
+ const char *filename,
+ unsigned line,
+ const char *section,
+ const char *lvalue,
+ const char *rvalue,
+ void *data,
+ void *userdata) {
+
+ ExecContext *c = data;
+ char *w;
+ size_t l;
+ char *state;
+
+ assert(filename);
+ assert(lvalue);
+ assert(rvalue);
+ assert(data);
+
+ FOREACH_WORD(w, l, rvalue, state) {
+ char *t;
+ int r;
+ unsigned cpu;
+
+ if (!(t = strndup(w, l)))
+ return -ENOMEM;
+
+ r = safe_atou(t, &cpu);
+ free(t);
+
+ if (r < 0 || cpu >= CPU_SETSIZE) {
+ log_error("[%s:%u] Failed to parse CPU affinity: %s", filename, line, rvalue);
+ return -EBADMSG;
+ }
+
+ CPU_SET(cpu, &c->cpu_affinity);
+ }
+
+ c->cpu_affinity_set = true;
+
+ return 0;
+}
+
+static int config_parse_capabilities(
+ const char *filename,
+ unsigned line,
+ const char *section,
+ const char *lvalue,
+ const char *rvalue,
+ void *data,
+ void *userdata) {
+
+ ExecContext *c = data;
+ cap_t cap;
+
+ assert(filename);
+ assert(lvalue);
+ assert(rvalue);
+ assert(data);
+
+ if (!(cap = cap_from_text(rvalue))) {
+ if (errno == ENOMEM)
+ return -ENOMEM;
+
+ log_error("[%s:%u] Failed to parse capabilities: %s", filename, line, rvalue);
+ return -EBADMSG;
+ }
+
+ if (c->capabilities)
+ cap_free(c->capabilities);
+ c->capabilities = cap;
+
+ return 0;
+}
+
+static int config_parse_secure_bits(
+ const char *filename,
+ unsigned line,
+ const char *section,
+ const char *lvalue,
+ const char *rvalue,
+ void *data,
+ void *userdata) {
+
+ ExecContext *c = data;
+ char *w;
+ size_t l;
+ char *state;
+
+ assert(filename);
+ assert(lvalue);
+ assert(rvalue);
+ assert(data);
+
+ FOREACH_WORD(w, l, rvalue, state) {
+ if (first_word(w, "keep-caps"))
+ c->secure_bits |= SECURE_KEEP_CAPS;
+ else if (first_word(w, "keep-caps-locked"))
+ c->secure_bits |= SECURE_KEEP_CAPS_LOCKED;
+ else if (first_word(w, "no-setuid-fixup"))
+ c->secure_bits |= SECURE_NO_SETUID_FIXUP;
+ else if (first_word(w, "no-setuid-fixup-locked"))
+ c->secure_bits |= SECURE_NO_SETUID_FIXUP_LOCKED;
+ else if (first_word(w, "noroot"))
+ c->secure_bits |= SECURE_NOROOT;
+ else if (first_word(w, "noroot-locked"))
+ c->secure_bits |= SECURE_NOROOT_LOCKED;
+ else {
+ log_error("[%s:%u] Failed to parse secure bits: %s", filename, line, rvalue);
+ return -EBADMSG;
+ }
+ }
+
+ return 0;
+}
+
+static int config_parse_bounding_set(
+ const char *filename,
+ unsigned line,
+ const char *section,
+ const char *lvalue,
+ const char *rvalue,
+ void *data,
+ void *userdata) {
+
+ ExecContext *c = data;
+ char *w;
+ size_t l;
+ char *state;
+
+ assert(filename);
+ assert(lvalue);
+ assert(rvalue);
+ assert(data);
+
+ FOREACH_WORD(w, l, rvalue, state) {
+ char *t;
+ int r;
+ cap_value_t cap;
+
+ if (!(t = strndup(w, l)))
+ return -ENOMEM;
+
+ r = cap_from_name(t, &cap);
+ free(t);
+
+ if (r < 0) {
+ log_error("[%s:%u] Failed to parse capability bounding set: %s", filename, line, rvalue);
+ return -EBADMSG;
+ }
+
+ c->capability_bounding_set_drop |= 1 << cap;
+ }
+
+ return 0;
+}
+
+static int config_parse_timer_slack_ns(
+ const char *filename,
+ unsigned line,
+ const char *section,
+ const char *lvalue,
+ const char *rvalue,
+ void *data,
+ void *userdata) {
+
+ ExecContext *c = data;
+ unsigned long u;
+ int r;
+
+ assert(filename);
+ assert(lvalue);
+ assert(rvalue);
+ assert(data);
+
+ if ((r = safe_atolu(rvalue, &u)) < 0) {
+ log_error("[%s:%u] Failed to parse time slack value: %s", filename, line, rvalue);
+ return r;
+ }
+
+ c->timer_slack_ns = u;
+
+ return 0;
+}
+
+static int config_parse_limit(
+ const char *filename,
+ unsigned line,
+ const char *section,
+ const char *lvalue,
+ const char *rvalue,
+ void *data,
+ void *userdata) {
+
+ struct rlimit **rl = data;
+ unsigned long long u;
+ int r;
+
+ assert(filename);
+ assert(lvalue);
+ assert(rvalue);
+ assert(data);
+
+ if ((r = safe_atollu(rvalue, &u)) < 0) {
+ log_error("[%s:%u] Failed to parse resource value: %s", filename, line, rvalue);
+ return r;
+ }
+
+ if (!*rl)
+ if (!(*rl = new(struct rlimit, 1)))
+ return -ENOMEM;
+
+ (*rl)->rlim_cur = (*rl)->rlim_max = (rlim_t) u;
+ return 0;
+}
+
+static int config_parse_cgroup(
+ const char *filename,
+ unsigned line,
+ const char *section,
+ const char *lvalue,
+ const char *rvalue,
+ void *data,
+ void *userdata) {
+
+ Unit *u = userdata;
+ char *w;
+ size_t l;
+ char *state;
+
+ FOREACH_WORD(w, l, rvalue, state) {
+ char *t;
+ int r;
+
+ if (!(t = strndup(w, l)))
+ return -ENOMEM;
+
+ r = unit_add_cgroup_from_text(u, t);
+ free(t);
+
+ if (r < 0)
+ return r;
+ }
+
+ return 0;
+}
+
+static int config_parse_sysv_priority(
+ const char *filename,
+ unsigned line,
+ const char *section,
+ const char *lvalue,
+ const char *rvalue,
+ void *data,
+ void *userdata) {
+
+ int *priority = data;
+ int r, i;
+
+ assert(filename);
+ assert(lvalue);
+ assert(rvalue);
+ assert(data);
+
+ if ((r = safe_atoi(rvalue, &i)) < 0 || i < 0) {
+ log_error("[%s:%u] Failed to parse SysV start priority: %s", filename, line, rvalue);
+ return r;
+ }
+
+ *priority = (int) i;
+ return 0;
+}
+
+DEFINE_CONFIG_PARSE_ENUM(config_parse_kill_mode, kill_mode, KillMode, "Failed to parse kill mode");
+
+#define FOLLOW_MAX 8
+
+static int open_follow(char **filename, FILE **_f, Set *names, char **_id) {
+ unsigned c = 0;
+ int fd, r;
+ FILE *f;
+ char *id = NULL;
+
+ assert(filename);
+ assert(*filename);
+ assert(_f);
+ assert(names);
+
+ /* This will update the filename pointer if the loaded file is
+ * reached by a symlink. The old string will be freed. */
+
+ for (;;) {
+ char *target, *k, *name;
+
+ if (c++ >= FOLLOW_MAX)
+ return -ELOOP;
+
+ path_kill_slashes(*filename);
+
+ /* Add the file name we are currently looking at to
+ * the names of this unit */
+ name = file_name_from_path(*filename);
+ if (!(id = set_get(names, name))) {
+
+ if (!(id = strdup(name)))
+ return -ENOMEM;
+
+ if ((r = set_put(names, id)) < 0) {
+ free(id);
+ return r;
+ }
+ }
+
+ /* Try to open the file name, but don't if its a symlink */
+ if ((fd = open(*filename, O_RDONLY|O_CLOEXEC|O_NOCTTY|O_NOFOLLOW)) >= 0)
+ break;
+
+ if (errno != ELOOP)
+ return -errno;
+
+ /* Hmm, so this is a symlink. Let's read the name, and follow it manually */
+ if ((r = readlink_malloc(*filename, &target)) < 0)
+ return r;
+
+ k = file_in_same_dir(*filename, target);
+ free(target);
+
+ if (!k)
+ return -ENOMEM;
+
+ free(*filename);
+ *filename = k;
+ }
+
+ if (!(f = fdopen(fd, "r"))) {
+ r = -errno;
+ assert(close_nointr(fd) == 0);
+ return r;
+ }
+
+ *_f = f;
+ *_id = id;
+ return 0;
+}
+
+static int merge_by_names(Unit **u, Set *names, const char *id) {
+ char *k;
+ int r;
+
+ assert(u);
+ assert(*u);
+ assert(names);
+
+ /* Let's try to add in all symlink names we found */
+ while ((k = set_steal_first(names))) {
+
+ /* First try to merge in the other name into our
+ * unit */
+ if ((r = unit_merge_by_name(*u, k)) < 0) {
+ Unit *other;
+
+ /* Hmm, we couldn't merge the other unit into
+ * ours? Then let's try it the other way
+ * round */
+
+ other = manager_get_unit((*u)->meta.manager, k);
+ free(k);
+
+ if (other)
+ if ((r = unit_merge(other, *u)) >= 0) {
+ *u = other;
+ return merge_by_names(u, names, NULL);
+ }
+
+ return r;
+ }
+
+ if (id == k)
+ unit_choose_id(*u, id);
+
+ free(k);
+ }
+
+ return 0;
+}
+
+static void dump_items(FILE *f, const ConfigItem *items) {
+ const ConfigItem *i;
+ const char *prev_section = NULL;
+ bool not_first = false;
+
+ struct {
+ ConfigParserCallback callback;
+ const char *rvalue;
+ } table[] = {
+ { config_parse_int, "INTEGER" },
+ { config_parse_unsigned, "UNSIGNED" },
+ { config_parse_size, "SIZE" },
+ { config_parse_bool, "BOOLEAN" },
+ { config_parse_string, "STRING" },
+ { config_parse_path, "PATH" },
+ { config_parse_strv, "STRING [...]" },
+ { config_parse_nice, "NICE" },
+ { config_parse_oom_adjust, "OOMADJUST" },
+ { config_parse_io_class, "IOCLASS" },
+ { config_parse_io_priority, "IOPRIORITY" },
+ { config_parse_cpu_sched_policy, "CPUSCHEDPOLICY" },
+ { config_parse_cpu_sched_prio, "CPUSCHEDPRIO" },
+ { config_parse_cpu_affinity, "CPUAFFINITY" },
+ { config_parse_mode, "MODE" },
+ { config_parse_output, "OUTPUT" },
+ { config_parse_input, "INPUT" },
+ { config_parse_facility, "FACILITY" },
+ { config_parse_level, "LEVEL" },
+ { config_parse_capabilities, "CAPABILITIES" },
+ { config_parse_secure_bits, "SECUREBITS" },
+ { config_parse_bounding_set, "BOUNDINGSET" },
+ { config_parse_timer_slack_ns, "TIMERSLACK" },
+ { config_parse_limit, "LIMIT" },
+ { config_parse_cgroup, "CGROUP [...]" },
+ { config_parse_deps, "UNIT [...]" },
+ { config_parse_names, "UNIT [...]" },
+ { config_parse_exec, "PATH [ARGUMENT [...]]" },
+ { config_parse_service_type, "SERVICETYPE" },
+ { config_parse_service_restart, "SERVICERESTART" },
+ { config_parse_sysv_priority, "SYSVPRIORITY" },
+ { config_parse_kill_mode, "KILLMODE" },
+ { config_parse_listen, "SOCKET [...]" },
+ { config_parse_socket_bind, "SOCKETBIND" },
+ { config_parse_bindtodevice, "NETWORKINTERFACE" }
+ };
+
+ assert(f);
+ assert(items);
+
+ for (i = items; i->lvalue; i++) {
+ unsigned j;
+ const char *rvalue = "OTHER";
+
+ if (!streq_ptr(i->section, prev_section)) {
+ if (!not_first)
+ not_first = true;
+ else
+ fputc('\n', f);
+
+ fprintf(f, "[%s]\n", i->section);
+ prev_section = i->section;
+ }
+
+ for (j = 0; j < ELEMENTSOF(table); j++)
+ if (i->parse == table[j].callback) {
+ rvalue = table[j].rvalue;
+ break;
+ }
+
+ fprintf(f, "%s=%s\n", i->lvalue, rvalue);
+ }
+}
+
+static int load_from_path(Unit *u, const char *path) {
+
+ static const char* const section_table[_UNIT_TYPE_MAX] = {
+ [UNIT_SERVICE] = "Service",
+ [UNIT_TIMER] = "Timer",
+ [UNIT_SOCKET] = "Socket",
+ [UNIT_TARGET] = "Target",
+ [UNIT_DEVICE] = "Device",
+ [UNIT_MOUNT] = "Mount",
+ [UNIT_AUTOMOUNT] = "Automount",
+ [UNIT_SNAPSHOT] = "Snapshot"