5be3ebc2e6970924e44497c7fc400031554bd731
[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 int main(int argc, char **argv)
54 {
55     using namespace std;
56     
57     bool do_stop = false;
58     bool show_help = argc < 2;
59     char *service_name = nullptr;
60     
61     std::string control_socket_str;
62     const char * control_socket_path = nullptr;
63     
64     bool verbose = true;
65     bool sys_dinit = false;  // communicate with system daemon
66     bool wait_for_service = true;
67     
68     int command = 0;
69     
70     constexpr int START_SERVICE = 1;
71     constexpr int STOP_SERVICE = 2;
72         
73     for (int i = 1; i < argc; i++) {
74         if (argv[i][0] == '-') {
75             if (strcmp(argv[i], "--help") == 0) {
76                 show_help = true;
77                 break;
78             }
79             else if (strcmp(argv[i], "--no-wait") == 0) {
80                 wait_for_service = false;
81             }
82             else if (strcmp(argv[i], "--quiet") == 0) {
83                 verbose = false;
84             }
85             else if (strcmp(argv[i], "--system") == 0 || strcmp(argv[i], "-s") == 0) {
86                 sys_dinit = true;
87             }
88             else {
89                 cerr << "Unrecognized command-line parameter: " << argv[i] << endl;
90                 return 1;
91             }
92         }
93         else if (command == 0) {
94             if (strcmp(argv[i], "start") == 0) {
95                 command = START_SERVICE; 
96             }
97             else if (strcmp(argv[i], "stop") == 0) {
98                 command = STOP_SERVICE;
99             }
100             else {
101                 show_help = true;
102                 break;
103             }
104         }
105         else {
106             // service name
107             service_name = argv[i];
108             // TODO support multiple services (or at least give error if multiple
109             //      services supplied)
110         }
111     }
112     
113     if (service_name == nullptr || command == 0) {
114         show_help = true;
115     }
116
117     if (show_help) {
118         cout << "dinit-start:   start a dinit service" << endl;
119         cout << "  --help           : show this help" << endl;
120         cout << "  --no-wait        : don't wait for service startup/shutdown to complete" << endl;
121         cout << "  --quiet          : suppress output (except errors)" << endl;
122         cout << "  -s, --system     : control system daemon instead of user daemon" << endl;
123         cout << "  <service-name>   : start the named service" << endl;
124         return 1;
125     }
126     
127     do_stop = (command == STOP_SERVICE);
128     
129     control_socket_path = "/dev/dinitctl";
130     
131     if (! sys_dinit) {
132         char * userhome = getenv("HOME");
133         if (userhome == nullptr) {
134             struct passwd * pwuid_p = getpwuid(getuid());
135             if (pwuid_p != nullptr) {
136                 userhome = pwuid_p->pw_dir;
137             }
138         }
139         
140         if (userhome != nullptr) {
141             control_socket_str = userhome;
142             control_socket_str += "/.dinitctl";
143             control_socket_path = control_socket_str.c_str();
144         }
145         else {
146             cerr << "Cannot locate user home directory (set HOME or check /etc/passwd file)" << endl;
147             return 1;
148         }
149     }
150     
151     int socknum = socket(AF_UNIX, SOCK_STREAM, 0);
152     if (socknum == -1) {
153         perror("socket");
154         return 1;
155     }
156
157     struct sockaddr_un * name;
158     uint sockaddr_size = offsetof(struct sockaddr_un, sun_path) + strlen(control_socket_path) + 1;
159     name = (struct sockaddr_un *) malloc(sockaddr_size);
160     if (name == nullptr) {
161         cerr << "dinit-start: out of memory" << endl;
162         return 1;
163     }
164     
165     name->sun_family = AF_UNIX;
166     strcpy(name->sun_path, control_socket_path);
167     
168     int connr = connect(socknum, (struct sockaddr *) name, sockaddr_size);
169     if (connr == -1) {
170         perror("connect");
171         return 1;
172     }
173     
174     // TODO should start by querying protocol version
175     
176     // Build buffer;
177     uint16_t sname_len = strlen(service_name);
178     int bufsize = 3 + sname_len;
179     char * buf = new char[bufsize];
180     
181     buf[0] = DINIT_CP_LOADSERVICE;
182     memcpy(buf + 1, &sname_len, 2);
183     memcpy(buf + 3, service_name, sname_len);
184     
185     int r = write(socknum, buf, bufsize);
186     // TODO make sure we write it all
187     delete [] buf;
188     if (r == -1) {
189         perror("write");
190         return 1;
191     }
192     
193     // Now we expect a reply:
194     // NOTE: should skip over information packets.
195     
196     try {
197         CPBuffer rbuffer;
198         fillBufferTo(&rbuffer, socknum, 1);
199         
200         ServiceState state;
201         ServiceState target_state;
202         handle_t handle;
203         
204         if (rbuffer[0] == DINIT_RP_SERVICERECORD) {
205             fillBufferTo(&rbuffer, socknum, 2 + sizeof(handle));
206             rbuffer.extract((char *) &handle, 2, sizeof(handle));
207             state = static_cast<ServiceState>(rbuffer[1]);
208             target_state = static_cast<ServiceState>(rbuffer[2 + sizeof(handle)]);
209             rbuffer.consume(3 + sizeof(handle));
210         }
211         else if (rbuffer[0] == DINIT_RP_NOSERVICE) {
212             cerr << "Failed to find/load service." << endl;
213             return 1;
214         }
215         else {
216             cerr << "Protocol error." << endl;
217             return 1;
218         }
219         
220         ServiceState wanted_state = do_stop ? ServiceState::STOPPED : ServiceState::STARTED;
221         int command = do_stop ? DINIT_CP_STOPSERVICE : DINIT_CP_STARTSERVICE;
222         
223         // Need to issue STOPSERVICE/STARTSERVICE
224         if (target_state != wanted_state) {
225             buf = new char[2 + sizeof(handle)];
226             buf[0] = command;
227             buf[1] = 0;  // don't pin
228             memcpy(buf + 2, &handle, sizeof(handle));
229             r = write(socknum, buf, 2 + sizeof(handle));
230             delete buf;
231         }
232         
233         if (state == wanted_state) {
234             if (verbose) {
235                 cout << "Service already " << describeState(do_stop) << "." << endl;
236             }
237             return 0; // success!
238         }
239         
240         if (! wait_for_service) {
241             return 0;
242         }
243         
244         ServiceEvent completionEvent;
245         ServiceEvent cancelledEvent;
246         
247         if (do_stop) {
248             completionEvent = ServiceEvent::STOPPED;
249             cancelledEvent = ServiceEvent::STOPCANCELLED;
250         }
251         else {
252             completionEvent = ServiceEvent::STARTED;
253             cancelledEvent = ServiceEvent::STARTCANCELLED;
254         }
255         
256         // Wait until service started:
257         r = rbuffer.fillTo(socknum, 2);
258         while (r > 0) {
259             if (rbuffer[0] >= 100) {
260                 int pktlen = (unsigned char) rbuffer[1];
261                 fillBufferTo(&rbuffer, socknum, pktlen);
262                 
263                 if (rbuffer[0] == DINIT_IP_SERVICEEVENT) {
264                     handle_t ev_handle;
265                     rbuffer.extract((char *) &ev_handle, 2, sizeof(ev_handle));
266                     ServiceEvent event = static_cast<ServiceEvent>(rbuffer[2 + sizeof(ev_handle)]);
267                     if (ev_handle == handle) {
268                         if (event == completionEvent) {
269                             if (verbose) {
270                                 cout << "Service " << describeState(do_stop) << "." << endl;
271                             }
272                             return 0;
273                         }
274                         else if (event == cancelledEvent) {
275                             if (verbose) {
276                                 cout << "Service " << describeVerb(do_stop) << " cancelled." << endl;
277                             }
278                             return 1;
279                         }
280                     }
281                 }
282             }
283             else {
284                 // Not an information packet?
285                 cerr << "protocol error" << endl;
286                 return 1;
287             }
288         }
289         
290         if (r == -1) {
291             perror("read");
292         }
293         else {
294             cerr << "protocol error (connection closed by server)" << endl;
295         }
296         return 1;
297     }
298     catch (ReadCPException &exc) {
299         cerr << "control socket read failure or protocol error" << endl;
300         return 1;
301     }
302     catch (std::bad_alloc &exc) {
303         cerr << "out of memory" << endl;
304         return 1;
305     }
306     
307     return 0;
308 }