+
+/* 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);
+}