12 #include <sys/types.h>
17 #include "proc-service.h"
18 #include "dinit-log.h"
19 #include "dinit-util.h"
21 using string = std::string;
22 using string_iterator = std::string::iterator;
24 // Convert a signal name to the corresponding signal number
25 static int signal_name_to_number(std::string &signame)
27 if (signame == "HUP") return SIGHUP;
28 if (signame == "INT") return SIGINT;
29 if (signame == "QUIT") return SIGQUIT;
30 if (signame == "USR1") return SIGUSR1;
31 if (signame == "USR2") return SIGUSR2;
35 static const char * uid_err_msg = "Specified user id contains invalid numeric characters or is outside allowed range.";
37 // Parse a userid parameter which may be a numeric user ID or a username. If a name, the
38 // userid is looked up via the system user database (getpwnam() function). In this case,
39 // the associated group is stored in the location specified by the group_p parameter iff
40 // it is not null and iff it contains the value -1.
41 static uid_t parse_uid_param(const std::string ¶m, const std::string &service_name, gid_t *group_p)
43 // Could be a name or a numeric id. But we should assume numeric first, just in case
44 // a user manages to give themselves a username that parses as a number.
47 // POSIX does not specify whether uid_t is an signed or unsigned, but regardless
48 // is is probably safe to assume that valid values are positive. We'll also assert
49 // that the value range fits within "unsigned long long" since it seems unlikely
50 // that would ever not be the case.
51 static_assert((uintmax_t)std::numeric_limits<uid_t>::max()
52 <= (uintmax_t)std::numeric_limits<unsigned long long>::max(), "uid_t is too large");
53 unsigned long long v = std::stoull(param, &ind, 0);
54 if (v > static_cast<unsigned long long>(std::numeric_limits<uid_t>::max()) || ind != param.length()) {
55 throw service_description_exc(service_name, uid_err_msg);
59 catch (std::out_of_range &exc) {
60 throw service_description_exc(service_name, uid_err_msg);
62 catch (std::invalid_argument &exc) {
63 // Ok, so it doesn't look like a number: proceed...
67 struct passwd * pwent = getpwnam(param.c_str());
68 if (pwent == nullptr) {
69 // Maybe an error, maybe just no entry.
71 throw service_description_exc(service_name, "Specified user \"" + param
72 + "\" does not exist in system database.");
75 throw service_description_exc(service_name, std::string("Error accessing user database: ")
80 if (group_p && *group_p != (gid_t)-1) {
81 *group_p = pwent->pw_gid;
87 static const char * num_err_msg = "Specified value contains invalid numeric characters or is outside "
90 // Parse an unsigned numeric parameter value
91 static unsigned long long parse_unum_param(const std::string ¶m, const std::string &service_name,
92 unsigned long long max = std::numeric_limits<unsigned long long>::max())
96 unsigned long long v = std::stoull(param, &ind, 0);
97 if (v > max || ind != param.length()) {
98 throw service_description_exc(service_name, num_err_msg);
102 catch (std::out_of_range &exc) {
103 throw service_description_exc(service_name, num_err_msg);
105 catch (std::invalid_argument &exc) {
106 throw service_description_exc(service_name, num_err_msg);
110 static const char * gid_err_msg = "Specified group id contains invalid numeric characters or is "
111 "outside allowed range.";
113 static gid_t parse_gid_param(const std::string ¶m, const std::string &service_name)
115 // Could be a name or a numeric id. But we should assume numeric first, just in case
116 // a user manages to give themselves a username that parses as a number.
119 // POSIX does not specify whether uid_t is an signed or unsigned, but regardless
120 // is is probably safe to assume that valid values are positive. We'll also assume
121 // that the value range fits with "unsigned long long" since it seems unlikely
122 // that would ever not be the case.
123 static_assert((uintmax_t)std::numeric_limits<gid_t>::max()
124 <= (uintmax_t)std::numeric_limits<unsigned long long>::max(), "gid_t is too large");
125 unsigned long long v = std::stoull(param, &ind, 0);
126 if (v > static_cast<unsigned long long>(std::numeric_limits<gid_t>::max())
127 || ind != param.length()) {
128 throw service_description_exc(service_name, gid_err_msg);
132 catch (std::out_of_range &exc) {
133 throw service_description_exc(service_name, gid_err_msg);
135 catch (std::invalid_argument &exc) {
136 // Ok, so it doesn't look like a number: proceed...
140 struct group * grent = getgrnam(param.c_str());
141 if (grent == nullptr) {
142 // Maybe an error, maybe just no entry.
144 throw service_description_exc(service_name, "Specified group \"" + param
145 + "\" does not exist in system database.");
148 throw service_description_exc(service_name, std::string("Error accessing group database: ")
153 return grent->gr_gid;
156 // Parse a time, specified as a decimal number of seconds (with optional fractional component after decimal
157 // point or decimal comma).
159 static void parse_timespec(const std::string ¶mval, const std::string &servicename,
160 const char * paramname, timespec &ts)
162 decltype(ts.tv_sec) isec = 0;
163 decltype(ts.tv_nsec) insec = 0;
164 auto max_secs = std::numeric_limits<decltype(isec)>::max() / 10;
165 auto len = paramval.length();
167 for (i = 0; i < len; i++) {
168 char ch = paramval[i];
169 if (ch == '.' || ch == ',') {
173 if (ch < '0' || ch > '9') {
174 throw service_description_exc(servicename, std::string("Bad value for ") + paramname);
176 // check for overflow
177 if (isec >= max_secs) {
178 throw service_description_exc(servicename, std::string("Too-large value for ") + paramname);
183 decltype(insec) insec_m = 100000000; // 10^8
184 for ( ; i < len; i++) {
185 char ch = paramval[i];
186 if (ch < '0' || ch > '9') {
187 throw service_description_exc(servicename, std::string("Bad value for ") + paramname);
189 insec += (ch - '0') * insec_m;
196 // Perform environment variable substitution on a command line, if specified.
197 // line - the string storing the command and arguments
198 // offsets - the [start,end) pair of offsets of the command and each argument within the string
200 static void do_env_subst(std::string &line, std::list<std::pair<unsigned,unsigned>> &offsets,
204 auto i = offsets.begin();
205 std::string r_line = line.substr(i->first, i->second - i->first); // copy command part
206 for (++i; i != offsets.end(); ++i) {
207 auto &offset_pair = *i;
208 if (line[offset_pair.first] == '$') {
209 // Do subsitution for this part:
210 auto env_name = line.substr(offset_pair.first + 1,
211 offset_pair.second - offset_pair.first - 1);
212 char *env_val = getenv(env_name.c_str());
213 if (env_val != nullptr) {
214 auto val_len = strlen(env_val);
216 offset_pair.first = r_line.length();
217 offset_pair.second = offset_pair.first + val_len;
221 // specified enironment variable not set: treat as an empty string
222 offset_pair.first = r_line.length();
223 offset_pair.second = offset_pair.first;
227 // No subsitution for this part:
229 auto new_offs = r_line.length();
230 auto len = offset_pair.second - offset_pair.first;
231 r_line += line.substr(offset_pair.first, len);
232 offset_pair.first = new_offs;
233 offset_pair.second = new_offs + len;
236 line = std::move(r_line);
240 // Process a dependency directory - filenames contained within correspond to service names which
241 // are loaded and added as a dependency of the given type. Expected use is with a directory
242 // containing symbolic links to other service descriptions, but this isn't required.
243 // Failure to read the directory contents, or to find a service listed within, is not considered
245 static void process_dep_dir(dirload_service_set &sset,
246 const char *servicename,
247 const string &service_filename,
248 std::list<prelim_dep> &deplist, const std::string &depdirpath,
249 dependency_type dep_type)
251 std::string depdir_fname = combine_paths(parent_path(service_filename), depdirpath.c_str());
253 DIR *depdir = opendir(depdir_fname.c_str());
254 if (depdir == nullptr) {
255 log(loglevel_t::WARN, "Could not open dependency directory '", depdir_fname,
256 "' for ", servicename, " service.");
261 dirent * dent = readdir(depdir);
262 while (dent != nullptr) {
263 char * name = dent->d_name;
264 if (name[0] != '.') {
266 service_record * sr = sset.load_service(name);
267 deplist.emplace_back(sr, dep_type);
269 catch (service_not_found &) {
270 log(loglevel_t::WARN, "Ignoring unresolved dependency '", name,
271 "' in dependency directory '", depdirpath,
272 "' for ", servicename, " service.");
275 dent = readdir(depdir);
279 log(loglevel_t::WARN, "Error reading dependency directory '", depdirpath,
280 "' for ", servicename, " service.");
284 // Find a service record, or load it from file. If the service has
285 // dependencies, load those also.
287 // Might throw a ServiceLoadExc exception if a dependency cycle is found or if another
288 // problem occurs (I/O error, service description not found etc). Throws std::bad_alloc
289 // if a memory allocation failure occurs.
291 service_record * dirload_service_set::load_service(const char * name)
303 using namespace dinit_load;
305 // First try and find an existing record...
306 service_record * rval = find_service(string(name));
308 if (rval->is_dummy()) {
309 throw service_cyclic_dependency(name);
314 ifstream service_file;
315 string service_filename;
317 // Couldn't find one. Have to load it.
318 for (auto &service_dir : service_dirs) {
319 service_filename = service_dir.get_dir();
320 if (*(service_filename.rbegin()) != '/') {
321 service_filename += '/';
323 service_filename += name;
325 service_file.open(service_filename.c_str(), ios::in);
326 if (service_file) break;
329 if (! service_file) {
330 throw service_not_found(string(name));
334 list<pair<unsigned,unsigned>> command_offsets;
336 list<pair<unsigned,unsigned>> stop_command_offsets;
340 bool do_sub_vars = false;
342 service_type_t service_type = service_type_t::PROCESS;
343 std::list<prelim_dep> depends;
345 service_flags_t onstart_flags;
346 int term_signal = -1; // additional termination signal
347 bool auto_restart = false;
348 bool smooth_recovery = false;
350 int socket_perms = 0666;
351 // Note: Posix allows that uid_t and gid_t may be unsigned types, but eg chown uses -1 as an
352 // invalid value, so it's safe to assume that we can do the same:
353 uid_t socket_uid = -1;
354 gid_t socket_gid = -1;
355 // Restart limit interval / count; default is 10 seconds, 3 restarts:
356 timespec restart_interval = { .tv_sec = 10, .tv_nsec = 0 };
357 int max_restarts = 3;
358 timespec restart_delay = { .tv_sec = 0, .tv_nsec = 200000000 };
359 timespec stop_timeout = { .tv_sec = 10, .tv_nsec = 0 };
360 timespec start_timeout = { .tv_sec = 60, .tv_nsec = 0 };
362 uid_t run_as_uid = -1;
363 gid_t run_as_gid = -1;
366 service_file.exceptions(ios::badbit | ios::failbit);
368 // Add a dummy service record now to prevent infinite recursion in case of cyclic dependency.
369 // We replace this with the real service later (or remove it if we find a configuration error).
370 rval = new service_record(this, string(name));
374 // getline can set failbit if it reaches end-of-file, we don't want an exception in that case:
375 service_file.exceptions(ios::badbit);
377 process_service_file(name, service_file,
378 [&](string &line, string &setting, string_iterator &i, string_iterator &end) -> void {
379 if (setting == "command") {
380 command = read_setting_value(i, end, &command_offsets);
382 else if (setting == "working-dir") {
383 working_dir = read_setting_value(i, end, nullptr);
385 else if (setting == "socket-listen") {
386 socket_path = read_setting_value(i, end, nullptr);
388 else if (setting == "socket-permissions") {
389 string sock_perm_str = read_setting_value(i, end, nullptr);
392 socket_perms = std::stoi(sock_perm_str, &ind, 8);
393 if (ind != sock_perm_str.length()) {
394 throw std::logic_error("");
397 catch (std::logic_error &exc) {
398 throw service_description_exc(name, "socket-permissions: Badly-formed or "
399 "out-of-range numeric value");
402 else if (setting == "socket-uid") {
403 string sock_uid_s = read_setting_value(i, end, nullptr);
404 socket_uid = parse_uid_param(sock_uid_s, name, &socket_gid);
406 else if (setting == "socket-gid") {
407 string sock_gid_s = read_setting_value(i, end, nullptr);
408 socket_gid = parse_gid_param(sock_gid_s, name);
410 else if (setting == "stop-command") {
411 stop_command = read_setting_value(i, end, &stop_command_offsets);
413 else if (setting == "pid-file") {
414 pid_file = read_setting_value(i, end);
416 else if (setting == "depends-on") {
417 string dependency_name = read_setting_value(i, end);
418 depends.emplace_back(load_service(dependency_name.c_str()), dependency_type::REGULAR);
420 else if (setting == "depends-ms") {
421 string dependency_name = read_setting_value(i, end);
422 depends.emplace_back(load_service(dependency_name.c_str()), dependency_type::MILESTONE);
424 else if (setting == "waits-for") {
425 string dependency_name = read_setting_value(i, end);
426 depends.emplace_back(load_service(dependency_name.c_str()), dependency_type::WAITS_FOR);
428 else if (setting == "waits-for.d") {
429 string waitsford = read_setting_value(i, end);
430 process_dep_dir(*this, name, service_filename, depends, waitsford,
431 dependency_type::WAITS_FOR);
433 else if (setting == "logfile") {
434 logfile = read_setting_value(i, end);
436 else if (setting == "restart") {
437 string restart = read_setting_value(i, end);
438 auto_restart = (restart == "yes" || restart == "true");
440 else if (setting == "smooth-recovery") {
441 string recovery = read_setting_value(i, end);
442 smooth_recovery = (recovery == "yes" || recovery == "true");
444 else if (setting == "type") {
445 string type_str = read_setting_value(i, end);
446 if (type_str == "scripted") {
447 service_type = service_type_t::SCRIPTED;
449 else if (type_str == "process") {
450 service_type = service_type_t::PROCESS;
452 else if (type_str == "bgprocess") {
453 service_type = service_type_t::BGPROCESS;
455 else if (type_str == "internal") {
456 service_type = service_type_t::INTERNAL;
459 throw service_description_exc(name, "Service type must be one of: \"scripted\","
460 " \"process\", \"bgprocess\" or \"internal\"");
463 else if (setting == "options") {
464 std::list<std::pair<unsigned,unsigned>> indices;
465 string onstart_cmds = read_setting_value(i, end, &indices);
466 for (auto indexpair : indices) {
467 string option_txt = onstart_cmds.substr(indexpair.first,
468 indexpair.second - indexpair.first);
469 if (option_txt == "starts-rwfs") {
470 onstart_flags.rw_ready = true;
472 else if (option_txt == "starts-log") {
473 onstart_flags.log_ready = true;
475 else if (option_txt == "no-sigterm") {
476 onstart_flags.no_sigterm = true;
478 else if (option_txt == "runs-on-console") {
479 onstart_flags.runs_on_console = true;
480 // A service that runs on the console necessarily starts on console:
481 onstart_flags.starts_on_console = true;
483 else if (option_txt == "starts-on-console") {
484 onstart_flags.starts_on_console = true;
486 else if (option_txt == "pass-cs-fd") {
487 onstart_flags.pass_cs_fd = true;
489 else if (option_txt == "start-interruptible") {
490 onstart_flags.start_interruptible = true;
492 else if (option_txt == "skippable") {
493 onstart_flags.skippable = true;
495 else if (option_txt == "signal-process-only") {
496 onstart_flags.signal_process_only = true;
499 throw service_description_exc(name, "Unknown option: " + option_txt);
503 else if (setting == "load-options") {
504 std::list<std::pair<unsigned,unsigned>> indices;
505 string load_opts = read_setting_value(i, end, &indices);
506 for (auto indexpair : indices) {
507 string option_txt = load_opts.substr(indexpair.first,
508 indexpair.second - indexpair.first);
509 if (option_txt == "sub-vars") {
510 // substitute environment variables in command line
513 else if (option_txt == "no-sub-vars") {
517 throw service_description_exc(name, "Unknown load option: " + option_txt);
521 else if (setting == "termsignal") {
522 string signame = read_setting_value(i, end, nullptr);
523 int signo = signal_name_to_number(signame);
525 throw service_description_exc(name, "Unknown/unsupported termination signal: "
532 else if (setting == "restart-limit-interval") {
533 string interval_str = read_setting_value(i, end, nullptr);
534 parse_timespec(interval_str, name, "restart-limit-interval", restart_interval);
536 else if (setting == "restart-delay") {
537 string rsdelay_str = read_setting_value(i, end, nullptr);
538 parse_timespec(rsdelay_str, name, "restart-delay", restart_delay);
540 else if (setting == "restart-limit-count") {
541 string limit_str = read_setting_value(i, end, nullptr);
542 max_restarts = parse_unum_param(limit_str, name, std::numeric_limits<int>::max());
544 else if (setting == "stop-timeout") {
545 string stoptimeout_str = read_setting_value(i, end, nullptr);
546 parse_timespec(stoptimeout_str, name, "stop-timeout", stop_timeout);
548 else if (setting == "start-timeout") {
549 string starttimeout_str = read_setting_value(i, end, nullptr);
550 parse_timespec(starttimeout_str, name, "start-timeout", start_timeout);
552 else if (setting == "run-as") {
553 string run_as_str = read_setting_value(i, end, nullptr);
554 run_as_uid = parse_uid_param(run_as_str, name, &run_as_gid);
557 throw service_description_exc(name, "Unknown setting: " + setting);
561 service_file.close();
563 if (service_type == service_type_t::PROCESS || service_type == service_type_t::BGPROCESS
564 || service_type == service_type_t::SCRIPTED) {
565 if (command.length() == 0) {
566 throw service_description_exc(name, "Service command not specified");
570 // Now replace the dummy service record with a real record:
571 for (auto iter = records.begin(); iter != records.end(); iter++) {
573 // We've found the dummy record
575 if (service_type == service_type_t::PROCESS) {
576 do_env_subst(command, command_offsets, do_sub_vars);
577 auto rvalps = new process_service(this, string(name), std::move(command),
578 command_offsets, depends);
579 rvalps->set_workding_dir(working_dir);
580 rvalps->set_restart_interval(restart_interval, max_restarts);
581 rvalps->set_restart_delay(restart_delay);
582 rvalps->set_stop_timeout(stop_timeout);
583 rvalps->set_start_timeout(start_timeout);
584 rvalps->set_extra_termination_signal(term_signal);
585 rvalps->set_run_as_uid_gid(run_as_uid, run_as_gid);
586 rvalps->set_workding_dir(working_dir);
587 // process service start / run on console must be the same:
588 onstart_flags.starts_on_console = onstart_flags.runs_on_console;
591 else if (service_type == service_type_t::BGPROCESS) {
592 do_env_subst(command, command_offsets, do_sub_vars);
593 auto rvalps = new bgproc_service(this, string(name), std::move(command),
594 command_offsets, depends);
595 rvalps->set_workding_dir(working_dir);
596 rvalps->set_pid_file(std::move(pid_file));
597 rvalps->set_restart_interval(restart_interval, max_restarts);
598 rvalps->set_restart_delay(restart_delay);
599 rvalps->set_stop_timeout(stop_timeout);
600 rvalps->set_start_timeout(start_timeout);
601 rvalps->set_extra_termination_signal(term_signal);
602 rvalps->set_run_as_uid_gid(run_as_uid, run_as_gid);
603 onstart_flags.runs_on_console = false;
606 else if (service_type == service_type_t::SCRIPTED) {
607 do_env_subst(command, command_offsets, do_sub_vars);
608 auto rvalps = new scripted_service(this, string(name), std::move(command),
609 command_offsets, depends);
610 rvalps->set_stop_command(stop_command, stop_command_offsets);
611 rvalps->set_workding_dir(working_dir);
612 rvalps->set_stop_timeout(stop_timeout);
613 rvalps->set_start_timeout(start_timeout);
614 rvalps->set_extra_termination_signal(term_signal);
615 rvalps->set_run_as_uid_gid(run_as_uid, run_as_gid);
619 rval = new service_record(this, string(name), service_type, depends);
621 rval->set_log_file(logfile);
622 rval->set_auto_restart(auto_restart);
623 rval->set_smooth_recovery(smooth_recovery);
624 rval->set_flags(onstart_flags);
625 rval->set_socket_details(std::move(socket_path), socket_perms, socket_uid, socket_gid);
633 catch (setting_exception &setting_exc)
635 // Must remove the dummy service record.
636 records.erase(std::find(records.begin(), records.end(), rval));
638 throw service_description_exc(name, std::move(setting_exc.get_info()));
641 // Must remove the dummy service record.
642 records.erase(std::find(records.begin(), records.end(), rval));