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