dinitctl.cc 20 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639
  1. #include <cstdio>
  2. #include <cstddef>
  3. #include <cstring>
  4. #include <string>
  5. #include <iostream>
  6. #include <system_error>
  7. #include <memory>
  8. #include <sys/types.h>
  9. #include <sys/socket.h>
  10. #include <sys/un.h>
  11. #include <unistd.h>
  12. #include <signal.h>
  13. #include <pwd.h>
  14. #include "control-cmds.h"
  15. #include "service-constants.h"
  16. #include "cpbuffer.h"
  17. // dinitctl: utility to control the Dinit daemon, including starting and stopping of services.
  18. // This utility communicates with the dinit daemon via a unix stream socket (/dev/initctl, or $HOME/.dinitctl).
  19. using handle_t = uint32_t;
  20. class read_cp_exception
  21. {
  22. public:
  23. int errcode;
  24. read_cp_exception(int err) : errcode(err) { }
  25. };
  26. enum class command_t;
  27. static int issue_load_service(int socknum, const char *service_name);
  28. static int check_load_reply(int socknum, cpbuffer<1024> &rbuffer, handle_t *handle_p, service_state_t *state_p);
  29. static int start_stop_service(int socknum, const char *service_name, command_t command, bool do_pin, bool wait_for_service, bool verbose);
  30. static int unpin_service(int socknum, const char *service_name, bool verbose);
  31. static int list_services(int socknum);
  32. // Fill a circular buffer from a file descriptor, reading at least _rlength_ bytes.
  33. // Throws ReadException if the requested number of bytes cannot be read, with:
  34. // errcode = 0 if end of stream (remote end closed)
  35. // errcode = errno if another error occurred
  36. // Note that EINTR is ignored (i.e. the read will be re-tried).
  37. static void fillBufferTo(cpbuffer<1024> *buf, int fd, int rlength)
  38. {
  39. do {
  40. int r = buf->fill_to(fd, rlength);
  41. if (r == -1) {
  42. if (errno != EINTR) {
  43. throw read_cp_exception(errno);
  44. }
  45. }
  46. else if (r == 0) {
  47. throw read_cp_exception(0);
  48. }
  49. else {
  50. return;
  51. }
  52. }
  53. while (true);
  54. }
  55. static const char * describeState(bool stopped)
  56. {
  57. return stopped ? "stopped" : "started";
  58. }
  59. static const char * describeVerb(bool stop)
  60. {
  61. return stop ? "stop" : "start";
  62. }
  63. // Wait for a reply packet, skipping over any information packets
  64. // that are received in the meantime.
  65. static void wait_for_reply(cpbuffer<1024> &rbuffer, int fd)
  66. {
  67. fillBufferTo(&rbuffer, fd, 1);
  68. while (rbuffer[0] >= 100) {
  69. // Information packet; discard.
  70. fillBufferTo(&rbuffer, fd, 2);
  71. int pktlen = (unsigned char) rbuffer[1];
  72. rbuffer.consume(1); // Consume one byte so we'll read one byte of the next packet
  73. fillBufferTo(&rbuffer, fd, pktlen);
  74. rbuffer.consume(pktlen - 1);
  75. }
  76. }
  77. // Write *all* the requested buffer and re-try if necessary until
  78. // the buffer is written or an unrecoverable error occurs.
  79. static int write_all(int fd, const void *buf, size_t count)
  80. {
  81. const char *cbuf = static_cast<const char *>(buf);
  82. int w = 0;
  83. while (count > 0) {
  84. int r = write(fd, cbuf, count);
  85. if (r == -1) {
  86. if (errno == EINTR) continue;
  87. return r;
  88. }
  89. w += r;
  90. cbuf += r;
  91. count -= r;
  92. }
  93. return w;
  94. }
  95. enum class command_t {
  96. NONE,
  97. START_SERVICE,
  98. WAKE_SERVICE,
  99. STOP_SERVICE,
  100. RELEASE_SERVICE,
  101. UNPIN_SERVICE,
  102. LIST_SERVICES
  103. };
  104. // Entry point.
  105. int main(int argc, char **argv)
  106. {
  107. using namespace std;
  108. bool show_help = argc < 2;
  109. char *service_name = nullptr;
  110. std::string control_socket_str;
  111. const char * control_socket_path = nullptr;
  112. bool verbose = true;
  113. bool sys_dinit = false; // communicate with system daemon
  114. bool wait_for_service = true;
  115. bool do_pin = false;
  116. command_t command = command_t::NONE;
  117. for (int i = 1; i < argc; i++) {
  118. if (argv[i][0] == '-') {
  119. if (strcmp(argv[i], "--help") == 0) {
  120. show_help = true;
  121. break;
  122. }
  123. else if (strcmp(argv[i], "--no-wait") == 0) {
  124. wait_for_service = false;
  125. }
  126. else if (strcmp(argv[i], "--quiet") == 0) {
  127. verbose = false;
  128. }
  129. else if (strcmp(argv[i], "--system") == 0 || strcmp(argv[i], "-s") == 0) {
  130. sys_dinit = true;
  131. }
  132. else if (strcmp(argv[i], "--pin") == 0) {
  133. do_pin = true;
  134. }
  135. else {
  136. return 1;
  137. }
  138. }
  139. else if (command == command_t::NONE) {
  140. if (strcmp(argv[i], "start") == 0) {
  141. command = command_t::START_SERVICE;
  142. }
  143. else if (strcmp(argv[i], "wake") == 0) {
  144. command = command_t::WAKE_SERVICE;
  145. }
  146. else if (strcmp(argv[i], "stop") == 0) {
  147. command = command_t::STOP_SERVICE;
  148. }
  149. else if (strcmp(argv[i], "release") == 0) {
  150. command = command_t::RELEASE_SERVICE;
  151. }
  152. else if (strcmp(argv[i], "unpin") == 0) {
  153. command = command_t::UNPIN_SERVICE;
  154. }
  155. else if (strcmp(argv[i], "list") == 0) {
  156. command = command_t::LIST_SERVICES;
  157. }
  158. else {
  159. show_help = true;
  160. break;
  161. }
  162. }
  163. else {
  164. // service name
  165. if (service_name != nullptr) {
  166. show_help = true;
  167. break;
  168. }
  169. service_name = argv[i];
  170. // TODO support multiple services
  171. }
  172. }
  173. if (service_name != nullptr && command == command_t::LIST_SERVICES) {
  174. show_help = true;
  175. }
  176. if ((service_name == nullptr && command != command_t::LIST_SERVICES) || command == command_t::NONE) {
  177. show_help = true;
  178. }
  179. if (show_help) {
  180. cout << "dinitctl: control Dinit services" << endl;
  181. cout << "\nUsage:" << endl;
  182. cout << " dinitctl [options] start [options] <service-name> : start and activate service" << endl;
  183. cout << " dinitctl [options] stop [options] <service-name> : stop service and cancel explicit activation" << endl;
  184. cout << " dinitctl [options] wake [options] <service-name> : start but do not mark activated" << endl;
  185. cout << " dinitctl [options] release [options] <service-name> : release activation, stop if no dependents" << endl;
  186. cout << " dinitctl [options] unpin <service-name> : un-pin the service (after a previous pin)" << endl;
  187. cout << " dinitctl list : list loaded services" << endl;
  188. cout << "\nNote: An activated service continues running when its dependents stop." << endl;
  189. cout << "\nGeneral options:" << endl;
  190. cout << " -s, --system : control system daemon instead of user daemon" << endl;
  191. cout << " --quiet : suppress output (except errors)" << endl;
  192. cout << "\nCommand options:" << endl;
  193. cout << " --help : show this help" << endl;
  194. cout << " --no-wait : don't wait for service startup/shutdown to complete" << endl;
  195. cout << " --pin : pin the service in the requested (started/stopped) state" << endl;
  196. return 1;
  197. }
  198. signal(SIGPIPE, SIG_IGN);
  199. control_socket_path = "/dev/dinitctl";
  200. // Locate control socket
  201. if (! sys_dinit) {
  202. char * userhome = getenv("HOME");
  203. if (userhome == nullptr) {
  204. struct passwd * pwuid_p = getpwuid(getuid());
  205. if (pwuid_p != nullptr) {
  206. userhome = pwuid_p->pw_dir;
  207. }
  208. }
  209. if (userhome != nullptr) {
  210. control_socket_str = userhome;
  211. control_socket_str += "/.dinitctl";
  212. control_socket_path = control_socket_str.c_str();
  213. }
  214. else {
  215. cerr << "Cannot locate user home directory (set HOME or check /etc/passwd file)" << endl;
  216. return 1;
  217. }
  218. }
  219. int socknum = socket(AF_UNIX, SOCK_STREAM, 0);
  220. if (socknum == -1) {
  221. perror("dinitctl: socket");
  222. return 1;
  223. }
  224. struct sockaddr_un * name;
  225. uint sockaddr_size = offsetof(struct sockaddr_un, sun_path) + strlen(control_socket_path) + 1;
  226. name = (struct sockaddr_un *) malloc(sockaddr_size);
  227. if (name == nullptr) {
  228. cerr << "dinitctl: Out of memory" << endl;
  229. return 1;
  230. }
  231. name->sun_family = AF_UNIX;
  232. strcpy(name->sun_path, control_socket_path);
  233. int connr = connect(socknum, (struct sockaddr *) name, sockaddr_size);
  234. if (connr == -1) {
  235. perror("dinitctl: connect");
  236. return 1;
  237. }
  238. // TODO should start by querying protocol version
  239. if (command == command_t::UNPIN_SERVICE) {
  240. return unpin_service(socknum, service_name, verbose);
  241. }
  242. else if (command == command_t::LIST_SERVICES) {
  243. return list_services(socknum);
  244. }
  245. return start_stop_service(socknum, service_name, command, do_pin, wait_for_service, verbose);
  246. }
  247. // Start/stop a service
  248. static int start_stop_service(int socknum, const char *service_name, command_t command, bool do_pin, bool wait_for_service, bool verbose)
  249. {
  250. using namespace std;
  251. bool do_stop = (command == command_t::STOP_SERVICE || command == command_t::RELEASE_SERVICE);
  252. if (issue_load_service(socknum, service_name)) {
  253. return 1;
  254. }
  255. // Now we expect a reply:
  256. try {
  257. cpbuffer<1024> rbuffer;
  258. wait_for_reply(rbuffer, socknum);
  259. service_state_t state;
  260. //service_state_t target_state;
  261. handle_t handle;
  262. if (check_load_reply(socknum, rbuffer, &handle, &state) != 0) {
  263. return 0;
  264. }
  265. service_state_t wanted_state = do_stop ? service_state_t::STOPPED : service_state_t::STARTED;
  266. int pcommand = 0;
  267. switch (command) {
  268. case command_t::STOP_SERVICE:
  269. pcommand = DINIT_CP_STOPSERVICE;
  270. break;
  271. case command_t::RELEASE_SERVICE:
  272. pcommand = DINIT_CP_RELEASESERVICE;
  273. break;
  274. case command_t::START_SERVICE:
  275. pcommand = DINIT_CP_STARTSERVICE;
  276. break;
  277. case command_t::WAKE_SERVICE:
  278. pcommand = DINIT_CP_WAKESERVICE;
  279. break;
  280. default: ;
  281. }
  282. // Need to issue STOPSERVICE/STARTSERVICE
  283. // We'll do this regardless of the current service state / target state, since issuing
  284. // start/stop also sets or clears the "explicitly started" flag on the service.
  285. {
  286. int r;
  287. {
  288. auto buf = new char[2 + sizeof(handle)];
  289. unique_ptr<char[]> ubuf(buf);
  290. buf[0] = pcommand;
  291. buf[1] = do_pin ? 1 : 0;
  292. memcpy(buf + 2, &handle, sizeof(handle));
  293. r = write_all(socknum, buf, 2 + sizeof(handle));
  294. }
  295. if (r == -1) {
  296. perror("dinitctl: write");
  297. return 1;
  298. }
  299. wait_for_reply(rbuffer, socknum);
  300. if (rbuffer[0] == DINIT_RP_ALREADYSS) {
  301. bool already = (state == wanted_state);
  302. if (verbose) {
  303. cout << "Service " << (already ? "(already) " : "") << describeState(do_stop) << "." << endl;
  304. }
  305. return 0; // success!
  306. }
  307. if (rbuffer[0] != DINIT_RP_ACK) {
  308. cerr << "dinitctl: Protocol error." << endl;
  309. return 1;
  310. }
  311. rbuffer.consume(1);
  312. }
  313. if (! wait_for_service) {
  314. if (verbose) {
  315. cout << "Issued " << describeVerb(do_stop) << " command successfully." << endl;
  316. }
  317. return 0;
  318. }
  319. service_event_t completionEvent;
  320. service_event_t cancelledEvent;
  321. if (do_stop) {
  322. completionEvent = service_event_t::STOPPED;
  323. cancelledEvent = service_event_t::STOPCANCELLED;
  324. }
  325. else {
  326. completionEvent = service_event_t::STARTED;
  327. cancelledEvent = service_event_t::STARTCANCELLED;
  328. }
  329. // Wait until service started:
  330. int r = rbuffer.fill_to(socknum, 2);
  331. while (r > 0) {
  332. if (rbuffer[0] >= 100) {
  333. int pktlen = (unsigned char) rbuffer[1];
  334. fillBufferTo(&rbuffer, socknum, pktlen);
  335. if (rbuffer[0] == DINIT_IP_SERVICEEVENT) {
  336. handle_t ev_handle;
  337. rbuffer.extract((char *) &ev_handle, 2, sizeof(ev_handle));
  338. service_event_t event = static_cast<service_event_t>(rbuffer[2 + sizeof(ev_handle)]);
  339. if (ev_handle == handle) {
  340. if (event == completionEvent) {
  341. if (verbose) {
  342. cout << "Service " << describeState(do_stop) << "." << endl;
  343. }
  344. return 0;
  345. }
  346. else if (event == cancelledEvent) {
  347. if (verbose) {
  348. cout << "Service " << describeVerb(do_stop) << " cancelled." << endl;
  349. }
  350. return 1;
  351. }
  352. else if (! do_stop && event == service_event_t::FAILEDSTART) {
  353. if (verbose) {
  354. cout << "Service failed to start." << endl;
  355. }
  356. return 1;
  357. }
  358. }
  359. }
  360. rbuffer.consume(pktlen);
  361. r = rbuffer.fill_to(socknum, 2);
  362. }
  363. else {
  364. // Not an information packet?
  365. cerr << "dinitctl: protocol error" << endl;
  366. return 1;
  367. }
  368. }
  369. if (r == -1) {
  370. perror("dinitctl: read");
  371. }
  372. else {
  373. cerr << "protocol error (connection closed by server)" << endl;
  374. }
  375. return 1;
  376. }
  377. catch (read_cp_exception &exc) {
  378. cerr << "dinitctl: control socket read failure or protocol error" << endl;
  379. return 1;
  380. }
  381. catch (std::bad_alloc &exc) {
  382. cerr << "dinitctl: out of memory" << endl;
  383. return 1;
  384. }
  385. return 0;
  386. }
  387. // Issue a "load service" command (DINIT_CP_LOADSERVICE), without waiting for
  388. // a response. Returns 1 on failure (with error logged), 0 on success.
  389. static int issue_load_service(int socknum, const char *service_name)
  390. {
  391. // Build buffer;
  392. uint16_t sname_len = strlen(service_name);
  393. int bufsize = 3 + sname_len;
  394. int r;
  395. try {
  396. std::unique_ptr<char[]> ubuf(new char[bufsize]);
  397. auto buf = ubuf.get();
  398. buf[0] = DINIT_CP_LOADSERVICE;
  399. memcpy(buf + 1, &sname_len, 2);
  400. memcpy(buf + 3, service_name, sname_len);
  401. r = write_all(socknum, buf, bufsize);
  402. }
  403. catch (std::bad_alloc &badalloc) {
  404. std::cerr << "dinitctl: " << badalloc.what() << std::endl;
  405. return 1;
  406. }
  407. if (r == -1) {
  408. perror("dinitctl: write");
  409. return 1;
  410. }
  411. return 0;
  412. }
  413. // Check that a "load service" reply was received, and that the requested service was found.
  414. static int check_load_reply(int socknum, cpbuffer<1024> &rbuffer, handle_t *handle_p, service_state_t *state_p)
  415. {
  416. using namespace std;
  417. if (rbuffer[0] == DINIT_RP_SERVICERECORD) {
  418. fillBufferTo(&rbuffer, socknum, 2 + sizeof(*handle_p));
  419. rbuffer.extract((char *) handle_p, 2, sizeof(*handle_p));
  420. if (state_p) *state_p = static_cast<service_state_t>(rbuffer[1]);
  421. //target_state = static_cast<service_state_t>(rbuffer[2 + sizeof(handle)]);
  422. rbuffer.consume(3 + sizeof(*handle_p));
  423. return 0;
  424. }
  425. else if (rbuffer[0] == DINIT_RP_NOSERVICE) {
  426. cerr << "dinitctl: Failed to find/load service." << endl;
  427. return 1;
  428. }
  429. else {
  430. cerr << "dinitctl: Protocol error." << endl;
  431. return 1;
  432. }
  433. }
  434. static int unpin_service(int socknum, const char *service_name, bool verbose)
  435. {
  436. using namespace std;
  437. // Build buffer;
  438. if (issue_load_service(socknum, service_name) == 1) {
  439. return 1;
  440. }
  441. // Now we expect a reply:
  442. try {
  443. cpbuffer<1024> rbuffer;
  444. wait_for_reply(rbuffer, socknum);
  445. handle_t handle;
  446. if (check_load_reply(socknum, rbuffer, &handle, nullptr) != 0) {
  447. return 1;
  448. }
  449. // Issue UNPIN command.
  450. {
  451. int r;
  452. {
  453. char *buf = new char[1 + sizeof(handle)];
  454. unique_ptr<char[]> ubuf(buf);
  455. buf[0] = DINIT_CP_UNPINSERVICE;
  456. memcpy(buf + 1, &handle, sizeof(handle));
  457. r = write_all(socknum, buf, 2 + sizeof(handle));
  458. }
  459. if (r == -1) {
  460. perror("dinitctl: write");
  461. return 1;
  462. }
  463. wait_for_reply(rbuffer, socknum);
  464. if (rbuffer[0] != DINIT_RP_ACK) {
  465. cerr << "dinitctl: Protocol error." << endl;
  466. return 1;
  467. }
  468. rbuffer.consume(1);
  469. }
  470. }
  471. catch (read_cp_exception &exc) {
  472. cerr << "dinitctl: Control socket read failure or protocol error" << endl;
  473. return 1;
  474. }
  475. catch (std::bad_alloc &exc) {
  476. cerr << "dinitctl: Out of memory" << endl;
  477. return 1;
  478. }
  479. if (verbose) {
  480. cout << "Service unpinned." << endl;
  481. }
  482. return 0;
  483. }
  484. static int list_services(int socknum)
  485. {
  486. using namespace std;
  487. try {
  488. char cmdbuf[] = { (char)DINIT_CP_LISTSERVICES };
  489. int r = write_all(socknum, cmdbuf, 1);
  490. if (r == -1) {
  491. perror("dinitctl: write");
  492. return 1;
  493. }
  494. cpbuffer<1024> rbuffer;
  495. wait_for_reply(rbuffer, socknum);
  496. while (rbuffer[0] == DINIT_RP_SVCINFO) {
  497. fillBufferTo(&rbuffer, socknum, 8);
  498. int nameLen = rbuffer[1];
  499. service_state_t current = static_cast<service_state_t>(rbuffer[2]);
  500. service_state_t target = static_cast<service_state_t>(rbuffer[3]);
  501. fillBufferTo(&rbuffer, socknum, nameLen + 8);
  502. char *name_ptr = rbuffer.get_ptr(8);
  503. int clength = std::min(rbuffer.get_contiguous_length(name_ptr), nameLen);
  504. string name = string(name_ptr, clength);
  505. name.append(rbuffer.get_buf_base(), nameLen - clength);
  506. cout << "[";
  507. cout << (target == service_state_t::STARTED ? "{" : " ");
  508. cout << (current == service_state_t::STARTED ? "+" : " ");
  509. cout << (target == service_state_t::STARTED ? "}" : " ");
  510. if (current == service_state_t::STARTING) {
  511. cout << "<<";
  512. }
  513. else if (current == service_state_t::STOPPING) {
  514. cout << ">>";
  515. }
  516. else {
  517. cout << " ";
  518. }
  519. cout << (target == service_state_t::STOPPED ? "{" : " ");
  520. cout << (current == service_state_t::STOPPED ? "-" : " ");
  521. cout << (target == service_state_t::STOPPED ? "}" : " ");
  522. cout << "] " << name << endl;
  523. rbuffer.consume(8 + nameLen);
  524. wait_for_reply(rbuffer, socknum);
  525. }
  526. if (rbuffer[0] != DINIT_RP_LISTDONE) {
  527. cerr << "dinitctl: Control socket protocol error" << endl;
  528. return 1;
  529. }
  530. }
  531. catch (read_cp_exception &exc) {
  532. cerr << "dinitctl: Control socket read failure or protocol error" << endl;
  533. return 1;
  534. }
  535. catch (std::bad_alloc &exc) {
  536. cerr << "dinitctl: Out of memory" << endl;
  537. return 1;
  538. }
  539. return 0;
  540. }