X-Git-Url: http://git.annexia.org/?a=blobdiff_plain;f=helper%2Fkernel.c;h=88dc940503163a88309afc1666bedfdb240a96b7;hb=65125379903231f1b0c2ccd0929123317c80bcc2;hp=33018263de5bbf0ab3a252d31143d5dc54ad7450;hpb=e23a9c8f05e3646feb826d5db36d8656a80a27ab;p=febootstrap.git diff --git a/helper/kernel.c b/helper/kernel.c index 3301826..88dc940 100644 --- a/helper/kernel.c +++ b/helper/kernel.c @@ -20,9 +20,11 @@ #include #include +#include #include #include #include +#include #include "error.h" #include "xvasprintf.h" @@ -35,6 +37,9 @@ #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) { @@ -48,6 +53,27 @@ 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; } @@ -94,7 +120,12 @@ has_modpath (const char *kernel_name) 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; @@ -104,6 +135,7 @@ create_kernel (const char *hostcpu, const char *kernel) 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"); @@ -122,16 +154,18 @@ create_kernel (const char *hostcpu, const char *kernel) 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]); @@ -146,3 +180,135 @@ create_kernel (const char *hostcpu, const char *kernel) "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-.tar.gz + * (file-/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); +}