#! /usr/bin/env tclsh
namespace eval ::tuapi {}
namespace eval ::tuapi::helper {}
namespace eval ::tuapi::cache {}
set ::tuapi::_mount_flags(bind) BIND
set ::tuapi::_mount_flags(move) MOVE
set ::tuapi::_mount_flags(remount) REMOUNT
set ::tuapi::_mount_flags(mandlock) MANDLOCK
set ::tuapi::_mount_flags(dirsync) DIRSYNC
set ::tuapi::_mount_flags(noatime) NOATIME
set ::tuapi::_mount_flags(nodiratime) NODIRATIME
set ::tuapi::_mount_flags(relatime) RELATIME
set ::tuapi::_mount_flags(strictatime) STRICTATIME
set ::tuapi::_mount_flags(nodev) NODEV
set ::tuapi::_mount_flags(noexec) NOEXEC
set ::tuapi::_mount_flags(nosuid) NOSUID
set ::tuapi::_mount_flags(ro) RDONLY
set ::tuapi::_mount_flags(silent) SILENT
set ::tuapi::_mount_flags(synchronous) SYNCHRONOUS
set ::tuapi::_mount_flags(sync) SYNCHRONOUS
# Determine where to mount a given device (usually by checking "/etc/fstab")
proc ::tuapi::helper::find_mountpoint {device} {
set data ""
catch {
set fd [open "/etc/fstab"]
set data [read -nonewline $fd]
close $fd
}
foreach line [split $data "\n"] {
set line [string trim [regsub {#.*$} $line ""]]
set line [regsub -all {[ \t][ \t][ \t]*} $line " "]
set work [split $line]
set curr_device [lindex $work 0]
set curr_mountpoint [lindex $work 1]
set curr_fstype [lindex $work 2]
set curr_opts [split [lindex $work 3] ","]
set curr_dumpfreq [lindex $work 4]
set curr_fsckpass [lindex $work 5]
if {$curr_device == $device || $curr_mountpoint == $device} {
return [list source $curr_device target $curr_mountpoint fstype $curr_fstype options $curr_opts dumpfreq $curr_dumpfreq fsckpass $curr_fsckpass]
}
}
return -code error "no entry found in \"/etc/fstab\" for \"$device\""
}
proc ::tuapi::mount args {
set options_list [list]
for {set idx 0} {$idx < [llength $args]} {incr idx} {
set curr_arg [lindex $args $idx]
switch -glob -- $curr_arg {
"-t" {
incr idx
set fstype [lindex $args $idx]
}
"-r" {
lappend options_list "RDONLY"
}
"-w" {
set idx [lsearch -exact $options_list "RDONLY"]
if {$idx != -1} {
set options_list [lreplace $options_list $idx $idx]
}
}
"-o" {
incr idx
set options [lindex $args $idx]
}
"--" {
incr idx
break
}
"-*" {
return -code error "unknown option \"$curr_arg\""
}
default {
break
}
}
}
set args [lrange $args $idx end]
if {[llength $args] < 1 || [llength $args] > 2} {
return -code error "wrong # args: should be \"::tuapi::mount ?options? source ?target?\""
}
set source [lindex $args 0]
if {[llength $args] == 2} {
set target [lindex $args 1]
} else {
array set mountinfo [::tuapi::helper::find_mountpoint $source]
set source $mountinfo(source)
set target $mountinfo(target)
if {![info exists fstype]} {
set fstype $mountinfo(fstype)
}
if {![info exists options]} {
set options $mountinfo(options)
}
}
# Ensure all mount-related parameters have been computed
if {![info exists fstype]} {
set fstype "auto"
}
if {![info exists options]} {
set options [list]
}
# Process options
foreach option $options {
set option_lc [string tolower $option]
# Special option handling
switch -- $option_lc {
"defaults" {
set options_list [list]
unset -nocomplain unknown_options
continue
}
"rw" {
set option_lc "noro"
}
"norw" {
set option_lc "ro"
}
}
# Example: noatime
if {[info exists ::tuapi::_mount_flags($option_lc)]} {
lappend options_list $::tuapi::_mount_flags($option_lc)
continue
}
# Example: atime
if {[info exists ::tuapi::_mount_flags(no$option_lc)]} {
set idx [lsearch -exact $options_list $::tuapi::_mount_flags(no$option_lc)]
if {$idx != -1} {
set options_list [lreplace $options_list $idx $idx]
}
continue
}
# Example: norelatime
if {[string match "no*" $option_lc]} {
set neg_option_lc [string range $option_lc 2 end]
if {[info exists ::tuapi::_mount_flags($neg_option_lc)]} {
set idx [lsearch -exact $options_list $::tuapi::_mount_flags($neg_option_lc)]
if {$idx != -1} {
set options_list [lreplace $options_list $idx $idx]
}
continue
}
}
# Accumulate unknown options
lappend unknown_options $option
}
# Use "swapon" if this is swap
if {$fstype == "swap"} {
return [::tuapi::syscall::swapon $source]
}
# Otherwise, call "mount" system call
## If we have accumulated any unknown options, pass them as a
## comma-seperated value string
if {[info exists unknown_options]} {
set data [join $unknown_options ","]
return [::tuapi::syscall::mount $source $target $fstype $options_list $data]
}
return [::tuapi::syscall::mount $source $target $fstype $options_list]
}
proc ::tuapi::umount {dir {flags ""}} {
return [::tuapi::syscall::umount $dir [string toupper $flags]]
}
proc ::tuapi::kill {pid sig} {
return [::tuapi::syscall::kill $pid [string toupper $sig]]
}
proc ::tuapi::killpg {pgroup sig} {
if {$pgroup <= 1} {
return -code error "invalid process group specified (must be greater than 1)"
}
return [::tuapi::syscall::kill -$pgroup [string toupper $sig]]
}
proc ::tuapi::ifconfig args {
if {[llength $args] == 0} {
# Return information on all interfaces
set retlist [list]
foreach interface [::tuapi::syscall::ifconfig] {
lappend retlist $interface [::tuapi::syscall::ifconfig $interface]
}
return $retlist
}
set interface [lindex $args 0]
set args [lrange $args 1 end]
array set ifaceinfo [::tuapi::syscall::ifconfig $interface]
if {[llength $args] == 0} {
return [array get ifaceinfo]
}
for {set idx 0} {$idx < [llength $args]} {incr idx} {
set opt [lindex $args $idx]
switch -- $opt {
"up" {
if {[info exists ifaceinfo(flags)]} {
set flags $ifaceinfo(flags)
} else {
set flags ""
}
foreach newflag [list UP] {
if {[lsearch -exact $flags $newflag] == -1} {
lappend flags $newflag
}
}
::tuapi::syscall::ifconfig $interface flags $flags
}
"down" {
if {[info exists ifaceinfo(flags)]} {
set flags $ifaceinfo(flags)
} else {
set flags ""
}
set flagidx [lsearch -exact $flags "UP"]
if {$flagidx != -1} {
set flags [lreplace $flags $flagidx $flagidx]
::tuapi::syscall::ifconfig $interface flags $flags
}
}
default {
incr idx
set optval [lindex $args $idx]
::tuapi::syscall::ifconfig $interface $opt $optval
}
}
}
}
proc ::tuapi::helper::foreach_line {fd sep code} {
while {![eof $fd]} {
gets $fd line
regsub { *#.*$} $line {} line
if {$line == ""} {
continue
}
set line [split $line $sep]
uplevel 1 [list set line $line]
uplevel 1 $code
}
uplevel 1 [list unset -nocomplain line]
}
proc ::tuapi::helper::recursive_glob {path pattern code {depth 1}} {
foreach filename [glob -nocomplain -directory $path -type f $pattern] {
uplevel $depth [list set filename $filename]
uplevel $depth $code
}
incr depth
foreach dirname [glob -nocomplain -directory $path -type d *] {
set dirinfo(type) unknown
catch {
file lstat $dirname dirinfo
}
if {$dirinfo(type) == "link"} {
continue
}
::tuapi::helper::recursive_glob $dirname $pattern $code $depth
}
}
proc ::tuapi::modprobe args {
# Process arguments
set options(call_insmod) 1
set idx 0
foreach arg $args {
switch -- $arg {
"-dontload" {
set options(call_insmod) 0
}
"--" {
incr idx
break
}
default {
break
}
}
incr idx
}
set args [lrange $args $idx end]
# Set initial retval
set retval [list]
# Set module base directory
set modules_dir [file join /lib/modules $::tcl_platform(osVersion)]
# Load device names
if {![info exists ::tuapi::cache::alias2module]} {
set devnames_file [file join $modules_dir modules.devname]
set fd [open $devnames_file]
::tuapi::helper::foreach_line $fd " " {
set module [lindex $line 0]
set device [lindex $line 1]
set id [lindex $line 2]
set id_type [string index $id 0]
set id_type [string map [list "c" "char" "b" "block"] $id_type]
set id [split [string range $id 1 end] :]
set id_alias "${id_type}-major-[lindex $id 0]-[lindex $id 1]"
set "alias2module(/dev/${device})" $module
set alias2module($id_alias) $module
}
close $fd
# Load aliases
set aliases_file [file join $modules_dir modules.alias]
set fd [open $aliases_file]
::tuapi::helper::foreach_line $fd " " {
set alias [lindex $line 1]
set module [lindex $line 2]
set alias2module($alias) $module
if {[string match {*\**} $alias]} {
set alias2module_wildcards($alias) $module
}
}
close $fd
# Load dependencies
set deps_file [file join $modules_dir modules.dep]
set fd [open $deps_file]
::tuapi::helper::foreach_line $fd ":" {
set module [string trim [lindex $line 0]]
set deps [split [string trim [join [lrange $line 1 end]]]]
set module_basename [file rootname [file tail $module]]
set module_basename_alt1 [string map [list "_" "-"] $module_basename]
set module_basename_alt2 [string map [list "-" "_"] $module_basename]
set alias2module($module_basename) $module
set alias2module($module_basename_alt1) $module
set alias2module($module_basename_alt2) $module
if {[llength $deps] != 0} {
set module2deps($module) $deps
}
}
close $fd
set ::tuapi::cache::alias2module [array get alias2module]
set ::tuapi::cache::alias2module_wildcards [array get alias2module_wildcards]
set ::tuapi::cache::module2deps [array get module2deps]
} else {
array set alias2module $::tuapi::cache::alias2module
array set alias2module_wildcards $::tuapi::cache::alias2module_wildcards
array set module2deps $::::tuapi::cache::module2deps
}
# Load modules
foreach modules $args {
foreach module $modules {
# If the module is given as an absolute path, ignore the path
# and process just as we would if the name were given alone
# This may be wrong, but otherwise dependency matching would
# be harder
if {[string index $module 0] == "/" && [file exists $module]} {
set module [file rootname [file tail $module]]
}
for {set try 0} {$try < 100} {incr try} {
if {![info exists alias2module($module)]} {
# If no exact match found, process wildcard entries
set found_wildcard_match 0
foreach alias [array name alias2module_wildcards] {
if {[string match $alias $module]} {
set module $alias2module_wildcards($alias)
set found_wildcard_match 1
break
}
}
if {!$found_wildcard_match} {
break
}
}
set module $alias2module($module)
}
if {[info exists module2deps($module)]} {
set load $module2deps($module)
} else {
set load [list]
}
lappend load $module
foreach module $load {
if {[string match "/dev/*" $module]} {
return -code error "Unable to lookup device node module for $module"
}
set module [file join $modules_dir $module]
if {$options(call_insmod)} {
if {[catch {
::tuapi::syscall::insmod $module
]} {
continue
}
}
lappend retval $module
}
}
}
return $retval
}
# Scan the various buses attached to the system and load the appropriate
# kernel modules
proc ::tuapi::scan_and_load_kernel_modules {{rescan_hardware 0}} {
set modules [list]
# Determine which modules are already loaded
foreach module [glob -tails -nocomplain -directory /sys/module -type d *] {
set alt_module1 [string map [list "_" "-"] $module]
set alt_module2 [string map [list "-" "_"] $module]
set loaded($module) 1
set loaded($alt_module1) 1
set loaded($alt_module2) 1
}
::tuapi::helper::recursive_glob /sys/devices modalias {
set fd [open $filename r]
::tuapi::helper::foreach_line $fd "\n" {
foreach module [::tuapi::modprobe -dontload -- $line] {
if {[lsearch -exact $modules $module] != -1} {
continue
}
if {![file exists $module]} {
continue
}
set rootname [file rootname [file tail $module]]
if {[info exists loaded($rootname)]} {
continue
}
lappend modules $module
}
}
close $fd
}
set failed_to_load [list]
set able_to_load [list]
foreach module $modules {
if {[::tuapi::modprobe $module] == ""} {
lappend failed_to_load $module
} else {
lappend able_to_load $module
}
}
return [list -failed $failed_to_load -loaded $able_to_load]
}
# Create UNIX-like procs meant to be used interactively
proc ::tuapi::create_unix_commands {} {
proc ::cat args {
foreach file $args {
if {[catch {
set fd [open $file]
} err]} {
puts stderr "Unable to open \"$file\": $err"
continue
}
fcopy $fd stdout
close $fd
}
}
proc ::ls args {
set options(long) 0
set options(one) 0
set options(skipdot) 1
set options(norecurseintotopleveldirs) 0
set idx 0
foreach arg $args {
if {[string match "-*" $arg]} {
set args [lreplace $args $idx $idx]
if {$arg == "--"} {
break
}
if {[string range $arg 0 1] == "--"} {
set opts [list [string range $arg 2 end]]
} else {
set opts [split [string range $arg 1 end] ""]
}
foreach opt $opts {
switch -- $opt {
"l" {
set options(long) 1
set options(one) 0
}
"1" {
set options(one) 1
set options(long) 0
}
"d" {
set options(norecurseintotopleveldirs) 1
}
"a" {
set options(skipdot) 0
}
}
}
continue
}
incr idx
}
if {[llength $args] == 0} {
set args [list "."]
}
set nodes [list]
foreach arg $args {
unset -nocomplain fileinfo
catch {
file stat $arg fileinfo
}
if {![info exists fileinfo]} {
puts stderr "No such file or directory: $arg"
continue
}
if {$fileinfo(type) == "directory"} {
if {$options(norecurseintotopleveldirs)} {
lappend nodes $arg
} else {
lappend nodes {*}[glob -nocomplain -directory $arg -tails *]
}
} else {
lappend nodes $arg
}
}
set newline_required 0
foreach node $nodes {
unset -nocomplain fileinfo
if {$options(one)} {
puts $node
} elseif {$options(long)} {
catch {
file stat $node fileinfo
}
if {![info exists fileinfo]} {
array set fileinfo [list mode 0 nlink 0 uid -1 gid -1 size 0 mtime 0]
}
set date [clock format $fileinfo(mtime) -format {%b %e %H:%M}]
switch -- $fileinfo(type) {
"directory" {
set typeid "d"
}
"blockSpecial" {
set typeid "b"
}
"characterSpecial" {
set typeid "c"
}
"file" {
set typeid "-"
}
"socket" {
set typeid "s"
}
default {
set typeid "?"
}
}
puts [format {%s%04o %5s %6s %6s %10s %12s %s} $typeid [expr {$fileinfo(mode) & 07777}] $fileinfo(nlink) $fileinfo(uid) $fileinfo(gid) $fileinfo(size) $date $node]
} else {
puts -nonewline "$node "
set newline_required 1
}
}
if {$newline_required} {
puts ""
}
}
proc ::modprobe args {
::tuapi::modprobe {*}$args
}
proc ::ps {} {
set format {%-6s %5s %5s %3s %5s %-6s %8s %s}
puts [format $format UID PID PPID C STIME TTY TIME CMD]
foreach pid [lsort -dictionary [glob -nocomplain -directory /proc -tails {[0-9]*}]] {
if {![string is integer $pid]} {
continue
}
set procfile [file join /proc $pid]
unset -nocomplain pidinfo
catch {
file stat $procfile pidinfo
}
if {![info exists pidinfo]} {
continue
}
set pidinfo(pid) $pid
set pidinfo(ppid) ?
set pidinfo(cpuutil) ?
set pidinfo(starttime) ?
set pidinfo(tty) ?
set pidinfo(cputime) ?
set pidinfo(cmd) ""
unset -nocomplain fd
catch {
set fd [open [file join $procfile cmdline]]
}
if {[info exists fd]} {
set pidinfo(cmd) [string trim [join [split [read $fd] "\0\n\r"]]]
close $fd
unset fd
}
if {![info exists pidinfo(cmd)] || $pidinfo(cmd) == ""} {
catch {
set fd [open [file join $procfile comm]]
}
if {[info exists fd]} {
set pidinfo(cmd) "\[[string trim [join [split [read $fd] "\0\n\r"]]]\]"
close $fd
}
}
puts [format $format $pidinfo(uid) $pidinfo(pid) $pidinfo(ppid) $pidinfo(cpuutil) $pidinfo(starttime) $pidinfo(tty) $pidinfo(cputime) $pidinfo(cmd)]
}
}
proc ::dmesg {} {
puts [::tuapi::syscall::klogctl read]
}
proc ::ulimit {limit {val ""}} {
set mapping(-c) [list CORE]
set mapping(-d) [list DATA]
set mapping(-e) [list NICE]
set mapping(-f) [list FSIZE]
set mapping(-i) [list SIGPENDING]
set mapping(-l) [list MEMLOCK]
set mapping(-m) [list RSS]
set mapping(-n) [list NOFILE]
set mapping(-q) [list MSGQUEUE]
set mapping(-r) [list RTPRIO]
set mapping(-s) [list STACK]
set mapping(-t) [list CPU]
set mapping(-u) [list NPROC]
set mapping(-v) [list AS]
set mapping(-x) [list LOCKS]
set help(CORE) {core file size (blocks, -c)}
set help(DATA) {data seg size (kbytes, -d)}
set help(NICE) {scheduling priority (-e)}
set help(FSIZE) {file size (blocks, -f)}
set help(SIGPENDING) {pending signals (-i)}
set help(MEMLOCK) {max locked memory (kbytes, -l)}
set help(RSS) {max memory size (kbytes, -m)}
set help(NOFILE) {open files (-n)}
set help(-p) {pipe size (512 bytes, -p)}
set help(MSGQUEUE) {POSIX message queues (bytes, -q)}
set help(RTPRIO) {real-time priority (-r)}
set help(STACK) {stack size (kbytes, -s)}
set help(CPU) {cpu time (seconds, -t)}
set help(NPROC) {max user processes (-u)}
set help(AS) {virtual memory (kbytes, -v)}
set help(LOCKS) {file locks (-x)}
foreach {limitopt limitoptvals} [array get mapping] {
foreach limitoptval $limitoptvals {
lappend mapping(-a) $limitoptval
}
}
set opts $mapping($limit)
if {[llength $opts] != 1 && $val != ""} {
return -code error "Unable to set multiple limits"
}
foreach opt $opts {
unset -nocomplain optval
if {$val != ""} {
catch {
::tuapi::syscall::rlimit set $opt $val
}
set include_help ""
} else {
set include_help "$help($opt) "
}
catch {
set optval [::tuapi::syscall::rlimit get $opt]
}
if {![info exists optval]} {
continue
}
puts "${include_help}$optval"
}
}
proc ::ifconfig {{ifacelist ""} {config ""}} {
if {$ifacelist == "" || $ifacelist == "-a"} {
set ifacelist [tuapi::syscall::ifconfig]
set config ""
}
if {$config != ""} {
if {[string match "*.*.*.*" [lindex $config 0]]} {
set config [linsert $config 0 "address"]
}
puts [list ::tuapi::ifconfig [lindex $ifacelist 0] {*}$config]
return [::tuapi::ifconfig [lindex $ifacelist 0] {*}$config]
}
foreach iface $ifacelist {
unset -nocomplain ifaceinfo
array set ifaceinfo [tuapi::syscall::ifconfig $iface]
set secondline ""
foreach {label entry} [list inet address netmask netmask broadcast broadcast] {
if {![info exists ifaceinfo($entry)]} {
continue
}
append secondline " $label $ifaceinfo($entry)"
}
puts "$iface: flags=<[join $ifaceinfo(flags) ,]> mtu $ifaceinfo(mtu) index $ifaceinfo(index)"
puts "\t[string trim $secondline]"
if {[info exists ifaceinfo(hwaddr)]} {
puts "\tether $ifaceinfo(hwaddr)"
}
}
}
}