From 1f1fdfc738756c1caecf08382c9b6ae725fdc70b Mon Sep 17 00:00:00 2001 From: 2Shirt <2xShirt@gmail.com> Date: Mon, 17 Feb 2020 16:44:33 -0700 Subject: [PATCH 01/15] Avoid rare crash when saving average temps --- scripts/wk/hw/sensors.py | 10 +++++++++- 1 file changed, 9 insertions(+), 1 deletion(-) diff --git a/scripts/wk/hw/sensors.py b/scripts/wk/hw/sensors.py index 77cbcfa3..3dc719d7 100644 --- a/scripts/wk/hw/sensors.py +++ b/scripts/wk/hw/sensors.py @@ -158,7 +158,15 @@ class Sensors(): for sources in adapters.values(): for source_data in sources.values(): temps = source_data['Temps'] - source_data[temp_label] = sum(temps) / len(temps) + try: + source_data[temp_label] = sum(temps) / len(temps) + except ZeroDivisionError: + # Going to use unrealistic 0°C instead + LOG.error( + 'No temps saved for %s', + source_data.get('label', 'UNKNOWN'), + ) + source_data[temp_label] = 0 def start_background_monitor( self, out_path, From 0a00e17536e8f63317e8fc1be203433361d7f7b1 Mon Sep 17 00:00:00 2001 From: 2Shirt <2xShirt@gmail.com> Date: Mon, 17 Feb 2020 16:54:51 -0700 Subject: [PATCH 02/15] Avoid another rare crash when saving average temps --- scripts/wk/hw/sensors.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/scripts/wk/hw/sensors.py b/scripts/wk/hw/sensors.py index 3dc719d7..784406da 100644 --- a/scripts/wk/hw/sensors.py +++ b/scripts/wk/hw/sensors.py @@ -150,7 +150,7 @@ class Sensors(): # Get temps for i in range(seconds): - self.update_sensor_data() + self.update_sensor_data(exit_on_thermal_limit=False) sleep(1) # Calculate averages From 94a428f6dabf6a4888b2f154f8430742b58c4c13 Mon Sep 17 00:00:00 2001 From: 2Shirt <2xShirt@gmail.com> Date: Tue, 25 Feb 2020 20:15:13 -0700 Subject: [PATCH 03/15] Added check for missing source/destination * Addresses issue #155 --- scripts/wk/hw/ddrescue.py | 25 +++++++++++++++++++++++-- 1 file changed, 23 insertions(+), 2 deletions(-) diff --git a/scripts/wk/hw/ddrescue.py b/scripts/wk/hw/ddrescue.py index e3433598..61905c14 100644 --- a/scripts/wk/hw/ddrescue.py +++ b/scripts/wk/hw/ddrescue.py @@ -1390,6 +1390,25 @@ def build_sfdisk_partition_line(table_type, dev_path, size, details): return line +def check_for_missing_items(state): + """Check if source or destination dissapeared.""" + items = { + 'Source': state.source, + 'Destination': state.destination, + } + for name, item in items.items(): + if not item: + continue + if hasattr(item, 'path'): + if not item.path.exists(): + std.print_error(f'{name} disappeared') + elif hasattr(item, 'exists'): + if not item.exists(): + std.print_error(f'{name} disappeared') + else: + LOG.error('Unknown %s type: %s', name, item) + + def clean_working_dir(working_dir): """Clean working directory to ensure a fresh recovery session. @@ -1686,7 +1705,8 @@ def main(): state = State() try: state.init_recovery(args) - except std.GenericAbort: + except (FileNotFoundError, std.GenericAbort): + check_for_missing_items(state) std.abort() # Show menu @@ -1926,7 +1946,8 @@ def run_recovery(state, main_menu, settings_menu, dry_run=True): state.mark_started() try: run_ddrescue(state, pair, pass_name, settings, dry_run=dry_run) - except (KeyboardInterrupt, std.GenericAbort): + except (FileNotFoundError, KeyboardInterrupt, std.GenericAbort): + check_for_missing_items(state) abort = True break From 45a6b31910680a5ee067e423d454df1d511b760a Mon Sep 17 00:00:00 2001 From: 2Shirt <2xShirt@gmail.com> Date: Tue, 25 Feb 2020 20:45:59 -0700 Subject: [PATCH 04/15] Added periodic destination health check * Addresses issue #158 --- scripts/wk/hw/ddrescue.py | 52 ++++++++++++++++++++++++++++++++++++++- 1 file changed, 51 insertions(+), 1 deletion(-) diff --git a/scripts/wk/hw/ddrescue.py b/scripts/wk/hw/ddrescue.py index 61905c14..66ac3d5a 100644 --- a/scripts/wk/hw/ddrescue.py +++ b/scripts/wk/hw/ddrescue.py @@ -1390,6 +1390,29 @@ def build_sfdisk_partition_line(table_type, dev_path, size, details): return line +def check_destination_health(destination): + """Check destination health, returns str.""" + result = '' + + # Bail early + if not isinstance(destination, hw_obj.Disk): + # Return empty string + return result + + # Run safety checks + try: + destination.safety_checks() + except hw_obj.CriticalHardwareError: + result = 'Critical hardware error detected on destination' + except hw_obj.SMARTSelfTestInProgressError: + result = 'SMART self-test in progress on destination' + except hw_obj.SMARTNotSupportedError: + pass + + # Done + return result + + def check_for_missing_items(state): """Check if source or destination dissapeared.""" items = { @@ -1820,6 +1843,7 @@ def mount_raw_image_macos(path): def run_ddrescue(state, block_pair, pass_name, settings, dry_run=True): + # pylint: disable=too-many-statements """Run ddrescue using passed settings.""" cmd = build_ddrescue_cmd(block_pair, pass_name, settings) state.update_progress_pane('Active') @@ -1854,6 +1878,13 @@ def run_ddrescue(state, block_pair, pass_name, settings, dry_run=True): if _i % 30 == 0: # Update SMART pane _update_smart_pane() + + # Check destination + warning_message = check_destination_health(state.destination) + if warning_message: + # Error detected on destination, stop recovery + stop_ddrescue(proc) + break if _i % 60 == 0: # Clear ddrescue pane tmux.clear_pane() @@ -1872,7 +1903,7 @@ def run_ddrescue(state, block_pair, pass_name, settings, dry_run=True): LOG.warning('ddrescue stopped by user') warning_message = 'Aborted' std.sleep(2) - exe.run_program(['sudo', 'kill', str(proc.pid)], check=False) + stop_ddrescue(proc, graceful=False) break except subprocess.TimeoutExpired: # Continue to next loop to update panes @@ -2149,6 +2180,25 @@ def set_mode(docopt_args): return mode +def stop_ddrescue(proc, graceful=True): + """Stop ddrescue.""" + running_as_root = os.geteuid() == 0 + + # Graceful exit + if graceful: + if running_as_root: + proc.terminate() + else: + exe.run_program(['sudo', 'kill', str(proc.pid)], check=False) + std.sleep(2) + + # Force exit + if running_as_root: + proc.kill() + else: + exe.run_program(['sudo', 'kill', '-9', str(proc.pid)], check=False) + + def unmount_loopback_device(path): """Unmount loopback device using OS specific methods.""" cmd = [] From a4df2f41d32c0b7b782262d7a1eb412634e62dde Mon Sep 17 00:00:00 2001 From: 2Shirt <2xShirt@gmail.com> Date: Tue, 25 Feb 2020 20:52:08 -0700 Subject: [PATCH 05/15] Added wk.exe.stop_process() * Replaced wk.hw.ddrescue.stop_ddrescue() --- scripts/wk/exe.py | 24 ++++++++++++++++++++++++ scripts/wk/hw/ddrescue.py | 23 ++--------------------- 2 files changed, 26 insertions(+), 21 deletions(-) diff --git a/scripts/wk/exe.py b/scripts/wk/exe.py index e49b51d6..2097469b 100644 --- a/scripts/wk/exe.py +++ b/scripts/wk/exe.py @@ -6,6 +6,7 @@ import logging import os import re import subprocess +import time from threading import Thread from queue import Queue, Empty @@ -214,6 +215,29 @@ def start_thread(function, args=None, daemon=True): return thread +def stop_process(proc, graceful=True): + """Stop process. + + NOTES: proc should be a subprocess.Popen obj. + If graceful is True then a SIGTERM is sent before SIGKILL. + """ + running_as_root = os.geteuid() == 0 + + # Graceful exit + if graceful: + if running_as_root: + proc.terminate() + else: + run_program(['sudo', 'kill', str(proc.pid)], check=False) + time.sleep(2) + + # Force exit + if running_as_root: + proc.kill() + else: + run_program(['sudo', 'kill', '-9', str(proc.pid)], check=False) + + def wait_for_procs(name, exact=True, timeout=None): """Wait for all process matching name.""" LOG.debug('name: %s, exact: %s, timeout: %s', name, exact, timeout) diff --git a/scripts/wk/hw/ddrescue.py b/scripts/wk/hw/ddrescue.py index 66ac3d5a..5d3d7e2e 100644 --- a/scripts/wk/hw/ddrescue.py +++ b/scripts/wk/hw/ddrescue.py @@ -1883,7 +1883,7 @@ def run_ddrescue(state, block_pair, pass_name, settings, dry_run=True): warning_message = check_destination_health(state.destination) if warning_message: # Error detected on destination, stop recovery - stop_ddrescue(proc) + exe.stop_process(proc) break if _i % 60 == 0: # Clear ddrescue pane @@ -1903,7 +1903,7 @@ def run_ddrescue(state, block_pair, pass_name, settings, dry_run=True): LOG.warning('ddrescue stopped by user') warning_message = 'Aborted' std.sleep(2) - stop_ddrescue(proc, graceful=False) + exe.stop_process(proc, graceful=False) break except subprocess.TimeoutExpired: # Continue to next loop to update panes @@ -2180,25 +2180,6 @@ def set_mode(docopt_args): return mode -def stop_ddrescue(proc, graceful=True): - """Stop ddrescue.""" - running_as_root = os.geteuid() == 0 - - # Graceful exit - if graceful: - if running_as_root: - proc.terminate() - else: - exe.run_program(['sudo', 'kill', str(proc.pid)], check=False) - std.sleep(2) - - # Force exit - if running_as_root: - proc.kill() - else: - exe.run_program(['sudo', 'kill', '-9', str(proc.pid)], check=False) - - def unmount_loopback_device(path): """Unmount loopback device using OS specific methods.""" cmd = [] From 24dbdf29fda069b4d508aa8a5ec507bfdbc90bef Mon Sep 17 00:00:00 2001 From: 2Shirt <2xShirt@gmail.com> Date: Mon, 6 Apr 2020 19:46:09 -0600 Subject: [PATCH 06/15] Added Windows Registry functions --- scripts/wk/os/win.py | 183 +++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 183 insertions(+) diff --git a/scripts/wk/os/win.py b/scripts/wk/os/win.py index 7f17af37..77b61999 100644 --- a/scripts/wk/os/win.py +++ b/scripts/wk/os/win.py @@ -5,7 +5,9 @@ import logging import os import pathlib import platform +import winreg +from contextlib import suppress from wk.borrowed import acpi from wk.exe import run_program from wk.io import non_clobber_path @@ -15,6 +17,39 @@ from wk.std import GenericError, GenericWarning, sleep # STATIC VARIABLES LOG = logging.getLogger(__name__) +KNOWN_HIVES = { + 'HKCR': winreg.HKEY_CLASSES_ROOT, + 'HKCU': winreg.HKEY_CURRENT_USER, + 'HKLM': winreg.HKEY_LOCAL_MACHINE, + 'HKU': winreg.HKEY_USERS, + 'HKEY_CLASSES_ROOT': winreg.HKEY_CLASSES_ROOT, + 'HKEY_CURRENT_USER': winreg.HKEY_CURRENT_USER, + 'HKEY_LOCAL_MACHINE': winreg.HKEY_LOCAL_MACHINE, + 'HKEY_USERS': winreg.HKEY_USERS, + } +KNOWN_HIVE_NAMES = { + winreg.HKEY_CLASSES_ROOT: 'HKCR', + winreg.HKEY_CURRENT_USER: 'HKCU', + winreg.HKEY_LOCAL_MACHINE: 'HKLM', + winreg.HKEY_USERS: 'HKU', + winreg.HKEY_CLASSES_ROOT: 'HKEY_CLASSES_ROOT', + winreg.HKEY_CURRENT_USER: 'HKEY_CURRENT_USER', + winreg.HKEY_LOCAL_MACHINE: 'HKEY_LOCAL_MACHINE', + winreg.HKEY_USERS: 'HKEY_USERS', + } +KNOWN_VALUE_TYPES = { + 'BINARY': winreg.REG_BINARY, + 'DWORD': winreg.REG_DWORD, + 'DWORD_LITTLE_ENDIAN': winreg.REG_DWORD_LITTLE_ENDIAN, + 'DWORD_BIG_ENDIAN': winreg.REG_DWORD_BIG_ENDIAN, + 'EXPAND_SZ': winreg.REG_EXPAND_SZ, + 'LINK': winreg.REG_LINK, + 'MULTI_SZ': winreg.REG_MULTI_SZ, + 'NONE': winreg.REG_NONE, + 'QWORD': winreg.REG_QWORD, + 'QWORD_LITTLE_ENDIAN': winreg.REG_QWORD_LITTLE_ENDIAN, + 'SZ': winreg.REG_SZ, + } OS_VERSION = float(platform.win32_ver()[0]) REG_MSISERVER = r'HKLM\SYSTEM\CurrentControlSet\Control\SafeBoot\Network\MSIServer' SLMGR = pathlib.Path(f'{os.environ.get("SYSTEMROOT")}/System32/slmgr.vbs') @@ -177,5 +212,153 @@ def run_sfc_scan(): raise OSError +# Registry Functions +def reg_delete_key(hive, key, recurse=False): + """Delete a key from the registry. + + NOTE: If recurse is False then it will only work on empty keys. + """ + hive = reg_get_hive(hive) + hive_name = KNOWN_HIVE_NAMES.get(hive, '???') + + # Delete subkeys first + if recurse: + with suppress(WindowsError), winreg.OpenKey(hive, key) as open_key: + while True: + subkey = fr'{key}\{winreg.EnumKey(open_key, 0)}' + reg_delete_key(hive, subkey, recurse=recurse) + + # Delete key + try: + winreg.DeleteKey(hive, key) + LOG.warning(r'Deleting registry key: %s\%s', hive_name, key) + except FileNotFoundError: + # Ignore + pass + except PermissionError: + LOG.error(r'Failed to delete registry key: %s\%s', hive_name, key) + if recurse: + # Re-raise exception + raise + + # recurse is not True so assuming we tried to remove a non-empty key + msg = fr'Refusing to remove non-empty key: {hive_name}\{key}' + raise FileExistsError(msg) + + +def reg_delete_value(hive, key, value): + """Delete a value from the registry.""" + access = winreg.KEY_ALL_ACCESS + hive = reg_get_hive(hive) + hive_name = KNOWN_HIVE_NAMES.get(hive, '???') + + # Delete value + with winreg.OpenKey(hive, key, access=access) as open_key: + try: + winreg.DeleteValue(open_key, value) + LOG.warning( + r'Deleting registry value: %s\%s "%s"', hive_name, key, value, + ) + except FileNotFoundError: + # Ignore + pass + except PermissionError: + LOG.error( + r'Failed to delete registry value: %s\%s "%s"', hive_name, key, value, + ) + # Re-raise exception + raise + + +def reg_get_hive(hive): + """Get winreg HKEY constant from string, returns HKEY constant.""" + if isinstance(hive, int): + # Assuming we're already a winreg HKEY constant + pass + else: + hive = KNOWN_HIVES[hive.upper()] + + # Done + return hive + + +def reg_get_value_type(value_type): + """Get registry value type from string, returns winreg constant.""" + if isinstance(value_type, int): + # Assuming we're already a winreg value type constant + pass + else: + value_type = KNOWN_VALUE_TYPES[value_type.upper()] + + # Done + return value_type + + +def reg_key_exists(hive, key): + """Test if the specified hive/key exists, returns bool.""" + exists = False + hive = reg_get_hive(hive) + + # Query key + try: + winreg.QueryValue(hive, key) + except FileNotFoundError: + # Leave set to False + pass + else: + exists = True + + # Done + return exists + + +def reg_read_value(hive, key, value, force_32=False, force_64=False): + """Query value from hive/hey, returns multiple types.""" + access = winreg.KEY_READ + data = None + hive = reg_get_hive(hive) + + # Set access + if force_32: + access = access | winreg.KEY_WOW64_32KEY + elif force_64: + access = access | winreg.KEY_WOW64_64KEY + + # Query value + with winreg.OpenKey(hive, key, access=access) as open_key: + # Returning first part of tuple and ignoreing type + data = winreg.QueryValueEx(open_key, value)[0] + + # Done + return data + + +def reg_write_settings(settings_dict): + """TODO""" + + +def reg_set_value( + hive, key, name, value, value_type, force_32=False, force_64=False): + # pylint: disable=too-many-arguments + """Set value for hive/key.""" + access = winreg.KEY_WRITE + value_type = reg_get_value_type(value_type) + hive = reg_get_hive(hive) + + # Set access + if force_32: + access = access | winreg.KEY_WOW64_32KEY + elif force_64: + access = access | winreg.KEY_WOW64_64KEY + + # Create key + winreg.CreateKeyEx(hive, key, access=access) + + # Set value + with winreg.OpenKey(hive, key, access=access) as open_key: + # Returning first part of tuple and ignoreing type + winreg.SetValueEx(open_key, name, 0, value_type, value) + + if __name__ == '__main__': print("This file is not meant to be called directly.") From 818e321682ad9866d0576837d2a0b20b0239b86f Mon Sep 17 00:00:00 2001 From: 2Shirt <2xShirt@gmail.com> Date: Mon, 6 Apr 2020 19:46:36 -0600 Subject: [PATCH 07/15] Added alias for ip * Use brief reporting with colors --- setup/linux/include/airootfs/etc/skel/.aliases | 1 + 1 file changed, 1 insertion(+) diff --git a/setup/linux/include/airootfs/etc/skel/.aliases b/setup/linux/include/airootfs/etc/skel/.aliases index 88eb4070..dff33e69 100644 --- a/setup/linux/include/airootfs/etc/skel/.aliases +++ b/setup/linux/include/airootfs/etc/skel/.aliases @@ -11,6 +11,7 @@ alias du='du -sch --apparent-size' alias fix-perms='find -type d -exec chmod 755 "{}" \; && find -type f -exec chmod 644 "{}" \;' alias hexedit='hexedit --color' alias hw-info='sudo hw-info | less -S' +alias ip='ip -br -color' alias less='less -S' alias ls='ls --color=auto' alias mkdir='mkdir -p' From 87533446653c0d6bf24e26ade852cf70fd83f247 Mon Sep 17 00:00:00 2001 From: 2Shirt <2xShirt@gmail.com> Date: Tue, 7 Apr 2020 23:05:40 -0600 Subject: [PATCH 08/15] Added reg_write_settings() * Replaces old write_registry_settings() * Uses tuples to combine all parts of the values * e.g. ('SampleValue', 'SampleData', 'SZ', '32) * This will allow merging multiple setting groups together * Should be more readable than the old method --- scripts/wk/os/win.py | 97 +++++++++++++++++++++++++++++++------------- 1 file changed, 69 insertions(+), 28 deletions(-) diff --git a/scripts/wk/os/win.py b/scripts/wk/os/win.py index 77b61999..95f8693a 100644 --- a/scripts/wk/os/win.py +++ b/scripts/wk/os/win.py @@ -8,6 +8,7 @@ import platform import winreg from contextlib import suppress + from wk.borrowed import acpi from wk.exe import run_program from wk.io import non_clobber_path @@ -17,6 +18,19 @@ from wk.std import GenericError, GenericWarning, sleep # STATIC VARIABLES LOG = logging.getLogger(__name__) +KNOWN_DATA_TYPES = { + 'BINARY': winreg.REG_BINARY, + 'DWORD': winreg.REG_DWORD, + 'DWORD_LITTLE_ENDIAN': winreg.REG_DWORD_LITTLE_ENDIAN, + 'DWORD_BIG_ENDIAN': winreg.REG_DWORD_BIG_ENDIAN, + 'EXPAND_SZ': winreg.REG_EXPAND_SZ, + 'LINK': winreg.REG_LINK, + 'MULTI_SZ': winreg.REG_MULTI_SZ, + 'NONE': winreg.REG_NONE, + 'QWORD': winreg.REG_QWORD, + 'QWORD_LITTLE_ENDIAN': winreg.REG_QWORD_LITTLE_ENDIAN, + 'SZ': winreg.REG_SZ, + } KNOWN_HIVES = { 'HKCR': winreg.HKEY_CLASSES_ROOT, 'HKCU': winreg.HKEY_CURRENT_USER, @@ -37,19 +51,6 @@ KNOWN_HIVE_NAMES = { winreg.HKEY_LOCAL_MACHINE: 'HKEY_LOCAL_MACHINE', winreg.HKEY_USERS: 'HKEY_USERS', } -KNOWN_VALUE_TYPES = { - 'BINARY': winreg.REG_BINARY, - 'DWORD': winreg.REG_DWORD, - 'DWORD_LITTLE_ENDIAN': winreg.REG_DWORD_LITTLE_ENDIAN, - 'DWORD_BIG_ENDIAN': winreg.REG_DWORD_BIG_ENDIAN, - 'EXPAND_SZ': winreg.REG_EXPAND_SZ, - 'LINK': winreg.REG_LINK, - 'MULTI_SZ': winreg.REG_MULTI_SZ, - 'NONE': winreg.REG_NONE, - 'QWORD': winreg.REG_QWORD, - 'QWORD_LITTLE_ENDIAN': winreg.REG_QWORD_LITTLE_ENDIAN, - 'SZ': winreg.REG_SZ, - } OS_VERSION = float(platform.win32_ver()[0]) REG_MSISERVER = r'HKLM\SYSTEM\CurrentControlSet\Control\SafeBoot\Network\MSIServer' SLMGR = pathlib.Path(f'{os.environ.get("SYSTEMROOT")}/System32/slmgr.vbs') @@ -282,16 +283,16 @@ def reg_get_hive(hive): return hive -def reg_get_value_type(value_type): - """Get registry value type from string, returns winreg constant.""" - if isinstance(value_type, int): +def reg_get_data_type(data_type): + """Get registry data type from string, returns winreg constant.""" + if isinstance(data_type, int): # Assuming we're already a winreg value type constant pass else: - value_type = KNOWN_VALUE_TYPES[value_type.upper()] + data_type = KNOWN_DATA_TYPES[data_type.upper()] # Done - return value_type + return data_type def reg_key_exists(hive, key): @@ -333,31 +334,71 @@ def reg_read_value(hive, key, value, force_32=False, force_64=False): return data -def reg_write_settings(settings_dict): - """TODO""" +def reg_write_settings(settings): + """Set registry values in bulk from a custom data structure. + + Data structure should be as follows: + EXAMPLE_SETTINGS = { + # See KNOWN_HIVES for valid hives + 'HKLM': { + r'Software\\2Shirt\\WizardKit': ( + # Value tuples should be in the form: + # (name, data, data-type, option), + # See KNOWN_DATA_TYPES for valid types + # The option item is optional + ('Sample Value #1', 'Sample Data', 'SZ'), + ('Sample Value #2', 14, 'DWORD'), + ), + r'Software\\2Shirt\\WizardKit\\Test': ( + ('Sample Value #3', 14000000000000, 'QWORD'), + ), + }, + 'HKCU': { + r'Software\\2Shirt\\WizardKit': ( + # The 4th item forces using the 32-bit registry + # See reg_set_value() for valid options + ('Sample Value #4', 'Sample Data', 'SZ', '32'), + ), + }, + } + """ + for hive, keys in settings.items(): + hive = reg_get_hive(hive) + for key, values in keys.items(): + for value in values: + reg_set_value(hive, key, *value) -def reg_set_value( - hive, key, name, value, value_type, force_32=False, force_64=False): +def reg_set_value(hive, key, name, data, data_type, option=None): # pylint: disable=too-many-arguments """Set value for hive/key.""" access = winreg.KEY_WRITE - value_type = reg_get_value_type(value_type) + data_type = reg_get_data_type(data_type) hive = reg_get_hive(hive) + option = str(option) + + # Safety check + if not name and option in ('32', '64'): + raise NotImplementedError( + 'Unable to set default values using alternate registry views', + ) # Set access - if force_32: + if option == '32': access = access | winreg.KEY_WOW64_32KEY - elif force_64: + elif option == '64': access = access | winreg.KEY_WOW64_64KEY # Create key winreg.CreateKeyEx(hive, key, access=access) # Set value - with winreg.OpenKey(hive, key, access=access) as open_key: - # Returning first part of tuple and ignoreing type - winreg.SetValueEx(open_key, name, 0, value_type, value) + if name: + with winreg.OpenKey(hive, key, access=access) as open_key: + winreg.SetValueEx(open_key, name, 0, data_type, data) + else: + # Set default value instead + winreg.SetValue(hive, key, data_type, data) if __name__ == '__main__': From d0d74b87637dafd4ac2364407260b37cd0c6069a Mon Sep 17 00:00:00 2001 From: 2Shirt <2xShirt@gmail.com> Date: Tue, 7 Apr 2020 23:23:11 -0600 Subject: [PATCH 09/15] Support creating emtpy keys in reg_write_settings() --- scripts/wk/os/win.py | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/scripts/wk/os/win.py b/scripts/wk/os/win.py index 95f8693a..34d1220c 100644 --- a/scripts/wk/os/win.py +++ b/scripts/wk/os/win.py @@ -349,6 +349,8 @@ def reg_write_settings(settings): ('Sample Value #1', 'Sample Data', 'SZ'), ('Sample Value #2', 14, 'DWORD'), ), + # An empty key will be created if no values are specified + r'Software\\2Shirt\\WizardKit\\Empty': (), r'Software\\2Shirt\\WizardKit\\Test': ( ('Sample Value #3', 14000000000000, 'QWORD'), ), @@ -365,6 +367,9 @@ def reg_write_settings(settings): for hive, keys in settings.items(): hive = reg_get_hive(hive) for key, values in keys.items(): + if not values: + # Create an empty key + winreg.CreateKey(hive, key) for value in values: reg_set_value(hive, key, *value) From 6c775bbba721a30f1e9b171a7d1722db52458969 Mon Sep 17 00:00:00 2001 From: 2Shirt <2xShirt@gmail.com> Date: Sun, 26 Apr 2020 16:24:35 -0600 Subject: [PATCH 10/15] Adjusted running as root checks * Suppress pylint errors when checking uid/euid/gid * Helpful when checking under Windows * Allow running wk.exe.stop_process() under Windows --- scripts/wk/exe.py | 18 +++++++++--------- scripts/wk/net.py | 4 ++-- 2 files changed, 11 insertions(+), 11 deletions(-) diff --git a/scripts/wk/exe.py b/scripts/wk/exe.py index 2097469b..87b90935 100644 --- a/scripts/wk/exe.py +++ b/scripts/wk/exe.py @@ -80,8 +80,9 @@ def build_cmd_kwargs(cmd, minimized=False, pipe=True, shell=False, **kwargs): } # Strip sudo if appropriate - if cmd[0] == 'sudo' and os.name == 'posix' and os.geteuid() == 0: - cmd.pop(0) + if cmd[0] == 'sudo': + if os.name == 'posix' and os.geteuid() == 0: # pylint: disable=no-member + cmd.pop(0) # Add additional kwargs if applicable for key in 'check cwd encoding errors stderr stdin stdout'.split(): @@ -221,21 +222,20 @@ def stop_process(proc, graceful=True): NOTES: proc should be a subprocess.Popen obj. If graceful is True then a SIGTERM is sent before SIGKILL. """ - running_as_root = os.geteuid() == 0 # Graceful exit if graceful: - if running_as_root: - proc.terminate() - else: + if os.name == 'posix' and os.geteuid() != 0: # pylint: disable=no-member run_program(['sudo', 'kill', str(proc.pid)], check=False) + else: + proc.terminate() time.sleep(2) # Force exit - if running_as_root: - proc.kill() - else: + if os.name == 'posix' and os.geteuid() != 0: # pylint: disable=no-member run_program(['sudo', 'kill', '-9', str(proc.pid)], check=False) + else: + proc.kill() def wait_for_procs(name, exact=True, timeout=None): diff --git a/scripts/wk/net.py b/scripts/wk/net.py index e8e763b5..d2ba5c5d 100644 --- a/scripts/wk/net.py +++ b/scripts/wk/net.py @@ -122,8 +122,8 @@ def mount_network_share(details, mount_point=None, read_write=False): '-t', 'cifs', '-o', ( f'{"rw" if read_write else "ro"}' - f',uid={os.getuid()}' - f',gid={os.getgid()}' + f',uid={os.getuid()}' # pylint: disable=no-member + f',gid={os.getgid()}' # pylint: disable=no-member f',username={username}' f',{"password=" if password else "guest"}{password}' ), From 9a53d4adad9d4838724e1087badd4f5f385cb48d Mon Sep 17 00:00:00 2001 From: 2Shirt <2xShirt@gmail.com> Date: Sun, 26 Apr 2020 16:28:23 -0600 Subject: [PATCH 11/15] Updated log handling to support Windows --- scripts/wk/log.py | 39 +++++++++++++++++++++++---------------- 1 file changed, 23 insertions(+), 16 deletions(-) diff --git a/scripts/wk/log.py b/scripts/wk/log.py index 2cb39764..d1b7c4e0 100644 --- a/scripts/wk/log.py +++ b/scripts/wk/log.py @@ -79,14 +79,20 @@ def get_root_logger_path(): return log_path -def remove_empty_log(): - """Remove log if empty.""" +def remove_empty_log(log_path=None): + """Remove log if empty. + + NOTE: Under Windows an empty log is 2 bytes long. + """ is_empty = False + # Get log path + if not log_path: + log_path = get_root_logger_path() + # Check if log is empty - log_path = get_root_logger_path() try: - is_empty = log_path and log_path.exists() and log_path.stat().st_size == 0 + is_empty = log_path and log_path.exists() and log_path.stat().st_size <= 2 except (FileNotFoundError, AttributeError): # File doesn't exist or couldn't verify it's empty pass @@ -122,34 +128,35 @@ def update_log_path( dest_dir=None, dest_name=None, keep_history=True, timestamp=True): """Moves current log file to new path and updates the root logger.""" root_logger = logging.getLogger() - cur_handler = None - cur_path = get_root_logger_path() new_path = format_log_path(dest_dir, dest_name, timestamp=timestamp) + old_handler = None + old_path = get_root_logger_path() os.makedirs(new_path.parent, exist_ok=True) # Get current logging file handler for handler in root_logger.handlers: if isinstance(handler, logging.FileHandler): - cur_handler = handler + old_handler = handler break - if not cur_handler: + if not old_handler: raise RuntimeError('Logging FileHandler not found') # Copy original log to new location if keep_history: if new_path.exists(): raise FileExistsError(f'Refusing to clobber: {new_path}') - shutil.move(cur_path, new_path) + shutil.copy(old_path, new_path) - # Remove old log if empty - remove_empty_log() - - # Create new cur_handler (preserving formatter settings) + # Create new handler (preserving formatter settings) new_handler = logging.FileHandler(new_path, mode='a') - new_handler.setFormatter(cur_handler.formatter) + new_handler.setFormatter(old_handler.formatter) - # Replace current handler - root_logger.removeHandler(cur_handler) + # Remove old_handler and log if empty + root_logger.removeHandler(old_handler) + old_handler.close() + remove_empty_log(old_path) + + # Add new handler root_logger.addHandler(new_handler) From 830395f6724bc3f3424065b4ed47dbd7f3569fae Mon Sep 17 00:00:00 2001 From: 2Shirt <2xShirt@gmail.com> Date: Thu, 30 Jul 2020 22:07:45 -0600 Subject: [PATCH 12/15] Update windows_builds.py to include 20H1 --- scripts/wk.prev/settings/windows_builds.py | 33 ++++++++++++++++++++++ 1 file changed, 33 insertions(+) diff --git a/scripts/wk.prev/settings/windows_builds.py b/scripts/wk.prev/settings/windows_builds.py index f7481294..7f243315 100644 --- a/scripts/wk.prev/settings/windows_builds.py +++ b/scripts/wk.prev/settings/windows_builds.py @@ -205,6 +205,7 @@ WINDOWS_BUILDS = { '18358': ('10', None, '19H1', None, 'preview build'), '18361': ('10', None, '19H1', None, 'preview build'), '18362': ('10', 'v1903', '19H1', 'May 2019 Update', None), + '18363': ('10', 'v1909', '19H2', 'November 2019 Update', None), '18836': ('10', None, '20H1', None, 'preview build'), '18841': ('10', None, '20H1', None, 'preview build'), '18845': ('10', None, '20H1', None, 'preview build'), @@ -218,6 +219,38 @@ WINDOWS_BUILDS = { '18894': ('10', None, '20H1', None, 'preview build'), '18895': ('10', None, '20H1', None, 'preview build'), '18898': ('10', None, '20H1', None, 'preview build'), + '18908': ('10', None, '20H1', None, 'preview build'), + '18912': ('10', None, '20H1', None, 'preview build'), + '18917': ('10', None, '20H1', None, 'preview build'), + '18922': ('10', None, '20H1', None, 'preview build'), + '18932': ('10', None, '20H1', None, 'preview build'), + '18936': ('10', None, '20H1', None, 'preview build'), + '18941': ('10', None, '20H1', None, 'preview build'), + '18945': ('10', None, '20H1', None, 'preview build'), + '18950': ('10', None, '20H1', None, 'preview build'), + '18956': ('10', None, '20H1', None, 'preview build'), + '18963': ('10', None, '20H1', None, 'preview build'), + '18965': ('10', None, '20H1', None, 'preview build'), + '18970': ('10', None, '20H1', None, 'preview build'), + '18975': ('10', None, '20H1', None, 'preview build'), + '18980': ('10', None, '20H1', None, 'preview build'), + '18985': ('10', None, '20H1', None, 'preview build'), + '18990': ('10', None, '20H1', None, 'preview build'), + '18995': ('10', None, '20H1', None, 'preview build'), + '18999': ('10', None, '20H1', None, 'preview build'), + '19002': ('10', None, '20H1', None, 'preview build'), + '19008': ('10', None, '20H1', None, 'preview build'), + '19013': ('10', None, '20H1', None, 'preview build'), + '19018': ('10', None, '20H1', None, 'preview build'), + '19023': ('10', None, '20H1', None, 'preview build'), + '19025': ('10', None, '20H1', None, 'preview build'), + '19028': ('10', None, '20H1', None, 'preview build'), + '19030': ('10', None, '20H1', None, 'preview build'), + '19033': ('10', None, '20H1', None, 'preview build'), + '19035': ('10', None, '20H1', None, 'preview build'), + '19037': ('10', None, '20H1', None, 'preview build'), + '19041': ('10', 'v2004', '20H1', 'May 2020 Update', None), + '19042': ('10', None, '20H1', None, 'preview build'), } From 7d77aa81b064e420fa7bfbff7f577ca7eafdb1bb Mon Sep 17 00:00:00 2001 From: 2Shirt <2xShirt@gmail.com> Date: Thu, 24 Dec 2020 21:08:53 -0700 Subject: [PATCH 13/15] Update sensors.py to improve CPU data Include AMD CCD sensors Exclude current sensors --- scripts/wk/hw/sensors.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/scripts/wk/hw/sensors.py b/scripts/wk/hw/sensors.py index 784406da..573c9c3b 100644 --- a/scripts/wk/hw/sensors.py +++ b/scripts/wk/hw/sensors.py @@ -261,7 +261,7 @@ def fix_sensor_name(name): name = name.replace('Smc', 'SMC') name = re.sub(r'(\D+)(\d+)', r'\1 \2', name, re.IGNORECASE) name = re.sub(r'^K (\d+)Temp', r'AMD K\1 Temps', name, re.IGNORECASE) - name = re.sub(r'T(ctl|die)', r'CPU (T\1)', name, re.IGNORECASE) + name = re.sub(r'T(ccd\s+\d+|ctl|die)', r'CPU (T\1)', name, re.IGNORECASE) name = re.sub(r'\s+', ' ', name) return name @@ -294,7 +294,7 @@ def get_sensor_data_linux(): ## current temp is labeled xxxx_input for source, labels in sources.items(): for label, temp in labels.items(): - if label.startswith('fan') or label.startswith('in'): + if label.startswith('fan') or label.startswith('in') or label.startswith('curr'): # Skip fan RPMs and voltages continue if 'input' in label: From ce912e9525b033a365accedc01d66282ed8c89c2 Mon Sep 17 00:00:00 2001 From: 2Shirt <2xShirt@gmail.com> Date: Sat, 9 Jan 2021 21:15:07 -0700 Subject: [PATCH 14/15] Update windows_builds.py to include 20H2 --- scripts/wk.prev/settings/windows_builds.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/scripts/wk.prev/settings/windows_builds.py b/scripts/wk.prev/settings/windows_builds.py index 7f243315..68b09bfe 100644 --- a/scripts/wk.prev/settings/windows_builds.py +++ b/scripts/wk.prev/settings/windows_builds.py @@ -250,7 +250,7 @@ WINDOWS_BUILDS = { '19035': ('10', None, '20H1', None, 'preview build'), '19037': ('10', None, '20H1', None, 'preview build'), '19041': ('10', 'v2004', '20H1', 'May 2020 Update', None), - '19042': ('10', None, '20H1', None, 'preview build'), + '19042': ('10', 'v20H2', '20H2', 'October 2020 Update', None), } From 31cd8d1e5641f2f001a217f0cdc3f74e006e96e0 Mon Sep 17 00:00:00 2001 From: 2Shirt <2xShirt@gmail.com> Date: Sun, 10 Jan 2021 17:19:27 -0700 Subject: [PATCH 15/15] Fix items_not_found logic --- scripts/wk/kit/ufd.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/scripts/wk/kit/ufd.py b/scripts/wk/kit/ufd.py index 483e2fbb..93d5b7ca 100644 --- a/scripts/wk/kit/ufd.py +++ b/scripts/wk/kit/ufd.py @@ -212,7 +212,8 @@ def copy_source(source, items, overwrite=False): linux.unmount('/mnt/Source') # Raise exception if item(s) were not found - raise FileNotFoundError('One or more items not found') + if items_not_found: + raise FileNotFoundError('One or more items not found') def create_table(dev_path, use_mbr=False):