#include "missing.h"
#include "unit-name.h"
+#define COMMENTS "#;\n"
+#define LINE_MAX 4096
+
#define DEFINE_CONFIG_PARSE_ENUM(function,name,type,msg) \
static int function( \
const char *filename, \
return 0;
}
-static int config_parse_description(
+static int config_parse_string_printf(
const char *filename,
unsigned line,
const char *section,
void *userdata) {
Unit *u = userdata;
+ char **s = data;
char *k;
assert(filename);
assert(lvalue);
assert(rvalue);
- assert(data);
+ assert(s);
+ assert(u);
if (!(k = unit_full_printf(u, rvalue)))
return -ENOMEM;
- free(u->meta.description);
-
+ free(*s);
if (*k)
- u->meta.description = k;
+ *s = k;
else {
free(k);
- u->meta.description = NULL;
+ *s = NULL;
}
return 0;
free(p);
return -ENOMEM;
}
+
+ path_kill_slashes(p->path);
} else {
p->type = SOCKET_SOCKET;
nce->argv = n;
nce->path = path;
+ path_kill_slashes(nce->path);
+
exec_command_append_list(e, nce);
return 0;
return 0;
}
+static int config_parse_timer(
+ const char *filename,
+ unsigned line,
+ const char *section,
+ const char *lvalue,
+ const char *rvalue,
+ void *data,
+ void *userdata) {
+
+ Timer *t = data;
+ usec_t u;
+ int r;
+ TimerValue *v;
+ TimerBase b;
+
+ assert(filename);
+ assert(lvalue);
+ assert(rvalue);
+ assert(data);
+
+ if ((b = timer_base_from_string(lvalue)) < 0) {
+ log_error("[%s:%u] Failed to parse timer base: %s", filename, line, lvalue);
+ return -EINVAL;
+ }
+
+ if ((r = parse_usec(rvalue, &u)) < 0) {
+ log_error("[%s:%u] Failed to parse timer value: %s", filename, line, rvalue);
+ return r;
+ }
+
+ if (!(v = new0(TimerValue, 1)))
+ return -ENOMEM;
+
+ v->base = b;
+ v->value = u;
+
+ LIST_PREPEND(TimerValue, value, t->values, v);
+
+ return 0;
+}
+
+static int config_parse_timer_unit(
+ const char *filename,
+ unsigned line,
+ const char *section,
+ const char *lvalue,
+ const char *rvalue,
+ void *data,
+ void *userdata) {
+
+ Timer *t = data;
+ int r;
+
+ if (endswith(rvalue, ".timer")) {
+ log_error("[%s:%u] Unit cannot be of type timer: %s", filename, line, rvalue);
+ return -EINVAL;
+ }
+
+ if ((r = manager_load_unit(t->meta.manager, rvalue, NULL, &t->unit)) < 0) {
+ log_error("[%s:%u] Failed to load unit: %s", filename, line, rvalue);
+ return r;
+ }
+
+ return 0;
+}
+
+static int config_parse_path_spec(
+ const char *filename,
+ unsigned line,
+ const char *section,
+ const char *lvalue,
+ const char *rvalue,
+ void *data,
+ void *userdata) {
+
+ Path *p = data;
+ PathSpec *s;
+ PathType b;
+
+ assert(filename);
+ assert(lvalue);
+ assert(rvalue);
+ assert(data);
+
+ if ((b = path_type_from_string(lvalue)) < 0) {
+ log_error("[%s:%u] Failed to parse path type: %s", filename, line, lvalue);
+ return -EINVAL;
+ }
+
+ if (!path_is_absolute(rvalue)) {
+ log_error("[%s:%u] Path is not absolute: %s", filename, line, rvalue);
+ return -EINVAL;
+ }
+
+ if (!(s = new0(PathSpec, 1)))
+ return -ENOMEM;
+
+ if (!(s->path = strdup(rvalue))) {
+ free(s);
+ return -ENOMEM;
+ }
+
+ path_kill_slashes(s->path);
+
+ s->type = b;
+ s->inotify_fd = -1;
+
+ LIST_PREPEND(PathSpec, spec, p->specs, s);
+
+ return 0;
+}
+
+static int config_parse_path_unit(
+ const char *filename,
+ unsigned line,
+ const char *section,
+ const char *lvalue,
+ const char *rvalue,
+ void *data,
+ void *userdata) {
+
+ Path *t = data;
+ int r;
+
+ if (endswith(rvalue, ".path")) {
+ log_error("[%s:%u] Unit cannot be of type path: %s", filename, line, rvalue);
+ return -EINVAL;
+ }
+
+ if ((r = manager_load_unit(t->meta.manager, rvalue, NULL, &t->unit)) < 0) {
+ log_error("[%s:%u] Failed to load unit: %s", filename, line, rvalue);
+ return r;
+ }
+
+ return 0;
+}
+
+static int config_parse_env_file(
+ const char *filename,
+ unsigned line,
+ const char *section,
+ const char *lvalue,
+ const char *rvalue,
+ void *data,
+ void *userdata) {
+
+ FILE *f;
+ int r;
+ char ***env = data;
+
+ assert(filename);
+ assert(lvalue);
+ assert(rvalue);
+ assert(data);
+
+ if (!(f = fopen(rvalue, "re"))) {
+ log_error("[%s:%u] Failed to open environment file '%s': %m", filename, line, rvalue);
+ return -errno;
+ }
+
+ while (!feof(f)) {
+ char l[LINE_MAX], *p;
+ char **t;
+
+ if (!fgets(l, sizeof(l), f)) {
+ if (feof(f))
+ break;
+
+ r = -errno;
+ log_error("[%s:%u] Failed to read environment file '%s': %m", filename, line, rvalue);
+ goto finish;
+ }
+
+ p = strstrip(l);
+
+ if (!*p)
+ continue;
+
+ if (strchr(COMMENTS, *p))
+ continue;
+
+ t = strv_env_set(*env, p);
+ strv_free(*env);
+ *env = t;
+ }
+
+ r = 0;
+
+finish:
+ if (f)
+ fclose(f);
+
+ return r;
+}
+
+static int config_parse_ip_tos(
+ const char *filename,
+ unsigned line,
+ const char *section,
+ const char *lvalue,
+ const char *rvalue,
+ void *data,
+ void *userdata) {
+
+ int *ip_tos = data, x;
+ int r;
+
+ assert(filename);
+ assert(lvalue);
+ assert(rvalue);
+ assert(data);
+
+ if ((x = ip_tos_from_string(rvalue)) < 0)
+ if ((r = safe_atoi(rvalue, &x)) < 0) {
+ log_error("[%s:%u] Failed to parse IP TOS value: %s", filename, line, rvalue);
+ return r;
+ }
+
+ *ip_tos = x;
+ return 0;
+}
+
+DEFINE_CONFIG_PARSE_ENUM(config_parse_notify_access, notify_access, NotifyAccess, "Failed to parse notify access specifier");
+
#define FOLLOW_MAX 8
static int open_follow(char **filename, FILE **_f, Set *names, char **_final) {
* reached by a symlink. The old string will be freed. */
for (;;) {
- char *target, *k, *name;
+ char *target, *name;
if (c++ >= FOLLOW_MAX)
return -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)
+ if ((r = readlink_and_make_absolute(*filename, &target)) < 0)
return r;
- k = file_in_same_dir(*filename, target);
- free(target);
-
- if (!k)
- return -ENOMEM;
-
free(*filename);
- *filename = k;
+ *filename = target;
}
if (!(f = fdopen(fd, "r"))) {
{ config_parse_cpu_sched_prio, "CPUSCHEDPRIO" },
{ config_parse_cpu_affinity, "CPUAFFINITY" },
{ config_parse_mode, "MODE" },
+ { config_parse_env_file, "FILE" },
{ config_parse_output, "OUTPUT" },
{ config_parse_input, "INPUT" },
{ config_parse_facility, "FACILITY" },
{ config_parse_usec, "SECONDS" },
{ config_parse_path_strv, "PATH [...]" },
{ config_parse_mount_flags, "MOUNTFLAG [...]" },
- { config_parse_description, "DESCRIPTION" },
+ { config_parse_string_printf, "STRING" },
+ { config_parse_timer, "TIMER" },
+ { config_parse_timer_unit, "NAME" },
+ { config_parse_path_spec, "PATH" },
+ { config_parse_path_unit, "UNIT" },
+ { config_parse_notify_access, "ACCESS" },
+ { config_parse_ip_tos, "TOS" },
};
assert(f);
[UNIT_MOUNT] = "Mount",
[UNIT_AUTOMOUNT] = "Automount",
[UNIT_SNAPSHOT] = "Snapshot",
- [UNIT_SWAP] = "Swap"
+ [UNIT_SWAP] = "Swap",
+ [UNIT_PATH] = "Path"
};
#define EXEC_CONTEXT_CONFIG_ITEMS(context, section) \
{ "WorkingDirectory", config_parse_path, &(context).working_directory, section }, \
{ "RootDirectory", config_parse_path, &(context).root_directory, section }, \
- { "User", config_parse_string, &(context).user, section }, \
- { "Group", config_parse_string, &(context).group, section }, \
+ { "User", config_parse_string_printf, &(context).user, section }, \
+ { "Group", config_parse_string_printf, &(context).group, section }, \
{ "SupplementaryGroups", config_parse_strv, &(context).supplementary_groups, section }, \
{ "Nice", config_parse_nice, &(context), section }, \
{ "OOMAdjust", config_parse_oom_adjust, &(context), section }, \
{ "CPUAffinity", config_parse_cpu_affinity, &(context), section }, \
{ "UMask", config_parse_mode, &(context).umask, section }, \
{ "Environment", config_parse_strv, &(context).environment, section }, \
+ { "EnvironmentFile", config_parse_env_file, &(context).environment, section }, \
{ "StandardInput", config_parse_input, &(context).std_input, section }, \
{ "StandardOutput", config_parse_output, &(context).std_output, section }, \
- { "StandardError", config_parse_output, &(context).std_output, section }, \
+ { "StandardError", config_parse_output, &(context).std_error, section }, \
{ "TTYPath", config_parse_path, &(context).tty_path, section }, \
- { "SyslogIdentifier", config_parse_string, &(context).syslog_identifier, section }, \
+ { "SyslogIdentifier", config_parse_string_printf, &(context).syslog_identifier, section }, \
{ "SyslogFacility", config_parse_facility, &(context).syslog_priority, section }, \
{ "SyslogLevel", config_parse_level, &(context).syslog_priority, section }, \
{ "SyslogNoPrefix", config_parse_bool, &(context).syslog_no_prefix, section }, \
{ "ReadOnlyDirectories", config_parse_path_strv, &(context).read_only_dirs, section }, \
{ "InaccessibleDirectories",config_parse_path_strv, &(context).inaccessible_dirs, section }, \
{ "PrivateTmp", config_parse_bool, &(context).private_tmp, section }, \
- { "MountFlags", config_parse_mount_flags, &(context), section }
+ { "MountFlags", config_parse_mount_flags, &(context), section }, \
+ { "TCPWrapName", config_parse_string_printf, &(context).tcpwrap_name, section }, \
+ { "PAMName", config_parse_string_printf, &(context).pam_name, section }
const ConfigItem items[] = {
{ "Names", config_parse_names, u, "Unit" },
- { "Description", config_parse_description, u, "Unit" },
+ { "Description", config_parse_string_printf, &u->meta.description, "Unit" },
{ "Requires", config_parse_deps, UINT_TO_PTR(UNIT_REQUIRES), "Unit" },
{ "RequiresOverridable", config_parse_deps, UINT_TO_PTR(UNIT_REQUIRES_OVERRIDABLE), "Unit" },
{ "Requisite", config_parse_deps, UINT_TO_PTR(UNIT_REQUISITE), "Unit" },
{ "RecursiveStop", config_parse_bool, &u->meta.recursive_stop, "Unit" },
{ "StopWhenUnneeded", config_parse_bool, &u->meta.stop_when_unneeded, "Unit" },
{ "OnlyByDependency", config_parse_bool, &u->meta.only_by_dependency, "Unit" },
+ { "DefaultDependencies", config_parse_bool, &u->meta.default_dependencies, "Unit" },
{ "PIDFile", config_parse_path, &u->service.pid_file, "Service" },
{ "ExecStartPre", config_parse_exec, u->service.exec_command+SERVICE_EXEC_START_PRE, "Service" },
{ "SysVStartPriority", config_parse_sysv_priority, &u->service.sysv_start_priority, "Service" },
{ "KillMode", config_parse_kill_mode, &u->service.kill_mode, "Service" },
{ "NonBlocking", config_parse_bool, &u->service.exec_context.non_blocking, "Service" },
- { "BusName", config_parse_string, &u->service.bus_name, "Service" },
+ { "BusName", config_parse_string_printf, &u->service.bus_name, "Service" },
+ { "NotifyAccess", config_parse_notify_access, &u->service.notify_access, "Service" },
EXEC_CONTEXT_CONFIG_ITEMS(u->service.exec_context, "Service"),
{ "ListenStream", config_parse_listen, &u->socket, "Socket" },
{ "SocketMode", config_parse_mode, &u->socket.socket_mode, "Socket" },
{ "KillMode", config_parse_kill_mode, &u->socket.kill_mode, "Socket" },
{ "Accept", config_parse_bool, &u->socket.accept, "Socket" },
+ { "MaxConnections", config_parse_unsigned, &u->socket.max_connections, "Socket" },
+ { "KeepAlive", config_parse_bool, &u->socket.keep_alive, "Socket" },
+ { "Priority", config_parse_int, &u->socket.priority, "Socket" },
+ { "ReceiveBuffer", config_parse_size, &u->socket.receive_buffer, "Socket" },
+ { "SendBuffer", config_parse_size, &u->socket.send_buffer, "Socket" },
+ { "IPTOS", config_parse_ip_tos, &u->socket.ip_tos, "Socket" },
+ { "IPTTL", config_parse_int, &u->socket.ip_ttl, "Socket" },
+ { "Mark", config_parse_int, &u->socket.mark, "Socket" },
+ { "PipeSize", config_parse_size, &u->socket.pipe_size, "Socket" },
+ { "FreeBind", config_parse_bool, &u->socket.free_bind, "Socket" },
EXEC_CONTEXT_CONFIG_ITEMS(u->socket.exec_context, "Socket"),
{ "What", config_parse_string, &u->mount.parameters_fragment.what, "Mount" },
{ "Type", config_parse_string, &u->mount.parameters_fragment.fstype, "Mount" },
{ "TimeoutSec", config_parse_usec, &u->mount.timeout_usec, "Mount" },
{ "KillMode", config_parse_kill_mode, &u->mount.kill_mode, "Mount" },
+ { "DirectoryMode", config_parse_mode, &u->mount.directory_mode, "Mount" },
EXEC_CONTEXT_CONFIG_ITEMS(u->mount.exec_context, "Mount"),
{ "Where", config_parse_path, &u->automount.where, "Automount" },
+ { "DirectoryMode", config_parse_mode, &u->automount.directory_mode, "Automount" },
+
+ { "What", config_parse_path, &u->swap.parameters_fragment.what, "Swap" },
+ { "Priority", config_parse_int, &u->swap.parameters_fragment.priority, "Swap" },
- { "What", config_parse_path, &u->swap.parameters_fragment.what, "Swap" },
- { "Priority", config_parse_int, &u->swap.parameters_fragment.priority, "Swap" },
+ { "OnActive", config_parse_timer, &u->timer, "Timer" },
+ { "OnBoot", config_parse_timer, &u->timer, "Timer" },
+ { "OnStartup", config_parse_timer, &u->timer, "Timer" },
+ { "OnUnitActive", config_parse_timer, &u->timer, "Timer" },
+ { "OnUnitInactive", config_parse_timer, &u->timer, "Timer" },
+ { "Unit", config_parse_timer_unit, &u->timer, "Timer" },
+
+ { "PathExists", config_parse_path_spec, &u->path, "Path" },
+ { "PathChanged", config_parse_path_spec, &u->path, "Path" },
+ { "DirectoryNotEmpty", config_parse_path_spec, &u->path, "Path" },
+ { "Unit", config_parse_path_unit, &u->path, "Path" },
+
+ /* The [Install] section is ignored here. */
+ { "Alias", NULL, NULL, "Install" },
+ { "WantedBy", NULL, NULL, "Install" },
+ { "Also", NULL, NULL, "Install" },
{ NULL, NULL, NULL, NULL }
};
#undef EXEC_CONTEXT_CONFIG_ITEMS
- const char *sections[3];
- char *k;
+ const char *sections[4];
int r;
Set *symlink_names;
FILE *f = NULL;
sections[0] = "Unit";
sections[1] = section_table[u->meta.type];
- sections[2] = NULL;
+ sections[2] = "Install";
+ sections[3] = NULL;
if (!(symlink_names = set_new(string_hash_func, string_compare_func)))
return -ENOMEM;
} else {
char **p;
- STRV_FOREACH(p, u->meta.manager->unit_path) {
+ STRV_FOREACH(p, u->meta.manager->lookup_paths.unit_path) {
/* Instead of opening the path right away, we manually
* follow all symlinks and add their name to our unit
}
/* Now, parse the file contents */
- if ((r = config_parse(filename, f, sections, items, u)) < 0)
+ if ((r = config_parse(filename, f, sections, items, false, u)) < 0)
goto finish;
free(u->meta.fragment_path);
r = 0;
finish:
- while ((k = set_steal_first(symlink_names)))
- free(k);
-
- set_free(symlink_names);
+ set_free_free(symlink_names);
free(filename);
if (f)