Clean up clang warnings
[oweals/dinit.git] / src / includes / dinit-client.h
1 #include <cstdint>
2 #include <cstring>
3
4 #include "cpbuffer.h"
5
6 // Client library for Dinit clients
7
8
9 using handle_t = uint32_t;
10 using cpbuffer_t = cpbuffer<1024>;
11
12 class cp_read_exception
13 {
14     public:
15     int errcode;
16     cp_read_exception(int err) : errcode(err) { }
17 };
18
19 class cp_write_exception
20 {
21     public:
22     int errcode;
23     cp_write_exception(int err) : errcode(err) { }
24 };
25
26 class cp_old_client_exception
27 {
28     // no body
29 };
30
31 class cp_old_server_exception
32 {
33     // no body
34 };
35
36
37
38 // static_membuf: a buffer of a fixed size (N) with one additional value (of type T). Don't use this
39 // directly, construct via membuf.
40 template <int N> class static_membuf
41 {
42     public:
43     static constexpr int size() { return N; }
44
45     private:
46     char buf[N];
47
48     public:
49     template <typename T>
50     static_membuf(const T &val)
51     {
52         static_assert(sizeof(T) == N, "must initialise with object of correct size");
53         memcpy(buf, &val, N);
54     }
55
56     template <int M, typename T>
57     static_membuf(char (&prevbuf)[M], const T &val)
58     {
59         static_assert(M + sizeof(T) == N, "size is not correct");
60         memcpy(buf, prevbuf, M);
61         memcpy(buf + M, &val, sizeof(val));
62     }
63
64     const char *data() const { return buf; }
65
66     template <typename U> static_membuf<N+sizeof(U)> append(const U &u)
67     {
68         return static_membuf<N+sizeof(U)>{buf, u};
69     }
70
71     void output(char *out)
72     {
73         memcpy(out, buf, size());
74     }
75 };
76
77 // "membuf" class provides a compile-time allocated buffer that we can add items to one-by-one. This is
78 // much safer than working with raw buffers and calculating offsets and sizes by hand (and with a decent
79 // compiler the end result is just as efficient).
80 //
81 // To use:
82 //     auto m = membuf().append(value1).append(value2).append(value3);
83 // Then:
84 //     m.size() - returns total size of the buffer (sizeof(value1)+...)
85 //     m.data() - returns a 'const char *' to the buffer contents
86 class membuf
87 {
88     public:
89
90     template <typename U> static_membuf<sizeof(U)> append(const U &u)
91     {
92         return static_membuf<sizeof(U)>(u);
93     }
94 };
95
96 // Fill a circular buffer from a file descriptor, until it contains at least _rlength_ bytes.
97 // Throws cp_read_exception if the requested number of bytes cannot be read, with:
98 //     errcode = 0   if end of stream (remote end closed)
99 //     errcode = errno   if another error occurred
100 // Note that EINTR is ignored (i.e. the read will be re-tried).
101 inline void fill_buffer_to(cpbuffer_t &buf, int fd, int rlength)
102 {
103     do {
104         int r = buf.fill_to(fd, rlength);
105         if (r == -1) {
106             if (errno != EINTR) {
107                 throw cp_read_exception(errno);
108             }
109         }
110         else if (r == 0) {
111             throw cp_read_exception(0);
112         }
113         else {
114             return;
115         }
116     }
117     while (true);
118 }
119
120 // Wait for a reply packet, skipping over any information packets that are received in the meantime.
121 inline void wait_for_reply(cpbuffer_t &rbuffer, int fd)
122 {
123     fill_buffer_to(rbuffer, fd, 1);
124
125     while (rbuffer[0] >= 100) {
126         // Information packet; discard.
127         fill_buffer_to(rbuffer, fd, 2);
128         int pktlen = (unsigned char) rbuffer[1];
129
130         rbuffer.consume(1);  // Consume one byte so we'll read one byte of the next packet
131         fill_buffer_to(rbuffer, fd, pktlen);
132         rbuffer.consume(pktlen - 1);
133     }
134 }
135
136 // Wait for an info packet. If any other reply packet comes, throw a cp_read_exception.
137 inline void wait_for_info(cpbuffer_t &rbuffer, int fd)
138 {
139     fill_buffer_to(rbuffer, fd, 2);
140
141     if (rbuffer[0] < 100) {
142         throw cp_read_exception(0);
143     }
144
145     int pktlen = (unsigned char) rbuffer[1];
146     fill_buffer_to(rbuffer, fd, pktlen);
147 }
148
149 // Write *all* the requested buffer and re-try if necessary until
150 // the buffer is written or an unrecoverable error occurs.
151 inline int write_all(int fd, const void *buf, size_t count)
152 {
153     const char *cbuf = static_cast<const char *>(buf);
154     int w = 0;
155     while (count > 0) {
156         int r = write(fd, cbuf, count);
157         if (r == -1) {
158             if (errno == EINTR) continue;
159             return r;
160         }
161         w += r;
162         cbuf += r;
163         count -= r;
164     }
165     return w;
166 }
167
168 // Write all the requested buffer, and throw an exception on failure.
169 inline void write_all_x(int fd, const void *buf, size_t count)
170 {
171     if (write_all(fd, buf, count) == -1) {
172         throw cp_write_exception(errno);
173     }
174 }
175
176 // Write all the requested buffer (eg membuf) and throw an exception on failure.
177 template <typename Buf> inline void write_all_x(int fd, const Buf &b)
178 {
179     write_all_x(fd, b.data(), b.size());
180 }
181
182 // Check the protocol version is compatible with the client.
183 //   minversion - minimum protocol version that client can speak
184 //   version - maximum protocol version that client can speak
185 //   rbuffer, fd -  communication buffer and socket
186 // returns: the actual protocol version
187 // throws an exception on protocol mismatch or error.
188 inline uint16_t check_protocol_version(int minversion, int version, cpbuffer_t &rbuffer, int fd)
189 {
190     constexpr int bufsize = 1;
191     char buf[bufsize] = { DINIT_CP_QUERYVERSION };
192     write_all_x(fd, buf, bufsize);
193
194     wait_for_reply(rbuffer, fd);
195     if (rbuffer[0] != DINIT_RP_CPVERSION) {
196         throw cp_read_exception{0};
197     }
198
199     // DINIT_RP_CVERSION, (2 byte) minimum compatible version, (2 byte) actual version
200     constexpr int rbufsize = 1 + 2 * sizeof(uint16_t);
201     fill_buffer_to(rbuffer, fd, rbufsize);
202     uint16_t rminversion;
203     uint16_t cpversion;
204
205     rbuffer.extract(reinterpret_cast<char *>(&rminversion), 1, sizeof(uint16_t));
206     rbuffer.extract(reinterpret_cast<char *>(&cpversion), 1 + sizeof(uint16_t), sizeof(uint16_t));
207     rbuffer.consume(rbufsize);
208
209     if (rminversion > version) {
210         // We are too old
211         throw cp_old_client_exception();
212     }
213     if (cpversion < minversion) {
214         // Server is too old
215         throw cp_old_server_exception();
216     }
217
218     return cpversion;
219 }