summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorSascha Hauer <s.hauer@pengutronix.de>2013-09-15 10:09:23 +0200
committerSascha Hauer <s.hauer@pengutronix.de>2013-10-14 15:28:06 +0200
commit61cf2b51bccbf18c3df505a77db759454e8574ca (patch)
tree6ab085153d4567d63476cf125d6d9ef620cae258
parentef5dac9c2bf7ac7ceb3027d75dadb5c52e633572 (diff)
downloadbarebox-61cf2b51bccbf18c3df505a77db759454e8574ca.tar.gz
barebox-61cf2b51bccbf18c3df505a77db759454e8574ca.tar.xz
add kernel-install tool for bootloader Spec
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@pengutronix.de>
-rw-r--r--common/Kconfig8
-rw-r--r--scripts/Makefile2
-rw-r--r--scripts/kernel-install.c1399
3 files changed, 1409 insertions, 0 deletions
diff --git a/common/Kconfig b/common/Kconfig
index dd60ec9a26..ccfbc800b9 100644
--- a/common/Kconfig
+++ b/common/Kconfig
@@ -451,6 +451,14 @@ config BLSPEC
on a device and it allows the Operating System to install / update
kernels.
+config KERNEL_INSTALL_TARGET
+ bool
+ prompt "Build kernel-install utility for the target"
+ help
+ Enable this to compile the kernel-install script using the cross
+ compiler. The utility for the target will be under
+ scripts/kernel-install-target
+
choice
prompt "console support"
default CONSOLE_FULL
diff --git a/scripts/Makefile b/scripts/Makefile
index 2c43f6682b..ce3fd53366 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-$(CONFIG_KERNEL_INSTALL_TARGET) += bareboxenv-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 0000000000..6086357a44
--- /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@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;
+}
+
+static 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'.
+ */
+static 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;
+}
+
+static 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);
+}
+
+static 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;
+ }
+}
+
+static 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;
+}
+
+static 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;
+}
+
+static 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);
+}
+
+static 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);
+}