#include #include #include #include #include #include #include #include #include #include #include #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 service_name; std::string exc_description; protected: service_load_exc(const std::string &serviceName, std::string &&desc) noexcept : service_name(serviceName), exc_description(std::move(desc)) { } }; class service_not_found : public service_load_exc { public: service_not_found(const std::string &serviceName) noexcept : service_load_exc(serviceName, "Service description not found.") { } }; class service_cyclic_dependency : public service_load_exc { public: service_cyclic_dependency(const std::string &serviceName) noexcept : service_load_exc(serviceName, "Has cyclic dependency.") { } }; class service_description_exc : public service_load_exc { public: service_description_exc(const std::string &serviceName, std::string &&extraInfo) noexcept : service_load_exc(serviceName, std::move(extraInfo)) { } }; namespace dinit_load { using string = std::string; using string_iterator = std::string::iterator; // exception thrown when encountering a syntax issue when reading a setting value class setting_exception { std::string info; public: setting_exception(const std::string &&exc_info) : info(std::move(exc_info)) { } std::string &get_info() { return info; } }; // Utility function to skip white space. Returns an iterator at the // first non-white-space position (or at end). inline string_iterator skipws(string_iterator i, string_iterator end) { using std::locale; using std::isspace; while (i != end) { if (! isspace(*i, locale::classic())) { break; } ++i; } 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) { using std::locale; using std::ctype; using std::use_facet; const ctype & facet = use_facet >(locale::classic()); string rval; // Allow alphabetical characters, and dash (-) in setting name while (i != end && (*i == '-' || *i == '.' || facet.is(ctype::alpha, *i))) { rval += *i; ++i; } return rval; } // 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 // the end of the value and the beginning of a comment (it should be preceded by // whitespace). // // Part of a value may be quoted using double quote marks, which prevents collapse // of whitespace and interpretation of most special characters (the quote marks will // not be considered part of the value). A backslash can precede a character (such // 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 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 (not including newline character if any) // part_positions - list of 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, std::list> * part_positions = nullptr) { using std::locale; using std::isspace; i = skipws(i, end); string rval; bool new_part = true; int part_start; while (i != end) { char c = *i; if (c == '\"') { if (new_part) { part_start = rval.length(); new_part = false; } // quoted string ++i; while (i != end) { c = *i; if (c == '\"') break; else if (c == '\\') { // A backslash escapes the following character. ++i; if (i != end) { c = *i; rval += c; } else { throw setting_exception("Line end follows backslash escape character (`\\')"); } } else { rval += c; } ++i; } if (i == end) { // String wasn't terminated throw setting_exception("Unterminated quoted string"); } } else if (c == '\\') { if (new_part) { part_start = rval.length(); new_part = false; } // A backslash escapes the next character ++i; if (i != end) { rval += *i; } else { throw setting_exception("Backslash escape (`\\') not followed by character"); } } else if (isspace(c, locale::classic())) { if (! new_part && part_positions != nullptr) { part_positions->emplace_back(part_start, rval.length()); new_part = true; } i = skipws(i, end); if (i == end) break; if (*i == '#') break; // comment rval += ' '; // collapse ws to a single space continue; } else if (c == '#') { // Possibly intended a comment; we require leading whitespace to reduce occurrence of accidental // comments in setting values. throw setting_exception("hashmark (`#') comment must be separated from setting value by whitespace"); } else { if (new_part) { part_start = rval.length(); new_part = false; } rval += c; } ++i; } // Got to end: if (part_positions != nullptr) { part_positions->emplace_back(part_start, rval.length()); } 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 ¶m, 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::max() <= (uintmax_t)std::numeric_limits::max(), "uid_t is too large"); unsigned long long v = std::stoull(param, &ind, 0); if (v > static_cast(std::numeric_limits::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 ¶m, 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::max() <= (uintmax_t)std::numeric_limits::max(), "gid_t is too large"); unsigned long long v = std::stoull(param, &ind, 0); if (v > static_cast(std::numeric_limits::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 ¶mval, 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::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 ¶m, const std::string &service_name, unsigned long long max = std::numeric_limits::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 &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::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::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 (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 // // May throw service load exceptions or I/O exceptions if enabled on stream. template void process_service_file(string name, std::istream &service_file, T func) { string line; while (getline(service_file, line)) { string::iterator i = line.begin(); string::iterator end = line.end(); i = skipws(i, end); if (i != end) { if (*i == '#') { continue; // comment line } string setting = read_setting_name(i, end); i = skipws(i, end); if (i == end || (*i != '=' && *i != ':')) { throw service_description_exc(name, "Badly formed line."); } i = skipws(++i, end); func(line, setting, i, end); } } } // A wrapper type for service parameters. It is parameterised by dependency type. template class service_settings_wrapper { template using pair = std::pair; template using list = std::list; public: string command; list> command_offsets; string stop_command; list> 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 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 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 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> 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> 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::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::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;