diff --git a/usb_reset.py b/usb_reset.py index 6ae6a80..eb631a0 100644 --- a/usb_reset.py +++ b/usb_reset.py @@ -1,4 +1,4 @@ -#! /usr/bin/env python +#! /usr/bin/env python3 # -*- coding: utf-8 -*- # # This file is part of ofunctions package @@ -19,7 +19,7 @@ __copyright__ = "Copyright (C) 2022 Orsiris de Jong" __description__ = "USB resetter allows to reset all USB controllers or a single USB device, also emulates lsusb" __licence__ = "BSD 3 Clause" -__version__ = "1.0.0" +__version__ = "1.1.0" __build__ = "2022102701" __compat__ = "python2.7+" @@ -31,6 +31,7 @@ import sys import glob from argparse import ArgumentParser +from collections import namedtuple # Equivalent of the _IO('U', 20) constant in the linux kernel. @@ -38,6 +39,7 @@ def reset_usb_controllers(): + # type: () -> None """ Allows to reset all USB controllers from a machine @@ -65,13 +67,18 @@ def reset_usb_controllers(): bind.write(current_controller) -def get_usb_devices_paths(vendor_id: str = None, product_id: str = None) -> List[str]: +def get_usb_devices_paths(vendor_id=None, product_id=None, list_only=False): + # type: (str, str, bool) -> List[str] """ Emulates lsusb by reading from /sys/kernel/debug/usb/devices Does not require lsusb to be installed and should work on a fair share of recent kernels """ + found_devices = [] device_paths = [] + Device = namedtuple( + "Devices", "vendor_id, product_id, device_path, manufacturer, product" + ) kernel_usb_debug_path = "/sys/kernel/debug/usb/devices" # see https://wiki.debian.org/HowToIdentifyADevice/USB if not os.path.isfile(kernel_usb_debug_path): @@ -79,22 +86,56 @@ def get_usb_devices_paths(vendor_id: str = None, product_id: str = None) -> List return device_paths with open(kernel_usb_debug_path, "r") as file_handle: + first_device = True + manufacturer = None + product = None + found_vendor_id = None + found_product_id = None while True: line = file_handle.readline() if not line: break match = re.match(r"T:\s+Bus=(\d+).*Dev#=\s+(\d+)", line, re.IGNORECASE) if match: + if not first_device: + # New device (begins with T:), let's reset previous values that belong to earlier found devices + found_devices.append( + Device( + vendor_id=found_vendor_id, + product_id=found_product_id, + device_path=device_path, + manufacturer=manufacturer, + product=product, + ) + ) + # print( + # "Found device {}:{} at {} Manufacturer={} Product={}".format( + # found_vendor_id, found_product_id, device_path, manufacturer, product + # ) + # ) + else: + first_device = False + manufacturer = None + product = None + found_vendor_id = None + found_product_id = None + # bus and dev are always 3 digit numbers, ex 001, 003, 004 - possible_bus = "{:03d}".format(int(match.group(1))) - possible_dev = "{:03d}".format(int(match.group(2))) + bus = "{:03d}".format(int(match.group(1))) + dev = "{:03d}".format(int(match.group(2))) + match = re.match(r"S:\s+Manufacturer=(.*)", line, re.IGNORECASE) + if match: + manufacturer = match.group(1) + match = re.match(r"S:\s+Product=(.*)", line, re.IGNORECASE) + if match: + product = match.group(1) match = re.match( r"P:\s+Vendor=([0-9A-F]{4})\s+ProdID=([0-9A-F]{4})", line, re.IGNORECASE ) if match: found_vendor_id = match.group(1) found_product_id = match.group(2) - device_path = os.path.join("/dev/bus/usb", possible_bus, possible_dev) + device_path = os.path.join("/dev/bus/usb", bus, dev) if vendor_id == found_vendor_id and product_id == found_product_id: if os.path.exists(device_path): device_paths.append(device_path) @@ -104,16 +145,26 @@ def get_usb_devices_paths(vendor_id: str = None, product_id: str = None) -> List possible_bus, possible_dev ) ) - elif not vendor_id and not product_id: - print( - "Found device {}:{} at {}".format( - found_vendor_id, found_product_id, device_path - ) - ) + # We need to add the final device if exists: + if bus and dev: + found_devices.append( + Device( + vendor_id=found_vendor_id, + product_id=found_product_id, + device_path=device_path, + manufacturer=manufacturer, + product=product, + ) + ) + + if list_only: + for device in found_devices: + print("Found device %s:%s at %s Manufacturer=%s, Product=%s" % device) return device_paths -def reset_usb_device(device_path: str) -> bool: +def reset_usb_device(device_path): + # type: (str) -> bool """ Resets a usb device by dending USBDEVFS_RESET IOCTL to device Device path is /dev/bus/usb/[bus_number]/[device_number] @@ -188,4 +239,4 @@ def reset_usb_device(device_path: str) -> bool: print("Bogus device {} given".format(device)) if args.list: - get_usb_devices_paths() \ No newline at end of file + get_usb_devices_paths(list_only=True)