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