12 #include <sys/types.h>
16 #include "proc-service.h"
18 using string = std::string;
19 using string_iterator = std::string::iterator;
21 // Utility function to skip white space. Returns an iterator at the
22 // first non-white-space position (or at end).
23 static string_iterator skipws(string_iterator i, string_iterator end)
29 if (! isspace(*i, locale::classic())) {
37 // Read a setting name.
38 static string read_setting_name(string_iterator & i, string_iterator end)
44 const ctype<char> & facet = use_facet<ctype<char> >(locale::classic());
47 // Allow alphabetical characters, and dash (-) in setting name
48 while (i != end && (*i == '-' || facet.is(ctype<char>::alpha, *i))) {
56 class setting_exception
61 setting_exception(const std::string &&exc_info) : info(std::move(exc_info))
65 std::string &get_info()
73 // Read a setting value
75 // In general a setting value is a single-line string. It may contain multiple parts
76 // separated by white space (which is normally collapsed). A hash mark - # - denotes
77 // the end of the value and the beginning of a comment (it should be preceded by
80 // Part of a value may be quoted using double quote marks, which prevents collapse
81 // of whitespace and interpretation of most special characters (the quote marks will
82 // not be considered part of the value). A backslash can precede a character (such
83 // as '#' or '"' or another backslash) to remove its special meaning. Newline
84 // characters are not allowed in values and cannot be quoted.
86 // This function expects the string to be in an ASCII-compatible, single byte
87 // encoding (the "classic" locale).
90 // service_name - the name of the service to which the setting applies
91 // i - reference to string iterator through the line
92 // end - iterator at end of line
93 // part_positions - list of <int,int> to which the position of each setting value
94 // part will be added as [start,end). May be null.
95 static string read_setting_value(string_iterator & i, string_iterator end,
96 std::list<std::pair<unsigned,unsigned>> * part_positions = nullptr)
104 bool new_part = true;
111 part_start = rval.length();
118 if (c == '\"') break;
120 throw setting_exception("Line end inside quoted string");
122 else if (c == '\\') {
123 // A backslash escapes the following character.
128 throw setting_exception("Line end follows backslash escape character (`\\')");
139 // String wasn't terminated
140 throw setting_exception("Unterminated quoted string");
143 else if (c == '\\') {
145 part_start = rval.length();
148 // A backslash escapes the next character
154 throw setting_exception("Backslash escape (`\\') not followed by character");
157 else if (isspace(c, locale::classic())) {
158 if (! new_part && part_positions != nullptr) {
159 part_positions->emplace_back(part_start, rval.length());
164 if (*i == '#') break; // comment
165 rval += ' '; // collapse ws to a single space
169 // Possibly intended a comment; we require leading whitespace to reduce occurrence of accidental
170 // comments in setting values.
171 throw setting_exception("hashmark (`#') comment must be separated from setting value by whitespace");
175 part_start = rval.length();
184 if (part_positions != nullptr) {
185 part_positions->emplace_back(part_start, rval.length());
191 static int signal_name_to_number(std::string &signame)
193 if (signame == "HUP") return SIGHUP;
194 if (signame == "INT") return SIGINT;
195 if (signame == "QUIT") return SIGQUIT;
196 if (signame == "USR1") return SIGUSR1;
197 if (signame == "USR2") return SIGUSR2;
201 static const char * uid_err_msg = "Specified user id contains invalid numeric characters or is outside allowed range.";
203 // Parse a userid parameter which may be a numeric user ID or a username. If a name, the
204 // userid is looked up via the system user database (getpwnam() function). In this case,
205 // the associated group is stored in the location specified by the group_p parameter iff
206 // it is not null and iff it contains the value -1.
207 static uid_t parse_uid_param(const std::string ¶m, const std::string &service_name, gid_t *group_p)
209 // Could be a name or a numeric id. But we should assume numeric first, just in case
210 // a user manages to give themselves a username that parses as a number.
213 // POSIX does not specify whether uid_t is an signed or unsigned, but regardless
214 // is is probably safe to assume that valid values are positive. We'll also assert
215 // that the value range fits within "unsigned long long" since it seems unlikely
216 // that would ever not be the case.
217 static_assert((uintmax_t)std::numeric_limits<uid_t>::max() <= (uintmax_t)std::numeric_limits<unsigned long long>::max(), "uid_t is too large");
218 unsigned long long v = std::stoull(param, &ind, 0);
219 if (v > static_cast<unsigned long long>(std::numeric_limits<uid_t>::max()) || ind != param.length()) {
220 throw service_description_exc(service_name, uid_err_msg);
224 catch (std::out_of_range &exc) {
225 throw service_description_exc(service_name, uid_err_msg);
227 catch (std::invalid_argument &exc) {
228 // Ok, so it doesn't look like a number: proceed...
232 struct passwd * pwent = getpwnam(param.c_str());
233 if (pwent == nullptr) {
234 // Maybe an error, maybe just no entry.
236 throw service_description_exc(service_name, "Specified user \"" + param + "\" does not exist in system database.");
239 throw service_description_exc(service_name, std::string("Error accessing user database: ") + strerror(errno));
243 if (group_p && *group_p != (gid_t)-1) {
244 *group_p = pwent->pw_gid;
247 return pwent->pw_uid;
250 static const char * num_err_msg = "Specified value contains invalid numeric characters or is outside allowed range.";
252 // Parse an unsigned numeric parameter value
253 static unsigned long long parse_unum_param(const std::string ¶m, const std::string &service_name,
254 unsigned long long max = std::numeric_limits<unsigned long long>::max())
258 unsigned long long v = std::stoull(param, &ind, 0);
259 if (v > max || ind != param.length()) {
260 throw service_description_exc(service_name, num_err_msg);
264 catch (std::out_of_range &exc) {
265 throw service_description_exc(service_name, num_err_msg);
267 catch (std::invalid_argument &exc) {
268 throw service_description_exc(service_name, num_err_msg);
272 static const char * gid_err_msg = "Specified group id contains invalid numeric characters or is outside allowed range.";
274 static gid_t parse_gid_param(const std::string ¶m, const std::string &service_name)
276 // Could be a name or a numeric id. But we should assume numeric first, just in case
277 // a user manages to give themselves a username that parses as a number.
280 // POSIX does not specify whether uid_t is an signed or unsigned, but regardless
281 // is is probably safe to assume that valid values are positive. We'll also assume
282 // that the value range fits with "unsigned long long" since it seems unlikely
283 // that would ever not be the case.
284 static_assert((uintmax_t)std::numeric_limits<gid_t>::max() <= (uintmax_t)std::numeric_limits<unsigned long long>::max(), "gid_t is too large");
285 unsigned long long v = std::stoull(param, &ind, 0);
286 if (v > static_cast<unsigned long long>(std::numeric_limits<gid_t>::max()) || ind != param.length()) {
287 throw service_description_exc(service_name, gid_err_msg);
291 catch (std::out_of_range &exc) {
292 throw service_description_exc(service_name, gid_err_msg);
294 catch (std::invalid_argument &exc) {
295 // Ok, so it doesn't look like a number: proceed...
299 struct group * grent = getgrnam(param.c_str());
300 if (grent == nullptr) {
301 // Maybe an error, maybe just no entry.
303 throw service_description_exc(service_name, "Specified group \"" + param + "\" does not exist in system database.");
306 throw service_description_exc(service_name, std::string("Error accessing group database: ") + strerror(errno));
310 return grent->gr_gid;
313 // Parse a time, specified as a decimal number of seconds (with optional fractional component after decimal
314 // point or decimal comma).
316 static void parse_timespec(const std::string ¶mval, const std::string &servicename,
317 const char * paramname, timespec &ts)
319 decltype(ts.tv_sec) isec = 0;
320 decltype(ts.tv_nsec) insec = 0;
321 auto max_secs = std::numeric_limits<decltype(isec)>::max() / 10;
322 auto len = paramval.length();
324 for (i = 0; i < len; i++) {
325 char ch = paramval[i];
326 if (ch == '.' || ch == ',') {
330 if (ch < '0' || ch > '9') {
331 throw service_description_exc(servicename, std::string("Bad value for ") + paramname);
333 // check for overflow
334 if (isec >= max_secs) {
335 throw service_description_exc(servicename, std::string("Too-large value for ") + paramname);
340 decltype(insec) insec_m = 100000000; // 10^8
341 for ( ; i < len; i++) {
342 char ch = paramval[i];
343 if (ch < '0' || ch > '9') {
344 throw service_description_exc(servicename, std::string("Bad value for ") + paramname);
346 insec += (ch - '0') * insec_m;
353 // Perform environment variable substitution on a command line, if specified.
354 // line - the string storing the command and arguments
355 // offsets - the [start,end) pair of offsets of the command and each argument within the string
357 static void do_env_subst(std::string &line, std::list<std::pair<unsigned,unsigned>> &offsets, bool do_sub_vars)
360 auto i = offsets.begin();
361 std::string r_line = line.substr(i->first, i->second - i->first); // copy command part
362 for (++i; i != offsets.end(); ++i) {
363 auto &offset_pair = *i;
364 if (line[offset_pair.first] == '$') {
365 // Do subsitution for this part:
366 auto env_name = line.substr(offset_pair.first + 1, offset_pair.second - offset_pair.first - 1);
367 char *env_val = getenv(env_name.c_str());
368 if (env_val != nullptr) {
369 auto val_len = strlen(env_val);
371 offset_pair.first = r_line.length();
372 offset_pair.second = offset_pair.first + val_len;
376 // specified enironment variable not set: treat as an empty string
377 offset_pair.first = r_line.length();
378 offset_pair.second = offset_pair.first;
382 // No subsitution for this part:
384 auto new_offs = r_line.length();
385 auto len = offset_pair.second - offset_pair.first;
386 r_line += line.substr(offset_pair.first, len);
387 offset_pair.first = new_offs;
388 offset_pair.second = new_offs + len;
391 line = std::move(r_line);
395 // Find a service record, or load it from file. If the service has
396 // dependencies, load those also.
398 // Might throw a ServiceLoadExc exception if a dependency cycle is found or if another
399 // problem occurs (I/O error, service description not found etc). Throws std::bad_alloc
400 // if a memory allocation failure occurs.
402 service_record * dirload_service_set::load_service(const char * name)
414 // First try and find an existing record...
415 service_record * rval = find_service(string(name));
417 if (rval->is_dummy()) {
418 throw service_cyclic_dependency(name);
423 ifstream service_file;
425 // Couldn't find one. Have to load it.
426 for (auto &service_dir : service_dirs) {
427 string service_filename = service_dir.get_dir();
428 if (*(service_filename.rbegin()) != '/') {
429 service_filename += '/';
431 service_filename += name;
433 service_file.open(service_filename.c_str(), ios::in);
434 if (service_file) break;
438 list<pair<unsigned,unsigned>> command_offsets;
440 list<pair<unsigned,unsigned>> stop_command_offsets;
444 bool do_sub_vars = false;
446 service_type_t service_type = service_type_t::PROCESS;
447 std::list<prelim_dep> depends;
449 onstart_flags_t onstart_flags;
450 int term_signal = -1; // additional termination signal
451 bool auto_restart = false;
452 bool smooth_recovery = false;
453 bool start_is_interruptible = false;
455 int socket_perms = 0666;
456 // Note: Posix allows that uid_t and gid_t may be unsigned types, but eg chown uses -1 as an
457 // invalid value, so it's safe to assume that we can do the same:
458 uid_t socket_uid = -1;
459 gid_t socket_gid = -1;
460 // Restart limit interval / count; default is 10 seconds, 3 restarts:
461 timespec restart_interval = { .tv_sec = 10, .tv_nsec = 0 };
462 int max_restarts = 3;
463 timespec restart_delay = { .tv_sec = 0, .tv_nsec = 200000000 };
464 timespec stop_timeout = { .tv_sec = 10, .tv_nsec = 0 };
465 timespec start_timeout = { .tv_sec = 60, .tv_nsec = 0 };
467 uid_t run_as_uid = -1;
468 gid_t run_as_gid = -1;
471 service_file.exceptions(ios::badbit | ios::failbit);
473 // Add a dummy service record now to prevent infinite recursion in case of cyclic dependency.
474 // We replace this with the real service later (or remove it if we find a configuration error).
475 rval = new service_record(this, string(name));
479 // getline can set failbit if it reaches end-of-file, we don't want an exception in that case:
480 service_file.exceptions(ios::badbit);
482 while (getline(service_file, line)) {
483 string::iterator i = line.begin();
484 string::iterator end = line.end();
489 continue; // comment line
491 string setting = read_setting_name(i, end);
493 if (i == end || (*i != '=' && *i != ':')) {
494 throw service_description_exc(name, "Badly formed line.");
496 i = skipws(++i, end);
498 if (setting == "command") {
499 command = read_setting_value(i, end, &command_offsets);
501 else if (setting == "working-dir") {
502 working_dir = read_setting_value(i, end, nullptr);
504 else if (setting == "socket-listen") {
505 socket_path = read_setting_value(i, end, nullptr);
507 else if (setting == "socket-permissions") {
508 string sock_perm_str = read_setting_value(i, end, nullptr);
511 socket_perms = std::stoi(sock_perm_str, &ind, 8);
512 if (ind != sock_perm_str.length()) {
513 throw std::logic_error("");
516 catch (std::logic_error &exc) {
517 throw service_description_exc(name, "socket-permissions: Badly-formed or out-of-range numeric value");
520 else if (setting == "socket-uid") {
521 string sock_uid_s = read_setting_value(i, end, nullptr);
522 socket_uid = parse_uid_param(sock_uid_s, name, &socket_gid);
524 else if (setting == "socket-gid") {
525 string sock_gid_s = read_setting_value(i, end, nullptr);
526 socket_gid = parse_gid_param(sock_gid_s, name);
528 else if (setting == "stop-command") {
529 stop_command = read_setting_value(i, end, &stop_command_offsets);
531 else if (setting == "pid-file") {
532 pid_file = read_setting_value(i, end);
534 else if (setting == "depends-on") {
535 string dependency_name = read_setting_value(i, end);
536 depends.emplace_back(load_service(dependency_name.c_str()), dependency_type::REGULAR);
538 else if (setting == "depends-ms") {
539 string dependency_name = read_setting_value(i, end);
540 depends.emplace_back(load_service(dependency_name.c_str()), dependency_type::MILESTONE);
542 else if (setting == "waits-for") {
543 string dependency_name = read_setting_value(i, end);
544 depends.emplace_back(load_service(dependency_name.c_str()), dependency_type::WAITS_FOR);
546 else if (setting == "logfile") {
547 logfile = read_setting_value(i, end);
549 else if (setting == "restart") {
550 string restart = read_setting_value(i, end);
551 auto_restart = (restart == "yes" || restart == "true");
553 else if (setting == "smooth-recovery") {
554 string recovery = read_setting_value(i, end);
555 smooth_recovery = (recovery == "yes" || recovery == "true");
557 else if (setting == "type") {
558 string type_str = read_setting_value(i, end);
559 if (type_str == "scripted") {
560 service_type = service_type_t::SCRIPTED;
562 else if (type_str == "process") {
563 service_type = service_type_t::PROCESS;
565 else if (type_str == "bgprocess") {
566 service_type = service_type_t::BGPROCESS;
568 else if (type_str == "internal") {
569 service_type = service_type_t::INTERNAL;
572 throw service_description_exc(name, "Service type must be one of: \"scripted\","
573 " \"process\", \"bgprocess\" or \"internal\"");
576 else if (setting == "options") {
577 std::list<std::pair<unsigned,unsigned>> indices;
578 string onstart_cmds = read_setting_value(i, end, &indices);
579 for (auto indexpair : indices) {
580 string option_txt = onstart_cmds.substr(indexpair.first, indexpair.second - indexpair.first);
581 if (option_txt == "starts-rwfs") {
582 onstart_flags.rw_ready = true;
584 else if (option_txt == "starts-log") {
585 onstart_flags.log_ready = true;
587 else if (option_txt == "no-sigterm") {
588 onstart_flags.no_sigterm = true;
590 else if (option_txt == "runs-on-console") {
591 onstart_flags.runs_on_console = true;
592 // A service that runs on the console necessarily starts on console:
593 onstart_flags.starts_on_console = true;
595 else if (option_txt == "starts-on-console") {
596 onstart_flags.starts_on_console = true;
598 else if (option_txt == "pass-cs-fd") {
599 onstart_flags.pass_cs_fd = true;
601 else if (option_txt == "start-interruptible") {
602 start_is_interruptible = true;
604 else if (option_txt == "skippable") {
605 onstart_flags.skippable = true;
608 throw service_description_exc(name, "Unknown option: " + option_txt);
612 else if (setting == "load-options") {
613 std::list<std::pair<unsigned,unsigned>> indices;
614 string load_opts = read_setting_value(i, end, &indices);
615 for (auto indexpair : indices) {
616 string option_txt = load_opts.substr(indexpair.first, indexpair.second - indexpair.first);
617 if (option_txt == "sub-vars") {
618 // substitute environment variables in command line
621 else if (option_txt == "no-sub-vars") {
625 throw service_description_exc(name, "Unknown load option: " + option_txt);
629 else if (setting == "termsignal") {
630 string signame = read_setting_value(i, end, nullptr);
631 int signo = signal_name_to_number(signame);
633 throw service_description_exc(name, "Unknown/unsupported termination signal: " + signame);
639 else if (setting == "restart-limit-interval") {
640 string interval_str = read_setting_value(i, end, nullptr);
641 parse_timespec(interval_str, name, "restart-limit-interval", restart_interval);
643 else if (setting == "restart-delay") {
644 string rsdelay_str = read_setting_value(i, end, nullptr);
645 parse_timespec(rsdelay_str, name, "restart-delay", restart_delay);
647 else if (setting == "restart-limit-count") {
648 string limit_str = read_setting_value(i, end, nullptr);
649 max_restarts = parse_unum_param(limit_str, name, std::numeric_limits<int>::max());
651 else if (setting == "stop-timeout") {
652 string stoptimeout_str = read_setting_value(i, end, nullptr);
653 parse_timespec(stoptimeout_str, name, "stop-timeout", stop_timeout);
655 else if (setting == "start-timeout") {
656 string starttimeout_str = read_setting_value(i, end, nullptr);
657 parse_timespec(starttimeout_str, name, "start-timeout", start_timeout);
659 else if (setting == "run-as") {
660 string run_as_str = read_setting_value(i, end, nullptr);
661 run_as_uid = parse_uid_param(run_as_str, name, &run_as_gid);
664 throw service_description_exc(name, "Unknown setting: " + setting);
669 service_file.close();
671 if (service_type == service_type_t::PROCESS || service_type == service_type_t::BGPROCESS || service_type == service_type_t::SCRIPTED) {
672 if (command.length() == 0) {
673 throw service_description_exc(name, "Service command not specified");
677 // Now replace the dummy service record with a real record:
678 for (auto iter = records.begin(); iter != records.end(); iter++) {
680 // We've found the dummy record
682 if (service_type == service_type_t::PROCESS) {
683 do_env_subst(command, command_offsets, do_sub_vars);
684 auto rvalps = new process_service(this, string(name), std::move(command),
685 command_offsets, depends);
686 rvalps->set_workding_dir(working_dir);
687 rvalps->set_restart_interval(restart_interval, max_restarts);
688 rvalps->set_restart_delay(restart_delay);
689 rvalps->set_stop_timeout(stop_timeout);
690 rvalps->set_start_timeout(start_timeout);
691 rvalps->set_start_interruptible(start_is_interruptible);
692 rvalps->set_extra_termination_signal(term_signal);
693 rvalps->set_run_as_uid_gid(run_as_uid, run_as_gid);
694 rvalps->set_workding_dir(working_dir);
695 // process service start / run on console must be the same:
696 onstart_flags.starts_on_console = onstart_flags.runs_on_console;
699 else if (service_type == service_type_t::BGPROCESS) {
700 do_env_subst(command, command_offsets, do_sub_vars);
701 auto rvalps = new bgproc_service(this, string(name), std::move(command),
702 command_offsets, depends);
703 rvalps->set_workding_dir(working_dir);
704 rvalps->set_pid_file(std::move(pid_file));
705 rvalps->set_restart_interval(restart_interval, max_restarts);
706 rvalps->set_restart_delay(restart_delay);
707 rvalps->set_stop_timeout(stop_timeout);
708 rvalps->set_start_timeout(start_timeout);
709 rvalps->set_start_interruptible(start_is_interruptible);
710 rvalps->set_extra_termination_signal(term_signal);
711 rvalps->set_run_as_uid_gid(run_as_uid, run_as_gid);
712 onstart_flags.runs_on_console = false;
715 else if (service_type == service_type_t::SCRIPTED) {
716 do_env_subst(command, command_offsets, do_sub_vars);
717 auto rvalps = new scripted_service(this, string(name), std::move(command),
718 command_offsets, depends);
719 rvalps->set_stop_command(stop_command, stop_command_offsets);
720 rvalps->set_workding_dir(working_dir);
721 rvalps->set_stop_timeout(stop_timeout);
722 rvalps->set_start_timeout(start_timeout);
723 rvalps->set_start_interruptible(start_is_interruptible);
724 rvalps->set_extra_termination_signal(term_signal);
725 rvalps->set_run_as_uid_gid(run_as_uid, run_as_gid);
729 rval = new service_record(this, string(name), service_type, depends);
731 rval->set_log_file(logfile);
732 rval->set_auto_restart(auto_restart);
733 rval->set_smooth_recovery(smooth_recovery);
734 rval->set_flags(onstart_flags);
735 rval->set_socket_details(std::move(socket_path), socket_perms, socket_uid, socket_gid);
743 catch (setting_exception &setting_exc)
745 // Must remove the dummy service record.
746 std::remove(records.begin(), records.end(), rval);
748 throw service_description_exc(name, std::move(setting_exc.get_info()));
751 // Must remove the dummy service record.
752 std::remove(records.begin(), records.end(), rval);