ui/ncurses: Add plugin menu and nc-plugin screen
[petitboot] / ui / common / discover-client.c
1
2 #include <assert.h>
3 #include <errno.h>
4 #include <unistd.h>
5 #include <stdlib.h>
6 #include <stdio.h>
7 #include <stdint.h>
8
9 #include <sys/socket.h>
10 #include <sys/un.h>
11 #include <asm/byteorder.h>
12
13 #include <talloc/talloc.h>
14 #include <log/log.h>
15
16 #include "discover-client.h"
17 #include "pb-protocol/pb-protocol.h"
18
19 struct discover_client {
20         int fd;
21         struct discover_client_ops ops;
22         int n_devices;
23         struct device **devices;
24 };
25
26 static int discover_client_destructor(void *arg)
27 {
28         struct discover_client *client = arg;
29
30         if (client->fd >= 0)
31                 close(client->fd);
32
33         return 0;
34 }
35
36 void discover_client_destroy(struct discover_client *client)
37 {
38         talloc_free(client);
39 }
40
41 static struct device *find_device(struct discover_client *client,
42                 const char *id)
43 {
44         int i;
45
46         for (i = 0; i < client->n_devices; i++) {
47                 struct device *dev = client->devices[i];
48                 if (!strcmp(dev->id, id))
49                         return dev;
50         }
51
52         return NULL;
53 }
54
55 static void device_add(struct discover_client *client, struct device *device)
56 {
57         client->n_devices++;
58         client->devices = talloc_realloc(client, client->devices,
59                         struct device *, client->n_devices);
60
61         client->devices[client->n_devices - 1] = device;
62         talloc_steal(client, device);
63         list_init(&device->boot_options);
64
65         if (client->ops.device_add)
66                 client->ops.device_add(device, client->ops.cb_arg);
67 }
68
69 static void boot_option_add(struct discover_client *client,
70                 struct boot_option *opt)
71 {
72         struct device *dev;
73
74         dev = find_device(client, opt->device_id);
75
76         /* we require that devices are already present before any boot options
77          * are added */
78         assert(dev);
79
80         talloc_steal(dev, opt);
81         list_add(&dev->boot_options, &opt->list);
82
83         if (client->ops.boot_option_add)
84                 client->ops.boot_option_add(dev, opt, client->ops.cb_arg);
85 }
86
87 static void device_remove(struct discover_client *client, const char *id)
88 {
89         struct device *device = NULL;
90         int i;
91
92         for (i = 0; i < client->n_devices; i++) {
93                 if (!strcmp(client->devices[i]->id, id)) {
94                         device = client->devices[i];
95                         break;
96                 }
97         }
98
99         if (!device)
100                 return;
101
102         /* remove the device from the client's device array */
103         client->n_devices--;
104         memmove(&client->devices[i], &client->devices[i+1],
105                         (client->n_devices - i) * sizeof(client->devices[0]));
106         client->devices = talloc_realloc(client, client->devices,
107                         struct device *, client->n_devices);
108
109         /* notify the UI */
110         client->ops.device_remove(device, client->ops.cb_arg);
111
112         talloc_free(device);
113 }
114
115 static void plugin_option_add(struct discover_client *client,
116                 struct plugin_option *opt)
117 {
118         talloc_steal(client, opt);
119
120         if (client->ops.plugin_option_add)
121                 client->ops.plugin_option_add(opt, client->ops.cb_arg);
122 }
123
124 static void plugins_remove(struct discover_client *client)
125 {
126         if (client->ops.plugins_remove)
127                 client->ops.plugins_remove(client->ops.cb_arg);
128 }
129
130 void discover_client_enumerate(struct discover_client *client)
131 {
132         struct boot_option *opt;
133         struct device *device;
134         int i;
135
136         for (i = 0; i < client->n_devices; i++) {
137                 device = client->devices[i];
138                 if (client->ops.device_add)
139                         client->ops.device_add(device, client->ops.cb_arg);
140
141                 list_for_each_entry(&device->boot_options, opt, list)
142                         if (client->ops.boot_option_add)
143                                 client->ops.boot_option_add(device, opt,
144                                                 client->ops.cb_arg);
145         }
146 }
147
148 static void update_status(struct discover_client *client,
149                 struct status *status)
150 {
151         if (client->ops.update_status)
152                 client->ops.update_status(status, client->ops.cb_arg);
153 }
154
155 static void update_sysinfo(struct discover_client *client,
156                 struct system_info *sysinfo)
157 {
158         if (client->ops.update_sysinfo)
159                 client->ops.update_sysinfo(sysinfo, client->ops.cb_arg);
160 }
161
162 static void update_config(struct discover_client *client,
163                 struct config *config)
164 {
165         if (client->ops.update_config)
166                 client->ops.update_config(config, client->ops.cb_arg);
167 }
168
169 static int discover_client_process(void *arg)
170 {
171         struct discover_client *client = arg;
172         struct pb_protocol_message *message;
173         struct plugin_option *p_opt;
174         struct system_info *sysinfo;
175         struct boot_option *opt;
176         struct status *status;
177         struct config *config;
178         struct device *dev;
179         char *dev_id;
180         void *ctx;
181         int rc;
182
183         /* We use a temporary context for processing one message; persistent
184          * data is re-parented to the client in the callbacks. */
185         ctx = talloc_new(client);
186
187         message = pb_protocol_read_message(ctx, client->fd);
188
189         if (!message)
190                 return -1;
191
192         switch (message->action) {
193         case PB_PROTOCOL_ACTION_DEVICE_ADD:
194                 dev = talloc_zero(ctx, struct device);
195                 list_init(&dev->boot_options);
196
197                 rc = pb_protocol_deserialise_device(dev, message);
198                 if (rc) {
199                         pb_log("%s: no device?\n", __func__);
200                         goto out;
201                 }
202
203                 device_add(client, dev);
204                 break;
205         case PB_PROTOCOL_ACTION_BOOT_OPTION_ADD:
206                 opt = talloc_zero(ctx, struct boot_option);
207
208                 rc = pb_protocol_deserialise_boot_option(opt, message);
209                 if (rc) {
210                         pb_log("%s: no boot_option?\n", __func__);
211                         goto out;
212                 }
213
214                 boot_option_add(client, opt);
215                 break;
216         case PB_PROTOCOL_ACTION_DEVICE_REMOVE:
217                 dev_id = pb_protocol_deserialise_string(ctx, message);
218                 if (!dev_id) {
219                         pb_log("%s: no device id?\n", __func__);
220                         goto out;
221                 }
222                 device_remove(client, dev_id);
223                 break;
224         case PB_PROTOCOL_ACTION_STATUS:
225                 status = talloc_zero(ctx, struct status);
226
227                 rc = pb_protocol_deserialise_boot_status(status, message);
228                 if (rc) {
229                         pb_log("%s: invalid status message?\n", __func__);
230                         goto out;
231                 }
232                 update_status(client, status);
233                 break;
234         case PB_PROTOCOL_ACTION_SYSTEM_INFO:
235                 sysinfo = talloc_zero(ctx, struct system_info);
236
237                 rc = pb_protocol_deserialise_system_info(sysinfo, message);
238                 if (rc) {
239                         pb_log("%s: invalid sysinfo message?\n", __func__);
240                         goto out;
241                 }
242                 update_sysinfo(client, sysinfo);
243                 break;
244         case PB_PROTOCOL_ACTION_CONFIG:
245                 config = talloc_zero(ctx, struct config);
246
247                 rc = pb_protocol_deserialise_config(config, message);
248                 if (rc) {
249                         pb_log("%s: invalid config message?\n", __func__);
250                         goto out;
251                 }
252                 update_config(client, config);
253                 break;
254         case PB_PROTOCOL_ACTION_PLUGIN_OPTION_ADD:
255                 p_opt = talloc_zero(ctx, struct plugin_option);
256
257                 rc = pb_protocol_deserialise_plugin_option(p_opt, message);
258                 if (rc) {
259                         pb_log("%s: no plugin_option?\n", __func__);
260                         goto out;
261                 }
262
263                 plugin_option_add(client, p_opt);
264                 break;
265         case PB_PROTOCOL_ACTION_PLUGINS_REMOVE:
266                 plugins_remove(client);
267                 break;
268         default:
269                 pb_log("%s: unknown action %d\n", __func__, message->action);
270         }
271
272 out:
273         talloc_free(ctx);
274
275         return 0;
276 }
277
278 struct discover_client* discover_client_init(struct waitset *waitset,
279         const struct discover_client_ops *ops, void *cb_arg)
280 {
281         struct discover_client *client;
282         struct sockaddr_un addr;
283
284         client = talloc(NULL, struct discover_client);
285         if (!client)
286                 return NULL;
287
288         memcpy(&client->ops, ops, sizeof(client->ops));
289         client->ops.cb_arg = cb_arg;
290
291         client->fd = socket(AF_UNIX, SOCK_STREAM, 0);
292         if (client->fd < 0) {
293                 pb_log("%s: socket: %s\n", __func__, strerror(errno));
294                 goto out_err;
295         }
296
297         talloc_set_destructor(client, discover_client_destructor);
298
299         client->n_devices = 0;
300         client->devices = NULL;
301
302         addr.sun_family = AF_UNIX;
303         strcpy(addr.sun_path, PB_SOCKET_PATH);
304
305         if (connect(client->fd, (struct sockaddr *)&addr, sizeof(addr))) {
306                 pb_log("%s: connect: %s\n", __func__, strerror(errno));
307                 goto out_err;
308         }
309
310         waiter_register_io(waitset, client->fd, WAIT_IN,
311                         discover_client_process, client);
312
313         return client;
314
315 out_err:
316         talloc_free(client);
317         return NULL;
318 }
319
320 /* accessors for discovered devices */
321 int discover_client_device_count(struct discover_client *client)
322 {
323         return client->n_devices;
324 }
325
326 struct device *discover_client_get_device(struct discover_client *client,
327                 int index)
328 {
329         if (index < 0 || index >= client->n_devices)
330                 return NULL;
331
332         return client->devices[index];
333 }
334
335 static void create_boot_command(struct boot_command *command,
336                 const struct device *device __attribute__((unused)),
337                 const struct boot_option *boot_option,
338                 const struct pb_boot_data *data)
339 {
340         command->option_id = boot_option ? boot_option->id : NULL;
341         command->boot_image_file = data->image;
342         command->initrd_file = data->initrd;
343         command->dtb_file = data->dtb;
344         command->boot_args = data->args;
345         command->args_sig_file = data->args_sig_file;
346         command->console = ttyname(STDIN_FILENO);
347 }
348
349 int discover_client_boot(struct discover_client *client,
350                 const struct device *device,
351                 const struct boot_option *boot_option,
352                 const struct pb_boot_data *data)
353 {
354         struct pb_protocol_message *message;
355         struct boot_command boot_command;
356         int len, rc;
357
358         create_boot_command(&boot_command, device, boot_option, data);
359
360         len = pb_protocol_boot_len(&boot_command);
361
362         message = pb_protocol_create_message(client,
363                         PB_PROTOCOL_ACTION_BOOT, len);
364
365         if (!message)
366                 return -1;
367
368         pb_protocol_serialise_boot_command(&boot_command,
369                         message->payload, len);
370
371         rc = pb_protocol_write_message(client->fd, message);
372
373         return rc;
374 }
375
376 int discover_client_cancel_default(struct discover_client *client)
377 {
378         struct pb_protocol_message *message;
379
380         message = pb_protocol_create_message(client,
381                         PB_PROTOCOL_ACTION_CANCEL_DEFAULT, 0);
382
383         if (!message)
384                 return -1;
385
386         return pb_protocol_write_message(client->fd, message);
387 }
388
389 int discover_client_send_reinit(struct discover_client *client)
390 {
391         struct pb_protocol_message *message;
392
393         message = pb_protocol_create_message(client,
394                         PB_PROTOCOL_ACTION_REINIT, 0);
395
396         if (!message)
397                 return -1;
398
399         return pb_protocol_write_message(client->fd, message);
400 }
401
402 int discover_client_send_config(struct discover_client *client,
403                 struct config *config)
404 {
405         struct pb_protocol_message *message;
406         int len;
407
408         len = pb_protocol_config_len(config);
409
410         message = pb_protocol_create_message(client,
411                                 PB_PROTOCOL_ACTION_CONFIG, len);
412         if (!message)
413                 return -1;
414
415         pb_protocol_serialise_config(config, message->payload, len);
416
417         return pb_protocol_write_message(client->fd, message);
418 }
419
420 int discover_client_send_url(struct discover_client *client,
421                 char *url)
422 {
423         struct pb_protocol_message *message;
424         int len;
425
426         len = pb_protocol_url_len(url);
427
428         message = pb_protocol_create_message(client,
429                                 PB_PROTOCOL_ACTION_ADD_URL, len);
430         if (!message)
431                 return -1;
432
433         pb_protocol_serialise_url(url, message->payload, len);
434
435         return pb_protocol_write_message(client->fd, message);
436 }
437
438 int discover_client_send_plugin_install(struct discover_client *client,
439                 char *file)
440 {
441         struct pb_protocol_message *message;
442         int len;
443
444         len = pb_protocol_url_len(file);
445
446         message = pb_protocol_create_message(client,
447                                 PB_PROTOCOL_ACTION_PLUGIN_INSTALL, len);
448         if (!message)
449                 return -1;
450
451         pb_protocol_serialise_url(file, message->payload, len);
452
453         return pb_protocol_write_message(client->fd, message);
454 }