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 stream socket (/dev/initctl, or $HOME/.dinitctl).
24 using handle_t = uint32_t;
31 ReadCPException(int err) : errcode(err) { }
36 static int issueLoadService(int socknum, const char *service_name);
37 static int checkLoadReply(int socknum, CPBuffer<1024> &rbuffer, handle_t *handle_p, ServiceState *state_p);
38 static int startStopService(int socknum, const char *service_name, Command command, bool do_pin, bool wait_for_service, bool verbose);
39 static int unpinService(int socknum, const char *service_name, bool verbose);
40 static int listServices(int socknum);
43 // Fill a circular buffer from a file descriptor, reading at least _rlength_ bytes.
44 // Throws ReadException if the requested number of bytes cannot be read, with:
45 // errcode = 0 if end of stream (remote end closed)
46 // errcode = errno if another error occurred
47 // Note that EINTR is ignored (i.e. the read will be re-tried).
48 static void fillBufferTo(CPBuffer<1024> *buf, int fd, int rlength)
51 int r = buf->fill_to(fd, rlength);
54 throw ReadCPException(errno);
58 throw ReadCPException(0);
67 static const char * describeState(bool stopped)
69 return stopped ? "stopped" : "started";
72 static const char * describeVerb(bool stop)
74 return stop ? "stop" : "start";
77 // Wait for a reply packet, skipping over any information packets
78 // that are received in the meantime.
79 static void wait_for_reply(CPBuffer<1024> &rbuffer, int fd)
81 fillBufferTo(&rbuffer, fd, 1);
83 while (rbuffer[0] >= 100) {
84 // Information packet; discard.
85 fillBufferTo(&rbuffer, fd, 1);
86 int pktlen = (unsigned char) rbuffer[1];
88 rbuffer.consume(1); // Consume one byte so we'll read one byte of the next packet
89 fillBufferTo(&rbuffer, fd, pktlen);
90 rbuffer.consume(pktlen - 1);
95 // Write *all* the requested buffer and re-try if necessary until
96 // the buffer is written or an unrecoverable error occurs.
97 static int write_all(int fd, const void *buf, size_t count)
99 const char *cbuf = static_cast<const char *>(buf);
102 int r = write(fd, cbuf, count);
104 if (errno == EINTR) continue;
126 int main(int argc, char **argv)
130 bool show_help = argc < 2;
131 char *service_name = nullptr;
133 std::string control_socket_str;
134 const char * control_socket_path = nullptr;
137 bool sys_dinit = false; // communicate with system daemon
138 bool wait_for_service = true;
141 Command command = Command::NONE;
143 for (int i = 1; i < argc; i++) {
144 if (argv[i][0] == '-') {
145 if (strcmp(argv[i], "--help") == 0) {
149 else if (strcmp(argv[i], "--no-wait") == 0) {
150 wait_for_service = false;
152 else if (strcmp(argv[i], "--quiet") == 0) {
155 else if (strcmp(argv[i], "--system") == 0 || strcmp(argv[i], "-s") == 0) {
158 else if (strcmp(argv[i], "--pin") == 0) {
165 else if (command == Command::NONE) {
166 if (strcmp(argv[i], "start") == 0) {
167 command = Command::START_SERVICE;
169 else if (strcmp(argv[i], "wake") == 0) {
170 command = Command::WAKE_SERVICE;
172 else if (strcmp(argv[i], "stop") == 0) {
173 command = Command::STOP_SERVICE;
175 else if (strcmp(argv[i], "release") == 0) {
176 command = Command::RELEASE_SERVICE;
178 else if (strcmp(argv[i], "unpin") == 0) {
179 command = Command::UNPIN_SERVICE;
181 else if (strcmp(argv[i], "list") == 0) {
182 command = Command::LIST_SERVICES;
191 if (service_name != nullptr) {
195 service_name = argv[i];
196 // TODO support multiple services
200 if (service_name != nullptr && command == Command::LIST_SERVICES) {
204 if ((service_name == nullptr && command != Command::LIST_SERVICES) || command == Command::NONE) {
209 cout << "dinitctl: control Dinit services" << endl;
211 cout << "\nUsage:" << endl;
212 cout << " dinitctl [options] start [options] <service-name> : start and activate service" << endl;
213 cout << " dinitctl [options] stop [options] <service-name> : stop service and cancel explicit activation" << endl;
214 cout << " dinitctl [options] wake [options] <service-name> : start but do not mark activated" << endl;
215 cout << " dinitctl [options] release [options] <service-name> : release activation, stop if no dependents" << endl;
216 cout << " dinitctl [options] unpin <service-name> : un-pin the service (after a previous pin)" << endl;
217 cout << " dinitctl list : list loaded services" << endl;
219 cout << "\nNote: An activated service continues running when its dependents stop." << endl;
221 cout << "\nGeneral options:" << endl;
222 cout << " -s, --system : control system daemon instead of user daemon" << endl;
223 cout << " --quiet : suppress output (except errors)" << endl;
225 cout << "\nCommand options:" << endl;
226 cout << " --help : show this help" << endl;
227 cout << " --no-wait : don't wait for service startup/shutdown to complete" << endl;
228 cout << " --pin : pin the service in the requested (started/stopped) state" << endl;
232 signal(SIGPIPE, SIG_IGN);
234 control_socket_path = "/dev/dinitctl";
236 // Locate control socket
238 char * userhome = getenv("HOME");
239 if (userhome == nullptr) {
240 struct passwd * pwuid_p = getpwuid(getuid());
241 if (pwuid_p != nullptr) {
242 userhome = pwuid_p->pw_dir;
246 if (userhome != nullptr) {
247 control_socket_str = userhome;
248 control_socket_str += "/.dinitctl";
249 control_socket_path = control_socket_str.c_str();
252 cerr << "Cannot locate user home directory (set HOME or check /etc/passwd file)" << endl;
257 int socknum = socket(AF_UNIX, SOCK_STREAM, 0);
259 perror("dinitctl: socket");
263 struct sockaddr_un * name;
264 uint sockaddr_size = offsetof(struct sockaddr_un, sun_path) + strlen(control_socket_path) + 1;
265 name = (struct sockaddr_un *) malloc(sockaddr_size);
266 if (name == nullptr) {
267 cerr << "dinitctl: Out of memory" << endl;
271 name->sun_family = AF_UNIX;
272 strcpy(name->sun_path, control_socket_path);
274 int connr = connect(socknum, (struct sockaddr *) name, sockaddr_size);
276 perror("dinitctl: connect");
280 // TODO should start by querying protocol version
282 if (command == Command::UNPIN_SERVICE) {
283 return unpinService(socknum, service_name, verbose);
285 else if (command == Command::LIST_SERVICES) {
286 return listServices(socknum);
289 return startStopService(socknum, service_name, command, do_pin, wait_for_service, verbose);
292 // Start/stop a service
293 static int startStopService(int socknum, const char *service_name, Command command, bool do_pin, bool wait_for_service, bool verbose)
297 bool do_stop = (command == Command::STOP_SERVICE || command == Command::RELEASE_SERVICE);
299 if (issueLoadService(socknum, service_name)) {
303 // Now we expect a reply:
306 CPBuffer<1024> rbuffer;
307 wait_for_reply(rbuffer, socknum);
310 //ServiceState target_state;
313 if (checkLoadReply(socknum, rbuffer, &handle, &state) != 0) {
317 ServiceState wanted_state = do_stop ? ServiceState::STOPPED : ServiceState::STARTED;
320 case Command::STOP_SERVICE:
321 pcommand = DINIT_CP_STOPSERVICE;
323 case Command::RELEASE_SERVICE:
324 pcommand = DINIT_CP_RELEASESERVICE;
326 case Command::START_SERVICE:
327 pcommand = DINIT_CP_STARTSERVICE;
329 case Command::WAKE_SERVICE:
330 pcommand = DINIT_CP_WAKESERVICE;
335 // Need to issue STOPSERVICE/STARTSERVICE
336 // We'll do this regardless of the current service state / target state, since issuing
337 // start/stop also sets or clears the "explicitly started" flag on the service.
342 auto buf = new char[2 + sizeof(handle)];
343 unique_ptr<char[]> ubuf(buf);
346 buf[1] = do_pin ? 1 : 0;
347 memcpy(buf + 2, &handle, sizeof(handle));
348 r = write_all(socknum, buf, 2 + sizeof(handle));
352 perror("dinitctl: write");
356 wait_for_reply(rbuffer, socknum);
357 if (rbuffer[0] == DINIT_RP_ALREADYSS) {
358 bool already = (state == wanted_state);
360 cout << "Service " << (already ? "(already) " : "") << describeState(do_stop) << "." << endl;
362 return 0; // success!
364 if (rbuffer[0] != DINIT_RP_ACK) {
365 cerr << "dinitctl: Protocol error." << endl;
371 if (! wait_for_service) {
373 cout << "Issued " << describeVerb(do_stop) << " command successfully." << endl;
378 ServiceEvent completionEvent;
379 ServiceEvent cancelledEvent;
382 completionEvent = ServiceEvent::STOPPED;
383 cancelledEvent = ServiceEvent::STOPCANCELLED;
386 completionEvent = ServiceEvent::STARTED;
387 cancelledEvent = ServiceEvent::STARTCANCELLED;
390 // Wait until service started:
391 int r = rbuffer.fill_to(socknum, 2);
393 if (rbuffer[0] >= 100) {
394 int pktlen = (unsigned char) rbuffer[1];
395 fillBufferTo(&rbuffer, socknum, pktlen);
397 if (rbuffer[0] == DINIT_IP_SERVICEEVENT) {
399 rbuffer.extract((char *) &ev_handle, 2, sizeof(ev_handle));
400 ServiceEvent event = static_cast<ServiceEvent>(rbuffer[2 + sizeof(ev_handle)]);
401 if (ev_handle == handle) {
402 if (event == completionEvent) {
404 cout << "Service " << describeState(do_stop) << "." << endl;
408 else if (event == cancelledEvent) {
410 cout << "Service " << describeVerb(do_stop) << " cancelled." << endl;
414 else if (! do_stop && event == ServiceEvent::FAILEDSTART) {
416 cout << "Service failed to start." << endl;
423 rbuffer.consume(pktlen);
424 r = rbuffer.fill_to(socknum, 2);
427 // Not an information packet?
428 cerr << "dinitctl: protocol error" << endl;
434 perror("dinitctl: read");
437 cerr << "protocol error (connection closed by server)" << endl;
441 catch (ReadCPException &exc) {
442 cerr << "dinitctl: control socket read failure or protocol error" << endl;
445 catch (std::bad_alloc &exc) {
446 cerr << "dinitctl: out of memory" << endl;
453 // Issue a "load service" command (DINIT_CP_LOADSERVICE), without waiting for
454 // a response. Returns 1 on failure (with error logged), 0 on success.
455 static int issueLoadService(int socknum, const char *service_name)
460 uint16_t sname_len = strlen(service_name);
461 int bufsize = 3 + sname_len;
465 // TODO: new: catch exception
466 unique_ptr<char[]> ubuf(new char[bufsize]);
467 auto buf = ubuf.get();
469 buf[0] = DINIT_CP_LOADSERVICE;
470 memcpy(buf + 1, &sname_len, 2);
471 memcpy(buf + 3, service_name, sname_len);
473 r = write_all(socknum, buf, bufsize);
477 perror("dinitctl: write");
484 // Check that a "load service" reply was received, and that the requested service was found.
485 static int checkLoadReply(int socknum, CPBuffer<1024> &rbuffer, handle_t *handle_p, ServiceState *state_p)
489 if (rbuffer[0] == DINIT_RP_SERVICERECORD) {
490 fillBufferTo(&rbuffer, socknum, 2 + sizeof(*handle_p));
491 rbuffer.extract((char *) handle_p, 2, sizeof(*handle_p));
492 if (state_p) *state_p = static_cast<ServiceState>(rbuffer[1]);
493 //target_state = static_cast<ServiceState>(rbuffer[2 + sizeof(handle)]);
494 rbuffer.consume(3 + sizeof(*handle_p));
497 else if (rbuffer[0] == DINIT_RP_NOSERVICE) {
498 cerr << "dinitctl: Failed to find/load service." << endl;
502 cerr << "dinitctl: Protocol error." << endl;
507 static int unpinService(int socknum, const char *service_name, bool verbose)
512 if (issueLoadService(socknum, service_name) == 1) {
516 // Now we expect a reply:
519 CPBuffer<1024> rbuffer;
520 wait_for_reply(rbuffer, socknum);
524 if (checkLoadReply(socknum, rbuffer, &handle, nullptr) != 0) {
528 // Issue UNPIN command.
533 char *buf = new char[1 + sizeof(handle)];
534 unique_ptr<char[]> ubuf(buf);
535 buf[0] = DINIT_CP_UNPINSERVICE;
536 memcpy(buf + 1, &handle, sizeof(handle));
537 r = write_all(socknum, buf, 2 + sizeof(handle));
541 perror("dinitctl: write");
545 wait_for_reply(rbuffer, socknum);
546 if (rbuffer[0] != DINIT_RP_ACK) {
547 cerr << "dinitctl: Protocol error." << endl;
553 catch (ReadCPException &exc) {
554 cerr << "dinitctl: Control socket read failure or protocol error" << endl;
557 catch (std::bad_alloc &exc) {
558 cerr << "dinitctl: Out of memory" << endl;
563 cout << "Service unpinned." << endl;
568 static int listServices(int socknum)
573 char cmdbuf[] = { (char)DINIT_CP_LISTSERVICES };
574 int r = write_all(socknum, cmdbuf, 1);
577 perror("dinitctl: write");
581 CPBuffer<1024> rbuffer;
582 wait_for_reply(rbuffer, socknum);
583 while (rbuffer[0] == DINIT_RP_SVCINFO) {
584 fillBufferTo(&rbuffer, socknum, 8);
585 int nameLen = rbuffer[1];
586 ServiceState current = static_cast<ServiceState>(rbuffer[2]);
587 ServiceState target = static_cast<ServiceState>(rbuffer[3]);
589 fillBufferTo(&rbuffer, socknum, nameLen + 8);
591 char *name_ptr = rbuffer.get_ptr(8);
592 int clength = std::min(rbuffer.get_contiguous_length(name_ptr), nameLen);
594 string name = string(name_ptr, clength);
595 name.append(rbuffer.get_buf_base(), nameLen - clength);
599 cout << (target == ServiceState::STARTED ? "{" : " ");
600 cout << (current == ServiceState::STARTED ? "+" : " ");
601 cout << (target == ServiceState::STARTED ? "}" : " ");
603 if (current == ServiceState::STARTING) {
606 else if (current == ServiceState::STOPPING) {
613 cout << (target == ServiceState::STOPPED ? "{" : " ");
614 cout << (current == ServiceState::STOPPED ? "-" : " ");
615 cout << (target == ServiceState::STOPPED ? "}" : " ");
617 cout << "] " << name << endl;
619 rbuffer.consume(8 + nameLen);
620 wait_for_reply(rbuffer, socknum);
623 if (rbuffer[0] != DINIT_RP_LISTDONE) {
624 cerr << "dinitctl: Control socket protocol error" << endl;
628 catch (ReadCPException &exc) {
629 cerr << "dinitctl: Control socket read failure or protocol error" << endl;
632 catch (std::bad_alloc &exc) {
633 cerr << "dinitctl: Out of memory" << endl;