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 // prelim_dep: A preliminary (unresolved) service dependency
32 dependency_type dep_type;
34 prelim_dep(std::string &name_p, dependency_type dep_type_p)
35 : name(name_p), dep_type(dep_type_p) { }
36 prelim_dep(std::string &&name_p, dependency_type dep_type_p)
37 : name(std::move(name_p)), dep_type(dep_type_p) { }
43 service_record(std::string name, std::list<prelim_dep> dependencies_p) : dependencies(dependencies_p) {}
46 bool finished_loading = false; // flag used to detect cyclic dependencies
47 std::list<prelim_dep> dependencies;
50 using service_set_t = std::map<std::string, service_record *>;
52 service_record *load_service(service_set_t &services, const std::string &name,
53 const service_dir_pathlist &service_dirs);
55 // Add some missing standard library functionality...
56 template <typename T> bool contains(std::vector<T> vec, const T& elem)
58 return std::find(vec.begin(), vec.end(), elem) != vec.end();
61 int main(int argc, char **argv)
65 service_dir_opt service_dir_opts;
66 bool am_system_init = (getuid() == 0);
68 std::vector<std::string> services_to_check;
70 // Process command line
72 for (int i = 1; i < argc; i++) {
73 if (argv[i][0] == '-') {
74 if (argv[i][0] == '-') {
76 if (strcmp(argv[i], "--services-dir") == 0 || strcmp(argv[i], "-d") == 0) {
78 service_dir_opts.set_specified_service_dir(argv[i]);
81 cerr << "dinitcheck: '--services-dir' (-d) requires an argument" << endl;
86 // TODO handle other options, err if unrecognized
91 service_dir_opts.build_paths(am_system_init);
93 // Temporary, for testing:
94 services_to_check.push_back("boot");
96 // Load named service(s)
97 std::map<std::string, service_record *> service_set;
99 // - load the service, store dependencies as strings
102 // TODO additional: check chain-to, other lint
104 for (size_t i = 0; i < services_to_check.size(); ++i) {
105 const std::string &name = services_to_check[i];
106 std::cout << "Checking service: " << name << "...\n";
108 service_record *sr = load_service(service_set, name, service_dir_opts.get_paths());
109 service_set[name] = sr;
110 // add dependencies to services_to_check
111 for (auto &dep : sr->dependencies) {
112 if (service_set.count(dep.name) == 0 && !contains(services_to_check, dep.name)) {
113 services_to_check.push_back(dep.name);
117 catch (service_load_exc &exc) {
118 std::cerr << "Unable to load service '" << name << "': " << exc.exc_description << "\n";
122 // TODO check for circular dependencies
127 static void report_unknown_setting_error(const std::string &service_name, const char *setting_name)
129 std::cerr << "Service '" << service_name << "', unknown setting: '" << setting_name << "'.\n";
132 static void report_error(dinit_load::setting_exception &exc, const std::string &service_name, const char *setting_name)
134 std::cerr << "Service '" << service_name << "', " << setting_name << ": " << exc.get_info() << "\n";
137 static void report_service_description_exc(service_description_exc &exc)
139 std::cerr << "Service '" << exc.service_name << "': " << exc.exc_description << "\n";
142 static void report_error(std::system_error &exc, const std::string &service_name)
144 std::cerr << "Service '" << service_name << "', error reading service description: " << exc.what() << "\n";
147 static void report_dir_error(const char *service_name, const std::string &dirpath)
149 std::cerr << "Service '" << service_name << "', error reading dependencies from directory " << dirpath
150 << ": " << strerror(errno) << "\n";
153 // Process a dependency directory - filenames contained within correspond to service names which
154 // are loaded and added as a dependency of the given type. Expected use is with a directory
155 // containing symbolic links to other service descriptions, but this isn't required.
156 // Failure to read the directory contents, or to find a service listed within, is not considered
158 static void process_dep_dir(const char *servicename,
159 const string &service_filename,
160 std::list<prelim_dep> &deplist, const std::string &depdirpath,
161 dependency_type dep_type)
163 std::string depdir_fname = combine_paths(parent_path(service_filename), depdirpath.c_str());
165 DIR *depdir = opendir(depdir_fname.c_str());
166 if (depdir == nullptr) {
167 report_dir_error(servicename, depdirpath);
172 dirent * dent = readdir(depdir);
173 while (dent != nullptr) {
174 char * name = dent->d_name;
175 if (name[0] != '.') {
176 deplist.emplace_back(name, dep_type);
178 dent = readdir(depdir);
182 report_dir_error(servicename, depdirpath);
188 // TODO: this is pretty much copy-paste from load_service.cc. Need to factor out common structure.
189 service_record *load_service(service_set_t &services, const std::string &name,
190 const service_dir_pathlist &service_dirs)
193 using namespace dinit_load;
195 auto found = services.find(name);
196 if (found != services.end()) {
197 return found->second;
200 string service_filename;
201 ifstream service_file;
203 // Couldn't find one. Have to load it.
204 for (auto &service_dir : service_dirs) {
205 service_filename = service_dir.get_dir();
206 if (*(service_filename.rbegin()) != '/') {
207 service_filename += '/';
209 service_filename += name;
211 service_file.open(service_filename.c_str(), ios::in);
212 if (service_file) break;
215 if (! service_file) {
216 throw service_not_found(string(name));
220 list<pair<unsigned,unsigned>> command_offsets;
222 list<pair<unsigned,unsigned>> stop_command_offsets;
227 bool do_sub_vars = false;
229 service_type_t service_type = service_type_t::PROCESS;
230 std::list<prelim_dep> depends;
232 service_flags_t onstart_flags;
233 int term_signal = -1; // additional termination signal
234 bool auto_restart = false;
235 bool smooth_recovery = false;
237 int socket_perms = 0666;
238 // Note: Posix allows that uid_t and gid_t may be unsigned types, but eg chown uses -1 as an
239 // invalid value, so it's safe to assume that we can do the same:
240 uid_t socket_uid = -1;
241 gid_t socket_gid = -1;
242 // Restart limit interval / count; default is 10 seconds, 3 restarts:
243 timespec restart_interval = { .tv_sec = 10, .tv_nsec = 0 };
244 int max_restarts = 3;
245 timespec restart_delay = { .tv_sec = 0, .tv_nsec = 200000000 };
246 timespec stop_timeout = { .tv_sec = 10, .tv_nsec = 0 };
247 timespec start_timeout = { .tv_sec = 60, .tv_nsec = 0 };
248 std::vector<service_rlimits> rlimits;
250 int readiness_fd = -1; // readiness fd in service process
251 std::string readiness_var; // environment var to hold readiness fd
253 uid_t run_as_uid = -1;
254 gid_t run_as_gid = -1;
256 string chain_to_name;
259 char inittab_id[sizeof(utmpx().ut_id)] = {0};
260 char inittab_line[sizeof(utmpx().ut_line)] = {0};
264 service_file.exceptions(ios::badbit);
267 process_service_file(name, service_file,
268 [&](string &line, string &setting, string_iterator &i, string_iterator &end) -> void {
270 if (setting == "command") {
271 command = read_setting_value(i, end, &command_offsets);
273 else if (setting == "working-dir") {
274 working_dir = read_setting_value(i, end, nullptr);
276 else if (setting == "env-file") {
277 env_file = read_setting_value(i, end, nullptr);
279 else if (setting == "socket-listen") {
280 socket_path = read_setting_value(i, end, nullptr);
282 else if (setting == "socket-permissions") {
283 string sock_perm_str = read_setting_value(i, end, nullptr);
286 socket_perms = std::stoi(sock_perm_str, &ind, 8);
287 if (ind != sock_perm_str.length()) {
288 throw std::logic_error("");
291 catch (std::logic_error &exc) {
292 throw service_description_exc(name, "socket-permissions: Badly-formed or "
293 "out-of-range numeric value");
296 else if (setting == "socket-uid") {
297 string sock_uid_s = read_setting_value(i, end, nullptr);
298 socket_uid = parse_uid_param(sock_uid_s, name, "socket-uid", &socket_gid);
300 else if (setting == "socket-gid") {
301 string sock_gid_s = read_setting_value(i, end, nullptr);
302 socket_gid = parse_gid_param(sock_gid_s, "socket-gid", name);
304 else if (setting == "stop-command") {
305 stop_command = read_setting_value(i, end, &stop_command_offsets);
307 else if (setting == "pid-file") {
308 pid_file = read_setting_value(i, end);
310 else if (setting == "depends-on") {
311 string dependency_name = read_setting_value(i, end);
312 depends.emplace_back(std::move(dependency_name), dependency_type::REGULAR);
314 else if (setting == "depends-ms") {
315 string dependency_name = read_setting_value(i, end);
316 depends.emplace_back(dependency_name, dependency_type::MILESTONE);
318 else if (setting == "waits-for") {
319 string dependency_name = read_setting_value(i, end);
320 depends.emplace_back(dependency_name, dependency_type::WAITS_FOR);
322 else if (setting == "waits-for.d") {
323 string waitsford = read_setting_value(i, end);
324 process_dep_dir(name.c_str(), service_filename, depends, waitsford,
325 dependency_type::WAITS_FOR);
327 else if (setting == "logfile") {
328 logfile = read_setting_value(i, end);
330 else if (setting == "restart") {
331 string restart = read_setting_value(i, end);
332 auto_restart = (restart == "yes" || restart == "true");
334 else if (setting == "smooth-recovery") {
335 string recovery = read_setting_value(i, end);
336 smooth_recovery = (recovery == "yes" || recovery == "true");
338 else if (setting == "type") {
339 string type_str = read_setting_value(i, end);
340 if (type_str == "scripted") {
341 service_type = service_type_t::SCRIPTED;
343 else if (type_str == "process") {
344 service_type = service_type_t::PROCESS;
346 else if (type_str == "bgprocess") {
347 service_type = service_type_t::BGPROCESS;
349 else if (type_str == "internal") {
350 service_type = service_type_t::INTERNAL;
353 throw service_description_exc(name, "Service type must be one of: \"scripted\","
354 " \"process\", \"bgprocess\" or \"internal\"");
357 else if (setting == "options") {
358 std::list<std::pair<unsigned,unsigned>> indices;
359 string onstart_cmds = read_setting_value(i, end, &indices);
360 for (auto indexpair : indices) {
361 string option_txt = onstart_cmds.substr(indexpair.first,
362 indexpair.second - indexpair.first);
363 if (option_txt == "starts-rwfs") {
364 onstart_flags.rw_ready = true;
366 else if (option_txt == "starts-log") {
367 onstart_flags.log_ready = true;
369 else if (option_txt == "no-sigterm") {
370 onstart_flags.no_sigterm = true;
372 else if (option_txt == "runs-on-console") {
373 onstart_flags.runs_on_console = true;
374 // A service that runs on the console necessarily starts on console:
375 onstart_flags.starts_on_console = true;
376 onstart_flags.shares_console = false;
378 else if (option_txt == "starts-on-console") {
379 onstart_flags.starts_on_console = true;
380 onstart_flags.shares_console = false;
382 else if (option_txt == "shares-console") {
383 onstart_flags.shares_console = true;
384 onstart_flags.runs_on_console = false;
385 onstart_flags.starts_on_console = false;
387 else if (option_txt == "pass-cs-fd") {
388 onstart_flags.pass_cs_fd = true;
390 else if (option_txt == "start-interruptible") {
391 onstart_flags.start_interruptible = true;
393 else if (option_txt == "skippable") {
394 onstart_flags.skippable = true;
396 else if (option_txt == "signal-process-only") {
397 onstart_flags.signal_process_only = true;
400 throw service_description_exc(name, "Unknown option: " + option_txt);
404 else if (setting == "load-options") {
405 std::list<std::pair<unsigned,unsigned>> indices;
406 string load_opts = read_setting_value(i, end, &indices);
407 for (auto indexpair : indices) {
408 string option_txt = load_opts.substr(indexpair.first,
409 indexpair.second - indexpair.first);
410 if (option_txt == "sub-vars") {
411 // substitute environment variables in command line
414 else if (option_txt == "no-sub-vars") {
418 throw service_description_exc(name, "Unknown load option: " + option_txt);
422 else if (setting == "term-signal" || setting == "termsignal") {
423 // Note: "termsignal" supported for legacy reasons.
424 string signame = read_setting_value(i, end, nullptr);
425 int signo = signal_name_to_number(signame);
427 throw service_description_exc(name, "Unknown/unsupported termination signal: "
434 else if (setting == "restart-limit-interval") {
435 string interval_str = read_setting_value(i, end, nullptr);
436 parse_timespec(interval_str, name, "restart-limit-interval", restart_interval);
438 else if (setting == "restart-delay") {
439 string rsdelay_str = read_setting_value(i, end, nullptr);
440 parse_timespec(rsdelay_str, name, "restart-delay", restart_delay);
442 else if (setting == "restart-limit-count") {
443 string limit_str = read_setting_value(i, end, nullptr);
444 max_restarts = parse_unum_param(limit_str, name, std::numeric_limits<int>::max());
446 else if (setting == "stop-timeout") {
447 string stoptimeout_str = read_setting_value(i, end, nullptr);
448 parse_timespec(stoptimeout_str, name, "stop-timeout", stop_timeout);
450 else if (setting == "start-timeout") {
451 string starttimeout_str = read_setting_value(i, end, nullptr);
452 parse_timespec(starttimeout_str, name, "start-timeout", start_timeout);
454 else if (setting == "run-as") {
455 string run_as_str = read_setting_value(i, end, nullptr);
456 run_as_uid = parse_uid_param(run_as_str, name, "run-as", &run_as_gid);
458 else if (setting == "chain-to") {
459 chain_to_name = read_setting_value(i, end, nullptr);
461 else if (setting == "ready-notification") {
462 string notify_setting = read_setting_value(i, end, nullptr);
463 if (starts_with(notify_setting, "pipefd:")) {
464 readiness_fd = parse_unum_param(notify_setting.substr(7 /* len 'pipefd:' */),
465 name, std::numeric_limits<int>::max());
467 else if (starts_with(notify_setting, "pipevar:")) {
468 readiness_var = notify_setting.substr(8 /* len 'pipevar:' */);
469 if (readiness_var.empty()) {
470 throw service_description_exc(name, "Invalid pipevar variable name "
471 "in ready-notification");
475 throw service_description_exc(name, "Unknown ready-notification setting: "
479 else if (setting == "inittab-id") {
480 string inittab_setting = read_setting_value(i, end, nullptr);
482 if (inittab_setting.length() > sizeof(inittab_id)) {
483 throw service_description_exc(name, "inittab-id setting is too long");
485 strncpy(inittab_id, inittab_setting.c_str(), sizeof(inittab_id));
488 else if (setting == "inittab-line") {
489 string inittab_setting = read_setting_value(i, end, nullptr);
491 if (inittab_setting.length() > sizeof(inittab_line)) {
492 throw service_description_exc(name, "inittab-line setting is too long");
494 strncpy(inittab_line, inittab_setting.c_str(), sizeof(inittab_line));
497 else if (setting == "rlimit-nofile") {
498 string nofile_setting = read_setting_value(i, end, nullptr);
499 service_rlimits &nofile_limits = find_rlimits(rlimits, RLIMIT_NOFILE);
500 parse_rlimit(line, name, "rlimit-nofile", nofile_limits);
502 else if (setting == "rlimit-core") {
503 string nofile_setting = read_setting_value(i, end, nullptr);
504 service_rlimits &nofile_limits = find_rlimits(rlimits, RLIMIT_CORE);
505 parse_rlimit(line, name, "rlimit-core", nofile_limits);
507 else if (setting == "rlimit-data") {
508 string nofile_setting = read_setting_value(i, end, nullptr);
509 service_rlimits &nofile_limits = find_rlimits(rlimits, RLIMIT_DATA);
510 parse_rlimit(line, name, "rlimit-data", nofile_limits);
512 else if (setting == "rlimit-addrspace") {
513 #if defined(RLIMIT_AS)
514 string nofile_setting = read_setting_value(i, end, nullptr);
515 service_rlimits &nofile_limits = find_rlimits(rlimits, RLIMIT_AS);
516 parse_rlimit(line, name, "rlimit-addrspace", nofile_limits);
520 report_unknown_setting_error(name, setting.c_str());
523 catch (service_description_exc &exc) {
524 report_service_description_exc(exc);
526 catch (setting_exception &exc) {
527 report_error(exc, name, setting.c_str());
531 catch (std::system_error &sys_err)
533 report_error(sys_err, name);
537 return new service_record(name, depends);