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) { }
34 static int issueLoadService(int socknum, const char *service_name);
35 static int checkLoadReply(int socknum, CPBuffer<1024> &rbuffer, handle_t *handle_p, ServiceState *state_p);
36 static int startStopService(int socknum, const char *service_name, int command, bool do_pin, bool wait_for_service, bool verbose);
37 static int unpinService(int socknum, const char *service_name, bool verbose);
40 // Fill a circular buffer from a file descriptor, reading at least _rlength_ bytes.
41 // Throws ReadException if the requested number of bytes cannot be read, with:
42 // errcode = 0 if end of stream (remote end closed)
43 // errcode = errno if another error occurred
44 // Note that EINTR is ignored (i.e. the read will be re-tried).
45 static void fillBufferTo(CPBuffer<1024> *buf, int fd, int rlength)
48 int r = buf->fill_to(fd, rlength);
51 throw ReadCPException(errno);
55 throw ReadCPException(0);
64 static const char * describeState(bool stopped)
66 return stopped ? "stopped" : "started";
69 static const char * describeVerb(bool stop)
71 return stop ? "stop" : "start";
74 // Wait for a reply packet, skipping over any information packets
75 // that are received in the meantime.
76 static void wait_for_reply(CPBuffer<1024> &rbuffer, int fd)
78 fillBufferTo(&rbuffer, fd, 1);
80 while (rbuffer[0] >= 100) {
81 // Information packet; discard.
82 fillBufferTo(&rbuffer, fd, 1);
83 int pktlen = (unsigned char) rbuffer[1];
85 rbuffer.consume(1); // Consume one byte so we'll read one byte of the next packet
86 fillBufferTo(&rbuffer, fd, pktlen);
87 rbuffer.consume(pktlen - 1);
92 // Write *all* the requested buffer and re-try if necessary until
93 // the buffer is written or an unrecoverable error occurs.
94 static int write_all(int fd, const void *buf, size_t count)
96 const char *cbuf = static_cast<const char *>(buf);
99 int r = write(fd, cbuf, count);
101 if (errno == EINTR) continue;
112 static const int START_SERVICE = 1;
113 static const int STOP_SERVICE = 2;
114 static const int UNPIN_SERVICE = 3;
118 int main(int argc, char **argv)
122 bool show_help = argc < 2;
123 char *service_name = nullptr;
125 std::string control_socket_str;
126 const char * control_socket_path = nullptr;
129 bool sys_dinit = false; // communicate with system daemon
130 bool wait_for_service = true;
135 for (int i = 1; i < argc; i++) {
136 if (argv[i][0] == '-') {
137 if (strcmp(argv[i], "--help") == 0) {
141 else if (strcmp(argv[i], "--no-wait") == 0) {
142 wait_for_service = false;
144 else if (strcmp(argv[i], "--quiet") == 0) {
147 else if (strcmp(argv[i], "--system") == 0 || strcmp(argv[i], "-s") == 0) {
150 else if (strcmp(argv[i], "--pin") == 0) {
157 else if (command == 0) {
158 if (strcmp(argv[i], "start") == 0) {
159 command = START_SERVICE;
161 else if (strcmp(argv[i], "stop") == 0) {
162 command = STOP_SERVICE;
164 else if (strcmp(argv[i], "unpin") == 0) {
165 command = UNPIN_SERVICE;
174 service_name = argv[i];
175 // TODO support multiple services (or at least give error if multiple
176 // services supplied)
180 if (service_name == nullptr || command == 0) {
185 cout << "dinitctl: control Dinit services" << endl;
187 cout << "\nUsage:" << endl;
188 cout << " dinitctl [options] start [options] <service-name> : start and activate service" << endl;
189 cout << " dinitctl [options] stop [options] <service-name> : stop service and cancel explicit activation" << endl;
190 cout << " dinitctl [options] unpin <service-name> : un-pin the service (after a previous pin)" << endl;
192 // cout << " dinitctl [options] wake <service-name> : start but don't activate service" << endl;
194 cout << "\nNote: An activated service keeps its dependencies running when possible." << endl;
196 cout << "\nGeneral options:" << endl;
197 cout << " -s, --system : control system daemon instead of user daemon" << endl;
198 cout << " --quiet : suppress output (except errors)" << endl;
200 cout << "\nCommand options:" << endl;
201 cout << " --help : show this help" << endl;
202 cout << " --no-wait : don't wait for service startup/shutdown to complete" << endl;
203 cout << " --pin : pin the service in the requested (started/stopped) state" << endl;
207 signal(SIGPIPE, SIG_IGN);
209 control_socket_path = "/dev/dinitctl";
211 // Locate control socket
213 char * userhome = getenv("HOME");
214 if (userhome == nullptr) {
215 struct passwd * pwuid_p = getpwuid(getuid());
216 if (pwuid_p != nullptr) {
217 userhome = pwuid_p->pw_dir;
221 if (userhome != nullptr) {
222 control_socket_str = userhome;
223 control_socket_str += "/.dinitctl";
224 control_socket_path = control_socket_str.c_str();
227 cerr << "Cannot locate user home directory (set HOME or check /etc/passwd file)" << endl;
232 int socknum = socket(AF_UNIX, SOCK_STREAM, 0);
234 perror("dinitctl: socket");
238 struct sockaddr_un * name;
239 uint sockaddr_size = offsetof(struct sockaddr_un, sun_path) + strlen(control_socket_path) + 1;
240 name = (struct sockaddr_un *) malloc(sockaddr_size);
241 if (name == nullptr) {
242 cerr << "dinitctl: Out of memory" << endl;
246 name->sun_family = AF_UNIX;
247 strcpy(name->sun_path, control_socket_path);
249 int connr = connect(socknum, (struct sockaddr *) name, sockaddr_size);
251 perror("dinitctl: connect");
255 // TODO should start by querying protocol version
257 if (command == UNPIN_SERVICE) {
258 return unpinService(socknum, service_name, verbose);
261 return startStopService(socknum, service_name, command, do_pin, wait_for_service, verbose);
264 // Start/stop a service
265 static int startStopService(int socknum, const char *service_name, int command, bool do_pin, bool wait_for_service, bool verbose)
269 bool do_stop = (command == STOP_SERVICE);
271 if (issueLoadService(socknum, service_name)) {
275 // Now we expect a reply:
278 CPBuffer<1024> rbuffer;
279 wait_for_reply(rbuffer, socknum);
282 //ServiceState target_state;
285 if (checkLoadReply(socknum, rbuffer, &handle, &state) != 0) {
289 ServiceState wanted_state = do_stop ? ServiceState::STOPPED : ServiceState::STARTED;
290 int command = do_stop ? DINIT_CP_STOPSERVICE : DINIT_CP_STARTSERVICE;
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) {
300 auto buf = new char[2 + sizeof(handle)];
301 unique_ptr<char[]> ubuf(buf);
304 buf[1] = do_pin ? 1 : 0;
305 memcpy(buf + 2, &handle, sizeof(handle));
306 r = write_all(socknum, buf, 2 + sizeof(handle));
310 perror("dinitctl: write");
314 wait_for_reply(rbuffer, socknum);
315 if (rbuffer[0] == DINIT_RP_ALREADYSS) {
316 bool already = (state == wanted_state);
318 cout << "Service " << (already ? "(already) " : "") << describeState(do_stop) << "." << endl;
320 return 0; // success!
322 if (rbuffer[0] != DINIT_RP_ACK) {
323 cerr << "dinitctl: Protocol error." << endl;
329 if (! wait_for_service) {
331 cout << "Issued " << describeVerb(do_stop) << " command successfully." << endl;
336 ServiceEvent completionEvent;
337 ServiceEvent cancelledEvent;
340 completionEvent = ServiceEvent::STOPPED;
341 cancelledEvent = ServiceEvent::STOPCANCELLED;
344 completionEvent = ServiceEvent::STARTED;
345 cancelledEvent = ServiceEvent::STARTCANCELLED;
348 // Wait until service started:
349 int r = rbuffer.fill_to(socknum, 2);
351 if (rbuffer[0] >= 100) {
352 int pktlen = (unsigned char) rbuffer[1];
353 fillBufferTo(&rbuffer, socknum, pktlen);
355 if (rbuffer[0] == DINIT_IP_SERVICEEVENT) {
357 rbuffer.extract((char *) &ev_handle, 2, sizeof(ev_handle));
358 ServiceEvent event = static_cast<ServiceEvent>(rbuffer[2 + sizeof(ev_handle)]);
359 if (ev_handle == handle) {
360 if (event == completionEvent) {
362 cout << "Service " << describeState(do_stop) << "." << endl;
366 else if (event == cancelledEvent) {
368 cout << "Service " << describeVerb(do_stop) << " cancelled." << endl;
372 else if (! do_stop && event == ServiceEvent::FAILEDSTART) {
374 cout << "Service failed to start." << endl;
381 rbuffer.consume(pktlen);
382 r = rbuffer.fill_to(socknum, 2);
385 // Not an information packet?
386 cerr << "dinitctl: protocol error" << endl;
392 perror("dinitctl: read");
395 cerr << "protocol error (connection closed by server)" << endl;
399 catch (ReadCPException &exc) {
400 cerr << "dinitctl: control socket read failure or protocol error" << endl;
403 catch (std::bad_alloc &exc) {
404 cerr << "dinitctl: out of memory" << endl;
411 // Issue a "load service" command (DINIT_CP_LOADSERVICE), without waiting for
412 // a response. Returns 1 on failure (with error logged), 0 on success.
413 static int issueLoadService(int socknum, const char *service_name)
418 uint16_t sname_len = strlen(service_name);
419 int bufsize = 3 + sname_len;
423 // TODO: new: catch exception
424 unique_ptr<char[]> ubuf(new char[bufsize]);
425 auto buf = ubuf.get();
427 buf[0] = DINIT_CP_LOADSERVICE;
428 memcpy(buf + 1, &sname_len, 2);
429 memcpy(buf + 3, service_name, sname_len);
431 r = write_all(socknum, buf, bufsize);
435 perror("dinitctl: write");
442 // Check that a "load service" reply was received, and that the requested service was found.
443 static int checkLoadReply(int socknum, CPBuffer<1024> &rbuffer, handle_t *handle_p, ServiceState *state_p)
447 if (rbuffer[0] == DINIT_RP_SERVICERECORD) {
448 fillBufferTo(&rbuffer, socknum, 2 + sizeof(*handle_p));
449 rbuffer.extract((char *) handle_p, 2, sizeof(*handle_p));
450 if (state_p) *state_p = static_cast<ServiceState>(rbuffer[1]);
451 //target_state = static_cast<ServiceState>(rbuffer[2 + sizeof(handle)]);
452 rbuffer.consume(3 + sizeof(*handle_p));
455 else if (rbuffer[0] == DINIT_RP_NOSERVICE) {
456 cerr << "dinitctl: Failed to find/load service." << endl;
460 cerr << "dinitctl: Protocol error." << endl;
465 static int unpinService(int socknum, const char *service_name, bool verbose)
470 if (issueLoadService(socknum, service_name) == 1) {
474 // Now we expect a reply:
477 CPBuffer<1024> rbuffer;
478 wait_for_reply(rbuffer, socknum);
482 if (checkLoadReply(socknum, rbuffer, &handle, nullptr) != 0) {
486 // Issue UNPIN command.
491 char *buf = new char[1 + sizeof(handle)];
492 unique_ptr<char[]> ubuf(buf);
493 buf[0] = DINIT_CP_UNPINSERVICE;
494 memcpy(buf + 1, &handle, sizeof(handle));
495 r = write_all(socknum, buf, 2 + sizeof(handle));
499 perror("dinitctl: write");
503 wait_for_reply(rbuffer, socknum);
504 if (rbuffer[0] != DINIT_RP_ACK) {
505 cerr << "dinitctl: Protocol error." << endl;
511 catch (ReadCPException &exc) {
512 cerr << "dinitctl: Control socket read failure or protocol error" << endl;
515 catch (std::bad_alloc &exc) {
516 cerr << "dinitctl: Out of memory" << endl;
521 cout << "Service unpinned." << endl;