at master 5.7 kB view raw
1"""OS-specific functions for: Linux 2""" 3import platform 4import shutil 5from pathlib import Path 6 7from milc import cli 8 9from qmk.constants import QMK_FIRMWARE, BOOTLOADER_VIDS_PIDS 10from .check import CheckStatus, release_info 11 12 13def _is_wsl(): 14 return 'microsoft' in platform.uname().release.lower() 15 16 17def _udev_rule(vid, pid=None, *args): 18 """ Helper function that return udev rules 19 """ 20 rule = "" 21 if pid: 22 rule = 'SUBSYSTEMS=="usb", ATTRS{idVendor}=="%s", ATTRS{idProduct}=="%s", TAG+="uaccess"' % ( 23 vid, 24 pid, 25 ) 26 else: 27 rule = 'SUBSYSTEMS=="usb", ATTRS{idVendor}=="%s", TAG+="uaccess"' % vid 28 if args: 29 rule = ', '.join([rule, *args]) 30 return rule 31 32 33def _generate_desired_rules(bootloader_vids_pids): 34 rules = dict() 35 for bl in bootloader_vids_pids.keys(): 36 rules[bl] = set() 37 for vid_pid in bootloader_vids_pids[bl]: 38 if bl == 'caterina' or bl == 'md-boot': 39 rules[bl].add(_udev_rule(vid_pid[0], vid_pid[1], 'ENV{ID_MM_DEVICE_IGNORE}="1"')) 40 else: 41 rules[bl].add(_udev_rule(vid_pid[0], vid_pid[1])) 42 return rules 43 44 45def _deprecated_udev_rule(vid, pid=None): 46 """ Helper function that return udev rules 47 48 Note: these are no longer the recommended rules, this is just used to check for them 49 """ 50 if pid: 51 return 'SUBSYSTEMS=="usb", ATTRS{idVendor}=="%s", ATTRS{idProduct}=="%s", MODE:="0666"' % (vid, pid) 52 else: 53 return 'SUBSYSTEMS=="usb", ATTRS{idVendor}=="%s", MODE:="0666"' % vid 54 55 56def check_udev_rules(): 57 """Make sure the udev rules look good. 58 """ 59 rc = CheckStatus.OK 60 udev_dirs = [ 61 Path("/usr/lib/udev/rules.d/"), 62 Path("/usr/local/lib/udev/rules.d/"), 63 Path("/run/udev/rules.d/"), 64 Path("/etc/udev/rules.d/"), 65 ] 66 67 desired_rules = _generate_desired_rules(BOOTLOADER_VIDS_PIDS) 68 69 # These rules are no longer recommended, only use them to check for their presence. 70 deprecated_rules = { 71 'atmel-dfu': {_deprecated_udev_rule("03eb", "2ff4"), _deprecated_udev_rule("03eb", "2ffb"), _deprecated_udev_rule("03eb", "2ff0")}, 72 'kiibohd': {_deprecated_udev_rule("1c11")}, 73 'stm32': {_deprecated_udev_rule("1eaf", "0003"), _deprecated_udev_rule("0483", "df11")}, 74 'bootloadhid': {_deprecated_udev_rule("16c0", "05df")}, 75 'caterina': {'ATTRS{idVendor}=="2a03", ENV{ID_MM_DEVICE_IGNORE}="1"', 'ATTRS{idVendor}=="2341", ENV{ID_MM_DEVICE_IGNORE}="1"'}, 76 'tmk': {_deprecated_udev_rule("feed")} 77 } 78 79 if any(udev_dir.exists() for udev_dir in udev_dirs): 80 udev_rules = [rule_file for udev_dir in udev_dirs for rule_file in udev_dir.glob('*.rules')] 81 current_rules = set() 82 83 # Collect all rules from the config files 84 for rule_file in udev_rules: 85 try: 86 for line in rule_file.read_text(encoding='utf-8').split('\n'): 87 line = line.strip() 88 if not line.startswith("#") and len(line): 89 current_rules.add(line) 90 except PermissionError: 91 cli.log.debug("Failed to read: %s", rule_file) 92 93 # Check if the desired rules are among the currently present rules 94 for bootloader, rules in desired_rules.items(): 95 if not rules.issubset(current_rules): 96 deprecated_rule = deprecated_rules.get(bootloader) 97 if deprecated_rule and deprecated_rule.issubset(current_rules): 98 cli.log.warning("{fg_yellow}Found old, deprecated udev rules for '%s' boards. The new rules on https://docs.qmk.fm/#/faq_build?id=linux-udev-rules offer better security with the same functionality.", bootloader) 99 else: 100 # For caterina, check if ModemManager is running 101 if bootloader == "caterina" and check_modem_manager(): 102 cli.log.warning("{fg_yellow}Detected ModemManager without the necessary udev rules. Please either disable it or set the appropriate udev rules if you are using a Pro Micro.") 103 104 rc = CheckStatus.WARNING 105 cli.log.warning("{fg_yellow}Missing or outdated udev rules for '%s' boards. Run 'sudo cp %s/util/udev/50-qmk.rules /etc/udev/rules.d/'.", bootloader, QMK_FIRMWARE) 106 107 else: 108 cli.log.warning("{fg_yellow}Can't find udev rules, skipping udev rule checking...") 109 cli.log.debug("Checked directories: %s", ', '.join(str(udev_dir) for udev_dir in udev_dirs)) 110 111 return rc 112 113 114def check_systemd(): 115 """Check if it's a systemd system 116 """ 117 return bool(shutil.which("systemctl")) 118 119 120def check_modem_manager(): 121 """Returns True if ModemManager is running. 122 123 """ 124 if check_systemd(): 125 mm_check = cli.run(["systemctl", "--quiet", "is-active", "ModemManager.service"], timeout=10) 126 if mm_check.returncode == 0: 127 return True 128 else: 129 """(TODO): Add check for non-systemd systems 130 """ 131 return False 132 133 134def os_test_linux(): 135 """Run the Linux specific tests. 136 """ 137 info = release_info() 138 release_id = info.get('PRETTY_NAME', info.get('ID', 'Unknown')) 139 plat = 'WSL, ' if _is_wsl() else '' 140 141 cli.log.info(f"Detected {{fg_cyan}}Linux ({plat}{release_id}){{fg_reset}}.") 142 143 # Don't bother with udev on WSL, for now 144 if _is_wsl(): 145 # https://github.com/microsoft/WSL/issues/4197 146 if QMK_FIRMWARE.as_posix().startswith("/mnt"): 147 cli.log.warning("I/O performance on /mnt may be extremely slow.") 148 return CheckStatus.WARNING 149 150 else: 151 rc = check_udev_rules() 152 if rc != CheckStatus.OK: 153 return rc 154 155 return CheckStatus.OK