0bce51c038215274c26dc979ea2319b6ad211b42
[oweals/dinit.git] / src / control.h
1 #ifndef DINIT_CONTROL_H
2 #define DINIT_CONTROL_H
3
4 #include <list>
5 #include <vector>
6 #include <unordered_map>
7 #include <limits>
8
9 #include <unistd.h>
10 #include <ev++.h>
11
12 #include "dinit-log.h"
13 #include "control-cmds.h"
14 #include "service-listener.h"
15 #include "cpbuffer.h"
16
17 // Control connection for dinit
18
19 // TODO: Use the input buffer as a circular buffer, instead of chomping data from
20 // the front using a data move.
21
22 // forward-declaration of callback:
23 static void control_conn_cb(struct ev_loop * loop, ev_io * w, int revents);
24
25 class ControlConn;
26
27 // Pointer to the control connection that is listening for rollback completion
28 extern ControlConn * rollback_handler_conn;
29
30 extern int active_control_conns;
31
32 // "packet" format:
33 // (1 byte) packet type
34 // (N bytes) additional data (service name, etc)
35 //   for LOADSERVICE/FINDSERVICE:
36 //      (2 bytes) service name length
37 //      (M bytes) service name (without nul terminator)
38
39 // Information packet:
40 // (1 byte) packet type, >= 100
41 // (1 byte) packet length (including all fields)
42 //       N bytes: packet data (N = (length - 2))
43
44 class ServiceSet;
45 class ServiceRecord;
46
47 class ControlConn : private ServiceListener
48 {
49     friend void control_conn_cb(struct ev_loop *, ev_io *, int);
50     
51     struct ev_io iob;
52     struct ev_loop *loop;
53     ServiceSet *service_set;
54     
55     bool bad_conn_close; // close when finished output?
56     bool oom_close;      // send final 'out of memory' indicator
57
58     // The packet length before we need to re-check if the packet is complete.
59     // processPacket() will not be called until the packet reaches this size.
60     int chklen;
61     
62     // Receive buffer
63     CPBuffer rbuf;
64     
65     template <typename T> using list = std::list<T>;
66     template <typename T> using vector = std::vector<T>;
67     
68     // A mapping between service records and their associated numerical identifier used
69     // in communction
70     using handle_t = uint32_t;
71     std::unordered_multimap<ServiceRecord *, handle_t> serviceKeyMap;
72     std::unordered_map<handle_t, ServiceRecord *> keyServiceMap;
73     
74     // Buffer for outgoing packets. Each outgoing back is represented as a vector<char>.
75     list<vector<char>> outbuf;
76     // Current index within the first outgoing packet (all previous bytes have been sent).
77     unsigned outpkt_index = 0;
78     
79     // Queue a packet to be sent
80     //  Returns:  true if the packet was successfully queued, false if otherwise
81     //            (eg if out of memory); in the latter case the connection might
82     //            no longer be valid (iff there are no outgoing packets queued).
83     bool queuePacket(vector<char> &&v) noexcept;
84     bool queuePacket(const char *pkt, unsigned size) noexcept;
85
86     // Process a packet. Can cause the ControlConn to be deleted iff there are no
87     // outgoing packets queued.
88     // Throws:
89     //    std::bad_alloc - if an out-of-memory condition prevents processing
90     void processPacket();
91     
92     // Process a STARTSERVICE/STOPSERVICE packet. May throw std::bad_alloc.
93     void processStartStop(int pktType);
94     
95     // Process a FINDSERVICE/LOADSERVICE packet. May throw std::bad_alloc.
96     void processFindLoad(int pktType);
97
98     // Notify that data is ready to be read from the socket. Returns true in cases where the
99     // connection was deleted with potentially pending outgoing packets.
100     bool dataReady() noexcept;
101     
102     void sendData() noexcept;
103     
104     // Allocate a new handle for a service; may throw std::bad_alloc
105     handle_t allocateServiceHandle(ServiceRecord *record);
106     
107     ServiceRecord *findServiceForKey(uint32_t key)
108     {
109         try {
110             return keyServiceMap.at(key);
111         }
112         catch (std::out_of_range &exc) {
113             return nullptr;
114         }
115     }
116     
117     // Close connection due to out-of-memory condition.
118     void doOomClose()
119     {
120         bad_conn_close = true;
121         oom_close = true;
122         ev_io_set(&iob, iob.fd, EV_WRITE);
123     }
124     
125     // Process service event broadcast.
126     void serviceEvent(ServiceRecord * service, ServiceEvent event) noexcept final override
127     {
128         // For each service handle corresponding to the event, send an information packet.
129         auto range = serviceKeyMap.equal_range(service);
130         auto & i = range.first;
131         auto & end = range.second;
132         try {
133             while (i != end) {
134                 uint32_t key = i->second;
135                 std::vector<char> pkt;
136                 constexpr int pktsize = 3 + sizeof(key);
137                 pkt.reserve(pktsize);
138                 pkt.push_back(DINIT_IP_SERVICEEVENT);
139                 pkt.push_back(pktsize);
140                 char * p = (char *) &key;
141                 for (int j = 0; j < (int)sizeof(key); j++) {
142                     pkt.push_back(*p++);
143                 }
144                 pkt.push_back(static_cast<char>(event));
145                 queuePacket(std::move(pkt));
146                 ++i;
147             }
148         }
149         catch (std::bad_alloc &exc) {
150             doOomClose();
151         }
152     }
153     
154     public:
155     ControlConn(struct ev_loop * loop, ServiceSet * service_set, int fd) : loop(loop), service_set(service_set), chklen(0)
156     {
157         ev_io_init(&iob, control_conn_cb, fd, EV_READ);
158         iob.data = this;
159         ev_io_start(loop, &iob);
160         
161         active_control_conns++;
162     }
163     
164     bool rollbackComplete() noexcept;
165         
166     virtual ~ControlConn() noexcept;
167 };
168
169
170 static void control_conn_cb(struct ev_loop * loop, ev_io * w, int revents)
171 {
172     ControlConn *conn = (ControlConn *) w->data;
173     if (revents & EV_READ) {
174         if (conn->dataReady()) {
175             // ControlConn was deleted
176             return;
177         }
178     }
179     if (revents & EV_WRITE) {
180         conn->sendData();
181     }    
182 }
183
184 #endif