582 lines · 11.9 KB · C
RawDownload
// SPDX-License-Identifier: GPL-2.0/* * Boot config tool for initrd image */#include <stdio.h>#include <stdlib.h>#include <sys/types.h>#include <sys/stat.h>#include <fcntl.h>#include <unistd.h>#include <string.h>#include <errno.h>#include <endian.h>#include <assert.h> #include <linux/bootconfig.h> #define pr_err(fmt, ...) fprintf(stderr, fmt, ##__VA_ARGS__) /* Bootconfig footer is [size][csum][BOOTCONFIG_MAGIC]. */#define BOOTCONFIG_FOOTER_SIZE	\	(sizeof(uint32_t) * 2 + BOOTCONFIG_MAGIC_LEN) static int xbc_show_value(struct xbc_node *node, bool semicolon){	const char *val, *eol;	char q;	int i = 0; 	eol = semicolon ? ";\n" : "\n";	xbc_array_for_each_value(node, val) {		if (strchr(val, '"'))			q = '\'';		else			q = '"';		printf("%c%s%c%s", q, val, q, xbc_node_is_array(node) ? ", " : eol);		i++;	}	return i;} static void xbc_show_compact_tree(void){	struct xbc_node *node, *cnode = NULL, *vnode;	int depth = 0, i; 	node = xbc_root_node();	while (node && xbc_node_is_key(node)) {		for (i = 0; i < depth; i++)			printf("\t");		if (!cnode)			cnode = xbc_node_get_child(node);		while (cnode && xbc_node_is_key(cnode) && !cnode->next) {			vnode = xbc_node_get_child(cnode);			/*			 * If @cnode has value and subkeys, this			 * should show it as below.			 *			 * key(@node) {			 *      key(@cnode) = value;			 *      key(@cnode) {			 *          subkeys;			 *      }			 * }			 */			if (vnode && xbc_node_is_value(vnode) && vnode->next)				break;			printf("%s.", xbc_node_get_data(node));			node = cnode;			cnode = vnode;		}		if (cnode && xbc_node_is_key(cnode)) {			printf("%s {\n", xbc_node_get_data(node));			depth++;			node = cnode;			cnode = NULL;			continue;		} else if (cnode && xbc_node_is_value(cnode)) {			printf("%s = ", xbc_node_get_data(node));			xbc_show_value(cnode, true);			/*			 * If @node has value and subkeys, continue			 * looping on subkeys with same node.			 */			if (cnode->next) {				cnode = xbc_node_get_next(cnode);				continue;			}		} else {			printf("%s;\n", xbc_node_get_data(node));		}		cnode = NULL; 		if (node->next) {			node = xbc_node_get_next(node);			continue;		}		while (!node->next) {			node = xbc_node_get_parent(node);			if (!node)				return;			if (!xbc_node_get_child(node)->next)				continue;			if (depth) {				depth--;				for (i = 0; i < depth; i++)					printf("\t");				printf("}\n");			}		}		node = xbc_node_get_next(node);	}} static void xbc_show_list(void){	char key[XBC_KEYLEN_MAX];	struct xbc_node *leaf;	const char *val;	int ret; 	xbc_for_each_key_value(leaf, val) {		ret = xbc_node_compose_key(leaf, key, XBC_KEYLEN_MAX);		if (ret < 0) {			fprintf(stderr, "Failed to compose key %d\n", ret);			break;		}		printf("%s = ", key);		if (!val || val[0] == '\0') {			printf("\"\"\n");			continue;		}		xbc_show_value(xbc_node_get_child(leaf), false);	}} #define PAGE_SIZE	4096 static int load_xbc_fd(int fd, char **buf, int size){	int ret; 	*buf = malloc(size + 1);	if (!*buf)		return -ENOMEM; 	ret = read(fd, *buf, size);	if (ret < 0)		return -errno;	(*buf)[size] = '\0'; 	return ret;} /* Return the read size or -errno */static int load_xbc_file(const char *path, char **buf){	struct stat stat;	int fd, ret; 	fd = open(path, O_RDONLY);	if (fd < 0)		return -errno;	ret = fstat(fd, &stat);	if (ret < 0) {		ret = -errno;		close(fd);		return ret;	} 	ret = load_xbc_fd(fd, buf, stat.st_size); 	close(fd); 	return ret;} static int pr_errno(const char *msg, int err){	pr_err("%s: %d\n", msg, err);	return err;} static int load_xbc_from_initrd(int fd, char **buf){	struct stat stat;	int ret;	uint32_t size = 0, csum = 0, rcsum;	char magic[BOOTCONFIG_MAGIC_LEN];	const char *msg; 	ret = fstat(fd, &stat);	if (ret < 0)		return -errno; 	if (stat.st_size < BOOTCONFIG_FOOTER_SIZE)		return 0; 	if (lseek(fd, -(off_t)BOOTCONFIG_MAGIC_LEN, SEEK_END) < 0)		return pr_errno("Failed to lseek for magic", -errno); 	if (read(fd, magic, BOOTCONFIG_MAGIC_LEN) < 0)		return pr_errno("Failed to read", -errno); 	/* Check the bootconfig magic bytes */	if (memcmp(magic, BOOTCONFIG_MAGIC, BOOTCONFIG_MAGIC_LEN) != 0)		return 0; 	if (lseek(fd, -(off_t)BOOTCONFIG_FOOTER_SIZE, SEEK_END) < 0)		return pr_errno("Failed to lseek for size", -errno); 	if (read(fd, &size, sizeof(uint32_t)) < 0)		return pr_errno("Failed to read size", -errno);	size = le32toh(size); 	if (read(fd, &csum, sizeof(uint32_t)) < 0)		return pr_errno("Failed to read checksum", -errno);	csum = le32toh(csum); 	/* Wrong size error  */	if (stat.st_size < size + BOOTCONFIG_FOOTER_SIZE) {		pr_err("bootconfig size is too big\n");		return -E2BIG;	} 	if (lseek(fd, stat.st_size - (size + BOOTCONFIG_FOOTER_SIZE),		  SEEK_SET) < 0)		return pr_errno("Failed to lseek", -errno); 	ret = load_xbc_fd(fd, buf, size);	if (ret < 0)		return ret; 	/* Wrong Checksum */	rcsum = xbc_calc_checksum(*buf, size);	if (csum != rcsum) {		pr_err("checksum error: %u != %u\n", csum, rcsum);		return -EINVAL;	} 	ret = xbc_init(*buf, size, &msg, NULL);	/* Wrong data */	if (ret < 0) {		pr_err("parse error: %s.\n", msg);		return ret;	} 	return size;} static void show_xbc_error(const char *data, const char *msg, int pos){	int lin = 1, col, i; 	if (pos < 0) {		pr_err("Error: %s.\n", msg);		return;	} 	/* Note that pos starts from 0 but lin and col should start from 1. */	col = pos + 1;	for (i = 0; i < pos; i++) {		if (data[i] == '\n') {			lin++;			col = pos - i;		}	}	pr_err("Parse Error: %s at %d:%d\n", msg, lin, col); } static int init_xbc_with_error(char *buf, int len){	char *copy = strdup(buf);	const char *msg;	int ret, pos; 	if (!copy)		return -ENOMEM; 	ret = xbc_init(buf, len, &msg, &pos);	if (ret < 0)		show_xbc_error(copy, msg, pos);	free(copy); 	return ret;} static int show_xbc_kernel_cmdline(void){	struct xbc_node *root;	char *buf = NULL;	int len, ret; 	root = xbc_find_node("kernel");	if (!root)		return 0;	/* no kernel.* keys: emit empty output */ 	len = xbc_snprint_cmdline(NULL, 0, root);	if (len < 0) {		pr_err("Failed to size cmdline output: %d\n", len);		return len;	}	if (len == 0)		return 0; 	buf = malloc(len + 1);	if (!buf)		return -ENOMEM; 	ret = xbc_snprint_cmdline(buf, len + 1, root);	if (ret < 0) {		pr_err("Failed to render cmdline output: %d\n", ret);		free(buf);		return ret;	} 	fputs(buf, stdout);	free(buf);	return 0;} static int show_xbc(const char *path, bool list, bool render_cmdline){	int ret, fd;	char *buf = NULL;	struct stat st; 	ret = stat(path, &st);	if (ret < 0) {		ret = -errno;		pr_err("Failed to stat %s: %d\n", path, ret);		return ret;	} 	fd = open(path, O_RDONLY);	if (fd < 0) {		ret = -errno;		pr_err("Failed to open initrd %s: %d\n", path, ret);		return ret;	} 	ret = load_xbc_from_initrd(fd, &buf);	close(fd);	if (ret < 0) {		pr_err("Failed to load a boot config from initrd: %d\n", ret);		goto out;	}	/* Assume a bootconfig file if it is enough small */	if (ret == 0 && st.st_size <= XBC_DATA_MAX) {		ret = load_xbc_file(path, &buf);		if (ret < 0) {			pr_err("Failed to load a boot config: %d\n", ret);			goto out;		}		if (init_xbc_with_error(buf, ret) < 0)			goto out;	}	if (render_cmdline)		ret = show_xbc_kernel_cmdline();	else if (list)		xbc_show_list();	else		xbc_show_compact_tree();	if (ret > 0)		ret = 0;out:	free(buf); 	return ret;} static int delete_xbc(const char *path){	struct stat stat;	int ret = 0, fd, size;	char *buf = NULL; 	fd = open(path, O_RDWR);	if (fd < 0) {		ret = -errno;		pr_err("Failed to open initrd %s: %d\n", path, ret);		return ret;	} 	size = load_xbc_from_initrd(fd, &buf);	if (size < 0) {		ret = size;		pr_err("Failed to load a boot config from initrd: %d\n", ret);	} else if (size > 0) {		ret = fstat(fd, &stat);		if (!ret)			ret = ftruncate(fd, stat.st_size					- size - BOOTCONFIG_FOOTER_SIZE);		if (ret)			ret = -errno;	} /* Ignore if there is no boot config in initrd */ 	close(fd);	free(buf); 	return ret;} static int apply_xbc(const char *path, const char *xbc_path){	struct {		uint32_t size;		uint32_t csum;		char magic[BOOTCONFIG_MAGIC_LEN];	} footer;	char *buf, *data;	size_t total_size;	struct stat stat;	const char *msg;	uint32_t size, csum;	int pos, pad;	int ret, fd; 	ret = load_xbc_file(xbc_path, &buf);	if (ret < 0) {		pr_err("Failed to load %s : %d\n", xbc_path, ret);		return ret;	}	size = strlen(buf) + 1;	csum = xbc_calc_checksum(buf, size); 	/* Backup the bootconfig data */	data = calloc(size + BOOTCONFIG_ALIGN + BOOTCONFIG_FOOTER_SIZE, 1);	if (!data) {		free(buf);		return -ENOMEM;	}	memcpy(data, buf, size); 	/* Check the data format */	ret = xbc_init(buf, size, &msg, &pos);	if (ret < 0) {		show_xbc_error(data, msg, pos);		free(data);		free(buf); 		return ret;	}	printf("Apply %s to %s\n", xbc_path, path);	xbc_get_info(&ret, NULL);	printf("\tNumber of nodes: %d\n", ret);	printf("\tSize: %u bytes\n", (unsigned int)size);	printf("\tChecksum: %u\n", (unsigned int)csum); 	/* TODO: Check the options by schema */	xbc_exit();	free(buf); 	/* Remove old boot config if exists */	ret = delete_xbc(path);	if (ret < 0) {		pr_err("Failed to delete previous boot config: %d\n", ret);		free(data);		return ret;	} 	/* Apply new one */	fd = open(path, O_RDWR | O_APPEND);	if (fd < 0) {		ret = -errno;		pr_err("Failed to open %s: %d\n", path, ret);		free(data);		return ret;	}	/* TODO: Ensure the @path is initramfs/initrd image */	if (fstat(fd, &stat) < 0) {		ret = -errno;		pr_err("Failed to get the size of %s\n", path);		goto out;	} 	/* To align up the total size to BOOTCONFIG_ALIGN, get padding size */	total_size = stat.st_size + size + BOOTCONFIG_FOOTER_SIZE;	pad = ((total_size + BOOTCONFIG_ALIGN - 1) & (~BOOTCONFIG_ALIGN_MASK)) - total_size;	size += pad; 	/* Add a footer */	footer.size = htole32(size);	footer.csum = htole32(csum);	memcpy(footer.magic, BOOTCONFIG_MAGIC, BOOTCONFIG_MAGIC_LEN);	static_assert(sizeof(footer) == BOOTCONFIG_FOOTER_SIZE);	memcpy(data + size, &footer, BOOTCONFIG_FOOTER_SIZE); 	total_size = size + BOOTCONFIG_FOOTER_SIZE; 	ret = write(fd, data, total_size);	if (ret < total_size) {		if (ret < 0)			ret = -errno;		pr_err("Failed to apply a boot config: %d\n", ret);		if (ret >= 0)			goto out_rollback;	} else		ret = 0;

Lines 1–500 of 582.