Created
May 14, 2021 20:37
-
-
Save tai/3fd798ecd5c6fc5f64069f15eb95ccc2 to your computer and use it in GitHub Desktop.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| #!/usr/bin/perl | |
| use English; | |
| use Getopt::Long; | |
| use YAML; | |
| use JSON; | |
| use strict; | |
| sub help { | |
| my $p = $0; $p =~ s|^.*?/||; | |
| print <<EOF; | |
| $p - List associated device files for each USB hub/device | |
| Usage: $p [-ra] <hub-or-dev-id> [<hub-or-dev-id>...] | |
| Options: | |
| -r : recursively track USB hub chain | |
| -a : show all USB hubs, including the one that has no device plugged in | |
| --cutoff <n=20> : hide device files longer than this length | |
| Example: | |
| \$ $p # list active USB hubs (that have device plugged in) | |
| \$ $p -a # list all USB hubs | |
| \$ $p 3-4.4 # list devices plugged to hub at bus3-port4-port4 | |
| 3-4.4.4 0557:2008 Prolific_Technology_Inc._USB-Serial_Controller | |
| - /dev/ttyUSB0 | |
| \$ $p -r # list all plugged devices recursively | |
| 1-4.4 0b95:1790 ASIX_Elec._Corp._AX88179_00000000001025 | |
| - enx58278cbf9025 | |
| 1-9 8087:0aa7 8087_0aa7 | |
| - hci0 | |
| ... | |
| EOF | |
| exit(0); | |
| } | |
| sub identify { | |
| my @devpath = split('/', shift); | |
| while ($_ = pop @devpath) { | |
| # root hub | |
| return ($1, undef) if /^usb(\d+)/; | |
| # device under a root hub | |
| return ($1, $2) if /^((\d+)-\d+)$/; | |
| # device under a hub | |
| return ($1, $2) if /^((\d+\-[\d\.]+)\.\d+)$/; | |
| } | |
| } | |
| sub vidpid { | |
| my @product = split('/', shift); | |
| return sprintf("%04s:%04s", $product[0], $product[1]); | |
| } | |
| sub scan { | |
| open(my $fh, "udevadm info -e |"); | |
| my $db = { hub => {}, dev => {} }; | |
| my %item; | |
| while (<$fh>) { | |
| chomp($_); | |
| undef %item if /^P:/; | |
| $item{$1} = $POSTMATCH if /^E: (DEVNAME|DEVPATH|DEVLINKS|DEVTYPE)=/; | |
| $item{$1} = $POSTMATCH if /^E: (ID_SERIAL|ID_MODEL_FROM_DATABASE|PRODUCT)=/; | |
| $item{$1} = $POSTMATCH if /^E: (ID_NET_NAME|RFKILL_NAME)=/; | |
| $item{$1} = $2 if m|^E: (TYPE)=(\d+)/\d+/\d+|; | |
| if (/^$/ && $item{DEVPATH} =~ m|/usb\d+/|o) { | |
| my($dev_id, $hub_id) = identify($item{DEVPATH}); | |
| # register USB device | |
| $db->{dev}{$dev_id} ||= { | |
| id => $item{ID_SERIAL} || $item{ID_MODEL_FROM_DATABASE}, | |
| vp => vidpid($item{PRODUCT}) | |
| }; | |
| # register USB hub | |
| $db->{hub}{$dev_id} ||= {} if $item{TYPE} == 9; | |
| # skip root hub | |
| next unless $hub_id; | |
| # register upstream USB hub of the device | |
| $db->{hub}{$hub_id} ||= {}; | |
| $db->{hub}{$hub_id}{$dev_id} ||= $db->{dev}{$dev_id}; | |
| # register device files | |
| if ($item{DEVTYPE} ne 'usb_device') { | |
| my @devs; | |
| push(@devs, $item{DEVNAME}) if $item{DEVNAME}; | |
| push(@devs, split(/\s+/, $item{DEVLINKS})) if $item{DEVLINKS}; | |
| # handle network interfaces | |
| unless (@devs) { | |
| my $ifname = $item{RFKILL_NAME} || $item{ID_NET_NAME}; | |
| push(@devs, $ifname) if $ifname; | |
| } | |
| if (@devs) { | |
| $db->{dev}{$dev_id}{devpath} ||= []; | |
| push(@{$db->{dev}{$dev_id}{devpath}}, @devs); | |
| } | |
| } | |
| } | |
| } | |
| return $db; | |
| } | |
| sub list_hub { | |
| my($opt, $db) = @_; | |
| my @hubs = sort keys %{$db->{hub}}; | |
| my $len = (sort(map(length, @hubs)))[-1]; | |
| foreach my $hub (@hubs) { | |
| # hide inactive (no plugged in device) hubs by default | |
| my $count = 0; | |
| scan_dev($opt, $db, $hub, sub { $count++ }); | |
| next unless $count > 0 || $opt->{all}; | |
| my $id = $db->{dev}{$hub}{id}; | |
| printf("%-*s: %s\n", $len, $hub, $id); | |
| } | |
| } | |
| sub scan_dev { | |
| my($opt, $db, $id, $hook) = @_; | |
| if ($db->{hub}{$id}) { | |
| while (my($key, $val) = each %{$db->{hub}{$id}}) { | |
| $hook->($key) unless $db->{hub}{$key}; | |
| scan_dev($opt, $db, $key, $hook) if $db->{hub}{$key} && $opt->{recursive}; | |
| } | |
| } | |
| else { | |
| $hook->($id) if $db->{dev}{$id}; | |
| } | |
| } | |
| sub list_dev { | |
| my($opt, $db, @ids) = @_; | |
| foreach my $id (@ids) { | |
| scan_dev($opt, $db, $id, sub { | |
| my $key = shift; | |
| print "$key $db->{dev}{$key}{vp} $db->{dev}{$key}{id}\n"; | |
| foreach my $dev (@{$db->{dev}{$key}{devpath}}) { | |
| next if $opt->{cutoff} > 0 && length($dev) > $opt->{cutoff}; | |
| print "- $dev\n"; | |
| } | |
| }); | |
| } | |
| } | |
| sub main { | |
| my $opt = {}; | |
| GetOptions($opt, qw(--help --all --recursive --cutoff=i)); | |
| $opt->{cutoff} ||= 20; | |
| help() if $opt->{help}; | |
| my $db = scan(); | |
| my @ids = @ARGV; | |
| if (@ids || $opt->{recursive}) { | |
| @ids = sort grep {/^\d+$/} keys %{$db->{hub}} unless @ids; | |
| list_dev($opt, $db, @ids); | |
| } | |
| else { | |
| list_hub($opt, $db); | |
| } | |
| } | |
| main(); | |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment