safe.tcl 46 KB

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