dinit-monitor.cc 15 KB

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