X-Git-Url: http://git.annexia.org/?a=blobdiff_plain;f=helper%2Fkernel.c;h=88dc940503163a88309afc1666bedfdb240a96b7;hb=2e1e2d686ca8f819c202d2ed4e8a5c5758451f26;hp=538942c5dffad4bbace1f4af34b7007b451056b9;hpb=25bc2ca090c135c280a46758e510d920f26f29a4;p=febootstrap.git diff --git a/helper/kernel.c b/helper/kernel.c index 538942c..88dc940 100644 --- a/helper/kernel.c +++ b/helper/kernel.c @@ -24,6 +24,7 @@ #include #include #include +#include #include "error.h" #include "xvasprintf.h" @@ -36,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) { @@ -49,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; } @@ -78,8 +103,6 @@ has_modpath (const char *kernel_name) } } -static const char *create_kernel_archlinux (const char *hostcpu, const char *kernel); - /* Create the kernel. This chooses an appropriate kernel and makes a * symlink to it. * @@ -97,9 +120,12 @@ static const char *create_kernel_archlinux (const char *hostcpu, const char *ker const char * create_kernel (const char *hostcpu, const char *kernel) { - /* In ArchLinux, kernel is always named /boot/vmlinuz26. */ - if (access ("/boot/vmlinuz26", F_OK) == 0) - return create_kernel_archlinux (hostcpu, kernel); + /* 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; @@ -155,55 +181,134 @@ create_kernel (const char *hostcpu, const char *kernel) exit (EXIT_FAILURE); } -/* In ArchLinux, kernel is always named /boot/vmlinuz26, and we have - * to use the 'file' command to work out what version it is. +/* 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_archlinux (const char *hostcpu, const char *kernel) +create_kernel_from_env (const char *hostcpu, const char *kernel, + const char *kernel_env, const char *modpath_env) { - const char *file_cmd = "file /boot/vmlinuz26 | awk '{print $9}'"; - FILE *pp; - char modversion[256]; - char *modpath; - size_t len; - - pp = popen (file_cmd, "r"); - if (pp == NULL) { - error: - fprintf (stderr, "febootstrap-supermin-helper: %s: command failed\n", - file_cmd); - exit (EXIT_FAILURE); + 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 (fgets (modversion, sizeof modversion, pp) == NULL) - goto error; - - if (pclose (pp) == -1) - goto error; - - /* Chomp final \n */ - len = strlen (modversion); - if (len > 0 && modversion[len-1] == '\n') { - modversion[len-1] = '\0'; - len--; + 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); } - /* Generate module path. */ - modpath = xasprintf (MODULESDIR "/%s", modversion); + 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); + } - /* Check module path is a directory. */ - if (!isdir (modpath)) { - fprintf (stderr, "febootstrap-supermin-helper: /boot/vmlinuz26 kernel exists but %s is not a valid module path\n", - modpath); + 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) { - /* Symlink from kernel to /boot/vmlinuz26. */ - if (symlink ("/boot/vmlinuz26", kernel) == -1) + 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 module path. */ - return modpath; + 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); }