1111import time
1212from argparse import ArgumentParser
1313from collections .abc import Iterator
14+ from functools import cache
15+ from pathlib import Path
1416from select import EPOLLHUP , EPOLLIN , epoll
1517from shutil import which
1618from types import TracebackType
@@ -25,25 +27,32 @@ class ArgumentError(Exception):
2527 pass
2628
2729
28- def get_master (interface ):
30+ def cpu_count () -> int :
31+ count = os .cpu_count ()
32+ if not count :
33+ return 1
34+
35+ return count - 1
36+
37+
38+ def get_master (interface : str ) -> str | None :
2939 master_path = pathlib .Path (f'/sys/class/net/{ interface } /master' )
3040 return master_path .readlink ().name if master_path .exists () else None
3141
3242
33- def gray (text ) :
43+ def gray (text : str ) -> str :
3444 return f'\033 [38;5;246m{ text } \033 [0m'
3545
3646
37- def orange (text ) :
47+ def orange (text : str ) -> str :
3848 return f'\033 [38;5;208m{ text } \033 [0m'
3949
4050
41- def red (text ) :
51+ def red (text : str ) -> str :
4252 return f'\033 [31m{ text } \033 [0m'
4353
4454
45- sudo_password = None # Gets populated later
46- harddrives = {}
55+ harddrives : dict [Path , str ] = {}
4756username = getpass .getuser ()
4857groupname = grp .getgrgid (os .getgid ()).gr_name
4958
@@ -71,37 +80,37 @@ def red(text):
7180hardware .add_argument ('--bios' , action = 'store_true' , help = 'Disables EFI (edk2/ovmf) and uses BIOS support instead' , default = False )
7281hardware .add_argument ('--memory' , nargs = '?' , help = 'Ammount of memory to supply the machine' , default = 8192 )
7382hardware .add_argument ('--harddrive' , action = 'append' , help = 'Sets up one or more virtio-scsi-pci, size is defined by --harddrive test.qcow2:15G' , type = str )
74- hardware .add_argument ('--cpu' , help = 'Sets the number of cores to allocate (default nproc -1)' , type = str , default = os . cpu_count () - 1 if os . cpu_count () else 1 )
83+ hardware .add_argument ('--cpu' , help = 'Sets the number of cores to allocate (default nproc -1)' , type = str , default = cpu_count ())
7584hardware .add_argument ('--resolution' , help = "Sets Qemu's VGA resolution" , type = str , default = '1920x1107' )
7685
7786kernel = parser .add_argument_group ('Kernel' , '--kernel specific arguments' )
7887kernel .add_argument ('--initrd' , nargs = '?' , help = 'Defines which ISO to run (skips build all together)' , default = None , type = pathlib .Path )
7988
80- args , unknowns = parser .parse_known_args () # pylint: disable=redefined-outer-name
89+ cli_args , _ = parser .parse_known_args ()
8190
82- if args .bios and args .uki :
91+ if cli_args .bios and cli_args .uki :
8392 raise ArgumentError ('Cannot boot a --uki image with --bios mode (at least not that I know of).' )
8493
85- if args .uki is None and args .kernel is None and args .iso is None and args .harddrive is None :
94+ if cli_args .uki is None and cli_args .kernel is None and cli_args .iso is None and cli_args .harddrive is None :
8695 raise ArgumentError ('Cannot boot this machine, define at least one of: --uki, --kernel, --iso, --harddrive' )
8796
88- if args .bridge is None and args .bridge_master :
97+ if cli_args .bridge is None and cli_args .bridge_master :
8998 raise ArgumentError ('Cannot use --bridge-master without defining --bridge' )
9099
91- if args .bridge is None and args .bridge_mac :
100+ if cli_args .bridge is None and cli_args .bridge_mac :
92101 raise ArgumentError ('Cannot use --bridge-mac without defining --bridge' )
93- elif args .bridge and args .bridge_mac is None :
94- args .bridge_mac = '52:54:00:00:00:1'
102+ elif cli_args .bridge and cli_args .bridge_mac is None :
103+ cli_args .bridge_mac = '52:54:00:00:00:1'
95104
96- if args .tap and not args .bridge and get_master (args .tap ) is None :
105+ if cli_args .tap and not cli_args .bridge and get_master (cli_args .tap ) is None :
97106 # We'll allow it, because maybe we're tesing what happens without networking, but the NIC exists.
98107 # Or the user has some creative iptables/nftables forwarding.
99108 print (orange ('--tap does not have a master, consider adding --bridge or manual set a master using ip-link(8).' ))
100109
101- if args .tap is None and args .bridge :
110+ if cli_args .tap is None and cli_args .bridge :
102111 print (orange ("--bridge* arguments will be ignored since there's no --tap defined" ))
103- elif args .tap and args .tap_mac is None :
104- args .tap_mac = '52:54:00:00:00:2'
112+ elif cli_args .tap and cli_args .tap_mac is None :
113+ cli_args .tap_mac = '52:54:00:00:00:2'
105114
106115
107116class SysCallError (Exception ):
@@ -176,7 +185,7 @@ def __contains__(self, key: bytes) -> bool:
176185
177186 return False
178187
179- def __iter__ (self , * args : str , ** kwargs : dict [str , Any ]) -> Iterator [bytes ]: # pylint: disable=redefined-outer-name
188+ def __iter__ (self , * args : str , ** kwargs : dict [str , Any ]) -> Iterator [bytes ]:
180189 last_line = self ._trace_log .rfind (b'\n ' )
181190 lines = filter (None , self ._trace_log [self ._trace_log_pos : last_line ].splitlines ())
182191 for line in lines :
@@ -334,81 +343,82 @@ def decode(self, encoding: str = 'UTF-8') -> str:
334343 return self ._trace_log .decode (encoding )
335344
336345
337- def ensure_sudo ():
338- global sudo_password # pylint: disable=global-statement
346+ @cache
347+ def get_sudo_password () -> str :
348+ sudo_password = getpass .getpass (f'[sudo] password for { username } : ' )
349+ if sudo_password == '' :
350+ raise ValueError ('Certain commands need sudo to work and no sudo password was given.' )
339351
340- if sudo_password is None :
341- if (sudo_password := getpass .getpass (f'[sudo] password for { username } : ' )) == '' :
342- raise ValueError ('Certain commands need sudo to work and no sudo password was given.' )
352+ return sudo_password
343353
344354
345- def setup_networking ():
346- if args .tap :
347- if pathlib .Path (f'/sys/class/net/{ args .tap } ' ).exists () is False :
348- print (gray (f'Creating { args .tap } for user { username } and group { groupname } ' ))
349- handle , pw_prompted = SysCommandWorker (f'sudo ip tuntap add dev { args .tap } mode tap user { username } group { groupname } ' ), False
355+ def setup_networking () -> None :
356+ if cli_args .tap :
357+ if pathlib .Path (f'/sys/class/net/{ cli_args .tap } ' ).exists () is False :
358+ print (gray (f'Creating { cli_args .tap } for user { username } and group { groupname } ' ))
359+ handle , pw_prompted = SysCommandWorker (f'sudo ip tuntap add dev { cli_args .tap } mode tap user { username } group { groupname } ' ), False
350360 while handle .is_alive ():
351361 if b'password for' in handle and pw_prompted is False :
352- ensure_sudo ()
353- handle .write (bytes (sudo_password , 'UTF-8' ))
362+ sudo_pw = get_sudo_password ()
363+ handle .write (bytes (sudo_pw , 'UTF-8' ))
354364 pw_prompted = True
355365
356- if args .bridge :
357- if pathlib .Path (f'/sys/class/net/{ args .bridge } ' ).exists () is False :
358- print (gray (f'Creating { args .bridge } ' ))
359- handle , pw_prompted = SysCommandWorker (f'sudo ip link add name { args .bridge } type bridge' ), False
366+ if cli_args .bridge :
367+ if pathlib .Path (f'/sys/class/net/{ cli_args .bridge } ' ).exists () is False :
368+ print (gray (f'Creating { cli_args .bridge } ' ))
369+ handle , pw_prompted = SysCommandWorker (f'sudo ip link add name { cli_args .bridge } type bridge' ), False
360370 while handle .is_alive ():
361371 if b'password for' in handle and pw_prompted is False :
362- ensure_sudo ()
363- handle .write (bytes (sudo_password , 'UTF-8' ))
372+ sudo_pw = get_sudo_password ()
373+ handle .write (bytes (sudo_pw , 'UTF-8' ))
364374 pw_prompted = True
365375
366- if args .bridge_mac :
367- handle , pw_prompted = SysCommandWorker (f'sudo ip link set dev { args .bridge } address { args .bridge_mac } ' ), False
368- print (gray (f'Setting bridge { args .bridge } MAC address to { args .bridge_mac } ' ))
376+ if cli_args .bridge_mac :
377+ handle , pw_prompted = SysCommandWorker (f'sudo ip link set dev { cli_args .bridge } address { cli_args .bridge_mac } ' ), False
378+ print (gray (f'Setting bridge { cli_args .bridge } MAC address to { cli_args .bridge_mac } ' ))
369379 while handle .is_alive ():
370380 if b'password for' in handle and pw_prompted is False :
371- ensure_sudo ()
372- handle .write (bytes (sudo_password , 'UTF-8' ))
381+ sudo_pw = get_sudo_password ()
382+ handle .write (bytes (sudo_pw , 'UTF-8' ))
373383 pw_prompted = True
374384
375- if args .bridge_master and get_master (args .bridge ) != args .bridge_master :
376- handle , pw_prompted = SysCommandWorker (f'sudo ip link set dev { args .bridge_master } master { args .bridge } ' ), False
377- print (gray (f'Setting interface { args .bridge_master } master to { args . bridge } ' ))
385+ if cli_args .bridge_master and get_master (cli_args .bridge ) != cli_args .bridge_master :
386+ handle , pw_prompted = SysCommandWorker (f'sudo ip link set dev { cli_args .bridge_master } master { cli_args .bridge } ' ), False
387+ print (gray (f'Setting interface { cli_args .bridge_master } master to { cli_args } ' ))
378388 while handle .is_alive ():
379389 if b'password for' in handle and pw_prompted is False :
380- ensure_sudo ()
381- handle .write (bytes (sudo_password , 'UTF-8' ))
390+ sudo_pw = get_sudo_password ()
391+ handle .write (bytes (sudo_pw , 'UTF-8' ))
382392 pw_prompted = True
383393
384- print (gray (f'Setting interface { args .tap } master to { args .bridge } ' ))
385- handle , pw_prompted = SysCommandWorker (f'sudo ip link set dev { args .tap } master { args .bridge } ' ), False
394+ print (gray (f'Setting interface { cli_args .tap } master to { cli_args .bridge } ' ))
395+ handle , pw_prompted = SysCommandWorker (f'sudo ip link set dev { cli_args .tap } master { cli_args .bridge } ' ), False
386396 while handle .is_alive ():
387397 if b'password for' in handle and pw_prompted is False :
388- ensure_sudo ()
389- handle .write (bytes (sudo_password , 'UTF-8' ))
398+ sudo_pw = get_sudo_password ()
399+ handle .write (bytes (sudo_pw , 'UTF-8' ))
390400 pw_prompted = True
391401
392- print (gray (f'Bringing up bridge { args .bridge } ' ))
393- handle , pw_prompted = SysCommandWorker (f'sudo ip link set dev { args .bridge } up' ), False
402+ print (gray (f'Bringing up bridge { cli_args .bridge } ' ))
403+ handle , pw_prompted = SysCommandWorker (f'sudo ip link set dev { cli_args .bridge } up' ), False
394404 while handle .is_alive ():
395405 if b'password for' in handle and pw_prompted is False :
396- ensure_sudo ()
397- handle .write (bytes (sudo_password , 'UTF-8' ))
406+ sudo_pw = get_sudo_password ()
407+ handle .write (bytes (sudo_pw , 'UTF-8' ))
398408 pw_prompted = True
399409
400- print (gray (f'Bringing interface { args .tap } up' ))
401- handle , pw_prompted = SysCommandWorker (f'sudo ip link set dev { args .tap } up' ), False
410+ print (gray (f'Bringing interface { cli_args .tap } up' ))
411+ handle , pw_prompted = SysCommandWorker (f'sudo ip link set dev { cli_args .tap } up' ), False
402412 while handle .is_alive ():
403413 if b'password for' in handle and pw_prompted is False :
404- ensure_sudo ()
405- handle .write (bytes (sudo_password , 'UTF-8' ))
414+ sudo_pw = get_sudo_password ()
415+ handle .write (bytes (sudo_pw , 'UTF-8' ))
406416 pw_prompted = True
407417
408418
409- def setup_disks ():
410- if args .harddrive :
411- for harddrive_arg in args .harddrive :
419+ def setup_disks () -> None :
420+ if cli_args .harddrive :
421+ for harddrive_arg in cli_args .harddrive :
412422 path , size = harddrive_arg .split (':' )
413423 path = pathlib .Path (path .strip ()).expanduser ().resolve ().absolute ()
414424 harddrives [path ] = size .strip ()
@@ -425,7 +435,8 @@ def setup_disks():
425435setup_networking ()
426436setup_disks ()
427437
428- if args .uki or args .bios is False :
438+ disk_paths_hash = ''
439+ if cli_args .uki or cli_args .bios is False :
429440 disk_paths_hash = hashlib .sha1 (('' .join (sorted ([str (x ) for x in harddrives .keys ()]))).encode ()).hexdigest ()
430441
431442 shutil .copy2 ('/usr/share/ovmf/x64/OVMF_CODE.secboot.4m.fd' , f'./OVMF_CODE.secboot.4m.fd.{ disk_paths_hash } ' )
@@ -439,16 +450,16 @@ def setup_disks():
439450qemu += ' -object rng-random,filename=/dev/urandom,id=rng0'
440451qemu += ' -device virtio-rng-pci,rng=rng0'
441452qemu += ' -global driver=cfi.pflash01,property=secure,value=on'
442- qemu += f' -smp { args .cpu } ,sockets=1,dies=1,cores={ args .cpu } ,threads=1'
453+ qemu += f' -smp { cli_args .cpu } ,sockets=1,dies=1,cores={ cli_args .cpu } ,threads=1'
443454# qemu += f' -vga vga'
444- qemu += f' -device VGA,edid=on,xres={ args .resolution .split ("x" )[0 ]} ,yres={ args .resolution .split ("x" )[1 ]} '
455+ qemu += f' -device VGA,edid=on,xres={ cli_args .resolution .split ("x" )[0 ]} ,yres={ cli_args .resolution .split ("x" )[1 ]} '
445456qemu += ' -device intel-iommu,device-iotlb=on,caching-mode=on'
446- qemu += f' -m { args .memory } '
447- if args .bios is False :
457+ qemu += f' -m { cli_args .memory } '
458+ if cli_args .bios is False :
448459 qemu += f' -drive if=pflash,format=raw,readonly=on,file=./OVMF_CODE.secboot.4m.fd.{ disk_paths_hash } '
449460 qemu += f' -drive if=pflash,format=raw,file=./OVMF_VARS.4m.fd.{ disk_paths_hash } '
450- if args .uki :
451- qemu += f' -kernel { args .uki } '
461+ if cli_args .uki :
462+ qemu += f' -kernel { cli_args .uki } '
452463 boot_index += 1
453464scsi_index = 0
454465for scsi_index , hdd in enumerate (harddrives .keys ()):
@@ -460,18 +471,18 @@ def setup_disks():
460471 qemu += f' -blockdev \' {{"driver":"file","filename":"{ hdd } ","aio":"threads","node-name":"libvirt-{ scsi_index } -storage","cache":{{"direct":false,"no-flush":false}},"auto-read-only":true,"discard":"unmap"}}\' ' # noqa: E501
461472 qemu += f' -blockdev \' {{"node-name":"libvirt-{ scsi_index } -format","read-only":false,"discard":"unmap","cache":{{"direct":true,"no-flush":false}},"driver":"qcow2","file":"libvirt-{ scsi_index } -storage","backing":null}}\' ' # noqa: E501
462473 boot_index += 1
463- if args .iso :
474+ if cli_args .iso :
464475 qemu += f' -device virtio-scsi-pci,bus=pcie.0,id=scsi{ scsi_index + 1 } '
465476 qemu += f' -device scsi-cd,drive=cdrom0,bus=scsi{ scsi_index + 1 } .0,bootindex={ boot_index } '
466- qemu += f' -drive file={ args .iso } ,media=cdrom,if=none,format=raw,cache=none,id=cdrom0'
477+ qemu += f' -drive file={ cli_args .iso } ,media=cdrom,if=none,format=raw,cache=none,id=cdrom0'
467478 boot_index += 1
468479
469- # if args .vfio:
470- # qemu += f' -drive file={args .vfio},index=2,media=cdrom'
480+ # if cli_args .vfio:
481+ # qemu += f' -drive file={cli_args .vfio},index=2,media=cdrom'
471482
472- if args .tap :
473- qemu += f' -device virtio-net-pci,mac={ args .tap_mac } ,id=network0,netdev=network0.0,status=on,bus=pcie.0'
474- qemu += f' -netdev tap,ifname={ args .tap } ,id=network0.0,script=no,downscript=no'
483+ if cli_args .tap :
484+ qemu += f' -device virtio-net-pci,mac={ cli_args .tap_mac } ,id=network0,netdev=network0.0,status=on,bus=pcie.0'
485+ qemu += f' -netdev tap,ifname={ cli_args .tap } ,id=network0.0,script=no,downscript=no'
475486
476487print (gray (qemu ))
477488
0 commit comments