9 #include <sys/resource.h>
13 struct service_flags_t
16 bool rw_ready : 1; // file system should be writable once this service starts
17 bool log_ready : 1; // syslog should be available once this service starts
19 // Other service options flags:
20 bool no_sigterm : 1; // do not send SIGTERM
21 bool runs_on_console : 1; // run "in the foreground"
22 bool starts_on_console : 1; // starts in the foreground
23 bool shares_console : 1; // run on console, but not exclusively
24 bool pass_cs_fd : 1; // pass this service a control socket connection via fd
25 bool start_interruptible : 1; // the startup of this service process is ok to interrupt with SIGINT
26 bool skippable : 1; // if interrupted the service is skipped (scripted services)
27 bool signal_process_only : 1; // signal the session process, not the whole group
29 service_flags_t() noexcept : rw_ready(false), log_ready(false), no_sigterm(false),
30 runs_on_console(false), starts_on_console(false), shares_console(false),
31 pass_cs_fd(false), start_interruptible(false), skippable(false), signal_process_only(false)
36 // Resource limits for a particular service & particular resource
37 struct service_rlimits
39 int resource_id; // RLIMIT_xxx identifying resource
44 service_rlimits(int id) : resource_id(id), soft_set(0), hard_set(0), limits({0,0}) { }
47 // Exception while loading a service
48 class service_load_exc
51 std::string service_name;
52 std::string exc_description;
55 service_load_exc(const std::string &serviceName, std::string &&desc) noexcept
56 : service_name(serviceName), exc_description(std::move(desc))
61 class service_not_found : public service_load_exc
64 service_not_found(const std::string &serviceName) noexcept
65 : service_load_exc(serviceName, "Service description not found.")
70 class service_cyclic_dependency : public service_load_exc
73 service_cyclic_dependency(const std::string &serviceName) noexcept
74 : service_load_exc(serviceName, "Has cyclic dependency.")
79 class service_description_exc : public service_load_exc
82 service_description_exc(const std::string &serviceName, std::string &&extraInfo) noexcept
83 : service_load_exc(serviceName, std::move(extraInfo))
88 namespace dinit_load {
90 using string = std::string;
91 using string_iterator = std::string::iterator;
93 // exception thrown when encountering a syntax issue when reading a setting value
94 class setting_exception
99 setting_exception(const std::string &&exc_info) : info(std::move(exc_info))
103 std::string &get_info()
110 // Utility function to skip white space. Returns an iterator at the
111 // first non-white-space position (or at end).
112 inline string_iterator skipws(string_iterator i, string_iterator end)
118 if (! isspace(*i, locale::classic())) {
126 // Convert a signal name to the corresponding signal number
127 inline int signal_name_to_number(std::string &signame)
129 if (signame == "HUP") return SIGHUP;
130 if (signame == "INT") return SIGINT;
131 if (signame == "QUIT") return SIGQUIT;
132 if (signame == "USR1") return SIGUSR1;
133 if (signame == "USR2") return SIGUSR2;
134 if (signame == "KILL") return SIGKILL;
138 // Read a setting name.
139 inline string read_setting_name(string_iterator & i, string_iterator end)
143 using std::use_facet;
145 const ctype<char> & facet = use_facet<ctype<char> >(locale::classic());
148 // Allow alphabetical characters, and dash (-) in setting name
149 while (i != end && (*i == '-' || *i == '.' || facet.is(ctype<char>::alpha, *i))) {
156 // Read a setting value.
158 // In general a setting value is a single-line string. It may contain multiple parts
159 // separated by white space (which is normally collapsed). A hash mark - # - denotes
160 // the end of the value and the beginning of a comment (it should be preceded by
163 // Part of a value may be quoted using double quote marks, which prevents collapse
164 // of whitespace and interpretation of most special characters (the quote marks will
165 // not be considered part of the value). A backslash can precede a character (such
166 // as '#' or '"' or another backslash) to remove its special meaning. Newline
167 // characters are not allowed in values and cannot be quoted.
169 // This function expects the string to be in an ASCII-compatible encoding (the "classic" locale).
171 // Throws setting_exception on error.
174 // service_name - the name of the service to which the setting applies
175 // i - reference to string iterator through the line
176 // end - iterator at end of line (not including newline character if any)
177 // part_positions - list of <int,int> to which the position of each setting value
178 // part will be added as [start,end). May be null.
179 inline string read_setting_value(string_iterator & i, string_iterator end,
180 std::list<std::pair<unsigned,unsigned>> * part_positions = nullptr)
188 bool new_part = true;
195 part_start = rval.length();
202 if (c == '\"') break;
203 else if (c == '\\') {
204 // A backslash escapes the following character.
211 throw setting_exception("Line end follows backslash escape character (`\\')");
220 // String wasn't terminated
221 throw setting_exception("Unterminated quoted string");
224 else if (c == '\\') {
226 part_start = rval.length();
229 // A backslash escapes the next character
235 throw setting_exception("Backslash escape (`\\') not followed by character");
238 else if (isspace(c, locale::classic())) {
239 if (! new_part && part_positions != nullptr) {
240 part_positions->emplace_back(part_start, rval.length());
245 if (*i == '#') break; // comment
246 rval += ' '; // collapse ws to a single space
250 // Possibly intended a comment; we require leading whitespace to reduce occurrence of accidental
251 // comments in setting values.
252 throw setting_exception("hashmark (`#') comment must be separated from setting value by whitespace");
256 part_start = rval.length();
265 if (part_positions != nullptr) {
266 part_positions->emplace_back(part_start, rval.length());
272 // Parse a userid parameter which may be a numeric user ID or a username. If a name, the
273 // userid is looked up via the system user database (getpwnam() function). In this case,
274 // the associated group is stored in the location specified by the group_p parameter iff
275 // it is not null and iff it contains the value -1.
276 inline uid_t parse_uid_param(const std::string ¶m, const std::string &service_name, gid_t *group_p)
278 const char * uid_err_msg = "Specified user id contains invalid numeric characters "
279 "or is outside allowed range.";
281 // Could be a name or a numeric id. But we should assume numeric first, just in case
282 // a user manages to give themselves a username that parses as a number.
285 // POSIX does not specify whether uid_t is an signed or unsigned, but regardless
286 // is is probably safe to assume that valid values are positive. We'll also assert
287 // that the value range fits within "unsigned long long" since it seems unlikely
288 // that would ever not be the case.
289 static_assert((uintmax_t)std::numeric_limits<uid_t>::max()
290 <= (uintmax_t)std::numeric_limits<unsigned long long>::max(), "uid_t is too large");
291 unsigned long long v = std::stoull(param, &ind, 0);
292 if (v > static_cast<unsigned long long>(std::numeric_limits<uid_t>::max())
293 || ind != param.length()) {
294 throw service_description_exc(service_name, uid_err_msg);
298 catch (std::out_of_range &exc) {
299 throw service_description_exc(service_name, uid_err_msg);
301 catch (std::invalid_argument &exc) {
302 // Ok, so it doesn't look like a number: proceed...
306 struct passwd * pwent = getpwnam(param.c_str());
307 if (pwent == nullptr) {
308 // Maybe an error, maybe just no entry.
310 throw service_description_exc(service_name, "Specified user \"" + param
311 + "\" does not exist in system database.");
314 throw service_description_exc(service_name, std::string("Error accessing user database: ")
319 if (group_p && *group_p != (gid_t)-1) {
320 *group_p = pwent->pw_gid;
323 return pwent->pw_uid;
326 inline gid_t parse_gid_param(const std::string ¶m, const std::string &service_name)
328 const char * gid_err_msg = "Specified group id contains invalid numeric characters or is "
329 "outside allowed range.";
331 // Could be a name or a numeric id. But we should assume numeric first, just in case
332 // a user manages to give themselves a username that parses as a number.
335 // POSIX does not specify whether uid_t is an signed or unsigned, but regardless
336 // is is probably safe to assume that valid values are positive. We'll also assume
337 // that the value range fits with "unsigned long long" since it seems unlikely
338 // that would ever not be the case.
339 static_assert((uintmax_t)std::numeric_limits<gid_t>::max()
340 <= (uintmax_t)std::numeric_limits<unsigned long long>::max(), "gid_t is too large");
341 unsigned long long v = std::stoull(param, &ind, 0);
342 if (v > static_cast<unsigned long long>(std::numeric_limits<gid_t>::max())
343 || ind != param.length()) {
344 throw service_description_exc(service_name, gid_err_msg);
348 catch (std::out_of_range &exc) {
349 throw service_description_exc(service_name, gid_err_msg);
351 catch (std::invalid_argument &exc) {
352 // Ok, so it doesn't look like a number: proceed...
356 struct group * grent = getgrnam(param.c_str());
357 if (grent == nullptr) {
358 // Maybe an error, maybe just no entry.
360 throw service_description_exc(service_name, "Specified group \"" + param
361 + "\" does not exist in system database.");
364 throw service_description_exc(service_name, std::string("Error accessing group database: ")
369 return grent->gr_gid;
372 // Parse a time, specified as a decimal number of seconds (with optional fractional component after decimal
373 // point or decimal comma).
374 inline void parse_timespec(const std::string ¶mval, const std::string &servicename,
375 const char * paramname, timespec &ts)
377 decltype(ts.tv_sec) isec = 0;
378 decltype(ts.tv_nsec) insec = 0;
379 auto max_secs = std::numeric_limits<decltype(isec)>::max() / 10;
380 auto len = paramval.length();
382 for (i = 0; i < len; i++) {
383 char ch = paramval[i];
384 if (ch == '.' || ch == ',') {
388 if (ch < '0' || ch > '9') {
389 throw service_description_exc(servicename, std::string("Bad value for ") + paramname);
391 // check for overflow
392 if (isec >= max_secs) {
393 throw service_description_exc(servicename, std::string("Too-large value for ") + paramname);
398 decltype(insec) insec_m = 100000000; // 10^8
399 for ( ; i < len; i++) {
400 char ch = paramval[i];
401 if (ch < '0' || ch > '9') {
402 throw service_description_exc(servicename, std::string("Bad value for ") + paramname);
404 insec += (ch - '0') * insec_m;
411 // Parse an unsigned numeric parameter value
412 inline unsigned long long parse_unum_param(const std::string ¶m, const std::string &service_name,
413 unsigned long long max = std::numeric_limits<unsigned long long>::max())
415 const char * num_err_msg = "Specified value contains invalid numeric characters or is outside "
420 unsigned long long v = std::stoull(param, &ind, 0);
421 if (v > max || ind != param.length()) {
422 throw service_description_exc(service_name, num_err_msg);
426 catch (std::out_of_range &exc) {
427 throw service_description_exc(service_name, num_err_msg);
429 catch (std::invalid_argument &exc) {
430 throw service_description_exc(service_name, num_err_msg);
434 // In a vector, find or create rlimits for a particular resource type.
435 inline service_rlimits &find_rlimits(std::vector<service_rlimits> &all_rlimits, int resource_id)
437 for (service_rlimits &limits : all_rlimits) {
438 if (limits.resource_id == resource_id) {
443 all_rlimits.emplace_back(resource_id);
444 return all_rlimits.back();
447 // Parse resource limits setting (can specify both hard and soft limit).
448 inline void parse_rlimit(const std::string &line, const std::string &service_name, const char *param_name,
449 service_rlimits &rlimit)
452 // 4:5 - soft:hard limits both set
453 // 4:- soft set, hard set to unlimited
454 // 4: soft set, hard limit unchanged
455 // 4 soft and hard limit set to same limit
458 throw service_description_exc(service_name, std::string("Bad value for ") + param_name);
461 const char *cline = line.c_str();
462 rlimit.hard_set = rlimit.soft_set = false;
465 const char * index = cline;
467 if (cline[0] != ':') {
468 rlimit.soft_set = true;
469 if (cline[0] == '-') {
470 rlimit.limits.rlim_cur = RLIM_INFINITY;
475 unsigned long long limit = std::strtoull(cline, &nindex, 0);
477 if (errno == ERANGE || limit > std::numeric_limits<rlim_t>::max()) throw std::out_of_range("");
478 if (index == cline) throw std::invalid_argument("");
479 rlimit.limits.rlim_cur = limit;
483 rlimit.hard_set = true;
484 rlimit.limits.rlim_max = rlimit.limits.rlim_cur;
489 throw service_description_exc(service_name, std::string("Bad value for ") + param_name);
494 if (*index == 0) return;
497 rlimit.limits.rlim_max = RLIM_INFINITY;
499 throw service_description_exc(service_name, std::string("Bad value for ") + param_name);
503 const char *hard_start = index;
505 unsigned long long limit = std::strtoull(cline, &nindex, 0);
507 if (errno == ERANGE || limit > std::numeric_limits<rlim_t>::max()) throw std::out_of_range("");
508 if (index == hard_start) throw std::invalid_argument("");
509 rlimit.limits.rlim_max = limit;
512 catch (std::invalid_argument &exc) {
513 throw service_description_exc(service_name, std::string("Bad value for ") + param_name);
515 catch (std::out_of_range &exc) {
516 throw service_description_exc(service_name, std::string("Too-large value for ") + param_name);
520 // Process an opened service file, line by line.
521 // name - the service name
522 // service_file - the service file input stream
523 // func - a function of the form:
524 // void(string &line, string &setting, string_iterator i, string_iterator end)
526 // line - the complete line (excluding newline character)
527 // setting - the setting name, from the beginning of the line
528 // i - iterator at the beginning of the setting value
529 // end - iterator marking the end of the line
531 // May throw service load exceptions or I/O exceptions if enabled on stream.
532 template <typename T>
533 void process_service_file(string name, std::istream &service_file, T func)
537 while (getline(service_file, line)) {
538 string::iterator i = line.begin();
539 string::iterator end = line.end();
544 continue; // comment line
546 string setting = read_setting_name(i, end);
548 if (i == end || (*i != '=' && *i != ':')) {
549 throw service_description_exc(name, "Badly formed line.");
551 i = skipws(++i, end);
553 func(line, setting, i, end);
558 } // namespace dinit_load
560 using dinit_load::process_service_file;