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 # slave. It runs in a master interpreter and sets up data structure and
6 # aliases that will be invoked when used from a slave interpreter.
8 # See the safe.n man page for details.
10 # Copyright (c) 1996-1997 Sun Microsystems, Inc.
12 # See the file "license.terms" for information on usage and redistribution
13 # of this file, and for a DISCLAIMER OF ALL WARRANTIES.
15 # RCS: @(#) $Id: safe.tcl,v 1.9.2.3 2005/07/22 21:59:41 dgp Exp $
18 # The implementation is based on namespaces. These naming conventions
20 # Private procs starts with uppercase.
21 # Public procs are exported and starts with lowercase
24 # Needed utilities package
25 package require opt
0.4.1;
27 # Create the safe namespace
28 namespace eval ::safe {
31 namespace export interpCreate interpInit interpConfigure interpDelete
\
32 interpAddToAccessPath interpFindInAccessPath setLogCmd
36 # Setup the arguments parsing
40 # Make sure that our temporary variable is local to this
41 # namespace. [Bug 981733]
44 # Share the descriptions
45 set temp
[::tcl::OptKeyRegister {
46 {-accessPath -list {} "access path for the slave"}
47 {-noStatics "prevent loading of statically linked pkgs"}
48 {-statics true
"loading of statically linked pkgs"}
49 {-nestedLoadOk "allow nested loading"}
50 {-nested false
"nested loading"}
51 {-deleteHook -script {} "delete hook"}
54 # create case (slave is optional)
55 ::tcl::OptKeyRegister {
56 {?slave?
-name {} "name of the slave (optional)"}
57 } ::safe::interpCreate
58 # adding the flags sub programs to the command program
59 # (relying on Opt's internal implementation details)
60 lappend ::tcl::OptDesc(::safe::interpCreate) $::tcl::OptDesc($temp)
62 # init and configure (slave is needed)
63 ::tcl::OptKeyRegister {
64 {slave
-name {} "name of the slave"}
66 # adding the flags sub programs to the command program
67 # (relying on Opt's internal implementation details)
68 lappend ::tcl::OptDesc(::safe::interpIC) $::tcl::OptDesc($temp)
69 # temp not needed anymore
70 ::tcl::OptKeyDelete $temp
73 # Helper function to resolve the dual way of specifying staticsok
74 # (either by -noStatics or -statics 0)
75 proc InterpStatics
{} {
76 foreach v
{Args statics noStatics
} {
79 set flag
[::tcl::OptProcArgGiven -noStatics];
80 if {$flag && (!$noStatics == !$statics)
81 && ([::tcl::OptProcArgGiven -statics])} {
83 "conflicting values given for -statics and -noStatics"
86 return [expr {!$noStatics}]
92 # Helper function to resolve the dual way of specifying nested loading
93 # (either by -nestedLoadOk or -nested 1)
94 proc InterpNested
{} {
95 foreach v
{Args nested nestedLoadOk
} {
98 set flag
[::tcl::OptProcArgGiven -nestedLoadOk];
99 # note that the test here is the opposite of the "InterpStatics"
100 # one (it is not -noNested... because of the wanted default value)
101 if {$flag && (!$nestedLoadOk != !$nested)
102 && ([::tcl::OptProcArgGiven -nested])} {
104 "conflicting values given for -nested and -nestedLoadOk"
107 # another difference with "InterpStatics"
116 # API entry points that needs argument parsing :
121 # Interface/entry point function and front end for "Create"
122 proc interpCreate
{args
} {
123 set Args
[::tcl::OptKeyParse ::safe::interpCreate $args]
124 InterpCreate
$slave $accessPath \
125 [InterpStatics
] [InterpNested
] $deleteHook
128 proc interpInit
{args
} {
129 set Args
[::tcl::OptKeyParse ::safe::interpIC $args]
130 if {![::interp exists
$slave]} {
131 return -code error "\"$slave\" is not an interpreter"
133 InterpInit
$slave $accessPath \
134 [InterpStatics
] [InterpNested
] $deleteHook;
137 proc CheckInterp
{slave
} {
138 if {![IsInterp
$slave]} {
140 "\"$slave\" is not an interpreter managed by ::safe::"
144 # Interface/entry point function and front end for "Configure"
145 # This code is awfully pedestrian because it would need
146 # more coupling and support between the way we store the
147 # configuration values in safe::interp's and the Opt package
148 # Obviously we would like an OptConfigure
149 # to avoid duplicating all this code everywhere. -> TODO
150 # (the app should share or access easily the program/value
152 # This is even more complicated by the boolean flags with no values
153 # that we had the bad idea to support for the sake of user simplicity
154 # in create/init but which makes life hard in configure...
155 # So this will be hopefully written and some integrated with opt1.0
156 # (hopefully for tcl8.1 ?)
157 proc interpConfigure
{args
} {
158 switch [llength $args] {
160 # If we have exactly 1 argument
161 # the semantic is to return all the current configuration
162 # We still call OptKeyParse though we know that "slave"
163 # is our given argument because it also checks
164 # for the "-help" option.
165 set Args
[::tcl::OptKeyParse ::safe::interpIC $args]
168 lappend res
[list -accessPath [Set
[PathListName
$slave]]]
169 lappend res
[list -statics [Set
[StaticsOkName
$slave]]]
170 lappend res
[list -nested [Set
[NestedOkName
$slave]]]
171 lappend res
[list -deleteHook [Set
[DeleteHookName
$slave]]]
175 # If we have exactly 2 arguments
176 # the semantic is a "configure get"
177 ::tcl::Lassign $args slave arg
178 # get the flag sub program (we 'know' about Opt's internal
179 # representation of data)
180 set desc
[lindex [::tcl::OptKeyGetDesc ::safe::interpIC] 2]
181 set hits
[::tcl::OptHits desc
$arg]
183 return -code error [::tcl::OptAmbigous $desc $arg]
184 } elseif
{$hits == 0} {
185 return -code error [::tcl::OptFlagUsage $desc $arg]
188 set item
[::tcl::OptCurDesc $desc]
189 set name
[::tcl::OptName $item]
190 switch -exact -- $name {
192 return [list -accessPath [Set
[PathListName
$slave]]]
195 return [list -statics [Set
[StaticsOkName
$slave]]]
198 return [list -nested [Set
[NestedOkName
$slave]]]
201 return [list -deleteHook [Set
[DeleteHookName
$slave]]]
204 # it is most probably a set in fact
205 # but we would need then to jump to the set part
206 # and it is not *sure* that it is a set action
207 # that the user want, so force it to use the
208 # unambigous -statics ?value? instead:
210 "ambigous query (get or set -noStatics ?)\
211 use -statics instead"
215 "ambigous query (get or set -nestedLoadOk ?)\
219 return -code error "unknown flag $name (bug)"
224 # Otherwise we want to parse the arguments like init and create
226 set Args
[::tcl::OptKeyParse ::safe::interpIC $args]
228 # Get the current (and not the default) values of
229 # whatever has not been given:
230 if {![::tcl::OptProcArgGiven -accessPath]} {
232 set accessPath
[Set
[PathListName
$slave]]
236 if {(![::tcl::OptProcArgGiven -statics]) \
237 && (![::tcl::OptProcArgGiven -noStatics]) } {
238 set statics
[Set
[StaticsOkName
$slave]]
240 set statics
[InterpStatics
]
242 if {([::tcl::OptProcArgGiven -nested]) \
243 ||
([::tcl::OptProcArgGiven -nestedLoadOk]) } {
244 set nested
[InterpNested
]
246 set nested
[Set
[NestedOkName
$slave]]
248 if {![::tcl::OptProcArgGiven -deleteHook]} {
249 set deleteHook
[Set
[DeleteHookName
$slave]]
251 # we can now reconfigure :
252 InterpSetConfig
$slave $accessPath $statics $nested $deleteHook
253 # auto_reset the slave (to completly synch the new access_path)
255 if {[catch {::interp eval $slave {auto_reset}} msg
]} {
256 Log
$slave "auto_reset failed: $msg"
258 Log
$slave "successful auto_reset" NOTICE
268 # Functions that actually implements the exported APIs
274 # safe::InterpCreate : doing the real job
276 # This procedure creates a safe slave and initializes it with the
278 # NB: slave name must be simple alphanumeric string, no spaces,
279 # no (), no {},... {because the state array is stored as part of the name}
281 # Returns the slave name.
283 # Optional Arguments :
284 # + slave name : if empty, generated name will be used
285 # + access_path: path list controlling where load/source can occur,
286 # if empty: the master auto_path will be used.
287 # + staticsok : flag, if 0 :no static package can be loaded (load {} Xxx)
288 # if 1 :static packages are ok.
289 # + nestedok: flag, if 0 :no loading to sub-sub interps (load xx xx sub)
290 # if 1 : multiple levels are ok.
292 # use the full name and no indent so auto_mkIndex can find us
293 proc ::safe::InterpCreate {
302 ::interp create
-safe $slave
304 # empty argument: generate slave name
305 set slave
[::interp create
-safe]
307 Log
$slave "Created" NOTICE
309 # Initialize it. (returns slave name)
310 InterpInit
$slave $access_path $staticsok $nestedok $deletehook
315 # InterpSetConfig (was setAccessPath) :
316 # Sets up slave virtual auto_path and corresponding structure
317 # within the master. Also sets the tcl_library in the slave
318 # to be the first directory in the path.
319 # Nb: If you change the path after the slave has been initialized
320 # you probably need to call "auto_reset" in the slave in order that it
321 # gets the right auto_index() array values.
323 proc ::safe::InterpSetConfig {slave access_path staticsok
\
324 nestedok deletehook
} {
326 # determine and store the access path if empty
327 if {$access_path eq
""} {
328 set access_path
[uplevel \#0 set auto_path]
329 # Make sure that tcl_library is in auto_path
330 # and at the first position (needed by setAccessPath)
331 set where
[lsearch -exact $access_path [info library
]]
334 set access_path
[concat [list [info library
]] $access_path]
335 Log
$slave "tcl_library was not in auto_path,\
336 added it to slave's access_path" NOTICE
337 } elseif
{$where != 0} {
338 # not first, move it first
339 set access_path
[concat [list [info library
]]\
340 [lreplace $access_path $where $where]]
341 Log
$slave "tcl_libray was not in first in auto_path,\
342 moved it to front of slave's access_path" NOTICE
346 # Add 1st level sub dirs (will searched by auto loading from tcl
347 # code in the slave using glob and thus fail, so we add them
348 # here so by default it works the same).
349 set access_path
[AddSubDirs
$access_path]
352 Log
$slave "Setting accessPath=($access_path) staticsok=$staticsok\
353 nestedok=$nestedok deletehook=($deletehook)" NOTICE
355 # clear old autopath if it existed
356 set nname
[PathNumberName
$slave]
357 if {[Exists
$nname]} {
359 for {set i
0} {$i<$n} {incr i
} {
360 Unset
[PathToken
$i $slave]
365 set slave_auto_path
{}
367 foreach dir
$access_path {
368 Set
[PathToken
$i $slave] $dir
369 lappend slave_auto_path
"\$[PathToken $i]"
373 Set
[PathListName
$slave] $access_path
374 Set
[VirtualPathListName
$slave] $slave_auto_path
376 Set
[StaticsOkName
$slave] $staticsok
377 Set
[NestedOkName
$slave] $nestedok
378 Set
[DeleteHookName
$slave] $deletehook
380 SyncAccessPath
$slave
386 # Search for a real directory and returns its virtual Id
387 # (including the "$")
388 proc ::safe::interpFindInAccessPath {slave path
} {
389 set access_path
[GetAccessPath
$slave]
390 set where
[lsearch -exact $access_path $path]
392 return -code error "$path not found in access path $access_path"
394 return "\$[PathToken $where]"
399 # add (if needed) a real directory to access path
400 # and return its virtual token (including the "$").
401 proc ::safe::interpAddToAccessPath {slave path
} {
402 # first check if the directory is already in there
403 if {![catch {interpFindInAccessPath
$slave $path} res
]} {
407 set nname
[PathNumberName
$slave]
409 Set
[PathToken
$n $slave] $path
411 set token
"\$[PathToken $n]"
413 Lappend
[VirtualPathListName
$slave] $token
414 Lappend
[PathListName
$slave] $path
415 Set
$nname [expr {$n+1}]
417 SyncAccessPath
$slave
422 # This procedure applies the initializations to an already existing
423 # interpreter. It is useful when you want to install the safe base
424 # aliases into a preexisting safe interpreter.
425 proc ::safe::InterpInit {
433 # Configure will generate an access_path when access_path is
435 InterpSetConfig
$slave $access_path $staticsok $nestedok $deletehook
437 # These aliases let the slave load files to define new commands
439 # NB we need to add [namespace current], aliases are always
441 ::interp alias
$slave source {} [namespace current
]::AliasSource $slave
442 ::interp alias
$slave load {} [namespace current
]::AliasLoad $slave
444 # This alias lets the slave use the encoding names, convertfrom,
445 # convertto, and system, but not "encoding system <name>" to set
446 # the system encoding.
448 ::interp alias
$slave encoding {} [namespace current
]::AliasEncoding \
451 # This alias lets the slave have access to a subset of the 'file'
452 # command functionality.
454 AliasSubset
$slave file file dir.
* join root.
* ext.
* tail
\
457 # This alias interposes on the 'exit' command and cleanly terminates
460 ::interp alias
$slave exit {} [namespace current
]::interpDelete $slave
462 # The allowed slave variables already have been set
466 # Source init.tcl into the slave, to get auto_load and other
467 # procedures defined:
469 # We don't try to use the -rsrc on the mac because it would get
470 # confusing if you would want to customize init.tcl
471 # for a given set of safe slaves, on all the platforms
472 # you just need to give a specific access_path and
473 # the mac should be no exception. As there is no
474 # obvious full "safe ressources" design nor implementation
475 # for the mac, safe interps there will just don't
476 # have that ability. (A specific app can still reenable
477 # that using custom aliases if they want to).
478 # It would also make the security analysis and the Safe Tcl security
479 # model platform dependant and thus more error prone.
481 if {[catch {::interp eval $slave\
482 {source [file join $tcl_library init.tcl
]}} msg
]} {
483 Log
$slave "can't source init.tcl ($msg)"
484 error "can't source init.tcl into slave $slave ($msg)"
491 # Add (only if needed, avoid duplicates) 1 level of
492 # sub directories to an existing path list.
493 # Also removes non directories from the returned list.
494 proc AddSubDirs
{pathList
} {
496 foreach dir
$pathList {
497 if {[file isdirectory
$dir]} {
498 # check that we don't have it yet as a children
500 if {[lsearch -exact $res $dir]<0} {
503 foreach sub
[glob -directory $dir -nocomplain *] {
504 if {([file isdirectory
$sub]) \
505 && ([lsearch -exact $res $sub]<0) } {
506 # new sub dir, add it !
515 # This procedure deletes a safe slave managed by Safe Tcl and
516 # cleans up associated state:
518 proc ::safe::interpDelete {slave
} {
520 Log
$slave "About to delete" NOTICE
522 # If the slave has a cleanup hook registered, call it.
523 # check the existance because we might be called to delete an interp
524 # which has not been registered with us at all
525 set hookname
[DeleteHookName
$slave]
526 if {[Exists
$hookname]} {
527 set hook
[Set
$hookname]
528 if {![::tcl::Lempty $hook]} {
529 # remove the hook now, otherwise if the hook
530 # calls us somehow, we'll loop
532 if {[catch {eval $hook [list $slave]} err
]} {
533 Log
$slave "Delete hook error ($err)"
538 # Discard the global array of state associated with the slave, and
539 # delete the interpreter.
541 set statename
[InterpStateName
$slave]
542 if {[Exists
$statename]} {
546 # if we have been called twice, the interp might have been deleted
548 if {[::interp exists
$slave]} {
549 ::interp delete
$slave
550 Log
$slave "Deleted" NOTICE
556 # Set (or get) the loging mecanism
558 proc ::safe::setLogCmd {args
} {
560 if {[llength $args] == 0} {
563 if {[llength $args] == 1} {
564 set Log
[lindex $args 0]
574 # ------------------- END OF PUBLIC METHODS ------------
578 # sets the slave auto_path to the master recorded value.
579 # also sets tcl_library to the first token of the virtual path.
581 proc SyncAccessPath
{slave
} {
582 set slave_auto_path
[Set
[VirtualPathListName
$slave]]
583 ::interp eval $slave [list set auto_path
$slave_auto_path]
584 Log
$slave "auto_path in $slave has been set to $slave_auto_path"\
586 ::interp eval $slave [list set tcl_library
[lindex $slave_auto_path 0]]
589 # base name for storing all the slave states
590 # the array variable name for slave foo is thus "Sfoo"
591 # and for sub slave {foo bar} "Sfoo bar" (spaces are handled
592 # ok everywhere (or should))
593 # We add the S prefix to avoid that a slave interp called "Log"
594 # would smash our "Log" variable.
595 proc InterpStateName
{slave
} {
599 # Check that the given slave is "one of us"
600 proc IsInterp
{slave
} {
601 expr {[Exists
[InterpStateName
$slave]] && [::interp exists
$slave]}
604 # returns the virtual token for directory number N
605 # if the slave argument is given,
606 # it will return the corresponding master global variable name
607 proc PathToken
{n
{slave
""}} {
609 return "[InterpStateName $slave](access_path,$n)"
611 # We need to have a ":" in the token string so
612 # [file join] on the mac won't turn it into a relative
617 # returns the variable name of the complete path list
618 proc PathListName
{slave
} {
619 return "[InterpStateName $slave](access_path)"
621 # returns the variable name of the complete path list
622 proc VirtualPathListName
{slave
} {
623 return "[InterpStateName $slave](access_path_slave)"
625 # returns the variable name of the number of items
626 proc PathNumberName
{slave
} {
627 return "[InterpStateName $slave](access_path,n)"
629 # returns the staticsok flag var name
630 proc StaticsOkName
{slave
} {
631 return "[InterpStateName $slave](staticsok)"
633 # returns the nestedok flag var name
634 proc NestedOkName
{slave
} {
635 return "[InterpStateName $slave](nestedok)"
637 # Run some code at the namespace toplevel
638 proc Toplevel
{args
} {
639 namespace eval [namespace current
] $args
643 eval [linsert $args 0 Toplevel
set]
645 # lappend on toplevel vars
646 proc Lappend
{args
} {
647 eval [linsert $args 0 Toplevel
lappend]
649 # unset a var/token (currently just an global level eval)
651 eval [linsert $args 0 Toplevel
unset]
654 proc Exists
{varname
} {
655 Toplevel
info exists
$varname
657 # short cut for access path getting
658 proc GetAccessPath
{slave
} {
659 Set
[PathListName
$slave]
661 # short cut for statics ok flag getting
662 proc StaticsOk
{slave
} {
663 Set
[StaticsOkName
$slave]
665 # short cut for getting the multiples interps sub loading ok flag
666 proc NestedOk
{slave
} {
667 Set
[NestedOkName
$slave]
669 # interp deletion storing hook name
670 proc DeleteHookName
{slave
} {
671 return [InterpStateName
$slave](cleanupHook
)
675 # translate virtual path into real path
677 proc TranslatePath
{slave path
} {
678 # somehow strip the namespaces 'functionality' out (the danger
679 # is that we would strip valid macintosh "../" queries... :
680 if {[regexp {(::)|
(\.
\.
)} $path]} {
681 error "invalid characters in path $path"
683 set n
[expr {[Set
[PathNumberName
$slave]]-1}]
684 for {} {$n>=0} {incr n
-1} {
685 # fill the token virtual names with their real value
686 set [PathToken
$n] [Set
[PathToken
$n $slave]]
688 # replaces the token by their value
689 subst -nobackslashes -nocommands $path
693 # Log eventually log an error
694 # to enable error logging, set Log to {puts stderr} for instance
695 proc Log
{slave msg
{type ERROR
}} {
697 if {[info exists Log
] && [llength $Log]} {
698 eval $Log [list "$type for slave $slave : $msg"]
703 # file name control (limit access to files/ressources that should be
704 # a valid tcl source file)
705 proc CheckFileName
{slave
file} {
706 # This used to limit what can be sourced to ".tcl" and forbid files
707 # with more than 1 dot and longer than 14 chars, but I changed that
708 # for 8.4 as a safe interp has enough internal protection already
709 # to allow sourcing anything. - hobbs
711 if {![file exists
$file]} {
712 # don't tell the file path
713 error "no such file or directory"
716 if {![file readable
$file]} {
717 # don't tell the file path
723 # AliasSource is the target of the "source" alias in safe interpreters.
725 proc AliasSource
{slave args
} {
727 set argc
[llength $args]
728 # Allow only "source filename"
729 # (and not mac specific -rsrc for instance - see comment in ::init
730 # for current rationale)
732 set msg
"wrong # args: should be \"source fileName\""
733 Log
$slave "$msg ($args)"
734 return -code error $msg
736 set file [lindex $args 0]
738 # get the real path from the virtual one.
739 if {[catch {set file [TranslatePath
$slave $file]} msg
]} {
741 return -code error "permission denied"
744 # check that the path is in the access path of that slave
745 if {[catch {FileInAccessPath
$slave $file} msg
]} {
747 return -code error "permission denied"
750 # do the checks on the filename :
751 if {[catch {CheckFileName
$slave $file} msg
]} {
752 Log
$slave "$file:$msg"
753 return -code error $msg
756 # passed all the tests , lets source it:
757 if {[catch {::interp invokehidden
$slave source $file} msg
]} {
759 return -code error "script error"
764 # AliasLoad is the target of the "load" alias in safe interpreters.
766 proc AliasLoad
{slave
file args
} {
768 set argc
[llength $args]
770 set msg
"load error: too many arguments"
771 Log
$slave "$msg ($argc) {$file $args}"
772 return -code error $msg
775 # package name (can be empty if file is not).
776 set package [lindex $args 0]
778 # Determine where to load. load use a relative interp path
779 # and {} means self, so we can directly and safely use passed arg.
780 set target
[lindex $args 1]
782 # we will try to load into a sub sub interp
783 # check that we want to authorize that.
784 if {![NestedOk
$slave]} {
785 Log
$slave "loading to a sub interp (nestedok)\
786 disabled (trying to load $package to $target)"
787 return -code error "permission denied (nested load)"
792 # Determine what kind of load is requested
794 # static package loading
795 if {$package eq
""} {
796 set msg
"load error: empty filename and no package name"
798 return -code error $msg
800 if {![StaticsOk
$slave]} {
801 Log
$slave "static packages loading disabled\
802 (trying to load $package to $target)"
803 return -code error "permission denied (static package)"
808 # get the real path from the virtual one.
809 if {[catch {set file [TranslatePath
$slave $file]} msg
]} {
811 return -code error "permission denied"
814 # check the translated path
815 if {[catch {FileInAccessPath
$slave $file} msg
]} {
817 return -code error "permission denied (path)"
821 if {[catch {::interp invokehidden
\
822 $slave load $file $package $target} msg
]} {
824 return -code error $msg
830 # FileInAccessPath raises an error if the file is not found in
831 # the list of directories contained in the (master side recorded) slave's
834 # the security here relies on "file dirname" answering the proper
835 # result.... needs checking ?
836 proc FileInAccessPath
{slave
file} {
838 set access_path
[GetAccessPath
$slave]
840 if {[file isdirectory
$file]} {
841 error "\"$file\": is a directory"
843 set parent
[file dirname
$file]
845 # Normalize paths for comparison since lsearch knows nothing of
846 # potential pathname anomalies.
847 set norm_parent
[file normalize
$parent]
848 foreach path
$access_path {
849 lappend norm_access_path
[file normalize
$path]
852 if {[lsearch -exact $norm_access_path $norm_parent] == -1} {
853 error "\"$file\": not in access_path"
857 # This procedure enables access from a safe interpreter to only a subset of
858 # the subcommands of a command:
860 proc Subset
{slave command okpat args
} {
861 set subcommand
[lindex $args 0]
862 if {[regexp $okpat $subcommand]} {
863 return [eval [linsert $args 0 $command]]
865 set msg
"not allowed to invoke subcommand $subcommand of $command"
870 # This procedure installs an alias in a slave that invokes "safesubset"
871 # in the master to execute allowed subcommands. It precomputes the pattern
872 # of allowed subcommands; you can use wildcards in the pattern if you wish
873 # to allow subcommand abbreviation.
875 # Syntax is: AliasSubset slave alias target subcommand1 subcommand2...
877 proc AliasSubset
{slave alias target args
} {
878 set pat ^
(; set sep
""
884 ::interp alias
$slave $alias {}\
885 [namespace current
]::Subset $slave $target $pat
888 # AliasEncoding is the target of the "encoding" alias in safe interpreters.
890 proc AliasEncoding
{slave args
} {
892 set argc
[llength $args]
894 set okpat
"^(name.*|convert.*)\$"
895 set subcommand
[lindex $args 0]
897 if {[regexp $okpat $subcommand]} {
898 return [eval [linsert $args 0 \
899 ::interp invokehidden
$slave encoding]]
902 if {[string first
$subcommand system
] == 0} {
904 # passed all the tests , lets source it:
905 if {[catch {::interp invokehidden
\
906 $slave encoding system
} msg
]} {
908 return -code error "script error"
911 set msg
"wrong # args: should be \"encoding system\""
916 set msg
"wrong # args: should be \"encoding option ?arg ...?\""