dinit-monitor.cc 15 KB

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