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