📄 safe.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 2003/02/08 22:03:20 hobbs 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 packagepackage require opt 0.4.1;# Create the safe namespacenamespace eval ::safe { # Exported API: namespace export interpCreate interpInit interpConfigure interpDelete \ interpAddToAccessPath interpFindInAccessPath setLogCmd #### # # Setup the arguments parsing # #### # 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 {[string equal "" $access_path]} { 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 <name>" 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
⌨️ 快捷键说明
复制代码
Ctrl + C
搜索代码
Ctrl + F
全屏模式
F11
切换主题
Ctrl + Shift + D
显示快捷键
?
增大字号
Ctrl + =
减小字号
Ctrl + -