937269ccc6011322631e6ce3866f202ed73b8ced
[oweals/dinit.git] / src / control.cc
1 #include "control.h"
2 #include "service.h"
3
4 void ControlConn::processPacket()
5 {
6     using std::string;
7     
8     // Note that where we call queuePacket, we must generally check the return value. If it
9     // returns false it has either deleted the connection or marked it for deletion; we
10     // shouldn't touch instance members after that point.
11
12     int pktType = rbuf[0];
13     if (pktType == DINIT_CP_QUERYVERSION) {
14         // Responds with:
15         // DINIT_RP_CVERSION, (2 byte) minimum compatible version, (2 byte) maximum compatible version
16         char replyBuf[] = { DINIT_RP_CPVERSION, 0, 0, 0, 0 };
17         if (! queuePacket(replyBuf, 1)) return;
18         rbuf.consume(1);
19         return;
20     }
21     if (pktType == DINIT_CP_FINDSERVICE || pktType == DINIT_CP_LOADSERVICE) {
22         processFindLoad(pktType);
23         return;
24     }
25     if (pktType == DINIT_CP_STARTSERVICE || pktType == DINIT_CP_STOPSERVICE
26             || pktType == DINIT_CP_WAKESERVICE || pktType == DINIT_CP_RELEASESERVICE) {
27         processStartStop(pktType);
28         return;
29     }
30     if (pktType == DINIT_CP_UNPINSERVICE) {
31         processUnpinService();
32         return;
33     }
34     if (pktType == DINIT_CP_SHUTDOWN) {
35         // Shutdown/reboot
36         if (rbuf.get_length() < 2) {
37             chklen = 2;
38             return;
39         }
40         
41         auto sd_type = static_cast<ShutdownType>(rbuf[1]);
42         
43         service_set->stop_all_services(sd_type);
44         char ackBuf[] = { DINIT_RP_ACK };
45         if (! queuePacket(ackBuf, 1)) return;
46         
47         // Clear the packet from the buffer
48         rbuf.consume(2);
49         chklen = 0;
50         return;
51     }
52     else {
53         // Unrecognized: give error response
54         char outbuf[] = { DINIT_RP_BADREQ };
55         if (! queuePacket(outbuf, 1)) return;
56         bad_conn_close = true;
57         iob.setWatchFlags(out_events);
58     }
59     return;
60 }
61
62 void ControlConn::processFindLoad(int pktType)
63 {
64     using std::string;
65     
66     constexpr int pkt_size = 4;
67     
68     if (rbuf.get_length() < pkt_size) {
69         chklen = pkt_size;
70         return;
71     }
72     
73     uint16_t svcSize;
74     rbuf.extract((char *)&svcSize, 1, 2);
75     chklen = svcSize + 3; // packet type + (2 byte) length + service name
76     if (svcSize <= 0 || chklen > 1024) {
77         // Queue error response / mark connection bad
78         char badreqRep[] = { DINIT_RP_BADREQ };
79         if (! queuePacket(badreqRep, 1)) return;
80         bad_conn_close = true;
81         iob.setWatchFlags(out_events);
82         return;
83     }
84     
85     if (rbuf.get_length() < chklen) {
86         // packet not complete yet; read more
87         return;
88     }
89     
90     ServiceRecord * record = nullptr;
91     
92     string serviceName = rbuf.extract_string(3, svcSize);
93     
94     if (pktType == DINIT_CP_LOADSERVICE) {
95         // LOADSERVICE
96         try {
97             record = service_set->loadService(serviceName);
98         }
99         catch (ServiceLoadExc &slexc) {
100             log(LogLevel::ERROR, "Could not load service ", slexc.serviceName, ": ", slexc.excDescription);
101         }
102     }
103     else {
104         // FINDSERVICE
105         record = service_set->findService(serviceName.c_str());
106     }
107     
108     if (record != nullptr) {
109         // Allocate a service handle
110         handle_t handle = allocateServiceHandle(record);
111         std::vector<char> rp_buf;
112         rp_buf.reserve(7);
113         rp_buf.push_back(DINIT_RP_SERVICERECORD);
114         rp_buf.push_back(static_cast<char>(record->getState()));
115         for (int i = 0; i < (int) sizeof(handle); i++) {
116             rp_buf.push_back(*(((char *) &handle) + i));
117         }
118         rp_buf.push_back(static_cast<char>(record->getTargetState()));
119         if (! queuePacket(std::move(rp_buf))) return;
120     }
121     else {
122         std::vector<char> rp_buf = { DINIT_RP_NOSERVICE };
123         if (! queuePacket(std::move(rp_buf))) return;
124     }
125     
126     // Clear the packet from the buffer
127     rbuf.consume(chklen);
128     chklen = 0;
129     return;
130 }
131
132 void ControlConn::processStartStop(int pktType)
133 {
134     using std::string;
135     
136     constexpr int pkt_size = 2 + sizeof(handle_t);
137     
138     if (rbuf.get_length() < pkt_size) {
139         chklen = pkt_size;
140         return;
141     }
142     
143     // 1 byte: packet type
144     // 1 byte: pin in requested state (0 = no pin, 1 = pin)
145     // 4 bytes: service handle
146     
147     bool do_pin = (rbuf[1] == 1);
148     handle_t handle;
149     rbuf.extract((char *) &handle, 2, sizeof(handle));
150     
151     ServiceRecord *service = findServiceForKey(handle);
152     if (service == nullptr) {
153         // Service handle is bad
154         char badreqRep[] = { DINIT_RP_BADREQ };
155         if (! queuePacket(badreqRep, 1)) return;
156         bad_conn_close = true;
157         iob.setWatchFlags(out_events);
158         return;
159     }
160     else {
161         bool already_there = false;
162         switch (pktType) {
163         case DINIT_CP_STARTSERVICE:
164             if (do_pin) service->pinStart();
165             service->start();
166             already_there = service->getState() == ServiceState::STARTED;
167             break;
168         case DINIT_CP_STOPSERVICE:
169             if (do_pin) service->pinStop();
170             service->stop();
171             already_there = service->getState() == ServiceState::STOPPED;
172             break;
173         case DINIT_CP_WAKESERVICE:
174             // re-start a stopped service.
175             if (do_pin) service->pinStart();
176             service->start(false);
177             already_there = service->getState() == ServiceState::STARTED;
178             break;
179         default: /* DINIT_CP_RELEASESERVICE */
180             // remove explicit start from a service, without necessarily stopping it.
181             // TODO.
182             break;
183         }
184         
185         char ack_buf[] = { (char)(already_there ? DINIT_RP_ALREADYSS : DINIT_RP_ACK) };
186         
187         if (! queuePacket(ack_buf, 1)) return;
188     }
189     
190     // Clear the packet from the buffer
191     rbuf.consume(pkt_size);
192     chklen = 0;
193     return;
194 }
195
196 void ControlConn::processUnpinService()
197 {
198     using std::string;
199     
200     constexpr int pkt_size = 1 + sizeof(handle_t);
201     
202     if (rbuf.get_length() < pkt_size) {
203         chklen = pkt_size;
204         return;
205     }
206     
207     // 1 byte: packet type
208     // 4 bytes: service handle
209     
210     handle_t handle;
211     rbuf.extract((char *) &handle, 1, sizeof(handle));
212     
213     ServiceRecord *service = findServiceForKey(handle);
214     if (service == nullptr) {
215         // Service handle is bad
216         char badreqRep[] = { DINIT_RP_BADREQ };
217         if (! queuePacket(badreqRep, 1)) return;
218         bad_conn_close = true;
219         iob.setWatchFlags(out_events);
220         return;
221     }
222     else {
223         service->unpin();
224         char ack_buf[] = { (char) DINIT_RP_ACK };
225         if (! queuePacket(ack_buf, 1)) return;
226     }
227     
228     // Clear the packet from the buffer
229     rbuf.consume(pkt_size);
230     chklen = 0;
231     return;
232 }
233
234 ControlConn::handle_t ControlConn::allocateServiceHandle(ServiceRecord *record)
235 {
236     bool is_unique = true;
237     handle_t largest_seen = 0;
238     handle_t candidate = 0;
239     for (auto p : keyServiceMap) {
240         if (p.first > largest_seen) largest_seen = p.first;
241         if (p.first == candidate) {
242             if (largest_seen == std::numeric_limits<handle_t>::max()) throw std::bad_alloc();
243             candidate = largest_seen + 1;
244         }
245         is_unique &= (p.second != record);
246     }
247     
248     keyServiceMap[candidate] = record;
249     serviceKeyMap.insert(std::make_pair(record, candidate));
250     
251     if (is_unique) {
252         record->addListener(this);
253     }
254     
255     return candidate;
256 }
257
258
259 bool ControlConn::queuePacket(const char *pkt, unsigned size) noexcept
260 {
261     if (bad_conn_close) return false;
262
263     bool was_empty = outbuf.empty();
264
265     if (was_empty) {
266         int wr = write(iob.fd, pkt, size);
267         if (wr == -1) {
268             if (errno == EPIPE) {
269                 delete this;
270                 return false;
271             }
272             if (errno != EAGAIN && errno != EWOULDBLOCK) {
273                 // TODO log error
274                 delete this;
275                 return false;
276             }
277         }
278         else {
279             if ((unsigned)wr == size) {
280                 // Ok, all written.
281                 return true;
282             }
283             pkt += wr;
284             size -= wr;
285         }
286         iob.setWatchFlags(in_events | out_events);
287     }
288     
289     // Create a vector out of the (remaining part of the) packet:
290     try {
291         outbuf.emplace_back(pkt, pkt + size);
292         return true;
293     }
294     catch (std::bad_alloc &baexc) {
295         // Mark the connection bad, and stop reading further requests
296         bad_conn_close = true;
297         oom_close = true;
298         if (was_empty) {
299             // We can't send out-of-memory response as we already wrote as much as we
300             // could above. Neither can we later send the response since we have currently
301             // sent an incomplete packet. All we can do is close the connection.
302             delete this;
303         }
304         else {
305             iob.setWatchFlags(out_events);
306         }
307         return false;    
308     }
309 }
310
311
312 bool ControlConn::queuePacket(std::vector<char> &&pkt) noexcept
313 {
314     if (bad_conn_close) return false;
315
316     bool was_empty = outbuf.empty();
317     
318     if (was_empty) {
319         outpkt_index = 0;
320         // We can try sending the packet immediately:
321         int wr = write(iob.fd, pkt.data(), pkt.size());
322         if (wr == -1) {
323             if (errno == EPIPE) {
324                 delete this;
325                 return false;
326             }
327             if (errno != EAGAIN && errno != EWOULDBLOCK) {
328                 // TODO log error
329                 delete this;
330                 return false;
331             }
332         }
333         else {
334             if ((unsigned)wr == pkt.size()) {
335                 // Ok, all written.
336                 return true;
337             }
338             outpkt_index = wr;
339         }
340         iob.setWatchFlags(in_events | out_events);
341     }
342     
343     try {
344         outbuf.emplace_back(pkt);
345         return true;
346     }
347     catch (std::bad_alloc &baexc) {
348         // Mark the connection bad, and stop reading further requests
349         bad_conn_close = true;
350         oom_close = true;
351         if (was_empty) {
352             // We can't send out-of-memory response as we already wrote as much as we
353             // could above. Neither can we later send the response since we have currently
354             // sent an incomplete packet. All we can do is close the connection.
355             delete this;
356         }
357         else {
358             iob.setWatchFlags(out_events);
359         }
360         return false;
361     }
362 }
363
364 bool ControlConn::rollbackComplete() noexcept
365 {
366     char ackBuf[2] = { DINIT_ROLLBACK_COMPLETED, 2 };
367     return queuePacket(ackBuf, 2);
368 }
369
370 bool ControlConn::dataReady() noexcept
371 {
372     int fd = iob.fd;
373     
374     int r = rbuf.fill(fd);
375     
376     // Note file descriptor is non-blocking
377     if (r == -1) {
378         if (errno != EAGAIN && errno != EWOULDBLOCK && errno != EINTR) {
379             // TODO log error
380             delete this;
381             return true;
382         }
383         return false;
384     }
385     
386     if (r == 0) {
387         delete this;
388         return true;
389     }
390     
391     // complete packet?
392     if (rbuf.get_length() >= chklen) {
393         try {
394             processPacket();
395         }
396         catch (std::bad_alloc &baexc) {
397             doOomClose();
398         }
399     }
400     
401     if (rbuf.get_length() == 1024) {
402         // Too big packet
403         // TODO log error?
404         // TODO error response?
405         bad_conn_close = true;
406         iob.setWatchFlags(out_events);
407     }
408     
409     return false;
410 }
411
412 void ControlConn::sendData() noexcept
413 {
414     if (outbuf.empty() && bad_conn_close) {
415         if (oom_close) {
416             // Send oom response
417             char oomBuf[] = { DINIT_RP_OOM };
418             write(iob.fd, oomBuf, 1);
419         }
420         delete this;
421         return;
422     }
423     
424     vector<char> & pkt = outbuf.front();
425     char *data = pkt.data();
426     int written = write(iob.fd, data + outpkt_index, pkt.size() - outpkt_index);
427     if (written == -1) {
428         if (errno == EPIPE) {
429             // read end closed
430             delete this;
431         }
432         else if (errno == EAGAIN || errno == EWOULDBLOCK) {
433             // spurious readiness notification?
434         }
435         else {
436             log(LogLevel::ERROR, "Error writing to control connection: ", strerror(errno));
437             delete this;
438         }
439         return;
440     }
441
442     outpkt_index += written;
443     if (outpkt_index == pkt.size()) {
444         // We've finished this packet, move on to the next:
445         outbuf.pop_front();
446         outpkt_index = 0;
447         if (outbuf.empty() && ! oom_close) {
448             if (! bad_conn_close) {
449                 iob.setWatchFlags(in_events);
450             }
451             else {
452                 delete this;
453             }
454         }
455     }
456 }
457
458 ControlConn::~ControlConn() noexcept
459 {
460     close(iob.fd);
461     iob.deregisterWatch(loop);
462     
463     // Clear service listeners
464     for (auto p : serviceKeyMap) {
465         p.first->removeListener(this);
466     }
467     
468     active_control_conns--;
469 }