Replace ArchLinux module detection with generic method.
authorErik Nolte <erik_nolte@acm.org>
Thu, 6 Oct 2011 20:47:26 +0000 (14:47 -0600)
committerRichard W.M. Jones <rjones@redhat.com>
Fri, 14 Oct 2011 09:58:24 +0000 (10:58 +0100)
ArchLinux used to keep its kernel in /boot/vmlinuz26 but, with
Linux 3.0, now uses /boot/vmlinuz-linux.  Instead of just changing the
kernel filename and module directory, this change removes the ArchLinux
specific code, lets febootstrap find a kernel, and then computes the
module directory from the version string extracted from the actual kernel
file.

helper/kernel.c

index 6a68e55..88dc940 100644 (file)
@@ -24,6 +24,7 @@
 #include <fnmatch.h>
 #include <unistd.h>
 #include <errno.h>
+#include <sys/utsname.h>
 
 #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,9 +103,6 @@ has_modpath (const char *kernel_name)
   }
 }
 
-static const char *create_kernel_archlinux (const char *hostcpu, const char *kernel);
-static const char *create_kernel_from_env (const char *hostcpu, const char *kernel, const char *kernel_env, const char *modpath_env);
-
 /* Create the kernel.  This chooses an appropriate kernel and makes a
  * symlink to it.
  *
@@ -105,10 +127,6 @@ create_kernel (const char *hostcpu, const char *kernel)
     return create_kernel_from_env (hostcpu, kernel, kernel_env, modpath_env);
   }
 
-  /* In ArchLinux, kernel is always named /boot/vmlinuz26. */
-  if (access ("/boot/vmlinuz26", F_OK) == 0)
-    return create_kernel_archlinux (hostcpu, kernel);
-
   /* In original: ls -1dvr /boot/vmlinuz-*.$arch* 2>/dev/null | grep -v xen */
   const char *patt;
   if (hostcpu[0] == 'i' && hostcpu[2] == '8' && hostcpu[3] == '6' &&
@@ -163,59 +181,6 @@ 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.
- */
-static const char *
-create_kernel_archlinux (const char *hostcpu, const char *kernel)
-{
-  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 (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--;
-  }
-
-  /* Generate module path. */
-  modpath = xasprintf (MODULESDIR "/%s", modversion);
-
-  /* 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);
-    exit (EXIT_FAILURE);
-  }
-
-  if (kernel) {
-    /* Symlink from kernel to /boot/vmlinuz26. */
-    if (symlink ("/boot/vmlinuz26", kernel) == -1)
-      error (EXIT_FAILURE, errno, "symlink kernel");
-  }
-
-  /* Return module path. */
-  return modpath;
-}
-
 /* 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.
@@ -276,3 +241,74 @@ create_kernel_from_env (const char *hostcpu, const char *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);
+}