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