#!/usr/bin/tclsh

# Wrapper (tcl) for usb_modeswitch, called from
# /lib/udev/rules.d/40-usb_modeswitch.rules
# (part of data pack "usb-modeswitch-data") via
# /lib/udev/usb_modeswitch
#
# Does ID check on newly discovered USB devices and calls
# the mode switching program with the matching parameter
# file from /usr/share/usb_modeswitch
#
# Part of usb-modeswitch-2.6.1 package, 2020/07/10
# (C) Josua Dietze 2009 - 2020
#
# This program is free software; you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation; either version 2 of the License, or
# (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
# GNU General Public License for more details:
#
# http://www.gnu.org/licenses/gpl.txt

set arg0 [lindex $argv 0]
if [regexp {\.tcl$} $arg0] {
	if [file exists $arg0] {
		set argv [lrange $argv 1 end]
		source $arg0
		exit
	}
}

# Setting of these switches is done in the global config
# file (/etc/usb_modeswitch.conf) if available

set flags(logging) 0
set flags(noswitching) 0
set flags(stordelay) 0
set flags(nombim) 0

set flags(logwrite) 0
# also settable in device config files
set flags(nombim) 0
set flags(althuawei) 0

# Execution starts at file bottom

proc {Main} {argv argc} {

global scsi usb config match device flags setup devdir loginit

set flags(config) ""

# arg0: optional custom location of configuration file
# arg1: the "kernel name" for the device to switch (udev: %k)
# The parameter "bus id" for the device (udev: %b) is now deprecated
#
# From version 2.5.0 upward %b is removed by udev sh script
# which can handle old and new udev params ('%b/%k' and '%k')

Log "Raw parameters: $argv"
set device "noname"
set arg0 ""
for {set i 0} {$i < [llength $argv]} {incr i} {
	set option [lindex $argv $i]
	switch -glob -- $option {
		"--config-file" {
			if [regexp {config-file=(\"[^\"]+\"|[^ ]+)} $option d param] {
				set arg0 [string trim $param {"}]
			} else {
				set arg0 [lindex $argv $i+1]
			}
		}
		"--switch-mode" {
			if [regexp {switch-mode=(.+)} $option d param] {
				set arg1 $param
			} else {
				set arg1 [lindex $argv $i+1]
			}
		}
	}
}
Log "[ParseGlobalConfig $arg0]"
set flags(logwrite) 1


# The facility to add a symbolic link pointing to the
# ttyUSB port which provides interrupt transfer, i.e.
# the port to connect through.
# Will check for interrupt endpoint in ttyUSB port (lowest if
# there is more than one); if found, return "gsmmodem[n]" name
# to udev for symlink creation

# This is run once for every port of LISTED devices by
# a udev rule

if {[lindex $argv 0] == "--symlink-name"} {
	puts -nonewline [SymLinkName [lindex $argv 1]]
	SafeExit
}


if {$flags(stordelay) > 0} {
	SetStorageDelay $flags(stordelay)
}

if [info exists arg1] {
	if {[string length $arg1] == 0} {
		ShowUsage
		Log "\nNo device provided for mode-switching. Exit"
		SafeExit
	}
} else {
	ShowUsage
	Log "\nNo command option given. Exit"
	SafeExit
}

if {![regexp {(.*?):.*$} $arg1 d device]} {
	if {![regexp {([0-9]+-[0-9]+\.?[0-9]*.*)} $arg1 d device]} {
		ShowUsage
		Log "Could not determine device dir from udev values! Exit"
		SafeExit
	}
}

set setup(dbdir) /usr/share/usb_modeswitch
set setup(dbdir_etc) /etc/usb_modeswitch.d
if {![file exists $setup(dbdir)] && ![file exists $setup(dbdir_etc)]} {
	Log "\nError: no config database found in /usr/share or /etc. Exit"
	SafeExit
}

set bindir /usr/sbin
set devList1 {}
set devList2 {}
set ifChk 0

set devdir /sys/bus/usb/devices/$device
if {![file isdirectory $devdir]} {
	Log "Top device directory not found ($devdir)! Exit"
	SafeExit
}
Log "Use top device dir $devdir"

set iface 0
Log "Check class of first interface ..."
set config(class) [IfClass 0 $devdir]
if {$config(class) < 0} {
	Log " No access to first interface. Exit"
	SafeExit
}
Log " Interface class is $config(class)."

set ifdir [file tail [IfDir $iface $devdir]]
regexp {:([0-9]+\.[0-9]+)$} $ifdir d iface

# Mapping of the short string identifiers (in the config
# file names) to the long name used here
#
# If we need them it's a snap to add new attributes here!

set match(sVe) scsi(vendor)
set match(sMo) scsi(model)
set match(sRe) scsi(rev)
set match(uMa) usb(manufacturer)
set match(uPr) usb(product)
set match(uSe) usb(serial)


# Now reading the USB attributes
if {![ReadUSBAttrs $devdir]} {
	Log "USB attributes not found in sysfs tree. Exit"
	SafeExit
}
set config(vendor) $usb(idVendor)
set config(product) $usb(idProduct)


if $flags(logging) {
	Log "\n----------------\nUSB values from sysfs:"
	foreach attr {manufacturer product serial} {
		Log "  $attr\t$usb($attr)"
	}
	Log "----------------"
}

if $flags(noswitching) {
	SysLog "usb_modeswitch: switching disabled, no action for $usb(idVendor):$usb(idProduct)"
	Log "\nSwitching globally disabled. Exit"
	SafeExit
}

if {$usb(bNumConfigurations) == "1"} {
	set configParam "-u -1"
	Log "bNumConfigurations is 1 - don't check for active configuration"
} else {
	set configParam ""
}

# Check (and switch) for operating system if Huawei device present

set flags(os) "linux"
if {$usb(idVendor) == "12d1" && [regexp -nocase {android} [exec cat /proc/version]]} {
	set flags(os) "android"
}
if {$flags(os) == "android"} {
	set configList [ConfigGet conflist $usb(idVendor):#android]
} else {
	set configList [ConfigGet conflist $usb(idVendor):$usb(idProduct)]
}

if {[llength $configList] == 0} {
	Log "Aargh! Config file missing for $usb(idVendor):$usb(idProduct)! Exit"
	SafeExit
}
Log "ConfigList: $configList"

# Check if there is more than one config file for this USB ID,
# which would make an attribute test necessary. If so, check if
# SCSI values are needed

set scsiNeeded 0
if {[llength $configList] > 1} {
	if [regexp {:s} $configList] {
		set scsiNeeded 1
	}
}
if $scsiNeeded {
	if [ReadSCSIAttrs $devdir:$iface] {
		Log "----------------\nSCSI values from sysfs:"
		foreach attr {vendor model rev} {
			Log " $attr\t$scsi($attr)"
		}
		Log "----------------"
	} else {
		Log "Could not get SCSI attributes, exclude devices with SCSI match"
	}
} else {
	Log "SCSI attributes not needed, move on"
}

# Now check for a matching config file. Matching is done
# by MatchDevice

set report ""
foreach mconfig $configList {

	# skipping installer leftovers like "*.rpmnew"
	if [regexp {\.(dpkg|rpm)} $mconfig] {continue}

	Log "Check config: $mconfig"
	if [MatchDevice $mconfig] {
		Log "! matched. Read config data"
		set flags(config) [ConfigGet conffile $mconfig]
		break
	} else {
		Log "* no match, don't use this config"
	}
}
if {$flags(config) == ""} {
	Log "No matching config file found. Exit"
	SafeExit
}

ParseDeviceConfig $flags(config)

if [regexp -nocase {0x([0-9a-f]+)} $config(TargetClass) d tc] {
	if {$tc == $config(class)} {
		Log "Class of interface 0 matches target. Do nothing"
		set report "ok:busdev"
	}
}

if [string length $usb(busnum)] {
	set busParam "-b [string trimleft $usb(busnum) 0]"
	set devParam "-g [string trimleft $usb(devnum) 0]"
} else {
	set busParam ""
	set devParam ""
}
if [regexp -nocase $flags(os) $flags(config)] {
	Log "Note: Using generic manufacturer configuration for \"$flags(os)\""
}
if $flags(althuawei) {
	regsub {HuaweiNewMode} $flags(config) {HuaweiAltMode} flags(config)
	Log "Alternative Huawei mode set globally, modify config"
}
if $flags(nombim) {
	set config(NoMBIMCheck) 1
}
if {$config(NoMBIMCheck)==0 && $usb(bNumConfigurations) > 1} {
	Log "Device may have an MBIM configuration, check driver ..."
	if [CheckMBIM] {
		Log " driver for MBIM devices is available"
		Log "Find MBIM configuration number ..."
		if [catch {set cfgno [exec /usr/sbin/usb_modeswitch -j -Q $busParam $devParam -v $usb(idVendor) -p $usb(idProduct)]} err] {
			Log "Error when trying to find MBIM configuration, switch to legacy modem mode"
		} else {
			set cfgno [string trim $cfgno]
			if {$cfgno > 0} {
				set config(Configuration) $cfgno
				set flags(config) "Configuration=$cfgno"
			} else {
				Log " No MBIM configuration found, switch t