{ list $state $varname [list 0 {}] $type $typeArgs $help; # ^ ^ # | | # hasBeenSet=+ +=currentValue } # Translate one item to canonical form proc OptNormalizeOne {item} { set lg [Lassign $item varname arg1 arg2 arg3]; # puts "called optnormalizeone '$item' v=($varname), lg=$lg"; set isflag [OptIsFlag $varname]; set isopt [OptIsOpt $varname]; if {$isflag} { set state "flags"; } elseif {$isopt} { set state "optValue"; } elseif {![string equal $varname "args"]} { set state "value"; } else { set state "args"; } # apply 'smart' 'fuzzy' logic to try to make # description writer's life easy, and our's difficult : # let's guess the missing arguments :-) switch $lg { 1 { if {$isflag} { return [OptNewInst $state $varname boolflag false ""]; } else { return [OptNewInst $state $varname any "" ""]; } } 2 { # varname default # varname help set type [OptGuessType $arg1] if {[string equal $type "string"]} { if {$isflag} { set type boolflag set def false } else { set type any set def "" } set help $arg1 } else { set help "" set def $arg1 } return [OptNewInst $state $varname $type $def $help]; } 3 { # varname type value # varname value comment if {[regexp {^-(.+)$} $arg1 x type]} { # flags/optValue as they are optional, need a "value", # on the contrary, for a variable (non optional), # default value is pointless, 'cept for choices : if {$isflag || $isopt || ($type == "choice")} { return [OptNewInst $state $varname $type $arg2 ""]; } else { return [OptNewInst $state $varname $type "" $arg2]; } } else { return [OptNewInst $state $varname\ [OptGuessType $arg1] $arg1 $arg2] } } 4 { if {[regexp {^-(.+)$} $arg1 x type]} { return [OptNewInst $state $varname $type $arg2 $arg3]; } else { return -code error [OptOptUsage $item]; } } default { return -code error [OptOptUsage $item]; } } } # Auto magic lasy type determination proc OptGuessType {arg} { if {[regexp -nocase {^(true|false)$} $arg]} { return boolean } if {[regexp {^(-+)?[0-9]+$} $arg]} { return int } if {![catch {expr {double($arg)}}]} { return float } return string } # Error messages front ends proc OptAmbigous {desc arg} { OptError "ambigous option \"$arg\", choose from:" [OptSelection $desc] } proc OptFlagUsage {desc arg} { OptError "bad flag \"$arg\", must be one of" $desc; } proc OptTooManyArgs {desc arguments} { OptError "too many arguments (unexpected argument(s): $arguments),\ usage:"\ $desc 1 } proc OptParamType {item} { if {[OptIsFlag $item]} { return "flag"; } else { return "parameter"; } } proc OptBadValue {item arg {err {}}} { # puts "bad val err = \"$err\""; OptError "bad value \"$arg\" for [OptParamType $item]"\ [list $item] } proc OptMissingValue {descriptions} { # set item [OptCurDescFinal $descriptions]; set item [OptCurDesc $descriptions]; OptError "no value given for [OptParamType $item] \"[OptName $item]\"\ (use -help for full usage) :"\ [list $item] } proc ::tcl::OptKeyError {prefix descKey {header 0}} { OptError $prefix [OptKeyGetDesc $descKey] $header; } # determine string length for nice tabulated output proc OptLengths {desc nlName tlName dlName} { upvar $nlName nl; upvar $tlName tl; upvar $dlName dl; foreach item $desc { if {[OptIsCounter $item]} continue; if {[OptIsPrg $item]} { OptLengths $item nl tl dl } else { SetMax nl [string length [OptName $item]] SetMax tl [string length [OptType $item]] set dv [OptTypeArgs $item]; if {[OptState $item] != "header"} { set dv "($dv)"; } set l [string length $dv]; # limit the space allocated to potentially big "choices" if {([OptType $item] != "choice") || ($l<=12)} { SetMax dl $l } else { if {![info exists dl]} { set dl 0 } } } } } # output the tree proc OptTree {desc nl tl dl} { set res ""; foreach item $desc { if {[OptIsCounter $item]} continue; if {[OptIsPrg $item]} { append res [OptTree $item $nl $tl $dl]; } else { set dv [OptTypeArgs $item]; if {[OptState $item] != "header"} { set dv "($dv)"; } append res [format "\n %-*s %-*s %-*s %s" \ $nl [OptName $item] $tl [OptType $item] \ $dl $dv [OptHelp $item]] } } return $res; } # Give nice usage string proc ::tcl::OptError {prefix desc {header 0}} { # determine length if {$header} { # add faked instruction set h [list [OptNewInst header Var/FlagName Type Value Help]]; lappend h [OptNewInst header ------------ ---- ----- ----]; lappend h [OptNewInst header {( -help} "" "" {gives this help )}] set desc [concat $h $desc] } OptLengths $desc nl tl dl # actually output return "$prefix[OptTree $desc $nl $tl $dl]" } ################ General Utility functions ####################### # # List utility functions # Naming convention: # "Lvarxxx" take the list VARiable name as argument # "Lxxxx" take the list value as argument # (which is not costly with Tcl8 objects system # as it's still a reference and not a copy of the values) # # Is that list empty ? proc ::tcl::Lempty {list} { expr {[llength $list]==0} } # Gets the value of one leaf of a lists tree proc ::tcl::Lget {list indexLst} { if {[llength $indexLst] <= 1} { return [lindex $list $indexLst]; } Lget [lindex $list [lindex $indexLst 0]] [lrange $indexLst 1 end]; } # Sets the value of one leaf of a lists tree # (we use the version that does not create the elements because # it would be even slower... needs to be written in C !) # (nb: there is a non trivial recursive problem with indexes 0, # which appear because there is no difference between a list # of 1 element and 1 element alone : [list "a"] == "a" while # it should be {a} and [listp a] should be 0 while [listp {a b}] would be 1 # and [listp "a b"] maybe 0. listp does not exist either...) proc ::tcl::Lvarset {listName indexLst newValue} { upvar $listName list; if {[llength $indexLst] <= 1} { Lvarset1nc list $indexLst $newValue; } else { set idx [lindex $indexLst 0]; set targetList [lindex $list $idx]; # reduce refcount on targetList (not really usefull now, # could be with optimizing compiler) # Lvarset1 list $idx {}; # recursively replace in targetList Lvarset targetList [lrange $indexLst 1 end] $newValue; # put updated sub list back in the tree Lvarset1nc list $idx $targetList; } } # Set one cell to a value, eventually create all the needed elements # (on level-1 of lists) variable emptyList {} proc ::tcl::Lvarset1 {listName index newValue} { upvar $listName list; if {$index < 0} {return -code error "invalid negative index"} set lg [llength $list]; if {$index >= $lg} { variable emptyList; for {set i $lg} {$i<$index} {incr i} { lappend list $emptyList; } lappend list $newValue; } else { set list [lreplace $list $index $index $newValue]; } } # same as Lvarset1 but no bound checking / creation proc ::tcl::Lvarset1nc {listName index newValue} { upvar $listName list; set list [lreplace $list $index $index $newValue]; } # Increments the value of one leaf of a lists tree # (which must exists) proc ::tcl::Lvarincr {listName indexLst {howMuch 1}} { upvar $listName list; if {[llength $indexLst] <= 1} { Lvarincr1 list $indexLst $howMuch; } else { set idx [lindex $indexLst 0]; set targetList [lindex $list $idx]; # reduce refcount on targetList Lvarset1nc list $idx {}; # recursively replace in targetList Lvarincr targetList [lrange $indexLst 1 end] $howMuch; # put updated sub list back in the tree Lvarset1nc list $idx $targetList; } } # Increments the value of one cell of a list proc ::tcl::Lvarincr1 {listName index {howMuch 1}} { upvar $listName list; set newValue [expr {[lindex $list $index]+$howMuch}]; set list [lreplace $list $index $index $newValue]; return $newValue; } # Removes the first element of a list # and returns the new list value proc ::tcl::Lvarpop1 {listName} { upvar $listName list; set list [lrange $list 1 end]; } # Same but returns the removed element # (Like the tclX version) proc ::tcl::Lvarpop {listName} { upvar $listName list; set el [lindex $list 0]; set list [lrange $list 1 end]; return $el; } # Assign list elements to variables and return the length of the list proc ::tcl::Lassign {list args} { # faster than direct blown foreach (which does not byte compile) set i 0; set lg [llength $list]; foreach vname $args { if {$i>=$lg} break uplevel 1 [list ::set $vname [lindex $list $i]]; incr i; } return $lg; } # Misc utilities # Set the varname to value if value is greater than varname's current value # or if varname is undefined proc ::tcl::SetMax {varname value} { upvar 1 $varname var if {![info exists var] || $value > $var} { set var $value } } # Set the varname to value if value is smaller than varname's current value # or if varname is undefined proc ::tcl::SetMin {varname value} { upvar 1 $varname var if {![info exists var] || $value < $var} { set var $value } } # everything loaded fine, lets create the test proc: # OptCreateTestProc # Don't need the create temp proc anymore: # rename OptCreateTestProc {} } # Tcl package index file, version 1.1 # This file is generated by the "pkg_mkIndex -direct" command # and sourced either when an application starts up or # by a "package unknown" script. It invokes the # "package ifneeded" command to set up package-related # information so that packages will be loaded automatically # in response to "package require" commands. When this # script is sourced, the variable $dir must contain the # full path name of this file's directory. if {![package vsatisfies [package provide Tcl] 8.2]} {return} package ifneeded opt 0.4.4.1 [list source [file join $dir optparse.tcl]] # safe.tcl -- # # This file provide a safe loading/sourcing mechanism for safe interpreters. # It implements a virtual path mecanism to hide the real pathnames from the # slave. It runs in a master interpreter and sets up data structure and # aliases that will be invoked when used from a slave interpreter. # # See the safe.n man page for details. # # Copyright (c) 1996-1997 Sun Microsystems, Inc. # # See the file "license.terms" for information on usage and redistribution # of this file, and for a DISCLAIMER OF ALL WARRANTIES. # # RCS: @(#) $Id: safe.tcl,v 1.9.2.3 2005/07/22 21:59:41 dgp Exp $ # # The implementation is based on namespaces. These naming conventions # are followed: # Private procs starts with uppercase. # Public procs are exported and starts with lowercase # # Needed utilities package package require opt 0.4.1; # Create the safe namespace namespace eval ::safe { # Exported API: namespace export interpCreate interpInit interpConfigure interpDelete \ interpAddToAccessPath interpFindInAccessPath setLogCmd #### # # Setup the arguments parsing # #### # Make sure that our temporary variable is local to this # namespace. [Bug 981733] variable temp # Share the descriptions set temp [::tcl::OptKeyRegister { {-accessPath -list {} "access path for the slave"} {-noStatics "prevent loading of statically linked pkgs"} {-statics true "loading of statically linked pkgs"} {-nestedLoadOk "allow nested loading"} {-nested false "nested loading"} {-deleteHook -script {} "delete hook"} }] # create case (slave is optional) ::tcl::OptKeyRegister { {?slave? -name {} "name of the slave (optional)"} } ::safe::interpCreate # adding the flags sub programs to the command program # (relying on Opt's internal implementation details) lappend ::tcl::OptDesc(::safe::interpCreate) $::tcl::OptDesc($temp) # init and configure (slave is needed) ::tcl::OptKeyRegister { {slave -name {} "name of the slave"} } ::safe::interpIC # adding the flags sub programs to the command program # (relying on Opt's internal implementation details) lappend ::tcl::OptDesc(::safe::interpIC) $::tcl::OptDesc($temp) # temp not needed anymore ::tcl::OptKeyDelete $temp # Helper function to resolve the dual way of specifying staticsok # (either by -noStatics or -statics 0) proc InterpStatics {} { foreach v {Args statics noStatics} { upvar $v $v } set flag [::tcl::OptProcArgGiven -noStatics]; if {$flag && (!$noStatics == !$statics) && ([::tcl::OptProcArgGiven -statics])} { return -code error\ "conflicting values given for -statics and -noStatics" } if {$flag} { return [expr {!$noStatics}] } else { return $statics } } # Helper function to resolve the dual way of specifying nested loading # (either by -nestedLoadOk or -nested 1) proc InterpNested {} { foreach v {Args nested nestedLoadOk} { upvar $v $v } set flag [::tcl::OptProcArgGiven -nestedLoadOk]; # note that the test here is the opposite of the "InterpStatics" # one (it is not -noNested... because of the wanted default value) if {$flag && (!$nestedLoadOk != !$nested) && ([::tcl::OptProcArgGiven -nested])} { return -code error\ "conflicting values given for -nested and -nestedLoadOk" } if {$flag} { # another difference with "InterpStatics" return $nestedLoadOk } else { return $nested } } #### # # API entry points that needs argument parsing : # #### # Interface/entry point function and front end for "Create" proc interpCreate {args} { set Args [::tcl::OptKeyParse ::safe::interpCreate $args] InterpCreate $slave $accessPath \ [InterpStatics] [InterpNested] $deleteHook } proc interpInit {args} { set Args [::tcl::OptKeyParse ::safe::interpIC $args] if {![::interp exists $slave]} { return -code error "\"$slave\" is not an interpreter" } InterpInit $slave $accessPath \ [InterpStatics] [InterpNested] $deleteHook; } proc CheckInterp {slave} { if {![IsInterp $slave]} { return -code error \ "\"$slave\" is not an interpreter managed by ::safe::" } } # Interface/entry point function and front end for "Configure" # This code is awfully pedestrian because it would need # more coupling and support between the way we store the # configuration values in safe::interp's and the Opt package # Obviously we would like an OptConfigure # to avoid duplicating all this code everywhere. -> TODO # (the app should share or access easily the program/value # stored by opt) # This is even more complicated by the boolean flags with no values # that we had the bad idea to support for the sake of user simplicity # in create/init but which makes life hard in configure... # So this will be hopefully written and some integrated with opt1.0 # (hopefully for tcl8.1 ?) proc interpConfigure {args} { switch [llength $args] { 1 { # If we have exactly 1 argument # the semantic is to return all the current configuration # We still call OptKeyParse though we know that "slave" # is our given argument because it also checks # for the "-help" option. set Args [::tcl::OptKeyParse ::safe::interpIC $args] CheckInterp $slave set res {} lappend res [list -accessPath [Set [PathListName $slave]]] lappend res [list -statics [Set [StaticsOkName $slave]]] lappend res [list -nested [Set [NestedOkName $slave]]] lappend res [list -deleteHook [Set [DeleteHookName $slave]]] join $res } 2 { # If we have exactly 2 arguments # the semantic is a "configure get" ::tcl::Lassign $args slave arg # get the flag sub program (we 'know' about Opt's internal # representation of data) set desc [lindex [::tcl::OptKeyGetDesc ::safe::interpIC] 2] set hits [::tcl::OptHits desc $arg] if {$hits > 1} { return -code error [::tcl::OptAmbigous $desc $arg] } elseif {$hits == 0} { return -code error [::tcl::OptFlagUsage $desc $arg] } CheckInterp $slave set item [::tcl::OptCurDesc $desc] set name [::tcl::OptName $item] switch -exact -- $name { -accessPath { return [list -accessPath [Set [PathListName $slave]]] } -statics { return [list -statics [Set [StaticsOkName $slave]]] } -nested { return [list -nested [Set [NestedOkName $slave]]] } -deleteHook { return [list -deleteHook [Set [DeleteHookName $slave]]] } -noStatics { # it is most probably a set in fact # but we would need then to jump to the set part # and it is not *sure* that it is a set action # that the user want, so force it to use the # unambigous -statics ?value? instead: return -code error\ "ambigous query (get or set -noStatics ?)\ use -statics instead" } -nestedLoadOk { return -code error\ "ambigous query (get or set -nestedLoadOk ?)\ use -nested instead" } default { return -code error "unknown flag $name (bug)" } } } default { # Otherwise we want to parse the arguments like init and create # did set Args [::tcl::OptKeyParse ::safe::interpIC $args] CheckInterp $slave # Get the current (and not the default) values of # whatever has not been given: if {![::tcl::OptProcArgGiven -accessPath]} { set doreset 1 set accessPath [Set [PathListName $slave]] } else { set doreset 0 } if {(![::tcl::OptProcArgGiven -statics]) \ && (![::tcl::OptProcArgGiven -noStatics]) } { set statics [Set [StaticsOkName $slave]] } else { set statics [InterpStatics] } if {([::tcl::OptProcArgGiven -nested]) \ || ([::tcl::OptProcArgGiven -nestedLoadOk]) } { set nested [InterpNested] } else { set nested [Set [NestedOkName $slave]] } if {![::tcl::OptProcArgGiven -deleteHook]} { set deleteHook [Set [DeleteHookName $slave]] } # we can now reconfigure : InterpSetConfig $slave $accessPath $statics $nested $deleteHook # auto_reset the slave (to completly synch the new access_path) if {$doreset} { if {[catch {::interp eval $slave {auto_reset}} msg]} { Log $slave "auto_reset failed: $msg" } else { Log $slave "successful auto_reset" NOTICE } } } } } #### # # Functions that actually implements the exported APIs # #### # # safe::InterpCreate : doing the real job # # This procedure creates a safe slave and initializes it with the # safe base aliases. # NB: slave name must be simple alphanumeric string, no spaces, # no (), no {},... {because the state array is stored as part of the name} # # Returns the slave name. # # Optional Arguments : # + slave name : if empty, generated name will be used # + access_path: path list controlling where load/source can occur, # if empty: the master auto_path will be used. # + staticsok : flag, if 0 :no static package can be loaded (load {} Xxx) # if 1 :static packages are ok. # + nestedok: flag, if 0 :no loading to sub-sub interps (load xx xx sub) # if 1 : multiple levels are ok. # use the full name and no indent so auto_mkIndex can find us proc ::safe::InterpCreate { slave access_path staticsok nestedok deletehook } { # Create the slave. if {$slave ne ""} { ::interp create -safe $slave } else { # empty argument: generate slave name set slave [::interp create -safe] } Log $slave "Created" NOTICE # Initialize it. (returns slave name) InterpInit $slave $access_path $staticsok $nestedok $deletehook } # # InterpSetConfig (was setAccessPath) : # Sets up slave virtual auto_path and corresponding structure # within the master. Also sets the tcl_library in the slave # to be the first directory in the path. # Nb: If you change the path after the slave has been initialized # you probably need to call "auto_reset" in the slave in order that it # gets the right auto_index() array values. proc ::safe::InterpSetConfig {slave access_path staticsok\ nestedok deletehook} { # determine and store the access path if empty if {$access_path eq ""} { set access_path [uplevel \#0 set auto_path] # Make sure that tcl_library is in auto_path # and at the first position (needed by setAccessPath) set where [lsearch -exact $access_path [info library]] if {$where == -1} { # not found, add it. set access_path [concat [list [info library]] $access_path] Log $slave "tcl_library was not in auto_path,\ added it to slave's access_path" NOTICE } elseif {$where != 0} { # not first, move it first set access_path [concat [list [info library]]\ [lreplace $access_path $where $where]] Log $slave "tcl_libray was not in first in auto_path,\ moved it to front of slave's access_path" NOTICE } # Add 1st level sub dirs (will searched by auto loading from tcl # code in the slave using glob and thus fail, so we add them # here so by default it works the same). set access_path [AddSubDirs $access_path] } Log $slave "Setting accessPath=($access_path) staticsok=$staticsok\ nestedok=$nestedok deletehook=($deletehook)" NOTICE # clear old autopath if it existed set nname [PathNumberName $slave] if {[Exists $nname]} { set n [Set $nname] for {set i 0} {$i<$n} {incr i} { Unset [PathToken $i $slave] } } # build new one set slave_auto_path {} set i 0 foreach dir $access_path { Set [PathToken $i $slave] $dir lappend slave_auto_path "\$[PathToken $i]" incr i } Set $nname $i Set [PathListName $slave] $access_path Set [VirtualPathListName $slave] $slave_auto_path Set [StaticsOkName $slave] $staticsok Set [NestedOkName $slave] $nestedok Set [DeleteHookName $slave] $deletehook SyncAccessPath $slave } # # # FindInAccessPath: # Search for a real directory and returns its virtual Id ٻڻۻܻݻ޻߻à»á»â»ã»ä»å»æ»ç»è» # (including the "$") proc ::safe::interpFindInAccessPath {slave path} { set access_path [GetAccessPath $slave] set where [lsearch -exact $access_path $path] if {$where == -1} { return -code error "$path not found in access path $access_path" } return "\$[PathToken $where]" } # # addToAccessPath: # add (if needed) a real directory to access path # and return its virtual token (including the "$"). proc ::safe::interpAddToAccessPath {slave path} { # first check if the directory is already in there if {![catch {interpFindInAccessPath $slave $path} res]} { return $res } # new one, add it: set nname [PathNumberName $slave] set n [Set $nname] Set [PathToken $n $slave] $path set token "\$[PathToken $n]" Lappend [VirtualPathListName $slave] $token Lappend [PathListName $slave] $path Set $nname [expr {$n+1}] SyncAccessPath $slave return $token } # This procedure applies the initializations to an already existing # interpreter. It is useful when you want to install the safe base # aliases into a preexisting safe interpreter. proc ::safe::InterpInit { slave access_path staticsok nestedok deletehook } { # Configure will generate an access_path when access_path is # empty. InterpSetConfig $slave $access_path $staticsok $nestedok $deletehook # These aliases let the slave load files to define new commands # NB we need to add [namespace current], aliases are always # absolute paths. ::interp alias $slave source {} [namespace current]::AliasSource $slave ::interp alias $slave load {} [namespace current]::AliasLoad $slave # This alias lets the slave use the encoding names, convertfrom, # convertto, and system, but not "encoding system " to set # the system encoding. ::interp alias $slave encoding {} [namespace current]::AliasEncoding \ $slave # This alias lets the slave have access to a subset of the 'file' # command functionality. AliasSubset $slave file file dir.* join root.* ext.* tail \ path.* split # This alias interposes on the 'exit' command and cleanly terminates # the slave. ::interp alias $slave exit {} [namespace current]::interpDelete $slave # The allowed slave variables already have been set # by Tcl_MakeSafe(3) # Source init.tcl into the slave, to get auto_load and other # procedures defined: # We don't try to use the -rsrc on the mac because it would get # confusing if you would want to customize init.tcl # for a given set of safe slaves, on all the platforms # you just need to give a specific access_path and # the mac should be no exception. As there is no # obvious full "safe ressources" design nor implementation # for the mac, safe interps there will just don't # have that ability. (A specific app can still reenable # that using custom aliases if they want to). # It would also make the security analysis and the Safe Tcl security # model platform dependant and thus more error prone. if {[catch {::interp eval $slave\ {source [file join $tcl_library init.tcl]}} msg]} { Log $slave "can't source init.tcl ($msg)" error "can't source init.tcl into slave $slave ($msg)" } return $slave } # Add (only if needed, avoid duplicates) 1 level of # sub directories to an existing path list. # Also removes non directories from the returned list. proc AddSubDirs {pathList} { set res {} foreach dir $pathList { if {[file isdirectory $dir]} { # check that we don't have it yet as a children # of a previous dir if {[lsearch -exact $res $dir]<0} { lappend res $dir } foreach sub [glob -directory $dir -nocomplain *] { if {([file isdirectory $sub]) \ && ([lsearch -exact $res $sub]<0) } { # new sub dir, add it ! lappend res $sub } } } } return $res } # This procedure deletes a safe slave managed by Safe Tcl and # cleans up associated state: proc ::safe::interpDelete {slave} { Log $slave "About to delete" NOTICE # If the slave has a cleanup hook registered, call it. # check the existance because we might be called to delete an interp # which has not been registered with us at all set hookname [DeleteHookName $slave] if {[Exists $hookname]} { set hook [Set $hookname] if {![::tcl::Lempty $hook]} { # remove the hook now, otherwise if the hook # calls us somehow, we'll loop Unset $hookname if {[catch {eval $hook [list $slave]} err]} { Log $slave "Delete hook error ($err)" } } } # Discard the global array of state associated with the slave, and # delete the interpreter. set statename [InterpStateName $slave] if {[Exists $statename]} { Unset $statename } # if we have been called twice, the interp might have been deleted # already if {[::interp exists $slave]} { ::interp delete $slave Log $slave "Deleted" NOTICE } return } # Set (or get) the loging mecanism proc ::safe::setLogCmd {args} { variable Log if {[llength $args] == 0} { return $Log } else { if {[llength $args] == 1} { set Log [lindex $args 0] } else { set Log $args } } } # internal variable variable Log {} # ------------------- END OF PUBLIC METHODS ------------ # # sets the slave auto_path to the master recorded value. # also sets tcl_library to the first token of the virtual path. # proc SyncAccessPath {slave} { set slave_auto_path [Set [VirtualPathListName $slave]] ::interp eval $slave [list set auto_path $slave_auto_path] Log $slave "auto_path in $slave has been set to $slave_auto_path"\ NOTICE ::interp eval $slave [list set tcl_library [lindex $slave_auto_path 0]] } # base name for storing all the slave states # the array variable name for slave foo is thus "Sfoo" # and for sub slave {foo bar} "Sfoo bar" (spaces are handled # ok everywhere (or should)) # We add the S prefix to avoid that a slave interp called "Log" # would smash our "Log" variable. proc InterpStateName {slave} { return "S$slave" } # Check that the given slave is "one of us" proc IsInterp {slave} { expr {[Exists [InterpStateName $slave]] && [::interp exists $slave]} } # returns the virtual token for directory number N # if the slave argument is given, # it will return the corresponding master global variable name proc PathToken {n {slave ""}} { if {$slave ne ""} { return "[InterpStateName $slave](access_path,$n)" } else { # We need to have a ":" in the token string so # [file join] on the mac won't turn it into a relative # path. return "p(:$n:)" } } # returns the variable name of the complete path list proc PathListName {slave} { return "[InterpStateName $slave](access_path)" } # returns the variable name of the complete path list proc VirtualPathListName {slave} { return "[InterpStateName $slave](access_path_slave)" } # returns the variable name of the number of items proc PathNumberName {slave} { return "[InterpStateName $slave](access_path,n)" } # returns the staticsok flag var name proc StaticsOkName {slave} { return "[InterpStateName $slave](staticsok)" } # returns the nestedok flag var name proc NestedOkName {slave} { return "[InterpStateName $slave](nestedok)" } # Run some code at the namespace toplevel proc Toplevel {args} { namespace eval [namespace current] $args } # set/get values proc Set {args} { eval [linsert $args 0 Toplevel set] } # lappend on toplevel vars proc Lappend {args} { eval [linsert $args 0 Toplevel lappend] } # unset a var/token (currently just an global level eval) proc Unset {args} { eval [linsert $args 0 Toplevel unset] } # test existance proc Exists {varname} { Toplevel info exists $varname } # short cut for access path getting proc GetAccessPath {slave} { Set [PathListName $slave] } # short cut for statics ok flag getting proc StaticsOk {slave} { Set [StaticsOkName $slave] } # short cut for getting the multiples interps sub loading ok flag proc NestedOk {slave} { Set [NestedOkName $slave] } # interp deletion storing hook name proc DeleteHookName {slave} { return [InterpStateName $slave](cleanupHook) } # # translate virtual path into real path # proc TranslatePath {slave path} { # somehow strip the namespaces 'functionality' out (the danger # is that we would strip valid macintosh "../" queries... : if {[regexp {(::)|(\.\.)} $path]} { error "invalid characters in path $path" } set n [expr {[Set [PathNumberName $slave]]-1}] for {} {$n>=0} {incr n -1} { # fill the token virtual names with their real value set [PathToken $n] [Set [PathToken $n $slave]] } # replaces the token by their value subst -nobackslashes -nocommands $path } # Log eventually log an error # to enable error logging, set Log to {puts stderr} for instance proc Log {slave msg {type ERROR}} { variable Log if {[info exists Log] && [llength $Log]} { eval $Log [list "$type for slave $slave : $msg"] } } # file name control (limit access to files/ressources that should be # a valid tcl source file) proc CheckFileName {slave file} { # This used to limit what can be sourced to ".tcl" and forbid files # with more than 1 dot and longer than 14 chars, but I changed that # for 8.4 as a safe interp has enough internal protection already # to allow sourcing anything. - hobbs if {![file exists $file]} { # don't tell the file path error "no such file or directory" } if {![file readable $file]} { # don't tell the file path error "not readable" } } # AliasSource is the target of the "source" alias in safe interpreters. proc AliasSource {slave args} { set argc [llength $args] # Allow only "source filename" # (and not mac specific -rsrc for instance - see comment in ::init # for current rationale) if {$argc != 1} { set msg "wrong # args: should be \"source fileName\"" Log $slave "$msg ($args)" return -code error $msg } set file [lindex $args 0] # get the real path from the virtual one. if {[catch {set file [TranslatePath $slave $file]} msg]} { Log $slave $msg return -code error "permission denied" } # check that the path is in the access path of that slave if {[catch {FileInAccessPath $slave $file} msg]} { Log $slave $msg return -code error "permission denied" } # do the checks on the filename : if {[catch {CheckFileName $slave $file} msg]} { Log $slave "$file:$msg" return -code error $msg } # passed all the tests , lets source it: if {[catch {::interp invokehidden $slave source $file} msg]} { Log $slave $msg return -code error "script error" } return $msg } # AliasLoad is the target of the "load" alias in safe interpreters. proc AliasLoad {slave file args} { set argc [llength $args] if {$argc > 2} { set msg "load error: too many arguments" Log $slave "$msg ($argc) {$file $args}" return -code error $msg } # package name (can be empty if file is not). set package [lindex $args 0] # Determine where to load. load use a relative interp path # and {} means self, so we can directly and safely use passed arg. set target [lindex $args 1] if {$target ne ""} { # we will try to load into a sub sub interp # check that we want to authorize that. if {![NestedOk $slave]} { Log $slave "loading to a sub interp (nestedok)\ disabled (trying to load $package to $target)" return -code error "permission denied (nested load)" } } # Determine what kind of load is requested if {$file eq ""} { # static package loading if {$package eq ""} { set msg "load error: empty filename and no package name" Log $slave $msg return -code error $msg } if {![StaticsOk $slave]} { Log $slave "static packages loading disabled\ (trying to load $package to $target)" return -code error "permission denied (static package)" } } else { # file loading # get the real path from the virtual one. if {[catch {set file [TranslatePath $slave $file]} msg]} { Log $slave $msg return -code error "permission denied" } # check the translated path if {[catch {FileInAccessPath $slave $file} msg]} { Log $slave $msg return -code error "permission denied (path)" } } if {[catch {::interp invokehidden\ $slave load $file $package $target} msg]} { Log $slave $msg return -code error $msg } return $msg } # FileInAccessPath raises an error if the file is not found in # the list of directories contained in the (master side recorded) slave's # access path. # the security here relies on "file dirname" answering the proper # result.... needs checking ? proc FileInAccessPath {slave file} { set access_path [GetAccessPath $slave] if {[file isdirectory $file]} { error "\"$file\": is a directory" } set parent [file dirname $file] # Normalize paths for comparison since lsearch knows nothing of # potential pathname anomalies. set norm_parent [file normalize $parent] foreach path $access_path { lappend norm_access_path [file normalize $path] } if {[lsearch -exact $norm_access_path $norm_parent] == -1} { error "\"$file\": not in access_path" } } # This procedure enables access from a safe interpreter to only a subset of # the subcommands of a command: proc Subset {slave command okpat args} { set subcommand [lindex $args 0] if {[regexp $okpat $subcommand]} { return [eval [linsert $args 0 $command]] } set msg "not allowed to invoke subcommand $subcommand of $command" Log $slave $msg error $msg } # This procedure installs an alias in a slave that invokes "safesubset" # in the master to execute allowed subcommands. It precomputes the pattern # of allowed subcommands; you can use wildcards in the pattern if you wish # to allow subcommand abbreviation. # # Syntax is: AliasSubset slave alias target subcommand1 subcommand2... proc AliasSubset {slave alias target args} { set pat ^(; set sep "" foreach sub $args { append pat $sep$sub set sep | } append pat )\$ ::interp alias $slave $alias {}\ [namespace current]::Subset $slave $target $pat } # AliasEncoding is the target of the "encoding" alias in safe interpreters. proc AliasEncoding {slave args} { set argc [llength $args] set okpat "^(name.*|convert.*)\$" set subcommand [lindex $args 0] if {[regexp $okpat $subcommand]} { return [eval [linsert $args 0 \ ::interp invokehidden $slave encoding]] } if {[string first $subcommand system] == 0} { if {$argc == 1} { # passed all the tests , lets source it: if {[catch {::interp invokehidden \ $slave encoding system} msg]} { Log $slave $msg return -code error "script error" } } else { set msg "wrong # args: should be \"encoding system\"" Log $slave $msg error $msg } } else { set msg "wrong # args: should be \"encoding option ?arg ...?\"" Log $slave $msg error $msg } return $msg } } ý$ .áô..# package.tcl -- # # utility procs formerly in init.tcl which can be loaded on demand # for package management. # # RCS: @(#) $Id: package.tcl,v 1.23.2.4 2006/09/22 01:26:24 andreas_kupries Exp $ # # Copyright (c) 1991-1993 The Regents of the University of California. # Copyright (c) 1994-1998 Sun Microsystems, Inc. # # See the file "license.terms" for information on usage and redistribution # of this file, and for a DISCLAIMER OF ALL WARRANTIES. # # Create the package namespace namespace eval ::pkg { } # pkg_compareExtension -- # # Used internally by pkg_mkIndex to compare the extension of a file to # a given extension. On Windows, it uses a case-insensitive comparison # because the file system can be file insensitive. # # Arguments: # fileName name of a file whose extension is compared # ext (optional) The extension to compare against; you must # provide the starting dot. # Defaults to [info sharedlibextension] # # Results: # Returns 1 if the extension matches, 0 otherwise proc pkg_compareExtension { fileName {ext {}} } { global tcl_platform if {$ext eq ""} {set ext [info sharedlibextension]} if {$tcl_platform(platform) eq "windows"} { return [string equal -nocase [file extension $fileName] $ext] } else { # Some unices add trailing numbers after the .so, so # we could have something like '.so.1.2'. set root $fileName while {1} { set currExt [file extension $root] if {$currExt eq $ext} { return 1 } # The current extension does not match; if it is not a numeric # value, quit, as we are only looking to ignore version number # extensions. Otherwise we might return 1 in this case: # pkg_compareExtension foo.so.bar .so # which should not match. if { ![string is integer -strict [string range $currExt 1 end]] } { return 0 } set root [file rootname $root] } } } # pkg_mkIndex -- # This procedure creates a package index in a given directory. The # package index consists of a "pkgIndex.tcl" file whose contents are # a Tcl script that sets up package information with "package require" # commands. The commands describe all of the packages defined by the # files given as arguments. # # Arguments: # -direct (optional) If this flag is present, the generated # code in pkgMkIndex.tcl will cause the package to be # loaded when "package require" is executed, rather # than lazily when the first reference to an exported # procedure in the package is made. # -verbose (optional) Verbose output; the name of each file that # was successfully rocessed is printed out. Additionally, # if processing of a file failed a message is printed. # -load pat (optional) Preload any packages whose names match # the pattern. Used to handle DLLs that depend on # other packages during their Init procedure. # dir - Name of the directory in which to create the index. # args - Any number of additional arguments, each giving # a glob pattern that matches the names of one or # more shared libraries or Tcl script files in # dir. proc pkg_mkIndex {args} { global errorCode errorInfo set usage {"pkg_mkIndex ?-direct? ?-lazy? ?-load pattern? ?-verbose? ?--? dir ?pattern ...?"}; set argCount [llength $args] if {$argCount < 1} { return -code error "wrong # args: should be\n$usage" } set more "" set direct 1 set doVerbose 0 set loadPat "" for {set idx 0} {$idx < $argCount} {incr idx} { set flag [lindex $args $idx] switch -glob -- $flag { -- { # done with the flags incr idx break } -verbose { set doVerbose 1 } -lazy { set direct 0 append more " -lazy" } -direct { append more " -direct" } -load { incr idx set loadPat [lindex $args $idx] append more " -load $loadPat" } -* { return -code error "unknown flag $flag: should be\n$usage" } default { # done with the flags break } } } set dir [lindex $args $idx] set patternList [lrange $args [expr {$idx + 1}] end] if {[llength $patternList] == 0} { set patternList [list "*.tcl" "*[info sharedlibextension]"] } set oldDir [pwd] cd $dir if {[catch {eval [linsert $patternList 0 glob --]} fileList]} { global errorCode errorInfo cd $oldDir return -code error -errorcode $errorCode -errorinfo $errorInfo $fileList } foreach file $fileList { # For each file, figure out what commands and packages it provides. # To do this, create a child interpreter, load the file into the # interpreter, and get a list of the new commands and packages # that are defined. if {$file eq "pkgIndex.tcl"} { continue } # Changed back to the original directory before initializing the # slave in case TCL_LIBRARY is a relative path (e.g. in the test # suite). cd $oldDir set c [interp create] # Load into the child any packages currently loaded in the parent # interpreter that match the -load pattern. if {$loadPat ne ""} { if {$doVerbose} { tclLog "currently loaded packages: '[info loaded]'" tclLog "trying to load all packages matching $loadPat" } if {![llength [info loaded]]} { tclLog "warning: no packages are currently loaded, nothing" tclLog "can possibly match '$loadPat'" } } foreach pkg [info loaded] { if {! [string match -nocase $loadPat [lindex $pkg 1]]} { continue } if {$doVerbose} { tclLog "package [lindex $pkg 1] matches '$loadPat'" } if {[catch { load [lindex $pkg 0] [lindex $pkg 1] $c } err]} { if {$doVerbose} { tclLog "warning: load [lindex $pkg 0] [lindex $pkg 1]\nfailed with: $err" } } elseif {$doVerbose} { tclLog "loaded [lindex $pkg 0] [lindex $pkg 1]" } if {[lindex $pkg 1] eq "Tk"} { # Withdraw . if Tk was loaded, to avoid showing a window. $c eval [list wm withdraw .] } } cd $dir $c eval { # Stub out the package command so packages can # require other packages. rename package __package_orig proc package {what args} { switch -- $what { require { return ; # ignore transitive requires } default { uplevel 1 [linsert $args 0 __package_orig $what] } } } proc tclPkgUnknown args {} package unknown tclPkgUnknown # Stub out the unknown command so package can call # into each other during their initialilzation. proc unknown {args} {} # Stub out the auto_import mechanism proc auto_import {args} {} # reserve the ::tcl namespace for support procs # and temporary variables. This might make it awkward # to generate a pkgIndex.tcl file for the ::tcl namespace. namespace eval ::tcl { variable file ;# Current file being processed variable direct ;# -direct flag value variable x ;# Loop variable variable debug ;# For debugging variable type ;# "load" or "source", for -direct variable namespaces ;# Existing namespaces (e.g., ::tcl) variable packages ;# Existing packages (e.g., Tcl) variable origCmds ;# Existing commands variable newCmds ;# Newly created commands variable newPkgs {} ;# Newly created packages } } $c eval [list set ::tcl::file $file] $c eval [list set ::tcl::direct $direct] # Download needed procedures into the slave because we've # just deleted the unknown procedure. This doesn't handle # procedures with default arguments. foreach p {pkg_compareExtension} { $c eval [list proc $p [info args $p] [info body $p]] } if {[catch { $c eval { set ::tcl::debug "loading or sourcing" # we need to track command defined by each package even in # the -direct case, because they are needed internally by # the "partial pkgIndex.tcl" step above. proc ::tcl::GetAllNamespaces {{root ::}} { set list $root foreach ns [namespace children $root] { eval [linsert [::tcl::GetAllNamespaces $ns] 0 \ lappend list] } return $list } # init the list of existing namespaces, packages, commands foreach ::tcl::x [::tcl::GetAllNamespaces] { set ::tcl::namespaces($::tcl::x) 1 } foreach ::tcl::x [package names] { if {[package provide $::tcl::x] ne ""} { set ::tcl::packages($::tcl::x) 1 } } set ::tcl::origCmds [info commands] # Try to load the file if it has the shared library # extension, otherwise source it. It's important not to # try to load files that aren't shared libraries, because # on some systems (like SunOS) the loader will abort the # whole application when it gets an error. if {[pkg_compareExtension $::tcl::file [info sharedlibextension]]} { # The "file join ." command below is necessary. # Without it, if the file name has no \'s and we're # on UNIX, the load command will invoke the # LD_LIBRARY_PATH search mechanism, which could cause # the wrong file to be used. set ::tcl::debug loading load [file join . $::tcl::file] set ::tcl::type load } else { set ::tcl::debug sourcing source $::tcl::file set ::tcl::type source } # As a performance optimization, if we are creating # direct load packages, don't bother figuring out the # set of commands created by the new packages. We # only need that list for setting up the autoloading # used in the non-direct case. if { !$::tcl::direct } { # See what new namespaces appeared, and import commands # from them. Only exported commands go into the index. foreach ::tcl::x [::tcl::GetAllNamespaces] { if {! [info exists ::tcl::namespaces($::tcl::x)]} { namespace import -force ${::tcl::x}::* } # Figure out what commands appeared foreach ::tcl::x [info commands] { set ::tcl::newCmds($::tcl::x) 1 } foreach ::tcl::x $::tcl::origCmds { unset -nocomplain ::tcl::newCmds($::tcl::x) } foreach ::tcl::x [array names ::tcl::newCmds] { # determine which namespace a command comes from set ::tcl::abs [namespace origin $::tcl::x] # special case so that global names have no leading # ::, this is required by the unknown command set ::tcl::abs \ [lindex [auto_qualify $::tcl::abs ::] 0] if {$::tcl::x ne $::tcl::abs} { # Name changed during qualification set ::tcl::newCmds($::tcl::abs) 1 unset ::tcl::newCmds($::tcl::x) } } } } # Look through the packages that appeared, and if there is # a version provided, then record it foreach ::tcl::x [package names] { if {[package provide $::tcl::x] ne "" && ![info exists ::tcl::packages($::tcl::x)]} { lappend ::tcl::newPkgs \ [list $::tcl::x [package provide $::tcl::x]] } } } } msg] == 1} { set what [$c eval set ::tcl::debug] if {$doVerbose} { tclLog "warning: error while $what $file: $msg" } } else { set what [$c eval set ::tcl::debug] if {$doVerbose} { tclLog "successful $what of $file" } set type [$c eval set ::tcl::type] set cmds [lsort [$c eval array names ::tcl::newCmds]] set pkgs [$c eval set ::tcl::newPkgs] if {$doVerbose} { if { !$direct } { tclLog "commands provided were $cmds" } tclLog "packages provided were $pkgs" } if {[llength $pkgs] > 1} { tclLog "warning: \"$file\" provides more than one package ($pkgs)" } foreach pkg $pkgs { # cmds is empty/not used in the direct case lappend files($pkg) [list $file $type $cmds] } if {$doVerbose} { tclLog "processed $file" } } interp delete $c } append index "# Tcl package index file, version 1.1\n" append index "# This file is generated by the \"pkg_mkIndex$more\" command\n" append index "# and sourced either when an application starts up or\n" append index "# by a \"package unknown\" script. It invokes the\n" append index "# \"package ifneeded\" command to set up package-related\n" append index "# information so that packages will be loaded automatically\n" append index "# in response to \"package require\" commands. When this\n" append index "# script is sourced, the variable \$dir÷»ø»ù»ú»û»ü»ý»þ»ÿ»¼¼¼¼ must contain the\n" append index "# full path name of this file's directory.\n" foreach pkg [lsort [array names files]] { set cmd {} foreach {name version} $pkg { break } lappend cmd ::pkg::create -name $name -version $version foreach spec $files($pkg) { foreach {file type procs} $spec { if { $direct } { set procs {} } lappend cmd "-$type" [list $file $procs] } } append index "\n[eval $cmd]" } set f [open pkgIndex.tcl w] puts $f $index close $f cd $oldDir } # tclPkgSetup -- # This is a utility procedure use by pkgIndex.tcl files. It is invoked # as part of a "package ifneeded" script. It calls "package provide" # to indicate that a package is available, then sets entries in the # auto_index array so that the package's files will be auto-loaded when # the commands are used. # # Arguments: # dir - Directory containing all the files for this package. # pkg - Name of the package (no version number). # version - Version number for the package, such as 2.1.3. # files - List of files that constitute the package. Each # element is a sub-list with three elements. The first # is the name of a file relative to $dir, the second is # "load" or "source", indicating whether the file is a # loadable binary or a script to source, and the third # is a list of commands defined by this file. proc tclPkgSetup {dir pkg version files} { global auto_index package provide $pkg $version foreach fileInfo $files { set f [lindex $fileInfo 0] set type [lindex $fileInfo 1] foreach cmd [lindex $fileInfo 2] { if {$type eq "load"} { set auto_index($cmd) [list load [file join $dir $f] $pkg] } else { set auto_index($cmd) [list source [file join $dir $f]] } } } } # tclPkgUnknown -- # This procedure provides the default for the "package unknown" function. # It is invoked when a package that's needed can't be found. It scans # the auto_path directories and their immediate children looking for # pkgIndex.tcl files and sources any such files that are found to setup # the package database. (On the Macintosh we also search for pkgIndex # TEXT resources in all files.) As it searches, it will recognize changes # to the auto_path and scan any new directories. # # Arguments: # name - Name of desired package. Not used. # version - Version of desired package. Not used. # exact - Either "-exact" or omitted. Not used. proc tclPkgUnknown [expr { [info exists tcl_platform(tip,268)] ? "name args" : "name version {exact {}}" }] { global auto_path env if {![info exists auto_path]} { return } # Cache the auto_path, because it may change while we run through # the first set of pkgIndex.tcl files set old_path [set use_path $auto_path] while {[llength $use_path]} { set dir [lindex $use_path end] # Make sure we only scan each directory one time. if {[info exists tclSeenPath($dir)]} { set use_path [lrange $use_path 0 end-1] continue } set tclSeenPath($dir) 1 # we can't use glob in safe interps, so enclose the following # in a catch statement, where we get the pkgIndex files out # of the subdirectories catch { foreach file [glob -directory $dir -join -nocomplain \ * pkgIndex.tcl] { set dir [file dirname $file] if {![info exists procdDirs($dir)] && [file readable $file]} { if {[catch {source $file} msg]} { tclLog "error reading package index file $file: $msg" } else { set procdDirs($dir) 1 } } } } set dir [lindex $use_path end] if {![info exists procdDirs($dir)]} { set file [file join $dir pkgIndex.tcl] # safe interps usually don't have "file readable", # nor stderr channel if {([interp issafe] || [file readable $file])} { if {[catch {source $file} msg] && ![interp issafe]} { tclLog "error reading package index file $file: $msg" } else { set procdDirs($dir) 1 } } } set use_path [lrange $use_path 0 end-1] # Check whether any of the index scripts we [source]d above # set a new value for $::auto_path. If so, then find any # new directories on the $::auto_path, and lappend them to # the $use_path we are working from. This gives index scripts # the (arguably unwise) power to expand the index script search # path while the search is in progress. set index 0 if {[llength $old_path] == [llength $auto_path]} { foreach dir $auto_path old $old_path { if {$dir ne $old} { # This entry in $::auto_path has changed. break } incr index } } # $index now points to the first element of $auto_path that # has changed, or the beginning if $auto_path has changed length # Scan the new elements of $auto_path for directories to add to # $use_path. Don't add directories we've already seen, or ones # already on the $use_path. foreach dir [lrange $auto_path $index end] { if {![info exists tclSeenPath($dir)] && ([lsearch -exact $use_path $dir] == -1) } { lappend use_path $dir } } set old_path $auto_path } } # tcl::MacOSXPkgUnknown -- # This procedure extends the "package unknown" function for MacOSX. # It scans the Resources/Scripts directories of the immediate children # of the auto_path directories for pkgIndex files. # Only installed in interps that are not safe so we don't check # for [interp issafe] as in tclPkgUnknown. # # Arguments: # original - original [package unknown] procedure # name - Name of desired package. Not used. #ifndef TCL_TIP268 # version - Version of desired package. Not used. # exact - Either "-exact" or omitted. Not used. #else # args - List of requirements. Not used. #endif if {[info exists tcl_platform(tip,268)]} { proc tcl::MacOSXPkgUnknown {original name args} { # First do the cross-platform default search uplevel 1 $original [linsert $args 0 $name] # Now do MacOSX specific searching global auto_path if {![info exists auto_path]} { return } # Cache the auto_path, because it may change while we run through # the first set of pkgIndex.tcl files set old_path [set use_path $auto_path] while {[llength $use_path]} { set dir [lindex $use_path end] # get the pkgIndex files out of the subdirectories foreach file [glob -directory $dir -join -nocomplain \ * Resources Scripts pkgIndex.tcl] { set dir [file dirname $file] if {[file readable $file] && ![info exists procdDirs($dir)]} { if {[catch {source $file} msg]} { tclLog "error reading package index file $file: $msg" } else { set procdDirs($dir) 1 } } } set use_path [lrange $use_path 0 end-1] if {$old_path ne $auto_path} { foreach dir $auto_path { lappend use_path $dir } set old_path $auto_path } } } } else { proc tcl::MacOSXPkgUnknown {original name version {exact {}}} { # First do the cross-platform default search uplevel 1 $original [list $name $version $exact] # Now do MacOSX specific searching global auto_path if {![info exists auto_path]} { return } # Cache the auto_path, because it may change while we run through # the first set of pkgIndex.tcl files set old_path [set use_path $auto_path] while {[llength $use_path]} { set dir [lindex $use_path end] # get the pkgIndex files out of the subdirectories foreach file [glob -directory $dir -join -nocomplain \ * Resources Scripts pkgIndex.tcl] { set dir [file dirname $file] if {[file readable $file] && ![info exists procdDirs($dir)]} { if {[catch {source $file} msg]} { tclLog "error reading package index file $file: $msg" } else { set procdDirs($dir) 1 } } } set use_path [lrange $use_path 0 end-1] if {$old_path ne $auto_path} { foreach dir $auto_path { lappend use_path $dir } set old_path $auto_path } } } } # tcl::MacPkgUnknown -- # This procedure extends the "package unknown" function for Mac. # It searches for pkgIndex TEXT resources in all files # Only installed in interps that are not safe so we don't check # for [interp issafe] as in tclPkgUnknown. # # Arguments: # original - original [package unknown] procedure # name - Name of desired package. Not used. # version - Version of desired package. Not used. # exact - Either "-exact" or omitted. Not used. proc tcl::MacPkgUnknown {original name version {exact {}}} { # First do the cross-platform default search uplevel 1 $original [list $name $version $exact] # Now do Mac specific searching global auto_path if {![info exists auto_path]} { return } # Cache the auto_path, because it may change while we run through # the first set of pkgIndex.tcl files set old_path [set use_path $auto_path] while {[llength $use_path]} { # We look for pkgIndex TEXT resources in the resource fork of shared libraries set dir [lindex $use_path end] foreach x [concat [list $dir] [glob -directory $dir -nocomplain *] ] { if {[file isdirectory $x] && ![info exists procdDirs($x)]} { set dir $x foreach x [glob -directory $dir -nocomplain *.shlb] { if {[file isfile $x]} { set res [resource open $x] foreach y [re