Initial gitification of petitboot
[petitboot] / devices.c
1 #include <stdlib.h>
2 #include <string.h>
3 #include <errno.h>
4 #include <unistd.h>
5 #include <sys/types.h>
6 #include <sys/socket.h>
7 #include <sys/un.h>
8 #include <asm/byteorder.h>
9
10 #include <libtwin/twin_png.h>
11 #include "petitboot.h"
12 #include "devices/message.h"
13
14 #define PBOOT_DEVICE_SOCKET     "/var/tmp/petitboot-dev"
15 #define PBOOT_DEFAULT_ICON      "artwork/usbpen.png"
16
17 static const char *default_icon = PBOOT_DEFAULT_ICON;
18
19 struct discovery_context {
20         /* nothing at present */
21         int pad;
22 } _ctx;
23
24 struct device_context {
25         struct discovery_context *discovery_ctx;
26         uint8_t action;
27         int device_idx;
28 };
29
30 static twin_pixmap_t *get_icon(const char *filename)
31 {
32         /* todo: cache */
33         twin_pixmap_t *icon;
34
35         if (!filename)
36                 filename = default_icon;
37
38 retry:
39         LOG("loading icon %s ... ", filename);
40         icon = twin_png_to_pixmap(filename, TWIN_ARGB32);
41         LOG("%s\n", icon ? "ok" : "failed");
42
43         if (!icon && filename != default_icon) {
44                 filename = default_icon;
45                 LOG("reverting to default icon %s\n", filename);
46                 goto retry;
47         }
48
49         return icon;
50 }
51
52 #define MAX_LEN 4096
53 static char *read_string(int fd)
54 {
55         int len;
56         uint32_t len_buf;
57         char *str, *pos;
58
59         if (read(fd, &len_buf, sizeof(len_buf)) != sizeof(len_buf)) {
60                 perror("read");
61                 return NULL;
62         }
63
64         len = __be32_to_cpu(len_buf);
65         if (len + 1 > MAX_LEN) {
66                 /* todo: truncate instead */
67                 return NULL;
68         }
69
70         pos = str = malloc(len + 1);
71
72         while (len) {
73                 int rc = read(fd, pos, len);
74                 if (rc <= 0) {
75                         free(str);
76                         LOG("read failed: %s\n", strerror(errno));
77                         return NULL;
78                 }
79                 pos += rc;
80                 len -= rc;
81         }
82         *pos = '\0';
83
84         return str;
85 }
86
87 static int _read_strings(int fd, void *ptr, int n_strings)
88 {
89         char **strings = ptr;
90         int i, ret = TWIN_TRUE;
91
92         for (i = 0; i < n_strings; i++) {
93                 strings[i] = read_string(fd);
94                 if (!strings[i]) {
95                         ret = TWIN_FALSE;
96                         break;
97                 }
98         }
99
100         if (!ret)
101                 while (i-- > 0) {
102                         free(strings[i]);
103                         strings[i] = NULL;
104                 }
105
106         return ret;
107 }
108
109 static void _free_strings(void *ptr, int n_strings)
110 {
111         char **strings = ptr;
112         int i;
113
114         for (i = 0; i < n_strings; i++)
115                 free(strings[i]);
116
117 }
118
119 #define n_strings(x) (sizeof((x)) / sizeof(char *))
120 #define read_strings(f,x) _read_strings((f), &(x), n_strings(x))
121 #define free_strings(x) _free_strings(&(x), n_strings(x))
122
123 static int read_action(int fd, uint8_t *action)
124 {
125         return read(fd, action, sizeof(*action)) != sizeof(*action);
126 }
127
128 static int read_device(int fd, struct device_context *dev_ctx)
129 {
130         /* name, description, icon_file */
131         struct device dev;
132         twin_pixmap_t *icon;
133         int index = -1;
134
135         if (!read_strings(fd, dev))
136                 return TWIN_FALSE;
137
138         LOG("got device: '%s'\n", dev.name);
139
140         icon = get_icon(dev.icon_file);
141
142         if (!icon)
143                 goto out;
144
145         index = dev_ctx->device_idx = pboot_add_device(dev.id, dev.name, icon);
146
147 out:
148         free_strings(dev);
149
150         return index != -1;
151 }
152
153 static int read_option(int fd, struct device_context *dev_ctx)
154 {
155         struct boot_option opt;
156         twin_pixmap_t *icon;
157         int index = -1;
158
159         if (!read_strings(fd, opt))
160                 return TWIN_FALSE;
161
162         LOG("got option: '%s'\n", opt.name);
163         icon = get_icon(opt.icon_file);
164
165         if (icon)
166                 index = pboot_add_option(dev_ctx->device_idx, opt.name,
167                                 opt.description, icon);
168
169         free_strings(opt);
170
171         return index != -1;
172 }
173
174 static twin_bool_t pboot_proc_client_sock(int sock, twin_file_op_t ops,
175                 void *closure)
176 {
177         struct device_context *dev_ctx = closure;
178         uint8_t action;
179
180         if (read_action(sock, &action))
181                 goto out_err;
182
183         if (action == DEV_ACTION_ADD_DEVICE) {
184                 if (!read_device(sock, dev_ctx))
185                         goto out_err;
186
187         } else if (action == DEV_ACTION_ADD_OPTION) {
188                 if (dev_ctx->device_idx == -1) {
189                         LOG("option, but no device has been sent?\n");
190                         goto out_err;
191                 }
192
193                 if (!read_option(sock, dev_ctx))
194                         goto out_err;
195
196         } else if (action == DEV_ACTION_REMOVE_DEVICE) {
197                 char *dev_id = read_string(sock);
198                 if (!dev_id)
199                         goto out_err;
200                 
201                 LOG("remove device %s\n", dev_id);
202                 pboot_remove_device(dev_id);
203
204         } else {
205                 LOG("unsupported action %d\n", action);
206                 goto out_err;
207         }
208
209         return TWIN_TRUE;
210
211 out_err:
212         close(sock);
213         return TWIN_FALSE;
214 }
215
216 static twin_bool_t pboot_proc_server_sock(int sock, twin_file_op_t ops,
217                 void *closure)
218 {
219         int fd;
220         struct discovery_context *disc_ctx = closure;
221         struct device_context *dev_ctx;
222
223         fd = accept(sock, NULL, 0);
224         if (fd < 0) {
225                 LOG("accept failed: %s", strerror(errno));
226                 return TWIN_FALSE;
227         }
228
229         dev_ctx = malloc(sizeof(*dev_ctx));
230         dev_ctx->discovery_ctx = disc_ctx;
231         dev_ctx->device_idx = -1;
232         dev_ctx->action = 0xff;
233
234         twin_set_file(pboot_proc_client_sock, fd, TWIN_READ, dev_ctx);
235
236         return TWIN_TRUE;
237 }
238
239 int pboot_start_device_discovery(void)
240 {
241         int sock;
242         struct sockaddr_un addr;
243
244         unlink(PBOOT_DEVICE_SOCKET);
245
246         sock = socket(PF_UNIX, SOCK_STREAM, 0);
247         if (sock < 0) {
248                 perror("socket");
249                 return TWIN_FALSE;
250         }
251
252         addr.sun_family = AF_UNIX;
253         strcpy(addr.sun_path, PBOOT_DEVICE_SOCKET);
254
255         if (bind(sock, (struct sockaddr *)&addr, sizeof(addr))) {
256                 LOG("can't bind to %s: %s", addr.sun_path, strerror(errno));
257                 return TWIN_FALSE;
258         }
259
260         if (listen(sock, 1)) {
261                 LOG("can't listen on socket %s: %s",
262                                 addr.sun_path, strerror(errno));
263                 return TWIN_FALSE;
264         }
265
266         LOG("listening on %s\n", addr.sun_path);
267
268         twin_set_file(pboot_proc_server_sock, sock, TWIN_READ, &_ctx);
269
270         return TWIN_TRUE;
271 }
272