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