Tcl slave interpreters as sandbox environment
Written by Wojciech Kocjan   
Very often you come across a need to run some Tcl scripts in a separate environment. Often it just needs not to share any data with remaining parts of application and have different set of resources. Sometimes it is a script that needs to run without access to certain files and/or functionality.

Tcl offers a very useful feature called slave interpreters. This allows you to create a new Tcl interpreter, disconnected from your current one (not sharing any variables, commands, open files/sockets). This can be used in different aspects - for security, for easier development, better handling of plugins. Or simply because some packages you use conflict with other ones. Tcl's interp command is used to create slave interpreters and managing them. To create a simple interpreter all you need to do is call interp create. You can then use interp command or the new command with the same name as the interpreter name. For example:

Basics of slave interpreters
% interp create myslave
myslave
% interp eval myslave {set somevalue 12}
12
% myslave eval {expr {$somevalue + 2}}
14
% set somevalue
can't read "somevalue": no such variable

As you can see, variables in the slave interpreters are completely different than the ones used in main interpreter.

You can also easily make commands from your interpreter available in the slave. To do this you need to use interp alias command or its equivalent with the slave interpreter command. Command interp alias accepts target interpreter, target command name and then source interpreter (where empty string means current interpreter), source command name and, optionally, arguments to prefix to the command. The alias subcommand of the slave interp command accepts everything with the target and source interpreter name skipped. For example:

Making commands available in slave interpreters
% interp create myslave
myslave
% proc mycommand {prefix text} {return "$prefix :: $text"}
% interp alias myslave slavecommand {} mycommand prefix1
slavecommand
% myslave alias othercommand mycommand prefix2
othercommand
% myslave eval {slavecommand "Some text"}
prefix1 :: Some text
% myslave eval {othercommand "Other text"}
prefix2 :: Other text

Another interesting feature is sharing file handle sharing. You can either pass a file handle to/from slave interpreter or make it available in both. To do this you can use either interp share or interp tranfer commands. Both commands require passing source interpreter, channel (file handle) and destination interpreter.

Temporarily passing a socket to slave interpreter
interp create myslave
set chan [open test.txt w]
interp transfer "" $chan myslave
myslave eval [list set chan $chan] ; # needed to set chan variable in slave
myslave eval {
    puts $chan [join [lsort [info commands m*]] ,]
    puts $chan "Commands starting with m in slave interpreter:"
}
interp transfer myslave $chan ""
puts $chan "Commands starting with m in main interpreter:"
puts $chan [join [lsort [info commands m*]] ,]
close $chan

Passing handles can only be done from main interpreter. Combining this with making a command available it is easy to do a full management of file handles. Another interesting aspect is creating safe interpreters. Safe interpreters offer a subset of Tcl functionality. It limits access to filesystem, reading/writing files and uses different way to load extensions which prohibits loading functionality which should not be used in a safe interpreter. To create a new safe interpreter, call the safe::interpCreate command along with interpreter name. For example:

Temporarily passing a socket to slave interpreter
% safe::interpCreate myslave
myslave
% myslave eval {expr {1+1}}
2
% myslave eval {package require http}
2.5.5
% myslave eval {source /script.tcl}
permission denied

As shown above, safe interpreters can load packages inside specific directories, but it is not possible to load scripts outside of those specified directories. By default safe interpreters only allow accessing directories in paths where Tcl searches for packages. This can be changed using safe::interpConfigure command to configure -accessPath parameter for this interpreter.

In next article I'll try to show how to use slave interpreters to handle plugins and how to easily do things like restarting all plugins without actually exiting the process. Stay tuned.