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