dinitctl: refactoring.
[oweals/dinit.git] / src / dinitctl.cc
1 #include <cstdio>
2 #include <cstddef>
3 #include <cstring>
4 #include <string>
5 #include <iostream>
6 #include <fstream>
7 #include <system_error>
8 #include <memory>
9 #include <algorithm>
10
11 #include <sys/types.h>
12 #include <sys/wait.h>
13 #include <sys/socket.h>
14 #include <sys/un.h>
15 #include <unistd.h>
16 #include <signal.h>
17 #include <pwd.h>
18
19 #include "control-cmds.h"
20 #include "service-constants.h"
21 #include "cpbuffer.h"
22 #include "dinit-client.h"
23 #include "load-service.h"
24
25 // dinitctl:  utility to control the Dinit daemon, including starting and stopping of services.
26
27 // This utility communicates with the dinit daemon via a unix stream socket (/dev/initctl,
28 // or $HOME/.dinitctl).
29
30 static constexpr uint16_t min_cp_version = 1;
31 static constexpr uint16_t max_cp_version = 1;
32
33 enum class command_t;
34
35 static int issue_load_service(int socknum, const char *service_name, bool find_only = false);
36 static int check_load_reply(int socknum, cpbuffer_t &, handle_t *handle_p, service_state_t *state_p);
37 static int start_stop_service(int socknum, cpbuffer_t &, const char *service_name, command_t command,
38         bool do_pin, bool wait_for_service, bool verbose);
39 static int unpin_service(int socknum, cpbuffer_t &, const char *service_name, bool verbose);
40 static int unload_service(int socknum, cpbuffer_t &, const char *service_name);
41 static int list_services(int socknum, cpbuffer_t &);
42 static int shutdown_dinit(int soclknum, cpbuffer_t &);
43 static int add_remove_dependency(int socknum, cpbuffer_t &rbuffer, bool add, const char *service_from,
44         const char *service_to, dependency_type dep_type);
45 static int enable_service(int socknum, cpbuffer_t &rbuffer, const char *from, const char *to);
46
47 static const char * describeState(bool stopped)
48 {
49     return stopped ? "stopped" : "started";
50 }
51
52 static const char * describeVerb(bool stop)
53 {
54     return stop ? "stop" : "start";
55 }
56
57 enum class command_t {
58     NONE,
59     START_SERVICE,
60     WAKE_SERVICE,
61     STOP_SERVICE,
62     RELEASE_SERVICE,
63     UNPIN_SERVICE,
64     UNLOAD_SERVICE,
65     LIST_SERVICES,
66     SHUTDOWN,
67     ADD_DEPENDENCY,
68     RM_DEPENDENCY,
69     ENABLE_SERVICE,
70     DISABLE_SERVICE
71 };
72
73
74 // Entry point.
75 int main(int argc, char **argv)
76 {
77     using namespace std;
78     
79     bool show_help = argc < 2;
80     const char *service_name = nullptr;
81     const char *to_service_name = nullptr;
82     dependency_type dep_type;
83     bool dep_type_set = false;
84     
85     std::string control_socket_str;
86     const char * control_socket_path = nullptr;
87     
88     bool verbose = true;
89     bool sys_dinit = false;  // communicate with system daemon
90     bool wait_for_service = true;
91     bool do_pin = false;
92     
93     command_t command = command_t::NONE;
94         
95     for (int i = 1; i < argc; i++) {
96         if (argv[i][0] == '-') {
97             if (strcmp(argv[i], "--help") == 0) {
98                 show_help = true;
99                 break;
100             }
101             else if (strcmp(argv[i], "--no-wait") == 0) {
102                 wait_for_service = false;
103             }
104             else if (strcmp(argv[i], "--quiet") == 0) {
105                 verbose = false;
106             }
107             else if (strcmp(argv[i], "--system") == 0 || strcmp(argv[i], "-s") == 0) {
108                 sys_dinit = true;
109             }
110             else if (strcmp(argv[i], "--pin") == 0) {
111                 do_pin = true;
112             }
113             else if ((command == command_t::ENABLE_SERVICE || command == command_t::DISABLE_SERVICE)
114                     && strcmp(argv[i], "--from") == 0) {
115                 ++i;
116                 if (i == argc) {
117                     cerr << "dinitctl: --from should be followed by a service name" << std::endl;
118                     return 1;
119                 }
120                 service_name = argv[i];
121             }
122             else {
123                 cerr << "dinitctl: unrecognized option: " << argv[i] << " (use --help for help)\n";
124                 return 1;
125             }
126         }
127         else if (command == command_t::NONE) {
128             if (strcmp(argv[i], "start") == 0) {
129                 command = command_t::START_SERVICE; 
130             }
131             else if (strcmp(argv[i], "wake") == 0) {
132                 command = command_t::WAKE_SERVICE;
133             }
134             else if (strcmp(argv[i], "stop") == 0) {
135                 command = command_t::STOP_SERVICE;
136             }
137             else if (strcmp(argv[i], "release") == 0) {
138                 command = command_t::RELEASE_SERVICE;
139             }
140             else if (strcmp(argv[i], "unpin") == 0) {
141                 command = command_t::UNPIN_SERVICE;
142             }
143             else if (strcmp(argv[i], "unload") == 0) {
144                 command = command_t::UNLOAD_SERVICE;
145             }
146             else if (strcmp(argv[i], "list") == 0) {
147                 command = command_t::LIST_SERVICES;
148             }
149             else if (strcmp(argv[i], "shutdown") == 0) {
150                 command = command_t::SHUTDOWN;
151             }
152             else if (strcmp(argv[i], "add-dep") == 0) {
153                 command = command_t::ADD_DEPENDENCY;
154             }
155             else if (strcmp(argv[i], "rm-dep") == 0) {
156                 command = command_t::RM_DEPENDENCY;
157             }
158             else if (strcmp(argv[i], "enable") == 0) {
159                 command = command_t::ENABLE_SERVICE;
160             }
161             else {
162                 cerr << "dinitctl: unrecognized command: " << argv[i] << " (use --help for help)\n";
163                 return 1;
164             }
165         }
166         else {
167             // service name / other non-option
168             if (command == command_t::ADD_DEPENDENCY || command == command_t::RM_DEPENDENCY) {
169                 if (! dep_type_set) {
170                     if (strcmp(argv[i], "regular") == 0) {
171                         dep_type = dependency_type::REGULAR;
172                     }
173                     else if (strcmp(argv[i], "milestone") == 0) {
174                         dep_type = dependency_type::MILESTONE;
175                     }
176                     else if (strcmp(argv[i], "waits-for") == 0) {
177                         dep_type = dependency_type::WAITS_FOR;
178                     }
179                     else {
180                         show_help = true;
181                         break;
182                     }
183                     dep_type_set = true;
184                 }
185                 else if (service_name == nullptr) {
186                     service_name = argv[i];
187                 }
188                 else if (to_service_name == nullptr) {
189                     to_service_name = argv[i];
190                 }
191                 else {
192                     show_help = true;
193                     break;
194                 }
195             }
196             else if (command == command_t::ENABLE_SERVICE || command == command_t::DISABLE_SERVICE) {
197                 if (to_service_name != nullptr) {
198                     show_help = true;
199                     break;
200                 }
201                 to_service_name = argv[i];
202             }
203             else {
204                 if (service_name != nullptr) {
205                     show_help = true;
206                     break;
207                 }
208                 service_name = argv[i];
209                 // TODO support multiple services
210             }
211         }
212     }
213     
214     bool no_service_cmd = (command == command_t::LIST_SERVICES || command == command_t::SHUTDOWN);
215
216     if (service_name != nullptr && no_service_cmd) {
217         show_help = true;
218     }
219     
220     if ((service_name == nullptr && ! no_service_cmd) || command == command_t::NONE) {
221         show_help = true;
222     }
223
224     if ((command == command_t::ADD_DEPENDENCY || command == command_t::RM_DEPENDENCY)
225             && (! dep_type_set || service_name == nullptr || to_service_name == nullptr)) {
226         show_help = true;
227     }
228
229     if (show_help) {
230         cout << "dinitctl:   control Dinit services\n"
231           "\n"
232           "Usage:\n"
233           "    dinitctl [options] start [options] <service-name>\n"
234           "    dinitctl [options] stop [options] <service-name>\n"
235           "    dinitctl [options] wake [options] <service-name>\n"
236           "    dinitctl [options] release [options] <service-name>\n"
237           "    dinitctl [options] unpin <service-name>\n"
238           "    dinitctl unload <service-name>\n"
239           "    dinitctl list\n"
240           "    dinitctl shutdown\n"
241           "    dinitctl add-dep <type> <from-service> <to-service>\n"
242           "    dinitctl rm-dep <type> <from-service> <to-service>\n"
243           "\n"
244           "Note: An activated service continues running when its dependents stop.\n"
245           "\n"
246           "General options:\n"
247           "  -s, --system     : control system daemon instead of user daemon\n"
248           "  --quiet          : suppress output (except errors)\n"
249           "\n"
250           "Command options:\n"
251           "  --help           : show this help\n"
252           "  --no-wait        : don't wait for service startup/shutdown to complete\n"
253           "  --pin            : pin the service in the requested state\n";
254         return 1;
255     }
256     
257     signal(SIGPIPE, SIG_IGN);
258     
259     control_socket_path = "/dev/dinitctl";
260     
261     // Locate control socket
262     if (! sys_dinit) {
263         char * userhome = getenv("HOME");
264         if (userhome == nullptr) {
265             struct passwd * pwuid_p = getpwuid(getuid());
266             if (pwuid_p != nullptr) {
267                 userhome = pwuid_p->pw_dir;
268             }
269         }
270         
271         if (userhome != nullptr) {
272             control_socket_str = userhome;
273             control_socket_str += "/.dinitctl";
274             control_socket_path = control_socket_str.c_str();
275         }
276         else {
277             cerr << "Cannot locate user home directory (set HOME or check /etc/passwd file)" << endl;
278             return 1;
279         }
280     }
281     
282     int socknum = socket(AF_UNIX, SOCK_STREAM, 0);
283     if (socknum == -1) {
284         perror("dinitctl: socket");
285         return 1;
286     }
287
288     struct sockaddr_un * name;
289     uint sockaddr_size = offsetof(struct sockaddr_un, sun_path) + strlen(control_socket_path) + 1;
290     name = (struct sockaddr_un *) malloc(sockaddr_size);
291     if (name == nullptr) {
292         cerr << "dinitctl: Out of memory" << endl;
293         return 1;
294     }
295     
296     name->sun_family = AF_UNIX;
297     strcpy(name->sun_path, control_socket_path);
298     
299     int connr = connect(socknum, (struct sockaddr *) name, sockaddr_size);
300     if (connr == -1) {
301         perror("dinitctl: connect");
302         return 1;
303     }
304     
305     try {
306         // Start by querying protocol version:
307         cpbuffer_t rbuffer;
308         check_protocol_version(min_cp_version, max_cp_version, rbuffer, socknum);
309
310         if (command == command_t::UNPIN_SERVICE) {
311             return unpin_service(socknum, rbuffer, service_name, verbose);
312         }
313         else if (command == command_t::UNLOAD_SERVICE) {
314             return unload_service(socknum, rbuffer, service_name);
315         }
316         else if (command == command_t::LIST_SERVICES) {
317             return list_services(socknum, rbuffer);
318         }
319         else if (command == command_t::SHUTDOWN) {
320             return shutdown_dinit(socknum, rbuffer);
321         }
322         else if (command == command_t::ADD_DEPENDENCY || command == command_t::RM_DEPENDENCY) {
323             return add_remove_dependency(socknum, rbuffer, command == command_t::ADD_DEPENDENCY,
324                     service_name, to_service_name, dep_type);
325         }
326         else if (command == command_t::ENABLE_SERVICE) {
327             // If only one service specified, assume that we enable for 'boot' service:
328             if (service_name == nullptr) {
329                 service_name = "boot";
330             }
331             return enable_service(socknum, rbuffer, service_name, to_service_name);
332         }
333         else {
334             return start_stop_service(socknum, rbuffer, service_name, command, do_pin,
335                     wait_for_service, verbose);
336         }
337     }
338     catch (cp_old_client_exception &e) {
339         std::cerr << "dinitctl: too old (server reports newer protocol version)" << std::endl;
340         return 1;
341     }
342     catch (cp_old_server_exception &e) {
343         std::cerr << "dinitctl: server too old or protocol error" << std::endl;
344         return 1;
345     }
346     catch (cp_read_exception &e) {
347         cerr << "dinitctl: control socket read failure or protocol error" << endl;
348         return 1;
349     }
350     catch (cp_write_exception &e) {
351         cerr << "dinitctl: control socket write error: " << std::strerror(e.errcode) << endl;
352         return 1;
353     }
354 }
355
356 // Extract/read a string of specified length from the buffer/socket. The string is consumed
357 // from the buffer.
358 static std::string read_string(int socknum, cpbuffer_t &rbuffer, uint32_t length)
359 {
360     int rb_len = rbuffer.get_length();
361     if (rb_len >= length) {
362         std::string r = rbuffer.extract_string(0, length);
363         rbuffer.consume(length);
364         return r;
365     }
366
367     std::string r = rbuffer.extract_string(0, rb_len);
368     uint32_t rlen = length - rb_len;
369     uint32_t clen;
370     do {
371         rbuffer.reset();
372         rbuffer.fill(socknum);
373         char *bptr = rbuffer.get_ptr(0);
374         clen = rbuffer.get_length();
375         clen = std::min(clen, rlen);
376         r.append(bptr, clen);
377         rlen -= clen;
378     } while (rlen > 0);
379
380     rbuffer.consume(clen);
381
382     return r;
383 }
384
385 // Load a service: issue load command, wait for reply. Return true on success, display error message
386 // and return false on failure.
387 //      socknum  - the socket fd to communicate via
388 //      rbuffer  - the buffer for communication
389 //      name     - the name of the service to load
390 //      handle   - where to store the handle of the loaded service
391 //      state    - where to store the state of the loaded service (may be null).
392 static bool load_service(int socknum, cpbuffer_t &rbuffer, const char *name, handle_t *handle, service_state_t *state)
393 {
394     // Load 'to' service:
395     if (issue_load_service(socknum, name)) {
396         return false;
397     }
398
399     wait_for_reply(rbuffer, socknum);
400
401     if (check_load_reply(socknum, rbuffer, handle, state) != 0) {
402         return false;
403     }
404
405     return true;
406 }
407
408 // Start/stop a service
409 static int start_stop_service(int socknum, cpbuffer_t &rbuffer, const char *service_name,
410         command_t command, bool do_pin, bool wait_for_service, bool verbose)
411 {
412     using namespace std;
413
414     bool do_stop = (command == command_t::STOP_SERVICE || command == command_t::RELEASE_SERVICE);
415
416     service_state_t state;
417     handle_t handle;
418     
419     if (! load_service(socknum, rbuffer, service_name, &handle, &state)) {
420         return 1;
421     }
422
423     service_state_t wanted_state = do_stop ? service_state_t::STOPPED : service_state_t::STARTED;
424     int pcommand = 0;
425     switch (command) {
426         case command_t::STOP_SERVICE:
427             pcommand = DINIT_CP_STOPSERVICE;
428             break;
429         case command_t::RELEASE_SERVICE:
430             pcommand = DINIT_CP_RELEASESERVICE;
431             break;
432         case command_t::START_SERVICE:
433             pcommand = DINIT_CP_STARTSERVICE;
434             break;
435         case command_t::WAKE_SERVICE:
436             pcommand = DINIT_CP_WAKESERVICE;
437             break;
438         default: ;
439     }
440
441     // Need to issue STOPSERVICE/STARTSERVICE
442     // We'll do this regardless of the current service state / target state, since issuing
443     // start/stop also sets or clears the "explicitly started" flag on the service.
444     {
445         char buf[2 + sizeof(handle)];
446         buf[0] = pcommand;
447         buf[1] = do_pin ? 1 : 0;
448         memcpy(buf + 2, &handle, sizeof(handle));
449         write_all_x(socknum, buf, 2 + sizeof(handle));
450         
451         wait_for_reply(rbuffer, socknum);
452         if (rbuffer[0] == DINIT_RP_ALREADYSS) {
453             bool already = (state == wanted_state);
454             if (verbose) {
455                 cout << "Service " << (already ? "(already) " : "")
456                         << describeState(do_stop) << "." << endl;
457             }
458             return 0; // success!
459         }
460         if (rbuffer[0] != DINIT_RP_ACK) {
461             cerr << "dinitctl: Protocol error." << endl;
462             return 1;
463         }
464         rbuffer.consume(1);
465     }
466
467     if (! wait_for_service) {
468         if (verbose) {
469             cout << "Issued " << describeVerb(do_stop) << " command successfully." << endl;
470         }
471         return 0;
472     }
473
474     service_event_t completionEvent;
475     service_event_t cancelledEvent;
476
477     if (do_stop) {
478         completionEvent = service_event_t::STOPPED;
479         cancelledEvent = service_event_t::STOPCANCELLED;
480     }
481     else {
482         completionEvent = service_event_t::STARTED;
483         cancelledEvent = service_event_t::STARTCANCELLED;
484     }
485
486     // Wait until service started:
487     int r = rbuffer.fill_to(socknum, 2);
488     while (r > 0) {
489         if (rbuffer[0] >= 100) {
490             int pktlen = (unsigned char) rbuffer[1];
491             fill_buffer_to(rbuffer, socknum, pktlen);
492
493             if (rbuffer[0] == DINIT_IP_SERVICEEVENT) {
494                 handle_t ev_handle;
495                 rbuffer.extract((char *) &ev_handle, 2, sizeof(ev_handle));
496                 service_event_t event = static_cast<service_event_t>(rbuffer[2 + sizeof(ev_handle)]);
497                 if (ev_handle == handle) {
498                     if (event == completionEvent) {
499                         if (verbose) {
500                             cout << "Service " << describeState(do_stop) << "." << endl;
501                         }
502                         return 0;
503                     }
504                     else if (event == cancelledEvent) {
505                         if (verbose) {
506                             cout << "Service " << describeVerb(do_stop) << " cancelled." << endl;
507                         }
508                         return 1;
509                     }
510                     else if (! do_stop && event == service_event_t::FAILEDSTART) {
511                         if (verbose) {
512                             cout << "Service failed to start." << endl;
513                         }
514                         return 1;
515                     }
516                 }
517             }
518
519             rbuffer.consume(pktlen);
520             r = rbuffer.fill_to(socknum, 2);
521         }
522         else {
523             // Not an information packet?
524             cerr << "dinitctl: protocol error" << endl;
525             return 1;
526         }
527     }
528
529     if (r == -1) {
530         perror("dinitctl: read");
531     }
532     else {
533         cerr << "protocol error (connection closed by server)" << endl;
534     }
535     return 1;
536 }
537
538 // Issue a "load service" command (DINIT_CP_LOADSERVICE), without waiting for
539 // a response. Returns 1 on failure (with error logged), 0 on success.
540 static int issue_load_service(int socknum, const char *service_name, bool find_only)
541 {
542     // Build buffer;
543     uint16_t sname_len = strlen(service_name);
544     int bufsize = 3 + sname_len;
545     
546     std::unique_ptr<char[]> ubuf(new char[bufsize]);
547     auto buf = ubuf.get();
548
549     buf[0] = find_only ? DINIT_CP_FINDSERVICE : DINIT_CP_LOADSERVICE;
550     memcpy(buf + 1, &sname_len, 2);
551     memcpy(buf + 3, service_name, sname_len);
552
553     write_all_x(socknum, buf, bufsize);
554     
555     return 0;
556 }
557
558 // Check that a "load service" reply was received, and that the requested service was found.
559 //   state_p may be null.
560 static int check_load_reply(int socknum, cpbuffer_t &rbuffer, handle_t *handle_p, service_state_t *state_p)
561 {
562     using namespace std;
563     
564     if (rbuffer[0] == DINIT_RP_SERVICERECORD) {
565         fill_buffer_to(rbuffer, socknum, 2 + sizeof(*handle_p));
566         rbuffer.extract((char *) handle_p, 2, sizeof(*handle_p));
567         if (state_p) *state_p = static_cast<service_state_t>(rbuffer[1]);
568         //target_state = static_cast<service_state_t>(rbuffer[2 + sizeof(handle)]);
569         rbuffer.consume(3 + sizeof(*handle_p));
570         return 0;
571     }
572     else if (rbuffer[0] == DINIT_RP_NOSERVICE) {
573         cerr << "dinitctl: failed to find/load service." << endl;
574         return 1;
575     }
576     else {
577         cerr << "dinitctl: protocol error." << endl;
578         return 1;
579     }
580 }
581
582 static int unpin_service(int socknum, cpbuffer_t &rbuffer, const char *service_name, bool verbose)
583 {
584     using namespace std;
585
586     handle_t handle;
587     
588     // Build buffer;
589     if (! load_service(socknum, rbuffer, service_name, &handle, nullptr)) {
590         return 1;
591     }
592     
593     // Issue UNPIN command.
594     {
595         char buf[1 + sizeof(handle)];
596         buf[0] = DINIT_CP_UNPINSERVICE;
597         memcpy(buf + 1, &handle, sizeof(handle));
598         write_all_x(socknum, buf, 2 + sizeof(handle));
599         
600         wait_for_reply(rbuffer, socknum);
601         if (rbuffer[0] != DINIT_RP_ACK) {
602             cerr << "dinitctl: protocol error." << endl;
603             return 1;
604         }
605         rbuffer.consume(1);
606     }
607
608     if (verbose) {
609         cout << "Service unpinned." << endl;
610     }
611     return 0;
612 }
613
614 static int unload_service(int socknum, cpbuffer_t &rbuffer, const char *service_name)
615 {
616     using namespace std;
617
618     if (issue_load_service(socknum, service_name, true) == 1) {
619         return 1;
620     }
621
622     wait_for_reply(rbuffer, socknum);
623
624     handle_t handle;
625
626     if (rbuffer[0] == DINIT_RP_NOSERVICE) {
627         cerr << "dinitctl: service not loaded." << endl;
628         return 1;
629     }
630
631     if (check_load_reply(socknum, rbuffer, &handle, nullptr) != 0) {
632         return 1;
633     }
634
635     // Issue UNLOAD command.
636     {
637         char buf[1 + sizeof(handle)];
638         buf[0] = DINIT_CP_UNLOADSERVICE;
639         memcpy(buf + 1, &handle, sizeof(handle));
640         write_all_x(socknum, buf, 2 + sizeof(handle));
641
642         wait_for_reply(rbuffer, socknum);
643         if (rbuffer[0] == DINIT_RP_NAK) {
644             cerr << "dinitctl: Could not unload service; service not stopped, or is a dependency of "
645                     "other service." << endl;
646             return 1;
647         }
648         if (rbuffer[0] != DINIT_RP_ACK) {
649             cerr << "dinitctl: Protocol error." << endl;
650             return 1;
651         }
652         rbuffer.consume(1);
653     }
654
655     cout << "Service unloaded." << endl;
656     return 0;
657 }
658
659 static int list_services(int socknum, cpbuffer_t &rbuffer)
660 {
661     using namespace std;
662     
663     char cmdbuf[] = { (char)DINIT_CP_LISTSERVICES };
664     write_all_x(socknum, cmdbuf, 1);
665
666     wait_for_reply(rbuffer, socknum);
667     while (rbuffer[0] == DINIT_RP_SVCINFO) {
668         int hdrsize = 8 + std::max(sizeof(int), sizeof(pid_t));
669         fill_buffer_to(rbuffer, socknum, hdrsize);
670         int nameLen = rbuffer[1];
671         service_state_t current = static_cast<service_state_t>(rbuffer[2]);
672         service_state_t target = static_cast<service_state_t>(rbuffer[3]);
673
674         int console_flags = rbuffer[4];
675         bool has_console = (console_flags & 2) != 0;
676         bool waiting_console = (console_flags & 1) != 0;
677         bool was_skipped = (console_flags & 4) != 0;
678
679         stopped_reason_t stop_reason = static_cast<stopped_reason_t>(rbuffer[5]);
680
681         pid_t service_pid;
682         int exit_status;
683         if (current != service_state_t::STOPPED) {
684             rbuffer.extract((char *)&service_pid, 8, sizeof(service_pid));
685         }
686         else {
687                 rbuffer.extract((char *)&exit_status, 8, sizeof(exit_status));
688         }
689
690         fill_buffer_to(rbuffer, socknum, nameLen + hdrsize);
691
692         char *name_ptr = rbuffer.get_ptr(hdrsize);
693         int clength = std::min(rbuffer.get_contiguous_length(name_ptr), nameLen);
694
695         string name = string(name_ptr, clength);
696         name.append(rbuffer.get_buf_base(), nameLen - clength);
697
698         cout << "[";
699
700         cout << (target  == service_state_t::STARTED ? "{" : " ");
701         if (current == service_state_t::STARTED) {
702             cout << (was_skipped ? "s" : "+");
703         }
704         else {
705             cout << " ";
706         }
707         cout << (target  == service_state_t::STARTED ? "}" : " ");
708         
709         if (current == service_state_t::STARTING) {
710             cout << "<<";
711         }
712         else if (current == service_state_t::STOPPING) {
713             cout << ">>";
714         }
715         else {
716             cout << "  ";
717         }
718         
719         cout << (target  == service_state_t::STOPPED ? "{" : " ");
720         if (current == service_state_t::STOPPED) {
721             bool did_fail = false;
722             if (stop_reason == stopped_reason_t::TERMINATED) {
723                 if (!WIFEXITED(exit_status) || WEXITSTATUS(exit_status) != 0) {
724                     did_fail = true;
725                 }
726             }
727             else did_fail = (stop_reason != stopped_reason_t::NORMAL);
728
729             cout << (did_fail ? "X" : "-");
730         }
731         else {
732                 cout << " ";
733         }
734         cout << (target == service_state_t::STOPPED ? "}" : " ");
735
736         cout << "] " << name;
737
738         if (current != service_state_t::STOPPED && service_pid != -1) {
739                 cout << " (pid: " << service_pid << ")";
740         }
741         
742         if (current == service_state_t::STOPPED && stop_reason == stopped_reason_t::TERMINATED) {
743             if (WIFEXITED(exit_status)) {
744                 cout << " (exit status: " << WEXITSTATUS(exit_status) << ")";
745             }
746             else if (WIFSIGNALED(exit_status)) {
747                 cout << " (signal: " << WSTOPSIG(exit_status) << ")";
748             }
749         }
750
751         if (has_console) {
752                 cout << " (has console)";
753         }
754         else if (waiting_console) {
755                 cout << " (waiting for console)";
756         }
757
758         cout << endl;
759
760         rbuffer.consume(hdrsize + nameLen);
761         wait_for_reply(rbuffer, socknum);
762     }
763
764     if (rbuffer[0] != DINIT_RP_LISTDONE) {
765         cerr << "dinitctl: Control socket protocol error" << endl;
766         return 1;
767     }
768
769     return 0;
770 }
771
772 static int add_remove_dependency(int socknum, cpbuffer_t &rbuffer, bool add,
773         const char *service_from, const char *service_to, dependency_type dep_type)
774 {
775     using namespace std;
776
777
778     handle_t from_handle;
779     handle_t to_handle;
780
781     if (! load_service(socknum, rbuffer, service_from, &from_handle, nullptr)
782             || ! load_service(socknum, rbuffer, service_to, &to_handle, nullptr)) {
783         return 1;
784     }
785
786     constexpr int pktsize = 2 + sizeof(handle_t) * 2;
787     char cmdbuf[pktsize] = { add ? (char)DINIT_CP_ADD_DEP : (char)DINIT_CP_REM_DEP, (char)dep_type};
788     memcpy(cmdbuf + 2, &from_handle, sizeof(from_handle));
789     memcpy(cmdbuf + 2 + sizeof(from_handle), &to_handle, sizeof(to_handle));
790     write_all_x(socknum, cmdbuf, pktsize);
791
792     wait_for_reply(rbuffer, socknum);
793
794     // check reply
795     if (rbuffer[0] == DINIT_RP_NAK) {
796         cerr << "dinitctl: Could not add dependency: circular dependency or wrong state" << endl;
797         return 1;
798     }
799     if (rbuffer[0] != DINIT_RP_ACK) {
800         cerr << "dinitctl: Control socket protocol error" << endl;
801         return 1;
802     }
803
804     return 0;
805 }
806
807 static int shutdown_dinit(int socknum, cpbuffer_t &rbuffer)
808 {
809     // TODO support no-wait option.
810     using namespace std;
811
812     // Build buffer;
813     constexpr int bufsize = 2;
814     char buf[bufsize];
815
816     buf[0] = DINIT_CP_SHUTDOWN;
817     buf[1] = static_cast<char>(shutdown_type_t::HALT);
818
819     write_all_x(socknum, buf, bufsize);
820
821     wait_for_reply(rbuffer, socknum);
822
823     if (rbuffer[0] != DINIT_RP_ACK) {
824         cerr << "dinitctl: Control socket protocol error" << endl;
825         return 1;
826     }
827
828     // Now wait for rollback complete:
829     try {
830         while (true) {
831             wait_for_info(rbuffer, socknum);
832             if (rbuffer[0] == DINIT_ROLLBACK_COMPLETED) {
833                 break;
834             }
835         }
836     }
837     catch (cp_read_exception &exc) {
838         // Dinit can terminate before replying: let's assume that happened.
839         // TODO: better check, possibly ensure that dinit actually sends rollback complete before
840         // termination.
841     }
842
843     return 0;
844 }
845
846 // Join two paths; the 2nd must be relative
847 static std::string join_paths(std::string p1, std::string p2)
848 {
849     std::string r = p1;
850     if (*(r.rbegin()) != '/') {
851         r += '/';
852     }
853     return r + p2;
854 }
855
856 // exception for cancelling a service operation
857 class service_op_cancel { };
858
859 static int enable_service(int socknum, cpbuffer_t &rbuffer, const char *from, const char *to)
860 {
861     using namespace std;
862
863     service_state_t from_state = service_state_t::STARTED;
864     handle_t from_handle;
865
866     handle_t to_handle;
867
868     if (! load_service(socknum, rbuffer, from, &from_handle, &from_state)
869             || ! load_service(socknum, rbuffer, to, &to_handle, nullptr)) {
870         return 1;
871     }
872
873     // Get service load path
874     char buf[1] = { DINIT_CP_QUERY_LOAD_MECH };
875     write_all_x(socknum, buf, 1);
876
877     wait_for_reply(rbuffer, socknum);
878
879     if (rbuffer[0] != DINIT_RP_LOADER_MECH) {
880         cerr << "dinitctl: Control socket protocol error" << endl;
881         return 1;
882     }
883
884     // Packet type, load mechanism type, packet size:
885     fill_buffer_to(rbuffer, socknum, 2 + sizeof(uint32_t));
886
887     if (rbuffer[1] != SSET_TYPE_DIRLOAD) {
888         cerr << "dinitctl: unknown configuration, unable to load service descriptions" << endl;
889         return 1;
890     }
891
892     vector<string> paths;
893
894     uint32_t pktsize;
895     rbuffer.extract(&pktsize, 2, sizeof(uint32_t));
896
897     fill_buffer_to(rbuffer, socknum, 2 + sizeof(uint32_t) * 3); // path entries, cwd length
898
899     uint32_t path_entries;  // number of service directories
900     rbuffer.extract(&path_entries, 2 + sizeof(uint32_t), sizeof(uint32_t));
901
902     uint32_t cwd_len;
903     rbuffer.extract(&cwd_len, 2 + sizeof(uint32_t) * 2, sizeof(uint32_t));
904     rbuffer.consume(2 + sizeof(uint32_t) * 3);
905     pktsize -= 2 + sizeof(uint32_t) * 3;
906
907     // Read current working directory of daemon:
908     std::string dinit_cwd = read_string(socknum, rbuffer, cwd_len);
909
910     // dinit daemon base directory against which service paths are resolved is in dinit_cwd
911
912     for (int i = 0; i < (int)path_entries; i++) {
913         uint32_t plen;
914         fill_buffer_to(rbuffer, socknum, sizeof(uint32_t));
915         rbuffer.extract(&plen, 0, sizeof(uint32_t));
916         rbuffer.consume(sizeof(uint32_t));
917         paths.push_back(read_string(socknum, rbuffer, plen));
918     }
919
920     // all service directories are now in the 'paths' vector
921     // Load/read service description for 'from' service:
922
923     ifstream service_file;
924     string service_file_path;
925
926     for (std::string path : paths) {
927         string test_path = join_paths(dinit_cwd + '/' + path, from);
928
929         service_file.open(test_path.c_str(), ios::in);
930         if (service_file) {
931             service_file_path = test_path;
932             break;
933         }
934     }
935
936     if (! service_file) {
937         cerr << "dinitctl: could not locate service file for service '" << from << "'" << endl;
938         return 1;
939     }
940
941     // We now need to read the service file, identify the waits-for.d directory (bail out if more than one),
942     // make sure the service is not listed as a dependency individually.
943
944     string waits_for_d;
945
946     try {
947         process_service_file(from, service_file, [&](string &line, string &setting,
948                 dinit_load::string_iterator i, dinit_load::string_iterator end) -> void {
949             if (setting == "waits-for" || setting == "depends-on" || setting == "depends-ms") {
950                 string dname = dinit_load::read_setting_value(i, end);
951                 if (dname == to) {
952                     // There is already a dependency
953                     cerr << "dinitctl: there is a fixed dependency to service '" << to
954                             << "' in the service description of '" << from << "'." << endl;
955                     throw service_op_cancel();
956                 }
957             }
958             else if (setting == "waits-for.d") {
959                 string dname = dinit_load::read_setting_value(i, end);
960                 if (! waits_for_d.empty()) {
961                     cerr << "dinitctl: service '" << from << "' has multiple waits-for.d directories "
962                             << "specified in service description" << endl;
963                     throw service_op_cancel();
964                 }
965                 waits_for_d = std::move(dname);
966             }
967         });
968     }
969     catch (const service_op_cancel &cexc) {
970         return 1;
971     }
972
973     // If the from service has no waits-for.d specified, we can't continue
974     if (waits_for_d.empty()) {
975         cerr << "dinitctl: service '" << from << "' has no waits-for.d directory specified" << endl;
976         return 1;
977     }
978
979     // check if dependency already exists
980     string dep_link_path = join_paths(waits_for_d, to);
981     struct stat stat_buf;
982     if (lstat(dep_link_path.c_str(), &stat_buf) == -1) {
983         if (errno != ENOENT) {
984             cerr << "dinitctl: checking for existing dependency link: " << dep_link_path << ": "
985                     << strerror(errno) << endl;
986             return 1;
987         }
988     }
989     else {
990         // dependency already exists
991         cerr << "dinitctl: service already enabled." << endl;
992         return 1;
993     }
994
995     // warn if 'from' service is not started
996     if (from_state != service_state_t::STARTED) {
997         cerr << "dinitctl: warning: enabling dependency for non-started service" << endl;
998     }
999
1000     // add dependency
1001     constexpr int enable_pktsize = 2 + sizeof(handle_t) * 2;
1002     char cmdbuf[enable_pktsize] = { char(DINIT_CP_ENABLESERVICE), char(dependency_type::WAITS_FOR)};
1003     memcpy(cmdbuf + 2, &from_handle, sizeof(from_handle));
1004     memcpy(cmdbuf + 2 + sizeof(from_handle), &to_handle, sizeof(to_handle));
1005     write_all_x(socknum, cmdbuf, enable_pktsize);
1006
1007     wait_for_reply(rbuffer, socknum);
1008
1009     // check reply
1010     if (rbuffer[0] == DINIT_RP_NAK) {
1011         cerr << "dinitctl: Could not enable service: possible circular dependency" << endl;
1012         return 1;
1013     }
1014     if (rbuffer[0] != DINIT_RP_ACK) {
1015         cerr << "dinitctl: Control socket protocol error" << endl;
1016         return 1;
1017     }
1018
1019     // create link
1020     if (symlink(dep_link_path.c_str(), (string("../") + to).c_str()) == -1) {
1021         cerr << "dinitctl: Could not create symlink at " << dep_link_path << ": " << strerror(errno) << "\n"
1022                 "dinitctl: Note: service was activated, but will not be enabled on restart." << endl;
1023         return 1;
1024     }
1025
1026     return 0;
1027 }