xref: /openbmc/linux/tools/bootconfig/main.c (revision b868a02e)
1 // SPDX-License-Identifier: GPL-2.0
2 /*
3  * Boot config tool for initrd image
4  */
5 #include <stdio.h>
6 #include <stdlib.h>
7 #include <sys/types.h>
8 #include <sys/stat.h>
9 #include <fcntl.h>
10 #include <unistd.h>
11 #include <string.h>
12 #include <errno.h>
13 #include <endian.h>
14 
15 #include <linux/bootconfig.h>
16 
17 #define pr_err(fmt, ...) fprintf(stderr, fmt, ##__VA_ARGS__)
18 
19 static int xbc_show_value(struct xbc_node *node, bool semicolon)
20 {
21 	const char *val, *eol;
22 	char q;
23 	int i = 0;
24 
25 	eol = semicolon ? ";\n" : "\n";
26 	xbc_array_for_each_value(node, val) {
27 		if (strchr(val, '"'))
28 			q = '\'';
29 		else
30 			q = '"';
31 		printf("%c%s%c%s", q, val, q, xbc_node_is_array(node) ? ", " : eol);
32 		i++;
33 	}
34 	return i;
35 }
36 
37 static void xbc_show_compact_tree(void)
38 {
39 	struct xbc_node *node, *cnode = NULL, *vnode;
40 	int depth = 0, i;
41 
42 	node = xbc_root_node();
43 	while (node && xbc_node_is_key(node)) {
44 		for (i = 0; i < depth; i++)
45 			printf("\t");
46 		if (!cnode)
47 			cnode = xbc_node_get_child(node);
48 		while (cnode && xbc_node_is_key(cnode) && !cnode->next) {
49 			vnode = xbc_node_get_child(cnode);
50 			/*
51 			 * If @cnode has value and subkeys, this
52 			 * should show it as below.
53 			 *
54 			 * key(@node) {
55 			 *      key(@cnode) = value;
56 			 *      key(@cnode) {
57 			 *          subkeys;
58 			 *      }
59 			 * }
60 			 */
61 			if (vnode && xbc_node_is_value(vnode) && vnode->next)
62 				break;
63 			printf("%s.", xbc_node_get_data(node));
64 			node = cnode;
65 			cnode = vnode;
66 		}
67 		if (cnode && xbc_node_is_key(cnode)) {
68 			printf("%s {\n", xbc_node_get_data(node));
69 			depth++;
70 			node = cnode;
71 			cnode = NULL;
72 			continue;
73 		} else if (cnode && xbc_node_is_value(cnode)) {
74 			printf("%s = ", xbc_node_get_data(node));
75 			xbc_show_value(cnode, true);
76 			/*
77 			 * If @node has value and subkeys, continue
78 			 * looping on subkeys with same node.
79 			 */
80 			if (cnode->next) {
81 				cnode = xbc_node_get_next(cnode);
82 				continue;
83 			}
84 		} else {
85 			printf("%s;\n", xbc_node_get_data(node));
86 		}
87 		cnode = NULL;
88 
89 		if (node->next) {
90 			node = xbc_node_get_next(node);
91 			continue;
92 		}
93 		while (!node->next) {
94 			node = xbc_node_get_parent(node);
95 			if (!node)
96 				return;
97 			if (!xbc_node_get_child(node)->next)
98 				continue;
99 			if (depth) {
100 				depth--;
101 				for (i = 0; i < depth; i++)
102 					printf("\t");
103 				printf("}\n");
104 			}
105 		}
106 		node = xbc_node_get_next(node);
107 	}
108 }
109 
110 static void xbc_show_list(void)
111 {
112 	char key[XBC_KEYLEN_MAX];
113 	struct xbc_node *leaf;
114 	const char *val;
115 	int ret;
116 
117 	xbc_for_each_key_value(leaf, val) {
118 		ret = xbc_node_compose_key(leaf, key, XBC_KEYLEN_MAX);
119 		if (ret < 0) {
120 			fprintf(stderr, "Failed to compose key %d\n", ret);
121 			break;
122 		}
123 		printf("%s = ", key);
124 		if (!val || val[0] == '\0') {
125 			printf("\"\"\n");
126 			continue;
127 		}
128 		xbc_show_value(xbc_node_get_child(leaf), false);
129 	}
130 }
131 
132 #define PAGE_SIZE	4096
133 
134 static int load_xbc_fd(int fd, char **buf, int size)
135 {
136 	int ret;
137 
138 	*buf = malloc(size + 1);
139 	if (!*buf)
140 		return -ENOMEM;
141 
142 	ret = read(fd, *buf, size);
143 	if (ret < 0)
144 		return -errno;
145 	(*buf)[size] = '\0';
146 
147 	return ret;
148 }
149 
150 /* Return the read size or -errno */
151 static int load_xbc_file(const char *path, char **buf)
152 {
153 	struct stat stat;
154 	int fd, ret;
155 
156 	fd = open(path, O_RDONLY);
157 	if (fd < 0)
158 		return -errno;
159 	ret = fstat(fd, &stat);
160 	if (ret < 0)
161 		return -errno;
162 
163 	ret = load_xbc_fd(fd, buf, stat.st_size);
164 
165 	close(fd);
166 
167 	return ret;
168 }
169 
170 static int pr_errno(const char *msg, int err)
171 {
172 	pr_err("%s: %d\n", msg, err);
173 	return err;
174 }
175 
176 static int load_xbc_from_initrd(int fd, char **buf)
177 {
178 	struct stat stat;
179 	int ret;
180 	uint32_t size = 0, csum = 0, rcsum;
181 	char magic[BOOTCONFIG_MAGIC_LEN];
182 	const char *msg;
183 
184 	ret = fstat(fd, &stat);
185 	if (ret < 0)
186 		return -errno;
187 
188 	if (stat.st_size < 8 + BOOTCONFIG_MAGIC_LEN)
189 		return 0;
190 
191 	if (lseek(fd, -BOOTCONFIG_MAGIC_LEN, SEEK_END) < 0)
192 		return pr_errno("Failed to lseek for magic", -errno);
193 
194 	if (read(fd, magic, BOOTCONFIG_MAGIC_LEN) < 0)
195 		return pr_errno("Failed to read", -errno);
196 
197 	/* Check the bootconfig magic bytes */
198 	if (memcmp(magic, BOOTCONFIG_MAGIC, BOOTCONFIG_MAGIC_LEN) != 0)
199 		return 0;
200 
201 	if (lseek(fd, -(8 + BOOTCONFIG_MAGIC_LEN), SEEK_END) < 0)
202 		return pr_errno("Failed to lseek for size", -errno);
203 
204 	if (read(fd, &size, sizeof(uint32_t)) < 0)
205 		return pr_errno("Failed to read size", -errno);
206 	size = le32toh(size);
207 
208 	if (read(fd, &csum, sizeof(uint32_t)) < 0)
209 		return pr_errno("Failed to read checksum", -errno);
210 	csum = le32toh(csum);
211 
212 	/* Wrong size error  */
213 	if (stat.st_size < size + 8 + BOOTCONFIG_MAGIC_LEN) {
214 		pr_err("bootconfig size is too big\n");
215 		return -E2BIG;
216 	}
217 
218 	if (lseek(fd, stat.st_size - (size + 8 + BOOTCONFIG_MAGIC_LEN),
219 		  SEEK_SET) < 0)
220 		return pr_errno("Failed to lseek", -errno);
221 
222 	ret = load_xbc_fd(fd, buf, size);
223 	if (ret < 0)
224 		return ret;
225 
226 	/* Wrong Checksum */
227 	rcsum = xbc_calc_checksum(*buf, size);
228 	if (csum != rcsum) {
229 		pr_err("checksum error: %d != %d\n", csum, rcsum);
230 		return -EINVAL;
231 	}
232 
233 	ret = xbc_init(*buf, size, &msg, NULL);
234 	/* Wrong data */
235 	if (ret < 0) {
236 		pr_err("parse error: %s.\n", msg);
237 		return ret;
238 	}
239 
240 	return size;
241 }
242 
243 static void show_xbc_error(const char *data, const char *msg, int pos)
244 {
245 	int lin = 1, col, i;
246 
247 	if (pos < 0) {
248 		pr_err("Error: %s.\n", msg);
249 		return;
250 	}
251 
252 	/* Note that pos starts from 0 but lin and col should start from 1. */
253 	col = pos + 1;
254 	for (i = 0; i < pos; i++) {
255 		if (data[i] == '\n') {
256 			lin++;
257 			col = pos - i;
258 		}
259 	}
260 	pr_err("Parse Error: %s at %d:%d\n", msg, lin, col);
261 
262 }
263 
264 static int init_xbc_with_error(char *buf, int len)
265 {
266 	char *copy = strdup(buf);
267 	const char *msg;
268 	int ret, pos;
269 
270 	if (!copy)
271 		return -ENOMEM;
272 
273 	ret = xbc_init(buf, len, &msg, &pos);
274 	if (ret < 0)
275 		show_xbc_error(copy, msg, pos);
276 	free(copy);
277 
278 	return ret;
279 }
280 
281 static int show_xbc(const char *path, bool list)
282 {
283 	int ret, fd;
284 	char *buf = NULL;
285 	struct stat st;
286 
287 	ret = stat(path, &st);
288 	if (ret < 0) {
289 		ret = -errno;
290 		pr_err("Failed to stat %s: %d\n", path, ret);
291 		return ret;
292 	}
293 
294 	fd = open(path, O_RDONLY);
295 	if (fd < 0) {
296 		ret = -errno;
297 		pr_err("Failed to open initrd %s: %d\n", path, ret);
298 		return ret;
299 	}
300 
301 	ret = load_xbc_from_initrd(fd, &buf);
302 	close(fd);
303 	if (ret < 0) {
304 		pr_err("Failed to load a boot config from initrd: %d\n", ret);
305 		goto out;
306 	}
307 	/* Assume a bootconfig file if it is enough small */
308 	if (ret == 0 && st.st_size <= XBC_DATA_MAX) {
309 		ret = load_xbc_file(path, &buf);
310 		if (ret < 0) {
311 			pr_err("Failed to load a boot config: %d\n", ret);
312 			goto out;
313 		}
314 		if (init_xbc_with_error(buf, ret) < 0)
315 			goto out;
316 	}
317 	if (list)
318 		xbc_show_list();
319 	else
320 		xbc_show_compact_tree();
321 	ret = 0;
322 out:
323 	free(buf);
324 
325 	return ret;
326 }
327 
328 static int delete_xbc(const char *path)
329 {
330 	struct stat stat;
331 	int ret = 0, fd, size;
332 	char *buf = NULL;
333 
334 	fd = open(path, O_RDWR);
335 	if (fd < 0) {
336 		ret = -errno;
337 		pr_err("Failed to open initrd %s: %d\n", path, ret);
338 		return ret;
339 	}
340 
341 	size = load_xbc_from_initrd(fd, &buf);
342 	if (size < 0) {
343 		ret = size;
344 		pr_err("Failed to load a boot config from initrd: %d\n", ret);
345 	} else if (size > 0) {
346 		ret = fstat(fd, &stat);
347 		if (!ret)
348 			ret = ftruncate(fd, stat.st_size
349 					- size - 8 - BOOTCONFIG_MAGIC_LEN);
350 		if (ret)
351 			ret = -errno;
352 	} /* Ignore if there is no boot config in initrd */
353 
354 	close(fd);
355 	free(buf);
356 
357 	return ret;
358 }
359 
360 static int apply_xbc(const char *path, const char *xbc_path)
361 {
362 	char *buf, *data, *p;
363 	size_t total_size;
364 	struct stat stat;
365 	const char *msg;
366 	uint32_t size, csum;
367 	int pos, pad;
368 	int ret, fd;
369 
370 	ret = load_xbc_file(xbc_path, &buf);
371 	if (ret < 0) {
372 		pr_err("Failed to load %s : %d\n", xbc_path, ret);
373 		return ret;
374 	}
375 	size = strlen(buf) + 1;
376 	csum = xbc_calc_checksum(buf, size);
377 
378 	/* Backup the bootconfig data */
379 	data = calloc(size + BOOTCONFIG_ALIGN +
380 		      sizeof(uint32_t) + sizeof(uint32_t) + BOOTCONFIG_MAGIC_LEN, 1);
381 	if (!data)
382 		return -ENOMEM;
383 	memcpy(data, buf, size);
384 
385 	/* Check the data format */
386 	ret = xbc_init(buf, size, &msg, &pos);
387 	if (ret < 0) {
388 		show_xbc_error(data, msg, pos);
389 		free(data);
390 		free(buf);
391 
392 		return ret;
393 	}
394 	printf("Apply %s to %s\n", xbc_path, path);
395 	xbc_get_info(&ret, NULL);
396 	printf("\tNumber of nodes: %d\n", ret);
397 	printf("\tSize: %u bytes\n", (unsigned int)size);
398 	printf("\tChecksum: %d\n", (unsigned int)csum);
399 
400 	/* TODO: Check the options by schema */
401 	xbc_exit();
402 	free(buf);
403 
404 	/* Remove old boot config if exists */
405 	ret = delete_xbc(path);
406 	if (ret < 0) {
407 		pr_err("Failed to delete previous boot config: %d\n", ret);
408 		free(data);
409 		return ret;
410 	}
411 
412 	/* Apply new one */
413 	fd = open(path, O_RDWR | O_APPEND);
414 	if (fd < 0) {
415 		ret = -errno;
416 		pr_err("Failed to open %s: %d\n", path, ret);
417 		free(data);
418 		return ret;
419 	}
420 	/* TODO: Ensure the @path is initramfs/initrd image */
421 	if (fstat(fd, &stat) < 0) {
422 		ret = -errno;
423 		pr_err("Failed to get the size of %s\n", path);
424 		goto out;
425 	}
426 
427 	/* To align up the total size to BOOTCONFIG_ALIGN, get padding size */
428 	total_size = stat.st_size + size + sizeof(uint32_t) * 2 + BOOTCONFIG_MAGIC_LEN;
429 	pad = ((total_size + BOOTCONFIG_ALIGN - 1) & (~BOOTCONFIG_ALIGN_MASK)) - total_size;
430 	size += pad;
431 
432 	/* Add a footer */
433 	p = data + size;
434 	*(uint32_t *)p = htole32(size);
435 	p += sizeof(uint32_t);
436 
437 	*(uint32_t *)p = htole32(csum);
438 	p += sizeof(uint32_t);
439 
440 	memcpy(p, BOOTCONFIG_MAGIC, BOOTCONFIG_MAGIC_LEN);
441 	p += BOOTCONFIG_MAGIC_LEN;
442 
443 	total_size = p - data;
444 
445 	ret = write(fd, data, total_size);
446 	if (ret < total_size) {
447 		if (ret < 0)
448 			ret = -errno;
449 		pr_err("Failed to apply a boot config: %d\n", ret);
450 		if (ret >= 0)
451 			goto out_rollback;
452 	} else
453 		ret = 0;
454 
455 out:
456 	close(fd);
457 	free(data);
458 
459 	return ret;
460 
461 out_rollback:
462 	/* Map the partial write to -ENOSPC */
463 	if (ret >= 0)
464 		ret = -ENOSPC;
465 	if (ftruncate(fd, stat.st_size) < 0) {
466 		ret = -errno;
467 		pr_err("Failed to rollback the write error: %d\n", ret);
468 		pr_err("The initrd %s may be corrupted. Recommend to rebuild.\n", path);
469 	}
470 	goto out;
471 }
472 
473 static int usage(void)
474 {
475 	printf("Usage: bootconfig [OPTIONS] <INITRD>\n"
476 		"Or     bootconfig <CONFIG>\n"
477 		" Apply, delete or show boot config to initrd.\n"
478 		" Options:\n"
479 		"		-a <config>: Apply boot config to initrd\n"
480 		"		-d : Delete boot config file from initrd\n"
481 		"		-l : list boot config in initrd or file\n\n"
482 		" If no option is given, show the bootconfig in the given file.\n");
483 	return -1;
484 }
485 
486 int main(int argc, char **argv)
487 {
488 	char *path = NULL;
489 	char *apply = NULL;
490 	bool delete = false, list = false;
491 	int opt;
492 
493 	while ((opt = getopt(argc, argv, "hda:l")) != -1) {
494 		switch (opt) {
495 		case 'd':
496 			delete = true;
497 			break;
498 		case 'a':
499 			apply = optarg;
500 			break;
501 		case 'l':
502 			list = true;
503 			break;
504 		case 'h':
505 		default:
506 			return usage();
507 		}
508 	}
509 
510 	if ((apply && delete) || (delete && list) || (apply && list)) {
511 		pr_err("Error: You can give one of -a, -d or -l at once.\n");
512 		return usage();
513 	}
514 
515 	if (optind >= argc) {
516 		pr_err("Error: No initrd is specified.\n");
517 		return usage();
518 	}
519 
520 	path = argv[optind];
521 
522 	if (apply)
523 		return apply_xbc(path, apply);
524 	else if (delete)
525 		return delete_xbc(path);
526 
527 	return show_xbc(path, list);
528 }
529