11 #include <sys/types.h>
13 #include <sys/resource.h>
17 #include "dinit-util.h"
18 #include "service-constants.h"
19 #include "load-service.h"
21 // dinitcheck: utility to check Dinit configuration for correctness/lint
23 using string = std::string;
24 using string_iterator = std::string::iterator;
26 static const char *user_home_path = nullptr;
28 // Get user home (and set user_home_path). (The return may become invalid after
29 // changing the environment (HOME variable) or using the getpwuid() function).
30 static const char * get_user_home()
32 if (user_home_path == nullptr) {
33 user_home_path = getenv("HOME");
34 if (user_home_path == nullptr) {
35 struct passwd * pwuid_p = getpwuid(getuid());
36 if (pwuid_p != nullptr) {
37 user_home_path = pwuid_p->pw_dir;
41 return user_home_path;
48 dependency_type dep_type;
50 prelim_dep(std::string &name_p, dependency_type dep_type_p)
51 : name(name_p), dep_type(dep_type_p) { }
52 prelim_dep(std::string &&name_p, dependency_type dep_type_p)
53 : name(std::move(name_p)), dep_type(dep_type_p) { }
59 service_record(std::string name, std::list<prelim_dep> dependencies_p) : dependencies(dependencies_p) {}
62 bool finished_loading = false; // flag used to detect cyclic dependencies
63 std::list<prelim_dep> dependencies;
66 using service_set_t = std::map<std::string, service_record *>;
68 service_record *load_service(service_set_t &services, const std::string &name,
69 const std::vector<dinit_load::dir_entry> &service_dirs);
71 // Add some missing standard library functionality...
72 template <typename T> bool contains(std::vector<T> vec, const T& elem)
74 return std::find(vec.begin(), vec.end(), elem) != vec.end();
77 int main(int argc, char **argv)
81 bool add_all_service_dirs = false;
82 bool for_system = false;
83 const char * service_dir = nullptr;
84 bool service_dir_dynamic = false; // service_dir dynamically allocated?
86 std::vector<std::string> services_to_check;
88 // Figure out service dirs
89 /* service directory name */
90 if (service_dir == nullptr && ! for_system) {
91 const char * userhome = get_user_home();
92 if (userhome != nullptr) {
93 const char * user_home = get_user_home();
94 size_t user_home_len = strlen(user_home);
95 size_t dinit_d_len = strlen("/dinit.d");
96 size_t full_len = user_home_len + dinit_d_len + 1;
97 char *service_dir_w = new char[full_len];
98 std::memcpy(service_dir_w, user_home, user_home_len);
99 std::memcpy(service_dir_w + user_home_len, "/dinit.d", dinit_d_len);
100 service_dir_w[full_len - 1] = 0;
102 service_dir = service_dir_w;
103 service_dir_dynamic = true;
107 if (service_dir == nullptr) {
108 service_dir = "/etc/dinit.d";
109 add_all_service_dirs = true;
112 std::vector<dinit_load::dir_entry> service_dirs;
114 service_dirs.emplace_back(service_dir, service_dir_dynamic);
115 if (add_all_service_dirs) {
116 service_dirs.emplace_back("/usr/local/lib/dinit.d", false);
117 service_dirs.emplace_back("/lib/dinit.d", false);
120 // Temporary, for testing:
121 services_to_check.push_back("boot");
123 // Load named service(s)
124 std::map<std::string, service_record *> service_set;
126 // - load the service, store dependencies as strings
129 // additional: check chain-to, other lint
131 for (const auto &name : services_to_check) {
133 service_record *sr = load_service(service_set, name, service_dirs);
134 service_set[name] = sr;
135 // add dependencies to services_to_check
136 for (auto &dep : sr->dependencies) {
137 if (service_set.count(dep.name) == 0 && !contains(services_to_check, dep.name)) {
138 services_to_check.push_back(dep.name);
142 catch (service_load_exc &exc) {
143 std::cerr << "Unable to load service '" << name << "': " << exc.exc_description << "\n";
147 // check for circular dependencies
152 static void report_error(dinit_load::setting_exception &exc, const std::string &service_name, const char *setting_name)
154 std::cerr << "Service '" << service_name << "', " << setting_name << ": " << exc.get_info() << "\n";
157 static void report_error(std::system_error &exc, const std::string &service_name)
159 std::cerr << "Service '" << service_name << "', error reading service description: " << exc.what() << "\n";
162 static void report_dir_error(const char *service_name, const std::string &dirpath)
164 std::cerr << "Service '" << service_name << "', error reading dependencies from directory " << dirpath
165 << ": " << strerror(errno) << "\n";
168 // Process a dependency directory - filenames contained within correspond to service names which
169 // are loaded and added as a dependency of the given type. Expected use is with a directory
170 // containing symbolic links to other service descriptions, but this isn't required.
171 // Failure to read the directory contents, or to find a service listed within, is not considered
173 static void process_dep_dir(const char *servicename,
174 const string &service_filename,
175 std::list<prelim_dep> &deplist, const std::string &depdirpath,
176 dependency_type dep_type)
178 std::string depdir_fname = combine_paths(parent_path(service_filename), depdirpath.c_str());
180 DIR *depdir = opendir(depdir_fname.c_str());
181 if (depdir == nullptr) {
182 report_dir_error(servicename, depdirpath);
187 dirent * dent = readdir(depdir);
188 while (dent != nullptr) {
189 char * name = dent->d_name;
190 if (name[0] != '.') {
191 deplist.emplace_back(name, dep_type);
193 dent = readdir(depdir);
197 report_dir_error(servicename, depdirpath);
203 service_record *load_service(service_set_t &services, const std::string &name,
204 const std::vector<dinit_load::dir_entry> &service_dirs)
207 using namespace dinit_load;
209 auto found = services.find(name);
210 if (found != services.end()) {
211 return found->second;
214 string service_filename;
215 ifstream service_file;
217 // Couldn't find one. Have to load it.
218 for (auto &service_dir : service_dirs) {
219 service_filename = service_dir.get_dir();
220 if (*(service_filename.rbegin()) != '/') {
221 service_filename += '/';
223 service_filename += name;
225 service_file.open(service_filename.c_str(), ios::in);
226 if (service_file) break;
229 if (! service_file) {
230 throw service_not_found(string(name));
234 list<pair<unsigned,unsigned>> command_offsets;
236 list<pair<unsigned,unsigned>> stop_command_offsets;
241 bool do_sub_vars = false;
243 service_type_t service_type = service_type_t::PROCESS;
244 std::list<prelim_dep> depends;
246 service_flags_t onstart_flags;
247 int term_signal = -1; // additional termination signal
248 bool auto_restart = false;
249 bool smooth_recovery = false;
251 int socket_perms = 0666;
252 // Note: Posix allows that uid_t and gid_t may be unsigned types, but eg chown uses -1 as an
253 // invalid value, so it's safe to assume that we can do the same:
254 uid_t socket_uid = -1;
255 gid_t socket_gid = -1;
256 // Restart limit interval / count; default is 10 seconds, 3 restarts:
257 timespec restart_interval = { .tv_sec = 10, .tv_nsec = 0 };
258 int max_restarts = 3;
259 timespec restart_delay = { .tv_sec = 0, .tv_nsec = 200000000 };
260 timespec stop_timeout = { .tv_sec = 10, .tv_nsec = 0 };
261 timespec start_timeout = { .tv_sec = 60, .tv_nsec = 0 };
262 std::vector<service_rlimits> rlimits;
264 int readiness_fd = -1; // readiness fd in service process
265 std::string readiness_var; // environment var to hold readiness fd
267 uid_t run_as_uid = -1;
268 gid_t run_as_gid = -1;
270 string chain_to_name;
273 char inittab_id[sizeof(utmpx().ut_id)] = {0};
274 char inittab_line[sizeof(utmpx().ut_line)] = {0};
278 service_file.exceptions(ios::badbit);
281 process_service_file(name, service_file,
282 [&](string &line, string &setting, string_iterator &i, string_iterator &end) -> void {
284 if (setting == "command") {
285 command = read_setting_value(i, end, &command_offsets);
287 else if (setting == "working-dir") {
288 working_dir = read_setting_value(i, end, nullptr);
290 else if (setting == "env-file") {
291 env_file = read_setting_value(i, end, nullptr);
293 else if (setting == "socket-listen") {
294 socket_path = read_setting_value(i, end, nullptr);
296 else if (setting == "socket-permissions") {
297 string sock_perm_str = read_setting_value(i, end, nullptr);
300 socket_perms = std::stoi(sock_perm_str, &ind, 8);
301 if (ind != sock_perm_str.length()) {
302 throw std::logic_error("");
305 catch (std::logic_error &exc) {
306 throw service_description_exc(name, "socket-permissions: Badly-formed or "
307 "out-of-range numeric value");
310 else if (setting == "socket-uid") {
311 string sock_uid_s = read_setting_value(i, end, nullptr);
312 socket_uid = parse_uid_param(sock_uid_s, name, &socket_gid);
314 else if (setting == "socket-gid") {
315 string sock_gid_s = read_setting_value(i, end, nullptr);
316 socket_gid = parse_gid_param(sock_gid_s, name);
318 else if (setting == "stop-command") {
319 stop_command = read_setting_value(i, end, &stop_command_offsets);
321 else if (setting == "pid-file") {
322 pid_file = read_setting_value(i, end);
324 else if (setting == "depends-on") {
325 string dependency_name = read_setting_value(i, end);
326 depends.emplace_back(std::move(dependency_name), dependency_type::REGULAR);
328 else if (setting == "depends-ms") {
329 string dependency_name = read_setting_value(i, end);
330 depends.emplace_back(dependency_name, dependency_type::MILESTONE);
332 else if (setting == "waits-for") {
333 string dependency_name = read_setting_value(i, end);
334 depends.emplace_back(dependency_name, dependency_type::WAITS_FOR);
336 else if (setting == "waits-for.d") {
337 string waitsford = read_setting_value(i, end);
338 process_dep_dir(name.c_str(), service_filename, depends, waitsford,
339 dependency_type::WAITS_FOR);
341 else if (setting == "logfile") {
342 logfile = read_setting_value(i, end);
344 else if (setting == "restart") {
345 string restart = read_setting_value(i, end);
346 auto_restart = (restart == "yes" || restart == "true");
348 else if (setting == "smooth-recovery") {
349 string recovery = read_setting_value(i, end);
350 smooth_recovery = (recovery == "yes" || recovery == "true");
352 else if (setting == "type") {
353 string type_str = read_setting_value(i, end);
354 if (type_str == "scripted") {
355 service_type = service_type_t::SCRIPTED;
357 else if (type_str == "process") {
358 service_type = service_type_t::PROCESS;
360 else if (type_str == "bgprocess") {
361 service_type = service_type_t::BGPROCESS;
363 else if (type_str == "internal") {
364 service_type = service_type_t::INTERNAL;
367 throw service_description_exc(name, "Service type must be one of: \"scripted\","
368 " \"process\", \"bgprocess\" or \"internal\"");
371 else if (setting == "options") {
372 std::list<std::pair<unsigned,unsigned>> indices;
373 string onstart_cmds = read_setting_value(i, end, &indices);
374 for (auto indexpair : indices) {
375 string option_txt = onstart_cmds.substr(indexpair.first,
376 indexpair.second - indexpair.first);
377 if (option_txt == "starts-rwfs") {
378 onstart_flags.rw_ready = true;
380 else if (option_txt == "starts-log") {
381 onstart_flags.log_ready = true;
383 else if (option_txt == "no-sigterm") {
384 onstart_flags.no_sigterm = true;
386 else if (option_txt == "runs-on-console") {
387 onstart_flags.runs_on_console = true;
388 // A service that runs on the console necessarily starts on console:
389 onstart_flags.starts_on_console = true;
390 onstart_flags.shares_console = false;
392 else if (option_txt == "starts-on-console") {
393 onstart_flags.starts_on_console = true;
394 onstart_flags.shares_console = false;
396 else if (option_txt == "shares-console") {
397 onstart_flags.shares_console = true;
398 onstart_flags.runs_on_console = false;
399 onstart_flags.starts_on_console = false;
401 else if (option_txt == "pass-cs-fd") {
402 onstart_flags.pass_cs_fd = true;
404 else if (option_txt == "start-interruptible") {
405 onstart_flags.start_interruptible = true;
407 else if (option_txt == "skippable") {
408 onstart_flags.skippable = true;
410 else if (option_txt == "signal-process-only") {
411 onstart_flags.signal_process_only = true;
414 throw service_description_exc(name, "Unknown option: " + option_txt);
418 else if (setting == "load-options") {
419 std::list<std::pair<unsigned,unsigned>> indices;
420 string load_opts = read_setting_value(i, end, &indices);
421 for (auto indexpair : indices) {
422 string option_txt = load_opts.substr(indexpair.first,
423 indexpair.second - indexpair.first);
424 if (option_txt == "sub-vars") {
425 // substitute environment variables in command line
428 else if (option_txt == "no-sub-vars") {
432 throw service_description_exc(name, "Unknown load option: " + option_txt);
436 else if (setting == "term-signal" || setting == "termsignal") {
437 // Note: "termsignal" supported for legacy reasons.
438 string signame = read_setting_value(i, end, nullptr);
439 int signo = signal_name_to_number(signame);
441 throw service_description_exc(name, "Unknown/unsupported termination signal: "
448 else if (setting == "restart-limit-interval") {
449 string interval_str = read_setting_value(i, end, nullptr);
450 parse_timespec(interval_str, name, "restart-limit-interval", restart_interval);
452 else if (setting == "restart-delay") {
453 string rsdelay_str = read_setting_value(i, end, nullptr);
454 parse_timespec(rsdelay_str, name, "restart-delay", restart_delay);
456 else if (setting == "restart-limit-count") {
457 string limit_str = read_setting_value(i, end, nullptr);
458 max_restarts = parse_unum_param(limit_str, name, std::numeric_limits<int>::max());
460 else if (setting == "stop-timeout") {
461 string stoptimeout_str = read_setting_value(i, end, nullptr);
462 parse_timespec(stoptimeout_str, name, "stop-timeout", stop_timeout);
464 else if (setting == "start-timeout") {
465 string starttimeout_str = read_setting_value(i, end, nullptr);
466 parse_timespec(starttimeout_str, name, "start-timeout", start_timeout);
468 else if (setting == "run-as") {
469 string run_as_str = read_setting_value(i, end, nullptr);
470 run_as_uid = parse_uid_param(run_as_str, name, &run_as_gid);
472 else if (setting == "chain-to") {
473 chain_to_name = read_setting_value(i, end, nullptr);
475 else if (setting == "ready-notification") {
476 string notify_setting = read_setting_value(i, end, nullptr);
477 if (starts_with(notify_setting, "pipefd:")) {
478 readiness_fd = parse_unum_param(notify_setting.substr(7 /* len 'pipefd:' */),
479 name, std::numeric_limits<int>::max());
481 else if (starts_with(notify_setting, "pipevar:")) {
482 readiness_var = notify_setting.substr(8 /* len 'pipevar:' */);
483 if (readiness_var.empty()) {
484 throw service_description_exc(name, "Invalid pipevar variable name "
485 "in ready-notification");
489 throw service_description_exc(name, "Unknown ready-notification setting: "
493 else if (setting == "inittab-id") {
494 string inittab_setting = read_setting_value(i, end, nullptr);
496 if (inittab_setting.length() > sizeof(inittab_id)) {
497 throw service_description_exc(name, "inittab-id setting is too long");
499 strncpy(inittab_id, inittab_setting.c_str(), sizeof(inittab_id));
502 else if (setting == "inittab-line") {
503 string inittab_setting = read_setting_value(i, end, nullptr);
505 if (inittab_setting.length() > sizeof(inittab_line)) {
506 throw service_description_exc(name, "inittab-line setting is too long");
508 strncpy(inittab_line, inittab_setting.c_str(), sizeof(inittab_line));
511 else if (setting == "rlimit-nofile") {
512 string nofile_setting = read_setting_value(i, end, nullptr);
513 service_rlimits &nofile_limits = find_rlimits(rlimits, RLIMIT_NOFILE);
514 parse_rlimit(line, name, "rlimit-nofile", nofile_limits);
516 else if (setting == "rlimit-core") {
517 string nofile_setting = read_setting_value(i, end, nullptr);
518 service_rlimits &nofile_limits = find_rlimits(rlimits, RLIMIT_CORE);
519 parse_rlimit(line, name, "rlimit-core", nofile_limits);
521 else if (setting == "rlimit-data") {
522 string nofile_setting = read_setting_value(i, end, nullptr);
523 service_rlimits &nofile_limits = find_rlimits(rlimits, RLIMIT_DATA);
524 parse_rlimit(line, name, "rlimit-data", nofile_limits);
526 else if (setting == "rlimit-addrspace") {
527 #if defined(RLIMIT_AS)
528 string nofile_setting = read_setting_value(i, end, nullptr);
529 service_rlimits &nofile_limits = find_rlimits(rlimits, RLIMIT_AS);
530 parse_rlimit(line, name, "rlimit-addrspace", nofile_limits);
534 throw service_description_exc(name, "Unknown setting: " + setting);
537 catch (setting_exception &exc) {
538 report_error(exc, name, setting.c_str());
542 catch (std::system_error &sys_err)
544 report_error(sys_err, name);
548 return new service_record(name, depends);