6 #include <system_error>
9 #include <sys/socket.h>
14 #include "control-cmds.h"
15 #include "service-constants.h"
18 // dinitctl: utility to control the Dinit daemon, including starting and stopping of services.
20 // This utility communicates with the dinit daemon via a unix socket (/dev/initctl).
22 using handle_t = uint32_t;
29 ReadCPException(int err) : errcode(err) { }
32 static void fillBufferTo(CPBuffer *buf, int fd, int rlength)
34 int r = buf->fillTo(fd, rlength);
36 throw ReadCPException(errno);
39 throw ReadCPException(0);
43 static const char * describeState(bool stopped)
45 return stopped ? "stopped" : "started";
48 static const char * describeVerb(bool stop)
50 return stop ? "stop" : "start";
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)
57 fillBufferTo(&rbuffer, fd, 1);
59 while (rbuffer[0] >= 100) {
60 // Information packet; discard.
61 fillBufferTo(&rbuffer, fd, 1);
62 int pktlen = (unsigned char) rbuffer[1];
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);
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)
75 const char *cbuf = static_cast<const char *>(buf);
78 int r = write(fd, cbuf, count);
80 if (errno == EINTR) continue;
91 int main(int argc, char **argv)
96 bool show_help = argc < 2;
97 char *service_name = nullptr;
99 std::string control_socket_str;
100 const char * control_socket_path = nullptr;
103 bool sys_dinit = false; // communicate with system daemon
104 bool wait_for_service = true;
108 constexpr int START_SERVICE = 1;
109 constexpr int STOP_SERVICE = 2;
111 for (int i = 1; i < argc; i++) {
112 if (argv[i][0] == '-') {
113 if (strcmp(argv[i], "--help") == 0) {
117 else if (strcmp(argv[i], "--no-wait") == 0) {
118 wait_for_service = false;
120 else if (strcmp(argv[i], "--quiet") == 0) {
123 else if (strcmp(argv[i], "--system") == 0 || strcmp(argv[i], "-s") == 0) {
127 cerr << "Unrecognized command-line parameter: " << argv[i] << endl;
131 else if (command == 0) {
132 if (strcmp(argv[i], "start") == 0) {
133 command = START_SERVICE;
135 else if (strcmp(argv[i], "stop") == 0) {
136 command = STOP_SERVICE;
145 service_name = argv[i];
146 // TODO support multiple services (or at least give error if multiple
147 // services supplied)
151 if (service_name == nullptr || command == 0) {
156 cout << "dinit-start: start a dinit service" << endl;
157 cout << " --help : show this help" << endl;
158 cout << " --no-wait : don't wait for service startup/shutdown to complete" << endl;
159 cout << " --quiet : suppress output (except errors)" << endl;
160 cout << " -s, --system : control system daemon instead of user daemon" << endl;
161 cout << " <service-name> : start the named service" << endl;
165 do_stop = (command == STOP_SERVICE);
167 control_socket_path = "/dev/dinitctl";
170 char * userhome = getenv("HOME");
171 if (userhome == nullptr) {
172 struct passwd * pwuid_p = getpwuid(getuid());
173 if (pwuid_p != nullptr) {
174 userhome = pwuid_p->pw_dir;
178 if (userhome != nullptr) {
179 control_socket_str = userhome;
180 control_socket_str += "/.dinitctl";
181 control_socket_path = control_socket_str.c_str();
184 cerr << "Cannot locate user home directory (set HOME or check /etc/passwd file)" << endl;
189 int socknum = socket(AF_UNIX, SOCK_STREAM, 0);
195 struct sockaddr_un * name;
196 uint sockaddr_size = offsetof(struct sockaddr_un, sun_path) + strlen(control_socket_path) + 1;
197 name = (struct sockaddr_un *) malloc(sockaddr_size);
198 if (name == nullptr) {
199 cerr << "dinit-start: out of memory" << endl;
203 name->sun_family = AF_UNIX;
204 strcpy(name->sun_path, control_socket_path);
206 int connr = connect(socknum, (struct sockaddr *) name, sockaddr_size);
212 // TODO should start by querying protocol version
215 uint16_t sname_len = strlen(service_name);
216 int bufsize = 3 + sname_len;
217 char * buf = new char[bufsize];
219 buf[0] = DINIT_CP_LOADSERVICE;
220 memcpy(buf + 1, &sname_len, 2);
221 memcpy(buf + 3, service_name, sname_len);
223 int r = write_all(socknum, buf, bufsize);
230 // Now we expect a reply:
234 wait_for_reply(rbuffer, socknum);
236 //ServiceState state;
237 //ServiceState target_state;
240 if (rbuffer[0] == DINIT_RP_SERVICERECORD) {
241 fillBufferTo(&rbuffer, socknum, 2 + sizeof(handle));
242 rbuffer.extract((char *) &handle, 2, sizeof(handle));
243 //state = static_cast<ServiceState>(rbuffer[1]);
244 //target_state = static_cast<ServiceState>(rbuffer[2 + sizeof(handle)]);
245 rbuffer.consume(3 + sizeof(handle));
247 else if (rbuffer[0] == DINIT_RP_NOSERVICE) {
248 cerr << "Failed to find/load service." << endl;
252 cerr << "Protocol error." << endl;
256 // ServiceState wanted_state = do_stop ? ServiceState::STOPPED : ServiceState::STARTED;
257 int command = do_stop ? DINIT_CP_STOPSERVICE : DINIT_CP_STARTSERVICE;
259 // Need to issue STOPSERVICE/STARTSERVICE
260 // We'll do this regardless of the current service state / target state, since issuing
261 // start/stop also sets or clears the "explicitly started" flag on the service.
262 //if (target_state != wanted_state) {
264 buf = new char[2 + sizeof(handle)];
266 buf[1] = 0; // don't pin
267 memcpy(buf + 2, &handle, sizeof(handle));
268 r = write_all(socknum, buf, 2 + sizeof(handle));
276 wait_for_reply(rbuffer, socknum);
277 if (rbuffer[0] == DINIT_RP_ALREADYSS) {
279 cout << "Service already " << describeState(do_stop) << "." << endl;
281 return 0; // success!
283 if (rbuffer[0] != DINIT_RP_ACK) {
284 cerr << "Protocol error." << endl;
291 if (state == wanted_state) {
293 cout << "Service already " << describeState(do_stop) << "." << endl;
295 return 0; // success!
299 if (! wait_for_service) {
301 cout << "Issued " << describeVerb(do_stop) << " command successfully." << endl;
306 ServiceEvent completionEvent;
307 ServiceEvent cancelledEvent;
310 completionEvent = ServiceEvent::STOPPED;
311 cancelledEvent = ServiceEvent::STOPCANCELLED;
314 completionEvent = ServiceEvent::STARTED;
315 cancelledEvent = ServiceEvent::STARTCANCELLED;
318 // Wait until service started:
319 r = rbuffer.fillTo(socknum, 2);
321 if (rbuffer[0] >= 100) {
322 int pktlen = (unsigned char) rbuffer[1];
323 fillBufferTo(&rbuffer, socknum, pktlen);
325 if (rbuffer[0] == DINIT_IP_SERVICEEVENT) {
327 rbuffer.extract((char *) &ev_handle, 2, sizeof(ev_handle));
328 ServiceEvent event = static_cast<ServiceEvent>(rbuffer[2 + sizeof(ev_handle)]);
329 if (ev_handle == handle) {
330 if (event == completionEvent) {
332 cout << "Service " << describeState(do_stop) << "." << endl;
336 else if (event == cancelledEvent) {
338 cout << "Service " << describeVerb(do_stop) << " cancelled." << endl;
342 else if (! do_stop && event == ServiceEvent::FAILEDSTART) {
344 cout << "Service failed to start." << endl;
351 rbuffer.consume(pktlen);
352 r = rbuffer.fillTo(socknum, 2);
355 // Not an information packet?
356 cerr << "protocol error" << endl;
365 cerr << "protocol error (connection closed by server)" << endl;
369 catch (ReadCPException &exc) {
370 cerr << "control socket read failure or protocol error" << endl;
373 catch (std::bad_alloc &exc) {
374 cerr << "out of memory" << endl;