#include <stdio.h>
#include <stdlib.h>
+#include <string.h>
#include <fnmatch.h>
#include <unistd.h>
#include <errno.h>
+#include <sys/utsname.h>
#include "error.h"
#include "xvasprintf.h"
#define KERNELDIR "/boot"
#define MODULESDIR "/lib/modules"
+static char* get_kernel_version (char* filename);
+static const char *create_kernel_from_env (const char *hostcpu, const char *kernel, const char *kernel_env, const char *modpath_env);
+
static char *
get_modpath (const char *kernel_name)
{
exit (EXIT_FAILURE);
}
+ if (! isdir (modpath)) {
+ char* path;
+ char* version;
+ path = xasprintf (KERNELDIR "/%s", kernel_name);
+ if (!path) {
+ perror ("xasprintf");
+ exit (EXIT_FAILURE);
+ }
+ version = get_kernel_version (path);
+ free (path);
+ if (version != NULL) {
+ free (modpath);
+ modpath = xasprintf (MODULESDIR "/%s", version);
+ free (version);
+ if (!path) {
+ perror ("xasprintf");
+ exit (EXIT_FAILURE);
+ }
+ }
+ }
+
return modpath;
}
const char *
create_kernel (const char *hostcpu, const char *kernel)
{
- char **all_files = read_dir (KERNELDIR);
+ /* Override kernel selection using environment variables? */
+ char *kernel_env = getenv ("FEBOOTSTRAP_KERNEL");
+ if (kernel_env) {
+ char *modpath_env = getenv ("FEBOOTSTRAP_MODULES");
+ return create_kernel_from_env (hostcpu, kernel, kernel_env, modpath_env);
+ }
/* In original: ls -1dvr /boot/vmlinuz-*.$arch* 2>/dev/null | grep -v xen */
const char *patt;
else
patt = xasprintf ("vmlinuz-*.%s*", hostcpu);
+ char **all_files = read_dir (KERNELDIR);
char **candidates;
candidates = filter_fnmatch (all_files, patt, FNM_NOESCAPE);
candidates = filter_notmatching_substring (candidates, "xen");
sort (candidates, reverse_filevercmp);
- /* Choose the first candidate. */
- char *tmp = xasprintf (KERNELDIR "/%s", candidates[0]);
+ if (kernel) {
+ /* Choose the first candidate. */
+ char *tmp = xasprintf (KERNELDIR "/%s", candidates[0]);
- if (verbose)
- fprintf (stderr, "creating symlink %s -> %s\n", kernel, tmp);
+ if (verbose >= 2)
+ fprintf (stderr, "creating symlink %s -> %s\n", kernel, tmp);
- if (symlink (tmp, kernel) == -1)
- error (EXIT_FAILURE, errno, "symlink kernel");
+ if (symlink (tmp, kernel) == -1)
+ error (EXIT_FAILURE, errno, "symlink kernel");
- free (tmp);
+ free (tmp);
+ }
return get_modpath (candidates[0]);
"febootstrap use, you shouldn't boot the Xen guest with it).\n");
exit (EXIT_FAILURE);
}
+
+/* Select the kernel from environment variables set by the user.
+ * modpath_env may be NULL, in which case we attempt to work it out
+ * from kernel_env.
+ */
+static const char *
+create_kernel_from_env (const char *hostcpu, const char *kernel,
+ const char *kernel_env, const char *modpath_env)
+{
+ if (verbose) {
+ fprintf (stderr,
+ "febootstrap-supermin-helper: using environment variable(s) FEBOOTSTRAP_* to\n"
+ "select kernel %s", kernel_env);
+ if (modpath_env)
+ fprintf (stderr, " and module path %s", modpath_env);
+ fprintf (stderr, "\n");
+ }
+
+ if (!isfile (kernel_env)) {
+ fprintf (stderr,
+ "febootstrap-supermin-helper: %s: not a regular file\n"
+ "(what is $FEBOOTSTRAP_KERNEL set to?)\n", kernel_env);
+ exit (EXIT_FAILURE);
+ }
+
+ if (!modpath_env) {
+ /* Try to guess modpath from kernel path. */
+ const char *p = strrchr (kernel_env, '/');
+ if (p) p++; else p = kernel_env;
+
+ /* NB: We need the extra test to ensure calling get_modpath is safe. */
+ if (strncmp (p, "vmlinuz-", 8) != 0) {
+ fprintf (stderr,
+ "febootstrap-supermin-helper: cannot guess module path.\n"
+ "Set $FEBOOTSTRAP_MODULES to the modules directory corresponding to\n"
+ "kernel %s, or unset $FEBOOTSTRAP_KERNEL to autoselect a kernel.\n",
+ kernel_env);
+ exit (EXIT_FAILURE);
+ }
+
+ modpath_env = get_modpath (p);
+ }
+
+ if (!isdir (modpath_env)) {
+ fprintf (stderr,
+ "febootstrap-supermin-helper: %s: not a directory\n"
+ "(what is $FEBOOTSTRAP_MODULES set to?)\n", modpath_env);
+ exit (EXIT_FAILURE);
+ }
+
+ /* Create the symlink. */
+ if (kernel) {
+ if (verbose >= 2)
+ fprintf (stderr, "creating symlink %s -> %s\n", kernel_env, kernel);
+
+ if (symlink (kernel_env, kernel) == -1)
+ error (EXIT_FAILURE, errno, "symlink kernel");
+ }
+
+ return modpath_env;
+}
+
+/* Read an unsigned little endian short at a specified offset in a file.
+ * Returns a non-negative int on success or -1 on failure.
+ */
+static int
+read_leshort (FILE* fp, int offset)
+{
+ char buf[2];
+ if (fseek (fp, offset, SEEK_SET) != 0 ||
+ fread (buf, sizeof(char), 2, fp) != 2)
+ {
+ return -1;
+ }
+ return ((buf[1] & 0xFF) << 8) | (buf[0] & 0xFF);
+}
+
+/* Extract the kernel version from a Linux kernel file.
+ * Returns a malloc'd string containing the version or NULL if the
+ * file can't be read, is not a Linux kernel, or the version can't
+ * be found.
+ *
+ * See ftp://ftp.astron.com/pub/file/file-<ver>.tar.gz
+ * (file-<ver>/magic/Magdir/linux) for the rules used to find the
+ * version number:
+ * 514 string HdrS Linux kernel
+ * >518 leshort >0x1ff
+ * >>(526.s+0x200) string >\0 version %s,
+ *
+ * Bugs: probably limited to x86 kernels.
+ */
+static char*
+get_kernel_version (char* filename)
+{
+ FILE* fp;
+ int size = 132;
+ char buf[size];
+ int offset;
+
+ fp = fopen (filename, "rb");
+
+ if (fseek (fp, 514, SEEK_SET) != 0 ||
+ fgets (buf, size, fp) == NULL ||
+ strncmp (buf, "HdrS", 4) != 0 ||
+ read_leshort (fp, 518) < 0x1FF)
+ {
+ /* not a Linux kernel */
+ fclose (fp);
+ return NULL;
+ }
+
+ offset = read_leshort (fp, 526);
+ if (offset == -1)
+ {
+ /* can't read version offset */
+ fclose (fp);
+ return NULL;
+ }
+
+ if (fseek (fp, offset + 0x200, SEEK_SET) != 0 ||
+ fgets (buf, size, fp) == NULL)
+ {
+ /* can't read version string */
+ fclose (fp);
+ return NULL;
+ }
+
+ fclose (fp);
+
+ buf[strcspn (buf, " \t\n")] = '\0';
+ return strdup (buf);
+}