6 #include <system_error>
10 #include <sys/socket.h>
16 #include "control-cmds.h"
17 #include "service-constants.h"
20 // dinitctl: utility to control the Dinit daemon, including starting and stopping of services.
22 // This utility communicates with the dinit daemon via a unix socket (/dev/initctl).
24 using handle_t = uint32_t;
31 ReadCPException(int err) : errcode(err) { }
34 static void fillBufferTo(CPBuffer<1024> *buf, int fd, int rlength)
36 int r = buf->fill_to(fd, rlength);
38 throw ReadCPException(errno);
41 throw ReadCPException(0);
45 static const char * describeState(bool stopped)
47 return stopped ? "stopped" : "started";
50 static const char * describeVerb(bool stop)
52 return stop ? "stop" : "start";
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)
59 fillBufferTo(&rbuffer, fd, 1);
61 while (rbuffer[0] >= 100) {
62 // Information packet; discard.
63 fillBufferTo(&rbuffer, fd, 1);
64 int pktlen = (unsigned char) rbuffer[1];
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);
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)
77 const char *cbuf = static_cast<const char *>(buf);
80 int r = write(fd, cbuf, count);
82 if (errno == EINTR) continue;
92 static int unpinService(int socknum, const char *service_name);
96 int main(int argc, char **argv)
100 bool do_stop = false;
101 bool show_help = argc < 2;
102 char *service_name = nullptr;
104 std::string control_socket_str;
105 const char * control_socket_path = nullptr;
108 bool sys_dinit = false; // communicate with system daemon
109 bool wait_for_service = true;
114 constexpr int START_SERVICE = 1;
115 constexpr int STOP_SERVICE = 2;
116 constexpr int UNPIN_SERVICE = 3;
118 for (int i = 1; i < argc; i++) {
119 if (argv[i][0] == '-') {
120 if (strcmp(argv[i], "--help") == 0) {
124 else if (strcmp(argv[i], "--no-wait") == 0) {
125 wait_for_service = false;
127 else if (strcmp(argv[i], "--quiet") == 0) {
130 else if (strcmp(argv[i], "--system") == 0 || strcmp(argv[i], "-s") == 0) {
133 else if (strcmp(argv[i], "--pin") == 0) {
137 cerr << "Unrecognized command-line parameter: " << argv[i] << endl;
141 else if (command == 0) {
142 if (strcmp(argv[i], "start") == 0) {
143 command = START_SERVICE;
145 else if (strcmp(argv[i], "stop") == 0) {
146 command = STOP_SERVICE;
148 else if (strcmp(argv[i], "unpin") == 0) {
149 command = UNPIN_SERVICE;
158 service_name = argv[i];
159 // TODO support multiple services (or at least give error if multiple
160 // services supplied)
164 if (service_name == nullptr || command == 0) {
169 cout << "dinitctl: control Dinit services" << endl;
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;
175 // cout << " dinitctl [options] wake <service-name> : start but don't activate service" << endl;
177 cout << "\nNote: An activated service keeps its dependencies running when possible." << endl;
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;
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;
190 signal(SIGPIPE, SIG_IGN);
192 control_socket_path = "/dev/dinitctl";
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;
203 if (userhome != nullptr) {
204 control_socket_str = userhome;
205 control_socket_str += "/.dinitctl";
206 control_socket_path = control_socket_str.c_str();
209 cerr << "Cannot locate user home directory (set HOME or check /etc/passwd file)" << endl;
214 int socknum = socket(AF_UNIX, SOCK_STREAM, 0);
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;
228 name->sun_family = AF_UNIX;
229 strcpy(name->sun_path, control_socket_path);
231 int connr = connect(socknum, (struct sockaddr *) name, sockaddr_size);
237 // TODO should start by querying protocol version
239 if (command == UNPIN_SERVICE) {
240 return unpinService(socknum, service_name);
243 do_stop = (command == STOP_SERVICE);
246 uint16_t sname_len = strlen(service_name);
247 int bufsize = 3 + sname_len;
251 unique_ptr<char[]> ubuf(new char[bufsize]);
252 auto *buf = ubuf.get();
254 buf[0] = DINIT_CP_LOADSERVICE;
255 memcpy(buf + 1, &sname_len, 2);
256 memcpy(buf + 3, service_name, sname_len);
258 r = write_all(socknum, buf, bufsize);
266 // Now we expect a reply:
269 CPBuffer<1024> rbuffer;
270 wait_for_reply(rbuffer, socknum);
272 //ServiceState state;
273 //ServiceState target_state;
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));
283 else if (rbuffer[0] == DINIT_RP_NOSERVICE) {
284 cerr << "Failed to find/load service." << endl;
288 cerr << "Protocol error." << endl;
292 // ServiceState wanted_state = do_stop ? ServiceState::STOPPED : ServiceState::STARTED;
293 int command = do_stop ? DINIT_CP_STOPSERVICE : DINIT_CP_STARTSERVICE;
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) {
301 auto buf = new char[2 + sizeof(handle)];
302 unique_ptr<char[]> ubuf(buf);
305 buf[1] = do_pin ? 1 : 0;
306 memcpy(buf + 2, &handle, sizeof(handle));
307 r = write_all(socknum, buf, 2 + sizeof(handle));
315 wait_for_reply(rbuffer, socknum);
316 if (rbuffer[0] == DINIT_RP_ALREADYSS) {
318 cout << "Service already " << describeState(do_stop) << "." << endl;
320 return 0; // success!
322 if (rbuffer[0] != DINIT_RP_ACK) {
323 cerr << "Protocol error." << endl;
330 if (state == wanted_state) {
332 cout << "Service already " << describeState(do_stop) << "." << endl;
334 return 0; // success!
338 if (! wait_for_service) {
340 cout << "Issued " << describeVerb(do_stop) << " command successfully." << endl;
345 ServiceEvent completionEvent;
346 ServiceEvent cancelledEvent;
349 completionEvent = ServiceEvent::STOPPED;
350 cancelledEvent = ServiceEvent::STOPCANCELLED;
353 completionEvent = ServiceEvent::STARTED;
354 cancelledEvent = ServiceEvent::STARTCANCELLED;
357 // Wait until service started:
358 r = rbuffer.fill_to(socknum, 2);
360 if (rbuffer[0] >= 100) {
361 int pktlen = (unsigned char) rbuffer[1];
362 fillBufferTo(&rbuffer, socknum, pktlen);
364 if (rbuffer[0] == DINIT_IP_SERVICEEVENT) {
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) {
371 cout << "Service " << describeState(do_stop) << "." << endl;
375 else if (event == cancelledEvent) {
377 cout << "Service " << describeVerb(do_stop) << " cancelled." << endl;
381 else if (! do_stop && event == ServiceEvent::FAILEDSTART) {
383 cout << "Service failed to start." << endl;
390 rbuffer.consume(pktlen);
391 r = rbuffer.fill_to(socknum, 2);
394 // Not an information packet?
395 cerr << "protocol error" << endl;
404 cerr << "protocol error (connection closed by server)" << endl;
408 catch (ReadCPException &exc) {
409 cerr << "control socket read failure or protocol error" << endl;
412 catch (std::bad_alloc &exc) {
413 cerr << "out of memory" << endl;
420 // TODO refactor shared code with above
421 static int unpinService(int socknum, const char *service_name)
426 uint16_t sname_len = strlen(service_name);
427 int bufsize = 3 + sname_len;
431 char * buf = new char[bufsize];
432 unique_ptr<char[]> ubuf(buf);
434 buf[0] = DINIT_CP_LOADSERVICE;
435 memcpy(buf + 1, &sname_len, 2);
436 memcpy(buf + 3, service_name, sname_len);
438 r = write_all(socknum, buf, bufsize);
446 // Now we expect a reply:
449 CPBuffer<1024> rbuffer;
450 wait_for_reply(rbuffer, socknum);
452 //ServiceState state;
453 //ServiceState target_state;
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));
463 else if (rbuffer[0] == DINIT_RP_NOSERVICE) {
464 cerr << "Failed to find/load service." << endl;
468 cerr << "Protocol error." << endl;
472 // Issue UNPIN command.
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));
487 wait_for_reply(rbuffer, socknum);
488 if (rbuffer[0] != DINIT_RP_ACK) {
489 cerr << "Protocol error." << endl;
495 catch (ReadCPException &exc) {
496 cerr << "control socket read failure or protocol error" << endl;
499 catch (std::bad_alloc &exc) {
500 cerr << "out of memory" << endl;
504 cout << "Service unpinned." << endl;