Mercurial > repos > rliterman > csp2
comparison 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 |
comparison
equal
deleted
inserted
replaced
67:0e9998148a16 | 68:5028fdace37b |
---|---|
1 # safe.tcl -- | |
2 # | |
3 # This file provide a safe loading/sourcing mechanism for safe interpreters. | |
4 # It implements a virtual path mechanism to hide the real pathnames from the | |
5 # child. It runs in a parent interpreter and sets up data structure and | |
6 # aliases that will be invoked when used from a child interpreter. | |
7 # | |
8 # See the safe.n man page for details. | |
9 # | |
10 # Copyright (c) 1996-1997 Sun Microsystems, Inc. | |
11 # | |
12 # See the file "license.terms" for information on usage and redistribution of | |
13 # this file, and for a DISCLAIMER OF ALL WARRANTIES. | |
14 | |
15 # | |
16 # The implementation is based on namespaces. These naming conventions are | |
17 # followed: | |
18 # Private procs starts with uppercase. | |
19 # Public procs are exported and starts with lowercase | |
20 # | |
21 | |
22 # Needed utilities package | |
23 package require opt 0.4.8 | |
24 | |
25 # Create the safe namespace | |
26 namespace eval ::safe { | |
27 # Exported API: | |
28 namespace export interpCreate interpInit interpConfigure interpDelete \ | |
29 interpAddToAccessPath interpFindInAccessPath setLogCmd | |
30 } | |
31 | |
32 # Helper function to resolve the dual way of specifying staticsok (either | |
33 # by -noStatics or -statics 0) | |
34 proc ::safe::InterpStatics {} { | |
35 foreach v {Args statics noStatics} { | |
36 upvar $v $v | |
37 } | |
38 set flag [::tcl::OptProcArgGiven -noStatics] | |
39 if {$flag && (!$noStatics == !$statics) | |
40 && ([::tcl::OptProcArgGiven -statics])} { | |
41 return -code error\ | |
42 "conflicting values given for -statics and -noStatics" | |
43 } | |
44 if {$flag} { | |
45 return [expr {!$noStatics}] | |
46 } else { | |
47 return $statics | |
48 } | |
49 } | |
50 | |
51 # Helper function to resolve the dual way of specifying nested loading | |
52 # (either by -nestedLoadOk or -nested 1) | |
53 proc ::safe::InterpNested {} { | |
54 foreach v {Args nested nestedLoadOk} { | |
55 upvar $v $v | |
56 } | |
57 set flag [::tcl::OptProcArgGiven -nestedLoadOk] | |
58 # note that the test here is the opposite of the "InterpStatics" one | |
59 # (it is not -noNested... because of the wanted default value) | |
60 if {$flag && (!$nestedLoadOk != !$nested) | |
61 && ([::tcl::OptProcArgGiven -nested])} { | |
62 return -code error\ | |
63 "conflicting values given for -nested and -nestedLoadOk" | |
64 } | |
65 if {$flag} { | |
66 # another difference with "InterpStatics" | |
67 return $nestedLoadOk | |
68 } else { | |
69 return $nested | |
70 } | |
71 } | |
72 | |
73 #### | |
74 # | |
75 # API entry points that needs argument parsing : | |
76 # | |
77 #### | |
78 | |
79 # Interface/entry point function and front end for "Create" | |
80 proc ::safe::interpCreate {args} { | |
81 set Args [::tcl::OptKeyParse ::safe::interpCreate $args] | |
82 RejectExcessColons $slave | |
83 InterpCreate $slave $accessPath \ | |
84 [InterpStatics] [InterpNested] $deleteHook | |
85 } | |
86 | |
87 proc ::safe::interpInit {args} { | |
88 set Args [::tcl::OptKeyParse ::safe::interpIC $args] | |
89 if {![::interp exists $slave]} { | |
90 return -code error "\"$slave\" is not an interpreter" | |
91 } | |
92 RejectExcessColons $slave | |
93 InterpInit $slave $accessPath \ | |
94 [InterpStatics] [InterpNested] $deleteHook | |
95 } | |
96 | |
97 # Check that the given child is "one of us" | |
98 proc ::safe::CheckInterp {child} { | |
99 namespace upvar ::safe [VarName $child] state | |
100 if {![info exists state] || ![::interp exists $child]} { | |
101 return -code error \ | |
102 "\"$child\" is not an interpreter managed by ::safe::" | |
103 } | |
104 } | |
105 | |
106 # Interface/entry point function and front end for "Configure". This code | |
107 # is awfully pedestrian because it would need more coupling and support | |
108 # between the way we store the configuration values in safe::interp's and | |
109 # the Opt package. Obviously we would like an OptConfigure to avoid | |
110 # duplicating all this code everywhere. | |
111 # -> TODO (the app should share or access easily the program/value stored | |
112 # by opt) | |
113 | |
114 # This is even more complicated by the boolean flags with no values that | |
115 # we had the bad idea to support for the sake of user simplicity in | |
116 # create/init but which makes life hard in configure... | |
117 # So this will be hopefully written and some integrated with opt1.0 | |
118 # (hopefully for tcl8.1 ?) | |
119 proc ::safe::interpConfigure {args} { | |
120 switch [llength $args] { | |
121 1 { | |
122 # If we have exactly 1 argument the semantic is to return all | |
123 # the current configuration. We still call OptKeyParse though | |
124 # we know that "child" is our given argument because it also | |
125 # checks for the "-help" option. | |
126 set Args [::tcl::OptKeyParse ::safe::interpIC $args] | |
127 CheckInterp $slave | |
128 namespace upvar ::safe [VarName $slave] state | |
129 | |
130 return [join [list \ | |
131 [list -accessPath $state(access_path)] \ | |
132 [list -statics $state(staticsok)] \ | |
133 [list -nested $state(nestedok)] \ | |
134 [list -deleteHook $state(cleanupHook)]]] | |
135 } | |
136 2 { | |
137 # If we have exactly 2 arguments the semantic is a "configure | |
138 # get" | |
139 lassign $args slave arg | |
140 | |
141 # get the flag sub program (we 'know' about Opt's internal | |
142 # representation of data) | |
143 set desc [lindex [::tcl::OptKeyGetDesc ::safe::interpIC] 2] | |
144 set hits [::tcl::OptHits desc $arg] | |
145 if {$hits > 1} { | |
146 return -code error [::tcl::OptAmbigous $desc $arg] | |
147 } elseif {$hits == 0} { | |
148 return -code error [::tcl::OptFlagUsage $desc $arg] | |
149 } | |
150 CheckInterp $slave | |
151 namespace upvar ::safe [VarName $slave] state | |
152 | |
153 set item [::tcl::OptCurDesc $desc] | |
154 set name [::tcl::OptName $item] | |
155 switch -exact -- $name { | |
156 -accessPath { | |
157 return [list -accessPath $state(access_path)] | |
158 } | |
159 -statics { | |
160 return [list -statics $state(staticsok)] | |
161 } | |
162 -nested { | |
163 return [list -nested $state(nestedok)] | |
164 } | |
165 -deleteHook { | |
166 return [list -deleteHook $state(cleanupHook)] | |
167 } | |
168 -noStatics { | |
169 # it is most probably a set in fact but we would need | |
170 # then to jump to the set part and it is not *sure* | |
171 # that it is a set action that the user want, so force | |
172 # it to use the unambigous -statics ?value? instead: | |
173 return -code error\ | |
174 "ambigous query (get or set -noStatics ?)\ | |
175 use -statics instead" | |
176 } | |
177 -nestedLoadOk { | |
178 return -code error\ | |
179 "ambigous query (get or set -nestedLoadOk ?)\ | |
180 use -nested instead" | |
181 } | |
182 default { | |
183 return -code error "unknown flag $name (bug)" | |
184 } | |
185 } | |
186 } | |
187 default { | |
188 # Otherwise we want to parse the arguments like init and | |
189 # create did | |
190 set Args [::tcl::OptKeyParse ::safe::interpIC $args] | |
191 CheckInterp $slave | |
192 namespace upvar ::safe [VarName $slave] state | |
193 | |
194 # Get the current (and not the default) values of whatever has | |
195 # not been given: | |
196 if {![::tcl::OptProcArgGiven -accessPath]} { | |
197 set doreset 0 | |
198 set accessPath $state(access_path) | |
199 } else { | |
200 set doreset 1 | |
201 } | |
202 if { | |
203 ![::tcl::OptProcArgGiven -statics] | |
204 && ![::tcl::OptProcArgGiven -noStatics] | |
205 } then { | |
206 set statics $state(staticsok) | |
207 } else { | |
208 set statics [InterpStatics] | |
209 } | |
210 if { | |
211 [::tcl::OptProcArgGiven -nested] || | |
212 [::tcl::OptProcArgGiven -nestedLoadOk] | |
213 } then { | |
214 set nested [InterpNested] | |
215 } else { | |
216 set nested $state(nestedok) | |
217 } | |
218 if {![::tcl::OptProcArgGiven -deleteHook]} { | |
219 set deleteHook $state(cleanupHook) | |
220 } | |
221 # we can now reconfigure : | |
222 InterpSetConfig $slave $accessPath $statics $nested $deleteHook | |
223 # auto_reset the child (to completly synch the new access_path) | |
224 if {$doreset} { | |
225 if {[catch {::interp eval $slave {auto_reset}} msg]} { | |
226 Log $slave "auto_reset failed: $msg" | |
227 } else { | |
228 Log $slave "successful auto_reset" NOTICE | |
229 } | |
230 | |
231 # Sync the paths used to search for Tcl modules. | |
232 ::interp eval $slave {tcl::tm::path remove {*}[tcl::tm::list]} | |
233 if {[llength $state(tm_path_slave)] > 0} { | |
234 ::interp eval $slave [list \ | |
235 ::tcl::tm::add {*}[lreverse $state(tm_path_slave)]] | |
236 } | |
237 | |
238 # Remove stale "package ifneeded" data for non-loaded packages. | |
239 # - Not for loaded packages, because "package forget" erases | |
240 # data from "package provide" as well as "package ifneeded". | |
241 # - This is OK because the script cannot reload any version of | |
242 # the package unless it first does "package forget". | |
243 foreach pkg [::interp eval $slave {package names}] { | |
244 if {[::interp eval $slave [list package provide $pkg]] eq ""} { | |
245 ::interp eval $slave [list package forget $pkg] | |
246 } | |
247 } | |
248 } | |
249 return | |
250 } | |
251 } | |
252 } | |
253 | |
254 #### | |
255 # | |
256 # Functions that actually implements the exported APIs | |
257 # | |
258 #### | |
259 | |
260 # | |
261 # safe::InterpCreate : doing the real job | |
262 # | |
263 # This procedure creates a safe interpreter and initializes it with the safe | |
264 # base aliases. | |
265 # NB: child name must be simple alphanumeric string, no spaces, no (), no | |
266 # {},... {because the state array is stored as part of the name} | |
267 # | |
268 # Returns the child name. | |
269 # | |
270 # Optional Arguments : | |
271 # + child name : if empty, generated name will be used | |
272 # + access_path: path list controlling where load/source can occur, | |
273 # if empty: the parent auto_path will be used. | |
274 # + staticsok : flag, if 0 :no static package can be loaded (load {} Xxx) | |
275 # if 1 :static packages are ok. | |
276 # + nestedok: flag, if 0 :no loading to sub-sub interps (load xx xx sub) | |
277 # if 1 : multiple levels are ok. | |
278 | |
279 # use the full name and no indent so auto_mkIndex can find us | |
280 proc ::safe::InterpCreate { | |
281 child | |
282 access_path | |
283 staticsok | |
284 nestedok | |
285 deletehook | |
286 } { | |
287 # Create the child. | |
288 # If evaluated in ::safe, the interpreter command for foo is ::foo; | |
289 # but for foo::bar is safe::foo::bar. So evaluate in :: instead. | |
290 if {$child ne ""} { | |
291 namespace eval :: [list ::interp create -safe $child] | |
292 } else { | |
293 # empty argument: generate child name | |
294 set child [::interp create -safe] | |
295 } | |
296 Log $child "Created" NOTICE | |
297 | |
298 # Initialize it. (returns child name) | |
299 InterpInit $child $access_path $staticsok $nestedok $deletehook | |
300 } | |
301 | |
302 # | |
303 # InterpSetConfig (was setAccessPath) : | |
304 # Sets up child virtual auto_path and corresponding structure within | |
305 # the parent. Also sets the tcl_library in the child to be the first | |
306 # directory in the path. | |
307 # NB: If you change the path after the child has been initialized you | |
308 # probably need to call "auto_reset" in the child in order that it gets | |
309 # the right auto_index() array values. | |
310 | |
311 proc ::safe::InterpSetConfig {child access_path staticsok nestedok deletehook} { | |
312 global auto_path | |
313 | |
314 # determine and store the access path if empty | |
315 if {$access_path eq ""} { | |
316 set access_path $auto_path | |
317 | |
318 # Make sure that tcl_library is in auto_path and at the first | |
319 # position (needed by setAccessPath) | |
320 set where [lsearch -exact $access_path [info library]] | |
321 if {$where < 0} { | |
322 # not found, add it. | |
323 set access_path [linsert $access_path 0 [info library]] | |
324 Log $child "tcl_library was not in auto_path,\ | |
325 added it to slave's access_path" NOTICE | |
326 } elseif {$where != 0} { | |
327 # not first, move it first | |
328 set access_path [linsert \ | |
329 [lreplace $access_path $where $where] \ | |
330 0 [info library]] | |
331 Log $child "tcl_libray was not in first in auto_path,\ | |
332 moved it to front of slave's access_path" NOTICE | |
333 } | |
334 | |
335 # Add 1st level sub dirs (will searched by auto loading from tcl | |
336 # code in the child using glob and thus fail, so we add them here | |
337 # so by default it works the same). | |
338 set access_path [AddSubDirs $access_path] | |
339 } | |
340 | |
341 Log $child "Setting accessPath=($access_path) staticsok=$staticsok\ | |
342 nestedok=$nestedok deletehook=($deletehook)" NOTICE | |
343 | |
344 namespace upvar ::safe [VarName $child] state | |
345 | |
346 # clear old autopath if it existed | |
347 # build new one | |
348 # Extend the access list with the paths used to look for Tcl Modules. | |
349 # We save the virtual form separately as well, as syncing it with the | |
350 # child has to be deferred until the necessary commands are present for | |
351 # setup. | |
352 | |
353 set norm_access_path {} | |
354 set slave_access_path {} | |
355 set map_access_path {} | |
356 set remap_access_path {} | |
357 set slave_tm_path {} | |
358 | |
359 set i 0 | |
360 foreach dir $access_path { | |
361 set token [PathToken $i] | |
362 lappend slave_access_path $token | |
363 lappend map_access_path $token $dir | |
364 lappend remap_access_path $dir $token | |
365 lappend norm_access_path [file normalize $dir] | |
366 incr i | |
367 } | |
368 | |
369 set morepaths [::tcl::tm::list] | |
370 set firstpass 1 | |
371 while {[llength $morepaths]} { | |
372 set addpaths $morepaths | |
373 set morepaths {} | |
374 | |
375 foreach dir $addpaths { | |
376 # Prevent the addition of dirs on the tm list to the | |
377 # result if they are already known. | |
378 if {[dict exists $remap_access_path $dir]} { | |
379 if {$firstpass} { | |
380 # $dir is in [::tcl::tm::list] and belongs in the slave_tm_path. | |
381 # Later passes handle subdirectories, which belong in the | |
382 # access path but not in the module path. | |
383 lappend slave_tm_path [dict get $remap_access_path $dir] | |
384 } | |
385 continue | |
386 } | |
387 | |
388 set token [PathToken $i] | |
389 lappend access_path $dir | |
390 lappend slave_access_path $token | |
391 lappend map_access_path $token $dir | |
392 lappend remap_access_path $dir $token | |
393 lappend norm_access_path [file normalize $dir] | |
394 if {$firstpass} { | |
395 # $dir is in [::tcl::tm::list] and belongs in the slave_tm_path. | |
396 # Later passes handle subdirectories, which belong in the | |
397 # access path but not in the module path. | |
398 lappend slave_tm_path $token | |
399 } | |
400 incr i | |
401 | |
402 # [Bug 2854929] | |
403 # Recursively find deeper paths which may contain | |
404 # modules. Required to handle modules with names like | |
405 # 'platform::shell', which translate into | |
406 # 'platform/shell-X.tm', i.e arbitrarily deep | |
407 # subdirectories. | |
408 lappend morepaths {*}[glob -nocomplain -directory $dir -type d *] | |
409 } | |
410 set firstpass 0 | |
411 } | |
412 | |
413 set state(access_path) $access_path | |
414 set state(access_path,map) $map_access_path | |
415 set state(access_path,remap) $remap_access_path | |
416 set state(access_path,norm) $norm_access_path | |
417 set state(access_path,slave) $slave_access_path | |
418 set state(tm_path_slave) $slave_tm_path | |
419 set state(staticsok) $staticsok | |
420 set state(nestedok) $nestedok | |
421 set state(cleanupHook) $deletehook | |
422 | |
423 SyncAccessPath $child | |
424 return | |
425 } | |
426 | |
427 # | |
428 # | |
429 # FindInAccessPath: | |
430 # Search for a real directory and returns its virtual Id (including the | |
431 # "$") | |
432 proc ::safe::interpFindInAccessPath {child path} { | |
433 CheckInterp $child | |
434 namespace upvar ::safe [VarName $child] state | |
435 | |
436 if {![dict exists $state(access_path,remap) $path]} { | |
437 return -code error "$path not found in access path" | |
438 } | |
439 | |
440 return [dict get $state(access_path,remap) $path] | |
441 } | |
442 | |
443 # | |
444 # addToAccessPath: | |
445 # add (if needed) a real directory to access path and return its | |
446 # virtual token (including the "$"). | |
447 proc ::safe::interpAddToAccessPath {child path} { | |
448 # first check if the directory is already in there | |
449 # (inlined interpFindInAccessPath). | |
450 CheckInterp $child | |
451 namespace upvar ::safe [VarName $child] state | |
452 | |
453 if {[dict exists $state(access_path,remap) $path]} { | |
454 return [dict get $state(access_path,remap) $path] | |
455 } | |
456 | |
457 # new one, add it: | |
458 set token [PathToken [llength $state(access_path)]] | |
459 | |
460 lappend state(access_path) $path | |
461 lappend state(access_path,slave) $token | |
462 lappend state(access_path,map) $token $path | |
463 lappend state(access_path,remap) $path $token | |
464 lappend state(access_path,norm) [file normalize $path] | |
465 | |
466 SyncAccessPath $child | |
467 return $token | |
468 } | |
469 | |
470 # This procedure applies the initializations to an already existing | |
471 # interpreter. It is useful when you want to install the safe base aliases | |
472 # into a preexisting safe interpreter. | |
473 proc ::safe::InterpInit { | |
474 child | |
475 access_path | |
476 staticsok | |
477 nestedok | |
478 deletehook | |
479 } { | |
480 # Configure will generate an access_path when access_path is empty. | |
481 InterpSetConfig $child $access_path $staticsok $nestedok $deletehook | |
482 | |
483 # NB we need to add [namespace current], aliases are always absolute | |
484 # paths. | |
485 | |
486 # These aliases let the child load files to define new commands | |
487 # This alias lets the child use the encoding names, convertfrom, | |
488 # convertto, and system, but not "encoding system <name>" to set the | |
489 # system encoding. | |
490 # Handling Tcl Modules, we need a restricted form of Glob. | |
491 # This alias interposes on the 'exit' command and cleanly terminates | |
492 # the child. | |
493 | |
494 foreach {command alias} { | |
495 source AliasSource | |
496 load AliasLoad | |
497 encoding AliasEncoding | |
498 exit interpDelete | |
499 glob AliasGlob | |
500 } { | |
501 ::interp alias $child $command {} [namespace current]::$alias $child | |
502 } | |
503 | |
504 # This alias lets the child have access to a subset of the 'file' | |
505 # command functionality. | |
506 | |
507 ::interp expose $child file | |
508 foreach subcommand {dirname extension rootname tail} { | |
509 ::interp alias $child ::tcl::file::$subcommand {} \ | |
510 ::safe::AliasFileSubcommand $child $subcommand | |
511 } | |
512 foreach subcommand { | |
513 atime attributes copy delete executable exists isdirectory isfile | |
514 link lstat mtime mkdir nativename normalize owned readable readlink | |
515 rename size stat tempfile type volumes writable | |
516 } { | |
517 ::interp alias $child ::tcl::file::$subcommand {} \ | |
518 ::safe::BadSubcommand $child file $subcommand | |
519 } | |
520 | |
521 # Subcommands of info | |
522 foreach {subcommand alias} { | |
523 nameofexecutable AliasExeName | |
524 } { | |
525 ::interp alias $child ::tcl::info::$subcommand \ | |
526 {} [namespace current]::$alias $child | |
527 } | |
528 | |
529 # The allowed child variables already have been set by Tcl_MakeSafe(3) | |
530 | |
531 # Source init.tcl and tm.tcl into the child, to get auto_load and | |
532 # other procedures defined: | |
533 | |
534 if {[catch {::interp eval $child { | |
535 source [file join $tcl_library init.tcl] | |
536 }} msg opt]} { | |
537 Log $child "can't source init.tcl ($msg)" | |
538 return -options $opt "can't source init.tcl into slave $child ($msg)" | |
539 } | |
540 | |
541 if {[catch {::interp eval $child { | |
542 source [file join $tcl_library tm.tcl] | |
543 }} msg opt]} { | |
544 Log $child "can't source tm.tcl ($msg)" | |
545 return -options $opt "can't source tm.tcl into slave $child ($msg)" | |
546 } | |
547 | |
548 # Sync the paths used to search for Tcl modules. This can be done only | |
549 # now, after tm.tcl was loaded. | |
550 namespace upvar ::safe [VarName $child] state | |
551 if {[llength $state(tm_path_slave)] > 0} { | |
552 ::interp eval $child [list \ | |
553 ::tcl::tm::add {*}[lreverse $state(tm_path_slave)]] | |
554 } | |
555 return $child | |
556 } | |
557 | |
558 # Add (only if needed, avoid duplicates) 1 level of sub directories to an | |
559 # existing path list. Also removes non directories from the returned | |
560 # list. | |
561 proc ::safe::AddSubDirs {pathList} { | |
562 set res {} | |
563 foreach dir $pathList { | |
564 if {[file isdirectory $dir]} { | |
565 # check that we don't have it yet as a children of a previous | |
566 # dir | |
567 if {$dir ni $res} { | |
568 lappend res $dir | |
569 } | |
570 foreach sub [glob -directory $dir -nocomplain *] { | |
571 if {[file isdirectory $sub] && ($sub ni $res)} { | |
572 # new sub dir, add it ! | |
573 lappend res $sub | |
574 } | |
575 } | |
576 } | |
577 } | |
578 return $res | |
579 } | |
580 | |
581 # This procedure deletes a safe interpreter managed by Safe Tcl and cleans up | |
582 # associated state. | |
583 # - The command will also delete non-Safe-Base interpreters. | |
584 # - This is regrettable, but to avoid breaking existing code this should be | |
585 # amended at the next major revision by uncommenting "CheckInterp". | |
586 | |
587 proc ::safe::interpDelete {child} { | |
588 Log $child "About to delete" NOTICE | |
589 | |
590 # CheckInterp $child | |
591 namespace upvar ::safe [VarName $child] state | |
592 | |
593 # When an interpreter is deleted with [interp delete], any sub-interpreters | |
594 # are deleted automatically, but this leaves behind their data in the Safe | |
595 # Base. To clean up properly, we call safe::interpDelete recursively on each | |
596 # Safe Base sub-interpreter, so each one is deleted cleanly and not by | |
597 # the automatic mechanism built into [interp delete]. | |
598 foreach sub [interp children $child] { | |
599 if {[info exists ::safe::[VarName [list $child $sub]]]} { | |
600 ::safe::interpDelete [list $child $sub] | |
601 } | |
602 } | |
603 | |
604 # If the child has a cleanup hook registered, call it. Check the | |
605 # existance because we might be called to delete an interp which has | |
606 # not been registered with us at all | |
607 | |
608 if {[info exists state(cleanupHook)]} { | |
609 set hook $state(cleanupHook) | |
610 if {[llength $hook]} { | |
611 # remove the hook now, otherwise if the hook calls us somehow, | |
612 # we'll loop | |
613 unset state(cleanupHook) | |
614 try { | |
615 {*}$hook $child | |
616 } on error err { | |
617 Log $child "Delete hook error ($err)" | |
618 } | |
619 } | |
620 } | |
621 | |
622 # Discard the global array of state associated with the child, and | |
623 # delete the interpreter. | |
624 | |
625 if {[info exists state]} { | |
626 unset state | |
627 } | |
628 | |
629 # if we have been called twice, the interp might have been deleted | |
630 # already | |
631 if {[::interp exists $child]} { | |
632 ::interp delete $child | |
633 Log $child "Deleted" NOTICE | |
634 } | |
635 | |
636 return | |
637 } | |
638 | |
639 # Set (or get) the logging mecanism | |
640 | |
641 proc ::safe::setLogCmd {args} { | |
642 variable Log | |
643 set la [llength $args] | |
644 if {$la == 0} { | |
645 return $Log | |
646 } elseif {$la == 1} { | |
647 set Log [lindex $args 0] | |
648 } else { | |
649 set Log $args | |
650 } | |
651 | |
652 if {$Log eq ""} { | |
653 # Disable logging completely. Calls to it will be compiled out | |
654 # of all users. | |
655 proc ::safe::Log {args} {} | |
656 } else { | |
657 # Activate logging, define proper command. | |
658 | |
659 proc ::safe::Log {child msg {type ERROR}} { | |
660 variable Log | |
661 {*}$Log "$type for slave $child : $msg" | |
662 return | |
663 } | |
664 } | |
665 } | |
666 | |
667 # ------------------- END OF PUBLIC METHODS ------------ | |
668 | |
669 # | |
670 # Sets the child auto_path to the parent recorded value. Also sets | |
671 # tcl_library to the first token of the virtual path. | |
672 # | |
673 proc ::safe::SyncAccessPath {child} { | |
674 namespace upvar ::safe [VarName $child] state | |
675 | |
676 set slave_access_path $state(access_path,slave) | |
677 ::interp eval $child [list set auto_path $slave_access_path] | |
678 | |
679 Log $child "auto_path in $child has been set to $slave_access_path"\ | |
680 NOTICE | |
681 | |
682 # This code assumes that info library is the first element in the | |
683 # list of auto_path's. See -> InterpSetConfig for the code which | |
684 # ensures this condition. | |
685 | |
686 ::interp eval $child [list \ | |
687 set tcl_library [lindex $slave_access_path 0]] | |
688 } | |
689 | |
690 # Returns the virtual token for directory number N. | |
691 proc ::safe::PathToken {n} { | |
692 # We need to have a ":" in the token string so [file join] on the | |
693 # mac won't turn it into a relative path. | |
694 return "\$p(:$n:)" ;# Form tested by case 7.2 | |
695 } | |
696 | |
697 # | |
698 # translate virtual path into real path | |
699 # | |
700 proc ::safe::TranslatePath {child path} { | |
701 namespace upvar ::safe [VarName $child] state | |
702 | |
703 # somehow strip the namespaces 'functionality' out (the danger is that | |
704 # we would strip valid macintosh "../" queries... : | |
705 if {[string match "*::*" $path] || [string match "*..*" $path]} { | |
706 return -code error "invalid characters in path $path" | |
707 } | |
708 | |
709 # Use a cached map instead of computed local vars and subst. | |
710 | |
711 return [string map $state(access_path,map) $path] | |
712 } | |
713 | |
714 # file name control (limit access to files/resources that should be a | |
715 # valid tcl source file) | |
716 proc ::safe::CheckFileName {child file} { | |
717 # This used to limit what can be sourced to ".tcl" and forbid files | |
718 # with more than 1 dot and longer than 14 chars, but I changed that | |
719 # for 8.4 as a safe interp has enough internal protection already to | |
720 # allow sourcing anything. - hobbs | |
721 | |
722 if {![file exists $file]} { | |
723 # don't tell the file path | |
724 return -code error "no such file or directory" | |
725 } | |
726 | |
727 if {![file readable $file]} { | |
728 # don't tell the file path | |
729 return -code error "not readable" | |
730 } | |
731 } | |
732 | |
733 # AliasFileSubcommand handles selected subcommands of [file] in safe | |
734 # interpreters that are *almost* safe. In particular, it just acts to | |
735 # prevent discovery of what home directories exist. | |
736 | |
737 proc ::safe::AliasFileSubcommand {child subcommand name} { | |
738 if {[string match ~* $name]} { | |
739 set name ./$name | |
740 } | |
741 tailcall ::interp invokehidden $child tcl:file:$subcommand $name | |
742 } | |
743 | |
744 # AliasGlob is the target of the "glob" alias in safe interpreters. | |
745 | |
746 proc ::safe::AliasGlob {child args} { | |
747 Log $child "GLOB ! $args" NOTICE | |
748 set cmd {} | |
749 set at 0 | |
750 array set got { | |
751 -directory 0 | |
752 -nocomplain 0 | |
753 -join 0 | |
754 -tails 0 | |
755 -- 0 | |
756 } | |
757 | |
758 if {$::tcl_platform(platform) eq "windows"} { | |
759 set dirPartRE {^(.*)[\\/]([^\\/]*)$} | |
760 } else { | |
761 set dirPartRE {^(.*)/([^/]*)$} | |
762 } | |
763 | |
764 set dir {} | |
765 set virtualdir {} | |
766 | |
767 while {$at < [llength $args]} { | |
768 switch -glob -- [set opt [lindex $args $at]] { | |
769 -nocomplain - -- - -tails { | |
770 lappend cmd $opt | |
771 set got($opt) 1 | |
772 incr at | |
773 } | |
774 -join { | |
775 set got($opt) 1 | |
776 incr at | |
777 } | |
778 -types - -type { | |
779 lappend cmd -types [lindex $args [incr at]] | |
780 incr at | |
781 } | |
782 -directory { | |
783 if {$got($opt)} { | |
784 return -code error \ | |
785 {"-directory" cannot be used with "-path"} | |
786 } | |
787 set got($opt) 1 | |
788 set virtualdir [lindex $args [incr at]] | |
789 incr at | |
790 } | |
791 -* { | |
792 Log $child "Safe base rejecting glob option '$opt'" | |
793 return -code error "Safe base rejecting glob option '$opt'" | |
794 } | |
795 default { | |
796 break | |
797 } | |
798 } | |
799 if {$got(--)} break | |
800 } | |
801 | |
802 # Get the real path from the virtual one and check that the path is in the | |
803 # access path of that child. Done after basic argument processing so that | |
804 # we know if -nocomplain is set. | |
805 if {$got(-directory)} { | |
806 try { | |
807 set dir [TranslatePath $child $virtualdir] | |
808 DirInAccessPath $child $dir | |
809 } on error msg { | |
810 Log $child $msg | |
811 if {$got(-nocomplain)} return | |
812 return -code error "permission denied" | |
813 } | |
814 if {$got(--)} { | |
815 set cmd [linsert $cmd end-1 -directory $dir] | |
816 } else { | |
817 lappend cmd -directory $dir | |
818 } | |
819 } else { | |
820 # The code after this "if ... else" block would conspire to return with | |
821 # no results in this case, if it were allowed to proceed. Instead, | |
822 # return now and reduce the number of cases to be considered later. | |
823 Log $child {option -directory must be supplied} | |
824 if {$got(-nocomplain)} return | |
825 return -code error "permission denied" | |
826 } | |
827 | |
828 # Apply the -join semantics ourselves. | |
829 if {$got(-join)} { | |
830 set args [lreplace $args $at end [join [lrange $args $at end] "/"]] | |
831 } | |
832 | |
833 # Process the pattern arguments. If we've done a join there is only one | |
834 # pattern argument. | |
835 | |
836 set firstPattern [llength $cmd] | |
837 foreach opt [lrange $args $at end] { | |
838 if {![regexp $dirPartRE $opt -> thedir thefile]} { | |
839 set thedir . | |
840 # The *.tm search comes here. | |
841 } | |
842 # "Special" treatment for (joined) argument {*/pkgIndex.tcl}. | |
843 # Do the expansion of "*" here, and filter out any directories that are | |
844 # not in the access path. The outcome is to lappend to cmd a path of | |
845 # the form $virtualdir/subdir/pkgIndex.tcl for each subdirectory subdir, | |
846 # after removing any subdir that are not in the access path. | |
847 if {($thedir eq "*") && ($thefile eq "pkgIndex.tcl")} { | |
848 set mapped 0 | |
849 foreach d [glob -directory [TranslatePath $child $virtualdir] \ | |
850 -types d -tails *] { | |
851 catch { | |
852 DirInAccessPath $child \ | |
853 [TranslatePath $child [file join $virtualdir $d]] | |
854 lappend cmd [file join $d $thefile] | |
855 set mapped 1 | |
856 } | |
857 } | |
858 if {$mapped} continue | |
859 # Don't [continue] if */pkgIndex.tcl has no matches in the access | |
860 # path. The pattern will now receive the same treatment as a | |
861 # "non-special" pattern (and will fail because it includes a "*" in | |
862 # the directory name). | |
863 } | |
864 # Any directory pattern that is not an exact (i.e. non-glob) match to a | |
865 # directory in the access path will be rejected here. | |
866 # - Rejections include any directory pattern that has glob matching | |
867 # patterns "*", "?", backslashes, braces or square brackets, (UNLESS | |
868 # it corresponds to a genuine directory name AND that directory is in | |
869 # the access path). | |
870 # - The only "special matching characters" that remain in patterns for | |
871 # processing by glob are in the filename tail. | |
872 # - [file join $anything ~${foo}] is ~${foo}, which is not an exact | |
873 # match to any directory in the access path. Hence directory patterns | |
874 # that begin with "~" are rejected here. Tests safe-16.[5-8] check | |
875 # that "file join" remains as required and does not expand ~${foo}. | |
876 # - Bug [3529949] relates to unwanted expansion of ~${foo} and this is | |
877 # how the present code avoids the bug. All tests safe-16.* relate. | |
878 try { | |
879 DirInAccessPath $child [TranslatePath $child \ | |
880 [file join $virtualdir $thedir]] | |
881 } on error msg { | |
882 Log $child $msg | |
883 if {$got(-nocomplain)} continue | |
884 return -code error "permission denied" | |
885 } | |
886 lappend cmd $opt | |
887 } | |
888 | |
889 Log $child "GLOB = $cmd" NOTICE | |
890 | |
891 if {$got(-nocomplain) && [llength $cmd] eq $firstPattern} { | |
892 return | |
893 } | |
894 try { | |
895 # >>>>>>>>>> HERE'S THE CALL TO SAFE INTERP GLOB <<<<<<<<<< | |
896 # - Pattern arguments added to cmd have NOT been translated from tokens. | |
897 # Only the virtualdir is translated (to dir). | |
898 # - In the pkgIndex.tcl case, there is no "*" in the pattern arguments, | |
899 # which are a list of names each with tail pkgIndex.tcl. The purpose | |
900 # of the call to glob is to remove the names for which the file does | |
901 # not exist. | |
902 set entries [::interp invokehidden $child glob {*}$cmd] | |
903 } on error msg { | |
904 # This is the only place that a call with -nocomplain and no invalid | |
905 # "dash-options" can return an error. | |
906 Log $child $msg | |
907 return -code error "script error" | |
908 } | |
909 | |
910 Log $child "GLOB < $entries" NOTICE | |
911 | |
912 # Translate path back to what the child should see. | |
913 set res {} | |
914 set l [string length $dir] | |
915 foreach p $entries { | |
916 if {[string equal -length $l $dir $p]} { | |
917 set p [string replace $p 0 [expr {$l-1}] $virtualdir] | |
918 } | |
919 lappend res $p | |
920 } | |
921 | |
922 Log $child "GLOB > $res" NOTICE | |
923 return $res | |
924 } | |
925 | |
926 # AliasSource is the target of the "source" alias in safe interpreters. | |
927 | |
928 proc ::safe::AliasSource {child args} { | |
929 set argc [llength $args] | |
930 # Extended for handling of Tcl Modules to allow not only "source | |
931 # filename", but "source -encoding E filename" as well. | |
932 if {[lindex $args 0] eq "-encoding"} { | |
933 incr argc -2 | |
934 set encoding [lindex $args 1] | |
935 set at 2 | |
936 if {$encoding eq "identity"} { | |
937 Log $child "attempt to use the identity encoding" | |
938 return -code error "permission denied" | |
939 } | |
940 } else { | |
941 set at 0 | |
942 set encoding {} | |
943 } | |
944 if {$argc != 1} { | |
945 set msg "wrong # args: should be \"source ?-encoding E? fileName\"" | |
946 Log $child "$msg ($args)" | |
947 return -code error $msg | |
948 } | |
949 set file [lindex $args $at] | |
950 | |
951 # get the real path from the virtual one. | |
952 if {[catch { | |
953 set realfile [TranslatePath $child $file] | |
954 } msg]} { | |
955 Log $child $msg | |
956 return -code error "permission denied" | |
957 } | |
958 | |
959 # check that the path is in the access path of that child | |
960 if {[catch { | |
961 FileInAccessPath $child $realfile | |
962 } msg]} { | |
963 Log $child $msg | |
964 return -code error "permission denied" | |
965 } | |
966 | |
967 # Check that the filename exists and is readable. If it is not, deliver | |
968 # this -errorcode so that caller in tclPkgUnknown does not write a message | |
969 # to tclLog. Has no effect on other callers of ::source, which are in | |
970 # "package ifneeded" scripts. | |
971 if {[catch { | |
972 CheckFileName $child $realfile | |
973 } msg]} { | |
974 Log $child "$realfile:$msg" | |
975 return -code error -errorcode {POSIX EACCES} $msg | |
976 } | |
977 | |
978 # Passed all the tests, lets source it. Note that we do this all manually | |
979 # because we want to control [info script] in the child so information | |
980 # doesn't leak so much. [Bug 2913625] | |
981 set old [::interp eval $child {info script}] | |
982 set replacementMsg "script error" | |
983 set code [catch { | |
984 set f [open $realfile] | |
985 fconfigure $f -eofchar "\032 {}" | |
986 if {$encoding ne ""} { | |
987 fconfigure $f -encoding $encoding | |
988 } | |
989 set contents [read $f] | |
990 close $f | |
991 ::interp eval $child [list info script $file] | |
992 } msg opt] | |
993 if {$code == 0} { | |
994 set code [catch {::interp eval $child $contents} msg opt] | |
995 set replacementMsg $msg | |
996 } | |
997 catch {interp eval $child [list info script $old]} | |
998 # Note that all non-errors are fine result codes from [source], so we must | |
999 # take a little care to do it properly. [Bug 2923613] | |
1000 if {$code == 1} { | |
1001 Log $child $msg | |
1002 return -code error $replacementMsg | |
1003 } | |
1004 return -code $code -options $opt $msg | |
1005 } | |
1006 | |
1007 # AliasLoad is the target of the "load" alias in safe interpreters. | |
1008 | |
1009 proc ::safe::AliasLoad {child file args} { | |
1010 set argc [llength $args] | |
1011 if {$argc > 2} { | |
1012 set msg "load error: too many arguments" | |
1013 Log $child "$msg ($argc) {$file $args}" | |
1014 return -code error $msg | |
1015 } | |
1016 | |
1017 # package name (can be empty if file is not). | |
1018 set package [lindex $args 0] | |
1019 | |
1020 namespace upvar ::safe [VarName $child] state | |
1021 | |
1022 # Determine where to load. load use a relative interp path and {} | |
1023 # means self, so we can directly and safely use passed arg. | |
1024 set target [lindex $args 1] | |
1025 if {$target ne ""} { | |
1026 # we will try to load into a sub sub interp; check that we want to | |
1027 # authorize that. | |
1028 if {!$state(nestedok)} { | |
1029 Log $child "loading to a sub interp (nestedok)\ | |
1030 disabled (trying to load $package to $target)" | |
1031 return -code error "permission denied (nested load)" | |
1032 } | |
1033 } | |
1034 | |
1035 # Determine what kind of load is requested | |
1036 if {$file eq ""} { | |
1037 # static package loading | |
1038 if {$package eq ""} { | |
1039 set msg "load error: empty filename and no package name" | |
1040 Log $child $msg | |
1041 return -code error $msg | |
1042 } | |
1043 if {!$state(staticsok)} { | |
1044 Log $child "static packages loading disabled\ | |
1045 (trying to load $package to $target)" | |
1046 return -code error "permission denied (static package)" | |
1047 } | |
1048 } else { | |
1049 # file loading | |
1050 | |
1051 # get the real path from the virtual one. | |
1052 try { | |
1053 set file [TranslatePath $child $file] | |
1054 } on error msg { | |
1055 Log $child $msg | |
1056 return -code error "permission denied" | |
1057 } | |
1058 | |
1059 # check the translated path | |
1060 try { | |
1061 FileInAccessPath $child $file | |
1062 } on error msg { | |
1063 Log $child $msg | |
1064 return -code error "permission denied (path)" | |
1065 } | |
1066 } | |
1067 | |
1068 try { | |
1069 return [::interp invokehidden $child load $file $package $target] | |
1070 } on error msg { | |
1071 # Some packages return no error message. | |
1072 set msg0 "load of binary library for package $package failed" | |
1073 if {$msg eq {}} { | |
1074 set msg $msg0 | |
1075 } else { | |
1076 set msg "$msg0: $msg" | |
1077 } | |
1078 Log $child $msg | |
1079 return -code error $msg | |
1080 } | |
1081 } | |
1082 | |
1083 # FileInAccessPath raises an error if the file is not found in the list of | |
1084 # directories contained in the (parent side recorded) child's access path. | |
1085 | |
1086 # the security here relies on "file dirname" answering the proper | |
1087 # result... needs checking ? | |
1088 proc ::safe::FileInAccessPath {child file} { | |
1089 namespace upvar ::safe [VarName $child] state | |
1090 set access_path $state(access_path) | |
1091 | |
1092 if {[file isdirectory $file]} { | |
1093 return -code error "\"$file\": is a directory" | |
1094 } | |
1095 set parent [file dirname $file] | |
1096 | |
1097 # Normalize paths for comparison since lsearch knows nothing of | |
1098 # potential pathname anomalies. | |
1099 set norm_parent [file normalize $parent] | |
1100 | |
1101 namespace upvar ::safe [VarName $child] state | |
1102 if {$norm_parent ni $state(access_path,norm)} { | |
1103 return -code error "\"$file\": not in access_path" | |
1104 } | |
1105 } | |
1106 | |
1107 proc ::safe::DirInAccessPath {child dir} { | |
1108 namespace upvar ::safe [VarName $child] state | |
1109 set access_path $state(access_path) | |
1110 | |
1111 if {[file isfile $dir]} { | |
1112 return -code error "\"$dir\": is a file" | |
1113 } | |
1114 | |
1115 # Normalize paths for comparison since lsearch knows nothing of | |
1116 # potential pathname anomalies. | |
1117 set norm_dir [file normalize $dir] | |
1118 | |
1119 namespace upvar ::safe [VarName $child] state | |
1120 if {$norm_dir ni $state(access_path,norm)} { | |
1121 return -code error "\"$dir\": not in access_path" | |
1122 } | |
1123 } | |
1124 | |
1125 # This procedure is used to report an attempt to use an unsafe member of an | |
1126 # ensemble command. | |
1127 | |
1128 proc ::safe::BadSubcommand {child command subcommand args} { | |
1129 set msg "not allowed to invoke subcommand $subcommand of $command" | |
1130 Log $child $msg | |
1131 return -code error -errorcode {TCL SAFE SUBCOMMAND} $msg | |
1132 } | |
1133 | |
1134 # AliasEncoding is the target of the "encoding" alias in safe interpreters. | |
1135 | |
1136 proc ::safe::AliasEncoding {child option args} { | |
1137 # Note that [encoding dirs] is not supported in safe children at all | |
1138 set subcommands {convertfrom convertto names system} | |
1139 try { | |
1140 set option [tcl::prefix match -error [list -level 1 -errorcode \ | |
1141 [list TCL LOOKUP INDEX option $option]] $subcommands $option] | |
1142 # Special case: [encoding system] ok, but [encoding system foo] not | |
1143 if {$option eq "system" && [llength $args]} { | |
1144 return -code error -errorcode {TCL WRONGARGS} \ | |
1145 "wrong # args: should be \"encoding system\"" | |
1146 } | |
1147 } on error {msg options} { | |
1148 Log $child $msg | |
1149 return -options $options $msg | |
1150 } | |
1151 tailcall ::interp invokehidden $child encoding $option {*}$args | |
1152 } | |
1153 | |
1154 # Various minor hiding of platform features. [Bug 2913625] | |
1155 | |
1156 proc ::safe::AliasExeName {child} { | |
1157 return "" | |
1158 } | |
1159 | |
1160 # ------------------------------------------------------------------------------ | |
1161 # Using Interpreter Names with Namespace Qualifiers | |
1162 # ------------------------------------------------------------------------------ | |
1163 # (1) We wish to preserve compatibility with existing code, in which Safe Base | |
1164 # interpreter names have no namespace qualifiers. | |
1165 # (2) safe::interpCreate and the rest of the Safe Base previously could not | |
1166 # accept namespace qualifiers in an interpreter name. | |
1167 # (3) The interp command will accept namespace qualifiers in an interpreter | |
1168 # name, but accepts distinct interpreters that will have the same command | |
1169 # name (e.g. foo, ::foo, and :::foo) (bug 66c2e8c974). | |
1170 # (4) To satisfy these constraints, Safe Base interpreter names will be fully | |
1171 # qualified namespace names with no excess colons and with the leading "::" | |
1172 # omitted. | |
1173 # (5) Trailing "::" implies a namespace tail {}, which interp reads as {{}}. | |
1174 # Reject such names. | |
1175 # (6) We could: | |
1176 # (a) EITHER reject usable but non-compliant names (e.g. excess colons) in | |
1177 # interpCreate, interpInit; | |
1178 # (b) OR accept such names and then translate to a compliant name in every | |
1179 # command. | |
1180 # The problem with (b) is that the user will expect to use the name with the | |
1181 # interp command and will find that it is not recognised. | |
1182 # E.g "interpCreate ::foo" creates interpreter "foo", and the user's name | |
1183 # "::foo" works with all the Safe Base commands, but "interp eval ::foo" | |
1184 # fails. | |
1185 # So we choose (a). | |
1186 # (7) The command | |
1187 # namespace upvar ::safe S$child state | |
1188 # becomes | |
1189 # namespace upvar ::safe [VarName $child] state | |
1190 # ------------------------------------------------------------------------------ | |
1191 | |
1192 proc ::safe::RejectExcessColons {child} { | |
1193 set stripped [regsub -all -- {:::*} $child ::] | |
1194 if {[string range $stripped end-1 end] eq {::}} { | |
1195 return -code error {interpreter name must not end in "::"} | |
1196 } | |
1197 if {$stripped ne $child} { | |
1198 set msg {interpreter name has excess colons in namespace separators} | |
1199 return -code error $msg | |
1200 } | |
1201 if {[string range $stripped 0 1] eq {::}} { | |
1202 return -code error {interpreter name must not begin "::"} | |
1203 } | |
1204 return | |
1205 } | |
1206 | |
1207 proc ::safe::VarName {child} { | |
1208 # return S$child | |
1209 return S[string map {:: @N @ @A} $child] | |
1210 } | |
1211 | |
1212 proc ::safe::Setup {} { | |
1213 #### | |
1214 # | |
1215 # Setup the arguments parsing | |
1216 # | |
1217 #### | |
1218 | |
1219 # Share the descriptions | |
1220 set temp [::tcl::OptKeyRegister { | |
1221 {-accessPath -list {} "access path for the slave"} | |
1222 {-noStatics "prevent loading of statically linked pkgs"} | |
1223 {-statics true "loading of statically linked pkgs"} | |
1224 {-nestedLoadOk "allow nested loading"} | |
1225 {-nested false "nested loading"} | |
1226 {-deleteHook -script {} "delete hook"} | |
1227 }] | |
1228 | |
1229 # create case (slave is optional) | |
1230 ::tcl::OptKeyRegister { | |
1231 {?slave? -name {} "name of the slave (optional)"} | |
1232 } ::safe::interpCreate | |
1233 | |
1234 # adding the flags sub programs to the command program (relying on Opt's | |
1235 # internal implementation details) | |
1236 lappend ::tcl::OptDesc(::safe::interpCreate) $::tcl::OptDesc($temp) | |
1237 | |
1238 # init and configure (slave is needed) | |
1239 ::tcl::OptKeyRegister { | |
1240 {slave -name {} "name of the slave"} | |
1241 } ::safe::interpIC | |
1242 | |
1243 # adding the flags sub programs to the command program (relying on Opt's | |
1244 # internal implementation details) | |
1245 lappend ::tcl::OptDesc(::safe::interpIC) $::tcl::OptDesc($temp) | |
1246 | |
1247 # temp not needed anymore | |
1248 ::tcl::OptKeyDelete $temp | |
1249 | |
1250 #### | |
1251 # | |
1252 # Default: No logging. | |
1253 # | |
1254 #### | |
1255 | |
1256 setLogCmd {} | |
1257 | |
1258 # Log eventually. | |
1259 # To enable error logging, set Log to {puts stderr} for instance, | |
1260 # via setLogCmd. | |
1261 return | |
1262 } | |
1263 | |
1264 namespace eval ::safe { | |
1265 # internal variables | |
1266 | |
1267 # Log command, set via 'setLogCmd'. Logging is disabled when empty. | |
1268 variable Log {} | |
1269 | |
1270 # The package maintains a state array per child interp under its | |
1271 # control. The name of this array is S<interp-name>. This array is | |
1272 # brought into scope where needed, using 'namespace upvar'. The S | |
1273 # prefix is used to avoid that a child interp called "Log" smashes | |
1274 # the "Log" variable. | |
1275 # | |
1276 # The array's elements are: | |
1277 # | |
1278 # access_path : List of paths accessible to the child. | |
1279 # access_path,norm : Ditto, in normalized form. | |
1280 # access_path,slave : Ditto, as the path tokens as seen by the child. | |
1281 # access_path,map : dict ( token -> path ) | |
1282 # access_path,remap : dict ( path -> token ) | |
1283 # tm_path_slave : List of TM root directories, as tokens seen by the child. | |
1284 # staticsok : Value of option -statics | |
1285 # nestedok : Value of option -nested | |
1286 # cleanupHook : Value of option -deleteHook | |
1287 } | |
1288 | |
1289 ::safe::Setup |