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"
20 #include "options-processing.h"
22 // dinitcheck: utility to check Dinit configuration for correctness/lint
24 using string = std::string;
25 using string_iterator = std::string::iterator;
27 static const char *user_home_path = nullptr;
29 // Get user home (and set user_home_path). (The return may become invalid after
30 // changing the environment (HOME variable) or using the getpwuid() function).
31 static const char * get_user_home()
33 if (user_home_path == nullptr) {
34 user_home_path = getenv("HOME");
35 if (user_home_path == nullptr) {
36 struct passwd * pwuid_p = getpwuid(getuid());
37 if (pwuid_p != nullptr) {
38 user_home_path = pwuid_p->pw_dir;
42 return user_home_path;
49 dependency_type dep_type;
51 prelim_dep(std::string &name_p, dependency_type dep_type_p)
52 : name(name_p), dep_type(dep_type_p) { }
53 prelim_dep(std::string &&name_p, dependency_type dep_type_p)
54 : name(std::move(name_p)), dep_type(dep_type_p) { }
60 service_record(std::string name, std::list<prelim_dep> dependencies_p) : dependencies(dependencies_p) {}
63 bool finished_loading = false; // flag used to detect cyclic dependencies
64 std::list<prelim_dep> dependencies;
67 using service_set_t = std::map<std::string, service_record *>;
69 service_record *load_service(service_set_t &services, const std::string &name,
70 const std::vector<dir_entry> &service_dirs);
72 // Add some missing standard library functionality...
73 template <typename T> bool contains(std::vector<T> vec, const T& elem)
75 return std::find(vec.begin(), vec.end(), elem) != vec.end();
78 int main(int argc, char **argv)
82 bool add_all_service_dirs = false;
83 bool for_system = false;
84 const char * service_dir = nullptr;
85 bool service_dir_dynamic = false; // service_dir dynamically allocated?
87 std::vector<std::string> services_to_check;
89 // Figure out service dirs
90 /* service directory name */
91 if (service_dir == nullptr && ! for_system) {
92 const char * userhome = get_user_home();
93 if (userhome != nullptr) {
94 const char * user_home = get_user_home();
95 size_t user_home_len = strlen(user_home);
96 size_t dinit_d_len = strlen("/dinit.d");
97 size_t full_len = user_home_len + dinit_d_len + 1;
98 char *service_dir_w = new char[full_len];
99 std::memcpy(service_dir_w, user_home, user_home_len);
100 std::memcpy(service_dir_w + user_home_len, "/dinit.d", dinit_d_len);
101 service_dir_w[full_len - 1] = 0;
103 service_dir = service_dir_w;
104 service_dir_dynamic = true;
108 if (service_dir == nullptr) {
109 service_dir = "/etc/dinit.d";
110 add_all_service_dirs = true;
113 std::vector<dir_entry> service_dirs;
115 service_dirs.emplace_back(service_dir, service_dir_dynamic);
116 if (add_all_service_dirs) {
117 service_dirs.emplace_back("/usr/local/lib/dinit.d", false);
118 service_dirs.emplace_back("/lib/dinit.d", false);
121 // Temporary, for testing:
122 services_to_check.push_back("boot");
124 // Load named service(s)
125 std::map<std::string, service_record *> service_set;
127 // - load the service, store dependencies as strings
130 // additional: check chain-to, other lint
132 for (const auto &name : services_to_check) {
134 service_record *sr = load_service(service_set, name, service_dirs);
135 service_set[name] = sr;
136 // add dependencies to services_to_check
137 for (auto &dep : sr->dependencies) {
138 if (service_set.count(dep.name) == 0 && !contains(services_to_check, dep.name)) {
139 services_to_check.push_back(dep.name);
143 catch (service_load_exc &exc) {
144 std::cerr << "Unable to load service '" << name << "': " << exc.exc_description << "\n";
148 // check for circular dependencies
153 static void report_error(dinit_load::setting_exception &exc, const std::string &service_name, const char *setting_name)
155 std::cerr << "Service '" << service_name << "', " << setting_name << ": " << exc.get_info() << "\n";
158 static void report_error(std::system_error &exc, const std::string &service_name)
160 std::cerr << "Service '" << service_name << "', error reading service description: " << exc.what() << "\n";
163 static void report_dir_error(const char *service_name, const std::string &dirpath)
165 std::cerr << "Service '" << service_name << "', error reading dependencies from directory " << dirpath
166 << ": " << strerror(errno) << "\n";
169 // Process a dependency directory - filenames contained within correspond to service names which
170 // are loaded and added as a dependency of the given type. Expected use is with a directory
171 // containing symbolic links to other service descriptions, but this isn't required.
172 // Failure to read the directory contents, or to find a service listed within, is not considered
174 static void process_dep_dir(const char *servicename,
175 const string &service_filename,
176 std::list<prelim_dep> &deplist, const std::string &depdirpath,
177 dependency_type dep_type)
179 std::string depdir_fname = combine_paths(parent_path(service_filename), depdirpath.c_str());
181 DIR *depdir = opendir(depdir_fname.c_str());
182 if (depdir == nullptr) {
183 report_dir_error(servicename, depdirpath);
188 dirent * dent = readdir(depdir);
189 while (dent != nullptr) {
190 char * name = dent->d_name;
191 if (name[0] != '.') {
192 deplist.emplace_back(name, dep_type);
194 dent = readdir(depdir);
198 report_dir_error(servicename, depdirpath);
204 service_record *load_service(service_set_t &services, const std::string &name,
205 const std::vector<dir_entry> &service_dirs)
208 using namespace dinit_load;
210 auto found = services.find(name);
211 if (found != services.end()) {
212 return found->second;
215 string service_filename;
216 ifstream service_file;
218 // Couldn't find one. Have to load it.
219 for (auto &service_dir : service_dirs) {
220 service_filename = service_dir.get_dir();
221 if (*(service_filename.rbegin()) != '/') {
222 service_filename += '/';
224 service_filename += name;
226 service_file.open(service_filename.c_str(), ios::in);
227 if (service_file) break;
230 if (! service_file) {
231 throw service_not_found(string(name));
235 list<pair<unsigned,unsigned>> command_offsets;
237 list<pair<unsigned,unsigned>> stop_command_offsets;
242 bool do_sub_vars = false;
244 service_type_t service_type = service_type_t::PROCESS;
245 std::list<prelim_dep> depends;
247 service_flags_t onstart_flags;
248 int term_signal = -1; // additional termination signal
249 bool auto_restart = false;
250 bool smooth_recovery = false;
252 int socket_perms = 0666;
253 // Note: Posix allows that uid_t and gid_t may be unsigned types, but eg chown uses -1 as an
254 // invalid value, so it's safe to assume that we can do the same:
255 uid_t socket_uid = -1;
256 gid_t socket_gid = -1;
257 // Restart limit interval / count; default is 10 seconds, 3 restarts:
258 timespec restart_interval = { .tv_sec = 10, .tv_nsec = 0 };
259 int max_restarts = 3;
260 timespec restart_delay = { .tv_sec = 0, .tv_nsec = 200000000 };
261 timespec stop_timeout = { .tv_sec = 10, .tv_nsec = 0 };
262 timespec start_timeout = { .tv_sec = 60, .tv_nsec = 0 };
263 std::vector<service_rlimits> rlimits;
265 int readiness_fd = -1; // readiness fd in service process
266 std::string readiness_var; // environment var to hold readiness fd
268 uid_t run_as_uid = -1;
269 gid_t run_as_gid = -1;
271 string chain_to_name;
274 char inittab_id[sizeof(utmpx().ut_id)] = {0};
275 char inittab_line[sizeof(utmpx().ut_line)] = {0};
279 service_file.exceptions(ios::badbit);
282 process_service_file(name, service_file,
283 [&](string &line, string &setting, string_iterator &i, string_iterator &end) -> void {
285 if (setting == "command") {
286 command = read_setting_value(i, end, &command_offsets);
288 else if (setting == "working-dir") {
289 working_dir = read_setting_value(i, end, nullptr);
291 else if (setting == "env-file") {
292 env_file = read_setting_value(i, end, nullptr);
294 else if (setting == "socket-listen") {
295 socket_path = read_setting_value(i, end, nullptr);
297 else if (setting == "socket-permissions") {
298 string sock_perm_str = read_setting_value(i, end, nullptr);
301 socket_perms = std::stoi(sock_perm_str, &ind, 8);
302 if (ind != sock_perm_str.length()) {
303 throw std::logic_error("");
306 catch (std::logic_error &exc) {
307 throw service_description_exc(name, "socket-permissions: Badly-formed or "
308 "out-of-range numeric value");
311 else if (setting == "socket-uid") {
312 string sock_uid_s = read_setting_value(i, end, nullptr);
313 socket_uid = parse_uid_param(sock_uid_s, name, &socket_gid);
315 else if (setting == "socket-gid") {
316 string sock_gid_s = read_setting_value(i, end, nullptr);
317 socket_gid = parse_gid_param(sock_gid_s, name);
319 else if (setting == "stop-command") {
320 stop_command = read_setting_value(i, end, &stop_command_offsets);
322 else if (setting == "pid-file") {
323 pid_file = read_setting_value(i, end);
325 else if (setting == "depends-on") {
326 string dependency_name = read_setting_value(i, end);
327 depends.emplace_back(std::move(dependency_name), dependency_type::REGULAR);
329 else if (setting == "depends-ms") {
330 string dependency_name = read_setting_value(i, end);
331 depends.emplace_back(dependency_name, dependency_type::MILESTONE);
333 else if (setting == "waits-for") {
334 string dependency_name = read_setting_value(i, end);
335 depends.emplace_back(dependency_name, dependency_type::WAITS_FOR);
337 else if (setting == "waits-for.d") {
338 string waitsford = read_setting_value(i, end);
339 process_dep_dir(name.c_str(), service_filename, depends, waitsford,
340 dependency_type::WAITS_FOR);
342 else if (setting == "logfile") {
343 logfile = read_setting_value(i, end);
345 else if (setting == "restart") {
346 string restart = read_setting_value(i, end);
347 auto_restart = (restart == "yes" || restart == "true");
349 else if (setting == "smooth-recovery") {
350 string recovery = read_setting_value(i, end);
351 smooth_recovery = (recovery == "yes" || recovery == "true");
353 else if (setting == "type") {
354 string type_str = read_setting_value(i, end);
355 if (type_str == "scripted") {
356 service_type = service_type_t::SCRIPTED;
358 else if (type_str == "process") {
359 service_type = service_type_t::PROCESS;
361 else if (type_str == "bgprocess") {
362 service_type = service_type_t::BGPROCESS;
364 else if (type_str == "internal") {
365 service_type = service_type_t::INTERNAL;
368 throw service_description_exc(name, "Service type must be one of: \"scripted\","
369 " \"process\", \"bgprocess\" or \"internal\"");
372 else if (setting == "options") {
373 std::list<std::pair<unsigned,unsigned>> indices;
374 string onstart_cmds = read_setting_value(i, end, &indices);
375 for (auto indexpair : indices) {
376 string option_txt = onstart_cmds.substr(indexpair.first,
377 indexpair.second - indexpair.first);
378 if (option_txt == "starts-rwfs") {
379 onstart_flags.rw_ready = true;
381 else if (option_txt == "starts-log") {
382 onstart_flags.log_ready = true;
384 else if (option_txt == "no-sigterm") {
385 onstart_flags.no_sigterm = true;
387 else if (option_txt == "runs-on-console") {
388 onstart_flags.runs_on_console = true;
389 // A service that runs on the console necessarily starts on console:
390 onstart_flags.starts_on_console = true;
391 onstart_flags.shares_console = false;
393 else if (option_txt == "starts-on-console") {
394 onstart_flags.starts_on_console = true;
395 onstart_flags.shares_console = false;
397 else if (option_txt == "shares-console") {
398 onstart_flags.shares_console = true;
399 onstart_flags.runs_on_console = false;
400 onstart_flags.starts_on_console = false;
402 else if (option_txt == "pass-cs-fd") {
403 onstart_flags.pass_cs_fd = true;
405 else if (option_txt == "start-interruptible") {
406 onstart_flags.start_interruptible = true;
408 else if (option_txt == "skippable") {
409 onstart_flags.skippable = true;
411 else if (option_txt == "signal-process-only") {
412 onstart_flags.signal_process_only = true;
415 throw service_description_exc(name, "Unknown option: " + option_txt);
419 else if (setting == "load-options") {
420 std::list<std::pair<unsigned,unsigned>> indices;
421 string load_opts = read_setting_value(i, end, &indices);
422 for (auto indexpair : indices) {
423 string option_txt = load_opts.substr(indexpair.first,
424 indexpair.second - indexpair.first);
425 if (option_txt == "sub-vars") {
426 // substitute environment variables in command line
429 else if (option_txt == "no-sub-vars") {
433 throw service_description_exc(name, "Unknown load option: " + option_txt);
437 else if (setting == "term-signal" || setting == "termsignal") {
438 // Note: "termsignal" supported for legacy reasons.
439 string signame = read_setting_value(i, end, nullptr);
440 int signo = signal_name_to_number(signame);
442 throw service_description_exc(name, "Unknown/unsupported termination signal: "
449 else if (setting == "restart-limit-interval") {
450 string interval_str = read_setting_value(i, end, nullptr);
451 parse_timespec(interval_str, name, "restart-limit-interval", restart_interval);
453 else if (setting == "restart-delay") {
454 string rsdelay_str = read_setting_value(i, end, nullptr);
455 parse_timespec(rsdelay_str, name, "restart-delay", restart_delay);
457 else if (setting == "restart-limit-count") {
458 string limit_str = read_setting_value(i, end, nullptr);
459 max_restarts = parse_unum_param(limit_str, name, std::numeric_limits<int>::max());
461 else if (setting == "stop-timeout") {
462 string stoptimeout_str = read_setting_value(i, end, nullptr);
463 parse_timespec(stoptimeout_str, name, "stop-timeout", stop_timeout);
465 else if (setting == "start-timeout") {
466 string starttimeout_str = read_setting_value(i, end, nullptr);
467 parse_timespec(starttimeout_str, name, "start-timeout", start_timeout);
469 else if (setting == "run-as") {
470 string run_as_str = read_setting_value(i, end, nullptr);
471 run_as_uid = parse_uid_param(run_as_str, name, &run_as_gid);
473 else if (setting == "chain-to") {
474 chain_to_name = read_setting_value(i, end, nullptr);
476 else if (setting == "ready-notification") {
477 string notify_setting = read_setting_value(i, end, nullptr);
478 if (starts_with(notify_setting, "pipefd:")) {
479 readiness_fd = parse_unum_param(notify_setting.substr(7 /* len 'pipefd:' */),
480 name, std::numeric_limits<int>::max());
482 else if (starts_with(notify_setting, "pipevar:")) {
483 readiness_var = notify_setting.substr(8 /* len 'pipevar:' */);
484 if (readiness_var.empty()) {
485 throw service_description_exc(name, "Invalid pipevar variable name "
486 "in ready-notification");
490 throw service_description_exc(name, "Unknown ready-notification setting: "
494 else if (setting == "inittab-id") {
495 string inittab_setting = read_setting_value(i, end, nullptr);
497 if (inittab_setting.length() > sizeof(inittab_id)) {
498 throw service_description_exc(name, "inittab-id setting is too long");
500 strncpy(inittab_id, inittab_setting.c_str(), sizeof(inittab_id));
503 else if (setting == "inittab-line") {
504 string inittab_setting = read_setting_value(i, end, nullptr);
506 if (inittab_setting.length() > sizeof(inittab_line)) {
507 throw service_description_exc(name, "inittab-line setting is too long");
509 strncpy(inittab_line, inittab_setting.c_str(), sizeof(inittab_line));
512 else if (setting == "rlimit-nofile") {
513 string nofile_setting = read_setting_value(i, end, nullptr);
514 service_rlimits &nofile_limits = find_rlimits(rlimits, RLIMIT_NOFILE);
515 parse_rlimit(line, name, "rlimit-nofile", nofile_limits);
517 else if (setting == "rlimit-core") {
518 string nofile_setting = read_setting_value(i, end, nullptr);
519 service_rlimits &nofile_limits = find_rlimits(rlimits, RLIMIT_CORE);
520 parse_rlimit(line, name, "rlimit-core", nofile_limits);
522 else if (setting == "rlimit-data") {
523 string nofile_setting = read_setting_value(i, end, nullptr);
524 service_rlimits &nofile_limits = find_rlimits(rlimits, RLIMIT_DATA);
525 parse_rlimit(line, name, "rlimit-data", nofile_limits);
527 else if (setting == "rlimit-addrspace") {
528 #if defined(RLIMIT_AS)
529 string nofile_setting = read_setting_value(i, end, nullptr);
530 service_rlimits &nofile_limits = find_rlimits(rlimits, RLIMIT_AS);
531 parse_rlimit(line, name, "rlimit-addrspace", nofile_limits);
535 throw service_description_exc(name, "Unknown setting: " + setting);
538 catch (setting_exception &exc) {
539 report_error(exc, name, setting.c_str());
543 catch (std::system_error &sys_err)
545 report_error(sys_err, name);
549 return new service_record(name, depends);