std::string name;
dependency_type dep_type;
- prelim_dep(std::string &name_p, dependency_type dep_type_p)
+ prelim_dep(const std::string &name_p, dependency_type dep_type_p)
: name(name_p), dep_type(dep_type_p) { }
prelim_dep(std::string &&name_p, dependency_type dep_type_p)
: name(std::move(name_p)), dep_type(dep_type_p) { }
class service_record
{
public:
- service_record(std::string name, std::list<prelim_dep> dependencies_p) : dependencies(dependencies_p) {}
+ service_record(std::string name_p, std::list<prelim_dep> dependencies_p)
+ : name(name_p), dependencies(dependencies_p) {}
std::string name;
- bool finished_loading = false; // flag used to detect cyclic dependencies
std::list<prelim_dep> dependencies;
+
+ bool visited = false; // flag used to detect cyclic dependencies
+ bool cycle_free = false;
};
using service_set_t = std::map<std::string, service_record *>;
return std::find(vec.begin(), vec.end(), elem) != vec.end();
}
+static bool errors_found = false;
+
int main(int argc, char **argv)
{
using namespace std;
if (argc > 1) {
for (int i = 1; i < argc; i++) {
if (argv[i][0] == '-') {
- if (argv[i][0] == '-') {
- // An option...
- if (strcmp(argv[i], "--services-dir") == 0 || strcmp(argv[i], "-d") == 0) {
- if (++i < argc) {
- service_dir_opts.set_specified_service_dir(argv[i]);
- }
- else {
- cerr << "dinitcheck: '--services-dir' (-d) requires an argument" << endl;
- return 1;
- }
+ // An option...
+ if (strcmp(argv[i], "--services-dir") == 0 || strcmp(argv[i], "-d") == 0) {
+ if (++i < argc) {
+ service_dir_opts.set_specified_service_dir(argv[i]);
+ }
+ else {
+ cerr << "dinitcheck: '--services-dir' (-d) requires an argument" << endl;
+ return 1;
}
}
- // TODO handle other options, err if unrecognized
+ else if (strcmp(argv[i], "--help") == 0) {
+ cout << "dinitcheck: check dinit service descriptions\n"
+ " --help display help\n"
+ " --services-dir <dir>, -d <dir>\n"
+ " set base directory for service description\n"
+ " files\n"
+ " <service-name> check service with name <service-name>\n";
+ return EXIT_SUCCESS;
+ }
+ else {
+ std::cerr << "dinitcheck: Unrecognized option: '" << argv[i] << "' (use '--help' for help)\n";
+ return EXIT_FAILURE;
+ }
+ }
+ else {
+ services_to_check.push_back(argv[i]);
}
}
}
service_dir_opts.build_paths(am_system_init);
- // Temporary, for testing:
- services_to_check.push_back("boot");
+ if (services_to_check.empty()) {
+ services_to_check.push_back("boot");
+ }
+
+ size_t num_services_to_check = services_to_check.size();
// Load named service(s)
- std::map<std::string, service_record *> service_set;
-
// - load the service, store dependencies as strings
// - recurse
- // TODO additional: check chain-to, other lint
+ std::map<std::string, service_record *> service_set;
for (size_t i = 0; i < services_to_check.size(); ++i) {
const std::string &name = services_to_check[i];
}
catch (service_load_exc &exc) {
std::cerr << "Unable to load service '" << name << "': " << exc.exc_description << "\n";
+ errors_found = true;
}
}
- // TODO check for circular dependencies
+ // Check for circular dependencies
+ std::vector<std::tuple<service_record *, size_t>> service_chain;
+
+ for (size_t i = 0; i < num_services_to_check; ++i) {
+ service_record *root = service_set[services_to_check[i]];
+ if (! root) continue;
+ if (root->visited) continue;
+
+ // invariant: service_chain is empty
+ service_chain.emplace_back(root, 0);
+
+ // Depth first traversal. If we find a link (dependency) on a service already visited (but not
+ // marked as cycle-free), we know then that we've found a cycle.
+ while (true) {
+ auto n = service_chain.size() - 1;
+ auto &last = service_chain[n];
+ service_record *last_record = std::get<0>(last);
+ size_t &index = std::get<1>(last);
+ if (index >= last_record->dependencies.size()) {
+ // Processed all dependencies, go back up:
+ last_record->cycle_free = true;
+ service_chain.pop_back();
+ if (n == 0) break;
+ size_t &prev_index = std::get<1>(service_chain[n - 1]);
+ ++prev_index;
+ continue;
+ }
+ // Down the tree:
+ auto dep_it = std::next(last_record->dependencies.begin(), index);
+ service_record *next_link = service_set[dep_it->name];
+ if (next_link == nullptr) {
+ ++index;
+ continue;
+ }
+ if (next_link->visited) {
+ if (! next_link->cycle_free) {
+ // We've found a cycle. Clear entries before the beginning of the cycle, then
+ // exit the loop.
+ auto first = std::find_if(service_chain.begin(), service_chain.end(),
+ [next_link](std::tuple<service_record *, size_t> &a) -> bool {
+ return std::get<0>(a) == next_link;
+ });
+ service_chain.erase(service_chain.begin(), first);
+ break;
+ }
+ }
+ next_link->visited = true;
+ service_chain.emplace_back(next_link, 0);
+ }
- return 0;
-}
+ // Report only one cycle; otherwise difficult to avoid reporting duplicates or overlapping
+ // cycles.
+ if (!service_chain.empty()) break;
+ }
-static void report_unknown_setting_error(const std::string &service_name, const char *setting_name)
-{
- std::cerr << "Service '" << service_name << "', unknown setting: '" << setting_name << "'.\n";
+ if (!service_chain.empty()) {
+ errors_found = true;
+ std::cerr << "Found dependency cycle:\n";
+ for (auto chain_link : service_chain) {
+ std::cerr << " " << std::get<0>(chain_link)->name << " ->\n";
+ }
+ std::cerr << " " << std::get<0>(service_chain[0])->name << ".\n";
+ }
+
+ // TODO additional: check chain-to, other lint
+
+ if (! errors_found) {
+ std::cout << "No problems found.\n";
+ }
+ else {
+ std::cout << "One or more errors found.\n";
+ }
+
+ return errors_found ? EXIT_FAILURE : EXIT_SUCCESS;
}
-static void report_error(dinit_load::setting_exception &exc, const std::string &service_name, const char *setting_name)
+static void report_service_description_err(const std::string &service_name, const std::string &what)
{
- std::cerr << "Service '" << service_name << "', " << setting_name << ": " << exc.get_info() << "\n";
+ std::cerr << "Service '" << service_name << "': " << what << "\n";
+ errors_found = true;
}
static void report_service_description_exc(service_description_exc &exc)
{
- std::cerr << "Service '" << exc.service_name << "': " << exc.exc_description << "\n";
+ report_service_description_err(exc.service_name, exc.exc_description);
}
static void report_error(std::system_error &exc, const std::string &service_name)
{
std::cerr << "Service '" << service_name << "', error reading service description: " << exc.what() << "\n";
+ errors_found = true;
}
static void report_dir_error(const char *service_name, const std::string &dirpath)
{
std::cerr << "Service '" << service_name << "', error reading dependencies from directory " << dirpath
<< ": " << strerror(errno) << "\n";
+ errors_found = true;
}
// Process a dependency directory - filenames contained within correspond to service names which
closedir(depdir);
}
-// TODO: this is pretty much copy-paste from load_service.cc. Need to factor out common structure.
service_record *load_service(service_set_t &services, const std::string &name,
const service_dir_pathlist &service_dirs)
{
throw service_not_found(string(name));
}
- string command;
- list<pair<unsigned,unsigned>> command_offsets;
- string stop_command;
- list<pair<unsigned,unsigned>> stop_command_offsets;
- string working_dir;
- string pid_file;
- string env_file;
-
- bool do_sub_vars = false;
-
- service_type_t service_type = service_type_t::PROCESS;
- std::list<prelim_dep> depends;
- string logfile;
- service_flags_t onstart_flags;
- int term_signal = -1; // additional termination signal
- bool auto_restart = false;
- bool smooth_recovery = false;
- string socket_path;
- int socket_perms = 0666;
- // Note: Posix allows that uid_t and gid_t may be unsigned types, but eg chown uses -1 as an
- // invalid value, so it's safe to assume that we can do the same:
- uid_t socket_uid = -1;
- gid_t socket_gid = -1;
- // Restart limit interval / count; default is 10 seconds, 3 restarts:
- timespec restart_interval = { .tv_sec = 10, .tv_nsec = 0 };
- int max_restarts = 3;
- timespec restart_delay = { .tv_sec = 0, .tv_nsec = 200000000 };
- timespec stop_timeout = { .tv_sec = 10, .tv_nsec = 0 };
- timespec start_timeout = { .tv_sec = 60, .tv_nsec = 0 };
- std::vector<service_rlimits> rlimits;
-
- int readiness_fd = -1; // readiness fd in service process
- std::string readiness_var; // environment var to hold readiness fd
-
- uid_t run_as_uid = -1;
- gid_t run_as_gid = -1;
-
- string chain_to_name;
-
- #if USE_UTMPX
- char inittab_id[sizeof(utmpx().ut_id)] = {0};
- char inittab_line[sizeof(utmpx().ut_line)] = {0};
- #endif
+ service_settings_wrapper<prelim_dep> settings;
string line;
service_file.exceptions(ios::badbit);
try {
process_service_file(name, service_file,
[&](string &line, string &setting, string_iterator &i, string_iterator &end) -> void {
+
+ auto process_dep_dir_n = [&](std::list<prelim_dep> &deplist, const std::string &waitsford,
+ dependency_type dep_type) -> void {
+ process_dep_dir(name.c_str(), service_filename, deplist, waitsford, dep_type);
+ };
+
+ auto load_service_n = [&](const string &dep_name) -> const string & {
+ return dep_name;
+ };
+
try {
- if (setting == "command") {
- command = read_setting_value(i, end, &command_offsets);
- }
- else if (setting == "working-dir") {
- working_dir = read_setting_value(i, end, nullptr);
- }
- else if (setting == "env-file") {
- env_file = read_setting_value(i, end, nullptr);
- }
- else if (setting == "socket-listen") {
- socket_path = read_setting_value(i, end, nullptr);
- }
- else if (setting == "socket-permissions") {
- string sock_perm_str = read_setting_value(i, end, nullptr);
- std::size_t ind = 0;
- try {
- socket_perms = std::stoi(sock_perm_str, &ind, 8);
- if (ind != sock_perm_str.length()) {
- throw std::logic_error("");
- }
- }
- catch (std::logic_error &exc) {
- throw service_description_exc(name, "socket-permissions: Badly-formed or "
- "out-of-range numeric value");
- }
- }
- else if (setting == "socket-uid") {
- string sock_uid_s = read_setting_value(i, end, nullptr);
- socket_uid = parse_uid_param(sock_uid_s, name, "socket-uid", &socket_gid);
- }
- else if (setting == "socket-gid") {
- string sock_gid_s = read_setting_value(i, end, nullptr);
- socket_gid = parse_gid_param(sock_gid_s, "socket-gid", name);
- }
- else if (setting == "stop-command") {
- stop_command = read_setting_value(i, end, &stop_command_offsets);
- }
- else if (setting == "pid-file") {
- pid_file = read_setting_value(i, end);
- }
- else if (setting == "depends-on") {
- string dependency_name = read_setting_value(i, end);
- depends.emplace_back(std::move(dependency_name), dependency_type::REGULAR);
- }
- else if (setting == "depends-ms") {
- string dependency_name = read_setting_value(i, end);
- depends.emplace_back(dependency_name, dependency_type::MILESTONE);
- }
- else if (setting == "waits-for") {
- string dependency_name = read_setting_value(i, end);
- depends.emplace_back(dependency_name, dependency_type::WAITS_FOR);
- }
- else if (setting == "waits-for.d") {
- string waitsford = read_setting_value(i, end);
- process_dep_dir(name.c_str(), service_filename, depends, waitsford,
- dependency_type::WAITS_FOR);
- }
- else if (setting == "logfile") {
- logfile = read_setting_value(i, end);
- }
- else if (setting == "restart") {
- string restart = read_setting_value(i, end);
- auto_restart = (restart == "yes" || restart == "true");
- }
- else if (setting == "smooth-recovery") {
- string recovery = read_setting_value(i, end);
- smooth_recovery = (recovery == "yes" || recovery == "true");
- }
- else if (setting == "type") {
- string type_str = read_setting_value(i, end);
- if (type_str == "scripted") {
- service_type = service_type_t::SCRIPTED;
- }
- else if (type_str == "process") {
- service_type = service_type_t::PROCESS;
- }
- else if (type_str == "bgprocess") {
- service_type = service_type_t::BGPROCESS;
- }
- else if (type_str == "internal") {
- service_type = service_type_t::INTERNAL;
- }
- else {
- throw service_description_exc(name, "Service type must be one of: \"scripted\","
- " \"process\", \"bgprocess\" or \"internal\"");
- }
- }
- else if (setting == "options") {
- std::list<std::pair<unsigned,unsigned>> indices;
- string onstart_cmds = read_setting_value(i, end, &indices);
- for (auto indexpair : indices) {
- string option_txt = onstart_cmds.substr(indexpair.first,
- indexpair.second - indexpair.first);
- if (option_txt == "starts-rwfs") {
- onstart_flags.rw_ready = true;
- }
- else if (option_txt == "starts-log") {
- onstart_flags.log_ready = true;
- }
- else if (option_txt == "no-sigterm") {
- onstart_flags.no_sigterm = true;
- }
- else if (option_txt == "runs-on-console") {
- onstart_flags.runs_on_console = true;
- // A service that runs on the console necessarily starts on console:
- onstart_flags.starts_on_console = true;
- onstart_flags.shares_console = false;
- }
- else if (option_txt == "starts-on-console") {
- onstart_flags.starts_on_console = true;
- onstart_flags.shares_console = false;
- }
- else if (option_txt == "shares-console") {
- onstart_flags.shares_console = true;
- onstart_flags.runs_on_console = false;
- onstart_flags.starts_on_console = false;
- }
- else if (option_txt == "pass-cs-fd") {
- onstart_flags.pass_cs_fd = true;
- }
- else if (option_txt == "start-interruptible") {
- onstart_flags.start_interruptible = true;
- }
- else if (option_txt == "skippable") {
- onstart_flags.skippable = true;
- }
- else if (option_txt == "signal-process-only") {
- onstart_flags.signal_process_only = true;
- }
- else {
- throw service_description_exc(name, "Unknown option: " + option_txt);
- }
- }
- }
- else if (setting == "load-options") {
- std::list<std::pair<unsigned,unsigned>> indices;
- string load_opts = read_setting_value(i, end, &indices);
- for (auto indexpair : indices) {
- string option_txt = load_opts.substr(indexpair.first,
- indexpair.second - indexpair.first);
- if (option_txt == "sub-vars") {
- // substitute environment variables in command line
- do_sub_vars = true;
- }
- else if (option_txt == "no-sub-vars") {
- do_sub_vars = false;
- }
- else {
- throw service_description_exc(name, "Unknown load option: " + option_txt);
- }
- }
- }
- else if (setting == "term-signal" || setting == "termsignal") {
- // Note: "termsignal" supported for legacy reasons.
- string signame = read_setting_value(i, end, nullptr);
- int signo = signal_name_to_number(signame);
- if (signo == -1) {
- throw service_description_exc(name, "Unknown/unsupported termination signal: "
- + signame);
- }
- else {
- term_signal = signo;
- }
- }
- else if (setting == "restart-limit-interval") {
- string interval_str = read_setting_value(i, end, nullptr);
- parse_timespec(interval_str, name, "restart-limit-interval", restart_interval);
- }
- else if (setting == "restart-delay") {
- string rsdelay_str = read_setting_value(i, end, nullptr);
- parse_timespec(rsdelay_str, name, "restart-delay", restart_delay);
- }
- else if (setting == "restart-limit-count") {
- string limit_str = read_setting_value(i, end, nullptr);
- max_restarts = parse_unum_param(limit_str, name, std::numeric_limits<int>::max());
- }
- else if (setting == "stop-timeout") {
- string stoptimeout_str = read_setting_value(i, end, nullptr);
- parse_timespec(stoptimeout_str, name, "stop-timeout", stop_timeout);
- }
- else if (setting == "start-timeout") {
- string starttimeout_str = read_setting_value(i, end, nullptr);
- parse_timespec(starttimeout_str, name, "start-timeout", start_timeout);
- }
- else if (setting == "run-as") {
- string run_as_str = read_setting_value(i, end, nullptr);
- run_as_uid = parse_uid_param(run_as_str, name, "run-as", &run_as_gid);
- }
- else if (setting == "chain-to") {
- chain_to_name = read_setting_value(i, end, nullptr);
- }
- else if (setting == "ready-notification") {
- string notify_setting = read_setting_value(i, end, nullptr);
- if (starts_with(notify_setting, "pipefd:")) {
- readiness_fd = parse_unum_param(notify_setting.substr(7 /* len 'pipefd:' */),
- name, std::numeric_limits<int>::max());
- }
- else if (starts_with(notify_setting, "pipevar:")) {
- readiness_var = notify_setting.substr(8 /* len 'pipevar:' */);
- if (readiness_var.empty()) {
- throw service_description_exc(name, "Invalid pipevar variable name "
- "in ready-notification");
- }
- }
- else {
- throw service_description_exc(name, "Unknown ready-notification setting: "
- + notify_setting);
- }
- }
- else if (setting == "inittab-id") {
- string inittab_setting = read_setting_value(i, end, nullptr);
- #if USE_UTMPX
- if (inittab_setting.length() > sizeof(inittab_id)) {
- throw service_description_exc(name, "inittab-id setting is too long");
- }
- strncpy(inittab_id, inittab_setting.c_str(), sizeof(inittab_id));
- #endif
- }
- else if (setting == "inittab-line") {
- string inittab_setting = read_setting_value(i, end, nullptr);
- #if USE_UTMPX
- if (inittab_setting.length() > sizeof(inittab_line)) {
- throw service_description_exc(name, "inittab-line setting is too long");
- }
- strncpy(inittab_line, inittab_setting.c_str(), sizeof(inittab_line));
- #endif
- }
- else if (setting == "rlimit-nofile") {
- string nofile_setting = read_setting_value(i, end, nullptr);
- service_rlimits &nofile_limits = find_rlimits(rlimits, RLIMIT_NOFILE);
- parse_rlimit(line, name, "rlimit-nofile", nofile_limits);
- }
- else if (setting == "rlimit-core") {
- string nofile_setting = read_setting_value(i, end, nullptr);
- service_rlimits &nofile_limits = find_rlimits(rlimits, RLIMIT_CORE);
- parse_rlimit(line, name, "rlimit-core", nofile_limits);
- }
- else if (setting == "rlimit-data") {
- string nofile_setting = read_setting_value(i, end, nullptr);
- service_rlimits &nofile_limits = find_rlimits(rlimits, RLIMIT_DATA);
- parse_rlimit(line, name, "rlimit-data", nofile_limits);
- }
- else if (setting == "rlimit-addrspace") {
- #if defined(RLIMIT_AS)
- string nofile_setting = read_setting_value(i, end, nullptr);
- service_rlimits &nofile_limits = find_rlimits(rlimits, RLIMIT_AS);
- parse_rlimit(line, name, "rlimit-addrspace", nofile_limits);
- #endif
- }
- else {
- report_unknown_setting_error(name, setting.c_str());
- }
+ process_service_line(settings, name.c_str(), line, setting, i, end, load_service_n, process_dep_dir_n);
}
catch (service_description_exc &exc) {
report_service_description_exc(exc);
}
- catch (setting_exception &exc) {
- report_error(exc, name, setting.c_str());
- }
});
}
catch (std::system_error &sys_err)
{
report_error(sys_err, name);
- return nullptr;
+ throw service_description_exc(name, "Error while reading service description.");
+ }
+
+ settings.finalise();
+
+ if (settings.service_type != service_type_t::INTERNAL && settings.command.length() == 0) {
+ report_service_description_err(name, "Service command not specified.");
}
- return new service_record(name, depends);
+ return new service_record(name, settings.depends);
}