helper: Move all_files down function.
[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 char *create_kernel_archlinux (const char *hostcpu, const char *kernel);
82
83 /* Create the kernel.  This chooses an appropriate kernel and makes a
84  * symlink to it.
85  *
86  * Look for the most recent kernel named vmlinuz-*.<arch>* which has a
87  * corresponding directory in /lib/modules/. If the architecture is
88  * x86, look for any x86 kernel.
89  *
90  * RHEL 5 didn't append the arch to the kernel name, so look for
91  * kernels without arch second.
92  *
93  * If no suitable kernel can be found, exit with an error.
94  *
95  * This function returns the module path (ie. /lib/modules/<version>).
96  */
97 const char *
98 create_kernel (const char *hostcpu, const char *kernel)
99 {
100   /* In ArchLinux, kernel is always named /boot/vmlinuz26. */
101   if (access ("/boot/vmlinuz26", F_OK) == 0)
102     return create_kernel_archlinux (hostcpu, kernel);
103
104   /* In original: ls -1dvr /boot/vmlinuz-*.$arch* 2>/dev/null | grep -v xen */
105   const char *patt;
106   if (hostcpu[0] == 'i' && hostcpu[2] == '8' && hostcpu[3] == '6' &&
107       hostcpu[4] == '\0')
108     patt = "vmlinuz-*.i?86*";
109   else
110     patt = xasprintf ("vmlinuz-*.%s*", hostcpu);
111
112   char **all_files = read_dir (KERNELDIR);
113   char **candidates;
114   candidates = filter_fnmatch (all_files, patt, FNM_NOESCAPE);
115   candidates = filter_notmatching_substring (candidates, "xen");
116   candidates = filter (candidates, has_modpath);
117
118   if (candidates[0] == NULL) {
119     /* In original: ls -1dvr /boot/vmlinuz-* 2>/dev/null | grep -v xen */
120     patt = "vmlinuz-*";
121     candidates = filter_fnmatch (all_files, patt, FNM_NOESCAPE);
122     candidates = filter_notmatching_substring (candidates, "xen");
123     candidates = filter (candidates, has_modpath);
124
125     if (candidates[0] == NULL)
126       goto no_kernels;
127   }
128
129   sort (candidates, reverse_filevercmp);
130
131   if (kernel) {
132     /* Choose the first candidate. */
133     char *tmp = xasprintf (KERNELDIR "/%s", candidates[0]);
134
135     if (verbose >= 2)
136       fprintf (stderr, "creating symlink %s -> %s\n", kernel, tmp);
137
138     if (symlink (tmp, kernel) == -1)
139       error (EXIT_FAILURE, errno, "symlink kernel");
140
141     free (tmp);
142   }
143
144   return get_modpath (candidates[0]);
145
146   /* Print more diagnostics here than the old script did. */
147  no_kernels:
148   fprintf (stderr,
149            "febootstrap-supermin-helper: failed to find a suitable kernel.\n"
150            "I looked for kernels in " KERNELDIR " and modules in " MODULESDIR
151            ".\n"
152            "If this is a Xen guest, and you only have Xen domU kernels\n"
153            "installed, try installing a fullvirt kernel (only for\n"
154            "febootstrap use, you shouldn't boot the Xen guest with it).\n");
155   exit (EXIT_FAILURE);
156 }
157
158 /* In ArchLinux, kernel is always named /boot/vmlinuz26, and we have
159  * to use the 'file' command to work out what version it is.
160  */
161 static char *
162 create_kernel_archlinux (const char *hostcpu, const char *kernel)
163 {
164   const char *file_cmd = "file /boot/vmlinuz26 | awk '{print $9}'";
165   FILE *pp;
166   char modversion[256];
167   char *modpath;
168   size_t len;
169
170   pp = popen (file_cmd, "r");
171   if (pp == NULL) {
172   error:
173     fprintf (stderr, "febootstrap-supermin-helper: %s: command failed\n",
174              file_cmd);
175     exit (EXIT_FAILURE);
176   }
177
178   if (fgets (modversion, sizeof modversion, pp) == NULL)
179     goto error;
180
181   if (pclose (pp) == -1)
182     goto error;
183
184   /* Chomp final \n */
185   len = strlen (modversion);
186   if (len > 0 && modversion[len-1] == '\n') {
187     modversion[len-1] = '\0';
188     len--;
189   }
190
191   /* Generate module path. */
192   modpath = xasprintf (MODULESDIR "/%s", modversion);
193
194   /* Check module path is a directory. */
195   if (!isdir (modpath)) {
196     fprintf (stderr, "febootstrap-supermin-helper: /boot/vmlinuz26 kernel exists but %s is not a valid module path\n",
197              modpath);
198     exit (EXIT_FAILURE);
199   }
200
201   if (kernel) {
202     /* Symlink from kernel to /boot/vmlinuz26. */
203     if (symlink ("/boot/vmlinuz26", kernel) == -1)
204       error (EXIT_FAILURE, errno, "symlink kernel");
205   }
206
207   /* Return module path. */
208   return modpath;
209 }