File: linux_kernels.ml

package info (click to toggle)
guestfs-tools 1.52.3-1
  • links: PTS, VCS
  • area: main
  • in suites: forky, sid, trixie
  • size: 69,236 kB
  • sloc: ansic: 15,698; ml: 15,621; sh: 7,396; xml: 5,478; makefile: 3,601; perl: 1,535; lex: 135; yacc: 128; python: 80
file content (355 lines) | stat: -rw-r--r-- 13,897 bytes parent folder | download | duplicates (2)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
(* virt-v2v
 * Copyright (C) 2009-2023 Red Hat Inc.
 *
 * This program is free software; you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation; either version 2 of the License, or
 * (at your option) any later version.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License along
 * with this program; if not, write to the Free Software Foundation, Inc.,
 * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
 *)

(* Detect which kernels are installed and offered by the bootloader. *)

open Printf

open Std_utils
open Tools_utils
open Common_gettext.Gettext

module G = Guestfs

(* Kernel information. *)
type kernel_info = {
  ki_app : G.application2;
  ki_name : string;
  ki_version : string;
  ki_arch : string;
  ki_vmlinuz : string;
  ki_vmlinuz_stat : G.statns;
  ki_initrd : string option;
  ki_modpath : string;
  ki_modules : string list;
  ki_supports_virtio_blk : bool;
  ki_supports_virtio_net : bool;
  ki_supports_virtio_rng : bool;
  ki_supports_virtio_balloon : bool;
  ki_supports_isa_pvpanic : bool;
  ki_supports_virtio_socket : bool;
  ki_is_xen_pv_only_kernel : bool;
  ki_is_debug : bool;
  ki_config_file : string option;
}

let print_kernel_info chan prefix ki =
  let fpf fs = output_string chan prefix; fprintf chan fs in
  fprintf chan "* %s %s (%s)\n" ki.ki_name ki.ki_version ki.ki_arch;
  fpf "%s\n" ki.ki_vmlinuz;
  fpf "%s\n" (match ki.ki_initrd with None -> "no initrd" | Some s -> s);
  fpf "%s\n" (match ki.ki_config_file with None -> "no config" | Some s -> s);
  fpf "%s\n" ki.ki_modpath;
  fpf "%d modules found\n" (List.length ki.ki_modules);
  fpf "virtio: blk=%b net=%b rng=%b balloon=%b\n"
      ki.ki_supports_virtio_blk ki.ki_supports_virtio_net
      ki.ki_supports_virtio_rng ki.ki_supports_virtio_balloon;
  fpf "pvpanic=%b vsock=%b xen=%b debug=%b\n"
      ki.ki_supports_isa_pvpanic ki.ki_supports_virtio_socket
      ki.ki_is_xen_pv_only_kernel ki.ki_is_debug

let rex_ko = PCRE.compile "\\.k?o(?:\\.(?:xz|zst))?$"
let rex_ko_extract = PCRE.compile "/([^/]+)\\.k?o(?:\\.(?:xz|zst))?$"

let detect_kernels (g : G.guestfs) root bootloader apps =
  (* What kernel/kernel-like packages are installed on the current guest? *)
  let installed_kernels : kernel_info list =
    let check_config feature = function
      | None -> false
      | Some config ->
        let prefix = "^CONFIG_" ^ String.uppercase_ascii feature ^ "=" in
        let lines = g#grep ~extended:true prefix config in
        let lines = Array.to_list lines in
        match lines with
        | [] -> false
        | line :: _ ->
          let kind = snd (String.split "=" line) in
          (match kind with
          | "m" | "y" -> true
          | _ -> false
          )
    in
    let rex_initrd =
      let is_debian_family =
        let distro = g#inspect_get_distro root in
        match distro with
        | "debian" | "ubuntu" | "linuxmint" | "kalilinux" -> true
        | _ -> false in
      if is_debian_family then
        PCRE.compile "^initrd.img-.*$"
      else
        PCRE.compile "^initr(?:d|amfs)-.*(?:\\.img)?$" in
    let kernel_pkgs = List.filter (
      fun { G.app2_name = name } ->
        name = "kernel"
          || (String.is_prefix name "kernel-" && not (String.is_suffix name "-devel"))
          || String.is_prefix name "linux-image-"
    ) apps in
    if verbose () then (
      let names = List.map (fun { G.app2_name = name } -> name) kernel_pkgs in
      eprintf "info: candidate kernel packages in this guest: %s%!\n"
        (String.concat " " names)
    );
    List.filter_map (
      fun ({ G.app2_name = name } as app) ->
        (try
           (* For each kernel, list the files directly owned by the kernel. *)
           let files = Linux.file_list_of_package g root app in

           (* Which of these is the kernel itself?  Also, make sure to check
            * it exists by stat'ing it.
            *)
           let vmlinuz = List.find (
             fun filename -> String.is_prefix filename "/boot/vmlinuz-"
           ) files in
           let vmlinuz_stat =
             try g#statns vmlinuz with G.Error _ -> raise Not_found in

           (* Determine the modpath from the package, falling back to the
            * version in the vmlinuz file name.
            *)
           let modpath, version =
             let prefix = "/lib/modules/" in
             let prefix_len = String.length prefix in
             try
               let modpath, version = List.find_map (
                 fun filename ->
                   let filename_len = String.length filename in
                   if filename_len > prefix_len &&
                      String.is_prefix filename prefix then (
                     let version = String.sub filename prefix_len
                                              (filename_len - prefix_len) in
                     Some (filename, version)
                   ) else
                     None
               ) files in
               (* Fall back to the version in the vmlinuz file name not only if
                * a candidate pathname couldn't be found under /lib/modules/,
                * but also in case the candidate pathname doesn't reference a
                * directory. See RHBZ#2175703.
                *
                * Note that this "is_dir" check is deliberately kept outside of
                * the "find_map"'s mapper function above: we want the first
                * candidate *to be* a directory, and not the first candidate
                * *that is* a directory.
                *)
               if not (g#is_dir ~followsymlinks:true modpath) then
                 raise Not_found;
               modpath, version
             with Not_found ->
               let version =
                 String.sub vmlinuz 14 (String.length vmlinuz - 14) in
               let modpath = prefix ^ version in
               (* Check that the modpath exists. *)
               if not (g#is_dir ~followsymlinks:true modpath) then
                 raise Not_found;
               modpath, version in

           (* Find the initramfs which corresponds to the kernel.
            * Since the initramfs is built at runtime, and doesn't have
            * to be covered by the RPM file list, this is basically
            * guesswork.
            *)
           let initrd =
             let files = g#ls "/boot" in
             let files = Array.to_list files in
             let files =
               List.filter (fun n -> PCRE.matches rex_initrd n) files in
             let files =
               List.filter (
                 fun n ->
                   String.find n version >= 0
               ) files in
             (* Don't consider kdump initramfs images (RHBZ#1138184). *)
             let files =
               List.filter (fun n -> String.find n "kdump" == -1) files in
             (* If several files match, take the shortest match.  This
              * handles the case where we have a mix of same-version non-Xen
              * and Xen kernels:
              *   initrd-2.6.18-308.el5.img
              *   initrd-2.6.18-308.el5xen.img
              * and kernel 2.6.18-308.el5 (non-Xen) will match both
              * (RHBZ#1141145).
              *)
             let cmp a b = compare (String.length a) (String.length b) in
             let files = List.sort cmp files in
             match files with
             | [] ->
               warning (f_"no initrd was found in /boot matching %s %s.")
                 name version;
               None
             | x :: _ -> Some ("/boot/" ^ x) in

           (* Get all modules, which might include custom-installed
            * modules that don't appear in 'files' list above.
            *)
           let modules = g#find modpath in
           let modules = Array.to_list modules in
           let modules =
             List.filter (fun m -> PCRE.matches rex_ko m) modules in
           assert (List.length modules > 0);

           (* Determine the kernel architecture by looking at the
            * architecture of a kernel module.
            *
            * To avoid architecture detection issues with 3rd party
            * modules (RHBZ#1690574), try to pick one of the well
            * known modules, if available.  Otherwise, an arbitrary
            * module is used.
            *)
           let arch =
             (* Well known kernel modules. *)
             let candidates = [ "virtio"; "kvm" ] in
             let all_candidates = List.flatten (
               List.map (
                 fun f ->
                   [ "/" ^ f ^ ".o";
                     "/" ^ f ^ ".ko";
                     "/" ^ f ^ ".ko.xz";
                     "/" ^ f ^ ".ko.zst";
                   ]
               ) candidates
             ) in
             let candidate =
               try
                 List.find (
                   fun m ->
                     List.exists (String.is_suffix m) all_candidates
                 ) modules
               with Not_found ->
                 (* No known module found, pick an arbitrary one
                  * (the first).
                  *)
                 List.hd modules in
             let candidate = modpath ^ candidate in
             g#file_architecture (g#realpath candidate) in

           (* Just return the module names, without path or extension. *)
           let modules = List.filter_map (
             fun m ->
               if PCRE.matches rex_ko_extract m then
                 Some (PCRE.sub 1)
               else
                 None
           ) modules in
           assert (List.length modules > 0);

           let config_file =
             let cfg = "/boot/config-" ^ version in
             if g#is_file ~followsymlinks:true cfg then Some cfg
             else None in

           let kernel_supports what kconf =
             List.mem what modules || check_config kconf config_file in

           let supports_virtio_blk =
             kernel_supports "virtio_blk" "VIRTIO_BLK" in
           let supports_virtio_net =
             kernel_supports "virtio_net" "VIRTIO_NET" in
           let supports_virtio_rng =
             kernel_supports "virtio-rng" "HW_RANDOM_VIRTIO" in
           let supports_virtio_balloon =
             kernel_supports "virtio_balloon" "VIRTIO_BALLOON" in
           let supports_isa_pvpanic =
             kernel_supports "pvpanic" "PVPANIC" in
           let supports_virtio_socket =
               kernel_supports "vmw_vsock_virtio_transport" "VIRTIO_VSOCKETS" in
           let is_xen_pv_only_kernel =
             check_config "X86_XEN" config_file ||
             check_config "X86_64_XEN" config_file in

           (* If the package name is like "kernel-debug", then it's
            * a debug kernel.
            *)
           let is_debug =
             String.is_suffix app.G.app2_name "-debug" ||
             String.is_suffix app.G.app2_name "-dbg" in

           Some {
             ki_app  = app;
             ki_name = name;
             ki_version = version;
             ki_arch = arch;
             ki_vmlinuz = vmlinuz;
             ki_vmlinuz_stat = vmlinuz_stat;
             ki_initrd = initrd;
             ki_modpath = modpath;
             ki_modules = modules;
             ki_supports_virtio_blk = supports_virtio_blk;
             ki_supports_virtio_net = supports_virtio_net;
             ki_supports_virtio_rng = supports_virtio_rng;
             ki_supports_virtio_balloon = supports_virtio_balloon;
             ki_supports_isa_pvpanic = supports_isa_pvpanic;
             ki_supports_virtio_socket = supports_virtio_socket;
             ki_is_xen_pv_only_kernel = is_xen_pv_only_kernel;
             ki_is_debug = is_debug;
             ki_config_file = config_file;
           }

         with Not_found -> None
        )
    ) kernel_pkgs in

  if verbose () then (
    eprintf "info: installed kernel packages in this guest:\n";
    List.iter (print_kernel_info stderr "\t") installed_kernels;
    flush stderr
  );

  if installed_kernels = [] then
    error (f_"no installed kernel packages were found.\n\nThis probably indicates that %s was unable to inspect this guest properly.")
      prog;

  (* Now the difficult bit.  Get the bootloader kernels.  The first in this
   * list is the default booting kernel.
   *)
  let bootloader_kernels : kernel_info list =
    let vmlinuzes = bootloader#list_kernels in

    (* Map these to installed kernels. *)
    List.filter_map (
      fun vmlinuz ->
        try
          let statbuf = g#statns vmlinuz in
          let kernel =
            List.find (
              fun { ki_vmlinuz_stat = s } ->
                statbuf.G.st_dev = s.G.st_dev && statbuf.G.st_ino = s.G.st_ino
            ) installed_kernels in
          Some kernel
        with
        | Not_found -> None
        | G.Error msg as exn ->
          (* If it isn't "no such file or directory", then re-raise it. *)
          if g#last_errno () <> G.Errno.errno_ENOENT then raise exn;
          warning (f_"ignoring kernel %s in bootloader, as it does not exist.")
            vmlinuz;
          None
    ) vmlinuzes in

  if verbose () then (
    eprintf "info: kernels offered by the bootloader in this guest (first in list is default):\n";
    List.iter (print_kernel_info stderr "\t") bootloader_kernels;
    flush stderr
  );

  if bootloader_kernels = [] then
    error (f_"no kernels were found in the bootloader configuration.\n\nThis probably indicates that %s was unable to parse the bootloader configuration of this guest.")
      prog;

  bootloader_kernels