8 #include <sys/resource.h>
12 struct service_flags_t
15 bool rw_ready : 1; // file system should be writable once this service starts
16 bool log_ready : 1; // syslog should be available once this service starts
18 // Other service options flags:
19 bool no_sigterm : 1; // do not send SIGTERM
20 bool runs_on_console : 1; // run "in the foreground"
21 bool starts_on_console : 1; // starts in the foreground
22 bool shares_console : 1; // run on console, but not exclusively
23 bool pass_cs_fd : 1; // pass this service a control socket connection via fd
24 bool start_interruptible : 1; // the startup of this service process is ok to interrupt with SIGINT
25 bool skippable : 1; // if interrupted the service is skipped (scripted services)
26 bool signal_process_only : 1; // signal the session process, not the whole group
28 service_flags_t() noexcept : rw_ready(false), log_ready(false), no_sigterm(false),
29 runs_on_console(false), starts_on_console(false), shares_console(false),
30 pass_cs_fd(false), start_interruptible(false), skippable(false), signal_process_only(false)
35 // Resource limits for a particular service & particular resource
36 struct service_rlimits
38 int resource_id; // RLIMIT_xxx identifying resource
43 service_rlimits(int id) : resource_id(id), soft_set(0), hard_set(0), limits({0,0}) { }
46 // Exception while loading a service
47 class service_load_exc
50 std::string service_name;
51 std::string exc_description;
54 service_load_exc(const std::string &serviceName, std::string &&desc) noexcept
55 : service_name(serviceName), exc_description(std::move(desc))
60 class service_not_found : public service_load_exc
63 service_not_found(const std::string &serviceName) noexcept
64 : service_load_exc(serviceName, "Service description not found.")
69 class service_cyclic_dependency : public service_load_exc
72 service_cyclic_dependency(const std::string &serviceName) noexcept
73 : service_load_exc(serviceName, "Has cyclic dependency.")
78 class service_description_exc : public service_load_exc
81 service_description_exc(const std::string &serviceName, std::string &&extraInfo) noexcept
82 : service_load_exc(serviceName, std::move(extraInfo))
87 namespace dinit_load {
89 using string = std::string;
90 using string_iterator = std::string::iterator;
92 // A service directory entry, tracking the directory as a nul-terminated string, which may either
93 // be static or dynamically allocated (via new char[...]).
97 bool dir_dyn_allocd; // dynamically allocated?
100 dir_entry(const char *dir_p, bool dir_dyn_allocd_p) :
101 dir(dir_p), dir_dyn_allocd(dir_dyn_allocd_p)
104 dir_entry(dir_entry &&other)
107 dir_dyn_allocd = other.dir_dyn_allocd;
108 other.dir_dyn_allocd = false;
111 dir_entry(const dir_entry &other) = delete;
115 if (dir_dyn_allocd) {
120 const char *get_dir() const
126 // exception thrown when encountering a syntax issue when reading a setting value
127 class setting_exception
132 setting_exception(const std::string &&exc_info) : info(std::move(exc_info))
136 std::string &get_info()
143 // Utility function to skip white space. Returns an iterator at the
144 // first non-white-space position (or at end).
145 inline string_iterator skipws(string_iterator i, string_iterator end)
151 if (! isspace(*i, locale::classic())) {
159 // Convert a signal name to the corresponding signal number
160 inline int signal_name_to_number(std::string &signame)
162 if (signame == "HUP") return SIGHUP;
163 if (signame == "INT") return SIGINT;
164 if (signame == "QUIT") return SIGQUIT;
165 if (signame == "USR1") return SIGUSR1;
166 if (signame == "USR2") return SIGUSR2;
167 if (signame == "KILL") return SIGKILL;
171 // Read a setting name.
172 inline string read_setting_name(string_iterator & i, string_iterator end)
176 using std::use_facet;
178 const ctype<char> & facet = use_facet<ctype<char> >(locale::classic());
181 // Allow alphabetical characters, and dash (-) in setting name
182 while (i != end && (*i == '-' || *i == '.' || facet.is(ctype<char>::alpha, *i))) {
189 // Read a setting value.
191 // In general a setting value is a single-line string. It may contain multiple parts
192 // separated by white space (which is normally collapsed). A hash mark - # - denotes
193 // the end of the value and the beginning of a comment (it should be preceded by
196 // Part of a value may be quoted using double quote marks, which prevents collapse
197 // of whitespace and interpretation of most special characters (the quote marks will
198 // not be considered part of the value). A backslash can precede a character (such
199 // as '#' or '"' or another backslash) to remove its special meaning. Newline
200 // characters are not allowed in values and cannot be quoted.
202 // This function expects the string to be in an ASCII-compatible encoding (the "classic" locale).
204 // Throws setting_exception on error.
207 // service_name - the name of the service to which the setting applies
208 // i - reference to string iterator through the line
209 // end - iterator at end of line (not including newline character if any)
210 // part_positions - list of <int,int> to which the position of each setting value
211 // part will be added as [start,end). May be null.
212 inline string read_setting_value(string_iterator & i, string_iterator end,
213 std::list<std::pair<unsigned,unsigned>> * part_positions = nullptr)
221 bool new_part = true;
228 part_start = rval.length();
235 if (c == '\"') break;
236 else if (c == '\\') {
237 // A backslash escapes the following character.
244 throw setting_exception("Line end follows backslash escape character (`\\')");
253 // String wasn't terminated
254 throw setting_exception("Unterminated quoted string");
257 else if (c == '\\') {
259 part_start = rval.length();
262 // A backslash escapes the next character
268 throw setting_exception("Backslash escape (`\\') not followed by character");
271 else if (isspace(c, locale::classic())) {
272 if (! new_part && part_positions != nullptr) {
273 part_positions->emplace_back(part_start, rval.length());
278 if (*i == '#') break; // comment
279 rval += ' '; // collapse ws to a single space
283 // Possibly intended a comment; we require leading whitespace to reduce occurrence of accidental
284 // comments in setting values.
285 throw setting_exception("hashmark (`#') comment must be separated from setting value by whitespace");
289 part_start = rval.length();
298 if (part_positions != nullptr) {
299 part_positions->emplace_back(part_start, rval.length());
305 // Parse a userid parameter which may be a numeric user ID or a username. If a name, the
306 // userid is looked up via the system user database (getpwnam() function). In this case,
307 // the associated group is stored in the location specified by the group_p parameter iff
308 // it is not null and iff it contains the value -1.
309 inline uid_t parse_uid_param(const std::string ¶m, const std::string &service_name, gid_t *group_p)
311 const char * uid_err_msg = "Specified user id contains invalid numeric characters "
312 "or is outside allowed range.";
314 // Could be a name or a numeric id. But we should assume numeric first, just in case
315 // a user manages to give themselves a username that parses as a number.
318 // POSIX does not specify whether uid_t is an signed or unsigned, but regardless
319 // is is probably safe to assume that valid values are positive. We'll also assert
320 // that the value range fits within "unsigned long long" since it seems unlikely
321 // that would ever not be the case.
322 static_assert((uintmax_t)std::numeric_limits<uid_t>::max()
323 <= (uintmax_t)std::numeric_limits<unsigned long long>::max(), "uid_t is too large");
324 unsigned long long v = std::stoull(param, &ind, 0);
325 if (v > static_cast<unsigned long long>(std::numeric_limits<uid_t>::max())
326 || ind != param.length()) {
327 throw service_description_exc(service_name, uid_err_msg);
331 catch (std::out_of_range &exc) {
332 throw service_description_exc(service_name, uid_err_msg);
334 catch (std::invalid_argument &exc) {
335 // Ok, so it doesn't look like a number: proceed...
339 struct passwd * pwent = getpwnam(param.c_str());
340 if (pwent == nullptr) {
341 // Maybe an error, maybe just no entry.
343 throw service_description_exc(service_name, "Specified user \"" + param
344 + "\" does not exist in system database.");
347 throw service_description_exc(service_name, std::string("Error accessing user database: ")
352 if (group_p && *group_p != (gid_t)-1) {
353 *group_p = pwent->pw_gid;
356 return pwent->pw_uid;
359 inline gid_t parse_gid_param(const std::string ¶m, const std::string &service_name)
361 const char * gid_err_msg = "Specified group id contains invalid numeric characters or is "
362 "outside allowed range.";
364 // Could be a name or a numeric id. But we should assume numeric first, just in case
365 // a user manages to give themselves a username that parses as a number.
368 // POSIX does not specify whether uid_t is an signed or unsigned, but regardless
369 // is is probably safe to assume that valid values are positive. We'll also assume
370 // that the value range fits with "unsigned long long" since it seems unlikely
371 // that would ever not be the case.
372 static_assert((uintmax_t)std::numeric_limits<gid_t>::max()
373 <= (uintmax_t)std::numeric_limits<unsigned long long>::max(), "gid_t is too large");
374 unsigned long long v = std::stoull(param, &ind, 0);
375 if (v > static_cast<unsigned long long>(std::numeric_limits<gid_t>::max())
376 || ind != param.length()) {
377 throw service_description_exc(service_name, gid_err_msg);
381 catch (std::out_of_range &exc) {
382 throw service_description_exc(service_name, gid_err_msg);
384 catch (std::invalid_argument &exc) {
385 // Ok, so it doesn't look like a number: proceed...
389 struct group * grent = getgrnam(param.c_str());
390 if (grent == nullptr) {
391 // Maybe an error, maybe just no entry.
393 throw service_description_exc(service_name, "Specified group \"" + param
394 + "\" does not exist in system database.");
397 throw service_description_exc(service_name, std::string("Error accessing group database: ")
402 return grent->gr_gid;
405 // Parse a time, specified as a decimal number of seconds (with optional fractional component after decimal
406 // point or decimal comma).
407 inline void parse_timespec(const std::string ¶mval, const std::string &servicename,
408 const char * paramname, timespec &ts)
410 decltype(ts.tv_sec) isec = 0;
411 decltype(ts.tv_nsec) insec = 0;
412 auto max_secs = std::numeric_limits<decltype(isec)>::max() / 10;
413 auto len = paramval.length();
415 for (i = 0; i < len; i++) {
416 char ch = paramval[i];
417 if (ch == '.' || ch == ',') {
421 if (ch < '0' || ch > '9') {
422 throw service_description_exc(servicename, std::string("Bad value for ") + paramname);
424 // check for overflow
425 if (isec >= max_secs) {
426 throw service_description_exc(servicename, std::string("Too-large value for ") + paramname);
431 decltype(insec) insec_m = 100000000; // 10^8
432 for ( ; i < len; i++) {
433 char ch = paramval[i];
434 if (ch < '0' || ch > '9') {
435 throw service_description_exc(servicename, std::string("Bad value for ") + paramname);
437 insec += (ch - '0') * insec_m;
444 // Parse an unsigned numeric parameter value
445 inline unsigned long long parse_unum_param(const std::string ¶m, const std::string &service_name,
446 unsigned long long max = std::numeric_limits<unsigned long long>::max())
448 const char * num_err_msg = "Specified value contains invalid numeric characters or is outside "
453 unsigned long long v = std::stoull(param, &ind, 0);
454 if (v > max || ind != param.length()) {
455 throw service_description_exc(service_name, num_err_msg);
459 catch (std::out_of_range &exc) {
460 throw service_description_exc(service_name, num_err_msg);
462 catch (std::invalid_argument &exc) {
463 throw service_description_exc(service_name, num_err_msg);
467 // In a vector, find or create rlimits for a particular resource type.
468 inline service_rlimits &find_rlimits(std::vector<service_rlimits> &all_rlimits, int resource_id)
470 for (service_rlimits &limits : all_rlimits) {
471 if (limits.resource_id == resource_id) {
476 all_rlimits.emplace_back(resource_id);
477 return all_rlimits.back();
480 // Parse resource limits setting (can specify both hard and soft limit).
481 inline void parse_rlimit(const std::string &line, const std::string &service_name, const char *param_name,
482 service_rlimits &rlimit)
485 // 4:5 - soft:hard limits both set
486 // 4:- soft set, hard set to unlimited
487 // 4: soft set, hard limit unchanged
488 // 4 soft and hard limit set to same limit
491 throw service_description_exc(service_name, std::string("Bad value for ") + param_name);
494 const char *cline = line.c_str();
495 rlimit.hard_set = rlimit.soft_set = false;
498 const char * index = cline;
500 if (cline[0] != ':') {
501 rlimit.soft_set = true;
502 if (cline[0] == '-') {
503 rlimit.limits.rlim_cur = RLIM_INFINITY;
508 unsigned long long limit = std::strtoull(cline, &nindex, 0);
510 if (errno == ERANGE || limit > std::numeric_limits<rlim_t>::max()) throw std::out_of_range("");
511 if (index == cline) throw std::invalid_argument("");
512 rlimit.limits.rlim_cur = limit;
516 rlimit.hard_set = true;
517 rlimit.limits.rlim_max = rlimit.limits.rlim_cur;
522 throw service_description_exc(service_name, std::string("Bad value for ") + param_name);
527 if (*index == 0) return;
530 rlimit.limits.rlim_max = RLIM_INFINITY;
532 throw service_description_exc(service_name, std::string("Bad value for ") + param_name);
536 const char *hard_start = index;
538 unsigned long long limit = std::strtoull(cline, &nindex, 0);
540 if (errno == ERANGE || limit > std::numeric_limits<rlim_t>::max()) throw std::out_of_range("");
541 if (index == hard_start) throw std::invalid_argument("");
542 rlimit.limits.rlim_max = limit;
545 catch (std::invalid_argument &exc) {
546 throw service_description_exc(service_name, std::string("Bad value for ") + param_name);
548 catch (std::out_of_range &exc) {
549 throw service_description_exc(service_name, std::string("Too-large value for ") + param_name);
553 // Process an opened service file, line by line.
554 // name - the service name
555 // service_file - the service file input stream
556 // func - a function of the form:
557 // void(string &line, string &setting, string_iterator i, string_iterator end)
559 // line - the complete line (excluding newline character)
560 // setting - the setting name, from the beginning of the line
561 // i - iterator at the beginning of the setting value
562 // end - iterator marking the end of the line
564 // May throw service load exceptions or I/O exceptions if enabled on stream.
565 template <typename T>
566 void process_service_file(string name, std::istream &service_file, T func)
570 while (getline(service_file, line)) {
571 string::iterator i = line.begin();
572 string::iterator end = line.end();
577 continue; // comment line
579 string setting = read_setting_name(i, end);
581 if (i == end || (*i != '=' && *i != ':')) {
582 throw service_description_exc(name, "Badly formed line.");
584 i = skipws(++i, end);
586 func(line, setting, i, end);
591 } // namespace dinit_load
593 using dinit_load::process_service_file;