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