dinit-monitor.cc 16 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482
  1. #include <iostream>
  2. #include <vector>
  3. #include <string>
  4. #include <unordered_map>
  5. #include <cstring>
  6. #include <csignal>
  7. #include <cstdio>
  8. #include <sys/types.h>
  9. #include <sys/socket.h>
  10. #include <sys/un.h>
  11. #include <unistd.h>
  12. #include "dinit-client.h"
  13. #include "service-constants.h"
  14. #include "control-datatypes.h"
  15. // dinit-monitor: watch service states and report them via execution of a notification command
  16. // common communication datatypes
  17. using namespace dinit_cptypes;
  18. static constexpr uint16_t min_cp_version = 1;
  19. static constexpr uint16_t max_cp_version = 1;
  20. struct stringview {
  21. const char *str;
  22. size_t len;
  23. };
  24. static std::vector<stringview> split_command(const char *cmd);
  25. static bool load_service(int socknum, cpbuffer_t &rbuffer, const char *name, handle_t *handle,
  26. service_state_t *state);
  27. // dummy handler, so we can wait for children
  28. static void sigchld_handler(int) { }
  29. void issue_command(const char* service_name, const char* event_str,std::vector<stringview> &command_parts);
  30. int dinit_monitor_main(int argc, char **argv)
  31. {
  32. bool show_help = argc < 2;
  33. std::string control_socket_str;
  34. const char *control_socket_path = nullptr;
  35. bool user_dinit = (getuid() != 0); // communicate with user daemon
  36. bool issue_init = false; // request initial service state
  37. const char *str_started = "started";
  38. const char *str_stopped = "stopped";
  39. const char *str_failed = "failed";
  40. const char *command_str = nullptr;
  41. std::vector<const char *> services;
  42. for (int i = 1; i < argc; i++) {
  43. if (argv[i][0] == '-') {
  44. if (strcmp(argv[i], "--help") == 0) {
  45. show_help = true;
  46. break;
  47. }
  48. else if (strcmp(argv[i], "--version") == 0) {
  49. std::cout << "Dinit version " << DINIT_VERSION << ".\n";
  50. return 0;
  51. }
  52. else if (strcmp(argv[i], "--system") == 0 || strcmp(argv[i], "-s") == 0) {
  53. user_dinit = false;
  54. }
  55. else if (strcmp(argv[i], "--user") == 0 || strcmp(argv[i], "-u") == 0) {
  56. user_dinit = true;
  57. }
  58. else if (strcmp(argv[i], "--initial") == 0 || strcmp(argv[i], "-i") == 0) {
  59. issue_init = true;
  60. }
  61. else if (strcmp(argv[i], "--socket-path") == 0 || strcmp(argv[i], "-p") == 0) {
  62. ++i;
  63. if (i == argc) {
  64. std::cerr << "dinit-monitor: --socket-path/-p should be followed by socket path\n";
  65. return 1;
  66. }
  67. control_socket_str = argv[i];
  68. }
  69. else if (strcmp(argv[i], "--str-started") == 0) {
  70. ++i;
  71. if (i == argc) {
  72. std::cerr << "dinit-monitor: --str-started should be followed by an argument\n";
  73. return 1;
  74. }
  75. str_started = argv[i];
  76. }
  77. else if (strcmp(argv[i], "--str-stopped") == 0) {
  78. ++i;
  79. if (i == argc) {
  80. std::cerr << "dinit-monitor: --str-stopped should be followed by an argument\n";
  81. return 1;
  82. }
  83. str_stopped = argv[i];
  84. }
  85. else if (strcmp(argv[i], "--str-failed") == 0) {
  86. ++i;
  87. if (i == argc) {
  88. std::cerr << "dinit-monitor: --str-failed should be followed by an argument\n";
  89. return 1;
  90. }
  91. str_failed = argv[i];
  92. }
  93. else if (strcmp(argv[i], "-c") == 0 || strcmp(argv[i], "--command")) {
  94. ++i;
  95. if (i == argc) {
  96. std::cerr << "dinit-monitor: --command/-c should be followed by command\n";
  97. return 1;
  98. }
  99. command_str = argv[i];
  100. }
  101. }
  102. else {
  103. services.emplace_back(argv[i]);
  104. }
  105. }
  106. if (show_help) {
  107. std::cout << "dinit-monitor: monitor Dinit services\n"
  108. "\n"
  109. "Usage:\n"
  110. " dinit-monitor [options] <service-names...>\n"
  111. "\n"
  112. "Options:\n"
  113. " --help : show this help\n"
  114. " -s, --system : monitor system daemon (default if run as root)\n"
  115. " -u, --user : monitor user daemon\n"
  116. " -i, --initial : also execute command for initial service state\n"
  117. " --str-started <started-text>\n"
  118. " : specify text describing status when service starts\n"
  119. " --str-stopped <stopped-text>\n"
  120. " : specify text describing status when service stops\n"
  121. " --str-failed <failed-text>\n"
  122. " : specify text describing status when service fails\n"
  123. " --socket-path <path>, -p <path>\n"
  124. " : specify socket for communication with daemon\n"
  125. " -c, --command : specify command to execute on service status change\n"
  126. " (%n for service name, %s for status)\n";
  127. return 1;
  128. }
  129. if (services.empty()) {
  130. std::cerr << "dinit-monitor: specify at least one service name\n";
  131. return 1;
  132. }
  133. if (command_str == nullptr) {
  134. std::cerr << "dinit-monitor: command must specified\n";
  135. return 1;
  136. }
  137. std::vector<stringview> command_parts = split_command(command_str);
  138. if (command_parts.size() == 0) {
  139. std::cerr << "dinit-monitor: specified command is empty\n";
  140. return 1;
  141. }
  142. // Ignore SIGPIPE to avoid dying due to it, and set up a SIGCHLD handler (but mask it)
  143. sigset_t signal_mask;
  144. sigprocmask(SIG_SETMASK, nullptr, &signal_mask);
  145. sigaddset(&signal_mask, SIGCHLD);
  146. sigprocmask(SIG_SETMASK, &signal_mask, nullptr);
  147. signal(SIGPIPE, SIG_IGN);
  148. signal(SIGCHLD, sigchld_handler);
  149. // Locate control socket
  150. if (! control_socket_str.empty()) {
  151. control_socket_path = control_socket_str.c_str();
  152. }
  153. else {
  154. control_socket_path = get_default_socket_path(control_socket_str, user_dinit);
  155. if (control_socket_path == nullptr) {
  156. std::cerr << "dinit-monitor: cannot locate user home directory (set XDG_RUNTIME_DIR, "
  157. "HOME, check /etc/passwd file, or specify socket path via -p)\n";
  158. return 1;
  159. }
  160. }
  161. try {
  162. int socknum = connect_to_daemon(control_socket_path);
  163. // Start by querying protocol version:
  164. cpbuffer_t rbuffer;
  165. check_protocol_version(min_cp_version, max_cp_version, rbuffer, socknum);
  166. // Load all services
  167. std::unordered_map<handle_t, const char *> service_map;
  168. std::vector<std::pair<const char *, service_state_t>> service_init_state;
  169. for (const char *service_name : services) {
  170. handle_t hndl;
  171. service_state_t state;
  172. if (!load_service(socknum, rbuffer, service_name, &hndl, &state)) {
  173. std::cerr << "dinit-monitor: cannot load service: " << service_name << "\n";
  174. return 1;
  175. }
  176. service_map.emplace(hndl, service_name);
  177. service_init_state.push_back(std::make_pair(service_name, state));
  178. }
  179. // Issue initial status commands if requested
  180. if (issue_init) {
  181. for (auto state : service_init_state ) {
  182. if (state.second == service_state_t::STARTED) {
  183. issue_command(state.first, str_started, command_parts);
  184. }
  185. else if (state.second == service_state_t::STOPPED) {
  186. issue_command(state.first, str_stopped, command_parts);
  187. }
  188. }
  189. }
  190. // Watch information packets; execute notification
  191. int r = rbuffer.fill_to(socknum, 2);
  192. while (r > 0) {
  193. if (rbuffer[0] >= 100) {
  194. int pktlen = (unsigned char) rbuffer[1];
  195. fill_buffer_to(rbuffer, socknum, pktlen);
  196. if (rbuffer[0] == (char)cp_info::SERVICEEVENT) {
  197. handle_t ev_handle;
  198. rbuffer.extract((char *) &ev_handle, 2, sizeof(ev_handle));
  199. service_event_t event = static_cast<service_event_t>(rbuffer[2 + sizeof(ev_handle)]);
  200. const char *service_name = service_map.at(ev_handle);
  201. const char *event_str;
  202. if (event == service_event_t::STARTED) {
  203. event_str = str_started;
  204. }
  205. else if (event == service_event_t::STOPPED) {
  206. event_str = str_stopped;
  207. }
  208. else if (event == service_event_t::FAILEDSTART) {
  209. event_str = str_failed;
  210. }
  211. else {
  212. goto consume_packet;
  213. }
  214. issue_command(service_name, event_str, command_parts);
  215. }
  216. consume_packet:
  217. rbuffer.consume(pktlen);
  218. r = rbuffer.fill_to(socknum, 2);
  219. }
  220. else {
  221. // Not an information packet?
  222. std::cerr << "dinit-monitor: protocol error\n";
  223. return 1;
  224. }
  225. }
  226. if (r == -1) {
  227. perror("dinit-monitor: read");
  228. }
  229. else {
  230. std::cerr << "dinit-monitor: connection closed by server\n";
  231. }
  232. return 1;
  233. }
  234. catch (cp_old_client_exception &e) {
  235. std::cerr << "dinit-monitor: too old (server reports newer protocol version)\n";
  236. }
  237. catch (cp_old_server_exception &e) {
  238. std::cerr << "dinit-monitor: server too old or protocol error\n";
  239. }
  240. catch (cp_read_exception &e) {
  241. std::cerr << "dinit-monitor: control socket read failure or protocol error\n";
  242. }
  243. catch (cp_write_exception &e) {
  244. std::cerr << "dinit-monitor: control socket write error: " << std::strerror(e.errcode) << "\n";
  245. }
  246. catch (dinit_protocol_error &e) {
  247. std::cerr << "dinit-monitor: protocol error\n";
  248. }
  249. catch (general_error &ge) {
  250. std::cerr << "dinit-monitor";
  251. if (ge.get_action() != nullptr) {
  252. std::cerr << ": " << ge.get_action();
  253. std::string &arg = ge.get_arg();
  254. if (!arg.empty()) {
  255. std::cerr << " " << arg;
  256. }
  257. }
  258. if (ge.get_err() != 0) {
  259. std::cerr << ": " << strerror(ge.get_err());
  260. }
  261. std::cerr << '\n';
  262. }
  263. return 1;
  264. }
  265. void issue_command(const char* service_name, const char* event_str, std::vector<stringview> &command_parts) {
  266. std::vector<std::string> final_cmd_parts;
  267. std::vector<const char *> final_cmd_parts_cstr;
  268. for (stringview cmd_part : command_parts) {
  269. std::string cmd_part_str;
  270. cmd_part_str.reserve(cmd_part.len);
  271. for (size_t i = 0; i < cmd_part.len; ++i) {
  272. if (cmd_part.str[i] == '%') {
  273. ++i;
  274. if (i >= cmd_part.len) {
  275. cmd_part_str.append(1, '%');
  276. break;
  277. }
  278. if (cmd_part.str[i] == 'n') {
  279. cmd_part_str.append(service_name);
  280. }
  281. else if (cmd_part.str[i] == 's') {
  282. cmd_part_str.append(event_str);
  283. }
  284. else {
  285. // invalid specifier, just output as is
  286. cmd_part_str.append(1, '%');
  287. cmd_part_str.append(1, cmd_part_str[i]);
  288. }
  289. }
  290. else if (cmd_part.str[i] == '"') {
  291. // consume.
  292. }
  293. else {
  294. cmd_part_str.append(1, cmd_part.str[i]);
  295. }
  296. }
  297. final_cmd_parts.emplace_back(std::move(cmd_part_str));
  298. }
  299. for (const std::string &cmd_part : final_cmd_parts) {
  300. final_cmd_parts_cstr.push_back(cmd_part.c_str());
  301. }
  302. final_cmd_parts_cstr.push_back(nullptr);
  303. pid_t child_pid = fork();
  304. if (child_pid == 0) {
  305. // we are the child
  306. char **argv = const_cast<char **>(final_cmd_parts_cstr.data());
  307. execvp(argv[0], argv);
  308. perror("dinit-monitor: exec");
  309. }
  310. else if (child_pid == -1) {
  311. perror("dinit-monitor: fork");
  312. }
  313. else {
  314. int wstatus;
  315. if (wait(&wstatus) != -1) {
  316. if (wstatus != 0) {
  317. if (WIFEXITED(wstatus)) {
  318. std::cerr << "dinit-monitor: notification command terminated with exit status "
  319. << WEXITSTATUS(wstatus) << "\n";
  320. }
  321. if (WIFSIGNALED(wstatus)) {
  322. std::cerr << "dinit-monitor: notification command terminated due to signal "
  323. << WTERMSIG(wstatus) << "\n";
  324. }
  325. }
  326. }
  327. // Don't bother clearing any pending SIGCHLD. POSIX says that:
  328. // - either SIGCHLD doesn't queue, in which case we're only leaving one pending signal
  329. // - or, it does queue, but wait() removes it from the queue.
  330. }
  331. }
  332. int main(int argc, char **argv) {
  333. try {
  334. return dinit_monitor_main(argc, argv);
  335. }
  336. catch (std::bad_alloc &e) {
  337. std::cerr << "dinit-monitor: out of memory\n";
  338. }
  339. return 1;
  340. }
  341. static std::vector<stringview> split_command(const char *cmd)
  342. {
  343. using std::locale;
  344. using std::isspace;
  345. const locale &classic_loc = locale::classic();
  346. std::vector<stringview> result;
  347. const char *c = cmd;
  348. do {
  349. while (*c != '\0' && isspace(*c, classic_loc)) ++c;
  350. if (*c == '\0') break;
  351. const char *str = c;
  352. while (*c != '\0' && !isspace(*c, classic_loc)) {
  353. if (*c == '"') {
  354. ++c; // skip begin quote
  355. while (*c != '\0' && *c != '"') ++c;
  356. }
  357. ++c;
  358. }
  359. size_t len = c - str;
  360. result.emplace_back(stringview {str, len});
  361. } while (*c != '\0');
  362. return result;
  363. }
  364. // Issue a "load service" command (LOADSERVICE), without waiting for
  365. // a response. Returns 1 on failure (with error logged), 0 on success.
  366. static int issue_load_service(int socknum, const char *service_name, bool find_only = false)
  367. {
  368. // Build buffer;
  369. srvname_len_t srvname_len = strlen(service_name);
  370. int bufsize = 3 + srvname_len;
  371. std::unique_ptr<char[]> ubuf(new char[bufsize]);
  372. auto buf = ubuf.get();
  373. buf[0] = (char)(find_only ? cp_cmd::FINDSERVICE : cp_cmd::LOADSERVICE);
  374. memcpy(buf + 1, &srvname_len, sizeof(srvname_len));
  375. memcpy(buf + 3, service_name, srvname_len);
  376. write_all_x(socknum, buf, bufsize);
  377. return 0;
  378. }
  379. // Check that a "load service" reply was received, and that the requested service was found.
  380. // state_p may be null.
  381. static int check_load_reply(int socknum, cpbuffer_t &rbuffer, handle_t *handle_p, service_state_t *state_p)
  382. {
  383. using namespace std;
  384. cp_rply reply_pkt_h = (cp_rply)rbuffer[0];
  385. if (reply_pkt_h == cp_rply::SERVICERECORD) {
  386. fill_buffer_to(rbuffer, socknum, 2 + sizeof(*handle_p));
  387. rbuffer.extract((char *) handle_p, 2, sizeof(*handle_p));
  388. if (state_p) *state_p = static_cast<service_state_t>(rbuffer[1]);
  389. //target_state = static_cast<service_state_t>(rbuffer[2 + sizeof(handle)]);
  390. rbuffer.consume(3 + sizeof(*handle_p));
  391. return 0;
  392. }
  393. else if (reply_pkt_h == cp_rply::NOSERVICE) {
  394. return 1;
  395. }
  396. else {
  397. throw dinit_protocol_error();
  398. }
  399. }
  400. // Load a service: issue load command, wait for reply. Return true on success, display error message
  401. // and return false on failure.
  402. // socknum - the socket fd to communicate via
  403. // rbuffer - the buffer for communication
  404. // name - the name of the service to load
  405. // handle - where to store the handle of the loaded service
  406. // state - where to store the state of the loaded service (may be null).
  407. // write_error - whether to write an error message if the service can't be loaded
  408. static bool load_service(int socknum, cpbuffer_t &rbuffer, const char *name, handle_t *handle,
  409. service_state_t *state)
  410. {
  411. // Load 'to' service:
  412. if (issue_load_service(socknum, name)) {
  413. return false;
  414. }
  415. wait_for_reply(rbuffer, socknum);
  416. if (check_load_reply(socknum, rbuffer, handle, state) != 0) {
  417. return false;
  418. }
  419. return true;
  420. }