Fix setting group when only uid specified
[oweals/dinit.git] / src / includes / load-service.h
index b27f9cd9ead41ef1fbed9b5f06d9467efea0f09d..8363f3b38cef73ed08443990b12179fce8cdd398 100644 (file)
@@ -1,16 +1,63 @@
 #include <iostream>
 #include <list>
+#include <limits>
+#include <csignal>
+#include <cstring>
+#include <utility>
+
+#include <sys/types.h>
+#include <sys/time.h>
+#include <sys/resource.h>
+#include <grp.h>
+#include <pwd.h>
+
+#include "dinit-utmp.h"
+#include "dinit-util.h"
+
+struct service_flags_t
+{
+    // on-start flags:
+    bool rw_ready : 1;  // file system should be writable once this service starts
+    bool log_ready : 1; // syslog should be available once this service starts
+
+    // Other service options flags:
+    bool no_sigterm : 1;  // do not send SIGTERM
+    bool runs_on_console : 1;  // run "in the foreground"
+    bool starts_on_console : 1; // starts in the foreground
+    bool shares_console : 1;    // run on console, but not exclusively
+    bool pass_cs_fd : 1;  // pass this service a control socket connection via fd
+    bool start_interruptible : 1; // the startup of this service process is ok to interrupt with SIGINT
+    bool skippable : 1;   // if interrupted the service is skipped (scripted services)
+    bool signal_process_only : 1;  // signal the session process, not the whole group
+
+    service_flags_t() noexcept : rw_ready(false), log_ready(false), no_sigterm(false),
+            runs_on_console(false), starts_on_console(false), shares_console(false),
+            pass_cs_fd(false), start_interruptible(false), skippable(false), signal_process_only(false)
+    {
+    }
+};
+
+// Resource limits for a particular service & particular resource
+struct service_rlimits
+{
+    int resource_id; // RLIMIT_xxx identifying resource
+    bool soft_set : 1;
+    bool hard_set : 1;
+    struct rlimit limits;
+
+    service_rlimits(int id) : resource_id(id), soft_set(0), hard_set(0), limits({0,0}) { }
+};
 
 // Exception while loading a service
 class service_load_exc
 {
     public:
-    std::string serviceName;
-    std::string excDescription;
+    std::string service_name;
+    std::string exc_description;
 
     protected:
     service_load_exc(const std::string &serviceName, std::string &&desc) noexcept
-        : serviceName(serviceName), excDescription(std::move(desc))
+        : service_name(serviceName), exc_description(std::move(desc))
     {
     }
 };
@@ -80,6 +127,18 @@ inline string_iterator skipws(string_iterator i, string_iterator end)
     return i;
 }
 
+// Convert a signal name to the corresponding signal number
+inline int signal_name_to_number(std::string &signame)
+{
+    if (signame == "HUP") return SIGHUP;
+    if (signame == "INT") return SIGINT;
+    if (signame == "QUIT") return SIGQUIT;
+    if (signame == "USR1") return SIGUSR1;
+    if (signame == "USR2") return SIGUSR2;
+    if (signame == "KILL") return SIGKILL;
+    return -1;
+}
+
 // Read a setting name.
 inline string read_setting_name(string_iterator & i, string_iterator end)
 {
@@ -98,7 +157,7 @@ inline string read_setting_name(string_iterator & i, string_iterator end)
     return rval;
 }
 
-// Read a setting value
+// Read a setting value.
 //
 // In general a setting value is a single-line string. It may contain multiple parts
 // separated by white space (which is normally collapsed). A hash mark - # - denotes
@@ -111,13 +170,14 @@ inline string read_setting_name(string_iterator & i, string_iterator end)
 // as '#' or '"' or another backslash) to remove its special meaning. Newline
 // characters are not allowed in values and cannot be quoted.
 //
-// This function expects the string to be in an ASCII-compatible, single byte
-// encoding (the "classic" locale).
+// This function expects the string to be in an ASCII-compatible encoding (the "classic" locale).
+//
+// Throws setting_exception on error.
 //
 // Params:
 //    service_name - the name of the service to which the setting applies
 //    i  -  reference to string iterator through the line
-//    end -   iterator at end of line
+//    end -   iterator at end of line (not including newline character if any)
 //    part_positions -  list of <int,int> to which the position of each setting value
 //                      part will be added as [start,end). May be null.
 inline string read_setting_value(string_iterator & i, string_iterator end,
@@ -144,19 +204,16 @@ inline string read_setting_value(string_iterator & i, string_iterator end,
             while (i != end) {
                 c = *i;
                 if (c == '\"') break;
-                if (c == '\n') {
-                    throw setting_exception("Line end inside quoted string");
-                }
                 else if (c == '\\') {
                     // A backslash escapes the following character.
                     ++i;
                     if (i != end) {
                         c = *i;
-                        if (c == '\n') {
-                            throw setting_exception("Line end follows backslash escape character (`\\')");
-                        }
                         rval += c;
                     }
+                    else {
+                        throw setting_exception("Line end follows backslash escape character (`\\')");
+                    }
                 }
                 else {
                     rval += c;
@@ -216,13 +273,261 @@ inline string read_setting_value(string_iterator & i, string_iterator end,
     return rval;
 }
 
+// Parse a userid parameter which may be a numeric user ID or a username. If a name, the
+// userid is looked up via the system user database (getpwnam() function). In this case,
+// the associated group is stored in the location specified by the group_p parameter if
+// it is not null.
+inline uid_t parse_uid_param(const std::string &param, const std::string &service_name, const char *setting_name, gid_t *group_p)
+{
+    const char * uid_err_msg = "Specified user id contains invalid numeric characters "
+            "or is outside allowed range.";
+
+    // Could be a name or a numeric id. But we should assume numeric first, just in case
+    // a user manages to give themselves a username that parses as a number.
+    std::size_t ind = 0;
+    try {
+        // POSIX does not specify whether uid_t is a signed or unsigned type, but regardless
+        // is is probably safe to assume that valid values are positive. We'll also assert
+        // that the value range fits within "unsigned long long" since it seems unlikely
+        // that would ever not be the case.
+        static_assert((uintmax_t)std::numeric_limits<uid_t>::max()
+                <= (uintmax_t)std::numeric_limits<unsigned long long>::max(), "uid_t is too large");
+        unsigned long long v = std::stoull(param, &ind, 0);
+        if (v > static_cast<unsigned long long>(std::numeric_limits<uid_t>::max())
+                || ind != param.length()) {
+            throw service_description_exc(service_name, std::string(setting_name) + ": " + uid_err_msg);
+        }
+        return v;
+    }
+    catch (std::out_of_range &exc) {
+        throw service_description_exc(service_name, uid_err_msg);
+    }
+    catch (std::invalid_argument &exc) {
+        // Ok, so it doesn't look like a number: proceed...
+    }
+
+    errno = 0;
+    struct passwd * pwent = getpwnam(param.c_str());
+    if (pwent == nullptr) {
+        // Maybe an error, maybe just no entry.
+        if (errno == 0) {
+            throw service_description_exc(service_name, std::string(setting_name) + ": Specified user \"" + param
+                    + "\" does not exist in system database.");
+        }
+        else {
+            throw service_description_exc(service_name, std::string("Error accessing user database: ")
+                    + strerror(errno));
+        }
+    }
+
+    if (group_p) {
+        *group_p = pwent->pw_gid;
+    }
+
+    return pwent->pw_uid;
+}
+
+inline gid_t parse_gid_param(const std::string &param, const char *setting_name, const std::string &service_name)
+{
+    const char * gid_err_msg = "Specified group id contains invalid numeric characters or is "
+            "outside allowed range.";
+
+    // Could be a name or a numeric id. But we should assume numeric first, just in case
+    // a user manages to give themselves a username that parses as a number.
+    std::size_t ind = 0;
+    try {
+        // POSIX does not specify whether uid_t is an signed or unsigned, but regardless
+        // is is probably safe to assume that valid values are positive. We'll also assume
+        // that the value range fits with "unsigned long long" since it seems unlikely
+        // that would ever not be the case.
+        static_assert((uintmax_t)std::numeric_limits<gid_t>::max()
+                <= (uintmax_t)std::numeric_limits<unsigned long long>::max(), "gid_t is too large");
+        unsigned long long v = std::stoull(param, &ind, 0);
+        if (v > static_cast<unsigned long long>(std::numeric_limits<gid_t>::max())
+                || ind != param.length()) {
+            throw service_description_exc(service_name, std::string(setting_name) + ": " + gid_err_msg);
+        }
+        return v;
+    }
+    catch (std::out_of_range &exc) {
+        throw service_description_exc(service_name, std::string(setting_name) + ": " + gid_err_msg);
+    }
+    catch (std::invalid_argument &exc) {
+        // Ok, so it doesn't look like a number: proceed...
+    }
+
+    errno = 0;
+    struct group * grent = getgrnam(param.c_str());
+    if (grent == nullptr) {
+        // Maybe an error, maybe just no entry.
+        if (errno == 0) {
+            throw service_description_exc(service_name, std::string(setting_name) + ": Specified group \"" + param
+                    + "\" does not exist in system database.");
+        }
+        else {
+            throw service_description_exc(service_name, std::string("Error accessing group database: ")
+                    + strerror(errno));
+        }
+    }
+
+    return grent->gr_gid;
+}
+
+// Parse a time, specified as a decimal number of seconds (with optional fractional component after decimal
+// point or decimal comma).
+inline void parse_timespec(const std::string &paramval, const std::string &servicename,
+        const char * paramname, timespec &ts)
+{
+    decltype(ts.tv_sec) isec = 0;
+    decltype(ts.tv_nsec) insec = 0;
+    auto max_secs = std::numeric_limits<decltype(isec)>::max() / 10;
+    auto len = paramval.length();
+    decltype(len) i;
+    for (i = 0; i < len; i++) {
+        char ch = paramval[i];
+        if (ch == '.' || ch == ',') {
+            i++;
+            break;
+        }
+        if (ch < '0' || ch > '9') {
+            throw service_description_exc(servicename, std::string("Bad value for ") + paramname);
+        }
+        // check for overflow
+        if (isec >= max_secs) {
+           throw service_description_exc(servicename, std::string("Too-large value for ") + paramname);
+        }
+        isec *= 10;
+        isec += ch - '0';
+    }
+    decltype(insec) insec_m = 100000000; // 10^8
+    for ( ; i < len; i++) {
+        char ch = paramval[i];
+        if (ch < '0' || ch > '9') {
+            throw service_description_exc(servicename, std::string("Bad value for ") + paramname);
+        }
+        insec += (ch - '0') * insec_m;
+        insec_m /= 10;
+    }
+    ts.tv_sec = isec;
+    ts.tv_nsec = insec;
+}
+
+// Parse an unsigned numeric parameter value
+inline unsigned long long parse_unum_param(const std::string &param, const std::string &service_name,
+        unsigned long long max = std::numeric_limits<unsigned long long>::max())
+{
+    const char * num_err_msg = "Specified value contains invalid numeric characters or is outside "
+            "allowed range.";
+
+    std::size_t ind = 0;
+    try {
+        unsigned long long v = std::stoull(param, &ind, 0);
+        if (v > max || ind != param.length()) {
+            throw service_description_exc(service_name, num_err_msg);
+        }
+        return v;
+    }
+    catch (std::out_of_range &exc) {
+        throw service_description_exc(service_name, num_err_msg);
+    }
+    catch (std::invalid_argument &exc) {
+        throw service_description_exc(service_name, num_err_msg);
+    }
+}
+
+// In a vector, find or create rlimits for a particular resource type.
+inline service_rlimits &find_rlimits(std::vector<service_rlimits> &all_rlimits, int resource_id)
+{
+    for (service_rlimits &limits : all_rlimits) {
+        if (limits.resource_id == resource_id) {
+            return limits;
+        }
+    }
+
+    all_rlimits.emplace_back(resource_id);
+    return all_rlimits.back();
+}
+
+// Parse resource limits setting (can specify both hard and soft limit).
+inline void parse_rlimit(const std::string &line, const std::string &service_name, const char *param_name,
+        service_rlimits &rlimit)
+{
+    // Examples:
+    // 4:5 - soft:hard limits both set
+    // 4:-   soft set, hard set to unlimited
+    // 4:    soft set, hard limit unchanged
+    // 4     soft and hard limit set to same limit
+
+    if (line.empty()) {
+        throw service_description_exc(service_name, std::string(param_name) + ": Bad value.");
+    }
+
+    const char *cline = line.c_str();
+    rlimit.hard_set = rlimit.soft_set = false;
+
+    try {
+        const char * index = cline;
+        errno = 0;
+        if (cline[0] != ':') {
+            rlimit.soft_set = true;
+            if (cline[0] == '-') {
+                rlimit.limits.rlim_cur = RLIM_INFINITY;
+                index = cline + 1;
+            }
+            else {
+                char *nindex;
+                unsigned long long limit = std::strtoull(cline, &nindex, 0);
+                index = nindex;
+                if (errno == ERANGE || limit > std::numeric_limits<rlim_t>::max()) throw std::out_of_range("");
+                if (index == cline) throw std::invalid_argument("");
+                rlimit.limits.rlim_cur = limit;
+            }
+
+            if (*index == 0) {
+                rlimit.hard_set = true;
+                rlimit.limits.rlim_max = rlimit.limits.rlim_cur;
+                return;
+            }
+
+            if (*index != ':') {
+                throw service_description_exc(service_name, std::string(param_name) + ": Bad value.");
+            }
+        }
+
+        index++;
+        if (*index == 0) return;
+
+        if (*index == '-') {
+            rlimit.limits.rlim_max = RLIM_INFINITY;
+            if (index[1] != 0) {
+                throw service_description_exc(service_name, std::string(param_name) + ": Bad value.");
+            }
+        }
+        else {
+            const char *hard_start = index;
+            char *nindex;
+            unsigned long long limit = std::strtoull(cline, &nindex, 0);
+            index = nindex;
+            if (errno == ERANGE || limit > std::numeric_limits<rlim_t>::max()) throw std::out_of_range("");
+            if (index == hard_start) throw std::invalid_argument("");
+            rlimit.limits.rlim_max = limit;
+        }
+    }
+    catch (std::invalid_argument &exc) {
+        throw service_description_exc(service_name, std::string(param_name) + ": Bad value.");
+    }
+    catch (std::out_of_range &exc) {
+        throw service_description_exc(service_name, std::string(param_name) + ": Too-large value.");
+    }
+}
+
 // Process an opened service file, line by line.
 //    name - the service name
 //    service_file - the service file input stream
 //    func - a function of the form:
 //             void(string &line, string &setting, string_iterator i, string_iterator end)
 //           Called with:
-//               line - the complete line
+//               line - the complete line (excluding newline character)
 //               setting - the setting name, from the beginning of the line
 //               i - iterator at the beginning of the setting value
 //               end - iterator marking the end of the line
@@ -254,6 +559,355 @@ void process_service_file(string name, std::istream &service_file, T func)
     }
 }
 
+// A wrapper type for service parameters. It is parameterised by dependency type.
+template <class dep_type>
+class service_settings_wrapper
+{
+    template <typename A, typename B> using pair = std::pair<A,B>;
+    template <typename A> using list = std::list<A>;
+
+    public:
+
+    string command;
+    list<pair<unsigned,unsigned>> command_offsets;
+    string stop_command;
+    list<pair<unsigned,unsigned>> stop_command_offsets;
+    string working_dir;
+    string pid_file;
+    string env_file;
+
+    bool do_sub_vars = false;
+
+    service_type_t service_type = service_type_t::PROCESS;
+    std::list<dep_type> depends;
+    string logfile;
+    service_flags_t onstart_flags;
+    int term_signal = -1;  // additional termination signal
+    bool auto_restart = false;
+    bool smooth_recovery = false;
+    string socket_path;
+    int socket_perms = 0666;
+    // Note: Posix allows that uid_t and gid_t may be unsigned types, but eg chown uses -1 as an
+    // invalid value, so it's safe to assume that we can do the same:
+    uid_t socket_uid = -1;
+    gid_t socket_uid_gid = -1;  // primary group of socket user if known
+    gid_t socket_gid = -1;
+    // Restart limit interval / count; default is 10 seconds, 3 restarts:
+    timespec restart_interval = { .tv_sec = 10, .tv_nsec = 0 };
+    int max_restarts = 3;
+    timespec restart_delay = { .tv_sec = 0, .tv_nsec = 200000000 };
+    timespec stop_timeout = { .tv_sec = 10, .tv_nsec = 0 };
+    timespec start_timeout = { .tv_sec = 60, .tv_nsec = 0 };
+    std::vector<service_rlimits> rlimits;
+
+    int readiness_fd = -1;      // readiness fd in service process
+    std::string readiness_var;  // environment var to hold readiness fd
+
+    uid_t run_as_uid = -1;
+    gid_t run_as_uid_gid = -1; // primary group of "run as" uid if known
+    gid_t run_as_gid = -1;
+
+    string chain_to_name;
+
+    #if USE_UTMPX
+    char inittab_id[sizeof(utmpx().ut_id)] = {0};
+    char inittab_line[sizeof(utmpx().ut_line)] = {0};
+    #endif
+
+    // Finalise settings (after processing all setting lines)
+    void finalise()
+    {
+        // If socket_gid hasn't been explicitly set, but the socket_uid was specified as a name (and
+        // we therefore recovered the primary group), use the primary group of the specified user.
+        if (socket_gid == (gid_t)-1) socket_gid = socket_uid_gid;
+        // likewise for "run as" gid/uid.
+        if (run_as_gid == (gid_t)-1) run_as_gid = run_as_uid_gid;
+    }
+};
+
+// Process a service description line. In general, parse the setting value and record the parsed value
+// in a service settings wrapper object. Errors will be reported via service_description_exc exception.
+//
+// type parameters:
+//     settings_wrapper : wrapper for service settings
+//     load_service_t   : type of load service function/lambda (see below)
+//     process_dep_dir_t : type of process_dep_dir funciton/lambda (see below)
+//
+// parameters:
+//     settings     : wrapper object for service settings
+//     name         : name of the service being processed
+//     line         : the current line of the service description file
+//     setting      : the name of the setting (from the beginning of line)
+//     i            : iterator at beginning of setting value (including whitespace)
+//     end          : iterator at end of line
+//     load_service : function to load a service
+//                      arguments:  const char *service_name
+//                      return: a value that can be used (with a dependency type) to construct a dependency
+//                              in the 'depends' vector within the 'settings' object
+//     process_dep_dir : function to process a dependency directory
+//                      arguments: decltype(settings.depends) &dependencies
+//                                 const string &waitsford - directory as specified in parameter
+//                                 dependency_type dep_type - type of dependency to add
+template <typename settings_wrapper,
+    typename load_service_t,
+    typename process_dep_dir_t>
+void process_service_line(settings_wrapper &settings, const char *name, string &line, string &setting,
+        string::iterator &i, string::iterator &end, load_service_t load_service,
+        process_dep_dir_t process_dep_dir)
+{
+    if (setting == "command") {
+        settings.command = read_setting_value(i, end, &settings.command_offsets);
+    }
+    else if (setting == "working-dir") {
+        settings.working_dir = read_setting_value(i, end, nullptr);
+    }
+    else if (setting == "env-file") {
+        settings.env_file = read_setting_value(i, end, nullptr);
+    }
+    else if (setting == "socket-listen") {
+        settings.socket_path = read_setting_value(i, end, nullptr);
+    }
+    else if (setting == "socket-permissions") {
+        string sock_perm_str = read_setting_value(i, end, nullptr);
+        std::size_t ind = 0;
+        try {
+            settings.socket_perms = std::stoi(sock_perm_str, &ind, 8);
+            if (ind != sock_perm_str.length()) {
+                throw std::logic_error("");
+            }
+        }
+        catch (std::logic_error &exc) {
+            throw service_description_exc(name, "socket-permissions: Badly-formed or "
+                    "out-of-range numeric value");
+        }
+    }
+    else if (setting == "socket-uid") {
+        string sock_uid_s = read_setting_value(i, end, nullptr);
+        settings.socket_uid = parse_uid_param(sock_uid_s, name, "socket-uid", &settings.socket_uid_gid);
+    }
+    else if (setting == "socket-gid") {
+        string sock_gid_s = read_setting_value(i, end, nullptr);
+        settings.socket_gid = parse_gid_param(sock_gid_s, "socket-gid", name);
+    }
+    else if (setting == "stop-command") {
+        settings.stop_command = read_setting_value(i, end, &settings.stop_command_offsets);
+    }
+    else if (setting == "pid-file") {
+        settings.pid_file = read_setting_value(i, end);
+    }
+    else if (setting == "depends-on") {
+        string dependency_name = read_setting_value(i, end);
+        settings.depends.emplace_back(load_service(dependency_name.c_str()), dependency_type::REGULAR);
+    }
+    else if (setting == "depends-ms") {
+        string dependency_name = read_setting_value(i, end);
+        settings.depends.emplace_back(load_service(dependency_name.c_str()), dependency_type::MILESTONE);
+    }
+    else if (setting == "waits-for") {
+        string dependency_name = read_setting_value(i, end);
+        settings.depends.emplace_back(load_service(dependency_name.c_str()), dependency_type::WAITS_FOR);
+    }
+    else if (setting == "waits-for.d") {
+        string waitsford = read_setting_value(i, end);
+        process_dep_dir(settings.depends, waitsford, dependency_type::WAITS_FOR);
+    }
+    else if (setting == "logfile") {
+        settings.logfile = read_setting_value(i, end);
+    }
+    else if (setting == "restart") {
+        string restart = read_setting_value(i, end);
+        settings.auto_restart = (restart == "yes" || restart == "true");
+    }
+    else if (setting == "smooth-recovery") {
+        string recovery = read_setting_value(i, end);
+        settings.smooth_recovery = (recovery == "yes" || recovery == "true");
+    }
+    else if (setting == "type") {
+        string type_str = read_setting_value(i, end);
+        if (type_str == "scripted") {
+            settings.service_type = service_type_t::SCRIPTED;
+        }
+        else if (type_str == "process") {
+            settings.service_type = service_type_t::PROCESS;
+        }
+        else if (type_str == "bgprocess") {
+            settings.service_type = service_type_t::BGPROCESS;
+        }
+        else if (type_str == "internal") {
+            settings.service_type = service_type_t::INTERNAL;
+        }
+        else {
+            throw service_description_exc(name, "Service type must be one of: \"scripted\","
+                " \"process\", \"bgprocess\" or \"internal\"");
+        }
+    }
+    else if (setting == "options") {
+        std::list<std::pair<unsigned,unsigned>> indices;
+        string onstart_cmds = read_setting_value(i, end, &indices);
+        for (auto indexpair : indices) {
+            string option_txt = onstart_cmds.substr(indexpair.first,
+                    indexpair.second - indexpair.first);
+            if (option_txt == "starts-rwfs") {
+                settings.onstart_flags.rw_ready = true;
+            }
+            else if (option_txt == "starts-log") {
+                settings.onstart_flags.log_ready = true;
+            }
+            else if (option_txt == "no-sigterm") {
+                settings.onstart_flags.no_sigterm = true;
+            }
+            else if (option_txt == "runs-on-console") {
+                settings.onstart_flags.runs_on_console = true;
+                // A service that runs on the console necessarily starts on console:
+                settings.onstart_flags.starts_on_console = true;
+                settings.onstart_flags.shares_console = false;
+            }
+            else if (option_txt == "starts-on-console") {
+                settings.onstart_flags.starts_on_console = true;
+                settings.onstart_flags.shares_console = false;
+            }
+            else if (option_txt == "shares-console") {
+                settings.onstart_flags.shares_console = true;
+                settings.onstart_flags.runs_on_console = false;
+                settings.onstart_flags.starts_on_console = false;
+            }
+            else if (option_txt == "pass-cs-fd") {
+                settings.onstart_flags.pass_cs_fd = true;
+            }
+            else if (option_txt == "start-interruptible") {
+                settings.onstart_flags.start_interruptible = true;
+            }
+            else if (option_txt == "skippable") {
+                settings.onstart_flags.skippable = true;
+            }
+            else if (option_txt == "signal-process-only") {
+                settings.onstart_flags.signal_process_only = true;
+            }
+            else {
+                throw service_description_exc(name, "Unknown option: " + option_txt);
+            }
+        }
+    }
+    else if (setting == "load-options") {
+        std::list<std::pair<unsigned,unsigned>> indices;
+        string load_opts = read_setting_value(i, end, &indices);
+        for (auto indexpair : indices) {
+            string option_txt = load_opts.substr(indexpair.first,
+                    indexpair.second - indexpair.first);
+            if (option_txt == "sub-vars") {
+                // substitute environment variables in command line
+                settings.do_sub_vars = true;
+            }
+            else if (option_txt == "no-sub-vars") {
+                settings.do_sub_vars = false;
+            }
+            else {
+                throw service_description_exc(name, "Unknown load option: " + option_txt);
+            }
+        }
+    }
+    else if (setting == "term-signal" || setting == "termsignal") {
+        // Note: "termsignal" supported for legacy reasons.
+        string signame = read_setting_value(i, end, nullptr);
+        int signo = signal_name_to_number(signame);
+        if (signo == -1) {
+            throw service_description_exc(name, "Unknown/unsupported termination signal: "
+                    + signame);
+        }
+        else {
+            settings.term_signal = signo;
+        }
+    }
+    else if (setting == "restart-limit-interval") {
+        string interval_str = read_setting_value(i, end, nullptr);
+        parse_timespec(interval_str, name, "restart-limit-interval", settings.restart_interval);
+    }
+    else if (setting == "restart-delay") {
+        string rsdelay_str = read_setting_value(i, end, nullptr);
+        parse_timespec(rsdelay_str, name, "restart-delay", settings.restart_delay);
+    }
+    else if (setting == "restart-limit-count") {
+        string limit_str = read_setting_value(i, end, nullptr);
+        settings.max_restarts = parse_unum_param(limit_str, name, std::numeric_limits<int>::max());
+    }
+    else if (setting == "stop-timeout") {
+        string stoptimeout_str = read_setting_value(i, end, nullptr);
+        parse_timespec(stoptimeout_str, name, "stop-timeout", settings.stop_timeout);
+    }
+    else if (setting == "start-timeout") {
+        string starttimeout_str = read_setting_value(i, end, nullptr);
+        parse_timespec(starttimeout_str, name, "start-timeout", settings.start_timeout);
+    }
+    else if (setting == "run-as") {
+        string run_as_str = read_setting_value(i, end, nullptr);
+        settings.run_as_uid = parse_uid_param(run_as_str, name, "run-as", &settings.run_as_uid_gid);
+    }
+    else if (setting == "chain-to") {
+        settings.chain_to_name = read_setting_value(i, end, nullptr);
+    }
+    else if (setting == "ready-notification") {
+        string notify_setting = read_setting_value(i, end, nullptr);
+        if (starts_with(notify_setting, "pipefd:")) {
+            settings.readiness_fd = parse_unum_param(notify_setting.substr(7 /* len 'pipefd:' */),
+                    name, std::numeric_limits<int>::max());
+        }
+        else if (starts_with(notify_setting, "pipevar:")) {
+            settings.readiness_var = notify_setting.substr(8 /* len 'pipevar:' */);
+            if (settings.readiness_var.empty()) {
+                throw service_description_exc(name, "Invalid pipevar variable name "
+                        "in ready-notification");
+            }
+        }
+        else {
+            throw service_description_exc(name, "Unknown ready-notification setting: "
+                    + notify_setting);
+        }
+    }
+    else if (setting == "inittab-id") {
+        string inittab_setting = read_setting_value(i, end, nullptr);
+        #if USE_UTMPX
+        if (inittab_setting.length() > sizeof(settings.inittab_id)) {
+            throw service_description_exc(name, "inittab-id setting is too long");
+        }
+        strncpy(settings.inittab_id, inittab_setting.c_str(), sizeof(settings.inittab_id));
+        #endif
+    }
+    else if (setting == "inittab-line") {
+        string inittab_setting = read_setting_value(i, end, nullptr);
+        #if USE_UTMPX
+        if (inittab_setting.length() > sizeof(settings.inittab_line)) {
+            throw service_description_exc(name, "inittab-line setting is too long");
+        }
+        strncpy(settings.inittab_line, inittab_setting.c_str(), sizeof(settings.inittab_line));
+        #endif
+    }
+    else if (setting == "rlimit-nofile") {
+        string nofile_setting = read_setting_value(i, end, nullptr);
+        service_rlimits &nofile_limits = find_rlimits(settings.rlimits, RLIMIT_NOFILE);
+        parse_rlimit(line, name, "rlimit-nofile", nofile_limits);
+    }
+    else if (setting == "rlimit-core") {
+        string nofile_setting = read_setting_value(i, end, nullptr);
+        service_rlimits &nofile_limits = find_rlimits(settings.rlimits, RLIMIT_CORE);
+        parse_rlimit(line, name, "rlimit-core", nofile_limits);
+    }
+    else if (setting == "rlimit-data") {
+        string nofile_setting = read_setting_value(i, end, nullptr);
+        service_rlimits &nofile_limits = find_rlimits(settings.rlimits, RLIMIT_DATA);
+        parse_rlimit(line, name, "rlimit-data", nofile_limits);
+    }
+    else if (setting == "rlimit-addrspace") {
+        #if defined(RLIMIT_AS)
+            string nofile_setting = read_setting_value(i, end, nullptr);
+            service_rlimits &nofile_limits = find_rlimits(settings.rlimits, RLIMIT_AS);
+            parse_rlimit(line, name, "rlimit-addrspace", nofile_limits);
+        #endif
+    }
+    else {
+        throw service_description_exc(name, "Unknown setting: '" + setting + "'.");
+    }
+}
+
 } // namespace dinit_load
 
 using dinit_load::process_service_file;