helper: Allow kernel and modpath to be selected from envvars (RHBZ#671082).
[febootstrap.git] / helper / kernel.c
1 /* febootstrap-supermin-helper reimplementation in C.
2  * Copyright (C) 2009-2010 Red Hat Inc.
3  *
4  * This program is free software; you can redistribute it and/or modify
5  * it under the terms of the GNU General Public License as published by
6  * the Free Software Foundation; either version 2 of the License, or
7  * (at your option) any later version.
8  *
9  * This program is distributed in the hope that it will be useful,
10  * but WITHOUT ANY WARRANTY; without even the implied warranty of
11  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
12  * GNU General Public License for more details.
13  *
14  * You should have received a copy of the GNU General Public License
15  * along with this program; if not, write to the Free Software
16  * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
17  */
18
19 #include <config.h>
20
21 #include <stdio.h>
22 #include <stdlib.h>
23 #include <string.h>
24 #include <fnmatch.h>
25 #include <unistd.h>
26 #include <errno.h>
27
28 #include "error.h"
29 #include "xvasprintf.h"
30
31 #include "helper.h"
32
33 /* Directory containing candidate kernels.  We could make this
34  * configurable at some point.
35  */
36 #define KERNELDIR "/boot"
37 #define MODULESDIR "/lib/modules"
38
39 static char *
40 get_modpath (const char *kernel_name)
41 {
42   /* Ignore "vmlinuz-" at the beginning of the kernel name. */
43   const char *version = &kernel_name[8];
44
45   /* /lib/modules/<version> */
46   char *modpath = xasprintf (MODULESDIR "/%s", version);
47   if (!modpath) {
48     perror ("xasprintf");
49     exit (EXIT_FAILURE);
50   }
51
52   return modpath;
53 }
54
55 /* kernel_name is "vmlinuz-*".  Check if there is a corresponding
56  * module path in /lib/modules.
57  */
58 static int
59 has_modpath (const char *kernel_name)
60 {
61   char *modpath = get_modpath (kernel_name);
62
63   if (verbose)
64     fprintf (stderr, "checking modpath %s is a directory\n", modpath);
65
66   int r = isdir (modpath);
67
68   if (r) {
69     if (verbose)
70       fprintf (stderr, "picked %s because modpath %s exists\n",
71                kernel_name, modpath);
72     free (modpath);
73     return 1;
74   }
75   else {
76     free (modpath);
77     return 0;
78   }
79 }
80
81 static const char *create_kernel_archlinux (const char *hostcpu, const char *kernel);
82 static const char *create_kernel_from_env (const char *hostcpu, const char *kernel, const char *kernel_env, const char *modpath_env);
83
84 /* Create the kernel.  This chooses an appropriate kernel and makes a
85  * symlink to it.
86  *
87  * Look for the most recent kernel named vmlinuz-*.<arch>* which has a
88  * corresponding directory in /lib/modules/. If the architecture is
89  * x86, look for any x86 kernel.
90  *
91  * RHEL 5 didn't append the arch to the kernel name, so look for
92  * kernels without arch second.
93  *
94  * If no suitable kernel can be found, exit with an error.
95  *
96  * This function returns the module path (ie. /lib/modules/<version>).
97  */
98 const char *
99 create_kernel (const char *hostcpu, const char *kernel)
100 {
101   /* Override kernel selection using environment variables? */
102   char *kernel_env = getenv ("FEBOOTSTRAP_KERNEL");
103   if (kernel_env) {
104     char *modpath_env = getenv ("FEBOOTSTRAP_MODULES");
105     return create_kernel_from_env (hostcpu, kernel, kernel_env, modpath_env);
106   }
107
108   /* In ArchLinux, kernel is always named /boot/vmlinuz26. */
109   if (access ("/boot/vmlinuz26", F_OK) == 0)
110     return create_kernel_archlinux (hostcpu, kernel);
111
112   /* In original: ls -1dvr /boot/vmlinuz-*.$arch* 2>/dev/null | grep -v xen */
113   const char *patt;
114   if (hostcpu[0] == 'i' && hostcpu[2] == '8' && hostcpu[3] == '6' &&
115       hostcpu[4] == '\0')
116     patt = "vmlinuz-*.i?86*";
117   else
118     patt = xasprintf ("vmlinuz-*.%s*", hostcpu);
119
120   char **all_files = read_dir (KERNELDIR);
121   char **candidates;
122   candidates = filter_fnmatch (all_files, patt, FNM_NOESCAPE);
123   candidates = filter_notmatching_substring (candidates, "xen");
124   candidates = filter (candidates, has_modpath);
125
126   if (candidates[0] == NULL) {
127     /* In original: ls -1dvr /boot/vmlinuz-* 2>/dev/null | grep -v xen */
128     patt = "vmlinuz-*";
129     candidates = filter_fnmatch (all_files, patt, FNM_NOESCAPE);
130     candidates = filter_notmatching_substring (candidates, "xen");
131     candidates = filter (candidates, has_modpath);
132
133     if (candidates[0] == NULL)
134       goto no_kernels;
135   }
136
137   sort (candidates, reverse_filevercmp);
138
139   if (kernel) {
140     /* Choose the first candidate. */
141     char *tmp = xasprintf (KERNELDIR "/%s", candidates[0]);
142
143     if (verbose >= 2)
144       fprintf (stderr, "creating symlink %s -> %s\n", kernel, tmp);
145
146     if (symlink (tmp, kernel) == -1)
147       error (EXIT_FAILURE, errno, "symlink kernel");
148
149     free (tmp);
150   }
151
152   return get_modpath (candidates[0]);
153
154   /* Print more diagnostics here than the old script did. */
155  no_kernels:
156   fprintf (stderr,
157            "febootstrap-supermin-helper: failed to find a suitable kernel.\n"
158            "I looked for kernels in " KERNELDIR " and modules in " MODULESDIR
159            ".\n"
160            "If this is a Xen guest, and you only have Xen domU kernels\n"
161            "installed, try installing a fullvirt kernel (only for\n"
162            "febootstrap use, you shouldn't boot the Xen guest with it).\n");
163   exit (EXIT_FAILURE);
164 }
165
166 /* In ArchLinux, kernel is always named /boot/vmlinuz26, and we have
167  * to use the 'file' command to work out what version it is.
168  */
169 static const char *
170 create_kernel_archlinux (const char *hostcpu, const char *kernel)
171 {
172   const char *file_cmd = "file /boot/vmlinuz26 | awk '{print $9}'";
173   FILE *pp;
174   char modversion[256];
175   char *modpath;
176   size_t len;
177
178   pp = popen (file_cmd, "r");
179   if (pp == NULL) {
180   error:
181     fprintf (stderr, "febootstrap-supermin-helper: %s: command failed\n",
182              file_cmd);
183     exit (EXIT_FAILURE);
184   }
185
186   if (fgets (modversion, sizeof modversion, pp) == NULL)
187     goto error;
188
189   if (pclose (pp) == -1)
190     goto error;
191
192   /* Chomp final \n */
193   len = strlen (modversion);
194   if (len > 0 && modversion[len-1] == '\n') {
195     modversion[len-1] = '\0';
196     len--;
197   }
198
199   /* Generate module path. */
200   modpath = xasprintf (MODULESDIR "/%s", modversion);
201
202   /* Check module path is a directory. */
203   if (!isdir (modpath)) {
204     fprintf (stderr, "febootstrap-supermin-helper: /boot/vmlinuz26 kernel exists but %s is not a valid module path\n",
205              modpath);
206     exit (EXIT_FAILURE);
207   }
208
209   if (kernel) {
210     /* Symlink from kernel to /boot/vmlinuz26. */
211     if (symlink ("/boot/vmlinuz26", kernel) == -1)
212       error (EXIT_FAILURE, errno, "symlink kernel");
213   }
214
215   /* Return module path. */
216   return modpath;
217 }
218
219 /* Select the kernel from environment variables set by the user.
220  * modpath_env may be NULL, in which case we attempt to work it out
221  * from kernel_env.
222  */
223 static const char *
224 create_kernel_from_env (const char *hostcpu, const char *kernel,
225                         const char *kernel_env, const char *modpath_env)
226 {
227   if (verbose) {
228     fprintf (stderr,
229              "febootstrap-supermin-helper: using environment variable(s) FEBOOTSTRAP_* to\n"
230              "select kernel %s", kernel_env);
231     if (modpath_env)
232       fprintf (stderr, " and module path %s", modpath_env);
233     fprintf (stderr, "\n");
234   }
235
236   if (!isfile (kernel_env)) {
237     fprintf (stderr,
238              "febootstrap-supermin-helper: %s: not a regular file\n"
239              "(what is $FEBOOTSTRAP_KERNEL set to?)\n", kernel_env);
240     exit (EXIT_FAILURE);
241   }
242
243   if (!modpath_env) {
244     /* Try to guess modpath from kernel path. */
245     const char *p = strrchr (kernel_env, '/');
246     if (p) p++; else p = kernel_env;
247
248     /* NB: We need the extra test to ensure calling get_modpath is safe. */
249     if (strncmp (p, "vmlinuz-", 8) != 0) {
250       fprintf (stderr,
251                "febootstrap-supermin-helper: cannot guess module path.\n"
252                "Set $FEBOOTSTRAP_MODULES to the modules directory corresponding to\n"
253                "kernel %s, or unset $FEBOOTSTRAP_KERNEL to autoselect a kernel.\n",
254                kernel_env);
255       exit (EXIT_FAILURE);
256     }
257
258     modpath_env = get_modpath (p);
259   }
260
261   if (!isdir (modpath_env)) {
262     fprintf (stderr,
263              "febootstrap-supermin-helper: %s: not a directory\n"
264              "(what is $FEBOOTSTRAP_MODULES set to?)\n", modpath_env);
265     exit (EXIT_FAILURE);
266   }
267
268   /* Create the symlink. */
269   if (kernel) {
270     if (verbose >= 2)
271       fprintf (stderr, "creating symlink %s -> %s\n", kernel_env, kernel);
272
273     if (symlink (kernel_env, kernel) == -1)
274       error (EXIT_FAILURE, errno, "symlink kernel");
275   }
276
277   return modpath_env;
278 }