4de9f1aa0f336eba53d2a9c8fea7fdf06dce8890
[oweals/dinit.git] / src / dinitctl.cc
1 #include <cstdio>
2 #include <cstddef>
3 #include <cstring>
4 #include <string>
5 #include <iostream>
6 #include <system_error>
7 #include <memory>
8 #include <algorithm>
9
10 #include <sys/types.h>
11 #include <sys/wait.h>
12 #include <sys/socket.h>
13 #include <sys/un.h>
14 #include <unistd.h>
15 #include <signal.h>
16 #include <pwd.h>
17
18 #include "control-cmds.h"
19 #include "service-constants.h"
20 #include "cpbuffer.h"
21 #include "dinit-client.h"
22
23 // dinitctl:  utility to control the Dinit daemon, including starting and stopping of services.
24
25 // This utility communicates with the dinit daemon via a unix stream socket (/dev/initctl,
26 // or $HOME/.dinitctl).
27
28 static constexpr uint16_t min_cp_version = 1;
29 static constexpr uint16_t max_cp_version = 1;
30
31 enum class command_t;
32
33 static int issue_load_service(int socknum, const char *service_name, bool find_only = false);
34 static int check_load_reply(int socknum, cpbuffer_t &, handle_t *handle_p, service_state_t *state_p);
35 static int start_stop_service(int socknum, cpbuffer_t &, const char *service_name, command_t command,
36         bool do_pin, bool wait_for_service, bool verbose);
37 static int unpin_service(int socknum, cpbuffer_t &, const char *service_name, bool verbose);
38 static int unload_service(int socknum, cpbuffer_t &, const char *service_name);
39 static int list_services(int socknum, cpbuffer_t &);
40 static int shutdown_dinit(int soclknum, cpbuffer_t &);
41 static int add_remove_dependency(int socknum, cpbuffer_t &rbuffer, bool add, char *service_from,
42         char *service_to, dependency_type dep_type);
43
44
45 static const char * describeState(bool stopped)
46 {
47     return stopped ? "stopped" : "started";
48 }
49
50 static const char * describeVerb(bool stop)
51 {
52     return stop ? "stop" : "start";
53 }
54
55 enum class command_t {
56     NONE,
57     START_SERVICE,
58     WAKE_SERVICE,
59     STOP_SERVICE,
60     RELEASE_SERVICE,
61     UNPIN_SERVICE,
62     UNLOAD_SERVICE,
63     LIST_SERVICES,
64     SHUTDOWN,
65     ADD_DEPENDENCY,
66     RM_DEPENDENCY
67 };
68
69
70 // Entry point.
71 int main(int argc, char **argv)
72 {
73     using namespace std;
74     
75     bool show_help = argc < 2;
76     char *service_name = nullptr;
77     char *to_service_name = nullptr;
78     dependency_type dep_type;
79     bool dep_type_set = false;
80     
81     std::string control_socket_str;
82     const char * control_socket_path = nullptr;
83     
84     bool verbose = true;
85     bool sys_dinit = false;  // communicate with system daemon
86     bool wait_for_service = true;
87     bool do_pin = false;
88     
89     command_t command = command_t::NONE;
90         
91     for (int i = 1; i < argc; i++) {
92         if (argv[i][0] == '-') {
93             if (strcmp(argv[i], "--help") == 0) {
94                 show_help = true;
95                 break;
96             }
97             else if (strcmp(argv[i], "--no-wait") == 0) {
98                 wait_for_service = false;
99             }
100             else if (strcmp(argv[i], "--quiet") == 0) {
101                 verbose = false;
102             }
103             else if (strcmp(argv[i], "--system") == 0 || strcmp(argv[i], "-s") == 0) {
104                 sys_dinit = true;
105             }
106             else if (strcmp(argv[i], "--pin") == 0) {
107                 do_pin = true;
108             }
109             else {
110                 cerr << "dinitctl: unrecognized option: " << argv[i] << " (use --help for help)\n";
111                 return 1;
112             }
113         }
114         else if (command == command_t::NONE) {
115             if (strcmp(argv[i], "start") == 0) {
116                 command = command_t::START_SERVICE; 
117             }
118             else if (strcmp(argv[i], "wake") == 0) {
119                 command = command_t::WAKE_SERVICE;
120             }
121             else if (strcmp(argv[i], "stop") == 0) {
122                 command = command_t::STOP_SERVICE;
123             }
124             else if (strcmp(argv[i], "release") == 0) {
125                 command = command_t::RELEASE_SERVICE;
126             }
127             else if (strcmp(argv[i], "unpin") == 0) {
128                 command = command_t::UNPIN_SERVICE;
129             }
130             else if (strcmp(argv[i], "unload") == 0) {
131                 command = command_t::UNLOAD_SERVICE;
132             }
133             else if (strcmp(argv[i], "list") == 0) {
134                 command = command_t::LIST_SERVICES;
135             }
136             else if (strcmp(argv[i], "shutdown") == 0) {
137                 command = command_t::SHUTDOWN;
138             }
139             else if (strcmp(argv[i], "add-dep") == 0) {
140                 command = command_t::ADD_DEPENDENCY;
141             }
142             else if (strcmp(argv[i], "rm-dep") == 0) {
143                 command = command_t::RM_DEPENDENCY;
144             }
145             else {
146                 cerr << "dinitctl: unrecognized command: " << argv[i] << " (use --help for help)\n";
147                 return 1;
148             }
149         }
150         else {
151             // service name / other non-option
152             if (command == command_t::ADD_DEPENDENCY || command == command_t::RM_DEPENDENCY) {
153                 if (! dep_type_set) {
154                     if (strcmp(argv[i], "regular") == 0) {
155                         dep_type = dependency_type::REGULAR;
156                     }
157                     else if (strcmp(argv[i], "milestone") == 0) {
158                         dep_type = dependency_type::MILESTONE;
159                     }
160                     else if (strcmp(argv[i], "waits-for") == 0) {
161                         dep_type = dependency_type::WAITS_FOR;
162                     }
163                     else {
164                         show_help = true;
165                         break;
166                     }
167                     dep_type_set = true;
168                 }
169                 else if (service_name == nullptr) {
170                     service_name = argv[i];
171                 }
172                 else if (to_service_name == nullptr) {
173                     to_service_name = argv[i];
174                 }
175                 else {
176                     show_help = true;
177                     break;
178                 }
179             }
180             else {
181                 if (service_name != nullptr) {
182                     show_help = true;
183                     break;
184                 }
185                 service_name = argv[i];
186                 // TODO support multiple services
187             }
188         }
189     }
190     
191     bool no_service_cmd = (command == command_t::LIST_SERVICES || command == command_t::SHUTDOWN);
192
193     if (service_name != nullptr && no_service_cmd) {
194         show_help = true;
195     }
196     
197     if ((service_name == nullptr && ! no_service_cmd) || command == command_t::NONE) {
198         show_help = true;
199     }
200
201     if ((command == command_t::ADD_DEPENDENCY || command == command_t::RM_DEPENDENCY)
202             && (! dep_type_set || service_name == nullptr || to_service_name == nullptr)) {
203         show_help = true;
204     }
205
206     if (show_help) {
207         cout << "dinitctl:   control Dinit services\n"
208           "\n"
209           "Usage:\n"
210           "    dinitctl [options] start [options] <service-name>\n"
211           "    dinitctl [options] stop [options] <service-name>\n"
212           "    dinitctl [options] wake [options] <service-name>\n"
213           "    dinitctl [options] release [options] <service-name>\n"
214           "    dinitctl [options] unpin <service-name>\n"
215           "    dinitctl unload <service-name>\n"
216           "    dinitctl list\n"
217           "    dinitctl shutdown\n"
218           "    dinitctl add-dep <type> <from-service> <to-service>\n"
219           "    dinitctl rm-dep <type> <from-service> <to-service>\n"
220           "\n"
221           "Note: An activated service continues running when its dependents stop.\n"
222           "\n"
223           "General options:\n"
224           "  -s, --system     : control system daemon instead of user daemon\n"
225           "  --quiet          : suppress output (except errors)\n"
226           "\n"
227           "Command options:\n"
228           "  --help           : show this help\n"
229           "  --no-wait        : don't wait for service startup/shutdown to complete\n"
230           "  --pin            : pin the service in the requested state\n";
231         return 1;
232     }
233     
234     signal(SIGPIPE, SIG_IGN);
235     
236     control_socket_path = "/dev/dinitctl";
237     
238     // Locate control socket
239     if (! sys_dinit) {
240         char * userhome = getenv("HOME");
241         if (userhome == nullptr) {
242             struct passwd * pwuid_p = getpwuid(getuid());
243             if (pwuid_p != nullptr) {
244                 userhome = pwuid_p->pw_dir;
245             }
246         }
247         
248         if (userhome != nullptr) {
249             control_socket_str = userhome;
250             control_socket_str += "/.dinitctl";
251             control_socket_path = control_socket_str.c_str();
252         }
253         else {
254             cerr << "Cannot locate user home directory (set HOME or check /etc/passwd file)" << endl;
255             return 1;
256         }
257     }
258     
259     int socknum = socket(AF_UNIX, SOCK_STREAM, 0);
260     if (socknum == -1) {
261         perror("dinitctl: socket");
262         return 1;
263     }
264
265     struct sockaddr_un * name;
266     uint sockaddr_size = offsetof(struct sockaddr_un, sun_path) + strlen(control_socket_path) + 1;
267     name = (struct sockaddr_un *) malloc(sockaddr_size);
268     if (name == nullptr) {
269         cerr << "dinitctl: Out of memory" << endl;
270         return 1;
271     }
272     
273     name->sun_family = AF_UNIX;
274     strcpy(name->sun_path, control_socket_path);
275     
276     int connr = connect(socknum, (struct sockaddr *) name, sockaddr_size);
277     if (connr == -1) {
278         perror("dinitctl: connect");
279         return 1;
280     }
281     
282     try {
283         // Start by querying protocol version:
284         cpbuffer_t rbuffer;
285         check_protocol_version(min_cp_version, max_cp_version, rbuffer, socknum);
286
287         if (command == command_t::UNPIN_SERVICE) {
288             return unpin_service(socknum, rbuffer, service_name, verbose);
289         }
290         else if (command == command_t::UNLOAD_SERVICE) {
291             return unload_service(socknum, rbuffer, service_name);
292         }
293         else if (command == command_t::LIST_SERVICES) {
294             return list_services(socknum, rbuffer);
295         }
296         else if (command == command_t::SHUTDOWN) {
297             return shutdown_dinit(socknum, rbuffer);
298         }
299         else if (command == command_t::ADD_DEPENDENCY || command == command_t::RM_DEPENDENCY) {
300             return add_remove_dependency(socknum, rbuffer, command == command_t::ADD_DEPENDENCY,
301                     service_name, to_service_name, dep_type);
302         }
303         else {
304             return start_stop_service(socknum, rbuffer, service_name, command, do_pin,
305                     wait_for_service, verbose);
306         }
307     }
308     catch (cp_old_client_exception &e) {
309         std::cerr << "dinitctl: too old (server reports newer protocol version)" << std::endl;
310         return 1;
311     }
312     catch (cp_old_server_exception &e) {
313         std::cerr << "dinitctl: server too old or protocol error" << std::endl;
314         return 1;
315     }
316     catch (cp_read_exception &e) {
317         cerr << "dinitctl: control socket read failure or protocol error" << endl;
318         return 1;
319     }
320     catch (cp_write_exception &e) {
321         cerr << "dinitctl: control socket write error: " << std::strerror(e.errcode) << endl;
322         return 1;
323     }
324 }
325
326 // Extract/read a string of specified length from the buffer/socket. The string is consumed
327 // from the buffer.
328 static std::string read_string(int socknum, cpbuffer_t &rbuffer, uint32_t length)
329 {
330     int rb_len = rbuffer.get_length();
331     if (rb_len >= length) {
332         std::string r = rbuffer.extract_string(0, length);
333         rbuffer.consume(length);
334         return r;
335     }
336
337     std::string r = rbuffer.extract_string(0, rb_len);
338     uint32_t rlen = length - rb_len;
339     uint32_t clen;
340     do {
341         rbuffer.reset();
342         rbuffer.fill(socknum);
343         char *bptr = rbuffer.get_ptr(0);
344         clen = rbuffer.get_length();
345         clen = std::min(clen, rlen);
346         r.append(bptr, clen);
347         rlen -= clen;
348     } while (rlen > 0);
349
350     rbuffer.consume(clen);
351
352     return r;
353 }
354
355 // Start/stop a service
356 static int start_stop_service(int socknum, cpbuffer_t &rbuffer, const char *service_name,
357         command_t command, bool do_pin, bool wait_for_service, bool verbose)
358 {
359     using namespace std;
360
361     bool do_stop = (command == command_t::STOP_SERVICE || command == command_t::RELEASE_SERVICE);
362     
363     if (issue_load_service(socknum, service_name)) {
364         return 1;
365     }
366
367     // Now we expect a reply:
368     
369     wait_for_reply(rbuffer, socknum);
370
371     service_state_t state;
372     //service_state_t target_state;
373     handle_t handle;
374
375     if (check_load_reply(socknum, rbuffer, &handle, &state) != 0) {
376         return 1;
377     }
378
379     service_state_t wanted_state = do_stop ? service_state_t::STOPPED : service_state_t::STARTED;
380     int pcommand = 0;
381     switch (command) {
382         case command_t::STOP_SERVICE:
383             pcommand = DINIT_CP_STOPSERVICE;
384             break;
385         case command_t::RELEASE_SERVICE:
386             pcommand = DINIT_CP_RELEASESERVICE;
387             break;
388         case command_t::START_SERVICE:
389             pcommand = DINIT_CP_STARTSERVICE;
390             break;
391         case command_t::WAKE_SERVICE:
392             pcommand = DINIT_CP_WAKESERVICE;
393             break;
394         default: ;
395     }
396
397     // Need to issue STOPSERVICE/STARTSERVICE
398     // We'll do this regardless of the current service state / target state, since issuing
399     // start/stop also sets or clears the "explicitly started" flag on the service.
400     {
401         char buf[2 + sizeof(handle)];
402         buf[0] = pcommand;
403         buf[1] = do_pin ? 1 : 0;
404         memcpy(buf + 2, &handle, sizeof(handle));
405         write_all_x(socknum, buf, 2 + sizeof(handle));
406         
407         wait_for_reply(rbuffer, socknum);
408         if (rbuffer[0] == DINIT_RP_ALREADYSS) {
409             bool already = (state == wanted_state);
410             if (verbose) {
411                 cout << "Service " << (already ? "(already) " : "")
412                         << describeState(do_stop) << "." << endl;
413             }
414             return 0; // success!
415         }
416         if (rbuffer[0] != DINIT_RP_ACK) {
417             cerr << "dinitctl: Protocol error." << endl;
418             return 1;
419         }
420         rbuffer.consume(1);
421     }
422
423     if (! wait_for_service) {
424         if (verbose) {
425             cout << "Issued " << describeVerb(do_stop) << " command successfully." << endl;
426         }
427         return 0;
428     }
429
430     service_event_t completionEvent;
431     service_event_t cancelledEvent;
432
433     if (do_stop) {
434         completionEvent = service_event_t::STOPPED;
435         cancelledEvent = service_event_t::STOPCANCELLED;
436     }
437     else {
438         completionEvent = service_event_t::STARTED;
439         cancelledEvent = service_event_t::STARTCANCELLED;
440     }
441
442     // Wait until service started:
443     int r = rbuffer.fill_to(socknum, 2);
444     while (r > 0) {
445         if (rbuffer[0] >= 100) {
446             int pktlen = (unsigned char) rbuffer[1];
447             fill_buffer_to(rbuffer, socknum, pktlen);
448
449             if (rbuffer[0] == DINIT_IP_SERVICEEVENT) {
450                 handle_t ev_handle;
451                 rbuffer.extract((char *) &ev_handle, 2, sizeof(ev_handle));
452                 service_event_t event = static_cast<service_event_t>(rbuffer[2 + sizeof(ev_handle)]);
453                 if (ev_handle == handle) {
454                     if (event == completionEvent) {
455                         if (verbose) {
456                             cout << "Service " << describeState(do_stop) << "." << endl;
457                         }
458                         return 0;
459                     }
460                     else if (event == cancelledEvent) {
461                         if (verbose) {
462                             cout << "Service " << describeVerb(do_stop) << " cancelled." << endl;
463                         }
464                         return 1;
465                     }
466                     else if (! do_stop && event == service_event_t::FAILEDSTART) {
467                         if (verbose) {
468                             cout << "Service failed to start." << endl;
469                         }
470                         return 1;
471                     }
472                 }
473             }
474
475             rbuffer.consume(pktlen);
476             r = rbuffer.fill_to(socknum, 2);
477         }
478         else {
479             // Not an information packet?
480             cerr << "dinitctl: protocol error" << endl;
481             return 1;
482         }
483     }
484
485     if (r == -1) {
486         perror("dinitctl: read");
487     }
488     else {
489         cerr << "protocol error (connection closed by server)" << endl;
490     }
491     return 1;
492 }
493
494 // Issue a "load service" command (DINIT_CP_LOADSERVICE), without waiting for
495 // a response. Returns 1 on failure (with error logged), 0 on success.
496 static int issue_load_service(int socknum, const char *service_name, bool find_only)
497 {
498     // Build buffer;
499     uint16_t sname_len = strlen(service_name);
500     int bufsize = 3 + sname_len;
501     
502     std::unique_ptr<char[]> ubuf(new char[bufsize]);
503     auto buf = ubuf.get();
504
505     buf[0] = find_only ? DINIT_CP_FINDSERVICE : DINIT_CP_LOADSERVICE;
506     memcpy(buf + 1, &sname_len, 2);
507     memcpy(buf + 3, service_name, sname_len);
508
509     write_all_x(socknum, buf, bufsize);
510     
511     return 0;
512 }
513
514 // Check that a "load service" reply was received, and that the requested service was found.
515 static int check_load_reply(int socknum, cpbuffer_t &rbuffer, handle_t *handle_p, service_state_t *state_p)
516 {
517     using namespace std;
518     
519     if (rbuffer[0] == DINIT_RP_SERVICERECORD) {
520         fill_buffer_to(rbuffer, socknum, 2 + sizeof(*handle_p));
521         rbuffer.extract((char *) handle_p, 2, sizeof(*handle_p));
522         if (state_p) *state_p = static_cast<service_state_t>(rbuffer[1]);
523         //target_state = static_cast<service_state_t>(rbuffer[2 + sizeof(handle)]);
524         rbuffer.consume(3 + sizeof(*handle_p));
525         return 0;
526     }
527     else if (rbuffer[0] == DINIT_RP_NOSERVICE) {
528         cerr << "dinitctl: failed to find/load service." << endl;
529         return 1;
530     }
531     else {
532         cerr << "dinitctl: protocol error." << endl;
533         return 1;
534     }
535 }
536
537 static int unpin_service(int socknum, cpbuffer_t &rbuffer, const char *service_name, bool verbose)
538 {
539     using namespace std;
540     
541     // Build buffer;
542     if (issue_load_service(socknum, service_name) == 1) {
543         return 1;
544     }
545
546     // Now we expect a reply:
547     
548     wait_for_reply(rbuffer, socknum);
549
550     handle_t handle;
551
552     if (check_load_reply(socknum, rbuffer, &handle, nullptr) != 0) {
553         return 1;
554     }
555
556     // Issue UNPIN command.
557     {
558         char buf[1 + sizeof(handle)];
559         buf[0] = DINIT_CP_UNPINSERVICE;
560         memcpy(buf + 1, &handle, sizeof(handle));
561         write_all_x(socknum, buf, 2 + sizeof(handle));
562         
563         wait_for_reply(rbuffer, socknum);
564         if (rbuffer[0] != DINIT_RP_ACK) {
565             cerr << "dinitctl: protocol error." << endl;
566             return 1;
567         }
568         rbuffer.consume(1);
569     }
570
571     if (verbose) {
572         cout << "Service unpinned." << endl;
573     }
574     return 0;
575 }
576
577 static int unload_service(int socknum, cpbuffer_t &rbuffer, const char *service_name)
578 {
579     using namespace std;
580
581     // Build buffer;
582     if (issue_load_service(socknum, service_name, true) == 1) {
583         return 1;
584     }
585
586     // Now we expect a reply:
587     wait_for_reply(rbuffer, socknum);
588
589     handle_t handle;
590
591     if (rbuffer[0] == DINIT_RP_NOSERVICE) {
592         cerr << "dinitctl: service not loaded." << endl;
593         return 1;
594     }
595
596     if (check_load_reply(socknum, rbuffer, &handle, nullptr) != 0) {
597         return 1;
598     }
599
600     // Issue UNLOAD command.
601     {
602         char buf[1 + sizeof(handle)];
603         buf[0] = DINIT_CP_UNLOADSERVICE;
604         memcpy(buf + 1, &handle, sizeof(handle));
605         write_all_x(socknum, buf, 2 + sizeof(handle));
606
607         wait_for_reply(rbuffer, socknum);
608         if (rbuffer[0] == DINIT_RP_NAK) {
609             cerr << "dinitctl: Could not unload service; service not stopped, or is a dependency of "
610                     "other service." << endl;
611             return 1;
612         }
613         if (rbuffer[0] != DINIT_RP_ACK) {
614             cerr << "dinitctl: Protocol error." << endl;
615             return 1;
616         }
617         rbuffer.consume(1);
618     }
619
620     cout << "Service unloaded." << endl;
621     return 0;
622 }
623
624 static int list_services(int socknum, cpbuffer_t &rbuffer)
625 {
626     using namespace std;
627     
628     char cmdbuf[] = { (char)DINIT_CP_LISTSERVICES };
629     write_all_x(socknum, cmdbuf, 1);
630
631     wait_for_reply(rbuffer, socknum);
632     while (rbuffer[0] == DINIT_RP_SVCINFO) {
633         int hdrsize = 8 + std::max(sizeof(int), sizeof(pid_t));
634         fill_buffer_to(rbuffer, socknum, hdrsize);
635         int nameLen = rbuffer[1];
636         service_state_t current = static_cast<service_state_t>(rbuffer[2]);
637         service_state_t target = static_cast<service_state_t>(rbuffer[3]);
638
639         int console_flags = rbuffer[4];
640         bool has_console = (console_flags & 2) != 0;
641         bool waiting_console = (console_flags & 1) != 0;
642         bool was_skipped = (console_flags & 4) != 0;
643
644         stopped_reason_t stop_reason = static_cast<stopped_reason_t>(rbuffer[5]);
645
646         pid_t service_pid;
647         int exit_status;
648         if (current != service_state_t::STOPPED) {
649             rbuffer.extract((char *)&service_pid, 8, sizeof(service_pid));
650         }
651         else {
652                 rbuffer.extract((char *)&exit_status, 8, sizeof(exit_status));
653         }
654
655         fill_buffer_to(rbuffer, socknum, nameLen + hdrsize);
656
657         char *name_ptr = rbuffer.get_ptr(hdrsize);
658         int clength = std::min(rbuffer.get_contiguous_length(name_ptr), nameLen);
659
660         string name = string(name_ptr, clength);
661         name.append(rbuffer.get_buf_base(), nameLen - clength);
662
663         cout << "[";
664
665         cout << (target  == service_state_t::STARTED ? "{" : " ");
666         if (current == service_state_t::STARTED) {
667             cout << (was_skipped ? "s" : "+");
668         }
669         else {
670             cout << " ";
671         }
672         cout << (target  == service_state_t::STARTED ? "}" : " ");
673         
674         if (current == service_state_t::STARTING) {
675             cout << "<<";
676         }
677         else if (current == service_state_t::STOPPING) {
678             cout << ">>";
679         }
680         else {
681             cout << "  ";
682         }
683         
684         cout << (target  == service_state_t::STOPPED ? "{" : " ");
685         if (current == service_state_t::STOPPED) {
686             bool did_fail = false;
687             if (stop_reason == stopped_reason_t::TERMINATED) {
688                 if (!WIFEXITED(exit_status) || WEXITSTATUS(exit_status) != 0) {
689                     did_fail = true;
690                 }
691             }
692             else did_fail = (stop_reason != stopped_reason_t::NORMAL);
693
694             cout << (did_fail ? "X" : "-");
695         }
696         else {
697                 cout << " ";
698         }
699         cout << (target == service_state_t::STOPPED ? "}" : " ");
700
701         cout << "] " << name;
702
703         if (current != service_state_t::STOPPED && service_pid != -1) {
704                 cout << " (pid: " << service_pid << ")";
705         }
706         
707         if (current == service_state_t::STOPPED && stop_reason == stopped_reason_t::TERMINATED) {
708             if (WIFEXITED(exit_status)) {
709                 cout << " (exit status: " << WEXITSTATUS(exit_status) << ")";
710             }
711             else if (WIFSIGNALED(exit_status)) {
712                 cout << " (signal: " << WSTOPSIG(exit_status) << ")";
713             }
714         }
715
716         if (has_console) {
717                 cout << " (has console)";
718         }
719         else if (waiting_console) {
720                 cout << " (waiting for console)";
721         }
722
723         cout << endl;
724
725         rbuffer.consume(hdrsize + nameLen);
726         wait_for_reply(rbuffer, socknum);
727     }
728
729     if (rbuffer[0] != DINIT_RP_LISTDONE) {
730         cerr << "dinitctl: Control socket protocol error" << endl;
731         return 1;
732     }
733
734     return 0;
735 }
736
737 static int add_remove_dependency(int socknum, cpbuffer_t &rbuffer, bool add, char *service_from,
738         char *service_to, dependency_type dep_type)
739 {
740     using namespace std;
741
742     // First find the "from" service:
743     if (issue_load_service(socknum, service_from, false) == 1) {
744         return 1;
745     }
746
747     wait_for_reply(rbuffer, socknum);
748
749     handle_t from_handle;
750
751     if (check_load_reply(socknum, rbuffer, &from_handle, nullptr) != 0) {
752         return 1;
753     }
754
755     // Then find or load the "to" service:
756     if (issue_load_service(socknum, service_to, false) == 1) {
757         return 1;
758     }
759
760     wait_for_reply(rbuffer, socknum);
761
762     handle_t to_handle;
763
764     if (check_load_reply(socknum, rbuffer, &to_handle, nullptr) != 0) {
765         return 1;
766     }
767
768     constexpr int pktsize = 2 + sizeof(handle_t) * 2;
769     char cmdbuf[pktsize] = { add ? (char)DINIT_CP_ADD_DEP : (char)DINIT_CP_REM_DEP, (char)dep_type};
770     memcpy(cmdbuf + 2, &from_handle, sizeof(from_handle));
771     memcpy(cmdbuf + 2 + sizeof(from_handle), &to_handle, sizeof(to_handle));
772     write_all_x(socknum, cmdbuf, pktsize);
773
774     wait_for_reply(rbuffer, socknum);
775
776     // check reply
777     if (rbuffer[0] == DINIT_RP_NAK) {
778         cerr << "dinitctl: Could not add dependency: circular dependency or wrong state" << endl;
779         return 1;
780     }
781     if (rbuffer[0] != DINIT_RP_ACK) {
782         cerr << "dinitctl: Control socket protocol error" << endl;
783         return 1;
784     }
785
786     return 0;
787 }
788
789 static int shutdown_dinit(int socknum, cpbuffer_t &rbuffer)
790 {
791     // TODO support no-wait option.
792     using namespace std;
793
794     // Build buffer;
795     constexpr int bufsize = 2;
796     char buf[bufsize];
797
798     buf[0] = DINIT_CP_SHUTDOWN;
799     buf[1] = static_cast<char>(shutdown_type_t::HALT);
800
801     write_all_x(socknum, buf, bufsize);
802
803     wait_for_reply(rbuffer, socknum);
804
805     if (rbuffer[0] != DINIT_RP_ACK) {
806         cerr << "dinitctl: Control socket protocol error" << endl;
807         return 1;
808     }
809
810     // Now wait for rollback complete:
811     try {
812         while (true) {
813             wait_for_info(rbuffer, socknum);
814             if (rbuffer[0] == DINIT_ROLLBACK_COMPLETED) {
815                 break;
816             }
817         }
818     }
819     catch (cp_read_exception &exc) {
820         // Dinit can terminate before replying: let's assume that happened.
821         // TODO: better check, possibly ensure that dinit actually sends rollback complete before
822         // termination.
823     }
824
825     return 0;
826 }