12 #include <sys/types.h>
17 #include "proc-service.h"
18 #include "dinit-log.h"
20 using string = std::string;
21 using string_iterator = std::string::iterator;
23 // Utility function to skip white space. Returns an iterator at the
24 // first non-white-space position (or at end).
25 static string_iterator skipws(string_iterator i, string_iterator end)
31 if (! isspace(*i, locale::classic())) {
39 // Read a setting name.
40 static string read_setting_name(string_iterator & i, string_iterator end)
46 const ctype<char> & facet = use_facet<ctype<char> >(locale::classic());
49 // Allow alphabetical characters, and dash (-) in setting name
50 while (i != end && (*i == '-' || facet.is(ctype<char>::alpha, *i))) {
58 class setting_exception
63 setting_exception(const std::string &&exc_info) : info(std::move(exc_info))
67 std::string &get_info()
75 // Read a setting value
77 // In general a setting value is a single-line string. It may contain multiple parts
78 // separated by white space (which is normally collapsed). A hash mark - # - denotes
79 // the end of the value and the beginning of a comment (it should be preceded by
82 // Part of a value may be quoted using double quote marks, which prevents collapse
83 // of whitespace and interpretation of most special characters (the quote marks will
84 // not be considered part of the value). A backslash can precede a character (such
85 // as '#' or '"' or another backslash) to remove its special meaning. Newline
86 // characters are not allowed in values and cannot be quoted.
88 // This function expects the string to be in an ASCII-compatible, single byte
89 // encoding (the "classic" locale).
92 // service_name - the name of the service to which the setting applies
93 // i - reference to string iterator through the line
94 // end - iterator at end of line
95 // part_positions - list of <int,int> to which the position of each setting value
96 // part will be added as [start,end). May be null.
97 static string read_setting_value(string_iterator & i, string_iterator end,
98 std::list<std::pair<unsigned,unsigned>> * part_positions = nullptr)
106 bool new_part = true;
113 part_start = rval.length();
120 if (c == '\"') break;
122 throw setting_exception("Line end inside quoted string");
124 else if (c == '\\') {
125 // A backslash escapes the following character.
130 throw setting_exception("Line end follows backslash escape character (`\\')");
141 // String wasn't terminated
142 throw setting_exception("Unterminated quoted string");
145 else if (c == '\\') {
147 part_start = rval.length();
150 // A backslash escapes the next character
156 throw setting_exception("Backslash escape (`\\') not followed by character");
159 else if (isspace(c, locale::classic())) {
160 if (! new_part && part_positions != nullptr) {
161 part_positions->emplace_back(part_start, rval.length());
166 if (*i == '#') break; // comment
167 rval += ' '; // collapse ws to a single space
171 // Possibly intended a comment; we require leading whitespace to reduce occurrence of accidental
172 // comments in setting values.
173 throw setting_exception("hashmark (`#') comment must be separated from setting value by whitespace");
177 part_start = rval.length();
186 if (part_positions != nullptr) {
187 part_positions->emplace_back(part_start, rval.length());
193 static int signal_name_to_number(std::string &signame)
195 if (signame == "HUP") return SIGHUP;
196 if (signame == "INT") return SIGINT;
197 if (signame == "QUIT") return SIGQUIT;
198 if (signame == "USR1") return SIGUSR1;
199 if (signame == "USR2") return SIGUSR2;
203 static const char * uid_err_msg = "Specified user id contains invalid numeric characters or is outside allowed range.";
205 // Parse a userid parameter which may be a numeric user ID or a username. If a name, the
206 // userid is looked up via the system user database (getpwnam() function). In this case,
207 // the associated group is stored in the location specified by the group_p parameter iff
208 // it is not null and iff it contains the value -1.
209 static uid_t parse_uid_param(const std::string ¶m, const std::string &service_name, gid_t *group_p)
211 // Could be a name or a numeric id. But we should assume numeric first, just in case
212 // a user manages to give themselves a username that parses as a number.
215 // POSIX does not specify whether uid_t is an signed or unsigned, but regardless
216 // is is probably safe to assume that valid values are positive. We'll also assert
217 // that the value range fits within "unsigned long long" since it seems unlikely
218 // that would ever not be the case.
219 static_assert((uintmax_t)std::numeric_limits<uid_t>::max()
220 <= (uintmax_t)std::numeric_limits<unsigned long long>::max(), "uid_t is too large");
221 unsigned long long v = std::stoull(param, &ind, 0);
222 if (v > static_cast<unsigned long long>(std::numeric_limits<uid_t>::max()) || ind != param.length()) {
223 throw service_description_exc(service_name, uid_err_msg);
227 catch (std::out_of_range &exc) {
228 throw service_description_exc(service_name, uid_err_msg);
230 catch (std::invalid_argument &exc) {
231 // Ok, so it doesn't look like a number: proceed...
235 struct passwd * pwent = getpwnam(param.c_str());
236 if (pwent == nullptr) {
237 // Maybe an error, maybe just no entry.
239 throw service_description_exc(service_name, "Specified user \"" + param + "\" does not exist in system database.");
242 throw service_description_exc(service_name, std::string("Error accessing user database: ") + strerror(errno));
246 if (group_p && *group_p != (gid_t)-1) {
247 *group_p = pwent->pw_gid;
250 return pwent->pw_uid;
253 static const char * num_err_msg = "Specified value contains invalid numeric characters or is outside allowed range.";
255 // Parse an unsigned numeric parameter value
256 static unsigned long long parse_unum_param(const std::string ¶m, const std::string &service_name,
257 unsigned long long max = std::numeric_limits<unsigned long long>::max())
261 unsigned long long v = std::stoull(param, &ind, 0);
262 if (v > max || ind != param.length()) {
263 throw service_description_exc(service_name, num_err_msg);
267 catch (std::out_of_range &exc) {
268 throw service_description_exc(service_name, num_err_msg);
270 catch (std::invalid_argument &exc) {
271 throw service_description_exc(service_name, num_err_msg);
275 static const char * gid_err_msg = "Specified group id contains invalid numeric characters or is outside allowed range.";
277 static gid_t parse_gid_param(const std::string ¶m, const std::string &service_name)
279 // Could be a name or a numeric id. But we should assume numeric first, just in case
280 // a user manages to give themselves a username that parses as a number.
283 // POSIX does not specify whether uid_t is an signed or unsigned, but regardless
284 // is is probably safe to assume that valid values are positive. We'll also assume
285 // that the value range fits with "unsigned long long" since it seems unlikely
286 // that would ever not be the case.
287 static_assert((uintmax_t)std::numeric_limits<gid_t>::max()
288 <= (uintmax_t)std::numeric_limits<unsigned long long>::max(), "gid_t is too large");
289 unsigned long long v = std::stoull(param, &ind, 0);
290 if (v > static_cast<unsigned long long>(std::numeric_limits<gid_t>::max()) || ind != param.length()) {
291 throw service_description_exc(service_name, gid_err_msg);
295 catch (std::out_of_range &exc) {
296 throw service_description_exc(service_name, gid_err_msg);
298 catch (std::invalid_argument &exc) {
299 // Ok, so it doesn't look like a number: proceed...
303 struct group * grent = getgrnam(param.c_str());
304 if (grent == nullptr) {
305 // Maybe an error, maybe just no entry.
307 throw service_description_exc(service_name, "Specified group \"" + param + "\" does not exist in system database.");
310 throw service_description_exc(service_name, std::string("Error accessing group database: ") + strerror(errno));
314 return grent->gr_gid;
317 // Parse a time, specified as a decimal number of seconds (with optional fractional component after decimal
318 // point or decimal comma).
320 static void parse_timespec(const std::string ¶mval, const std::string &servicename,
321 const char * paramname, timespec &ts)
323 decltype(ts.tv_sec) isec = 0;
324 decltype(ts.tv_nsec) insec = 0;
325 auto max_secs = std::numeric_limits<decltype(isec)>::max() / 10;
326 auto len = paramval.length();
328 for (i = 0; i < len; i++) {
329 char ch = paramval[i];
330 if (ch == '.' || ch == ',') {
334 if (ch < '0' || ch > '9') {
335 throw service_description_exc(servicename, std::string("Bad value for ") + paramname);
337 // check for overflow
338 if (isec >= max_secs) {
339 throw service_description_exc(servicename, std::string("Too-large value for ") + paramname);
344 decltype(insec) insec_m = 100000000; // 10^8
345 for ( ; i < len; i++) {
346 char ch = paramval[i];
347 if (ch < '0' || ch > '9') {
348 throw service_description_exc(servicename, std::string("Bad value for ") + paramname);
350 insec += (ch - '0') * insec_m;
357 // Perform environment variable substitution on a command line, if specified.
358 // line - the string storing the command and arguments
359 // offsets - the [start,end) pair of offsets of the command and each argument within the string
361 static void do_env_subst(std::string &line, std::list<std::pair<unsigned,unsigned>> &offsets, bool do_sub_vars)
364 auto i = offsets.begin();
365 std::string r_line = line.substr(i->first, i->second - i->first); // copy command part
366 for (++i; i != offsets.end(); ++i) {
367 auto &offset_pair = *i;
368 if (line[offset_pair.first] == '$') {
369 // Do subsitution for this part:
370 auto env_name = line.substr(offset_pair.first + 1, offset_pair.second - offset_pair.first - 1);
371 char *env_val = getenv(env_name.c_str());
372 if (env_val != nullptr) {
373 auto val_len = strlen(env_val);
375 offset_pair.first = r_line.length();
376 offset_pair.second = offset_pair.first + val_len;
380 // specified enironment variable not set: treat as an empty string
381 offset_pair.first = r_line.length();
382 offset_pair.second = offset_pair.first;
386 // No subsitution for this part:
388 auto new_offs = r_line.length();
389 auto len = offset_pair.second - offset_pair.first;
390 r_line += line.substr(offset_pair.first, len);
391 offset_pair.first = new_offs;
392 offset_pair.second = new_offs + len;
395 line = std::move(r_line);
399 // Process a dependency directory - filenames contained within correspond to service names which
400 // are loaded and added as a dependency of the given type. Expected use is with a directory
401 // containing symbolic links to other service descriptions, but this isn't required.
402 // Failure to read the directory contents, or to find a service listed within, is not considered
404 static void process_dep_dir(dirload_service_set &sset,
405 const char *servicename,
406 std::list<prelim_dep> &deplist, const std::string &depdirpath,
407 dependency_type dep_type)
409 DIR *depdir = opendir(depdirpath.c_str());
410 if (depdir == nullptr) {
411 log(loglevel_t::WARN, "Could not open dependency directory '", depdirpath,
412 "' for ", servicename, " service.");
417 dirent * dent = readdir(depdir);
418 while (dent != nullptr) {
419 char * name = dent->d_name;
420 if (name[0] != '.') {
422 service_record * sr = sset.load_service(name);
423 deplist.emplace_back(sr, dep_type);
425 catch (service_not_found &) {
426 log(loglevel_t::WARN, "Ignoring unresolved dependency '", name,
427 "' in dependency directory '", depdirpath,
428 "' for ", servicename, " service.");
431 dent = readdir(depdir);
435 log(loglevel_t::WARN, "Error reading dependency directory '", depdirpath,
436 "' for ", servicename, " service.");
440 // Find a service record, or load it from file. If the service has
441 // dependencies, load those also.
443 // Might throw a ServiceLoadExc exception if a dependency cycle is found or if another
444 // problem occurs (I/O error, service description not found etc). Throws std::bad_alloc
445 // if a memory allocation failure occurs.
447 service_record * dirload_service_set::load_service(const char * name)
459 // First try and find an existing record...
460 service_record * rval = find_service(string(name));
462 if (rval->is_dummy()) {
463 throw service_cyclic_dependency(name);
468 ifstream service_file;
470 // Couldn't find one. Have to load it.
471 for (auto &service_dir : service_dirs) {
472 string service_filename = service_dir.get_dir();
473 if (*(service_filename.rbegin()) != '/') {
474 service_filename += '/';
476 service_filename += name;
478 service_file.open(service_filename.c_str(), ios::in);
479 if (service_file) break;
482 if (! service_file) {
483 throw service_not_found(string(name));
487 list<pair<unsigned,unsigned>> command_offsets;
489 list<pair<unsigned,unsigned>> stop_command_offsets;
493 bool do_sub_vars = false;
495 service_type_t service_type = service_type_t::PROCESS;
496 std::list<prelim_dep> depends;
498 service_flags_t onstart_flags;
499 int term_signal = -1; // additional termination signal
500 bool auto_restart = false;
501 bool smooth_recovery = false;
503 int socket_perms = 0666;
504 // Note: Posix allows that uid_t and gid_t may be unsigned types, but eg chown uses -1 as an
505 // invalid value, so it's safe to assume that we can do the same:
506 uid_t socket_uid = -1;
507 gid_t socket_gid = -1;
508 // Restart limit interval / count; default is 10 seconds, 3 restarts:
509 timespec restart_interval = { .tv_sec = 10, .tv_nsec = 0 };
510 int max_restarts = 3;
511 timespec restart_delay = { .tv_sec = 0, .tv_nsec = 200000000 };
512 timespec stop_timeout = { .tv_sec = 10, .tv_nsec = 0 };
513 timespec start_timeout = { .tv_sec = 60, .tv_nsec = 0 };
515 uid_t run_as_uid = -1;
516 gid_t run_as_gid = -1;
519 service_file.exceptions(ios::badbit | ios::failbit);
521 // Add a dummy service record now to prevent infinite recursion in case of cyclic dependency.
522 // We replace this with the real service later (or remove it if we find a configuration error).
523 rval = new service_record(this, string(name));
527 // getline can set failbit if it reaches end-of-file, we don't want an exception in that case:
528 service_file.exceptions(ios::badbit);
530 while (getline(service_file, line)) {
531 string::iterator i = line.begin();
532 string::iterator end = line.end();
537 continue; // comment line
539 string setting = read_setting_name(i, end);
541 if (i == end || (*i != '=' && *i != ':')) {
542 throw service_description_exc(name, "Badly formed line.");
544 i = skipws(++i, end);
546 if (setting == "command") {
547 command = read_setting_value(i, end, &command_offsets);
549 else if (setting == "working-dir") {
550 working_dir = read_setting_value(i, end, nullptr);
552 else if (setting == "socket-listen") {
553 socket_path = read_setting_value(i, end, nullptr);
555 else if (setting == "socket-permissions") {
556 string sock_perm_str = read_setting_value(i, end, nullptr);
559 socket_perms = std::stoi(sock_perm_str, &ind, 8);
560 if (ind != sock_perm_str.length()) {
561 throw std::logic_error("");
564 catch (std::logic_error &exc) {
565 throw service_description_exc(name, "socket-permissions: Badly-formed or out-of-range numeric value");
568 else if (setting == "socket-uid") {
569 string sock_uid_s = read_setting_value(i, end, nullptr);
570 socket_uid = parse_uid_param(sock_uid_s, name, &socket_gid);
572 else if (setting == "socket-gid") {
573 string sock_gid_s = read_setting_value(i, end, nullptr);
574 socket_gid = parse_gid_param(sock_gid_s, name);
576 else if (setting == "stop-command") {
577 stop_command = read_setting_value(i, end, &stop_command_offsets);
579 else if (setting == "pid-file") {
580 pid_file = read_setting_value(i, end);
582 else if (setting == "depends-on") {
583 string dependency_name = read_setting_value(i, end);
584 depends.emplace_back(load_service(dependency_name.c_str()), dependency_type::REGULAR);
586 else if (setting == "depends-ms") {
587 string dependency_name = read_setting_value(i, end);
588 depends.emplace_back(load_service(dependency_name.c_str()), dependency_type::MILESTONE);
590 else if (setting == "waits-for") {
591 string dependency_name = read_setting_value(i, end);
592 depends.emplace_back(load_service(dependency_name.c_str()), dependency_type::WAITS_FOR);
594 else if (setting == "waits-for.d") {
595 string waitsford = read_setting_value(i, end);
596 process_dep_dir(*this, name, depends, waitsford, dependency_type::WAITS_FOR);
598 else if (setting == "logfile") {
599 logfile = read_setting_value(i, end);
601 else if (setting == "restart") {
602 string restart = read_setting_value(i, end);
603 auto_restart = (restart == "yes" || restart == "true");
605 else if (setting == "smooth-recovery") {
606 string recovery = read_setting_value(i, end);
607 smooth_recovery = (recovery == "yes" || recovery == "true");
609 else if (setting == "type") {
610 string type_str = read_setting_value(i, end);
611 if (type_str == "scripted") {
612 service_type = service_type_t::SCRIPTED;
614 else if (type_str == "process") {
615 service_type = service_type_t::PROCESS;
617 else if (type_str == "bgprocess") {
618 service_type = service_type_t::BGPROCESS;
620 else if (type_str == "internal") {
621 service_type = service_type_t::INTERNAL;
624 throw service_description_exc(name, "Service type must be one of: \"scripted\","
625 " \"process\", \"bgprocess\" or \"internal\"");
628 else if (setting == "options") {
629 std::list<std::pair<unsigned,unsigned>> indices;
630 string onstart_cmds = read_setting_value(i, end, &indices);
631 for (auto indexpair : indices) {
632 string option_txt = onstart_cmds.substr(indexpair.first, indexpair.second - indexpair.first);
633 if (option_txt == "starts-rwfs") {
634 onstart_flags.rw_ready = true;
636 else if (option_txt == "starts-log") {
637 onstart_flags.log_ready = true;
639 else if (option_txt == "no-sigterm") {
640 onstart_flags.no_sigterm = true;
642 else if (option_txt == "runs-on-console") {
643 onstart_flags.runs_on_console = true;
644 // A service that runs on the console necessarily starts on console:
645 onstart_flags.starts_on_console = true;
647 else if (option_txt == "starts-on-console") {
648 onstart_flags.starts_on_console = true;
650 else if (option_txt == "pass-cs-fd") {
651 onstart_flags.pass_cs_fd = true;
653 else if (option_txt == "start-interruptible") {
654 onstart_flags.start_interruptible = true;
656 else if (option_txt == "skippable") {
657 onstart_flags.skippable = true;
659 else if (option_txt == "signal-process-only") {
660 onstart_flags.signal_process_only = true;
663 throw service_description_exc(name, "Unknown option: " + option_txt);
667 else if (setting == "load-options") {
668 std::list<std::pair<unsigned,unsigned>> indices;
669 string load_opts = read_setting_value(i, end, &indices);
670 for (auto indexpair : indices) {
671 string option_txt = load_opts.substr(indexpair.first, indexpair.second - indexpair.first);
672 if (option_txt == "sub-vars") {
673 // substitute environment variables in command line
676 else if (option_txt == "no-sub-vars") {
680 throw service_description_exc(name, "Unknown load option: " + option_txt);
684 else if (setting == "termsignal") {
685 string signame = read_setting_value(i, end, nullptr);
686 int signo = signal_name_to_number(signame);
688 throw service_description_exc(name, "Unknown/unsupported termination signal: " + signame);
694 else if (setting == "restart-limit-interval") {
695 string interval_str = read_setting_value(i, end, nullptr);
696 parse_timespec(interval_str, name, "restart-limit-interval", restart_interval);
698 else if (setting == "restart-delay") {
699 string rsdelay_str = read_setting_value(i, end, nullptr);
700 parse_timespec(rsdelay_str, name, "restart-delay", restart_delay);
702 else if (setting == "restart-limit-count") {
703 string limit_str = read_setting_value(i, end, nullptr);
704 max_restarts = parse_unum_param(limit_str, name, std::numeric_limits<int>::max());
706 else if (setting == "stop-timeout") {
707 string stoptimeout_str = read_setting_value(i, end, nullptr);
708 parse_timespec(stoptimeout_str, name, "stop-timeout", stop_timeout);
710 else if (setting == "start-timeout") {
711 string starttimeout_str = read_setting_value(i, end, nullptr);
712 parse_timespec(starttimeout_str, name, "start-timeout", start_timeout);
714 else if (setting == "run-as") {
715 string run_as_str = read_setting_value(i, end, nullptr);
716 run_as_uid = parse_uid_param(run_as_str, name, &run_as_gid);
719 throw service_description_exc(name, "Unknown setting: " + setting);
724 service_file.close();
726 if (service_type == service_type_t::PROCESS || service_type == service_type_t::BGPROCESS
727 || service_type == service_type_t::SCRIPTED) {
728 if (command.length() == 0) {
729 throw service_description_exc(name, "Service command not specified");
733 // Now replace the dummy service record with a real record:
734 for (auto iter = records.begin(); iter != records.end(); iter++) {
736 // We've found the dummy record
738 if (service_type == service_type_t::PROCESS) {
739 do_env_subst(command, command_offsets, do_sub_vars);
740 auto rvalps = new process_service(this, string(name), std::move(command),
741 command_offsets, depends);
742 rvalps->set_workding_dir(working_dir);
743 rvalps->set_restart_interval(restart_interval, max_restarts);
744 rvalps->set_restart_delay(restart_delay);
745 rvalps->set_stop_timeout(stop_timeout);
746 rvalps->set_start_timeout(start_timeout);
747 rvalps->set_extra_termination_signal(term_signal);
748 rvalps->set_run_as_uid_gid(run_as_uid, run_as_gid);
749 rvalps->set_workding_dir(working_dir);
750 // process service start / run on console must be the same:
751 onstart_flags.starts_on_console = onstart_flags.runs_on_console;
754 else if (service_type == service_type_t::BGPROCESS) {
755 do_env_subst(command, command_offsets, do_sub_vars);
756 auto rvalps = new bgproc_service(this, string(name), std::move(command),
757 command_offsets, depends);
758 rvalps->set_workding_dir(working_dir);
759 rvalps->set_pid_file(std::move(pid_file));
760 rvalps->set_restart_interval(restart_interval, max_restarts);
761 rvalps->set_restart_delay(restart_delay);
762 rvalps->set_stop_timeout(stop_timeout);
763 rvalps->set_start_timeout(start_timeout);
764 rvalps->set_extra_termination_signal(term_signal);
765 rvalps->set_run_as_uid_gid(run_as_uid, run_as_gid);
766 onstart_flags.runs_on_console = false;
769 else if (service_type == service_type_t::SCRIPTED) {
770 do_env_subst(command, command_offsets, do_sub_vars);
771 auto rvalps = new scripted_service(this, string(name), std::move(command),
772 command_offsets, depends);
773 rvalps->set_stop_command(stop_command, stop_command_offsets);
774 rvalps->set_workding_dir(working_dir);
775 rvalps->set_stop_timeout(stop_timeout);
776 rvalps->set_start_timeout(start_timeout);
777 rvalps->set_extra_termination_signal(term_signal);
778 rvalps->set_run_as_uid_gid(run_as_uid, run_as_gid);
782 rval = new service_record(this, string(name), service_type, depends);
784 rval->set_log_file(logfile);
785 rval->set_auto_restart(auto_restart);
786 rval->set_smooth_recovery(smooth_recovery);
787 rval->set_flags(onstart_flags);
788 rval->set_socket_details(std::move(socket_path), socket_perms, socket_uid, socket_gid);
796 catch (setting_exception &setting_exc)
798 // Must remove the dummy service record.
799 records.erase(std::find(records.begin(), records.end(), rval));
801 throw service_description_exc(name, std::move(setting_exc.get_info()));
804 // Must remove the dummy service record.
805 records.erase(std::find(records.begin(), records.end(), rval));