From 61cf2b51bccbf18c3df505a77db759454e8574ca Mon Sep 17 00:00:00 2001 From: Sascha Hauer Date: Sun, 15 Sep 2013 10:09:23 +0200 Subject: [PATCH] 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 --- common/Kconfig | 8 + scripts/Makefile | 2 + scripts/kernel-install.c | 1399 ++++++++++++++++++++++++++++++++++++++ 3 files changed, 1409 insertions(+) create mode 100644 scripts/kernel-install.c diff --git a/common/Kconfig b/common/Kconfig index dd60ec9a2..ccfbc800b 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 2c43f6682..ce3fd5336 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 000000000..6086357a4 --- /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 + * + * 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= or --initrd=. + * + * 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= - 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 +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +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 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 is not\n" +" present ask for it interactively\n" +"--once[=num] start an entry once\n" +"\n" +"other options:\n" +"\n" +"--kernel-version= Specify kernel version, used for generating\n" +" config filenames/directories. must be unique\n" +" for each installed operating system\n" +"--title= Title for the entry. If unspecified defaults\n" +" to \"Linux-\"\n" +"--machine-id= Specify machine id. Should be unique for each\n" +" installation. Can be left unspecified when it\n" +" can be read from /etc/machine-id.\n" +"--kernel= Path to the kernel to install\n" +"--initrd= Path to the initrd to install, optional\n" +"--devicetree= Path to the devicetree to install, optional\n" +"-o, --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= 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= Specify device to work on\n" +"--root= Specify path or device to use as '/', defaults to '/'\n" +"--boot= 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); +}