9
0
Fork 0
barebox/scripts/kernel-install.c

1408 lines
29 KiB
C

/*
* 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;
}
fprintf(stderr, "cannot mount %s\n", in_path);
return NULL;
case MOUNT_MOUNT:
out_path = mount_path_mount(in_path);
if (out_path) {
*newmount = 1;
return out_path;
}
fprintf(stderr, "cannot mount %s\n", in_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) {
fprintf(stderr, "failed to create directory %s: %s\n",
conf_dir, strerror(errno));
return ret;
}
ret = make_directory(host_images_dir);
if (ret) {
fprintf(stderr, "failed to create directory %s: %s\n",
host_images_dir, strerror(errno));
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 < 0)
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);
}