docs: Use multiple =item's instead of =item foo|bar
[febootstrap.git] / helper / kernel.c
index 3301826..6a68e55 100644 (file)
@@ -20,6 +20,7 @@
 
 #include <stdio.h>
 #include <stdlib.h>
+#include <string.h>
 #include <fnmatch.h>
 #include <unistd.h>
 #include <errno.h>
@@ -77,6 +78,9 @@ 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.
  *
@@ -94,7 +98,16 @@ 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 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;
@@ -104,6 +117,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 +136,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 +162,117 @@ create_kernel (const char *hostcpu, const char *kernel)
            "febootstrap use, you shouldn't boot the Xen guest with it).\n");
   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.
+ */
+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;
+}