dinitctl.cc 22 KB

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