Add initial control protocol test.
[oweals/dinit.git] / src / includes / 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 <map>
8 #include <limits>
9 #include <cstddef>
10
11 #include <unistd.h>
12
13 #include "dinit.h"
14 #include "dinit-log.h"
15 #include "control-cmds.h"
16 #include "service-listener.h"
17 #include "cpbuffer.h"
18
19 // Control connection for dinit
20
21 class control_conn_t;
22 class control_conn_watcher;
23
24 // forward-declaration of callback:
25 inline dasynq::rearm control_conn_cb(eventloop_t *loop, control_conn_watcher *watcher, int revents);
26
27 // Pointer to the control connection that is listening for rollback completion
28 extern control_conn_t * 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 service_set;
45 class service_record;
46
47 class control_conn_watcher : public eventloop_t::bidi_fd_watcher_impl<control_conn_watcher>
48 {
49     inline rearm receive_event(eventloop_t &loop, int fd, int flags) noexcept
50     {
51         return control_conn_cb(&loop, this, flags);
52     }
53
54     eventloop_t * event_loop;
55
56     public:
57     control_conn_watcher(eventloop_t & event_loop_p) : event_loop(&event_loop_p)
58     {
59         // constructor
60     }
61
62     rearm read_ready(eventloop_t &loop, int fd) noexcept
63     {
64         return receive_event(loop, fd, dasynq::IN_EVENTS);
65     }
66     
67     rearm write_ready(eventloop_t &loop, int fd) noexcept
68     {
69         return receive_event(loop, fd, dasynq::OUT_EVENTS);
70     }
71
72     void set_watches(int flags)
73     {
74         eventloop_t::bidi_fd_watcher::set_watches(*event_loop, flags);
75     }
76 };
77
78 class control_conn_t : private service_listener
79 {
80     friend rearm control_conn_cb(eventloop_t *loop, control_conn_watcher *watcher, int revents);
81     
82     control_conn_watcher iob;
83     eventloop_t &loop;
84     service_set *services;
85     
86     bool bad_conn_close = false; // close when finished output?
87     bool oom_close = false;      // send final 'out of memory' indicator
88
89     // The packet length before we need to re-check if the packet is complete.
90     // process_packet() will not be called until the packet reaches this size.
91     int chklen;
92     
93     // Receive buffer
94     cpbuffer<1024> rbuf;
95     
96     template <typename T> using list = std::list<T>;
97     template <typename T> using vector = std::vector<T>;
98     
99     // A mapping between service records and their associated numerical identifier used
100     // in communction
101     using handle_t = uint32_t;
102     std::unordered_multimap<service_record *, handle_t> service_key_map;
103     std::map<handle_t, service_record *> key_service_map;
104     
105     // Buffer for outgoing packets. Each outgoing back is represented as a vector<char>.
106     list<vector<char>> outbuf;
107     // Current index within the first outgoing packet (all previous bytes have been sent).
108     unsigned outpkt_index = 0;
109     
110     // Queue a packet to be sent
111     //  Returns:  false if the packet could not be queued and a suitable error packet
112     //              could not be sent/queued (the connection should be closed);
113     //            true (with bad_conn_close == false) if the packet was successfully
114     //              queued;
115     //            true (with bad_conn_close == true) if the packet was not successfully
116     //              queued (but a suitable error packate has been queued).
117     // The in/out watch enabled state will also be set appropriately.
118     bool queue_packet(vector<char> &&v) noexcept;
119     bool queue_packet(const char *pkt, unsigned size) noexcept;
120
121     // Process a packet.
122     //  Returns:  true (with bad_conn_close == false) if successful
123     //            true (with bad_conn_close == true) if an error packet was queued
124     //            false if an error occurred but no error packet could be queued
125     //                (connection should be closed).
126     // Throws:
127     //    std::bad_alloc - if an out-of-memory condition prevents processing
128     bool process_packet();
129     
130     // Process a STARTSERVICE/STOPSERVICE packet. May throw std::bad_alloc.
131     bool process_start_stop(int pktType);
132     
133     // Process a FINDSERVICE/LOADSERVICE packet. May throw std::bad_alloc.
134     bool process_find_load(int pktType);
135
136     // Process an UNPINSERVICE packet. May throw std::bad_alloc.
137     bool process_unpin_service();
138     
139     // Process an UNLOADSERVICE packet.
140     bool process_unload_service();
141
142     bool list_services();
143
144     bool add_service_dep();
145
146     bool rm_service_dep();
147
148     // Notify that data is ready to be read from the socket. Returns true if the connection should
149     // be closed.
150     bool data_ready() noexcept;
151     
152     bool send_data() noexcept;
153     
154     // Allocate a new handle for a service; may throw std::bad_alloc
155     handle_t allocate_service_handle(service_record *record);
156     
157     // Find the service corresponding to a service handle; returns nullptr if not found.
158     service_record *find_service_for_key(handle_t key) noexcept
159     {
160         try {
161             return key_service_map.at(key);
162         }
163         catch (std::out_of_range &exc) {
164             return nullptr;
165         }
166     }
167     
168     // Close connection due to out-of-memory condition.
169     void do_oom_close() noexcept
170     {
171         bad_conn_close = true;
172         oom_close = true;
173         iob.set_watches(dasynq::OUT_EVENTS);
174     }
175     
176     // Process service event broadcast.
177     // Note that this can potentially be called during packet processing (upon issuing
178     // service start/stop orders etc).
179     void service_event(service_record * service, service_event_t event) noexcept final override
180     {
181         // For each service handle corresponding to the event, send an information packet.
182         auto range = service_key_map.equal_range(service);
183         auto & i = range.first;
184         auto & end = range.second;
185         try {
186             while (i != end) {
187                 uint32_t key = i->second;
188                 std::vector<char> pkt;
189                 constexpr int pktsize = 3 + sizeof(key);
190                 pkt.reserve(pktsize);
191                 pkt.push_back(DINIT_IP_SERVICEEVENT);
192                 pkt.push_back(pktsize);
193                 char * p = (char *) &key;
194                 for (int j = 0; j < (int)sizeof(key); j++) {
195                     pkt.push_back(*p++);
196                 }
197                 pkt.push_back(static_cast<char>(event));
198                 queue_packet(std::move(pkt));
199                 ++i;
200             }
201         }
202         catch (std::bad_alloc &exc) {
203             do_oom_close();
204         }
205     }
206     
207     public:
208     control_conn_t(eventloop_t &loop, service_set * services_p, int fd)
209             : iob(loop), loop(loop), services(services_p), chklen(0)
210     {
211         iob.add_watch(loop, fd, dasynq::IN_EVENTS);
212         active_control_conns++;
213     }
214     
215     control_conn_t(const control_conn_t &) = delete;
216
217     bool rollback_complete() noexcept;
218         
219     virtual ~control_conn_t() noexcept;
220 };
221
222
223 inline dasynq::rearm control_conn_cb(eventloop_t * loop, control_conn_watcher * watcher, int revents)
224 {
225     // Get the address of the containing control_connt_t object:
226     _Pragma ("GCC diagnostic push")
227     _Pragma ("GCC diagnostic ignored \"-Winvalid-offsetof\"")
228     char * cc_addr = (reinterpret_cast<char *>(watcher)) - offsetof(control_conn_t, iob);
229     control_conn_t *conn = reinterpret_cast<control_conn_t *>(cc_addr);
230     _Pragma ("GCC diagnostic pop")
231
232     if (revents & dasynq::IN_EVENTS) {
233         if (conn->data_ready()) {
234             delete conn;
235             return dasynq::rearm::REMOVED;
236         }
237     }
238     if (revents & dasynq::OUT_EVENTS) {
239         if (conn->send_data()) {
240             delete conn;
241             return dasynq::rearm::REMOVED;
242         }
243     }
244     
245     return dasynq::rearm::NOOP;
246 }
247
248 #endif