Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Create a TCL interpreter which supports only commands I supply

Say I have defined proc f1 proc f2 and proc f3. Now I want to create a TCL interpreter, source the code of proc f1 proc f2 and proc f3 into that interpreter and restrict all commands other than f1, f2 and f3 are within that interpreter. How I can do that?

EDIT:

If a command other that f1, f2 and f3 are called within the interpreter I have created an error message should be issued and execution of the code sourced in the interpreter (assume that this another code which is sourced in the same interpreter, after sourcing the code with f1, f2 and f3 procs) should be stopped.

like image 493
Narek Avatar asked May 08 '12 15:05

Narek


2 Answers

You can't quite do that, but you can do something that's similar enough for most purposes.

What you should do is create the commands f1, f2 and f3 in an interpreter normally, then create a sub-interpreter that has no Tcl commands at all, and alias the commands that you do want to expose in that sub-interpreter to the commands in the parent.

# First define f1-f3 in whatever way you want

# Now make the context; we'll use a safe interpreter for good measure...
set slave [interp create -safe]

# Scrub namespaces, then global vars, then commands
foreach ns [$slave eval namespace children ::] {
    $slave eval namespace delete $ns
}
foreach v [$slave eval info vars] {
    $slave eval unset $v
}
foreach cmd [$slave eval info commands] {
    # Note: we're hiding, not completely removing
    $slave hide $cmd
}

# Make the aliases for the things we want
foreach cmd {f1 f2 f3} {
    $slave alias $cmd $cmd
}

# And evaluate the untrusted script in it
catch {$slave invokehidden source $theScript}

# Finally, kill the untrusted interpreter
interp delete $slave
like image 97
Donal Fellows Avatar answered Sep 30 '22 11:09

Donal Fellows


Here is a simple solution: read input line by line. If the the first token is f1, f2, or f3, then execute the command, hit Control+C or type exit to exit the loop:

proc f1 {args} { puts "f1:$args" }
proc f2 {args} { puts "f2:$args" }
proc f3 {args} { puts "f3:$args" }

while 1 {
    puts -nonewline ">"
    flush stdout
    gets stdin line
    set firstToken [lindex $line 0]
    if {[lsearch {f1 f2 f3 exit} $firstToken] != -1} {
        eval $line
    }
}

There are several problems with this solution:

  1. Eval is considered dangerous
  2. Not much editing capability, you might want to get fancy and use the tclreadline package

Despite these drawbacks, the solution is very simple to implement.

like image 28
Hai Vu Avatar answered Sep 30 '22 11:09

Hai Vu