@@ -6,9 +6,12 @@ import shutil
6
6
import subprocess
7
7
import sys
8
8
import tempfile
9
+ import time
10
+ from multiprocessing import Process
9
11
10
12
from dataclasses import dataclass
11
13
from enum import IntEnum
14
+ import guestfs
12
15
from typing import Callable
13
16
14
17
sys .path .insert (0 , os .path .dirname (os .path .abspath (__file__ )))
@@ -304,6 +307,100 @@ def diff_live_sysroot(diff_from, diff_to):
304
307
git_diff (dir_from , dir_to )
305
308
306
309
310
+ def get_metal_path (build_target ):
311
+ metal_file = build_target .meta .get ('images' , {}).get ('metal' )
312
+
313
+ if not metal_file :
314
+ raise Exception (f"Could not find metal image for build { build_target .id } " )
315
+ return os .path .join (build_target .dir , metal_file ['path' ])
316
+
317
+
318
+ def diff_metal_partitions (diff_from , diff_to ):
319
+ metal_from = get_metal_path (diff_from )
320
+ metal_to = get_metal_path (diff_to )
321
+ diff_cmd_outputs (['sgdisk' , '-p' ], metal_from , metal_to )
322
+
323
+
324
+ def run_guestfs_mount (image_path , mount_target ):
325
+ """This function runs in a background thread."""
326
+ g = None
327
+ try :
328
+ g = guestfs .GuestFS (python_return_dict = True )
329
+ g .set_backend ("direct" )
330
+ g .add_drive_opts (image_path , readonly = 1 )
331
+ g .launch ()
332
+
333
+ # Mount the disks in the guestfs VM
334
+ root = g .findfs_label ("root" )
335
+ g .mount_ro (root , "/" )
336
+ boot = g .findfs_label ("boot" )
337
+ g .mount_ro (boot , "/boot" )
338
+ efi = g .findfs_label ("EFI-SYSTEM" )
339
+ g .mount_ro (efi , "/boot/efi" )
340
+
341
+ # This is a blocking call that runs the FUSE server
342
+ g .mount_local (mount_target )
343
+ g .mount_local_run ()
344
+
345
+ except Exception as e :
346
+ print (f"Error in guestfs process for { image_path } : { e } " , file = sys .stderr )
347
+ finally :
348
+ if g :
349
+ g .close ()
350
+
351
+
352
+ def diff_metal (diff_from , diff_to ):
353
+ metal_from = get_metal_path (diff_from )
354
+ metal_to = get_metal_path (diff_to )
355
+
356
+ mount_dir_from = os .path .join (cache_dir ("metal" ), diff_from .id )
357
+ mount_dir_to = os .path .join (cache_dir ("metal" ), diff_to .id )
358
+
359
+ for d in [mount_dir_from , mount_dir_to ]:
360
+ if os .path .exists (d ):
361
+ shutil .rmtree (d )
362
+ os .makedirs (d )
363
+
364
+ # As the libreguest mount call is blocking until unmounted, let's
365
+ # do that in a separate thread
366
+ p_from = Process (target = run_guestfs_mount , args = (metal_from , mount_dir_from ))
367
+ p_to = Process (target = run_guestfs_mount , args = (metal_to , mount_dir_to ))
368
+
369
+ try :
370
+ p_from .start ()
371
+ p_to .start ()
372
+ # Wait for the FUSE mounts to be ready. We'll check for a known file.
373
+ for i , d in enumerate ([mount_dir_from , mount_dir_to ]):
374
+ p = p_from if i == 0 else p_to
375
+ timeout = 60 # seconds
376
+ start_time = time .time ()
377
+ check_file = os .path .join (d , 'ostree' )
378
+ while not os .path .exists (check_file ):
379
+ time .sleep (1 )
380
+ if time .time () - start_time > timeout :
381
+ raise Exception (f"Timeout waiting for mount in { d } " )
382
+ if not p .is_alive ():
383
+ raise Exception (f"A guestfs process for { os .path .basename (d )} died unexpectedly." )
384
+
385
+ # Now that the mounts are live, we can diff them
386
+ git_diff (mount_dir_from , mount_dir_to )
387
+
388
+ finally :
389
+ # Unmount the FUSE binds, this will make the guestfs mount calls return
390
+ runcmd (['fusermount' , '-u' , mount_dir_from ], check = False )
391
+ runcmd (['fusermount' , '-u' , mount_dir_to ], check = False )
392
+
393
+ # Ensure the background processes are terminated
394
+ def shutdown_process (process ):
395
+ process .joint (timeout = 5 )
396
+ if process .is_alive ():
397
+ process .terminate ()
398
+ process .join ()
399
+
400
+ shutdown_process (p_from )
401
+ shutdown_process (p_to )
402
+
403
+
307
404
def diff_cmd_outputs (cmd , file_from , file_to ):
308
405
with tempfile .NamedTemporaryFile (prefix = cmd [0 ] + '-' ) as f_from , \
309
406
tempfile .NamedTemporaryFile (prefix = cmd [0 ] + '-' ) as f_to :
@@ -356,6 +453,10 @@ DIFFERS = [
356
453
needs_ostree = OSTreeImport .NO , function = diff_live_sysroot_tree ),
357
454
Differ ("live-sysroot" , "Diff live '/root.[ero|squash]fs' (embed into live-rootfs) content" ,
358
455
needs_ostree = OSTreeImport .NO , function = diff_live_sysroot ),
456
+ Differ ("metal-part-table" , "Diff metal disk image partition tables" ,
457
+ needs_ostree = OSTreeImport .NO , function = diff_metal_partitions ),
458
+ Differ ("metal" , "Diff metal disk image content" ,
459
+ needs_ostree = OSTreeImport .NO , function = diff_metal ),
359
460
]
360
461
361
462
if __name__ == '__main__' :
0 commit comments