dinitcheck.cc 16 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464
  1. #include <algorithm>
  2. #include <iostream>
  3. #include <fstream>
  4. #include <cstring>
  5. #include <string>
  6. #include <vector>
  7. #include <list>
  8. #include <map>
  9. #include <unistd.h>
  10. #include <sys/types.h>
  11. #include <sys/stat.h>
  12. #include <sys/time.h>
  13. #include <sys/resource.h>
  14. #include <pwd.h>
  15. #include <dirent.h>
  16. #include "dinit-util.h"
  17. #include "service-constants.h"
  18. #include "load-service.h"
  19. #include "options-processing.h"
  20. // dinitcheck: utility to check Dinit configuration for correctness/lint
  21. using string = std::string;
  22. using string_iterator = std::string::iterator;
  23. static void report_service_description_err(const std::string &service_name, const std::string &what);
  24. // prelim_dep: A preliminary (unresolved) service dependency
  25. class prelim_dep
  26. {
  27. public:
  28. std::string name;
  29. dependency_type dep_type;
  30. prelim_dep(const std::string &name_p, dependency_type dep_type_p)
  31. : name(name_p), dep_type(dep_type_p) { }
  32. prelim_dep(std::string &&name_p, dependency_type dep_type_p)
  33. : name(std::move(name_p)), dep_type(dep_type_p) { }
  34. };
  35. class service_record
  36. {
  37. public:
  38. service_record(const std::string &name_p, const std::string &chain_to_p,
  39. std::list<prelim_dep> dependencies_p, std::list<string> before_svcs)
  40. : name(name_p), dependencies(dependencies_p), before_svcs(before_svcs) {}
  41. std::string name;
  42. std::string chain_to;
  43. std::list<prelim_dep> dependencies;
  44. std::list<string> before_svcs;
  45. bool visited = false; // flag used to detect cyclic dependencies
  46. bool cycle_free = false;
  47. };
  48. using service_set_t = std::map<std::string, service_record *>;
  49. service_record *load_service(service_set_t &services, const std::string &name,
  50. const service_dir_pathlist &service_dirs);
  51. // Add some missing standard library functionality...
  52. template <typename T> bool contains(std::vector<T> vec, const T& elem)
  53. {
  54. return std::find(vec.begin(), vec.end(), elem) != vec.end();
  55. }
  56. static bool errors_found = false;
  57. int main(int argc, char **argv)
  58. {
  59. using namespace std;
  60. service_dir_opt service_dir_opts;
  61. bool am_system_init = (getuid() == 0);
  62. std::vector<std::string> services_to_check;
  63. // Process command line
  64. if (argc > 1) {
  65. for (int i = 1; i < argc; i++) {
  66. if (argv[i][0] == '-') {
  67. // An option...
  68. if (strcmp(argv[i], "--services-dir") == 0 || strcmp(argv[i], "-d") == 0) {
  69. if (++i < argc) {
  70. service_dir_opts.set_specified_service_dir(argv[i]);
  71. }
  72. else {
  73. cerr << "dinitcheck: '--services-dir' (-d) requires an argument" << endl;
  74. return 1;
  75. }
  76. }
  77. else if (strcmp(argv[i], "--help") == 0) {
  78. cout << "dinitcheck: check dinit service descriptions\n"
  79. " --help display help\n"
  80. " --services-dir <dir>, -d <dir>\n"
  81. " set base directory for service description\n"
  82. " files, can be specified multiple times\n"
  83. " <service-name> check service with name <service-name>\n";
  84. return EXIT_SUCCESS;
  85. }
  86. else {
  87. std::cerr << "dinitcheck: Unrecognized option: '" << argv[i] << "' (use '--help' for help)\n";
  88. return EXIT_FAILURE;
  89. }
  90. }
  91. else {
  92. services_to_check.push_back(argv[i]);
  93. }
  94. }
  95. }
  96. service_dir_opts.build_paths(am_system_init);
  97. if (services_to_check.empty()) {
  98. services_to_check.push_back("boot");
  99. }
  100. size_t num_services_to_check = services_to_check.size();
  101. // Load named service(s)
  102. // - load the service, store dependencies as strings
  103. // - recurse
  104. std::map<std::string, service_record *> service_set;
  105. for (size_t i = 0; i < services_to_check.size(); ++i) {
  106. const std::string &name = services_to_check[i];
  107. std::cout << "Checking service: " << name << "...\n";
  108. try {
  109. service_record *sr = load_service(service_set, name, service_dir_opts.get_paths());
  110. service_set[name] = sr;
  111. // add dependencies to services_to_check
  112. for (auto &dep : sr->dependencies) {
  113. if (service_set.count(dep.name) == 0 && !contains(services_to_check, dep.name)) {
  114. services_to_check.push_back(dep.name);
  115. }
  116. }
  117. // add chain_to to services_to_check
  118. if (!sr->chain_to.empty() && !contains(services_to_check, sr->chain_to)) {
  119. if (!contains(services_to_check, sr->chain_to)) {
  120. services_to_check.push_back(sr->chain_to);
  121. }
  122. }
  123. // add before_svcs to services_to_check
  124. for (const std::string &before_name : sr->before_svcs) {
  125. if (!contains(services_to_check, before_name)) {
  126. services_to_check.push_back(before_name);
  127. }
  128. }
  129. }
  130. catch (service_load_exc &exc) {
  131. std::cerr << "Unable to load service '" << name << "': " << exc.exc_description << "\n";
  132. errors_found = true;
  133. }
  134. }
  135. // For "before" reverse-dependencies, set up dependencies in the forwards direction (from the dependent)
  136. for (const auto &svc_name_record : service_set) {
  137. for (const std::string &before_name : svc_name_record.second->before_svcs) {
  138. auto before_svc_it = service_set.find(before_name);
  139. if (before_svc_it != service_set.end()) {
  140. before_svc_it->second->dependencies.emplace_back(svc_name_record.first,
  141. dependency_type::BEFORE);
  142. }
  143. }
  144. }
  145. // Check for circular dependencies
  146. std::vector<std::tuple<service_record *, size_t>> service_chain;
  147. for (size_t i = 0; i < num_services_to_check; ++i) {
  148. service_record *root = service_set[services_to_check[i]];
  149. if (! root) continue;
  150. if (root->visited) continue;
  151. // invariant: service_chain is empty
  152. service_chain.emplace_back(root, 0);
  153. // Depth first traversal. If we find a link (dependency) on a service already visited (but not
  154. // marked as cycle-free), we know then that we've found a cycle.
  155. while (true) {
  156. auto n = service_chain.size() - 1;
  157. auto &last = service_chain[n];
  158. service_record *last_record = std::get<0>(last);
  159. size_t &index = std::get<1>(last);
  160. if (index >= last_record->dependencies.size()) {
  161. // Processed all dependencies, go back up:
  162. last_record->cycle_free = true;
  163. service_chain.pop_back();
  164. if (n == 0) break;
  165. size_t &prev_index = std::get<1>(service_chain[n - 1]);
  166. ++prev_index;
  167. continue;
  168. }
  169. // Down the tree:
  170. auto dep_it = std::next(last_record->dependencies.begin(), index);
  171. service_record *next_link = service_set[dep_it->name];
  172. if (next_link == nullptr) {
  173. ++index;
  174. continue;
  175. }
  176. if (next_link->visited) {
  177. if (! next_link->cycle_free) {
  178. // We've found a cycle. Clear entries before the beginning of the cycle, then
  179. // exit the loop.
  180. auto first = std::find_if(service_chain.begin(), service_chain.end(),
  181. [next_link](std::tuple<service_record *, size_t> &a) -> bool {
  182. return std::get<0>(a) == next_link;
  183. });
  184. service_chain.erase(service_chain.begin(), first);
  185. break;
  186. }
  187. }
  188. next_link->visited = true;
  189. service_chain.emplace_back(next_link, 0);
  190. }
  191. // Report only one cycle; otherwise difficult to avoid reporting duplicates or overlapping
  192. // cycles.
  193. if (!service_chain.empty()) break;
  194. }
  195. if (!service_chain.empty()) {
  196. errors_found = true;
  197. std::cerr << "Found dependency cycle:\n";
  198. for (auto chain_link : service_chain) {
  199. service_record *svc = std::get<0>(chain_link);
  200. size_t dep_index = std::get<1>(chain_link);
  201. std::cerr << " " << svc->name << " ->";
  202. auto dep_it = std::next(svc->dependencies.begin(), dep_index);
  203. if (dep_it->dep_type == dependency_type::BEFORE) {
  204. std::cerr << " (via 'before')";
  205. }
  206. std::cerr << "\n";
  207. }
  208. std::cerr << " " << std::get<0>(service_chain[0])->name << ".\n";
  209. }
  210. if (! errors_found) {
  211. std::cout << "No problems found.\n";
  212. }
  213. else {
  214. std::cout << "One or more errors/warnings issued.\n";
  215. }
  216. return errors_found ? EXIT_FAILURE : EXIT_SUCCESS;
  217. }
  218. static void report_service_description_err(const std::string &service_name, unsigned line_num,
  219. const std::string &what)
  220. {
  221. std::cerr << "Service '" << service_name << "' (line " << line_num << "): " << what << "\n";
  222. errors_found = true;
  223. }
  224. static void report_service_description_err(const std::string &service_name, const char *setting_name,
  225. const std::string &what)
  226. {
  227. std::cerr << "Service '" << service_name << "' setting '" << setting_name << "': " << what << "\n";
  228. errors_found = true;
  229. }
  230. static void report_service_description_err(const std::string &service_name, const std::string &what)
  231. {
  232. std::cerr << "Service '" << service_name << "': " << what << "\n";
  233. errors_found = true;
  234. }
  235. static void report_service_description_exc(service_description_exc &exc)
  236. {
  237. if (exc.line_num != (unsigned)-1) {
  238. report_service_description_err(exc.service_name, exc.line_num, exc.exc_description);
  239. }
  240. else {
  241. report_service_description_err(exc.service_name, exc.setting_name, exc.exc_description);
  242. }
  243. }
  244. static void report_error(std::system_error &exc, const std::string &service_name)
  245. {
  246. std::cerr << "Service '" << service_name << "', error reading service description: " << exc.what() << "\n";
  247. errors_found = true;
  248. }
  249. static void report_dir_error(const char *service_name, const std::string &dirpath)
  250. {
  251. std::cerr << "Service '" << service_name << "', error reading dependencies from directory " << dirpath
  252. << ": " << strerror(errno) << "\n";
  253. errors_found = true;
  254. }
  255. // Process a dependency directory - filenames contained within correspond to service names which
  256. // are loaded and added as a dependency of the given type. Expected use is with a directory
  257. // containing symbolic links to other service descriptions, but this isn't required.
  258. // Failure to read the directory contents, or to find a service listed within, is not considered
  259. // a fatal error.
  260. static void process_dep_dir(const char *servicename,
  261. const string &service_filename,
  262. std::list<prelim_dep> &deplist, const std::string &depdirpath,
  263. dependency_type dep_type)
  264. {
  265. std::string depdir_fname = combine_paths(parent_path(service_filename), depdirpath.c_str());
  266. DIR *depdir = opendir(depdir_fname.c_str());
  267. if (depdir == nullptr) {
  268. report_dir_error(servicename, depdirpath);
  269. return;
  270. }
  271. errno = 0;
  272. dirent * dent = readdir(depdir);
  273. while (dent != nullptr) {
  274. char * name = dent->d_name;
  275. if (name[0] != '.') {
  276. deplist.emplace_back(name, dep_type);
  277. }
  278. dent = readdir(depdir);
  279. }
  280. if (errno != 0) {
  281. report_dir_error(servicename, depdirpath);
  282. }
  283. closedir(depdir);
  284. }
  285. service_record *load_service(service_set_t &services, const std::string &name,
  286. const service_dir_pathlist &service_dirs)
  287. {
  288. using namespace std;
  289. using namespace dinit_load;
  290. auto found = services.find(name);
  291. if (found != services.end()) {
  292. return found->second;
  293. }
  294. string service_filename;
  295. ifstream service_file;
  296. int fail_load_errno = 0;
  297. std::string fail_load_path;
  298. // Couldn't find one. Have to load it.
  299. for (auto &service_dir : service_dirs) {
  300. service_filename = service_dir.get_dir();
  301. if (*(service_filename.rbegin()) != '/') {
  302. service_filename += '/';
  303. }
  304. service_filename += name;
  305. service_file.open(service_filename.c_str(), ios::in);
  306. if (service_file) break;
  307. if (errno != ENOENT && fail_load_errno == 0) {
  308. fail_load_errno = errno;
  309. fail_load_path = std::move(service_filename);
  310. }
  311. }
  312. if (!service_file) {
  313. if (fail_load_errno == 0) {
  314. throw service_not_found(string(name));
  315. }
  316. else {
  317. throw service_load_error(name, std::move(fail_load_path), fail_load_errno);
  318. }
  319. }
  320. service_settings_wrapper<prelim_dep> settings;
  321. environment menv{};
  322. environment renv{};
  323. string line;
  324. service_file.exceptions(ios::badbit);
  325. try {
  326. process_service_file(name, service_file,
  327. [&](string &line, unsigned line_num, string &setting,
  328. string_iterator &i, string_iterator &end) -> void {
  329. auto process_dep_dir_n = [&](std::list<prelim_dep> &deplist, const std::string &waitsford,
  330. dependency_type dep_type) -> void {
  331. process_dep_dir(name.c_str(), service_filename, deplist, waitsford, dep_type);
  332. };
  333. auto load_service_n = [&](const string &dep_name) -> const string & {
  334. return dep_name;
  335. };
  336. try {
  337. process_service_line(settings, name.c_str(), line, line_num, setting, i, end,
  338. load_service_n, process_dep_dir_n);
  339. }
  340. catch (service_description_exc &exc) {
  341. if (exc.service_name.empty()) {
  342. exc.service_name = name;
  343. }
  344. report_service_description_exc(exc);
  345. }
  346. });
  347. }
  348. catch (std::system_error &sys_err)
  349. {
  350. report_error(sys_err, name);
  351. throw service_load_exc(name, "error while reading service description.");
  352. }
  353. auto report_err = [&](const char *msg) {
  354. report_service_description_err(name, msg);
  355. };
  356. bool issued_var_subst_warning = false;
  357. environment::env_map renvmap = renv.build(menv);
  358. auto resolve_var = [&](const string &name, environment::env_map const &envmap) {
  359. if (!issued_var_subst_warning) {
  360. report_service_description_err(name, "warning: variable substitution performed by dinitcheck "
  361. "for file paths may not match dinitd (environment may differ)");
  362. issued_var_subst_warning = true;
  363. }
  364. return resolve_env_var(name, envmap);
  365. };
  366. settings.finalise(report_err, renvmap, report_err, resolve_var);
  367. auto check_command = [&](const char *setting_name, const char *command) {
  368. struct stat command_stat;
  369. if (stat(command, &command_stat) == -1) {
  370. report_service_description_err(name,
  371. std::string("could not stat ") + setting_name + " executable '" + command
  372. + "': " + strerror(errno));
  373. }
  374. else {
  375. if ((command_stat.st_mode & S_IFMT) != S_IFREG) {
  376. report_service_description_err(name, std::string(setting_name) + " executable '"
  377. + command + "' is not a regular file.");
  378. }
  379. else if ((command_stat.st_mode & S_IXUSR) == 0) {
  380. report_service_description_err(name, std::string(setting_name) + " executable '" + command
  381. + "' is not executable by owner.");
  382. }
  383. }
  384. };
  385. if (!settings.command.empty()) {
  386. int offset_start = settings.command_offsets.front().first;
  387. int offset_end = settings.command_offsets.front().second;
  388. check_command("command", settings.command.substr(offset_start, offset_end - offset_start).c_str());
  389. }
  390. if (!settings.stop_command.empty()) {
  391. int offset_start = settings.stop_command_offsets.front().first;
  392. int offset_end = settings.stop_command_offsets.front().second;
  393. check_command("stop command",
  394. settings.stop_command.substr(offset_start, offset_end - offset_start).c_str());
  395. }
  396. return new service_record(name, settings.chain_to_name, settings.depends, settings.before_svcs);
  397. }