[PATCH 4/4] add kernel-install tool for bootloader Spec

Sascha Hauer s.hauer at pengutronix.de
Mon Sep 30 05:43:08 EDT 2013


This adds a tool for installing kernels according to the bootloader
spec. systemd already has a similar tool, but it is limited to installing
kernels on the currently running system. The barebox kernel-install
tool instead can also be used to install kernels on removable media on a
development host for cross development. It is compiled in two variants,
as 'kernel-install' for the host and as 'kernel-install-target' using
$CROSS_COMPILE.

Signed-off-by: Sascha Hauer <s.hauer at pengutronix.de>
---
 scripts/Makefile         |    2 +
 scripts/kernel-install.c | 1399 ++++++++++++++++++++++++++++++++++++++++++++++
 2 files changed, 1401 insertions(+)
 create mode 100644 scripts/kernel-install.c

diff --git a/scripts/Makefile b/scripts/Makefile
index 2c43f66..a5cdf30 100644
--- a/scripts/Makefile
+++ b/scripts/Makefile
@@ -8,6 +8,7 @@ hostprogs-y                      += bin2c
 hostprogs-y                      += mkimage
 hostprogs-y                      += fix_size
 hostprogs-y                      += bareboxenv
+hostprogs-y                      += kernel-install
 hostprogs-$(CONFIG_KALLSYMS)     += kallsyms
 hostprogs-$(CONFIG_ARCH_MVEBU)   += kwbimage kwboot
 hostprogs-$(CONFIG_ARCH_NETX)    += gen_netx_image
@@ -24,6 +25,7 @@ subdir-$(CONFIG_X86)		+= setupmbr
 subdir-$(CONFIG_DTC)		+= dtc
 
 targetprogs-$(CONFIG_BAREBOXENV_TARGET) += bareboxenv-target
+targetprogs-y += kernel-install-target
 
 # Let clean descend into subdirs
 subdir-	+= basic kconfig setupmbr
diff --git a/scripts/kernel-install.c b/scripts/kernel-install.c
new file mode 100644
index 0000000..ed9f8bd
--- /dev/null
+++ b/scripts/kernel-install.c
@@ -0,0 +1,1399 @@
+/*
+ * kernel-install - install a kernel according to the bootloader spec:
+ * http://www.freedesktop.org/wiki/Specifications/BootLoaderSpec/
+ *
+ * Copyright (C) 2013 Sascha Hauer, Pengutronix <s.hauer at pengutronix.de>
+ *
+ * This tool is useful for installing kernels in a bootloader spec
+ * conformant way. It can be used to install kernels for the currently
+ * running system, but also to install kernels for another system which
+ * is available as a removable media such as an SD card.
+ *
+ * Some examples:
+ *
+ * kernel-install --add --kernel-version=3.11 --kernel=/somewhere/zImage \
+ *		--title "Linux-3.11"
+ *
+ * This is the simplest example. It assumes we want to install a kernel for the
+ * currently running system. Usually the kernel should get some commandline
+ * options which can be passed using the -o option. Devicetree and initrd can be
+ * specified with --devicetree=<file> or --initrd=<file>.
+ *
+ * For preparing boot media from another host (or the same host, but another
+ * rootfs) things get slightly more complicated. Apart from the image files
+ * kernel-install generally needs a machine-id (which is, in native mode, read
+ * from /etc/machine-id) and access to /boot of newly installed entry.
+ * /boot can be specified in different ways:
+ *
+ * --boot=/boot             - specify the path where /boot is mounted
+ * --boot=/dev/sdd1         - specify the partition which contains /boot.
+ *                            It is mounted using pmount or mount
+ * --device=/dev/sdd        - If this option is given kernel-install tries
+ *                            to find /boot on this device using the mechanisms
+ *                            described in the bootloader spec.
+ *
+ * machine-id can be specified with:
+ * --machine-id=<machine-id>    - explicitly specify a machine-id
+ * --root=/root or
+ * --root=/dev/sdd2             - specify where the root of the installed system
+ *                                can be found. The machine id is then taken
+ *                                from /etc/machine-id from this filesystem/path
+ *
+ * Optionally kernel-install can automatically generate a root=PARTUUID= kernel
+ * parameter for the kernel to find its root filesystem. This is done with the
+ * --add-root-option parameter. Additionally the --device= parameter must be
+ * specified so that kernel-install can determine the UUID of the device.
+ *
+ * Now for an example using most of the available features:
+ *
+ * kernel-install --device=/dev/sdd --root=/dev/sdd2 --title="Linux-3.12" \
+ *	--kernel-version="3.12" --kernel=/some/zImage \
+ *	--devicetree=/some/devicetree --initrd=/some/initrd \
+ *	--add-root-option --options="console=ttyS0,115200"
+ *
+ * This would install a kernel on /dev/sdd. The /boot partition would be found
+ * automatically, the root partition has to be specified due to the usage of
+ * --add-root-option
+ *
+ * BUGS:
+ * - Currently only DOS partition tables are supported. There's no support
+ *   for GPT yet.
+ *
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License as
+ * published by the Free Software Foundation; either version 2 of
+ * the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ */
+
+#define _GNU_SOURCE
+#include <stdio.h>
+#include <string.h>
+#include <stdlib.h>
+#include <errno.h>
+#include <sys/types.h>
+#include <sys/stat.h>
+#include <unistd.h>
+#include <stdarg.h>
+#include <getopt.h>
+#include <fcntl.h>
+#include <stdint.h>
+#include <libgen.h>
+#include <ctype.h>
+#include <dirent.h>
+
+static int verbose;
+static int force;
+static int interactive = 1;
+static int remove_kernel_num = -1;
+static int set_default_num = -1;
+static int set_once_num = -1;
+
+static char *host_root_path, *kernel_image, *options, *device_path;
+static char *host_boot_path, *kernel_version, *title, *machine_id;
+static char *initrd_image, *devicetree_image;
+static char *host_mount_root_path, *host_mount_boot_path;
+
+static uint32_t nt_disk_signature;
+static int root_partition_num;
+
+struct loader_entry {
+	char *title;
+	char *machine_id;
+	char *options;
+	char *kernel;
+	char *devicetree;
+	char *initrd;
+	char *version;
+	char *host_path;
+	char *config_file;
+	int num;
+	struct loader_entry *next;
+};
+
+static struct loader_entry *loader_entries;
+
+static void loader_entry_var_set(struct loader_entry *e, const char *name, char *val)
+{
+	if (!strcmp(name, "title"))
+		e->title = val;
+	else if (!strcmp(name, "machine-id"))
+		e->machine_id = val;
+	else if (!strcmp(name, "options"))
+		e->options = val;
+	else if (!strcmp(name, "linux"))
+		e->kernel = val;
+	else if (!strcmp(name, "devicetree"))
+		e->devicetree = val;
+	else if (!strcmp(name, "initrd"))
+		e->initrd = val;
+	else if (!strcmp(name, "version"))
+		e->version = val;
+}
+
+static struct loader_entry *loader_entry_open(const char *path)
+{
+	FILE *f;
+	struct loader_entry *e;
+	int ret;
+
+	f = fopen(path, "r");
+	if (!f)
+		return NULL;
+
+	e = calloc(sizeof(*e), 1);
+
+	e->host_path = strdup(path);
+
+	while (1) {
+		char *line = NULL;
+		char *name, *val, *end;
+		size_t s;
+
+		ret = getline(&line, &s, f);
+		if (ret < 0)
+			break;
+
+		if (line[strlen(line) - 1] == '\n')
+			line[strlen(line) - 1] = '\0';
+
+		name = line;
+		end = name;
+
+		while (*end && (*end != ' ' && *end != '\t'))
+			end++;
+
+		if (*line == '#') {
+			free(line);
+			continue;
+		}
+
+		if (!*end) {
+			loader_entry_var_set(e, name, NULL);
+			continue;
+		}
+
+		*end = 0;
+
+		end++;
+
+		while (*end == ' ' || *end == '\t')
+			end++;
+
+		if (!*end) {
+			loader_entry_var_set(e, name, NULL);
+			continue;
+		}
+
+		val = end;
+
+		loader_entry_var_set(e, name, val);
+	}
+
+	fclose(f);
+
+	return e;
+}
+
+/*
+ * printf wrapper around 'system'
+ */
+static int systemp(const char *fmt, ...)
+{
+	va_list args;
+	char *buf;
+	int ret;
+
+	va_start (args, fmt);
+
+	ret = vasprintf(&buf, fmt, args);
+
+	va_end (args);
+
+	if (ret < 0) {
+		fprintf(stderr, "out of memory\n");
+		exit(1);
+	}
+
+	if (verbose)
+		fprintf(stderr, "executing command: %s\n", buf);
+
+	ret = system(buf);
+
+	if (ret > 0)
+		ret = WEXITSTATUS(ret);
+
+	free(buf);
+
+	return ret;
+}
+
+static void *safe_asprintf(const char *fmt, ...)
+{
+	va_list args;
+	char *buf = NULL;
+	int ret;
+
+	va_start (args, fmt);
+
+	ret = vasprintf(&buf, fmt, args);
+
+	va_end (args);
+
+	if (ret < 0) {
+		fprintf(stderr, "out of memory\n");
+		exit(1);
+	}
+
+	return buf;
+}
+
+static void verbose_printf(const char *fmt, ...)
+{
+	va_list args;
+
+	if (!verbose)
+		return;
+
+	va_start (args, fmt);
+
+	vprintf(fmt, args);
+
+	va_end (args);
+}
+
+static int make_directory(const char *dir)
+{
+	char *s = strdup(dir);
+	char *path = s;
+	char c;
+	int ret = 0;
+
+	do {
+		c = 0;
+
+		/* Bypass leading non-'/'s and then subsequent '/'s. */
+		while (*s) {
+			if (*s == '/') {
+				do {
+					++s;
+				} while (*s == '/');
+				c = *s;		/* Save the current char */
+				*s = 0;		/* and replace it with nul. */
+				break;
+			}
+			++s;
+		}
+
+		if (mkdir(path, 0777) < 0) {
+
+			/* If we failed for any other reason than the directory
+			 * already exists, output a diagnostic and return -1.*/
+			if (errno != EEXIST) {
+				ret = -errno;
+				break;
+			}
+		}
+		if (!c)
+			goto out;
+
+		/* Remove any inserted nul from the path (recursive mode). */
+		*s = c;
+
+	} while (1);
+
+out:
+	free(path);
+	if (ret)
+		errno = -ret;
+	return ret;
+}
+
+static int append_option(const char *fmt, ...)
+{
+	va_list args;
+	char *buf;
+	int ret;
+
+	va_start (args, fmt);
+
+	ret = vasprintf(&buf,  fmt, args);
+
+	va_end (args);
+
+	if (ret < 0) {
+		fprintf(stderr, "out of memory\n");
+		exit (1);
+	}
+
+	if (options) {
+		char *new_options = safe_asprintf("%s %s", options, buf);
+		free(options);
+		free(buf);
+		options = new_options;
+	} else {
+		options = buf;
+	}
+
+	return 0;
+}
+
+char *get_mount_path(char *path)
+{
+	FILE *f;
+	int ret;
+	char *out_path = NULL;
+
+	f = fopen("/proc/mounts", "r");
+	if (!f) {
+		fprintf(stderr, "Cannot open /proc/mounts: %s\n", strerror(errno));
+		return NULL;
+	}
+
+	while (1) {
+		char *line = NULL, *delim;
+		size_t insize;
+
+		ret = getline(&line, &insize, f);
+		if (ret < 0)
+			break;
+
+		delim = strchr(line, ' ');
+		if (!delim) {
+			free(line);
+			continue;
+		}
+
+		*delim = 0;
+
+		if (strcmp(line, path)) {
+			free(line);
+			continue;
+		}
+
+		delim++;
+
+		out_path = delim;
+
+		delim = strchr(delim, ' ');
+		if (!delim) {
+			free(line);
+			out_path = NULL;
+			break;
+		}
+
+		*delim = 0;
+		break;
+	}
+
+	fclose(f);
+
+	if (out_path)
+		return strdup(out_path);
+	else
+		return NULL;
+}
+
+enum mount_type {
+	MOUNT_UNKNOWN,
+	MOUNT_PMOUNT,
+	MOUNT_MOUNT,
+	MOUNT_ERROR,
+};
+
+static enum mount_type get_mount_type(void)
+{
+	static enum mount_type mount_type = MOUNT_UNKNOWN;
+	int ret;
+	uid_t uid;
+
+	if (mount_type != MOUNT_UNKNOWN)
+		return mount_type;
+
+	ret = systemp("which pmount");
+	if (!ret) {
+		mount_type = MOUNT_PMOUNT;
+		goto out;
+	}
+
+	verbose_printf("pmount not found\n");
+
+	uid = getuid();
+	if (uid == 0) {
+		mount_type = MOUNT_MOUNT;
+		goto out;
+	}
+
+	fprintf(stderr, "'pmount' not found and I am not root. Unable to mount\n");
+	mount_type = MOUNT_ERROR;
+out:
+	return mount_type;
+}
+
+static char *mount_path_pmount(char *in_path)
+{
+	char *out_path;
+	int ret;
+
+	ret = systemp("pmount %s", in_path);
+	if (ret) {
+		fprintf(stderr, "failed to pmount %s\n", in_path);
+		return NULL;
+	}
+
+	out_path = safe_asprintf("/media/%s", basename(in_path));
+
+	return out_path;
+}
+
+static char *mount_path_mount(char *in_path)
+{
+	char *out_path, *str;
+	int ret;
+
+	str = safe_asprintf("/tmp/kernel-install-%s-XXXXXX", basename(in_path));
+
+	out_path = mkdtemp(str);
+	if (!out_path) {
+		fprintf(stderr, "unable to create temporary directory: %s\n",
+				strerror(errno));
+		free(str);
+		return NULL;
+	}
+
+	ret = systemp("mount %s %s", in_path, out_path);
+	if (ret) {
+		fprintf(stderr, "failed to mount %s: %s\n", in_path,
+				strerror(errno));
+		rmdir(out_path);
+		free(out_path);
+		return NULL;
+	}
+
+	return out_path;
+}
+
+/*
+ * mount_path - make a device or directory available.
+ * @in_path:   the input device or directory
+ * @newmount:  if this function mounts a device, this variable is true
+ *             on exit.
+ *
+ * returns the path under which the device is available.
+ *
+ * We do our best to make a device or directory available. If the input
+ * path is a directory, just return it. If it is a block device and the
+ * device is already mounted according to /proc/mounts, return the path
+ * where it's mounted. If it's not mounted already try to mount it. We
+ * first try pmount if that's available. If not, see if we are root and
+ * can use regular 'mount'.
+ */
+char *mount_path(char *in_path, int *newmount)
+{
+	struct stat s;
+	int ret;
+	char *out_path;
+
+	*newmount = 0;
+
+	ret = stat(in_path, &s);
+	if (ret) {
+		fprintf(stderr, "Cannot mount %s: %s\n", in_path, strerror(errno));
+		return NULL;
+	}
+
+	if (S_ISDIR(s.st_mode))
+		return strdup(in_path);
+
+	if (!S_ISBLK(s.st_mode)) {
+		fprintf(stderr, "%s is not a directory and not a block device\n",
+				in_path);
+		return NULL;
+	}
+
+	out_path = get_mount_path(in_path);
+	if (out_path) {
+		verbose_printf("%s already mounted at %s\n", in_path, out_path);
+		return out_path;
+	}
+
+	switch (get_mount_type()) {
+	default:
+	case MOUNT_ERROR:
+		return NULL;
+	case MOUNT_PMOUNT:
+		out_path = mount_path_pmount(in_path);
+		if (out_path) {
+			*newmount = 1;
+			return out_path;
+		}
+		return NULL;
+	case MOUNT_MOUNT:
+		out_path = mount_path_mount(in_path);
+		if (out_path) {
+			*newmount = 1;
+			return out_path;
+		}
+		return NULL;
+	}
+
+
+	return NULL;
+}
+
+void detect_root_partition_num(char *device)
+{
+	struct stat s;
+	int ret;
+	char digit;
+
+	ret = stat(device, &s);
+	if (ret) {
+		fprintf(stderr, "%s: %s\n", device, strerror(errno));
+		return;
+	}
+
+	if (!S_ISBLK(s.st_mode))
+		return;
+
+	digit = device[strlen(device) - 1];
+	if (!isdigit(digit))
+		return;
+
+	root_partition_num = digit - '0';
+	printf("rootnum: %d\n", root_partition_num);
+}
+
+void umount_path(const char *path)
+{
+	switch (get_mount_type()) {
+	case MOUNT_PMOUNT:
+		systemp("pumount %s", path);
+		break;
+	case MOUNT_MOUNT:
+		systemp("umount %s", path);
+		break;
+	default:
+	case MOUNT_ERROR:
+		break;
+	}
+}
+
+int determine_root_boot_path(const char *device_path)
+{
+	unsigned char *buf;
+	int ret, fd, i;
+	char *partname;
+	struct stat s;
+
+	buf = malloc(512);
+	if (!buf)
+		return -ENOMEM;
+
+	fd = open(device_path, O_RDONLY);
+	if (fd < 0) {
+		perror("open");
+		return -errno;
+	}
+
+	ret = read(fd, buf, 512);
+	if (ret < 512)
+		perror("read");
+
+	close(fd);
+
+	if (ret < 512)
+		return -errno;
+
+	if (buf[510] != 0x55 || buf[511] != 0xaa) {
+		fprintf(stderr, "not a DOS bootsector\n");
+		return EINVAL;
+	}
+
+	nt_disk_signature = buf[440] | (buf[441] << 8) | (buf[442] << 16) | (buf[443] << 24);
+
+	for (i = 0; i < 4; i++) {
+		uint8_t type = buf[446 + 4 + i * 64];
+		if (type == 0xea) {
+			verbose_printf("using partition %d as /boot\n", i);
+			break;
+		}
+	}
+
+	if (i == 4 && !host_boot_path) {
+		fprintf(stderr, "cannot find a valid /boot partition on %s\n",
+				device_path);
+		return -EINVAL;
+	}
+
+	/* /dev/sdgx */
+	partname = safe_asprintf("%s%c", device_path, '1' + i);
+	ret = stat(partname, &s);
+	if (!ret) {
+		host_boot_path = partname;
+		return 0;
+	}
+
+	free(partname);
+
+	/* /dev/mmcblkxpy */
+	partname = safe_asprintf("%sp%c", device_path, '1' + i);
+	ret = stat(partname, &s);
+	if (!ret) {
+		host_boot_path = partname;
+		return 0;
+	}
+
+	free(partname);
+
+	/* /dev/disk/by-xxx/xxx-party */
+	partname = safe_asprintf("%s-part%c", device_path, '1' + i);
+	ret = stat(partname, &s);
+	if (!ret) {
+		host_boot_path = partname;
+		return 0;
+	}
+
+	free(partname);
+
+	return 0;
+}
+
+int determine_machine_id(void)
+{
+	char buf[512] = {};
+	int fd, ret;
+	char *path, *tmp;
+
+	if (machine_id)
+		return 0;
+
+	if (!host_root_path)
+		return -EINVAL;
+
+	path = safe_asprintf("%s/etc/machine-id", host_root_path);
+
+	fd = open(path, O_RDONLY);
+	if (fd < 0) {
+		perror("open");
+		return -errno;
+	}
+
+	ret = read(fd, buf, 512);
+	if (ret < 0) {
+		perror("read");
+		goto out;
+	}
+
+	if (ret == 512) {
+		fprintf(stderr, "machine-id file too big\n");
+		ret = -EINVAL;
+		goto out;
+	}
+
+	tmp = buf;
+	while (*tmp) {
+		if (!isalnum(*tmp)) {
+			*tmp = '\0';
+			break;
+		}
+		tmp++;
+	}
+
+	machine_id = strdup(buf);
+
+	ret = 0;
+out:
+	close(fd);
+	return ret;
+}
+
+void cleanup(void)
+{
+	if (host_mount_root_path)
+		umount_path(host_mount_root_path);
+	if (host_mount_boot_path)
+		umount_path(host_mount_boot_path);
+}
+
+int yesno(const char *str)
+{
+	int ch;
+
+	if (force)
+		return 0;
+	if (!interactive)
+		return 1;
+	printf("%s", str);
+
+	ch = getchar();
+	if (ch == 'y')
+		return 0;
+	return 1;
+}
+
+static int do_add_kernel(void)
+{
+	char *conf_path, *conf_file, *conf_dir, *images_dir;
+	char *kernel_path, *host_images_dir, *host_kernel_path;
+	char *initrd_path, *host_initrd_path;
+	char *devicetree_path, *host_devicetree_path;
+	int ret, fd;
+	struct stat s;
+
+	ret = determine_machine_id();
+	if (ret) {
+		fprintf(stderr, "failed to determine machine-id\n");
+		return -EINVAL;
+	}
+
+	if (!machine_id) {
+		fprintf(stderr, "No machine-id given\n");
+		return -EINVAL;
+	}
+
+	if (!kernel_version) {
+		fprintf(stderr, "no Kernel version given\n");
+		return -EINVAL;
+	}
+
+	if (!kernel_image) {
+		fprintf(stderr, "No Linux image given\n");
+		return -EINVAL;
+	}
+
+	conf_dir = safe_asprintf("%s/loader/entries", host_boot_path);
+	conf_file = safe_asprintf("%s-%s.conf", machine_id, kernel_version);
+	conf_path = safe_asprintf("%s/%s", conf_dir, conf_file);
+	images_dir = safe_asprintf("%s/%s", machine_id, kernel_version);
+	host_images_dir = safe_asprintf("%s/%s", host_boot_path, images_dir);
+	kernel_path = safe_asprintf("%s/linux", images_dir);
+	host_kernel_path = safe_asprintf("%s/linux", host_images_dir);
+	initrd_path = safe_asprintf("%s/initrd", images_dir);
+	host_initrd_path = safe_asprintf("%s/initrd", host_images_dir);
+	devicetree_path = safe_asprintf("%s/devicetree", images_dir);
+	host_devicetree_path = safe_asprintf("%s/devicetree", host_images_dir);
+
+	ret = stat(conf_path, &s);
+	if (!ret) {
+		fprintf(stderr, "entry %s already exists.\n", conf_file);
+		ret = yesno("overwrite? (y/n) ");
+		if (ret)
+			return -EINVAL;
+	}
+
+	ret = make_directory(conf_dir);
+	if (ret)
+		return ret;
+
+	ret = make_directory(host_images_dir);
+	if (ret)
+		return ret;
+
+	fd = open(conf_path, O_WRONLY | O_CREAT | O_TRUNC, 0644);
+	if (fd < 0) {
+		fprintf(stderr, "failed to create %s: %s\n", conf_path, strerror(errno));
+		return -errno;
+	}
+
+	dprintf(fd, "title %s\n", title);
+	dprintf(fd, "version %s\n", kernel_version);
+	dprintf(fd, "machine-id %s\n", machine_id);
+	if (options)
+		dprintf(fd, "options %s\n", options);
+	dprintf(fd, "linux %s\n", kernel_path);
+	if (initrd_image)
+		dprintf(fd, "initrd %s\n", initrd_path);
+	if (devicetree_image)
+		dprintf(fd, "devicetree %s\n", devicetree_path);
+
+	ret = close(fd);
+	if (ret)
+		return ret;
+
+	ret = systemp("cp %s %s", kernel_image, host_kernel_path);
+	if (ret) {
+		fprintf(stderr, "unable to copy kernel image\n");
+		return ret;
+	}
+
+	if (initrd_image) {
+		ret = systemp("cp %s %s", initrd_image, host_initrd_path);
+		if (ret) {
+			fprintf(stderr, "unable to copy initrd image\n");
+			return ret;
+		}
+	}
+
+	if (devicetree_image) {
+		ret = systemp("cp %s %s", devicetree_image, host_devicetree_path);
+		if (ret) {
+			fprintf(stderr, "unable to copy devicetree image\n");
+			return ret;
+		}
+	}
+
+	printf("written config file: %s\n", conf_path);
+
+	return 0;
+}
+
+static int do_open_entries(void)
+{
+	DIR *dir;
+	char *entries, *entry_path;
+	struct loader_entry *e = NULL, *first = NULL;
+	int i = 0;
+
+	if (loader_entries)
+		return 0;
+
+	entries = safe_asprintf("%s/loader/entries", host_boot_path);
+
+	dir = opendir(entries);
+	if (!dir) {
+		fprintf(stderr, "cannot open %s\n", entries);
+		return -errno;
+	}
+
+	while (1) {
+		struct dirent *ent;
+		struct loader_entry *tmp;
+
+		ent = readdir(dir);
+		if (!ent)
+			break;
+		if (ent->d_name[0] == '.')
+			continue;
+		entry_path = safe_asprintf("%s/%s", entries, ent->d_name);
+
+		tmp = loader_entry_open(entry_path);
+		if (!tmp) {
+			fprintf(stderr, "cannot open %s\n", entry_path);
+			break;
+		}
+
+		tmp->config_file = strdup(ent->d_name);
+
+		tmp->num = i++;
+
+		if (first)
+			e->next = tmp;
+		else
+			first = tmp;
+
+		e = tmp;
+	}
+
+	closedir(dir);
+
+	loader_entries = first;
+
+	return 0;
+}
+
+static struct loader_entry *loader_entry_by_num(int num)
+{
+	struct loader_entry *e;
+
+	e = loader_entries;
+
+	while (e) {
+		if (e->num == num)
+			return e;
+		e = e->next;
+	}
+
+	return NULL;
+}
+
+static int do_list_entries(void)
+{
+	struct loader_entry *e;
+	int ret;
+
+	ret = do_open_entries();
+	if (ret)
+		return ret;
+
+	e = loader_entries;
+
+	while (e) {
+		printf("Entry %d:\n", e->num);
+
+		if (e->title)
+			printf("\ttitle:      %s\n", e->title);
+		if (e->version)
+			printf("\tversion:    %s\n", e->version);
+		if (e->machine_id)
+			printf("\tmachine_id: %s\n", e->machine_id);
+		if (e->options)
+			printf("\toptions:    %s\n", e->options);
+		if (e->kernel)
+			printf("\tlinux:      %s\n", e->kernel);
+		if (e->devicetree)
+			printf("\tdevicetree: %s\n", e->devicetree);
+		if (e->initrd)
+			printf("\tinitrd:     %s\n", e->initrd);
+		e = e->next;
+	}
+
+	return 0;
+}
+
+static int is_file_referenced(const char *filename)
+{
+	struct loader_entry *e = loader_entries;
+
+	while (e) {
+		if (e->kernel && !strcmp(e->kernel, filename))
+			return 1;
+		if (e->initrd && !strcmp(e->initrd, filename))
+			return 1;
+		if (e->devicetree && !strcmp(e->devicetree, filename))
+			return 1;
+		e = e->next;
+	}
+
+	return 0;
+}
+
+static int remove_if_unreferenced(const char *filename)
+{
+	char *path, *dir;
+	int ret;
+
+	if (!filename)
+		return -EINVAL;
+
+	if (is_file_referenced(filename))
+		return -EBUSY;
+
+	path = safe_asprintf("%s/%s", host_boot_path, filename);
+
+	verbose_printf("removing unrefenced %s\n", path);
+
+	ret = unlink(path);
+	if (ret) {
+		fprintf(stderr, "cannot remove %s: %s\n", path, strerror(errno));
+		return ret;
+	}
+
+	dir = dirname(path);
+	rmdir(dir);
+	dir = dirname(path);
+	rmdir(dir);
+
+	free(path);
+
+	return 0;
+}
+
+static int do_remove_kernel(void)
+{
+	char *input = NULL;
+	size_t insize;
+	int remove_num = -1;
+	struct loader_entry *e;
+	int ret;
+	char *kernel, *devicetree, *initrd;
+
+	do_open_entries();
+
+	if (!loader_entries) {
+		fprintf(stderr, "No entries to remove\n");
+		return -ENOENT;
+	}
+
+	if (remove_kernel_num >= 0)
+		remove_num = remove_kernel_num;
+
+	if (remove_num < 0 && interactive) {
+		do_list_entries();
+		printf("which kernel do you like to remove?\n");
+		ret = getline(&input, &insize, stdin);
+		if (ret)
+			return -errno;
+		if (!strlen(input))
+			return -EINVAL;
+		if (!isdigit(*input))
+			return -EINVAL;
+		remove_num = atoi(input);
+	}
+
+	if (remove_num < 0) {
+		fprintf(stderr, "no entry number given\n");
+		return -EINVAL;
+	}
+
+	e = loader_entry_by_num(remove_num);
+	if (!e) {
+		fprintf(stderr, "no entry with num %d\n", remove_num);
+		return -ENOENT;
+	}
+
+	verbose_printf("removing entry %s\n", e->host_path);
+
+	ret = unlink(e->host_path);
+	if (ret) {
+		fprintf(stderr, "cannot remove %s\n", e->host_path);
+		return -errno;
+	}
+
+	kernel = e->kernel;
+	devicetree = e->devicetree;
+	initrd = e->initrd;
+
+	e->kernel = NULL;
+	e->devicetree = NULL;
+	e->initrd = NULL;
+
+	remove_if_unreferenced(kernel);
+	remove_if_unreferenced(initrd);
+	remove_if_unreferenced(devicetree);
+
+	return 0;
+}
+
+static int do_set_once_default(const char *name, int entry)
+{
+	int fd, ret;
+	struct loader_entry *e;
+	char *host_default_path;
+
+	do_open_entries();
+
+	if (!loader_entries) {
+		fprintf(stderr, "No entries found\n");
+		return -ENOENT;
+	}
+
+	if (entry < 0 && interactive) {
+		char *input = NULL;
+		size_t insize;
+
+		do_list_entries();
+
+		printf("\nwhich entry shall be used?\n");
+		ret = getline(&input, &insize, stdin);
+		if (ret < 0)
+			return -errno;
+		if (!strlen(input))
+			return -EINVAL;
+		if (!isdigit(*input))
+			return -EINVAL;
+		entry = atoi(input);
+	}
+
+	if (entry < 0) {
+		fprintf(stderr, "no entry number given\n");
+		return -EINVAL;
+	}
+
+	e = loader_entry_by_num(entry);
+	if (!e) {
+		fprintf(stderr, "no entry with num %d\n", entry);
+		return -ENOENT;
+	}
+
+	host_default_path = safe_asprintf("%s/%s", host_boot_path, name);
+
+	fd = open(host_default_path, O_WRONLY | O_CREAT | O_TRUNC, 0644);
+	if (fd < 0) {
+		fprintf(stderr, "failed to create %s: %s\n", host_default_path, strerror(errno));
+		return -errno;
+	}
+
+	dprintf(fd, "loader/entries/%s\n", e->config_file);
+
+	ret = close(fd);
+	if (ret)
+		return ret;
+
+	return 0;
+}
+
+static int do_set_default(void)
+{
+	return do_set_once_default("default", set_default_num);
+}
+
+static int do_set_once(void)
+{
+	return do_set_once_default("once", set_once_num);
+}
+
+enum opt {
+	OPT_KERNEL = 1,
+	OPT_INITRD,
+	OPT_DEVICETREE,
+	OPT_ONCE,
+	OPT_ROOT_PARTITION_NO,
+	OPT_DEVICE,
+	OPT_ADD_ROOT_ARGUMENT,
+};
+
+static struct option long_options[] = {
+	{"add",		no_argument,		0,	'a' },
+	{"remove",	optional_argument,	0,	'r' },
+	{"list",	no_argument,		0,	'l' },
+	{"default",	optional_argument,	0,	'd' },
+	{"once",	optional_argument,	0,	OPT_ONCE },
+	{"device",	required_argument,	0,	OPT_DEVICE },
+	{"root",	required_argument,	0,	'R' },
+	{"boot",	required_argument,	0,	'b' },
+	{"kernel-version", required_argument,	0,	'k' },
+	{"title",	required_argument,	0,	't' },
+	{"machine-id",	required_argument,	0,	'm' },
+	{"kernel",	required_argument,	0,	OPT_KERNEL },
+	{"initrd",	required_argument,	0,	OPT_INITRD },
+	{"devicetree",	required_argument,	0,	OPT_DEVICETREE },
+	{"options",	required_argument,	0,	'o' },
+	{"verbose",	no_argument,		0,	'v' },
+	{"add-root-option", no_argument,	0,	OPT_ADD_ROOT_ARGUMENT },
+	{"num-root-part", required_argument,	0,	OPT_ROOT_PARTITION_NO },
+	{"help",	 no_argument,		0,	'h' },
+	{0,		0,			0,	0 }
+};
+
+static void usage(char *name)
+{
+	printf(
+"Usage: %s [OPTIONS]\n"
+"Install, uninstall and list kernels according to the bootloader spec\n"
+"\n"
+"command options, exactly one must be present:\n"
+"\n"
+"-a, --add                      Add a new boot entry\n"
+"-r, --remove[=num]             Remove a boot entry. If <num> is not present\n"
+"                               ask for it interactively\n"
+"-l, --list                     List all available entries\n"
+"-d, --default[=num]            Make an entry the default. If <num> is not\n"
+"                               present ask for it interactively\n"
+"--once[=num]                   start an entry once\n"
+"\n"
+"other options:\n"
+"\n"
+"--kernel-version=<version>     Specify kernel version, used for generating\n"
+"                               config filenames/directories. must be unique\n"
+"                               for each installed operating system\n"
+"--title=<name>                 Title for the entry. If unspecified defaults\n"
+"                               to \"Linux-<version>\"\n"
+"--machine-id=<id>              Specify machine id. Should be unique for each\n"
+"                               installation. Can be left unspecified when it\n"
+"                               can be read from <rootpath>/etc/machine-id.\n"
+"--kernel=<kernel>              Path to the kernel to install\n"
+"--initrd=<initrd>              Path to the initrd to install, optional\n"
+"--devicetree=<devicetree>      Path to the devicetree to install, optional\n"
+"-o, --options=<options>        Commandline options for the kernel, can be\n"
+"                               given multiple times\n"
+"-v, --verbose                  Be more verbose\n"
+"--add-root-option              If present, add a \"root=PARTUUID=xxxxxxxx-yy\"\n"
+"                               option to the kernel commandline. The partuuid\n"
+"                               is determined from the device given with the\n"
+"                               --device option and the partition number\n"
+"                               determined from either the device specified\n"
+"                               with --root or from the --num-root-part option.\n"
+"--num-root-part=<partnum>      Specify partition number for --add-root-option\n"
+"-h, --help                     This help\n"
+"\n"
+"Options for non-native mode:\n"
+"\n"
+"Each of the following options disables native mode. Useful for preparing\n"
+"boot media on another host.\n"
+"--device=<devicepath>          Specify device to work on\n"
+"--root=<path|device>           Specify path or device to use as '/', defaults to '/'\n"
+"--boot=<path|device>           Specify path or device to use as '/boot', defaults to '/boot'\n",
+	name);
+}
+
+int main(int argc, char *argv[])
+{
+	int c;
+	char *root = NULL;
+	int option_index, add_kernel = 0, remove_kernel = 0, add_root_argument = 0;
+	int ret, list = 0, set_default = 0, newmount;
+	int native_mode = 1, set_once = 0;
+
+	while (1) {
+		c = getopt_long(argc, argv, "b:R:d:k:p:m:lo:aruvh", long_options, &option_index);
+		if (c < 0)
+			break;
+		switch (c) {
+		case 'h':
+			usage(argv[0]);
+			exit(0);
+		case 'b':
+			native_mode = 0;
+			host_boot_path = optarg;
+			break;
+		case 'R':
+			native_mode = 0;
+			root = optarg;
+			break;
+		case 'l':
+			list = 1;
+			break;
+		case 'd':
+			set_default = 1;
+			if (optarg)
+				set_default_num = atoi(optarg);
+			break;
+		case OPT_ONCE:
+			set_once = 1;
+			if (optarg)
+				set_once_num = atoi(optarg);
+			break;
+		case OPT_DEVICE:
+			native_mode = 0;
+			device_path = optarg;
+			break;
+		case 'k':
+			kernel_version = optarg;
+			break;
+		case 't':
+			title = optarg;
+			break;
+		case 'm':
+			machine_id = optarg;
+			break;
+		case OPT_KERNEL:
+			kernel_image = optarg;
+			break;
+		case 'o':
+			append_option("%s", optarg);
+			break;
+		case 'a':
+			add_kernel = 1;
+			break;
+		case 'r':
+			remove_kernel = 1;
+			if (optarg)
+				remove_kernel_num = atoi(optarg);
+			break;
+		case OPT_ADD_ROOT_ARGUMENT:
+			add_root_argument = 1;
+			break;
+		case OPT_ROOT_PARTITION_NO:
+			root_partition_num = atoi(optarg);
+			break;
+		case OPT_INITRD:
+			initrd_image = optarg;
+			break;
+		case OPT_DEVICETREE:
+			devicetree_image = optarg;
+			break;
+		case 'v':
+			verbose++;
+			break;
+		}
+	}
+
+	if (!list && !remove_kernel && !set_default && !add_kernel && !set_once) {
+		fprintf(stderr, "no command given\n");
+		exit (1);
+	}
+
+	if (native_mode) {
+		host_boot_path = "/boot";
+		host_root_path = "";
+	}
+
+	if (device_path) {
+		ret = determine_root_boot_path(device_path);
+		if (ret)
+			exit(1);
+	}
+
+	if (host_boot_path) {
+		verbose_printf("using partition %s for /boot as determined by device argument\n",
+				host_boot_path);
+	}
+
+	if (!host_boot_path) {
+		fprintf(stderr, "No partition or directory given for /boot\n");
+		goto out;
+	}
+
+	host_boot_path = mount_path(host_boot_path, &newmount);
+	if (!host_boot_path)
+		goto out;
+
+	if (newmount)
+		host_mount_boot_path = host_boot_path;
+
+	if (root) {
+		host_root_path = mount_path(root, &newmount);
+		if (!host_root_path)
+			goto out;
+		if (newmount)
+			host_mount_root_path = host_root_path;
+	}
+
+	if (!title)
+		title = safe_asprintf("Linux-%s", kernel_version);
+
+	if (add_root_argument) {
+		if (!nt_disk_signature) {
+			fprintf(stderr, "no nt disk signature found for root-uuid\n"
+					"Cannot add root argument\n");
+			goto out;
+		}
+
+		if (!root_partition_num) {
+			if (!root) {
+				fprintf(stderr, "no root partition number and no device for / given\n"
+						"Cannot add root argument\n");
+				goto out;
+			}
+
+			detect_root_partition_num(root);
+		}
+
+		if (!root_partition_num) {
+			fprintf(stderr, "no root partition number given\n"
+					"Cannot add root argument\n");
+
+			goto out;
+		}
+
+		append_option("root=PARTUUID=%08X-%02d", nt_disk_signature, root_partition_num);
+	}
+
+	if (list) {
+		ret = do_list_entries();
+		goto out;
+	}
+
+	if (remove_kernel) {
+		ret = do_remove_kernel();
+		goto out;
+	}
+
+	if (set_default) {
+		ret = do_set_default();
+		goto out;
+	}
+
+	if (set_once) {
+		ret = do_set_once();
+		goto out;
+	}
+
+	if (add_kernel) {
+		ret = do_add_kernel();
+		goto out;
+	}
+
+	ret = 0;
+
+out:
+	cleanup();
+	exit(ret == 0 ? 0 : 1);
+}
-- 
1.8.4.rc3




More information about the barebox mailing list