dinitcheck.cc 31 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816
  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 <sys/types.h>
  10. #include <sys/stat.h>
  11. #include <sys/time.h>
  12. #include <sys/resource.h>
  13. #include <sys/socket.h>
  14. #include <sys/un.h>
  15. #include <signal.h>
  16. #include <unistd.h>
  17. #include <pwd.h>
  18. #include <dirent.h>
  19. #include "dinit-util.h"
  20. #include "dinit-client.h"
  21. #include "service-constants.h"
  22. #include "load-service.h"
  23. #include "options-processing.h"
  24. // dinitcheck: utility to check Dinit configuration for correctness/lint
  25. using string = std::string;
  26. using string_iterator = std::string::iterator;
  27. static constexpr uint16_t min_cp_version = 1;
  28. static constexpr uint16_t max_cp_version = 1;
  29. static void report_service_description_err(const std::string &service_name, const std::string &what);
  30. // prelim_dep: A preliminary (unresolved) service dependency
  31. class prelim_dep
  32. {
  33. public:
  34. std::string name;
  35. dependency_type dep_type;
  36. prelim_dep(const std::string &name_p, dependency_type dep_type_p)
  37. : name(name_p), dep_type(dep_type_p) { }
  38. prelim_dep(std::string &&name_p, dependency_type dep_type_p)
  39. : name(std::move(name_p)), dep_type(dep_type_p) { }
  40. };
  41. class service_record
  42. {
  43. public:
  44. service_record(const std::string &name_p, service_type_t service_type, const std::string &chain_to_p,
  45. std::list<prelim_dep> dependencies_p, const std::list<string> &before_svcs,
  46. const std::list<string> &after_svcs, std::string consumer_of_name, log_type_id log_type)
  47. : name(name_p), service_type(service_type), dependencies(dependencies_p),
  48. before_svcs(before_svcs), after_svcs(after_svcs), consumer_of_name(consumer_of_name),
  49. log_type(log_type)
  50. {
  51. // (constructor)
  52. }
  53. std::string name;
  54. service_type_t service_type;
  55. std::string chain_to;
  56. std::list<prelim_dep> dependencies;
  57. std::list<string> before_svcs;
  58. std::list<string> after_svcs;
  59. std::string consumer_of_name;
  60. log_type_id log_type;
  61. bool visited = false; // flag used to detect cyclic dependencies
  62. bool cycle_free = false;
  63. };
  64. using service_set_t = std::map<std::string, service_record *>;
  65. service_record *load_service(service_set_t &services, const std::string &name,
  66. const service_dir_pathlist &service_dirs);
  67. // Add some missing standard library functionality...
  68. template <typename T> bool contains(std::vector<T> vec, const T& elem)
  69. {
  70. return std::find(vec.begin(), vec.end(), elem) != vec.end();
  71. }
  72. static bool errors_found = false;
  73. static bool offline_operation = true;
  74. // environment - populated from running dinit instance if possible
  75. environment menv;
  76. // Get the environment from remote dinit instance
  77. static void get_remote_env(int csfd, cpbuffer_t &rbuffer)
  78. {
  79. char buf[2] = { (char)cp_cmd::GETALLENV, 0 };
  80. write_all_x(csfd, buf, 2);
  81. wait_for_reply(rbuffer, csfd);
  82. if (rbuffer[0] != (char)cp_rply::ALLENV) {
  83. throw dinit_protocol_error();
  84. }
  85. // 1-byte packet header, then size_t data size
  86. constexpr size_t allenv_hdr_size = 1 + sizeof(size_t);
  87. rbuffer.fill_to(csfd, allenv_hdr_size);
  88. size_t data_size;
  89. rbuffer.extract(&data_size, 1, sizeof(data_size));
  90. rbuffer.consume(allenv_hdr_size);
  91. if (data_size == 0) return;
  92. if (rbuffer.get_length() == 0) {
  93. fill_some(rbuffer, csfd);
  94. }
  95. std::string env_var;
  96. while (data_size > 0) {
  97. // look for a nul terminator
  98. get_var:
  99. unsigned contig_len = rbuffer.get_contiguous_length(rbuffer.get_ptr(0));
  100. unsigned check_len = std::min((size_t) contig_len, data_size);
  101. for (unsigned i = 0; i < check_len; ++i) {
  102. if (rbuffer[i] == '\0') {
  103. // append the last portion
  104. env_var.append(rbuffer.get_ptr(0), rbuffer.get_ptr(0) + i);
  105. rbuffer.consume(i + 1);
  106. data_size -= (i + 1);
  107. menv.set_var(std::move(env_var));
  108. env_var.clear();
  109. if (data_size == 0) {
  110. // that's the last one
  111. return;
  112. }
  113. goto get_var;
  114. }
  115. }
  116. // copy what we have so far to the string, and fill some more
  117. env_var.append(rbuffer.get_ptr(0), rbuffer.get_ptr(0) + check_len);
  118. rbuffer.consume(check_len);
  119. data_size -= check_len;
  120. if (data_size == 0) {
  121. // This shouldn't happen, we didn't find the nul terminator at the end
  122. throw dinit_protocol_error();
  123. }
  124. if (rbuffer.get_length() == 0) {
  125. fill_some(rbuffer, csfd);
  126. }
  127. }
  128. }
  129. int main(int argc, char **argv)
  130. {
  131. using namespace std;
  132. service_dir_opt service_dir_opts;
  133. bool user_dinit = (getuid() != 0); // use user instance defaults/daemon instance
  134. std::string control_socket_str;
  135. const char * control_socket_path = nullptr;
  136. std::string env_file;
  137. std::vector<std::string> services_to_check;
  138. // Process command line
  139. if (argc > 1) {
  140. for (int i = 1; i < argc; i++) {
  141. if (argv[i][0] == '-') {
  142. // An option...
  143. if (strcmp(argv[i], "--services-dir") == 0 || strcmp(argv[i], "-d") == 0) {
  144. if (++i < argc && argv[i][0] != '\0') {
  145. service_dir_opts.set_specified_service_dir(argv[i]);
  146. }
  147. else {
  148. cerr << "dinitcheck: '--services-dir' (-d) requires an argument" << endl;
  149. return 1;
  150. }
  151. }
  152. else if (strcmp(argv[i], "--system") == 0 || strcmp(argv[i], "-s") == 0) {
  153. user_dinit = false;
  154. }
  155. else if (strcmp(argv[i], "--user") == 0 || strcmp(argv[i], "-u") == 0) {
  156. user_dinit = true;
  157. }
  158. else if (strcmp(argv[i], "--socket-path") == 0 || strcmp(argv[i], "-p") == 0) {
  159. ++i;
  160. if (i == argc || argv[i][0] == '\0') {
  161. cerr << "dinitcheck: --socket-path/-p should be followed by socket path\n";
  162. return 1;
  163. }
  164. control_socket_str = argv[i];
  165. }
  166. else if (strcmp(argv[i], "--online") == 0 || strcmp(argv[i], "-n") == 0) {
  167. offline_operation = false;
  168. }
  169. else if (strcmp(argv[i], "--env-file") == 0 || strcmp(argv[i], "-e") == 0) {
  170. ++i;
  171. if (i == argc || argv[i][0] == '\0') {
  172. cerr << "dinitcheck: --env-file/-e should be followed by environment file path\n";
  173. return 1;
  174. }
  175. env_file = argv[i];
  176. }
  177. else if (strcmp(argv[i], "--help") == 0) {
  178. cout << "dinitcheck: check dinit service descriptions\n"
  179. " --help display help\n"
  180. " --services-dir <dir>, -d <dir>\n"
  181. " set base directory for service description\n"
  182. " files, can be specified multiple times\n"
  183. " --online, -n use service dirs and environment from running\n"
  184. " dinit instance\n"
  185. " --socket-path <path>, -p <path>\n"
  186. " use specified socket to connect to daemon (online\n"
  187. " mode)\n"
  188. " --env-file, -e <file> read environment from specified file\n"
  189. " --system, -s use defaults for system manager mode\n"
  190. " --user, -u use defaults for user mode\n"
  191. " <service-name> check service with name <service-name>\n";
  192. return EXIT_SUCCESS;
  193. }
  194. else {
  195. std::cerr << "dinitcheck: Unrecognized option: '" << argv[i] << "' (use '--help' for help)\n";
  196. return EXIT_FAILURE;
  197. }
  198. }
  199. else {
  200. services_to_check.push_back(argv[i]);
  201. }
  202. }
  203. }
  204. int socknum = -1;
  205. cpbuffer_t rbuffer;
  206. signal(SIGPIPE, SIG_IGN);
  207. service_dir_pathlist service_dir_paths;
  208. std::vector<std::string> service_dir_strs; // storage if needed for service_dir_paths
  209. if (offline_operation) {
  210. service_dir_opts.build_paths(!user_dinit);
  211. service_dir_paths = std::move(service_dir_opts.get_paths());
  212. if (env_file.empty()) {
  213. if (!user_dinit) {
  214. env_file = "/etc/dinit/environment";
  215. }
  216. }
  217. if (!env_file.empty()) {
  218. auto log_inv_env_setting = [&](int line_num) {
  219. std::cerr << "dinitcheck: warning: Invalid environment variable setting in environment file "
  220. << env_file << " (line " << std::to_string(line_num) << ")\n";
  221. };
  222. auto log_bad_env_command = [&](int line_num) {
  223. std::cerr << "dinitcheck: warning: Bad command in environment file "
  224. << env_file << " (line " << std::to_string(line_num) << ")\n";
  225. };
  226. try {
  227. read_env_file_inline(env_file.c_str(), true, menv, false, log_inv_env_setting, log_bad_env_command);
  228. }
  229. catch (std::system_error &err) {
  230. std::cerr << "dinitcheck: error read environment file " << env_file << ": "
  231. << err.code().message() << "\n";
  232. return EXIT_FAILURE;
  233. }
  234. }
  235. }
  236. else {
  237. if (!control_socket_str.empty()) {
  238. control_socket_path = control_socket_str.c_str();
  239. }
  240. else {
  241. control_socket_path = get_default_socket_path(control_socket_str, user_dinit);
  242. if (control_socket_path == nullptr) {
  243. cerr << "dinitcheck: cannot locate user home directory (set XDG_RUNTIME_DIR, HOME, check /etc/passwd file, or "
  244. "specify socket path via -p)" << endl;
  245. return EXIT_FAILURE;
  246. }
  247. }
  248. try {
  249. socknum = connect_to_daemon(control_socket_path);
  250. // Start by querying protocol version:
  251. check_protocol_version(min_cp_version, max_cp_version, rbuffer, socknum);
  252. // Read service directories
  253. service_dir_strs = get_service_description_dirs(socknum, rbuffer);
  254. for (const std::string &service_dir : service_dir_strs) {
  255. service_dir_paths.emplace_back(dir_entry(service_dir.c_str(), false));
  256. }
  257. menv.clear_no_inherit();
  258. get_remote_env(socknum, rbuffer);
  259. }
  260. catch (cp_old_client_exception &e) {
  261. std::cerr << "dinitcheck: too old (daemon reports newer protocol version)" << std::endl;
  262. return EXIT_FAILURE;
  263. }
  264. catch (cp_old_server_exception &e) {
  265. std::cerr << "dinitcheck: daemon too old or protocol error" << std::endl;
  266. return EXIT_FAILURE;
  267. }
  268. catch (cp_read_exception &e) {
  269. cerr << "dinitcheck: control socket read failure or protocol error" << endl;
  270. return EXIT_FAILURE;
  271. }
  272. catch (cp_write_exception &e) {
  273. cerr << "dinitcheck: control socket write error: " << std::strerror(e.errcode) << endl;
  274. return EXIT_FAILURE;
  275. }
  276. catch (dinit_protocol_error &e) {
  277. cerr << "dinitcheck: protocol error" << endl;
  278. return EXIT_FAILURE;
  279. }
  280. catch (general_error &ge) {
  281. std::cerr << "dinitcheck";
  282. if (ge.get_action() != nullptr) {
  283. std::cerr << ": " << ge.get_action();
  284. std::string &arg = ge.get_arg();
  285. if (!arg.empty()) {
  286. std::cerr << " " << arg;
  287. }
  288. }
  289. if (ge.get_err() != 0) {
  290. std::cerr << ": " << strerror(ge.get_err());
  291. }
  292. std::cerr << '\n';
  293. return EXIT_FAILURE;
  294. }
  295. }
  296. if (services_to_check.empty()) {
  297. services_to_check.push_back("boot");
  298. }
  299. size_t num_services_to_check = services_to_check.size();
  300. // Load named service(s)
  301. // - load the service, store dependencies as strings
  302. // - recurse
  303. std::map<std::string, service_record *> service_set;
  304. for (size_t i = 0; i < services_to_check.size(); ++i) {
  305. const std::string &name = services_to_check[i];
  306. std::cout << "Checking service: " << name << "...\n";
  307. try {
  308. service_record *sr = load_service(service_set, name, service_dir_paths);
  309. service_set[name] = sr;
  310. // add dependencies to services_to_check
  311. for (auto &dep : sr->dependencies) {
  312. if (service_set.count(dep.name) == 0 && !contains(services_to_check, dep.name)) {
  313. services_to_check.push_back(dep.name);
  314. }
  315. }
  316. // add chain_to to services_to_check
  317. if (!sr->chain_to.empty() && !contains(services_to_check, sr->chain_to)) {
  318. if (!contains(services_to_check, sr->chain_to)) {
  319. services_to_check.push_back(sr->chain_to);
  320. }
  321. }
  322. // add before_svcs and after_svcs to services_to_check
  323. for (const std::string &before_name : sr->before_svcs) {
  324. if (!contains(services_to_check, before_name)) {
  325. services_to_check.push_back(before_name);
  326. }
  327. }
  328. for (const std::string &after_name : sr->after_svcs) {
  329. if (!contains(services_to_check, after_name)) {
  330. services_to_check.push_back(after_name);
  331. }
  332. }
  333. // add consumed service (if any) to services to check
  334. if (!sr->consumer_of_name.empty()) {
  335. services_to_check.push_back(sr->consumer_of_name);
  336. }
  337. }
  338. catch (service_load_exc &exc) {
  339. std::cerr << "Unable to load service '" << name << "': " << exc.exc_description << "\n";
  340. errors_found = true;
  341. }
  342. }
  343. std::cout << "Performing secondary checks...\n";
  344. for (const auto &svc_name_record : service_set) {
  345. if (!svc_name_record.second->consumer_of_name.empty()) {
  346. auto consumer_of_it = service_set.find(svc_name_record.second->consumer_of_name);
  347. if (consumer_of_it != service_set.end()) {
  348. if (consumer_of_it->second->log_type != log_type_id::PIPE) {
  349. std::cerr << "Service '" << svc_name_record.first << "': specified as consumer of service '"
  350. << consumer_of_it->first << "' which has log-type that is not 'pipe'.\n";
  351. errors_found = true;
  352. }
  353. else if (!value(consumer_of_it->second->service_type).is_in(service_type_t::PROCESS,
  354. service_type_t::BGPROCESS, service_type_t::SCRIPTED)) {
  355. std::cerr << "Service '" << svc_name_record.first << "': specified as consumer of service '"
  356. << consumer_of_it->first << "' which is not a process-based service.\n";
  357. errors_found = true;
  358. }
  359. }
  360. else {
  361. std::cerr << "Warning: Service '" << svc_name_record.first << "' specified as consumer of service '"
  362. << consumer_of_it->first << "' which was not found.\n";
  363. }
  364. }
  365. // "before" ordering links are like reverse-dependencies: set up dependencies in the forwards direction
  366. // (from the dependent). Similarly for "after" links set up a dependency. These dependencies allow cycle
  367. // checking.
  368. for (const std::string &before_name : svc_name_record.second->before_svcs) {
  369. auto before_svc_it = service_set.find(before_name);
  370. if (before_svc_it != service_set.end()) {
  371. before_svc_it->second->dependencies.emplace_back(svc_name_record.first,
  372. dependency_type::BEFORE);
  373. }
  374. }
  375. for (const std::string &after_name : svc_name_record.second->after_svcs) {
  376. auto after_svc_it = service_set.find(after_name);
  377. if (after_svc_it != service_set.end()) {
  378. svc_name_record.second->dependencies.emplace_back(after_svc_it->first,
  379. dependency_type::AFTER);
  380. }
  381. }
  382. }
  383. // Check for circular dependencies
  384. std::vector<std::tuple<service_record *, size_t>> service_chain;
  385. for (size_t i = 0; i < num_services_to_check; ++i) {
  386. service_record *root = service_set[services_to_check[i]];
  387. if (! root) continue;
  388. if (root->visited) continue;
  389. // invariant: service_chain is empty
  390. service_chain.emplace_back(root, 0);
  391. // Depth first traversal. If we find a link (dependency) on a service already visited (but not
  392. // marked as cycle-free), we know then that we've found a cycle.
  393. while (true) {
  394. auto n = service_chain.size() - 1;
  395. auto &last = service_chain[n];
  396. service_record *last_record = std::get<0>(last);
  397. size_t &index = std::get<1>(last);
  398. if (index >= last_record->dependencies.size()) {
  399. // Processed all dependencies, go back up:
  400. last_record->cycle_free = true;
  401. service_chain.pop_back();
  402. if (n == 0) break;
  403. size_t &prev_index = std::get<1>(service_chain[n - 1]);
  404. ++prev_index;
  405. continue;
  406. }
  407. // Down the tree:
  408. auto dep_it = std::next(last_record->dependencies.begin(), index);
  409. service_record *next_link = service_set[dep_it->name];
  410. if (next_link == nullptr) {
  411. ++index;
  412. continue;
  413. }
  414. if (next_link->visited) {
  415. if (! next_link->cycle_free) {
  416. // We've found a cycle. Clear entries before the beginning of the cycle, then
  417. // exit the loop.
  418. auto first = std::find_if(service_chain.begin(), service_chain.end(),
  419. [next_link](std::tuple<service_record *, size_t> &a) -> bool {
  420. return std::get<0>(a) == next_link;
  421. });
  422. service_chain.erase(service_chain.begin(), first);
  423. break;
  424. }
  425. }
  426. next_link->visited = true;
  427. service_chain.emplace_back(next_link, 0);
  428. }
  429. // Report only one cycle; otherwise difficult to avoid reporting duplicates or overlapping
  430. // cycles.
  431. if (!service_chain.empty()) break;
  432. }
  433. if (!service_chain.empty()) {
  434. errors_found = true;
  435. std::cerr << "Found dependency cycle:\n";
  436. for (auto chain_link : service_chain) {
  437. service_record *svc = std::get<0>(chain_link);
  438. size_t dep_index = std::get<1>(chain_link);
  439. std::cerr << " " << svc->name << " ->";
  440. auto dep_it = std::next(svc->dependencies.begin(), dep_index);
  441. if (dep_it->dep_type == dependency_type::BEFORE) {
  442. std::cerr << " (via 'before')";
  443. }
  444. if (dep_it->dep_type == dependency_type::AFTER) {
  445. std::cerr << " (via 'after')";
  446. }
  447. std::cerr << "\n";
  448. }
  449. std::cerr << " " << std::get<0>(service_chain[0])->name << ".\n";
  450. }
  451. std::cerr << "Secondary checks complete.\n";
  452. if (! errors_found) {
  453. std::cout << "No problems found.\n";
  454. }
  455. else {
  456. std::cout << "One or more errors/warnings issued.\n";
  457. }
  458. return errors_found ? EXIT_FAILURE : EXIT_SUCCESS;
  459. }
  460. static void report_service_description_err(const std::string &service_name, unsigned line_num,
  461. const std::string &what)
  462. {
  463. std::cerr << "Service '" << service_name << "' (line " << line_num << "): " << what << "\n";
  464. errors_found = true;
  465. }
  466. static void report_service_description_err(const std::string &service_name, const char *setting_name,
  467. const std::string &what)
  468. {
  469. std::cerr << "Service '" << service_name << "' setting '" << setting_name << "': " << what << "\n";
  470. errors_found = true;
  471. }
  472. static void report_service_description_err(const std::string &service_name, const std::string &what)
  473. {
  474. std::cerr << "Service '" << service_name << "': " << what << "\n";
  475. errors_found = true;
  476. }
  477. static void report_service_description_exc(service_description_exc &exc)
  478. {
  479. if (exc.line_num != (unsigned)-1) {
  480. report_service_description_err(exc.service_name, exc.line_num, exc.exc_description);
  481. }
  482. else {
  483. report_service_description_err(exc.service_name, exc.setting_name, exc.exc_description);
  484. }
  485. }
  486. static void report_error(std::system_error &exc, const std::string &service_name)
  487. {
  488. std::cerr << "Service '" << service_name << "', error reading service description: " << exc.what() << "\n";
  489. errors_found = true;
  490. }
  491. static void report_dir_error(const char *service_name, const std::string &dirpath)
  492. {
  493. std::cerr << "Service '" << service_name << "', error reading dependencies from directory " << dirpath
  494. << ": " << strerror(errno) << "\n";
  495. errors_found = true;
  496. }
  497. static void report_general_warning(string_view msg)
  498. {
  499. std::cerr << "dinitcheck: Warning: " << msg.data() << "\n";
  500. }
  501. // Process a dependency directory - filenames contained within correspond to service names which
  502. // are loaded and added as a dependency of the given type. Expected use is with a directory
  503. // containing symbolic links to other service descriptions, but this isn't required.
  504. // Failure to read the directory contents, or to find a service listed within, is not considered
  505. // a fatal error.
  506. static void process_dep_dir(const char *servicename,
  507. const string &service_filename,
  508. std::list<prelim_dep> &deplist, const std::string &depdirpath,
  509. dependency_type dep_type)
  510. {
  511. std::string depdir_fname = combine_paths(parent_path(service_filename), depdirpath.c_str());
  512. DIR *depdir = opendir(depdir_fname.c_str());
  513. if (depdir == nullptr) {
  514. report_dir_error(servicename, depdirpath);
  515. return;
  516. }
  517. errno = 0;
  518. dirent * dent = readdir(depdir);
  519. while (dent != nullptr) {
  520. char * name = dent->d_name;
  521. if (name[0] != '.') {
  522. deplist.emplace_back(name, dep_type);
  523. }
  524. dent = readdir(depdir);
  525. }
  526. if (errno != 0) {
  527. report_dir_error(servicename, depdirpath);
  528. }
  529. closedir(depdir);
  530. }
  531. service_record *load_service(service_set_t &services, const std::string &name,
  532. const service_dir_pathlist &service_dirs)
  533. {
  534. using namespace std;
  535. using namespace dinit_load;
  536. auto found = services.find(name);
  537. if (found != services.end()) {
  538. return found->second;
  539. }
  540. string service_wdir;
  541. string service_filename;
  542. ifstream service_file;
  543. int dirfd;
  544. int fail_load_errno = 0;
  545. std::string fail_load_path;
  546. // Couldn't find one. Have to load it.
  547. for (auto &service_dir : service_dirs) {
  548. service_filename = service_dir.get_dir();
  549. service_wdir = service_filename;
  550. if (*(service_filename.rbegin()) != '/') {
  551. service_filename += '/';
  552. }
  553. service_filename += name;
  554. service_file.open(service_filename.c_str(), ios::in);
  555. if (service_file) break;
  556. if (errno != ENOENT && fail_load_errno == 0) {
  557. fail_load_errno = errno;
  558. fail_load_path = std::move(service_filename);
  559. }
  560. }
  561. if (!service_file) {
  562. if (fail_load_errno == 0) {
  563. throw service_not_found(string(name));
  564. }
  565. else {
  566. throw service_load_error(name, std::move(fail_load_path), fail_load_errno);
  567. }
  568. }
  569. service_settings_wrapper<prelim_dep> settings;
  570. string line;
  571. service_file.exceptions(ios::badbit);
  572. try {
  573. process_service_file(name, service_file,
  574. [&](string &line, unsigned line_num, string &setting,
  575. string_iterator &i, string_iterator &end) -> void {
  576. auto process_dep_dir_n = [&](std::list<prelim_dep> &deplist, const std::string &waitsford,
  577. dependency_type dep_type) -> void {
  578. process_dep_dir(name.c_str(), service_filename, deplist, waitsford, dep_type);
  579. };
  580. auto load_service_n = [&](const string &dep_name) -> const string & {
  581. return dep_name;
  582. };
  583. try {
  584. process_service_line(settings, name.c_str(), line, line_num, setting, i, end,
  585. load_service_n, process_dep_dir_n);
  586. }
  587. catch (service_description_exc &exc) {
  588. if (exc.service_name.empty()) {
  589. exc.service_name = name;
  590. }
  591. report_service_description_exc(exc);
  592. }
  593. });
  594. }
  595. catch (std::system_error &sys_err)
  596. {
  597. report_error(sys_err, name);
  598. throw service_load_exc(name, "error while reading service description.");
  599. }
  600. auto report_err = [&](const char *msg) {
  601. report_service_description_err(name, msg);
  602. };
  603. bool issued_var_subst_warning = false;
  604. environment srv_env{};
  605. // Fill user vars before reading env file
  606. if (settings.export_passwd_vars) {
  607. try {
  608. fill_environment_userinfo(settings.run_as_uid, name, srv_env);
  609. }
  610. catch (service_load_exc &load_exc) {
  611. report_service_description_err(name, load_exc.exc_description);
  612. }
  613. }
  614. // Set service name in environment if desired
  615. if (settings.export_service_name) {
  616. std::string envname = "DINIT_SERVICE=";
  617. envname += name;
  618. srv_env.set_var(std::move(envname));
  619. }
  620. if (!settings.env_file.empty()) {
  621. try {
  622. std::string fullpath = combine_paths(service_wdir, settings.env_file.c_str());
  623. auto log_inv_env_setting = [&](int line_num) {
  624. report_service_description_err(name,
  625. std::string("Invalid environment variable setting in environment file " + fullpath
  626. + " (line ") + std::to_string(line_num) + ")");
  627. };
  628. auto log_bad_env_command = [&](int line_num) {
  629. report_service_description_err(name,
  630. std::string("Bad command in environment file ") + fullpath + " (line " + std::to_string(line_num) + ")");
  631. };
  632. read_env_file_inline(fullpath.c_str(), false, srv_env, true, log_inv_env_setting, log_bad_env_command);
  633. } catch (const std::system_error &se) {
  634. report_service_description_err(name, std::string("could not load environment file: ") + se.what());
  635. }
  636. }
  637. environment::env_map renvmap = srv_env.build(menv);
  638. auto resolve_var = [&](const string &name, environment::env_map const &envmap) {
  639. if (offline_operation && !issued_var_subst_warning) {
  640. report_general_warning("Variable substitution performed by dinitcheck "
  641. "for file paths may not match dinit daemon (environment may differ); "
  642. "use --online to avoid this warning");
  643. issued_var_subst_warning = true;
  644. }
  645. return resolve_env_var(name, envmap);
  646. };
  647. settings.finalise(report_err, renvmap, report_err, resolve_var);
  648. if (!settings.working_dir.empty()) {
  649. service_wdir = settings.working_dir;
  650. }
  651. int oflags = O_DIRECTORY;
  652. #ifdef O_PATH
  653. oflags |= O_PATH;
  654. #else
  655. oflags |= O_RDONLY;
  656. #endif
  657. dirfd = open(service_wdir.c_str(), oflags);
  658. if (dirfd < 0) {
  659. report_service_description_err(name,
  660. std::string("could not open service working directory: ") + strerror(errno));
  661. dirfd = AT_FDCWD;
  662. }
  663. auto check_command = [&](const char *setting_name, const char *command) {
  664. struct stat command_stat;
  665. if (fstatat(dirfd, command, &command_stat, 0) == -1) {
  666. report_service_description_err(name,
  667. std::string("could not stat ") + setting_name + " executable '" + command
  668. + "': " + strerror(errno));
  669. }
  670. else {
  671. if ((command_stat.st_mode & S_IFMT) != S_IFREG) {
  672. report_service_description_err(name, std::string(setting_name) + " executable '"
  673. + command + "' is not a regular file.");
  674. }
  675. else if ((command_stat.st_mode & S_IXUSR) == 0) {
  676. report_service_description_err(name, std::string(setting_name) + " executable '" + command
  677. + "' is not executable by owner.");
  678. }
  679. }
  680. };
  681. if (!settings.command.empty()) {
  682. int offset_start = settings.command_offsets.front().first;
  683. int offset_end = settings.command_offsets.front().second;
  684. check_command("command", settings.command.substr(offset_start, offset_end - offset_start).c_str());
  685. }
  686. if (!settings.stop_command.empty()) {
  687. int offset_start = settings.stop_command_offsets.front().first;
  688. int offset_end = settings.stop_command_offsets.front().second;
  689. check_command("stop command",
  690. settings.stop_command.substr(offset_start, offset_end - offset_start).c_str());
  691. }
  692. if (settings.log_type == log_type_id::LOGFILE && !settings.logfile.empty()) {
  693. string logfile_dir = parent_path(settings.logfile);
  694. if (!logfile_dir.empty()) {
  695. struct stat logfile_dir_stat;
  696. if (fstatat(dirfd, logfile_dir.c_str(), &logfile_dir_stat, 0) == -1) {
  697. report_service_description_err(name,
  698. std::string("could not access logfile directory '") + logfile_dir + "': " + strerror(errno));
  699. }
  700. else {
  701. if ((logfile_dir_stat.st_mode & S_IFDIR) == 0) {
  702. report_service_description_err(name, std::string("logfile directory '")
  703. + logfile_dir + "' exists but is not a directory.");
  704. }
  705. }
  706. }
  707. }
  708. if (dirfd != AT_FDCWD) {
  709. close(dirfd);
  710. }
  711. return new service_record(name, settings.service_type, settings.chain_to_name, settings.depends,
  712. settings.before_svcs, settings.after_svcs, settings.consumer_of_name, settings.log_type);
  713. }