platforms/powerpc: Check for a powerpc platform in the probe function
[petitboot] / discover / platform-powerpc.c
1
2 #include <string.h>
3 #include <stdlib.h>
4 #include <limits.h>
5 #include <sys/types.h>
6 #include <sys/wait.h>
7 #include <sys/stat.h>
8
9 #include <talloc/talloc.h>
10 #include <list/list.h>
11 #include <log/log.h>
12 #include <process/process.h>
13
14 #include "platform.h"
15
16 static const char *partition = "common";
17
18 struct param {
19         char                    *name;
20         char                    *value;
21         bool                    modified;
22         struct list_item        list;
23 };
24
25 struct platform_powerpc {
26         struct list             params;
27 };
28
29 static const char *known_params[] = {
30         "auto-boot?",
31         "petitboot,network",
32         "petitboot,timeout",
33         NULL,
34 };
35
36 #define to_platform_powerpc(p) \
37         (struct platform_powerpc *)(p->platform_data)
38
39 /* a partition max a max size of 64k * 16bytes = 1M */
40 static const int max_partition_size = 64 * 1024 * 16;
41
42 static bool param_is_known(const char *param, unsigned int len)
43 {
44         const char *known_param;
45         unsigned int i;
46
47         for (i = 0; known_params[i]; i++) {
48                 known_param = known_params[i];
49                 if (len == strlen(known_param) &&
50                                 !strncmp(param, known_param, len))
51                         return true;
52         }
53
54         return false;
55 }
56
57 static int parse_nvram_params(struct platform_powerpc *platform,
58                 char *buf, int len)
59 {
60         char *pos, *name, *value;
61         unsigned int paramlen;
62         int i, count;
63
64         /* discard 2 header lines:
65          * "common" partiton"
66          * ------------------
67          */
68         pos = buf;
69         count = 0;
70
71         for (i = 0; i < len; i++) {
72                 if (pos[i] == '\n')
73                         count++;
74                 if (count == 2)
75                         break;
76         }
77
78         if (i == len) {
79                 fprintf(stderr, "failure parsing nvram output\n");
80                 return -1;
81         }
82
83         for (pos = buf + i; pos < buf + len; pos += paramlen + 1) {
84                 unsigned int namelen;
85                 struct param *param;
86                 char *newline;
87
88                 newline = strchr(pos, '\n');
89                 if (!newline)
90                         break;
91
92                 *newline = '\0';
93
94                 paramlen = strlen(pos);
95
96                 name = pos;
97                 value = strchr(pos, '=');
98                 if (!value)
99                         continue;
100
101                 namelen = value - name;
102                 if (namelen == 0)
103                         continue;
104
105                 if (!param_is_known(name, namelen))
106                         continue;
107
108                 value++;
109
110                 param = talloc(platform, struct param);
111                 param->modified = false;
112                 param->name = talloc_strndup(platform, name, namelen);
113                 param->value = talloc_strdup(platform, value);
114                 list_add(&platform->params, &param->list);
115         }
116
117         return 0;
118 }
119
120 static int parse_nvram(struct platform_powerpc *platform)
121 {
122         struct process *process;
123         const char *argv[5];
124         int rc;
125
126         argv[0] = "nvram";
127         argv[1] = "--print-config";
128         argv[2] = "--partition";
129         argv[3] = partition;
130         argv[4] = NULL;
131
132         process = process_create(platform);
133         process->path = "nvram";
134         process->argv = argv;
135         process->keep_stdout = true;
136
137         rc = process_run_sync(process);
138
139         if (rc || !process_exit_ok(process)) {
140                 fprintf(stderr, "nvram process returned "
141                                 "non-zero exit status\n");
142                 rc = -1;
143         } else {
144                 rc = parse_nvram_params(platform, process->stdout_buf,
145                                             process->stdout_len);
146         }
147
148         process_release(process);
149         return rc;
150 }
151
152 static int write_nvram(struct platform_powerpc *platform)
153 {
154         struct process *process;
155         struct param *param;
156         const char *argv[6];
157         int rc;
158
159         argv[0] = "nvram";
160         argv[1] = "--update-config";
161         argv[2] = NULL;
162         argv[3] = "--partition";
163         argv[4] = partition;
164         argv[5] = NULL;
165
166         process = process_create(platform);
167         process->path = "nvram";
168         process->argv = argv;
169
170         list_for_each_entry(&platform->params, param, list) {
171                 char *paramstr;
172
173                 if (!param->modified)
174                         continue;
175
176                 paramstr = talloc_asprintf(platform, "%s=%s",
177                                 param->name, param->value);
178                 argv[2] = paramstr;
179
180                 rc = process_run_sync(process);
181
182                 talloc_free(paramstr);
183
184                 if (rc || !process_exit_ok(process)) {
185                         rc = -1;
186                         pb_log("nvram update process returned "
187                                         "non-zero exit status\n");
188                         break;
189                 }
190         }
191
192         process_release(process);
193         return rc;
194 }
195
196 static const char *get_param(struct platform_powerpc *platform,
197                 const char *name)
198 {
199         struct param *param;
200
201         list_for_each_entry(&platform->params, param, list)
202                 if (!strcmp(param->name, name))
203                         return param->value;
204         return NULL;
205 }
206
207 static void set_param(struct platform_powerpc *platform, const char *name,
208                 const char *value)
209 {
210         struct param *param;
211
212         list_for_each_entry(&platform->params, param, list) {
213                 if (strcmp(param->name, name))
214                         continue;
215
216                 if (!strcmp(param->value, value))
217                         return;
218
219                 talloc_free(param->value);
220                 param->value = talloc_strdup(param, value);
221                 param->modified = true;
222                 return;
223         }
224
225
226         param = talloc(platform, struct param);
227         param->modified = true;
228         param->name = talloc_strdup(platform, name);
229         param->value = talloc_strdup(platform, value);
230         list_add(&platform->params, &param->list);
231 }
232
233 static int parse_hwaddr(struct interface_config *ifconf, char *str)
234 {
235         int i;
236
237         if (strlen(str) != strlen("00:00:00:00:00:00"))
238                 return -1;
239
240         for (i = 0; i < HWADDR_SIZE; i++) {
241                 char byte[3], *endp;
242                 unsigned long x;
243
244                 byte[0] = str[i * 3 + 0];
245                 byte[1] = str[i * 3 + 1];
246                 byte[2] = '\0';
247
248                 x = strtoul(byte, &endp, 16);
249                 if (endp != byte + 2)
250                         return -1;
251
252                 ifconf->hwaddr[i] = x & 0xff;
253         }
254
255         return 0;
256 }
257
258 static int parse_one_interface_config(struct config *config,
259                 char *confstr)
260 {
261         struct interface_config *ifconf;
262         char *tok, *saveptr;
263
264         ifconf = talloc_zero(config, struct interface_config);
265
266         if (!confstr || !strlen(confstr))
267                 goto out_err;
268
269         /* first token should be the mac address */
270         tok = strtok_r(confstr, ",", &saveptr);
271         if (!tok)
272                 goto out_err;
273
274         if (parse_hwaddr(ifconf, tok))
275                 goto out_err;
276
277         /* second token is the method */
278         tok = strtok_r(NULL, ",", &saveptr);
279         if (!tok || !strlen(tok) || !strcmp(tok, "ignore")) {
280                 ifconf->ignore = true;
281
282         } else if (!strcmp(tok, "dhcp")) {
283                 ifconf->method = CONFIG_METHOD_DHCP;
284
285         } else if (!strcmp(tok, "static")) {
286                 ifconf->method = CONFIG_METHOD_STATIC;
287
288                 /* ip/mask, [optional] gateway */
289                 tok = strtok_r(NULL, ",", &saveptr);
290                 if (!tok)
291                         goto out_err;
292                 ifconf->static_config.address =
293                         talloc_strdup(ifconf, tok);
294
295                 tok = strtok_r(NULL, ",", &saveptr);
296                 if (tok) {
297                         ifconf->static_config.gateway =
298                                 talloc_strdup(ifconf, tok);
299                 }
300
301         } else {
302                 pb_log("Unknown network configuration method %s\n", tok);
303                 goto out_err;
304         }
305
306         config->network.interfaces = talloc_realloc(config,
307                         config->network.interfaces,
308                         struct interface_config *,
309                         ++config->network.n_interfaces);
310
311         config->network.interfaces[config->network.n_interfaces - 1] = ifconf;
312
313         return 0;
314 out_err:
315         talloc_free(ifconf);
316         return -1;
317 }
318
319 static int parse_one_dns_config(struct config *config,
320                 char *confstr)
321 {
322         char *tok, *saveptr;
323
324         for (tok = strtok_r(confstr, ",", &saveptr); tok;
325                         tok = strtok_r(NULL, ",", &saveptr)) {
326
327                 char *server = talloc_strdup(config, tok);
328
329                 config->network.dns_servers = talloc_realloc(config,
330                                 config->network.dns_servers, const char *,
331                                 ++config->network.n_dns_servers);
332
333                 config->network.dns_servers[config->network.n_dns_servers - 1]
334                                 = server;
335         }
336
337         return 0;
338 }
339
340 static void populate_network_config(struct platform_powerpc *platform,
341                 struct config *config)
342 {
343         const char *cval;
344         char *val;
345         int i;
346
347         cval = get_param(platform, "petitboot,network");
348         if (!cval || !strlen(cval))
349                 return;
350
351         val = talloc_strdup(config, cval);
352
353         for (i = 0; ; i++) {
354                 char *tok, *saveptr;
355
356                 tok = strtok_r(i == 0 ? val : NULL, " ", &saveptr);
357                 if (!tok)
358                         break;
359
360                 if (!strncasecmp(tok, "dns,", strlen("dns,")))
361                         parse_one_dns_config(config, tok + strlen("dns,"));
362                 else
363                         parse_one_interface_config(config, tok);
364
365         }
366
367         talloc_free(val);
368 }
369
370 static void populate_config(struct platform_powerpc *platform,
371                 struct config *config)
372 {
373         const char *val;
374         char *end;
375         unsigned long timeout;
376
377         /* if the "auto-boot?' property is present and "false", disable auto
378          * boot */
379         val = get_param(platform, "auto-boot?");
380         config->autoboot_enabled = !val || strcmp(val, "false");
381
382         val = get_param(platform, "petitboot,timeout");
383         if (val) {
384                 timeout = strtoul(val, &end, 10);
385                 if (end != val) {
386                         if (timeout >= INT_MAX)
387                                 timeout = INT_MAX;
388                         config->autoboot_timeout_sec = (int)timeout;
389                 }
390         }
391
392         populate_network_config(platform, config);
393 }
394
395 static char *iface_config_str(void *ctx, struct interface_config *config)
396 {
397         char *str;
398
399         /* todo: HWADDR size is hardcoded as 6, but we may need to handle
400          * different hardware address formats */
401         str = talloc_asprintf(ctx, "%02x:%02x:%02x:%02x:%02x:%02x,",
402                         config->hwaddr[0], config->hwaddr[1],
403                         config->hwaddr[2], config->hwaddr[3],
404                         config->hwaddr[4], config->hwaddr[5]);
405
406         if (config->ignore) {
407                 str = talloc_asprintf_append(str, "ignore");
408
409         } else if (config->method == CONFIG_METHOD_DHCP) {
410                 str = talloc_asprintf_append(str, "dhcp");
411
412         } else if (config->method == CONFIG_METHOD_STATIC) {
413                 str = talloc_asprintf_append(str, "static,%s%s%s",
414                                 config->static_config.address,
415                                 config->static_config.gateway ? "," : "",
416                                 config->static_config.gateway ?: "");
417         }
418         return str;
419 }
420
421 static char *dns_config_str(void *ctx, const char **dns_servers, int n)
422 {
423         char *str;
424         int i;
425
426         str = talloc_strdup(ctx, "dns,");
427         for (i = 0; i < n; i++) {
428                 str = talloc_asprintf_append(str, "%s%s",
429                                 i == 0 ? "" : ",",
430                                 dns_servers[i]);
431         }
432
433         return str;
434 }
435
436 static void update_string_config(struct platform_powerpc *platform,
437                 const char *name, const char *value)
438 {
439         const char *cur;
440
441         cur = get_param(platform, name);
442
443         /* don't set an empty parameter if it doesn't already exist */
444         if (!cur && !strlen(value))
445                 return;
446
447         set_param(platform, name, value);
448 }
449
450 static void update_network_config(struct platform_powerpc *platform,
451         struct config *config)
452 {
453         unsigned int i;
454         char *val;
455
456         val = talloc_strdup(platform, "");
457
458         for (i = 0; i < config->network.n_interfaces; i++) {
459                 char *iface_str = iface_config_str(platform,
460                                         config->network.interfaces[i]);
461                 val = talloc_asprintf_append(val, "%s%s",
462                                 *val == '\0' ? "" : " ", iface_str);
463                 talloc_free(iface_str);
464         }
465
466         if (config->network.n_dns_servers) {
467                 char *dns_str = dns_config_str(platform,
468                                                 config->network.dns_servers,
469                                                 config->network.n_dns_servers);
470                 val = talloc_asprintf_append(val, "%s%s",
471                                 *val == '\0' ? "" : " ", dns_str);
472                 talloc_free(dns_str);
473         }
474
475         update_string_config(platform, "petitboot,network", val);
476
477         talloc_free(val);
478 }
479
480 static int update_config(struct platform_powerpc *platform,
481                 struct config *config, struct config *defaults)
482 {
483         char *tmp = NULL;
484         const char *val;
485
486         if (config->autoboot_enabled == defaults->autoboot_enabled)
487                 val = "";
488         else
489                 val = config->autoboot_enabled ? "true" : "false";
490         update_string_config(platform, "auto-boot?", val);
491
492         if (config->autoboot_timeout_sec == defaults->autoboot_timeout_sec)
493                 val = "";
494         else
495                 val = tmp = talloc_asprintf(platform, "%d",
496                                 config->autoboot_timeout_sec);
497
498         update_string_config(platform, "petitboot,timeout", val);
499         if (tmp)
500                 talloc_free(tmp);
501
502         update_network_config(platform, config);
503
504         return write_nvram(platform);
505 }
506
507 static int load_config(struct platform *p, struct config *config)
508 {
509         struct platform_powerpc *platform = to_platform_powerpc(p);
510         int rc;
511
512         rc = parse_nvram(platform);
513         if (rc)
514                 return rc;
515
516         populate_config(platform, config);
517
518         return 0;
519 }
520
521 static int save_config(struct platform *p, struct config *config)
522 {
523         struct platform_powerpc *platform = to_platform_powerpc(p);
524         struct config *defaults;
525         int rc;
526
527         defaults = talloc_zero(platform, struct config);
528         config_set_defaults(defaults);
529
530         rc = update_config(platform, config, defaults);
531
532         talloc_free(defaults);
533         return rc;
534 }
535
536 static bool probe(struct platform *p, void *ctx)
537 {
538         struct platform_powerpc *platform;
539         struct stat statbuf;
540         int rc;
541
542         /* we need a device tree and a working nvram binary */
543         rc = stat("/proc/device-tree", &statbuf);
544         if (rc)
545                 return false;
546
547         if (!S_ISDIR(statbuf.st_mode))
548                 return false;
549
550         rc = process_run_simple(ctx, "nvram", "--print-config", NULL);
551         if (!WIFEXITED(rc) || WEXITSTATUS(rc) != 0)
552                 return false;
553
554         platform = talloc(ctx, struct platform_powerpc);
555         list_init(&platform->params);
556
557         p->platform_data = platform;
558         return true;
559 }
560
561 static struct platform platform_powerpc = {
562         .name           = "powerpc",
563         .probe          = probe,
564         .load_config    = load_config,
565         .save_config    = save_config,
566 };
567
568 register_platform(platform_powerpc);