discover: Fix boot with initrd error
[petitboot] / lib / pb-protocol / pb-protocol.c
1
2 #include <assert.h>
3 #include <errno.h>
4 #include <string.h>
5 #include <stdint.h>
6 #include <asm/byteorder.h>
7
8 #include <talloc/talloc.h>
9 #include <list/list.h>
10 #include <log/log.h>
11
12 #include "pb-protocol.h"
13
14
15 /* Message format:
16  *
17  * 4-byte action, determines the remaining message content
18  * 4-byte total payload len
19  *   - not including action and payload len header
20  *
21  * action = 0x1: device add message
22  *  payload:
23  *   4-byte len, id
24  *   4-byte len, name
25  *   4-byte len, description
26  *   4-byte len, icon_file
27  *
28  *   4-byte option count
29  *   for each option:
30  *    4-byte len, id
31  *    4-byte len, name
32  *    4-byte len, description
33  *    4-byte len, icon_file
34  *    4-byte len, boot_image_file
35  *    4-byte len, initrd_file
36  *    4-byte len, boot_args
37  *
38  * action = 0x2: device remove message
39  *  payload:
40  *   4-byte len, id
41  *
42  * action = 0x3: boot
43  *  payload:
44  *   4-byte len, boot option id
45  *   4-byte len, boot_image_file
46  *   4-byte len, initrd_file
47  *   4-byte len, boot_args
48  *
49  */
50
51 void pb_protocol_dump_device(const struct device *dev, const char *text,
52         FILE *stream)
53 {
54         struct boot_option *opt;
55
56         fprintf(stream, "%snew dev:\n", text);
57         fprintf(stream, "%s\tid:   %s\n", text, dev->id);
58         fprintf(stream, "%s\tname: %s\n", text, dev->name);
59         fprintf(stream, "%s\tdesc: %s\n", text, dev->description);
60         fprintf(stream, "%s\ticon: %s\n", text, dev->icon_file);
61         fprintf(stream, "%s\tboot options:\n", text);
62         list_for_each_entry(&dev->boot_options, opt, list) {
63                 fprintf(stream, "%s\t\tid:   %s\n", text, opt->id);
64                 fprintf(stream, "%s\t\tname: %s\n", text, opt->name);
65                 fprintf(stream, "%s\t\tdesc: %s\n", text, opt->description);
66                 fprintf(stream, "%s\t\ticon: %s\n", text, opt->icon_file);
67                 fprintf(stream, "%s\t\tboot: %s\n", text, opt->boot_image_file);
68                 fprintf(stream, "%s\t\tinit: %s\n", text, opt->initrd_file);
69                 fprintf(stream, "%s\t\targs: %s\n", text, opt->boot_args);
70         }
71 }
72
73 int pb_protocol_device_cmp(const struct device *a, const struct device *b)
74 {
75         return !strcmp(a->id, b->id);
76 }
77
78 int pb_protocol_boot_option_cmp(const struct boot_option *a,
79         const struct boot_option *b)
80 {
81         return !strcmp(a->id, b->id);
82 }
83
84 /* Write a string into the buffer, starting at pos.
85  *
86  * Returns the total length used for the write, including length header.
87  */
88 int pb_protocol_serialise_string(char *pos, const char *str)
89 {
90         int len = 0;
91
92         if (str)
93                 len = strlen(str);
94
95         *(uint32_t *)pos = __cpu_to_be32(len);
96         pos += sizeof(uint32_t);
97
98         memcpy(pos, str, len);
99
100         return len + sizeof(uint32_t);
101 }
102
103 /* Read a string from a buffer, allocating the new string as necessary.
104  *
105  * @param[in] ctx       The talloc context to base the allocation on
106  * @param[in,out] pos   Where to start reading
107  * @param[in,out] len   The amount of data remaining in the buffer
108  * @param[out] str      Pointer to resuling string
109  * @return              zero on success, non-zero on failure
110  */
111 static int read_string(void *ctx, const char **pos, unsigned int *len,
112         char **str)
113 {
114         uint32_t str_len, read_len;
115
116         if (*len < sizeof(uint32_t))
117                 return -1;
118
119         str_len = __be32_to_cpu(*(uint32_t *)(*pos));
120         read_len = sizeof(uint32_t);
121
122         if (read_len + str_len > *len)
123                 return -1;
124
125         if (str_len == 0)
126                 *str = NULL;
127         else
128                 *str = talloc_strndup(ctx, *pos + read_len, str_len);
129
130         read_len += str_len;
131
132         /* all ok, update the caller's pointers */
133         *pos += read_len;
134         *len -= read_len;
135
136         return 0;
137 }
138
139 char *pb_protocol_deserialise_string(void *ctx,
140                 const struct pb_protocol_message *message)
141 {
142         const char *buf;
143         char *str;
144         unsigned int len;
145
146         len = message->payload_len;
147         buf = message->payload;
148
149         if (read_string(ctx, &buf, &len, &str))
150                 return NULL;
151
152         return str;
153 }
154
155 static int optional_strlen(const char *str)
156 {
157         if (!str)
158                 return 0;
159         return strlen(str);
160 }
161
162 int pb_protocol_device_len(const struct device *dev)
163 {
164         struct boot_option *opt;
165         int len;
166
167         len =   4 + optional_strlen(dev->id) +
168                 4 + optional_strlen(dev->name) +
169                 4 + optional_strlen(dev->description) +
170                 4 + optional_strlen(dev->icon_file) +
171                 4;
172
173         list_for_each_entry(&dev->boot_options, opt, list) {
174                 len +=  4 + optional_strlen(opt->id) +
175                         4 + optional_strlen(opt->name) +
176                         4 + optional_strlen(opt->description) +
177                         4 + optional_strlen(opt->icon_file) +
178                         4 + optional_strlen(opt->boot_image_file) +
179                         4 + optional_strlen(opt->initrd_file) +
180                         4 + optional_strlen(opt->boot_args);
181         }
182
183         return len;
184 }
185
186 int pb_protocol_boot_len(const struct boot_command *boot)
187 {
188         return  4 + optional_strlen(boot->option_id) +
189                 4 + optional_strlen(boot->boot_image_file) +
190                 4 + optional_strlen(boot->initrd_file) +
191                 4 + optional_strlen(boot->boot_args);
192 }
193
194 int pb_protocol_serialise_device(const struct device *dev, char *buf, int buf_len)
195 {
196         struct boot_option *opt;
197         uint32_t n;
198         char *pos;
199
200         pos = buf;
201
202         /* construct payload into buffer */
203         pos += pb_protocol_serialise_string(pos, dev->id);
204         pos += pb_protocol_serialise_string(pos, dev->name);
205         pos += pb_protocol_serialise_string(pos, dev->description);
206         pos += pb_protocol_serialise_string(pos, dev->icon_file);
207
208         /* write option count */
209         n = 0;
210
211         list_for_each_entry(&dev->boot_options, opt, list)
212                 n++;
213
214         *(uint32_t *)pos = __cpu_to_be32(n);
215         pos += sizeof(uint32_t);
216
217         /* write each option */
218         list_for_each_entry(&dev->boot_options, opt, list) {
219                 pos += pb_protocol_serialise_string(pos, opt->id);
220                 pos += pb_protocol_serialise_string(pos, opt->name);
221                 pos += pb_protocol_serialise_string(pos, opt->description);
222                 pos += pb_protocol_serialise_string(pos, opt->icon_file);
223                 pos += pb_protocol_serialise_string(pos, opt->boot_image_file);
224                 pos += pb_protocol_serialise_string(pos, opt->initrd_file);
225                 pos += pb_protocol_serialise_string(pos, opt->boot_args);
226         }
227
228         assert(pos <= buf + buf_len);
229         (void)buf_len;
230
231         return 0;
232 }
233
234 int pb_protocol_serialise_boot_command(const struct boot_command *boot,
235                 char *buf, int buf_len)
236 {
237         char *pos = buf;
238
239         pos += pb_protocol_serialise_string(pos, boot->option_id);
240         pos += pb_protocol_serialise_string(pos, boot->boot_image_file);
241         pos += pb_protocol_serialise_string(pos, boot->initrd_file);
242         pos += pb_protocol_serialise_string(pos, boot->boot_args);
243
244         assert(pos <= buf + buf_len);
245         (void)buf_len;
246
247         return 0;
248 }
249
250 int pb_protocol_write_message(int fd, struct pb_protocol_message *message)
251 {
252         int total_len, rc;
253         char *pos;
254
255         total_len = sizeof(*message) + message->payload_len;
256
257         message->payload_len = __cpu_to_be32(message->payload_len);
258         message->action = __cpu_to_be32(message->action);
259
260         for (pos = (void *)message; total_len;) {
261                 rc = write(fd, pos, total_len);
262
263                 if (rc <= 0)
264                         break;
265
266                 total_len -= rc;
267                 pos += rc;
268         }
269
270         talloc_free(message);
271
272         if (!total_len)
273                 return 0;
274
275         pb_log("%s: failed: %s\n", __func__, strerror(errno));
276         return -1;
277 }
278
279 struct pb_protocol_message *pb_protocol_create_message(void *ctx,
280                 enum pb_protocol_action action, int payload_len)
281 {
282         struct pb_protocol_message *message;
283
284         if (payload_len > PB_PROTOCOL_MAX_PAYLOAD_SIZE) {
285                 pb_log("%s: payload too big %u/%u\n", __func__, payload_len,
286                         PB_PROTOCOL_MAX_PAYLOAD_SIZE);
287                 return NULL;
288         }
289
290         message = talloc_size(ctx, sizeof(*message) + payload_len);
291
292         /* we convert these to big-endian in write_message() */
293         message->action = action;
294         message->payload_len = payload_len;
295
296         return message;
297
298 }
299
300 struct pb_protocol_message *pb_protocol_read_message(void *ctx, int fd)
301 {
302         struct pb_protocol_message *message, m;
303         int rc;
304         unsigned int len;
305
306         /* use the stack for the initial 8-byte read */
307
308         rc = read(fd, &m, sizeof(m));
309         if (rc != sizeof(m))
310                 return NULL;
311
312         m.payload_len = __be32_to_cpu(m.payload_len);
313         m.action = __be32_to_cpu(m.action);
314
315         if (m.payload_len > PB_PROTOCOL_MAX_PAYLOAD_SIZE) {
316                 pb_log("%s: payload too big %u/%u\n", __func__, m.payload_len,
317                         PB_PROTOCOL_MAX_PAYLOAD_SIZE);
318                 return NULL;
319         }
320
321         message = talloc_size(ctx, sizeof(m) + m.payload_len);
322         memcpy(message, &m, sizeof(m));
323
324         for (len = 0; len < m.payload_len;) {
325                 rc = read(fd, message->payload + len, m.payload_len - len);
326
327                 if (rc <= 0) {
328                         talloc_free(message);
329                         pb_log("%s: failed (%u): %s\n", __func__, len,
330                                 strerror(errno));
331                         return NULL;
332                 }
333
334                 len += rc;
335         }
336
337         return message;
338 }
339
340
341 struct device *pb_protocol_deserialise_device(void *ctx,
342                 const struct pb_protocol_message *message)
343 {
344         struct device *dev;
345         const char *pos;
346         int i, n_options;
347         unsigned int len;
348
349         len = message->payload_len;
350         pos = message->payload;
351
352         dev = talloc(ctx, struct device);
353
354         if (read_string(dev, &pos, &len, &dev->id))
355                 goto out_err;
356
357         if (read_string(dev, &pos, &len, &dev->name))
358                 goto out_err;
359
360         if (read_string(dev, &pos, &len, &dev->description))
361                 goto out_err;
362
363         if (read_string(dev, &pos, &len, &dev->icon_file))
364                 goto out_err;
365
366         n_options = __be32_to_cpu(*(uint32_t *)pos);
367         pos += sizeof(uint32_t);
368
369         dev->n_options = n_options;
370
371         list_init(&dev->boot_options);
372
373         for (i = 0; i < n_options; i++) {
374                 struct boot_option *opt;
375
376                 opt = talloc(dev, struct boot_option);
377
378                 if (read_string(opt, &pos, &len, &opt->id))
379                         goto out_err;
380                 if (read_string(opt, &pos, &len, &opt->name))
381                         goto out_err;
382                 if (read_string(opt, &pos, &len,
383                                         &opt->description))
384                         goto out_err;
385                 if (read_string(opt, &pos, &len,
386                                         &opt->icon_file))
387                         goto out_err;
388                 if (read_string(opt, &pos, &len,
389                                         &opt->boot_image_file))
390                         goto out_err;
391                 if (read_string(opt, &pos, &len,
392                                         &opt->initrd_file))
393                         goto out_err;
394                 if (read_string(opt, &pos, &len,
395                                         &opt->boot_args))
396                         goto out_err;
397
398                 list_add(&dev->boot_options, &opt->list);
399         }
400
401         return dev;
402
403 out_err:
404         talloc_free(dev);
405         return NULL;
406 }
407
408 struct boot_command *pb_protocol_deserialise_boot_command(void *ctx,
409                 const struct pb_protocol_message *message)
410 {
411         struct boot_command *cmd;
412         const char *pos;
413         unsigned int len;
414
415         len = message->payload_len;
416         pos = message->payload;
417
418         cmd = talloc(ctx, struct boot_command);
419
420         if (read_string(cmd, &pos, &len, &cmd->option_id))
421                 goto out_err;
422
423         if (read_string(cmd, &pos, &len, &cmd->boot_image_file))
424                 goto out_err;
425
426         if (read_string(cmd, &pos, &len, &cmd->initrd_file))
427                 goto out_err;
428
429         if (read_string(cmd, &pos, &len, &cmd->boot_args))
430                 goto out_err;
431
432         return cmd;
433
434 out_err:
435         talloc_free(cmd);
436         return NULL;
437 }