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