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