]> git.ozlabs.org Git - petitboot/blob - discover/platform-powerpc.c
discover/udev: fix double free on udev_init failures
[petitboot] / discover / platform-powerpc.c
1
2 #include <assert.h>
3 #include <string.h>
4 #include <stdlib.h>
5 #include <limits.h>
6 #include <errno.h>
7 #include <sys/types.h>
8 #include <sys/wait.h>
9 #include <sys/fcntl.h>
10 #include <sys/stat.h>
11
12 #include <talloc/talloc.h>
13 #include <list/list.h>
14 #include <log/log.h>
15 #include <process/process.h>
16
17 #include "platform.h"
18
19 static const char *partition = "common";
20 static const char *sysparams_dir = "/sys/firmware/opal/sysparams/";
21
22 struct param {
23         char                    *name;
24         char                    *value;
25         bool                    modified;
26         struct list_item        list;
27 };
28
29 struct platform_powerpc {
30         struct list             params;
31 };
32
33 static const char *known_params[] = {
34         "auto-boot?",
35         "petitboot,network",
36         "petitboot,timeout",
37         NULL,
38 };
39
40 #define to_platform_powerpc(p) \
41         (struct platform_powerpc *)(p->platform_data)
42
43 /* a partition max a max size of 64k * 16bytes = 1M */
44 static const int max_partition_size = 64 * 1024 * 16;
45
46 static bool param_is_known(const char *param, unsigned int len)
47 {
48         const char *known_param;
49         unsigned int i;
50
51         for (i = 0; known_params[i]; i++) {
52                 known_param = known_params[i];
53                 if (len == strlen(known_param) &&
54                                 !strncmp(param, known_param, len))
55                         return true;
56         }
57
58         return false;
59 }
60
61 static int parse_nvram_params(struct platform_powerpc *platform,
62                 char *buf, int len)
63 {
64         char *pos, *name, *value;
65         unsigned int paramlen;
66         int i, count;
67
68         /* discard 2 header lines:
69          * "common" partiton"
70          * ------------------
71          */
72         pos = buf;
73         count = 0;
74
75         for (i = 0; i < len; i++) {
76                 if (pos[i] == '\n')
77                         count++;
78                 if (count == 2)
79                         break;
80         }
81
82         if (i == len) {
83                 fprintf(stderr, "failure parsing nvram output\n");
84                 return -1;
85         }
86
87         for (pos = buf + i; pos < buf + len; pos += paramlen + 1) {
88                 unsigned int namelen;
89                 struct param *param;
90                 char *newline;
91
92                 newline = strchr(pos, '\n');
93                 if (!newline)
94                         break;
95
96                 *newline = '\0';
97
98                 paramlen = strlen(pos);
99
100                 name = pos;
101                 value = strchr(pos, '=');
102                 if (!value)
103                         continue;
104
105                 namelen = value - name;
106                 if (namelen == 0)
107                         continue;
108
109                 if (!param_is_known(name, namelen))
110                         continue;
111
112                 value++;
113
114                 param = talloc(platform, struct param);
115                 param->modified = false;
116                 param->name = talloc_strndup(platform, name, namelen);
117                 param->value = talloc_strdup(platform, value);
118                 list_add(&platform->params, &param->list);
119         }
120
121         return 0;
122 }
123
124 static int parse_nvram(struct platform_powerpc *platform)
125 {
126         struct process *process;
127         const char *argv[5];
128         int rc;
129
130         argv[0] = "nvram";
131         argv[1] = "--print-config";
132         argv[2] = "--partition";
133         argv[3] = partition;
134         argv[4] = NULL;
135
136         process = process_create(platform);
137         process->path = "nvram";
138         process->argv = argv;
139         process->keep_stdout = true;
140
141         rc = process_run_sync(process);
142
143         if (rc || !process_exit_ok(process)) {
144                 fprintf(stderr, "nvram process returned "
145                                 "non-zero exit status\n");
146                 rc = -1;
147         } else {
148                 rc = parse_nvram_params(platform, process->stdout_buf,
149                                             process->stdout_len);
150         }
151
152         process_release(process);
153         return rc;
154 }
155
156 static int write_nvram(struct platform_powerpc *platform)
157 {
158         struct process *process;
159         struct param *param;
160         const char *argv[6];
161         int rc;
162
163         argv[0] = "nvram";
164         argv[1] = "--update-config";
165         argv[2] = NULL;
166         argv[3] = "--partition";
167         argv[4] = partition;
168         argv[5] = NULL;
169
170         process = process_create(platform);
171         process->path = "nvram";
172         process->argv = argv;
173
174         list_for_each_entry(&platform->params, param, list) {
175                 char *paramstr;
176
177                 if (!param->modified)
178                         continue;
179
180                 paramstr = talloc_asprintf(platform, "%s=%s",
181                                 param->name, param->value);
182                 argv[2] = paramstr;
183
184                 rc = process_run_sync(process);
185
186                 talloc_free(paramstr);
187
188                 if (rc || !process_exit_ok(process)) {
189                         rc = -1;
190                         pb_log("nvram update process returned "
191                                         "non-zero exit status\n");
192                         break;
193                 }
194         }
195
196         process_release(process);
197         return rc;
198 }
199
200 static const char *get_param(struct platform_powerpc *platform,
201                 const char *name)
202 {
203         struct param *param;
204
205         list_for_each_entry(&platform->params, param, list)
206                 if (!strcmp(param->name, name))
207                         return param->value;
208         return NULL;
209 }
210
211 static void set_param(struct platform_powerpc *platform, const char *name,
212                 const char *value)
213 {
214         struct param *param;
215
216         list_for_each_entry(&platform->params, param, list) {
217                 if (strcmp(param->name, name))
218                         continue;
219
220                 if (!strcmp(param->value, value))
221                         return;
222
223                 talloc_free(param->value);
224                 param->value = talloc_strdup(param, value);
225                 param->modified = true;
226                 return;
227         }
228
229
230         param = talloc(platform, struct param);
231         param->modified = true;
232         param->name = talloc_strdup(platform, name);
233         param->value = talloc_strdup(platform, value);
234         list_add(&platform->params, &param->list);
235 }
236
237 static int parse_hwaddr(struct interface_config *ifconf, char *str)
238 {
239         int i;
240
241         if (strlen(str) != strlen("00:00:00:00:00:00"))
242                 return -1;
243
244         for (i = 0; i < HWADDR_SIZE; i++) {
245                 char byte[3], *endp;
246                 unsigned long x;
247
248                 byte[0] = str[i * 3 + 0];
249                 byte[1] = str[i * 3 + 1];
250                 byte[2] = '\0';
251
252                 x = strtoul(byte, &endp, 16);
253                 if (endp != byte + 2)
254                         return -1;
255
256                 ifconf->hwaddr[i] = x & 0xff;
257         }
258
259         return 0;
260 }
261
262 static int parse_one_interface_config(struct config *config,
263                 char *confstr)
264 {
265         struct interface_config *ifconf;
266         char *tok, *saveptr;
267
268         ifconf = talloc_zero(config, struct interface_config);
269
270         if (!confstr || !strlen(confstr))
271                 goto out_err;
272
273         /* first token should be the mac address */
274         tok = strtok_r(confstr, ",", &saveptr);
275         if (!tok)
276                 goto out_err;
277
278         if (parse_hwaddr(ifconf, tok))
279                 goto out_err;
280
281         /* second token is the method */
282         tok = strtok_r(NULL, ",", &saveptr);
283         if (!tok || !strlen(tok) || !strcmp(tok, "ignore")) {
284                 ifconf->ignore = true;
285
286         } else if (!strcmp(tok, "dhcp")) {
287                 ifconf->method = CONFIG_METHOD_DHCP;
288
289         } else if (!strcmp(tok, "static")) {
290                 ifconf->method = CONFIG_METHOD_STATIC;
291
292                 /* ip/mask, [optional] gateway */
293                 tok = strtok_r(NULL, ",", &saveptr);
294                 if (!tok)
295                         goto out_err;
296                 ifconf->static_config.address =
297                         talloc_strdup(ifconf, tok);
298
299                 tok = strtok_r(NULL, ",", &saveptr);
300                 if (tok) {
301                         ifconf->static_config.gateway =
302                                 talloc_strdup(ifconf, tok);
303                 }
304
305         } else {
306                 pb_log("Unknown network configuration method %s\n", tok);
307                 goto out_err;
308         }
309
310         config->network.interfaces = talloc_realloc(config,
311                         config->network.interfaces,
312                         struct interface_config *,
313                         ++config->network.n_interfaces);
314
315         config->network.interfaces[config->network.n_interfaces - 1] = ifconf;
316
317         return 0;
318 out_err:
319         talloc_free(ifconf);
320         return -1;
321 }
322
323 static int parse_one_dns_config(struct config *config,
324                 char *confstr)
325 {
326         char *tok, *saveptr;
327
328         for (tok = strtok_r(confstr, ",", &saveptr); tok;
329                         tok = strtok_r(NULL, ",", &saveptr)) {
330
331                 char *server = talloc_strdup(config, tok);
332
333                 config->network.dns_servers = talloc_realloc(config,
334                                 config->network.dns_servers, const char *,
335                                 ++config->network.n_dns_servers);
336
337                 config->network.dns_servers[config->network.n_dns_servers - 1]
338                                 = server;
339         }
340
341         return 0;
342 }
343
344 static void populate_network_config(struct platform_powerpc *platform,
345                 struct config *config)
346 {
347         char *val, *saveptr = NULL;
348         const char *cval;
349         int i;
350
351         cval = get_param(platform, "petitboot,network");
352         if (!cval || !strlen(cval))
353                 return;
354
355         val = talloc_strdup(config, cval);
356
357         for (i = 0; ; i++) {
358                 char *tok;
359
360                 tok = strtok_r(i == 0 ? val : NULL, " ", &saveptr);
361                 if (!tok)
362                         break;
363
364                 if (!strncasecmp(tok, "dns,", strlen("dns,")))
365                         parse_one_dns_config(config, tok + strlen("dns,"));
366                 else
367                         parse_one_interface_config(config, tok);
368
369         }
370
371         talloc_free(val);
372 }
373
374 static void populate_config(struct platform_powerpc *platform,
375                 struct config *config)
376 {
377         const char *val;
378         char *end;
379         unsigned long timeout;
380
381         /* if the "auto-boot?' property is present and "false", disable auto
382          * boot */
383         val = get_param(platform, "auto-boot?");
384         config->autoboot_enabled = !val || strcmp(val, "false");
385
386         val = get_param(platform, "petitboot,timeout");
387         if (val) {
388                 timeout = strtoul(val, &end, 10);
389                 if (end != val) {
390                         if (timeout >= INT_MAX)
391                                 timeout = INT_MAX;
392                         config->autoboot_timeout_sec = (int)timeout;
393                 }
394         }
395
396         populate_network_config(platform, config);
397 }
398
399 static char *iface_config_str(void *ctx, struct interface_config *config)
400 {
401         char *str;
402
403         /* todo: HWADDR size is hardcoded as 6, but we may need to handle
404          * different hardware address formats */
405         str = talloc_asprintf(ctx, "%02x:%02x:%02x:%02x:%02x:%02x,",
406                         config->hwaddr[0], config->hwaddr[1],
407                         config->hwaddr[2], config->hwaddr[3],
408                         config->hwaddr[4], config->hwaddr[5]);
409
410         if (config->ignore) {
411                 str = talloc_asprintf_append(str, "ignore");
412
413         } else if (config->method == CONFIG_METHOD_DHCP) {
414                 str = talloc_asprintf_append(str, "dhcp");
415
416         } else if (config->method == CONFIG_METHOD_STATIC) {
417                 str = talloc_asprintf_append(str, "static,%s%s%s",
418                                 config->static_config.address,
419                                 config->static_config.gateway ? "," : "",
420                                 config->static_config.gateway ?: "");
421         }
422         return str;
423 }
424
425 static char *dns_config_str(void *ctx, const char **dns_servers, int n)
426 {
427         char *str;
428         int i;
429
430         str = talloc_strdup(ctx, "dns,");
431         for (i = 0; i < n; i++) {
432                 str = talloc_asprintf_append(str, "%s%s",
433                                 i == 0 ? "" : ",",
434                                 dns_servers[i]);
435         }
436
437         return str;
438 }
439
440 static void update_string_config(struct platform_powerpc *platform,
441                 const char *name, const char *value)
442 {
443         const char *cur;
444
445         cur = get_param(platform, name);
446
447         /* don't set an empty parameter if it doesn't already exist */
448         if (!cur && !strlen(value))
449                 return;
450
451         set_param(platform, name, value);
452 }
453
454 static void update_network_config(struct platform_powerpc *platform,
455         struct config *config)
456 {
457         unsigned int i;
458         char *val;
459
460         val = talloc_strdup(platform, "");
461
462         for (i = 0; i < config->network.n_interfaces; i++) {
463                 char *iface_str = iface_config_str(platform,
464                                         config->network.interfaces[i]);
465                 val = talloc_asprintf_append(val, "%s%s",
466                                 *val == '\0' ? "" : " ", iface_str);
467                 talloc_free(iface_str);
468         }
469
470         if (config->network.n_dns_servers) {
471                 char *dns_str = dns_config_str(platform,
472                                                 config->network.dns_servers,
473                                                 config->network.n_dns_servers);
474                 val = talloc_asprintf_append(val, "%s%s",
475                                 *val == '\0' ? "" : " ", dns_str);
476                 talloc_free(dns_str);
477         }
478
479         update_string_config(platform, "petitboot,network", val);
480
481         talloc_free(val);
482 }
483
484 static int update_config(struct platform_powerpc *platform,
485                 struct config *config, struct config *defaults)
486 {
487         char *tmp = NULL;
488         const char *val;
489
490         if (config->autoboot_enabled == defaults->autoboot_enabled)
491                 val = "";
492         else
493                 val = config->autoboot_enabled ? "true" : "false";
494         update_string_config(platform, "auto-boot?", val);
495
496         if (config->autoboot_timeout_sec == defaults->autoboot_timeout_sec)
497                 val = "";
498         else
499                 val = tmp = talloc_asprintf(platform, "%d",
500                                 config->autoboot_timeout_sec);
501
502         update_string_config(platform, "petitboot,timeout", val);
503         if (tmp)
504                 talloc_free(tmp);
505
506         update_network_config(platform, config);
507
508         return write_nvram(platform);
509 }
510
511 static void set_exclusive_devtype(struct config *config,
512                 enum device_type devtype)
513 {
514         config->n_boot_priorities = 2;
515         config->boot_priorities = talloc_realloc(config,
516                         config->boot_priorities, struct boot_priority,
517                         config->n_boot_priorities);
518         config->boot_priorities[0].type = devtype;
519         config->boot_priorities[0].priority = 0;
520         config->boot_priorities[1].type = DEVICE_TYPE_ANY;
521         config->boot_priorities[1].priority = -1;
522 }
523
524 /* bootdev options that we recognise */
525 enum ipmi_bootdev {
526         IPMI_BOOTDEV_NONE = 0x00,
527         IPMI_BOOTDEV_NETWORK = 0x01,
528         IPMI_BOOTDEV_DISK = 0x2,
529         IPMI_BOOTDEV_CDROM = 0x5,
530         IPMI_BOOTDEV_SETUP = 0x6,
531 };
532
533 static int read_bootdev_sysparam(const char *name, uint8_t *val)
534 {
535         uint8_t buf[2];
536         char path[50];
537         int fd, rc;
538
539         strcpy(path, sysparams_dir);
540         assert(strlen(name) < sizeof(path) - strlen(path));
541         strcat(path, name);
542
543         fd = open(path, O_RDONLY);
544         if (fd < 0) {
545                 pb_debug("powerpc: can't access sysparam %s\n",
546                                 name);
547                 return -1;
548         }
549
550         rc = read(fd, buf, sizeof(buf));
551
552         close(fd);
553
554         /* bootdev definitions should only be one byte in size */
555         if (rc != 1) {
556                 pb_debug("powerpc: sysparam %s read returned %d\n",
557                                 name, rc);
558                 return -1;
559         }
560
561         pb_debug("powerpc: sysparam %s: 0x%02x\n", name, buf[0]);
562
563         switch (buf[0]) {
564         default:
565                 return -1;
566         case IPMI_BOOTDEV_NONE:
567         case IPMI_BOOTDEV_NETWORK:
568         case IPMI_BOOTDEV_DISK:
569         case IPMI_BOOTDEV_CDROM:
570         case IPMI_BOOTDEV_SETUP:
571                 *val = buf[0];
572         }
573
574         return 0;
575 }
576
577 static int write_bootdev_sysparam(const char *name, uint8_t val)
578 {
579         char path[50];
580         int fd, rc;
581
582         strcpy(path, sysparams_dir);
583         assert(strlen(name) < sizeof(path) - strlen(path));
584         strcat(path, name);
585
586         fd = open(path, O_WRONLY);
587         if (fd < 0) {
588                 pb_debug("powerpc: can't access sysparam %s for writing\n",
589                                 name);
590                 return -1;
591         }
592
593         for (;;) {
594                 errno = 0;
595                 rc = write(fd, &val, sizeof(val));
596                 if (rc == sizeof(val)) {
597                         rc = 0;
598                         break;
599                 }
600
601                 if (rc <= 0 && errno != EINTR) {
602                         pb_log("powerpc: error updating sysparam %s: %s",
603                                         name, strerror(errno));
604                         rc = -1;
605                         break;
606                 }
607         }
608
609         close(fd);
610
611         if (!rc)
612                 pb_debug("powerpc: set sysparam %s: 0x%02x\n", name, val);
613
614         return rc;
615 }
616
617 static void parse_opal_sysparams(struct config *config)
618 {
619         uint8_t next_bootdev, default_bootdev;
620         bool next_valid, default_valid;
621         int rc;
622
623         rc = read_bootdev_sysparam("next-boot-device", &next_bootdev);
624         next_valid = rc == 0;
625
626         rc = read_bootdev_sysparam("default-boot-device", &default_bootdev);
627         default_valid = rc == 0;
628
629         /* nothing valid? no need to change the config */
630         if (!next_valid && !default_valid)
631                 return;
632
633         if (next_valid) {
634                 /* invalidate next-boot-device setting */
635                 write_bootdev_sysparam("next-boot-device", 0xff);
636         } else {
637                 next_bootdev = default_bootdev;
638         }
639
640         switch (next_bootdev) {
641         case IPMI_BOOTDEV_NONE:
642                 return;
643         case IPMI_BOOTDEV_DISK:
644                 set_exclusive_devtype(config, DEVICE_TYPE_DISK);
645                 break;
646         case IPMI_BOOTDEV_NETWORK:
647                 set_exclusive_devtype(config, DEVICE_TYPE_NETWORK);
648                 break;
649         case IPMI_BOOTDEV_CDROM:
650                 set_exclusive_devtype(config, DEVICE_TYPE_OPTICAL);
651                 break;
652         case IPMI_BOOTDEV_SETUP:
653                 config->autoboot_enabled = false;
654                 break;
655         }
656 }
657
658 static int load_config(struct platform *p, struct config *config)
659 {
660         struct platform_powerpc *platform = to_platform_powerpc(p);
661         int rc;
662
663         rc = parse_nvram(platform);
664         if (rc)
665                 return rc;
666
667         populate_config(platform, config);
668
669         parse_opal_sysparams(config);
670
671         return 0;
672 }
673
674 static int save_config(struct platform *p, struct config *config)
675 {
676         struct platform_powerpc *platform = to_platform_powerpc(p);
677         struct config *defaults;
678         int rc;
679
680         defaults = talloc_zero(platform, struct config);
681         config_set_defaults(defaults);
682
683         rc = update_config(platform, config, defaults);
684
685         talloc_free(defaults);
686         return rc;
687 }
688
689 static bool probe(struct platform *p, void *ctx)
690 {
691         struct platform_powerpc *platform;
692         struct stat statbuf;
693         int rc;
694
695         /* we need a device tree and a working nvram binary */
696         rc = stat("/proc/device-tree", &statbuf);
697         if (rc)
698                 return false;
699
700         if (!S_ISDIR(statbuf.st_mode))
701                 return false;
702
703         rc = process_run_simple(ctx, "nvram", "--print-config", NULL);
704         if (!WIFEXITED(rc) || WEXITSTATUS(rc) != 0)
705                 return false;
706
707         platform = talloc(ctx, struct platform_powerpc);
708         list_init(&platform->params);
709
710         p->platform_data = platform;
711         return true;
712 }
713
714 static struct platform platform_powerpc = {
715         .name           = "powerpc",
716         .dhcp_arch_id   = 0x000e,
717         .probe          = probe,
718         .load_config    = load_config,
719         .save_config    = save_config,
720 };
721
722 register_platform(platform_powerpc);