|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
package require opt 0.4.1 |
|
|
|
|
|
namespace eval ::safe { |
|
|
|
namespace export interpCreate interpInit interpConfigure interpDelete \ |
|
interpAddToAccessPath interpFindInAccessPath setLogCmd |
|
} |
|
|
|
|
|
|
|
proc ::safe::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 |
|
} |
|
} |
|
|
|
|
|
|
|
proc ::safe::InterpNested {} { |
|
foreach v {Args nested nestedLoadOk} { |
|
upvar $v $v |
|
} |
|
set flag [::tcl::OptProcArgGiven -nestedLoadOk] |
|
|
|
|
|
if {$flag && (!$nestedLoadOk != !$nested) |
|
&& ([::tcl::OptProcArgGiven -nested])} { |
|
return -code error\ |
|
"conflicting values given for -nested and -nestedLoadOk" |
|
} |
|
if {$flag} { |
|
|
|
return $nestedLoadOk |
|
} else { |
|
return $nested |
|
} |
|
} |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
proc ::safe::interpCreate {args} { |
|
set Args [::tcl::OptKeyParse ::safe::interpCreate $args] |
|
InterpCreate $slave $accessPath \ |
|
[InterpStatics] [InterpNested] $deleteHook |
|
} |
|
|
|
proc ::safe::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 ::safe::CheckInterp {slave} { |
|
namespace upvar ::safe S$slave state |
|
if {![info exists state] || ![::interp exists $slave]} { |
|
return -code error \ |
|
"\"$slave\" is not an interpreter managed by ::safe::" |
|
} |
|
} |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
proc ::safe::interpConfigure {args} { |
|
switch [llength $args] { |
|
1 { |
|
|
|
|
|
|
|
|
|
set Args [::tcl::OptKeyParse ::safe::interpIC $args] |
|
CheckInterp $slave |
|
namespace upvar ::safe S$slave state |
|
|
|
return [join [list \ |
|
[list -accessPath $state(access_path)] \ |
|
[list -statics $state(staticsok)] \ |
|
[list -nested $state(nestedok)] \ |
|
[list -deleteHook $state(cleanupHook)]]] |
|
} |
|
2 { |
|
|
|
|
|
lassign $args slave arg |
|
|
|
|
|
|
|
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 |
|
namespace upvar ::safe S$slave state |
|
|
|
set item [::tcl::OptCurDesc $desc] |
|
set name [::tcl::OptName $item] |
|
switch -exact -- $name { |
|
-accessPath { |
|
return [list -accessPath $state(access_path)] |
|
} |
|
-statics { |
|
return [list -statics $state(staticsok)] |
|
} |
|
-nested { |
|
return [list -nested $state(nestedok)] |
|
} |
|
-deleteHook { |
|
return [list -deleteHook $state(cleanupHook)] |
|
} |
|
-noStatics { |
|
|
|
|
|
|
|
|
|
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 { |
|
|
|
|
|
set Args [::tcl::OptKeyParse ::safe::interpIC $args] |
|
CheckInterp $slave |
|
namespace upvar ::safe S$slave state |
|
|
|
|
|
|
|
if {![::tcl::OptProcArgGiven -accessPath]} { |
|
set doreset 1 |
|
set accessPath $state(access_path) |
|
} else { |
|
set doreset 0 |
|
} |
|
if { |
|
![::tcl::OptProcArgGiven -statics] |
|
&& ![::tcl::OptProcArgGiven -noStatics] |
|
} then { |
|
set statics $state(staticsok) |
|
} else { |
|
set statics [InterpStatics] |
|
} |
|
if { |
|
[::tcl::OptProcArgGiven -nested] || |
|
[::tcl::OptProcArgGiven -nestedLoadOk] |
|
} then { |
|
set nested [InterpNested] |
|
} else { |
|
set nested $state(nestedok) |
|
} |
|
if {![::tcl::OptProcArgGiven -deleteHook]} { |
|
set deleteHook $state(cleanupHook) |
|
} |
|
|
|
InterpSetConfig $slave $accessPath $statics $nested $deleteHook |
|
|
|
if {$doreset} { |
|
if {[catch {::interp eval $slave {auto_reset}} msg]} { |
|
Log $slave "auto_reset failed: $msg" |
|
} else { |
|
Log $slave "successful auto_reset" NOTICE |
|
} |
|
} |
|
} |
|
} |
|
} |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
proc ::safe::InterpCreate { |
|
slave |
|
access_path |
|
staticsok |
|
nestedok |
|
deletehook |
|
} { |
|
|
|
if {$slave ne ""} { |
|
::interp create -safe $slave |
|
} else { |
|
|
|
set slave [::interp create -safe] |
|
} |
|
Log $slave "Created" NOTICE |
|
|
|
|
|
InterpInit $slave $access_path $staticsok $nestedok $deletehook |
|
} |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
proc ::safe::InterpSetConfig {slave access_path staticsok nestedok deletehook} { |
|
global auto_path |
|
|
|
|
|
if {$access_path eq ""} { |
|
set access_path $auto_path |
|
|
|
|
|
|
|
set where [lsearch -exact $access_path [info library]] |
|
if {$where == -1} { |
|
|
|
set access_path [linsert $access_path 0 [info library]] |
|
Log $slave "tcl_library was not in auto_path,\ |
|
added it to slave's access_path" NOTICE |
|
} elseif {$where != 0} { |
|
|
|
set access_path [linsert \ |
|
[lreplace $access_path $where $where] \ |
|
0 [info library]] |
|
Log $slave "tcl_libray was not in first in auto_path,\ |
|
moved it to front of slave's access_path" NOTICE |
|
} |
|
|
|
|
|
|
|
|
|
set access_path [AddSubDirs $access_path] |
|
} |
|
|
|
Log $slave "Setting accessPath=($access_path) staticsok=$staticsok\ |
|
nestedok=$nestedok deletehook=($deletehook)" NOTICE |
|
|
|
namespace upvar ::safe S$slave state |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
set norm_access_path {} |
|
set slave_access_path {} |
|
set map_access_path {} |
|
set remap_access_path {} |
|
set slave_tm_path {} |
|
|
|
set i 0 |
|
foreach dir $access_path { |
|
set token [PathToken $i] |
|
lappend slave_access_path $token |
|
lappend map_access_path $token $dir |
|
lappend remap_access_path $dir $token |
|
lappend norm_access_path [file normalize $dir] |
|
incr i |
|
} |
|
|
|
set morepaths [::tcl::tm::list] |
|
while {[llength $morepaths]} { |
|
set addpaths $morepaths |
|
set morepaths {} |
|
|
|
foreach dir $addpaths { |
|
|
|
|
|
if {[dict exists $remap_access_path $dir]} { |
|
continue |
|
} |
|
|
|
set token [PathToken $i] |
|
lappend access_path $dir |
|
lappend slave_access_path $token |
|
lappend map_access_path $token $dir |
|
lappend remap_access_path $dir $token |
|
lappend norm_access_path [file normalize $dir] |
|
lappend slave_tm_path $token |
|
incr i |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
lappend morepaths {*}[glob -nocomplain -directory $dir -type d *] |
|
} |
|
} |
|
|
|
set state(access_path) $access_path |
|
set state(access_path,map) $map_access_path |
|
set state(access_path,remap) $remap_access_path |
|
set state(access_path,norm) $norm_access_path |
|
set state(access_path,slave) $slave_access_path |
|
set state(tm_path_slave) $slave_tm_path |
|
set state(staticsok) $staticsok |
|
set state(nestedok) $nestedok |
|
set state(cleanupHook) $deletehook |
|
|
|
SyncAccessPath $slave |
|
} |
|
|
|
|
|
|
|
|
|
|
|
|
|
proc ::safe::interpFindInAccessPath {slave path} { |
|
namespace upvar ::safe S$slave state |
|
|
|
if {![dict exists $state(access_path,remap) $path]} { |
|
return -code error "$path not found in access path $access_path" |
|
} |
|
|
|
return [dict get $state(access_path,remap) $path] |
|
} |
|
|
|
|
|
|
|
|
|
|
|
proc ::safe::interpAddToAccessPath {slave path} { |
|
|
|
|
|
namespace upvar ::safe S$slave state |
|
|
|
if {[dict exists $state(access_path,remap) $path]} { |
|
return [dict get $state(access_path,remap) $path] |
|
} |
|
|
|
|
|
set token [PathToken [llength $state(access_path)]] |
|
|
|
lappend state(access_path) $path |
|
lappend state(access_path,slave) $token |
|
lappend state(access_path,map) $token $path |
|
lappend state(access_path,remap) $path $token |
|
lappend state(access_path,norm) [file normalize $path] |
|
|
|
SyncAccessPath $slave |
|
return $token |
|
} |
|
|
|
|
|
|
|
|
|
proc ::safe::InterpInit { |
|
slave |
|
access_path |
|
staticsok |
|
nestedok |
|
deletehook |
|
} { |
|
|
|
InterpSetConfig $slave $access_path $staticsok $nestedok $deletehook |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
foreach {command alias} { |
|
source AliasSource |
|
load AliasLoad |
|
encoding AliasEncoding |
|
exit interpDelete |
|
glob AliasGlob |
|
} { |
|
::interp alias $slave $command {} [namespace current]::$alias $slave |
|
} |
|
|
|
|
|
|
|
|
|
::interp expose $slave file |
|
foreach subcommand {dirname extension rootname tail} { |
|
::interp alias $slave ::tcl::file::$subcommand {} \ |
|
::safe::AliasFileSubcommand $slave $subcommand |
|
} |
|
foreach subcommand { |
|
atime attributes copy delete executable exists isdirectory isfile |
|
link lstat mtime mkdir nativename normalize owned readable readlink |
|
rename size stat tempfile type volumes writable |
|
} { |
|
::interp alias $slave ::tcl::file::$subcommand {} \ |
|
::safe::BadSubcommand $slave file $subcommand |
|
} |
|
|
|
|
|
foreach {subcommand alias} { |
|
nameofexecutable AliasExeName |
|
} { |
|
::interp alias $slave ::tcl::info::$subcommand \ |
|
{} [namespace current]::$alias $slave |
|
} |
|
|
|
|
|
|
|
|
|
|
|
|
|
if {[catch {::interp eval $slave { |
|
source [file join $tcl_library init.tcl] |
|
}} msg opt]} { |
|
Log $slave "can't source init.tcl ($msg)" |
|
return -options $opt "can't source init.tcl into slave $slave ($msg)" |
|
} |
|
|
|
if {[catch {::interp eval $slave { |
|
source [file join $tcl_library tm.tcl] |
|
}} msg opt]} { |
|
Log $slave "can't source tm.tcl ($msg)" |
|
return -options $opt "can't source tm.tcl into slave $slave ($msg)" |
|
} |
|
|
|
|
|
|
|
namespace upvar ::safe S$slave state |
|
if {[llength $state(tm_path_slave)] > 0} { |
|
::interp eval $slave [list \ |
|
::tcl::tm::add {*}[lreverse $state(tm_path_slave)]] |
|
} |
|
return $slave |
|
} |
|
|
|
|
|
|
|
|
|
proc ::safe::AddSubDirs {pathList} { |
|
set res {} |
|
foreach dir $pathList { |
|
if {[file isdirectory $dir]} { |
|
|
|
|
|
if {$dir ni $res} { |
|
lappend res $dir |
|
} |
|
foreach sub [glob -directory $dir -nocomplain *] { |
|
if {[file isdirectory $sub] && ($sub ni $res)} { |
|
|
|
lappend res $sub |
|
} |
|
} |
|
} |
|
} |
|
return $res |
|
} |
|
|
|
|
|
|
|
|
|
proc ::safe::interpDelete {slave} { |
|
Log $slave "About to delete" NOTICE |
|
|
|
namespace upvar ::safe S$slave state |
|
|
|
|
|
|
|
|
|
|
|
if {[info exists state(cleanupHook)]} { |
|
set hook $state(cleanupHook) |
|
if {[llength $hook]} { |
|
|
|
|
|
unset state(cleanupHook) |
|
try { |
|
{*}$hook $slave |
|
} on error err { |
|
Log $slave "Delete hook error ($err)" |
|
} |
|
} |
|
} |
|
|
|
|
|
|
|
|
|
if {[info exists state]} { |
|
unset state |
|
} |
|
|
|
|
|
|
|
if {[::interp exists $slave]} { |
|
::interp delete $slave |
|
Log $slave "Deleted" NOTICE |
|
} |
|
|
|
return |
|
} |
|
|
|
|
|
|
|
proc ::safe::setLogCmd {args} { |
|
variable Log |
|
set la [llength $args] |
|
if {$la == 0} { |
|
return $Log |
|
} elseif {$la == 1} { |
|
set Log [lindex $args 0] |
|
} else { |
|
set Log $args |
|
} |
|
|
|
if {$Log eq ""} { |
|
|
|
|
|
proc ::safe::Log {args} {} |
|
} else { |
|
|
|
|
|
proc ::safe::Log {slave msg {type ERROR}} { |
|
variable Log |
|
{*}$Log "$type for slave $slave : $msg" |
|
return |
|
} |
|
} |
|
} |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
proc ::safe::SyncAccessPath {slave} { |
|
namespace upvar ::safe S$slave state |
|
|
|
set slave_access_path $state(access_path,slave) |
|
::interp eval $slave [list set auto_path $slave_access_path] |
|
|
|
Log $slave "auto_path in $slave has been set to $slave_access_path"\ |
|
NOTICE |
|
|
|
|
|
|
|
|
|
|
|
::interp eval $slave [list \ |
|
set tcl_library [lindex $slave_access_path 0]] |
|
} |
|
|
|
|
|
proc ::safe::PathToken {n} { |
|
|
|
|
|
return "\$p(:$n:)" |
|
} |
|
|
|
|
|
|
|
|
|
proc ::safe::TranslatePath {slave path} { |
|
namespace upvar ::safe S$slave state |
|
|
|
|
|
|
|
if {[string match "*::*" $path] || [string match "*..*" $path]} { |
|
return -code error "invalid characters in path $path" |
|
} |
|
|
|
|
|
|
|
return [string map $state(access_path,map) $path] |
|
} |
|
|
|
|
|
|
|
proc ::safe::CheckFileName {slave file} { |
|
|
|
|
|
|
|
|
|
|
|
if {![file exists $file]} { |
|
|
|
return -code error "no such file or directory" |
|
} |
|
|
|
if {![file readable $file]} { |
|
|
|
return -code error "not readable" |
|
} |
|
} |
|
|
|
|
|
|
|
|
|
|
|
proc ::safe::AliasFileSubcommand {slave subcommand name} { |
|
if {[string match ~* $name]} { |
|
set name ./$name |
|
} |
|
tailcall ::interp invokehidden $slave tcl:file:$subcommand $name |
|
} |
|
|
|
|
|
|
|
proc ::safe::AliasGlob {slave args} { |
|
Log $slave "GLOB ! $args" NOTICE |
|
set cmd {} |
|
set at 0 |
|
array set got { |
|
-directory 0 |
|
-nocomplain 0 |
|
-join 0 |
|
-tails 0 |
|
-- 0 |
|
} |
|
|
|
if {$::tcl_platform(platform) eq "windows"} { |
|
set dirPartRE {^(.*)[\\/]([^\\/]*)$} |
|
} else { |
|
set dirPartRE {^(.*)/([^/]*)$} |
|
} |
|
|
|
set dir {} |
|
set virtualdir {} |
|
|
|
while {$at < [llength $args]} { |
|
switch -glob -- [set opt [lindex $args $at]] { |
|
-nocomplain - -- - -join - -tails { |
|
lappend cmd $opt |
|
set got($opt) 1 |
|
incr at |
|
} |
|
-types - -type { |
|
lappend cmd -types [lindex $args [incr at]] |
|
incr at |
|
} |
|
-directory { |
|
if {$got($opt)} { |
|
return -code error \ |
|
{"-directory" cannot be used with "-path"} |
|
} |
|
set got($opt) 1 |
|
set virtualdir [lindex $args [incr at]] |
|
incr at |
|
} |
|
pkgIndex.tcl { |
|
|
|
|
|
|
|
|
|
return -code error "unknown command glob" |
|
} |
|
-* { |
|
Log $slave "Safe base rejecting glob option '$opt'" |
|
return -code error "Safe base rejecting glob option '$opt'" |
|
} |
|
default { |
|
break |
|
} |
|
} |
|
if {$got(--)} break |
|
} |
|
|
|
|
|
|
|
|
|
if {$got(-directory)} { |
|
try { |
|
set dir [TranslatePath $slave $virtualdir] |
|
DirInAccessPath $slave $dir |
|
} on error msg { |
|
Log $slave $msg |
|
if {$got(-nocomplain)} return |
|
return -code error "permission denied" |
|
} |
|
lappend cmd -directory $dir |
|
} |
|
|
|
|
|
if {$got(-join)} { |
|
set args [lreplace $args $at end [join [lrange $args $at end] "/"]] |
|
} |
|
|
|
|
|
set firstPattern [llength $cmd] |
|
foreach opt [lrange $args $at end] { |
|
if {![regexp $dirPartRE $opt -> thedir thefile]} { |
|
set thedir . |
|
} elseif {[string match ~* $thedir]} { |
|
set thedir ./$thedir |
|
} |
|
if {$thedir eq "*" && |
|
($thefile eq "pkgIndex.tcl" || $thefile eq "*.tm")} { |
|
set mapped 0 |
|
foreach d [glob -directory [TranslatePath $slave $virtualdir] \ |
|
-types d -tails *] { |
|
catch { |
|
DirInAccessPath $slave \ |
|
[TranslatePath $slave [file join $virtualdir $d]] |
|
lappend cmd [file join $d $thefile] |
|
set mapped 1 |
|
} |
|
} |
|
if {$mapped} continue |
|
} |
|
try { |
|
DirInAccessPath $slave [TranslatePath $slave \ |
|
[file join $virtualdir $thedir]] |
|
} on error msg { |
|
Log $slave $msg |
|
if {$got(-nocomplain)} continue |
|
return -code error "permission denied" |
|
} |
|
lappend cmd $opt |
|
} |
|
|
|
Log $slave "GLOB = $cmd" NOTICE |
|
|
|
if {$got(-nocomplain) && [llength $cmd] eq $firstPattern} { |
|
return |
|
} |
|
try { |
|
set entries [::interp invokehidden $slave glob {*}$cmd] |
|
} on error msg { |
|
Log $slave $msg |
|
return -code error "script error" |
|
} |
|
|
|
Log $slave "GLOB < $entries" NOTICE |
|
|
|
|
|
set res {} |
|
set l [string length $dir] |
|
foreach p $entries { |
|
if {[string equal -length $l $dir $p]} { |
|
set p [string replace $p 0 [expr {$l-1}] $virtualdir] |
|
} |
|
lappend res $p |
|
} |
|
|
|
Log $slave "GLOB > $res" NOTICE |
|
return $res |
|
} |
|
|
|
|
|
|
|
proc ::safe::AliasSource {slave args} { |
|
set argc [llength $args] |
|
|
|
|
|
if {[lindex $args 0] eq "-encoding"} { |
|
incr argc -2 |
|
set encoding [lindex $args 1] |
|
set at 2 |
|
if {$encoding eq "identity"} { |
|
Log $slave "attempt to use the identity encoding" |
|
return -code error "permission denied" |
|
} |
|
} else { |
|
set at 0 |
|
set encoding {} |
|
} |
|
if {$argc != 1} { |
|
set msg "wrong # args: should be \"source ?-encoding E? fileName\"" |
|
Log $slave "$msg ($args)" |
|
return -code error $msg |
|
} |
|
set file [lindex $args $at] |
|
|
|
|
|
if {[catch { |
|
set realfile [TranslatePath $slave $file] |
|
} msg]} { |
|
Log $slave $msg |
|
return -code error "permission denied" |
|
} |
|
|
|
|
|
if {[catch { |
|
FileInAccessPath $slave $realfile |
|
} msg]} { |
|
Log $slave $msg |
|
return -code error "permission denied" |
|
} |
|
|
|
|
|
if {[catch { |
|
CheckFileName $slave $realfile |
|
} msg]} { |
|
Log $slave "$realfile:$msg" |
|
return -code error $msg |
|
} |
|
|
|
|
|
|
|
|
|
set old [::interp eval $slave {info script}] |
|
set replacementMsg "script error" |
|
set code [catch { |
|
set f [open $realfile] |
|
fconfigure $f -eofchar \032 |
|
if {$encoding ne ""} { |
|
fconfigure $f -encoding $encoding |
|
} |
|
set contents [read $f] |
|
close $f |
|
::interp eval $slave [list info script $file] |
|
} msg opt] |
|
if {$code == 0} { |
|
set code [catch {::interp eval $slave $contents} msg opt] |
|
set replacementMsg $msg |
|
} |
|
catch {interp eval $slave [list info script $old]} |
|
|
|
|
|
if {$code == 1} { |
|
Log $slave $msg |
|
return -code error $replacementMsg |
|
} |
|
return -code $code -options $opt $msg |
|
} |
|
|
|
|
|
|
|
proc ::safe::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 |
|
} |
|
|
|
|
|
set package [lindex $args 0] |
|
|
|
namespace upvar ::safe S$slave state |
|
|
|
|
|
|
|
set target [lindex $args 1] |
|
if {$target ne ""} { |
|
|
|
|
|
if {!$state(nestedok)} { |
|
Log $slave "loading to a sub interp (nestedok)\ |
|
disabled (trying to load $package to $target)" |
|
return -code error "permission denied (nested load)" |
|
} |
|
} |
|
|
|
|
|
if {$file eq ""} { |
|
|
|
if {$package eq ""} { |
|
set msg "load error: empty filename and no package name" |
|
Log $slave $msg |
|
return -code error $msg |
|
} |
|
if {!$state(staticsok)} { |
|
Log $slave "static packages loading disabled\ |
|
(trying to load $package to $target)" |
|
return -code error "permission denied (static package)" |
|
} |
|
} else { |
|
|
|
|
|
|
|
try { |
|
set file [TranslatePath $slave $file] |
|
} on error msg { |
|
Log $slave $msg |
|
return -code error "permission denied" |
|
} |
|
|
|
|
|
try { |
|
FileInAccessPath $slave $file |
|
} on error msg { |
|
Log $slave $msg |
|
return -code error "permission denied (path)" |
|
} |
|
} |
|
|
|
try { |
|
return [::interp invokehidden $slave load $file $package $target] |
|
} on error msg { |
|
Log $slave $msg |
|
return -code error $msg |
|
} |
|
} |
|
|
|
|
|
|
|
|
|
|
|
|
|
proc ::safe::FileInAccessPath {slave file} { |
|
namespace upvar ::safe S$slave state |
|
set access_path $state(access_path) |
|
|
|
if {[file isdirectory $file]} { |
|
return -code error "\"$file\": is a directory" |
|
} |
|
set parent [file dirname $file] |
|
|
|
|
|
|
|
set norm_parent [file normalize $parent] |
|
|
|
namespace upvar ::safe S$slave state |
|
if {$norm_parent ni $state(access_path,norm)} { |
|
return -code error "\"$file\": not in access_path" |
|
} |
|
} |
|
|
|
proc ::safe::DirInAccessPath {slave dir} { |
|
namespace upvar ::safe S$slave state |
|
set access_path $state(access_path) |
|
|
|
if {[file isfile $dir]} { |
|
return -code error "\"$dir\": is a file" |
|
} |
|
|
|
|
|
|
|
set norm_dir [file normalize $dir] |
|
|
|
namespace upvar ::safe S$slave state |
|
if {$norm_dir ni $state(access_path,norm)} { |
|
return -code error "\"$dir\": not in access_path" |
|
} |
|
} |
|
|
|
|
|
|
|
|
|
proc ::safe::BadSubcommand {slave command subcommand args} { |
|
set msg "not allowed to invoke subcommand $subcommand of $command" |
|
Log $slave $msg |
|
return -code error -errorcode {TCL SAFE SUBCOMMAND} $msg |
|
} |
|
|
|
|
|
|
|
proc ::safe::AliasEncoding {slave option args} { |
|
|
|
set subcommands {convertfrom convertto names system} |
|
try { |
|
set option [tcl::prefix match -error [list -level 1 -errorcode \ |
|
[list TCL LOOKUP INDEX option $option]] $subcommands $option] |
|
|
|
if {$option eq "system" && [llength $args]} { |
|
return -code error -errorcode {TCL WRONGARGS} \ |
|
"wrong # args: should be \"encoding system\"" |
|
} |
|
} on error {msg options} { |
|
Log $slave $msg |
|
return -options $options $msg |
|
} |
|
tailcall ::interp invokehidden $slave encoding $option {*}$args |
|
} |
|
|
|
|
|
|
|
proc ::safe::AliasExeName {slave} { |
|
return "" |
|
} |
|
|
|
proc ::safe::Setup {} { |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
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"} |
|
}] |
|
|
|
|
|
::tcl::OptKeyRegister { |
|
{?slave? -name {} "name of the slave (optional)"} |
|
} ::safe::interpCreate |
|
|
|
|
|
|
|
lappend ::tcl::OptDesc(::safe::interpCreate) $::tcl::OptDesc($temp) |
|
|
|
|
|
::tcl::OptKeyRegister { |
|
{slave -name {} "name of the slave"} |
|
} ::safe::interpIC |
|
|
|
|
|
|
|
lappend ::tcl::OptDesc(::safe::interpIC) $::tcl::OptDesc($temp) |
|
|
|
|
|
::tcl::OptKeyDelete $temp |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
setLogCmd {} |
|
|
|
|
|
|
|
|
|
return |
|
} |
|
|
|
namespace eval ::safe { |
|
|
|
|
|
|
|
variable Log {} |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
} |
|
|
|
::safe::Setup |
|
|