-
Notifications
You must be signed in to change notification settings - Fork 164
/
inspect.ml
399 lines (345 loc) · 12.6 KB
/
inspect.ml
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
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
(* guestfs-inspection
* Copyright (C) 2009-2020 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.
*)
open Printf
open Std_utils
open Utils
open Mountable
open Inspect_types
let re_primary_partition = PCRE.compile "^/dev/(?:h|s|v)d.[1234]$"
let rec inspect_os () =
Mount_utils.umount_all ();
(* Iterate over all detected filesystems. Inspect each one in turn. *)
let fses = Listfs.list_filesystems () in
let fses =
List.filter_map (
fun (mountable, vfs_type) ->
Inspect_fs.check_for_filesystem_on mountable vfs_type
) fses in
if verbose () then (
eprintf "inspect_os: fses:\n";
List.iter (fun fs -> eprintf "%s" (string_of_fs fs)) fses;
flush stderr
);
(* The OS inspection information for CoreOS are gathered by inspecting
* multiple filesystems. Gather all the inspected information in the
* inspect_fs struct of the root filesystem.
*)
let fses = collect_coreos_inspection_info fses in
(* Check if the same filesystem was listed twice as root in fses.
* This may happen for the *BSD root partition where an MBR partition
* is a shadow of the real root partition probably /dev/sda5
*)
let fses = check_for_duplicated_bsd_root fses in
(* For Linux guests with a separate /usr filesystem, merge some of the
* inspected information in that partition to the inspect_fs struct
* of the root filesystem.
*)
let fses = collect_linux_inspection_info fses in
(* Save what we found in a global variable. *)
Inspect_types.inspect_fses := fses;
(* At this point we have, in the handle, a list of all filesystems
* found and data about each one. Now we assemble the list of
* filesystems which are root devices.
*
* Fall through to inspect_get_roots to do that.
*)
inspect_get_roots ()
(* Traverse through the filesystem list and find out if it contains
* the [/] and [/usr] filesystems of a CoreOS image. If this is the
* case, sum up all the collected information on the root fs.
*)
and collect_coreos_inspection_info fses =
(* Split the list into CoreOS root(s), CoreOS usr(s), and
* everything else.
*)
let rec loop roots usrs others = function
| [] -> roots, usrs, others
| ({ role = RoleRoot { distro = Some DISTRO_COREOS } } as r) :: rest ->
loop (r::roots) usrs others rest
| ({ role = RoleUsr { distro = Some DISTRO_COREOS } } as u) :: rest ->
loop roots (u::usrs) others rest
| o :: rest ->
loop roots usrs (o::others) rest
in
let roots, usrs, others = loop [] [] [] fses in
match roots with
(* If there are no CoreOS roots, then there's nothing to do. *)
| [] -> fses
(* If there are more than one CoreOS roots, we cannot inspect the guest. *)
| _::_::_ -> failwith "multiple CoreOS root filesystems found"
| [root] ->
match usrs with
(* If there are no CoreOS usr partitions, nothing to do. *)
| [] -> fses
| usrs ->
(* CoreOS is designed to contain 2 /usr partitions (USR-A, USR-B):
* https://coreos.com/docs/sdk-distributors/sdk/disk-partitions/
* One is active and one passive. During the initial boot, the
* passive partition is empty and it gets filled up when an
* update is performed. Then, when the system reboots, the
* boot loader is instructed to boot from the passive partition.
* If both partitions are valid, we cannot determine which the
* active and which the passive is, unless we peep into the
* boot loader. As a workaround, we check the OS versions and
* pick the one with the higher version as active.
*)
let compare_versions u1 u2 =
let v1 =
match u1 with
| { role = RoleUsr { version = Some v } } -> v
| _ -> (0, 0) in
let v2 =
match u2 with
| { role = RoleUsr { version = Some v } } -> v
| _ -> (0, 0) in
compare v2 v1 (* reverse order *)
in
let usrs = List.sort compare_versions usrs in
let usr = List.hd usrs in
merge usr root;
root :: others
(* On *BSD systems, sometimes [/dev/sda[1234]] is a shadow of the
* real root filesystem that is probably [/dev/sda5] (see:
* [http://www.freebsd.org/doc/handbook/disk-organization.html])
*)
and check_for_duplicated_bsd_root fses =
try
let is_primary_partition = function
| { m_type = (MountablePath | MountableBtrfsVol _) } -> false
| { m_type = MountableDevice; m_device = d } ->
PCRE.matches re_primary_partition d
in
(* Try to find a "BSD primary", if there is one. *)
let bsd_primary =
List.find (
function
| { fs_location = { mountable };
role = RoleRoot { os_type = Some t } } ->
(t = OS_TYPE_FREEBSD || t = OS_TYPE_NETBSD || t = OS_TYPE_OPENBSD)
&& is_primary_partition mountable
| _ -> false
) fses in
let bsd_primary_os_type =
match bsd_primary with
| { role = RoleRoot { os_type = Some t } } -> t
| _ -> assert false in
(* Try to find a shadow of the primary, and if it is found the
* primary is removed.
*)
let fses_without_bsd_primary = List.filter ((!=) bsd_primary) fses in
let shadow_exists =
List.exists (
function
| { role = RoleRoot { os_type = Some t } } ->
t = bsd_primary_os_type
| _ -> false
) fses_without_bsd_primary in
if shadow_exists then fses_without_bsd_primary else fses
with
Not_found -> fses
(* Traverse through the filesystem list and find out if it contains
* the [/] and [/usr] filesystems of a Linux image (but not CoreOS,
* for which there is a separate [collect_coreos_inspection_info]).
*
* If this is the case, sum up all the collected information on each
* root fs from the respective [/usr] filesystems.
*)
and collect_linux_inspection_info fses =
List.map (
function
| { role = RoleRoot { distro = Some DISTRO_COREOS } } as root -> root
| { role = RoleRoot _ } as root ->
collect_linux_inspection_info_for fses root
| fs -> fs
) fses
(* Traverse through the filesystems and find the /usr filesystem for
* the specified C<root>: if found, merge its basic inspection details
* to the root when they were set (i.e. because the /usr had os-release
* or other ways to identify the OS).
*)
and collect_linux_inspection_info_for fses root =
let root_fstab =
match root with
| { role = RoleRoot { fstab = f } } -> f
| _ -> assert false in
try
let usr =
List.find (
function
| { role = RoleUsr _; fs_location = usr_mp } ->
(* This checks that this usr is found in the fstab of
* the root filesystem.
*)
List.exists (
fun (mountable, _) ->
usr_mp.mountable = mountable
) root_fstab
| _ -> false
) fses in
eprintf "collect_linux_inspection_info_for: merging:\n%sinto:\n%s"
(string_of_fs usr) (string_of_fs root);
merge usr root;
root
with
Not_found -> root
and inspect_get_roots () =
let fses = !Inspect_types.inspect_fses in
let roots =
List.filter_map (
fun fs -> try Some (root_of_fs fs) with Invalid_argument _ -> None
) fses in
if verbose () then (
eprintf "inspect_get_roots: roots:\n";
List.iter (fun root -> eprintf "%s" (string_of_root root)) roots;
flush stderr
);
(* Only return the list of mountables, since subsequent calls will
* be used to retrieve the other information.
*)
List.map (fun { root_location = { mountable = m } } -> m) roots
and root_of_fs =
function
| { fs_location = location; role = RoleRoot data } ->
{ root_location = location; inspection_data = data }
| { role = (RoleUsr _ | RoleSwap | RoleOther) } ->
invalid_arg "root_of_fs"
and inspect_get_mountpoints root_mountable =
let root = search_for_root root_mountable in
let fstab = root.inspection_data.fstab in
(* If no fstab information (Windows) return just the root. *)
if fstab = [] then
[ "/", root_mountable ]
else (
List.filter_map (
fun (mountable, mp) ->
if String.length mp > 0 && mp.[0] = '/' then
Some (mp, mountable)
else
None
) fstab
)
and inspect_get_filesystems root_mountable =
let root = search_for_root root_mountable in
let fstab = root.inspection_data.fstab in
(* If no fstab information (Windows) return just the root. *)
if fstab = [] then
[ root_mountable ]
else
List.map fst fstab
and inspect_get_format root = "installed"
and inspect_get_type root =
let root = search_for_root root in
match root.inspection_data.os_type with
| Some v -> string_of_os_type v
| None -> "unknown"
and inspect_get_distro root =
let root = search_for_root root in
match root.inspection_data.distro with
| Some v -> string_of_distro v
| None -> "unknown"
and inspect_get_package_format root =
let root = search_for_root root in
match root.inspection_data.package_format with
| Some v -> string_of_package_format v
| None -> "unknown"
and inspect_get_package_management root =
let root = search_for_root root in
match root.inspection_data.package_management with
| Some v -> string_of_package_management v
| None -> "unknown"
and inspect_get_product_name root =
let root = search_for_root root in
match root.inspection_data.product_name with
| Some v -> v
| None -> "unknown"
and inspect_get_product_variant root =
let root = search_for_root root in
match root.inspection_data.product_variant with
| Some v -> v
| None -> "unknown"
and inspect_get_major_version root =
let root = search_for_root root in
match root.inspection_data.version with
| Some (major, _) -> major
| None -> 0
and inspect_get_minor_version root =
let root = search_for_root root in
match root.inspection_data.version with
| Some (_, minor) -> minor
| None -> 0
and inspect_get_arch root =
let root = search_for_root root in
match root.inspection_data.arch with
| Some v -> v
| None -> "unknown"
and inspect_get_hostname root =
let root = search_for_root root in
match root.inspection_data.hostname with
| Some v -> v
| None -> "unknown"
and inspect_get_build_id root =
let root = search_for_root root in
match root.inspection_data.build_id with
| Some v -> v
| None -> "unknown"
and inspect_get_windows_systemroot root =
let root = search_for_root root in
match root.inspection_data.windows_systemroot with
| Some v -> v
| None ->
failwith "not a Windows guest, or systemroot could not be determined"
and inspect_get_windows_system_hive root =
let root = search_for_root root in
match root.inspection_data.windows_system_hive with
| Some v -> v
| None ->
failwith "not a Windows guest, or system hive not found"
and inspect_get_windows_software_hive root =
let root = search_for_root root in
match root.inspection_data.windows_software_hive with
| Some v -> v
| None ->
failwith "not a Windows guest, or software hive not found"
and inspect_get_windows_current_control_set root =
let root = search_for_root root in
match root.inspection_data.windows_current_control_set with
| Some v -> v
| None ->
failwith "not a Windows guest, or CurrentControlSet could not be determined"
and inspect_is_live root = false
and inspect_is_netinst root = false
and inspect_is_multipart root = false
and inspect_get_drive_mappings root =
let root = search_for_root root in
root.inspection_data.drive_mappings
and search_for_root root =
let fses = !Inspect_types.inspect_fses in
if fses = [] then
failwith "no inspection data: call guestfs_inspect_os first";
let root =
try
List.find (
function
| { fs_location = { mountable = m }; role = RoleRoot _ } -> root = m
| _ -> false
) fses
with
Not_found ->
failwithf "%s: root device not found: only call this function with a root device previously returned by guestfs_inspect_os"
(Mountable.to_string root) in
root_of_fs root