+void discover_client_destroy(struct discover_client *client)
+{
+ talloc_free(client);
+}
+
+static struct device *find_device(struct discover_client *client,
+ const char *id)
+{
+ int i;
+
+ for (i = 0; i < client->n_devices; i++) {
+ struct device *dev = client->devices[i];
+ if (!strcmp(dev->id, id))
+ return dev;
+ }
+
+ return NULL;
+}
+
+static void device_add(struct discover_client *client, struct device *device)
+{
+ client->n_devices++;
+ client->devices = talloc_realloc(client, client->devices,
+ struct device *, client->n_devices);
+
+ client->devices[client->n_devices - 1] = device;
+ talloc_steal(client, device);
+ list_init(&device->boot_options);
+
+ if (client->ops.device_add)
+ client->ops.device_add(device, client->ops.cb_arg);
+}
+
+static void boot_option_add(struct discover_client *client,
+ struct boot_option *opt)
+{
+ struct device *dev;
+
+ dev = find_device(client, opt->device_id);
+
+ /* we require that devices are already present before any boot options
+ * are added */
+ assert(dev);
+
+ talloc_steal(dev, opt);
+ list_add(&dev->boot_options, &opt->list);
+
+ if (client->ops.boot_option_add)
+ client->ops.boot_option_add(dev, opt, client->ops.cb_arg);
+}
+
+static void device_remove(struct discover_client *client, const char *id)
+{
+ struct device *device = NULL;
+ int i;
+
+ for (i = 0; i < client->n_devices; i++) {
+ if (!strcmp(client->devices[i]->id, id)) {
+ device = client->devices[i];
+ break;
+ }
+ }
+
+ if (!device)
+ return;
+
+ /* remove the device from the client's device array */
+ client->n_devices--;
+ memmove(&client->devices[i], &client->devices[i+1],
+ (client->n_devices - i) * sizeof(client->devices[0]));
+ client->devices = talloc_realloc(client, client->devices,
+ struct device *, client->n_devices);
+
+ /* notify the UI */
+ client->ops.device_remove(device, client->ops.cb_arg);
+
+ talloc_free(device);
+}
+
+void discover_client_enumerate(struct discover_client *client)
+{
+ struct boot_option *opt;
+ struct device *device;
+ int i;
+
+ for (i = 0; i < client->n_devices; i++) {
+ device = client->devices[i];
+ if (client->ops.device_add)
+ client->ops.device_add(device, client->ops.cb_arg);
+
+ list_for_each_entry(&device->boot_options, opt, list)
+ if (client->ops.boot_option_add)
+ client->ops.boot_option_add(device, opt,
+ client->ops.cb_arg);
+ }
+}
+
+static void update_status(struct discover_client *client,
+ struct boot_status *status)
+{
+ if (client->ops.update_status)
+ client->ops.update_status(status, client->ops.cb_arg);
+}
+
+static void update_sysinfo(struct discover_client *client,
+ struct system_info *sysinfo)
+{
+ if (client->ops.update_sysinfo)
+ client->ops.update_sysinfo(sysinfo, client->ops.cb_arg);
+}
+
+static void update_config(struct discover_client *client,
+ struct config *config)
+{
+ if (client->ops.update_config)
+ client->ops.update_config(config, client->ops.cb_arg);
+}
+
+static int discover_client_process(void *arg)
+{
+ struct discover_client *client = arg;
+ struct pb_protocol_message *message;
+ struct system_info *sysinfo;
+ struct boot_status *status;
+ struct boot_option *opt;
+ struct config *config;
+ struct device *dev;
+ char *dev_id;
+ void *ctx;
+ int rc;
+
+ /* We use a temporary context for processing one message; persistent
+ * data is re-parented to the client in the callbacks. */
+ ctx = talloc_new(client);
+
+ message = pb_protocol_read_message(ctx, client->fd);
+
+ if (!message)
+ return -1;
+
+ switch (message->action) {
+ case PB_PROTOCOL_ACTION_DEVICE_ADD:
+ dev = talloc_zero(ctx, struct device);
+ list_init(&dev->boot_options);
+
+ rc = pb_protocol_deserialise_device(dev, message);
+ if (rc) {
+ pb_log("%s: no device?\n", __func__);
+ return 0;
+ }
+
+ device_add(client, dev);
+ break;
+ case PB_PROTOCOL_ACTION_BOOT_OPTION_ADD:
+ opt = talloc_zero(ctx, struct boot_option);
+
+ rc = pb_protocol_deserialise_boot_option(opt, message);
+ if (rc) {
+ pb_log("%s: no boot_option?\n", __func__);
+ return 0;
+ }
+
+ boot_option_add(client, opt);
+ break;
+ case PB_PROTOCOL_ACTION_DEVICE_REMOVE:
+ dev_id = pb_protocol_deserialise_string(ctx, message);
+ if (!dev_id) {
+ pb_log("%s: no device id?\n", __func__);
+ return 0;
+ }
+ device_remove(client, dev_id);
+ break;
+ case PB_PROTOCOL_ACTION_STATUS:
+ status = talloc_zero(ctx, struct boot_status);
+
+ rc = pb_protocol_deserialise_boot_status(status, message);
+ if (rc) {
+ pb_log("%s: invalid status message?\n", __func__);
+ return 0;
+ }
+ update_status(client, status);
+ break;
+ case PB_PROTOCOL_ACTION_SYSTEM_INFO:
+ sysinfo = talloc_zero(ctx, struct system_info);
+
+ rc = pb_protocol_deserialise_system_info(sysinfo, message);
+ if (rc) {
+ pb_log("%s: invalid sysinfo message?\n", __func__);
+ return 0;
+ }
+ update_sysinfo(client, sysinfo);
+ break;
+ case PB_PROTOCOL_ACTION_CONFIG:
+ config = talloc_zero(ctx, struct config);
+
+ rc = pb_protocol_deserialise_config(config, message);
+ if (rc) {
+ pb_log("%s: invalid config message?\n", __func__);
+ return 0;
+ }
+ update_config(client, config);
+ break;
+ default:
+ pb_log("%s: unknown action %d\n", __func__, message->action);
+ }
+
+ talloc_free(ctx);
+
+ return 0;
+}
+
+struct discover_client* discover_client_init(struct waitset *waitset,
+ const struct discover_client_ops *ops, void *cb_arg)