11 #include <sys/types.h>
15 #include "proc-service.h"
17 using string = std::string;
18 using string_iterator = std::string::iterator;
20 // Utility function to skip white space. Returns an iterator at the
21 // first non-white-space position (or at end).
22 static string_iterator skipws(string_iterator i, string_iterator end)
28 if (! isspace(*i, locale::classic())) {
36 // Read a setting name.
37 static string read_setting_name(string_iterator & i, string_iterator end)
43 const ctype<char> & facet = use_facet<ctype<char> >(locale::classic());
46 // Allow alphabetical characters, and dash (-) in setting name
47 while (i != end && (*i == '-' || facet.is(ctype<char>::alpha, *i))) {
55 class setting_exception
60 setting_exception(const std::string &&exc_info) : info(std::move(exc_info))
64 std::string &get_info()
72 // Read a setting value
74 // In general a setting value is a single-line string. It may contain multiple parts
75 // separated by white space (which is normally collapsed). A hash mark - # - denotes
76 // the end of the value and the beginning of a comment (it should be preceded by
79 // Part of a value may be quoted using double quote marks, which prevents collapse
80 // of whitespace and interpretation of most special characters (the quote marks will
81 // not be considered part of the value). A backslash can precede a character (such
82 // as '#' or '"' or another backslash) to remove its special meaning. Newline
83 // characters are not allowed in values and cannot be quoted.
85 // This function expects the string to be in an ASCII-compatible, single byte
86 // encoding (the "classic" locale).
89 // service_name - the name of the service to which the setting applies
90 // i - reference to string iterator through the line
91 // end - iterator at end of line
92 // part_positions - list of <int,int> to which the position of each setting value
93 // part will be added as [start,end). May be null.
94 static string read_setting_value(string_iterator & i, string_iterator end,
95 std::list<std::pair<unsigned,unsigned>> * part_positions = nullptr)
103 bool new_part = true;
110 part_start = rval.length();
117 if (c == '\"') break;
119 throw setting_exception("Line end inside quoted string");
121 else if (c == '\\') {
122 // A backslash escapes the following character.
127 throw setting_exception("Line end follows backslash escape character (`\\')");
138 // String wasn't terminated
139 throw setting_exception("Unterminated quoted string");
142 else if (c == '\\') {
144 part_start = rval.length();
147 // A backslash escapes the next character
153 throw setting_exception("Backslash escape (`\\') not followed by character");
156 else if (isspace(c, locale::classic())) {
157 if (! new_part && part_positions != nullptr) {
158 part_positions->emplace_back(part_start, rval.length());
163 if (*i == '#') break; // comment
164 rval += ' '; // collapse ws to a single space
168 // Possibly intended a comment; we require leading whitespace to reduce occurrence of accidental
169 // comments in setting values.
170 throw setting_exception("hashmark (`#') comment must be separated from setting value by whitespace");
174 part_start = rval.length();
183 if (part_positions != nullptr) {
184 part_positions->emplace_back(part_start, rval.length());
190 static int signal_name_to_number(std::string &signame)
192 if (signame == "HUP") return SIGHUP;
193 if (signame == "INT") return SIGINT;
194 if (signame == "QUIT") return SIGQUIT;
195 if (signame == "USR1") return SIGUSR1;
196 if (signame == "USR2") return SIGUSR2;
200 static const char * uid_err_msg = "Specified user id contains invalid numeric characters or is outside allowed range.";
202 // Parse a userid parameter which may be a numeric user ID or a username. If a name, the
203 // userid is looked up via the system user database (getpwnam() function). In this case,
204 // the associated group is stored in the location specified by the group_p parameter iff
205 // it is not null and iff it contains the value -1.
206 static uid_t parse_uid_param(const std::string ¶m, const std::string &service_name, gid_t *group_p)
208 // Could be a name or a numeric id. But we should assume numeric first, just in case
209 // a user manages to give themselves a username that parses as a number.
212 // POSIX does not specify whether uid_t is an signed or unsigned, but regardless
213 // is is probably safe to assume that valid values are positive. We'll also assert
214 // that the value range fits within "unsigned long long" since it seems unlikely
215 // that would ever not be the case.
216 static_assert((uintmax_t)std::numeric_limits<uid_t>::max() <= (uintmax_t)std::numeric_limits<unsigned long long>::max(), "uid_t is too large");
217 unsigned long long v = std::stoull(param, &ind, 0);
218 if (v > static_cast<unsigned long long>(std::numeric_limits<uid_t>::max()) || ind != param.length()) {
219 throw service_description_exc(service_name, uid_err_msg);
223 catch (std::out_of_range &exc) {
224 throw service_description_exc(service_name, uid_err_msg);
226 catch (std::invalid_argument &exc) {
227 // Ok, so it doesn't look like a number: proceed...
231 struct passwd * pwent = getpwnam(param.c_str());
232 if (pwent == nullptr) {
233 // Maybe an error, maybe just no entry.
235 throw service_description_exc(service_name, "Specified user \"" + param + "\" does not exist in system database.");
238 throw service_description_exc(service_name, std::string("Error accessing user database: ") + strerror(errno));
242 if (group_p && *group_p != (gid_t)-1) {
243 *group_p = pwent->pw_gid;
246 return pwent->pw_uid;
249 static const char * num_err_msg = "Specified value contains invalid numeric characters or is outside allowed range.";
251 // Parse an unsigned numeric parameter value
252 static unsigned long long parse_unum_param(const std::string ¶m, const std::string &service_name,
253 unsigned long long max = std::numeric_limits<unsigned long long>::max())
257 unsigned long long v = std::stoull(param, &ind, 0);
258 if (v > max || ind != param.length()) {
259 throw service_description_exc(service_name, num_err_msg);
263 catch (std::out_of_range &exc) {
264 throw service_description_exc(service_name, num_err_msg);
266 catch (std::invalid_argument &exc) {
267 throw service_description_exc(service_name, num_err_msg);
271 static const char * gid_err_msg = "Specified group id contains invalid numeric characters or is outside allowed range.";
273 static gid_t parse_gid_param(const std::string ¶m, const std::string &service_name)
275 // Could be a name or a numeric id. But we should assume numeric first, just in case
276 // a user manages to give themselves a username that parses as a number.
279 // POSIX does not specify whether uid_t is an signed or unsigned, but regardless
280 // is is probably safe to assume that valid values are positive. We'll also assume
281 // that the value range fits with "unsigned long long" since it seems unlikely
282 // that would ever not be the case.
283 static_assert((uintmax_t)std::numeric_limits<gid_t>::max() <= (uintmax_t)std::numeric_limits<unsigned long long>::max(), "gid_t is too large");
284 unsigned long long v = std::stoull(param, &ind, 0);
285 if (v > static_cast<unsigned long long>(std::numeric_limits<gid_t>::max()) || ind != param.length()) {
286 throw service_description_exc(service_name, gid_err_msg);
290 catch (std::out_of_range &exc) {
291 throw service_description_exc(service_name, gid_err_msg);
293 catch (std::invalid_argument &exc) {
294 // Ok, so it doesn't look like a number: proceed...
298 struct group * grent = getgrnam(param.c_str());
299 if (grent == nullptr) {
300 // Maybe an error, maybe just no entry.
302 throw service_description_exc(service_name, "Specified group \"" + param + "\" does not exist in system database.");
305 throw service_description_exc(service_name, std::string("Error accessing group database: ") + strerror(errno));
309 return grent->gr_gid;
312 // Parse a time, specified as a decimal number of seconds (with optional fractional component after decimal
313 // point or decimal comma).
315 static void parse_timespec(const std::string ¶mval, const std::string &servicename,
316 const char * paramname, timespec &ts)
318 decltype(ts.tv_sec) isec = 0;
319 decltype(ts.tv_nsec) insec = 0;
320 auto max_secs = std::numeric_limits<decltype(isec)>::max() / 10;
321 auto len = paramval.length();
323 for (i = 0; i < len; i++) {
324 char ch = paramval[i];
325 if (ch == '.' || ch == ',') {
329 if (ch < '0' || ch > '9') {
330 throw service_description_exc(servicename, std::string("Bad value for ") + paramname);
332 // check for overflow
333 if (isec >= max_secs) {
334 throw service_description_exc(servicename, std::string("Too-large value for ") + paramname);
339 decltype(insec) insec_m = 100000000; // 10^8
340 for ( ; i < len; i++) {
341 char ch = paramval[i];
342 if (ch < '0' || ch > '9') {
343 throw service_description_exc(servicename, std::string("Bad value for ") + paramname);
345 insec += (ch - '0') * insec_m;
352 // Find a service record, or load it from file. If the service has
353 // dependencies, load those also.
355 // Might throw a ServiceLoadExc exception if a dependency cycle is found or if another
356 // problem occurs (I/O error, service description not found etc). Throws std::bad_alloc
357 // if a memory allocation failure occurs.
359 service_record * dirload_service_set::load_service(const char * name)
371 // First try and find an existing record...
372 service_record * rval = find_service(string(name));
374 if (rval->is_dummy()) {
375 throw service_cyclic_dependency(name);
380 // Couldn't find one. Have to load it.
381 string service_filename = service_dir;
382 if (*(service_filename.rbegin()) != '/') {
383 service_filename += '/';
385 service_filename += name;
388 list<pair<unsigned,unsigned>> command_offsets;
390 list<pair<unsigned,unsigned>> stop_command_offsets;
393 service_type_t service_type = service_type_t::PROCESS;
394 std::list<prelim_dep> depends;
396 onstart_flags_t onstart_flags;
397 int term_signal = -1; // additional termination signal
398 bool auto_restart = false;
399 bool smooth_recovery = false;
400 bool start_is_interruptible = false;
402 int socket_perms = 0666;
403 // Note: Posix allows that uid_t and gid_t may be unsigned types, but eg chown uses -1 as an
404 // invalid value, so it's safe to assume that we can do the same:
405 uid_t socket_uid = -1;
406 gid_t socket_gid = -1;
407 // Restart limit interval / count; default is 10 seconds, 3 restarts:
408 timespec restart_interval = { .tv_sec = 10, .tv_nsec = 0 };
409 int max_restarts = 3;
410 timespec restart_delay = { .tv_sec = 0, .tv_nsec = 200000000 };
411 timespec stop_timeout = { .tv_sec = 10, .tv_nsec = 0 };
412 timespec start_timeout = { .tv_sec = 60, .tv_nsec = 0 };
414 uid_t run_as_uid = -1;
415 gid_t run_as_gid = -1;
418 ifstream service_file;
419 service_file.exceptions(ios::badbit | ios::failbit);
422 service_file.open(service_filename.c_str(), ios::in);
424 catch (std::ios_base::failure &exc) {
425 throw service_not_found(name);
428 // Add a dummy service record now to prevent infinite recursion in case of cyclic dependency.
429 // We replace this with the real service later (or remove it if we find a configuration error).
430 rval = new service_record(this, string(name));
434 // getline can set failbit if it reaches end-of-file, we don't want an exception in that case:
435 service_file.exceptions(ios::badbit);
437 while (! (service_file.rdstate() & ios::eofbit)) {
438 getline(service_file, line);
439 string::iterator i = line.begin();
440 string::iterator end = line.end();
445 continue; // comment line
447 string setting = read_setting_name(i, end);
449 if (i == end || (*i != '=' && *i != ':')) {
450 throw service_description_exc(name, "Badly formed line.");
452 i = skipws(++i, end);
454 if (setting == "command") {
455 command = read_setting_value(i, end, &command_offsets);
457 else if (setting == "socket-listen") {
458 socket_path = read_setting_value(i, end, nullptr);
460 else if (setting == "socket-permissions") {
461 string sock_perm_str = read_setting_value(i, end, nullptr);
464 socket_perms = std::stoi(sock_perm_str, &ind, 8);
465 if (ind != sock_perm_str.length()) {
466 throw std::logic_error("");
469 catch (std::logic_error &exc) {
470 throw service_description_exc(name, "socket-permissions: Badly-formed or out-of-range numeric value");
473 else if (setting == "socket-uid") {
474 string sock_uid_s = read_setting_value(i, end, nullptr);
475 socket_uid = parse_uid_param(sock_uid_s, name, &socket_gid);
477 else if (setting == "socket-gid") {
478 string sock_gid_s = read_setting_value(i, end, nullptr);
479 socket_gid = parse_gid_param(sock_gid_s, name);
481 else if (setting == "stop-command") {
482 stop_command = read_setting_value(i, end, &stop_command_offsets);
484 else if (setting == "pid-file") {
485 pid_file = read_setting_value(i, end);
487 else if (setting == "depends-on") {
488 string dependency_name = read_setting_value(i, end);
489 depends.emplace_back(load_service(dependency_name.c_str()), dependency_type::REGULAR);
491 else if (setting == "depends-ms") {
492 string dependency_name = read_setting_value(i, end);
493 depends.emplace_back(load_service(dependency_name.c_str()), dependency_type::MILESTONE);
495 else if (setting == "waits-for") {
496 string dependency_name = read_setting_value(i, end);
497 depends.emplace_back(load_service(dependency_name.c_str()), dependency_type::WAITS_FOR);
499 else if (setting == "logfile") {
500 logfile = read_setting_value(i, end);
502 else if (setting == "restart") {
503 string restart = read_setting_value(i, end);
504 auto_restart = (restart == "yes" || restart == "true");
506 else if (setting == "smooth-recovery") {
507 string recovery = read_setting_value(i, end);
508 smooth_recovery = (recovery == "yes" || recovery == "true");
510 else if (setting == "type") {
511 string type_str = read_setting_value(i, end);
512 if (type_str == "scripted") {
513 service_type = service_type_t::SCRIPTED;
515 else if (type_str == "process") {
516 service_type = service_type_t::PROCESS;
518 else if (type_str == "bgprocess") {
519 service_type = service_type_t::BGPROCESS;
521 else if (type_str == "internal") {
522 service_type = service_type_t::INTERNAL;
525 throw service_description_exc(name, "Service type must be one of: \"scripted\","
526 " \"process\", \"bgprocess\" or \"internal\"");
529 else if (setting == "options") {
530 std::list<std::pair<unsigned,unsigned>> indices;
531 string onstart_cmds = read_setting_value(i, end, &indices);
532 for (auto indexpair : indices) {
533 string option_txt = onstart_cmds.substr(indexpair.first, indexpair.second - indexpair.first);
534 if (option_txt == "starts-rwfs") {
535 onstart_flags.rw_ready = true;
537 else if (option_txt == "starts-log") {
538 onstart_flags.log_ready = true;
540 else if (option_txt == "no-sigterm") {
541 onstart_flags.no_sigterm = true;
543 else if (option_txt == "runs-on-console") {
544 onstart_flags.runs_on_console = true;
545 // A service that runs on the console necessarily starts on console:
546 onstart_flags.starts_on_console = true;
548 else if (option_txt == "starts-on-console") {
549 onstart_flags.starts_on_console = true;
551 else if (option_txt == "pass-cs-fd") {
552 onstart_flags.pass_cs_fd = true;
554 else if (option_txt == "start-interruptible") {
555 start_is_interruptible = true;
558 throw service_description_exc(name, "Unknown option: " + option_txt);
562 else if (setting == "termsignal") {
563 string signame = read_setting_value(i, end, nullptr);
564 int signo = signal_name_to_number(signame);
566 throw service_description_exc(name, "Unknown/unsupported termination signal: " + signame);
572 else if (setting == "restart-limit-interval") {
573 string interval_str = read_setting_value(i, end, nullptr);
574 parse_timespec(interval_str, name, "restart-limit-interval", restart_interval);
576 else if (setting == "restart-delay") {
577 string rsdelay_str = read_setting_value(i, end, nullptr);
578 parse_timespec(rsdelay_str, name, "restart-delay", restart_delay);
580 else if (setting == "restart-limit-count") {
581 string limit_str = read_setting_value(i, end, nullptr);
582 max_restarts = parse_unum_param(limit_str, name, std::numeric_limits<int>::max());
584 else if (setting == "stop-timeout") {
585 string stoptimeout_str = read_setting_value(i, end, nullptr);
586 parse_timespec(stoptimeout_str, name, "stop-timeout", stop_timeout);
588 else if (setting == "start-timeout") {
589 string starttimeout_str = read_setting_value(i, end, nullptr);
590 parse_timespec(starttimeout_str, name, "start-timeout", start_timeout);
592 else if (setting == "run-as") {
593 string run_as_str = read_setting_value(i, end, nullptr);
594 run_as_uid = parse_uid_param(run_as_str, name, &run_as_gid);
597 throw service_description_exc(name, "Unknown setting: " + setting);
602 service_file.close();
604 if (service_type == service_type_t::PROCESS || service_type == service_type_t::BGPROCESS || service_type == service_type_t::SCRIPTED) {
605 if (command.length() == 0) {
606 throw service_description_exc(name, "Service command not specified");
610 // Now replace the dummy service record with a real record:
611 for (auto iter = records.begin(); iter != records.end(); iter++) {
613 // We've found the dummy record
615 if (service_type == service_type_t::PROCESS) {
616 auto rvalps = new process_service(this, string(name), std::move(command),
617 command_offsets, depends);
618 rvalps->set_restart_interval(restart_interval, max_restarts);
619 rvalps->set_restart_delay(restart_delay);
620 rvalps->set_stop_timeout(stop_timeout);
621 rvalps->set_start_timeout(start_timeout);
622 rvalps->set_start_interruptible(start_is_interruptible);
623 rvalps->set_extra_termination_signal(term_signal);
624 rvalps->set_run_as_uid_gid(run_as_uid, run_as_gid);
627 else if (service_type == service_type_t::BGPROCESS) {
628 auto rvalps = new bgproc_service(this, string(name), std::move(command),
629 command_offsets, depends);
630 rvalps->set_pid_file(std::move(pid_file));
631 rvalps->set_restart_interval(restart_interval, max_restarts);
632 rvalps->set_restart_delay(restart_delay);
633 rvalps->set_stop_timeout(stop_timeout);
634 rvalps->set_start_timeout(start_timeout);
635 rvalps->set_start_interruptible(start_is_interruptible);
636 rvalps->set_extra_termination_signal(term_signal);
637 rvalps->set_run_as_uid_gid(run_as_uid, run_as_gid);
640 else if (service_type == service_type_t::SCRIPTED) {
641 auto rvalps = new scripted_service(this, string(name), std::move(command),
642 command_offsets, depends);
643 rvalps->set_stop_command(stop_command, stop_command_offsets);
644 rvalps->set_stop_timeout(stop_timeout);
645 rvalps->set_start_timeout(start_timeout);
646 rvalps->set_start_interruptible(start_is_interruptible);
647 rvalps->set_extra_termination_signal(term_signal);
648 rvalps->set_run_as_uid_gid(run_as_uid, run_as_gid);
652 rval = new service_record(this, string(name), service_type, depends);
654 rval->set_log_file(logfile);
655 rval->set_auto_restart(auto_restart);
656 rval->set_smooth_recovery(smooth_recovery);
657 rval->set_flags(onstart_flags);
658 rval->set_socket_details(std::move(socket_path), socket_perms, socket_uid, socket_gid);
666 catch (setting_exception &setting_exc)
668 // Must remove the dummy service record.
669 std::remove(records.begin(), records.end(), rval);
671 throw service_description_exc(name, std::move(setting_exc.get_info()));
674 // Must remove the dummy service record.
675 std::remove(records.begin(), records.end(), rval);