diff CSP2/CSP2_env/env-d9b9114564458d9d-741b3de822f2aaca6c6caa4325c4afce/lib/tcl8.6/safe.tcl @ 68:5028fdace37b

planemo upload commit 2e9511a184a1ca667c7be0c6321a36dc4e3d116d
author jpayne
date Tue, 18 Mar 2025 16:23:26 -0400
parents
children
line wrap: on
line diff
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/CSP2/CSP2_env/env-d9b9114564458d9d-741b3de822f2aaca6c6caa4325c4afce/lib/tcl8.6/safe.tcl	Tue Mar 18 16:23:26 2025 -0400
@@ -0,0 +1,1289 @@
+# safe.tcl --
+#
+# This file provide a safe loading/sourcing mechanism for safe interpreters.
+# It implements a virtual path mechanism to hide the real pathnames from the
+# child. It runs in a parent interpreter and sets up data structure and
+# aliases that will be invoked when used from a child 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.
+
+#
+# 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.8
+
+# Create the safe namespace
+namespace eval ::safe {
+    # Exported API:
+    namespace export interpCreate interpInit interpConfigure interpDelete \
+	interpAddToAccessPath interpFindInAccessPath setLogCmd
+}
+
+# Helper function to resolve the dual way of specifying staticsok (either
+# by -noStatics or -statics 0)
+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
+    }
+}
+
+# Helper function to resolve the dual way of specifying nested loading
+# (either by -nestedLoadOk or -nested 1)
+proc ::safe::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 ::safe::interpCreate {args} {
+    set Args [::tcl::OptKeyParse ::safe::interpCreate $args]
+    RejectExcessColons $slave
+    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"
+    }
+    RejectExcessColons $slave
+    InterpInit $slave $accessPath \
+	[InterpStatics] [InterpNested] $deleteHook
+}
+
+# Check that the given child is "one of us"
+proc ::safe::CheckInterp {child} {
+    namespace upvar ::safe [VarName $child] state
+    if {![info exists state] || ![::interp exists $child]} {
+	return -code error \
+	    "\"$child\" 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 ::safe::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 "child" is our given argument because it also
+	    # checks for the "-help" option.
+	    set Args [::tcl::OptKeyParse ::safe::interpIC $args]
+	    CheckInterp $slave
+	    namespace upvar ::safe [VarName $slave] state
+
+	    return [join [list \
+		[list -accessPath $state(access_path)] \
+		[list -statics    $state(staticsok)]   \
+		[list -nested     $state(nestedok)]    \
+		[list -deleteHook $state(cleanupHook)]]]
+	}
+	2 {
+	    # If we have exactly 2 arguments the semantic is a "configure
+	    # get"
+	    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
+	    namespace upvar ::safe [VarName $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 {
+		    # 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
+	    namespace upvar ::safe [VarName $slave] state
+
+	    # Get the current (and not the default) values of whatever has
+	    # not been given:
+	    if {![::tcl::OptProcArgGiven -accessPath]} {
+		set doreset 0
+		set accessPath $state(access_path)
+	    } else {
+		set doreset 1
+	    }
+	    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)
+	    }
+	    # we can now reconfigure :
+	    InterpSetConfig $slave $accessPath $statics $nested $deleteHook
+	    # auto_reset the child (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
+		}
+
+		# Sync the paths used to search for Tcl modules.
+		::interp eval $slave {tcl::tm::path remove {*}[tcl::tm::list]}
+		if {[llength $state(tm_path_slave)] > 0} {
+		    ::interp eval $slave [list \
+			    ::tcl::tm::add {*}[lreverse $state(tm_path_slave)]]
+		}
+
+		# Remove stale "package ifneeded" data for non-loaded packages.
+		# - Not for loaded packages, because "package forget" erases
+		#   data from "package provide" as well as "package ifneeded".
+		# - This is OK because the script cannot reload any version of
+		#   the package unless it first does "package forget".
+		foreach pkg [::interp eval $slave {package names}] {
+		    if {[::interp eval $slave [list package provide $pkg]] eq ""} {
+			::interp eval $slave [list package forget $pkg]
+		    }
+		}
+	    }
+	    return
+	}
+    }
+}
+
+####
+#
+#  Functions that actually implements the exported APIs
+#
+####
+
+#
+# safe::InterpCreate : doing the real job
+#
+# This procedure creates a safe interpreter and initializes it with the safe
+# base aliases.
+# NB: child name must be simple alphanumeric string, no spaces, no (), no
+# {},...  {because the state array is stored as part of the name}
+#
+# Returns the child name.
+#
+# Optional Arguments :
+# + child name : if empty, generated name will be used
+# + access_path: path list controlling where load/source can occur,
+#                if empty: the parent 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 {
+			   child
+			   access_path
+			   staticsok
+			   nestedok
+			   deletehook
+		       } {
+    # Create the child.
+    # If evaluated in ::safe, the interpreter command for foo is ::foo;
+    # but for foo::bar is safe::foo::bar.  So evaluate in :: instead.
+    if {$child ne ""} {
+	namespace eval :: [list ::interp create -safe $child]
+    } else {
+	# empty argument: generate child name
+	set child [::interp create -safe]
+    }
+    Log $child "Created" NOTICE
+
+    # Initialize it. (returns child name)
+    InterpInit $child $access_path $staticsok $nestedok $deletehook
+}
+
+#
+# InterpSetConfig (was setAccessPath) :
+#    Sets up child virtual auto_path and corresponding structure within
+#    the parent. Also sets the tcl_library in the child to be the first
+#    directory in the path.
+#    NB: If you change the path after the child has been initialized you
+#    probably need to call "auto_reset" in the child in order that it gets
+#    the right auto_index() array values.
+
+proc ::safe::InterpSetConfig {child access_path staticsok nestedok deletehook} {
+    global auto_path
+
+    # determine and store the access path if empty
+    if {$access_path eq ""} {
+	set access_path $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 < 0} {
+	    # not found, add it.
+	    set access_path [linsert $access_path 0 [info library]]
+	    Log $child "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 [linsert \
+				 [lreplace $access_path $where $where] \
+				 0 [info library]]
+	    Log $child "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 child using glob and thus fail, so we add them here
+	# so by default it works the same).
+	set access_path [AddSubDirs $access_path]
+    }
+
+    Log $child "Setting accessPath=($access_path) staticsok=$staticsok\
+		nestedok=$nestedok deletehook=($deletehook)" NOTICE
+
+    namespace upvar ::safe [VarName $child] state
+
+    # clear old autopath if it existed
+    # build new one
+    # Extend the access list with the paths used to look for Tcl Modules.
+    # We save the virtual form separately as well, as syncing it with the
+    # child has to be deferred until the necessary commands are present for
+    # setup.
+
+    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]
+    set firstpass 1
+    while {[llength $morepaths]} {
+	set addpaths $morepaths
+	set morepaths {}
+
+	foreach dir $addpaths {
+	    # Prevent the addition of dirs on the tm list to the
+	    # result if they are already known.
+	    if {[dict exists $remap_access_path $dir]} {
+	        if {$firstpass} {
+		    # $dir is in [::tcl::tm::list] and belongs in the slave_tm_path.
+		    # Later passes handle subdirectories, which belong in the
+		    # access path but not in the module path.
+		    lappend slave_tm_path  [dict get $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]
+	    if {$firstpass} {
+		# $dir is in [::tcl::tm::list] and belongs in the slave_tm_path.
+		# Later passes handle subdirectories, which belong in the
+		# access path but not in the module path.
+		lappend slave_tm_path  $token
+	    }
+	    incr i
+
+	    # [Bug 2854929]
+	    # Recursively find deeper paths which may contain
+	    # modules. Required to handle modules with names like
+	    # 'platform::shell', which translate into
+	    # 'platform/shell-X.tm', i.e arbitrarily deep
+	    # subdirectories.
+	    lappend morepaths {*}[glob -nocomplain -directory $dir -type d *]
+	}
+	set firstpass 0
+    }
+
+    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 $child
+    return
+}
+
+#
+#
+# FindInAccessPath:
+#    Search for a real directory and returns its virtual Id (including the
+#    "$")
+proc ::safe::interpFindInAccessPath {child path} {
+    CheckInterp $child
+    namespace upvar ::safe [VarName $child] state
+
+    if {![dict exists $state(access_path,remap) $path]} {
+	return -code error "$path not found in access path"
+    }
+
+    return [dict get $state(access_path,remap) $path]
+}
+
+#
+# addToAccessPath:
+#    add (if needed) a real directory to access path and return its
+#    virtual token (including the "$").
+proc ::safe::interpAddToAccessPath {child path} {
+    # first check if the directory is already in there
+    # (inlined interpFindInAccessPath).
+    CheckInterp $child
+    namespace upvar ::safe [VarName $child] state
+
+    if {[dict exists $state(access_path,remap) $path]} {
+	return [dict get $state(access_path,remap) $path]
+    }
+
+    # new one, add it:
+    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 $child
+    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 {
+			 child
+			 access_path
+			 staticsok
+			 nestedok
+			 deletehook
+		     } {
+    # Configure will generate an access_path when access_path is empty.
+    InterpSetConfig $child $access_path $staticsok $nestedok $deletehook
+
+    # NB we need to add [namespace current], aliases are always absolute
+    # paths.
+
+    # These aliases let the child load files to define new commands
+    # This alias lets the child use the encoding names, convertfrom,
+    # convertto, and system, but not "encoding system <name>" to set the
+    # system encoding.
+    # Handling Tcl Modules, we need a restricted form of Glob.
+    # This alias interposes on the 'exit' command and cleanly terminates
+    # the child.
+
+    foreach {command alias} {
+	source   AliasSource
+	load     AliasLoad
+	encoding AliasEncoding
+	exit     interpDelete
+	glob     AliasGlob
+    } {
+	::interp alias $child $command {} [namespace current]::$alias $child
+    }
+
+    # This alias lets the child have access to a subset of the 'file'
+    # command functionality.
+
+    ::interp expose $child file
+    foreach subcommand {dirname extension rootname tail} {
+	::interp alias $child ::tcl::file::$subcommand {} \
+	    ::safe::AliasFileSubcommand $child $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 $child ::tcl::file::$subcommand {} \
+	    ::safe::BadSubcommand $child file $subcommand
+    }
+
+    # Subcommands of info
+    foreach {subcommand alias} {
+	nameofexecutable   AliasExeName
+    } {
+	::interp alias $child ::tcl::info::$subcommand \
+	    {} [namespace current]::$alias $child
+    }
+
+    # The allowed child variables already have been set by Tcl_MakeSafe(3)
+
+    # Source init.tcl and tm.tcl into the child, to get auto_load and
+    # other procedures defined:
+
+    if {[catch {::interp eval $child {
+	source [file join $tcl_library init.tcl]
+    }} msg opt]} {
+	Log $child "can't source init.tcl ($msg)"
+	return -options $opt "can't source init.tcl into slave $child ($msg)"
+    }
+
+    if {[catch {::interp eval $child {
+	source [file join $tcl_library tm.tcl]
+    }} msg opt]} {
+	Log $child "can't source tm.tcl ($msg)"
+	return -options $opt "can't source tm.tcl into slave $child ($msg)"
+    }
+
+    # Sync the paths used to search for Tcl modules. This can be done only
+    # now, after tm.tcl was loaded.
+    namespace upvar ::safe [VarName $child] state
+    if {[llength $state(tm_path_slave)] > 0} {
+	::interp eval $child [list \
+		::tcl::tm::add {*}[lreverse $state(tm_path_slave)]]
+    }
+    return $child
+}
+
+# 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 ::safe::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 {$dir ni $res} {
+		lappend res $dir
+	    }
+	    foreach sub [glob -directory $dir -nocomplain *] {
+		if {[file isdirectory $sub] && ($sub ni $res)} {
+		    # new sub dir, add it !
+		    lappend res $sub
+		}
+	    }
+	}
+    }
+    return $res
+}
+
+# This procedure deletes a safe interpreter managed by Safe Tcl and cleans up
+# associated state.
+# - The command will also delete non-Safe-Base interpreters.
+# - This is regrettable, but to avoid breaking existing code this should be
+#   amended at the next major revision by uncommenting "CheckInterp".
+
+proc ::safe::interpDelete {child} {
+    Log $child "About to delete" NOTICE
+
+    # CheckInterp $child
+    namespace upvar ::safe [VarName $child] state
+
+    # When an interpreter is deleted with [interp delete], any sub-interpreters
+    # are deleted automatically, but this leaves behind their data in the Safe
+    # Base. To clean up properly, we call safe::interpDelete recursively on each
+    # Safe Base sub-interpreter, so each one is deleted cleanly and not by
+    # the automatic mechanism built into [interp delete].
+    foreach sub [interp children $child] {
+        if {[info exists ::safe::[VarName [list $child $sub]]]} {
+            ::safe::interpDelete [list $child $sub]
+        }
+    }
+
+    # If the child 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
+
+    if {[info exists state(cleanupHook)]} {
+	set hook $state(cleanupHook)
+	if {[llength $hook]} {
+	    # remove the hook now, otherwise if the hook calls us somehow,
+	    # we'll loop
+	    unset state(cleanupHook)
+	    try {
+		{*}$hook $child
+	    } on error err {
+		Log $child "Delete hook error ($err)"
+	    }
+	}
+    }
+
+    # Discard the global array of state associated with the child, and
+    # delete the interpreter.
+
+    if {[info exists state]} {
+	unset state
+    }
+
+    # if we have been called twice, the interp might have been deleted
+    # already
+    if {[::interp exists $child]} {
+	::interp delete $child
+	Log $child "Deleted" NOTICE
+    }
+
+    return
+}
+
+# Set (or get) the logging mecanism
+
+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 ""} {
+	# Disable logging completely. Calls to it will be compiled out
+	# of all users.
+	proc ::safe::Log {args} {}
+    } else {
+	# Activate logging, define proper command.
+
+	proc ::safe::Log {child msg {type ERROR}} {
+	    variable Log
+	    {*}$Log "$type for slave $child : $msg"
+	    return
+	}
+    }
+}
+
+# ------------------- END OF PUBLIC METHODS ------------
+
+#
+# Sets the child auto_path to the parent recorded value.  Also sets
+# tcl_library to the first token of the virtual path.
+#
+proc ::safe::SyncAccessPath {child} {
+    namespace upvar ::safe [VarName $child] state
+
+    set slave_access_path $state(access_path,slave)
+    ::interp eval $child [list set auto_path $slave_access_path]
+
+    Log $child "auto_path in $child has been set to $slave_access_path"\
+	NOTICE
+
+    # This code assumes that info library is the first element in the
+    # list of auto_path's. See -> InterpSetConfig for the code which
+    # ensures this condition.
+
+    ::interp eval $child [list \
+	      set tcl_library [lindex $slave_access_path 0]]
+}
+
+# Returns the virtual token for directory number N.
+proc ::safe::PathToken {n} {
+    # 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:)" ;# Form tested by case 7.2
+}
+
+#
+# translate virtual path into real path
+#
+proc ::safe::TranslatePath {child path} {
+    namespace upvar ::safe [VarName $child] state
+
+    # somehow strip the namespaces 'functionality' out (the danger is that
+    # we would strip valid macintosh "../" queries... :
+    if {[string match "*::*" $path] || [string match "*..*" $path]} {
+	return -code error "invalid characters in path $path"
+    }
+
+    # Use a cached map instead of computed local vars and subst.
+
+    return [string map $state(access_path,map) $path]
+}
+
+# file name control (limit access to files/resources that should be a
+# valid tcl source file)
+proc ::safe::CheckFileName {child 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
+	return -code error "no such file or directory"
+    }
+
+    if {![file readable $file]} {
+	# don't tell the file path
+	return -code error "not readable"
+    }
+}
+
+# AliasFileSubcommand handles selected subcommands of [file] in safe
+# interpreters that are *almost* safe. In particular, it just acts to
+# prevent discovery of what home directories exist.
+
+proc ::safe::AliasFileSubcommand {child subcommand name} {
+    if {[string match ~* $name]} {
+	set name ./$name
+    }
+    tailcall ::interp invokehidden $child tcl:file:$subcommand $name
+}
+
+# AliasGlob is the target of the "glob" alias in safe interpreters.
+
+proc ::safe::AliasGlob {child args} {
+    Log $child "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 - -- - -tails {
+		lappend cmd $opt
+		set got($opt) 1
+		incr at
+	    }
+	    -join {
+		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
+	    }
+	    -* {
+		Log $child "Safe base rejecting glob option '$opt'"
+		return -code error "Safe base rejecting glob option '$opt'"
+	    }
+	    default {
+		break
+	    }
+	}
+	if {$got(--)} break
+    }
+
+    # Get the real path from the virtual one and check that the path is in the
+    # access path of that child. Done after basic argument processing so that
+    # we know if -nocomplain is set.
+    if {$got(-directory)} {
+	try {
+	    set dir [TranslatePath $child $virtualdir]
+	    DirInAccessPath $child $dir
+	} on error msg {
+	    Log $child $msg
+	    if {$got(-nocomplain)} return
+	    return -code error "permission denied"
+	}
+	if {$got(--)} {
+	    set cmd [linsert $cmd end-1 -directory $dir]
+	} else {
+	    lappend cmd -directory $dir
+	}
+    } else {
+	# The code after this "if ... else" block would conspire to return with
+	# no results in this case, if it were allowed to proceed.  Instead,
+	# return now and reduce the number of cases to be considered later.
+	Log $child {option -directory must be supplied}
+	if {$got(-nocomplain)} return
+	return -code error "permission denied"
+    }
+
+    # Apply the -join semantics ourselves.
+    if {$got(-join)} {
+	set args [lreplace $args $at end [join [lrange $args $at end] "/"]]
+    }
+
+    # Process the pattern arguments.  If we've done a join there is only one
+    # pattern argument.
+
+    set firstPattern [llength $cmd]
+    foreach opt [lrange $args $at end] {
+	if {![regexp $dirPartRE $opt -> thedir thefile]} {
+	    set thedir .
+	    # The *.tm search comes here.
+	}
+	# "Special" treatment for (joined) argument {*/pkgIndex.tcl}.
+	# Do the expansion of "*" here, and filter out any directories that are
+	# not in the access path.  The outcome is to lappend to cmd a path of
+	# the form $virtualdir/subdir/pkgIndex.tcl for each subdirectory subdir,
+	# after removing any subdir that are not in the access path.
+	if {($thedir eq "*") && ($thefile eq "pkgIndex.tcl")} {
+	    set mapped 0
+	    foreach d [glob -directory [TranslatePath $child $virtualdir] \
+			   -types d -tails *] {
+		catch {
+		    DirInAccessPath $child \
+			[TranslatePath $child [file join $virtualdir $d]]
+		    lappend cmd [file join $d $thefile]
+		    set mapped 1
+		}
+	    }
+	    if {$mapped} continue
+	    # Don't [continue] if */pkgIndex.tcl has no matches in the access
+	    # path.  The pattern will now receive the same treatment as a
+	    # "non-special" pattern (and will fail because it includes a "*" in
+	    # the directory name).
+	}
+	# Any directory pattern that is not an exact (i.e. non-glob) match to a
+	# directory in the access path will be rejected here.
+	# - Rejections include any directory pattern that has glob matching
+	#   patterns "*", "?", backslashes, braces or square brackets, (UNLESS
+	#   it corresponds to a genuine directory name AND that directory is in
+	#   the access path).
+	# - The only "special matching characters" that remain in patterns for
+	#   processing by glob are in the filename tail.
+	# - [file join $anything ~${foo}] is ~${foo}, which is not an exact
+	#   match to any directory in the access path.  Hence directory patterns
+	#   that begin with "~" are rejected here.  Tests safe-16.[5-8] check
+	#   that "file join" remains as required and does not expand ~${foo}.
+	# - Bug [3529949] relates to unwanted expansion of ~${foo} and this is
+	#   how the present code avoids the bug.  All tests safe-16.* relate.
+	try {
+	    DirInAccessPath $child [TranslatePath $child \
+		    [file join $virtualdir $thedir]]
+	} on error msg {
+	    Log $child $msg
+	    if {$got(-nocomplain)} continue
+	    return -code error "permission denied"
+	}
+	lappend cmd $opt
+    }
+
+    Log $child "GLOB = $cmd" NOTICE
+
+    if {$got(-nocomplain) && [llength $cmd] eq $firstPattern} {
+	return
+    }
+    try {
+	# >>>>>>>>>> HERE'S THE CALL TO SAFE INTERP GLOB <<<<<<<<<<
+	# - Pattern arguments added to cmd have NOT been translated from tokens.
+	#   Only the virtualdir is translated (to dir).
+	# - In the pkgIndex.tcl case, there is no "*" in the pattern arguments,
+	#   which are a list of names each with tail pkgIndex.tcl.  The purpose
+	#   of the call to glob is to remove the names for which the file does
+	#   not exist.
+	set entries [::interp invokehidden $child glob {*}$cmd]
+    } on error msg {
+	# This is the only place that a call with -nocomplain and no invalid
+	# "dash-options" can return an error.
+	Log $child $msg
+	return -code error "script error"
+    }
+
+    Log $child "GLOB < $entries" NOTICE
+
+    # Translate path back to what the child should see.
+    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 $child "GLOB > $res" NOTICE
+    return $res
+}
+
+# AliasSource is the target of the "source" alias in safe interpreters.
+
+proc ::safe::AliasSource {child args} {
+    set argc [llength $args]
+    # Extended for handling of Tcl Modules to allow not only "source
+    # filename", but "source -encoding E filename" as well.
+    if {[lindex $args 0] eq "-encoding"} {
+	incr argc -2
+	set encoding [lindex $args 1]
+	set at 2
+	if {$encoding eq "identity"} {
+	    Log $child "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 $child "$msg ($args)"
+	return -code error $msg
+    }
+    set file [lindex $args $at]
+
+    # get the real path from the virtual one.
+    if {[catch {
+	set realfile [TranslatePath $child $file]
+    } msg]} {
+	Log $child $msg
+	return -code error "permission denied"
+    }
+
+    # check that the path is in the access path of that child
+    if {[catch {
+	FileInAccessPath $child $realfile
+    } msg]} {
+	Log $child $msg
+	return -code error "permission denied"
+    }
+
+    # Check that the filename exists and is readable.  If it is not, deliver
+    # this -errorcode so that caller in tclPkgUnknown does not write a message
+    # to tclLog.  Has no effect on other callers of ::source, which are in
+    # "package ifneeded" scripts.
+    if {[catch {
+	CheckFileName $child $realfile
+    } msg]} {
+	Log $child "$realfile:$msg"
+	return -code error -errorcode {POSIX EACCES} $msg
+    }
+
+    # Passed all the tests, lets source it. Note that we do this all manually
+    # because we want to control [info script] in the child so information
+    # doesn't leak so much. [Bug 2913625]
+    set old [::interp eval $child {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 $child [list info script $file]
+    } msg opt]
+    if {$code == 0} {
+	set code [catch {::interp eval $child $contents} msg opt]
+	set replacementMsg $msg
+    }
+    catch {interp eval $child [list info script $old]}
+    # Note that all non-errors are fine result codes from [source], so we must
+    # take a little care to do it properly. [Bug 2923613]
+    if {$code == 1} {
+	Log $child $msg
+	return -code error $replacementMsg
+    }
+    return -code $code -options $opt $msg
+}
+
+# AliasLoad is the target of the "load" alias in safe interpreters.
+
+proc ::safe::AliasLoad {child file args} {
+    set argc [llength $args]
+    if {$argc > 2} {
+	set msg "load error: too many arguments"
+	Log $child "$msg ($argc) {$file $args}"
+	return -code error $msg
+    }
+
+    # package name (can be empty if file is not).
+    set package [lindex $args 0]
+
+    namespace upvar ::safe [VarName $child] state
+
+    # 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 {!$state(nestedok)} {
+	    Log $child "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 $child $msg
+	    return -code error $msg
+	}
+	if {!$state(staticsok)} {
+	    Log $child "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.
+	try {
+	    set file [TranslatePath $child $file]
+	} on error msg {
+	    Log $child $msg
+	    return -code error "permission denied"
+	}
+
+	# check the translated path
+	try {
+	    FileInAccessPath $child $file
+	} on error msg {
+	    Log $child $msg
+	    return -code error "permission denied (path)"
+	}
+    }
+
+    try {
+	return [::interp invokehidden $child load $file $package $target]
+    } on error msg {
+	# Some packages return no error message.
+	set msg0 "load of binary library for package $package failed"
+	if {$msg eq {}} {
+	    set msg $msg0
+	} else {
+	    set msg "$msg0: $msg"
+	}
+	Log $child $msg
+	return -code error $msg
+    }
+}
+
+# FileInAccessPath raises an error if the file is not found in the list of
+# directories contained in the (parent side recorded) child's access path.
+
+# the security here relies on "file dirname" answering the proper
+# result... needs checking ?
+proc ::safe::FileInAccessPath {child file} {
+    namespace upvar ::safe [VarName $child] state
+    set access_path $state(access_path)
+
+    if {[file isdirectory $file]} {
+	return -code 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]
+
+    namespace upvar ::safe [VarName $child] state
+    if {$norm_parent ni $state(access_path,norm)} {
+	return -code error "\"$file\": not in access_path"
+    }
+}
+
+proc ::safe::DirInAccessPath {child dir} {
+    namespace upvar ::safe [VarName $child] state
+    set access_path $state(access_path)
+
+    if {[file isfile $dir]} {
+	return -code error "\"$dir\": is a file"
+    }
+
+    # Normalize paths for comparison since lsearch knows nothing of
+    # potential pathname anomalies.
+    set norm_dir [file normalize $dir]
+
+    namespace upvar ::safe [VarName $child] state
+    if {$norm_dir ni $state(access_path,norm)} {
+	return -code error "\"$dir\": not in access_path"
+    }
+}
+
+# This procedure is used to report an attempt to use an unsafe member of an
+# ensemble command.
+
+proc ::safe::BadSubcommand {child command subcommand args} {
+    set msg "not allowed to invoke subcommand $subcommand of $command"
+    Log $child $msg
+    return -code error -errorcode {TCL SAFE SUBCOMMAND} $msg
+}
+
+# AliasEncoding is the target of the "encoding" alias in safe interpreters.
+
+proc ::safe::AliasEncoding {child option args} {
+    # Note that [encoding dirs] is not supported in safe children at all
+    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]
+	# Special case: [encoding system] ok, but [encoding system foo] not
+	if {$option eq "system" && [llength $args]} {
+	    return -code error -errorcode {TCL WRONGARGS} \
+		"wrong # args: should be \"encoding system\""
+	}
+    } on error {msg options} {
+	Log $child $msg
+	return -options $options $msg
+    }
+    tailcall ::interp invokehidden $child encoding $option {*}$args
+}
+
+# Various minor hiding of platform features. [Bug 2913625]
+
+proc ::safe::AliasExeName {child} {
+    return ""
+}
+
+# ------------------------------------------------------------------------------
+# Using Interpreter Names with Namespace Qualifiers
+# ------------------------------------------------------------------------------
+# (1) We wish to preserve compatibility with existing code, in which Safe Base
+#     interpreter names have no namespace qualifiers.
+# (2) safe::interpCreate and the rest of the Safe Base previously could not
+#     accept namespace qualifiers in an interpreter name.
+# (3) The interp command will accept namespace qualifiers in an interpreter
+#     name, but accepts distinct interpreters that will have the same command
+#     name (e.g. foo, ::foo, and :::foo) (bug 66c2e8c974).
+# (4) To satisfy these constraints, Safe Base interpreter names will be fully
+#     qualified namespace names with no excess colons and with the leading "::"
+#     omitted.
+# (5) Trailing "::" implies a namespace tail {}, which interp reads as {{}}.
+#     Reject such names.
+# (6) We could:
+#     (a) EITHER reject usable but non-compliant names (e.g. excess colons) in
+#         interpCreate, interpInit;
+#     (b) OR accept such names and then translate to a compliant name in every
+#         command.
+#     The problem with (b) is that the user will expect to use the name with the
+#     interp command and will find that it is not recognised.
+#     E.g "interpCreate ::foo" creates interpreter "foo", and the user's name
+#     "::foo" works with all the Safe Base commands, but "interp eval ::foo"
+#     fails.
+#     So we choose (a).
+# (7) The command
+#         namespace upvar ::safe S$child state
+#     becomes
+#         namespace upvar ::safe [VarName $child] state
+# ------------------------------------------------------------------------------
+
+proc ::safe::RejectExcessColons {child} {
+    set stripped [regsub -all -- {:::*} $child ::]
+    if {[string range $stripped end-1 end] eq {::}} {
+        return -code error {interpreter name must not end in "::"}
+    }
+    if {$stripped ne $child} {
+        set msg {interpreter name has excess colons in namespace separators}
+        return -code error $msg
+    }
+    if {[string range $stripped 0 1] eq {::}} {
+        return -code error {interpreter name must not begin "::"}
+    }
+    return
+}
+
+proc ::safe::VarName {child} {
+    # return S$child
+    return S[string map {:: @N @ @A} $child]
+}
+
+proc ::safe::Setup {} {
+    ####
+    #
+    # 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
+
+    ####
+    #
+    # Default: No logging.
+    #
+    ####
+
+    setLogCmd {}
+
+    # Log eventually.
+    # To enable error logging, set Log to {puts stderr} for instance,
+    # via setLogCmd.
+    return
+}
+
+namespace eval ::safe {
+    # internal variables
+
+    # Log command, set via 'setLogCmd'. Logging is disabled when empty.
+    variable Log {}
+
+    # The package maintains a state array per child interp under its
+    # control. The name of this array is S<interp-name>. This array is
+    # brought into scope where needed, using 'namespace upvar'. The S
+    # prefix is used to avoid that a child interp called "Log" smashes
+    # the "Log" variable.
+    #
+    # The array's elements are:
+    #
+    # access_path       : List of paths accessible to the child.
+    # access_path,norm  : Ditto, in normalized form.
+    # access_path,slave : Ditto, as the path tokens as seen by the child.
+    # access_path,map   : dict ( token -> path )
+    # access_path,remap : dict ( path -> token )
+    # tm_path_slave     : List of TM root directories, as tokens seen by the child.
+    # staticsok         : Value of option -statics
+    # nestedok          : Value of option -nested
+    # cleanupHook       : Value of option -deleteHook
+}
+
+::safe::Setup