File Index
Files and directories
Sign in
FILE

main.c

File preview with syntax highlighting.

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